fix: Chapter title, next video icon, play video with sound, dark opacity, fix hls, cleanup logs

This commit is contained in:
Yiannis Christodoulou 2025-09-20 02:39:40 +03:00
parent 8d31ff71e0
commit 5fda4610da
18 changed files with 535 additions and 1002 deletions

4
.gitignore vendored
View File

@ -30,6 +30,4 @@ static/video_editor/videos/sample-video-37s.mp4
.DS_Store
static/video_editor/videos/sample-video-10m.mp4
static/video_editor/videos/sample-video-10s.mp4
/frontend-tools/chapters-editor/node_modules
frontend-tools/chapters-editor/client/public/videos/sample-video.mp4
frontend-tools/video-js/
frontend-tools/video-js/public/videos/sample-video-white.mp4

View File

@ -46,6 +46,70 @@ html {
justify-content: flex-start !important;
padding: 0 12px;
height: 48px;
/* position: relative !important; */
}
/* YouTube-style bottom gradient overlay - covers entire video bottom when controls active */
.video-js::after {
content: "";
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 120px;
background: linear-gradient(
to top,
rgba(0, 0, 0, 0.8) 0%,
rgba(0, 0, 0, 0.6) 25%,
rgba(0, 0, 0, 0.4) 50%,
rgba(0, 0, 0, 0.2) 75%,
transparent 100%
);
pointer-events: none;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 2;
}
/* Show overlay when controls are active - YouTube style */
.video-js.vjs-user-active::after,
.video-js.vjs-paused::after,
.video-js.vjs-ended::after {
opacity: 1;
}
/* Ensure control bar is above the overlay */
.video-js .vjs-control-bar {
z-index: 6 !important;
}
/* Progress control above overlay */
.video-js .vjs-progress-control.vjs-control {
z-index: 7 !important;
}
/* Clean white icons - overlay provides the contrast */
.video-js .vjs-control-bar .vjs-button .vjs-icon-placeholder::before {
color: #ffffff !important;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6) !important;
}
/* Clean white time displays */
.video-js .vjs-control-bar .vjs-time-control {
color: #ffffff !important;
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6) !important;
font-weight: 500 !important;
}
/* Keep original progress bar styling */
/* Volume control visibility */
.video-js .vjs-volume-control .vjs-volume-bar {
background: rgba(255, 255, 255, 0.3) !important;
}
.video-js .vjs-volume-control .vjs-volume-level {
background: #ffffff !important;
}
.video-container {
@ -93,6 +157,7 @@ html {
align-items: center;
justify-content: center;
margin: auto;
display: none !important;
}
.vjs-next-video-control .vjs-icon-placeholder svg {
width: 100%;
@ -577,8 +642,7 @@ html {
background-color: rgba(255, 255, 255, 0.1) !important;
}
.video-js .vjs-settings-button:focus {
outline: 2px solid #fff !important;
outline-offset: 2px !important;
outline: none !important;
}
.video-js .vjs-settings-button .vjs-icon-cog {

View File

@ -1,207 +0,0 @@
import React from 'react';
function ChapterList() {
const playlistData = [
{
id: 1,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: true,
},
{
id: 2,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: false,
},
{
id: 3,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: false,
},
{
id: 4,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: false,
},
{
id: 5,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: false,
},
{
id: 6,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: false,
},
{
id: 7,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: false,
},
{
id: 8,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: false,
},
{
id: 9,
title: 'Class 12 Chapter 1 || Electric Charges and Fields 01 || Quantisation and Conservation of Charge',
channel: 'Physics Wallah - Alakh Pandey',
duration: '40:13',
thumbnail:
'https://i.ytimg.com/vi/m5VbK66a254/hqdefault.jpg?sqp=-oaymwEmCKgBEF5IWvKriqkDGQgBFQAAiEIYAdgBAeIBCggYEAIYBjgBQAE=&rs=AOn4CLCt2rMJW2jZAYkcDi9wLQOGVkLSTw',
selected: false,
},
];
return (
<div className="video-chapter">
<div className="chapter-head">
<div className="playlist-title">
<div className="chapter-title">
<h3>
<a href="">12 chapter 1 II Electri charges and Fields JEE MAINS/NEET</a>
</h3>
<p>
<a href="">Physics Wallah - Alakh Pandey</a> <span>1 / 17</span>
</p>
</div>
<div className="chapter-close">
<button>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12.7096 12L20.8596 20.15L20.1496 20.86L11.9996 12.71L3.84965 20.86L3.13965 20.15L11.2896 12L3.14965 3.85001L3.85965 3.14001L11.9996 11.29L20.1496 3.14001L20.8596 3.85001L12.7096 12Z"
fill="black"
/>
</svg>
</button>
</div>
</div>
<div className="playlist-action-menu">
<div className="start-action">
<button>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21.0002 13H22.0002V18L3.93023 18.03L6.55023 20.65L5.84023 21.36L1.99023 17.5L5.84023 13.65L6.55023 14.36L3.88023 17.03L21.0002 17V13ZM3.00023 7.00002L20.1202 6.97002L17.4502 9.64002L18.1602 10.35L22.0102 6.50002L18.1602 2.65002L17.4502 3.36002L20.0702 5.98002L2.00023 6.00002V11H3.00023V7.00002Z"
fill="black"
/>
</svg>
</button>
<button>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M18.15 13.65L22 17.5L18.15 21.35L17.44 20.64L20.09 18H19C16.16 18 13.47 16.77 11.61 14.62L12.37 13.97C14.03 15.89 16.45 17 19 17H20.09L17.44 14.35L18.15 13.65ZM19 7.00003H20.09L17.44 9.65003L18.15 10.36L22 6.51003L18.15 2.66003L17.44 3.37003L20.09 6.00003H19C15.42 6.00003 12.14 7.95003 10.43 11.09L9.7 12.43C8.16 15.25 5.21 17 2 17V18C5.58 18 8.86 16.05 10.57 12.91L11.3 11.57C12.84 8.75003 15.79 7.00003 19 7.00003ZM8.59 9.98003L9.34 9.32003C7.49 7.21003 4.81 6.00003 2 6.00003V7.00003C4.52 7.00003 6.92 8.09003 8.59 9.98003Z"
fill="black"
/>
</svg>
</button>
</div>
<div className="end-action">
<button>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 16.5C12.83 16.5 13.5 17.17 13.5 18C13.5 18.83 12.83 19.5 12 19.5C11.17 19.5 10.5 18.83 10.5 18C10.5 17.17 11.17 16.5 12 16.5ZM10.5 12C10.5 12.83 11.17 13.5 12 13.5C12.83 13.5 13.5 12.83 13.5 12C13.5 11.17 12.83 10.5 12 10.5C11.17 10.5 10.5 11.17 10.5 12ZM10.5 6C10.5 6.83 11.17 7.5 12 7.5C12.83 7.5 13.5 6.83 13.5 6C13.5 5.17 12.83 4.5 12 4.5C11.17 4.5 10.5 5.17 10.5 6Z"
fill="black"
/>
</svg>
</button>
</div>
</div>
</div>
<div className="chapter-body">
<ul>
{playlistData.map((item) => (
<li key={item.id}>
<div className={`playlist-items ${item.selected ? 'selected' : ''}`}>
<a href="#">
<div className="playlist-drag-handle">{item.selected ? '▶' : item.id}</div>
<div className="thumbnail-container">
<img src={item.thumbnail} alt={item.title} />
<span>{item.duration}</span>
</div>
<div className="thumbnail-meta">
<h4>{item.title}</h4>
<span>{item.channel}</span>
</div>
<div className="thumbnail-action">
<button>
<svg
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12 16.5C12.83 16.5 13.5 17.17 13.5 18C13.5 18.83 12.83 19.5 12 19.5C11.17 19.5 10.5 18.83 10.5 18C10.5 17.17 11.17 16.5 12 16.5ZM10.5 12C10.5 12.83 11.17 13.5 12 13.5C12.83 13.5 13.5 12.83 13.5 12C13.5 11.17 12.83 10.5 12 10.5C11.17 10.5 10.5 11.17 10.5 12ZM10.5 6C10.5 6.83 11.17 7.5 12 7.5C12.83 7.5 13.5 6.83 13.5 6C13.5 5.17 12.83 4.5 12 4.5C11.17 4.5 10.5 5.17 10.5 6Z"
fill="currentColor"
/>
</svg>
</button>
</div>
</a>
</div>
</li>
))}
</ul>
</div>
</div>
);
}
export default ChapterList;

View File

@ -11,10 +11,8 @@ class AutoplayToggleButton extends Button {
if (this.userPreferences) {
const savedAutoplay = this.userPreferences.getPreference('autoplay');
this.isAutoplayEnabled = savedAutoplay === true; // Explicit boolean check
console.log('Autoplay button initialized with saved preference:', this.isAutoplayEnabled);
} else {
this.isAutoplayEnabled = false;
console.log('Autoplay button initialized with default (no userPreferences):', this.isAutoplayEnabled);
}
// Bind methods
@ -37,13 +35,10 @@ class AutoplayToggleButton extends Button {
});
// Set initial icon state directly
console.log('AutoplayToggleButton createEl: isAutoplayEnabled =', this.isAutoplayEnabled);
if (this.isAutoplayEnabled) {
this.iconSpan.innerHTML = `<span style="font-size: 1.2em; color: #ff4444;">●</span>`;
console.log('Setting RED icon (autoplay ON)');
} else {
this.iconSpan.innerHTML = `<span style="font-size: 1.2em; color: #ccc;">○</span>`;
console.log('Setting GRAY icon (autoplay OFF)');
}
// Create control text span
@ -51,8 +46,6 @@ class AutoplayToggleButton extends Button {
className: 'vjs-control-text',
});
controlTextSpan.textContent = this.isAutoplayEnabled ? 'Autoplay is on' : 'Autoplay is off';
console.log('✓ Autoplay button created with initial tooltip:', this.isAutoplayEnabled ? 'Autoplay is on' : 'Autoplay is off');
// Append both spans to button
button.appendChild(this.iconSpan);
@ -65,49 +58,46 @@ class AutoplayToggleButton extends Button {
}
updateIcon() {
// Add transition and start fade-out
this.iconSpan.style.transition = 'opacity 0.1s ease';
this.iconSpan.style.opacity = '0';
// Add transition and start fade-out
this.iconSpan.style.transition = 'opacity 0.1s ease';
this.iconSpan.style.opacity = '0';
// After fade-out complete, update innerHTML and fade back in
setTimeout(() => {
if (this.isAutoplayEnabled) {
this.iconSpan.innerHTML = `<span style="transform: inherit !important; margin: 20px 0 0; font-size: 1.2em; color: #ff4444;">
// After fade-out complete, update innerHTML and fade back in
setTimeout(() => {
if (this.isAutoplayEnabled) {
this.iconSpan.innerHTML = `<span style="transform: inherit !important; margin: 20px 0 0; font-size: 1.2em; color: #ff4444;">
<svg width="198" height="100" viewBox="0 0 198 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect y="18" width="180" height="64" rx="32" fill="white"/>
<rect x="98" width="100" height="100" rx="50" fill="white"/>
<path d="M133 69L163 50L133 31V69ZM138.455 59.0929V40.9071L152.773 50L138.455 59.0929Z" fill="#1C1B1F"/>
</svg>
</span>`;
if (this.el()) {
this.el().title = 'Autoplay is on';
this.el().setAttribute('aria-label', 'Autoplay is on');
const controlText = this.el().querySelector('.vjs-control-text');
if (controlText) controlText.textContent = 'Autoplay is on';
console.log('✓ Autoplay tooltip updated to: "Autoplay is on"');
}
} else {
this.iconSpan.innerHTML = `<span style="transform: inherit !important; margin: 20px 0 0; font-size: 1.2em; color: #ccc;">
if (this.el()) {
this.el().title = 'Autoplay is on';
this.el().setAttribute('aria-label', 'Autoplay is on');
const controlText = this.el().querySelector('.vjs-control-text');
if (controlText) controlText.textContent = 'Autoplay is on';
}
} else {
this.iconSpan.innerHTML = `<span style="transform: inherit !important; margin: 20px 0 0; font-size: 1.2em; color: #ccc;">
<svg width="198" height="100" viewBox="0 0 198 100" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="18" y="18" width="180" height="64" rx="32" fill="white"/>
<rect width="100" height="100" rx="50" fill="white"/>
<path d="M52.1429 65V35H65V65H52.1429ZM35 65V35H47.8571V65H35ZM56.4286 60.7143H60.7143V39.2857H56.4286V60.7143ZM39.2857 60.7143H43.5714V39.2857H39.2857V60.7143Z" fill="#1C1B1F"/>
</svg>
</span>`;
if (this.el()) {
this.el().title = 'Autoplay is off';
this.el().setAttribute('aria-label', 'Autoplay is off');
const controlText = this.el().querySelector('.vjs-control-text');
if (controlText) controlText.textContent = 'Autoplay is off';
console.log('✓ Autoplay tooltip updated to: "Autoplay is off"');
if (this.el()) {
this.el().title = 'Autoplay is off';
this.el().setAttribute('aria-label', 'Autoplay is off');
const controlText = this.el().querySelector('.vjs-control-text');
if (controlText) controlText.textContent = 'Autoplay is off';
}
}
}
// Fade back in
this.iconSpan.style.opacity = '1';
}, 100);
}
// Fade back in
this.iconSpan.style.opacity = '1';
}, 100);
}
handleClick() {
// Toggle autoplay state
@ -116,15 +106,11 @@ class AutoplayToggleButton extends Button {
// Save preference if userPreferences is available
if (this.userPreferences) {
this.userPreferences.setAutoplayPreference(this.isAutoplayEnabled);
console.log('Autoplay preference saved to localStorage:', this.isAutoplayEnabled);
}
// Update icon and accessibility attributes
this.updateIcon();
console.log('Autoplay toggled:', this.isAutoplayEnabled ? 'ON' : 'OFF');
console.log('✓ Tooltip should now show:', this.isAutoplayEnabled ? 'Autoplay is on' : 'Autoplay is off');
// Trigger custom event for other components to listen to
this.player().trigger('autoplayToggle', { autoplay: this.isAutoplayEnabled });
}
@ -141,38 +127,38 @@ class AutoplayToggleButton extends Button {
let touchHandled = false;
// Touch start
button.addEventListener('touchstart', (e) => {
touchStartTime = Date.now();
touchHandled = false;
}, { passive: true });
button.addEventListener(
'touchstart',
(e) => {
touchStartTime = Date.now();
touchHandled = false;
},
{ passive: true }
);
// Touch end
button.addEventListener('touchend', (e) => {
const touchDuration = Date.now() - touchStartTime;
// Only show tooltip for quick taps (not swipes)
if (touchDuration < 500) {
e.preventDefault();
e.stopPropagation();
// Show tooltip
button.classList.add('touch-active');
touchHandled = true;
// Hide tooltip after delay
setTimeout(() => {
button.classList.remove('touch-active');
}, 2000);
}
}, { passive: false });
button.addEventListener(
'touchend',
(e) => {
const touchDuration = Date.now() - touchStartTime;
// Click fallback for desktop
button.addEventListener('click', (e) => {
if (!touchHandled) {
// This is a desktop click, tooltip will show on hover
console.log('Desktop click on autoplay button');
}
});
// Only show tooltip for quick taps (not swipes)
if (touchDuration < 500) {
e.preventDefault();
e.stopPropagation();
// Show tooltip
button.classList.add('touch-active');
touchHandled = true;
// Hide tooltip after delay
setTimeout(() => {
button.classList.remove('touch-active');
}, 2000);
}
},
{ passive: false }
);
}
}

