mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-10 09:28:53 -05:00
feat: Create the custom icon and chapters sidebar
This commit is contained in:
parent
b3ab626c91
commit
dc9a5492db
@ -0,0 +1,201 @@
|
||||
// components/controls/CustomChaptersOverlay.js
|
||||
import videojs from 'video.js';
|
||||
|
||||
// Get the Component base class from Video.js
|
||||
const Component = videojs.getComponent('Component');
|
||||
|
||||
class CustomChaptersOverlay extends Component {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.chaptersData = options.chaptersData || [];
|
||||
this.overlay = null;
|
||||
this.chaptersList = null;
|
||||
|
||||
// Bind methods
|
||||
this.createOverlay = this.createOverlay.bind(this);
|
||||
this.updateCurrentChapter = this.updateCurrentChapter.bind(this);
|
||||
this.toggleOverlay = this.toggleOverlay.bind(this);
|
||||
|
||||
// Initialize after player is ready
|
||||
this.player().ready(() => {
|
||||
this.createOverlay();
|
||||
this.setupChaptersButton();
|
||||
});
|
||||
}
|
||||
|
||||
createOverlay() {
|
||||
if (!this.chaptersData || this.chaptersData.length === 0) {
|
||||
console.log('⚠ No chapters data available for overlay');
|
||||
return;
|
||||
}
|
||||
|
||||
const playerEl = this.player().el();
|
||||
|
||||
// Create overlay element
|
||||
this.overlay = document.createElement('div');
|
||||
this.overlay.className = 'custom-chapters-overlay';
|
||||
this.overlay.style.cssText = `
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 300px;
|
||||
height: 100%;
|
||||
background: linear-gradient(180deg, rgba(20, 20, 30, 0.95) 0%, rgba(40, 40, 50, 0.95) 100%);
|
||||
color: white;
|
||||
z-index: 1000;
|
||||
display: none;
|
||||
overflow-y: auto;
|
||||
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.5);
|
||||
`;
|
||||
|
||||
// Create header
|
||||
const header = document.createElement('div');
|
||||
header.style.cssText = `
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
font-weight: bold;
|
||||
font-size: 14px;
|
||||
letter-spacing: 2px;
|
||||
border-bottom: 2px solid #4a90e2;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
`;
|
||||
header.textContent = 'CHAPTERS';
|
||||
this.overlay.appendChild(header);
|
||||
|
||||
// Create close button
|
||||
const closeBtn = document.createElement('div');
|
||||
closeBtn.style.cssText = `
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
z-index: 10;
|
||||
`;
|
||||
closeBtn.textContent = '×';
|
||||
closeBtn.onclick = () => {
|
||||
this.overlay.style.display = 'none';
|
||||
};
|
||||
this.overlay.appendChild(closeBtn);
|
||||
|
||||
// Create chapters list
|
||||
this.chaptersList = document.createElement('div');
|
||||
this.chaptersList.style.cssText = `
|
||||
padding: 10px 0;
|
||||
`;
|
||||
|
||||
// Add chapters from data
|
||||
this.chaptersData.forEach((chapter) => {
|
||||
const chapterItem = document.createElement('div');
|
||||
chapterItem.style.cssText = `
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
cursor: pointer;
|
||||
transition: background 0.2s ease;
|
||||
font-size: 14px;
|
||||
line-height: 1.4;
|
||||
`;
|
||||
chapterItem.textContent = chapter.text;
|
||||
|
||||
// Add hover effect
|
||||
chapterItem.onmouseenter = () => {
|
||||
chapterItem.style.background = 'rgba(74, 144, 226, 0.2)';
|
||||
};
|
||||
chapterItem.onmouseleave = () => {
|
||||
chapterItem.style.background = 'transparent';
|
||||
};
|
||||
|
||||
// Add click handler
|
||||
chapterItem.onclick = () => {
|
||||
this.player().currentTime(chapter.startTime);
|
||||
this.overlay.style.display = 'none';
|
||||
|
||||
// Update active state
|
||||
this.chaptersList.querySelectorAll('div').forEach((item) => {
|
||||
item.style.background = 'transparent';
|
||||
item.style.fontWeight = 'normal';
|
||||
});
|
||||
chapterItem.style.background = 'rgba(74, 144, 226, 0.4)';
|
||||
chapterItem.style.fontWeight = 'bold';
|
||||
};
|
||||
|
||||
this.chaptersList.appendChild(chapterItem);
|
||||
});
|
||||
|
||||
this.overlay.appendChild(this.chaptersList);
|
||||
|
||||
// Add to player
|
||||
playerEl.appendChild(this.overlay);
|
||||
|
||||
// Set up time update listener
|
||||
this.player().on('timeupdate', this.updateCurrentChapter);
|
||||
|
||||
console.log('✓ Custom chapters overlay created');
|
||||
}
|
||||
|
||||
setupChaptersButton() {
|
||||
const chaptersButton = this.player().getChild('controlBar').getChild('chaptersButton');
|
||||
if (chaptersButton) {
|
||||
// Override the click handler
|
||||
chaptersButton.off('click'); // Remove default handler
|
||||
chaptersButton.on('click', this.toggleOverlay);
|
||||
}
|
||||
}
|
||||
|
||||
toggleOverlay() {
|
||||
if (!this.overlay) return;
|
||||
|
||||
if (this.overlay.style.display === 'none' || !this.overlay.style.display) {
|
||||
this.overlay.style.display = 'block';
|
||||
} else {
|
||||
this.overlay.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
updateCurrentChapter() {
|
||||
if (!this.chaptersList || !this.chaptersData) return;
|
||||
|
||||
const currentTime = this.player().currentTime();
|
||||
const chapterItems = this.chaptersList.querySelectorAll('div');
|
||||
|
||||
chapterItems.forEach((item, index) => {
|
||||
const chapter = this.chaptersData[index];
|
||||
const isPlaying =
|
||||
currentTime >= chapter.startTime &&
|
||||
(index === this.chaptersData.length - 1 || currentTime < this.chaptersData[index + 1].startTime);
|
||||
|
||||
if (isPlaying) {
|
||||
item.style.borderLeft = '4px solid #10b981';
|
||||
item.style.paddingLeft = '16px';
|
||||
} else {
|
||||
item.style.borderLeft = 'none';
|
||||
item.style.paddingLeft = '20px';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
if (this.overlay) {
|
||||
this.overlay.remove();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Set component name for Video.js
|
||||
CustomChaptersOverlay.prototype.controlText_ = 'Chapters Overlay';
|
||||
|
||||
// Register the component with Video.js
|
||||
videojs.registerComponent('CustomChaptersOverlay', CustomChaptersOverlay);
|
||||
|
||||
export default CustomChaptersOverlay;
|
||||
@ -0,0 +1,115 @@
|
||||
/* CustomSettingsMenu.css */
|
||||
|
||||
/* Settings button styling */
|
||||
.vjs-settings-button {
|
||||
width: 3em;
|
||||
height: 3em;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Settings button icon styling */
|
||||
.vjs-icon-cog1 {
|
||||
font-size: 30px !important;
|
||||
position: relative;
|
||||
top: -8px !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
/* Settings overlay styling */
|
||||
.custom-settings-overlay {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
right: 0;
|
||||
width: 250px;
|
||||
background: rgba(28, 28, 28, 0.95);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
||||
display: none;
|
||||
z-index: 1000;
|
||||
font-size: 14px;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* Settings header */
|
||||
.settings-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Settings items */
|
||||
.settings-item {
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.settings-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.settings-item:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Speed submenu */
|
||||
.speed-submenu {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(28, 28, 28, 0.95);
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Submenu header */
|
||||
.submenu-header {
|
||||
padding: 12px 16px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.submenu-header:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
/* Speed options */
|
||||
.speed-option {
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
transition: background 0.2s ease;
|
||||
}
|
||||
|
||||
.speed-option:hover {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.speed-option.active {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
.vjs-icon-cog:before {
|
||||
font-size: 20px !important;
|
||||
position: relative;
|
||||
top: -5px !important;
|
||||
}
|
||||
@ -0,0 +1,242 @@
|
||||
// components/controls/CustomSettingsMenu.js
|
||||
import videojs from 'video.js';
|
||||
import './CustomSettingsMenu.css';
|
||||
|
||||
// Get the Component base class from Video.js
|
||||
const Component = videojs.getComponent('Component');
|
||||
|
||||
class CustomSettingsMenu extends Component {
|
||||
constructor(player, options) {
|
||||
super(player, options);
|
||||
|
||||
this.settingsButton = null;
|
||||
this.settingsOverlay = null;
|
||||
this.speedSubmenu = null;
|
||||
|
||||
// Bind methods
|
||||
this.createSettingsButton = this.createSettingsButton.bind(this);
|
||||
this.createSettingsOverlay = this.createSettingsOverlay.bind(this);
|
||||
this.positionButton = this.positionButton.bind(this);
|
||||
this.toggleSettings = this.toggleSettings.bind(this);
|
||||
this.handleSpeedChange = this.handleSpeedChange.bind(this);
|
||||
this.handleClickOutside = this.handleClickOutside.bind(this);
|
||||
|
||||
// Initialize after player is ready
|
||||
this.player().ready(() => {
|
||||
this.createSettingsButton();
|
||||
this.createSettingsOverlay();
|
||||
this.setupEventListeners();
|
||||
});
|
||||
}
|
||||
|
||||
createSettingsButton() {
|
||||
const controlBar = this.player().getChild('controlBar');
|
||||
|
||||
// Hide default playback rate button
|
||||
const playbackRateButton = controlBar.getChild('playbackRateMenuButton');
|
||||
if (playbackRateButton) {
|
||||
playbackRateButton.hide();
|
||||
}
|
||||
|
||||
// Create settings button
|
||||
this.settingsButton = controlBar.addChild('button', {
|
||||
controlText: 'Settings',
|
||||
className: 'vjs-settings-button',
|
||||
});
|
||||
|
||||
// Style the settings button (gear icon)
|
||||
const settingsButtonEl = this.settingsButton.el();
|
||||
settingsButtonEl.innerHTML = `
|
||||
<span class="vjs-icon-cog"></span>
|
||||
`;
|
||||
|
||||
// Position the settings button at the end of the control bar
|
||||
this.positionButton();
|
||||
|
||||
// Add click handler
|
||||
this.settingsButton.on('click', this.toggleSettings);
|
||||
}
|
||||
|
||||
createSettingsOverlay() {
|
||||
const controlBar = this.player().getChild('controlBar');
|
||||
|
||||
// Create settings overlay
|
||||
this.settingsOverlay = document.createElement('div');
|
||||
this.settingsOverlay.className = 'custom-settings-overlay';
|
||||
|
||||
// Settings menu content
|
||||
this.settingsOverlay.innerHTML = `
|
||||
<div class="settings-header">Settings</div>
|
||||
|
||||
<div class="settings-item" data-setting="playback-speed">
|
||||
<span>Playback speed</span>
|
||||
<span class="current-speed">Normal</span>
|
||||
</div>
|
||||
|
||||
<div class="settings-item" data-setting="quality">
|
||||
<span>Quality</span>
|
||||
<span class="current-quality">Auto</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Create speed submenu
|
||||
this.createSpeedSubmenu();
|
||||
|
||||
// Add to control bar
|
||||
controlBar.el().appendChild(this.settingsOverlay);
|
||||
}
|
||||
|
||||
createSpeedSubmenu() {
|
||||
const speedOptions = [
|
||||
{ label: '0.25', value: 0.25 },
|
||||
{ label: '0.5', value: 0.5 },
|
||||
{ label: '0.75', value: 0.75 },
|
||||
{ label: 'Normal', value: 1 },
|
||||
{ label: '1.25', value: 1.25 },
|
||||
{ label: '1.5', value: 1.5 },
|
||||
{ label: '1.75', value: 1.75 },
|
||||
{ label: '2', value: 2 },
|
||||
];
|
||||
|
||||
this.speedSubmenu = document.createElement('div');
|
||||
this.speedSubmenu.className = 'speed-submenu';
|
||||
|
||||
this.speedSubmenu.innerHTML = `
|
||||
<div class="submenu-header">
|
||||
<span style="margin-right: 8px;">←</span>
|
||||
<span>Playback speed</span>
|
||||
</div>
|
||||
${speedOptions
|
||||
.map(
|
||||
(option) => `
|
||||
<div class="speed-option ${option.value === 1 ? 'active' : ''}" data-speed="${option.value}">
|
||||
<span>${option.label}</span>
|
||||
${option.value === 1 ? '<span>✓</span>' : ''}
|
||||
</div>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
`;
|
||||
|
||||
this.settingsOverlay.appendChild(this.speedSubmenu);
|
||||
}
|
||||
|
||||
positionButton() {
|
||||
const controlBar = this.player().getChild('controlBar');
|
||||
const fullscreenToggle = controlBar.getChild('fullscreenToggle');
|
||||
|
||||
if (this.settingsButton && fullscreenToggle) {
|
||||
// Small delay to ensure all buttons are created
|
||||
setTimeout(() => {
|
||||
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
|
||||
controlBar.removeChild(this.settingsButton);
|
||||
controlBar.addChild(this.settingsButton, {}, fullscreenIndex + 1);
|
||||
console.log('✓ Settings button positioned after fullscreen toggle');
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Settings item clicks
|
||||
this.settingsOverlay.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
|
||||
if (e.target.closest('[data-setting="playback-speed"]')) {
|
||||
this.speedSubmenu.style.display = 'flex';
|
||||
}
|
||||
});
|
||||
|
||||
// Speed submenu header (back button)
|
||||
this.speedSubmenu.querySelector('.submenu-header').addEventListener('click', () => {
|
||||
this.speedSubmenu.style.display = 'none';
|
||||
});
|
||||
|
||||
// Speed option clicks
|
||||
this.speedSubmenu.addEventListener('click', (e) => {
|
||||
const speedOption = e.target.closest('.speed-option');
|
||||
if (speedOption) {
|
||||
const speed = parseFloat(speedOption.dataset.speed);
|
||||
this.handleSpeedChange(speed, speedOption);
|
||||
}
|
||||
});
|
||||
|
||||
// Close menu when clicking outside
|
||||
document.addEventListener('click', this.handleClickOutside);
|
||||
|
||||
// Add hover effects
|
||||
this.settingsOverlay.addEventListener('mouseover', (e) => {
|
||||
const item = e.target.closest('.settings-item, .speed-option');
|
||||
if (item && !item.style.background.includes('0.1')) {
|
||||
item.style.background = 'rgba(255, 255, 255, 0.05)';
|
||||
}
|
||||
});
|
||||
|
||||
this.settingsOverlay.addEventListener('mouseout', (e) => {
|
||||
const item = e.target.closest('.settings-item, .speed-option');
|
||||
if (item && !item.style.background.includes('0.1')) {
|
||||
item.style.background = 'transparent';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
toggleSettings(e) {
|
||||
e.stopPropagation();
|
||||
const isVisible = this.settingsOverlay.style.display === 'block';
|
||||
this.settingsOverlay.style.display = isVisible ? 'none' : 'block';
|
||||
this.speedSubmenu.style.display = 'none'; // Hide submenu when main menu toggles
|
||||
}
|
||||
|
||||
handleSpeedChange(speed, speedOption) {
|
||||
// Update player speed
|
||||
this.player().playbackRate(speed);
|
||||
|
||||
// Update UI
|
||||
document.querySelectorAll('.speed-option').forEach((opt) => {
|
||||
opt.style.background = 'transparent';
|
||||
opt.querySelector('span:last-child')?.remove();
|
||||
});
|
||||
|
||||
speedOption.style.background = 'rgba(255, 255, 255, 0.1)';
|
||||
speedOption.insertAdjacentHTML('beforeend', '<span>✓</span>');
|
||||
|
||||
// Update main menu display
|
||||
const currentSpeedDisplay = this.settingsOverlay.querySelector('.current-speed');
|
||||
currentSpeedDisplay.textContent = speedOption.querySelector('span').textContent;
|
||||
|
||||
// Hide menus
|
||||
this.settingsOverlay.style.display = 'none';
|
||||
this.speedSubmenu.style.display = 'none';
|
||||
}
|
||||
|
||||
handleClickOutside(e) {
|
||||
if (
|
||||
this.settingsOverlay &&
|
||||
this.settingsButton &&
|
||||
!this.settingsOverlay.contains(e.target) &&
|
||||
!this.settingsButton.el().contains(e.target)
|
||||
) {
|
||||
this.settingsOverlay.style.display = 'none';
|
||||
this.speedSubmenu.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
dispose() {
|
||||
// Remove event listeners
|
||||
document.removeEventListener('click', this.handleClickOutside);
|
||||
|
||||
// Remove DOM elements
|
||||
if (this.settingsOverlay) {
|
||||
this.settingsOverlay.remove();
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Set component name for Video.js
|
||||
CustomSettingsMenu.prototype.controlText_ = 'Settings Menu';
|
||||
|
||||
// Register the component with Video.js
|
||||
videojs.registerComponent('CustomSettingsMenu', CustomSettingsMenu);
|
||||
|
||||
export default CustomSettingsMenu;
|
||||
@ -7,6 +7,8 @@ import EndScreenOverlay from '../overlays/EndScreenOverlay';
|
||||
import ChapterMarkers from '../markers/ChapterMarkers';
|
||||
import NextVideoButton from '../controls/NextVideoButton';
|
||||
import CustomRemainingTime from '../controls/CustomRemainingTime';
|
||||
import CustomChaptersOverlay from '../controls/CustomChaptersOverlay';
|
||||
import CustomSettingsMenu from '../controls/CustomSettingsMenu';
|
||||
|
||||
function VideoJSPlayer() {
|
||||
const videoRef = useRef(null);
|
||||
@ -366,7 +368,7 @@ function VideoJSPlayer() {
|
||||
descriptionsButton: true,
|
||||
|
||||
// Subtitles button
|
||||
subtitlesButton: true,
|
||||
subtitlesButton: false,
|
||||
|
||||
// Captions button (disabled to avoid duplicate)
|
||||
captionsButton: false,
|
||||
@ -421,7 +423,19 @@ function VideoJSPlayer() {
|
||||
});
|
||||
|
||||
// Event listeners
|
||||
playerRef.current.on('ready', () => {
|
||||
/* playerRef.current.on('ready', () => {
|
||||
console.log('Video.js player ready');
|
||||
}); */
|
||||
playerRef.current.ready(() => {
|
||||
// Get control bar and its children
|
||||
const controlBar = playerRef.current.getChild('controlBar');
|
||||
const playToggle = controlBar.getChild('playToggle');
|
||||
const currentTimeDisplay = controlBar.getChild('currentTimeDisplay');
|
||||
const progressControl = controlBar.getChild('progressControl');
|
||||
const seekBar = progressControl.getChild('seekBar');
|
||||
const chaptersButton = controlBar.getChild('chaptersButton');
|
||||
const fullscreenToggle = controlBar.getChild('fullscreenToggle');
|
||||
|
||||
// Auto-play video when navigating from next button
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const hasVideoParam = urlParams.get('m');
|
||||
@ -436,8 +450,8 @@ function VideoJSPlayer() {
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// Add English subtitle track after player is ready
|
||||
playerRef.current.addRemoteTextTrack(
|
||||
// BEGIN: Add subtitle tracks
|
||||
const subtitleTracks = [
|
||||
{
|
||||
kind: 'subtitles',
|
||||
src: '/sample-subtitles.vtt',
|
||||
@ -445,11 +459,6 @@ function VideoJSPlayer() {
|
||||
label: 'English Subtitles',
|
||||
default: false,
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
// Add Greek subtitle track
|
||||
playerRef.current.addRemoteTextTrack(
|
||||
{
|
||||
kind: 'subtitles',
|
||||
src: '/sample-subtitles-greek.vtt',
|
||||
@ -457,46 +466,34 @@ function VideoJSPlayer() {
|
||||
label: 'Greek Subtitles (Ελληνικά)',
|
||||
default: false,
|
||||
},
|
||||
false
|
||||
);
|
||||
];
|
||||
|
||||
// Create a text track for chapters programmatically
|
||||
const chaptersTrack = playerRef.current.addTextTrack('chapters', 'Chapters', 'en');
|
||||
|
||||
// Add cues to the chapters track
|
||||
chaptersData.forEach((chapter) => {
|
||||
const cue = new (window.VTTCue || window.TextTrackCue)(
|
||||
chapter.startTime,
|
||||
chapter.endTime,
|
||||
chapter.text
|
||||
);
|
||||
chaptersTrack.addCue(cue);
|
||||
subtitleTracks.forEach((track) => {
|
||||
playerRef.current.addRemoteTextTrack(track, false);
|
||||
});
|
||||
// END: Add subtitle tracks
|
||||
|
||||
// BEGIN: Chapters Implementation
|
||||
if (chaptersData && chaptersData.length > 0) {
|
||||
const chaptersTrack = playerRef.current.addTextTrack('chapters', 'Chapters', 'en');
|
||||
// Add cues to the chapters track
|
||||
chaptersData.forEach((chapter) => {
|
||||
const cue = new (window.VTTCue || window.TextTrackCue)(
|
||||
chapter.startTime,
|
||||
chapter.endTime,
|
||||
chapter.text
|
||||
);
|
||||
chaptersTrack.addCue(cue);
|
||||
});
|
||||
}
|
||||
// END: Chapters Implementation
|
||||
|
||||
// Force chapter markers update after chapters are loaded
|
||||
setTimeout(() => {
|
||||
const progressControl = playerRef.current
|
||||
.getChild('controlBar')
|
||||
.getChild('progressControl');
|
||||
if (progressControl) {
|
||||
const seekBar = progressControl.getChild('seekBar');
|
||||
if (seekBar) {
|
||||
const markers = seekBar.getChild('ChapterMarkers');
|
||||
if (markers && markers.updateChapterMarkers) {
|
||||
markers.updateChapterMarkers();
|
||||
}
|
||||
}
|
||||
/* setTimeout(() => {
|
||||
if (chapterMarkers && chapterMarkers.updateChapterMarkers) {
|
||||
chapterMarkers.updateChapterMarkers();
|
||||
}
|
||||
}, 500);
|
||||
|
||||
console.log('Subtitles loaded but disabled by default - use CC button to enable');
|
||||
});
|
||||
|
||||
// Add Next Video button to control bar and reorder chapters button
|
||||
playerRef.current.ready(() => {
|
||||
const controlBar = playerRef.current.getChild('controlBar');
|
||||
const playToggle = controlBar.getChild('playToggle');
|
||||
const currentTimeDisplay = controlBar.getChild('currentTimeDisplay');
|
||||
}, 500); */
|
||||
|
||||
// BEGIN: Implement custom time display component
|
||||
const customRemainingTime = new CustomRemainingTime(playerRef.current, {
|
||||
@ -523,7 +520,7 @@ function VideoJSPlayer() {
|
||||
// END: Implement custom next video button
|
||||
|
||||
// Remove duplicate captions button and move chapters to end
|
||||
const cleanupControls = () => {
|
||||
/* const cleanupControls = () => {
|
||||
// Log all current children for debugging
|
||||
const allChildren = controlBar.children();
|
||||
|
||||
@ -561,12 +558,12 @@ function VideoJSPlayer() {
|
||||
console.log('✗ Failed to move chapters button:', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
}; */
|
||||
|
||||
// Try multiple times with different delays
|
||||
setTimeout(cleanupControls, 200);
|
||||
/* setTimeout(cleanupControls, 200);
|
||||
setTimeout(cleanupControls, 500);
|
||||
setTimeout(cleanupControls, 1000);
|
||||
setTimeout(cleanupControls, 1000); */
|
||||
|
||||
// Make menus clickable instead of hover-only
|
||||
setTimeout(() => {
|
||||
@ -628,15 +625,46 @@ function VideoJSPlayer() {
|
||||
setupClickableMenus();
|
||||
}, 1500);
|
||||
|
||||
// Add chapter markers to progress control
|
||||
const progressControl = controlBar.getChild('progressControl');
|
||||
if (progressControl) {
|
||||
const progressHolder = progressControl.getChild('seekBar');
|
||||
if (progressHolder) {
|
||||
const chapterMarkers = new ChapterMarkers(playerRef.current);
|
||||
progressHolder.addChild(chapterMarkers);
|
||||
// BEGIN: Add chapter markers to progress control
|
||||
if (progressControl && seekBar) {
|
||||
const chapterMarkers = new ChapterMarkers(playerRef.current);
|
||||
seekBar.addChild(chapterMarkers);
|
||||
}
|
||||
// END: Add chapter markers to progress control
|
||||
|
||||
// BEGIN: Move chapters button after fullscreen toggle
|
||||
if (chaptersButton && fullscreenToggle) {
|
||||
try {
|
||||
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
|
||||
controlBar.addChild(chaptersButton, {}, fullscreenIndex + 1);
|
||||
console.log('✓ Chapters button moved after fullscreen toggle');
|
||||
} catch (e) {
|
||||
console.log('✗ Failed to move chapters button:', e);
|
||||
}
|
||||
}
|
||||
// END: Move chapters button after fullscreen toggle
|
||||
|
||||
// Store custom components for potential future use (cleanup, method access, etc.)
|
||||
const customComponents = {};
|
||||
|
||||
// BEGIN: Add Chapters Overlay Component
|
||||
if (chaptersData && chaptersData.length > 0) {
|
||||
customComponents.chaptersOverlay = new CustomChaptersOverlay(playerRef.current, {
|
||||
chaptersData: chaptersData,
|
||||
});
|
||||
console.log('✓ Custom chapters overlay component created');
|
||||
} else {
|
||||
console.log('⚠ No chapters data available for overlay');
|
||||
}
|
||||
// END: Add Chapters Overlay Component
|
||||
|
||||
// BEGIN: Add Settings Menu Component
|
||||
customComponents.settingsMenu = new CustomSettingsMenu(playerRef.current);
|
||||
console.log('✓ Custom settings menu component created');
|
||||
// END: Add Settings Menu Component
|
||||
|
||||
// Store components reference for potential cleanup
|
||||
console.log('Custom components initialized:', Object.keys(customComponents));
|
||||
});
|
||||
|
||||
// Listen for next video event
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user