Removed legacy peerjs sync party
This commit is contained in:
@@ -29,7 +29,6 @@
|
||||
- Android TV support.
|
||||
- Support for disabling certificate validation for homelab users.
|
||||
- Sync your watch progress between Invidious sessions.
|
||||
- Watch sync parties.
|
||||
- Sponsorblock built-in.
|
||||
- [Return YouTube Dislike](https://github.com/Anarios/return-youtube-dislike) (RYD) built-in.
|
||||
- [DeArrow](https://github.com/ajayyy/DeArrow) built-in.
|
||||
|
||||
Generated
-67
@@ -47,7 +47,6 @@
|
||||
"melt": "^0.44.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mysql2": "^3.18.2",
|
||||
"peerjs": "^1.5.5",
|
||||
"pg": "^8.19.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.7",
|
||||
@@ -3529,15 +3528,6 @@
|
||||
"integrity": "sha512-ztmtTd6xwnuh2/xu+Vb01btgV8SQWYCaK56CkRK8gEkWe5TuDyBcYJ0wgkMRn+2VcE9KUmhvkz+N9GHrqw/C0g==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"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==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/hashes": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz",
|
||||
@@ -8070,12 +8060,6 @@
|
||||
"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==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.3.0",
|
||||
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
|
||||
@@ -12107,38 +12091,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/peerjs": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/peerjs/-/peerjs-1.5.5.tgz",
|
||||
"integrity": "sha512-viMUCPDL6CSfOu0ZqVcFqbWRXNHIbv2lPqNbrBIjbFYrflebOjItJ4hPfhjnuUCstqciHVu9vVJ7jFqqKi/EuQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@msgpack/msgpack": "^2.8.0",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"peerjs-js-binarypack": "^2.1.0",
|
||||
"webrtc-adapter": "^9.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==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/peer"
|
||||
}
|
||||
},
|
||||
"node_modules/pend": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
|
||||
@@ -13664,12 +13616,6 @@
|
||||
"node": ">=v12.22.7"
|
||||
}
|
||||
},
|
||||
"node_modules/sdp": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-3.2.1.tgz",
|
||||
"integrity": "sha512-lwsAIzOPlH8/7IIjjz3K0zYBk7aBVVcvjMwt3M4fLxpjMYyy7i3I97SLHebgn4YBjirkzfp3RvRDWSKsh/+WFw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.7.3",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
|
||||
@@ -15763,19 +15709,6 @@
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause"
|
||||
},
|
||||
"node_modules/webrtc-adapter": {
|
||||
"version": "9.0.3",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-9.0.3.tgz",
|
||||
"integrity": "sha512-5fALBcroIl31OeXAdd1YUntxiZl1eHlZZWzNg3U4Fn+J9/cGL3eT80YlrsWGvj2ojuz1rZr2OXkgCzIxAZ7vRQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"sdp": "^3.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-mimetype": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
|
||||
|
||||
@@ -85,7 +85,6 @@
|
||||
"melt": "^0.44.0",
|
||||
"mousetrap": "^1.6.5",
|
||||
"mysql2": "^3.18.2",
|
||||
"peerjs": "^1.5.5",
|
||||
"pg": "^8.19.0",
|
||||
"pg-hstore": "^2.3.4",
|
||||
"sequelize": "^6.37.7",
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import {
|
||||
determinePeerJsInstance,
|
||||
peerJs,
|
||||
removeWindowQueryFlag,
|
||||
setWindowQueryFlag,
|
||||
shareURL
|
||||
} from '$lib/misc';
|
||||
import type { PlayerEvents } from '$lib/player/index';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
import { _ } from '$lib/i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { syncPartyConnectionsStore, syncPartyPeerStore } from '../store';
|
||||
import z from 'zod';
|
||||
import { Buffer } from 'buffer';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { addToast } from './Toast.svelte';
|
||||
|
||||
const SyncInfo = z.object({
|
||||
host: z.string(),
|
||||
path: z.string(),
|
||||
port: z.number(),
|
||||
syncId: z.string()
|
||||
});
|
||||
|
||||
let syncPartyManualInput: string = $state('');
|
||||
|
||||
const syncOrigin = Capacitor.isNativePlatform() ? 'https://sync.materialio.us' : location.origin;
|
||||
|
||||
function changeVideoEvent(conn: DataConnection) {
|
||||
conn.on('data', (data) => {
|
||||
const events = data as PlayerEvents;
|
||||
const currentUrl = get(page).url;
|
||||
|
||||
const blockedPages = ['subscriptions', 'playlists', 'history', 'subscriptions/manage'];
|
||||
|
||||
events.events.forEach((event) => {
|
||||
if (event.type === 'change-video' && event.videoId) {
|
||||
currentUrl.pathname = resolve(`/watch/[videoId]`, { videoId: event.videoId });
|
||||
// eslint-disable-next-line svelte/no-navigation-without-resolve
|
||||
goto(currentUrl);
|
||||
} else if (event.type === 'goto' && event.path && event.path !== $page.url.pathname) {
|
||||
if (blockedPages.includes(event.path.replace('/', ''))) {
|
||||
addToast({
|
||||
data: {
|
||||
text: $_('syncParty.userOnPrivatePage'),
|
||||
icon: 'shield_person'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
currentUrl.pathname = event.path;
|
||||
// eslint-disable-next-line svelte/no-navigation-without-resolve
|
||||
goto(currentUrl);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function createShareBase64(): string {
|
||||
return Buffer.from(
|
||||
JSON.stringify({
|
||||
...determinePeerJsInstance(),
|
||||
syncId: $syncPartyPeerStore?.id
|
||||
}),
|
||||
'utf-8'
|
||||
).toString('base64');
|
||||
}
|
||||
|
||||
async function startWatchSync() {
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.destroy();
|
||||
syncPartyPeerStore.set(null);
|
||||
removeWindowQueryFlag('sync');
|
||||
return;
|
||||
}
|
||||
|
||||
const peerId = crypto.randomUUID();
|
||||
setWindowQueryFlag('sync', peerId);
|
||||
$syncPartyPeerStore = await peerJs(peerId);
|
||||
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.on('connection', (conn) => {
|
||||
conn.on('open', async () => {
|
||||
addToast({
|
||||
data: {
|
||||
text: $_('syncParty.userJoined'),
|
||||
icon: 'person_add'
|
||||
}
|
||||
});
|
||||
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'goto',
|
||||
path: $page.url.pathname
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
|
||||
changeVideoEvent(conn);
|
||||
syncPartyConnectionsStore.set([...($syncPartyConnectionsStore || []), conn]);
|
||||
});
|
||||
|
||||
conn.on('close', async () => {
|
||||
addToast({
|
||||
data: {
|
||||
text: $_('syncParty.userLeft'),
|
||||
icon: 'person_remove'
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
page.subscribe((newPage) => {
|
||||
if (!newPage.url.pathname.startsWith(resolve('/watch', {})) && $syncPartyPeerStore) {
|
||||
$syncPartyConnectionsStore?.forEach((conn) => {
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'goto',
|
||||
path: newPage.url.pathname
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
async function joinSyncParty(syncInfo: string) {
|
||||
const parseInfo = SyncInfo.safeParse(
|
||||
JSON.parse(Buffer.from(syncInfo, 'base64').toString('utf-8'))
|
||||
);
|
||||
|
||||
if (parseInfo.error) {
|
||||
return;
|
||||
}
|
||||
|
||||
$syncPartyPeerStore = await peerJs(crypto.randomUUID(), parseInfo.data);
|
||||
$syncPartyPeerStore.on('open', () => {
|
||||
if (!$syncPartyPeerStore) return;
|
||||
|
||||
const conn = $syncPartyPeerStore.connect(parseInfo.data.syncId);
|
||||
syncPartyConnectionsStore.set([...($syncPartyConnectionsStore || []), conn]);
|
||||
|
||||
changeVideoEvent(conn);
|
||||
});
|
||||
|
||||
$syncPartyPeerStore.on('disconnected', () => {
|
||||
syncPartyPeerStore.set(null);
|
||||
syncPartyConnectionsStore.set(null);
|
||||
});
|
||||
}
|
||||
|
||||
onMount(async () => {
|
||||
const currentUrl = get(page).url;
|
||||
const syncInfo = currentUrl.searchParams.get('sync');
|
||||
if (syncInfo) {
|
||||
await joinSyncParty(syncInfo);
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
if ($syncPartyPeerStore) {
|
||||
$syncPartyPeerStore.destroy();
|
||||
$syncPartyPeerStore = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<dialog id="sync-party" class="padding">
|
||||
<h6 class="no-margin">{$_('layout.syncParty')}</h6>
|
||||
<p class="no-margin">{$_('layout.syncPartyWarning')}</p>
|
||||
{#if $syncPartyPeerStore}
|
||||
<nav>
|
||||
<div class="field label border max">
|
||||
<input
|
||||
name="sync-share"
|
||||
readonly
|
||||
value={`${syncOrigin}${resolve(`/?sync=${createShareBase64()}`, {})}`}
|
||||
type="text"
|
||||
/>
|
||||
<label class="active" for="sync-share">{$_('layout.shareURL')}</label>
|
||||
</div>
|
||||
<button
|
||||
onclick={async () => {
|
||||
await shareURL(`${syncOrigin}${resolve(`/?sync=${createShareBase64()}`, {})}`);
|
||||
}}
|
||||
class="round"
|
||||
>
|
||||
<i>content_copy</i>
|
||||
</button>
|
||||
</nav>
|
||||
{/if}
|
||||
<div class="space"></div>
|
||||
|
||||
<button
|
||||
class="no-margin"
|
||||
onclick={startWatchSync}
|
||||
data-ui={`${$syncPartyPeerStore ? '#sync-party' : ''}`}
|
||||
>{#if $syncPartyPeerStore}
|
||||
{$_('layout.endSyncParty')}
|
||||
{:else}
|
||||
{$_('layout.startSyncParty')}
|
||||
{/if}
|
||||
</button>
|
||||
|
||||
{#if !$syncPartyPeerStore}
|
||||
<div class="space"></div>
|
||||
<hr />
|
||||
<div class="space"></div>
|
||||
<h6 class="no-margin">{$_('layout.joinSyncParty')}</h6>
|
||||
<form
|
||||
onsubmit={async (event) => {
|
||||
event.preventDefault();
|
||||
|
||||
try {
|
||||
await joinSyncParty(new URL(syncPartyManualInput).searchParams.get('sync') || '');
|
||||
} catch {
|
||||
// Continue regardless of error
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div class="field label border max">
|
||||
<input bind:value={syncPartyManualInput} type="text" name="sync-input" />
|
||||
<label for="sync-input">{$_('layout.shareURL')}</label>
|
||||
</div>
|
||||
</form>
|
||||
{/if}
|
||||
</dialog>
|
||||
@@ -15,15 +15,12 @@
|
||||
VideoBase,
|
||||
VideoWatchHistory
|
||||
} from '$lib/api/model';
|
||||
import type { PlayerEvents } from '$lib/player';
|
||||
import {
|
||||
deArrowEnabledStore,
|
||||
isAndroidTvStore,
|
||||
playerSavePlaybackPositionStore,
|
||||
playerState,
|
||||
rawMasterKeyStore,
|
||||
syncPartyConnectionsStore,
|
||||
syncPartyPeerStore
|
||||
rawMasterKeyStore
|
||||
} from '$lib/store';
|
||||
import { relativeTimestamp } from '$lib/time';
|
||||
import { queueGetWatchHistory } from '$lib/api/backend/historyPool';
|
||||
@@ -49,12 +46,6 @@
|
||||
|
||||
let beenWatched: boolean = $state(false);
|
||||
|
||||
syncPartyPeerStore.subscribe((peer) => {
|
||||
if (peer) {
|
||||
watchUrl.searchParams.set('sync', peer.id);
|
||||
}
|
||||
});
|
||||
|
||||
let progress: string | undefined = $state();
|
||||
if (get(playerSavePlaybackPositionStore)) {
|
||||
try {
|
||||
@@ -136,22 +127,6 @@
|
||||
|
||||
function onVideoSelected() {
|
||||
playerState.set(undefined);
|
||||
|
||||
if ($syncPartyConnectionsStore) {
|
||||
const events = {
|
||||
events: [{ type: 'change-video', videoId: video.videoId }]
|
||||
} as PlayerEvents;
|
||||
|
||||
if (playlistId) {
|
||||
events.events.unshift({
|
||||
type: 'playlist',
|
||||
playlistId: playlistId
|
||||
});
|
||||
}
|
||||
$syncPartyConnectionsStore.forEach((conn) => {
|
||||
conn.send(events);
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { goto, pushState } from '$app/navigation';
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
import he from 'he';
|
||||
import type Peer from 'peerjs';
|
||||
import { get } from 'svelte/store';
|
||||
import { env } from '$env/dynamic/public';
|
||||
import {
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
feedCacheStore,
|
||||
invidiousInstanceStore,
|
||||
interfaceAndroidUseNativeShare,
|
||||
isAndroidTvStore,
|
||||
playlistCacheStore,
|
||||
rawMasterKeyStore,
|
||||
searchCacheStore
|
||||
@@ -26,7 +24,6 @@ import type {
|
||||
VideoBase,
|
||||
VideoWatchHistory
|
||||
} from './api/model';
|
||||
import { page } from '$app/state';
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import { Share } from '@capacitor/share';
|
||||
import { Clipboard } from '@capacitor/clipboard';
|
||||
@@ -68,44 +65,6 @@ export function unsafeRandomItem(array: any[]): any {
|
||||
return array[Math.floor(Math.random() * array.length)];
|
||||
}
|
||||
|
||||
export function setWindowQueryFlag(key: string, value: string) {
|
||||
page.url.searchParams.set(key, value);
|
||||
// eslint-disable-next-line svelte/no-navigation-without-resolve
|
||||
pushState(page.url, page.state);
|
||||
}
|
||||
|
||||
export function removeWindowQueryFlag(key: string) {
|
||||
page.url.searchParams.delete(key);
|
||||
// eslint-disable-next-line svelte/no-navigation-without-resolve
|
||||
pushState(page.url, page.state);
|
||||
}
|
||||
|
||||
let PeerInstance: typeof Peer;
|
||||
export interface PeerInstance {
|
||||
host: string;
|
||||
path: string;
|
||||
port: number;
|
||||
}
|
||||
|
||||
export function determinePeerJsInstance(): PeerInstance {
|
||||
return {
|
||||
host: getPublicEnv('DEFAULT_PEERJS_HOST') || '0.peerjs.com',
|
||||
path: getPublicEnv('DEFAULT_PEERJS_PATH') || '/',
|
||||
port: getPublicEnv('DEFAULT_PEERJS_PORT') ? Number(getPublicEnv('DEFAULT_PEERJS_PORT')) : 443
|
||||
};
|
||||
}
|
||||
|
||||
export async function peerJs(
|
||||
id: string,
|
||||
instance: PeerInstance = determinePeerJsInstance()
|
||||
): Promise<Peer> {
|
||||
// https://github.com/peers/peerjs/issues/819
|
||||
if (typeof PeerInstance === 'undefined') {
|
||||
PeerInstance = (await import('peerjs')).Peer;
|
||||
}
|
||||
return new PeerInstance(id, instance);
|
||||
}
|
||||
|
||||
export async function shareURL(url: string) {
|
||||
if (
|
||||
Capacitor.getPlatform() === 'android' &&
|
||||
|
||||
@@ -7,8 +7,7 @@ import {
|
||||
playerDefaultQualityStore,
|
||||
playerPlaylistHistory,
|
||||
playerState,
|
||||
playlistSettingsStore,
|
||||
syncPartyConnectionsStore
|
||||
playlistSettingsStore
|
||||
} from '$lib/store';
|
||||
import { goto } from '$app/navigation';
|
||||
import { resolve } from '$app/paths';
|
||||
@@ -17,18 +16,6 @@ import { unsafeRandomItem } from '$lib/misc';
|
||||
import type shaka from 'shaka-player/dist/shaka-player.ui';
|
||||
import ISO6391 from 'iso-639-1';
|
||||
|
||||
export interface PlayerEvent {
|
||||
type: 'pause' | 'seek' | 'change-video' | 'play' | 'playlist' | 'goto';
|
||||
path?: string;
|
||||
time?: number;
|
||||
videoId?: string;
|
||||
playlistId?: string;
|
||||
}
|
||||
|
||||
export interface PlayerEvents {
|
||||
events: PlayerEvent[];
|
||||
}
|
||||
|
||||
export const playbackRates = [
|
||||
0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3, 3.25, 3.5, 3.75, 4
|
||||
];
|
||||
@@ -92,17 +79,6 @@ export async function goToNextVideo(video: VideoPlay, playlistId: string | null)
|
||||
}
|
||||
|
||||
if (typeof goToVideo !== 'undefined') {
|
||||
get(syncPartyConnectionsStore)?.forEach((conn) => {
|
||||
if (typeof goToVideo === 'undefined') return;
|
||||
|
||||
conn.send({
|
||||
events: [
|
||||
{ type: 'change-video', videoId: goToVideo.videoId },
|
||||
{ type: 'playlist', playlistId: playlistId }
|
||||
]
|
||||
} as PlayerEvents);
|
||||
});
|
||||
|
||||
goto(
|
||||
resolve(`/${isAndroidTv ? 'tv' : 'watch'}/[videoId]?playlist=${playlistId}`, {
|
||||
videoId: goToVideo.videoId
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { Capacitor } from '@capacitor/core';
|
||||
import type Peer from 'peerjs';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import { writable, type Writable } from 'svelte/store';
|
||||
import { Preferences } from '@capacitor/preferences';
|
||||
import {
|
||||
@@ -330,9 +328,6 @@ export const watchHistoryEnabledStore: Writable<boolean> = persist(
|
||||
'watchHistoryEnabled'
|
||||
);
|
||||
|
||||
export const syncPartyPeerStore: Writable<Peer | null> = writable(null);
|
||||
export const syncPartyConnectionsStore: Writable<DataConnection[] | null> = writable();
|
||||
|
||||
export const playlistSettingsStore: Writable<Record<string, { shuffle: boolean; loop: boolean }>> =
|
||||
writable({});
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
import PageLoading from '$lib/components/PageLoading.svelte';
|
||||
import Search from '$lib/components/Search.svelte';
|
||||
import Settings from '$lib/components/settings/Settings.svelte';
|
||||
import SyncParty from '$lib/components/SyncParty.svelte';
|
||||
import Thumbnail from '$lib/components/thumbnail/VideoThumbnail.svelte';
|
||||
import Player from '$lib/components/player/Player.svelte';
|
||||
import { getPages } from '$lib/navPages';
|
||||
@@ -21,7 +20,6 @@
|
||||
playerState,
|
||||
playertheatreModeIsActive,
|
||||
rawMasterKeyStore,
|
||||
syncPartyPeerStore,
|
||||
themeColorStore,
|
||||
backendInUseStore,
|
||||
hideSearchStore
|
||||
@@ -252,10 +250,6 @@
|
||||
<Search on:searchCancelled={() => (mobileSearchShow = false)} />
|
||||
</div>
|
||||
{:else}
|
||||
<button data-ui="#sync-party" class="circle large transparent">
|
||||
<i class:primary-text={$syncPartyPeerStore}>group</i>
|
||||
<div class="tooltip bottom">{$_('layout.syncParty')}</div>
|
||||
</button>
|
||||
{#if $invidiousAuthStore && !isYTBackend()}
|
||||
<button
|
||||
class="circle large transparent"
|
||||
@@ -386,7 +380,6 @@
|
||||
{@render children?.()}
|
||||
{/if}
|
||||
|
||||
<SyncParty />
|
||||
<Toast />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -22,15 +22,12 @@
|
||||
playerTheatreModeByDefaultStore,
|
||||
playertheatreModeIsActive,
|
||||
playlistCacheStore,
|
||||
syncPartyConnectionsStore,
|
||||
type PlayerState
|
||||
} from '$lib/store';
|
||||
import ui from 'beercss';
|
||||
import type { DataConnection } from 'peerjs';
|
||||
import { onDestroy, onMount, tick } from 'svelte';
|
||||
import { _ } from '$lib/i18n';
|
||||
import { get } from 'svelte/store';
|
||||
import { loadEntirePlaylist } from '$lib/playlist';
|
||||
import Author from '$lib/components/Author.svelte';
|
||||
import Description from '$lib/components/watch/Description.svelte';
|
||||
import LikesDislikes from '$lib/components/watch/LikesDislikes.svelte';
|
||||
@@ -41,7 +38,6 @@
|
||||
import { page } from '$app/state';
|
||||
import Share from '$lib/components/Share.svelte';
|
||||
import Playlist from '$lib/components/watch/Playlist.svelte';
|
||||
import type { PlayerEvents } from '$lib/player/index';
|
||||
|
||||
let { data = $bindable() } = $props();
|
||||
|
||||
@@ -81,137 +77,6 @@
|
||||
}
|
||||
});
|
||||
|
||||
function playerSyncEvents(conn: DataConnection) {
|
||||
if (playerElement) {
|
||||
conn.send({
|
||||
events: [{ type: 'seek', time: playerElement.currentTime }]
|
||||
} as PlayerEvents);
|
||||
}
|
||||
|
||||
if (data.playlistId) {
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'playlist',
|
||||
playlistId: data.playlistId
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
}
|
||||
|
||||
conn.on('data', (rawData) => {
|
||||
const events = rawData as PlayerEvents;
|
||||
|
||||
events.events.forEach(async (event) => {
|
||||
if (!playerElement) return;
|
||||
|
||||
if (event.type === 'pause') {
|
||||
playerElement.pause();
|
||||
} else if (event.type === 'play') {
|
||||
playerElement.play();
|
||||
} else if (event.type === 'seek' && event.time) {
|
||||
const timeDiff = playerElement.currentTime - event.time;
|
||||
|
||||
if (timeDiff > 5 || timeDiff < -5) {
|
||||
playerElement.currentTime = event.time;
|
||||
}
|
||||
} else if (
|
||||
event.type === 'playlist' &&
|
||||
event.playlistId &&
|
||||
event.playlistId !== data.playlistId
|
||||
) {
|
||||
data.playlistId = event.playlistId;
|
||||
await loadEntirePlaylist(event.playlistId);
|
||||
goToCurrentPlaylistItem();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (!playerElement) return;
|
||||
|
||||
playerElement.addEventListener('error', () => {
|
||||
if (!playerElement) return;
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'seek',
|
||||
time: playerElement.currentTime
|
||||
},
|
||||
{
|
||||
type: 'play'
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
});
|
||||
|
||||
playerElement.addEventListener('pause', () => {
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'pause'
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
});
|
||||
|
||||
playerElement.addEventListener('playing', () => {
|
||||
if (!playerElement) return;
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'seek',
|
||||
time: playerElement.currentTime
|
||||
},
|
||||
{
|
||||
type: 'play'
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
});
|
||||
|
||||
playerElement.addEventListener('play', () => {
|
||||
if (!playerElement) return;
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'seek',
|
||||
time: playerElement.currentTime
|
||||
},
|
||||
{
|
||||
type: 'play'
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
});
|
||||
|
||||
playerElement.addEventListener('waiting', () => {
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'pause'
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
});
|
||||
|
||||
playerElement.addEventListener('seeked', () => {
|
||||
if (!playerElement) return;
|
||||
conn.send({
|
||||
events: [
|
||||
{
|
||||
type: 'seek',
|
||||
time: playerElement.currentTime
|
||||
}
|
||||
]
|
||||
} as PlayerEvents);
|
||||
});
|
||||
}
|
||||
|
||||
syncPartyConnectionsStore.subscribe((connections) => {
|
||||
if (!connections || !playerElement) return;
|
||||
playerSyncEvents(connections[connections.length - 1]);
|
||||
});
|
||||
|
||||
async function load(state: PlayerState) {
|
||||
playerElement = state.playerElement;
|
||||
|
||||
@@ -224,12 +89,6 @@
|
||||
expandSummery('chapter-section');
|
||||
}
|
||||
|
||||
if ($syncPartyConnectionsStore) {
|
||||
$syncPartyConnectionsStore.forEach((conn) => {
|
||||
playerSyncEvents(conn);
|
||||
});
|
||||
}
|
||||
|
||||
if (playerElement) {
|
||||
playerElement.addEventListener('timeupdate', () => {
|
||||
if (!playerElement) return;
|
||||
|
||||
Reference in New Issue
Block a user