diff --git a/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js b/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js
new file mode 100644
index 00000000..f3db81ec
--- /dev/null
+++ b/frontend-tools/video-js/src/components/controls/CustomChaptersOverlay.js
@@ -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;
diff --git a/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.css b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.css
new file mode 100644
index 00000000..c2ac6d8b
--- /dev/null
+++ b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.css
@@ -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;
+}
diff --git a/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js
new file mode 100644
index 00000000..054cd86c
--- /dev/null
+++ b/frontend-tools/video-js/src/components/controls/CustomSettingsMenu.js
@@ -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 = `
+
+ `;
+
+ // 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 = `
+
+
+
+ Playback speed
+ Normal
+
+
+
+ Quality
+ Auto
+
+ `;
+
+ // 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 = `
+
+ ${speedOptions
+ .map(
+ (option) => `
+
+ ${option.label}
+ ${option.value === 1 ? '✓' : ''}
+
+ `
+ )
+ .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', '✓');
+
+ // 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;
diff --git a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx
index a416de17..67c214de 100644
--- a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx
+++ b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx
@@ -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