Compare commits
4 Commits
acb0cd3161
...
vidstack
| Author | SHA1 | Date | |
|---|---|---|---|
|
81a3a2a30c
|
|||
|
f7636f61a7
|
|||
|
707ba4cdf2
|
|||
|
1a20111501
|
17
bun.lock
17
bun.lock
@@ -19,6 +19,7 @@
|
|||||||
"svelte-check": "^4.4.2",
|
"svelte-check": "^4.4.2",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
"vidstack": "^1.12.13",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-devtools-json": "^1.0.0",
|
"vite-plugin-devtools-json": "^1.0.0",
|
||||||
"wrangler": "^4.81.1",
|
"wrangler": "^4.81.1",
|
||||||
@@ -98,6 +99,12 @@
|
|||||||
|
|
||||||
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
|
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="],
|
||||||
|
|
||||||
|
"@floating-ui/core": ["@floating-ui/core@1.7.5", "", { "dependencies": { "@floating-ui/utils": "^0.2.11" } }, "sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ=="],
|
||||||
|
|
||||||
|
"@floating-ui/dom": ["@floating-ui/dom@1.7.6", "", { "dependencies": { "@floating-ui/core": "^1.7.5", "@floating-ui/utils": "^0.2.11" } }, "sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ=="],
|
||||||
|
|
||||||
|
"@floating-ui/utils": ["@floating-ui/utils@0.2.11", "", {}, "sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg=="],
|
||||||
|
|
||||||
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
|
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
|
||||||
|
|
||||||
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
|
||||||
@@ -342,10 +349,14 @@
|
|||||||
|
|
||||||
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
|
||||||
|
|
||||||
|
"lit-html": ["lit-html@2.8.0", "", { "dependencies": { "@types/trusted-types": "^2.0.2" } }, "sha512-o9t+MQM3P4y7M7yNzqAyjp7z+mQGa4NS4CxiyLqFPyFWyc4O+nodLrkrxSaCTrla6M5YOLaT3RpbbqjszB5g3Q=="],
|
||||||
|
|
||||||
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
|
||||||
|
|
||||||
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
|
||||||
|
|
||||||
|
"media-captions": ["media-captions@1.0.4", "", {}, "sha512-cyDNmuZvvO4H27rcBq2Eudxo9IZRDCOX/I7VEyqbxsEiD2Ei7UYUhG/Sc5fvMZjmathgz3fEK7iAKqvpY+Ux1w=="],
|
||||||
|
|
||||||
"miniflare": ["miniflare@4.20260409.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260409.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-ayl6To4av0YuXsSivGgWLj+Ug8xZ0Qz3sGV8+Ok2LhNVl6m8m5ktEBM3LX9iT9MtLZRJwBlJrKcraNs/DlZQfA=="],
|
"miniflare": ["miniflare@4.20260409.0", "", { "dependencies": { "@cspotcode/source-map-support": "0.8.1", "sharp": "^0.34.5", "undici": "7.24.4", "workerd": "1.20260409.1", "ws": "8.18.0", "youch": "4.1.0-beta.10" }, "bin": { "miniflare": "bootstrap.js" } }, "sha512-ayl6To4av0YuXsSivGgWLj+Ug8xZ0Qz3sGV8+Ok2LhNVl6m8m5ktEBM3LX9iT9MtLZRJwBlJrKcraNs/DlZQfA=="],
|
||||||
|
|
||||||
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
"mri": ["mri@1.2.0", "", {}, "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA=="],
|
||||||
@@ -424,16 +435,22 @@
|
|||||||
|
|
||||||
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
"unenv": ["unenv@2.0.0-rc.24", "", { "dependencies": { "pathe": "^2.0.3" } }, "sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw=="],
|
||||||
|
|
||||||
|
"unplugin": ["unplugin@1.16.1", "", { "dependencies": { "acorn": "^8.14.0", "webpack-virtual-modules": "^0.6.2" } }, "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w=="],
|
||||||
|
|
||||||
"url-polyfill": ["url-polyfill@1.1.14", "", {}, "sha512-p4f3TTAG6ADVF3mwbXw7hGw+QJyw5CnNGvYh5fCuQQZIiuKUswqcznyV3pGDP9j0TSmC4UvRKm8kl1QsX1diiQ=="],
|
"url-polyfill": ["url-polyfill@1.1.14", "", {}, "sha512-p4f3TTAG6ADVF3mwbXw7hGw+QJyw5CnNGvYh5fCuQQZIiuKUswqcznyV3pGDP9j0TSmC4UvRKm8kl1QsX1diiQ=="],
|
||||||
|
|
||||||
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
"uuid": ["uuid@11.1.0", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A=="],
|
||||||
|
|
||||||
|
"vidstack": ["vidstack@1.12.13", "", { "dependencies": { "@floating-ui/dom": "^1.6.10", "lit-html": "^2.8.0", "media-captions": "^1.0.4", "unplugin": "^1.12.0" } }, "sha512-vuNeyRmWoH/7EoFVDYjp9nkgcqtCMmal518LDeb78dYKgWb+p6+vtY0AzDhrkBv5q1UiCn+xwmjMmwvSlPLuhQ=="],
|
||||||
|
|
||||||
"vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
|
"vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
|
||||||
|
|
||||||
"vite-plugin-devtools-json": ["vite-plugin-devtools-json@1.0.0", "", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw=="],
|
"vite-plugin-devtools-json": ["vite-plugin-devtools-json@1.0.0", "", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, "sha512-MobvwqX76Vqt/O4AbnNMNWoXWGrKUqZbphCUle/J2KXH82yKQiunOeKnz/nqEPosPsoWWPP9FtNuPBSYpiiwkw=="],
|
||||||
|
|
||||||
"vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
|
"vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
|
||||||
|
|
||||||
|
"webpack-virtual-modules": ["webpack-virtual-modules@0.6.2", "", {}, "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="],
|
||||||
|
|
||||||
"workerd": ["workerd@1.20260409.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260409.1", "@cloudflare/workerd-darwin-arm64": "1.20260409.1", "@cloudflare/workerd-linux-64": "1.20260409.1", "@cloudflare/workerd-linux-arm64": "1.20260409.1", "@cloudflare/workerd-windows-64": "1.20260409.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-kuWP20fAaqaLBqLbvUfY9nCF6c3C78L60G9lS6eVwBf+v8trVFIsAdLB/FtrnKm7vgVvpDzvFAfB80VIiVj95w=="],
|
"workerd": ["workerd@1.20260409.1", "", { "optionalDependencies": { "@cloudflare/workerd-darwin-64": "1.20260409.1", "@cloudflare/workerd-darwin-arm64": "1.20260409.1", "@cloudflare/workerd-linux-64": "1.20260409.1", "@cloudflare/workerd-linux-arm64": "1.20260409.1", "@cloudflare/workerd-windows-64": "1.20260409.1" }, "bin": { "workerd": "bin/workerd" } }, "sha512-kuWP20fAaqaLBqLbvUfY9nCF6c3C78L60G9lS6eVwBf+v8trVFIsAdLB/FtrnKm7vgVvpDzvFAfB80VIiVj95w=="],
|
||||||
|
|
||||||
"worktop": ["worktop@0.8.0-next.18", "", { "dependencies": { "mrmime": "^2.0.0", "regexparam": "^3.0.0" } }, "sha512-+TvsA6VAVoMC3XDKR5MoC/qlLqDixEfOBysDEKnPIPou/NvoPWCAuXHXMsswwlvmEuvX56lQjvELLyLuzTKvRw=="],
|
"worktop": ["worktop@0.8.0-next.18", "", { "dependencies": { "mrmime": "^2.0.0", "regexparam": "^3.0.0" } }, "sha512-+TvsA6VAVoMC3XDKR5MoC/qlLqDixEfOBysDEKnPIPou/NvoPWCAuXHXMsswwlvmEuvX56lQjvELLyLuzTKvRw=="],
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
"svelte-check": "^4.4.2",
|
"svelte-check": "^4.4.2",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
|
"vidstack": "^1.12.13",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vite-plugin-devtools-json": "^1.0.0",
|
"vite-plugin-devtools-json": "^1.0.0",
|
||||||
"wrangler": "^4.81.1"
|
"wrangler": "^4.81.1"
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const spacetimedb = schema({
|
|||||||
{
|
{
|
||||||
id: t.u64().primaryKey(),
|
id: t.u64().primaryKey(),
|
||||||
url: t.string(),
|
url: t.string(),
|
||||||
|
subtitleUrl: t.string(),
|
||||||
timePosition: t.f64(),
|
timePosition: t.f64(),
|
||||||
isPlaying: t.bool(),
|
isPlaying: t.bool(),
|
||||||
lastUpdatedAt: t.timestamp(),
|
lastUpdatedAt: t.timestamp(),
|
||||||
@@ -19,6 +20,7 @@ export const init = spacetimedb.init((ctx) => {
|
|||||||
ctx.db.videoState.insert({
|
ctx.db.videoState.insert({
|
||||||
id: 1n,
|
id: 1n,
|
||||||
url: "https://cdn.cazzzer.com/LycoReco08.mkv",
|
url: "https://cdn.cazzzer.com/LycoReco08.mkv",
|
||||||
|
subtitleUrl: "",
|
||||||
timePosition: 0.0,
|
timePosition: 0.0,
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
lastUpdatedAt: ctx.timestamp,
|
lastUpdatedAt: ctx.timestamp,
|
||||||
@@ -34,6 +36,7 @@ export const set_url = spacetimedb.reducer({ url: t.string() }, (ctx, { url }) =
|
|||||||
ctx.db.videoState.id.update({
|
ctx.db.videoState.id.update({
|
||||||
...row,
|
...row,
|
||||||
url,
|
url,
|
||||||
|
subtitleUrl: "", // Clear subtitle on new video
|
||||||
timePosition: 0.0,
|
timePosition: 0.0,
|
||||||
isPlaying: false,
|
isPlaying: false,
|
||||||
lastUpdatedAt: ctx.timestamp,
|
lastUpdatedAt: ctx.timestamp,
|
||||||
@@ -71,3 +74,13 @@ export const seek = spacetimedb.reducer({ time_position: t.f64() }, (ctx, { time
|
|||||||
lastUpdatedAt: ctx.timestamp,
|
lastUpdatedAt: ctx.timestamp,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const set_subtitle_url = spacetimedb.reducer({ url: t.string() }, (ctx, { url }) => {
|
||||||
|
const row = ctx.db.videoState.id.find(1n);
|
||||||
|
if (!row) return;
|
||||||
|
ctx.db.videoState.id.update({
|
||||||
|
...row,
|
||||||
|
subtitleUrl: url,
|
||||||
|
lastUpdatedAt: ctx.timestamp,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import {
|
|||||||
import PauseReducer from "./pause_reducer";
|
import PauseReducer from "./pause_reducer";
|
||||||
import PlayReducer from "./play_reducer";
|
import PlayReducer from "./play_reducer";
|
||||||
import SeekReducer from "./seek_reducer";
|
import SeekReducer from "./seek_reducer";
|
||||||
|
import SetSubtitleUrlReducer from "./set_subtitle_url_reducer";
|
||||||
import SetUrlReducer from "./set_url_reducer";
|
import SetUrlReducer from "./set_url_reducer";
|
||||||
|
|
||||||
// Import all procedure arg schemas
|
// Import all procedure arg schemas
|
||||||
@@ -66,6 +67,7 @@ const reducersSchema = __reducers(
|
|||||||
__reducerSchema("pause", PauseReducer),
|
__reducerSchema("pause", PauseReducer),
|
||||||
__reducerSchema("play", PlayReducer),
|
__reducerSchema("play", PlayReducer),
|
||||||
__reducerSchema("seek", SeekReducer),
|
__reducerSchema("seek", SeekReducer),
|
||||||
|
__reducerSchema("set_subtitle_url", SetSubtitleUrlReducer),
|
||||||
__reducerSchema("set_url", SetUrlReducer),
|
__reducerSchema("set_url", SetUrlReducer),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
15
src/lib/st-bindings/set_subtitle_url_reducer.ts
Normal file
15
src/lib/st-bindings/set_subtitle_url_reducer.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
// THIS FILE IS AUTOMATICALLY GENERATED BY SPACETIMEDB. EDITS TO THIS FILE
|
||||||
|
// WILL NOT BE SAVED. MODIFY TABLES IN YOUR MODULE SOURCE CODE INSTEAD.
|
||||||
|
|
||||||
|
/* eslint-disable */
|
||||||
|
/* tslint:disable */
|
||||||
|
import {
|
||||||
|
TypeBuilder as __TypeBuilder,
|
||||||
|
t as __t,
|
||||||
|
type AlgebraicTypeType as __AlgebraicTypeType,
|
||||||
|
type Infer as __Infer,
|
||||||
|
} from "spacetimedb";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
url: __t.string(),
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
export const VideoState = __t.object("VideoState", {
|
export const VideoState = __t.object("VideoState", {
|
||||||
id: __t.u64(),
|
id: __t.u64(),
|
||||||
url: __t.string(),
|
url: __t.string(),
|
||||||
|
subtitleUrl: __t.string(),
|
||||||
timePosition: __t.f64(),
|
timePosition: __t.f64(),
|
||||||
isPlaying: __t.bool(),
|
isPlaying: __t.bool(),
|
||||||
lastUpdatedAt: __t.timestamp(),
|
lastUpdatedAt: __t.timestamp(),
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import { type Infer as __Infer } from "spacetimedb";
|
|||||||
import PauseReducer from "../pause_reducer";
|
import PauseReducer from "../pause_reducer";
|
||||||
import PlayReducer from "../play_reducer";
|
import PlayReducer from "../play_reducer";
|
||||||
import SeekReducer from "../seek_reducer";
|
import SeekReducer from "../seek_reducer";
|
||||||
|
import SetSubtitleUrlReducer from "../set_subtitle_url_reducer";
|
||||||
import SetUrlReducer from "../set_url_reducer";
|
import SetUrlReducer from "../set_url_reducer";
|
||||||
|
|
||||||
export type PauseParams = __Infer<typeof PauseReducer>;
|
export type PauseParams = __Infer<typeof PauseReducer>;
|
||||||
export type PlayParams = __Infer<typeof PlayReducer>;
|
export type PlayParams = __Infer<typeof PlayReducer>;
|
||||||
export type SeekParams = __Infer<typeof SeekReducer>;
|
export type SeekParams = __Infer<typeof SeekReducer>;
|
||||||
|
export type SetSubtitleUrlParams = __Infer<typeof SetSubtitleUrlReducer>;
|
||||||
export type SetUrlParams = __Infer<typeof SetUrlReducer>;
|
export type SetUrlParams = __Infer<typeof SetUrlReducer>;
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
export default __t.row({
|
export default __t.row({
|
||||||
id: __t.u64().primaryKey(),
|
id: __t.u64().primaryKey(),
|
||||||
url: __t.string(),
|
url: __t.string(),
|
||||||
|
subtitleUrl: __t.string().name("subtitle_url"),
|
||||||
timePosition: __t.f64().name("time_position"),
|
timePosition: __t.f64().name("time_position"),
|
||||||
isPlaying: __t.bool().name("is_playing"),
|
isPlaying: __t.bool().name("is_playing"),
|
||||||
lastUpdatedAt: __t.timestamp().name("last_updated_at"),
|
lastUpdatedAt: __t.timestamp().name("last_updated_at"),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useSpacetimeDB, useTable, useReducer } from "spacetimedb/svelte";
|
import { useSpacetimeDB, useTable, useReducer } from "spacetimedb/svelte";
|
||||||
import { tables, reducers } from "$lib/st-bindings";
|
import { tables, reducers } from "$lib/st-bindings";
|
||||||
|
import "vidstack/bundle";
|
||||||
|
import type { MediaPlayerElement } from "vidstack/elements";
|
||||||
|
|
||||||
const conn = useSpacetimeDB();
|
const conn = useSpacetimeDB();
|
||||||
|
|
||||||
@@ -8,12 +10,14 @@
|
|||||||
const videoState = $derived($videoStates.find((state) => state.id === 1n));
|
const videoState = $derived($videoStates.find((state) => state.id === 1n));
|
||||||
|
|
||||||
const setUrlReducer = useReducer(reducers.setUrl);
|
const setUrlReducer = useReducer(reducers.setUrl);
|
||||||
|
const setSubtitleUrlReducer = useReducer(reducers.setSubtitleUrl);
|
||||||
const playReducer = useReducer(reducers.play);
|
const playReducer = useReducer(reducers.play);
|
||||||
const pauseReducer = useReducer(reducers.pause);
|
const pauseReducer = useReducer(reducers.pause);
|
||||||
const seekReducer = useReducer(reducers.seek);
|
const seekReducer = useReducer(reducers.seek);
|
||||||
|
|
||||||
let videoElement: HTMLVideoElement | undefined = $state();
|
let videoElement: MediaPlayerElement | undefined = $state();
|
||||||
let newUrl = $state("");
|
let newUrl = $state("");
|
||||||
|
let newSubtitleUrl = $state("");
|
||||||
|
|
||||||
let isSyncing = false;
|
let isSyncing = false;
|
||||||
let syncTimeout: ReturnType<typeof setTimeout> | undefined;
|
let syncTimeout: ReturnType<typeof setTimeout> | undefined;
|
||||||
@@ -32,10 +36,6 @@
|
|||||||
const el = videoElement;
|
const el = videoElement;
|
||||||
const state = videoState;
|
const state = videoState;
|
||||||
|
|
||||||
if (el.src !== state.url) {
|
|
||||||
el.src = state.url;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Account for server to client clock drift slightly, but MVP assumes relatively synced clocks.
|
// Account for server to client clock drift slightly, but MVP assumes relatively synced clocks.
|
||||||
const timeElapsedMicros = BigInt(Date.now()) * 1000n - state.lastUpdatedAt.microsSinceUnixEpoch;
|
const timeElapsedMicros = BigInt(Date.now()) * 1000n - state.lastUpdatedAt.microsSinceUnixEpoch;
|
||||||
const expectedTime = state.isPlaying
|
const expectedTime = state.isPlaying
|
||||||
@@ -45,8 +45,9 @@
|
|||||||
const diff = Math.abs(el.currentTime - expectedTime);
|
const diff = Math.abs(el.currentTime - expectedTime);
|
||||||
|
|
||||||
if (!state.isPlaying) {
|
if (!state.isPlaying) {
|
||||||
if (el.currentTime < expectedTime && diff <= 2 && !el.paused) {
|
if (el.currentTime < expectedTime && diff <= 2) {
|
||||||
// Behind pause point. Keep playing to catch up. (timeupdate will pause it)
|
// Behind pause point. Keep playing to catch up. (timeupdate will pause it)
|
||||||
|
// If locally paused, stay paused (we likely dispatched the pause event + buffer)
|
||||||
} else {
|
} else {
|
||||||
// Ahead or reached pause point. Pause and rewind if needed.
|
// Ahead or reached pause point. Pause and rewind if needed.
|
||||||
syncAction(() => {
|
syncAction(() => {
|
||||||
@@ -72,12 +73,14 @@
|
|||||||
|
|
||||||
function handlePlay() {
|
function handlePlay() {
|
||||||
if (isSyncing) return;
|
if (isSyncing) return;
|
||||||
playReducer({ timePosition: videoElement?.currentTime ?? 0 });
|
// Send play slightly in future to adjust for network latency
|
||||||
|
playReducer({ timePosition: (videoElement?.currentTime ?? 0) + 0.15 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePause() {
|
function handlePause() {
|
||||||
if (isSyncing) return;
|
if (isSyncing) return;
|
||||||
pauseReducer({ timePosition: videoElement?.currentTime ?? 0 });
|
// Send pause slightly in future, so remote clients coast into it
|
||||||
|
pauseReducer({ timePosition: (videoElement?.currentTime ?? 0) + 0.15 });
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSeeked() {
|
function handleSeeked() {
|
||||||
@@ -108,6 +111,13 @@
|
|||||||
setUrlReducer({ url: newUrl });
|
setUrlReducer({ url: newUrl });
|
||||||
newUrl = "";
|
newUrl = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleSetSubtitle(e: SubmitEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!newSubtitleUrl.trim() || !$conn.isActive) return;
|
||||||
|
setSubtitleUrlReducer({ url: newSubtitleUrl });
|
||||||
|
newSubtitleUrl = "";
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
@@ -131,18 +141,42 @@
|
|||||||
<button type="submit" class="p-2" disabled={!$conn.isActive}>Set URL</button>
|
<button type="submit" class="p-2" disabled={!$conn.isActive}>Set URL</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<form onsubmit={handleSetSubtitle} class="mb-2">
|
||||||
|
<input
|
||||||
|
type="url"
|
||||||
|
placeholder="Enter subtitle track URL (.vtt)"
|
||||||
|
bind:value={newSubtitleUrl}
|
||||||
|
class="mr-2 w-96 p-2"
|
||||||
|
/>
|
||||||
|
<button type="submit" class="p-2">Set Subtitles</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<!-- svelte-ignore a11y_media_has_caption -->
|
{#if videoState?.url}
|
||||||
<video
|
<media-player
|
||||||
bind:this={videoElement}
|
bind:this={videoElement}
|
||||||
controls
|
src={videoState.url}
|
||||||
|
muted
|
||||||
|
crossOrigin
|
||||||
onplay={handlePlay}
|
onplay={handlePlay}
|
||||||
onpause={handlePause}
|
onpause={handlePause}
|
||||||
onseeked={handleSeeked}
|
onseeked={handleSeeked}
|
||||||
ontimeupdate={handleTimeUpdate}
|
ontimeupdate={handleTimeUpdate}
|
||||||
class="w-full max-w-2xl bg-black"
|
class="w-full max-w-2xl bg-black"
|
||||||
>
|
>
|
||||||
Your browser does not support the video tag.
|
<media-provider>
|
||||||
</video>
|
{#if videoState?.subtitleUrl}
|
||||||
|
<track
|
||||||
|
src={videoState.subtitleUrl}
|
||||||
|
kind="subtitles"
|
||||||
|
srclang="en"
|
||||||
|
label="English"
|
||||||
|
default
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
|
</media-provider>
|
||||||
|
<media-video-layout> </media-video-layout>
|
||||||
|
</media-player>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -11,7 +11,11 @@
|
|||||||
"sourceMap": true,
|
"sourceMap": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
"types": ["./src/worker-configuration.d.ts", "node"]
|
"types": [
|
||||||
|
"./src/worker-configuration.d.ts",
|
||||||
|
"node",
|
||||||
|
"vidstack/svelte",
|
||||||
|
]
|
||||||
}
|
}
|
||||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||||
|
|||||||
@@ -2,5 +2,6 @@ import devtoolsJson from "vite-plugin-devtools-json";
|
|||||||
import tailwindcss from "@tailwindcss/vite";
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
import { sveltekit } from "@sveltejs/kit/vite";
|
import { sveltekit } from "@sveltejs/kit/vite";
|
||||||
import { defineConfig } from "vite";
|
import { defineConfig } from "vite";
|
||||||
|
import { vite as vidstack } from "vidstack/plugins";
|
||||||
|
|
||||||
export default defineConfig({ plugins: [tailwindcss(), sveltekit(), devtoolsJson()] });
|
export default defineConfig({ plugins: [tailwindcss(), vidstack(), sveltekit(), devtoolsJson()] });
|
||||||
|
|||||||
Reference in New Issue
Block a user