vidply 1.0.34 → 1.0.36
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/dev/{vidply.SoundCloudRenderer-RIA3QKP3.js → vidply.SoundCloudRenderer-HCMKXHSX.js} +1 -3
- package/dist/dev/vidply.SoundCloudRenderer-HCMKXHSX.js.map +7 -0
- package/dist/dev/{vidply.TranscriptManager-T3BVTZHZ.js → vidply.TranscriptManager-EIIN5YOF.js} +2 -2
- package/dist/dev/{vidply.VimeoRenderer-DY2FG7LZ.js → vidply.VimeoRenderer-SLEBCZTT.js} +1 -2
- package/dist/dev/vidply.VimeoRenderer-SLEBCZTT.js.map +7 -0
- package/dist/dev/{vidply.YouTubeRenderer-EVXXE34A.js → vidply.YouTubeRenderer-E6F4UGVF.js} +1 -2
- package/dist/dev/vidply.YouTubeRenderer-E6F4UGVF.js.map +7 -0
- package/dist/dev/{vidply.chunk-74NJTDQI.js → vidply.chunk-AXXU22HR.js} +87 -10
- package/dist/dev/{vidply.chunk-74NJTDQI.js.map → vidply.chunk-AXXU22HR.js.map} +2 -2
- package/dist/dev/vidply.esm.js +83 -50
- package/dist/dev/vidply.esm.js.map +2 -2
- package/dist/legacy/vidply.js +170 -58
- package/dist/legacy/vidply.js.map +3 -3
- package/dist/legacy/vidply.min.js +1 -1
- package/dist/legacy/vidply.min.meta.json +18 -18
- package/dist/prod/vidply.SoundCloudRenderer-D2FNOEG6.min.js +6 -0
- package/dist/prod/{vidply.TranscriptManager-GPAOXEK4.min.js → vidply.TranscriptManager-VXCTCJ7X.min.js} +1 -1
- package/dist/prod/vidply.VimeoRenderer-QELFZVDU.min.js +6 -0
- package/dist/prod/vidply.YouTubeRenderer-ZL6YUHTF.min.js +6 -0
- package/dist/prod/{vidply.chunk-OM7DNW5P.min.js → vidply.chunk-Z6BHMOGK.min.js} +1 -1
- package/dist/prod/vidply.esm.min.js +3 -3
- package/dist/vidply.css +293 -108
- package/dist/vidply.esm.min.meta.json +33 -33
- package/dist/vidply.min.css +1 -1
- package/package.json +3 -3
- package/src/controls/ControlBar.js +3 -0
- package/src/controls/KeyboardManager.js +19 -0
- package/src/core/Player.js +75 -64
- package/src/core/SignLanguageManager.js +18 -4
- package/src/index.js +3 -1
- package/src/renderers/SoundCloudRenderer.js +0 -2
- package/src/renderers/VimeoRenderer.js +0 -1
- package/src/renderers/YouTubeRenderer.js +0 -1
- package/src/styles/vidply.css +293 -108
- package/src/utils/DraggableResizable.js +123 -12
- package/dist/dev/vidply.SoundCloudRenderer-RIA3QKP3.js.map +0 -7
- package/dist/dev/vidply.VimeoRenderer-DY2FG7LZ.js.map +0 -7
- package/dist/dev/vidply.YouTubeRenderer-EVXXE34A.js.map +0 -7
- package/dist/prod/vidply.SoundCloudRenderer-BFV5SSIU.min.js +0 -6
- package/dist/prod/vidply.VimeoRenderer-UQWHQ4LC.min.js +0 -6
- package/dist/prod/vidply.YouTubeRenderer-K7A57ICA.min.js +0 -6
- /package/dist/dev/{vidply.TranscriptManager-T3BVTZHZ.js.map → vidply.TranscriptManager-EIIN5YOF.js.map} +0 -0
|
@@ -145,6 +145,9 @@ export class ControlBar {
|
|
|
145
145
|
// Ensure menu doesn't go off top or bottom
|
|
146
146
|
if (menuRect.top < 10) {
|
|
147
147
|
menu.style.top = '10px';
|
|
148
|
+
// Important: clear bottom constraint, otherwise top+bottom will squeeze the menu height
|
|
149
|
+
// into a tiny strip on some mobile layouts.
|
|
150
|
+
menu.style.bottom = 'auto';
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
if (menuRect.bottom > viewportHeight - 10) {
|
|
@@ -46,6 +46,25 @@ export class KeyboardManager {
|
|
|
46
46
|
if (playlistButton) {
|
|
47
47
|
return; // Let the playlist handle keyboard events
|
|
48
48
|
}
|
|
49
|
+
|
|
50
|
+
// Don't steal arrow keys when the sign-language overlay is in keyboard drag/resize mode.
|
|
51
|
+
// DraggableResizable listens on the overlay itself, but we run in CAPTURE phase, so we must opt out here.
|
|
52
|
+
const signWrapper = activeElement.closest('.vidply-sign-language-wrapper');
|
|
53
|
+
if (signWrapper) {
|
|
54
|
+
const draggable = this.player.signLanguageManager?.draggable;
|
|
55
|
+
if (draggable?.keyboardDragMode || draggable?.keyboardResizeMode) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Same idea for the transcript floating window (it also uses DraggableResizable).
|
|
61
|
+
const transcriptWindow = activeElement.closest('.vidply-transcript-window');
|
|
62
|
+
if (transcriptWindow) {
|
|
63
|
+
const draggable = this.player.transcriptManager?.draggableResizable;
|
|
64
|
+
if (draggable?.keyboardDragMode || draggable?.keyboardResizeMode) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
49
68
|
}
|
|
50
69
|
|
|
51
70
|
const key = e.key;
|
package/src/core/Player.js
CHANGED
|
@@ -588,6 +588,7 @@ export class Player extends EventEmitter {
|
|
|
588
588
|
this.container.classList.add(`${this.options.classPrefix}-responsive`);
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
|
|
591
592
|
// Create video wrapper (for proper positioning of controls)
|
|
592
593
|
this.videoWrapper = DOMUtils.createElement('div', {
|
|
593
594
|
className: `${this.options.classPrefix}-video-wrapper`
|
|
@@ -639,35 +640,10 @@ export class Player extends EventEmitter {
|
|
|
639
640
|
: this.options.height;
|
|
640
641
|
}
|
|
641
642
|
|
|
642
|
-
// If no explicit height is set, ensure video players still have a stable layout box
|
|
643
|
-
// even before any media is loaded (important for deferLoad + playlists).
|
|
644
|
-
// We use the element's width/height attributes (e.g. from TYPO3) as aspect ratio.
|
|
645
|
-
if (this.element.tagName === 'VIDEO' && !this.options.height) {
|
|
646
|
-
const wAttr = parseInt(this.element.getAttribute('width') || '', 10);
|
|
647
|
-
const hAttr = parseInt(this.element.getAttribute('height') || '', 10);
|
|
648
|
-
if (Number.isFinite(wAttr) && Number.isFinite(hAttr) && wAttr > 0 && hAttr > 0) {
|
|
649
|
-
// Only set if not already defined by CSS/inline style
|
|
650
|
-
if (!this.container.style.aspectRatio) {
|
|
651
|
-
this.container.style.aspectRatio = `${wAttr} / ${hAttr}`;
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// The actual visual box is the videoWrapper (the video element is 100% height).
|
|
655
|
-
// Give the wrapper the same aspect ratio so posters render correctly before metadata is loaded.
|
|
656
|
-
if (this.videoWrapper && !this.videoWrapper.style.aspectRatio) {
|
|
657
|
-
this.videoWrapper.style.aspectRatio = `${wAttr} / ${hAttr}`;
|
|
658
|
-
// Override default CSS height:100% (which depends on parent having a height)
|
|
659
|
-
this.videoWrapper.style.height = 'auto';
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
|
|
664
643
|
// Set poster (convert relative paths to absolute URLs)
|
|
665
644
|
if (this.options.poster && this.element.tagName === 'VIDEO') {
|
|
666
645
|
const resolvedPoster = this.resolvePosterPath(this.options.poster);
|
|
667
646
|
this.element.poster = resolvedPoster;
|
|
668
|
-
// If we intentionally have no media loaded yet (e.g. deferLoad/playlist),
|
|
669
|
-
// use poster aspect ratio to size the wrapper so the poster isn't stretched.
|
|
670
|
-
this.applyPosterAspectRatio(resolvedPoster);
|
|
671
647
|
}
|
|
672
648
|
|
|
673
649
|
// Create centered play button overlay (only for video)
|
|
@@ -711,45 +687,6 @@ export class Player extends EventEmitter {
|
|
|
711
687
|
}, { once: true });
|
|
712
688
|
}
|
|
713
689
|
|
|
714
|
-
/**
|
|
715
|
-
* Apply aspect ratio to the video wrapper based on the poster's intrinsic size.
|
|
716
|
-
* This helps render correct poster sizing before media metadata is available.
|
|
717
|
-
*/
|
|
718
|
-
applyPosterAspectRatio(posterUrl) {
|
|
719
|
-
try {
|
|
720
|
-
if (!posterUrl) return;
|
|
721
|
-
if (this.element.tagName !== 'VIDEO') return;
|
|
722
|
-
if (!this.videoWrapper) return;
|
|
723
|
-
|
|
724
|
-
// If user explicitly configured dimensions, don't override.
|
|
725
|
-
if (this.options.width || this.options.height) return;
|
|
726
|
-
|
|
727
|
-
// Avoid repeated work
|
|
728
|
-
if (this._posterAspectAppliedFor === posterUrl) return;
|
|
729
|
-
this._posterAspectAppliedFor = posterUrl;
|
|
730
|
-
|
|
731
|
-
const img = new Image();
|
|
732
|
-
img.decoding = 'async';
|
|
733
|
-
img.onload = () => {
|
|
734
|
-
const w = img.naturalWidth;
|
|
735
|
-
const h = img.naturalHeight;
|
|
736
|
-
if (!w || !h) return;
|
|
737
|
-
|
|
738
|
-
// Apply to wrapper (the actual layout box)
|
|
739
|
-
this.videoWrapper.style.aspectRatio = `${w} / ${h}`;
|
|
740
|
-
this.videoWrapper.style.height = 'auto';
|
|
741
|
-
|
|
742
|
-
// Also apply to container if not explicitly set
|
|
743
|
-
if (this.container && !this.container.style.aspectRatio) {
|
|
744
|
-
this.container.style.aspectRatio = `${w} / ${h}`;
|
|
745
|
-
}
|
|
746
|
-
};
|
|
747
|
-
img.src = posterUrl;
|
|
748
|
-
} catch (e) {
|
|
749
|
-
// ignore
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
|
|
753
690
|
createPlayButtonOverlay() {
|
|
754
691
|
// Create complete SVG play button from Icons.js
|
|
755
692
|
this.playButtonOverlay = createPlayOverlay();
|
|
@@ -1675,6 +1612,9 @@ export class Player extends EventEmitter {
|
|
|
1675
1612
|
this.state.fullscreen = true;
|
|
1676
1613
|
this.container.classList.add(`${this.options.classPrefix}-fullscreen`);
|
|
1677
1614
|
|
|
1615
|
+
// Add body class for CSS targeting (fallback for browsers without :has() support)
|
|
1616
|
+
document.body.classList.add('vidply-fullscreen-active');
|
|
1617
|
+
|
|
1678
1618
|
// Store current scroll position for restoration later
|
|
1679
1619
|
this._originalScrollX = window.scrollX || window.pageXOffset;
|
|
1680
1620
|
this._originalScrollY = window.scrollY || window.pageYOffset;
|
|
@@ -1685,11 +1625,15 @@ export class Player extends EventEmitter {
|
|
|
1685
1625
|
this._originalBodyWidth = document.body.style.width;
|
|
1686
1626
|
this._originalBodyHeight = document.body.style.height;
|
|
1687
1627
|
this._originalHtmlOverflow = document.documentElement.style.overflow;
|
|
1628
|
+
this._originalBodyBackground = document.body.style.background;
|
|
1629
|
+
this._originalHtmlBackground = document.documentElement.style.background;
|
|
1688
1630
|
|
|
1689
1631
|
document.body.style.overflow = 'hidden';
|
|
1690
1632
|
document.body.style.width = '100%';
|
|
1691
1633
|
document.body.style.height = '100%';
|
|
1634
|
+
document.body.style.background = '#000';
|
|
1692
1635
|
document.documentElement.style.overflow = 'hidden';
|
|
1636
|
+
document.documentElement.style.background = '#000';
|
|
1693
1637
|
|
|
1694
1638
|
// On iOS, also lock the viewport and scroll to top
|
|
1695
1639
|
this._originalViewport = document.querySelector('meta[name="viewport"]')?.getAttribute('content');
|
|
@@ -1701,11 +1645,62 @@ export class Player extends EventEmitter {
|
|
|
1701
1645
|
// Scroll to top on iOS to prevent positioning issues
|
|
1702
1646
|
window.scrollTo(0, 0);
|
|
1703
1647
|
|
|
1648
|
+
// Make all other page content inert to prevent keyboard focus escaping to background
|
|
1649
|
+
this._makeBackgroundInert();
|
|
1650
|
+
|
|
1704
1651
|
this.emit('fullscreenchange', true);
|
|
1705
1652
|
this.emit('enterfullscreen');
|
|
1706
1653
|
}
|
|
1654
|
+
|
|
1655
|
+
/**
|
|
1656
|
+
* Makes all page content except the fullscreen player inert (non-focusable)
|
|
1657
|
+
* This prevents keyboard navigation from focusing on hidden background elements
|
|
1658
|
+
*/
|
|
1659
|
+
_makeBackgroundInert() {
|
|
1660
|
+
this._inertElements = [];
|
|
1661
|
+
|
|
1662
|
+
// Find all siblings and ancestors' siblings that should be made inert
|
|
1663
|
+
let current = this.container;
|
|
1664
|
+
while (current && current !== document.body && current !== document.documentElement) {
|
|
1665
|
+
const parent = current.parentElement;
|
|
1666
|
+
if (parent) {
|
|
1667
|
+
// Make all siblings inert
|
|
1668
|
+
Array.from(parent.children).forEach(sibling => {
|
|
1669
|
+
if (sibling !== current &&
|
|
1670
|
+
sibling.nodeType === Node.ELEMENT_NODE &&
|
|
1671
|
+
!sibling.hasAttribute('inert') &&
|
|
1672
|
+
sibling.tagName !== 'SCRIPT' &&
|
|
1673
|
+
sibling.tagName !== 'STYLE' &&
|
|
1674
|
+
sibling.tagName !== 'LINK' &&
|
|
1675
|
+
sibling.tagName !== 'META') {
|
|
1676
|
+
sibling.setAttribute('inert', '');
|
|
1677
|
+
this._inertElements.push(sibling);
|
|
1678
|
+
}
|
|
1679
|
+
});
|
|
1680
|
+
}
|
|
1681
|
+
current = parent;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
/**
|
|
1686
|
+
* Restores interactivity to elements that were made inert during fullscreen
|
|
1687
|
+
*/
|
|
1688
|
+
_restoreBackgroundInteractivity() {
|
|
1689
|
+
if (this._inertElements) {
|
|
1690
|
+
this._inertElements.forEach(el => {
|
|
1691
|
+
el.removeAttribute('inert');
|
|
1692
|
+
});
|
|
1693
|
+
this._inertElements = [];
|
|
1694
|
+
}
|
|
1695
|
+
}
|
|
1707
1696
|
|
|
1708
1697
|
_disablePseudoFullscreen() {
|
|
1698
|
+
// Remove body class for CSS targeting
|
|
1699
|
+
document.body.classList.remove('vidply-fullscreen-active');
|
|
1700
|
+
|
|
1701
|
+
// Restore interactivity to background elements
|
|
1702
|
+
this._restoreBackgroundInteractivity();
|
|
1703
|
+
|
|
1709
1704
|
// Restore body scrolling
|
|
1710
1705
|
if (this._originalBodyOverflow !== undefined) {
|
|
1711
1706
|
document.body.style.overflow = this._originalBodyOverflow;
|
|
@@ -1727,6 +1722,14 @@ export class Player extends EventEmitter {
|
|
|
1727
1722
|
document.documentElement.style.overflow = this._originalHtmlOverflow;
|
|
1728
1723
|
delete this._originalHtmlOverflow;
|
|
1729
1724
|
}
|
|
1725
|
+
if (this._originalBodyBackground !== undefined) {
|
|
1726
|
+
document.body.style.background = this._originalBodyBackground;
|
|
1727
|
+
delete this._originalBodyBackground;
|
|
1728
|
+
}
|
|
1729
|
+
if (this._originalHtmlBackground !== undefined) {
|
|
1730
|
+
document.documentElement.style.background = this._originalHtmlBackground;
|
|
1731
|
+
delete this._originalHtmlBackground;
|
|
1732
|
+
}
|
|
1730
1733
|
|
|
1731
1734
|
// Restore viewport settings
|
|
1732
1735
|
if (this._originalViewport !== undefined) {
|
|
@@ -4522,8 +4525,16 @@ export class Player extends EventEmitter {
|
|
|
4522
4525
|
|
|
4523
4526
|
if (isFullscreen) {
|
|
4524
4527
|
this.container.classList.add(`${this.options.classPrefix}-fullscreen`);
|
|
4528
|
+
// Add body class for CSS targeting (fallback for browsers without :has() support)
|
|
4529
|
+
document.body.classList.add('vidply-fullscreen-active');
|
|
4530
|
+
// Make background content inert to prevent keyboard focus escaping
|
|
4531
|
+
this._makeBackgroundInert();
|
|
4525
4532
|
} else {
|
|
4526
4533
|
this.container.classList.remove(`${this.options.classPrefix}-fullscreen`);
|
|
4534
|
+
// Remove body class for CSS targeting
|
|
4535
|
+
document.body.classList.remove('vidply-fullscreen-active');
|
|
4536
|
+
// Restore background interactivity
|
|
4537
|
+
this._restoreBackgroundInteractivity();
|
|
4527
4538
|
// Clean up pseudo-fullscreen state when exiting
|
|
4528
4539
|
this._disablePseudoFullscreen();
|
|
4529
4540
|
}
|
|
@@ -459,8 +459,11 @@ export class SignLanguageManager {
|
|
|
459
459
|
_setupInteraction() {
|
|
460
460
|
const isMobile = window.innerWidth < 768;
|
|
461
461
|
const isFullscreen = this.player.state.fullscreen;
|
|
462
|
-
|
|
463
|
-
|
|
462
|
+
|
|
463
|
+
// Historically, drag/resize was disabled on mobile unless fullscreen to avoid scroll conflicts.
|
|
464
|
+
// Now that we support touch/pointer dragging with proper `touch-action` handling, enable it
|
|
465
|
+
// by default on iOS/Android as well. Allow opting out via option.
|
|
466
|
+
if (isMobile && !isFullscreen && this.player?.options?.signLanguageDragOnMobile === false) {
|
|
464
467
|
if (this.draggable) {
|
|
465
468
|
this.draggable.destroy();
|
|
466
469
|
this.draggable = null;
|
|
@@ -473,7 +476,9 @@ export class SignLanguageManager {
|
|
|
473
476
|
const classPrefix = this.player.options.classPrefix;
|
|
474
477
|
|
|
475
478
|
this.draggable = new DraggableResizable(this.wrapper, {
|
|
476
|
-
|
|
479
|
+
// Allow dragging from anywhere on the sign-language window (better for touch).
|
|
480
|
+
// We still block dragging when interacting with controls via `onDragStart` below.
|
|
481
|
+
dragHandle: this.wrapper,
|
|
477
482
|
resizeHandles: this.resizeHandles,
|
|
478
483
|
constrainToViewport: true,
|
|
479
484
|
maintainAspectRatio: true,
|
|
@@ -761,9 +766,18 @@ export class SignLanguageManager {
|
|
|
761
766
|
hasTextClass: true,
|
|
762
767
|
onClick: () => {
|
|
763
768
|
this.toggleKeyboardDragMode();
|
|
764
|
-
|
|
769
|
+
// Keep focus off the settings button so arrow keys go to the draggable overlay.
|
|
770
|
+
this.hideSettingsMenu({ focusButton: false });
|
|
771
|
+
// If we just enabled keyboard drag mode, focus the overlay.
|
|
772
|
+
if (this.draggable?.keyboardDragMode) {
|
|
773
|
+
setTimeout(() => {
|
|
774
|
+
this.wrapper?.focus?.({ preventScroll: true });
|
|
775
|
+
}, 20);
|
|
776
|
+
}
|
|
765
777
|
}
|
|
766
778
|
});
|
|
779
|
+
// Allow CSS to hide this option on touch/mobile where dragging is always enabled
|
|
780
|
+
dragOption.setAttribute('data-setting', 'keyboard-drag');
|
|
767
781
|
dragOption.setAttribute('role', 'switch');
|
|
768
782
|
dragOption.setAttribute('aria-checked', 'false');
|
|
769
783
|
this._removeTooltipFromMenuItem(dragOption);
|
package/src/index.js
CHANGED
|
@@ -57,7 +57,9 @@ function parseDataAttributes(dataset) {
|
|
|
57
57
|
'keyboard': 'keyboard',
|
|
58
58
|
'responsive': 'responsive',
|
|
59
59
|
'pipButton': 'pipButton',
|
|
60
|
-
'fullscreenButton': 'fullscreenButton'
|
|
60
|
+
'fullscreenButton': 'fullscreenButton',
|
|
61
|
+
|
|
62
|
+
// Layout
|
|
61
63
|
};
|
|
62
64
|
|
|
63
65
|
// Parse each data attribute
|
|
@@ -125,10 +125,8 @@ export class SoundCloudRenderer {
|
|
|
125
125
|
// Use different aspect ratio for playlists vs single tracks
|
|
126
126
|
// Playlists need more height to show the track list
|
|
127
127
|
if (this.isPlaylist()) {
|
|
128
|
-
this.iframe.style.aspectRatio = '16 / 9'; // More height for playlist
|
|
129
128
|
this.iframe.classList.add('vidply-soundcloud-iframe', 'vidply-soundcloud-playlist');
|
|
130
129
|
} else {
|
|
131
|
-
this.iframe.style.aspectRatio = '16 / 3'; // Banner-like for single track
|
|
132
130
|
this.iframe.classList.add('vidply-soundcloud-iframe');
|
|
133
131
|
}
|
|
134
132
|
this.iframe.style.maxHeight = '100%';
|
|
@@ -70,7 +70,6 @@ export class VimeoRenderer {
|
|
|
70
70
|
this.iframe = document.createElement('div');
|
|
71
71
|
this.iframe.id = `vimeo-player-${Math.random().toString(36).substr(2, 9)}`;
|
|
72
72
|
this.iframe.style.width = '100%';
|
|
73
|
-
this.iframe.style.aspectRatio = '16 / 9';
|
|
74
73
|
this.iframe.style.maxHeight = '100%';
|
|
75
74
|
|
|
76
75
|
this.player.element.parentNode.insertBefore(this.iframe, this.player.element);
|
|
@@ -86,7 +86,6 @@ export class YouTubeRenderer {
|
|
|
86
86
|
this.iframe = document.createElement('div');
|
|
87
87
|
this.iframe.id = `youtube-player-${Math.random().toString(36).substr(2, 9)}`;
|
|
88
88
|
this.iframe.style.width = '100%';
|
|
89
|
-
this.iframe.style.aspectRatio = '16 / 9';
|
|
90
89
|
this.iframe.style.maxHeight = '100%';
|
|
91
90
|
|
|
92
91
|
this.player.element.parentNode.insertBefore(this.iframe, this.player.element);
|