diff options
Diffstat (limited to 'web')
| -rw-r--r-- | web/script/player/mediacaps.ts | 3 | ||||
| -rw-r--r-- | web/script/player/mod.ts | 40 | ||||
| -rw-r--r-- | web/script/player/player.ts | 2 | ||||
| -rw-r--r-- | web/script/player/popup.ts | 58 | ||||
| -rw-r--r-- | web/script/player/track.ts | 6 | ||||
| -rw-r--r-- | web/style/js-player.css | 14 | ||||
| -rw-r--r-- | web/style/layout.css | 11 | 
7 files changed, 122 insertions, 12 deletions
| diff --git a/web/script/player/mediacaps.ts b/web/script/player/mediacaps.ts index 7b57302..33682be 100644 --- a/web/script/player/mediacaps.ts +++ b/web/script/player/mediacaps.ts @@ -96,7 +96,8 @@ const FFMPEG_ENCODER_CODEC_MAP: { [key: string]: string } = {      "libopus": "A_OPUS",  } -export function get_track_kind(track: SourceTrackKind): "audio" | "video" | "subtitles" { +export type TrackKind = "audio" | "video" | "subtitles" +export function get_track_kind(track: SourceTrackKind): TrackKind {      if (track.audio) return "audio"      if (track.video) return "video"      if (track.subtitles) return "subtitles" diff --git a/web/script/player/mod.ts b/web/script/player/mod.ts index ce3c113..11690b6 100644 --- a/web/script/player/mod.ts +++ b/web/script/player/mod.ts @@ -7,7 +7,9 @@ import { OVar, show } from "../jshelper/mod.ts";  import { e } from "../jshelper/mod.ts";  import { Logger } from "../jshelper/src/log.ts";  import { EncodingProfile } from "./jhls.d.ts"; +import { TrackKind } from "./mediacaps.ts";  import { Player } from "./player.ts"; +import { Popup } from "./popup.ts";  globalThis.addEventListener("DOMContentLoaded", () => {      if (document.body.classList.contains("player")) { @@ -20,6 +22,12 @@ globalThis.addEventListener("DOMContentLoaded", () => {      }  }) +const MEDIA_KIND_ICONS: { [key in TrackKind]: [string, string] } = { +    video: ["tv_off", "tv"], +    audio: ["volume_off", "volume_up"], +    subtitles: ["subtitles_off", "subtitles"], +} +  function initialize_player(el: HTMLElement, node_id: string) {      el.innerHTML = "" // clear the body @@ -32,8 +40,28 @@ function initialize_player(el: HTMLElement, node_id: string) {      let pri_current: HTMLElement;      let pri: HTMLElement; + +    const popups = e("div") + +    const track_select = (kind: TrackKind) => { +        let enabled = true +        const button = e("button", MEDIA_KIND_ICONS[kind][+enabled], { +            class: "iicon", +            onclick: () => { +                enabled = !enabled +                button.textContent = MEDIA_KIND_ICONS[kind][+enabled] +            } +        }) +        new Popup(button, popups, () => +            e("div", { class: "jsp-track-select-popup" }, "This is some text") +        ) +        return button +    } +      const controls = e("div", { class: "jsp-controls" }, -        player.playing.map(playing => e("button", playing ? "||" : "|>", { onclick: toggle_playing })), +        player.playing.map(playing => +            e("button", { class: "iicon" }, playing ? "pause" : "play_arrow", { onclick: toggle_playing }) +        ),          e("p", { class: "jsp-status" },              player.position.map(v => e("span", show.duration(v))), e("br"),              player.position.map(v => e("span", show.duration(v - player.duration.value))) @@ -55,7 +83,13 @@ function initialize_player(el: HTMLElement, node_id: string) {                  )))              )          ), -        e("button", "X", { +        e("div", { class: "jsp-track-select" }, +            track_select("video"), +            track_select("audio"), +            track_select("subtitles") +        ), +        e("button", "fullscreen", { +            class: "iicon",              onclick() {                  if (document.fullscreenElement) document.exitFullscreen()                  else document.documentElement.requestFullscreen() @@ -76,6 +110,7 @@ function initialize_player(el: HTMLElement, node_id: string) {              )          ))),          logger.element, +        popups,          controls,      )      el.append(pel) @@ -102,7 +137,6 @@ function initialize_player(el: HTMLElement, node_id: string) {          else return;          k.preventDefault()      }) -  }  function mouse_idle(e: HTMLElement, timeout: number, cb: (b: boolean) => unknown) { diff --git a/web/script/player/player.ts b/web/script/player/player.ts index cc778f0..81a315e 100644 --- a/web/script/player/player.ts +++ b/web/script/player/player.ts @@ -83,7 +83,7 @@ export class Player {          this.profile_selector = new ProfileSelector(this, this.downloader.bandwidth, metadata)          this.set_pers("Checking codec support...")          await this.profile_selector.init() -         +          this.duration.value = metadata.duration          this.video.src = URL.createObjectURL(this.media_source)          this.media_source.addEventListener("sourceopen", async () => { diff --git a/web/script/player/popup.ts b/web/script/player/popup.ts new file mode 100644 index 0000000..fb596b0 --- /dev/null +++ b/web/script/player/popup.ts @@ -0,0 +1,58 @@ + +export class Popup { +    trigger_hov = false +    content_hov = false +    content?: HTMLElement +    shown = false + +    constructor( +        public trigger: HTMLElement, +        public container: HTMLElement, +        public new_content: () => HTMLElement +    ) { +        trigger.onmouseenter = () => { +            this.trigger_hov = true; +            this.update_hov() +        } +        trigger.onmouseleave = () => { +            this.trigger_hov = false; +            this.update_hov() +        } +    } + +    set_shown(s: boolean) { +        if (this.shown == s) return +        if (s) { +            this.content = this.new_content() +            this.content.addEventListener("mouseenter", () => { +                this.content_hov = true; +                this.update_hov() +            }) +            this.content.addEventListener("mouseleave", () => { +                this.content_hov = false; +                this.update_hov() +            }) +            this.container.append(this.content) +        } else { +            this.container.removeChild(this.content!) +            this.content = undefined +        } +        this.shown = s +    } + +    hide_timeout?: number +    update_hov() { +        if (this.content_hov || this.trigger_hov) { +            this.set_shown(true) +            if (this.hide_timeout !== undefined) { +                clearTimeout(this.hide_timeout) +                this.hide_timeout = undefined +            } +        } else { +            if (this.hide_timeout === undefined) this.hide_timeout = setTimeout(() => { +                this.set_shown(false) +                this.hide_timeout = undefined +            }, 100) +        } +    } +} diff --git a/web/script/player/track.ts b/web/script/player/track.ts index cc21763..c7d90da 100644 --- a/web/script/player/track.ts +++ b/web/script/player/track.ts @@ -32,7 +32,7 @@ export class PlayerTrack {      async init() {          await this.player.profile_selector.select_optimal_profile(this.track_index, this.profile);          const ct = track_to_content_type(this.track_from_profile())! -        console.log("source buffer content-type: " + ct); +        console.log(`track ${this.track_index} source buffer content-type: ${ct}`);          this.source_buffer = this.player.media_source.addSourceBuffer(ct);          this.source_buffer.mode = "segments";          this.source_buffer.addEventListener("updateend", () => { @@ -51,6 +51,10 @@ export class PlayerTrack {              console.error("sourcebuffer abort", e);          });      } +    destroy() { +        console.log(`destroy source buffer for track ${this.track_index}`); +        this.player.media_source.removeSourceBuffer(this.source_buffer) +    }      track_from_profile(): SourceTrack {          if (this.profile.value) return profile_to_partial_track(this.profile.value) diff --git a/web/style/js-player.css b/web/style/js-player.css index 6d1bd71..ba8f4c4 100644 --- a/web/style/js-player.css +++ b/web/style/js-player.css @@ -28,6 +28,9 @@      border: none;      background-color: #ffffff10;  } +.jsp .jsp-controls .jsp-track-select button { +    background-color: #ffaaff10; +}  .jsp-controls p.jsp-status {      display: inline-block; @@ -37,6 +40,17 @@      font-family: monospace;      margin: 0.4em;  } +.jsp-track-select { +    margin-right: 1em; +    display: inherit; +} +.jsp-track-select-popup { +    position: absolute; +    background-color: #303a; +    bottom: var(--csize); +    right: 0px; +    padding: 1em; +}  .jsp-pri {      position: relative; diff --git a/web/style/layout.css b/web/style/layout.css index 9f2d51c..ef151c4 100644 --- a/web/style/layout.css +++ b/web/style/layout.css @@ -163,11 +163,10 @@ footer p {      content: "arrow_back";  } -.icon::before { -    margin-right: 0.2em; -} - -.material-icons, .cardhover.item .play, .page.node .title .play::before, .icon::before { +.cardhover.item .play, +.page.node .title .play::before, +.icon::before, +.iicon {      font-family: "Material Icons";      line-height: 1;      font-size: 1.1em; @@ -175,4 +174,4 @@ footer p {      display: inline-block;      text-rendering: optimizeLegibility;      font-feature-settings: "liga"; -}
\ No newline at end of file +} | 
