mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-07 16:08:54 -05:00
feat: Create the component CustomRemainingTime
This commit is contained in:
parent
b314f7d628
commit
3d08f3b29f
@ -35,15 +35,6 @@
|
|||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Responsive App container */
|
|
||||||
.App {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 1400px;
|
|
||||||
margin: 0 auto;
|
|
||||||
padding: 0 20px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Ensure video.js responsive behavior */
|
/* Ensure video.js responsive behavior */
|
||||||
.video-js.vjs-fluid {
|
.video-js.vjs-fluid {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
@ -248,9 +239,8 @@
|
|||||||
|
|
||||||
.vjs-chapter-floating-tooltip {
|
.vjs-chapter-floating-tooltip {
|
||||||
font-family:
|
font-family:
|
||||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans",
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
"Droid Sans", "Helvetica Neue", sans-serif !important;
|
||||||
sans-serif !important;
|
|
||||||
line-height: 1.4 !important;
|
line-height: 1.4 !important;
|
||||||
animation: fadeIn 0.2s ease-in-out;
|
animation: fadeIn 0.2s ease-in-out;
|
||||||
}
|
}
|
||||||
@ -353,3 +343,106 @@
|
|||||||
font-size: 12px !important;
|
font-size: 12px !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Played portion, buffered portion, unplayed portion */
|
||||||
|
.vjs-play-progress {
|
||||||
|
background-color: #019932 !important;
|
||||||
|
}
|
||||||
|
.vjs-load-progress {
|
||||||
|
background: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
.vjs-progress-holder {
|
||||||
|
background: rgba(255, 255, 255, 0.5) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Move progress control out of control bar and position it above */
|
||||||
|
.video-js .vjs-progress-control {
|
||||||
|
position: absolute !important;
|
||||||
|
bottom: 46px !important;
|
||||||
|
left: 0 !important;
|
||||||
|
right: 0 !important;
|
||||||
|
width: 100% !important;
|
||||||
|
height: 0 !important;
|
||||||
|
z-index: 3 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 auto !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Hide the original progress control from the control bar */
|
||||||
|
.video-js .vjs-control-bar .vjs-progress-control {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optional: Ensure the progress control is visible */
|
||||||
|
.video-js .vjs-progress-control.vjs-control {
|
||||||
|
display: block !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make the seek bar thicker */
|
||||||
|
/* .video-js .vjs-play-progress,
|
||||||
|
.video-js .vjs-load-progress,
|
||||||
|
.video-js .vjs-progress-holder {
|
||||||
|
height: 4px !important;
|
||||||
|
} */
|
||||||
|
|
||||||
|
/* Remove the semi-transparent background from control bar */
|
||||||
|
.video-js .vjs-control-bar {
|
||||||
|
background: transparent !important;
|
||||||
|
background-color: transparent !important;
|
||||||
|
background-image: none !important;
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: row !important;
|
||||||
|
align-items: center !important;
|
||||||
|
justify-content: flex-start !important;
|
||||||
|
gap: 6px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Push specific buttons to the right */
|
||||||
|
.video-js .vjs-playback-rate,
|
||||||
|
.video-js .vjs-picture-in-picture-control,
|
||||||
|
.video-js .vjs-fullscreen-control {
|
||||||
|
margin-left: auto !important;
|
||||||
|
order: 999 !important;
|
||||||
|
}
|
||||||
|
.video-js .vjs-picture-in-picture-control {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
.video-js .vjs-fullscreen-control {
|
||||||
|
margin-left: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Make all control bar icons bigger */
|
||||||
|
.video-js .vjs-control-bar .vjs-icon-placeholder,
|
||||||
|
.video-js .vjs-control-bar .vjs-button .vjs-icon-placeholder,
|
||||||
|
.video-js .vjs-control-bar [class*="vjs-icon-"] {
|
||||||
|
font-size: 1.5em !important; /* 1.5x bigger */
|
||||||
|
transform: translateY(-28px) !important; /* Move icons up by 3px */
|
||||||
|
}
|
||||||
|
.video-js .vjs-control-bar svg {
|
||||||
|
width: 3em !important;
|
||||||
|
height: 3em !important;
|
||||||
|
transform: translateY(-16px) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/******** BEGIN: Custom Remaining Time Styles *********/
|
||||||
|
.vjs-control-bar .custom-remaining-time .vjs-remaining-time-display {
|
||||||
|
font-size: 14px !important; /* Increase font size */
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Ensure proper vertical alignment within control bar */
|
||||||
|
.vjs-control-bar .custom-remaining-time {
|
||||||
|
top: -5px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/********* END: Custom Remaining Time Styles *********/
|
||||||
|
|||||||
@ -0,0 +1,106 @@
|
|||||||
|
// components/controls/CustomRemainingTime.js
|
||||||
|
import videojs from 'video.js';
|
||||||
|
|
||||||
|
// Get the Component base class from Video.js
|
||||||
|
const Component = videojs.getComponent('Component');
|
||||||
|
|
||||||
|
class CustomRemainingTime extends Component {
|
||||||
|
constructor(player, options) {
|
||||||
|
super(player, options);
|
||||||
|
|
||||||
|
// Bind methods to ensure correct 'this' context
|
||||||
|
this.updateContent = this.updateContent.bind(this);
|
||||||
|
|
||||||
|
// Set up event listeners
|
||||||
|
this.on(player, 'timeupdate', this.updateContent);
|
||||||
|
this.on(player, 'durationchange', this.updateContent);
|
||||||
|
this.on(player, 'loadedmetadata', this.updateContent);
|
||||||
|
|
||||||
|
// Store custom options
|
||||||
|
this.options_ = {
|
||||||
|
displayNegative: false,
|
||||||
|
customPrefix: '',
|
||||||
|
customSuffix: '',
|
||||||
|
...options,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the component's DOM element
|
||||||
|
*/
|
||||||
|
createEl() {
|
||||||
|
const el = videojs.dom.createEl('div', {
|
||||||
|
className: 'vjs-remaining-time vjs-time-control vjs-control custom-remaining-time',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add ARIA accessibility
|
||||||
|
el.innerHTML = `
|
||||||
|
<span class="vjs-control-text" role="presentation">Time Display </span>
|
||||||
|
<span class="vjs-remaining-time-display" role="presentation">0:00 / 0:00</span>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the time display
|
||||||
|
*/
|
||||||
|
updateContent() {
|
||||||
|
const player = this.player();
|
||||||
|
const currentTime = player.currentTime();
|
||||||
|
const duration = player.duration();
|
||||||
|
|
||||||
|
const display = this.el().querySelector('.vjs-remaining-time-display');
|
||||||
|
|
||||||
|
if (display) {
|
||||||
|
const formattedCurrentTime = this.formatTime(isNaN(currentTime) ? 0 : currentTime);
|
||||||
|
const formattedDuration = this.formatTime(isNaN(duration) ? 0 : duration);
|
||||||
|
display.textContent = `${formattedCurrentTime} / ${formattedDuration}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format time with custom logic
|
||||||
|
*/
|
||||||
|
formatTime(seconds) {
|
||||||
|
const { customPrefix, customSuffix } = this.options_;
|
||||||
|
|
||||||
|
const hours = Math.floor(seconds / 3600);
|
||||||
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
|
||||||
|
let timeString;
|
||||||
|
if (hours > 0) {
|
||||||
|
timeString = `${hours}:${minutes.toString().padStart(2, '0')}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
} else {
|
||||||
|
timeString = `${minutes}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${customPrefix}${timeString}${customSuffix}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add click handler for additional functionality
|
||||||
|
*/
|
||||||
|
handleClick() {
|
||||||
|
// Example: Toggle between different time formats
|
||||||
|
console.log('Time display clicked');
|
||||||
|
// Could toggle between current/duration vs remaining time
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Component disposal cleanup
|
||||||
|
*/
|
||||||
|
dispose() {
|
||||||
|
// Clean up any additional resources if needed
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set component name for Video.js
|
||||||
|
CustomRemainingTime.prototype.controlText_ = 'Time Display';
|
||||||
|
|
||||||
|
// Register the component with Video.js
|
||||||
|
videojs.registerComponent('CustomRemainingTime', CustomRemainingTime);
|
||||||
|
|
||||||
|
export default CustomRemainingTime;
|
||||||
@ -6,6 +6,7 @@ import 'video.js/dist/video-js.css';
|
|||||||
import EndScreenOverlay from '../overlays/EndScreenOverlay';
|
import EndScreenOverlay from '../overlays/EndScreenOverlay';
|
||||||
import ChapterMarkers from '../markers/ChapterMarkers';
|
import ChapterMarkers from '../markers/ChapterMarkers';
|
||||||
import NextVideoButton from '../controls/NextVideoButton';
|
import NextVideoButton from '../controls/NextVideoButton';
|
||||||
|
import CustomRemainingTime from '../controls/CustomRemainingTime';
|
||||||
|
|
||||||
function VideoJSPlayer() {
|
function VideoJSPlayer() {
|
||||||
const videoRef = useRef(null);
|
const videoRef = useRef(null);
|
||||||
@ -47,9 +48,7 @@ function VideoJSPlayer() {
|
|||||||
sources: mediaData.data?.original_media_url
|
sources: mediaData.data?.original_media_url
|
||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
src:
|
src: mediaData.siteUrl + mediaData.data.original_media_url,
|
||||||
mediaData.siteUrl +
|
|
||||||
mediaData.data.original_media_url,
|
|
||||||
type: 'video/mp4',
|
type: 'video/mp4',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
@ -155,11 +154,7 @@ function VideoJSPlayer() {
|
|||||||
|
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
// Double-check that we still don't have a player and element exists
|
// Double-check that we still don't have a player and element exists
|
||||||
if (
|
if (!playerRef.current && videoRef.current && !videoRef.current.player) {
|
||||||
!playerRef.current &&
|
|
||||||
videoRef.current &&
|
|
||||||
!videoRef.current.player
|
|
||||||
) {
|
|
||||||
playerRef.current = videojs(videoRef.current, {
|
playerRef.current = videojs(videoRef.current, {
|
||||||
// ===== STANDARD <video> ELEMENT OPTIONS =====
|
// ===== STANDARD <video> ELEMENT OPTIONS =====
|
||||||
|
|
||||||
@ -318,9 +313,7 @@ function VideoJSPlayer() {
|
|||||||
|
|
||||||
// Function to override play/pause key (default: 'k' and Space)
|
// Function to override play/pause key (default: 'k' and Space)
|
||||||
playPauseKey: function (event) {
|
playPauseKey: function (event) {
|
||||||
return (
|
return event.which === 75 || event.which === 32; // 'k' or Space
|
||||||
event.which === 75 || event.which === 32
|
|
||||||
); // 'k' or Space
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -345,9 +338,10 @@ function VideoJSPlayer() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Remaining time display configuration
|
// Remaining time display configuration
|
||||||
remainingTimeDisplay: {
|
remainingTimeDisplay: false,
|
||||||
|
/* remainingTimeDisplay: {
|
||||||
displayNegative: true,
|
displayNegative: true,
|
||||||
},
|
}, */
|
||||||
|
|
||||||
// Volume panel configuration
|
// Volume panel configuration
|
||||||
volumePanel: {
|
volumePanel: {
|
||||||
@ -425,22 +419,14 @@ function VideoJSPlayer() {
|
|||||||
// Event listeners
|
// Event listeners
|
||||||
playerRef.current.on('ready', () => {
|
playerRef.current.on('ready', () => {
|
||||||
// Auto-play video when navigating from next button
|
// Auto-play video when navigating from next button
|
||||||
const urlParams = new URLSearchParams(
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
window.location.search
|
|
||||||
);
|
|
||||||
const hasVideoParam = urlParams.get('m');
|
const hasVideoParam = urlParams.get('m');
|
||||||
if (hasVideoParam) {
|
if (hasVideoParam) {
|
||||||
// Small delay to ensure everything is loaded
|
// Small delay to ensure everything is loaded
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (
|
if (playerRef.current && !playerRef.current.isDisposed()) {
|
||||||
playerRef.current &&
|
|
||||||
!playerRef.current.isDisposed()
|
|
||||||
) {
|
|
||||||
playerRef.current.play().catch((error) => {
|
playerRef.current.play().catch((error) => {
|
||||||
console.log(
|
console.log('Autoplay was prevented:', error);
|
||||||
'Autoplay was prevented:',
|
|
||||||
error
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
@ -471,16 +457,11 @@ function VideoJSPlayer() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create a text track for chapters programmatically
|
// Create a text track for chapters programmatically
|
||||||
const chaptersTrack = playerRef.current.addTextTrack(
|
const chaptersTrack = playerRef.current.addTextTrack('chapters', 'Chapters', 'en');
|
||||||
'chapters',
|
|
||||||
'Chapters',
|
|
||||||
'en'
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add cues to the chapters track
|
// Add cues to the chapters track
|
||||||
chaptersData.forEach((chapter) => {
|
chaptersData.forEach((chapter) => {
|
||||||
const cue = new (window.VTTCue ||
|
const cue = new (window.VTTCue || window.TextTrackCue)(
|
||||||
window.TextTrackCue)(
|
|
||||||
chapter.startTime,
|
chapter.startTime,
|
||||||
chapter.endTime,
|
chapter.endTime,
|
||||||
chapter.text
|
chapter.text
|
||||||
@ -494,44 +475,45 @@ function VideoJSPlayer() {
|
|||||||
.getChild('controlBar')
|
.getChild('controlBar')
|
||||||
.getChild('progressControl');
|
.getChild('progressControl');
|
||||||
if (progressControl) {
|
if (progressControl) {
|
||||||
const seekBar =
|
const seekBar = progressControl.getChild('seekBar');
|
||||||
progressControl.getChild('seekBar');
|
|
||||||
if (seekBar) {
|
if (seekBar) {
|
||||||
const markers =
|
const markers = seekBar.getChild('ChapterMarkers');
|
||||||
seekBar.getChild('ChapterMarkers');
|
if (markers && markers.updateChapterMarkers) {
|
||||||
if (
|
|
||||||
markers &&
|
|
||||||
markers.updateChapterMarkers
|
|
||||||
) {
|
|
||||||
markers.updateChapterMarkers();
|
markers.updateChapterMarkers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, 500);
|
}, 500);
|
||||||
|
|
||||||
console.log(
|
console.log('Subtitles loaded but disabled by default - use CC button to enable');
|
||||||
'Subtitles loaded but disabled by default - use CC button to enable'
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add Next Video button to control bar and reorder chapters button
|
// Add Next Video button to control bar and reorder chapters button
|
||||||
playerRef.current.ready(() => {
|
playerRef.current.ready(() => {
|
||||||
const controlBar =
|
const controlBar = playerRef.current.getChild('controlBar');
|
||||||
playerRef.current.getChild('controlBar');
|
|
||||||
const nextVideoButton = new NextVideoButton(
|
|
||||||
playerRef.current
|
|
||||||
);
|
|
||||||
|
|
||||||
// Insert after play button
|
|
||||||
const playToggle = controlBar.getChild('playToggle');
|
const playToggle = controlBar.getChild('playToggle');
|
||||||
const playToggleIndex = controlBar
|
const currentTimeDisplay = controlBar.getChild('currentTimeDisplay');
|
||||||
.children()
|
|
||||||
.indexOf(playToggle);
|
// Implement custom time display component
|
||||||
controlBar.addChild(
|
const customRemainingTime = new CustomRemainingTime(playerRef.current, {
|
||||||
nextVideoButton,
|
displayNegative: false,
|
||||||
{},
|
customPrefix: '',
|
||||||
playToggleIndex + 1
|
customSuffix: '',
|
||||||
);
|
});
|
||||||
|
|
||||||
|
// Insert it in the desired position (e.g., after current time display)
|
||||||
|
if (currentTimeDisplay) {
|
||||||
|
const currentTimeIndex = controlBar.children().indexOf(currentTimeDisplay);
|
||||||
|
controlBar.addChild(customRemainingTime, {}, currentTimeIndex + 1);
|
||||||
|
} else {
|
||||||
|
controlBar.addChild(customRemainingTime, {}, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implement custom next video button
|
||||||
|
const nextVideoButton = new NextVideoButton(playerRef.current);
|
||||||
|
|
||||||
|
const playToggleIndex = controlBar.children().indexOf(playToggle); // Insert it after play button
|
||||||
|
controlBar.addChild(nextVideoButton, {}, playToggleIndex + 1);
|
||||||
|
|
||||||
// Remove duplicate captions button and move chapters to end
|
// Remove duplicate captions button and move chapters to end
|
||||||
const cleanupControls = () => {
|
const cleanupControls = () => {
|
||||||
@ -539,10 +521,7 @@ function VideoJSPlayer() {
|
|||||||
const allChildren = controlBar.children();
|
const allChildren = controlBar.children();
|
||||||
|
|
||||||
// Try to find and remove captions/subs-caps button (but keep subtitles)
|
// Try to find and remove captions/subs-caps button (but keep subtitles)
|
||||||
const possibleCaptionButtons = [
|
const possibleCaptionButtons = ['captionsButton', 'subsCapsButton'];
|
||||||
'captionsButton',
|
|
||||||
'subsCapsButton',
|
|
||||||
];
|
|
||||||
possibleCaptionButtons.forEach((buttonName) => {
|
possibleCaptionButtons.forEach((buttonName) => {
|
||||||
const button = controlBar.getChild(buttonName);
|
const button = controlBar.getChild(buttonName);
|
||||||
if (button) {
|
if (button) {
|
||||||
@ -550,47 +529,29 @@ function VideoJSPlayer() {
|
|||||||
controlBar.removeChild(button);
|
controlBar.removeChild(button);
|
||||||
console.log(`✓ Removed ${buttonName}`);
|
console.log(`✓ Removed ${buttonName}`);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(
|
console.log(`✗ Failed to remove ${buttonName}:`, e);
|
||||||
`✗ Failed to remove ${buttonName}:`,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Alternative: hide buttons we can't remove
|
// Alternative: hide buttons we can't remove
|
||||||
allChildren.forEach((child, index) => {
|
allChildren.forEach((child, index) => {
|
||||||
const name = (
|
const name = (child.name_ || child.constructor.name || '').toLowerCase();
|
||||||
child.name_ ||
|
if (name.includes('caption') && !name.includes('subtitle')) {
|
||||||
child.constructor.name ||
|
|
||||||
''
|
|
||||||
).toLowerCase();
|
|
||||||
if (
|
|
||||||
name.includes('caption') &&
|
|
||||||
!name.includes('subtitle')
|
|
||||||
) {
|
|
||||||
child.hide();
|
child.hide();
|
||||||
console.log(
|
console.log(`✓ Hidden button at index ${index}: ${name}`);
|
||||||
`✓ Hidden button at index ${index}: ${name}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Move chapters button to the very end
|
// Move chapters button to the very end
|
||||||
const chaptersButton =
|
const chaptersButton = controlBar.getChild('chaptersButton');
|
||||||
controlBar.getChild('chaptersButton');
|
|
||||||
if (chaptersButton) {
|
if (chaptersButton) {
|
||||||
try {
|
try {
|
||||||
controlBar.removeChild(chaptersButton);
|
controlBar.removeChild(chaptersButton);
|
||||||
controlBar.addChild(chaptersButton);
|
controlBar.addChild(chaptersButton);
|
||||||
console.log(
|
console.log('✓ Chapters button moved to last position');
|
||||||
'✓ Chapters button moved to last position'
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(
|
console.log('✗ Failed to move chapters button:', e);
|
||||||
'✗ Failed to move chapters button:',
|
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -604,15 +565,10 @@ function VideoJSPlayer() {
|
|||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const setupClickableMenus = () => {
|
const setupClickableMenus = () => {
|
||||||
// Find all menu buttons (chapters, subtitles, etc.)
|
// Find all menu buttons (chapters, subtitles, etc.)
|
||||||
const menuButtons = [
|
const menuButtons = ['chaptersButton', 'subtitlesButton', 'playbackRateMenuButton'];
|
||||||
'chaptersButton',
|
|
||||||
'subtitlesButton',
|
|
||||||
'playbackRateMenuButton',
|
|
||||||
];
|
|
||||||
|
|
||||||
menuButtons.forEach((buttonName) => {
|
menuButtons.forEach((buttonName) => {
|
||||||
const button =
|
const button = controlBar.getChild(buttonName);
|
||||||
controlBar.getChild(buttonName);
|
|
||||||
if (button && button.menuButton_) {
|
if (button && button.menuButton_) {
|
||||||
// Override the menu button behavior
|
// Override the menu button behavior
|
||||||
const menuButton = button.menuButton_;
|
const menuButton = button.menuButton_;
|
||||||
@ -623,75 +579,40 @@ function VideoJSPlayer() {
|
|||||||
|
|
||||||
// Add click-to-toggle behavior
|
// Add click-to-toggle behavior
|
||||||
menuButton.on('click', function () {
|
menuButton.on('click', function () {
|
||||||
if (
|
if (this.menu.hasClass('vjs-lock-showing')) {
|
||||||
this.menu.hasClass(
|
this.menu.removeClass('vjs-lock-showing');
|
||||||
'vjs-lock-showing'
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
this.menu.removeClass(
|
|
||||||
'vjs-lock-showing'
|
|
||||||
);
|
|
||||||
this.menu.hide();
|
this.menu.hide();
|
||||||
} else {
|
} else {
|
||||||
this.menu.addClass(
|
this.menu.addClass('vjs-lock-showing');
|
||||||
'vjs-lock-showing'
|
|
||||||
);
|
|
||||||
this.menu.show();
|
this.menu.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(
|
console.log(`✓ Made ${buttonName} clickable`);
|
||||||
`✓ Made ${buttonName} clickable`
|
|
||||||
);
|
|
||||||
} else if (button) {
|
} else if (button) {
|
||||||
// For buttons without menuButton_ property
|
// For buttons without menuButton_ property
|
||||||
const buttonEl = button.el();
|
const buttonEl = button.el();
|
||||||
if (buttonEl) {
|
if (buttonEl) {
|
||||||
// Add click handler to show/hide menu
|
// Add click handler to show/hide menu
|
||||||
buttonEl.addEventListener(
|
buttonEl.addEventListener('click', function (e) {
|
||||||
'click',
|
e.preventDefault();
|
||||||
function (e) {
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
|
||||||
e.stopPropagation();
|
|
||||||
|
|
||||||
const menu =
|
const menu = buttonEl.querySelector('.vjs-menu');
|
||||||
buttonEl.querySelector(
|
if (menu) {
|
||||||
'.vjs-menu'
|
if (menu.style.display === 'block') {
|
||||||
);
|
menu.style.display = 'none';
|
||||||
if (menu) {
|
} else {
|
||||||
if (
|
// Hide other menus first
|
||||||
menu.style
|
document.querySelectorAll('.vjs-menu').forEach((m) => {
|
||||||
.display ===
|
if (m !== menu) m.style.display = 'none';
|
||||||
'block'
|
});
|
||||||
) {
|
menu.style.display = 'block';
|
||||||
menu.style.display =
|
|
||||||
'none';
|
|
||||||
} else {
|
|
||||||
// Hide other menus first
|
|
||||||
document
|
|
||||||
.querySelectorAll(
|
|
||||||
'.vjs-menu'
|
|
||||||
)
|
|
||||||
.forEach(
|
|
||||||
(m) => {
|
|
||||||
if (
|
|
||||||
m !==
|
|
||||||
menu
|
|
||||||
)
|
|
||||||
m.style.display =
|
|
||||||
'none';
|
|
||||||
}
|
|
||||||
);
|
|
||||||
menu.style.display =
|
|
||||||
'block';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
});
|
||||||
|
|
||||||
console.log(
|
console.log(`✓ Added click handler to ${buttonName}`);
|
||||||
`✓ Added click handler to ${buttonName}`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -701,15 +622,11 @@ function VideoJSPlayer() {
|
|||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
||||||
// Add chapter markers to progress control
|
// Add chapter markers to progress control
|
||||||
const progressControl =
|
const progressControl = controlBar.getChild('progressControl');
|
||||||
controlBar.getChild('progressControl');
|
|
||||||
if (progressControl) {
|
if (progressControl) {
|
||||||
const progressHolder =
|
const progressHolder = progressControl.getChild('seekBar');
|
||||||
progressControl.getChild('seekBar');
|
|
||||||
if (progressHolder) {
|
if (progressHolder) {
|
||||||
const chapterMarkers = new ChapterMarkers(
|
const chapterMarkers = new ChapterMarkers(playerRef.current);
|
||||||
playerRef.current
|
|
||||||
);
|
|
||||||
progressHolder.addChild(chapterMarkers);
|
progressHolder.addChild(chapterMarkers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -738,23 +655,16 @@ function VideoJSPlayer() {
|
|||||||
|
|
||||||
// Keep controls active after video ends
|
// Keep controls active after video ends
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (
|
if (playerRef.current && !playerRef.current.isDisposed()) {
|
||||||
playerRef.current &&
|
|
||||||
!playerRef.current.isDisposed()
|
|
||||||
) {
|
|
||||||
// Remove vjs-ended class if it disables controls
|
// Remove vjs-ended class if it disables controls
|
||||||
const playerEl = playerRef.current.el();
|
const playerEl = playerRef.current.el();
|
||||||
if (playerEl) {
|
if (playerEl) {
|
||||||
// Keep the visual ended state but ensure controls work
|
// Keep the visual ended state but ensure controls work
|
||||||
const controlBar =
|
const controlBar = playerRef.current.getChild('controlBar');
|
||||||
playerRef.current.getChild(
|
|
||||||
'controlBar'
|
|
||||||
);
|
|
||||||
if (controlBar) {
|
if (controlBar) {
|
||||||
controlBar.show();
|
controlBar.show();
|
||||||
controlBar.el().style.opacity = '1';
|
controlBar.el().style.opacity = '1';
|
||||||
controlBar.el().style.pointerEvents =
|
controlBar.el().style.pointerEvents = 'auto';
|
||||||
'auto';
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -762,9 +672,7 @@ function VideoJSPlayer() {
|
|||||||
|
|
||||||
// Prevent creating multiple end screens
|
// Prevent creating multiple end screens
|
||||||
if (endScreen) {
|
if (endScreen) {
|
||||||
console.log(
|
console.log('End screen already exists, removing previous one');
|
||||||
'End screen already exists, removing previous one'
|
|
||||||
);
|
|
||||||
playerRef.current.removeChild(endScreen);
|
playerRef.current.removeChild(endScreen);
|
||||||
endScreen = null;
|
endScreen = null;
|
||||||
}
|
}
|
||||||
@ -809,26 +717,15 @@ function VideoJSPlayer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('fullscreenchange', () => {
|
playerRef.current.on('fullscreenchange', () => {
|
||||||
console.log(
|
console.log('Fullscreen changed:', playerRef.current.isFullscreen());
|
||||||
'Fullscreen changed:',
|
|
||||||
playerRef.current.isFullscreen()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('volumechange', () => {
|
playerRef.current.on('volumechange', () => {
|
||||||
console.log(
|
console.log('Volume changed:', playerRef.current.volume(), 'Muted:', playerRef.current.muted());
|
||||||
'Volume changed:',
|
|
||||||
playerRef.current.volume(),
|
|
||||||
'Muted:',
|
|
||||||
playerRef.current.muted()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('ratechange', () => {
|
playerRef.current.on('ratechange', () => {
|
||||||
console.log(
|
console.log('Playback rate changed:', playerRef.current.playbackRate());
|
||||||
'Playback rate changed:',
|
|
||||||
playerRef.current.playbackRate()
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('texttrackchange', () => {
|
playerRef.current.on('texttrackchange', () => {
|
||||||
@ -853,18 +750,13 @@ function VideoJSPlayer() {
|
|||||||
// Focus the player element
|
// Focus the player element
|
||||||
if (playerRef.current.el()) {
|
if (playerRef.current.el()) {
|
||||||
playerRef.current.el().focus();
|
playerRef.current.el().focus();
|
||||||
console.log(
|
console.log('Video player focused for keyboard controls');
|
||||||
'Video player focused for keyboard controls'
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start playing the video immediately if autoplay is enabled
|
// Start playing the video immediately if autoplay is enabled
|
||||||
if (playerRef.current.autoplay()) {
|
if (playerRef.current.autoplay()) {
|
||||||
playerRef.current.play().catch((error) => {
|
playerRef.current.play().catch((error) => {
|
||||||
console.log(
|
console.log('Autoplay prevented by browser:', error);
|
||||||
'Autoplay prevented by browser:',
|
|
||||||
error
|
|
||||||
);
|
|
||||||
// If autoplay fails, we can still focus the element
|
// If autoplay fails, we can still focus the element
|
||||||
// so the user can manually start and use keyboard controls
|
// so the user can manually start and use keyboard controls
|
||||||
});
|
});
|
||||||
@ -915,21 +807,12 @@ function VideoJSPlayer() {
|
|||||||
setTimeout(focusVideo, 500);
|
setTimeout(focusVideo, 500);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener(
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
'visibilitychange',
|
|
||||||
handleVisibilityChange
|
|
||||||
);
|
|
||||||
window.removeEventListener('focus', handleWindowFocus);
|
window.removeEventListener('focus', handleWindowFocus);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return <video ref={videoRef} className="video-js vjs-default-skin" tabIndex="0" />;
|
||||||
<video
|
|
||||||
ref={videoRef}
|
|
||||||
className='video-js vjs-default-skin'
|
|
||||||
tabIndex='0'
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default VideoJSPlayer;
|
export default VideoJSPlayer;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user