mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-05 23:18:53 -05:00
Compare commits
5 Commits
567bb18e91
...
5625239c9d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5625239c9d | ||
|
|
f90e03740e | ||
|
|
c33b4a6a17 | ||
|
|
41b0bdcb50 | ||
|
|
61bfa67e42 |
@ -213,7 +213,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
// Only trigger if it's a tap (not a swipe)
|
// Only trigger if it's a tap (not a swipe)
|
||||||
if (distance < 50) {
|
if (distance < 50) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
touchHandled = true;
|
touchHandled = true;
|
||||||
this.toggleSettings(e);
|
this.toggleSettings(e);
|
||||||
}
|
}
|
||||||
@ -227,7 +227,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
// Only handle click if touch wasn't already handled
|
// Only handle click if touch wasn't already handled
|
||||||
if (!touchHandled) {
|
if (!touchHandled) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
this.toggleSettings(e);
|
this.toggleSettings(e);
|
||||||
}
|
}
|
||||||
touchHandled = false; // Reset for next interaction
|
touchHandled = false; // Reset for next interaction
|
||||||
@ -347,7 +347,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.settingsOverlay.addEventListener(
|
this.settingsOverlay.addEventListener(
|
||||||
'touchstart',
|
'touchstart',
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
},
|
},
|
||||||
{ passive: true }
|
{ passive: true }
|
||||||
);
|
);
|
||||||
@ -562,16 +562,6 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup method
|
|
||||||
dispose() {
|
|
||||||
this.stopSubtitleSync();
|
|
||||||
// Remove event listeners
|
|
||||||
document.removeEventListener('click', this.handleClickOutside);
|
|
||||||
// Remove text track change listener
|
|
||||||
if (this.player()) {
|
|
||||||
this.player().off('texttrackchange');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getAvailableQualities() {
|
getAvailableQualities() {
|
||||||
// Priority: provided options -> MEDIA_DATA JSON -> player sources -> default
|
// Priority: provided options -> MEDIA_DATA JSON -> player sources -> default
|
||||||
@ -656,7 +646,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
const closeButton = this.settingsOverlay.querySelector('.settings-close-btn');
|
const closeButton = this.settingsOverlay.querySelector('.settings-close-btn');
|
||||||
if (closeButton) {
|
if (closeButton) {
|
||||||
const closeFunction = (e) => {
|
const closeFunction = (e) => {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
this.settingsOverlay.classList.remove('show');
|
this.settingsOverlay.classList.remove('show');
|
||||||
this.settingsOverlay.style.display = 'none';
|
this.settingsOverlay.style.display = 'none';
|
||||||
this.speedSubmenu.style.display = 'none';
|
this.speedSubmenu.style.display = 'none';
|
||||||
@ -681,7 +671,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
// Settings item clicks
|
// Settings item clicks
|
||||||
this.settingsOverlay.addEventListener('click', (e) => {
|
this.settingsOverlay.addEventListener('click', (e) => {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
|
|
||||||
if (e.target.closest('[data-setting="playback-speed"]')) {
|
if (e.target.closest('[data-setting="playback-speed"]')) {
|
||||||
this.speedSubmenu.style.display = 'flex';
|
this.speedSubmenu.style.display = 'flex';
|
||||||
@ -722,7 +712,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.settingsOverlay.addEventListener(
|
this.settingsOverlay.addEventListener(
|
||||||
'touchend',
|
'touchend',
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
if (this.isTouchScrolling) {
|
if (this.isTouchScrolling) {
|
||||||
this.isTouchScrolling = false;
|
this.isTouchScrolling = false;
|
||||||
return;
|
return;
|
||||||
@ -760,7 +750,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
'touchend',
|
'touchend',
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
this.speedSubmenu.style.display = 'none';
|
this.speedSubmenu.style.display = 'none';
|
||||||
},
|
},
|
||||||
{ passive: false }
|
{ passive: false }
|
||||||
@ -775,7 +765,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
'touchend',
|
'touchend',
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
this.qualitySubmenu.style.display = 'none';
|
this.qualitySubmenu.style.display = 'none';
|
||||||
},
|
},
|
||||||
{ passive: false }
|
{ passive: false }
|
||||||
@ -790,7 +780,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
'touchend',
|
'touchend',
|
||||||
(e) => {
|
(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
this.subtitlesSubmenu.style.display = 'none';
|
this.subtitlesSubmenu.style.display = 'none';
|
||||||
},
|
},
|
||||||
{ passive: false }
|
{ passive: false }
|
||||||
@ -826,7 +816,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.speedSubmenu.addEventListener(
|
this.speedSubmenu.addEventListener(
|
||||||
'touchend',
|
'touchend',
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
if (this.isTouchScrolling) {
|
if (this.isTouchScrolling) {
|
||||||
this.isTouchScrolling = false;
|
this.isTouchScrolling = false;
|
||||||
return;
|
return;
|
||||||
@ -870,7 +860,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.qualitySubmenu.addEventListener(
|
this.qualitySubmenu.addEventListener(
|
||||||
'touchend',
|
'touchend',
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
if (this.isTouchScrolling) {
|
if (this.isTouchScrolling) {
|
||||||
this.isTouchScrolling = false;
|
this.isTouchScrolling = false;
|
||||||
return;
|
return;
|
||||||
@ -915,7 +905,7 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.subtitlesSubmenu.addEventListener(
|
this.subtitlesSubmenu.addEventListener(
|
||||||
'touchend',
|
'touchend',
|
||||||
(e) => {
|
(e) => {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
if (this.isTouchScrolling) {
|
if (this.isTouchScrolling) {
|
||||||
this.isTouchScrolling = false;
|
this.isTouchScrolling = false;
|
||||||
return;
|
return;
|
||||||
@ -953,13 +943,16 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toggleSettings(e) {
|
toggleSettings(e) {
|
||||||
e.stopPropagation();
|
// e.stopPropagation();
|
||||||
const isVisible = this.settingsOverlay.classList.contains('show');
|
const isVisible = this.settingsOverlay.classList.contains('show');
|
||||||
|
|
||||||
if (isVisible) {
|
if (isVisible) {
|
||||||
this.settingsOverlay.classList.remove('show');
|
this.settingsOverlay.classList.remove('show');
|
||||||
this.settingsOverlay.style.display = 'none';
|
this.settingsOverlay.style.display = 'none';
|
||||||
|
|
||||||
|
// Stop keeping controls visible
|
||||||
|
this.stopKeepingControlsVisible();
|
||||||
|
|
||||||
// Restore body scroll on mobile when closing
|
// Restore body scroll on mobile when closing
|
||||||
if (this.isMobile) {
|
if (this.isMobile) {
|
||||||
document.body.style.overflow = '';
|
document.body.style.overflow = '';
|
||||||
@ -970,6 +963,9 @@ class CustomSettingsMenu extends Component {
|
|||||||
this.settingsOverlay.classList.add('show');
|
this.settingsOverlay.classList.add('show');
|
||||||
this.settingsOverlay.style.display = 'block';
|
this.settingsOverlay.style.display = 'block';
|
||||||
|
|
||||||
|
// Keep controls visible while settings menu is open
|
||||||
|
this.keepControlsVisible();
|
||||||
|
|
||||||
// Add haptic feedback on mobile when opening
|
// Add haptic feedback on mobile when opening
|
||||||
if (this.isMobile && navigator.vibrate) {
|
if (this.isMobile && navigator.vibrate) {
|
||||||
navigator.vibrate(30);
|
navigator.vibrate(30);
|
||||||
@ -1029,11 +1025,40 @@ class CustomSettingsMenu extends Component {
|
|||||||
return this.settingsOverlay && this.settingsOverlay.classList.contains('show');
|
return this.settingsOverlay && this.settingsOverlay.classList.contains('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Keep controls visible while settings menu is open
|
||||||
|
keepControlsVisible() {
|
||||||
|
const player = this.player();
|
||||||
|
if (!player) return;
|
||||||
|
|
||||||
|
// Keep player in active state
|
||||||
|
player.userActive(true);
|
||||||
|
|
||||||
|
// Set up interval to periodically keep player active
|
||||||
|
this.controlsVisibilityInterval = setInterval(() => {
|
||||||
|
if (this.isMenuOpen()) {
|
||||||
|
player.userActive(true);
|
||||||
|
} else {
|
||||||
|
this.stopKeepingControlsVisible();
|
||||||
|
}
|
||||||
|
}, 1000); // Check every second
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop keeping controls visible
|
||||||
|
stopKeepingControlsVisible() {
|
||||||
|
if (this.controlsVisibilityInterval) {
|
||||||
|
clearInterval(this.controlsVisibilityInterval);
|
||||||
|
this.controlsVisibilityInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Close the settings menu
|
// Close the settings menu
|
||||||
closeMenu() {
|
closeMenu() {
|
||||||
if (this.settingsOverlay) {
|
if (this.settingsOverlay) {
|
||||||
this.settingsOverlay.classList.remove('show');
|
this.settingsOverlay.classList.remove('show');
|
||||||
this.settingsOverlay.style.display = 'none';
|
this.settingsOverlay.style.display = 'none';
|
||||||
|
|
||||||
|
// Stop keeping controls visible
|
||||||
|
this.stopKeepingControlsVisible();
|
||||||
this.speedSubmenu.style.display = 'none';
|
this.speedSubmenu.style.display = 'none';
|
||||||
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
||||||
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = 'none';
|
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = 'none';
|
||||||
@ -1447,6 +1472,17 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dispose() {
|
dispose() {
|
||||||
|
// Stop subtitle sync and clear interval
|
||||||
|
this.stopSubtitleSync();
|
||||||
|
|
||||||
|
// Stop keeping controls visible
|
||||||
|
this.stopKeepingControlsVisible();
|
||||||
|
|
||||||
|
// Remove text track change listener
|
||||||
|
if (this.player()) {
|
||||||
|
this.player().off('texttrackchange');
|
||||||
|
}
|
||||||
|
|
||||||
// Remove event listeners
|
// Remove event listeners
|
||||||
document.removeEventListener('click', this.handleClickOutside);
|
document.removeEventListener('click', this.handleClickOutside);
|
||||||
|
|
||||||
|
|||||||
@ -31,7 +31,60 @@ button {
|
|||||||
visibility: hidden !important;
|
visibility: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Adjust subtitle position when controls are visible */
|
/* iOS Native Text Tracks - Position captions above control bar */
|
||||||
|
/* Using ::cue which is the only way to style native tracks on iOS */
|
||||||
|
video::cue {
|
||||||
|
line: -4; /* Move captions up by 4 lines from bottom */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mobile-specific caption font size increases */
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
/* iOS native text tracks */
|
||||||
|
video::cue {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Video.js text tracks for non-iOS */
|
||||||
|
.video-js .vjs-text-track-display {
|
||||||
|
font-size: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-text-track-cue {
|
||||||
|
font-size: 1em !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Extra small screens - even larger captions */
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
video::cue {
|
||||||
|
font-size: 1.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-text-track-display {
|
||||||
|
font-size: 1.2em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-text-track-cue {
|
||||||
|
font-size: 1.2em !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tablet size - moderate increase */
|
||||||
|
@media (min-width: 768px) and (max-width: 1024px) {
|
||||||
|
video::cue {
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-text-track-display {
|
||||||
|
font-size: 1em !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-js .vjs-text-track-cue {
|
||||||
|
font-size: 1em !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Adjust subtitle position when controls are visible (for non-native Video.js tracks) */
|
||||||
/* When controls are VISIBLE (user is active), add extra bottom margin */
|
/* When controls are VISIBLE (user is active), add extra bottom margin */
|
||||||
.video-js:not(.vjs-user-inactive) .vjs-text-track-display {
|
.video-js:not(.vjs-user-inactive) .vjs-text-track-display {
|
||||||
margin-bottom: 2em; /* Adjust this value to move subtitles higher when controls are visible */
|
margin-bottom: 2em; /* Adjust this value to move subtitles higher when controls are visible */
|
||||||
|
|||||||
@ -185,6 +185,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
|
return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Utility function to detect iOS devices
|
||||||
|
const isIOS = useMemo(() => {
|
||||||
|
return (
|
||||||
|
/iPad|iPhone|iPod/.test(navigator.userAgent) ||
|
||||||
|
(navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1)
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
// Environment-based development mode configuration
|
// Environment-based development mode configuration
|
||||||
const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app');
|
const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app');
|
||||||
// Safely access window.MEDIA_DATA with fallback using useMemo
|
// Safely access window.MEDIA_DATA with fallback using useMemo
|
||||||
@ -1592,14 +1600,16 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
|
|
||||||
// Default sample video
|
// Default sample video
|
||||||
return [
|
return [
|
||||||
/* {
|
|
||||||
src: '/videos/sample-video-white.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
}, */
|
|
||||||
{
|
{
|
||||||
|
src: '/videos/sample-video.mp4',
|
||||||
|
// src: '/videos/sample-video-white.mp4',
|
||||||
|
//src: '/videos/sample-video.big.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
},
|
||||||
|
/* {
|
||||||
src: '/videos/sample-video.mp3',
|
src: '/videos/sample-video.mp3',
|
||||||
type: 'audio/mpeg',
|
type: 'audio/mpeg',
|
||||||
},
|
}, */
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1921,8 +1931,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
|
|
||||||
// Milliseconds of inactivity before user considered inactive (0 = never)
|
// Milliseconds of inactivity before user considered inactive (0 = never)
|
||||||
// For embed players, use longer timeout to keep controls visible
|
// For embed players, use longer timeout to keep controls visible
|
||||||
//inactivityTimeout: isEmbedPlayer ? 5000 : 2000,
|
inactivityTimeout: isEmbedPlayer || isTouchDevice ? 5000 : 2000,
|
||||||
inactivityTimeout: 2000,
|
|
||||||
|
|
||||||
// Language code for player (e.g., 'en', 'es', 'fr')
|
// Language code for player (e.g., 'en', 'es', 'fr')
|
||||||
language: 'en',
|
language: 'en',
|
||||||
@ -2088,9 +2097,9 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
// Use native audio tracks instead of emulated - disabled for consistency
|
// Use native audio tracks instead of emulated - disabled for consistency
|
||||||
nativeAudioTracks: false,
|
nativeAudioTracks: false,
|
||||||
|
|
||||||
// Use Video.js text tracks for full positioning control on all devices
|
// Use native text tracks on iOS for fullscreen caption support
|
||||||
// Native tracks don't allow CSS positioning control and cause duplicates
|
// On other devices, use Video.js text tracks for full CSS positioning control
|
||||||
nativeTextTracks: true,
|
nativeTextTracks: isIOS,
|
||||||
|
|
||||||
// Use native video tracks instead of emulated - disabled for consistency
|
// Use native video tracks instead of emulated - disabled for consistency
|
||||||
nativeVideoTracks: false,
|
nativeVideoTracks: false,
|
||||||
@ -2558,6 +2567,70 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
playerRef.current.one('canplay', () =>
|
playerRef.current.one('canplay', () =>
|
||||||
userPreferences.current.applySubtitlePreference(playerRef.current)
|
userPreferences.current.applySubtitlePreference(playerRef.current)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// iOS-specific: Adjust native text track cues to position them above control bar
|
||||||
|
if (isIOS && hasSubtitles) {
|
||||||
|
const adjustIOSCues = (linePosition) => {
|
||||||
|
// If no line position specified, determine based on user activity
|
||||||
|
if (linePosition === undefined) {
|
||||||
|
const isUserInactive = playerRef.current.hasClass('vjs-user-inactive');
|
||||||
|
linePosition = isUserInactive ? -2 : -4;
|
||||||
|
}
|
||||||
|
|
||||||
|
const textTracks = playerRef.current.textTracks();
|
||||||
|
for (let i = 0; i < textTracks.length; i++) {
|
||||||
|
const track = textTracks[i];
|
||||||
|
if (track.kind === 'subtitles' || track.kind === 'captions') {
|
||||||
|
// Wait for cues to load
|
||||||
|
if (track.cues && track.cues.length > 0) {
|
||||||
|
for (let j = 0; j < track.cues.length; j++) {
|
||||||
|
const cue = track.cues[j];
|
||||||
|
// Set line position to move captions up
|
||||||
|
// Negative values count from bottom, positive from top
|
||||||
|
// -4 when controls visible, -2 when controls hidden
|
||||||
|
cue.line = linePosition;
|
||||||
|
cue.size = 90; // Make width 90% to ensure it fits
|
||||||
|
cue.position = 'auto'; // Center horizontally
|
||||||
|
cue.align = 'center'; // Center align text
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If cues aren't loaded yet, listen for the cuechange event
|
||||||
|
const onCueChange = () => {
|
||||||
|
if (track.cues && track.cues.length > 0) {
|
||||||
|
for (let j = 0; j < track.cues.length; j++) {
|
||||||
|
const cue = track.cues[j];
|
||||||
|
cue.line = linePosition;
|
||||||
|
cue.size = 90;
|
||||||
|
cue.position = 'auto';
|
||||||
|
cue.align = 'center';
|
||||||
|
}
|
||||||
|
track.removeEventListener('cuechange', onCueChange);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
track.addEventListener('cuechange', onCueChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Try to adjust immediately and also after a delay
|
||||||
|
setTimeout(() => adjustIOSCues(), 100);
|
||||||
|
setTimeout(() => adjustIOSCues(), 500);
|
||||||
|
setTimeout(() => adjustIOSCues(), 1000);
|
||||||
|
|
||||||
|
// Listen for user activity changes to adjust caption position dynamically
|
||||||
|
playerRef.current.on('userinactive', () => {
|
||||||
|
adjustIOSCues(-2); // Controls hidden - move captions closer to bottom
|
||||||
|
});
|
||||||
|
|
||||||
|
playerRef.current.on('useractive', () => {
|
||||||
|
adjustIOSCues(-4); // Controls visible - move captions higher
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also adjust when tracks change
|
||||||
|
playerRef.current.textTracks().addEventListener('addtrack', () => adjustIOSCues());
|
||||||
|
playerRef.current.textTracks().addEventListener('change', () => adjustIOSCues());
|
||||||
|
}
|
||||||
// END: Add subtitle tracks
|
// END: Add subtitle tracks
|
||||||
|
|
||||||
// BEGIN: Chapters Implementation
|
// BEGIN: Chapters Implementation
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user