aboutsummaryrefslogtreecommitdiff
path: root/web/script/player/mod.ts
diff options
context:
space:
mode:
Diffstat (limited to 'web/script/player/mod.ts')
-rw-r--r--web/script/player/mod.ts115
1 files changed, 94 insertions, 21 deletions
diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts
index fa83a2b..3774c62 100644
--- a/web/script/player/mod.ts
+++ b/web/script/player/mod.ts
@@ -31,6 +31,12 @@ const MEDIA_KIND_ICONS: { [key in TrackKind]: [string, string] } = {
subtitles: ["subtitles_off", "subtitles"],
}
+function toggle_fullscreen() {
+ if (document.fullscreenElement) document.exitFullscreen()
+ else document.documentElement.requestFullscreen()
+}
+
+
function initialize_player(el: HTMLElement, node_id: string) {
el.innerHTML = "" // clear the body
@@ -40,15 +46,41 @@ function initialize_player(el: HTMLElement, node_id: string) {
const idle_inhibit = new OVar(false)
const sync_state = new OVar<Playersync | undefined>(undefined)
+ let mute_saved_volume = 1;
+ const toggle_mute = () => {
+ if (player.volume.value == 0) {
+ logger.log("Unmuted.");
+ player.volume.value = mute_saved_volume
+ }
+ else {
+ logger.log("Muted.");
+ mute_saved_volume = player.volume.value
+ player.volume.value = 0.
+ }
+ }
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;
const popups = e("div")
+ const step_track_kind = (kind: TrackKind) => {
+ // TODO cycle through all of them
+ const active = player.active_tracks.value.filter(
+ ts => get_track_kind(player.tracks![ts.track_index].kind) == kind)
+ if (active.length > 0) {
+ for (const t of active) player.set_track_enabled(t.track_index, false)
+ } else {
+ const all_kind = (player.tracks ?? [])
+ .map((track, index) => ({ index, track }))
+ .filter(({ track }) => get_track_kind(track.kind) == kind)
+ if (all_kind.length < 1) return logger.log(`No ${kind} tracks available`)
+ player.set_track_enabled(all_kind[0].index, true)
+ }
+ }
+
const track_select = (kind: TrackKind) => {
const button = e("div", player.active_tracks.map(_ => {
const active = player.active_tracks.value.filter(
@@ -68,7 +100,6 @@ function initialize_player(el: HTMLElement, node_id: string) {
if (all_kind.length < 1) return
player.set_track_enabled(all_kind[0].index, true)
}
-
}
})
}))
@@ -81,7 +112,13 @@ function initialize_player(el: HTMLElement, node_id: string) {
slider.valueAsNumber = player.video.volume
slider.onchange = () => player.video.volume = slider.valueAsNumber
slider.onmousemove = () => player.video.volume = slider.valueAsNumber
- return [e("div", { class: "jsp-controlgroup" }, e("label", "Volume", slider))]
+ return [e("div", { class: ["jsp-controlgroup", "jsp-volumecontrol"] },
+ e("label", `Volume`),
+ e("span", { class: "jsp-volume" }, player.volume.map(v =>
+ `${(v * 100).toFixed(2)}% | ${v == 0 ? "-∞" : (Math.log2(v) * 10).toFixed(2)}dB` as string
+ )),
+ slider
+ )]
}
new Popup(button, popups, () =>
@@ -90,10 +127,12 @@ function initialize_player(el: HTMLElement, node_id: string) {
...(kind == "audio" ? volume() : []),
- player.active_tracks.map(_ =>
- e("ul", { class: "jsp-track-list" }, ...(player.tracks ?? [])
+ player.active_tracks.map(_ => {
+ const tracks_avail = (player.tracks ?? [])
.map((track, index) => ({ index, track }))
- .filter(({ track }) => get_track_kind(track.kind) == kind)
+ .filter(({ track }) => get_track_kind(track.kind) == kind);
+ if (!tracks_avail.length) return e("p", `No ${kind} tracks available.`) as HTMLElement;
+ return e("ul", { class: "jsp-track-list" }, ...tracks_avail
.map(({ track, index }): HTMLElement => {
const active = player.active_tracks.value.find(ts => ts.track_index == index) !== undefined
const onclick = () => {
@@ -106,7 +145,8 @@ function initialize_player(el: HTMLElement, node_id: string) {
e("span", { class: "jsp-track-lang" }, `(${track.language})`)
)
})
- ))
+ )
+ })
)
)
return button
@@ -119,7 +159,7 @@ function initialize_player(el: HTMLElement, node_id: string) {
playersync_controls(sync_state, player),
e("button", "Launch Native Player", {
onclick: () => {
- window.location.href = `jellynative://player-fullscreen/${window.location.protocol}//${window.location.host}/n/${encodeURIComponent(node_id)}/stream?format=hlsmaster`
+ window.location.href = `?kind=nativefullscreen`
}
})
))
@@ -136,6 +176,15 @@ function initialize_player(el: HTMLElement, node_id: string) {
),
pri = e("div", { class: "jsp-pri" },
pri_current = e("div", { class: "jsp-pri-current" }),
+ player.chapters.map(
+ chapters => e("div", ...chapters.map(chap => e("div", {
+ class: "jsp-chapter",
+ style: {
+ left: pri_map(chap.time_start ?? 0),
+ width: pri_map((chap.time_end ?? player.duration.value) - (chap.time_start ?? 0))
+ }
+ }, e("p", chap.labels[0][1]))))
+ ),
player.active_tracks.map(
tracks => e("div", ...tracks.map((t, i) => t.buffered.map(
ranges => e("div", ...ranges.map(
@@ -158,13 +207,7 @@ function initialize_player(el: HTMLElement, node_id: string) {
track_select("subtitles")
),
settings_popup(),
- e("button", "fullscreen", {
- class: "icon",
- onclick() {
- if (document.fullscreenElement) document.exitFullscreen()
- else document.documentElement.requestFullscreen()
- }
- })
+ e("button", "fullscreen", { class: "icon", onclick: toggle_fullscreen })
)
player.position.onchangeinit(p => pri_current.style.width = pri_map(p))
@@ -200,10 +243,21 @@ function initialize_player(el: HTMLElement, node_id: string) {
const p = (ev.clientX - r.left) / (r.right - r.left)
player.seek(p * player.duration.value)
})
+
+
+
document.body.addEventListener("keydown", k => {
- if (k.ctrlKey) return
+ if (k.ctrlKey || k.altKey || k.metaKey) return
if (k.code == "Period") player.pause(), player.frame_forward()
- if (k.code == "Space") toggle_playing()
+ else if (k.code == "Space") toggle_playing()
+ else if (k.code == "KeyP") toggle_playing()
+ else if (k.code == "KeyF") toggle_fullscreen()
+ else if (k.code == "KeyQ") window.history.back()
+ else if (k.code == "KeyS") screenshot_video(player.video)
+ else if (k.code == "KeyJ") step_track_kind("subtitles")
+ else if (k.code == "KeyM") toggle_mute()
+ else if (k.key == "#") step_track_kind("audio")
+ else if (k.key == "_") step_track_kind("video")
else if (k.code == "KeyV") show_stats.value = !show_stats.value
else if (k.code == "ArrowLeft") player.seek(player.position.value - 5)
else if (k.code == "ArrowRight") player.seek(player.position.value + 5)
@@ -215,6 +269,25 @@ function initialize_player(el: HTMLElement, node_id: string) {
send_player_progress(node_id, player)
}
+function screenshot_video(video: HTMLVideoElement) {
+ // TODO bug: video needs to be played to take a screenshot. if you have just seeked somewhere it wont work.
+ const canvas = document.createElement("canvas")
+ canvas.width = video.videoWidth
+ canvas.height = video.videoHeight
+ const context = canvas.getContext("2d")!
+ context.fillStyle = "#ff00ff"
+ context.fillRect(0, 0, video.videoWidth, video.videoHeight)
+ context.drawImage(video, 0, 0)
+ canvas.toBlob(blob => {
+ if (!blob) throw new Error("failed to create blob");
+ const a = document.createElement("a");
+ a.download = "screenshot.webp";
+ a.href = window.URL.createObjectURL(blob)
+ a.click()
+ setTimeout(() => URL.revokeObjectURL(a.href), 0)
+ }, "image/webp", 0.95)
+}
+
let sent_watched = false;
function send_player_progress(node_id: string, player: Player) {
let t = 0;
@@ -237,9 +310,9 @@ function send_player_progress(node_id: string, player: Player) {
function mouse_idle(e: HTMLElement, timeout: number): OVar<boolean> {
let ct: number;
const idle = new OVar(false)
- // e.onmouseleave = () => {
- // clearTimeout(ct)
- // }
+ e.onmouseleave = () => {
+ clearTimeout(ct)
+ }
e.onmousemove = () => {
clearTimeout(ct)
if (idle) {
@@ -257,4 +330,4 @@ function show_profile(profile: EncodingProfile): string {
if (profile.video) return `codec=${profile.video.codec} br=${show.metric(profile.video.bitrate, "b/s")} w=${profile.video.width} preset=${profile.video.preset}`
if (profile.subtitles) return `codec=${profile.subtitles.codec}`
return `???`
-}
+} \ No newline at end of file