vidply 1.1.3 → 1.1.5
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 +51 -2
- package/dist/dev/{vidply.AudioDescriptionManager-AXPP5OKO.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.FloatingPlayerManager-AEMEBLF7.js +514 -0
- package/dist/dev/vidply.FloatingPlayerManager-AEMEBLF7.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-55W33EGZ.js → vidply.chunk-2RIPRXWI.js} +3 -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 +871 -15
- package/dist/legacy/vidply.js.map +4 -4
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +72 -20
- package/dist/prod/{vidply.AudioDescriptionManager-623CQKLU.min.js → vidply.AudioDescriptionManager-JJFZSG7G.min.js} +1 -1
- package/dist/prod/vidply.FloatingPlayerManager-JVNKAK4N.min.js +6 -0
- package/dist/prod/vidply.FloatingPlayerManager-NRKSWDTL.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-2YTNSNRD.min.js → vidply.chunk-MOAZJSWV.min.js} +1 -1
- 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 +169 -75
- package/dist/vidply.min.css +1 -1
- package/package.json +1 -1
- package/src/controls/CaptionManager.ts +555 -555
- package/src/controls/ControlBar.ts +172 -19
- package/src/core/FloatingPlayerManager.ts +636 -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-55W33EGZ.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-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-AXPP5OKO.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
|
@@ -8,7 +8,7 @@ A modern, feature-rich media player authored in strict TypeScript and shipped as
|
|
|
8
8
|

|
|
9
9
|

|
|
10
10
|

|
|
11
|
-

|
|
12
12
|
|
|
13
13
|
## Live Demos
|
|
14
14
|
|
|
@@ -81,7 +81,14 @@ Try VidPly in action:
|
|
|
81
81
|
- **Volume Control** - 0-100% with mute
|
|
82
82
|
- **Loop Playback** - Single or playlist loop
|
|
83
83
|
- **Fullscreen Mode** - Native fullscreen API with smart playlist overlay
|
|
84
|
-
- **Picture-in-Picture** - PiP support
|
|
84
|
+
- **Picture-in-Picture** - Native browser PiP support (toggled via the standard PiP button)
|
|
85
|
+
- **Custom Floating Player (Miniplayer)** - Optional in-page floating window that
|
|
86
|
+
- automatically floats when the original player scrolls out of the viewport and docks back when it scrolls in
|
|
87
|
+
- can be pinned/unpinned manually via the PiP button (manual pin overrides scroll behavior)
|
|
88
|
+
- is fully draggable and resizable, with persistent geometry per player
|
|
89
|
+
- keeps VidPly captions, transport controls and fullscreen working inside the floating shell
|
|
90
|
+
- suppresses native browser PiP automatically while enabled
|
|
91
|
+
- is desktop-only (disabled below 768 px viewport width by default)
|
|
85
92
|
|
|
86
93
|
### Internationalization
|
|
87
94
|
Built-in support for 5 languages:
|
|
@@ -258,6 +265,43 @@ const player = new Player('#my-video', {
|
|
|
258
265
|
});
|
|
259
266
|
```
|
|
260
267
|
|
|
268
|
+
### Custom Floating Player (Miniplayer)
|
|
269
|
+
|
|
270
|
+
Enable the in-page floating player ("own PiP"). When the original video scrolls
|
|
271
|
+
out of the viewport, VidPly pops up a draggable, resizable floating shell in the
|
|
272
|
+
chosen corner; when the original scrolls back in, it docks again. Users can also
|
|
273
|
+
manually pin/unpin the floating player via the PiP button in the control bar.
|
|
274
|
+
The native browser Picture-in-Picture API is automatically suppressed while
|
|
275
|
+
floating is enabled, so users get a single, consistent experience.
|
|
276
|
+
|
|
277
|
+
Enable via the `data-vidply-options` JSON blob:
|
|
278
|
+
|
|
279
|
+
```html
|
|
280
|
+
<video
|
|
281
|
+
data-vidply
|
|
282
|
+
data-vidply-options='{"floating": true, "floatingPosition": "bottom-right", "floatingMinViewportWidth": 768}'
|
|
283
|
+
src="video.mp4"
|
|
284
|
+
width="800" height="450">
|
|
285
|
+
<track kind="subtitles" src="captions.vtt" srclang="en" label="English">
|
|
286
|
+
</video>
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
Or programmatically:
|
|
290
|
+
|
|
291
|
+
```javascript
|
|
292
|
+
const player = new Player('#my-video', {
|
|
293
|
+
floating: true,
|
|
294
|
+
floatingPosition: 'bottom-right', // or 'bottom-left' | 'top-right' | 'top-left'
|
|
295
|
+
floatingMinViewportWidth: 768 // disable feature below this viewport width
|
|
296
|
+
});
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
Notes:
|
|
300
|
+
- Audio-only players (`<audio>`) ignore the floating option.
|
|
301
|
+
- Closing the floating window pauses playback and prevents auto-float again until the next user-initiated `play`.
|
|
302
|
+
- The floating window persists its size/position per player via local storage.
|
|
303
|
+
- Below `floatingMinViewportWidth` (default 768 px) the PiP button is hidden and the floating feature is disabled.
|
|
304
|
+
|
|
261
305
|
### DASH + HLS + MP4 Fallback
|
|
262
306
|
|
|
263
307
|
For maximum device compatibility, provide all three formats:
|
|
@@ -312,6 +356,11 @@ const player = new Player('#video', {
|
|
|
312
356
|
downloadButton: false, // Show a download button in the control bar
|
|
313
357
|
downloadUrl: null, // Optional explicit download URL (falls back to current src)
|
|
314
358
|
|
|
359
|
+
// Custom Floating Player (in-page miniplayer / "own PiP")
|
|
360
|
+
floating: false, // Enable the custom floating player; also disables native browser PiP
|
|
361
|
+
floatingPosition: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'
|
|
362
|
+
floatingMinViewportWidth: 768, // Floating feature is hidden below this viewport width (px)
|
|
363
|
+
|
|
315
364
|
// Captions
|
|
316
365
|
captions: true,
|
|
317
366
|
captionsDefault: false,
|
|
@@ -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
|