1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
|
/*
This file is part of jellything (https://codeberg.org/metamuffin/jellything)
which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
Copyright (C) 2024 metamuffin <metamuffin.org>
*/
/// <reference lib="dom" />
import { Logger } from "../jshelper/src/log.ts";
import { Player } from "./player.ts"
function get_username() {
return document.querySelector("nav .account .username")?.textContent ?? "Unknown User"
}
interface Packet {
time?: number,
playing?: boolean,
join?: string,
leave?: string,
}
export class Playersync {
private ws: WebSocket
private on_destroy: (() => void)[] = []
public name: string
private cancel_pers: undefined | (() => void)
set_pers(s?: string) {
if (this.cancel_pers) this.cancel_pers(), this.cancel_pers = undefined
if (s) this.cancel_pers = this.logger?.log_persistent(s)
}
constructor(private player: Player, private logger: Logger<string>, private channel_name?: string) {
this.set_pers("Playersync enabling...")
channel_name ??= Math.random().toString(16).padEnd(5, "0").substring(2).substring(0, 6)
let [localpart, remotepart, port] = channel_name.split(":")
if (!remotepart?.length) remotepart = window.location.host
if (port) remotepart += ":" + port
this.name = localpart + ":" + remotepart
this.ws = new WebSocket(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${remotepart}/playersync/${encodeURIComponent(localpart)}`)
this.on_destroy.push(() => this.ws.close())
this.ws.onopen = () => {
this.set_pers()
this.logger.log(`Playersync connected.`)
this.send({ join: get_username() })
}
this.ws.onerror = () => {
this.set_pers(`Playersync websocket error.`)
}
this.ws.onclose = () => {
this.set_pers(`Playersync websocket closed.`)
}
let last_time = 0;
this.ws.onmessage = ev => {
const packet: Packet = JSON.parse(ev.data)
console.log("playersync recv", packet);
if (packet.time !== undefined) {
this.player.seek(packet.time)
last_time = packet.time
}
if (packet.playing === true) this.player.play()
if (packet.playing === false) this.player.pause()
if (packet.join) this.logger.log(`${packet.join} joined.`)
if (packet.leave) this.logger.log(`${packet.leave} left.`)
}
let cb: () => void
const send_time = () => {
const time = this.player.video.currentTime
if (Math.abs(last_time - time) < 0.01) return
this.send({ time: this.player.video.currentTime })
}
player.video.addEventListener("play", cb = () => {
send_time()
this.send({ playing: true })
})
this.on_destroy.push(() => player.video.removeEventListener("play", cb))
player.video.addEventListener("pause", cb = () => {
this.send({ playing: false })
send_time()
})
this.on_destroy.push(() => player.video.removeEventListener("pause", cb))
player.video.addEventListener("seeking", cb = () => {
send_time()
})
this.on_destroy.push(() => player.video.removeEventListener("seeking", cb))
}
destroy() {
this.set_pers()
this.logger.log("Playersync disabled.")
this.on_destroy.forEach(f => f())
}
send(p: Packet) {
console.log("playersync send", p);
this.ws.send(JSON.stringify(p))
}
}
|