mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 07:28:53 -05:00
fix: Chapter title, next video icon, play video with sound, dark opacity, fix hls, cleanup logs
This commit is contained in:
parent
8d31ff71e0
commit
5fda4610da
4
.gitignore
vendored
4
.gitignore
vendored
@ -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
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
@ -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
|
||||
@ -52,8 +47,6 @@ class AutoplayToggleButton extends Button {
|
||||
});
|
||||
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);
|
||||
button.appendChild(controlTextSpan);
|
||||
@ -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;
|
||||
button.addEventListener(
|
||||
'touchend',
|
||||
(e) => {
|
||||
const touchDuration = Date.now() - touchStartTime;
|
||||
|
||||
// Only show tooltip for quick taps (not swipes)
|
||||
if (touchDuration < 500) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// Only show tooltip for quick taps (not swipes)
|
||||
if (touchDuration < 500) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Show tooltip
|
||||
button.classList.add('touch-active');
|
||||
touchHandled = true;
|
||||
// Show tooltip
|
||||
button.classList.add('touch-active');
|
||||
touchHandled = true;
|
||||
|
||||
// Hide tooltip after delay
|
||||
setTimeout(() => {
|
||||
button.classList.remove('touch-active');
|
||||
}, 2000);
|
||||
}
|
||||
}, { passive: false });
|
||||
|
||||
// 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');
|
||||
}
|
||||
});
|
||||
// Hide tooltip after delay
|
||||
setTimeout(() => {
|
||||
button.classList.remove('touch-active');
|
||||
}, 2000);
|
||||
}
|
||||
},
|
||||
{ passive: false }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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() {
|
||||
|
||||
@ -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
|
||||
*/
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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');
|
||||
}
|
||||
}
|
||||
|
||||
@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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',
|
||||
});
|
||||
@ -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,8 +146,16 @@ 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
|
||||
@ -205,11 +201,11 @@ class EndScreenOverlay extends Component {
|
||||
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',
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -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');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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
Loading…
x
Reference in New Issue
Block a user