vidply 1.0.9 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/vidply.css +47 -14
- package/dist/vidply.esm.js +1063 -663
- package/dist/vidply.esm.js.map +3 -3
- package/dist/vidply.esm.min.js +3 -3
- package/dist/vidply.esm.min.meta.json +28 -10
- package/dist/vidply.js +1063 -663
- package/dist/vidply.js.map +3 -3
- package/dist/vidply.min.css +1 -1
- package/dist/vidply.min.js +3 -3
- package/dist/vidply.min.meta.json +27 -9
- package/package.json +1 -1
- package/src/controls/ControlBar.js +24 -14
- package/src/controls/TranscriptManager.js +344 -578
- package/src/core/Player.js +150 -276
- package/src/i18n/translations.js +50 -5
- package/src/styles/vidply.css +47 -14
- package/src/utils/DraggableResizable.js +771 -0
package/src/core/Player.js
CHANGED
|
@@ -16,6 +16,7 @@ import {HLSRenderer} from '../renderers/HLSRenderer.js';
|
|
|
16
16
|
import {createPlayOverlay} from '../icons/Icons.js';
|
|
17
17
|
import {i18n} from '../i18n/i18n.js';
|
|
18
18
|
import {StorageManager} from '../utils/StorageManager.js';
|
|
19
|
+
import {DraggableResizable} from '../utils/DraggableResizable.js';
|
|
19
20
|
|
|
20
21
|
export class Player extends EventEmitter {
|
|
21
22
|
constructor(element, options = {}) {
|
|
@@ -214,6 +215,7 @@ export class Player extends EventEmitter {
|
|
|
214
215
|
this.originalAudioDescriptionSource = null;
|
|
215
216
|
// Store caption tracks that should be swapped for audio description
|
|
216
217
|
this.audioDescriptionCaptionTracks = [];
|
|
218
|
+
this._audioDescriptionDesiredState = false;
|
|
217
219
|
|
|
218
220
|
// DOM query cache (for performance optimization)
|
|
219
221
|
this._textTracksCache = null;
|
|
@@ -396,7 +398,6 @@ export class Player extends EventEmitter {
|
|
|
396
398
|
// This allows custom controls to work on iOS devices
|
|
397
399
|
if (this.element.tagName === 'VIDEO' && this.options.playsInline) {
|
|
398
400
|
this.element.setAttribute('playsinline', '');
|
|
399
|
-
this.element.setAttribute('webkit-playsinline', ''); // For older iOS versions
|
|
400
401
|
this.element.playsInline = true; // Property version
|
|
401
402
|
}
|
|
402
403
|
|
|
@@ -437,6 +438,16 @@ export class Player extends EventEmitter {
|
|
|
437
438
|
this.toggle();
|
|
438
439
|
}
|
|
439
440
|
});
|
|
441
|
+
|
|
442
|
+
this.on('play', () => {
|
|
443
|
+
this.hidePosterOverlay();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
this.on('timeupdate', () => {
|
|
447
|
+
if (this.state.currentTime > 0) {
|
|
448
|
+
this.hidePosterOverlay();
|
|
449
|
+
}
|
|
450
|
+
});
|
|
440
451
|
}
|
|
441
452
|
|
|
442
453
|
createPlayButtonOverlay() {
|
|
@@ -653,6 +664,33 @@ export class Player extends EventEmitter {
|
|
|
653
664
|
return this.trackElements.find(el => el.track === track);
|
|
654
665
|
}
|
|
655
666
|
|
|
667
|
+
showPosterOverlay() {
|
|
668
|
+
if (!this.videoWrapper || this.element.tagName !== 'VIDEO') {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
const poster =
|
|
673
|
+
this.element.getAttribute('poster') ||
|
|
674
|
+
this.element.poster ||
|
|
675
|
+
this.options.poster;
|
|
676
|
+
|
|
677
|
+
if (!poster) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
this.videoWrapper.style.setProperty('--vidply-poster-image', `url("${poster}")`);
|
|
682
|
+
this.videoWrapper.classList.add('vidply-forced-poster');
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
hidePosterOverlay() {
|
|
686
|
+
if (!this.videoWrapper) {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
this.videoWrapper.classList.remove('vidply-forced-poster');
|
|
691
|
+
this.videoWrapper.style.removeProperty('--vidply-poster-image');
|
|
692
|
+
}
|
|
693
|
+
|
|
656
694
|
/**
|
|
657
695
|
* Set a managed timeout that will be cleaned up on destroy
|
|
658
696
|
* @param {Function} callback - Callback function
|
|
@@ -1048,6 +1086,11 @@ export class Player extends EventEmitter {
|
|
|
1048
1086
|
// Store current playback state
|
|
1049
1087
|
const currentTime = this.state.currentTime;
|
|
1050
1088
|
const wasPlaying = this.state.playing;
|
|
1089
|
+
const shouldKeepPoster = !wasPlaying && currentTime === 0;
|
|
1090
|
+
|
|
1091
|
+
if (shouldKeepPoster) {
|
|
1092
|
+
this.showPosterOverlay();
|
|
1093
|
+
}
|
|
1051
1094
|
|
|
1052
1095
|
// Store swapped tracks for transcript reload (declare at function scope)
|
|
1053
1096
|
let swappedTracksForTranscript = [];
|
|
@@ -1316,8 +1359,17 @@ export class Player extends EventEmitter {
|
|
|
1316
1359
|
if (sourceInfo.descSrc) {
|
|
1317
1360
|
newSource.setAttribute('data-desc-src', sourceInfo.descSrc);
|
|
1318
1361
|
}
|
|
1319
|
-
this.element.
|
|
1362
|
+
const firstTrack = this.element.querySelector('track');
|
|
1363
|
+
if (firstTrack) {
|
|
1364
|
+
this.element.insertBefore(newSource, firstTrack);
|
|
1365
|
+
} else {
|
|
1366
|
+
this.element.appendChild(newSource);
|
|
1367
|
+
}
|
|
1320
1368
|
});
|
|
1369
|
+
|
|
1370
|
+
// Ensure cached source references are refreshed after rebuilding the list
|
|
1371
|
+
this._sourceElementsDirty = true;
|
|
1372
|
+
this._sourceElementsCache = null;
|
|
1321
1373
|
|
|
1322
1374
|
// Force reload by calling load() on the element
|
|
1323
1375
|
// This should pick up the new src attributes from the re-added source elements
|
|
@@ -1336,28 +1388,23 @@ export class Player extends EventEmitter {
|
|
|
1336
1388
|
// Wait a bit more for tracks to be recognized and loaded after video metadata loads
|
|
1337
1389
|
await new Promise(resolve => setTimeout(resolve, 300));
|
|
1338
1390
|
|
|
1339
|
-
//
|
|
1340
|
-
if (
|
|
1341
|
-
|
|
1342
|
-
// Setting readyState check or seeking to 0.001 seconds will hide the poster
|
|
1343
|
-
if (this.element.readyState >= 1) { // HAVE_METADATA
|
|
1344
|
-
// Seek to a tiny fraction to trigger poster hiding without actually moving
|
|
1345
|
-
this.element.currentTime = 0.001;
|
|
1346
|
-
// Then seek back to 0 after a brief moment to ensure poster stays hidden
|
|
1347
|
-
setTimeout(() => {
|
|
1348
|
-
this.element.currentTime = 0;
|
|
1349
|
-
}, 10);
|
|
1350
|
-
}
|
|
1391
|
+
// Restore playback position (avoid forcing first frame if still at start)
|
|
1392
|
+
if (currentTime > 0) {
|
|
1393
|
+
this.seek(currentTime);
|
|
1351
1394
|
}
|
|
1352
1395
|
|
|
1353
|
-
// Restore playback position
|
|
1354
|
-
this.seek(currentTime);
|
|
1355
|
-
|
|
1356
1396
|
if (wasPlaying) {
|
|
1357
1397
|
this.play();
|
|
1358
1398
|
}
|
|
1359
1399
|
|
|
1400
|
+
if (!shouldKeepPoster) {
|
|
1401
|
+
this.hidePosterOverlay();
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1360
1404
|
// Update state and emit event
|
|
1405
|
+
if (!this._audioDescriptionDesiredState) {
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1361
1408
|
this.state.audioDescriptionEnabled = true;
|
|
1362
1409
|
this.emit('audiodescriptionenabled');
|
|
1363
1410
|
} else {
|
|
@@ -1606,8 +1653,10 @@ export class Player extends EventEmitter {
|
|
|
1606
1653
|
}
|
|
1607
1654
|
}
|
|
1608
1655
|
|
|
1609
|
-
// Restore playback position
|
|
1610
|
-
|
|
1656
|
+
// Restore playback position (avoid forcing first frame if still at start)
|
|
1657
|
+
if (currentTime > 0) {
|
|
1658
|
+
this.seek(currentTime);
|
|
1659
|
+
}
|
|
1611
1660
|
|
|
1612
1661
|
if (wasPlaying) {
|
|
1613
1662
|
this.play();
|
|
@@ -1854,6 +1903,14 @@ export class Player extends EventEmitter {
|
|
|
1854
1903
|
}
|
|
1855
1904
|
}
|
|
1856
1905
|
|
|
1906
|
+
if (!shouldKeepPoster) {
|
|
1907
|
+
this.hidePosterOverlay();
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1910
|
+
if (!this._audioDescriptionDesiredState) {
|
|
1911
|
+
return;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1857
1914
|
this.state.audioDescriptionEnabled = true;
|
|
1858
1915
|
this.emit('audiodescriptionenabled');
|
|
1859
1916
|
}
|
|
@@ -1928,8 +1985,17 @@ export class Player extends EventEmitter {
|
|
|
1928
1985
|
if (sourceInfo.descSrc) {
|
|
1929
1986
|
newSource.setAttribute('data-desc-src', sourceInfo.descSrc);
|
|
1930
1987
|
}
|
|
1931
|
-
this.element.
|
|
1988
|
+
const firstTrack = this.element.querySelector('track');
|
|
1989
|
+
if (firstTrack) {
|
|
1990
|
+
this.element.insertBefore(newSource, firstTrack);
|
|
1991
|
+
} else {
|
|
1992
|
+
this.element.appendChild(newSource);
|
|
1993
|
+
}
|
|
1932
1994
|
});
|
|
1995
|
+
|
|
1996
|
+
// Ensure cached source references are refreshed after rebuilding the list
|
|
1997
|
+
this._sourceElementsDirty = true;
|
|
1998
|
+
this._sourceElementsCache = null;
|
|
1933
1999
|
|
|
1934
2000
|
// Force reload
|
|
1935
2001
|
this.element.load();
|
|
@@ -1940,20 +2006,29 @@ export class Player extends EventEmitter {
|
|
|
1940
2006
|
this.element.load();
|
|
1941
2007
|
}
|
|
1942
2008
|
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
2009
|
+
if (currentTime > 0 || wasPlaying) {
|
|
2010
|
+
// Wait for new source to load
|
|
2011
|
+
await new Promise((resolve) => {
|
|
2012
|
+
const onLoadedMetadata = () => {
|
|
2013
|
+
this.element.removeEventListener('loadedmetadata', onLoadedMetadata);
|
|
2014
|
+
resolve();
|
|
2015
|
+
};
|
|
2016
|
+
this.element.addEventListener('loadedmetadata', onLoadedMetadata);
|
|
2017
|
+
});
|
|
1951
2018
|
|
|
1952
|
-
|
|
1953
|
-
|
|
2019
|
+
if (currentTime > 0) {
|
|
2020
|
+
this.seek(currentTime);
|
|
2021
|
+
}
|
|
1954
2022
|
|
|
1955
|
-
|
|
1956
|
-
|
|
2023
|
+
if (wasPlaying) {
|
|
2024
|
+
this.play();
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
if (!wasPlaying && currentTime === 0) {
|
|
2029
|
+
this.showPosterOverlay();
|
|
2030
|
+
} else {
|
|
2031
|
+
this.hidePosterOverlay();
|
|
1957
2032
|
}
|
|
1958
2033
|
|
|
1959
2034
|
// Reload transcript if visible (after video metadata loaded, tracks should be available)
|
|
@@ -1967,6 +2042,10 @@ export class Player extends EventEmitter {
|
|
|
1967
2042
|
}, 500);
|
|
1968
2043
|
}
|
|
1969
2044
|
|
|
2045
|
+
if (this._audioDescriptionDesiredState) {
|
|
2046
|
+
return;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
1970
2049
|
this.state.audioDescriptionEnabled = false;
|
|
1971
2050
|
this.emit('audiodescriptiondisabled');
|
|
1972
2051
|
}
|
|
@@ -1982,10 +2061,12 @@ export class Player extends EventEmitter {
|
|
|
1982
2061
|
if (descriptionTrack && hasAudioDescriptionSrc) {
|
|
1983
2062
|
// We have both: toggle description track AND swap caption tracks/sources
|
|
1984
2063
|
if (this.state.audioDescriptionEnabled) {
|
|
2064
|
+
this._audioDescriptionDesiredState = false;
|
|
1985
2065
|
// Disable: toggle description track off and swap captions/sources back
|
|
1986
2066
|
descriptionTrack.mode = 'hidden';
|
|
1987
2067
|
await this.disableAudioDescription();
|
|
1988
2068
|
} else {
|
|
2069
|
+
this._audioDescriptionDesiredState = true;
|
|
1989
2070
|
// Enable: swap caption tracks/sources and toggle description track on
|
|
1990
2071
|
await this.enableAudioDescription();
|
|
1991
2072
|
// Wait for tracks to be ready after source swap, then enable description track
|
|
@@ -2022,10 +2103,12 @@ export class Player extends EventEmitter {
|
|
|
2022
2103
|
// Only description track, no audio-described video source to swap
|
|
2023
2104
|
// Toggle description track
|
|
2024
2105
|
if (descriptionTrack.mode === 'showing') {
|
|
2106
|
+
this._audioDescriptionDesiredState = false;
|
|
2025
2107
|
descriptionTrack.mode = 'hidden';
|
|
2026
2108
|
this.state.audioDescriptionEnabled = false;
|
|
2027
2109
|
this.emit('audiodescriptiondisabled');
|
|
2028
2110
|
} else {
|
|
2111
|
+
this._audioDescriptionDesiredState = true;
|
|
2029
2112
|
descriptionTrack.mode = 'showing';
|
|
2030
2113
|
this.state.audioDescriptionEnabled = true;
|
|
2031
2114
|
this.emit('audiodescriptionenabled');
|
|
@@ -2033,8 +2116,10 @@ export class Player extends EventEmitter {
|
|
|
2033
2116
|
} else if (hasAudioDescriptionSrc) {
|
|
2034
2117
|
// Use audio-described video source (no description track)
|
|
2035
2118
|
if (this.state.audioDescriptionEnabled) {
|
|
2119
|
+
this._audioDescriptionDesiredState = false;
|
|
2036
2120
|
await this.disableAudioDescription();
|
|
2037
2121
|
} else {
|
|
2122
|
+
this._audioDescriptionDesiredState = true;
|
|
2038
2123
|
await this.enableAudioDescription();
|
|
2039
2124
|
}
|
|
2040
2125
|
}
|
|
@@ -2164,235 +2249,27 @@ export class Player extends EventEmitter {
|
|
|
2164
2249
|
setupSignLanguageInteraction() {
|
|
2165
2250
|
if (!this.signLanguageWrapper) return;
|
|
2166
2251
|
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
startX = e.clientX;
|
|
2185
|
-
startY = e.clientY;
|
|
2186
|
-
const rect = this.signLanguageWrapper.getBoundingClientRect();
|
|
2187
|
-
startLeft = rect.left;
|
|
2188
|
-
startTop = rect.top;
|
|
2189
|
-
this.signLanguageWrapper.classList.add('vidply-sign-dragging');
|
|
2190
|
-
};
|
|
2191
|
-
|
|
2192
|
-
// Mouse resize on handles
|
|
2193
|
-
const onMouseDownHandle = (e) => {
|
|
2194
|
-
if (!e.target.classList.contains('vidply-sign-resize-handle')) return;
|
|
2195
|
-
e.preventDefault();
|
|
2196
|
-
e.stopPropagation();
|
|
2197
|
-
isResizing = true;
|
|
2198
|
-
resizeDirection = e.target.getAttribute('data-direction');
|
|
2199
|
-
startX = e.clientX;
|
|
2200
|
-
startY = e.clientY;
|
|
2201
|
-
const rect = this.signLanguageWrapper.getBoundingClientRect();
|
|
2202
|
-
startLeft = rect.left;
|
|
2203
|
-
startTop = rect.top;
|
|
2204
|
-
startWidth = rect.width;
|
|
2205
|
-
startHeight = rect.height;
|
|
2206
|
-
this.signLanguageWrapper.classList.add('vidply-sign-resizing');
|
|
2207
|
-
};
|
|
2208
|
-
|
|
2209
|
-
const onMouseMove = (e) => {
|
|
2210
|
-
if (isDragging) {
|
|
2211
|
-
const deltaX = e.clientX - startX;
|
|
2212
|
-
const deltaY = e.clientY - startY;
|
|
2213
|
-
|
|
2214
|
-
// Get videoWrapper and container dimensions
|
|
2215
|
-
const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
|
|
2216
|
-
const containerRect = this.container.getBoundingClientRect();
|
|
2217
|
-
const wrapperRect = this.signLanguageWrapper.getBoundingClientRect();
|
|
2218
|
-
|
|
2219
|
-
// Calculate videoWrapper position relative to container
|
|
2220
|
-
const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
|
|
2221
|
-
const videoWrapperTop = videoWrapperRect.top - containerRect.top;
|
|
2222
|
-
|
|
2223
|
-
// Calculate new position (in client coordinates)
|
|
2224
|
-
let newLeft = startLeft + deltaX - containerRect.left;
|
|
2225
|
-
let newTop = startTop + deltaY - containerRect.top;
|
|
2226
|
-
|
|
2227
|
-
const controlsHeight = 95; // Height of controls when visible
|
|
2228
|
-
|
|
2229
|
-
// Constrain to videoWrapper bounds (ensuring it stays above controls)
|
|
2230
|
-
newLeft = Math.max(videoWrapperLeft, Math.min(newLeft, videoWrapperLeft + videoWrapperRect.width - wrapperRect.width));
|
|
2231
|
-
newTop = Math.max(videoWrapperTop, Math.min(newTop, videoWrapperTop + videoWrapperRect.height - wrapperRect.height - controlsHeight));
|
|
2232
|
-
|
|
2233
|
-
this.signLanguageWrapper.style.left = `${newLeft}px`;
|
|
2234
|
-
this.signLanguageWrapper.style.top = `${newTop}px`;
|
|
2235
|
-
this.signLanguageWrapper.style.right = 'auto';
|
|
2236
|
-
this.signLanguageWrapper.style.bottom = 'auto';
|
|
2237
|
-
// Remove position classes
|
|
2238
|
-
this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter(c => c.startsWith('vidply-sign-position-')));
|
|
2239
|
-
} else if (isResizing) {
|
|
2240
|
-
const deltaX = e.clientX - startX;
|
|
2241
|
-
|
|
2242
|
-
// Get videoWrapper and container dimensions
|
|
2243
|
-
const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
|
|
2244
|
-
const containerRect = this.container.getBoundingClientRect();
|
|
2245
|
-
|
|
2246
|
-
let newWidth = startWidth;
|
|
2247
|
-
let newLeft = startLeft - containerRect.left;
|
|
2248
|
-
|
|
2249
|
-
// Only resize width, let height auto-adjust to maintain aspect ratio
|
|
2250
|
-
if (resizeDirection.includes('e')) {
|
|
2251
|
-
newWidth = Math.max(150, startWidth + deltaX);
|
|
2252
|
-
// Constrain width to not exceed videoWrapper right edge
|
|
2253
|
-
const maxWidth = (videoWrapperRect.right - startLeft);
|
|
2254
|
-
newWidth = Math.min(newWidth, maxWidth);
|
|
2255
|
-
}
|
|
2256
|
-
if (resizeDirection.includes('w')) {
|
|
2257
|
-
const proposedWidth = Math.max(150, startWidth - deltaX);
|
|
2258
|
-
const proposedLeft = startLeft + (startWidth - proposedWidth) - containerRect.left;
|
|
2259
|
-
// Constrain to not go beyond videoWrapper left edge
|
|
2260
|
-
const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
|
|
2261
|
-
if (proposedLeft >= videoWrapperLeft) {
|
|
2262
|
-
newWidth = proposedWidth;
|
|
2263
|
-
newLeft = proposedLeft;
|
|
2264
|
-
}
|
|
2265
|
-
}
|
|
2266
|
-
|
|
2267
|
-
this.signLanguageWrapper.style.width = `${newWidth}px`;
|
|
2268
|
-
this.signLanguageWrapper.style.height = 'auto'; // Let video maintain aspect ratio
|
|
2269
|
-
if (resizeDirection.includes('w')) {
|
|
2270
|
-
this.signLanguageWrapper.style.left = `${newLeft}px`;
|
|
2271
|
-
}
|
|
2272
|
-
this.signLanguageWrapper.style.right = 'auto';
|
|
2273
|
-
this.signLanguageWrapper.style.bottom = 'auto';
|
|
2274
|
-
// Remove position classes
|
|
2275
|
-
this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter(c => c.startsWith('vidply-sign-position-')));
|
|
2276
|
-
}
|
|
2277
|
-
};
|
|
2278
|
-
|
|
2279
|
-
const onMouseUp = () => {
|
|
2280
|
-
if (isDragging || isResizing) {
|
|
2281
|
-
this.saveSignLanguagePreferences();
|
|
2282
|
-
}
|
|
2283
|
-
isDragging = false;
|
|
2284
|
-
isResizing = false;
|
|
2285
|
-
resizeDirection = null;
|
|
2286
|
-
this.signLanguageWrapper.classList.remove('vidply-sign-dragging', 'vidply-sign-resizing');
|
|
2287
|
-
};
|
|
2288
|
-
|
|
2289
|
-
// Keyboard controls
|
|
2290
|
-
const onKeyDown = (e) => {
|
|
2291
|
-
// Toggle drag mode with D key
|
|
2292
|
-
if (e.key === 'd' || e.key === 'D') {
|
|
2293
|
-
dragMode = !dragMode;
|
|
2294
|
-
resizeMode = false;
|
|
2295
|
-
this.signLanguageWrapper.classList.toggle('vidply-sign-keyboard-drag', dragMode);
|
|
2296
|
-
this.signLanguageWrapper.classList.remove('vidply-sign-keyboard-resize');
|
|
2297
|
-
e.preventDefault();
|
|
2298
|
-
return;
|
|
2299
|
-
}
|
|
2300
|
-
|
|
2301
|
-
// Toggle resize mode with R key
|
|
2302
|
-
if (e.key === 'r' || e.key === 'R') {
|
|
2303
|
-
resizeMode = !resizeMode;
|
|
2304
|
-
dragMode = false;
|
|
2305
|
-
this.signLanguageWrapper.classList.toggle('vidply-sign-keyboard-resize', resizeMode);
|
|
2306
|
-
this.signLanguageWrapper.classList.remove('vidply-sign-keyboard-drag');
|
|
2307
|
-
e.preventDefault();
|
|
2308
|
-
return;
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
// Escape to exit modes
|
|
2312
|
-
if (e.key === 'Escape') {
|
|
2313
|
-
dragMode = false;
|
|
2314
|
-
resizeMode = false;
|
|
2315
|
-
this.signLanguageWrapper.classList.remove('vidply-sign-keyboard-drag', 'vidply-sign-keyboard-resize');
|
|
2316
|
-
e.preventDefault();
|
|
2317
|
-
return;
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
// Arrow keys for drag/resize
|
|
2321
|
-
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight'].includes(e.key)) {
|
|
2322
|
-
const step = e.shiftKey ? 10 : 5;
|
|
2323
|
-
const rect = this.signLanguageWrapper.getBoundingClientRect();
|
|
2324
|
-
|
|
2325
|
-
// Get videoWrapper and container bounds
|
|
2326
|
-
const videoWrapperRect = this.videoWrapper.getBoundingClientRect();
|
|
2327
|
-
const containerRect = this.container.getBoundingClientRect();
|
|
2328
|
-
|
|
2329
|
-
// Calculate videoWrapper position relative to container
|
|
2330
|
-
const videoWrapperLeft = videoWrapperRect.left - containerRect.left;
|
|
2331
|
-
const videoWrapperTop = videoWrapperRect.top - containerRect.top;
|
|
2332
|
-
|
|
2333
|
-
if (dragMode) {
|
|
2334
|
-
// Get current position relative to container
|
|
2335
|
-
let left = rect.left - containerRect.left;
|
|
2336
|
-
let top = rect.top - containerRect.top;
|
|
2337
|
-
|
|
2338
|
-
if (e.key === 'ArrowLeft') left -= step;
|
|
2339
|
-
if (e.key === 'ArrowRight') left += step;
|
|
2340
|
-
if (e.key === 'ArrowUp') top -= step;
|
|
2341
|
-
if (e.key === 'ArrowDown') top += step;
|
|
2342
|
-
|
|
2343
|
-
const controlsHeight = 95; // Height of controls when visible
|
|
2344
|
-
|
|
2345
|
-
// Constrain to videoWrapper bounds (ensuring it stays above controls)
|
|
2346
|
-
left = Math.max(videoWrapperLeft, Math.min(left, videoWrapperLeft + videoWrapperRect.width - rect.width));
|
|
2347
|
-
top = Math.max(videoWrapperTop, Math.min(top, videoWrapperTop + videoWrapperRect.height - rect.height - controlsHeight));
|
|
2348
|
-
|
|
2349
|
-
this.signLanguageWrapper.style.left = `${left}px`;
|
|
2350
|
-
this.signLanguageWrapper.style.top = `${top}px`;
|
|
2351
|
-
this.signLanguageWrapper.style.right = 'auto';
|
|
2352
|
-
this.signLanguageWrapper.style.bottom = 'auto';
|
|
2353
|
-
// Remove position classes
|
|
2354
|
-
this.signLanguageWrapper.classList.remove(...Array.from(this.signLanguageWrapper.classList).filter(c => c.startsWith('vidply-sign-position-')));
|
|
2355
|
-
this.saveSignLanguagePreferences();
|
|
2356
|
-
e.preventDefault();
|
|
2357
|
-
} else if (resizeMode) {
|
|
2358
|
-
let width = rect.width;
|
|
2359
|
-
|
|
2360
|
-
// Only adjust width, height will auto-adjust to maintain aspect ratio
|
|
2361
|
-
if (e.key === 'ArrowLeft') width -= step;
|
|
2362
|
-
if (e.key === 'ArrowRight') width += step;
|
|
2363
|
-
// Up/Down also adjusts width for simplicity
|
|
2364
|
-
if (e.key === 'ArrowUp') width += step;
|
|
2365
|
-
if (e.key === 'ArrowDown') width -= step;
|
|
2366
|
-
|
|
2367
|
-
// Constrain width
|
|
2368
|
-
width = Math.max(150, width);
|
|
2369
|
-
// Don't let it exceed videoWrapper width
|
|
2370
|
-
width = Math.min(width, videoWrapperRect.width);
|
|
2371
|
-
|
|
2372
|
-
this.signLanguageWrapper.style.width = `${width}px`;
|
|
2373
|
-
this.signLanguageWrapper.style.height = 'auto';
|
|
2374
|
-
this.saveSignLanguagePreferences();
|
|
2375
|
-
e.preventDefault();
|
|
2376
|
-
}
|
|
2377
|
-
}
|
|
2378
|
-
};
|
|
2379
|
-
|
|
2380
|
-
// Attach event listeners
|
|
2381
|
-
this.signLanguageVideo.addEventListener('mousedown', onMouseDownVideo);
|
|
2382
|
-
const handles = this.signLanguageWrapper.querySelectorAll('.vidply-sign-resize-handle');
|
|
2383
|
-
handles.forEach(handle => handle.addEventListener('mousedown', onMouseDownHandle));
|
|
2384
|
-
document.addEventListener('mousemove', onMouseMove);
|
|
2385
|
-
document.addEventListener('mouseup', onMouseUp);
|
|
2386
|
-
this.signLanguageWrapper.addEventListener('keydown', onKeyDown);
|
|
2252
|
+
// Get resize handles
|
|
2253
|
+
const resizeHandles = Array.from(this.signLanguageWrapper.querySelectorAll('.vidply-sign-resize-handle'));
|
|
2254
|
+
|
|
2255
|
+
// Create DraggableResizable utility
|
|
2256
|
+
this.signLanguageDraggable = new DraggableResizable(this.signLanguageWrapper, {
|
|
2257
|
+
dragHandle: this.signLanguageVideo,
|
|
2258
|
+
resizeHandles: resizeHandles,
|
|
2259
|
+
constrainToViewport: true,
|
|
2260
|
+
maintainAspectRatio: true,
|
|
2261
|
+
minWidth: 150,
|
|
2262
|
+
minHeight: 100,
|
|
2263
|
+
classPrefix: 'vidply-sign',
|
|
2264
|
+
keyboardDragKey: 'd',
|
|
2265
|
+
keyboardResizeKey: 'r',
|
|
2266
|
+
keyboardStep: 5,
|
|
2267
|
+
keyboardStepLarge: 10
|
|
2268
|
+
});
|
|
2387
2269
|
|
|
2388
2270
|
// Store for cleanup
|
|
2389
2271
|
this.signLanguageInteractionHandlers = {
|
|
2390
|
-
|
|
2391
|
-
mouseDownHandle: onMouseDownHandle,
|
|
2392
|
-
mouseMove: onMouseMove,
|
|
2393
|
-
mouseUp: onMouseUp,
|
|
2394
|
-
keyDown: onKeyDown,
|
|
2395
|
-
handles
|
|
2272
|
+
draggable: this.signLanguageDraggable
|
|
2396
2273
|
};
|
|
2397
2274
|
}
|
|
2398
2275
|
|
|
@@ -2483,23 +2360,14 @@ export class Player extends EventEmitter {
|
|
|
2483
2360
|
this.signLanguageHandlers = null;
|
|
2484
2361
|
}
|
|
2485
2362
|
|
|
2486
|
-
//
|
|
2487
|
-
if (this.
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
}
|
|
2491
|
-
if (this.signLanguageInteractionHandlers.handles) {
|
|
2492
|
-
this.signLanguageInteractionHandlers.handles.forEach(handle => {
|
|
2493
|
-
handle.removeEventListener('mousedown', this.signLanguageInteractionHandlers.mouseDownHandle);
|
|
2494
|
-
});
|
|
2495
|
-
}
|
|
2496
|
-
document.removeEventListener('mousemove', this.signLanguageInteractionHandlers.mouseMove);
|
|
2497
|
-
document.removeEventListener('mouseup', this.signLanguageInteractionHandlers.mouseUp);
|
|
2498
|
-
if (this.signLanguageWrapper) {
|
|
2499
|
-
this.signLanguageWrapper.removeEventListener('keydown', this.signLanguageInteractionHandlers.keyDown);
|
|
2500
|
-
}
|
|
2501
|
-
this.signLanguageInteractionHandlers = null;
|
|
2363
|
+
// Destroy draggable utility
|
|
2364
|
+
if (this.signLanguageDraggable) {
|
|
2365
|
+
this.signLanguageDraggable.destroy();
|
|
2366
|
+
this.signLanguageDraggable = null;
|
|
2502
2367
|
}
|
|
2368
|
+
|
|
2369
|
+
// Clear interaction handlers reference
|
|
2370
|
+
this.signLanguageInteractionHandlers = null;
|
|
2503
2371
|
|
|
2504
2372
|
// Remove video and wrapper elements
|
|
2505
2373
|
if (this.signLanguageWrapper && this.signLanguageWrapper.parentNode) {
|
|
@@ -2619,7 +2487,10 @@ export class Player extends EventEmitter {
|
|
|
2619
2487
|
}
|
|
2620
2488
|
|
|
2621
2489
|
if (this.transcriptManager && this.transcriptManager.isVisible) {
|
|
2622
|
-
|
|
2490
|
+
// Only auto-position if user hasn't manually moved it
|
|
2491
|
+
if (!this.transcriptManager.draggableResizable || !this.transcriptManager.draggableResizable.manuallyPositioned) {
|
|
2492
|
+
this.transcriptManager.positionTranscript();
|
|
2493
|
+
}
|
|
2623
2494
|
}
|
|
2624
2495
|
};
|
|
2625
2496
|
|
|
@@ -2632,7 +2503,10 @@ export class Player extends EventEmitter {
|
|
|
2632
2503
|
// Wait for layout to settle
|
|
2633
2504
|
setTimeout(() => {
|
|
2634
2505
|
if (this.transcriptManager && this.transcriptManager.isVisible) {
|
|
2635
|
-
|
|
2506
|
+
// Only auto-position if user hasn't manually moved it
|
|
2507
|
+
if (!this.transcriptManager.draggableResizable || !this.transcriptManager.draggableResizable.manuallyPositioned) {
|
|
2508
|
+
this.transcriptManager.positionTranscript();
|
|
2509
|
+
}
|
|
2636
2510
|
}
|
|
2637
2511
|
}, 100);
|
|
2638
2512
|
};
|