vidply 1.1.17 → 1.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/dev/{vidply.AudioDescriptionManager-TP36B55G.js → vidply.AudioDescriptionManager-NRMNKZAL.js} +5 -5
- package/dist/dev/{vidply.DASHRenderer-FPC5VDPD.js → vidply.DASHRenderer-SYXX6RRS.js} +5 -5
- package/dist/dev/{vidply.DASHRenderer-FPC5VDPD.js.map → vidply.DASHRenderer-SYXX6RRS.js.map} +1 -1
- package/dist/dev/{vidply.FloatingPlayerManager-TEJ2WTYY.js → vidply.FloatingPlayerManager-HJVGUQLT.js} +13 -6
- package/dist/dev/vidply.FloatingPlayerManager-HJVGUQLT.js.map +7 -0
- package/dist/dev/{vidply.HLSRenderer-KA4ZQX2S.js → vidply.HLSRenderer-YAFEUBMC.js} +4 -4
- package/dist/dev/{vidply.HLSRenderer-KA4ZQX2S.js.map → vidply.HLSRenderer-YAFEUBMC.js.map} +1 -1
- package/dist/dev/vidply.HTML5Renderer-ITDUMSTL.js +12 -0
- package/dist/dev/{vidply.SignLanguageManager-JQ6UMDE6.js → vidply.SignLanguageManager-ZZ6DIDTP.js} +7 -7
- package/dist/dev/{vidply.SoundCloudRenderer-QOCG5WAJ.js → vidply.SoundCloudRenderer-OFBGL7UV.js} +2 -2
- package/dist/dev/{vidply.TranscriptManager-QFLILDPN.js → vidply.TranscriptManager-4553RRA6.js} +9 -9
- package/dist/dev/{vidply.VimeoRenderer-4U5V6KDI.js → vidply.VimeoRenderer-EVW2RNSF.js} +2 -2
- package/dist/dev/{vidply.YouTubeRenderer-WW36YFHJ.js → vidply.YouTubeRenderer-PZHT3GMF.js} +2 -2
- package/dist/dev/{vidply.chunk-OUZQINDG.js → vidply.chunk-24QLI7ZK.js} +2 -2
- package/dist/dev/{vidply.chunk-RJLEOT7A.js → vidply.chunk-AQDY6B5B.js} +4 -4
- package/dist/dev/{vidply.chunk-PIFEZ5XT.js → vidply.chunk-KMACPXZ5.js} +6 -6
- package/dist/dev/{vidply.chunk-GOPW4PFQ.js → vidply.chunk-MQAA3FKI.js} +2 -2
- package/dist/dev/{vidply.chunk-42ATI2WJ.js → vidply.chunk-NUTGO3RK.js} +3 -3
- package/dist/dev/{vidply.chunk-IP6EUW6U.js → vidply.chunk-PTYRD6ZP.js} +2 -2
- package/dist/dev/{vidply.chunk-XDLIS2QJ.js → vidply.chunk-QF77EYBM.js} +3 -3
- package/dist/dev/{vidply.chunk-ALYC3NTP.js → vidply.chunk-RQGZAPWY.js} +14 -3
- package/dist/dev/vidply.chunk-RQGZAPWY.js.map +7 -0
- package/dist/dev/{vidply.chunk-DVCNZ6IQ.js → vidply.chunk-TM5HFGG5.js} +5 -5
- package/dist/dev/{vidply.de-FSCAHYDY.js → vidply.de-27ZM6TOB.js} +2 -2
- package/dist/dev/{vidply.es-U2WEWIJJ.js → vidply.es-XNGJJLWC.js} +2 -2
- package/dist/dev/vidply.esm.js +17 -17
- package/dist/dev/{vidply.fr-WRUKVAOJ.js → vidply.fr-AVZKPUBH.js} +2 -2
- package/dist/dev/{vidply.ja-YL4HAWF6.js → vidply.ja-IZK2UXDC.js} +2 -2
- package/dist/legacy/vidply.js +25 -7
- package/dist/legacy/vidply.js.map +2 -2
- package/dist/legacy/vidply.min.js +2 -2
- package/dist/legacy/vidply.min.meta.json +5 -5
- package/dist/prod/{vidply.AudioDescriptionManager-7SDSPPDJ.min.js → vidply.AudioDescriptionManager-QVL4TD4R.min.js} +2 -2
- package/dist/prod/{vidply.DASHRenderer-DJZFCNUJ.min.js → vidply.DASHRenderer-DVBO6JRL.min.js} +2 -2
- package/dist/prod/vidply.FloatingPlayerManager-FNM5V76W.min.js +6 -0
- package/dist/prod/{vidply.HLSRenderer-TBXS7GFK.min.js → vidply.HLSRenderer-AHFQI64X.min.js} +2 -2
- package/dist/prod/vidply.HTML5Renderer-WNGD745H.min.js +6 -0
- package/dist/prod/{vidply.SignLanguageManager-PN7XLJWY.min.js → vidply.SignLanguageManager-E65WKF37.min.js} +2 -2
- package/dist/prod/{vidply.SoundCloudRenderer-OWW37MXH.min.js → vidply.SoundCloudRenderer-XD3TMM5O.min.js} +1 -1
- package/dist/prod/{vidply.TranscriptManager-LZHT5TKO.min.js → vidply.TranscriptManager-6JM6CJTM.min.js} +2 -2
- package/dist/prod/{vidply.VimeoRenderer-KXSGMJ37.min.js → vidply.VimeoRenderer-7L6RWMWE.min.js} +1 -1
- package/dist/prod/{vidply.YouTubeRenderer-JBDDHZBP.min.js → vidply.YouTubeRenderer-ZAQTBKM4.min.js} +1 -1
- package/dist/prod/{vidply.chunk-CALP5FG7.min.js → vidply.chunk-3L3VQFPN.min.js} +2 -2
- package/dist/prod/{vidply.chunk-N53MUMSX.min.js → vidply.chunk-5644WA3F.min.js} +2 -2
- package/dist/prod/{vidply.chunk-YKYJS6QY.min.js → vidply.chunk-6DG7I4DA.min.js} +1 -1
- package/dist/prod/{vidply.chunk-UGISMYG2.min.js → vidply.chunk-D7PUX3IJ.min.js} +1 -1
- package/dist/prod/{vidply.chunk-HRWGKYM7.min.js → vidply.chunk-HKXIYL5T.min.js} +2 -2
- package/dist/prod/{vidply.chunk-7JWNY7FY.min.js → vidply.chunk-OFMLXH4A.min.js} +2 -2
- package/dist/prod/vidply.chunk-VX4CE6WF.min.js +6 -0
- package/dist/prod/{vidply.chunk-FTATHA52.min.js → vidply.chunk-W5YQ5TEO.min.js} +2 -2
- package/dist/prod/{vidply.chunk-HXX7FB5L.min.js → vidply.chunk-WE6VR5E2.min.js} +1 -1
- package/dist/prod/{vidply.de-XTVWKFGS.min.js → vidply.de-RZ2FVGU4.min.js} +1 -1
- package/dist/prod/{vidply.es-HKIP5HJC.min.js → vidply.es-KUCGSMAA.min.js} +1 -1
- package/dist/prod/vidply.esm.min.js +2 -2
- package/dist/prod/{vidply.fr-HF25O4JJ.min.js → vidply.fr-2FOZ6M7L.min.js} +1 -1
- package/dist/prod/{vidply.ja-6VXDR66J.min.js → vidply.ja-5H6AMU2R.min.js} +1 -1
- package/dist/vidply.css +5892 -5881
- package/dist/vidply.esm.min.meta.json +76 -76
- package/dist/vidply.min.css +1 -1
- package/package.json +92 -92
- package/src/core/FloatingPlayerManager.ts +14 -1
- package/src/renderers/DASHRenderer.ts +3 -3
- package/src/renderers/HLSRenderer.ts +1 -1
- package/src/styles/vidply.css +5892 -5881
- package/src/utils/DraggableResizable.ts +30 -2
- package/dist/dev/vidply.FloatingPlayerManager-TEJ2WTYY.js.map +0 -7
- package/dist/dev/vidply.HTML5Renderer-VC3PWUU7.js +0 -12
- package/dist/dev/vidply.chunk-ALYC3NTP.js.map +0 -7
- package/dist/prod/vidply.FloatingPlayerManager-UIRBHHUB.min.js +0 -6
- package/dist/prod/vidply.HTML5Renderer-ATMOQBRN.min.js +0 -6
- package/dist/prod/vidply.chunk-ROK74CAE.min.js +0 -6
- /package/dist/dev/{vidply.AudioDescriptionManager-TP36B55G.js.map → vidply.AudioDescriptionManager-NRMNKZAL.js.map} +0 -0
- /package/dist/dev/{vidply.HTML5Renderer-VC3PWUU7.js.map → vidply.HTML5Renderer-ITDUMSTL.js.map} +0 -0
- /package/dist/dev/{vidply.SignLanguageManager-JQ6UMDE6.js.map → vidply.SignLanguageManager-ZZ6DIDTP.js.map} +0 -0
- /package/dist/dev/{vidply.SoundCloudRenderer-QOCG5WAJ.js.map → vidply.SoundCloudRenderer-OFBGL7UV.js.map} +0 -0
- /package/dist/dev/{vidply.TranscriptManager-QFLILDPN.js.map → vidply.TranscriptManager-4553RRA6.js.map} +0 -0
- /package/dist/dev/{vidply.VimeoRenderer-4U5V6KDI.js.map → vidply.VimeoRenderer-EVW2RNSF.js.map} +0 -0
- /package/dist/dev/{vidply.YouTubeRenderer-WW36YFHJ.js.map → vidply.YouTubeRenderer-PZHT3GMF.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-OUZQINDG.js.map → vidply.chunk-24QLI7ZK.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-RJLEOT7A.js.map → vidply.chunk-AQDY6B5B.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-PIFEZ5XT.js.map → vidply.chunk-KMACPXZ5.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-GOPW4PFQ.js.map → vidply.chunk-MQAA3FKI.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-42ATI2WJ.js.map → vidply.chunk-NUTGO3RK.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-IP6EUW6U.js.map → vidply.chunk-PTYRD6ZP.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-XDLIS2QJ.js.map → vidply.chunk-QF77EYBM.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-DVCNZ6IQ.js.map → vidply.chunk-TM5HFGG5.js.map} +0 -0
- /package/dist/dev/{vidply.de-FSCAHYDY.js.map → vidply.de-27ZM6TOB.js.map} +0 -0
- /package/dist/dev/{vidply.es-U2WEWIJJ.js.map → vidply.es-XNGJJLWC.js.map} +0 -0
- /package/dist/dev/{vidply.fr-WRUKVAOJ.js.map → vidply.fr-AVZKPUBH.js.map} +0 -0
- /package/dist/dev/{vidply.ja-YL4HAWF6.js.map → vidply.ja-IZK2UXDC.js.map} +0 -0
package/README.md
CHANGED
|
@@ -46,11 +46,11 @@ Try VidPly in action:
|
|
|
46
46
|
- **Vimeo Integration** - Seamless Vimeo player integration
|
|
47
47
|
- **SoundCloud Integration** - Play SoundCloud tracks and sets via the Widget API with unified controls
|
|
48
48
|
- **HLS Streaming** - Adaptive bitrate streaming with quality selection and dynamic subtitle detection
|
|
49
|
-
- Uses
|
|
49
|
+
- Uses **hls.js 1.6.16** on Chrome / Firefox / Edge / desktop Safari for full feature parity (quality menu, captions,
|
|
50
50
|
transcript)
|
|
51
51
|
- Falls back to native HLS on iOS / iPadOS where MSE is unavailable; native text tracks are still surfaced through the
|
|
52
52
|
VidPly captions and transcript UI
|
|
53
|
-
- **DASH Streaming** - MPEG-DASH support via dash.js with adaptive quality, TTML and WebVTT subtitles
|
|
53
|
+
- **DASH Streaming** - MPEG-DASH support via **dash.js 5.2.0** (modern UMD) with adaptive quality, TTML and WebVTT subtitles
|
|
54
54
|
- **Buffering Spinner** - Centered loading spinner shown automatically while media is buffering (HTML5, HLS, DASH)
|
|
55
55
|
- **Download Button** - Optional download control with custom URL support (`downloadButton` + `downloadUrl`)
|
|
56
56
|
- **Preview Thumbnails** - Video preview thumbnails on progress bar hover
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* VidPly v1.1.
|
|
2
|
+
* VidPly v1.1.19 - Universal, Accessible Video Player
|
|
3
3
|
* (c) 2026 Matthias Peltzer
|
|
4
4
|
* Released under GPL-2.0-or-later License
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
CaptionManager
|
|
8
|
-
} from "./vidply.chunk-
|
|
9
|
-
import "./vidply.chunk-
|
|
10
|
-
import "./vidply.chunk-
|
|
8
|
+
} from "./vidply.chunk-AQDY6B5B.js";
|
|
9
|
+
import "./vidply.chunk-QF77EYBM.js";
|
|
10
|
+
import "./vidply.chunk-KMACPXZ5.js";
|
|
11
11
|
|
|
12
12
|
// src/core/AudioDescriptionManager.ts
|
|
13
13
|
var AudioDescriptionManager = class {
|
|
@@ -589,4 +589,4 @@ var AudioDescriptionManager = class {
|
|
|
589
589
|
export {
|
|
590
590
|
AudioDescriptionManager
|
|
591
591
|
};
|
|
592
|
-
//# sourceMappingURL=vidply.AudioDescriptionManager-
|
|
592
|
+
//# sourceMappingURL=vidply.AudioDescriptionManager-NRMNKZAL.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* VidPly v1.1.
|
|
2
|
+
* VidPly v1.1.19 - Universal, Accessible Video Player
|
|
3
3
|
* (c) 2026 Matthias Peltzer
|
|
4
4
|
* Released under GPL-2.0-or-later License
|
|
5
5
|
*/
|
|
@@ -77,7 +77,7 @@ var DASHRenderer = class {
|
|
|
77
77
|
// NOTE on pre-play preload: we deliberately do NOT set
|
|
78
78
|
// streaming.scheduling.scheduleWhilePaused = false here. While that
|
|
79
79
|
// is the documented dash.js way to suppress segment downloads while
|
|
80
|
-
// paused / before the first play, in our setup (dash.js 5.
|
|
80
|
+
// paused / before the first play, in our setup (dash.js 5.2.0 +
|
|
81
81
|
// dash.initialize(media, null, false) + attachSource at init) it
|
|
82
82
|
// tears down the SourceBuffers mid-init with
|
|
83
83
|
// "SourceBuffer has been removed from the parent media source"
|
|
@@ -159,12 +159,12 @@ var DASHRenderer = class {
|
|
|
159
159
|
}
|
|
160
160
|
/**
|
|
161
161
|
* Load dash.js. Pinned to an exact version (the previous default
|
|
162
|
-
* `5.
|
|
162
|
+
* `5.2.0` is preserved) and overridable via `options.dashScriptUrl`
|
|
163
163
|
* (URL) / `options.dashScriptIntegrity` (SRI hash). See
|
|
164
164
|
* HLSRenderer.loadHlsJs() for the SRI computation command.
|
|
165
165
|
*/
|
|
166
166
|
async loadDashJs() {
|
|
167
|
-
const defaultUrl = "https://cdn.jsdelivr.net/npm/dashjs@5.
|
|
167
|
+
const defaultUrl = "https://cdn.jsdelivr.net/npm/dashjs@5.2.0/dist/modern/umd/dash.all.min.js";
|
|
168
168
|
const url = this.player.options.dashScriptUrl || defaultUrl;
|
|
169
169
|
const integrity = this.player.options.dashScriptIntegrity;
|
|
170
170
|
return new Promise((resolve, reject) => {
|
|
@@ -855,4 +855,4 @@ var DASHRenderer = class {
|
|
|
855
855
|
export {
|
|
856
856
|
DASHRenderer
|
|
857
857
|
};
|
|
858
|
-
//# sourceMappingURL=vidply.DASHRenderer-
|
|
858
|
+
//# sourceMappingURL=vidply.DASHRenderer-SYXX6RRS.js.map
|
package/dist/dev/{vidply.DASHRenderer-FPC5VDPD.js.map → vidply.DASHRenderer-SYXX6RRS.js.map}
RENAMED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/renderers/DASHRenderer.ts"],
|
|
4
|
-
"sourcesContent": ["import type { Renderer } from '../types/renderer.js';\nimport type { Player } from '../core/Player.js';\n\ninterface DashTextTrack {\n lang?: string;\n language?: string;\n srclang?: string;\n kind?: string;\n label?: string;\n labels?: string;\n isTTML?: boolean;\n codec?: string;\n mimeType?: string;\n [key: string]: unknown;\n}\n\n/**\n * Shape of payloads emitted by VidPly's `captionsenabled` event when\n * routed into a DASH renderer. These come from CaptionManager and carry\n * both the language and the underlying TextTrack reference.\n */\ninterface CaptionTrackSelection {\n language?: string;\n label?: string;\n track?: TextTrack;\n}\n\ntype CaptionEnabledHandler = (track: CaptionTrackSelection) => void;\ntype CaptionDisabledHandler = () => void;\n\nexport class DASHRenderer implements Renderer {\n player: Player;\n media: HTMLMediaElement;\n dash: DashMediaPlayerInstance | null;\n readonly isStreaming = true;\n _dashSourceLoaded: boolean;\n _pendingSrc: string | null;\n _dashSubtitleTracksCount: number | undefined;\n _dashTextTracks: DashTextTrack[];\n _cueUpdateTimer: ReturnType<typeof setInterval> | null;\n _captionEnabledHandler: CaptionEnabledHandler | null;\n _captionDisabledHandler: CaptionDisabledHandler | null;\n _lastKnownCueCount: number;\n _dashTextIsTtml: boolean;\n _pendingTimeouts: ReturnType<typeof setTimeout>[];\n _ttmlDiv: HTMLElement | null;\n _manifestUrl: string | null;\n\n constructor(player: Player) {\n this.player = player;\n this.media = player.element;\n this.dash = null;\n this._dashSourceLoaded = false;\n this._pendingSrc = null;\n this._dashSubtitleTracksCount = undefined;\n this._dashTextTracks = [];\n this._cueUpdateTimer = null;\n this._captionEnabledHandler = null;\n this._captionDisabledHandler = null;\n this._lastKnownCueCount = 0;\n this._dashTextIsTtml = false;\n this._pendingTimeouts = [];\n this._ttmlDiv = null;\n this._manifestUrl = null;\n }\n\n async init() {\n this.player.log('Using dash.js for DASH support');\n await this.initDashJs();\n }\n\n async initDashJs() {\n this.media.controls = false;\n this.media.removeAttribute('controls');\n\n if (!window.dashjs) {\n await this.loadDashJs();\n }\n\n // Remove <source> children — dash.js manages the src via MSE\n const sourceElements = Array.from(this.media.querySelectorAll('source'));\n let originalSrc = null;\n if (sourceElements.length > 0) {\n originalSrc = sourceElements[0].getAttribute('src');\n sourceElements.forEach(source => source.remove());\n this.player.log('Removed <source> elements for HTML5 validity (dash.js uses MSE)');\n }\n\n const dashjs = window.dashjs;\n if (!dashjs) {\n throw new Error('dash.js not available');\n }\n this.dash = dashjs.MediaPlayer().create();\n\n this.dash.updateSettings({\n debug: {\n logLevel: this.player.options.debug ? 4 : 0\n },\n streaming: {\n // Override dash.js default of 'lowestStartupDelay'. For audio\n // AdaptationSets that tie on selectionPriority and role=main (e.g.\n // Axinom's three en/en-low/en-high tracks), 'lowestStartupDelay'\n // falls through to 'highestEfficiency' which, for audio, has no\n // meaningful pixels-per-bit metric and collapses to \"highest\n // bitrate\". 'firstTrack' respects manifest order instead, which is\n // both predictable and closer to the MPD author's intent.\n selectionModeForInitialTrack: 'firstTrack',\n // NOTE on pre-play preload: we deliberately do NOT set\n // streaming.scheduling.scheduleWhilePaused = false here. While that\n // is the documented dash.js way to suppress segment downloads while\n // paused / before the first play, in our setup (dash.js 5.1.1 +\n // dash.initialize(media, null, false) + attachSource at init) it\n // tears down the SourceBuffers mid-init with\n // \"SourceBuffer has been removed from the parent media source\"\n // exceptions, which leaves the player unable to seek or play. The\n // PR #3785 fix that was supposed to handle the initial-playback /\n // autoPlay=false case is fragile against our usage pattern.\n // Instead we keep dash.js's default scheduling (scheduleWhilePaused\n // stays at its default `true`) and let the buffer caps below limit\n // how much is fetched before play. With a single ~6s segment size,\n // the visible network preload is one init segment per track plus\n // 1–2 media segments — the same \"first two chunks\" behavior the\n // user previously confirmed as acceptable for DASH.\n buffer: {\n bufferTimeAtTopQuality: 30,\n bufferTimeAtTopQualityLongForm: 60,\n // dash.js 5.x: use bufferTimeDefault (replaces removed stableBufferTime).\n // Keep at 12s — going lower (0 / 1) was tested but dash.js still\n // loads the first segment regardless because it's needed to make\n // the MediaSource playable, so the savings are negligible while\n // hurting mid-playback resilience on slow networks.\n bufferTimeDefault: 12,\n bufferToKeep: 20,\n bufferPruningInterval: 10\n },\n retryAttempts: {\n MPD: 4,\n MediaSegment: 6,\n InitializationSegment: 4,\n BitstreamSwitchingSegment: 4\n },\n retryIntervals: {\n MPD: 1000,\n MediaSegment: 1000,\n InitializationSegment: 1000,\n BitstreamSwitchingSegment: 1000\n },\n abr: {\n autoSwitchBitrate: { video: true, audio: true }\n },\n text: {\n defaultEnabled: true\n }\n }\n });\n\n // Create the TTML rendering div before initialize() so the DOM element\n // exists, but attach it to dash.js after initialize() because\n // attachTTMLRenderingDiv() requires attachView() which initialize() calls.\n this._ttmlDiv = document.createElement('div');\n this._ttmlDiv.className = 'vidply-dash-ttml';\n this._ttmlDiv.style.visibility = 'hidden';\n const wrapper = this.player.videoWrapper || this.media.parentElement;\n if (wrapper) {\n wrapper.appendChild(this._ttmlDiv);\n }\n\n this.dash.initialize(this.media, null, false);\n this.dash.attachTTMLRenderingDiv(this._ttmlDiv);\n\n // Resolve source URL\n let src = this.player.currentSource;\n\n if (!src && originalSrc) {\n src = originalSrc;\n }\n\n if (!src) {\n src = this.player.element.getAttribute('data-vidply-src');\n }\n\n if (!src) {\n const elementSrc = this.player.element.getAttribute('src') || this.player.element.src;\n if (elementSrc && !elementSrc.startsWith('blob:')) {\n src = elementSrc;\n }\n }\n\n this.player.log(`Loading DASH source: ${src}`, 'log');\n\n if (!src) {\n throw new Error('No DASH source found');\n }\n\n // Always attach the source at init so the manifest is fetched and the\n // seekbar / duration / quality list are available before the first play.\n // We deliberately don't honor `deferLoad` here: deferring attachSource\n // would leave duration unknown and seek requests unserviceable. The\n // small preload (init segments + ~1 segment of media data) that dash.js\n // does up to bufferTimeDefault is acceptable; in exchange the seekbar\n // becomes functional immediately.\n this._pendingSrc = src;\n this._manifestUrl = src;\n this.dash.attachSource(src);\n this._dashSourceLoaded = true;\n\n // Force the vidply poster overlay even though dash.js will paint the\n // first decoded frame into the <video> element. With MSE attached and\n // init/startup segments appended, the browser ignores the native\n // `poster` attribute and shows the first frame instead. We instead use\n // the `vidply-forced-poster` CSS class, which sets `<video>` to\n // opacity:0 and renders the poster image as a wrapper background — the\n // user sees the artwork until they press play, just like on a fully\n // native HTML5 video. The 'play' handler in Player.ts already calls\n // hidePosterOverlay() on first playback, so we don't need to remove it\n // manually here.\n this.player.showPosterOverlay();\n\n this.attachDashEvents();\n this.attachMediaEvents();\n this._setupCaptionSync();\n }\n\n /**\n * Load dash.js. Pinned to an exact version (the previous default\n * `5.1.1` is preserved) and overridable via `options.dashScriptUrl`\n * (URL) / `options.dashScriptIntegrity` (SRI hash). See\n * HLSRenderer.loadHlsJs() for the SRI computation command.\n */\n async loadDashJs(): Promise<void> {\n const defaultUrl = 'https://cdn.jsdelivr.net/npm/dashjs@5.1.1/dist/modern/umd/dash.all.min.js';\n const url: string = (this.player.options.dashScriptUrl as string | undefined) || defaultUrl;\n const integrity = this.player.options.dashScriptIntegrity as string | undefined;\n\n return new Promise<void>((resolve, reject) => {\n const script = document.createElement('script');\n script.src = url;\n if (integrity) {\n script.integrity = integrity;\n script.crossOrigin = 'anonymous';\n script.referrerPolicy = 'no-referrer';\n }\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('Failed to load dash.js'));\n document.head.appendChild(script);\n });\n }\n\n _setTimeout(fn: () => void, delay: number) {\n const id = setTimeout(() => {\n this._pendingTimeouts = this._pendingTimeouts.filter(t => t !== id);\n fn();\n }, delay);\n this._pendingTimeouts.push(id);\n return id;\n }\n\n attachDashEvents() {\n const dashjs = window.dashjs;\n const dash = this.dash;\n if (!dashjs || !dash) return;\n const dashEvents = dashjs.MediaPlayer.events;\n\n dash.on(dashEvents.MANIFEST_LOADED, (...args: unknown[]) => {\n const e = args[0] as { data?: unknown } | undefined;\n const data = e?.data ?? e;\n this.player.log('DASH manifest loaded');\n this.player.emit('dashmanifestloaded', data);\n\n if (this.player.container) {\n this.player.container.classList.remove('vidply-external-controls');\n }\n\n this._setTimeout(() => {\n this._checkSubtitleTracks();\n }, 500);\n });\n\n dash.on(dashEvents.QUALITY_CHANGE_RENDERED, (...args: unknown[]) => {\n const e = args[0] as { mediaType?: string; newQuality?: number };\n if (e.mediaType === 'video') {\n this.player.log('DASH quality changed to index ' + e.newQuality);\n this.player.emit('dashqualitychanged', e);\n }\n });\n\n dash.on(dashEvents.TEXT_TRACKS_ADDED, (...args: unknown[]) => {\n const e = args[0] as { tracks?: DashTextTrack[] } | undefined;\n const tracks = e?.tracks ?? [];\n this._dashTextTracks = tracks;\n this._dashTextIsTtml = tracks.some((t) =>\n t.isTTML || /stpp|ttml/i.test(t.codec || '') || /ttml/i.test(t.mimeType || '')\n );\n this.player.log(`DASH text tracks added: ${tracks.length} tracks, format: ${this._dashTextIsTtml ? 'TTML' : 'WebVTT'}`);\n this._dashSubtitleTracksCount = tracks.length;\n this.player.emit('dashsubtitletracksupdated', { tracks });\n this.updateCaptionButtonsForDash();\n\n if (tracks.length > 0) {\n try {\n dash.setTextTrack(0);\n } catch {\n // ignore if not ready yet\n }\n if (!this._dashTextIsTtml) {\n this._startCueUpdatePolling();\n }\n }\n });\n\n dash.on(dashEvents.STREAM_INITIALIZED, () => {\n this.player.log('DASH stream initialized');\n this.player.emit('dashstreaminitialized');\n\n this._setTimeout(() => {\n const qualities = this.getQualities();\n if (qualities.length > 0) {\n this.player.emit('dashmanifestparsed', { qualities });\n }\n }, 300);\n });\n\n dash.on(dashEvents.ERROR, (...args: unknown[]) => {\n this.handleDashError(args[0]);\n });\n\n dash.on(dashEvents.FRAGMENT_LOADING_COMPLETED, (...args: unknown[]) => {\n const e = args[0] as { request?: { mediaType?: string } } | undefined;\n this.player.state.buffering = false;\n if (e?.request?.mediaType === 'text' && !this._dashTextIsTtml) {\n this._setTimeout(() => {\n const count = this._getTotalCueCount();\n if (count > this._lastKnownCueCount) {\n this._lastKnownCueCount = count;\n this.player.emit('textcuesupdate');\n }\n }, 100);\n }\n });\n }\n\n /**\n * Count total cues across all subtitle/caption tracks (for WebVTT DASH).\n */\n _getTotalCueCount() {\n const textTracks = this.media.textTracks;\n let total = 0;\n if (!textTracks) return total;\n for (let i = 0; i < textTracks.length; i++) {\n const track = textTracks[i];\n if ((track.kind === 'subtitles' || track.kind === 'captions') && !track._vidplyStale && track.cues) {\n total += track.cues.length;\n }\n }\n return total;\n }\n\n /**\n * Return true if `time` falls inside any TimeRange the SourceBuffer already\n * holds, with a small tolerance to absorb GOP boundaries. Used by the\n * seeking handler to decide whether to surface a 'waiting' event for the\n * spinner UI when the user scrubs while paused.\n */\n _isTimeBuffered(time: number): boolean {\n const buffered = this.media.buffered;\n if (!buffered || buffered.length === 0) return false;\n const tolerance = 0.25;\n for (let i = 0; i < buffered.length; i++) {\n if (time >= buffered.start(i) - tolerance && time <= buffered.end(i) + tolerance) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Sync VidPly caption track switches with dash.js so it loads\n * subtitle segments for the selected language.\n */\n _setupCaptionSync() {\n this._captionEnabledHandler = (selectedTrack) => {\n if (this._dashTextIsTtml) {\n // dash.js only renders TTML content to the rendering div when the\n // TextTrack mode is 'showing'. CaptionManager sets it to 'hidden',\n // so we override it here. VidPly's own caption overlay is skipped\n // via handlesOwnCaptions().\n if (selectedTrack.track) {\n selectedTrack.track.mode = 'showing';\n }\n if (this._ttmlDiv) {\n this._ttmlDiv.style.visibility = 'visible';\n }\n }\n this._syncDashTextTrack(selectedTrack);\n };\n this._captionDisabledHandler = () => {\n if (this._dashTextIsTtml && this._ttmlDiv) {\n this._ttmlDiv.style.visibility = 'hidden';\n }\n if (this.dash) {\n try { this.dash.setTextTrack(-1); } catch { /* ignore */ }\n }\n };\n this.player.on('captionsenabled', this._captionEnabledHandler);\n this.player.on('captionsdisabled', this._captionDisabledHandler);\n }\n\n /**\n * Map a VidPly caption track to the corresponding dash.js track index\n * and switch dash.js to load segments for that language.\n */\n _syncDashTextTrack(selectedTrack: CaptionTrackSelection) {\n if (!this.dash || !this._dashTextTracks.length) return;\n\n const lang = selectedTrack.language;\n if (!lang) return;\n\n const dashIndex = this._dashTextTracks.findIndex(dt => {\n const dtLang = dt.lang || dt.language || dt.srclang || '';\n if (!dtLang) return false;\n return dtLang === lang || dtLang.startsWith(lang) || lang.startsWith(dtLang);\n });\n\n if (dashIndex >= 0) {\n this.player.log(`Syncing DASH text track to index ${dashIndex} (${lang})`);\n try {\n this.dash.setTextTrack(dashIndex);\n } catch { /* ignore */ }\n if (!this._dashTextIsTtml) {\n this._lastKnownCueCount = 0;\n this._startCueUpdatePolling();\n }\n }\n }\n\n /**\n * Poll for new WebVTT cues being added by dash.js as subtitle segments load.\n * Emits events for transcript refresh when new cues arrive.\n */\n _startCueUpdatePolling() {\n this._stopCueUpdatePolling();\n let prevCueCount = 0;\n let stableRounds = 0;\n\n this._cueUpdateTimer = setInterval(() => {\n const count = this._getTotalCueCount();\n\n if (count > prevCueCount) {\n prevCueCount = count;\n stableRounds = 0;\n this.player.emit('textcuesupdate');\n } else {\n stableRounds++;\n if (stableRounds >= 8) {\n this._stopCueUpdatePolling();\n if (count > 0) {\n this.player.emit('textcuesupdate');\n }\n }\n }\n }, 500);\n }\n\n _stopCueUpdatePolling() {\n if (this._cueUpdateTimer) {\n clearInterval(this._cueUpdateTimer);\n this._cueUpdateTimer = null;\n }\n }\n\n _checkSubtitleTracks() {\n if (this._dashSubtitleTracksCount !== undefined && this._dashSubtitleTracksCount > 0) {\n return;\n }\n const tracks = this.media.textTracks;\n let count = 0;\n for (let i = 0; i < tracks.length; i++) {\n const track = tracks[i];\n if ((track.kind === 'subtitles' || track.kind === 'captions') && !track._vidplyStale) {\n count++;\n }\n }\n this._dashSubtitleTracksCount = count;\n this.updateCaptionButtonsForDash();\n }\n\n updateCaptionButtonsForDash(retryCount = 0) {\n const tracksCount = this._dashSubtitleTracksCount || 0;\n\n const doUpdate = () => {\n this.player.invalidateTrackCache();\n\n if (tracksCount > 0) {\n if (this.player.captionManager) {\n const found = this.player.captionManager.refreshTracks();\n\n // dash.js fires TEXT_TRACKS_ADDED when it knows about the\n // tracks, but the browser TextTrackList may not yet enumerate\n // the new TextTrack objects synchronously. If refreshTracks\n // found nothing, retry a few times with increasing delays.\n if (found === 0 && retryCount < 5) {\n const delay = (retryCount + 1) * 200;\n this.player.log(`DASH caption tracks not yet on video element, retrying in ${delay}ms (attempt ${retryCount + 1})`, 'info');\n this._setTimeout(() => {\n this.updateCaptionButtonsForDash(retryCount + 1);\n }, delay);\n return;\n }\n }\n\n if (!this._dashTextIsTtml && this.player.transcriptManager?.isVisible) {\n this.player.transcriptManager.loadTranscriptData();\n this.player.transcriptManager.updateLanguageSelector();\n }\n\n if (this.player.controlBar) {\n this.player.controlBar.ensureCaptionsButton();\n if (!this._dashTextIsTtml) {\n this.player.controlBar.ensureCaptionStyleButton();\n this.player.controlBar.ensureTranscriptButton();\n }\n }\n } else {\n if (this.player.captionManager) {\n this.player.captionManager.refreshTracks();\n }\n\n if (this.player.transcriptManager?.isVisible) {\n this.player.transcriptManager.hideTranscript();\n }\n\n if (this.player.controlBar) {\n this.player.controlBar.removeHlsCaptionButtons(true);\n }\n }\n };\n\n if (this.player.controlBar) {\n doUpdate();\n return;\n }\n\n const onReady = () => {\n this.player.off('ready', onReady);\n doUpdate();\n };\n this.player.on('ready', onReady);\n }\n\n attachMediaEvents() {\n this.media.addEventListener('loadedmetadata', () => {\n this.player.state.duration = this.media.duration;\n this.player.emit('loadedmetadata');\n });\n\n this.media.addEventListener('durationchange', () => {\n const duration = this.media.duration;\n if (duration && isFinite(duration) && duration > 0) {\n this.player.state.duration = duration;\n this.player.emit('durationchange', duration);\n }\n });\n\n this.media.addEventListener('play', () => {\n this.player.state.playing = true;\n this.player.state.paused = false;\n this.player.state.ended = false;\n this.player.emit('play');\n\n if (this.player.options.onPlay) {\n this.player.options.onPlay.call(this.player);\n }\n });\n\n this.media.addEventListener('pause', () => {\n this.player.state.playing = false;\n this.player.state.paused = true;\n this.player.emit('pause');\n\n if (this.player.options.onPause) {\n this.player.options.onPause.call(this.player);\n }\n });\n\n this.media.addEventListener('ended', () => {\n this.player.state.playing = false;\n this.player.state.paused = true;\n this.player.state.ended = true;\n this.player.emit('ended');\n\n if (this.player.options.onEnded) {\n this.player.options.onEnded.call(this.player);\n }\n\n if (this.player.options.loop) {\n this.player.seek(0);\n this.player.play();\n }\n });\n\n this.media.addEventListener('timeupdate', () => {\n this.player.state.currentTime = this.media.currentTime;\n this.player.emit('timeupdate', this.media.currentTime);\n\n if (this.player.options.onTimeUpdate) {\n this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);\n }\n });\n\n this.media.addEventListener('volumechange', () => {\n this.player.state.volume = this.media.volume;\n this.player.state.muted = this.media.muted;\n this.player.emit('volumechange', this.media.volume);\n });\n\n this.media.addEventListener('seeking', () => {\n this.player.state.seeking = true;\n this.player.emit('seeking');\n\n // Browsers do not fire `waiting` when seeking on a paused media element\n // — there is nothing to \"wait\" for since playback isn't requested. With\n // dash.js + MSE, scrubbing while paused (or before the first play)\n // still triggers a real fragment download, but the spinner stays hidden\n // because no `waiting` event reaches the buffering UI. Detect a seek\n // into an unbuffered range here and surface it as 'waiting' so the\n // spinner appears. Cleared again by the existing `canplay` /\n // `seeked` paths.\n if (!this._isTimeBuffered(this.media.currentTime)) {\n this.player.state.buffering = true;\n this.player.emit('waiting');\n }\n });\n\n this.media.addEventListener('seeked', () => {\n this.player.state.seeking = false;\n this.player.emit('seeked');\n });\n\n this.media.addEventListener('waiting', () => {\n this.player.state.buffering = true;\n this.player.emit('waiting');\n });\n\n this.media.addEventListener('canplay', () => {\n this.player.state.buffering = false;\n this.player.emit('canplay');\n });\n\n this.media.addEventListener('error', () => {\n this.player.handleError(this.media.error);\n });\n }\n\n handleDashError(e: unknown) {\n const wrapped = e as { error?: { code?: number; message?: string }; code?: number; message?: string } | undefined;\n const error = wrapped?.error ?? wrapped;\n if (!error) return;\n const code = error.code ?? '';\n const message = error.message || '';\n this.player.log(`DASH Error - Code: ${code}, Message: ${message}`, 'warn');\n\n if (typeof code === 'number' && code >= 100) {\n this.player.log('Fatal DASH error', 'error');\n this.player.handleError(new Error(`DASH Error: ${code} - ${message}`));\n } else {\n this.player.log('Non-fatal DASH error: ' + (message || String(error)), 'warn');\n }\n }\n\n ensureLoaded() {\n if (!this.player.options.deferLoad) {\n return;\n }\n\n if (!this.dash) {\n return;\n }\n\n if (this._dashSourceLoaded) {\n return;\n }\n\n const src = this._pendingSrc || this.player._pendingSource || this.player.currentSource;\n if (!src) {\n return;\n }\n\n try {\n this.dash.attachSource(src);\n this._dashSourceLoaded = true;\n } catch {\n // ignore\n }\n }\n\n play() {\n const scrollX = window.scrollX;\n const scrollY = window.scrollY;\n\n // Defer-load (legacy code path, kept for completeness): the source is\n // normally attached at init and dash.js is configured with\n // scheduling.scheduleWhilePaused: false to keep prefetch at zero until\n // play. If for any reason the source is still pending here, attach it\n // now. Note that media.play() is called immediately afterwards; on\n // modern dash.js this races cleanly because attachSource() is sync\n // enough for the MediaSource to be ready by the time the play promise\n // resolves, and the configured zero-prefetch settings make the race\n // window very short. The general \"no preload\" UX no longer relies on\n // this branch.\n if (this.player.options.deferLoad && this.dash && !this._dashSourceLoaded) {\n const src = this._pendingSrc || this.player.currentSource;\n if (src) {\n try {\n this.dash.attachSource(src);\n this._dashSourceLoaded = true;\n } catch {\n // ignore and let media.play() surface errors if any\n }\n }\n }\n\n const promise = this.media.play();\n\n window.scrollTo(scrollX, scrollY);\n\n if (promise !== undefined) {\n promise.catch(error => {\n this.player.log('Play failed:', error, 'warn');\n });\n }\n }\n\n pause() {\n // dash.js stops scheduling new fragment downloads once `paused` is true,\n // because we configured `streaming.scheduling.scheduleWhilePaused: false`\n // at init. Already-buffered data stays in the SourceBuffer, so a\n // subsequent play() resumes near-instantly while pause saves bandwidth.\n this.media.pause();\n }\n\n seek(time: number) {\n this.media.currentTime = time;\n }\n\n setVolume(volume: number) {\n this.media.volume = volume;\n }\n\n setMuted(muted: boolean) {\n this.media.muted = muted;\n }\n\n setPlaybackSpeed(speed: number) {\n this.media.playbackRate = speed;\n }\n\n switchQuality(qualityIndex: number) {\n if (!this.dash) return;\n\n if (qualityIndex === -1) {\n // Re-enable ABR auto-switching\n if (typeof this.dash.setAutoSwitchQualityFor === 'function') {\n this.dash.setAutoSwitchQualityFor('video', true);\n } else {\n this.dash.updateSettings({\n streaming: { abr: { autoSwitchBitrate: { video: true } } }\n });\n }\n } else {\n // Disable ABR and lock to the chosen quality\n if (typeof this.dash.setAutoSwitchQualityFor === 'function') {\n this.dash.setAutoSwitchQualityFor('video', false);\n } else {\n this.dash.updateSettings({\n streaming: { abr: { autoSwitchBitrate: { video: false } } }\n });\n }\n\n if (typeof this.dash.setRepresentationForTypeByIndex === 'function') {\n this.dash.setRepresentationForTypeByIndex('video', qualityIndex);\n } else if (typeof this.dash.setQualityFor === 'function') {\n this.dash.setQualityFor('video', qualityIndex, true);\n }\n }\n }\n\n getQualities() {\n if (!this.dash) return [];\n\n try {\n // dash.js v5+: getRepresentationsByType\n let reps = null;\n if (typeof this.dash.getRepresentationsByType === 'function') {\n reps = this.dash.getRepresentationsByType('video');\n }\n\n if (reps && reps.length > 0) {\n const heightCounts: Record<number, number> = {};\n reps.forEach((r: DashRepresentation) => {\n const h = Number(r.height) || 0;\n heightCounts[h] = (heightCounts[h] || 0) + 1;\n });\n\n return reps.map((rep: DashRepresentation, index: number) => {\n const height = Number(rep.height) || 0;\n const bitrate = Number(rep.bandwidth || rep.bitrate) || 0;\n const kb = bitrate > 0 ? Math.round(bitrate / 1000) : 0;\n let name;\n if (height > 0 && heightCounts[height] > 1 && kb > 0) {\n name = `${height}p (${kb} kbps)`;\n } else if (height > 0) {\n name = `${height}p`;\n } else {\n name = kb > 0 ? `${kb} kbps` : 'Auto';\n }\n return {\n index,\n id: rep.id,\n height: rep.height,\n width: rep.width,\n bitrate,\n name\n };\n });\n }\n\n // Fallback: dash.js v4 and earlier\n const bitrateList = this.dash.getBitrateInfoListFor('video');\n if (!bitrateList || bitrateList.length === 0) return [];\n\n const heightCounts: Record<number, number> = {};\n bitrateList.forEach((info: DashBitrateInfo) => {\n const h = Number(info.height) || 0;\n heightCounts[h] = (heightCounts[h] || 0) + 1;\n });\n\n return bitrateList.map((info: DashBitrateInfo, index: number) => {\n const height = Number(info.height) || 0;\n const bitrate = Number(info.bitrate) || 0;\n const kb = bitrate > 0 ? Math.round(bitrate / 1000) : 0;\n let name;\n if (height > 0 && heightCounts[height] > 1 && kb > 0) {\n name = `${height}p (${kb} kbps)`;\n } else if (height > 0) {\n name = `${height}p`;\n } else {\n name = kb > 0 ? `${kb} kbps` : 'Auto';\n }\n return {\n index,\n height: info.height,\n width: info.width,\n bitrate: info.bitrate,\n name\n };\n });\n } catch {\n return [];\n }\n }\n\n getCurrentQuality() {\n if (!this.dash) return -1;\n try {\n if (typeof this.dash.getRepresentationsByType === 'function') {\n const reps = this.dash.getRepresentationsByType('video');\n const current = this.dash.getCurrentRepresentationForType?.('video');\n if (current && reps) {\n const idx = reps.findIndex((r: DashRepresentation) => r.id === current.id);\n if (idx >= 0) return idx;\n }\n }\n return this.dash.getQualityFor('video');\n } catch {\n return -1;\n }\n }\n\n handlesOwnCaptions() {\n return this._dashTextIsTtml;\n }\n\n /**\n * Tell dash.js to activate the text track for `lang` so it begins\n * downloading subtitle segments and populating cues for that language.\n */\n activateTextTrackForLanguage(lang: string): boolean {\n if (!this.dash || !this._dashTextTracks.length || !lang) return false;\n\n let dashIndex = this._dashTextTracks.findIndex(dt => {\n const dtLang = dt.lang || dt.language || dt.srclang || '';\n if (!dtLang) return false;\n return dtLang === lang || dtLang.startsWith(lang) || lang.startsWith(dtLang);\n });\n\n if (dashIndex < 0) {\n dashIndex = this._dashTextTracks.findIndex(dt => {\n const dtLabel = (dt.label || dt.labels || '').toString().toLowerCase();\n return dtLabel.includes(lang.toLowerCase());\n });\n }\n\n if (dashIndex < 0) return false;\n\n this.player.log(`Activating DASH text track index ${dashIndex} for transcript language \"${lang}\"`);\n try {\n this.dash.setTextTrack(dashIndex);\n } catch { /* ignore */ }\n\n // dash.js's text scheduler only fetches segments while the video is\n // playing. When paused, a brief play–pause cycle forces the scheduler\n // to buffer text data for the newly selected track.\n if (this.media.paused) {\n const pos = this.media.currentTime;\n const wasMuted = this.media.muted;\n this.media.muted = true;\n const playPromise = this.media.play();\n const doPause = () => {\n if (this.media && !this.media.paused) {\n this.media.pause();\n this.media.muted = wasMuted;\n // Restore position in case it drifted\n if (Math.abs(this.media.currentTime - pos) > 0.5) {\n this.media.currentTime = pos;\n }\n }\n };\n if (playPromise && typeof playPromise.then === 'function') {\n playPromise.then(() => {\n this._setTimeout(doPause, 250);\n }).catch(() => {\n this.media.muted = wasMuted;\n });\n } else {\n this._setTimeout(doPause, 250);\n }\n }\n\n if (!this._dashTextIsTtml) {\n this._lastKnownCueCount = 0;\n this._startCueUpdatePolling();\n }\n return true;\n }\n\n getTextTrackURLs(): { lang: string; url: string }[] {\n if (!this.dash || !this._manifestUrl) return [];\n try {\n // dash.js manifest objects are loosely shaped (XML-derived), so we\n // walk them through index-keyed records and narrow as we go.\n type ManifestNode = Record<string, unknown>;\n const manifest = this.dash.getManifest?.() as ManifestNode | undefined;\n if (!manifest) return [];\n\n const baseUrl = this._manifestUrl.substring(0, this._manifestUrl.lastIndexOf('/') + 1);\n const results: { lang: string; url: string }[] = [];\n\n const rawPeriods = manifest.Period || manifest.period || manifest.periods || manifest;\n const periods = (Array.isArray(rawPeriods) ? rawPeriods : [rawPeriods]) as ManifestNode[];\n for (const period of periods) {\n const rawAdaptSets = period.AdaptationSet || period.adaptationSet || period.AdaptationSet_asArray || [];\n const adaptSets = (Array.isArray(rawAdaptSets) ? rawAdaptSets : [rawAdaptSets]) as ManifestNode[];\n for (const as of adaptSets) {\n const ct = String(as.contentType || as.ContentType || '');\n const mime = String(as.mimeType || as.MimeType || '');\n if (ct !== 'text' && !/text\\/vtt|application\\/ttml/i.test(mime)) continue;\n\n const lang = String(as.lang || as.language || '');\n const rawReps = as.Representation || as.representation || as.Representation_asArray || [];\n const reps = (Array.isArray(rawReps) ? rawReps : [rawReps]) as ManifestNode[];\n for (const rep of reps) {\n const bu = (rep.BaseURL || rep.baseURL || rep.BaseURL_asArray) as\n | string\n | { __text?: string }\n | Array<string | { __text?: string }>\n | undefined;\n let rawUrl: string | undefined;\n if (Array.isArray(bu)) {\n const first = bu[0];\n rawUrl = typeof first === 'string' ? first : first?.__text;\n } else if (typeof bu === 'string') {\n rawUrl = bu;\n } else {\n rawUrl = bu?.__text;\n }\n if (!rawUrl) continue;\n const url = rawUrl.startsWith('http') ? rawUrl : new URL(rawUrl, baseUrl).href;\n results.push({ lang, url });\n break;\n }\n }\n }\n return results;\n } catch {\n return [];\n }\n }\n\n supportsAutoQuality() {\n return true;\n }\n\n isAutoQuality() {\n if (!this.dash) return true;\n try {\n if (typeof this.dash.getAutoSwitchQualityFor === 'function') {\n return this.dash.getAutoSwitchQualityFor('video');\n }\n const settings = this.dash.getSettings?.();\n return settings?.streaming?.abr?.autoSwitchBitrate?.video !== false;\n } catch {\n return true;\n }\n }\n\n destroy() {\n this._pendingTimeouts.forEach(id => clearTimeout(id));\n this._pendingTimeouts = [];\n this._stopCueUpdatePolling();\n this._lastKnownCueCount = 0;\n\n if (this._captionEnabledHandler) {\n this.player.off('captionsenabled', this._captionEnabledHandler);\n this._captionEnabledHandler = null;\n }\n if (this._captionDisabledHandler) {\n this.player.off('captionsdisabled', this._captionDisabledHandler);\n this._captionDisabledHandler = null;\n }\n\n if (this._ttmlDiv && this._ttmlDiv.parentNode) {\n this._ttmlDiv.parentNode.removeChild(this._ttmlDiv);\n this._ttmlDiv = null;\n }\n\n // Mark all subtitle/caption text tracks as stale. dash.js creates\n // programmatic TextTrack objects that persist on the <video> element\n // after destroy() and cannot be removed via standard APIs. The stale\n // flag prevents _checkSubtitleTracks() in a new renderer instance from\n // treating them as tracks belonging to the current stream.\n const textTracks = this.media.textTracks;\n for (let i = 0; i < textTracks.length; i++) {\n const track = textTracks[i];\n if (track.kind === 'subtitles' || track.kind === 'captions') {\n track._vidplyStale = true;\n track.mode = 'disabled';\n }\n }\n\n if (this.dash) {\n try {\n // Silence dash.js logging during teardown to suppress harmless\n // SourceBuffer errors from in-flight async operations.\n this.dash.updateSettings({ debug: { logLevel: 0 } });\n // reset() cleanly detaches from MediaSource and cancels pending\n // buffer operations before destroy() releases all resources.\n this.dash.reset();\n } catch { /* ignore teardown errors */ }\n try {\n this.dash.destroy();\n } catch { /* ignore teardown errors */ }\n this.dash = null;\n }\n this._dashTextTracks = [];\n this._dashTextIsTtml = false;\n this._manifestUrl = null;\n }\n}\n"],
|
|
4
|
+
"sourcesContent": ["import type { Renderer } from '../types/renderer.js';\nimport type { Player } from '../core/Player.js';\n\ninterface DashTextTrack {\n lang?: string;\n language?: string;\n srclang?: string;\n kind?: string;\n label?: string;\n labels?: string;\n isTTML?: boolean;\n codec?: string;\n mimeType?: string;\n [key: string]: unknown;\n}\n\n/**\n * Shape of payloads emitted by VidPly's `captionsenabled` event when\n * routed into a DASH renderer. These come from CaptionManager and carry\n * both the language and the underlying TextTrack reference.\n */\ninterface CaptionTrackSelection {\n language?: string;\n label?: string;\n track?: TextTrack;\n}\n\ntype CaptionEnabledHandler = (track: CaptionTrackSelection) => void;\ntype CaptionDisabledHandler = () => void;\n\nexport class DASHRenderer implements Renderer {\n player: Player;\n media: HTMLMediaElement;\n dash: DashMediaPlayerInstance | null;\n readonly isStreaming = true;\n _dashSourceLoaded: boolean;\n _pendingSrc: string | null;\n _dashSubtitleTracksCount: number | undefined;\n _dashTextTracks: DashTextTrack[];\n _cueUpdateTimer: ReturnType<typeof setInterval> | null;\n _captionEnabledHandler: CaptionEnabledHandler | null;\n _captionDisabledHandler: CaptionDisabledHandler | null;\n _lastKnownCueCount: number;\n _dashTextIsTtml: boolean;\n _pendingTimeouts: ReturnType<typeof setTimeout>[];\n _ttmlDiv: HTMLElement | null;\n _manifestUrl: string | null;\n\n constructor(player: Player) {\n this.player = player;\n this.media = player.element;\n this.dash = null;\n this._dashSourceLoaded = false;\n this._pendingSrc = null;\n this._dashSubtitleTracksCount = undefined;\n this._dashTextTracks = [];\n this._cueUpdateTimer = null;\n this._captionEnabledHandler = null;\n this._captionDisabledHandler = null;\n this._lastKnownCueCount = 0;\n this._dashTextIsTtml = false;\n this._pendingTimeouts = [];\n this._ttmlDiv = null;\n this._manifestUrl = null;\n }\n\n async init() {\n this.player.log('Using dash.js for DASH support');\n await this.initDashJs();\n }\n\n async initDashJs() {\n this.media.controls = false;\n this.media.removeAttribute('controls');\n\n if (!window.dashjs) {\n await this.loadDashJs();\n }\n\n // Remove <source> children — dash.js manages the src via MSE\n const sourceElements = Array.from(this.media.querySelectorAll('source'));\n let originalSrc = null;\n if (sourceElements.length > 0) {\n originalSrc = sourceElements[0].getAttribute('src');\n sourceElements.forEach(source => source.remove());\n this.player.log('Removed <source> elements for HTML5 validity (dash.js uses MSE)');\n }\n\n const dashjs = window.dashjs;\n if (!dashjs) {\n throw new Error('dash.js not available');\n }\n this.dash = dashjs.MediaPlayer().create();\n\n this.dash.updateSettings({\n debug: {\n logLevel: this.player.options.debug ? 4 : 0\n },\n streaming: {\n // Override dash.js default of 'lowestStartupDelay'. For audio\n // AdaptationSets that tie on selectionPriority and role=main (e.g.\n // Axinom's three en/en-low/en-high tracks), 'lowestStartupDelay'\n // falls through to 'highestEfficiency' which, for audio, has no\n // meaningful pixels-per-bit metric and collapses to \"highest\n // bitrate\". 'firstTrack' respects manifest order instead, which is\n // both predictable and closer to the MPD author's intent.\n selectionModeForInitialTrack: 'firstTrack',\n // NOTE on pre-play preload: we deliberately do NOT set\n // streaming.scheduling.scheduleWhilePaused = false here. While that\n // is the documented dash.js way to suppress segment downloads while\n // paused / before the first play, in our setup (dash.js 5.2.0 +\n // dash.initialize(media, null, false) + attachSource at init) it\n // tears down the SourceBuffers mid-init with\n // \"SourceBuffer has been removed from the parent media source\"\n // exceptions, which leaves the player unable to seek or play. The\n // PR #3785 fix that was supposed to handle the initial-playback /\n // autoPlay=false case is fragile against our usage pattern.\n // Instead we keep dash.js's default scheduling (scheduleWhilePaused\n // stays at its default `true`) and let the buffer caps below limit\n // how much is fetched before play. With a single ~6s segment size,\n // the visible network preload is one init segment per track plus\n // 1–2 media segments — the same \"first two chunks\" behavior the\n // user previously confirmed as acceptable for DASH.\n buffer: {\n bufferTimeAtTopQuality: 30,\n bufferTimeAtTopQualityLongForm: 60,\n // dash.js 5.x: use bufferTimeDefault (replaces removed stableBufferTime).\n // Keep at 12s — going lower (0 / 1) was tested but dash.js still\n // loads the first segment regardless because it's needed to make\n // the MediaSource playable, so the savings are negligible while\n // hurting mid-playback resilience on slow networks.\n bufferTimeDefault: 12,\n bufferToKeep: 20,\n bufferPruningInterval: 10\n },\n retryAttempts: {\n MPD: 4,\n MediaSegment: 6,\n InitializationSegment: 4,\n BitstreamSwitchingSegment: 4\n },\n retryIntervals: {\n MPD: 1000,\n MediaSegment: 1000,\n InitializationSegment: 1000,\n BitstreamSwitchingSegment: 1000\n },\n abr: {\n autoSwitchBitrate: { video: true, audio: true }\n },\n text: {\n defaultEnabled: true\n }\n }\n });\n\n // Create the TTML rendering div before initialize() so the DOM element\n // exists, but attach it to dash.js after initialize() because\n // attachTTMLRenderingDiv() requires attachView() which initialize() calls.\n this._ttmlDiv = document.createElement('div');\n this._ttmlDiv.className = 'vidply-dash-ttml';\n this._ttmlDiv.style.visibility = 'hidden';\n const wrapper = this.player.videoWrapper || this.media.parentElement;\n if (wrapper) {\n wrapper.appendChild(this._ttmlDiv);\n }\n\n this.dash.initialize(this.media, null, false);\n this.dash.attachTTMLRenderingDiv(this._ttmlDiv);\n\n // Resolve source URL\n let src = this.player.currentSource;\n\n if (!src && originalSrc) {\n src = originalSrc;\n }\n\n if (!src) {\n src = this.player.element.getAttribute('data-vidply-src');\n }\n\n if (!src) {\n const elementSrc = this.player.element.getAttribute('src') || this.player.element.src;\n if (elementSrc && !elementSrc.startsWith('blob:')) {\n src = elementSrc;\n }\n }\n\n this.player.log(`Loading DASH source: ${src}`, 'log');\n\n if (!src) {\n throw new Error('No DASH source found');\n }\n\n // Always attach the source at init so the manifest is fetched and the\n // seekbar / duration / quality list are available before the first play.\n // We deliberately don't honor `deferLoad` here: deferring attachSource\n // would leave duration unknown and seek requests unserviceable. The\n // small preload (init segments + ~1 segment of media data) that dash.js\n // does up to bufferTimeDefault is acceptable; in exchange the seekbar\n // becomes functional immediately.\n this._pendingSrc = src;\n this._manifestUrl = src;\n this.dash.attachSource(src);\n this._dashSourceLoaded = true;\n\n // Force the vidply poster overlay even though dash.js will paint the\n // first decoded frame into the <video> element. With MSE attached and\n // init/startup segments appended, the browser ignores the native\n // `poster` attribute and shows the first frame instead. We instead use\n // the `vidply-forced-poster` CSS class, which sets `<video>` to\n // opacity:0 and renders the poster image as a wrapper background — the\n // user sees the artwork until they press play, just like on a fully\n // native HTML5 video. The 'play' handler in Player.ts already calls\n // hidePosterOverlay() on first playback, so we don't need to remove it\n // manually here.\n this.player.showPosterOverlay();\n\n this.attachDashEvents();\n this.attachMediaEvents();\n this._setupCaptionSync();\n }\n\n /**\n * Load dash.js. Pinned to an exact version (the previous default\n * `5.2.0` is preserved) and overridable via `options.dashScriptUrl`\n * (URL) / `options.dashScriptIntegrity` (SRI hash). See\n * HLSRenderer.loadHlsJs() for the SRI computation command.\n */\n async loadDashJs(): Promise<void> {\n const defaultUrl = 'https://cdn.jsdelivr.net/npm/dashjs@5.2.0/dist/modern/umd/dash.all.min.js';\n const url: string = (this.player.options.dashScriptUrl as string | undefined) || defaultUrl;\n const integrity = this.player.options.dashScriptIntegrity as string | undefined;\n\n return new Promise<void>((resolve, reject) => {\n const script = document.createElement('script');\n script.src = url;\n if (integrity) {\n script.integrity = integrity;\n script.crossOrigin = 'anonymous';\n script.referrerPolicy = 'no-referrer';\n }\n script.onload = () => resolve();\n script.onerror = () => reject(new Error('Failed to load dash.js'));\n document.head.appendChild(script);\n });\n }\n\n _setTimeout(fn: () => void, delay: number) {\n const id = setTimeout(() => {\n this._pendingTimeouts = this._pendingTimeouts.filter(t => t !== id);\n fn();\n }, delay);\n this._pendingTimeouts.push(id);\n return id;\n }\n\n attachDashEvents() {\n const dashjs = window.dashjs;\n const dash = this.dash;\n if (!dashjs || !dash) return;\n const dashEvents = dashjs.MediaPlayer.events;\n\n dash.on(dashEvents.MANIFEST_LOADED, (...args: unknown[]) => {\n const e = args[0] as { data?: unknown } | undefined;\n const data = e?.data ?? e;\n this.player.log('DASH manifest loaded');\n this.player.emit('dashmanifestloaded', data);\n\n if (this.player.container) {\n this.player.container.classList.remove('vidply-external-controls');\n }\n\n this._setTimeout(() => {\n this._checkSubtitleTracks();\n }, 500);\n });\n\n dash.on(dashEvents.QUALITY_CHANGE_RENDERED, (...args: unknown[]) => {\n const e = args[0] as { mediaType?: string; newQuality?: number };\n if (e.mediaType === 'video') {\n this.player.log('DASH quality changed to index ' + e.newQuality);\n this.player.emit('dashqualitychanged', e);\n }\n });\n\n dash.on(dashEvents.TEXT_TRACKS_ADDED, (...args: unknown[]) => {\n const e = args[0] as { tracks?: DashTextTrack[] } | undefined;\n const tracks = e?.tracks ?? [];\n this._dashTextTracks = tracks;\n this._dashTextIsTtml = tracks.some((t) =>\n t.isTTML || /stpp|ttml/i.test(t.codec || '') || /ttml/i.test(t.mimeType || '')\n );\n this.player.log(`DASH text tracks added: ${tracks.length} tracks, format: ${this._dashTextIsTtml ? 'TTML' : 'WebVTT'}`);\n this._dashSubtitleTracksCount = tracks.length;\n this.player.emit('dashsubtitletracksupdated', { tracks });\n this.updateCaptionButtonsForDash();\n\n if (tracks.length > 0) {\n try {\n dash.setTextTrack(0);\n } catch {\n // ignore if not ready yet\n }\n if (!this._dashTextIsTtml) {\n this._startCueUpdatePolling();\n }\n }\n });\n\n dash.on(dashEvents.STREAM_INITIALIZED, () => {\n this.player.log('DASH stream initialized');\n this.player.emit('dashstreaminitialized');\n\n this._setTimeout(() => {\n const qualities = this.getQualities();\n if (qualities.length > 0) {\n this.player.emit('dashmanifestparsed', { qualities });\n }\n }, 300);\n });\n\n dash.on(dashEvents.ERROR, (...args: unknown[]) => {\n this.handleDashError(args[0]);\n });\n\n dash.on(dashEvents.FRAGMENT_LOADING_COMPLETED, (...args: unknown[]) => {\n const e = args[0] as { request?: { mediaType?: string } } | undefined;\n this.player.state.buffering = false;\n if (e?.request?.mediaType === 'text' && !this._dashTextIsTtml) {\n this._setTimeout(() => {\n const count = this._getTotalCueCount();\n if (count > this._lastKnownCueCount) {\n this._lastKnownCueCount = count;\n this.player.emit('textcuesupdate');\n }\n }, 100);\n }\n });\n }\n\n /**\n * Count total cues across all subtitle/caption tracks (for WebVTT DASH).\n */\n _getTotalCueCount() {\n const textTracks = this.media.textTracks;\n let total = 0;\n if (!textTracks) return total;\n for (let i = 0; i < textTracks.length; i++) {\n const track = textTracks[i];\n if ((track.kind === 'subtitles' || track.kind === 'captions') && !track._vidplyStale && track.cues) {\n total += track.cues.length;\n }\n }\n return total;\n }\n\n /**\n * Return true if `time` falls inside any TimeRange the SourceBuffer already\n * holds, with a small tolerance to absorb GOP boundaries. Used by the\n * seeking handler to decide whether to surface a 'waiting' event for the\n * spinner UI when the user scrubs while paused.\n */\n _isTimeBuffered(time: number): boolean {\n const buffered = this.media.buffered;\n if (!buffered || buffered.length === 0) return false;\n const tolerance = 0.25;\n for (let i = 0; i < buffered.length; i++) {\n if (time >= buffered.start(i) - tolerance && time <= buffered.end(i) + tolerance) {\n return true;\n }\n }\n return false;\n }\n\n /**\n * Sync VidPly caption track switches with dash.js so it loads\n * subtitle segments for the selected language.\n */\n _setupCaptionSync() {\n this._captionEnabledHandler = (selectedTrack) => {\n if (this._dashTextIsTtml) {\n // dash.js only renders TTML content to the rendering div when the\n // TextTrack mode is 'showing'. CaptionManager sets it to 'hidden',\n // so we override it here. VidPly's own caption overlay is skipped\n // via handlesOwnCaptions().\n if (selectedTrack.track) {\n selectedTrack.track.mode = 'showing';\n }\n if (this._ttmlDiv) {\n this._ttmlDiv.style.visibility = 'visible';\n }\n }\n this._syncDashTextTrack(selectedTrack);\n };\n this._captionDisabledHandler = () => {\n if (this._dashTextIsTtml && this._ttmlDiv) {\n this._ttmlDiv.style.visibility = 'hidden';\n }\n if (this.dash) {\n try { this.dash.setTextTrack(-1); } catch { /* ignore */ }\n }\n };\n this.player.on('captionsenabled', this._captionEnabledHandler);\n this.player.on('captionsdisabled', this._captionDisabledHandler);\n }\n\n /**\n * Map a VidPly caption track to the corresponding dash.js track index\n * and switch dash.js to load segments for that language.\n */\n _syncDashTextTrack(selectedTrack: CaptionTrackSelection) {\n if (!this.dash || !this._dashTextTracks.length) return;\n\n const lang = selectedTrack.language;\n if (!lang) return;\n\n const dashIndex = this._dashTextTracks.findIndex(dt => {\n const dtLang = dt.lang || dt.language || dt.srclang || '';\n if (!dtLang) return false;\n return dtLang === lang || dtLang.startsWith(lang) || lang.startsWith(dtLang);\n });\n\n if (dashIndex >= 0) {\n this.player.log(`Syncing DASH text track to index ${dashIndex} (${lang})`);\n try {\n this.dash.setTextTrack(dashIndex);\n } catch { /* ignore */ }\n if (!this._dashTextIsTtml) {\n this._lastKnownCueCount = 0;\n this._startCueUpdatePolling();\n }\n }\n }\n\n /**\n * Poll for new WebVTT cues being added by dash.js as subtitle segments load.\n * Emits events for transcript refresh when new cues arrive.\n */\n _startCueUpdatePolling() {\n this._stopCueUpdatePolling();\n let prevCueCount = 0;\n let stableRounds = 0;\n\n this._cueUpdateTimer = setInterval(() => {\n const count = this._getTotalCueCount();\n\n if (count > prevCueCount) {\n prevCueCount = count;\n stableRounds = 0;\n this.player.emit('textcuesupdate');\n } else {\n stableRounds++;\n if (stableRounds >= 8) {\n this._stopCueUpdatePolling();\n if (count > 0) {\n this.player.emit('textcuesupdate');\n }\n }\n }\n }, 500);\n }\n\n _stopCueUpdatePolling() {\n if (this._cueUpdateTimer) {\n clearInterval(this._cueUpdateTimer);\n this._cueUpdateTimer = null;\n }\n }\n\n _checkSubtitleTracks() {\n if (this._dashSubtitleTracksCount !== undefined && this._dashSubtitleTracksCount > 0) {\n return;\n }\n const tracks = this.media.textTracks;\n let count = 0;\n for (let i = 0; i < tracks.length; i++) {\n const track = tracks[i];\n if ((track.kind === 'subtitles' || track.kind === 'captions') && !track._vidplyStale) {\n count++;\n }\n }\n this._dashSubtitleTracksCount = count;\n this.updateCaptionButtonsForDash();\n }\n\n updateCaptionButtonsForDash(retryCount = 0) {\n const tracksCount = this._dashSubtitleTracksCount || 0;\n\n const doUpdate = () => {\n this.player.invalidateTrackCache();\n\n if (tracksCount > 0) {\n if (this.player.captionManager) {\n const found = this.player.captionManager.refreshTracks();\n\n // dash.js fires TEXT_TRACKS_ADDED when it knows about the\n // tracks, but the browser TextTrackList may not yet enumerate\n // the new TextTrack objects synchronously. If refreshTracks\n // found nothing, retry a few times with increasing delays.\n if (found === 0 && retryCount < 5) {\n const delay = (retryCount + 1) * 200;\n this.player.log(`DASH caption tracks not yet on video element, retrying in ${delay}ms (attempt ${retryCount + 1})`, 'info');\n this._setTimeout(() => {\n this.updateCaptionButtonsForDash(retryCount + 1);\n }, delay);\n return;\n }\n }\n\n if (!this._dashTextIsTtml && this.player.transcriptManager?.isVisible) {\n this.player.transcriptManager.loadTranscriptData();\n this.player.transcriptManager.updateLanguageSelector();\n }\n\n if (this.player.controlBar) {\n this.player.controlBar.ensureCaptionsButton();\n if (!this._dashTextIsTtml) {\n this.player.controlBar.ensureCaptionStyleButton();\n this.player.controlBar.ensureTranscriptButton();\n }\n }\n } else {\n if (this.player.captionManager) {\n this.player.captionManager.refreshTracks();\n }\n\n if (this.player.transcriptManager?.isVisible) {\n this.player.transcriptManager.hideTranscript();\n }\n\n if (this.player.controlBar) {\n this.player.controlBar.removeHlsCaptionButtons(true);\n }\n }\n };\n\n if (this.player.controlBar) {\n doUpdate();\n return;\n }\n\n const onReady = () => {\n this.player.off('ready', onReady);\n doUpdate();\n };\n this.player.on('ready', onReady);\n }\n\n attachMediaEvents() {\n this.media.addEventListener('loadedmetadata', () => {\n this.player.state.duration = this.media.duration;\n this.player.emit('loadedmetadata');\n });\n\n this.media.addEventListener('durationchange', () => {\n const duration = this.media.duration;\n if (duration && isFinite(duration) && duration > 0) {\n this.player.state.duration = duration;\n this.player.emit('durationchange', duration);\n }\n });\n\n this.media.addEventListener('play', () => {\n this.player.state.playing = true;\n this.player.state.paused = false;\n this.player.state.ended = false;\n this.player.emit('play');\n\n if (this.player.options.onPlay) {\n this.player.options.onPlay.call(this.player);\n }\n });\n\n this.media.addEventListener('pause', () => {\n this.player.state.playing = false;\n this.player.state.paused = true;\n this.player.emit('pause');\n\n if (this.player.options.onPause) {\n this.player.options.onPause.call(this.player);\n }\n });\n\n this.media.addEventListener('ended', () => {\n this.player.state.playing = false;\n this.player.state.paused = true;\n this.player.state.ended = true;\n this.player.emit('ended');\n\n if (this.player.options.onEnded) {\n this.player.options.onEnded.call(this.player);\n }\n\n if (this.player.options.loop) {\n this.player.seek(0);\n this.player.play();\n }\n });\n\n this.media.addEventListener('timeupdate', () => {\n this.player.state.currentTime = this.media.currentTime;\n this.player.emit('timeupdate', this.media.currentTime);\n\n if (this.player.options.onTimeUpdate) {\n this.player.options.onTimeUpdate.call(this.player, this.media.currentTime);\n }\n });\n\n this.media.addEventListener('volumechange', () => {\n this.player.state.volume = this.media.volume;\n this.player.state.muted = this.media.muted;\n this.player.emit('volumechange', this.media.volume);\n });\n\n this.media.addEventListener('seeking', () => {\n this.player.state.seeking = true;\n this.player.emit('seeking');\n\n // Browsers do not fire `waiting` when seeking on a paused media element\n // — there is nothing to \"wait\" for since playback isn't requested. With\n // dash.js + MSE, scrubbing while paused (or before the first play)\n // still triggers a real fragment download, but the spinner stays hidden\n // because no `waiting` event reaches the buffering UI. Detect a seek\n // into an unbuffered range here and surface it as 'waiting' so the\n // spinner appears. Cleared again by the existing `canplay` /\n // `seeked` paths.\n if (!this._isTimeBuffered(this.media.currentTime)) {\n this.player.state.buffering = true;\n this.player.emit('waiting');\n }\n });\n\n this.media.addEventListener('seeked', () => {\n this.player.state.seeking = false;\n this.player.emit('seeked');\n });\n\n this.media.addEventListener('waiting', () => {\n this.player.state.buffering = true;\n this.player.emit('waiting');\n });\n\n this.media.addEventListener('canplay', () => {\n this.player.state.buffering = false;\n this.player.emit('canplay');\n });\n\n this.media.addEventListener('error', () => {\n this.player.handleError(this.media.error);\n });\n }\n\n handleDashError(e: unknown) {\n const wrapped = e as { error?: { code?: number; message?: string }; code?: number; message?: string } | undefined;\n const error = wrapped?.error ?? wrapped;\n if (!error) return;\n const code = error.code ?? '';\n const message = error.message || '';\n this.player.log(`DASH Error - Code: ${code}, Message: ${message}`, 'warn');\n\n if (typeof code === 'number' && code >= 100) {\n this.player.log('Fatal DASH error', 'error');\n this.player.handleError(new Error(`DASH Error: ${code} - ${message}`));\n } else {\n this.player.log('Non-fatal DASH error: ' + (message || String(error)), 'warn');\n }\n }\n\n ensureLoaded() {\n if (!this.player.options.deferLoad) {\n return;\n }\n\n if (!this.dash) {\n return;\n }\n\n if (this._dashSourceLoaded) {\n return;\n }\n\n const src = this._pendingSrc || this.player._pendingSource || this.player.currentSource;\n if (!src) {\n return;\n }\n\n try {\n this.dash.attachSource(src);\n this._dashSourceLoaded = true;\n } catch {\n // ignore\n }\n }\n\n play() {\n const scrollX = window.scrollX;\n const scrollY = window.scrollY;\n\n // Defer-load (legacy code path, kept for completeness): the source is\n // normally attached at init and dash.js is configured with\n // scheduling.scheduleWhilePaused: false to keep prefetch at zero until\n // play. If for any reason the source is still pending here, attach it\n // now. Note that media.play() is called immediately afterwards; on\n // modern dash.js this races cleanly because attachSource() is sync\n // enough for the MediaSource to be ready by the time the play promise\n // resolves, and the configured zero-prefetch settings make the race\n // window very short. The general \"no preload\" UX no longer relies on\n // this branch.\n if (this.player.options.deferLoad && this.dash && !this._dashSourceLoaded) {\n const src = this._pendingSrc || this.player.currentSource;\n if (src) {\n try {\n this.dash.attachSource(src);\n this._dashSourceLoaded = true;\n } catch {\n // ignore and let media.play() surface errors if any\n }\n }\n }\n\n const promise = this.media.play();\n\n window.scrollTo(scrollX, scrollY);\n\n if (promise !== undefined) {\n promise.catch(error => {\n this.player.log('Play failed:', error, 'warn');\n });\n }\n }\n\n pause() {\n // dash.js stops scheduling new fragment downloads once `paused` is true,\n // because we configured `streaming.scheduling.scheduleWhilePaused: false`\n // at init. Already-buffered data stays in the SourceBuffer, so a\n // subsequent play() resumes near-instantly while pause saves bandwidth.\n this.media.pause();\n }\n\n seek(time: number) {\n this.media.currentTime = time;\n }\n\n setVolume(volume: number) {\n this.media.volume = volume;\n }\n\n setMuted(muted: boolean) {\n this.media.muted = muted;\n }\n\n setPlaybackSpeed(speed: number) {\n this.media.playbackRate = speed;\n }\n\n switchQuality(qualityIndex: number) {\n if (!this.dash) return;\n\n if (qualityIndex === -1) {\n // Re-enable ABR auto-switching\n if (typeof this.dash.setAutoSwitchQualityFor === 'function') {\n this.dash.setAutoSwitchQualityFor('video', true);\n } else {\n this.dash.updateSettings({\n streaming: { abr: { autoSwitchBitrate: { video: true } } }\n });\n }\n } else {\n // Disable ABR and lock to the chosen quality\n if (typeof this.dash.setAutoSwitchQualityFor === 'function') {\n this.dash.setAutoSwitchQualityFor('video', false);\n } else {\n this.dash.updateSettings({\n streaming: { abr: { autoSwitchBitrate: { video: false } } }\n });\n }\n\n if (typeof this.dash.setRepresentationForTypeByIndex === 'function') {\n this.dash.setRepresentationForTypeByIndex('video', qualityIndex);\n } else if (typeof this.dash.setQualityFor === 'function') {\n this.dash.setQualityFor('video', qualityIndex, true);\n }\n }\n }\n\n getQualities() {\n if (!this.dash) return [];\n\n try {\n // dash.js v5+: getRepresentationsByType\n let reps = null;\n if (typeof this.dash.getRepresentationsByType === 'function') {\n reps = this.dash.getRepresentationsByType('video');\n }\n\n if (reps && reps.length > 0) {\n const heightCounts: Record<number, number> = {};\n reps.forEach((r: DashRepresentation) => {\n const h = Number(r.height) || 0;\n heightCounts[h] = (heightCounts[h] || 0) + 1;\n });\n\n return reps.map((rep: DashRepresentation, index: number) => {\n const height = Number(rep.height) || 0;\n const bitrate = Number(rep.bandwidth || rep.bitrate) || 0;\n const kb = bitrate > 0 ? Math.round(bitrate / 1000) : 0;\n let name;\n if (height > 0 && heightCounts[height] > 1 && kb > 0) {\n name = `${height}p (${kb} kbps)`;\n } else if (height > 0) {\n name = `${height}p`;\n } else {\n name = kb > 0 ? `${kb} kbps` : 'Auto';\n }\n return {\n index,\n id: rep.id,\n height: rep.height,\n width: rep.width,\n bitrate,\n name\n };\n });\n }\n\n // Fallback: dash.js v4 and earlier\n const bitrateList = this.dash.getBitrateInfoListFor('video');\n if (!bitrateList || bitrateList.length === 0) return [];\n\n const heightCounts: Record<number, number> = {};\n bitrateList.forEach((info: DashBitrateInfo) => {\n const h = Number(info.height) || 0;\n heightCounts[h] = (heightCounts[h] || 0) + 1;\n });\n\n return bitrateList.map((info: DashBitrateInfo, index: number) => {\n const height = Number(info.height) || 0;\n const bitrate = Number(info.bitrate) || 0;\n const kb = bitrate > 0 ? Math.round(bitrate / 1000) : 0;\n let name;\n if (height > 0 && heightCounts[height] > 1 && kb > 0) {\n name = `${height}p (${kb} kbps)`;\n } else if (height > 0) {\n name = `${height}p`;\n } else {\n name = kb > 0 ? `${kb} kbps` : 'Auto';\n }\n return {\n index,\n height: info.height,\n width: info.width,\n bitrate: info.bitrate,\n name\n };\n });\n } catch {\n return [];\n }\n }\n\n getCurrentQuality() {\n if (!this.dash) return -1;\n try {\n if (typeof this.dash.getRepresentationsByType === 'function') {\n const reps = this.dash.getRepresentationsByType('video');\n const current = this.dash.getCurrentRepresentationForType?.('video');\n if (current && reps) {\n const idx = reps.findIndex((r: DashRepresentation) => r.id === current.id);\n if (idx >= 0) return idx;\n }\n }\n return this.dash.getQualityFor('video');\n } catch {\n return -1;\n }\n }\n\n handlesOwnCaptions() {\n return this._dashTextIsTtml;\n }\n\n /**\n * Tell dash.js to activate the text track for `lang` so it begins\n * downloading subtitle segments and populating cues for that language.\n */\n activateTextTrackForLanguage(lang: string): boolean {\n if (!this.dash || !this._dashTextTracks.length || !lang) return false;\n\n let dashIndex = this._dashTextTracks.findIndex(dt => {\n const dtLang = dt.lang || dt.language || dt.srclang || '';\n if (!dtLang) return false;\n return dtLang === lang || dtLang.startsWith(lang) || lang.startsWith(dtLang);\n });\n\n if (dashIndex < 0) {\n dashIndex = this._dashTextTracks.findIndex(dt => {\n const dtLabel = (dt.label || dt.labels || '').toString().toLowerCase();\n return dtLabel.includes(lang.toLowerCase());\n });\n }\n\n if (dashIndex < 0) return false;\n\n this.player.log(`Activating DASH text track index ${dashIndex} for transcript language \"${lang}\"`);\n try {\n this.dash.setTextTrack(dashIndex);\n } catch { /* ignore */ }\n\n // dash.js's text scheduler only fetches segments while the video is\n // playing. When paused, a brief play–pause cycle forces the scheduler\n // to buffer text data for the newly selected track.\n if (this.media.paused) {\n const pos = this.media.currentTime;\n const wasMuted = this.media.muted;\n this.media.muted = true;\n const playPromise = this.media.play();\n const doPause = () => {\n if (this.media && !this.media.paused) {\n this.media.pause();\n this.media.muted = wasMuted;\n // Restore position in case it drifted\n if (Math.abs(this.media.currentTime - pos) > 0.5) {\n this.media.currentTime = pos;\n }\n }\n };\n if (playPromise && typeof playPromise.then === 'function') {\n playPromise.then(() => {\n this._setTimeout(doPause, 250);\n }).catch(() => {\n this.media.muted = wasMuted;\n });\n } else {\n this._setTimeout(doPause, 250);\n }\n }\n\n if (!this._dashTextIsTtml) {\n this._lastKnownCueCount = 0;\n this._startCueUpdatePolling();\n }\n return true;\n }\n\n getTextTrackURLs(): { lang: string; url: string }[] {\n if (!this.dash || !this._manifestUrl) return [];\n try {\n // dash.js manifest objects are loosely shaped (XML-derived), so we\n // walk them through index-keyed records and narrow as we go.\n type ManifestNode = Record<string, unknown>;\n const manifest = this.dash.getManifest?.() as ManifestNode | undefined;\n if (!manifest) return [];\n\n const baseUrl = this._manifestUrl.substring(0, this._manifestUrl.lastIndexOf('/') + 1);\n const results: { lang: string; url: string }[] = [];\n\n const rawPeriods = manifest.Period || manifest.period || manifest.periods || manifest;\n const periods = (Array.isArray(rawPeriods) ? rawPeriods : [rawPeriods]) as ManifestNode[];\n for (const period of periods) {\n const rawAdaptSets = period.AdaptationSet || period.adaptationSet || period.AdaptationSet_asArray || [];\n const adaptSets = (Array.isArray(rawAdaptSets) ? rawAdaptSets : [rawAdaptSets]) as ManifestNode[];\n for (const as of adaptSets) {\n const ct = String(as.contentType || as.ContentType || '');\n const mime = String(as.mimeType || as.MimeType || '');\n if (ct !== 'text' && !/text\\/vtt|application\\/ttml/i.test(mime)) continue;\n\n const lang = String(as.lang || as.language || '');\n const rawReps = as.Representation || as.representation || as.Representation_asArray || [];\n const reps = (Array.isArray(rawReps) ? rawReps : [rawReps]) as ManifestNode[];\n for (const rep of reps) {\n const bu = (rep.BaseURL || rep.baseURL || rep.BaseURL_asArray) as\n | string\n | { __text?: string }\n | Array<string | { __text?: string }>\n | undefined;\n let rawUrl: string | undefined;\n if (Array.isArray(bu)) {\n const first = bu[0];\n rawUrl = typeof first === 'string' ? first : first?.__text;\n } else if (typeof bu === 'string') {\n rawUrl = bu;\n } else {\n rawUrl = bu?.__text;\n }\n if (!rawUrl) continue;\n const url = rawUrl.startsWith('http') ? rawUrl : new URL(rawUrl, baseUrl).href;\n results.push({ lang, url });\n break;\n }\n }\n }\n return results;\n } catch {\n return [];\n }\n }\n\n supportsAutoQuality() {\n return true;\n }\n\n isAutoQuality() {\n if (!this.dash) return true;\n try {\n if (typeof this.dash.getAutoSwitchQualityFor === 'function') {\n return this.dash.getAutoSwitchQualityFor('video');\n }\n const settings = this.dash.getSettings?.();\n return settings?.streaming?.abr?.autoSwitchBitrate?.video !== false;\n } catch {\n return true;\n }\n }\n\n destroy() {\n this._pendingTimeouts.forEach(id => clearTimeout(id));\n this._pendingTimeouts = [];\n this._stopCueUpdatePolling();\n this._lastKnownCueCount = 0;\n\n if (this._captionEnabledHandler) {\n this.player.off('captionsenabled', this._captionEnabledHandler);\n this._captionEnabledHandler = null;\n }\n if (this._captionDisabledHandler) {\n this.player.off('captionsdisabled', this._captionDisabledHandler);\n this._captionDisabledHandler = null;\n }\n\n if (this._ttmlDiv && this._ttmlDiv.parentNode) {\n this._ttmlDiv.parentNode.removeChild(this._ttmlDiv);\n this._ttmlDiv = null;\n }\n\n // Mark all subtitle/caption text tracks as stale. dash.js creates\n // programmatic TextTrack objects that persist on the <video> element\n // after destroy() and cannot be removed via standard APIs. The stale\n // flag prevents _checkSubtitleTracks() in a new renderer instance from\n // treating them as tracks belonging to the current stream.\n const textTracks = this.media.textTracks;\n for (let i = 0; i < textTracks.length; i++) {\n const track = textTracks[i];\n if (track.kind === 'subtitles' || track.kind === 'captions') {\n track._vidplyStale = true;\n track.mode = 'disabled';\n }\n }\n\n if (this.dash) {\n try {\n // Silence dash.js logging during teardown to suppress harmless\n // SourceBuffer errors from in-flight async operations.\n this.dash.updateSettings({ debug: { logLevel: 0 } });\n // reset() cleanly detaches from MediaSource and cancels pending\n // buffer operations before destroy() releases all resources.\n this.dash.reset();\n } catch { /* ignore teardown errors */ }\n try {\n this.dash.destroy();\n } catch { /* ignore teardown errors */ }\n this.dash = null;\n }\n this._dashTextTracks = [];\n this._dashTextIsTtml = false;\n this._manifestUrl = null;\n }\n}\n"],
|
|
5
5
|
"mappings": "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",
|
|
6
6
|
"names": ["heightCounts"]
|
|
7
7
|
}
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* VidPly v1.1.
|
|
2
|
+
* VidPly v1.1.19 - Universal, Accessible Video Player
|
|
3
3
|
* (c) 2026 Matthias Peltzer
|
|
4
4
|
* Released under GPL-2.0-or-later License
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
DraggableResizable
|
|
8
|
-
} from "./vidply.chunk-
|
|
8
|
+
} from "./vidply.chunk-RQGZAPWY.js";
|
|
9
9
|
import {
|
|
10
10
|
createIconElement
|
|
11
|
-
} from "./vidply.chunk-
|
|
11
|
+
} from "./vidply.chunk-MQAA3FKI.js";
|
|
12
12
|
import {
|
|
13
13
|
DOMUtils,
|
|
14
14
|
i18n
|
|
15
|
-
} from "./vidply.chunk-
|
|
15
|
+
} from "./vidply.chunk-KMACPXZ5.js";
|
|
16
16
|
|
|
17
17
|
// src/core/FloatingPlayerManager.ts
|
|
18
18
|
var FLOATING_CLAIM_EVENT = "vidply:floating-claim";
|
|
@@ -43,6 +43,7 @@ var FloatingPlayerManager = class {
|
|
|
43
43
|
_destroyed;
|
|
44
44
|
_triggerFocusEl;
|
|
45
45
|
_claimId;
|
|
46
|
+
_lastAutoExitTime;
|
|
46
47
|
constructor(player) {
|
|
47
48
|
this.player = player;
|
|
48
49
|
this.classPrefix = player.options.classPrefix || "vidply";
|
|
@@ -67,6 +68,7 @@ var FloatingPlayerManager = class {
|
|
|
67
68
|
this._destroyed = false;
|
|
68
69
|
this._triggerFocusEl = null;
|
|
69
70
|
this._claimId = `floating-${player.instanceId}-${Date.now()}`;
|
|
71
|
+
this._lastAutoExitTime = 0;
|
|
70
72
|
this._setupClaimListener();
|
|
71
73
|
this._setupFullscreenGuard();
|
|
72
74
|
this._startObserving();
|
|
@@ -115,6 +117,9 @@ var FloatingPlayerManager = class {
|
|
|
115
117
|
exit(reason = "manual") {
|
|
116
118
|
if (this._destroyed && reason !== "destroy") return;
|
|
117
119
|
if (!this.player.state.floating) return;
|
|
120
|
+
if (reason === "auto") {
|
|
121
|
+
this._lastAutoExitTime = Date.now();
|
|
122
|
+
}
|
|
118
123
|
this._unmountFromShell();
|
|
119
124
|
this._teardownShell();
|
|
120
125
|
const priorTrigger = this._triggerFocusEl;
|
|
@@ -271,7 +276,7 @@ var FloatingPlayerManager = class {
|
|
|
271
276
|
}
|
|
272
277
|
}
|
|
273
278
|
if (this.player.state.floating === "auto") {
|
|
274
|
-
if (entry.intersectionRatio
|
|
279
|
+
if (entry.intersectionRatio >= 0.5) {
|
|
275
280
|
this.exit("auto");
|
|
276
281
|
}
|
|
277
282
|
return;
|
|
@@ -280,6 +285,8 @@ var FloatingPlayerManager = class {
|
|
|
280
285
|
return;
|
|
281
286
|
}
|
|
282
287
|
if (entry.intersectionRatio < 0.1 && this._canFloat("auto")) {
|
|
288
|
+
const AUTO_EXIT_COOLDOWN_MS = 500;
|
|
289
|
+
if (Date.now() - this._lastAutoExitTime < AUTO_EXIT_COOLDOWN_MS) return;
|
|
283
290
|
this.enter("auto");
|
|
284
291
|
}
|
|
285
292
|
}, { threshold: [0, 0.1, 0.5, 0.9] });
|
|
@@ -522,4 +529,4 @@ var FloatingPlayerManager = class {
|
|
|
522
529
|
export {
|
|
523
530
|
FloatingPlayerManager
|
|
524
531
|
};
|
|
525
|
-
//# sourceMappingURL=vidply.FloatingPlayerManager-
|
|
532
|
+
//# sourceMappingURL=vidply.FloatingPlayerManager-HJVGUQLT.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/core/FloatingPlayerManager.ts"],
|
|
4
|
+
"sourcesContent": ["/**\r\n * Floating Player Manager\r\n *\r\n * Custom in-page Picture-in-Picture. Moves the entire player.container\r\n * into a position:fixed, draggable and resizable shell that lives outside\r\n * the document flow (appended to document.body). A transparent placeholder\r\n * preserves layout and, while floating, serves as the IntersectionObserver\r\n * sentinel so scroll-based auto-float/redock decisions stay anchored to\r\n * the original slot.\r\n *\r\n * Triggers:\r\n * - pinned: the user clicks the control-bar PiP button (only when\r\n * options.floating is true; otherwise the button uses native PiP).\r\n * Pinned floating ignores scroll-based docking.\r\n * - auto: the video is playing and the original slot has scrolled out\r\n * of the viewport. Auto-float redocks automatically when the slot\r\n * scrolls back in.\r\n *\r\n * Close button: pauses playback, exits the floating shell and marks the\r\n * current play session as dismissed so auto-float will not re-trigger\r\n * until the user presses play again (matches YouTube miniplayer UX).\r\n *\r\n * Only one player is allowed to float at a time across a page; enter()\r\n * broadcasts a 'vidply:floating-claim' CustomEvent that other managers\r\n * listen for and auto-exit on.\r\n */\r\n\r\nimport { DOMUtils } from '../utils/DOMUtils.js';\r\nimport { DraggableResizable } from '../utils/DraggableResizable.js';\r\nimport { createIconElement } from '../icons/Icons.js';\r\nimport { i18n } from '../i18n/i18n.js';\r\nimport type { Player } from './Player.js';\r\n\r\ntype FloatingState = 'pinned' | 'auto';\r\ntype ExitReason = 'manual' | 'auto' | 'dismiss' | 'claim' | 'destroy';\r\n\r\nconst FLOATING_CLAIM_EVENT = 'vidply:floating-claim';\r\nconst DEFAULT_WIDTH = 400;\r\nconst MIN_WIDTH = 240;\r\nconst EDGE_MARGIN = 16;\r\n\r\ninterface FloatingPrefs {\r\n width?: number;\r\n height?: number;\r\n left?: number;\r\n top?: number;\r\n}\r\n\r\nexport class FloatingPlayerManager {\r\n player: Player;\r\n classPrefix: string;\r\n\r\n shell: HTMLElement | null;\r\n dragHandle: HTMLElement | null;\r\n closeButton: HTMLButtonElement | null;\r\n resizeHandles: HTMLElement[];\r\n placeholder: HTMLElement | null;\r\n draggable: DraggableResizable | null;\r\n\r\n originalParent: HTMLElement | null;\r\n originalNextSibling: Node | null;\r\n\r\n intersectionObserver: IntersectionObserver | null;\r\n observerTarget: HTMLElement | null;\r\n lastRatio: number;\r\n\r\n _autoDismissedThisPlay: boolean;\r\n _playListenerAttached: boolean;\r\n _onPlayAfterDismiss: (() => void) | null;\r\n _onClaim: ((event: Event) => void) | null;\r\n _onResize: (() => void) | null;\r\n _onKeyDown: ((event: KeyboardEvent) => void) | null;\r\n _onEnterFullscreen: (() => void) | null;\r\n _destroyed: boolean;\r\n _triggerFocusEl: HTMLElement | null;\r\n _claimId: string;\r\n _lastAutoExitTime: number;\r\n\r\n constructor(player: Player) {\r\n this.player = player;\r\n this.classPrefix = player.options.classPrefix || 'vidply';\r\n\r\n this.shell = null;\r\n this.dragHandle = null;\r\n this.closeButton = null;\r\n this.resizeHandles = [];\r\n this.placeholder = null;\r\n this.draggable = null;\r\n\r\n this.originalParent = null;\r\n this.originalNextSibling = null;\r\n\r\n this.intersectionObserver = null;\r\n this.observerTarget = null;\r\n this.lastRatio = 1;\r\n\r\n this._autoDismissedThisPlay = false;\r\n this._playListenerAttached = false;\r\n this._onPlayAfterDismiss = null;\r\n this._onClaim = null;\r\n this._onResize = null;\r\n this._onKeyDown = null;\r\n this._onEnterFullscreen = null;\r\n this._destroyed = false;\r\n this._triggerFocusEl = null;\r\n this._claimId = `floating-${player.instanceId}-${Date.now()}`;\r\n this._lastAutoExitTime = 0;\r\n\r\n this._setupClaimListener();\r\n this._setupFullscreenGuard();\r\n this._startObserving();\r\n }\r\n\r\n // ---------------------------------------------------------------\r\n // Public API\r\n // ---------------------------------------------------------------\r\n\r\n togglePinned(triggerEl?: HTMLElement | null) {\r\n if (this._destroyed) return;\r\n if (this.player.state.floating === 'pinned') {\r\n // Manual unpin: suppress auto-float for the remainder of the\r\n // current play session so scrolling doesn't immediately re-pop\r\n // the floating shell. A fresh 'play' event clears the flag.\r\n this._autoDismissedThisPlay = true;\r\n this._armPlayListenerToClearDismiss();\r\n this.exit('manual');\r\n return;\r\n }\r\n // If the user pins while in 'auto' mode, that's an explicit request\r\n // to float; any previous auto-dismiss flag should be cleared so the\r\n // pinned state behaves naturally.\r\n this._autoDismissedThisPlay = false;\r\n this._triggerFocusEl = triggerEl || this._activeElement();\r\n this.enter('pinned');\r\n }\r\n\r\n enter(reason: FloatingState) {\r\n if (this._destroyed) return;\r\n if (this.player.state.floating === reason) return;\r\n\r\n if (!this._canFloat(reason)) {\r\n return;\r\n }\r\n\r\n // Upgrade 'auto' -> 'pinned' without rebuilding the shell\r\n if (this.player.state.floating && this.player.state.floating !== reason) {\r\n this.player.state.floating = reason;\r\n this.player.emit('floatingchange', reason);\r\n return;\r\n }\r\n\r\n this._claimSingleton();\r\n\r\n this._ensureShell();\r\n this._mountIntoShell();\r\n this._applyInitialGeometry();\r\n\r\n this.player.state.floating = reason;\r\n this.player.emit('floatingchange', reason);\r\n\r\n // Focus the close button after a microtask so screen readers announce\r\n // the dialog opening.\r\n queueMicrotask(() => {\r\n if (this.closeButton && this.player.state.floating) {\r\n try { this.closeButton.focus({ preventScroll: true }); } catch { /* ignore */ }\r\n }\r\n });\r\n }\r\n\r\n exit(reason: ExitReason = 'manual') {\r\n if (this._destroyed && reason !== 'destroy') return;\r\n if (!this.player.state.floating) return;\r\n\r\n if (reason === 'auto') {\r\n // Record when an auto-exit occurred so the IntersectionObserver\r\n // callback can apply a cooldown before re-entering auto-float.\r\n this._lastAutoExitTime = Date.now();\r\n }\r\n\r\n this._unmountFromShell();\r\n this._teardownShell();\r\n\r\n const priorTrigger = this._triggerFocusEl;\r\n this._triggerFocusEl = null;\r\n\r\n this.player.state.floating = null;\r\n this.player.emit('floatingchange', null);\r\n\r\n // Restore focus to the element that triggered floating (pinned flow).\r\n // For scroll-based exits we leave focus untouched.\r\n if ((reason === 'manual' || reason === 'dismiss') && priorTrigger) {\r\n try { priorTrigger.focus({ preventScroll: true }); } catch { /* ignore */ }\r\n }\r\n }\r\n\r\n /**\r\n * Close button: pause, dismiss, and prevent auto-float until the next\r\n * user-initiated play event.\r\n */\r\n dismiss() {\r\n if (this._destroyed) return;\r\n this._autoDismissedThisPlay = true;\r\n this._armPlayListenerToClearDismiss();\r\n\r\n try {\r\n this.player.pause();\r\n } catch { /* ignore */ }\r\n\r\n this.exit('dismiss');\r\n }\r\n\r\n destroy() {\r\n if (this._destroyed) return;\r\n this._destroyed = true;\r\n\r\n if (this.player.state && this.player.state.floating) {\r\n try { this.exit('destroy'); } catch { /* ignore */ }\r\n }\r\n\r\n if (this.intersectionObserver) {\r\n try { this.intersectionObserver.disconnect(); } catch { /* ignore */ }\r\n this.intersectionObserver = null;\r\n }\r\n this.observerTarget = null;\r\n\r\n if (this._onClaim) {\r\n window.removeEventListener(FLOATING_CLAIM_EVENT, this._onClaim as EventListener);\r\n this._onClaim = null;\r\n }\r\n if (this._onResize) {\r\n window.removeEventListener('resize', this._onResize);\r\n this._onResize = null;\r\n }\r\n const enterFs = this._onEnterFullscreen;\r\n if (enterFs) {\r\n this.player.off('enterfullscreen', enterFs);\r\n this._onEnterFullscreen = null;\r\n }\r\n const playAfterDismiss = this._onPlayAfterDismiss;\r\n if (playAfterDismiss && this._playListenerAttached) {\r\n this.player.off('play', playAfterDismiss);\r\n this._playListenerAttached = false;\r\n this._onPlayAfterDismiss = null;\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------\r\n // Internal: guards\r\n // ---------------------------------------------------------------\r\n\r\n _canFloat(reason: FloatingState): boolean {\r\n if (!this.player.options.floating) return false;\r\n if (!this.player.container) return false;\r\n if (!this.player.element || this.player.element.tagName !== 'VIDEO') return false;\r\n if (this.player.state.fullscreen) return false;\r\n\r\n // Playlist players move through multiple tracks and manage their own\r\n // UI; floating support for playlists is out of scope for this v1.\r\n if (this.player.playlistManager) return false;\r\n\r\n const minWidth = this.player.options.floatingMinViewportWidth ?? 768;\r\n if (window.innerWidth < minWidth) return false;\r\n\r\n // Auto-float only engages when the user is actively watching.\r\n if (reason === 'auto') {\r\n if (this._autoDismissedThisPlay) return false;\r\n if (this.player.state.paused) return false;\r\n // Never auto-float before the user has started playback; stops\r\n // auto-entry from firing while the page is still below the fold\r\n // on initial load.\r\n if (!this.player.state.hasStartedPlayback) return false;\r\n }\r\n\r\n return true;\r\n }\r\n\r\n _claimSingleton() {\r\n try {\r\n window.dispatchEvent(new CustomEvent(FLOATING_CLAIM_EVENT, {\r\n detail: { claimId: this._claimId }\r\n }));\r\n } catch { /* ignore (older browsers) */ }\r\n }\r\n\r\n _setupClaimListener() {\r\n this._onClaim = (event: Event) => {\r\n const detail = (event as CustomEvent).detail;\r\n if (!detail || detail.claimId === this._claimId) return;\r\n if (this.player.state.floating) {\r\n this.exit('claim');\r\n }\r\n };\r\n const signal = this.player.lifecycleSignal;\r\n window.addEventListener(FLOATING_CLAIM_EVENT, this._onClaim as EventListener, { signal });\r\n\r\n this._onResize = () => {\r\n const minWidth = this.player.options.floatingMinViewportWidth ?? 768;\r\n if (this.player.state.floating && window.innerWidth < minWidth) {\r\n this.exit('auto');\r\n }\r\n };\r\n window.addEventListener('resize', this._onResize, { signal });\r\n }\r\n\r\n _setupFullscreenGuard() {\r\n const onEnterFullscreen = () => {\r\n if (this.player.state.floating) {\r\n this.exit('manual');\r\n }\r\n };\r\n this._onEnterFullscreen = onEnterFullscreen;\r\n this.player.on('enterfullscreen', onEnterFullscreen);\r\n }\r\n\r\n _armPlayListenerToClearDismiss() {\r\n if (this._playListenerAttached) return;\r\n const onPlayAfterDismiss = () => {\r\n this._autoDismissedThisPlay = false;\r\n const handler = this._onPlayAfterDismiss;\r\n if (handler) {\r\n this.player.off('play', handler);\r\n }\r\n this._playListenerAttached = false;\r\n this._onPlayAfterDismiss = null;\r\n };\r\n this._onPlayAfterDismiss = onPlayAfterDismiss;\r\n this.player.on('play', onPlayAfterDismiss);\r\n this._playListenerAttached = true;\r\n }\r\n\r\n // ---------------------------------------------------------------\r\n // Internal: IntersectionObserver for scroll-triggered auto-float\r\n // ---------------------------------------------------------------\r\n\r\n _startObserving() {\r\n if (!('IntersectionObserver' in window)) return;\r\n if (!this.player.container) return;\r\n\r\n this.observerTarget = this.player.container;\r\n this.intersectionObserver = new IntersectionObserver((entries) => {\r\n const entry = entries[entries.length - 1];\r\n if (!entry) return;\r\n this.lastRatio = entry.intersectionRatio;\r\n\r\n if (this.player.options.debug) {\r\n try {\r\n console.log('[vidply:floating] intersection', {\r\n ratio: Number(entry.intersectionRatio.toFixed(3)),\r\n state: this.player.state.floating,\r\n paused: this.player.state.paused,\r\n hasStartedPlayback: this.player.state.hasStartedPlayback,\r\n dismissed: this._autoDismissedThisPlay\r\n });\r\n } catch { /* ignore */ }\r\n }\r\n\r\n if (this.player.state.floating === 'auto') {\r\n if (entry.intersectionRatio >= 0.5) {\r\n this.exit('auto');\r\n }\r\n return;\r\n }\r\n\r\n // Pinned state: scroll is a no-op, the user has taken control.\r\n if (this.player.state.floating === 'pinned') {\r\n return;\r\n }\r\n\r\n if (entry.intersectionRatio < 0.1 && this._canFloat('auto')) {\r\n // Brief cooldown after an auto-exit prevents the layout reflow that\r\n // accompanies the placeholder→container swap from immediately\r\n // re-triggering auto-float (oscillation guard).\r\n const AUTO_EXIT_COOLDOWN_MS = 500;\r\n if (Date.now() - this._lastAutoExitTime < AUTO_EXIT_COOLDOWN_MS) return;\r\n this.enter('auto');\r\n }\r\n }, { threshold: [0, 0.1, 0.5, 0.9] });\r\n\r\n this.intersectionObserver.observe(this.observerTarget);\r\n }\r\n\r\n _retargetObserver(target: HTMLElement) {\r\n if (!this.intersectionObserver) return;\r\n if (this.observerTarget) {\r\n try { this.intersectionObserver.unobserve(this.observerTarget); } catch { /* ignore */ }\r\n }\r\n this.observerTarget = target;\r\n try { this.intersectionObserver.observe(target); } catch { /* ignore */ }\r\n }\r\n\r\n // ---------------------------------------------------------------\r\n // Internal: shell DOM\r\n // ---------------------------------------------------------------\r\n\r\n _ensureShell() {\r\n if (this.shell) return;\r\n\r\n this.shell = DOMUtils.createElement('div', {\r\n className: `${this.classPrefix}-floating-shell`,\r\n attributes: {\r\n 'role': 'dialog',\r\n 'aria-modal': 'false',\r\n 'aria-label': i18n.t('player.floatingPlayer'),\r\n 'data-vidply-floating': 'true',\r\n 'tabindex': '-1'\r\n }\r\n });\r\n\r\n this.dragHandle = DOMUtils.createElement('div', {\r\n className: `${this.classPrefix}-floating-drag-handle`,\r\n attributes: { 'aria-hidden': 'true' }\r\n });\r\n this.shell.appendChild(this.dragHandle);\r\n\r\n this.closeButton = DOMUtils.createElement('button', {\r\n className: `${this.classPrefix}-floating-close`,\r\n attributes: {\r\n 'type': 'button',\r\n 'aria-label': i18n.t('player.floatingPlayerClose'),\r\n 'title': i18n.t('player.floatingPlayerClose')\r\n }\r\n }) as HTMLButtonElement;\r\n this.closeButton.appendChild(createIconElement('close'));\r\n this.closeButton.addEventListener('click', (event) => {\r\n event.stopPropagation();\r\n this.dismiss();\r\n });\r\n this.shell.appendChild(this.closeButton);\r\n\r\n this._createResizeHandles();\r\n const shell = this.shell;\r\n this.resizeHandles.forEach(handle => shell.appendChild(handle));\r\n\r\n this._onKeyDown = (event: KeyboardEvent) => {\r\n if (event.key === 'Escape') {\r\n event.stopPropagation();\r\n this.dismiss();\r\n }\r\n };\r\n this.shell.addEventListener('keydown', this._onKeyDown as EventListener);\r\n }\r\n\r\n _createResizeHandles() {\r\n // Resize handles are always-on for the floating shell. Do NOT set\r\n // `data-vidply-managed-resize=\"true\"`: that flag is used by the\r\n // transcript / sign-language overlays to gate the handles behind\r\n // an \"R\" keypress, and DraggableResizable hides managed handles\r\n // during init() — which would leave the shell with no visible or\r\n // hittable edges/corners.\r\n const dirs = ['n', 's', 'e', 'w', 'ne', 'nw', 'se', 'sw'];\r\n this.resizeHandles = dirs.map(dir => DOMUtils.createElement('div', {\r\n className: `${this.classPrefix}-floating-resize-handle ${this.classPrefix}-floating-resize-${dir}`,\r\n attributes: {\r\n 'data-direction': dir,\r\n 'aria-hidden': 'true'\r\n }\r\n }));\r\n }\r\n\r\n _teardownShell() {\r\n if (this.draggable) {\r\n try { this.draggable.destroy(); } catch { /* ignore */ }\r\n this.draggable = null;\r\n }\r\n if (this.shell) {\r\n if (this._onKeyDown) {\r\n this.shell.removeEventListener('keydown', this._onKeyDown as EventListener);\r\n this._onKeyDown = null;\r\n }\r\n if (this.shell.parentNode) {\r\n this.shell.parentNode.removeChild(this.shell);\r\n }\r\n }\r\n this.shell = null;\r\n this.dragHandle = null;\r\n this.closeButton = null;\r\n this.resizeHandles = [];\r\n }\r\n\r\n // ---------------------------------------------------------------\r\n // Internal: mount / unmount the player.container\r\n // ---------------------------------------------------------------\r\n\r\n _mountIntoShell() {\r\n const container = this.player.container;\r\n if (!container || !container.parentNode) return;\r\n if (!this.shell) return;\r\n\r\n const rect = container.getBoundingClientRect();\r\n this.originalParent = container.parentNode as HTMLElement;\r\n this.originalNextSibling = container.nextSibling;\r\n\r\n this.placeholder = DOMUtils.createElement('div', {\r\n className: `${this.classPrefix}-floating-placeholder`,\r\n attributes: { 'aria-hidden': 'true' }\r\n });\r\n this.placeholder.style.width = `${Math.max(1, rect.width)}px`;\r\n this.placeholder.style.height = `${Math.max(1, rect.height)}px`;\r\n\r\n // Centred PiP glyph that signals \"video popped out\" in the slot the\r\n // player used to occupy. Lives as a regular child so it can inherit\r\n // currentColor and scale with the placeholder; both the icon and\r\n // the placeholder itself are removed wholesale on _unmountFromShell().\r\n const placeholderIcon = createIconElement('pip', `${this.classPrefix}-floating-placeholder-icon`);\r\n this.placeholder.appendChild(placeholderIcon);\r\n\r\n this.originalParent.insertBefore(this.placeholder, container);\r\n\r\n this.shell.appendChild(container);\r\n document.body.appendChild(this.shell);\r\n\r\n container.classList.add(`${this.classPrefix}-is-floating`);\r\n\r\n // Observe the placeholder while floating so scroll-back decisions are\r\n // anchored to the original slot instead of the moved container.\r\n this._retargetObserver(this.placeholder);\r\n }\r\n\r\n _unmountFromShell() {\r\n const container = this.player.container;\r\n\r\n if (container) {\r\n container.classList.remove(`${this.classPrefix}-is-floating`);\r\n // Clear inline sizes applied while floating\r\n container.style.removeProperty('width');\r\n container.style.removeProperty('height');\r\n }\r\n\r\n if (this.placeholder && this.placeholder.parentNode) {\r\n if (container) {\r\n this.placeholder.parentNode.insertBefore(container, this.placeholder);\r\n }\r\n this.placeholder.parentNode.removeChild(this.placeholder);\r\n } else if (container && this.originalParent) {\r\n if (this.originalNextSibling && this.originalNextSibling.parentNode === this.originalParent) {\r\n this.originalParent.insertBefore(container, this.originalNextSibling);\r\n } else {\r\n this.originalParent.appendChild(container);\r\n }\r\n }\r\n\r\n this.placeholder = null;\r\n this.originalParent = null;\r\n this.originalNextSibling = null;\r\n\r\n // Retarget the observer to the docked container\r\n if (container) {\r\n this._retargetObserver(container);\r\n }\r\n }\r\n\r\n // ---------------------------------------------------------------\r\n // Internal: initial geometry + drag/resize wiring\r\n // ---------------------------------------------------------------\r\n\r\n _applyInitialGeometry() {\r\n if (!this.shell) return;\r\n\r\n const prefs = (this.player.storage?.getFloatingPreferences?.() || {}) as FloatingPrefs;\r\n\r\n const vw = window.innerWidth;\r\n const vh = window.innerHeight;\r\n\r\n let width = prefs.width && prefs.width >= MIN_WIDTH ? prefs.width : DEFAULT_WIDTH;\r\n width = Math.min(width, Math.max(MIN_WIDTH, vw - EDGE_MARGIN * 2));\r\n\r\n // If we know the source container's aspect ratio, match it by default.\r\n const containerRect = this.player.container?.getBoundingClientRect();\r\n const aspect = containerRect && containerRect.height > 0\r\n ? containerRect.width / containerRect.height\r\n : 16 / 9;\r\n const defaultHeight = Math.round(width / aspect);\r\n let height = prefs.height && prefs.height >= 100 ? prefs.height : defaultHeight;\r\n height = Math.min(height, Math.max(100, vh - EDGE_MARGIN * 2));\r\n\r\n let left: number;\r\n let top: number;\r\n\r\n if (typeof prefs.left === 'number' && typeof prefs.top === 'number') {\r\n left = Math.max(EDGE_MARGIN, Math.min(prefs.left, vw - width - EDGE_MARGIN));\r\n top = Math.max(EDGE_MARGIN, Math.min(prefs.top, vh - height - EDGE_MARGIN));\r\n } else {\r\n const pos = this.player.options.floatingPosition || 'bottom-right';\r\n switch (pos) {\r\n case 'bottom-left':\r\n left = EDGE_MARGIN;\r\n top = vh - height - EDGE_MARGIN;\r\n break;\r\n case 'top-right':\r\n left = vw - width - EDGE_MARGIN;\r\n top = EDGE_MARGIN;\r\n break;\r\n case 'top-left':\r\n left = EDGE_MARGIN;\r\n top = EDGE_MARGIN;\r\n break;\r\n case 'bottom-right':\r\n default:\r\n left = vw - width - EDGE_MARGIN;\r\n top = vh - height - EDGE_MARGIN;\r\n break;\r\n }\r\n }\r\n\r\n this.shell.style.width = `${width}px`;\r\n this.shell.style.height = `${height}px`;\r\n this.shell.style.left = `${left}px`;\r\n this.shell.style.top = `${top}px`;\r\n\r\n this._initDraggable();\r\n }\r\n\r\n _initDraggable() {\r\n if (!this.shell) return;\r\n if (this.draggable) return;\r\n\r\n this.draggable = new DraggableResizable(this.shell, {\r\n dragHandle: this.dragHandle,\r\n resizeHandles: this.resizeHandles,\r\n constrainToViewport: true,\r\n maintainAspectRatio: true,\r\n minWidth: MIN_WIDTH,\r\n minHeight: 100,\r\n maxWidth: () => Math.max(MIN_WIDTH, window.innerWidth - EDGE_MARGIN * 2),\r\n maxHeight: () => Math.max(100, window.innerHeight - EDGE_MARGIN * 2),\r\n classPrefix: `${this.classPrefix}-floating`,\r\n keyboardDragKey: 'd',\r\n keyboardResizeKey: 'r',\r\n keyboardStep: 10,\r\n keyboardStepLarge: 50,\r\n pointerResizeIndicatorText: i18n.t('player.floatingPlayerDialog'),\r\n onDragEnd: () => this._savePrefs(),\r\n onResizeEnd: () => this._savePrefs(),\r\n onDragStart: (event: Event) => {\r\n const target = event.target as HTMLElement;\r\n if (!target) return true;\r\n // Don't start drag when interacting with the close button or\r\n // with any control inside the embedded player (play/pause,\r\n // captions menu, etc.).\r\n if (target.closest(`.${this.classPrefix}-floating-close`)) return false;\r\n if (target.closest(`.${this.classPrefix}-controls`)) return false;\r\n if (target.closest(`.${this.classPrefix}-floating-resize-handle`)) return false;\r\n return true;\r\n }\r\n });\r\n }\r\n\r\n _savePrefs() {\r\n if (!this.shell || !this.player.storage?.saveFloatingPreferences) return;\r\n const rect = this.shell.getBoundingClientRect();\r\n this.player.storage.saveFloatingPreferences({\r\n width: Math.round(rect.width),\r\n height: Math.round(rect.height),\r\n left: Math.round(rect.left),\r\n top: Math.round(rect.top)\r\n });\r\n }\r\n\r\n _activeElement(): HTMLElement | null {\r\n const active = document.activeElement;\r\n return active && active instanceof HTMLElement ? active : null;\r\n }\r\n}\r\n"],
|
|
5
|
+
"mappings": ";;;;;;;;;;;;;;;;;AAoCA,IAAM,uBAAuB;AAC7B,IAAM,gBAAgB;AACtB,IAAM,YAAY;AAClB,IAAM,cAAc;AASb,IAAM,wBAAN,MAA4B;AAAA,EAC/B;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEA,YAAY,QAAgB;AACxB,SAAK,SAAS;AACd,SAAK,cAAc,OAAO,QAAQ,eAAe;AAEjD,SAAK,QAAQ;AACb,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,gBAAgB,CAAC;AACtB,SAAK,cAAc;AACnB,SAAK,YAAY;AAEjB,SAAK,iBAAiB;AACtB,SAAK,sBAAsB;AAE3B,SAAK,uBAAuB;AAC5B,SAAK,iBAAiB;AACtB,SAAK,YAAY;AAEjB,SAAK,yBAAyB;AAC9B,SAAK,wBAAwB;AAC7B,SAAK,sBAAsB;AAC3B,SAAK,WAAW;AAChB,SAAK,YAAY;AACjB,SAAK,aAAa;AAClB,SAAK,qBAAqB;AAC1B,SAAK,aAAa;AAClB,SAAK,kBAAkB;AACvB,SAAK,WAAW,YAAY,OAAO,UAAU,IAAI,KAAK,IAAI,CAAC;AAC3D,SAAK,oBAAoB;AAEzB,SAAK,oBAAoB;AACzB,SAAK,sBAAsB;AAC3B,SAAK,gBAAgB;AAAA,EACzB;AAAA;AAAA;AAAA;AAAA,EAMA,aAAa,WAAgC;AACzC,QAAI,KAAK,WAAY;AACrB,QAAI,KAAK,OAAO,MAAM,aAAa,UAAU;AAIzC,WAAK,yBAAyB;AAC9B,WAAK,+BAA+B;AACpC,WAAK,KAAK,QAAQ;AAClB;AAAA,IACJ;AAIA,SAAK,yBAAyB;AAC9B,SAAK,kBAAkB,aAAa,KAAK,eAAe;AACxD,SAAK,MAAM,QAAQ;AAAA,EACvB;AAAA,EAEA,MAAM,QAAuB;AACzB,QAAI,KAAK,WAAY;AACrB,QAAI,KAAK,OAAO,MAAM,aAAa,OAAQ;AAE3C,QAAI,CAAC,KAAK,UAAU,MAAM,GAAG;AACzB;AAAA,IACJ;AAGA,QAAI,KAAK,OAAO,MAAM,YAAY,KAAK,OAAO,MAAM,aAAa,QAAQ;AACrE,WAAK,OAAO,MAAM,WAAW;AAC7B,WAAK,OAAO,KAAK,kBAAkB,MAAM;AACzC;AAAA,IACJ;AAEA,SAAK,gBAAgB;AAErB,SAAK,aAAa;AAClB,SAAK,gBAAgB;AACrB,SAAK,sBAAsB;AAE3B,SAAK,OAAO,MAAM,WAAW;AAC7B,SAAK,OAAO,KAAK,kBAAkB,MAAM;AAIzC,mBAAe,MAAM;AACjB,UAAI,KAAK,eAAe,KAAK,OAAO,MAAM,UAAU;AAChD,YAAI;AAAE,eAAK,YAAY,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,QAAG,QAAQ;AAAA,QAAe;AAAA,MAClF;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EAEA,KAAK,SAAqB,UAAU;AAChC,QAAI,KAAK,cAAc,WAAW,UAAW;AAC7C,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU;AAEjC,QAAI,WAAW,QAAQ;AAGnB,WAAK,oBAAoB,KAAK,IAAI;AAAA,IACtC;AAEA,SAAK,kBAAkB;AACvB,SAAK,eAAe;AAEpB,UAAM,eAAe,KAAK;AAC1B,SAAK,kBAAkB;AAEvB,SAAK,OAAO,MAAM,WAAW;AAC7B,SAAK,OAAO,KAAK,kBAAkB,IAAI;AAIvC,SAAK,WAAW,YAAY,WAAW,cAAc,cAAc;AAC/D,UAAI;AAAE,qBAAa,MAAM,EAAE,eAAe,KAAK,CAAC;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IAC9E;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU;AACN,QAAI,KAAK,WAAY;AACrB,SAAK,yBAAyB;AAC9B,SAAK,+BAA+B;AAEpC,QAAI;AACA,WAAK,OAAO,MAAM;AAAA,IACtB,QAAQ;AAAA,IAAe;AAEvB,SAAK,KAAK,SAAS;AAAA,EACvB;AAAA,EAEA,UAAU;AACN,QAAI,KAAK,WAAY;AACrB,SAAK,aAAa;AAElB,QAAI,KAAK,OAAO,SAAS,KAAK,OAAO,MAAM,UAAU;AACjD,UAAI;AAAE,aAAK,KAAK,SAAS;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IACvD;AAEA,QAAI,KAAK,sBAAsB;AAC3B,UAAI;AAAE,aAAK,qBAAqB,WAAW;AAAA,MAAG,QAAQ;AAAA,MAAe;AACrE,WAAK,uBAAuB;AAAA,IAChC;AACA,SAAK,iBAAiB;AAEtB,QAAI,KAAK,UAAU;AACf,aAAO,oBAAoB,sBAAsB,KAAK,QAAyB;AAC/E,WAAK,WAAW;AAAA,IACpB;AACA,QAAI,KAAK,WAAW;AAChB,aAAO,oBAAoB,UAAU,KAAK,SAAS;AACnD,WAAK,YAAY;AAAA,IACrB;AACA,UAAM,UAAU,KAAK;AACrB,QAAI,SAAS;AACT,WAAK,OAAO,IAAI,mBAAmB,OAAO;AAC1C,WAAK,qBAAqB;AAAA,IAC9B;AACA,UAAM,mBAAmB,KAAK;AAC9B,QAAI,oBAAoB,KAAK,uBAAuB;AAChD,WAAK,OAAO,IAAI,QAAQ,gBAAgB;AACxC,WAAK,wBAAwB;AAC7B,WAAK,sBAAsB;AAAA,IAC/B;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAMA,UAAU,QAAgC;AACtC,QAAI,CAAC,KAAK,OAAO,QAAQ,SAAU,QAAO;AAC1C,QAAI,CAAC,KAAK,OAAO,UAAW,QAAO;AACnC,QAAI,CAAC,KAAK,OAAO,WAAW,KAAK,OAAO,QAAQ,YAAY,QAAS,QAAO;AAC5E,QAAI,KAAK,OAAO,MAAM,WAAY,QAAO;AAIzC,QAAI,KAAK,OAAO,gBAAiB,QAAO;AAExC,UAAM,WAAW,KAAK,OAAO,QAAQ,4BAA4B;AACjE,QAAI,OAAO,aAAa,SAAU,QAAO;AAGzC,QAAI,WAAW,QAAQ;AACnB,UAAI,KAAK,uBAAwB,QAAO;AACxC,UAAI,KAAK,OAAO,MAAM,OAAQ,QAAO;AAIrC,UAAI,CAAC,KAAK,OAAO,MAAM,mBAAoB,QAAO;AAAA,IACtD;AAEA,WAAO;AAAA,EACX;AAAA,EAEA,kBAAkB;AACd,QAAI;AACA,aAAO,cAAc,IAAI,YAAY,sBAAsB;AAAA,QACvD,QAAQ,EAAE,SAAS,KAAK,SAAS;AAAA,MACrC,CAAC,CAAC;AAAA,IACN,QAAQ;AAAA,IAAgC;AAAA,EAC5C;AAAA,EAEA,sBAAsB;AAClB,SAAK,WAAW,CAAC,UAAiB;AAC9B,YAAM,SAAU,MAAsB;AACtC,UAAI,CAAC,UAAU,OAAO,YAAY,KAAK,SAAU;AACjD,UAAI,KAAK,OAAO,MAAM,UAAU;AAC5B,aAAK,KAAK,OAAO;AAAA,MACrB;AAAA,IACJ;AACA,UAAM,SAAS,KAAK,OAAO;AAC3B,WAAO,iBAAiB,sBAAsB,KAAK,UAA2B,EAAE,OAAO,CAAC;AAExF,SAAK,YAAY,MAAM;AACnB,YAAM,WAAW,KAAK,OAAO,QAAQ,4BAA4B;AACjE,UAAI,KAAK,OAAO,MAAM,YAAY,OAAO,aAAa,UAAU;AAC5D,aAAK,KAAK,MAAM;AAAA,MACpB;AAAA,IACJ;AACA,WAAO,iBAAiB,UAAU,KAAK,WAAW,EAAE,OAAO,CAAC;AAAA,EAChE;AAAA,EAEA,wBAAwB;AACpB,UAAM,oBAAoB,MAAM;AAC5B,UAAI,KAAK,OAAO,MAAM,UAAU;AAC5B,aAAK,KAAK,QAAQ;AAAA,MACtB;AAAA,IACJ;AACA,SAAK,qBAAqB;AAC1B,SAAK,OAAO,GAAG,mBAAmB,iBAAiB;AAAA,EACvD;AAAA,EAEA,iCAAiC;AAC7B,QAAI,KAAK,sBAAuB;AAChC,UAAM,qBAAqB,MAAM;AAC7B,WAAK,yBAAyB;AAC9B,YAAM,UAAU,KAAK;AACrB,UAAI,SAAS;AACT,aAAK,OAAO,IAAI,QAAQ,OAAO;AAAA,MACnC;AACA,WAAK,wBAAwB;AAC7B,WAAK,sBAAsB;AAAA,IAC/B;AACA,SAAK,sBAAsB;AAC3B,SAAK,OAAO,GAAG,QAAQ,kBAAkB;AACzC,SAAK,wBAAwB;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB;AACd,QAAI,EAAE,0BAA0B,QAAS;AACzC,QAAI,CAAC,KAAK,OAAO,UAAW;AAE5B,SAAK,iBAAiB,KAAK,OAAO;AAClC,SAAK,uBAAuB,IAAI,qBAAqB,CAAC,YAAY;AAC9D,YAAM,QAAQ,QAAQ,QAAQ,SAAS,CAAC;AACxC,UAAI,CAAC,MAAO;AACZ,WAAK,YAAY,MAAM;AAEvB,UAAI,KAAK,OAAO,QAAQ,OAAO;AAC3B,YAAI;AACA,kBAAQ,IAAI,kCAAkC;AAAA,YAC1C,OAAO,OAAO,MAAM,kBAAkB,QAAQ,CAAC,CAAC;AAAA,YAChD,OAAO,KAAK,OAAO,MAAM;AAAA,YACzB,QAAQ,KAAK,OAAO,MAAM;AAAA,YAC1B,oBAAoB,KAAK,OAAO,MAAM;AAAA,YACtC,WAAW,KAAK;AAAA,UACpB,CAAC;AAAA,QACL,QAAQ;AAAA,QAAe;AAAA,MAC3B;AAEA,UAAI,KAAK,OAAO,MAAM,aAAa,QAAQ;AACvC,YAAI,MAAM,qBAAqB,KAAK;AAChC,eAAK,KAAK,MAAM;AAAA,QACpB;AACA;AAAA,MACJ;AAGA,UAAI,KAAK,OAAO,MAAM,aAAa,UAAU;AACzC;AAAA,MACJ;AAEA,UAAI,MAAM,oBAAoB,OAAO,KAAK,UAAU,MAAM,GAAG;AAIzD,cAAM,wBAAwB;AAC9B,YAAI,KAAK,IAAI,IAAI,KAAK,oBAAoB,sBAAuB;AACjE,aAAK,MAAM,MAAM;AAAA,MACrB;AAAA,IACJ,GAAG,EAAE,WAAW,CAAC,GAAG,KAAK,KAAK,GAAG,EAAE,CAAC;AAEpC,SAAK,qBAAqB,QAAQ,KAAK,cAAc;AAAA,EACzD;AAAA,EAEA,kBAAkB,QAAqB;AACnC,QAAI,CAAC,KAAK,qBAAsB;AAChC,QAAI,KAAK,gBAAgB;AACrB,UAAI;AAAE,aAAK,qBAAqB,UAAU,KAAK,cAAc;AAAA,MAAG,QAAQ;AAAA,MAAe;AAAA,IAC3F;AACA,SAAK,iBAAiB;AACtB,QAAI;AAAE,WAAK,qBAAqB,QAAQ,MAAM;AAAA,IAAG,QAAQ;AAAA,IAAe;AAAA,EAC5E;AAAA;AAAA;AAAA;AAAA,EAMA,eAAe;AACX,QAAI,KAAK,MAAO;AAEhB,SAAK,QAAQ,SAAS,cAAc,OAAO;AAAA,MACvC,WAAW,GAAG,KAAK,WAAW;AAAA,MAC9B,YAAY;AAAA,QACR,QAAQ;AAAA,QACR,cAAc;AAAA,QACd,cAAc,KAAK,EAAE,uBAAuB;AAAA,QAC5C,wBAAwB;AAAA,QACxB,YAAY;AAAA,MAChB;AAAA,IACJ,CAAC;AAED,SAAK,aAAa,SAAS,cAAc,OAAO;AAAA,MAC5C,WAAW,GAAG,KAAK,WAAW;AAAA,MAC9B,YAAY,EAAE,eAAe,OAAO;AAAA,IACxC,CAAC;AACD,SAAK,MAAM,YAAY,KAAK,UAAU;AAEtC,SAAK,cAAc,SAAS,cAAc,UAAU;AAAA,MAChD,WAAW,GAAG,KAAK,WAAW;AAAA,MAC9B,YAAY;AAAA,QACR,QAAQ;AAAA,QACR,cAAc,KAAK,EAAE,4BAA4B;AAAA,QACjD,SAAS,KAAK,EAAE,4BAA4B;AAAA,MAChD;AAAA,IACJ,CAAC;AACD,SAAK,YAAY,YAAY,kBAAkB,OAAO,CAAC;AACvD,SAAK,YAAY,iBAAiB,SAAS,CAAC,UAAU;AAClD,YAAM,gBAAgB;AACtB,WAAK,QAAQ;AAAA,IACjB,CAAC;AACD,SAAK,MAAM,YAAY,KAAK,WAAW;AAEvC,SAAK,qBAAqB;AAC1B,UAAM,QAAQ,KAAK;AACnB,SAAK,cAAc,QAAQ,YAAU,MAAM,YAAY,MAAM,CAAC;AAE9D,SAAK,aAAa,CAAC,UAAyB;AACxC,UAAI,MAAM,QAAQ,UAAU;AACxB,cAAM,gBAAgB;AACtB,aAAK,QAAQ;AAAA,MACjB;AAAA,IACJ;AACA,SAAK,MAAM,iBAAiB,WAAW,KAAK,UAA2B;AAAA,EAC3E;AAAA,EAEA,uBAAuB;AAOnB,UAAM,OAAO,CAAC,KAAK,KAAK,KAAK,KAAK,MAAM,MAAM,MAAM,IAAI;AACxD,SAAK,gBAAgB,KAAK,IAAI,SAAO,SAAS,cAAc,OAAO;AAAA,MAC/D,WAAW,GAAG,KAAK,WAAW,2BAA2B,KAAK,WAAW,oBAAoB,GAAG;AAAA,MAChG,YAAY;AAAA,QACR,kBAAkB;AAAA,QAClB,eAAe;AAAA,MACnB;AAAA,IACJ,CAAC,CAAC;AAAA,EACN;AAAA,EAEA,iBAAiB;AACb,QAAI,KAAK,WAAW;AAChB,UAAI;AAAE,aAAK,UAAU,QAAQ;AAAA,MAAG,QAAQ;AAAA,MAAe;AACvD,WAAK,YAAY;AAAA,IACrB;AACA,QAAI,KAAK,OAAO;AACZ,UAAI,KAAK,YAAY;AACjB,aAAK,MAAM,oBAAoB,WAAW,KAAK,UAA2B;AAC1E,aAAK,aAAa;AAAA,MACtB;AACA,UAAI,KAAK,MAAM,YAAY;AACvB,aAAK,MAAM,WAAW,YAAY,KAAK,KAAK;AAAA,MAChD;AAAA,IACJ;AACA,SAAK,QAAQ;AACb,SAAK,aAAa;AAClB,SAAK,cAAc;AACnB,SAAK,gBAAgB,CAAC;AAAA,EAC1B;AAAA;AAAA;AAAA;AAAA,EAMA,kBAAkB;AACd,UAAM,YAAY,KAAK,OAAO;AAC9B,QAAI,CAAC,aAAa,CAAC,UAAU,WAAY;AACzC,QAAI,CAAC,KAAK,MAAO;AAEjB,UAAM,OAAO,UAAU,sBAAsB;AAC7C,SAAK,iBAAiB,UAAU;AAChC,SAAK,sBAAsB,UAAU;AAErC,SAAK,cAAc,SAAS,cAAc,OAAO;AAAA,MAC7C,WAAW,GAAG,KAAK,WAAW;AAAA,MAC9B,YAAY,EAAE,eAAe,OAAO;AAAA,IACxC,CAAC;AACD,SAAK,YAAY,MAAM,QAAQ,GAAG,KAAK,IAAI,GAAG,KAAK,KAAK,CAAC;AACzD,SAAK,YAAY,MAAM,SAAS,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,CAAC;AAM3D,UAAM,kBAAkB,kBAAkB,OAAO,GAAG,KAAK,WAAW,4BAA4B;AAChG,SAAK,YAAY,YAAY,eAAe;AAE5C,SAAK,eAAe,aAAa,KAAK,aAAa,SAAS;AAE5D,SAAK,MAAM,YAAY,SAAS;AAChC,aAAS,KAAK,YAAY,KAAK,KAAK;AAEpC,cAAU,UAAU,IAAI,GAAG,KAAK,WAAW,cAAc;AAIzD,SAAK,kBAAkB,KAAK,WAAW;AAAA,EAC3C;AAAA,EAEA,oBAAoB;AAChB,UAAM,YAAY,KAAK,OAAO;AAE9B,QAAI,WAAW;AACX,gBAAU,UAAU,OAAO,GAAG,KAAK,WAAW,cAAc;AAE5D,gBAAU,MAAM,eAAe,OAAO;AACtC,gBAAU,MAAM,eAAe,QAAQ;AAAA,IAC3C;AAEA,QAAI,KAAK,eAAe,KAAK,YAAY,YAAY;AACjD,UAAI,WAAW;AACX,aAAK,YAAY,WAAW,aAAa,WAAW,KAAK,WAAW;AAAA,MACxE;AACA,WAAK,YAAY,WAAW,YAAY,KAAK,WAAW;AAAA,IAC5D,WAAW,aAAa,KAAK,gBAAgB;AACzC,UAAI,KAAK,uBAAuB,KAAK,oBAAoB,eAAe,KAAK,gBAAgB;AACzF,aAAK,eAAe,aAAa,WAAW,KAAK,mBAAmB;AAAA,MACxE,OAAO;AACH,aAAK,eAAe,YAAY,SAAS;AAAA,MAC7C;AAAA,IACJ;AAEA,SAAK,cAAc;AACnB,SAAK,iBAAiB;AACtB,SAAK,sBAAsB;AAG3B,QAAI,WAAW;AACX,WAAK,kBAAkB,SAAS;AAAA,IACpC;AAAA,EACJ;AAAA;AAAA;AAAA;AAAA,EAMA,wBAAwB;AACpB,QAAI,CAAC,KAAK,MAAO;AAEjB,UAAM,QAAS,KAAK,OAAO,SAAS,yBAAyB,KAAK,CAAC;AAEnE,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,OAAO;AAElB,QAAI,QAAQ,MAAM,SAAS,MAAM,SAAS,YAAY,MAAM,QAAQ;AACpE,YAAQ,KAAK,IAAI,OAAO,KAAK,IAAI,WAAW,KAAK,cAAc,CAAC,CAAC;AAGjE,UAAM,gBAAgB,KAAK,OAAO,WAAW,sBAAsB;AACnE,UAAM,SAAS,iBAAiB,cAAc,SAAS,IACjD,cAAc,QAAQ,cAAc,SACpC,KAAK;AACX,UAAM,gBAAgB,KAAK,MAAM,QAAQ,MAAM;AAC/C,QAAI,SAAS,MAAM,UAAU,MAAM,UAAU,MAAM,MAAM,SAAS;AAClE,aAAS,KAAK,IAAI,QAAQ,KAAK,IAAI,KAAK,KAAK,cAAc,CAAC,CAAC;AAE7D,QAAI;AACJ,QAAI;AAEJ,QAAI,OAAO,MAAM,SAAS,YAAY,OAAO,MAAM,QAAQ,UAAU;AACjE,aAAO,KAAK,IAAI,aAAa,KAAK,IAAI,MAAM,MAAM,KAAK,QAAQ,WAAW,CAAC;AAC3E,YAAM,KAAK,IAAI,aAAa,KAAK,IAAI,MAAM,KAAK,KAAK,SAAS,WAAW,CAAC;AAAA,IAC9E,OAAO;AACH,YAAM,MAAM,KAAK,OAAO,QAAQ,oBAAoB;AACpD,cAAQ,KAAK;AAAA,QACT,KAAK;AACD,iBAAO;AACP,gBAAM,KAAK,SAAS;AACpB;AAAA,QACJ,KAAK;AACD,iBAAO,KAAK,QAAQ;AACpB,gBAAM;AACN;AAAA,QACJ,KAAK;AACD,iBAAO;AACP,gBAAM;AACN;AAAA,QACJ,KAAK;AAAA,QACL;AACI,iBAAO,KAAK,QAAQ;AACpB,gBAAM,KAAK,SAAS;AACpB;AAAA,MACR;AAAA,IACJ;AAEA,SAAK,MAAM,MAAM,QAAQ,GAAG,KAAK;AACjC,SAAK,MAAM,MAAM,SAAS,GAAG,MAAM;AACnC,SAAK,MAAM,MAAM,OAAO,GAAG,IAAI;AAC/B,SAAK,MAAM,MAAM,MAAM,GAAG,GAAG;AAE7B,SAAK,eAAe;AAAA,EACxB;AAAA,EAEA,iBAAiB;AACb,QAAI,CAAC,KAAK,MAAO;AACjB,QAAI,KAAK,UAAW;AAEpB,SAAK,YAAY,IAAI,mBAAmB,KAAK,OAAO;AAAA,MAChD,YAAY,KAAK;AAAA,MACjB,eAAe,KAAK;AAAA,MACpB,qBAAqB;AAAA,MACrB,qBAAqB;AAAA,MACrB,UAAU;AAAA,MACV,WAAW;AAAA,MACX,UAAU,MAAM,KAAK,IAAI,WAAW,OAAO,aAAa,cAAc,CAAC;AAAA,MACvE,WAAW,MAAM,KAAK,IAAI,KAAK,OAAO,cAAc,cAAc,CAAC;AAAA,MACnE,aAAa,GAAG,KAAK,WAAW;AAAA,MAChC,iBAAiB;AAAA,MACjB,mBAAmB;AAAA,MACnB,cAAc;AAAA,MACd,mBAAmB;AAAA,MACnB,4BAA4B,KAAK,EAAE,6BAA6B;AAAA,MAChE,WAAW,MAAM,KAAK,WAAW;AAAA,MACjC,aAAa,MAAM,KAAK,WAAW;AAAA,MACnC,aAAa,CAAC,UAAiB;AAC3B,cAAM,SAAS,MAAM;AACrB,YAAI,CAAC,OAAQ,QAAO;AAIpB,YAAI,OAAO,QAAQ,IAAI,KAAK,WAAW,iBAAiB,EAAG,QAAO;AAClE,YAAI,OAAO,QAAQ,IAAI,KAAK,WAAW,WAAW,EAAG,QAAO;AAC5D,YAAI,OAAO,QAAQ,IAAI,KAAK,WAAW,yBAAyB,EAAG,QAAO;AAC1E,eAAO;AAAA,MACX;AAAA,IACJ,CAAC;AAAA,EACL;AAAA,EAEA,aAAa;AACT,QAAI,CAAC,KAAK,SAAS,CAAC,KAAK,OAAO,SAAS,wBAAyB;AAClE,UAAM,OAAO,KAAK,MAAM,sBAAsB;AAC9C,SAAK,OAAO,QAAQ,wBAAwB;AAAA,MACxC,OAAO,KAAK,MAAM,KAAK,KAAK;AAAA,MAC5B,QAAQ,KAAK,MAAM,KAAK,MAAM;AAAA,MAC9B,MAAM,KAAK,MAAM,KAAK,IAAI;AAAA,MAC1B,KAAK,KAAK,MAAM,KAAK,GAAG;AAAA,IAC5B,CAAC;AAAA,EACL;AAAA,EAEA,iBAAqC;AACjC,UAAM,SAAS,SAAS;AACxB,WAAO,UAAU,kBAAkB,cAAc,SAAS;AAAA,EAC9D;AACJ;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* VidPly v1.1.
|
|
2
|
+
* VidPly v1.1.19 - Universal, Accessible Video Player
|
|
3
3
|
* (c) 2026 Matthias Peltzer
|
|
4
4
|
* Released under GPL-2.0-or-later License
|
|
5
5
|
*/
|
|
@@ -63,7 +63,7 @@ var HLSRenderer = class {
|
|
|
63
63
|
return video.canPlayType("application/vnd.apple.mpegurl") !== "";
|
|
64
64
|
}
|
|
65
65
|
async initNative() {
|
|
66
|
-
const HTML5Renderer = (await import("./vidply.HTML5Renderer-
|
|
66
|
+
const HTML5Renderer = (await import("./vidply.HTML5Renderer-ITDUMSTL.js")).HTML5Renderer;
|
|
67
67
|
const renderer = new HTML5Renderer(this.player);
|
|
68
68
|
await renderer.init();
|
|
69
69
|
const rendererBag = renderer;
|
|
@@ -208,7 +208,7 @@ var HLSRenderer = class {
|
|
|
208
208
|
* silently break playback for consumers who upgrade hls.js.
|
|
209
209
|
*/
|
|
210
210
|
async loadHlsJs() {
|
|
211
|
-
const defaultUrl = "https://cdn.jsdelivr.net/npm/hls.js@1.
|
|
211
|
+
const defaultUrl = "https://cdn.jsdelivr.net/npm/hls.js@1.6.16/dist/hls.min.js";
|
|
212
212
|
const url = this.player.options.hlsScriptUrl || defaultUrl;
|
|
213
213
|
const integrity = this.player.options.hlsScriptIntegrity;
|
|
214
214
|
return new Promise((resolve, reject) => {
|
|
@@ -666,4 +666,4 @@ var HLSRenderer = class {
|
|
|
666
666
|
export {
|
|
667
667
|
HLSRenderer
|
|
668
668
|
};
|
|
669
|
-
//# sourceMappingURL=vidply.HLSRenderer-
|
|
669
|
+
//# sourceMappingURL=vidply.HLSRenderer-YAFEUBMC.js.map
|