vidply 1.1.2 → 1.1.4
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 +56 -16
- package/dist/dev/{vidply.AudioDescriptionManager-2LDAC6BR.js → vidply.AudioDescriptionManager-DCZFE3N2.js} +4 -4
- package/dist/dev/vidply.FloatingPlayerManager-5EA4BYSR.js +515 -0
- package/dist/dev/vidply.FloatingPlayerManager-5EA4BYSR.js.map +7 -0
- package/dist/dev/{vidply.SignLanguageManager-OOZLX2GI.js → vidply.SignLanguageManager-4NOFV4EZ.js} +7 -5
- package/dist/dev/{vidply.SignLanguageManager-OOZLX2GI.js.map → vidply.SignLanguageManager-4NOFV4EZ.js.map} +1 -1
- package/dist/dev/{vidply.TranscriptManager-BOYEUAA4.js → vidply.TranscriptManager-7TS37MMM.js} +9 -7
- package/dist/dev/{vidply.TranscriptManager-BOYEUAA4.js.map → vidply.TranscriptManager-7TS37MMM.js.map} +1 -1
- package/dist/dev/{vidply.chunk-ELJA4GMZ.js → vidply.chunk-2RIPRXWI.js} +28 -3
- package/dist/dev/vidply.chunk-2RIPRXWI.js.map +7 -0
- package/dist/dev/{vidply.chunk-S36ISCDT.js → vidply.chunk-5SDVVGOD.js} +2 -2
- package/dist/dev/vidply.chunk-COW6OPNQ.js +222 -0
- package/dist/dev/vidply.chunk-COW6OPNQ.js.map +7 -0
- package/dist/dev/{vidply.chunk-37V7VOIQ.js → vidply.chunk-D6GBU4V6.js} +2 -212
- package/dist/dev/vidply.chunk-D6GBU4V6.js.map +7 -0
- package/dist/dev/{vidply.chunk-VLF6H5PZ.js → vidply.chunk-FDUUJHK6.js} +13 -5
- package/dist/dev/vidply.chunk-FDUUJHK6.js.map +7 -0
- package/dist/dev/{vidply.chunk-SXDZXXZD.js → vidply.chunk-X6PJOVWZ.js} +7 -1
- package/dist/dev/{vidply.chunk-SXDZXXZD.js.map → vidply.chunk-X6PJOVWZ.js.map} +2 -2
- package/dist/dev/{vidply.de-HLO4IOPX.js → vidply.de-TEYITA3Z.js} +9 -1
- package/dist/dev/vidply.de-TEYITA3Z.js.map +7 -0
- package/dist/dev/{vidply.es-OBOYSHKU.js → vidply.es-OKQLRQPJ.js} +9 -1
- package/dist/dev/vidply.es-OKQLRQPJ.js.map +7 -0
- package/dist/dev/vidply.esm.js +320 -24
- package/dist/dev/vidply.esm.js.map +3 -3
- package/dist/dev/{vidply.fr-WDRPLLB6.js → vidply.fr-52424DPC.js} +9 -1
- package/dist/dev/vidply.fr-52424DPC.js.map +7 -0
- package/dist/dev/{vidply.ja-ILEIPUNW.js → vidply.ja-X4QNECSO.js} +9 -1
- package/dist/dev/vidply.ja-X4QNECSO.js.map +7 -0
- package/dist/legacy/vidply.js +898 -15
- package/dist/legacy/vidply.js.map +4 -4
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +73 -21
- package/dist/prod/{vidply.AudioDescriptionManager-ILVU6JG2.min.js → vidply.AudioDescriptionManager-JJFZSG7G.min.js} +1 -1
- package/dist/prod/vidply.FloatingPlayerManager-JVNKAK4N.min.js +6 -0
- package/dist/prod/vidply.SignLanguageManager-6FBSQB4U.min.js +6 -0
- package/dist/prod/vidply.TranscriptManager-L5E737CR.min.js +6 -0
- package/dist/prod/{vidply.chunk-ALJYNEGS.min.js → vidply.chunk-DPEZK7YT.min.js} +1 -1
- package/dist/prod/vidply.chunk-KXQK2NF7.min.js +6 -0
- package/dist/prod/vidply.chunk-MOAZJSWV.min.js +6 -0
- package/dist/prod/vidply.chunk-QGVFMQUO.min.js +6 -0
- package/dist/prod/vidply.chunk-VSKF7AQ2.min.js +6 -0
- package/dist/prod/{vidply.chunk-KYQFJSLN.min.js → vidply.chunk-ZDIQYKKX.min.js} +1 -1
- package/dist/prod/vidply.de-EIZK243P.min.js +6 -0
- package/dist/prod/vidply.es-JGYFGRSI.min.js +6 -0
- package/dist/prod/vidply.esm.min.js +1 -1
- package/dist/prod/vidply.fr-XAQ6ZIB7.min.js +6 -0
- package/dist/prod/vidply.ja-S77KR54S.min.js +6 -0
- package/dist/vidply.css +475 -209
- package/dist/vidply.esm.min.meta.json +171 -77
- package/dist/vidply.min.css +1 -1
- package/package.json +12 -6
- package/src/controls/CaptionManager.ts +555 -523
- package/src/controls/ControlBar.ts +172 -19
- package/src/core/FloatingPlayerManager.ts +631 -0
- package/src/core/Player.ts +6036 -5946
- package/src/i18n/languages/de.ts +8 -0
- package/src/i18n/languages/en.ts +8 -0
- package/src/i18n/languages/es.ts +8 -0
- package/src/i18n/languages/fr.ts +8 -0
- package/src/i18n/languages/ja.ts +8 -0
- package/src/index.ts +5 -0
- package/src/styles/vidply.css +475 -209
- package/src/types/options.ts +6 -0
- package/src/types/state.ts +1 -0
- package/src/utils/DownloadInfo.ts +148 -0
- package/src/utils/StorageManager.ts +8 -0
- package/dist/dev/vidply.chunk-37V7VOIQ.js.map +0 -7
- package/dist/dev/vidply.chunk-ELJA4GMZ.js.map +0 -7
- package/dist/dev/vidply.chunk-VLF6H5PZ.js.map +0 -7
- package/dist/dev/vidply.de-HLO4IOPX.js.map +0 -7
- package/dist/dev/vidply.es-OBOYSHKU.js.map +0 -7
- package/dist/dev/vidply.fr-WDRPLLB6.js.map +0 -7
- package/dist/dev/vidply.ja-ILEIPUNW.js.map +0 -7
- package/dist/prod/vidply.SignLanguageManager-UPGXSO5F.min.js +0 -6
- package/dist/prod/vidply.TranscriptManager-PLVKPYNB.min.js +0 -6
- package/dist/prod/vidply.chunk-6XO2O3OT.min.js +0 -6
- package/dist/prod/vidply.chunk-UO33KGOT.min.js +0 -6
- package/dist/prod/vidply.chunk-XHLTMSR5.min.js +0 -6
- package/dist/prod/vidply.de-CKLFPHNF.min.js +0 -6
- package/dist/prod/vidply.es-FYZBTBVH.min.js +0 -6
- package/dist/prod/vidply.fr-E7FMYILU.min.js +0 -6
- package/dist/prod/vidply.ja-NOW6P3IH.min.js +0 -6
- /package/dist/dev/{vidply.AudioDescriptionManager-2LDAC6BR.js.map → vidply.AudioDescriptionManager-DCZFE3N2.js.map} +0 -0
- /package/dist/dev/{vidply.chunk-S36ISCDT.js.map → vidply.chunk-5SDVVGOD.js.map} +0 -0
package/README.md
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
**Universal, Accessible Video & Audio Player**
|
|
4
4
|
|
|
5
|
-
A modern, feature-rich media player
|
|
5
|
+
A modern, feature-rich media player authored in strict TypeScript and shipped as a zero-dependency ES module. Combines the best accessibility features from AblePlayer with the streaming capabilities of MediaElement.js. Fully internationalized with support for 5 languages and complete WCAG 2.2 AA compliance.
|
|
6
6
|
|
|
7
7
|

