vidply 1.0.28 → 1.0.29

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 (61) hide show
  1. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js → vidply.TranscriptManager-T677KF4N.js} +4 -5
  2. package/dist/dev/{vidply.TranscriptManager-QSF2PWUN.js.map → vidply.TranscriptManager-T677KF4N.js.map} +2 -2
  3. package/dist/dev/{vidply.chunk-SRM7VNHG.js → vidply.chunk-GS2JX5RQ.js} +136 -95
  4. package/dist/dev/vidply.chunk-GS2JX5RQ.js.map +7 -0
  5. package/dist/dev/vidply.esm.js +1674 -310
  6. package/dist/dev/vidply.esm.js.map +4 -4
  7. package/dist/legacy/vidply.js +1776 -348
  8. package/dist/legacy/vidply.js.map +4 -4
  9. package/dist/legacy/vidply.min.js +1 -1
  10. package/dist/legacy/vidply.min.meta.json +92 -24
  11. package/dist/prod/vidply.TranscriptManager-WFZSW6NR.min.js +6 -0
  12. package/dist/prod/vidply.chunk-LGTJRPUL.min.js +6 -0
  13. package/dist/prod/vidply.esm.min.js +8 -8
  14. package/dist/vidply.esm.min.meta.json +92 -24
  15. package/package.json +1 -1
  16. package/src/controls/ControlBar.js +3 -7
  17. package/src/controls/TranscriptManager.js +7 -7
  18. package/src/core/AudioDescriptionManager.js +701 -0
  19. package/src/core/Player.js +4776 -4921
  20. package/src/core/SignLanguageManager.js +1134 -0
  21. package/src/utils/DOMUtils.js +153 -114
  22. package/src/utils/MenuFactory.js +374 -0
  23. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js +0 -1744
  24. package/dist/dev/vidply.TranscriptManager-GZKY44ON.js.map +0 -7
  25. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js +0 -1744
  26. package/dist/dev/vidply.TranscriptManager-UTJBQC5B.js.map +0 -7
  27. package/dist/dev/vidply.chunk-5663PYKK.js +0 -1631
  28. package/dist/dev/vidply.chunk-5663PYKK.js.map +0 -7
  29. package/dist/dev/vidply.chunk-SRM7VNHG.js.map +0 -7
  30. package/dist/dev/vidply.chunk-UH5MTGKF.js +0 -1630
  31. package/dist/dev/vidply.chunk-UH5MTGKF.js.map +0 -7
  32. package/dist/dev/vidply.de-RXAJM5QE.js +0 -181
  33. package/dist/dev/vidply.de-RXAJM5QE.js.map +0 -7
  34. package/dist/dev/vidply.de-THBIMP4S.js +0 -180
  35. package/dist/dev/vidply.de-THBIMP4S.js.map +0 -7
  36. package/dist/dev/vidply.es-6VWDNNNL.js +0 -180
  37. package/dist/dev/vidply.es-6VWDNNNL.js.map +0 -7
  38. package/dist/dev/vidply.es-SADVLJTQ.js +0 -181
  39. package/dist/dev/vidply.es-SADVLJTQ.js.map +0 -7
  40. package/dist/dev/vidply.fr-V3VAYBBT.js +0 -181
  41. package/dist/dev/vidply.fr-V3VAYBBT.js.map +0 -7
  42. package/dist/dev/vidply.fr-WHTWCHWT.js +0 -180
  43. package/dist/dev/vidply.fr-WHTWCHWT.js.map +0 -7
  44. package/dist/dev/vidply.ja-BFQNPOFI.js +0 -180
  45. package/dist/dev/vidply.ja-BFQNPOFI.js.map +0 -7
  46. package/dist/dev/vidply.ja-KL2TLZGJ.js +0 -181
  47. package/dist/dev/vidply.ja-KL2TLZGJ.js.map +0 -7
  48. package/dist/prod/vidply.TranscriptManager-DZ2WZU3K.min.js +0 -6
  49. package/dist/prod/vidply.TranscriptManager-E5QHGFIR.min.js +0 -6
  50. package/dist/prod/vidply.TranscriptManager-UZ6DUFB6.min.js +0 -6
  51. package/dist/prod/vidply.chunk-5DWTMWEO.min.js +0 -6
  52. package/dist/prod/vidply.chunk-IBNYTGGM.min.js +0 -6
  53. package/dist/prod/vidply.chunk-MBUR3U5L.min.js +0 -6
  54. package/dist/prod/vidply.de-HGJBCLLE.min.js +0 -6
  55. package/dist/prod/vidply.de-SWFW4HYT.min.js +0 -6
  56. package/dist/prod/vidply.es-7BJ2DJAY.min.js +0 -6
  57. package/dist/prod/vidply.es-CZEBXCZN.min.js +0 -6
  58. package/dist/prod/vidply.fr-DPVR5DFY.min.js +0 -6
  59. package/dist/prod/vidply.fr-HFOL7MWA.min.js +0 -6
  60. package/dist/prod/vidply.ja-PEBVWKVH.min.js +0 -6
  61. package/dist/prod/vidply.ja-QTVU5C25.min.js +0 -6
@@ -21,7 +21,7 @@ import {
21
21
  focusFirstMenuItem,
22
22
  i18n,
23
23
  preventDragOnElement
24
- } from "./vidply.chunk-SRM7VNHG.js";
24
+ } from "./vidply.chunk-GS2JX5RQ.js";
25
25
 
26
26
  // src/utils/EventEmitter.js
27
27
  var EventEmitter = class {
@@ -64,6 +64,33 @@ var EventEmitter = class {
64
64
  }
65
65
  };
66
66
 
67
+ // src/utils/PerformanceUtils.js
68
+ function debounce(func, wait = 100) {
69
+ let timeout;
70
+ return function executedFunction(...args) {
71
+ const later = () => {
72
+ clearTimeout(timeout);
73
+ func(...args);
74
+ };
75
+ clearTimeout(timeout);
76
+ timeout = setTimeout(later, wait);
77
+ };
78
+ }
79
+ function isMobile(breakpoint = 768) {
80
+ return window.innerWidth < breakpoint;
81
+ }
82
+ function rafWithTimeout(callback, timeout = 100) {
83
+ let called = false;
84
+ const execute = () => {
85
+ if (!called) {
86
+ called = true;
87
+ callback();
88
+ }
89
+ };
90
+ requestAnimationFrame(execute);
91
+ setTimeout(execute, timeout);
92
+ }
93
+
67
94
  // src/controls/ControlBar.js
