aboutsummaryrefslogtreecommitdiff
path: root/web
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-01-21 03:05:08 +0100
committermetamuffin <metamuffin@disroot.org>2024-01-21 03:05:08 +0100
commit62ef60aca4e39f02cb8de972def0cc366e220542 (patch)
tree2091c42390d703983fc18717c2814ddeec032ea4 /web
parent5177497b3cf376403a8daab2e5ca9408ad2625bd (diff)
downloadjellything-62ef60aca4e39f02cb8de972def0cc366e220542.tar
jellything-62ef60aca4e39f02cb8de972def0cc366e220542.tar.bz2
jellything-62ef60aca4e39f02cb8de972def0cc366e220542.tar.zst
added bad federated lsvp
Diffstat (limited to 'web')
-rw-r--r--web/script/player/mod.ts33
-rw-r--r--web/script/player/sync.ts92
-rw-r--r--web/style/js-player.css38
3 files changed, 157 insertions, 6 deletions
diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts
index fdb4e4a..6aca36c 100644
--- a/web/script/player/mod.ts
+++ b/web/script/player/mod.ts
@@ -11,6 +11,7 @@ import { EncodingProfile } from "./jhls.d.ts";
import { TrackKind, get_track_kind } from "./mediacaps.ts";
import { Player } from "./player.ts";
import { Popup } from "./popup.ts";
+import { Playersync } from "./sync.ts"
globalThis.addEventListener("DOMContentLoaded", () => {
if (document.body.classList.contains("player")) {
@@ -35,10 +36,12 @@ function initialize_player(el: HTMLElement, node_id: string) {
const logger = new Logger<string>(s => e("p", s))
const player = new Player(node_id, logger)
const show_stats = new OVar(false);
+ const sync_state = new OVar<Playersync | undefined>(undefined)
const toggle_playing = () => player.playing.value ? player.pause() : player.play()
const pri_map = (v: number) => (v / player.duration.value * 100) + "%"
+
let pri_current: HTMLElement;
let pri: HTMLElement;
@@ -49,12 +52,14 @@ function initialize_player(el: HTMLElement, node_id: string) {
const button = e("button", MEDIA_KIND_ICONS[kind][+enabled], {
class: "icon",
onclick: () => {
+ // sync_state.value = new Playersync(player, logger, "test")
enabled = !enabled
button.textContent = MEDIA_KIND_ICONS[kind][+enabled]
}
})
new Popup(button, popups, () =>
e("div", { class: "jsp-track-select-popup" },
+ e("h3", `${kind[0].toUpperCase()}${kind.substring(1)}`),
...(player.tracks ?? [])
.map((track, index) => ({ index, track }))
.filter(({ track }) => get_track_kind(track.kind) == kind)
@@ -74,6 +79,33 @@ function initialize_player(el: HTMLElement, node_id: string) {
)
return button
}
+ const settings_popup = () => {
+ const button = e("button", "settings", { class: "icon" })
+ let channelname: HTMLInputElement;
+ new Popup(button, popups, () => e("div", { class: "jsp-settings-popup" },
+ e("h2", "Settings"),
+ e("div", { class: "jsp-playersync-controls" },
+ e("h3", "Playersync"),
+ sync_state.map(sync => sync
+ ? e("div",
+ e("span", "Sync enabled."),
+ e("button", "Disable", {
+ onclick: () => { sync_state.value?.destroy(); sync_state.value = undefined }
+ }))
+ : e("div",
+ channelname = e("input", { type: "text" }),
+ e("button", "Sync!", {
+ onclick: () => {
+ if (!channelname.value.length) return
+ sync_state.value?.destroy()
+ sync_state.value = new Playersync(player, logger, channelname.value)
+ }
+ }))
+ )
+ )
+ ))
+ return button;
+ }
const controls = e("div", { class: "jsp-controls" },
player.playing.map(playing =>
@@ -105,6 +137,7 @@ function initialize_player(el: HTMLElement, node_id: string) {
track_select("audio"),
track_select("subtitles")
),
+ settings_popup(),
e("button", "fullscreen", {
class: "icon",
onclick() {
diff --git a/web/script/player/sync.ts b/web/script/player/sync.ts
new file mode 100644
index 0000000..a2029ea
--- /dev/null
+++ b/web/script/player/sync.ts
@@ -0,0 +1,92 @@
+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)[] = []
+
+ 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...")
+
+ let [localpart, remotepart] = channel_name.split(":")
+ if (!remotepart?.length) remotepart = window.location.host
+
+ 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.join} left.`)
+ }
+
+ let cb: () => void
+
+ player.video.addEventListener("play", cb = () => {
+ this.send({ playing: true })
+ })
+ this.on_destroy.push(() => player.video.removeEventListener("play", cb))
+
+ player.video.addEventListener("pause", cb = () => {
+ this.send({ playing: false })
+ })
+ this.on_destroy.push(() => player.video.removeEventListener("pause", cb))
+
+ player.video.addEventListener("seeking", cb = () => {
+ const time = this.player.video.currentTime
+ if (Math.abs(last_time - time) < 0.01) return
+ this.send({ time: this.player.video.currentTime })
+ })
+ this.on_destroy.push(() => player.video.removeEventListener("seeking", cb))
+ }
+
+ destroy() {
+ this.set_pers()
+ this.logger.log("Playersync disabled.")
+ this.on_destroy.forEach(f => f())
+ this.send({ leave: get_username() })
+ }
+
+ send(p: Packet) {
+ console.log("playersync send", p);
+ this.ws.send(JSON.stringify(p))
+ }
+}
+
diff --git a/web/style/js-player.css b/web/style/js-player.css
index 40455ed..8f047f1 100644
--- a/web/style/js-player.css
+++ b/web/style/js-player.css
@@ -52,10 +52,6 @@
.jsp-track-state:hover {
background-color: rgba(113, 113, 113, 0.333);
}
-.jsp-track-select-popup {
- background-color: #303a;
- padding: 1em;
-}
.jsp-pri {
position: relative;
@@ -149,14 +145,16 @@
bottom: var(--csize);
right: 0px;
animation-name: popup-in;
+ animation-delay: 180ms;
animation-duration: 100ms;
- animation-fill-mode: forwards;
+ animation-fill-mode: both;
animation-timing-function: ease-out;
}
.jsp-popup-out {
animation-name: popup-out;
+ animation-delay: 0ms;
animation-duration: 100ms;
- animation-fill-mode: backwards;
+ animation-fill-mode: both;
animation-timing-function: ease-in;
}
@keyframes popup-in {
@@ -179,3 +177,31 @@
opacity: 0;
}
}
+
+.jsp-settings-popup {
+ padding: 1em;
+ min-width: 14em;
+ background-color: rgba(45, 24, 104, 0.548);
+}
+.jsp-track-select-popup {
+ min-width: 14em;
+ background-color: #303a;
+ padding: 1em;
+}
+.jsp-settings-popup h2,
+.jsp-settings-popup h3 {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+.jsp-playersync-controls button {
+ background-color: black;
+ border: 2px solid var(--accent-light);
+ font-size: medium;
+ padding: 0.3em;
+ border-radius: 7px;
+}
+.jsp-playersync-controls {
+ padding: 1em;
+ background-color: #0005;
+}