Started watch sync feature
This commit is contained in:
@@ -12,6 +12,7 @@
|
||||

|
||||
|
||||
# Features
|
||||
- Watch sync parties!
|
||||
- Sponsorblock built-in.
|
||||
- Return YouTube dislikes built-in.
|
||||
- DeArrow built-in (With local processing fallback).
|
||||
|
||||
Generated
+186
@@ -11,6 +11,7 @@
|
||||
"beercss": "^3.5.1",
|
||||
"human-number": "^2.0.4",
|
||||
"media-icons": "^0.10.0",
|
||||
"peerjs": "^1.5.2",
|
||||
"sponsorblock-api": "^0.2.4",
|
||||
"svelte-infinite-loading": "^1.3.8",
|
||||
"svelte-persisted-store": "^0.9.1",
|
||||
@@ -1984,6 +1985,78 @@
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cbor-extract/cbor-extract-darwin-arm64": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-arm64/-/cbor-extract-darwin-arm64-2.2.0.tgz",
|
||||
"integrity": "sha512-P7swiOAdF7aSi0H+tHtHtr6zrpF3aAq/W9FXx5HektRvLTM2O89xCyXF3pk7pLc7QpaY7AoaE8UowVf9QBdh3w==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@cbor-extract/cbor-extract-darwin-x64": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-darwin-x64/-/cbor-extract-darwin-x64-2.2.0.tgz",
|
||||
"integrity": "sha512-1liF6fgowph0JxBbYnAS7ZlqNYLf000Qnj4KjqPNW4GViKrEql2MgZnAsExhY9LSy8dnvA4C0qHEBgPrll0z0w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@cbor-extract/cbor-extract-linux-arm": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm/-/cbor-extract-linux-arm-2.2.0.tgz",
|
||||
"integrity": "sha512-QeBcBXk964zOytiedMPQNZr7sg0TNavZeuUCD6ON4vEOU/25+pLhNN6EDIKJ9VLTKaZ7K7EaAriyYQ1NQ05s/Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@cbor-extract/cbor-extract-linux-arm64": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-arm64/-/cbor-extract-linux-arm64-2.2.0.tgz",
|
||||
"integrity": "sha512-rQvhNmDuhjTVXSPFLolmQ47/ydGOFXtbR7+wgkSY0bdOxCFept1hvg59uiLPT2fVDuJFuEy16EImo5tE2x3RsQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@cbor-extract/cbor-extract-linux-x64": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-linux-x64/-/cbor-extract-linux-x64-2.2.0.tgz",
|
||||
"integrity": "sha512-cWLAWtT3kNLHSvP4RKDzSTX9o0wvQEEAj4SKvhWuOVZxiDAeQazr9A+PSiRILK1VYMLeDml89ohxCnUNQNQNCw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@cbor-extract/cbor-extract-win32-x64": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@cbor-extract/cbor-extract-win32-x64/-/cbor-extract-win32-x64-2.2.0.tgz",
|
||||
"integrity": "sha512-l2M+Z8DO2vbvADOBNLbbh9y5ST1RY5sqkWOg/58GkUPBYou/cuNZ68SGQ644f1CvZ8kcOxyZtw06+dxWHIoN/w==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.20.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz",
|
||||
@@ -2544,6 +2617,14 @@
|
||||
"resolved": "https://registry.npmjs.org/@material/material-color-utilities/-/material-color-utilities-0.2.7.tgz",
|
||||
"integrity": "sha512-0FCeqG6WvK4/Cc06F/xXMd/pv4FeisI0c1tUpBbfhA2n9Y8eZEv4Karjbmf2ZqQCPUWMrGp8A571tCjizxoTiQ=="
|
||||
},
|
||||
"node_modules/@msgpack/msgpack": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
|
||||
"integrity": "sha512-h9u4u/jiIRKbq25PM+zymTyW6bhTzELvOoUd+AvYriWOAKpLGnIamaET3pnHYoI5iYphAHBI4ayx0MehR+VVPQ==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@@ -3695,6 +3776,35 @@
|
||||
],
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/cbor-extract": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/cbor-extract/-/cbor-extract-2.2.0.tgz",
|
||||
"integrity": "sha512-Ig1zM66BjLfTXpNgKpvBePq271BPOvu8MR0Jl080yG7Jsl+wAZunfrwiwA+9ruzm/WEdIV5QF/bjDZTqyAIVHA==",
|
||||
"hasInstallScript": true,
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"node-gyp-build-optional-packages": "5.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"download-cbor-prebuilds": "bin/download-prebuilds.js"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@cbor-extract/cbor-extract-darwin-arm64": "2.2.0",
|
||||
"@cbor-extract/cbor-extract-darwin-x64": "2.2.0",
|
||||
"@cbor-extract/cbor-extract-linux-arm": "2.2.0",
|
||||
"@cbor-extract/cbor-extract-linux-arm64": "2.2.0",
|
||||
"@cbor-extract/cbor-extract-linux-x64": "2.2.0",
|
||||
"@cbor-extract/cbor-extract-win32-x64": "2.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cbor-x": {
|
||||
"version": "1.5.4",
|
||||
"resolved": "https://registry.npmjs.org/cbor-x/-/cbor-x-1.5.4.tgz",
|
||||
"integrity": "sha512-PVKILDn+Rf6MRhhcyzGXi5eizn1i0i3F8Fe6UMMxXBnWkalq9+C5+VTmlIjAYM4iF2IYF2N+zToqAfYOp+3rfw==",
|
||||
"optionalDependencies": {
|
||||
"cbor-extract": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
|
||||
@@ -4039,6 +4149,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/detect-libc": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
|
||||
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
|
||||
"optional": true,
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/devalue": {
|
||||
"version": "4.3.2",
|
||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-4.3.2.tgz",
|
||||
@@ -4536,6 +4655,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
|
||||
@@ -5957,6 +6081,20 @@
|
||||
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/node-gyp-build-optional-packages": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-gyp-build-optional-packages/-/node-gyp-build-optional-packages-5.1.1.tgz",
|
||||
"integrity": "sha512-+P72GAjVAbTxjjwUmwjVrqrdZROD4nf8KgpBoDxqXXTiYZZt/ud60dE5yvCSr9lRO8e8yv6kgJIC0K0PfZFVQw==",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"detect-libc": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"node-gyp-build-optional-packages": "bin.js",
|
||||
"node-gyp-build-optional-packages-optional": "optional.js",
|
||||
"node-gyp-build-optional-packages-test": "build-test.js"
|
||||
}
|
||||
},
|
||||
"node_modules/node-releases": {
|
||||
"version": "2.0.14",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
|
||||
@@ -6122,6 +6260,37 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/peerjs": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/peerjs/-/peerjs-1.5.2.tgz",
|
||||
"integrity": "sha512-pPrtNwPyWJHRPxy2y+rHcdlrG8UwUBB1nl+3Yj6r7FLwcbBpcB2NvGNvLvcrxAVGGGX9fsdA5VT5zBKTZcm1DQ==",
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^2.8.0",
|
||||
"cbor-x": "1.5.4",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"peerjs-js-binarypack": "^2.1.0",
|
||||
"webrtc-adapter": "^8.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/peer"
|
||||
}
|
||||
},
|
||||
"node_modules/peerjs-js-binarypack": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/peerjs-js-binarypack/-/peerjs-js-binarypack-2.1.0.tgz",
|
||||
"integrity": "sha512-YIwCC+pTzp3Bi8jPI9UFKO0t0SLo6xALnHkiNt/iUFmUUZG0fEEmEyFKvjsDKweiFitzHRyhuh6NvyJZ4nNxMg==",
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/peer"
|
||||
}
|
||||
},
|
||||
"node_modules/periscopic": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/periscopic/-/periscopic-3.1.0.tgz",
|
||||
@@ -6692,6 +6861,11 @@
|
||||
"rimraf": "bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/sdp": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.0.tgz",
|
||||
"integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw=="
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.6.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
|
||||
@@ -7784,6 +7958,18 @@
|
||||
"resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.1.tgz",
|
||||
"integrity": "sha512-poXpCylU7ExuvZK8z+On3kX+S8o/2dQ/SVYueKA0D4WEMXROXgY8Ez50/bQEUmvoSMMrWcrJqCHuhAbsiwg7Dg=="
|
||||
},
|
||||
"node_modules/webrtc-adapter": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-8.2.3.tgz",
|
||||
"integrity": "sha512-gnmRz++suzmvxtp3ehQts6s2JtAGPuDPjA1F3a9ckNpG1kYdYuHWYpazoAnL9FS5/B21tKlhkorbdCXat0+4xQ==",
|
||||
"dependencies": {
|
||||
"sdp": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
|
||||
|
||||
@@ -38,6 +38,7 @@
|
||||
"beercss": "^3.5.1",
|
||||
"human-number": "^2.0.4",
|
||||
"media-icons": "^0.10.0",
|
||||
"peerjs": "^1.5.2",
|
||||
"sponsorblock-api": "^0.2.4",
|
||||
"svelte-infinite-loading": "^1.3.8",
|
||||
"svelte-persisted-store": "^0.9.1",
|
||||
|
||||
@@ -24,8 +24,8 @@
|
||||
export let currentTime: number = 0;
|
||||
export let audioMode = false;
|
||||
export let playlistVideos: PlaylistPageVideo[] | null = null;
|
||||
export let player: MediaPlayerElement;
|
||||
|
||||
let player: MediaPlayerElement;
|
||||
let src: PlayerSrc = [];
|
||||
let categoryBeingSkipped = '';
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
export interface PlayerEvent {
|
||||
type: "pause" | "seek" | "change-video" | "play";
|
||||
time?: number;
|
||||
videoId?: string;
|
||||
}
|
||||
@@ -13,8 +13,12 @@
|
||||
import Player from '$lib/Player.svelte';
|
||||
import Thumbnail from '$lib/Thumbnail.svelte';
|
||||
import { cleanNumber, numberWithCommas } from '$lib/misc.js';
|
||||
import { onMount } from 'svelte';
|
||||
import type { PlayerEvent } from '$lib/player';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import Peer from 'peerjs';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import type { MediaPlayerElement } from 'vidstack/elements';
|
||||
import { activePage, auth, playerListenByDefault } from '../../../store';
|
||||
|
||||
export let data;
|
||||
@@ -23,10 +27,83 @@
|
||||
|
||||
activePage.set(null);
|
||||
|
||||
const currentUrl = new URL(location.href);
|
||||
|
||||
let playlistVideos: PlaylistPageVideo[] = [];
|
||||
let playlist: PlaylistPage | null = null;
|
||||
|
||||
let audioMode = get(playerListenByDefault);
|
||||
let currentTime: number;
|
||||
let seekTo: (time: number) => void;
|
||||
let player: MediaPlayerElement;
|
||||
|
||||
let peer: Peer | null = null;
|
||||
|
||||
function playerSyncEvents(conn: DataConnection) {
|
||||
conn.on('data', (data) => {
|
||||
const events = data as PlayerEvent[];
|
||||
|
||||
events.forEach((event) => {
|
||||
if (event.type === 'pause') {
|
||||
player.pause();
|
||||
} else if (event.type === 'play') {
|
||||
player.play();
|
||||
} else if (event.type === 'seek' && event.time) {
|
||||
player.currentTime = event.time;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
player.addEventListener('pause', () => {
|
||||
conn.send([
|
||||
{
|
||||
type: 'pause'
|
||||
},
|
||||
{
|
||||
type: 'seek',
|
||||
time: player.currentTime
|
||||
}
|
||||
] as PlayerEvent[]);
|
||||
});
|
||||
|
||||
player.addEventListener('playing', () => {
|
||||
conn.send([
|
||||
{
|
||||
type: 'play'
|
||||
},
|
||||
{
|
||||
type: 'seek',
|
||||
time: player.currentTime
|
||||
}
|
||||
] as PlayerEvent[]);
|
||||
});
|
||||
|
||||
player.addEventListener('waiting', () => {
|
||||
conn.send([
|
||||
{
|
||||
type: 'pause'
|
||||
},
|
||||
{
|
||||
type: 'seek',
|
||||
time: player.currentTime
|
||||
}
|
||||
] as PlayerEvent[]);
|
||||
});
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const syncId = currentUrl.searchParams.get('sync');
|
||||
if (syncId) {
|
||||
peer = new Peer(crypto.randomUUID());
|
||||
peer.on('open', () => {
|
||||
if (!peer) return;
|
||||
|
||||
const conn = peer.connect(syncId);
|
||||
|
||||
playerSyncEvents(conn);
|
||||
});
|
||||
}
|
||||
|
||||
if (!data.playlistId) return;
|
||||
|
||||
for (let page = 1; page < Infinity; page++) {
|
||||
@@ -54,6 +131,34 @@
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if (peer) {
|
||||
peer.disconnect();
|
||||
peer = null;
|
||||
}
|
||||
});
|
||||
|
||||
async function startWatchSync() {
|
||||
if (peer) {
|
||||
peer.disconnect();
|
||||
peer = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const peerId = crypto.randomUUID();
|
||||
|
||||
currentUrl.searchParams.set('sync', peerId);
|
||||
window.history.pushState({ path: currentUrl.toString() }, '', currentUrl.toString());
|
||||
|
||||
peer = new Peer(peerId);
|
||||
|
||||
peer.on('connection', (conn) => {
|
||||
conn.on('open', () => {
|
||||
playerSyncEvents(conn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function addVideoToPlaylist(playlistId: string) {
|
||||
await addPlaylistVideo(playlistId, data.video.videoId);
|
||||
|
||||
@@ -83,17 +188,13 @@
|
||||
|
||||
data.subscribed = !data.subscribed;
|
||||
}
|
||||
|
||||
let audioMode = get(playerListenByDefault);
|
||||
let currentTime: number;
|
||||
let seekTo: (time: number) => void;
|
||||
</script>
|
||||
|
||||
{#if data}
|
||||
<div class="grid">
|
||||
<div class="s12 m12 l10">
|
||||
{#key data.video.videoId}
|
||||
<Player {data} {audioMode} {playlistVideos} bind:seekTo bind:currentTime />
|
||||
<Player {data} {audioMode} {playlistVideos} bind:seekTo bind:currentTime bind:player />
|
||||
{/key}
|
||||
|
||||
<h5>{data.video.title}</h5>
|
||||
@@ -150,6 +251,18 @@
|
||||
</div>
|
||||
|
||||
<div class="s12 m12 l4 video-actions">
|
||||
{#if data.video.hlsUrl}
|
||||
<button disabled>
|
||||
<i>group</i>
|
||||
<span>Watch sync</span>
|
||||
<div class="tooltip">Sync not available for live streams</div>
|
||||
</button>
|
||||
{:else}
|
||||
<button on:click={startWatchSync} class:border={!peer}>
|
||||
<i>group</i>
|
||||
<span>Watch sync</span>
|
||||
</button>
|
||||
{/if}
|
||||
<button
|
||||
class="no-margin"
|
||||
on:click={() => (audioMode = !audioMode)}
|
||||
|
||||
Reference in New Issue
Block a user