UNPKG

@7sage/vidstack

Version:

UI component library for building high-quality, accessible video and audio experiences on the web.

301 lines (274 loc) 10 kB
import { Host, effect, Component, signal, setAttribute, isBoolean, computed, isString, useState } from './vidstack-BGSTndAW.js'; import { Captions, Gesture, MediaAnnouncer, Controls, ControlsGroup, GoogleCastButton, ToggleButton, Tooltip, TooltipTrigger, TooltipContent, ChaptersRadioGroup, AudioGainRadioGroup, RadioGroup, SliderVideo, AudioGainSlider, SpeedSlider, QualitySlider, SliderChapters } from './vidstack-0dMjVFPr.js'; import { useMediaContext } from './vidstack-DJDnh4xT.js'; import { watchCueTextChange } from './vidstack-DYbwIVLq.js'; import { html } from 'lit-html'; import { requestScopedAnimationFrame, isHTMLElement, cloneTemplateContent, createTemplate, cloneTemplate } from './vidstack-C2US-gSO.js'; import { LitElement } from './vidstack-CwTj4H1w.js'; import { MenuPortal, Slider, sliderState } from './vidstack-B5rN6wRF.js'; import { renderMenuItemsTemplate } from './vidstack-K_Jbv5ge.js'; class MediaCaptionsElement extends Host(HTMLElement, Captions) { static tagName = "media-captions"; } class MediaGestureElement extends Host(HTMLElement, Gesture) { static tagName = "media-gesture"; } class MediaAnnouncerElement extends Host(HTMLElement, MediaAnnouncer) { static tagName = "media-announcer"; } class MediaControlsElement extends Host(HTMLElement, Controls) { static tagName = "media-controls"; } class MediaControlsGroupElement extends Host(HTMLElement, ControlsGroup) { static tagName = "media-controls-group"; } class Title extends Component { } class MediaTitleElement extends Host(HTMLElement, Title) { static tagName = "media-title"; #media; onSetup() { this.#media = useMediaContext(); } onConnect() { effect(this.#watchTitle.bind(this)); } #watchTitle() { const { title } = this.#media.$state; this.textContent = title(); } } class ChapterTitle extends Component { static props = { defaultText: "" }; } class MediaChapterTitleElement extends Host(HTMLElement, ChapterTitle) { static tagName = "media-chapter-title"; #media; #chapterTitle; onSetup() { this.#media = useMediaContext(); this.#chapterTitle = signal(""); } onConnect() { const tracks = this.#media.textTracks; watchCueTextChange(tracks, "chapters", this.#chapterTitle.set); effect(this.#watchChapterTitle.bind(this)); } #watchChapterTitle() { const { defaultText } = this.$props; this.textContent = this.#chapterTitle() || defaultText(); } } class Spinner extends Component { static props = { size: 96, trackWidth: 8, fillPercent: 50 }; onConnect(el) { requestScopedAnimationFrame(() => { if (!this.connectScope) return; const root = el.querySelector("svg"), track = root.firstElementChild, trackFill = track.nextElementSibling; effect(this.#update.bind(this, root, track, trackFill)); }); } #update(root, track, trackFill) { const { size, trackWidth, fillPercent } = this.$props; setAttribute(root, "width", size()); setAttribute(root, "height", size()); setAttribute(track, "stroke-width", trackWidth()); setAttribute(trackFill, "stroke-width", trackWidth()); setAttribute(trackFill, "stroke-dashoffset", 100 - fillPercent()); } } class MediaSpinnerElement extends Host(LitElement, Spinner) { static tagName = "media-spinner"; render() { return html` <svg fill="none" viewBox="0 0 120 120" aria-hidden="true" data-part="root"> <circle cx="60" cy="60" r="54" stroke="currentColor" data-part="track"></circle> <circle cx="60" cy="60" r="54" stroke="currentColor" pathLength="100" stroke-dasharray="100" data-part="track-fill" ></circle> </svg> `; } } class MediaLayout extends Component { static props = { when: false }; } class MediaLayoutElement extends Host(HTMLElement, MediaLayout) { static tagName = "media-layout"; #media; onSetup() { this.#media = useMediaContext(); } onConnect() { effect(this.#watchWhen.bind(this)); } #watchWhen() { const root = this.firstElementChild, isTemplate = root?.localName === "template", when = this.$props.when(), matches = isBoolean(when) ? when : computed(() => when(this.#media.player.state))(); if (!matches) { if (isTemplate) { this.textContent = ""; this.appendChild(root); } else if (isHTMLElement(root)) { root.style.display = "none"; } return; } if (isTemplate) { this.append(root.content.cloneNode(true)); } else if (isHTMLElement(root)) { root.style.display = ""; } } } class MediaGoogleCastButtonElement extends Host(HTMLElement, GoogleCastButton) { static tagName = "media-google-cast-button"; } class MediaToggleButtonElement extends Host(HTMLElement, ToggleButton) { static tagName = "media-toggle-button"; } class MediaTooltipElement extends Host(HTMLElement, Tooltip) { static tagName = "media-tooltip"; } class MediaTooltipTriggerElement extends Host(HTMLElement, TooltipTrigger) { static tagName = "media-tooltip-trigger"; onConnect() { this.style.display = "contents"; } } class MediaTooltipContentElement extends Host(HTMLElement, TooltipContent) { static tagName = "media-tooltip-content"; } class MediaMenuPortalElement extends Host(HTMLElement, MenuPortal) { static tagName = "media-menu-portal"; static attrs = { disabled: { converter(value) { if (isString(value)) return value; return value !== null; } } }; } class MediaChaptersRadioGroupElement extends Host(HTMLElement, ChaptersRadioGroup) { static tagName = "media-chapters-radio-group"; onConnect() { renderMenuItemsTemplate(this, (el, option) => { const { cue, startTime, duration } = option, thumbnailEl = el.querySelector(".vds-thumbnail,media-thumbnail"), startEl = el.querySelector('[data-part="start-time"]'), durationEl = el.querySelector('[data-part="duration"]'); if (startEl) startEl.textContent = startTime; if (durationEl) durationEl.textContent = duration; if (thumbnailEl) { thumbnailEl.setAttribute("time", cue.startTime + ""); effect(() => { const thumbnails = this.$props.thumbnails(); if ("src" in thumbnailEl) { thumbnailEl.src = thumbnails; } else if (isString(thumbnails)) { thumbnailEl.setAttribute("src", thumbnails); } }); } }); } } class MediaAudioGainRadioGroupElement extends Host(HTMLElement, AudioGainRadioGroup) { static tagName = "media-audio-gain-radio-group"; onConnect() { renderMenuItemsTemplate(this); } } class MediaRadioGroupElement extends Host(HTMLElement, RadioGroup) { static tagName = "media-radio-group"; } class MediaSliderElement extends Host(HTMLElement, Slider) { static tagName = "media-slider"; } const videoTemplate = /* @__PURE__ */ createTemplate( `<video muted playsinline preload="none" style="max-width: unset;"></video>` ); class MediaSliderVideoElement extends Host(HTMLElement, SliderVideo) { static tagName = "media-slider-video"; #media; #video = this.#createVideo(); onSetup() { this.#media = useMediaContext(); this.$state.video.set(this.#video); } onConnect() { const { canLoad } = this.#media.$state, { src, crossOrigin } = this.$state; if (this.#video.parentNode !== this) { this.prepend(this.#video); } effect(() => { setAttribute(this.#video, "crossorigin", crossOrigin()); setAttribute(this.#video, "preload", canLoad() ? "auto" : "none"); setAttribute(this.#video, "src", src()); }); } #createVideo() { return cloneTemplateContent(videoTemplate); } } class MediaAudioGainSliderElement extends Host(HTMLElement, AudioGainSlider) { static tagName = "media-audio-gain-slider"; } class MediaSpeedSliderElement extends Host(HTMLElement, SpeedSlider) { static tagName = "media-speed-slider"; } class MediaQualitySliderElement extends Host(HTMLElement, QualitySlider) { static tagName = "media-quality-slider"; } class MediaSliderChaptersElement extends Host(HTMLElement, SliderChapters) { static tagName = "media-slider-chapters"; #template = null; #connectedRanOnce = false; onConnect() { if (this.#connectedRanOnce) return; this.#connectedRanOnce = true; requestScopedAnimationFrame(() => { if (!this.connectScope) return; const template = this.querySelector("template"); if (template) { this.#template = template; effect(this.#renderTemplate.bind(this)); } }); } #renderTemplate() { if (!this.#template) return; const elements = cloneTemplate(this.#template, this.cues.length || 1); this.setRefs(elements); } } class SliderSteps extends Component { } class MediaSliderStepsElement extends Host(HTMLElement, SliderSteps) { static tagName = "media-slider-steps"; #template = null; onConnect(el) { requestScopedAnimationFrame(() => { if (!this.connectScope) return; this.#template = el.querySelector("template"); if (this.#template) effect(this.#render.bind(this)); }); } #render() { if (!this.#template) return; const { min, max, step } = useState(sliderState), steps = (max() - min()) / step(); cloneTemplate(this.#template, Math.floor(steps) + 1); } } export { MediaAnnouncerElement, MediaAudioGainRadioGroupElement, MediaAudioGainSliderElement, MediaCaptionsElement, MediaChapterTitleElement, MediaChaptersRadioGroupElement, MediaControlsElement, MediaControlsGroupElement, MediaGestureElement, MediaGoogleCastButtonElement, MediaLayoutElement, MediaMenuPortalElement, MediaQualitySliderElement, MediaRadioGroupElement, MediaSliderChaptersElement, MediaSliderElement, MediaSliderStepsElement, MediaSliderVideoElement, MediaSpeedSliderElement, MediaSpinnerElement, MediaTitleElement, MediaToggleButtonElement, MediaTooltipContentElement, MediaTooltipElement, MediaTooltipTriggerElement };