simple video test

This commit is contained in:
Alan Daniels 2025-11-07 22:29:51 +11:00
parent fd584886c6
commit fc4558be2c
10 changed files with 195 additions and 59 deletions

View file

@ -6,30 +6,31 @@ const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
const optimize_mode = builtin.mode;
const model_cat = @import("./models/cat.zig");
const model_tag = @import("./models/tag.zig");
db_connection: *sqlite.Db,
counter: u32 = 0,
pub fn init(connection: *sqlite.Db) !@This() {
try connection.execDynamic(model_tag.schema, .{}, .{});
// try connection.execDynamic(model_cat.schema, .{}, .{});
// try connection.execDynamic(model_tag.schema, .{}, .{});
return .{
.db_connection = connection,
};
}
pub fn SendInertiaResponse(endpoint: anytype, context: *@This(), r: zap.Request, arena: Allocator, component: []const u8, props: anytype) !void {
const tags = try model_tag.getTopLevelTags(context.db_connection, arena);
_ = context;
// const tags = try model_tag.getTopLevelTags(context.db_connection, arena);
// const cats = try model_cat.getTopLevelcats(context.db_connection, arena);
const inertia_json = try std.fmt.allocPrint(
arena,
"{f}",
.{std.json.fmt(.{
.component = component,
.props = .{
.tags = tags,
.page = props,
},
.props = props,
.url = endpoint.path,
.version = "",
}, .{
@ -41,7 +42,6 @@ pub fn SendInertiaResponse(endpoint: anytype, context: *@This(), r: zap.Request,
try r.setHeader("X-Inertia", "true");
try r.setHeader("Content-Type", "application/json");
r.setStatus(.ok);
try r.sendBody(inertia_json);
} else {
try r.setHeader("Content-Type", "text/html");
@ -68,7 +68,6 @@ pub fn SendInertiaResponse(endpoint: anytype, context: *@This(), r: zap.Request,
},
);
r.setStatus(.ok);
try r.sendBody(response_text);
}
}
@ -91,5 +90,6 @@ pub const HtmlEncodeFormatter = struct {
pub fn unhandledRequest(ctx: *@This(), arena: Allocator, r: zap.Request) anyerror!void {
const path = r.path orelse "/not-found";
r.setStatus(.not_found);
try SendInertiaResponse(.{ .path = path }, ctx, r, arena, "404", .{});
}

View file

@ -30,6 +30,7 @@ const SimpleEndpoint = struct {
pub fn get(e: *SimpleEndpoint, arena: Allocator, context: *Context, r: zap.Request) !void {
const thread_id = std.Thread.getCurrentId();
r.setStatus(.ok);
try Context.SendInertiaResponse(e, context, r, arena, e.component, .{
.thread_id = thread_id,
.counter = context.counter,
@ -85,12 +86,14 @@ pub fn main() !void {
// create the endpoints
var home_endpoint = SimpleEndpoint.init("/", "home/home");
var test_endpoint = SimpleEndpoint.init("/test", "home/test");
var test_video_endpoint = SimpleEndpoint.init("/video", "media/video");
var stop_endpoint: StopEndpoint = .{ .path = "/stop" };
// register the endpoints with the App
try App.register(&stop_endpoint);
try App.register(&home_endpoint);
try App.register(&test_endpoint);
try App.register(&test_video_endpoint);
// listen on the network
try App.listen(.{

47
src/models/cat.zig Normal file
View file

@ -0,0 +1,47 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const builtin = @import("builtin");
const optimize_mode = builtin.mode;
const sqlite = @import("sqlite");
pub const schema =
\\CREATE TABLE IF NOT EXISTS cat(
\\ id INTEGER PRIMARY KEY AUTOINCREMENT,
\\ key TEXT UNIQUE NOT NULL,
\\ name TEXT NOT NULL
\\);
;
id: usize,
key: []const u8,
name: []const u8,
const ArrList = std.ArrayList(@This());
pub fn getTopLevelcats(conn: *sqlite.Db, arena: Allocator) ![]@This() {
var stmt = try conn.prepareDynamic("SELECT id, key, name FROM cat WHERE TRUE;");
defer stmt.deinit();
var list = ArrList.empty;
defer list.deinit(arena);
var iter = try stmt.iterator(@This(), .{});
while (try iter.nextAlloc(arena, .{})) |row| {
try list.append(arena, row);
}
return list.toOwnedSlice(arena);
}
pub fn jsonStringify(v: @This(), jws: anytype) !void {
try jws.beginObject();
try jws.objectField("key");
try jws.write(v.key);
try jws.objectField("name");
try jws.write(v.name);
try jws.endObject();
}

View file

@ -5,77 +5,43 @@ const optimize_mode = builtin.mode;
const sqlite = @import("sqlite");
const Self = @This();
pub const schema =
\\CREATE TABLE IF NOT EXISTS tag(
\\ id INTEGER PRIMARY KEY AUTOINCREMENT,
\\ key TEXT UNIQUE NOT NULL,
\\ parent_key TEXT TEXT,
\\ FOREIGN KEY (parent_key) REFERENCES tag(key) ON DELETE RESTRICT
\\ id INTEGER PRIMARY KEY AUTOINCREMENT,
\\ key TEXT UNIQUE NOT NULL,
\\ cat_id INTEGER NOT NULL,
\\ FOREIGN KEY (cat_id) REFERENCES cat(id) ON DELETE RESTRICT
\\);
;
id: usize,
cat_id: usize,
key: []const u8,
parent: ?*@This() = null,
children: ?[]@This() = null,
const internal = struct {
id: usize,
key: []const u8,
parent_key: ?[]const u8,
};
const ArrList = std.ArrayList(Self);
const ArrList = std.ArrayList(@This());
pub fn getTopLevelTags(conn: *sqlite.Db, arena: Allocator) ![]@This() {
var stmt = try conn.prepareDynamic("SELECT id, key, parent_key FROM tag WHERE parent_key IS NULL;");
pub fn getTopLevelTags(conn: *sqlite.Db, arena: Allocator) ![]Self {
var stmt = try conn.prepareDynamic("SELECT id, cat_id, key FROM tag WHERE TRUE;");
defer stmt.deinit();
var list = ArrList.empty;
defer list.deinit(arena);
var iter = try stmt.iterator(internal, .{});
var iter = try stmt.iterator(Self, .{});
while (try iter.nextAlloc(arena, .{})) |row| {
var tag: @This() = .{
.id = row.id,
.key = row.key,
};
try tag.getChildren(conn, arena);
try list.append(arena, tag);
try list.append(arena, row);
}
return list.toOwnedSlice(arena);
}
pub fn getChildren(self: *@This(), conn: *sqlite.Db, arena: Allocator) !void {
var stmt = try conn.prepareDynamic("SELECT id, key, parent_key FROM tag WHERE parent_key LIKE ?;");
defer stmt.deinit();
var list = ArrList.empty;
defer list.deinit(arena);
var iter = try stmt.iterator(internal, .{self.key});
while (try iter.nextAlloc(arena, .{})) |row| {
var tag: @This() = .{
.id = row.id,
.key = row.key,
};
tag.parent = self;
try tag.getChildren(conn, arena);
try list.append(arena, tag);
}
self.children = try list.toOwnedSlice(arena);
}
pub fn jsonStringify(v: @This(), jws: anytype) !void {
pub fn jsonStringify(v: Self, jws: anytype) !void {
try jws.beginObject();
try jws.objectField("key");
try jws.write(v.key);
try jws.objectField("children");
try jws.write(v.children);
try jws.endObject();
}

View file

@ -2,8 +2,7 @@
import Layout from "@/layout/Layout.vue";
import { Head, Link } from "@inertiajs/vue3";
const props = defineProps<{ page: object }>();
const { thread_id, counter } = props.page;
const props = defineProps<{ thread_id, counter }>();
</script>
<template>

View file

@ -2,8 +2,7 @@
import Layout from "@/layout/Layout.vue";
import { Head, Link } from "@inertiajs/vue3";
const props = defineProps<{ page: object }>();
const { thread_id, counter } = props.page;
const props = defineProps<{ thread_id, counter }>();
</script>
<template>

View file

@ -0,0 +1,81 @@
<template>
<video
@pause="pause"
@ended="pause"
@keyup="changeSpeed"
ref="video"
:poster="previewImageLink"
:controls="isControls"
:title="title"
>
<source :src="link" type="application/x-mpegURL" />
</video>
</template>
<script setup>
import { onMounted, onUpdated, ref } from "vue";
import Hls from "hls.js";
const props = defineProps({
previewImageLink: {
type: String,
default: "",
},
link: {
type: String,
default: "",
},
progress: {
type: Number,
default: 0,
},
title: {
type: String,
default: "",
},
isMuted: {
type: Boolean,
default: false,
},
isControls: {
type: Boolean,
default: true,
},
});
const emit = defineEmits(["pause", "test"]);
const video = ref(null);
onMounted(() => {
prepareVideoPlayer();
});
onUpdated(() => {
prepareVideoPlayer();
});
function prepareVideoPlayer() {
let hls = new Hls();
let stream = props.link;
hls.loadSource(stream);
if (video.value) {
hls.attachMedia(video.value);
video.value.muted = props.isMuted;
video.value.currentTime = props.progress;
}
}
function pause() {
const currentTime = video?.value?.currentTime || 0;
emit("pause", currentTime);
}
function changeSpeed(e) {
if (e.key === "w" && video.value) {
video.value.playbackRate = video.value.playbackRate + 0.25;
} else if (e.key === "s" && video.value) {
video.value.playbackRate = video.value.playbackRate - 0.25;
}
}
</script>

View file

@ -0,0 +1,33 @@
<script setup lang="ts">
import Layout from "@/layout/Layout.vue";
import { Head, Link } from "@inertiajs/vue3";
import BasePlayer from "./BasePlayer.vue";
const props = defineProps<{ thread_id; counter }>();
function pause(currentTime) {
console.log("PAUSE", currentTime);
}
const link = '/test.m3u8';
const previewImageLink = '/test.jpg';
const isMuted = false;
const isControls = true;
const progress_S = 15;
</script>
<template>
<Layout>
<div class="bg-gray-800">
<BasePlayer
:previewImageLink="previewImageLink"
:link="link"
:progress="progress_S"
:isMuted="isMuted"
:isControls="isControls"
@pause="pause"
class="aspect-video bg-black mx-auto max-h-[80vh]"
/>
</div>
</Layout>
</template>