|
|
8
|
-

|
|
9
|
+

|
|
9
10
|

|
|
10
|
-

|
|
11
12
|
|
|
12
13
|
## Live Demos
|
|
13
14
|
|
|
@@ -21,11 +22,12 @@ Try VidPly in action:
|
|
|
21
22
|
|
|
22
23
|
## Why VidPly?
|
|
23
24
|
|
|
24
|
-
- **Zero Dependencies** - Pure vanilla JavaScript, no frameworks required
|
|
25
|
+
- **Zero Dependencies** - Pure vanilla JavaScript / TypeScript, no frameworks required
|
|
26
|
+
- **TypeScript Native** - Authored in strict TypeScript with shipped type declarations (`dist/types/index.d.ts`)
|
|
25
27
|
- **Accessibility First** - WCAG 2.2 AA compliant with full keyboard and screen reader support
|
|
26
28
|
- **Multilingual** - Built-in translations for 5 languages with easy extensibility
|
|
27
29
|
- **Fully Customizable** - CSS variables and comprehensive API
|
|
28
|
-
- **Modern Build** -
|
|
30
|
+
- **Modern Build** - ES modules with tree-shaking, code-splitting and source maps
|
|
29
31
|
- **Production Ready** - Thoroughly tested with real-world media content
|
|
30
32
|
|
|
31
33
|
## Features
|
|
@@ -35,8 +37,13 @@ Try VidPly in action:
|
|
|
35
37
|
- **Multiple Formats** - MP3, OGG, WAV (audio) / MP4, WebM (video)
|
|
36
38
|
- **YouTube Integration** - Embed YouTube videos with unified controls
|
|
37
39
|
- **Vimeo Integration** - Seamless Vimeo player integration
|
|
40
|
+
- **SoundCloud Integration** - Play SoundCloud tracks and sets via the Widget API with unified controls
|
|
38
41
|
- **HLS Streaming** - Adaptive bitrate streaming with quality selection and dynamic subtitle detection
|
|
42
|
+
- Uses `hls.js` on Chrome / Firefox / Edge / desktop Safari for full feature parity (quality menu, captions, transcript)
|
|
43
|
+
- Falls back to native HLS on iOS / iPadOS where MSE is unavailable; native text tracks are still surfaced through the VidPly captions and transcript UI
|
|
39
44
|
- **DASH Streaming** - MPEG-DASH support via dash.js with adaptive quality, TTML and WebVTT subtitles
|
|
45
|
+
- **Buffering Spinner** - Centered loading spinner shown automatically while media is buffering (HTML5, HLS, DASH)
|
|
46
|
+
- **Download Button** - Optional download control with custom URL support (`downloadButton` + `downloadUrl`)
|
|
40
47
|
- **Preview Thumbnails** - Video preview thumbnails on progress bar hover
|
|
41
48
|
- **Playlists** - Full playlist support with auto-advance and navigation
|
|
42
49
|
- Audio playlists with track info
|
|
@@ -148,10 +155,10 @@ This creates minified files in the `dist/` folder.
|
|
|
148
155
|
</script>
|
|
149
156
|
```
|
|
150
157
|
|
|
151
|
-
### Option 3: Development (
|
|
158
|
+
### Option 3: Development (TypeScript Sources)
|
|
152
159
|
|
|
153
|
-
```
|
|
154
|
-
import Player from './src/index
|
|
160
|
+
```typescript
|
|
161
|
+
import Player from './src/index';
|
|
155
162
|
|
|
156
163
|
const player = new Player('#my-video', {
|
|
157
164
|
controls: true,
|
|
@@ -161,6 +168,8 @@ const player = new Player('#my-video', {
|
|
|
161
168
|
});
|
|
162
169
|
```
|
|
163
170
|
|
|
171
|
+
> The library is authored in strict TypeScript. Type declarations ship to `dist/types/index.d.ts` so consumers using `tsc` or `vite` get full IntelliSense without any extra `@types` package.
|
|
172
|
+
|
|
164
173
|
## Quick Start
|
|
165
174
|
|
|
166
175
|
### 1. Build the Player
|
|
@@ -223,6 +232,32 @@ That's it! The player auto-initializes.
|
|
|
223
232
|
<video data-vidply src="https://example.com/manifest.mpd"></video>
|
|
224
233
|
```
|
|
225
234
|
|
|
235
|
+
### SoundCloud
|
|
236
|
+
|
|
237
|
+
```html
|
|
238
|
+
<audio data-vidply src="https://soundcloud.com/artist/track"></audio>
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Download Button
|
|
242
|
+
|
|
243
|
+
Enable the download button and (optionally) provide a custom URL:
|
|
244
|
+
|
|
245
|
+
```html
|
|
246
|
+
<video
|
|
247
|
+
data-vidply
|
|
248
|
+
data-vidply-download-button="true"
|
|
249
|
+
data-vidply-download-url="/files/lecture.mp4"
|
|
250
|
+
src="/streams/lecture/manifest.mpd">
|
|
251
|
+
</video>
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
const player = new Player('#my-video', {
|
|
256
|
+
downloadButton: true,
|
|
257
|
+
downloadUrl: '/files/lecture.mp4' // optional, falls back to current src
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
226
261
|
### DASH + HLS + MP4 Fallback
|
|
227
262
|
|
|
228
263
|
For maximum device compatibility, provide all three formats:
|
|
@@ -274,7 +309,9 @@ const player = new Player('#video', {
|
|
|
274
309
|
signLanguageButton: true,
|
|
275
310
|
fullscreenButton: true,
|
|
276
311
|
pipButton: true,
|
|
277
|
-
|
|
312
|
+
downloadButton: false, // Show a download button in the control bar
|
|
313
|
+
downloadUrl: null, // Optional explicit download URL (falls back to current src)
|
|
314
|
+
|
|
278
315
|
// Captions
|
|
279
316
|
captions: true,
|
|
280
317
|
captionsDefault: false,
|
|
@@ -613,8 +650,8 @@ VidPly provides extensive CSS variables for easy customization:
|
|
|
613
650
|
|
|
614
651
|
#### Option 2: JavaScript API
|
|
615
652
|
|
|
616
|
-
```
|
|
617
|
-
import { i18n } from './src/i18n/i18n
|
|
653
|
+
```typescript
|
|
654
|
+
import { i18n } from './src/i18n/i18n';
|
|
618
655
|
|
|
619
656
|
// Load language file from URL
|
|
620
657
|
await i18n.loadLanguageFromUrl('pt', 'languages/pt.json');
|
|
@@ -631,8 +668,8 @@ i18n.setLanguage('pt');
|
|
|
631
668
|
|
|
632
669
|
#### Option 3: Add Translations Programmatically
|
|
633
670
|
|
|
634
|
-
```
|
|
635
|
-
import { i18n } from './src/i18n/i18n
|
|
671
|
+
```typescript
|
|
672
|
+
import { i18n } from './src/i18n/i18n';
|
|
636
673
|
|
|
637
674
|
i18n.addTranslation('pt', {
|
|
638
675
|
player: {
|
|
@@ -671,14 +708,16 @@ The player supports both JSON and YAML formats for language files.
|
|
|
671
708
|
|
|
672
709
|
## Build Process
|
|
673
710
|
|
|
674
|
-
VidPly uses a modern build system with esbuild for
|
|
711
|
+
VidPly uses a modern build system with esbuild for TypeScript bundling, the TypeScript compiler for `.d.ts` declarations, and clean-css for CSS.
|
|
675
712
|
|
|
676
713
|
### Available Scripts
|
|
677
714
|
|
|
678
715
|
```bash
|
|
679
|
-
npm run build # Build everything (JS + CSS)
|
|
680
|
-
npm run build:js #
|
|
716
|
+
npm run build # Build everything (JS + types + CSS)
|
|
717
|
+
npm run build:js # Bundle TypeScript with esbuild (ESM + IIFE)
|
|
718
|
+
npm run build:types # Emit type declarations to dist/types/
|
|
681
719
|
npm run build:css # Build CSS only
|
|
720
|
+
npm run typecheck # Run tsc --noEmit
|
|
682
721
|
npm run watch # Watch mode for development
|
|
683
722
|
npm run clean # Clean dist directory
|
|
684
723
|
npm run dev # Start dev server
|
|
@@ -693,6 +732,7 @@ npm run test:all # Run all tests
|
|
|
693
732
|
- `dist/prod/vidply.esm.min.js` - ES Module (production)
|
|
694
733
|
- `dist/legacy/vidply.js` - IIFE (development)
|
|
695
734
|
- `dist/legacy/vidply.min.js` - IIFE (production)
|
|
735
|
+
- `dist/types/index.d.ts` - TypeScript declarations
|
|
696
736
|
- `dist/vidply.css` - Styles (unminified)
|
|
697
737
|
- `dist/vidply.min.css` - Styles (minified)
|
|
698
738
|
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import {
|
|
7
7
|
CaptionManager
|
|
8
|
-
} from "./vidply.chunk-
|
|
9
|
-
import "./vidply.chunk-
|
|
10
|
-
import "./vidply.chunk-
|
|
8
|
+
} from "./vidply.chunk-2RIPRXWI.js";
|
|
9
|
+
import "./vidply.chunk-X6PJOVWZ.js";
|
|
10
|
+
import "./vidply.chunk-FDUUJHK6.js";
|
|
11
11
|
|
|
12
12
|
// src/core/AudioDescriptionManager.ts
|
|
13
13
|
var AudioDescriptionManager = class {
|
|
@@ -569,4 +569,4 @@ var AudioDescriptionManager = class {
|
|
|
569
569
|
export {
|
|
570
570
|
AudioDescriptionManager
|
|
571
571
|
};
|
|
572
|
-
//# sourceMappingURL=vidply.AudioDescriptionManager-
|
|
572
|
+
//# sourceMappingURL=vidply.AudioDescriptionManager-DCZFE3N2.js.map
|
|
@@ -0,0 +1,515 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Universal, Accessible Video Player
|
|
3
|
+
* (c) 2026 Matthias Peltzer
|
|
4
|
+
* Released under GPL-2.0-or-later License
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
DraggableResizable,
|
|
8
|
+
createIconElement
|
|
9
|
+
} from "./vidply.chunk-D6GBU4V6.js";
|
|
10
|
+
import {
|
|
11
|
+
DOMUtils,
|
|
12
|
+
i18n
|
|
13
|
+
} from "./vidply.chunk-FDUUJHK6.js";
|
|
14
|
+
|
|
15
|
+
// src/core/FloatingPlayerManager.ts
|
|
16
|
+
var FLOATING_CLAIM_EVENT = "vidply:floating-claim";
|
|
17
|
+
var DEFAULT_WIDTH = 400;
|
|
18
|
+
var MIN_WIDTH = 240;
|
|
19
|
+
var EDGE_MARGIN = 16;
|
|
20
|
+
var FloatingPlayerManager = class {
|
|
21
|
+
player;
|
|
22
|
+
classPrefix;
|
|
23
|
+
shell;
|
|
24
|
+
dragHandle;
|
|
25
|
+
closeButton;
|
|
26
|
+
resizeHandles;
|
|
27
|
+
placeholder;
|
|
28
|
+
draggable;
|
|
29
|
+
originalParent;
|
|
30
|
+
originalNextSibling;
|
|
31
|
+
intersectionObserver;
|
|
32
|
+
observerTarget;
|
|
33
|
+
lastRatio;
|
|
34
|
+
_autoDismissedThisPlay;
|
|
35
|
+
_playListenerAttached;
|
|
36
|
+
_onPlayAfterDismiss;
|
|
37
|
+
_onClaim;
|
|
38
|
+
_onResize;
|
|
39
|
+
_onKeyDown;
|
|
40
|
+
_onEnterFullscreen;
|
|
41
|
+
_destroyed;
|
|
42
|
+
_triggerFocusEl;
|
|
43
|
+
_claimId;
|
|
44
|
+
constructor(player) {
|
|
45
|
+
this.player = player;
|
|
46
|
+
this.classPrefix = player.options.classPrefix || "vidply";
|
|
47
|
+
this.shell = null;
|
|
48
|
+
this.dragHandle = null;
|
|
49
|
+
this.closeButton = null;
|
|
50
|
+
this.resizeHandles = [];
|
|
51
|
+
this.placeholder = null;
|
|
52
|
+
this.draggable = null;
|
|
53
|
+
this.originalParent = null;
|
|
54
|
+
this.originalNextSibling = null;
|
|
55
|
+
this.intersectionObserver = null;
|
|
56
|
+
this.observerTarget = null;
|
|
57
|
+
this.lastRatio = 1;
|
|
58
|
+
this._autoDismissedThisPlay = false;
|
|
59
|
+
this._playListenerAttached = false;
|
|
60
|
+
this._onPlayAfterDismiss = null;
|
|
61
|
+
this._onClaim = null;
|
|
62
|
+
this._onResize = null;
|
|
63
|
+
this._onKeyDown = null;
|
|
64
|
+
this._onEnterFullscreen = null;
|
|
65
|
+
this._destroyed = false;
|
|
66
|
+
this._triggerFocusEl = null;
|
|
67
|
+
this._claimId = `floating-${player.instanceId}-${Date.now()}`;
|
|
68
|
+
this._setupClaimListener();
|
|
69
|
+
this._setupFullscreenGuard();
|
|
70
|
+
this._startObserving();
|
|
71
|
+
}
|
|
72
|
+
// ---------------------------------------------------------------
|
|
73
|
+
// Public API
|
|
74
|
+
// ---------------------------------------------------------------
|
|
75
|
+
togglePinned(triggerEl) {
|
|
76
|
+
if (this._destroyed) return;
|
|
77
|
+
if (this.player.state.floating === "pinned") {
|
|
78
|
+
this._autoDismissedThisPlay = true;
|
|
79
|
+
this._armPlayListenerToClearDismiss();
|
|
80
|
+
this.exit("manual");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
this._autoDismissedThisPlay = false;
|
|
84
|
+
this._triggerFocusEl = triggerEl || this._activeElement();
|
|
85
|
+
this.enter("pinned");
|
|
86
|
+
}
|
|
87
|
+
enter(reason) {
|
|
88
|
+
if (this._destroyed) return;
|
|
89
|
+
if (this.player.state.floating === reason) return;
|
|
90
|
+
if (!this._canFloat(reason)) {
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (this.player.state.floating && this.player.state.floating !== reason) {
|
|
94
|
+
this.player.state.floating = reason;
|
|
95
|
+
this.player.emit("floatingchange", reason);
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
this._claimSingleton();
|
|
99
|
+
this._ensureShell();
|
|
100
|
+
this._mountIntoShell();
|
|
101
|
+
this._applyInitialGeometry();
|
|
102
|
+
this.player.state.floating = reason;
|
|
103
|
+
this.player.emit("floatingchange", reason);
|
|
104
|
+
queueMicrotask(() => {
|
|
105
|
+
if (this.closeButton && this.player.state.floating) {
|
|
106
|
+
try {
|
|
107
|
+
this.closeButton.focus({ preventScroll: true });
|
|
108
|
+
} catch {
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
exit(reason = "manual") {
|
|
114
|
+
if (this._destroyed && reason !== "destroy") return;
|
|
115
|
+
if (!this.player.state.floating) return;
|
|
116
|
+
this._unmountFromShell();
|
|
117
|
+
this._teardownShell();
|
|
118
|
+
const priorTrigger = this._triggerFocusEl;
|
|
119
|
+
this._triggerFocusEl = null;
|
|
120
|
+
this.player.state.floating = null;
|
|
121
|
+
this.player.emit("floatingchange", null);
|
|
122
|
+
if ((reason === "manual" || reason === "dismiss") && priorTrigger) {
|
|
123
|
+
try {
|
|
124
|
+
priorTrigger.focus({ preventScroll: true });
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Close button: pause, dismiss, and prevent auto-float until the next
|
|
131
|
+
* user-initiated play event.
|
|
132
|
+
*/
|
|
133
|
+
dismiss() {
|
|
134
|
+
if (this._destroyed) return;
|
|
135
|
+
this._autoDismissedThisPlay = true;
|
|
136
|
+
this._armPlayListenerToClearDismiss();
|
|
137
|
+
try {
|
|
138
|
+
this.player.pause();
|
|
139
|
+
} catch {
|
|
140
|
+
}
|
|
141
|
+
this.exit("dismiss");
|
|
142
|
+
}
|
|
143
|
+
destroy() {
|
|
144
|
+
if (this._destroyed) return;
|
|
145
|
+
this._destroyed = true;
|
|
146
|
+
if (this.player.state && this.player.state.floating) {
|
|
147
|
+
try {
|
|
148
|
+
this.exit("destroy");
|
|
149
|
+
} catch {
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (this.intersectionObserver) {
|
|
153
|
+
try {
|
|
154
|
+
this.intersectionObserver.disconnect();
|
|
155
|
+
} catch {
|
|
156
|
+
}
|
|
157
|
+
this.intersectionObserver = null;
|
|
158
|
+
}
|
|
159
|
+
this.observerTarget = null;
|
|
160
|
+
if (this._onClaim) {
|
|
161
|
+
window.removeEventListener(FLOATING_CLAIM_EVENT, this._onClaim);
|
|
162
|
+
this._onClaim = null;
|
|
163
|
+
}
|
|
164
|
+
if (this._onResize) {
|
|
165
|
+
window.removeEventListener("resize", this._onResize);
|
|
166
|
+
this._onResize = null;
|
|
167
|
+
}
|
|
168
|
+
if (this._onEnterFullscreen) {
|
|
169
|
+
this.player.off("enterfullscreen", this._onEnterFullscreen);
|
|
170
|
+
this._onEnterFullscreen = null;
|
|
171
|
+
}
|
|
172
|
+
if (this._onPlayAfterDismiss && this._playListenerAttached) {
|
|
173
|
+
this.player.off("play", this._onPlayAfterDismiss);
|
|
174
|
+
this._playListenerAttached = false;
|
|
175
|
+
this._onPlayAfterDismiss = null;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// ---------------------------------------------------------------
|
|
179
|
+
// Internal: guards
|
|
180
|
+
// ---------------------------------------------------------------
|
|
181
|
+
_canFloat(reason) {
|
|
182
|
+
if (!this.player.options.floating) return false;
|
|
183
|
+
if (!this.player.container) return false;
|
|
184
|
+
if (!this.player.element || this.player.element.tagName !== "VIDEO") return false;
|
|
185
|
+
if (this.player.state.fullscreen) return false;
|
|
186
|
+
if (this.player.playlistManager) return false;
|
|
187
|
+
const minWidth = this.player.options.floatingMinViewportWidth ?? 768;
|
|
188
|
+
if (window.innerWidth < minWidth) return false;
|
|
189
|
+
if (reason === "auto") {
|
|
190
|
+
if (this._autoDismissedThisPlay) return false;
|
|
191
|
+
if (this.player.state.paused) return false;
|
|
192
|
+
if (!this.player.state.hasStartedPlayback) return false;
|
|
193
|
+
}
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
_claimSingleton() {
|
|
197
|
+
try {
|
|
198
|
+
window.dispatchEvent(new CustomEvent(FLOATING_CLAIM_EVENT, {
|
|
199
|
+
detail: { claimId: this._claimId }
|
|
200
|
+
}));
|
|
201
|
+
} catch {
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
_setupClaimListener() {
|
|
205
|
+
this._onClaim = (event) => {
|
|
206
|
+
const detail = event.detail;
|
|
207
|
+
if (!detail || detail.claimId === this._claimId) return;
|
|
208
|
+
if (this.player.state.floating) {
|
|
209
|
+
this.exit("claim");
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
window.addEventListener(FLOATING_CLAIM_EVENT, this._onClaim);
|
|
213
|
+
this._onResize = () => {
|
|
214
|
+
const minWidth = this.player.options.floatingMinViewportWidth ?? 768;
|
|
215
|
+
if (this.player.state.floating && window.innerWidth < minWidth) {
|
|
216
|
+
this.exit("auto");
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
window.addEventListener("resize", this._onResize);
|
|
220
|
+
}
|
|
221
|
+
_setupFullscreenGuard() {
|
|
222
|
+
this._onEnterFullscreen = () => {
|
|
223
|
+
if (this.player.state.floating) {
|
|
224
|
+
this.exit("manual");
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
this.player.on("enterfullscreen", this._onEnterFullscreen);
|
|
228
|
+
}
|
|
229
|
+
_armPlayListenerToClearDismiss() {
|
|
230
|
+
if (this._playListenerAttached) return;
|
|
231
|
+
this._onPlayAfterDismiss = () => {
|
|
232
|
+
this._autoDismissedThisPlay = false;
|
|
233
|
+
if (this._onPlayAfterDismiss) {
|
|
234
|
+
this.player.off("play", this._onPlayAfterDismiss);
|
|
235
|
+
}
|
|
236
|
+
this._playListenerAttached = false;
|
|
237
|
+
this._onPlayAfterDismiss = null;
|
|
238
|
+
};
|
|
239
|
+
this.player.on("play", this._onPlayAfterDismiss);
|
|
240
|
+
this._playListenerAttached = true;
|
|
241
|
+
}
|
|
242
|
+
// ---------------------------------------------------------------
|
|
243
|
+
// Internal: IntersectionObserver for scroll-triggered auto-float
|
|
244
|
+
// ---------------------------------------------------------------
|
|
245
|
+
_startObserving() {
|
|
246
|
+
if (!("IntersectionObserver" in window)) return;
|
|
247
|
+
if (!this.player.container) return;
|
|
248
|
+
this.observerTarget = this.player.container;
|
|
249
|
+
this.intersectionObserver = new IntersectionObserver((entries) => {
|
|
250
|
+
const entry = entries[entries.length - 1];
|
|
251
|
+
if (!entry) return;
|
|
252
|
+
this.lastRatio = entry.intersectionRatio;
|
|
253
|
+
if (this.player.options.debug) {
|
|
254
|
+
try {
|
|
255
|
+
console.log("[vidply:floating] intersection", {
|
|
256
|
+
ratio: Number(entry.intersectionRatio.toFixed(3)),
|
|
257
|
+
state: this.player.state.floating,
|
|
258
|
+
paused: this.player.state.paused,
|
|
259
|
+
hasStartedPlayback: this.player.state.hasStartedPlayback,
|
|
260
|
+
dismissed: this._autoDismissedThisPlay
|
|
261
|
+
});
|
|
262
|
+
} catch {
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (this.player.state.floating === "auto") {
|
|
266
|
+
if (entry.intersectionRatio > 0.5) {
|
|
267
|
+
this.exit("auto");
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (this.player.state.floating === "pinned") {
|
|
272
|
+
return;
|
|
273
|
+
}
|
|
274
|
+
if (entry.intersectionRatio < 0.1 && this._canFloat("auto")) {
|
|
275
|
+
this.enter("auto");
|
|
276
|
+
}
|
|
277
|
+
}, { threshold: [0, 0.1, 0.5, 0.9] });
|
|
278
|
+
this.intersectionObserver.observe(this.observerTarget);
|
|
279
|
+
}
|
|
280
|
+
_retargetObserver(target) {
|
|
281
|
+
if (!this.intersectionObserver) return;
|
|
282
|
+
if (this.observerTarget) {
|
|
283
|
+
try {
|
|
284
|
+
this.intersectionObserver.unobserve(this.observerTarget);
|
|
285
|
+
} catch {
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
this.observerTarget = target;
|
|
289
|
+
try {
|
|
290
|
+
this.intersectionObserver.observe(target);
|
|
291
|
+
} catch {
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
// ---------------------------------------------------------------
|
|
295
|
+
// Internal: shell DOM
|
|
296
|
+
// ---------------------------------------------------------------
|
|
297
|
+
_ensureShell() {
|
|
298
|
+
if (this.shell) return;
|
|
299
|
+
this.shell = DOMUtils.createElement("div", {
|
|
300
|
+
className: `${this.classPrefix}-floating-shell`,
|
|
301
|
+
attributes: {
|
|
302
|
+
"role": "dialog",
|
|
303
|
+
"aria-modal": "false",
|
|
304
|
+
"aria-label": i18n.t("player.floatingPlayer"),
|
|
305
|
+
"data-vidply-floating": "true",
|
|
306
|
+
"tabindex": "-1"
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
this.dragHandle = DOMUtils.createElement("div", {
|
|
310
|
+
className: `${this.classPrefix}-floating-drag-handle`,
|
|
311
|
+
attributes: { "aria-hidden": "true" }
|
|
312
|
+
});
|
|
313
|
+
this.shell.appendChild(this.dragHandle);
|
|
314
|
+
this.closeButton = DOMUtils.createElement("button", {
|
|
315
|
+
className: `${this.classPrefix}-floating-close`,
|
|
316
|
+
attributes: {
|
|
317
|
+
"type": "button",
|
|
318
|
+
"aria-label": i18n.t("player.floatingPlayerClose"),
|
|
319
|
+
"title": i18n.t("player.floatingPlayerClose")
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
this.closeButton.appendChild(createIconElement("close"));
|
|
323
|
+
this.closeButton.addEventListener("click", (event) => {
|
|
324
|
+
event.stopPropagation();
|
|
325
|
+
this.dismiss();
|
|
326
|
+
});
|
|
327
|
+
this.shell.appendChild(this.closeButton);
|
|
328
|
+
this._createResizeHandles();
|
|
329
|
+
this.resizeHandles.forEach((handle) => this.shell.appendChild(handle));
|
|
330
|
+
this._onKeyDown = (event) => {
|
|
331
|
+
if (event.key === "Escape") {
|
|
332
|
+
event.stopPropagation();
|
|
333
|
+
this.dismiss();
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
this.shell.addEventListener("keydown", this._onKeyDown);
|
|
337
|
+
}
|
|
338
|
+
_createResizeHandles() {
|
|
339
|
+
const dirs = ["n", "s", "e", "w", "ne", "nw", "se", "sw"];
|
|
340
|
+
this.resizeHandles = dirs.map((dir) => DOMUtils.createElement("div", {
|
|
341
|
+
className: `${this.classPrefix}-floating-resize-handle ${this.classPrefix}-floating-resize-${dir}`,
|
|
342
|
+
attributes: {
|
|
343
|
+
"data-direction": dir,
|
|
344
|
+
"data-vidply-managed-resize": "true",
|
|
345
|
+
"aria-hidden": "true"
|
|
346
|
+
}
|
|
347
|
+
}));
|
|
348
|
+
}
|
|
349
|
+
_teardownShell() {
|
|
350
|
+
if (this.draggable) {
|
|
351
|
+
try {
|
|
352
|
+
this.draggable.destroy();
|
|
353
|
+
} catch {
|
|
354
|
+
}
|
|
355
|
+
this.draggable = null;
|
|
356
|
+
}
|
|
357
|
+
if (this.shell) {
|
|
358
|
+
if (this._onKeyDown) {
|
|
359
|
+
this.shell.removeEventListener("keydown", this._onKeyDown);
|
|
360
|
+
this._onKeyDown = null;
|
|
361
|
+
}
|
|
362
|
+
if (this.shell.parentNode) {
|
|
363
|
+
this.shell.parentNode.removeChild(this.shell);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
this.shell = null;
|
|
367
|
+
this.dragHandle = null;
|
|
368
|
+
this.closeButton = null;
|
|
369
|
+
this.resizeHandles = [];
|
|
370
|
+
}
|
|
371
|
+
// ---------------------------------------------------------------
|
|
372
|
+
// Internal: mount / unmount the player.container
|
|
373
|
+
// ---------------------------------------------------------------
|
|
374
|
+
_mountIntoShell() {
|
|
375
|
+
const container = this.player.container;
|
|
376
|
+
if (!container || !container.parentNode) return;
|
|
377
|
+
if (!this.shell) return;
|
|
378
|
+
const rect = container.getBoundingClientRect();
|
|
379
|
+
this.originalParent = container.parentNode;
|
|
380
|
+
this.originalNextSibling = container.nextSibling;
|
|
381
|
+
this.placeholder = DOMUtils.createElement("div", {
|
|
382
|
+
className: `${this.classPrefix}-floating-placeholder`,
|
|
383
|
+
attributes: { "aria-hidden": "true" }
|
|
384
|
+
});
|
|
385
|
+
this.placeholder.style.width = `${Math.max(1, rect.width)}px`;
|
|
386
|
+
this.placeholder.style.height = `${Math.max(1, rect.height)}px`;
|
|
387
|
+
this.originalParent.insertBefore(this.placeholder, container);
|
|
388
|
+
this.shell.appendChild(container);
|
|
389
|
+
document.body.appendChild(this.shell);
|
|
390
|
+
container.classList.add(`${this.classPrefix}-is-floating`);
|
|
391
|
+
this._retargetObserver(this.placeholder);
|
|
392
|
+
}
|
|
393
|
+
_unmountFromShell() {
|
|
394
|
+
const container = this.player.container;
|
|
395
|
+
if (container) {
|
|
396
|
+
container.classList.remove(`${this.classPrefix}-is-floating`);
|
|
397
|
+
container.style.removeProperty("width");
|
|
398
|
+
container.style.removeProperty("height");
|
|
399
|
+
}
|
|
400
|
+
if (this.placeholder && this.placeholder.parentNode) {
|
|
401
|
+
if (container) {
|
|
402
|
+
this.placeholder.parentNode.insertBefore(container, this.placeholder);
|
|
403
|
+
}
|
|
404
|
+
this.placeholder.parentNode.removeChild(this.placeholder);
|
|
405
|
+
} else if (container && this.originalParent) {
|
|
406
|
+
if (this.originalNextSibling && this.originalNextSibling.parentNode === this.originalParent) {
|
|
407
|
+
this.originalParent.insertBefore(container, this.originalNextSibling);
|
|
408
|
+
} else {
|
|
409
|
+
this.originalParent.appendChild(container);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
this.placeholder = null;
|
|
413
|
+
this.originalParent = null;
|
|
414
|
+
this.originalNextSibling = null;
|
|
415
|
+
if (container) {
|
|
416
|
+
this._retargetObserver(container);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// ---------------------------------------------------------------
|
|
420
|
+
// Internal: initial geometry + drag/resize wiring
|
|
421
|
+
// ---------------------------------------------------------------
|
|
422
|
+
_applyInitialGeometry() {
|
|
423
|
+
if (!this.shell) return;
|
|
424
|
+
const prefs = this.player.storage?.getFloatingPreferences?.() || {};
|
|
425
|
+
const vw = window.innerWidth;
|
|
426
|
+
const vh = window.innerHeight;
|
|
427
|
+
let width = prefs.width && prefs.width >= MIN_WIDTH ? prefs.width : DEFAULT_WIDTH;
|
|
428
|
+
width = Math.min(width, Math.max(MIN_WIDTH, vw - EDGE_MARGIN * 2));
|
|
429
|
+
const containerRect = this.player.container?.getBoundingClientRect();
|
|
430
|
+
const aspect = containerRect && containerRect.height > 0 ? containerRect.width / containerRect.height : 16 / 9;
|
|
431
|
+
const defaultHeight = Math.round(width / aspect);
|
|
432
|
+
let height = prefs.height && prefs.height >= 100 ? prefs.height : defaultHeight;
|
|
433
|
+
height = Math.min(height, Math.max(100, vh - EDGE_MARGIN * 2));
|
|
434
|
+
let left;
|
|
435
|
+
let top;
|
|
436
|
+
if (typeof prefs.left === "number" && typeof prefs.top === "number") {
|
|
437
|
+
left = Math.max(EDGE_MARGIN, Math.min(prefs.left, vw - width - EDGE_MARGIN));
|
|
438
|
+
top = Math.max(EDGE_MARGIN, Math.min(prefs.top, vh - height - EDGE_MARGIN));
|
|
439
|
+
} else {
|
|
440
|
+
const pos = this.player.options.floatingPosition || "bottom-right";
|
|
441
|
+
switch (pos) {
|
|
442
|
+
case "bottom-left":
|
|
443
|
+
left = EDGE_MARGIN;
|
|
444
|
+
top = vh - height - EDGE_MARGIN;
|
|
445
|
+
break;
|
|
446
|
+
case "top-right":
|
|
447
|
+
left = vw - width - EDGE_MARGIN;
|
|
448
|
+
top = EDGE_MARGIN;
|
|
449
|
+
break;
|
|
450
|
+
case "top-left":
|
|
451
|
+
left = EDGE_MARGIN;
|
|
452
|
+
top = EDGE_MARGIN;
|
|
453
|
+
break;
|
|
454
|
+
case "bottom-right":
|
|
455
|
+
default:
|
|
456
|
+
left = vw - width - EDGE_MARGIN;
|
|
457
|
+
top = vh - height - EDGE_MARGIN;
|
|
458
|
+
break;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
this.shell.style.width = `${width}px`;
|
|
462
|
+
this.shell.style.height = `${height}px`;
|
|
463
|
+
this.shell.style.left = `${left}px`;
|
|
464
|
+
this.shell.style.top = `${top}px`;
|
|
465
|
+
this._initDraggable();
|
|
466
|
+
}
|
|
467
|
+
_initDraggable() {
|
|
468
|
+
if (!this.shell) return;
|
|
469
|
+
if (this.draggable) return;
|
|
470
|
+
this.draggable = new DraggableResizable(this.shell, {
|
|
471
|
+
dragHandle: this.dragHandle,
|
|
472
|
+
resizeHandles: this.resizeHandles,
|
|
473
|
+
constrainToViewport: true,
|
|
474
|
+
maintainAspectRatio: true,
|
|
475
|
+
minWidth: MIN_WIDTH,
|
|
476
|
+
minHeight: 100,
|
|
477
|
+
maxWidth: () => Math.max(MIN_WIDTH, window.innerWidth - EDGE_MARGIN * 2),
|
|
478
|
+
maxHeight: () => Math.max(100, window.innerHeight - EDGE_MARGIN * 2),
|
|
479
|
+
classPrefix: `${this.classPrefix}-floating`,
|
|
480
|
+
keyboardDragKey: "d",
|
|
481
|
+
keyboardResizeKey: "r",
|
|
482
|
+
keyboardStep: 10,
|
|
483
|
+
keyboardStepLarge: 50,
|
|
484
|
+
pointerResizeIndicatorText: i18n.t("player.floatingPlayerDialog"),
|
|
485
|
+
onDragEnd: () => this._savePrefs(),
|
|
486
|
+
onResizeEnd: () => this._savePrefs(),
|
|
487
|
+
onDragStart: (event) => {
|
|
488
|
+
const target = event.target;
|
|
489
|
+
if (!target) return true;
|
|
490
|
+
if (target.closest(`.${this.classPrefix}-floating-close`)) return false;
|
|
491
|
+
if (target.closest(`.${this.classPrefix}-controls`)) return false;
|
|
492
|
+
if (target.closest(`.${this.classPrefix}-floating-resize-handle`)) return false;
|
|
493
|
+
return true;
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
_savePrefs() {
|
|
498
|
+
if (!this.shell || !this.player.storage?.saveFloatingPreferences) return;
|
|
499
|
+
const rect = this.shell.getBoundingClientRect();
|
|
500
|
+
this.player.storage.saveFloatingPreferences({
|
|
501
|
+
width: Math.round(rect.width),
|
|
502
|
+
height: Math.round(rect.height),
|
|
503
|
+
left: Math.round(rect.left),
|
|
504
|
+
top: Math.round(rect.top)
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
_activeElement() {
|
|
508
|
+
const active = document.activeElement;
|
|
509
|
+
return active && active instanceof HTMLElement ? active : null;
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
export {
|
|
513
|
+
FloatingPlayerManager
|
|
514
|
+
};
|
|
515
|
+
//# sourceMappingURL=vidply.FloatingPlayerManager-5EA4BYSR.js.map
|