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.
Files changed (85) hide show
  1. package/README.md +56 -16
  2. package/dist/dev/{vidply.AudioDescriptionManager-2LDAC6BR.js → vidply.AudioDescriptionManager-DCZFE3N2.js} +4 -4
  3. package/dist/dev/vidply.FloatingPlayerManager-5EA4BYSR.js +515 -0
  4. package/dist/dev/vidply.FloatingPlayerManager-5EA4BYSR.js.map +7 -0
  5. package/dist/dev/{vidply.SignLanguageManager-OOZLX2GI.js → vidply.SignLanguageManager-4NOFV4EZ.js} +7 -5
  6. package/dist/dev/{vidply.SignLanguageManager-OOZLX2GI.js.map → vidply.SignLanguageManager-4NOFV4EZ.js.map} +1 -1
  7. package/dist/dev/{vidply.TranscriptManager-BOYEUAA4.js → vidply.TranscriptManager-7TS37MMM.js} +9 -7
  8. package/dist/dev/{vidply.TranscriptManager-BOYEUAA4.js.map → vidply.TranscriptManager-7TS37MMM.js.map} +1 -1
  9. package/dist/dev/{vidply.chunk-ELJA4GMZ.js → vidply.chunk-2RIPRXWI.js} +28 -3
  10. package/dist/dev/vidply.chunk-2RIPRXWI.js.map +7 -0
  11. package/dist/dev/{vidply.chunk-S36ISCDT.js → vidply.chunk-5SDVVGOD.js} +2 -2
  12. package/dist/dev/vidply.chunk-COW6OPNQ.js +222 -0
  13. package/dist/dev/vidply.chunk-COW6OPNQ.js.map +7 -0
  14. package/dist/dev/{vidply.chunk-37V7VOIQ.js → vidply.chunk-D6GBU4V6.js} +2 -212
  15. package/dist/dev/vidply.chunk-D6GBU4V6.js.map +7 -0
  16. package/dist/dev/{vidply.chunk-VLF6H5PZ.js → vidply.chunk-FDUUJHK6.js} +13 -5
  17. package/dist/dev/vidply.chunk-FDUUJHK6.js.map +7 -0
  18. package/dist/dev/{vidply.chunk-SXDZXXZD.js → vidply.chunk-X6PJOVWZ.js} +7 -1
  19. package/dist/dev/{vidply.chunk-SXDZXXZD.js.map → vidply.chunk-X6PJOVWZ.js.map} +2 -2
  20. package/dist/dev/{vidply.de-HLO4IOPX.js → vidply.de-TEYITA3Z.js} +9 -1
  21. package/dist/dev/vidply.de-TEYITA3Z.js.map +7 -0
  22. package/dist/dev/{vidply.es-OBOYSHKU.js → vidply.es-OKQLRQPJ.js} +9 -1
  23. package/dist/dev/vidply.es-OKQLRQPJ.js.map +7 -0
  24. package/dist/dev/vidply.esm.js +320 -24
  25. package/dist/dev/vidply.esm.js.map +3 -3
  26. package/dist/dev/{vidply.fr-WDRPLLB6.js → vidply.fr-52424DPC.js} +9 -1
  27. package/dist/dev/vidply.fr-52424DPC.js.map +7 -0
  28. package/dist/dev/{vidply.ja-ILEIPUNW.js → vidply.ja-X4QNECSO.js} +9 -1
  29. package/dist/dev/vidply.ja-X4QNECSO.js.map +7 -0
  30. package/dist/legacy/vidply.js +898 -15
  31. package/dist/legacy/vidply.js.map +4 -4
  32. package/dist/legacy/vidply.min.js +1 -1
  33. package/dist/legacy/vidply.min.meta.json +73 -21
  34. package/dist/prod/{vidply.AudioDescriptionManager-ILVU6JG2.min.js → vidply.AudioDescriptionManager-JJFZSG7G.min.js} +1 -1
  35. package/dist/prod/vidply.FloatingPlayerManager-JVNKAK4N.min.js +6 -0
  36. package/dist/prod/vidply.SignLanguageManager-6FBSQB4U.min.js +6 -0
  37. package/dist/prod/vidply.TranscriptManager-L5E737CR.min.js +6 -0
  38. package/dist/prod/{vidply.chunk-ALJYNEGS.min.js → vidply.chunk-DPEZK7YT.min.js} +1 -1
  39. package/dist/prod/vidply.chunk-KXQK2NF7.min.js +6 -0
  40. package/dist/prod/vidply.chunk-MOAZJSWV.min.js +6 -0
  41. package/dist/prod/vidply.chunk-QGVFMQUO.min.js +6 -0
  42. package/dist/prod/vidply.chunk-VSKF7AQ2.min.js +6 -0
  43. package/dist/prod/{vidply.chunk-KYQFJSLN.min.js → vidply.chunk-ZDIQYKKX.min.js} +1 -1
  44. package/dist/prod/vidply.de-EIZK243P.min.js +6 -0
  45. package/dist/prod/vidply.es-JGYFGRSI.min.js +6 -0
  46. package/dist/prod/vidply.esm.min.js +1 -1
  47. package/dist/prod/vidply.fr-XAQ6ZIB7.min.js +6 -0
  48. package/dist/prod/vidply.ja-S77KR54S.min.js +6 -0
  49. package/dist/vidply.css +475 -209
  50. package/dist/vidply.esm.min.meta.json +171 -77
  51. package/dist/vidply.min.css +1 -1
  52. package/package.json +12 -6
  53. package/src/controls/CaptionManager.ts +555 -523
  54. package/src/controls/ControlBar.ts +172 -19
  55. package/src/core/FloatingPlayerManager.ts +631 -0
  56. package/src/core/Player.ts +6036 -5946
  57. package/src/i18n/languages/de.ts +8 -0
  58. package/src/i18n/languages/en.ts +8 -0
  59. package/src/i18n/languages/es.ts +8 -0
  60. package/src/i18n/languages/fr.ts +8 -0
  61. package/src/i18n/languages/ja.ts +8 -0
  62. package/src/index.ts +5 -0
  63. package/src/styles/vidply.css +475 -209
  64. package/src/types/options.ts +6 -0
  65. package/src/types/state.ts +1 -0
  66. package/src/utils/DownloadInfo.ts +148 -0
  67. package/src/utils/StorageManager.ts +8 -0
  68. package/dist/dev/vidply.chunk-37V7VOIQ.js.map +0 -7
  69. package/dist/dev/vidply.chunk-ELJA4GMZ.js.map +0 -7
  70. package/dist/dev/vidply.chunk-VLF6H5PZ.js.map +0 -7
  71. package/dist/dev/vidply.de-HLO4IOPX.js.map +0 -7
  72. package/dist/dev/vidply.es-OBOYSHKU.js.map +0 -7
  73. package/dist/dev/vidply.fr-WDRPLLB6.js.map +0 -7
  74. package/dist/dev/vidply.ja-ILEIPUNW.js.map +0 -7
  75. package/dist/prod/vidply.SignLanguageManager-UPGXSO5F.min.js +0 -6
  76. package/dist/prod/vidply.TranscriptManager-PLVKPYNB.min.js +0 -6
  77. package/dist/prod/vidply.chunk-6XO2O3OT.min.js +0 -6
  78. package/dist/prod/vidply.chunk-UO33KGOT.min.js +0 -6
  79. package/dist/prod/vidply.chunk-XHLTMSR5.min.js +0 -6
  80. package/dist/prod/vidply.de-CKLFPHNF.min.js +0 -6
  81. package/dist/prod/vidply.es-FYZBTBVH.min.js +0 -6
  82. package/dist/prod/vidply.fr-E7FMYILU.min.js +0 -6
  83. package/dist/prod/vidply.ja-NOW6P3IH.min.js +0 -6
  84. /package/dist/dev/{vidply.AudioDescriptionManager-2LDAC6BR.js.map → vidply.AudioDescriptionManager-DCZFE3N2.js.map} +0 -0
  85. /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 built with vanilla ES6 JavaScript. 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.
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
  ![License](https://img.shields.io/badge/license-GPL--2.0--or--later-blue.svg)
8
- ![ES6](https://img.shields.io/badge/ES6-Module-yellow.svg)
8
+ ![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue.svg)
9
+ ![ESM](https://img.shields.io/badge/ESM-Module-yellow.svg)
9
10
  ![WCAG](https://img.shields.io/badge/WCAG-2.2%20AA-green.svg)
10
- ![Version](https://img.shields.io/badge/version-1.0.45-brightgreen.svg)
11
+ ![Version](https://img.shields.io/badge/version-1.1.3-brightgreen.svg)
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** - ES6 modules with tree-shaking support
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 (Source Files)
158
+ ### Option 3: Development (TypeScript Sources)
152
159
 
153
- ```javascript
154
- import Player from './src/index.js';
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
- ```javascript
617
- import { i18n } from './src/i18n/i18n.js';
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
- ```javascript
635
- import { i18n } from './src/i18n/i18n.js';
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 JavaScript and clean-css for CSS.
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 # Build JavaScript only
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-ELJA4GMZ.js";
9
- import "./vidply.chunk-SXDZXXZD.js";
10
- import "./vidply.chunk-VLF6H5PZ.js";
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-2LDAC6BR.js.map
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