68
95
  var ControlBar = class {
69
96
  constructor(player) {
@@ -84,17 +111,13 @@ var ControlBar = class {
84
111
  this.setupAutoHide();
85
112
  this.setupOverflowDetection();
86
113
  }
87
- // Helper method to check if we're on a mobile device
88
- isMobile() {
89
- return window.innerWidth < 768;
90
- }
91
114
  // Helper method to detect touch devices
92
115
  isTouchDevice() {
93
116
  return "ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
94
117
  }
95
118
  // Smart menu positioning to avoid overflow
96
119
  positionMenu(menu, button, immediate = false) {
97
- const isMobile2 = this.isMobile();
120
+ const mobile = isMobile();
98
121
  const isOverflowMenu = menu.classList.contains(`${this.player.options.classPrefix}-overflow-menu-list`);
99
122
  const isFullscreen = this.player.state.fullscreen;
100
123
  if (isFullscreen && menu.parentElement === this.player.container) {
@@ -135,7 +158,7 @@ var ControlBar = class {
135
158
  }
136
159
  return;
137
160
  }
138
- if (isMobile2) {
161
+ if (mobile) {
139
162
  const isVolumeMenu = menu.classList.contains(`${this.player.options.classPrefix}-volume-menu`);
140
163
  const doMobilePositioning = () => {
141
164
  const parentContainer = button.parentElement;
@@ -2503,33 +2526,6 @@ var ControlBar = class {
2503
2526
  }
2504
2527
  };
2505
2528
 
2506
- // src/utils/PerformanceUtils.js
2507
- function debounce(func, wait = 100) {
2508
- let timeout;
2509
- return function executedFunction(...args) {
2510
- const later = () => {
2511
- clearTimeout(timeout);
2512
- func(...args);
2513
- };
2514
- clearTimeout(timeout);
2515
- timeout = setTimeout(later, wait);
2516
- };
2517
- }
2518
- function isMobile(breakpoint = 768) {
2519
- return window.innerWidth < breakpoint;
2520
- }
2521
- function rafWithTimeout(callback, timeout = 100) {
2522
- let called = false;
2523
- const execute = () => {
2524
- if (!called) {
2525
- called = true;
2526
- callback();
2527
- }
2528
- };
2529
- requestAnimationFrame(execute);
2530
- setTimeout(execute, timeout);
2531
- }
2532
-
2533
2529
  // src/controls/CaptionManager.js
2534
2530
  var CaptionManager = class {
2535
2531
  constructor(player) {
@@ -3059,97 +3055,1552 @@ var KeyboardManager = class {
3059
3055
  }
3060
3056
  };
3061
3057
 
3062
- // src/core/Player.js
3063
- var playerInstanceCounter = 0;
3064
- var Player = class _Player extends EventEmitter {
3065
- constructor(element, options = {}) {
3066
- super();
3067
- this.element = typeof element === "string" ? document.querySelector(element) : element;
3068
- if (!this.element) {
3069
- throw new Error("VidPly: Element not found");
3058
+ // src/core/AudioDescriptionManager.js
3059
+ var AudioDescriptionManager = class {
3060
+ constructor(player) {
3061
+ this.player = player;
3062
+ this.enabled = false;
3063
+ this.desiredState = false;
3064
+ this.src = player.options.audioDescriptionSrc;
3065
+ this.sourceElement = null;
3066
+ this.originalSource = null;
3067
+ this.captionTracks = [];
3068
+ }
3069
+ /**
3070
+ * Initialize audio description from source elements
3071
+ * Called during player initialization
3072
+ */
3073
+ initFromSourceElements(sourceElements, trackElements) {
3074
+ for (const sourceEl of sourceElements) {
3075
+ const descSrc = sourceEl.getAttribute("data-desc-src");
3076
+ const origSrc = sourceEl.getAttribute("data-orig-src");
3077
+ if (descSrc || origSrc) {
3078
+ if (!this.sourceElement) {
3079
+ this.sourceElement = sourceEl;
3080
+ }
3081
+ if (origSrc) {
3082
+ if (!this.originalSource) {
3083
+ this.originalSource = origSrc;
3084
+ }
3085
+ if (!this.player.originalSrc) {
3086
+ this.player.originalSrc = origSrc;
3087
+ }
3088
+ } else {
3089
+ const currentSrcAttr = sourceEl.getAttribute("src");
3090
+ if (!this.originalSource && currentSrcAttr) {
3091
+ this.originalSource = currentSrcAttr;
3092
+ }
3093
+ if (!this.player.originalSrc && currentSrcAttr) {
3094
+ this.player.originalSrc = currentSrcAttr;
3095
+ }
3096
+ }
3097
+ if (descSrc && !this.src) {
3098
+ this.src = descSrc;
3099
+ }
3100
+ }
3070
3101
  }
3071
- playerInstanceCounter++;
3072
- this.instanceId = playerInstanceCounter;
3073
- if (this.element.tagName !== "VIDEO" && this.element.tagName !== "AUDIO") {
3074
- const mediaType = options.mediaType || "video";
3075
- const mediaElement = document.createElement(mediaType);
3076
- Array.from(this.element.attributes).forEach((attr) => {
3077
- if (attr.name !== "id" && attr.name !== "class" && !attr.name.startsWith("data-")) {
3078
- mediaElement.setAttribute(attr.name, attr.value);
3102
+ trackElements.forEach((trackEl) => {
3103
+ const trackKind = trackEl.getAttribute("kind");
3104
+ const trackDescSrc = trackEl.getAttribute("data-desc-src");
3105
+ if ((trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters" || trackKind === "descriptions") && trackDescSrc) {
3106
+ this.captionTracks.push({
3107
+ trackElement: trackEl,
3108
+ originalSrc: trackEl.getAttribute("src"),
3109
+ describedSrc: trackDescSrc,
3110
+ originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
3111
+ explicit: true
3112
+ });
3113
+ this.player.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute("src")} -> ${trackDescSrc}`);
3114
+ }
3115
+ });
3116
+ }
3117
+ /**
3118
+ * Check if audio description is available
3119
+ */
3120
+ isAvailable() {
3121
+ const hasSourceElementsWithDesc = this.player.sourceElements.some(
3122
+ (el) => el.getAttribute("data-desc-src")
3123
+ );
3124
+ return !!(this.src || hasSourceElementsWithDesc || this.captionTracks.length > 0);
3125
+ }
3126
+ /**
3127
+ * Enable audio description
3128
+ */
3129
+ async enable() {
3130
+ const hasSourceElementsWithDesc = this.player.sourceElements.some(
3131
+ (el) => el.getAttribute("data-desc-src")
3132
+ );
3133
+ const hasTracksWithDesc = this.captionTracks.length > 0;
3134
+ if (!this.src && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
3135
+ console.warn("VidPly: No audio description source, source elements, or tracks provided");
3136
+ return;
3137
+ }
3138
+ this.desiredState = true;
3139
+ const currentTime = this.player.state.currentTime;
3140
+ const wasPlaying = this.player.state.playing;
3141
+ const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
3142
+ const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
3143
+ const currentCaptionText = this._getCurrentCaptionText();
3144
+ if (this.sourceElement) {
3145
+ await this._enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
3146
+ } else {
3147
+ await this._enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster);
3148
+ }
3149
+ }
3150
+ /**
3151
+ * Disable audio description
3152
+ */
3153
+ async disable() {
3154
+ if (!this.player.originalSrc) {
3155
+ return;
3156
+ }
3157
+ this.desiredState = false;
3158
+ const currentTime = this.player.state.currentTime;
3159
+ const wasPlaying = this.player.state.playing;
3160
+ const posterValue = this.player.element.poster || this.player.element.getAttribute("poster") || this.player.options.poster;
3161
+ const shouldKeepPoster = currentTime < 0.1 && !wasPlaying;
3162
+ const currentCaptionText = this._getCurrentCaptionText();
3163
+ if (this.sourceElement) {
3164
+ await this._disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText);
3165
+ } else {
3166
+ await this._disableWithDirectSrc(currentTime, wasPlaying, posterValue);
3167
+ }
3168
+ }
3169
+ /**
3170
+ * Toggle audio description
3171
+ */
3172
+ async toggle() {
3173
+ const descriptionTrack = this.player.findTextTrack("descriptions");
3174
+ const hasAudioDescriptionSrc = this.isAvailable();
3175
+ if (descriptionTrack && !hasAudioDescriptionSrc) {
3176
+ if (descriptionTrack.mode === "showing") {
3177
+ descriptionTrack.mode = "hidden";
3178
+ this.enabled = false;
3179
+ this.player.emit("audiodescriptiondisabled");
3180
+ } else {
3181
+ descriptionTrack.mode = "showing";
3182
+ this.enabled = true;
3183
+ this.player.emit("audiodescriptionenabled");
3184
+ }
3185
+ } else if (descriptionTrack && hasAudioDescriptionSrc) {
3186
+ if (this.enabled) {
3187
+ this.desiredState = false;
3188
+ await this.disable();
3189
+ } else {
3190
+ descriptionTrack.mode = "showing";
3191
+ this.desiredState = true;
3192
+ await this.enable();
3193
+ }
3194
+ } else if (hasAudioDescriptionSrc) {
3195
+ if (this.enabled) {
3196
+ this.desiredState = false;
3197
+ await this.disable();
3198
+ } else {
3199
+ this.desiredState = true;
3200
+ await this.enable();
3201
+ }
3202
+ }
3203
+ }
3204
+ /**
3205
+ * Get current caption text for synchronization
3206
+ */
3207
+ _getCurrentCaptionText() {
3208
+ if (this.player.captionManager && this.player.captionManager.currentTrack && this.player.captionManager.currentCue) {
3209
+ return this.player.captionManager.currentCue.text;
3210
+ }
3211
+ return null;
3212
+ }
3213
+ /**
3214
+ * Validate that a track URL exists
3215
+ */
3216
+ async _validateTrackExists(url) {
3217
+ try {
3218
+ const response = await fetch(url, { method: "HEAD" });
3219
+ return response.ok;
3220
+ } catch {
3221
+ return false;
3222
+ }
3223
+ }
3224
+ /**
3225
+ * Swap caption tracks to described versions
3226
+ */
3227
+ async _swapCaptionTracks(toDescribed = true) {
3228
+ if (this.captionTracks.length === 0) return [];
3229
+ const swappedTracks = [];
3230
+ const validationPromises = this.captionTracks.map(async (trackInfo) => {
3231
+ if (trackInfo.trackElement && trackInfo.describedSrc) {
3232
+ if (trackInfo.explicit === true) {
3233
+ try {
3234
+ const exists = await this._validateTrackExists(
3235
+ toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc
3236
+ );
3237
+ return { trackInfo, exists };
3238
+ } catch {
3239
+ return { trackInfo, exists: false };
3240
+ }
3241
+ }
3242
+ }
3243
+ return { trackInfo, exists: false };
3244
+ });
3245
+ const validationResults = await Promise.all(validationPromises);
3246
+ const tracksToSwap = validationResults.filter((result) => result.exists);
3247
+ if (tracksToSwap.length > 0) {
3248
+ const trackModes = /* @__PURE__ */ new Map();
3249
+ tracksToSwap.forEach(({ trackInfo }) => {
3250
+ const textTrack = trackInfo.trackElement.track;
3251
+ if (textTrack) {
3252
+ trackModes.set(trackInfo, {
3253
+ wasShowing: textTrack.mode === "showing",
3254
+ wasHidden: textTrack.mode === "hidden"
3255
+ });
3256
+ } else {
3257
+ trackModes.set(trackInfo, { wasShowing: false, wasHidden: false });
3079
3258
  }
3080
3259
  });
3081
- const tracks = this.element.querySelectorAll("track");
3082
- tracks.forEach((track) => {
3083
- mediaElement.appendChild(track.cloneNode(true));
3260
+ const tracksToReadd = tracksToSwap.map(({ trackInfo }) => {
3261
+ const attributes = {};
3262
+ Array.from(trackInfo.trackElement.attributes).forEach((attr) => {
3263
+ attributes[attr.name] = attr.value;
3264
+ });
3265
+ const result = {
3266
+ trackInfo,
3267
+ oldSrc: trackInfo.trackElement.getAttribute("src"),
3268
+ parent: trackInfo.trackElement.parentNode,
3269
+ nextSibling: trackInfo.trackElement.nextSibling,
3270
+ attributes
3271
+ };
3272
+ trackInfo.trackElement.remove();
3273
+ return result;
3274
+ });
3275
+ this.player.element.load();
3276
+ await new Promise((resolve) => {
3277
+ setTimeout(() => {
3278
+ tracksToReadd.forEach(({ trackInfo, parent, nextSibling, attributes }) => {
3279
+ swappedTracks.push(trackInfo);
3280
+ const newTrackElement = document.createElement("track");
3281
+ const newSrc = toDescribed ? trackInfo.describedSrc : trackInfo.originalSrc;
3282
+ newTrackElement.setAttribute("src", newSrc);
3283
+ Object.keys(attributes).forEach((attrName) => {
3284
+ if (attrName !== "src" && attrName !== "data-desc-src") {
3285
+ newTrackElement.setAttribute(attrName, attributes[attrName]);
3286
+ }
3287
+ });
3288
+ const targetParent = parent || this.player.element;
3289
+ if (nextSibling && nextSibling.parentNode) {
3290
+ targetParent.insertBefore(newTrackElement, nextSibling);
3291
+ } else {
3292
+ targetParent.appendChild(newTrackElement);
3293
+ }
3294
+ trackInfo.trackElement = newTrackElement;
3295
+ });
3296
+ this.player.invalidateTrackCache();
3297
+ const setupNewTracks = () => {
3298
+ this.player.setManagedTimeout(() => {
3299
+ swappedTracks.forEach((trackInfo) => {
3300
+ const newTextTrack = trackInfo.trackElement.track;
3301
+ if (newTextTrack) {
3302
+ const modeInfo = trackModes.get(trackInfo) || { wasShowing: false, wasHidden: false };
3303
+ newTextTrack.mode = "hidden";
3304
+ const restoreMode = () => {
3305
+ if (modeInfo.wasShowing || modeInfo.wasHidden) {
3306
+ newTextTrack.mode = "hidden";
3307
+ } else {
3308
+ newTextTrack.mode = "disabled";
3309
+ }
3310
+ };
3311
+ if (newTextTrack.readyState >= 2) {
3312
+ restoreMode();
3313
+ } else {
3314
+ newTextTrack.addEventListener("load", restoreMode, { once: true });
3315
+ newTextTrack.addEventListener("error", restoreMode, { once: true });
3316
+ }
3317
+ }
3318
+ });
3319
+ }, 300);
3320
+ };
3321
+ if (this.player.element.readyState >= 1) {
3322
+ setTimeout(setupNewTracks, 200);
3323
+ } else {
3324
+ this.player.element.addEventListener("loadedmetadata", setupNewTracks, { once: true });
3325
+ setTimeout(setupNewTracks, 2e3);
3326
+ }
3327
+ resolve();
3328
+ }, 100);
3084
3329
  });
3085
- this.element.innerHTML = "";
3086
- this.element.appendChild(mediaElement);
3087
- this.element = mediaElement;
3088
3330
  }
3089
- this._originalElement = this.element;
3090
- this.options = {
3091
- // Display
3092
- width: null,
3093
- height: null,
3094
- poster: null,
3095
- responsive: true,
3096
- fillContainer: false,
3097
- // Playback
3098
- autoplay: false,
3099
- loop: false,
3100
- muted: false,
3101
- volume: 0.8,
3102
- playbackSpeed: 1,
3103
- preload: "metadata",
3104
- startTime: 0,
3105
- playsInline: true,
3106
- // Enable inline playback on iOS (prevents native fullscreen)
3107
- // Controls
3108
- controls: true,
3109
- hideControlsDelay: 3e3,
3110
- playPauseButton: true,
3111
- progressBar: true,
3112
- currentTime: true,
3113
- duration: true,
3114
- volumeControl: true,
3115
- muteButton: true,
3116
- chaptersButton: true,
3117
- qualityButton: true,
3118
- captionStyleButton: true,
3119
- speedButton: true,
3120
- captionsButton: true,
3121
- transcriptButton: true,
3122
- fullscreenButton: true,
3123
- pipButton: false,
3124
- // Seeking
3125
- seekInterval: 10,
3126
- seekIntervalLarge: 30,
3127
- // Captions
3128
- captions: true,
3129
- captionsDefault: false,
3130
- captionsFontSize: "100%",
3131
- captionsFontFamily: "sans-serif",
3132
- captionsColor: "#FFFFFF",
3133
- captionsBackgroundColor: "#000000",
3134
- captionsOpacity: 0.8,
3135
- // Audio Description
3136
- audioDescription: true,
3137
- audioDescriptionSrc: null,
3138
- // URL to audio-described version
3139
- audioDescriptionButton: true,
3140
- // Sign Language
3141
- signLanguage: true,
3142
- signLanguageSrc: null,
3143
- // URL to sign language video
3144
- signLanguageButton: true,
3145
- signLanguagePosition: "bottom-right",
3146
- // Position: 'bottom-right', 'bottom-left', 'top-right', 'top-left'
3147
- // Transcripts
3148
- transcript: false,
3149
- transcriptPosition: "external",
3150
- transcriptContainer: null,
3151
- // Keyboard
3152
- keyboard: true,
3331
+ return swappedTracks;
3332
+ }
3333
+ /**
3334
+ * Update source elements to described versions
3335
+ */
3336
+ _updateSourceElements(toDescribed = true) {
3337
+ const sourceElements = this.player.sourceElements;
3338
+ const sourcesToUpdate = [];
3339
+ sourceElements.forEach((sourceEl) => {
3340
+ const descSrcAttr = sourceEl.getAttribute("data-desc-src");
3341
+ const currentSrc = sourceEl.getAttribute("src");
3342
+ if (descSrcAttr) {
3343
+ const type = sourceEl.getAttribute("type");
3344
+ let origSrc = sourceEl.getAttribute("data-orig-src") || currentSrc;
3345
+ sourcesToUpdate.push({
3346
+ src: toDescribed ? descSrcAttr : origSrc,
3347
+ type,
3348
+ origSrc,
3349
+ descSrc: descSrcAttr
3350
+ });
3351
+ } else {
3352
+ sourcesToUpdate.push({
3353
+ src: sourceEl.getAttribute("src"),
3354
+ type: sourceEl.getAttribute("type"),
3355
+ origSrc: null,
3356
+ descSrc: null
3357
+ });
3358
+ }
3359
+ });
3360
+ if (this.player.element.hasAttribute("src")) {
3361
+ this.player.element.removeAttribute("src");
3362
+ }
3363
+ sourceElements.forEach((sourceEl) => sourceEl.remove());
3364
+ sourcesToUpdate.forEach((sourceInfo) => {
3365
+ const newSource = document.createElement("source");
3366
+ newSource.setAttribute("src", sourceInfo.src);
3367
+ if (sourceInfo.type) {
3368
+ newSource.setAttribute("type", sourceInfo.type);
3369
+ }
3370
+ if (sourceInfo.origSrc) {
3371
+ newSource.setAttribute("data-orig-src", sourceInfo.origSrc);
3372
+ }
3373
+ if (sourceInfo.descSrc) {
3374
+ newSource.setAttribute("data-desc-src", sourceInfo.descSrc);
3375
+ }
3376
+ const firstTrack = this.player.element.querySelector("track");
3377
+ if (firstTrack) {
3378
+ this.player.element.insertBefore(newSource, firstTrack);
3379
+ } else {
3380
+ this.player.element.appendChild(newSource);
3381
+ }
3382
+ });
3383
+ this.player._sourceElementsDirty = true;
3384
+ this.player._sourceElementsCache = null;
3385
+ }
3386
+ /**
3387
+ * Wait for media to be ready
3388
+ */
3389
+ async _waitForMediaReady(needSeek = false) {
3390
+ await new Promise((resolve) => {
3391
+ if (this.player.element.readyState >= 1) {
3392
+ resolve();
3393
+ } else {
3394
+ const onLoad = () => {
3395
+ this.player.element.removeEventListener("loadedmetadata", onLoad);
3396
+ resolve();
3397
+ };
3398
+ this.player.element.addEventListener("loadedmetadata", onLoad);
3399
+ }
3400
+ });
3401
+ await new Promise((resolve) => setTimeout(resolve, 300));
3402
+ if (needSeek) {
3403
+ await new Promise((resolve) => {
3404
+ if (this.player.element.readyState >= 3) {
3405
+ resolve();
3406
+ } else {
3407
+ const onCanPlay = () => {
3408
+ this.player.element.removeEventListener("canplay", onCanPlay);
3409
+ this.player.element.removeEventListener("canplaythrough", onCanPlay);
3410
+ resolve();
3411
+ };
3412
+ this.player.element.addEventListener("canplay", onCanPlay, { once: true });
3413
+ this.player.element.addEventListener("canplaythrough", onCanPlay, { once: true });
3414
+ setTimeout(() => {
3415
+ this.player.element.removeEventListener("canplay", onCanPlay);
3416
+ this.player.element.removeEventListener("canplaythrough", onCanPlay);
3417
+ resolve();
3418
+ }, 3e3);
3419
+ }
3420
+ });
3421
+ }
3422
+ }
3423
+ /**
3424
+ * Restore playback state after source change
3425
+ */
3426
+ async _restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText) {
3427
+ let syncTime = currentTime;
3428
+ if (currentCaptionText && this.player.captionManager && this.player.captionManager.tracks.length > 0) {
3429
+ await new Promise((resolve) => setTimeout(resolve, 500));
3430
+ const matchingTime = this.player.findMatchingCaptionTime(
3431
+ currentCaptionText,
3432
+ this.player.captionManager.tracks
3433
+ );
3434
+ if (matchingTime !== null) {
3435
+ syncTime = matchingTime;
3436
+ if (this.player.options.debug) {
3437
+ this.player.log(`[VidPly] Syncing via caption: ${currentTime}s -> ${syncTime}s`);
3438
+ }
3439
+ }
3440
+ }
3441
+ if (syncTime > 0) {
3442
+ this.player.seek(syncTime);
3443
+ await new Promise((resolve) => setTimeout(resolve, 100));
3444
+ }
3445
+ if (wasPlaying) {
3446
+ await this.player.play();
3447
+ this.player.setManagedTimeout(() => {
3448
+ this.player.hidePosterOverlay();
3449
+ }, 100);
3450
+ } else {
3451
+ this.player.pause();
3452
+ if (!shouldKeepPoster) {
3453
+ this.player.hidePosterOverlay();
3454
+ }
3455
+ }
3456
+ }
3457
+ /**
3458
+ * Enable with source element method
3459
+ */
3460
+ async _enableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
3461
+ await this._swapCaptionTracks(true);
3462
+ this._updateSourceElements(true);
3463
+ if (posterValue && this.player.element.tagName === "VIDEO") {
3464
+ this.player.element.poster = posterValue;
3465
+ }
3466
+ this.player.element.load();
3467
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
3468
+ await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
3469
+ if (!this.desiredState) return;
3470
+ this.enabled = true;
3471
+ this.player.state.audioDescriptionEnabled = true;
3472
+ this.player.emit("audiodescriptionenabled");
3473
+ this._reloadTranscript();
3474
+ }
3475
+ /**
3476
+ * Enable with direct src method
3477
+ */
3478
+ async _enableWithDirectSrc(currentTime, wasPlaying, posterValue, shouldKeepPoster) {
3479
+ await this._swapCaptionTracks(true);
3480
+ if (posterValue && this.player.element.tagName === "VIDEO") {
3481
+ this.player.element.poster = posterValue;
3482
+ }
3483
+ this.player.element.src = this.src;
3484
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
3485
+ if (currentTime > 0) {
3486
+ this.player.seek(currentTime);
3487
+ await new Promise((resolve) => setTimeout(resolve, 100));
3488
+ }
3489
+ if (wasPlaying) {
3490
+ await this.player.play();
3491
+ } else {
3492
+ this.player.pause();
3493
+ if (!shouldKeepPoster) {
3494
+ this.player.hidePosterOverlay();
3495
+ }
3496
+ }
3497
+ if (!this.desiredState) return;
3498
+ this.enabled = true;
3499
+ this.player.state.audioDescriptionEnabled = true;
3500
+ this.player.emit("audiodescriptionenabled");
3501
+ this._reloadTranscript();
3502
+ }
3503
+ /**
3504
+ * Disable with source element method
3505
+ */
3506
+ async _disableWithSourceElement(currentTime, wasPlaying, posterValue, shouldKeepPoster, currentCaptionText) {
3507
+ await this._swapCaptionTracks(false);
3508
+ this._updateSourceElements(false);
3509
+ if (posterValue && this.player.element.tagName === "VIDEO") {
3510
+ this.player.element.poster = posterValue;
3511
+ }
3512
+ this.player.element.load();
3513
+ this.player.invalidateTrackCache();
3514
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
3515
+ await this._restorePlaybackState(currentTime, wasPlaying, shouldKeepPoster, currentCaptionText);
3516
+ if (this.player.captionManager) {
3517
+ this.player.captionManager.destroy();
3518
+ this.player.captionManager = new CaptionManager(this.player);
3519
+ }
3520
+ if (this.desiredState) return;
3521
+ this.enabled = false;
3522
+ this.player.state.audioDescriptionEnabled = false;
3523
+ this.player.emit("audiodescriptiondisabled");
3524
+ this._reloadTranscript();
3525
+ }
3526
+ /**
3527
+ * Disable with direct src method
3528
+ */
3529
+ async _disableWithDirectSrc(currentTime, wasPlaying, posterValue) {
3530
+ await this._swapCaptionTracks(false);
3531
+ if (posterValue && this.player.element.tagName === "VIDEO") {
3532
+ this.player.element.poster = posterValue;
3533
+ }
3534
+ const originalSrcToUse = this.originalSource || this.player.originalSrc;
3535
+ this.player.element.src = originalSrcToUse;
3536
+ this.player.element.load();
3537
+ await this._waitForMediaReady(currentTime > 0 || wasPlaying);
3538
+ if (currentTime > 0) {
3539
+ this.player.seek(currentTime);
3540
+ }
3541
+ if (wasPlaying) {
3542
+ await this.player.play();
3543
+ }
3544
+ if (this.desiredState) return;
3545
+ this.enabled = false;
3546
+ this.player.state.audioDescriptionEnabled = false;
3547
+ this.player.emit("audiodescriptiondisabled");
3548
+ this._reloadTranscript();
3549
+ }
3550
+ /**
3551
+ * Reload transcript after audio description state change
3552
+ */
3553
+ _reloadTranscript() {
3554
+ if (this.player.transcriptManager && this.player.transcriptManager.isVisible) {
3555
+ this.player.setManagedTimeout(() => {
3556
+ if (this.player.transcriptManager && this.player.transcriptManager.loadTranscriptData) {
3557
+ this.player.transcriptManager.loadTranscriptData();
3558
+ }
3559
+ }, 800);
3560
+ }
3561
+ }
3562
+ /**
3563
+ * Update sources (called when playlist changes)
3564
+ */
3565
+ updateSources(audioDescriptionSrc) {
3566
+ this.src = audioDescriptionSrc || null;
3567
+ this.enabled = false;
3568
+ this.desiredState = false;
3569
+ this.sourceElement = null;
3570
+ this.originalSource = null;
3571
+ this.captionTracks = [];
3572
+ }
3573
+ /**
3574
+ * Reinitialize from current player elements (called after playlist loads new tracks)
3575
+ */
3576
+ reinitialize() {
3577
+ this.player.invalidateTrackCache();
3578
+ this.initFromSourceElements(this.player.sourceElements, this.player.trackElements);
3579
+ }
3580
+ /**
3581
+ * Cleanup
3582
+ */
3583
+ destroy() {
3584
+ this.enabled = false;
3585
+ this.desiredState = false;
3586
+ this.captionTracks = [];
3587
+ this.sourceElement = null;
3588
+ this.originalSource = null;
3589
+ }
3590
+ };
3591
+
3592
+ // src/core/SignLanguageManager.js
3593
+ var SignLanguageManager = class {
3594
+ constructor(player) {
3595
+ this.player = player;
3596
+ this.src = player.options.signLanguageSrc;
3597
+ this.sources = player.options.signLanguageSources || {};
3598
+ this.currentLanguage = null;
3599
+ this.desiredPosition = player.options.signLanguagePosition || "bottom-right";
3600
+ this.wrapper = null;
3601
+ this.header = null;
3602
+ this.video = null;
3603
+ this.selector = null;
3604
+ this.settingsButton = null;
3605
+ this.settingsMenu = null;
3606
+ this.resizeHandles = [];
3607
+ this.enabled = false;
3608
+ this.settingsMenuVisible = false;
3609
+ this.settingsMenuJustOpened = false;
3610
+ this.documentClickHandlerAdded = false;
3611
+ this.handlers = null;
3612
+ this.settingsHandlers = null;
3613
+ this.interactionHandlers = null;
3614
+ this.draggable = null;
3615
+ this.documentClickHandler = null;
3616
+ this.settingsMenuKeyHandler = null;
3617
+ this.customKeyHandler = null;
3618
+ this.dragOptionButton = null;
3619
+ this.dragOptionText = null;
3620
+ this.resizeOptionButton = null;
3621
+ this.resizeOptionText = null;
3622
+ }
3623
+ /**
3624
+ * Check if sign language is available
3625
+ */
3626
+ isAvailable() {
3627
+ return Object.keys(this.sources).length > 0 || !!this.src;
3628
+ }
3629
+ /**
3630
+ * Enable sign language video
3631
+ */
3632
+ enable() {
3633
+ const hasMultipleSources = Object.keys(this.sources).length > 0;
3634
+ const hasSingleSource = !!this.src;
3635
+ if (!hasMultipleSources && !hasSingleSource) {
3636
+ console.warn("No sign language video source provided");
3637
+ return;
3638
+ }
3639
+ if (this.wrapper) {
3640
+ this.wrapper.style.display = "block";
3641
+ this.enabled = true;
3642
+ this.player.state.signLanguageEnabled = true;
3643
+ this.player.emit("signlanguageenabled");
3644
+ this.player.setManagedTimeout(() => {
3645
+ if (this.settingsButton && document.contains(this.settingsButton)) {
3646
+ this.settingsButton.focus({ preventScroll: true });
3647
+ }
3648
+ }, 150);
3649
+ return;
3650
+ }
3651
+ let initialLang = null;
3652
+ let initialSrc = null;
3653
+ if (hasMultipleSources) {
3654
+ initialLang = this._determineInitialLanguage();
3655
+ initialSrc = this.sources[initialLang];
3656
+ this.currentLanguage = initialLang;
3657
+ } else {
3658
+ initialSrc = this.src;
3659
+ }
3660
+ this._createWrapper();
3661
+ this._createHeader(hasMultipleSources, initialLang);
3662
+ this._createVideo(initialSrc);
3663
+ this._createResizeHandles();
3664
+ this.wrapper.appendChild(this.header);
3665
+ this.wrapper.appendChild(this.video);
3666
+ this.resizeHandles.forEach((handle) => this.wrapper.appendChild(handle));
3667
+ this._applyInitialSize();
3668
+ this.player.container.appendChild(this.wrapper);
3669
+ requestAnimationFrame(() => {
3670
+ this.constrainPosition();
3671
+ });
3672
+ this.video.currentTime = this.player.state.currentTime;
3673
+ if (!this.player.state.paused) {
3674
+ this.video.play();
3675
+ }
3676
+ this._setupInteraction();
3677
+ this._setupEventHandlers(hasMultipleSources);
3678
+ this.enabled = true;
3679
+ this.player.state.signLanguageEnabled = true;
3680
+ this.player.emit("signlanguageenabled");
3681
+ this.player.setManagedTimeout(() => {
3682
+ if (this.settingsButton && document.contains(this.settingsButton)) {
3683
+ this.settingsButton.focus({ preventScroll: true });
3684
+ }
3685
+ }, 150);
3686
+ }
3687
+ /**
3688
+ * Disable sign language video
3689
+ */
3690
+ disable() {
3691
+ if (this.settingsMenuVisible) {
3692
+ this.hideSettingsMenu({ focusButton: false });
3693
+ }
3694
+ if (this.wrapper) {
3695
+ this.wrapper.style.display = "none";
3696
+ }
3697
+ this.enabled = false;
3698
+ this.player.state.signLanguageEnabled = false;
3699
+ this.player.emit("signlanguagedisabled");
3700
+ }
3701
+ /**
3702
+ * Toggle sign language video
3703
+ */
3704
+ toggle() {
3705
+ if (this.enabled) {
3706
+ this.disable();
3707
+ } else {
3708
+ this.enable();
3709
+ }
3710
+ }
3711
+ /**
3712
+ * Switch to a different sign language
3713
+ */
3714
+ switchLanguage(langCode) {
3715
+ if (!this.sources[langCode] || !this.video) {
3716
+ return;
3717
+ }
3718
+ const currentTime = this.video.currentTime;
3719
+ const wasPlaying = !this.video.paused;
3720
+ this.video.src = this.sources[langCode];
3721
+ this.currentLanguage = langCode;
3722
+ this.video.currentTime = currentTime;
3723
+ if (wasPlaying) {
3724
+ this.video.play().catch(() => {
3725
+ });
3726
+ }
3727
+ this.player.emit("signlanguagelanguagechanged", langCode);
3728
+ }
3729
+ /**
3730
+ * Get language label
3731
+ */
3732
+ getLanguageLabel(langCode) {
3733
+ const langNames = {
3734
+ "en": "English",
3735
+ "de": "Deutsch",
3736
+ "es": "Español",
3737
+ "fr": "Français",
3738
+ "it": "Italiano",
3739
+ "ja": "日本語",
3740
+ "pt": "Português",
3741
+ "ar": "العربية",
3742
+ "hi": "हिन्दी"
3743
+ };
3744
+ return langNames[langCode] || langCode.toUpperCase();
3745
+ }
3746
+ /**
3747
+ * Determine initial sign language
3748
+ */
3749
+ _determineInitialLanguage() {
3750
+ if (this.player.captionManager && this.player.captionManager.currentTrack) {
3751
+ const captionLang = this.player.captionManager.currentTrack.language?.toLowerCase().split("-")[0];
3752
+ if (captionLang && this.sources[captionLang]) {
3753
+ return captionLang;
3754
+ }
3755
+ }
3756
+ if (this.player.options.language) {
3757
+ const playerLang = this.player.options.language.toLowerCase().split("-")[0];
3758
+ if (this.sources[playerLang]) {
3759
+ return playerLang;
3760
+ }
3761
+ }
3762
+ return Object.keys(this.sources)[0];
3763
+ }
3764
+ /**
3765
+ * Create wrapper element
3766
+ */
3767
+ _createWrapper() {
3768
+ this.wrapper = document.createElement("div");
3769
+ this.wrapper.className = "vidply-sign-language-wrapper";
3770
+ this.wrapper.setAttribute("tabindex", "0");
3771
+ this.wrapper.setAttribute("aria-label", i18n.t("player.signLanguageDragResize"));
3772
+ }
3773
+ /**
3774
+ * Create header element
3775
+ */
3776
+ _createHeader(hasMultipleSources, initialLang) {
3777
+ const classPrefix = this.player.options.classPrefix;
3778
+ this.header = DOMUtils.createElement("div", {
3779
+ className: `${classPrefix}-sign-language-header`,
3780
+ attributes: { "tabindex": "0" }
3781
+ });
3782
+ const headerLeft = DOMUtils.createElement("div", {
3783
+ className: `${classPrefix}-sign-language-header-left`
3784
+ });
3785
+ const title = DOMUtils.createElement("h3", {
3786
+ textContent: i18n.t("player.signLanguageVideo")
3787
+ });
3788
+ this._createSettingsButton(headerLeft);
3789
+ if (hasMultipleSources) {
3790
+ this._createLanguageSelector(headerLeft, initialLang);
3791
+ }
3792
+ headerLeft.appendChild(title);
3793
+ const closeButton = this._createCloseButton();
3794
+ this.header.appendChild(headerLeft);
3795
+ this.header.appendChild(closeButton);
3796
+ this.settingsMenuVisible = false;
3797
+ this.settingsMenu = null;
3798
+ this.settingsMenuJustOpened = false;
3799
+ }
3800
+ /**
3801
+ * Create settings button
3802
+ */
3803
+ _createSettingsButton(container) {
3804
+ const classPrefix = this.player.options.classPrefix;
3805
+ const ariaLabel = i18n.t("player.signLanguageSettings");
3806
+ this.settingsButton = DOMUtils.createElement("button", {
3807
+ className: `${classPrefix}-sign-language-settings`,
3808
+ attributes: {
3809
+ "type": "button",
3810
+ "aria-label": ariaLabel,
3811
+ "aria-expanded": "false"
3812
+ }
3813
+ });
3814
+ this.settingsButton.appendChild(createIconElement("settings"));
3815
+ DOMUtils.attachTooltip(this.settingsButton, ariaLabel, classPrefix);
3816
+ this.settingsHandlers = {
3817
+ click: (e) => {
3818
+ e.preventDefault();
3819
+ e.stopPropagation();
3820
+ if (this.documentClickHandler) {
3821
+ this.settingsMenuJustOpened = true;
3822
+ setTimeout(() => {
3823
+ this.settingsMenuJustOpened = false;
3824
+ }, 100);
3825
+ }
3826
+ if (this.settingsMenuVisible) {
3827
+ this.hideSettingsMenu();
3828
+ } else {
3829
+ this.showSettingsMenu();
3830
+ }
3831
+ },
3832
+ keydown: (e) => {
3833
+ if (e.key === "d" || e.key === "D") {
3834
+ e.preventDefault();
3835
+ e.stopPropagation();
3836
+ this.toggleKeyboardDragMode();
3837
+ } else if (e.key === "r" || e.key === "R") {
3838
+ e.preventDefault();
3839
+ e.stopPropagation();
3840
+ this.toggleResizeMode();
3841
+ } else if (e.key === "Escape" && this.settingsMenuVisible) {
3842
+ e.preventDefault();
3843
+ e.stopPropagation();
3844
+ this.hideSettingsMenu();
3845
+ }
3846
+ }
3847
+ };
3848
+ this.settingsButton.addEventListener("click", this.settingsHandlers.click);
3849
+ this.settingsButton.addEventListener("keydown", this.settingsHandlers.keydown);
3850
+ container.appendChild(this.settingsButton);
3851
+ }
3852
+ /**
3853
+ * Create language selector
3854
+ */
3855
+ _createLanguageSelector(container, initialLang) {
3856
+ const classPrefix = this.player.options.classPrefix;
3857
+ const selectId = `${classPrefix}-sign-language-select-${Date.now()}`;
3858
+ const options = Object.keys(this.sources).map((langCode) => ({
3859
+ value: langCode,
3860
+ text: this.getLanguageLabel(langCode),
3861
+ selected: langCode === initialLang
3862
+ }));
3863
+ const { label, select } = createLabeledSelect({
3864
+ classPrefix,
3865
+ labelClass: `${classPrefix}-sign-language-label`,
3866
+ selectClass: `${classPrefix}-sign-language-select`,
3867
+ labelText: "settings.language",
3868
+ selectId,
3869
+ options,
3870
+ onChange: (e) => {
3871
+ e.stopPropagation();
3872
+ this.switchLanguage(e.target.value);
3873
+ }
3874
+ });
3875
+ this.selector = select;
3876
+ const selectorWrapper = DOMUtils.createElement("div", {
3877
+ className: `${classPrefix}-sign-language-selector-wrapper`
3878
+ });
3879
+ selectorWrapper.appendChild(label);
3880
+ selectorWrapper.appendChild(this.selector);
3881
+ preventDragOnElement(selectorWrapper);
3882
+ container.appendChild(selectorWrapper);
3883
+ }
3884
+ /**
3885
+ * Create close button
3886
+ */
3887
+ _createCloseButton() {
3888
+ const classPrefix = this.player.options.classPrefix;
3889
+ const ariaLabel = i18n.t("player.closeSignLanguage");
3890
+ const closeButton = DOMUtils.createElement("button", {
3891
+ className: `${classPrefix}-sign-language-close`,
3892
+ attributes: {
3893
+ "type": "button",
3894
+ "aria-label": ariaLabel
3895
+ }
3896
+ });
3897
+ closeButton.appendChild(createIconElement("close"));
3898
+ DOMUtils.attachTooltip(closeButton, ariaLabel, classPrefix);
3899
+ closeButton.addEventListener("click", () => {
3900
+ this.disable();
3901
+ if (this.player.controlBar?.controls?.signLanguage) {
3902
+ setTimeout(() => {
3903
+ this.player.controlBar.controls.signLanguage.focus({ preventScroll: true });
3904
+ }, 0);
3905
+ }
3906
+ });
3907
+ return closeButton;
3908
+ }
3909
+ /**
3910
+ * Create video element
3911
+ */
3912
+ _createVideo(src) {
3913
+ this.video = document.createElement("video");
3914
+ this.video.className = "vidply-sign-language-video";
3915
+ this.video.src = src;
3916
+ this.video.setAttribute("aria-label", i18n.t("player.signLanguage"));
3917
+ this.video.muted = true;
3918
+ this.video.setAttribute("playsinline", "");
3919
+ }
3920
+ /**
3921
+ * Create resize handles
3922
+ */
3923
+ _createResizeHandles() {
3924
+ const classPrefix = this.player.options.classPrefix;
3925
+ this.resizeHandles = ["n", "s", "e", "w", "ne", "nw", "se", "sw"].map((dir) => {
3926
+ const handle = DOMUtils.createElement("div", {
3927
+ className: `${classPrefix}-sign-resize-handle ${classPrefix}-sign-resize-${dir}`,
3928
+ attributes: {
3929
+ "data-direction": dir,
3930
+ "data-vidply-managed-resize": "true",
3931
+ "aria-hidden": "true"
3932
+ }
3933
+ });
3934
+ handle.style.display = "none";
3935
+ return handle;
3936
+ });
3937
+ }
3938
+ /**
3939
+ * Apply initial size
3940
+ */
3941
+ _applyInitialSize() {
3942
+ const saved = this.player.storage.getSignLanguagePreferences();
3943
+ if (saved?.size?.width) {
3944
+ this.wrapper.style.width = saved.size.width;
3945
+ } else {
3946
+ this.wrapper.style.width = "280px";
3947
+ }
3948
+ this.wrapper.style.height = "auto";
3949
+ }
3950
+ /**
3951
+ * Setup interaction (drag and resize)
3952
+ */
3953
+ _setupInteraction() {
3954
+ const isMobile2 = window.innerWidth < 768;
3955
+ const isFullscreen = this.player.state.fullscreen;
3956
+ if (isMobile2 && !isFullscreen) {
3957
+ if (this.draggable) {
3958
+ this.draggable.destroy();
3959
+ this.draggable = null;
3960
+ }
3961
+ return;
3962
+ }
3963
+ if (this.draggable) return;
3964
+ const classPrefix = this.player.options.classPrefix;
3965
+ this.draggable = new DraggableResizable(this.wrapper, {
3966
+ dragHandle: this.header,
3967
+ resizeHandles: this.resizeHandles,
3968
+ constrainToViewport: true,
3969
+ maintainAspectRatio: true,
3970
+ minWidth: 150,
3971
+ minHeight: 100,
3972
+ classPrefix: `${classPrefix}-sign`,
3973
+ keyboardDragKey: "d",
3974
+ keyboardResizeKey: "r",
3975
+ keyboardStep: 10,
3976
+ keyboardStepLarge: 50,
3977
+ pointerResizeIndicatorText: i18n.t("player.signLanguageResizeActive"),
3978
+ onPointerResizeToggle: (enabled) => {
3979
+ this.resizeHandles.forEach((handle) => {
3980
+ handle.style.display = enabled ? "block" : "none";
3981
+ });
3982
+ },
3983
+ onDragStart: (e) => {
3984
+ if (e.target.closest(`.${classPrefix}-sign-language-close`) || e.target.closest(`.${classPrefix}-sign-language-settings`) || e.target.closest(`.${classPrefix}-sign-language-select`) || e.target.closest(`.${classPrefix}-sign-language-label`) || e.target.closest(`.${classPrefix}-sign-language-settings-menu`)) {
3985
+ return false;
3986
+ }
3987
+ return true;
3988
+ }
3989
+ });
3990
+ this._setupCustomKeyHandler();
3991
+ this.interactionHandlers = {
3992
+ draggable: this.draggable,
3993
+ customKeyHandler: this.customKeyHandler
3994
+ };
3995
+ }
3996
+ /**
3997
+ * Setup custom keyboard handler
3998
+ */
3999
+ _setupCustomKeyHandler() {
4000
+ this.customKeyHandler = (e) => {
4001
+ const key = e.key.toLowerCase();
4002
+ if (this.settingsMenuVisible) return;
4003
+ if (key === "home") {
4004
+ e.preventDefault();
4005
+ e.stopPropagation();
4006
+ if (this.draggable) {
4007
+ if (this.draggable.pointerResizeMode) {
4008
+ this.draggable.disablePointerResizeMode();
4009
+ }
4010
+ this.draggable.manuallyPositioned = false;
4011
+ this.constrainPosition();
4012
+ }
4013
+ return;
4014
+ }
4015
+ if (key === "r") {
4016
+ e.preventDefault();
4017
+ e.stopPropagation();
4018
+ if (this.toggleResizeMode()) {
4019
+ this.wrapper.focus({ preventScroll: true });
4020
+ }
4021
+ return;
4022
+ }
4023
+ if (key === "escape") {
4024
+ e.preventDefault();
4025
+ e.stopPropagation();
4026
+ if (this.draggable?.pointerResizeMode) {
4027
+ this.draggable.disablePointerResizeMode();
4028
+ return;
4029
+ }
4030
+ if (this.draggable?.keyboardDragMode) {
4031
+ this.draggable.disableKeyboardDragMode();
4032
+ return;
4033
+ }
4034
+ this.disable();
4035
+ if (this.player.controlBar?.controls?.signLanguage) {
4036
+ setTimeout(() => {
4037
+ this.player.controlBar.controls.signLanguage.focus({ preventScroll: true });
4038
+ }, 0);
4039
+ }
4040
+ }
4041
+ };
4042
+ this.wrapper.addEventListener("keydown", this.customKeyHandler);
4043
+ }
4044
+ /**
4045
+ * Setup event handlers
4046
+ */
4047
+ _setupEventHandlers(hasMultipleSources) {
4048
+ this.handlers = {
4049
+ play: () => {
4050
+ if (this.video) this.video.play();
4051
+ },
4052
+ pause: () => {
4053
+ if (this.video) this.video.pause();
4054
+ },
4055
+ timeupdate: () => {
4056
+ if (this.video && Math.abs(this.video.currentTime - this.player.state.currentTime) > 0.5) {
4057
+ this.video.currentTime = this.player.state.currentTime;
4058
+ }
4059
+ },
4060
+ ratechange: () => {
4061
+ if (this.video) this.video.playbackRate = this.player.state.playbackSpeed;
4062
+ }
4063
+ };
4064
+ this.player.on("play", this.handlers.play);
4065
+ this.player.on("pause", this.handlers.pause);
4066
+ this.player.on("timeupdate", this.handlers.timeupdate);
4067
+ this.player.on("ratechange", this.handlers.ratechange);
4068
+ if (hasMultipleSources) {
4069
+ this.handlers.captionChange = () => {
4070
+ if (this.player.captionManager?.currentTrack && this.selector) {
4071
+ const captionLang = this.player.captionManager.currentTrack.language?.toLowerCase().split("-")[0];
4072
+ if (captionLang && this.sources[captionLang] && this.currentLanguage !== captionLang) {
4073
+ this.switchLanguage(captionLang);
4074
+ this.selector.value = captionLang;
4075
+ }
4076
+ }
4077
+ };
4078
+ this.player.on("captionsenabled", this.handlers.captionChange);
4079
+ }
4080
+ }
4081
+ /**
4082
+ * Constrain position within video wrapper
4083
+ */
4084
+ constrainPosition() {
4085
+ if (!this.wrapper || !this.player.videoWrapper) return;
4086
+ if (this.draggable?.manuallyPositioned) return;
4087
+ if (!this.wrapper.style.width) {
4088
+ this.wrapper.style.width = "280px";
4089
+ }
4090
+ const videoWrapperRect = this.player.videoWrapper.getBoundingClientRect();
4091
+ const containerRect = this.player.container.getBoundingClientRect();
4092
+ const wrapperRect = this.wrapper.getBoundingClientRect();
4093
+ const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
4094
+ const videoWrapperTop = videoWrapperRect.top - containerRect.top;
4095
+ const videoWrapperWidth = videoWrapperRect.width;
4096
+ const videoWrapperHeight = videoWrapperRect.height;
4097
+ let wrapperWidth = wrapperRect.width || 280;
4098
+ let wrapperHeight = wrapperRect.height || 280 * 9 / 16;
4099
+ let left, top;
4100
+ const margin = 16;
4101
+ const controlsHeight = 95;
4102
+ const position = this.desiredPosition || "bottom-right";
4103
+ switch (position) {
4104
+ case "bottom-right":
4105
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
4106
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
4107
+ break;
4108
+ case "bottom-left":
4109
+ left = videoWrapperLeft + margin;
4110
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
4111
+ break;
4112
+ case "top-right":
4113
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
4114
+ top = videoWrapperTop + margin;
4115
+ break;
4116
+ case "top-left":
4117
+ left = videoWrapperLeft + margin;
4118
+ top = videoWrapperTop + margin;
4119
+ break;
4120
+ default:
4121
+ left = videoWrapperLeft + videoWrapperWidth - wrapperWidth - margin;
4122
+ top = videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight;
4123
+ }
4124
+ left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperWidth - wrapperWidth));
4125
+ top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperHeight - wrapperHeight - controlsHeight));
4126
+ this.wrapper.style.left = `${left}px`;
4127
+ this.wrapper.style.top = `${top}px`;
4128
+ this.wrapper.style.right = "auto";
4129
+ this.wrapper.style.bottom = "auto";
4130
+ }
4131
+ /**
4132
+ * Show settings menu
4133
+ */
4134
+ showSettingsMenu() {
4135
+ this.settingsMenuJustOpened = true;
4136
+ setTimeout(() => {
4137
+ this.settingsMenuJustOpened = false;
4138
+ }, 350);
4139
+ this._addDocumentClickHandler();
4140
+ if (this.settingsMenu) {
4141
+ this.settingsMenu.style.display = "block";
4142
+ this.settingsMenuVisible = true;
4143
+ this.settingsButton?.setAttribute("aria-expanded", "true");
4144
+ this._attachMenuKeyboardNavigation();
4145
+ this._positionSettingsMenu();
4146
+ this._updateDragOptionState();
4147
+ this._updateResizeOptionState();
4148
+ focusFirstMenuItem(this.settingsMenu, `.${this.player.options.classPrefix}-sign-language-settings-item`);
4149
+ return;
4150
+ }
4151
+ this._createSettingsMenu();
4152
+ }
4153
+ /**
4154
+ * Hide settings menu
4155
+ */
4156
+ hideSettingsMenu({ focusButton = true } = {}) {
4157
+ if (this.settingsMenu) {
4158
+ this.settingsMenu.style.display = "none";
4159
+ this.settingsMenuVisible = false;
4160
+ this.settingsMenuJustOpened = false;
4161
+ if (this.settingsMenuKeyHandler) {
4162
+ this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler);
4163
+ this.settingsMenuKeyHandler = null;
4164
+ }
4165
+ const classPrefix = this.player.options.classPrefix;
4166
+ const menuItems = Array.from(this.settingsMenu.querySelectorAll(`.${classPrefix}-sign-language-settings-item`));
4167
+ menuItems.forEach((item) => item.setAttribute("tabindex", "-1"));
4168
+ if (this.settingsButton) {
4169
+ this.settingsButton.setAttribute("aria-expanded", "false");
4170
+ if (focusButton) {
4171
+ this.settingsButton.focus({ preventScroll: true });
4172
+ }
4173
+ }
4174
+ }
4175
+ }
4176
+ /**
4177
+ * Add document click handler
4178
+ */
4179
+ _addDocumentClickHandler() {
4180
+ if (this.documentClickHandlerAdded) return;
4181
+ this.documentClickHandler = (e) => {
4182
+ if (this.settingsMenuJustOpened) return;
4183
+ if (this.settingsButton && (this.settingsButton === e.target || this.settingsButton.contains(e.target))) {
4184
+ return;
4185
+ }
4186
+ if (this.settingsMenu && this.settingsMenu.contains(e.target)) {
4187
+ return;
4188
+ }
4189
+ if (this.settingsMenuVisible) {
4190
+ this.hideSettingsMenu();
4191
+ }
4192
+ };
4193
+ setTimeout(() => {
4194
+ document.addEventListener("mousedown", this.documentClickHandler, true);
4195
+ this.documentClickHandlerAdded = true;
4196
+ }, 300);
4197
+ }
4198
+ /**
4199
+ * Create settings menu
4200
+ */
4201
+ _createSettingsMenu() {
4202
+ const classPrefix = this.player.options.classPrefix;
4203
+ this.settingsMenu = DOMUtils.createElement("div", {
4204
+ className: `${classPrefix}-sign-language-settings-menu`,
4205
+ attributes: { "role": "menu" }
4206
+ });
4207
+ const dragOption = createMenuItem({
4208
+ classPrefix,
4209
+ itemClass: `${classPrefix}-sign-language-settings-item`,
4210
+ icon: "move",
4211
+ label: "player.enableSignDragMode",
4212
+ hasTextClass: true,
4213
+ onClick: () => {
4214
+ this.toggleKeyboardDragMode();
4215
+ this.hideSettingsMenu();
4216
+ }
4217
+ });
4218
+ dragOption.setAttribute("role", "switch");
4219
+ dragOption.setAttribute("aria-checked", "false");
4220
+ this._removeTooltipFromMenuItem(dragOption);
4221
+ this.dragOptionButton = dragOption;
4222
+ this.dragOptionText = dragOption.querySelector(`.${classPrefix}-settings-text`);
4223
+ this._updateDragOptionState();
4224
+ const resizeOption = createMenuItem({
4225
+ classPrefix,
4226
+ itemClass: `${classPrefix}-sign-language-settings-item`,
4227
+ icon: "resize",
4228
+ label: "player.enableSignResizeMode",
4229
+ hasTextClass: true,
4230
+ onClick: (event) => {
4231
+ event.preventDefault();
4232
+ event.stopPropagation();
4233
+ const enabled = this.toggleResizeMode({ focus: false });
4234
+ if (enabled) {
4235
+ this.hideSettingsMenu({ focusButton: false });
4236
+ setTimeout(() => {
4237
+ if (this.wrapper) this.wrapper.focus({ preventScroll: true });
4238
+ }, 20);
4239
+ } else {
4240
+ this.hideSettingsMenu({ focusButton: true });
4241
+ }
4242
+ }
4243
+ });
4244
+ resizeOption.setAttribute("role", "switch");
4245
+ resizeOption.setAttribute("aria-checked", "false");
4246
+ this._removeTooltipFromMenuItem(resizeOption);
4247
+ this.resizeOptionButton = resizeOption;
4248
+ this.resizeOptionText = resizeOption.querySelector(`.${classPrefix}-settings-text`);
4249
+ this._updateResizeOptionState();
4250
+ const closeOption = createMenuItem({
4251
+ classPrefix,
4252
+ itemClass: `${classPrefix}-sign-language-settings-item`,
4253
+ icon: "close",
4254
+ label: "transcript.closeMenu",
4255
+ onClick: () => this.hideSettingsMenu()
4256
+ });
4257
+ this._removeTooltipFromMenuItem(closeOption);
4258
+ this.settingsMenu.appendChild(dragOption);
4259
+ this.settingsMenu.appendChild(resizeOption);
4260
+ this.settingsMenu.appendChild(closeOption);
4261
+ this.settingsMenu.style.visibility = "hidden";
4262
+ this.settingsMenu.style.display = "block";
4263
+ if (this.settingsButton?.parentNode) {
4264
+ this.settingsButton.insertAdjacentElement("afterend", this.settingsMenu);
4265
+ } else if (this.wrapper) {
4266
+ this.wrapper.appendChild(this.settingsMenu);
4267
+ }
4268
+ this._positionSettingsMenuImmediate();
4269
+ requestAnimationFrame(() => {
4270
+ if (this.settingsMenu) {
4271
+ this.settingsMenu.style.visibility = "visible";
4272
+ }
4273
+ });
4274
+ this._attachMenuKeyboardNavigation();
4275
+ this.settingsMenuVisible = true;
4276
+ this.settingsButton?.setAttribute("aria-expanded", "true");
4277
+ this._updateDragOptionState();
4278
+ this._updateResizeOptionState();
4279
+ focusFirstMenuItem(this.settingsMenu, `.${classPrefix}-sign-language-settings-item`);
4280
+ }
4281
+ /**
4282
+ * Remove tooltip from menu item
4283
+ */
4284
+ _removeTooltipFromMenuItem(item) {
4285
+ const classPrefix = this.player.options.classPrefix;
4286
+ const tooltip = item.querySelector(`.${classPrefix}-tooltip`);
4287
+ if (tooltip) tooltip.remove();
4288
+ const buttonText = item.querySelector(`.${classPrefix}-button-text`);
4289
+ if (buttonText) buttonText.remove();
4290
+ }
4291
+ /**
4292
+ * Attach menu keyboard navigation
4293
+ */
4294
+ _attachMenuKeyboardNavigation() {
4295
+ if (this.settingsMenuKeyHandler) {
4296
+ this.settingsMenu.removeEventListener("keydown", this.settingsMenuKeyHandler);
4297
+ }
4298
+ this.settingsMenuKeyHandler = attachMenuKeyboardNavigation(
4299
+ this.settingsMenu,
4300
+ this.settingsButton,
4301
+ `.${this.player.options.classPrefix}-sign-language-settings-item`,
4302
+ () => this.hideSettingsMenu({ focusButton: true })
4303
+ );
4304
+ }
4305
+ /**
4306
+ * Position settings menu immediately
4307
+ */
4308
+ _positionSettingsMenuImmediate() {
4309
+ if (!this.settingsMenu || !this.settingsButton) return;
4310
+ const buttonRect = this.settingsButton.getBoundingClientRect();
4311
+ const menuRect = this.settingsMenu.getBoundingClientRect();
4312
+ const viewportWidth = window.innerWidth;
4313
+ const viewportHeight = window.innerHeight;
4314
+ const parentContainer = this.settingsButton.parentElement;
4315
+ if (!parentContainer) return;
4316
+ const parentRect = parentContainer.getBoundingClientRect();
4317
+ const buttonCenterX = buttonRect.left + buttonRect.width / 2 - parentRect.left;
4318
+ const buttonBottom = buttonRect.bottom - parentRect.top;
4319
+ const buttonTop = buttonRect.top - parentRect.top;
4320
+ const spaceAbove = buttonRect.top;
4321
+ const spaceBelow = viewportHeight - buttonRect.bottom;
4322
+ let menuTop = buttonBottom + 8;
4323
+ let menuBottom = null;
4324
+ if (spaceBelow < menuRect.height + 20 && spaceAbove > spaceBelow) {
4325
+ menuTop = null;
4326
+ const parentHeight = parentRect.bottom - parentRect.top;
4327
+ menuBottom = parentHeight - buttonTop + 8;
4328
+ this.settingsMenu.classList.add("vidply-menu-above");
4329
+ } else {
4330
+ this.settingsMenu.classList.remove("vidply-menu-above");
4331
+ }
4332
+ let menuLeft = buttonCenterX - menuRect.width / 2;
4333
+ let menuRight = "auto";
4334
+ let transformX = "translateX(0)";
4335
+ const menuLeftAbsolute = buttonRect.left + buttonRect.width / 2 - menuRect.width / 2;
4336
+ if (menuLeftAbsolute < 10) {
4337
+ menuLeft = 0;
4338
+ } else if (menuLeftAbsolute + menuRect.width > viewportWidth - 10) {
4339
+ menuLeft = "auto";
4340
+ menuRight = 0;
4341
+ } else {
4342
+ menuLeft = buttonCenterX;
4343
+ transformX = "translateX(-50%)";
4344
+ }
4345
+ if (menuTop !== null) {
4346
+ this.settingsMenu.style.top = `${menuTop}px`;
4347
+ this.settingsMenu.style.bottom = "auto";
4348
+ } else if (menuBottom !== null) {
4349
+ this.settingsMenu.style.top = "auto";
4350
+ this.settingsMenu.style.bottom = `${menuBottom}px`;
4351
+ }
4352
+ if (menuLeft !== "auto") {
4353
+ this.settingsMenu.style.left = `${menuLeft}px`;
4354
+ this.settingsMenu.style.right = "auto";
4355
+ } else {
4356
+ this.settingsMenu.style.left = "auto";
4357
+ this.settingsMenu.style.right = `${menuRight}px`;
4358
+ }
4359
+ this.settingsMenu.style.transform = transformX;
4360
+ }
4361
+ /**
4362
+ * Position settings menu with RAF
4363
+ */
4364
+ _positionSettingsMenu() {
4365
+ requestAnimationFrame(() => {
4366
+ setTimeout(() => {
4367
+ this._positionSettingsMenuImmediate();
4368
+ }, 10);
4369
+ });
4370
+ }
4371
+ /**
4372
+ * Toggle keyboard drag mode
4373
+ */
4374
+ toggleKeyboardDragMode() {
4375
+ if (this.draggable) {
4376
+ const wasEnabled = this.draggable.keyboardDragMode;
4377
+ this.draggable.toggleKeyboardDragMode();
4378
+ const isEnabled = this.draggable.keyboardDragMode;
4379
+ if (!wasEnabled && isEnabled) {
4380
+ this._enableMoveMode();
4381
+ }
4382
+ this._updateDragOptionState();
4383
+ }
4384
+ }
4385
+ /**
4386
+ * Enable move mode visual feedback
4387
+ */
4388
+ _enableMoveMode() {
4389
+ this.wrapper.classList.add(`${this.player.options.classPrefix}-sign-move-mode`);
4390
+ this._updateResizeOptionState();
4391
+ setTimeout(() => {
4392
+ this.wrapper.classList.remove(`${this.player.options.classPrefix}-sign-move-mode`);
4393
+ }, 2e3);
4394
+ }
4395
+ /**
4396
+ * Toggle resize mode
4397
+ */
4398
+ toggleResizeMode({ focus = true } = {}) {
4399
+ if (!this.draggable) return false;
4400
+ if (this.draggable.pointerResizeMode) {
4401
+ this.draggable.disablePointerResizeMode({ focus });
4402
+ this._updateResizeOptionState();
4403
+ return false;
4404
+ }
4405
+ this.draggable.enablePointerResizeMode({ focus });
4406
+ this._updateResizeOptionState();
4407
+ return true;
4408
+ }
4409
+ /**
4410
+ * Update drag option state
4411
+ */
4412
+ _updateDragOptionState() {
4413
+ if (!this.dragOptionButton) return;
4414
+ const isEnabled = !!this.draggable?.keyboardDragMode;
4415
+ const text = isEnabled ? i18n.t("player.disableSignDragMode") : i18n.t("player.enableSignDragMode");
4416
+ const ariaLabel = isEnabled ? i18n.t("player.disableSignDragModeAria") : i18n.t("player.enableSignDragModeAria");
4417
+ this.dragOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
4418
+ this.dragOptionButton.setAttribute("aria-label", ariaLabel);
4419
+ if (this.dragOptionText) {
4420
+ this.dragOptionText.textContent = text;
4421
+ }
4422
+ }
4423
+ /**
4424
+ * Update resize option state
4425
+ */
4426
+ _updateResizeOptionState() {
4427
+ if (!this.resizeOptionButton) return;
4428
+ const isEnabled = !!this.draggable?.pointerResizeMode;
4429
+ const text = isEnabled ? i18n.t("player.disableSignResizeMode") : i18n.t("player.enableSignResizeMode");
4430
+ const ariaLabel = isEnabled ? i18n.t("player.disableSignResizeModeAria") : i18n.t("player.enableSignResizeModeAria");
4431
+ this.resizeOptionButton.setAttribute("aria-checked", isEnabled ? "true" : "false");
4432
+ this.resizeOptionButton.setAttribute("aria-label", ariaLabel);
4433
+ if (this.resizeOptionText) {
4434
+ this.resizeOptionText.textContent = text;
4435
+ }
4436
+ }
4437
+ /**
4438
+ * Save preferences
4439
+ */
4440
+ savePreferences() {
4441
+ if (!this.wrapper) return;
4442
+ this.player.storage.saveSignLanguagePreferences({
4443
+ size: { width: this.wrapper.style.width }
4444
+ });
4445
+ }
4446
+ /**
4447
+ * Update sources (called when playlist changes)
4448
+ */
4449
+ updateSources(signLanguageSrc, signLanguageSources) {
4450
+ this.src = signLanguageSrc || null;
4451
+ this.sources = signLanguageSources || {};
4452
+ this.currentLanguage = null;
4453
+ }
4454
+ /**
4455
+ * Cleanup
4456
+ */
4457
+ cleanup() {
4458
+ if (this.settingsMenuVisible) {
4459
+ this.hideSettingsMenu({ focusButton: false });
4460
+ }
4461
+ if (this.documentClickHandler && this.documentClickHandlerAdded) {
4462
+ document.removeEventListener("mousedown", this.documentClickHandler, true);
4463
+ this.documentClickHandlerAdded = false;
4464
+ this.documentClickHandler = null;
4465
+ }
4466
+ if (this.settingsHandlers && this.settingsButton) {
4467
+ this.settingsButton.removeEventListener("click", this.settingsHandlers.click);
4468
+ this.settingsButton.removeEventListener("keydown", this.settingsHandlers.keydown);
4469
+ }
4470
+ this.settingsHandlers = null;
4471
+ if (this.handlers) {
4472
+ this.player.off("play", this.handlers.play);
4473
+ this.player.off("pause", this.handlers.pause);
4474
+ this.player.off("timeupdate", this.handlers.timeupdate);
4475
+ this.player.off("ratechange", this.handlers.ratechange);
4476
+ if (this.handlers.captionChange) {
4477
+ this.player.off("captionsenabled", this.handlers.captionChange);
4478
+ }
4479
+ this.handlers = null;
4480
+ }
4481
+ if (this.wrapper && this.customKeyHandler) {
4482
+ this.wrapper.removeEventListener("keydown", this.customKeyHandler);
4483
+ }
4484
+ if (this.draggable) {
4485
+ if (this.draggable.pointerResizeMode) {
4486
+ this.draggable.disablePointerResizeMode();
4487
+ }
4488
+ this.draggable.destroy();
4489
+ this.draggable = null;
4490
+ }
4491
+ this.interactionHandlers = null;
4492
+ if (this.wrapper?.parentNode) {
4493
+ if (this.video) {
4494
+ this.video.pause();
4495
+ this.video.src = "";
4496
+ }
4497
+ this.wrapper.parentNode.removeChild(this.wrapper);
4498
+ }
4499
+ this.wrapper = null;
4500
+ this.video = null;
4501
+ this.settingsButton = null;
4502
+ this.settingsMenu = null;
4503
+ }
4504
+ /**
4505
+ * Destroy
4506
+ */
4507
+ destroy() {
4508
+ this.cleanup();
4509
+ this.enabled = false;
4510
+ }
4511
+ };
4512
+
4513
+ // src/core/Player.js
4514
+ var playerInstanceCounter = 0;
4515
+ var Player = class _Player extends EventEmitter {
4516
+ constructor(element, options = {}) {
4517
+ super();
4518
+ this.element = typeof element === "string" ? document.querySelector(element) : element;
4519
+ if (!this.element) {
4520
+ throw new Error("VidPly: Element not found");
4521
+ }
4522
+ playerInstanceCounter++;
4523
+ this.instanceId = playerInstanceCounter;
4524
+ if (this.element.tagName !== "VIDEO" && this.element.tagName !== "AUDIO") {
4525
+ const mediaType = options.mediaType || "video";
4526
+ const mediaElement = document.createElement(mediaType);
4527
+ Array.from(this.element.attributes).forEach((attr) => {
4528
+ if (attr.name !== "id" && attr.name !== "class" && !attr.name.startsWith("data-")) {
4529
+ mediaElement.setAttribute(attr.name, attr.value);
4530
+ }
4531
+ });
4532
+ const tracks = this.element.querySelectorAll("track");
4533
+ tracks.forEach((track) => {
4534
+ mediaElement.appendChild(track.cloneNode(true));
4535
+ });
4536
+ this.element.innerHTML = "";
4537
+ this.element.appendChild(mediaElement);
4538
+ this.element = mediaElement;
4539
+ }
4540
+ this._originalElement = this.element;
4541
+ this.options = {
4542
+ // Display
4543
+ width: null,
4544
+ height: null,
4545
+ poster: null,
4546
+ responsive: true,
4547
+ fillContainer: false,
4548
+ // Playback
4549
+ autoplay: false,
4550
+ loop: false,
4551
+ muted: false,
4552
+ volume: 0.8,
4553
+ playbackSpeed: 1,
4554
+ preload: "metadata",
4555
+ startTime: 0,
4556
+ playsInline: true,
4557
+ // Enable inline playback on iOS (prevents native fullscreen)
4558
+ // Controls
4559
+ controls: true,
4560
+ hideControlsDelay: 3e3,
4561
+ playPauseButton: true,
4562
+ progressBar: true,
4563
+ currentTime: true,
4564
+ duration: true,
4565
+ volumeControl: true,
4566
+ muteButton: true,
4567
+ chaptersButton: true,
4568
+ qualityButton: true,
4569
+ captionStyleButton: true,
4570
+ speedButton: true,
4571
+ captionsButton: true,
4572
+ transcriptButton: true,
4573
+ fullscreenButton: true,
4574
+ pipButton: false,
4575
+ // Seeking
4576
+ seekInterval: 10,
4577
+ seekIntervalLarge: 30,
4578
+ // Captions
4579
+ captions: true,
4580
+ captionsDefault: false,
4581
+ captionsFontSize: "100%",
4582
+ captionsFontFamily: "sans-serif",
4583
+ captionsColor: "#FFFFFF",
4584
+ captionsBackgroundColor: "#000000",
4585
+ captionsOpacity: 0.8,
4586
+ // Audio Description
4587
+ audioDescription: true,
4588
+ audioDescriptionSrc: null,
4589
+ // URL to audio-described version
4590
+ audioDescriptionButton: true,
4591
+ // Sign Language
4592
+ signLanguage: true,
4593
+ signLanguageSrc: null,
4594
+ // URL to sign language video
4595
+ signLanguageButton: true,
4596
+ signLanguagePosition: "bottom-right",
4597
+ // Position: 'bottom-right', 'bottom-left', 'top-right', 'top-left'
4598
+ // Transcripts
4599
+ transcript: false,
4600
+ transcriptPosition: "external",
4601
+ transcriptContainer: null,
4602
+ // Keyboard
4603
+ keyboard: true,
3153
4604
  keyboardShortcuts: {
3154
4605
  "play-pause": [" ", "p", "k"],
3155
4606
  "volume-up": ["ArrowUp"],
@@ -3246,6 +4697,58 @@ var Player = class _Player extends EventEmitter {
3246
4697
  this.settingsDialog = null;
3247
4698
  this.metadataCueChangeHandler = null;
3248
4699
  this.metadataAlertHandlers = /* @__PURE__ */ new Map();
4700
+ this.audioDescriptionManager = new AudioDescriptionManager(this);
4701
+ this.signLanguageManager = new SignLanguageManager(this);
4702
+ Object.defineProperties(this, {
4703
+ signLanguageWrapper: {
4704
+ get: () => this.signLanguageManager.wrapper,
4705
+ set: (v) => {
4706
+ this.signLanguageManager.wrapper = v;
4707
+ }
4708
+ },
4709
+ signLanguageVideo: {
4710
+ get: () => this.signLanguageManager.video,
4711
+ set: (v) => {
4712
+ this.signLanguageManager.video = v;
4713
+ }
4714
+ },
4715
+ signLanguageHeader: {
4716
+ get: () => this.signLanguageManager.header,
4717
+ set: (v) => {
4718
+ this.signLanguageManager.header = v;
4719
+ }
4720
+ },
4721
+ signLanguageSettingsButton: {
4722
+ get: () => this.signLanguageManager.settingsButton,
4723
+ set: (v) => {
4724
+ this.signLanguageManager.settingsButton = v;
4725
+ }
4726
+ },
4727
+ signLanguageSettingsMenu: {
4728
+ get: () => this.signLanguageManager.settingsMenu,
4729
+ set: (v) => {
4730
+ this.signLanguageManager.settingsMenu = v;
4731
+ }
4732
+ },
4733
+ signLanguageSettingsMenuVisible: {
4734
+ get: () => this.signLanguageManager.settingsMenuVisible,
4735
+ set: (v) => {
4736
+ this.signLanguageManager.settingsMenuVisible = v;
4737
+ }
4738
+ },
4739
+ signLanguageDraggable: {
4740
+ get: () => this.signLanguageManager.draggable,
4741
+ set: (v) => {
4742
+ this.signLanguageManager.draggable = v;
4743
+ }
4744
+ },
4745
+ currentSignLanguage: {
4746
+ get: () => this.signLanguageManager.currentLanguage,
4747
+ set: (v) => {
4748
+ this.signLanguageManager.currentLanguage = v;
4749
+ }
4750
+ }
4751
+ });
3249
4752
  this.init();
3250
4753
  }
3251
4754
  async init() {
@@ -3339,7 +4842,7 @@ var Player = class _Player extends EventEmitter {
3339
4842
  if (!this.options.transcript && !this.options.transcriptButton) {
3340
4843
  return null;
3341
4844
  }
3342
- const module = await import("./vidply.TranscriptManager-QSF2PWUN.js");
4845
+ const module = await import("./vidply.TranscriptManager-T677KF4N.js");
3343
4846
  const Manager = module.TranscriptManager || module.default;
3344
4847
  if (!Manager) {
3345
4848
  return null;
@@ -3506,53 +5009,7 @@ var Player = class _Player extends EventEmitter {
3506
5009
  }
3507
5010
  this.currentSource = src;
3508
5011
  this._pendingSource = null;
3509
- const sourceElements = this.sourceElements;
3510
- for (const sourceEl of sourceElements) {
3511
- const descSrc = sourceEl.getAttribute("data-desc-src");
3512
- const origSrc = sourceEl.getAttribute("data-orig-src");
3513
- if (descSrc || origSrc) {
3514
- if (!this.audioDescriptionSourceElement) {
3515
- this.audioDescriptionSourceElement = sourceEl;
3516
- }
3517
- if (origSrc) {
3518
- if (!this.originalAudioDescriptionSource) {
3519
- this.originalAudioDescriptionSource = origSrc;
3520
- }
3521
- if (!this.originalSrc) {
3522
- this.originalSrc = origSrc;
3523
- }
3524
- } else {
3525
- const currentSrcAttr = sourceEl.getAttribute("src");
3526
- if (!this.originalAudioDescriptionSource && currentSrcAttr) {
3527
- this.originalAudioDescriptionSource = currentSrcAttr;
3528
- }
3529
- if (!this.originalSrc && currentSrcAttr) {
3530
- this.originalSrc = currentSrcAttr;
3531
- }
3532
- }
3533
- if (descSrc && !this.audioDescriptionSrc) {
3534
- this.audioDescriptionSrc = descSrc;
3535
- }
3536
- }
3537
- }
3538
- const trackElements = this.trackElements;
3539
- trackElements.forEach((trackEl) => {
3540
- const trackKind = trackEl.getAttribute("kind");
3541
- const trackDescSrc = trackEl.getAttribute("data-desc-src");
3542
- if (trackKind === "captions" || trackKind === "subtitles" || trackKind === "chapters") {
3543
- if (trackDescSrc) {
3544
- this.audioDescriptionCaptionTracks.push({
3545
- trackElement: trackEl,
3546
- originalSrc: trackEl.getAttribute("src"),
3547
- describedSrc: trackDescSrc,
3548
- originalTrackSrc: trackEl.getAttribute("data-orig-src") || trackEl.getAttribute("src"),
3549
- explicit: true
3550
- // Explicitly defined, so we should validate it
3551
- });
3552
- this.log(`Found explicit described ${trackKind} track: ${trackEl.getAttribute("src")} -> ${trackDescSrc}`);
3553
- }
3554
- }
3555
- });
5012
+ this.audioDescriptionManager.initFromSourceElements(this.sourceElements, this.trackElements);
3556
5013
  if (!this.originalSrc) {
3557
5014
  this.originalSrc = src;
3558
5015
  }
@@ -3796,6 +5253,9 @@ var Player = class _Player extends EventEmitter {
3796
5253
  if (trackConfig.default) {
3797
5254
  track.default = true;
3798
5255
  }
5256
+ if (trackConfig.describedSrc) {
5257
+ track.setAttribute("data-desc-src", trackConfig.describedSrc);
5258
+ }
3799
5259
  const firstChild = this.element.firstChild;
3800
5260
  if (firstChild && firstChild.nodeType === Node.ELEMENT_NODE && firstChild.tagName !== "TRACK") {
3801
5261
  this.element.insertBefore(track, firstChild);
@@ -3810,6 +5270,13 @@ var Player = class _Player extends EventEmitter {
3810
5270
  this.audioDescriptionSrc = config.audioDescriptionSrc || null;
3811
5271
  this.signLanguageSrc = config.signLanguageSrc || null;
3812
5272
  this.originalSrc = config.src;
5273
+ if (this.audioDescriptionManager) {
5274
+ this.audioDescriptionManager.updateSources(config.audioDescriptionSrc);
5275
+ this.audioDescriptionManager.reinitialize();
5276
+ }
5277
+ if (this.signLanguageManager) {
5278
+ this.signLanguageManager.updateSources(config.signLanguageSrc, config.signLanguageSources);
5279
+ }
3813
5280
  if (wasAudioDescriptionEnabled) {
3814
5281
  this.disableAudioDescription();
3815
5282
  }
@@ -4217,8 +5684,12 @@ var Player = class _Player extends EventEmitter {
4217
5684
  }
4218
5685
  return null;
4219
5686
  }
4220
- // Audio Description
5687
+ // Audio Description (delegated to AudioDescriptionManager)
4221
5688
  async enableAudioDescription() {
5689
+ return this.audioDescriptionManager.enable();
5690
+ }
5691
+ // Legacy method body preserved for reference - can be removed after testing
5692
+ async _legacyEnableAudioDescription() {
4222
5693
  const hasSourceElementsWithDesc = this.sourceElements.some((el) => el.getAttribute("data-desc-src"));
4223
5694
  const hasTracksWithDesc = this.audioDescriptionCaptionTracks.length > 0;
4224
5695
  if (!this.audioDescriptionSrc && !hasSourceElementsWithDesc && !hasTracksWithDesc) {
@@ -4951,6 +6422,10 @@ var Player = class _Player extends EventEmitter {
4951
6422
  this.emit("audiodescriptionenabled");
4952
6423
  }
4953
6424
  async disableAudioDescription() {
6425
+ return this.audioDescriptionManager.disable();
6426
+ }
6427
+ // Legacy method body preserved for reference - can be removed after testing
6428
+ async _legacyDisableAudioDescription() {
4954
6429
  if (!this.originalSrc) {
4955
6430
  return;
4956
6431
  }
@@ -5225,64 +6700,14 @@ var Player = class _Player extends EventEmitter {
5225
6700
  this.emit("audiodescriptiondisabled");
5226
6701
  }
5227
6702
  async toggleAudioDescription() {
5228
- const descriptionTrack = this.findTextTrack("descriptions");
5229
- const hasAudioDescriptionSrc = this.audioDescriptionSrc || this.sourceElements.some((el) => el.getAttribute("data-desc-src"));
5230
- if (descriptionTrack && hasAudioDescriptionSrc) {
5231
- if (this.state.audioDescriptionEnabled) {
5232
- this._audioDescriptionDesiredState = false;
5233
- descriptionTrack.mode = "hidden";
5234
- await this.disableAudioDescription();
5235
- } else {
5236
- this._audioDescriptionDesiredState = true;
5237
- await this.enableAudioDescription();
5238
- const enableDescriptionTrack = () => {
5239
- this.invalidateTrackCache();
5240
- const descTrack = this.findTextTrack("descriptions");
5241
- if (descTrack) {
5242
- if (descTrack.mode === "disabled") {
5243
- descTrack.mode = "hidden";
5244
- this.setManagedTimeout(() => {
5245
- descTrack.mode = "showing";
5246
- }, 50);
5247
- } else {
5248
- descTrack.mode = "showing";
5249
- }
5250
- } else if (this.element.readyState < 2) {
5251
- this.setManagedTimeout(enableDescriptionTrack, 100);
5252
- }
5253
- };
5254
- if (this.element.readyState >= 1) {
5255
- this.setManagedTimeout(enableDescriptionTrack, 200);
5256
- } else {
5257
- this.element.addEventListener("loadedmetadata", () => {
5258
- this.setManagedTimeout(enableDescriptionTrack, 200);
5259
- }, { once: true });
5260
- }
5261
- }
5262
- } else if (descriptionTrack) {
5263
- if (descriptionTrack.mode === "showing") {
5264
- this._audioDescriptionDesiredState = false;
5265
- descriptionTrack.mode = "hidden";
5266
- this.state.audioDescriptionEnabled = false;
5267
- this.emit("audiodescriptiondisabled");
5268
- } else {
5269
- this._audioDescriptionDesiredState = true;
5270
- descriptionTrack.mode = "showing";
5271
- this.state.audioDescriptionEnabled = true;
5272
- this.emit("audiodescriptionenabled");
5273
- }
5274
- } else if (hasAudioDescriptionSrc) {
5275
- if (this.state.audioDescriptionEnabled) {
5276
- this._audioDescriptionDesiredState = false;
5277
- await this.disableAudioDescription();
5278
- } else {
5279
- this._audioDescriptionDesiredState = true;
5280
- await this.enableAudioDescription();
5281
- }
5282
- }
6703
+ return this.audioDescriptionManager.toggle();
5283
6704
  }
5284
- // Sign Language
6705
+ // Sign Language (delegated to SignLanguageManager)
5285
6706
  enableSignLanguage() {
6707
+ return this.signLanguageManager.enable();
6708
+ }
6709
+ // Legacy method body preserved for reference - can be removed after testing
6710
+ _legacyEnableSignLanguage() {
5286
6711
  const hasMultipleSources = Object.keys(this.signLanguageSources).length > 0;
5287
6712
  const hasSingleSource = !!this.signLanguageSrc;
5288
6713
  if (!hasMultipleSources && !hasSingleSource) {
@@ -5533,23 +6958,16 @@ var Player = class _Player extends EventEmitter {
5533
6958
  }, 150);
5534
6959
  }
5535
6960
  disableSignLanguage() {
5536
- if (this.signLanguageSettingsMenuVisible) {
5537
- this.hideSignLanguageSettingsMenu({ focusButton: false });
5538
- }
5539
- if (this.signLanguageWrapper) {
5540
- this.signLanguageWrapper.style.display = "none";
5541
- }
5542
- this.state.signLanguageEnabled = false;
5543
- this.emit("signlanguagedisabled");
6961
+ return this.signLanguageManager.disable();
5544
6962
  }
5545
6963
  toggleSignLanguage() {
5546
- if (this.state.signLanguageEnabled) {
5547
- this.disableSignLanguage();
5548
- } else {
5549
- this.enableSignLanguage();
5550
- }
6964
+ return this.signLanguageManager.toggle();
5551
6965
  }
5552
6966
  setupSignLanguageInteraction() {
6967
+ return this.signLanguageManager._setupInteraction();
6968
+ }
6969
+ // Legacy method preserved for reference
6970
+ _legacySetupSignLanguageInteraction() {
5553
6971
  if (!this.signLanguageWrapper) return;
5554
6972
  const isMobile2 = window.innerWidth < 768;
5555
6973
  const isFullscreen = this.state.fullscreen;
@@ -5687,6 +7105,10 @@ var Player = class _Player extends EventEmitter {
5687
7105
  return langNames[langCode] || langCode.toUpperCase();
5688
7106
  }
5689
7107
  switchSignLanguage(langCode) {
7108
+ return this.signLanguageManager.switchLanguage(langCode);
7109
+ }
7110
+ // Legacy method preserved for reference
7111
+ _legacySwitchSignLanguage(langCode) {
5690
7112
  if (!this.signLanguageSources[langCode] || !this.signLanguageVideo) {
5691
7113
  return;
5692
7114
  }
@@ -5702,6 +7124,10 @@ var Player = class _Player extends EventEmitter {
5702
7124
  this.emit("signlanguagelanguagechanged", langCode);
5703
7125
  }
5704
7126
  showSignLanguageSettingsMenu() {
7127
+ return this.signLanguageManager.showSettingsMenu();
7128
+ }
7129
+ // Legacy method preserved for reference
7130
+ _legacyShowSignLanguageSettingsMenu() {
5705
7131
  this.signLanguageSettingsMenuJustOpened = true;
5706
7132
  setTimeout(() => {
5707
7133
  this.signLanguageSettingsMenuJustOpened = false;
@@ -5845,25 +7271,7 @@ var Player = class _Player extends EventEmitter {
5845
7271
  focusFirstMenuItem(this.signLanguageSettingsMenu, `.${this.options.classPrefix}-sign-language-settings-item`);
5846
7272
  }
5847
7273
  hideSignLanguageSettingsMenu({ focusButton = true } = {}) {
5848
- if (this.signLanguageSettingsMenu) {
5849
- this.signLanguageSettingsMenu.style.display = "none";
5850
- this.signLanguageSettingsMenuVisible = false;
5851
- this.signLanguageSettingsMenuJustOpened = false;
5852
- if (this.signLanguageSettingsMenuKeyHandler) {
5853
- this.signLanguageSettingsMenu.removeEventListener("keydown", this.signLanguageSettingsMenuKeyHandler);
5854
- this.signLanguageSettingsMenuKeyHandler = null;
5855
- }
5856
- const menuItems = Array.from(this.signLanguageSettingsMenu.querySelectorAll(`.${this.options.classPrefix}-sign-language-settings-item`));
5857
- menuItems.forEach((item) => {
5858
- item.setAttribute("tabindex", "-1");
5859
- });
5860
- if (this.signLanguageSettingsButton) {
5861
- this.signLanguageSettingsButton.setAttribute("aria-expanded", "false");
5862
- if (focusButton) {
5863
- this.signLanguageSettingsButton.focus({ preventScroll: true });
5864
- }
5865
- }
5866
- }
7274
+ return this.signLanguageManager.hideSettingsMenu({ focusButton });
5867
7275
  }
5868
7276
  positionSignLanguageSettingsMenuImmediate() {
5869
7277
  if (!this.signLanguageSettingsMenu || !this.signLanguageSettingsButton) return;
@@ -5967,6 +7375,13 @@ var Player = class _Player extends EventEmitter {
5967
7375
  }
5968
7376
  }
5969
7377
  constrainSignLanguagePosition() {
7378
+ return this.signLanguageManager.constrainPosition();
7379
+ }
7380
+ saveSignLanguagePreferences() {
7381
+ return this.signLanguageManager.savePreferences();
7382
+ }
7383
+ // Legacy methods preserved for reference - can be removed after testing
7384
+ _legacyConstrainSignLanguagePosition() {
5970
7385
  if (!this.signLanguageWrapper || !this.videoWrapper) return;
5971
7386
  if (this.signLanguageDraggable && this.signLanguageDraggable.manuallyPositioned) {
5972
7387
  return;
@@ -6016,7 +7431,7 @@ var Player = class _Player extends EventEmitter {
6016
7431
  this.signLanguageWrapper.style.bottom = "auto";
6017
7432
  this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter((c) => c.startsWith("vidply-sign-position-")));
6018
7433
  }
6019
- saveSignLanguagePreferences() {
7434
+ _legacySaveSignLanguagePreferences() {
6020
7435
  if (!this.signLanguageWrapper) return;
6021
7436
  this.storage.saveSignLanguagePreferences({
6022
7437
  size: {
@@ -6026,58 +7441,7 @@ var Player = class _Player extends EventEmitter {
6026
7441
  });
6027
7442
  }
6028
7443
  cleanupSignLanguage() {
6029
- if (this.signLanguageSettingsMenuVisible) {
6030
- this.hideSignLanguageSettingsMenu({ focusButton: false });
6031
- }
6032
- if (this.signLanguageDocumentClickHandler && this.signLanguageDocumentClickHandlerAdded) {
6033
- document.removeEventListener("mousedown", this.signLanguageDocumentClickHandler, true);
6034
- this.signLanguageDocumentClickHandlerAdded = false;
6035
- this.signLanguageDocumentClickHandler = null;
6036
- }
6037
- if (this.signLanguageSettingsHandlers) {
6038
- if (this.signLanguageSettingsButton) {
6039
- this.signLanguageSettingsButton.removeEventListener("click", this.signLanguageSettingsHandlers.settingsClick);
6040
- this.signLanguageSettingsButton.removeEventListener("keydown", this.signLanguageSettingsHandlers.settingsKeydown);
6041
- }
6042
- this.signLanguageSettingsHandlers = null;
6043
- }
6044
- if (this.signLanguageHandlers) {
6045
- this.off("play", this.signLanguageHandlers.play);
6046
- this.off("pause", this.signLanguageHandlers.pause);
6047
- this.off("timeupdate", this.signLanguageHandlers.timeupdate);
6048
- this.off("ratechange", this.signLanguageHandlers.ratechange);
6049
- if (this.signLanguageHandlers.captionChange) {
6050
- this.off("captionsenabled", this.signLanguageHandlers.captionChange);
6051
- }
6052
- this.signLanguageHandlers = null;
6053
- }
6054
- if (this.signLanguageInteractionHandlers) {
6055
- if (this.signLanguageHeader && this.signLanguageInteractionHandlers.headerKeyHandler) {
6056
- this.signLanguageHeader.removeEventListener("keydown", this.signLanguageInteractionHandlers.headerKeyHandler);
6057
- }
6058
- if (this.signLanguageWrapper && this.signLanguageInteractionHandlers.customKeyHandler) {
6059
- this.signLanguageWrapper.removeEventListener("keydown", this.signLanguageInteractionHandlers.customKeyHandler);
6060
- }
6061
- }
6062
- if (this.signLanguageDraggable) {
6063
- if (this.signLanguageDraggable.pointerResizeMode) {
6064
- this.signLanguageDraggable.disablePointerResizeMode();
6065
- }
6066
- this.signLanguageDraggable.destroy();
6067
- this.signLanguageDraggable = null;
6068
- }
6069
- this.signLanguageInteractionHandlers = null;
6070
- if (this.signLanguageWrapper && this.signLanguageWrapper.parentNode) {
6071
- if (this.signLanguageVideo) {
6072
- this.signLanguageVideo.pause();
6073
- this.signLanguageVideo.src = "";
6074
- }
6075
- this.signLanguageWrapper.parentNode.removeChild(this.signLanguageWrapper);
6076
- }
6077
- this.signLanguageWrapper = null;
6078
- this.signLanguageVideo = null;
6079
- this.signLanguageSettingsButton = null;
6080
- this.signLanguageSettingsMenu = null;
7444
+ return this.signLanguageManager.cleanup();
6081
7445
  }
6082
7446
  // Settings
6083
7447
  // Settings dialog removed - using individual control buttons instead