View File

@ -47,7 +47,6 @@ class CustomChaptersOverlay extends Component {
createOverlay() {
if (!this.chaptersData || this.chaptersData.length === 0) {
console.log('⚠ No chapters data available for overlay');
return;
}
@ -232,8 +231,6 @@ class CustomChaptersOverlay extends Component {
playerEl.appendChild(this.overlay);
this.player().on('timeupdate', this.updateCurrentChapter);
console.log('✓ Custom chapters overlay created');
}
setupChaptersButton() {

View File

@ -79,15 +79,6 @@ class CustomRemainingTime extends Component {
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
*/

View File

@ -363,32 +363,19 @@ class CustomSettingsMenu extends Component {
const player = this.player();
const tracks = player.textTracks();
let currentSubtitleLabel = 'Off';
let activeTrack = null;
// Find the active subtitle track
for (let i = 0; i < tracks.length; i++) {
const t = tracks[i];
if (t.kind === 'subtitles' && t.mode === 'showing') {
currentSubtitleLabel = t.label || t.language || 'Subtitles';
activeTrack = t;
break;
}
}
const currentSubtitlesDisplay = this.settingsOverlay.querySelector('.current-subtitles');
if (currentSubtitlesDisplay) {
const oldValue = currentSubtitlesDisplay.textContent;
currentSubtitlesDisplay.textContent = currentSubtitleLabel;
// Only log if the value actually changed
if (oldValue !== currentSubtitleLabel) {
console.log(`Updated current subtitle display: "${oldValue}" → "${currentSubtitleLabel}"`);
if (activeTrack) {
console.log(
`Active track details: language="${activeTrack.language}", label="${activeTrack.label}", mode="${activeTrack.mode}"`
);
}
}
}
} catch (error) {
console.error('Error updating current subtitle display:', error);
@ -402,7 +389,6 @@ class CustomSettingsMenu extends Component {
// Listen for real-time subtitle changes
this.player().on('texttrackchange', () => {
console.log('Text track changed - updating subtitle display');
this.updateCurrentSubtitleDisplay();
// Also refresh the subtitle submenu to show correct selection
this.refreshSubtitlesSubmenu();
@ -516,7 +502,6 @@ class CustomSettingsMenu extends Component {
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);
}
}
@ -872,8 +857,6 @@ class CustomSettingsMenu extends Component {
// Close only the speed submenu (keep overlay open)
this.speedSubmenu.style.display = 'none';
console.log('Playback speed preference saved:', speed);
}
handleQualityChange(value, qualityOption) {
@ -1063,8 +1046,6 @@ class CustomSettingsMenu extends Component {
// Close only the quality submenu (keep overlay open)
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
console.log('Quality preference saved:', value);
}
handleSubtitleChange(lang, optionEl) {

View File

@ -24,7 +24,7 @@ class NextVideoButton extends Button {
// Create SVG that matches Video.js icon dimensions
iconSpan.innerHTML = `
<svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<svg width="34" height="34" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M14 34L28.1667 24L14 14V34ZM30.6667 14V34H34V14H30.6667Z" fill="currentColor"/>
</svg>
@ -44,7 +44,6 @@ class NextVideoButton extends Button {
}
handleClick() {
// console.log('NextVideoButton handleClick', this.nextLink);
this.player().trigger('nextVideo');
}
}

View File

@ -49,7 +49,7 @@ class ChapterMarkers extends Component {
this.chaptersData.push({
startTime: cue.startTime,
endTime: cue.endTime,
chapterTitle: cue.chapterTitle,
chapterTitle: cue.text,
});
}
@ -217,17 +217,17 @@ class ChapterMarkers extends Component {
// Update text content without rebuilding DOM
this.chapterTitle.textContent = currentChapter.chapterTitle;
this.chapterInfo.textContent = `Chapter: ${startTime} - ${endTime}`;
this.chapterInfo.textContent = `${startTime} - ${endTime}`;
// this.positionInfo.textContent = `Position: ${timeAtPosition}`;
// Update sprite thumbnail
this.updateSpriteThumbnail(currentTime);
this.chapterImage.style.display = 'block';
} else {
const timeAtPosition = this.formatTime(currentTime);
this.chapterTitle.textContent = 'No Chapter';
// const timeAtPosition = this.formatTime(currentTime);
this.chapterTitle.textContent = '';
this.chapterInfo.textContent = '';
this.positionInfo.textContent = `Position: ${timeAtPosition}`;
// this.positionInfo.textContent = `Position: ${timeAtPosition}`;
// Still show sprite thumbnail even when not in a chapter
this.updateSpriteThumbnail(currentTime);
@ -274,7 +274,6 @@ class ChapterMarkers extends Component {
if (!this.previewSprite || !this.previewSprite.url) {
// Hide image if no sprite data available
this.chapterImage.style.display = 'none';
console.log('No sprite data available:', this.previewSprite);
return;
}
@ -304,10 +303,6 @@ class ChapterMarkers extends Component {
const xPos = -(frameCol * width);
const yPos = -(frameRow * height);
console.log(
`Time: ${currentTime}s, Duration: ${this.player().duration()}s, Interval: ${frameInterval}s, Frame: ${frameIndex}/${maxFrames - 1}, Row: ${frameRow}, Col: ${frameCol}, Pos: ${xPos}px ${yPos}px, URL: ${url}`
);
// Apply sprite background
this.chapterImage.style.backgroundImage = `url("${url}")`;
this.chapterImage.style.backgroundPosition = `${xPos}px ${yPos}px`;
@ -321,7 +316,6 @@ class ChapterMarkers extends Component {
if (frameIndex >= 3 && currentTime > 30) {
const fallbackYPos = -(2 * height); // Frame 2 (20-30s range)
this.chapterImage.style.backgroundPosition = `${xPos}px ${fallbackYPos}px`;
console.log(`Fallback: Using frame 2 instead of frame ${frameIndex} for time ${currentTime}s`);
}
}

View File

@ -33,7 +33,6 @@ class SpritePreview extends Component {
// Only setup if we have sprite data
if (!this.previewSprite || !this.previewSprite.url) {
console.log('No sprite data available for preview:', this.previewSprite);
return;
}
@ -46,23 +45,23 @@ class SpritePreview extends Component {
// Style the floating tooltip
Object.assign(this.tooltip.style, {
position: 'absolute',
position: 'absolute',
zIndex: '1000',
bottom: '45px',
transform: 'translateX(-50%)',
display: 'none',
minWidth: '172px', // Accommodate 166px image + 3px border on each side
maxWidth: '172px',
width: '172px',
width: '172px',
});
// Create stable DOM structure
this.spriteImage = videojs.dom.createEl('div', {
className: 'sprite-image-preview',
});
Object.assign(this.spriteImage.style, {
Object.assign(this.spriteImage.style, {
display: 'block',
overflow: 'hidden',
overflow: 'hidden',
});
// Append sprite image to tooltip (no time info)
@ -161,7 +160,6 @@ class SpritePreview extends Component {
if (!this.previewSprite || !this.previewSprite.url) {
// Hide image if no sprite data available
this.spriteImage.style.display = 'none';
console.log('No sprite data available:', this.previewSprite);
return;
}
@ -191,10 +189,6 @@ class SpritePreview extends Component {
const xPos = -(frameCol * width);
const yPos = -(frameRow * height);
console.log(
`Sprite Preview - Time: ${currentTime}s, Duration: ${this.player().duration()}s, Interval: ${frameInterval}s, Frame: ${frameIndex}/${maxFrames - 1}, Row: ${frameRow}, Col: ${frameCol}, Pos: ${xPos}px ${yPos}px, URL: ${url}`
);
// Apply sprite background
this.spriteImage.style.backgroundImage = `url("${url}")`;
this.spriteImage.style.backgroundPosition = `${xPos}px ${yPos}px`;
@ -211,7 +205,6 @@ class SpritePreview extends Component {
if (frameIndex >= 3 && currentTime > 30) {
const fallbackYPos = -(2 * height); // Frame 2 (20-30s range)
this.spriteImage.style.backgroundPosition = `${xPos}px ${fallbackYPos}px`;
console.log(`Fallback: Using frame 2 instead of frame ${frameIndex} for time ${currentTime}s`);
}
}

View File

@ -118,8 +118,6 @@ class AutoplayCountdownOverlay extends Component {
this.handlePlayNext();
}
}, 1000);
console.log('Autoplay countdown started:', this.countdownSeconds, 'seconds');
}
stopCountdown() {
@ -129,7 +127,6 @@ class AutoplayCountdownOverlay extends Component {
this.countdownInterval = null;
}
this.hide();
console.log('Autoplay countdown stopped');
}
updateCountdownDisplay() {
@ -140,7 +137,6 @@ class AutoplayCountdownOverlay extends Component {
}
handlePlayNext() {
console.log('Autoplay: Playing next video immediately');
try {
this.stopCountdown();
this.onPlayNext();
@ -150,7 +146,6 @@ class AutoplayCountdownOverlay extends Component {
}
handleCancel() {
console.log('Autoplay: Cancelled by user');
try {
this.stopCountdown();
this.onCancel();

View File

@ -14,12 +14,6 @@ class EndScreenOverlay extends Component {
// Now set the instance property after super() completes
this.relatedVideos = options && options.relatedVideos ? options.relatedVideos : [];
// console.log(
// 'EndScreenOverlay created with',
// this.relatedVideos.length,
// 'related videos'
// );
}
createEl() {
@ -30,12 +24,6 @@ class EndScreenOverlay extends Component {
const maxVideos = this.getMaxVideosForScreen();
const videosToShow = relatedVideos.slice(0, maxVideos);
// console.log(
// 'Creating end screen with',
// videosToShow.length,
// 'related videos'
// );
const overlay = super.createEl('div', {
className: 'vjs-end-screen-overlay',
});
@ -79,7 +67,7 @@ class EndScreenOverlay extends Component {
// Use real YouTube thumbnail or fallback to placeholder
const thumbnailSrc = video.thumbnail || this.getPlaceholderImage(video.title);
const thumbnail = videojs.dom.createEl('img', {
className: 'vjs-related-video-thumbnail',
src: thumbnailSrc,
@ -88,7 +76,7 @@ class EndScreenOverlay extends Component {
onerror: () => {
// Fallback to placeholder if image fails to load
thumbnail.src = this.getPlaceholderImage(video.title);
}
},
});
const overlay = videojs.dom.createEl('div', {
@ -158,10 +146,18 @@ class EndScreenOverlay extends Component {
// Generate a placeholder image using a service or create a data URL
// For now, we'll use a simple colored placeholder based on the title
const colors = [
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'
'#FF6B6B',
'#4ECDC4',
'#45B7D1',
'#96CEB4',
'#FFEAA7',
'#DDA0DD',
'#98D8C8',
'#F7DC6F',
'#BB8FCE',
'#85C1E9',
];
// Use title hash to consistently assign colors
let hash = 0;
for (let i = 0; i < title.length; i++) {
@ -169,47 +165,47 @@ class EndScreenOverlay extends Component {
}
const colorIndex = Math.abs(hash) % colors.length;
const color = colors[colorIndex];
// Create a simple placeholder with the first letter of the title
const firstLetter = title.charAt(0).toUpperCase();
// Create a data URL for a simple placeholder image
const canvas = document.createElement('canvas');
canvas.width = 320;
canvas.height = 180;
const ctx = canvas.getContext('2d');
// Background
ctx.fillStyle = color;
ctx.fillRect(0, 0, 320, 180);
// Add a subtle pattern
ctx.fillStyle = 'rgba(255, 255, 255, 0.1)';
for (let i = 0; i < 20; i++) {
ctx.fillRect(Math.random() * 320, Math.random() * 180, 2, 2);
}
// Add the first letter
ctx.fillStyle = 'white';
ctx.font = 'bold 48px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(firstLetter, 160, 90);
return canvas.toDataURL();
}
getMaxVideosForScreen() {
const width = window.innerWidth;
if (width >= 1200) {
return 12; // 4x3 grid for large desktop
} else if (width >= 1024) {
return 9; // 3x3 grid for desktop
return 9; // 3x3 grid for desktop
} else if (width >= 768) {
return 6; // 3x2 grid for tablet
return 6; // 3x2 grid for tablet
} else {
return 4; // 2x2 grid for mobile
return 4; // 2x2 grid for mobile
}
}
@ -221,7 +217,7 @@ class EndScreenOverlay extends Component {
author: 'Bro Code',
views: '2.1M views',
duration: 1800,
thumbnail: 'https://img.youtube.com/vi/dGcsHMXbSOA/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/dGcsHMXbSOA/maxresdefault.jpg',
},
{
id: 'sample2',
@ -229,7 +225,7 @@ class EndScreenOverlay extends Component {
author: 'Tech Tutorials',
views: '850K views',
duration: 1200,
thumbnail: 'https://img.youtube.com/vi/WZQc7RUAg18/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/WZQc7RUAg18/maxresdefault.jpg',
},
{
id: 'sample3',
@ -237,7 +233,7 @@ class EndScreenOverlay extends Component {
author: 'Web Dev Academy',
views: '1.2M views',
duration: 2400,
thumbnail: 'https://img.youtube.com/vi/0xMQfnTU6oo/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/0xMQfnTU6oo/maxresdefault.jpg',
},
{
id: 'sample4',
@ -245,7 +241,7 @@ class EndScreenOverlay extends Component {
author: 'Code Master',
views: '650K views',
duration: 3600,
thumbnail: 'https://img.youtube.com/vi/fBNz6F-Cowg/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/fBNz6F-Cowg/maxresdefault.jpg',
},
{
id: 'sample5',
@ -253,7 +249,7 @@ class EndScreenOverlay extends Component {
author: 'Frontend Pro',
views: '980K views',
duration: 2800,
thumbnail: 'https://img.youtube.com/vi/qZXt1Aom3Cs/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/qZXt1Aom3Cs/maxresdefault.jpg',
},
{
id: 'sample6',
@ -261,7 +257,7 @@ class EndScreenOverlay extends Component {
author: 'Data Academy',
views: '1.5M views',
duration: 4200,
thumbnail: 'https://img.youtube.com/vi/ua-CiDNNj30/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/ua-CiDNNj30/maxresdefault.jpg',
},
{
id: 'sample7',
@ -269,7 +265,7 @@ class EndScreenOverlay extends Component {
author: 'TypeScript Expert',
views: '720K views',
duration: 2100,
thumbnail: 'https://img.youtube.com/vi/BwuLxPH8IDs/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/BwuLxPH8IDs/maxresdefault.jpg',
},
{
id: 'sample8',
@ -277,7 +273,7 @@ class EndScreenOverlay extends Component {
author: 'Database Pro',
views: '890K views',
duration: 1800,
thumbnail: 'https://img.youtube.com/vi/-56x56UppqQ/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/-56x56UppqQ/maxresdefault.jpg',
},
{
id: 'sample9',
@ -285,7 +281,7 @@ class EndScreenOverlay extends Component {
author: 'DevOps Master',
views: '1.1M views',
duration: 3200,
thumbnail: 'https://img.youtube.com/vi/pTFZFxd4hOI/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/pTFZFxd4hOI/maxresdefault.jpg',
},
{
id: 'sample10',
@ -293,7 +289,7 @@ class EndScreenOverlay extends Component {
author: 'Cloud Expert',
views: '1.3M views',
duration: 4500,
thumbnail: 'https://img.youtube.com/vi/ITcXLS3h2qU/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/ITcXLS3h2qU/maxresdefault.jpg',
},
{
id: 'sample11',
@ -301,7 +297,7 @@ class EndScreenOverlay extends Component {
author: 'API Specialist',
views: '680K views',
duration: 2600,
thumbnail: 'https://img.youtube.com/vi/ed8SzALpx1Q/maxresdefault.jpg'
thumbnail: 'https://img.youtube.com/vi/ed8SzALpx1Q/maxresdefault.jpg',
},
{
id: 'sample12',
@ -309,8 +305,8 @@ class EndScreenOverlay extends Component {
author: 'AI Academy',
views: '2.3M views',
duration: 5400,
thumbnail: 'https://img.youtube.com/vi/i_LwzRVP7bg/maxresdefault.jpg'
}
thumbnail: 'https://img.youtube.com/vi/i_LwzRVP7bg/maxresdefault.jpg',
},
];
}

View File

@ -26,9 +26,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// Environment-based development mode configuration
const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app');
console.log('isDevMode', isDevMode);
console.log('window.location.hostname', window.location.hostname);
// Safely access window.MEDIA_DATA with fallback using useMemo
const mediaData = useMemo(
() =>
@ -40,7 +37,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
poster_url:
'https://demo.mediacms.io/media/original/thumbnails/user/markos/7dedcb56bde9463dbc0766768a99be0f_C8E5GFY.20250605_110647.mp4.jpg',
chapter_data: [
{ startTime: '00:00:00.000', endTime: '00:00:24.295', chapterTitle: 'A1 test' },
{ startTime: '00:00:00.000', endTime: '00:00:08.295', chapterTitle: 'A1 test' },
{ startTime: '00:00:24.295', endTime: '00:00:48.590', chapterTitle: 'A2 of Marine Life' },
{
startTime: '00:00:48.590',
@ -1052,6 +1049,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
siteUrl: '',
nextLink: 'https://demo.mediacms.io/view?m=YjGJafibO',
urlAutoplay: true,
urlMuted: false,
},
[]
);
@ -1083,14 +1081,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
return hours * 3600 + minutes * 60 + seconds;
};
// Test the conversion function
if (isDevMode) {
console.log('Testing time conversion:');
console.log('00:00:24.295 ->', convertTimeStringToSeconds('00:00:24.295')); // Should be 24.295
console.log('00:01:30.500 ->', convertTimeStringToSeconds('00:01:30.500')); // Should be 90.5
console.log('01:00:00.000 ->', convertTimeStringToSeconds('01:00:00.000')); // Should be 3600
}
// Convert chapters data from backend format to required format with memoization
const convertChaptersData = useMemo(() => {
return (rawChaptersData) => {
@ -1098,8 +1088,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
return [];
}
console.log('Converting raw chapters data:', rawChaptersData);
const convertedData = rawChaptersData.map((chapter) => ({
startTime: convertTimeStringToSeconds(chapter.startTime),
endTime: convertTimeStringToSeconds(chapter.endTime),
@ -1178,26 +1166,80 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
return 'video/mp4';
};
// Get user's quality preference for dependency tracking
const userQualityPreference = userPreferences.current.getQualityPreference();
// Get video data from mediaData
const currentVideo = useMemo(() => {
// Get video sources based on available data
// Get video sources based on available data and user preferences
const getVideoSources = () => {
// Use the extracted quality preference
const userQuality = userQualityPreference;
// Check if HLS info is available and not empty
if (mediaData.data?.hls_info && mediaData.data.hls_info.master_file) {
// Use master file as the primary source (auto quality)
return [
{
src: mediaData.siteUrl + mediaData.data.hls_info.master_file,
type: 'application/x-mpegURL', // HLS MIME type
label: 'Auto',
},
];
if (mediaData.data?.hls_info) {
// If user prefers auto quality or master file doesn't exist for specific quality
if (userQuality === 'auto' && mediaData.data.hls_info.master_file) {
return [
{
src: mediaData.siteUrl + mediaData.data.hls_info.master_file,
type: 'application/x-mpegURL', // HLS MIME type
label: 'Auto',
},
];
}
// If user has selected a specific quality, try to use that playlist
if (userQuality !== 'auto') {
const qualityKey = `${userQuality.replace('p', '')}_playlist`;
if (mediaData.data.hls_info[qualityKey]) {
return [
{
src: mediaData.data.hls_info[qualityKey],
type: 'application/x-mpegURL', // HLS MIME type
label: `${userQuality}p`,
},
];
}
}
// Fallback to master file if specific quality not available
if (mediaData.data.hls_info.master_file) {
return [
{
src: mediaData.siteUrl + mediaData.data.hls_info.master_file,
type: 'application/x-mpegURL', // HLS MIME type
label: 'Auto',
},
];
}
}
// Fallback to encoded qualities if available
if (mediaData.data?.encodings_info) {
const sources = [];
const encodings = mediaData.data.encodings_info;
const userQuality = userQualityPreference;
// If user has selected a specific quality, try to use that encoding first
if (userQuality !== 'auto') {
const qualityNumber = userQuality.replace('p', ''); // Remove 'p' from '240p' -> '240'
if (
encodings[qualityNumber] &&
encodings[qualityNumber].h264 &&
encodings[qualityNumber].h264.url
) {
return [
{
src: encodings[qualityNumber].h264.url,
type: getMimeType(encodings[qualityNumber].h264.url, mediaData.data?.media_type),
label: `${qualityNumber}p`,
},
];
}
}
// If auto quality or specific quality not available, return all available qualities
const sources = [];
// Get available qualities dynamically from encodings_info
const availableQualities = Object.keys(encodings)
@ -1231,14 +1273,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// Default sample video
return [
/* {
src: '/videos/sample-video.mp4',
type: 'video/mp4',
}, */
{
src: '/videos/sample-video-white.mp4',
type: 'video/mp4',
},
/* {
src: '/videos/sample-video.mp3',
type: 'audio/mpeg',
},
}, */
];
};
@ -1250,9 +1292,10 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
related_media: mediaData.data?.related_media || [],
nextLink: mediaData?.nextLink || null,
urlAutoplay: mediaData?.urlAutoplay || true,
urlMuted: mediaData?.urlMuted || false,
sources: getVideoSources(),
};
}, [mediaData]);
}, [mediaData, userQualityPreference]);
// Compute available qualities. Prefer JSON (mediaData.data.qualities), otherwise build from encodings_info or current source.
const availableQualities = useMemo(() => {
@ -1446,14 +1489,8 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
}))
: [];
console.log('mediaData?.data?.thumbnail_time', mediaData?.data?.thumbnail_time);
console.log('mediaData?.data?.sprites_url', mediaData?.data?.sprites_url);
console.log('mediaData', mediaData);
// Function to navigate to next video
const goToNextVideo = () => {
console.log('Next video functionality disabled for single video mode');
if (mediaData.onClickNextCallback && typeof mediaData.onClickNextCallback === 'function') {
mediaData.onClickNextCallback();
}
@ -1464,7 +1501,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
if (videoRef.current && !playerRef.current) {
// Check if element is already a Video.js player
if (videoRef.current.player) {
// console.log('Video.js already initialized on this element');
return;
}
@ -1478,8 +1514,8 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
controls: true,
// Player dimensions - removed for responsive design
// Autoplay behavior: Use 'muted' to comply with browser policies
autoplay: 'muted', // Auto-start muted to comply with browser policies (true/false, play, muted, any)
// Autoplay behavior: Try unmuted first, fallback to muted if needed
autoplay: true, // Try unmuted autoplay first (true/false, play, muted, any)
// Start video over when it ends
loop: false,
@ -1741,9 +1777,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
});
// Event listeners
/* playerRef.current.on('ready', () => {
console.log('Video.js player ready');
}); */
playerRef.current.ready(() => {
// Apply user preferences to player
userPreferences.current.applyToPlayer(playerRef.current);
@ -1788,15 +1821,89 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
}
}
// Handle URL autoplay parameter or auto-start on page load
// Detect if user has interacted with the page
const hasUserInteracted = () => {
// Check various indicators of user interaction
return (
document.hasFocus() ||
document.visibilityState === 'visible' ||
sessionStorage.getItem('userInteracted') === 'true'
);
};
// Handle autoplay while respecting user's saved preferences
const handleAutoplay = async () => {
const userInteracted = hasUserInteracted();
const savedMuteState = userPreferences.current.getPreference('muted');
try {
// Respect user's saved mute preference, but try unmuted if user interacted and hasn't explicitly muted
if (!mediaData.urlMuted && userInteracted && savedMuteState !== true) {
playerRef.current.muted(false);
}
// First attempt: try to play with current mute state
await playerRef.current.play();
} catch (error) {
// Fallback to muted autoplay unless user explicitly wants to stay unmuted
if (!playerRef.current.muted()) {
try {
playerRef.current.muted(true);
await playerRef.current.play();
// Only try to restore sound if user hasn't explicitly saved mute=true
if (savedMuteState !== true) {
// Aggressively try to restore sound
const restoreSound = () => {
if (playerRef.current && !playerRef.current.isDisposed()) {
playerRef.current.muted(false);
playerRef.current.trigger('notify', '🔊 Sound enabled!');
}
};
// Try to restore sound immediately if user has interacted
if (userInteracted) {
setTimeout(restoreSound, 100);
} else {
// Show notification for manual interaction
setTimeout(() => {
if (playerRef.current && !playerRef.current.isDisposed()) {
playerRef.current.trigger(
'notify',
'🔇 Click anywhere to enable sound'
);
}
}, 1000);
// Set up interaction listeners
const enableSound = () => {
restoreSound();
// Mark user interaction for future videos
sessionStorage.setItem('userInteracted', 'true');
// Remove listeners
document.removeEventListener('click', enableSound);
document.removeEventListener('keydown', enableSound);
document.removeEventListener('touchstart', enableSound);
};
document.addEventListener('click', enableSound, { once: true });
document.addEventListener('keydown', enableSound, { once: true });
document.addEventListener('touchstart', enableSound, { once: true });
}
}
} catch (mutedError) {
console.error('❌ Even muted autoplay was blocked:', mutedError.message);
}
}
}
};
if (mediaData?.urlAutoplay) {
playerRef.current.play();
// Explicit autoplay requested via URL parameter
handleAutoplay();
} else {
// Auto-start video on page load/reload (muted to comply with browser policies)
playerRef.current.play().catch((error) => {
console.log(' Browser prevented autoplay (normal behavior):', error.message);
// Fallback: ensure video is ready to play when user interacts
});
// Auto-start video on page load/reload with fallback strategy
handleAutoplay();
}
const setupMobilePlayPause = () => {
@ -1857,18 +1964,28 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
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');
if (hasVideoParam) {
// Small delay to ensure everything is loaded
setTimeout(() => {
setTimeout(async () => {
if (playerRef.current && !playerRef.current.isDisposed()) {
playerRef.current.play().catch((error) => {
console.log(' Browser prevented autoplay (normal behavior):', error.message);
});
try {
await playerRef.current.play();
} catch (error) {
console.error(' Browser prevented play:', error.message);
// Try muted playback as fallback
if (!playerRef.current.muted()) {
try {
playerRef.current.muted(true);
await playerRef.current.play();
} catch (mutedError) {
console.error(' Even muted play was blocked:', mutedError.message);
}
}
}
}
}, 100);
}
@ -1918,7 +2035,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
const cue = new (window.VTTCue || window.TextTrackCue)(
chapter.startTime,
chapter.endTime,
chapter.text
chapter.chapterTitle
);
chaptersTrack.addCue(cue);
});
@ -1965,9 +2082,11 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
const autoplayToggleButton = new AutoplayToggleButton(playerRef.current, {
userPreferences: userPreferences.current,
});
// Add it after the play button
const fullscreenToggleIndex = controlBar.children().indexOf(fullscreenToggle);
controlBar.addChild(autoplayToggleButton, {}, fullscreenToggleIndex - 1);
// Add it before the chapters button (or at a suitable position)
const chaptersButtonIndex = controlBar.children().indexOf(chaptersButton);
const insertIndex =
chaptersButtonIndex > 0 ? chaptersButtonIndex : controlBar.children().length - 3;
controlBar.addChild(autoplayToggleButton, {}, insertIndex);
// Store reference for later use
customComponents.current.autoplayToggleButton = autoplayToggleButton;
@ -1975,62 +2094,13 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// Force update icon after adding to DOM to ensure correct display
setTimeout(() => {
autoplayToggleButton.updateIcon();
console.log('✓ Autoplay toggle button icon updated after DOM insertion');
}, 100);
console.log('✓ Autoplay toggle button added successfully');
} catch (error) {
console.error('✗ Failed to add autoplay toggle button:', error);
}
}
// END: Implement autoplay toggle button
// Remove duplicate captions button and move chapters to end
/* const cleanupControls = () => {
// Log all current children for debugging
const allChildren = controlBar.children();
// Try to find and remove captions/subs-caps button (but keep subtitles)
const possibleCaptionButtons = ['captionsButton', 'subsCapsButton'];
possibleCaptionButtons.forEach((buttonName) => {
const button = controlBar.getChild(buttonName);
if (button) {
try {
controlBar.removeChild(button);
console.log(`✓ Removed ${buttonName}`);
} catch (e) {
console.log(`✗ Failed to remove ${buttonName}:`, e);
}
}
});
// Alternative: hide buttons we can't remove
allChildren.forEach((child, index) => {
const name = (child.name_ || child.constructor.name || '').toLowerCase();
if (name.includes('caption') && !name.includes('subtitle')) {
child.hide();
console.log(`✓ Hidden button at index ${index}: ${name}`);
}
});
// Move chapters button to the very end
const chaptersButton = controlBar.getChild('chaptersButton');
if (chaptersButton) {
try {
controlBar.removeChild(chaptersButton);
controlBar.addChild(chaptersButton);
console.log('✓ Chapters button moved to last position');
} catch (e) {
console.log('✗ Failed to move chapters button:', e);
}
}
}; */
// Try multiple times with different delays
/* setTimeout(cleanupControls, 200);
setTimeout(cleanupControls, 500);
setTimeout(cleanupControls, 1000); */
// Make menus clickable instead of hover-only
setTimeout(() => {
const setupClickableMenus = () => {
@ -2057,8 +2127,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
this.menu.show();
}
});
console.log(`✓ Made ${buttonName} clickable`);
} else if (button) {
// For buttons without menuButton_ property
const buttonEl = button.el();
@ -2081,8 +2149,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
}
}
});
console.log(`✓ Added click handler to ${buttonName}`);
}
}
});
@ -2232,27 +2298,17 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// BEGIN: Add chapter markers and sprite preview to progress control
if (progressControl && seekBar) {
console.log('Setting up sprite preview and chapter markers...');
console.log('mediaData.previewSprite:', mediaData.previewSprite);
console.log('chaptersData:', chaptersData);
// Check if we have chapters
const hasChapters = chaptersData && chaptersData.length > 0;
if (hasChapters) {
// Use original ChapterMarkers with sprite functionality when chapters exist
console.log(
'✓ Adding ChapterMarkers component with sprite functionality (chapters exist)'
);
const chapterMarkers = new ChapterMarkers(playerRef.current, {
previewSprite: mediaData.previewSprite,
});
seekBar.addChild(chapterMarkers);
} else if (mediaData.previewSprite) {
// Use separate SpritePreview component only when no chapters but sprite data exists
console.log(
'✓ Adding SpritePreview component (no chapters, but sprite data available)'
);
const spritePreview = new SpritePreview(playerRef.current, {
previewSprite: mediaData.previewSprite,
});
@ -2260,19 +2316,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
// Setup sprite preview hover functionality
setTimeout(() => {
console.log('✓ Setting up sprite preview hover functionality');
spritePreview.setupProgressBarHover();
}, 100);
} else {
console.log('✗ No chapters and no sprite data available');
}
}
// END: Add chapter markers and sprite preview to progress control
// BEGIN: Simple button layout fix - use CSS approach
setTimeout(() => {
console.log('Setting up simplified button layout...');
// Add a simple spacer div using DOM manipulation (simpler approach)
const spacerDiv = document.createElement('div');
spacerDiv.className = 'vjs-spacer-control vjs-control';
@ -2287,22 +2338,32 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
const durationEl = durationDisplay.el();
const nextSibling = durationEl.nextSibling;
controlBarEl.insertBefore(spacerDiv, nextSibling);
console.log('✓ Simple spacer added after duration display');
}
}, 300);
// END: Simple button layout fix
// BEGIN: Move chapters button after fullscreen toggle
if (chaptersButton && fullscreenToggle) {
// BEGIN: Move Picture-in-Picture and Fullscreen buttons to the very end
setTimeout(() => {
try {
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
controlBar.addChild(chaptersButton, {}, fullscreenIndex + 1);
console.log('✓ Chapters button moved after fullscreen toggle');
const pictureInPictureToggle = controlBar.getChild('pictureInPictureToggle');
const fullscreenToggle = controlBar.getChild('fullscreenToggle');
// Move Picture-in-Picture button to the very end (if it exists)
if (pictureInPictureToggle) {
controlBar.removeChild(pictureInPictureToggle);
controlBar.addChild(pictureInPictureToggle);
}
// Move Fullscreen button to the very end (after PiP)
if (fullscreenToggle) {
controlBar.removeChild(fullscreenToggle);
controlBar.addChild(fullscreenToggle);
}
} catch (e) {
console.log('✗ Failed to move chapters button:', e);
console.error('✗ Failed to move PiP/Fullscreen buttons to end:', e);
}
}
// END: Move chapters button after fullscreen toggle
}, 100);
// END: Move Picture-in-Picture and Fullscreen buttons to the very end
// BEGIN: Add Chapters Overlay Component
if (chaptersData && chaptersData.length > 0) {
@ -2312,8 +2373,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
channelName: 'Chapter',
thumbnail: mediaData?.data?.thumbnail_url || mediaData?.data?.author_thumbnail || '',
});
} else {
console.log('⚠ No chapters data available for overlay');
}
// END: Add Chapters Overlay Component
@ -2399,199 +2458,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
};
// END: Add custom arrow key seek functionality
// Log current user preferences
console.log('Current user preferences:', userPreferences.current.getPreferences());
window.debugSubtitles = {
showTracks: () => {
const textTracks = playerRef.current.textTracks();
console.log('=== Available Text Tracks ===');
for (let i = 0; i < textTracks.length; i++) {
const track = textTracks[i];
console.log(
`${i}: ${track.kind} | ${track.language} | ${track.label} | mode: ${track.mode}`
);
}
},
enableEnglish: () => {
const textTracks = playerRef.current.textTracks();
for (let i = 0; i < textTracks.length; i++) {
const track = textTracks[i];
if (track.kind === 'subtitles' && track.language === 'en') {
track.mode = 'showing';
console.log('Enabled English subtitles');
break;
}
}
},
enableGreek: () => {
const textTracks = playerRef.current.textTracks();
for (let i = 0; i < textTracks.length; i++) {
const track = textTracks[i];
if (track.kind === 'subtitles' && track.language === 'el') {
track.mode = 'showing';
console.log('Enabled Greek subtitles');
break;
}
}
},
disableAll: () => {
const textTracks = playerRef.current.textTracks();
for (let i = 0; i < textTracks.length; i++) {
const track = textTracks[i];
if (track.kind === 'subtitles') {
track.mode = 'disabled';
}
}
console.log('Disabled all subtitles');
},
getPrefs: () => {
console.log('Saved preferences:', userPreferences.current.getPreferences());
},
reapplyPrefs: () => {
userPreferences.current.applySubtitlePreference(playerRef.current);
},
showMenu: () => {
const controlBar = playerRef.current.getChild('controlBar');
// Try different button names
const possibleNames = ['subtitlesButton', 'captionsButton', 'subsCapsButton'];
let subtitlesButton = null;
for (const name of possibleNames) {
const button = controlBar.getChild(name);
if (button) {
console.log(`Found subtitle button: ${name}`);
subtitlesButton = button;
break;
}
}
if (subtitlesButton && subtitlesButton.menu) {
console.log('=== Subtitle Menu Items ===');
subtitlesButton.menu.children_.forEach((item, index) => {
if (item.track) {
console.log(
`${index}: ${item.track.label} (${item.track.language}) - selected: ${item.selected()}`
);
} else {
console.log(
`${index}: ${item.label || 'Unknown'} - selected: ${item.selected()}`
);
}
});
} else {
console.log('No subtitle menu found, checking DOM...');
// Check DOM for subtitle menu items
const menuItems = playerRef.current.el().querySelectorAll('.vjs-menu-item');
console.log(`Found ${menuItems.length} menu items in DOM`);
menuItems.forEach((item, index) => {
if (
item.textContent.toLowerCase().includes('subtitle') ||
item.textContent.toLowerCase().includes('caption') ||
item.textContent.toLowerCase().includes('off')
) {
console.log(
`DOM item ${index}: ${item.textContent} - classes: ${item.className}`
);
}
});
}
},
testMenuClick: (index) => {
const controlBar = playerRef.current.getChild('controlBar');
const possibleNames = ['subtitlesButton', 'captionsButton', 'subsCapsButton'];
let subtitlesButton = null;
for (const name of possibleNames) {
const button = controlBar.getChild(name);
if (button) {
subtitlesButton = button;
break;
}
}
if (subtitlesButton && subtitlesButton.menu && subtitlesButton.menu.children_[index]) {
const menuItem = subtitlesButton.menu.children_[index];
console.log('Simulating click on menu item:', index);
menuItem.handleClick();
} else {
console.log('Menu item not found at index:', index, 'trying DOM approach...');
// Try DOM approach
const menuItems = playerRef.current.el().querySelectorAll('.vjs-menu-item');
const subtitleItems = Array.from(menuItems).filter(
(item) =>
item.textContent.toLowerCase().includes('subtitle') ||
item.textContent.toLowerCase().includes('caption') ||
item.textContent.toLowerCase().includes('off')
);
if (subtitleItems[index]) {
console.log('Clicking DOM element:', subtitleItems[index].textContent);
subtitleItems[index].click();
} else {
console.log('No DOM subtitle item found at index:', index);
}
}
},
forceEnableEnglish: () => {
console.log('Force enabling English subtitles...');
const textTracks = playerRef.current.textTracks();
for (let i = 0; i < textTracks.length; i++) {
const track = textTracks[i];
if (track.kind === 'subtitles') {
track.mode = track.language === 'en' ? 'showing' : 'disabled';
}
}
userPreferences.current.setPreference('subtitleLanguage', 'en');
console.log('English subtitles enabled and saved');
},
watchSubtitleChanges: () => {
console.log('👀 Watching subtitle preference changes...');
const originalSetPreference = userPreferences.current.setPreference;
userPreferences.current.setPreference = function (key, value) {
if (key === 'subtitleLanguage') {
console.log(`🎯 SUBTITLE CHANGE: ${value} at ${new Date().toISOString()}`);
console.trace('Change origin:');
}
return originalSetPreference.call(this, key, value);
};
console.log('Subtitle change monitoring activated');
},
checkRestorationFlag: () => {
console.log('Restoration flag:', userPreferences.current.isRestoringSubtitles);
console.log('Auto-save disabled:', userPreferences.current.subtitleAutoSaveDisabled);
},
forceSaveGreek: () => {
console.log('🚀 Force saving Greek subtitle preference...');
userPreferences.current.forceSetSubtitleLanguage('el');
console.log('Check result:', userPreferences.current.getPreferences());
},
forceSaveEnglish: () => {
console.log('🚀 Force saving English subtitle preference...');
userPreferences.current.forceSetSubtitleLanguage('en');
console.log('Check result:', userPreferences.current.getPreferences());
},
forceSaveNull: () => {
console.log('🚀 Force saving null subtitle preference...');
userPreferences.current.forceSetSubtitleLanguage(null);
console.log('Check result:', userPreferences.current.getPreferences());
},
};
});
// Listen for next video event
playerRef.current.on('nextVideo', () => {
console.log('Next video requested');
goToNextVideo();
});
playerRef.current.on('play', () => {
console.log('Video started playing');
// Only show play indicator if not changing quality
if (!playerRef.current.isChangingQuality && customComponents.current.seekIndicator) {
customComponents.current.seekIndicator.show('play');
@ -2599,7 +2473,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
});
playerRef.current.on('pause', () => {
console.log('Video paused');
// Only show pause indicator if not changing quality
if (!playerRef.current.isChangingQuality && customComponents.current.seekIndicator) {
customComponents.current.seekIndicator.show('pause');
@ -2611,9 +2484,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
let autoplayCountdown = null;
playerRef.current.on('ended', () => {
console.log('Video ended');
console.log('Available relatedVideos:', relatedVideos);
// Keep controls active after video ends
setTimeout(() => {
if (playerRef.current && !playerRef.current.isDisposed()) {
@ -2631,18 +2501,10 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
}
}, 50);
console.log('mediaData.previewSprite', mediaData.previewSprite);
console.log('mediaData.nextLink', mediaData.nextLink);
console.log('userPreferences', userPreferences);
// Check if autoplay is enabled and there's a next video
const isAutoplayEnabled = userPreferences.current.getAutoplayPreference();
const hasNextVideo = mediaData.nextLink !== null;
console.log('isAutoplayEnabled', isAutoplayEnabled);
console.log('hasNextVideo', hasNextVideo);
console.log('isEmbedPlayer', isEmbedPlayer);
if (!isEmbedPlayer && isAutoplayEnabled && hasNextVideo) {
// Get next video data for countdown display - find the next video in related videos
let nextVideoData = {
@ -2680,11 +2542,9 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
nextVideoData: nextVideoData,
countdownSeconds: 5,
onPlayNext: () => {
console.log('Autoplay: Navigating to next video');
goToNextVideo();
},
onCancel: () => {
console.log('Autoplay: User cancelled, showing related videos');
// Hide countdown and show end screen instead
if (autoplayCountdown) {
playerRef.current.removeChild(autoplayCountdown);
@ -2705,7 +2565,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
function showEndScreen() {
// Prevent creating multiple end screens
if (endScreen) {
console.log('End screen already exists, removing previous one');
playerRef.current.removeChild(endScreen);
endScreen = null;
}
@ -2753,35 +2612,23 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
});
playerRef.current.on('error', (error) => {
console.error('Video.js error:', error);
// console.error('Video.js error:', error);
});
playerRef.current.on('fullscreenchange', () => {
console.log('Fullscreen changed:', playerRef.current.isFullscreen());
// console.log('Fullscreen changed:', playerRef.current.isFullscreen());
});
playerRef.current.on('volumechange', () => {
console.log('Volume changed:', playerRef.current.volume(), 'Muted:', playerRef.current.muted());
// console.log('Volume changed:', playerRef.current.volume(), 'Muted:', playerRef.current.muted());
});
playerRef.current.on('ratechange', () => {
console.log('Playback rate changed:', playerRef.current.playbackRate());
// console.log('Playback rate changed:', playerRef.current.playbackRate());
});
playerRef.current.on('texttrackchange', () => {
console.log('Text track changed');
const textTracks = playerRef.current.textTracks();
for (let i = 0; i < textTracks.length; i++) {
console.log(
'Track',
i,
':',
textTracks[i].kind,
textTracks[i].label,
'Mode:',
textTracks[i].mode
);
}
// console.log('Text track changed');
});
// Focus the player element so keyboard controls work
@ -2793,7 +2640,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
const videoElement = playerRef.current.el();
videoElement.setAttribute('tabindex', '0');
videoElement.focus();
console.log('Video player focused for keyboard controls');
// Add custom keyboard event handler for space key
const handleKeyPress = (event) => {
@ -2828,15 +2674,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
document.removeEventListener('keydown', handleKeyPress);
};
}
// Start playing the video immediately if autoplay is enabled
if (playerRef.current.autoplay()) {
playerRef.current.play().catch((error) => {
console.log(' Browser prevented autoplay (normal behavior):', error.message);
// If autoplay fails, we can still focus the element
// so the user can manually start and use keyboard controls
});
}
});
}
}, 0);
@ -2867,7 +2704,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
const videoElement = playerRef.current.el();
videoElement.setAttribute('tabindex', '0');
videoElement.focus();
console.log('Video element focused for keyboard controls');
}
};

View File

@ -3,7 +3,6 @@ import { createRoot } from 'react-dom/client';
import './VideoJS.css';
import VideoJS from './VideoJS.jsx';
// import ChapterList from './components/chapter/ChapterList.jsx';
// Mount the components when the DOM is ready
const mountComponents = () => {
@ -16,7 +15,6 @@ const mountComponents = () => {
<VideoJS videoId="video-main" />
</StrictMode>
);
console.log('Mounted main VideoJS player');
}
// Mount embed video player
@ -28,7 +26,6 @@ const mountComponents = () => {
<VideoJS videoId="video-embed" />
</StrictMode>
);
console.log('Mounted embed VideoJS player');
}
};
@ -37,7 +34,6 @@ window.triggerVideoJSMount = mountComponents;
// Listen for custom events to trigger mounting
document.addEventListener('triggerVideoJSMount', () => {
console.log('Received triggerVideoJSMount event, attempting to mount VideoJS components...');
mountComponents();
});
@ -52,7 +48,6 @@ if (document.readyState === 'loading') {
setInterval(() => {
const embedContainer = document.getElementById('video-js-root-embed');
if (embedContainer && !embedContainer.hasChildNodes()) {
console.log('Found unmounted embed container during periodic check, mounting...');
mountComponents();
}
}, 1000);

View File

@ -12,7 +12,7 @@ class UserPreferences {
subtitleLanguage: null, // No subtitles by default
subtitleEnabled: false, // Subtitles off by default
muted: false,
autoplay: false, // Autoplay disabled by default
autoplay: true, // Autoplay disabled by default
};
}
@ -43,7 +43,6 @@ class UserPreferences {
const currentPrefs = this.getPreferences();
const updatedPrefs = { ...currentPrefs, ...preferences };
localStorage.setItem(this.storageKey, JSON.stringify(updatedPrefs));
console.log('User preferences saved:', updatedPrefs);
} catch (error) {
console.warn('Error saving user preferences to localStorage:', error);
}
@ -68,21 +67,13 @@ class UserPreferences {
setPreference(key, value, forceSet = false) {
// Add special logging for subtitle language changes
if (key === 'subtitleLanguage') {
console.log(
`🔄 Setting subtitleLanguage: ${value} (restoring: ${this.isRestoringSubtitles}, autoSaveDisabled: ${this.subtitleAutoSaveDisabled}, forceSet: ${forceSet})`
);
// Block subtitle language changes during restoration, but allow forced sets
if (this.isRestoringSubtitles) {
console.log('🚫 BLOCKED: Subtitle language change during restoration');
return; // Don't save during restoration
}
// Allow forced sets even if auto-save is disabled (for direct user clicks)
if (this.subtitleAutoSaveDisabled && !forceSet) {
console.log(
'🚫 BLOCKED: Subtitle language change during auto-save disabled period (use forceSet=true to override)'
);
return; // Don't save if disabled unless forced
}
@ -97,7 +88,6 @@ class UserPreferences {
resetPreferences() {
try {
localStorage.removeItem(this.storageKey);
console.log('User preferences reset to defaults');
} catch (error) {
console.warn('Error resetting user preferences:', error);
}
@ -112,12 +102,10 @@ class UserPreferences {
// DISABLE subtitle auto-save completely during initial load
this.subtitleAutoSaveDisabled = true;
console.log('🔒 Subtitle auto-save DISABLED during initial load');
// Re-enable after 3 seconds to ensure everything has settled
setTimeout(() => {
this.subtitleAutoSaveDisabled = false;
console.log('🔓 Subtitle auto-save RE-ENABLED after initial load period');
}, 3000);
// Apply volume and mute state
@ -133,11 +121,6 @@ class UserPreferences {
if (typeof prefs.playbackRate === 'number' && prefs.playbackRate > 0) {
player.playbackRate(prefs.playbackRate);
}
// Apply subtitle language (will be handled separately for text tracks)
// Quality setting will be handled by the settings menu component
console.log('Applied user preferences to player:', prefs);
}
/**
@ -160,7 +143,6 @@ class UserPreferences {
player.on('texttrackchange', () => {
// Skip saving if we're currently restoring subtitles
if (this.isRestoringSubtitles) {
console.log('Skipping subtitle save - currently restoring preferences');
return;
}
@ -173,24 +155,16 @@ class UserPreferences {
const track = textTracks[i];
if (track.kind === 'subtitles' && track.mode === 'showing') {
activeLanguage = track.language;
console.log('Active subtitle language detected:', activeLanguage);
break;
}
}
// If no subtitles are active, save null
if (!activeLanguage) {
console.log('No subtitles active, saving null');
}
this.setPreference('subtitleLanguage', activeLanguage);
}, 100);
});
// Also hook into subtitle menu clicks directly
this.setupSubtitleMenuListeners(player);
console.log('Auto-save preferences listeners set up');
}
/**
@ -201,7 +175,6 @@ class UserPreferences {
// Wait for the control bar to be ready
setTimeout(() => {
const controlBar = player.getChild('controlBar');
console.log('=== Searching for subtitle controls ===');
// Check all possible subtitle button names
const possibleNames = ['subtitlesButton', 'captionsButton', 'subsCapsButton', 'textTrackButton'];
@ -210,7 +183,6 @@ class UserPreferences {
for (const name of possibleNames) {
const button = controlBar.getChild(name);
if (button) {
console.log(`Found subtitle button: ${name}`);
subtitlesButton = button;
break;
}
@ -218,26 +190,21 @@ class UserPreferences {
// Also try to find by scanning all children
if (!subtitlesButton) {
console.log('Scanning all control bar children...');
const children = controlBar.children();
children.forEach((child, index) => {
children.forEach((child) => {
const name = child.name_ || child.constructor.name || 'Unknown';
console.log(`Child ${index}: ${name}`);
if (
name.toLowerCase().includes('subtitle') ||
name.toLowerCase().includes('caption') ||
name.toLowerCase().includes('text')
) {
console.log(`Potential subtitle button found: ${name}`);
subtitlesButton = child;
}
});
}
if (subtitlesButton) {
console.log('Found subtitles button, setting up menu listeners');
// Wait a bit more for the menu to be created
setTimeout(() => {
this.attachMenuItemListeners(player, subtitlesButton);
@ -248,8 +215,6 @@ class UserPreferences {
this.attachMenuItemListeners(player, subtitlesButton);
}, 2000);
} else {
console.log('No subtitles button found after exhaustive search');
// Try alternative approach - listen to DOM changes
this.setupDOMBasedListeners(player);
}
@ -261,8 +226,6 @@ class UserPreferences {
* @param {Object} player - Video.js player instance
*/
setupDOMBasedListeners(player) {
console.log('Setting up DOM-based subtitle listeners as fallback');
// Wait for DOM to be ready
setTimeout(() => {
const playerEl = player.el();
@ -277,8 +240,6 @@ class UserPreferences {
target.closest('.vjs-captions-menu-item') ||
(target.closest('.vjs-menu-item') && target.textContent.toLowerCase().includes('subtitle'))
) {
console.log('Subtitle menu item clicked via DOM listener:', target.textContent);
// Extract language from the clicked item
setTimeout(() => {
this.detectActiveSubtitleFromDOM(player, true); // Force set for user clicks
@ -287,14 +248,11 @@ class UserPreferences {
// Also handle "captions off" clicks
if (target.closest('.vjs-menu-item') && target.textContent.toLowerCase().includes('off')) {
console.log('Captions off clicked via DOM listener');
setTimeout(() => {
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
}, 200);
}
});
console.log('DOM-based subtitle listeners attached');
}
}, 1500);
}
@ -307,7 +265,6 @@ class UserPreferences {
detectActiveSubtitleFromDOM(player, forceSet = false) {
// Skip saving if we're currently restoring subtitles
if (this.isRestoringSubtitles) {
console.log('Skipping DOM subtitle save - currently restoring preferences');
return;
}
@ -318,7 +275,6 @@ class UserPreferences {
const track = textTracks[i];
if (track.kind === 'subtitles' && track.mode === 'showing') {
activeLanguage = track.language;
console.log('DOM detection - Active subtitle language:', activeLanguage, track.label);
break;
}
}
@ -335,49 +291,37 @@ class UserPreferences {
try {
const menu = subtitlesButton.menu;
if (menu && menu.children_) {
console.log('Found subtitle menu with', menu.children_.length, 'items');
menu.children_.forEach((menuItem, index) => {
menu.children_.forEach((menuItem) => {
if (menuItem.track) {
const track = menuItem.track;
console.log(`Menu item ${index}: ${track.label} (${track.language})`);
// Override the handleClick method
const originalHandleClick = menuItem.handleClick.bind(menuItem);
menuItem.handleClick = () => {
console.log('Subtitle menu item clicked:', track.label, track.language);
// Call original click handler
originalHandleClick();
// Save the preference after a short delay
setTimeout(() => {
if (track.mode === 'showing') {
console.log('Saving subtitle preference:', track.language);
this.setPreference('subtitleLanguage', track.language, true); // Force set for user clicks
} else {
console.log('Subtitle disabled, saving null');
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
}
}, 100);
};
} else if (menuItem.label && menuItem.label.toLowerCase().includes('off')) {
// Handle "captions off" option
console.log('Found captions off menu item');
const originalHandleClick = menuItem.handleClick.bind(menuItem);
menuItem.handleClick = () => {
console.log('Captions off clicked');
originalHandleClick();
setTimeout(() => {
console.log('Saving subtitle preference: null (off)');
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
}, 100);
};
}
});
} else {
console.log('Could not find subtitle menu or menu items');
}
} catch (error) {
console.error('Error setting up subtitle menu listeners:', error);
@ -395,20 +339,9 @@ class UserPreferences {
if (savedLanguage) {
// Set flag to prevent auto-save during restoration
this.isRestoringSubtitles = true;
console.log('isRestoringSubtitles', this.isRestoringSubtitles);
// Multiple attempts with increasing delays to ensure text tracks are loaded
const attemptToApplySubtitles = (attempt = 1) => {
const textTracks = player.textTracks();
console.log(`Subtitle application attempt ${attempt}, found ${textTracks.length} text tracks`);
// Log all available tracks for debugging
for (let i = 0; i < textTracks.length; i++) {
const track = textTracks[i];
console.log(
`Track ${i}: kind=${track.kind}, language=${track.language}, label=${track.label}, mode=${track.mode}`
);
}
// First, disable all subtitle tracks
for (let i = 0; i < textTracks.length; i++) {
@ -432,7 +365,6 @@ class UserPreferences {
const track = textTracks[i];
if (track.kind === 'subtitles' && matchesLang(track, savedLanguage)) {
track.mode = 'showing';
console.log('✓ Applied saved subtitle language:', savedLanguage, track.label);
found = true;
// Also update the menu UI to reflect the selection
@ -452,10 +384,7 @@ class UserPreferences {
const track = textTracks[i];
if (track.kind === 'subtitles') {
track.mode = 'showing';
console.log(
'Fallback ✓ Enabled first available subtitles track:',
track.label || track.language || track.srclang
);
// Save back the language we actually enabled for future precise matches
const langToSave = track.language || track.srclang || null;
if (langToSave) this.setPreference('subtitleLanguage', langToSave, true);
@ -472,12 +401,10 @@ class UserPreferences {
// Clear the restoration flag after a longer delay to ensure all events have settled
setTimeout(() => {
this.isRestoringSubtitles = false;
console.log('✅ Subtitle restoration complete, auto-save re-enabled');
}, 600); // Increased to 3 seconds
// If not found and we haven't tried too many times, try again
if (!found && attempt < 5) {
console.log(`Subtitle language ${savedLanguage} not found, retrying in ${attempt * 50}ms...`);
setTimeout(() => attemptToApplySubtitles(attempt + 1), attempt * 50);
} else if (!found) {
console.warn('Could not find subtitle track for language:', savedLanguage);
@ -518,8 +445,9 @@ class UserPreferences {
// Update custom settings menu to show "Off" as selected
this.updateCustomSettingsMenuUI(player);
} catch (e) {}
console.log('No subtitle auto-apply on load (disabled or no language).');
} catch (e) {
console.error('Error applying subtitle preference:', e);
}
}
}
@ -538,13 +466,9 @@ class UserPreferences {
if (enabled) {
buttonEl.classList.add('vjs-subs-active');
console.log('✓ Added vjs-subs-active class to subtitle button');
} else {
buttonEl.classList.remove('vjs-subs-active');
console.log('✓ Removed vjs-subs-active class from subtitle button');
}
} else {
console.log('Subtitle button not found for visual state update');
}
} catch (error) {
console.error('Error updating subtitle button visual state:', error);
@ -569,7 +493,6 @@ class UserPreferences {
if (menuItem.track) {
if (menuItem.track === activeTrack) {
menuItem.selected(true);
console.log('Updated menu UI for:', menuItem.track.label);
} else {
menuItem.selected(false);
}
@ -598,16 +521,11 @@ class UserPreferences {
const customSettingsMenu = controlBar.getChild('CustomSettingsMenu');
if (customSettingsMenu && customSettingsMenu.refreshSubtitlesSubmenu) {
console.log('Updating custom settings menu UI...');
customSettingsMenu.refreshSubtitlesSubmenu();
} else if (attempt < 5) {
// Retry after a short delay if menu not found
console.log(
`Custom settings menu not found, retrying in ${attempt * 200}ms... (attempt ${attempt})`
);
setTimeout(() => attemptUpdate(attempt + 1), attempt * 200);
} else {
console.log('Custom settings menu not found after multiple attempts');
}
} catch (error) {
console.error('Error updating custom settings menu UI:', error);
@ -639,12 +557,10 @@ class UserPreferences {
* @param {string} language - Subtitle language code
*/
forceSetSubtitleLanguage(language) {
console.log(`🚀 FORCE SAVING subtitle language: ${language}`);
const currentPrefs = this.getPreferences();
const updatedPrefs = { ...currentPrefs, subtitleLanguage: language };
try {
localStorage.setItem(this.storageKey, JSON.stringify(updatedPrefs));
console.log('✅ Force saved subtitle language:', language);
} catch (error) {
console.error('❌ Error force saving subtitle language:', error);
}
@ -664,7 +580,6 @@ class UserPreferences {
*/
setAutoplayPreference(autoplay) {
this.setPreference('autoplay', autoplay);
console.log('Autoplay preference saved:', autoplay);
}
}

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