simple video test
This commit is contained in:
parent
fd584886c6
commit
fc4558be2c
10 changed files with 195 additions and 59 deletions
7
package-lock.json
generated
7
package-lock.json
generated
|
|
@ -6,6 +6,7 @@
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inertiajs/vue3": "^2.2.11",
|
"@inertiajs/vue3": "^2.2.11",
|
||||||
|
"hls.js": "^1.6.14",
|
||||||
"tailwindcss": "^4.1.16"
|
"tailwindcss": "^4.1.16"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
@ -2134,6 +2135,12 @@
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/hls.js": {
|
||||||
|
"version": "1.6.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/hls.js/-/hls.js-1.6.14.tgz",
|
||||||
|
"integrity": "sha512-CSpT2aXsv71HST8C5ETeVo+6YybqCpHBiYrCRQSn3U5QUZuLTSsvtq/bj+zuvjLVADeKxoebzo16OkH8m1+65Q==",
|
||||||
|
"license": "Apache-2.0"
|
||||||
|
},
|
||||||
"node_modules/immutable": {
|
"node_modules/immutable": {
|
||||||
"version": "5.1.4",
|
"version": "5.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.4.tgz",
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@inertiajs/vue3": "^2.2.11",
|
"@inertiajs/vue3": "^2.2.11",
|
||||||
|
"hls.js": "^1.6.14",
|
||||||
"tailwindcss": "^4.1.16"
|
"tailwindcss": "^4.1.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,30 +6,31 @@ const Allocator = std.mem.Allocator;
|
||||||
const builtin = @import("builtin");
|
const builtin = @import("builtin");
|
||||||
const optimize_mode = builtin.mode;
|
const optimize_mode = builtin.mode;
|
||||||
|
|
||||||
|
const model_cat = @import("./models/cat.zig");
|
||||||
const model_tag = @import("./models/tag.zig");
|
const model_tag = @import("./models/tag.zig");
|
||||||
|
|
||||||
db_connection: *sqlite.Db,
|
db_connection: *sqlite.Db,
|
||||||
counter: u32 = 0,
|
counter: u32 = 0,
|
||||||
|
|
||||||
pub fn init(connection: *sqlite.Db) !@This() {
|
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 .{
|
return .{
|
||||||
.db_connection = connection,
|
.db_connection = connection,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn SendInertiaResponse(endpoint: anytype, context: *@This(), r: zap.Request, arena: Allocator, component: []const u8, props: anytype) !void {
|
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(
|
const inertia_json = try std.fmt.allocPrint(
|
||||||
arena,
|
arena,
|
||||||
"{f}",
|
"{f}",
|
||||||
.{std.json.fmt(.{
|
.{std.json.fmt(.{
|
||||||
.component = component,
|
.component = component,
|
||||||
.props = .{
|
.props = props,
|
||||||
.tags = tags,
|
|
||||||
.page = props,
|
|
||||||
},
|
|
||||||
.url = endpoint.path,
|
.url = endpoint.path,
|
||||||
.version = "",
|
.version = "",
|
||||||
}, .{
|
}, .{
|
||||||
|
|
@ -41,7 +42,6 @@ pub fn SendInertiaResponse(endpoint: anytype, context: *@This(), r: zap.Request,
|
||||||
try r.setHeader("X-Inertia", "true");
|
try r.setHeader("X-Inertia", "true");
|
||||||
try r.setHeader("Content-Type", "application/json");
|
try r.setHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
r.setStatus(.ok);
|
|
||||||
try r.sendBody(inertia_json);
|
try r.sendBody(inertia_json);
|
||||||
} else {
|
} else {
|
||||||
try r.setHeader("Content-Type", "text/html");
|
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);
|
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 {
|
pub fn unhandledRequest(ctx: *@This(), arena: Allocator, r: zap.Request) anyerror!void {
|
||||||
const path = r.path orelse "/not-found";
|
const path = r.path orelse "/not-found";
|
||||||
|
r.setStatus(.not_found);
|
||||||
try SendInertiaResponse(.{ .path = path }, ctx, r, arena, "404", .{});
|
try SendInertiaResponse(.{ .path = path }, ctx, r, arena, "404", .{});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ const SimpleEndpoint = struct {
|
||||||
pub fn get(e: *SimpleEndpoint, arena: Allocator, context: *Context, r: zap.Request) !void {
|
pub fn get(e: *SimpleEndpoint, arena: Allocator, context: *Context, r: zap.Request) !void {
|
||||||
const thread_id = std.Thread.getCurrentId();
|
const thread_id = std.Thread.getCurrentId();
|
||||||
|
|
||||||
|
r.setStatus(.ok);
|
||||||
try Context.SendInertiaResponse(e, context, r, arena, e.component, .{
|
try Context.SendInertiaResponse(e, context, r, arena, e.component, .{
|
||||||
.thread_id = thread_id,
|
.thread_id = thread_id,
|
||||||
.counter = context.counter,
|
.counter = context.counter,
|
||||||
|
|
@ -85,12 +86,14 @@ pub fn main() !void {
|
||||||
// create the endpoints
|
// create the endpoints
|
||||||
var home_endpoint = SimpleEndpoint.init("/", "home/home");
|
var home_endpoint = SimpleEndpoint.init("/", "home/home");
|
||||||
var test_endpoint = SimpleEndpoint.init("/test", "home/test");
|
var test_endpoint = SimpleEndpoint.init("/test", "home/test");
|
||||||
|
var test_video_endpoint = SimpleEndpoint.init("/video", "media/video");
|
||||||
var stop_endpoint: StopEndpoint = .{ .path = "/stop" };
|
var stop_endpoint: StopEndpoint = .{ .path = "/stop" };
|
||||||
|
|
||||||
// register the endpoints with the App
|
// register the endpoints with the App
|
||||||
try App.register(&stop_endpoint);
|
try App.register(&stop_endpoint);
|
||||||
try App.register(&home_endpoint);
|
try App.register(&home_endpoint);
|
||||||
try App.register(&test_endpoint);
|
try App.register(&test_endpoint);
|
||||||
|
try App.register(&test_video_endpoint);
|
||||||
|
|
||||||
// listen on the network
|
// listen on the network
|
||||||
try App.listen(.{
|
try App.listen(.{
|
||||||
|
|
|
||||||
47
src/models/cat.zig
Normal file
47
src/models/cat.zig
Normal 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();
|
||||||
|
}
|
||||||
|
|
@ -5,77 +5,43 @@ const optimize_mode = builtin.mode;
|
||||||
|
|
||||||
const sqlite = @import("sqlite");
|
const sqlite = @import("sqlite");
|
||||||
|
|
||||||
|
const Self = @This();
|
||||||
|
|
||||||
pub const schema =
|
pub const schema =
|
||||||
\\CREATE TABLE IF NOT EXISTS tag(
|
\\CREATE TABLE IF NOT EXISTS tag(
|
||||||
\\ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
\\ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
\\ key TEXT UNIQUE NOT NULL,
|
\\ key TEXT UNIQUE NOT NULL,
|
||||||
\\ parent_key TEXT TEXT,
|
\\ cat_id INTEGER NOT NULL,
|
||||||
\\ FOREIGN KEY (parent_key) REFERENCES tag(key) ON DELETE RESTRICT
|
\\ FOREIGN KEY (cat_id) REFERENCES cat(id) ON DELETE RESTRICT
|
||||||
\\);
|
\\);
|
||||||
;
|
;
|
||||||
|
|
||||||
id: usize,
|
id: usize,
|
||||||
|
cat_id: usize,
|
||||||
key: []const u8,
|
key: []const u8,
|
||||||
parent: ?*@This() = null,
|
|
||||||
children: ?[]@This() = null,
|
|
||||||
|
|
||||||
const internal = struct {
|
const ArrList = std.ArrayList(Self);
|
||||||
id: usize,
|
|
||||||
key: []const u8,
|
|
||||||
parent_key: ?[]const u8,
|
|
||||||
};
|
|
||||||
|
|
||||||
const ArrList = std.ArrayList(@This());
|
pub fn getTopLevelTags(conn: *sqlite.Db, arena: Allocator) ![]Self {
|
||||||
|
var stmt = try conn.prepareDynamic("SELECT id, cat_id, key FROM tag WHERE TRUE;");
|
||||||
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;");
|
|
||||||
defer stmt.deinit();
|
defer stmt.deinit();
|
||||||
|
|
||||||
var list = ArrList.empty;
|
var list = ArrList.empty;
|
||||||
defer list.deinit(arena);
|
defer list.deinit(arena);
|
||||||
|
|
||||||
var iter = try stmt.iterator(internal, .{});
|
var iter = try stmt.iterator(Self, .{});
|
||||||
while (try iter.nextAlloc(arena, .{})) |row| {
|
while (try iter.nextAlloc(arena, .{})) |row| {
|
||||||
var tag: @This() = .{
|
try list.append(arena, row);
|
||||||
.id = row.id,
|
|
||||||
.key = row.key,
|
|
||||||
};
|
|
||||||
try tag.getChildren(conn, arena);
|
|
||||||
try list.append(arena, tag);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return list.toOwnedSlice(arena);
|
return list.toOwnedSlice(arena);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn getChildren(self: *@This(), conn: *sqlite.Db, arena: Allocator) !void {
|
pub fn jsonStringify(v: Self, jws: anytype) !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 {
|
|
||||||
try jws.beginObject();
|
try jws.beginObject();
|
||||||
|
|
||||||
try jws.objectField("key");
|
try jws.objectField("key");
|
||||||
try jws.write(v.key);
|
try jws.write(v.key);
|
||||||
|
|
||||||
try jws.objectField("children");
|
|
||||||
try jws.write(v.children);
|
|
||||||
|
|
||||||
try jws.endObject();
|
try jws.endObject();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
import Layout from "@/layout/Layout.vue";
|
import Layout from "@/layout/Layout.vue";
|
||||||
import { Head, Link } from "@inertiajs/vue3";
|
import { Head, Link } from "@inertiajs/vue3";
|
||||||
|
|
||||||
const props = defineProps<{ page: object }>();
|
const props = defineProps<{ thread_id, counter }>();
|
||||||
const { thread_id, counter } = props.page;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,7 @@
|
||||||
import Layout from "@/layout/Layout.vue";
|
import Layout from "@/layout/Layout.vue";
|
||||||
import { Head, Link } from "@inertiajs/vue3";
|
import { Head, Link } from "@inertiajs/vue3";
|
||||||
|
|
||||||
const props = defineProps<{ page: object }>();
|
const props = defineProps<{ thread_id, counter }>();
|
||||||
const { thread_id, counter } = props.page;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|
|
||||||
81
src/modules/media/BasePlayer.vue
Normal file
81
src/modules/media/BasePlayer.vue
Normal 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>
|
||||||
33
src/modules/media/video.vue
Normal file
33
src/modules/media/video.vue
Normal 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>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue