mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 15:38: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
|
.DS_Store
|
||||||
static/video_editor/videos/sample-video-10m.mp4
|
static/video_editor/videos/sample-video-10m.mp4
|
||||||
static/video_editor/videos/sample-video-10s.mp4
|
static/video_editor/videos/sample-video-10s.mp4
|
||||||
/frontend-tools/chapters-editor/node_modules
|
frontend-tools/video-js/public/videos/sample-video-white.mp4
|
||||||
frontend-tools/chapters-editor/client/public/videos/sample-video.mp4
|
|
||||||
frontend-tools/video-js/
|
|
||||||
|
|||||||
@ -46,6 +46,70 @@ html {
|
|||||||
justify-content: flex-start !important;
|
justify-content: flex-start !important;
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
height: 48px;
|
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 {
|
.video-container {
|
||||||
@ -93,6 +157,7 @@ html {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
margin: auto;
|
margin: auto;
|
||||||
|
display: none !important;
|
||||||
}
|
}
|
||||||
.vjs-next-video-control .vjs-icon-placeholder svg {
|
.vjs-next-video-control .vjs-icon-placeholder svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -577,8 +642,7 @@ html {
|
|||||||
background-color: rgba(255, 255, 255, 0.1) !important;
|
background-color: rgba(255, 255, 255, 0.1) !important;
|
||||||
}
|
}
|
||||||
.video-js .vjs-settings-button:focus {
|
.video-js .vjs-settings-button:focus {
|
||||||
outline: 2px solid #fff !important;
|
outline: none !important;
|
||||||
outline-offset: 2px !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js .vjs-settings-button .vjs-icon-cog {
|
.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) {
|
if (this.userPreferences) {
|
||||||
const savedAutoplay = this.userPreferences.getPreference('autoplay');
|
const savedAutoplay = this.userPreferences.getPreference('autoplay');
|
||||||
this.isAutoplayEnabled = savedAutoplay === true; // Explicit boolean check
|
this.isAutoplayEnabled = savedAutoplay === true; // Explicit boolean check
|
||||||
console.log('Autoplay button initialized with saved preference:', this.isAutoplayEnabled);
|
|
||||||
} else {
|
} else {
|
||||||
this.isAutoplayEnabled = false;
|
this.isAutoplayEnabled = false;
|
||||||
console.log('Autoplay button initialized with default (no userPreferences):', this.isAutoplayEnabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bind methods
|
// Bind methods
|
||||||
@ -37,13 +35,10 @@ class AutoplayToggleButton extends Button {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Set initial icon state directly
|
// Set initial icon state directly
|
||||||
console.log('AutoplayToggleButton createEl: isAutoplayEnabled =', this.isAutoplayEnabled);
|
|
||||||
if (this.isAutoplayEnabled) {
|
if (this.isAutoplayEnabled) {
|
||||||
this.iconSpan.innerHTML = `<span style="font-size: 1.2em; color: #ff4444;">●</span>`;
|
this.iconSpan.innerHTML = `<span style="font-size: 1.2em; color: #ff4444;">●</span>`;
|
||||||
console.log('Setting RED icon (autoplay ON)');
|
|
||||||
} else {
|
} else {
|
||||||
this.iconSpan.innerHTML = `<span style="font-size: 1.2em; color: #ccc;">○</span>`;
|
this.iconSpan.innerHTML = `<span style="font-size: 1.2em; color: #ccc;">○</span>`;
|
||||||
console.log('Setting GRAY icon (autoplay OFF)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create control text span
|
// Create control text span
|
||||||
@ -52,8 +47,6 @@ class AutoplayToggleButton extends Button {
|
|||||||
});
|
});
|
||||||
controlTextSpan.textContent = this.isAutoplayEnabled ? 'Autoplay is on' : 'Autoplay is off';
|
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
|
// Append both spans to button
|
||||||
button.appendChild(this.iconSpan);
|
button.appendChild(this.iconSpan);
|
||||||
button.appendChild(controlTextSpan);
|
button.appendChild(controlTextSpan);
|
||||||
@ -84,7 +77,6 @@ class AutoplayToggleButton extends Button {
|
|||||||
this.el().setAttribute('aria-label', 'Autoplay is on');
|
this.el().setAttribute('aria-label', 'Autoplay is on');
|
||||||
const controlText = this.el().querySelector('.vjs-control-text');
|
const controlText = this.el().querySelector('.vjs-control-text');
|
||||||
if (controlText) controlText.textContent = 'Autoplay is on';
|
if (controlText) controlText.textContent = 'Autoplay is on';
|
||||||
console.log('✓ Autoplay tooltip updated to: "Autoplay is on"');
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.iconSpan.innerHTML = `<span style="transform: inherit !important; margin: 20px 0 0; font-size: 1.2em; color: #ccc;">
|
this.iconSpan.innerHTML = `<span style="transform: inherit !important; margin: 20px 0 0; font-size: 1.2em; color: #ccc;">
|
||||||
@ -99,15 +91,13 @@ class AutoplayToggleButton extends Button {
|
|||||||
this.el().setAttribute('aria-label', 'Autoplay is off');
|
this.el().setAttribute('aria-label', 'Autoplay is off');
|
||||||
const controlText = this.el().querySelector('.vjs-control-text');
|
const controlText = this.el().querySelector('.vjs-control-text');
|
||||||
if (controlText) controlText.textContent = 'Autoplay is off';
|
if (controlText) controlText.textContent = 'Autoplay is off';
|
||||||
console.log('✓ Autoplay tooltip updated to: "Autoplay is off"');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fade back in
|
// Fade back in
|
||||||
this.iconSpan.style.opacity = '1';
|
this.iconSpan.style.opacity = '1';
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
handleClick() {
|
handleClick() {
|
||||||
// Toggle autoplay state
|
// Toggle autoplay state
|
||||||
@ -116,15 +106,11 @@ class AutoplayToggleButton extends Button {
|
|||||||
// Save preference if userPreferences is available
|
// Save preference if userPreferences is available
|
||||||
if (this.userPreferences) {
|
if (this.userPreferences) {
|
||||||
this.userPreferences.setAutoplayPreference(this.isAutoplayEnabled);
|
this.userPreferences.setAutoplayPreference(this.isAutoplayEnabled);
|
||||||
console.log('Autoplay preference saved to localStorage:', this.isAutoplayEnabled);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update icon and accessibility attributes
|
// Update icon and accessibility attributes
|
||||||
this.updateIcon();
|
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
|
// Trigger custom event for other components to listen to
|
||||||
this.player().trigger('autoplayToggle', { autoplay: this.isAutoplayEnabled });
|
this.player().trigger('autoplayToggle', { autoplay: this.isAutoplayEnabled });
|
||||||
}
|
}
|
||||||
@ -141,13 +127,19 @@ class AutoplayToggleButton extends Button {
|
|||||||
let touchHandled = false;
|
let touchHandled = false;
|
||||||
|
|
||||||
// Touch start
|
// Touch start
|
||||||
button.addEventListener('touchstart', (e) => {
|
button.addEventListener(
|
||||||
|
'touchstart',
|
||||||
|
(e) => {
|
||||||
touchStartTime = Date.now();
|
touchStartTime = Date.now();
|
||||||
touchHandled = false;
|
touchHandled = false;
|
||||||
}, { passive: true });
|
},
|
||||||
|
{ passive: true }
|
||||||
|
);
|
||||||
|
|
||||||
// Touch end
|
// Touch end
|
||||||
button.addEventListener('touchend', (e) => {
|
button.addEventListener(
|
||||||
|
'touchend',
|
||||||
|
(e) => {
|
||||||
const touchDuration = Date.now() - touchStartTime;
|
const touchDuration = Date.now() - touchStartTime;
|
||||||
|
|
||||||
// Only show tooltip for quick taps (not swipes)
|
// Only show tooltip for quick taps (not swipes)
|
||||||
@ -164,15 +156,9 @@ class AutoplayToggleButton extends Button {
|
|||||||
button.classList.remove('touch-active');
|
button.classList.remove('touch-active');
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
}, { passive: false });
|
},
|
||||||
|
{ 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');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -47,7 +47,6 @@ class CustomChaptersOverlay extends Component {
|
|||||||
|
|
||||||
createOverlay() {
|
createOverlay() {
|
||||||
if (!this.chaptersData || this.chaptersData.length === 0) {
|
if (!this.chaptersData || this.chaptersData.length === 0) {
|
||||||
console.log('⚠ No chapters data available for overlay');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -232,8 +231,6 @@ class CustomChaptersOverlay extends Component {
|
|||||||
playerEl.appendChild(this.overlay);
|
playerEl.appendChild(this.overlay);
|
||||||
|
|
||||||
this.player().on('timeupdate', this.updateCurrentChapter);
|
this.player().on('timeupdate', this.updateCurrentChapter);
|
||||||
|
|
||||||
console.log('✓ Custom chapters overlay created');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setupChaptersButton() {
|
setupChaptersButton() {
|
||||||
|
|||||||
@ -79,15 +79,6 @@ class CustomRemainingTime extends Component {
|
|||||||
return `${customPrefix}${timeString}${customSuffix}`;
|
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
|
* Component disposal cleanup
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -363,32 +363,19 @@ class CustomSettingsMenu extends Component {
|
|||||||
const player = this.player();
|
const player = this.player();
|
||||||
const tracks = player.textTracks();
|
const tracks = player.textTracks();
|
||||||
let currentSubtitleLabel = 'Off';
|
let currentSubtitleLabel = 'Off';
|
||||||
let activeTrack = null;
|
|
||||||
|
|
||||||
// Find the active subtitle track
|
// Find the active subtitle track
|
||||||
for (let i = 0; i < tracks.length; i++) {
|
for (let i = 0; i < tracks.length; i++) {
|
||||||
const t = tracks[i];
|
const t = tracks[i];
|
||||||
if (t.kind === 'subtitles' && t.mode === 'showing') {
|
if (t.kind === 'subtitles' && t.mode === 'showing') {
|
||||||
currentSubtitleLabel = t.label || t.language || 'Subtitles';
|
currentSubtitleLabel = t.label || t.language || 'Subtitles';
|
||||||
activeTrack = t;
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const currentSubtitlesDisplay = this.settingsOverlay.querySelector('.current-subtitles');
|
const currentSubtitlesDisplay = this.settingsOverlay.querySelector('.current-subtitles');
|
||||||
if (currentSubtitlesDisplay) {
|
if (currentSubtitlesDisplay) {
|
||||||
const oldValue = currentSubtitlesDisplay.textContent;
|
|
||||||
currentSubtitlesDisplay.textContent = currentSubtitleLabel;
|
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) {
|
} catch (error) {
|
||||||
console.error('Error updating current subtitle display:', error);
|
console.error('Error updating current subtitle display:', error);
|
||||||
@ -402,7 +389,6 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
// Listen for real-time subtitle changes
|
// Listen for real-time subtitle changes
|
||||||
this.player().on('texttrackchange', () => {
|
this.player().on('texttrackchange', () => {
|
||||||
console.log('Text track changed - updating subtitle display');
|
|
||||||
this.updateCurrentSubtitleDisplay();
|
this.updateCurrentSubtitleDisplay();
|
||||||
// Also refresh the subtitle submenu to show correct selection
|
// Also refresh the subtitle submenu to show correct selection
|
||||||
this.refreshSubtitlesSubmenu();
|
this.refreshSubtitlesSubmenu();
|
||||||
@ -516,7 +502,6 @@ class CustomSettingsMenu extends Component {
|
|||||||
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
|
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
|
||||||
controlBar.removeChild(this.settingsButton);
|
controlBar.removeChild(this.settingsButton);
|
||||||
controlBar.addChild(this.settingsButton, {}, fullscreenIndex + 1);
|
controlBar.addChild(this.settingsButton, {}, fullscreenIndex + 1);
|
||||||
console.log('✓ Settings button positioned after fullscreen toggle');
|
|
||||||
}, 50);
|
}, 50);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -872,8 +857,6 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
// Close only the speed submenu (keep overlay open)
|
// Close only the speed submenu (keep overlay open)
|
||||||
this.speedSubmenu.style.display = 'none';
|
this.speedSubmenu.style.display = 'none';
|
||||||
|
|
||||||
console.log('Playback speed preference saved:', speed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleQualityChange(value, qualityOption) {
|
handleQualityChange(value, qualityOption) {
|
||||||
@ -1063,8 +1046,6 @@ class CustomSettingsMenu extends Component {
|
|||||||
|
|
||||||
// Close only the quality submenu (keep overlay open)
|
// Close only the quality submenu (keep overlay open)
|
||||||
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
if (this.qualitySubmenu) this.qualitySubmenu.style.display = 'none';
|
||||||
|
|
||||||
console.log('Quality preference saved:', value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
handleSubtitleChange(lang, optionEl) {
|
handleSubtitleChange(lang, optionEl) {
|
||||||
|
|||||||
@ -24,7 +24,7 @@ class NextVideoButton extends Button {
|
|||||||
|
|
||||||
// Create SVG that matches Video.js icon dimensions
|
// Create SVG that matches Video.js icon dimensions
|
||||||
iconSpan.innerHTML = `
|
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"/>
|
<path d="M14 34L28.1667 24L14 14V34ZM30.6667 14V34H34V14H30.6667Z" fill="currentColor"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|
||||||
@ -44,7 +44,6 @@ class NextVideoButton extends Button {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleClick() {
|
handleClick() {
|
||||||
// console.log('NextVideoButton handleClick', this.nextLink);
|
|
||||||
this.player().trigger('nextVideo');
|
this.player().trigger('nextVideo');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -49,7 +49,7 @@ class ChapterMarkers extends Component {
|
|||||||
this.chaptersData.push({
|
this.chaptersData.push({
|
||||||
startTime: cue.startTime,
|
startTime: cue.startTime,
|
||||||
endTime: cue.endTime,
|
endTime: cue.endTime,
|
||||||
chapterTitle: cue.chapterTitle,
|
chapterTitle: cue.text,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,17 +217,17 @@ class ChapterMarkers extends Component {
|
|||||||
|
|
||||||
// Update text content without rebuilding DOM
|
// Update text content without rebuilding DOM
|
||||||
this.chapterTitle.textContent = currentChapter.chapterTitle;
|
this.chapterTitle.textContent = currentChapter.chapterTitle;
|
||||||
this.chapterInfo.textContent = `Chapter: ${startTime} - ${endTime}`;
|
this.chapterInfo.textContent = `${startTime} - ${endTime}`;
|
||||||
// this.positionInfo.textContent = `Position: ${timeAtPosition}`;
|
// this.positionInfo.textContent = `Position: ${timeAtPosition}`;
|
||||||
|
|
||||||
// Update sprite thumbnail
|
// Update sprite thumbnail
|
||||||
this.updateSpriteThumbnail(currentTime);
|
this.updateSpriteThumbnail(currentTime);
|
||||||
this.chapterImage.style.display = 'block';
|
this.chapterImage.style.display = 'block';
|
||||||
} else {
|
} else {
|
||||||
const timeAtPosition = this.formatTime(currentTime);
|
// const timeAtPosition = this.formatTime(currentTime);
|
||||||
this.chapterTitle.textContent = 'No Chapter';
|
this.chapterTitle.textContent = '';
|
||||||
this.chapterInfo.textContent = '';
|
this.chapterInfo.textContent = '';
|
||||||
this.positionInfo.textContent = `Position: ${timeAtPosition}`;
|
// this.positionInfo.textContent = `Position: ${timeAtPosition}`;
|
||||||
|
|
||||||
// Still show sprite thumbnail even when not in a chapter
|
// Still show sprite thumbnail even when not in a chapter
|
||||||
this.updateSpriteThumbnail(currentTime);
|
this.updateSpriteThumbnail(currentTime);
|
||||||
@ -274,7 +274,6 @@ class ChapterMarkers extends Component {
|
|||||||
if (!this.previewSprite || !this.previewSprite.url) {
|
if (!this.previewSprite || !this.previewSprite.url) {
|
||||||
// Hide image if no sprite data available
|
// Hide image if no sprite data available
|
||||||
this.chapterImage.style.display = 'none';
|
this.chapterImage.style.display = 'none';
|
||||||
console.log('No sprite data available:', this.previewSprite);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -304,10 +303,6 @@ class ChapterMarkers extends Component {
|
|||||||
const xPos = -(frameCol * width);
|
const xPos = -(frameCol * width);
|
||||||
const yPos = -(frameRow * height);
|
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
|
// Apply sprite background
|
||||||
this.chapterImage.style.backgroundImage = `url("${url}")`;
|
this.chapterImage.style.backgroundImage = `url("${url}")`;
|
||||||
this.chapterImage.style.backgroundPosition = `${xPos}px ${yPos}px`;
|
this.chapterImage.style.backgroundPosition = `${xPos}px ${yPos}px`;
|
||||||
@ -321,7 +316,6 @@ class ChapterMarkers extends Component {
|
|||||||
if (frameIndex >= 3 && currentTime > 30) {
|
if (frameIndex >= 3 && currentTime > 30) {
|
||||||
const fallbackYPos = -(2 * height); // Frame 2 (20-30s range)
|
const fallbackYPos = -(2 * height); // Frame 2 (20-30s range)
|
||||||
this.chapterImage.style.backgroundPosition = `${xPos}px ${fallbackYPos}px`;
|
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
|
// Only setup if we have sprite data
|
||||||
if (!this.previewSprite || !this.previewSprite.url) {
|
if (!this.previewSprite || !this.previewSprite.url) {
|
||||||
console.log('No sprite data available for preview:', this.previewSprite);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,7 +160,6 @@ class SpritePreview extends Component {
|
|||||||
if (!this.previewSprite || !this.previewSprite.url) {
|
if (!this.previewSprite || !this.previewSprite.url) {
|
||||||
// Hide image if no sprite data available
|
// Hide image if no sprite data available
|
||||||
this.spriteImage.style.display = 'none';
|
this.spriteImage.style.display = 'none';
|
||||||
console.log('No sprite data available:', this.previewSprite);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,10 +189,6 @@ class SpritePreview extends Component {
|
|||||||
const xPos = -(frameCol * width);
|
const xPos = -(frameCol * width);
|
||||||
const yPos = -(frameRow * height);
|
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
|
// Apply sprite background
|
||||||
this.spriteImage.style.backgroundImage = `url("${url}")`;
|
this.spriteImage.style.backgroundImage = `url("${url}")`;
|
||||||
this.spriteImage.style.backgroundPosition = `${xPos}px ${yPos}px`;
|
this.spriteImage.style.backgroundPosition = `${xPos}px ${yPos}px`;
|
||||||
@ -211,7 +205,6 @@ class SpritePreview extends Component {
|
|||||||
if (frameIndex >= 3 && currentTime > 30) {
|
if (frameIndex >= 3 && currentTime > 30) {
|
||||||
const fallbackYPos = -(2 * height); // Frame 2 (20-30s range)
|
const fallbackYPos = -(2 * height); // Frame 2 (20-30s range)
|
||||||
this.spriteImage.style.backgroundPosition = `${xPos}px ${fallbackYPos}px`;
|
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();
|
this.handlePlayNext();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
console.log('Autoplay countdown started:', this.countdownSeconds, 'seconds');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
stopCountdown() {
|
stopCountdown() {
|
||||||
@ -129,7 +127,6 @@ class AutoplayCountdownOverlay extends Component {
|
|||||||
this.countdownInterval = null;
|
this.countdownInterval = null;
|
||||||
}
|
}
|
||||||
this.hide();
|
this.hide();
|
||||||
console.log('Autoplay countdown stopped');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCountdownDisplay() {
|
updateCountdownDisplay() {
|
||||||
@ -140,7 +137,6 @@ class AutoplayCountdownOverlay extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handlePlayNext() {
|
handlePlayNext() {
|
||||||
console.log('Autoplay: Playing next video immediately');
|
|
||||||
try {
|
try {
|
||||||
this.stopCountdown();
|
this.stopCountdown();
|
||||||
this.onPlayNext();
|
this.onPlayNext();
|
||||||
@ -150,7 +146,6 @@ class AutoplayCountdownOverlay extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleCancel() {
|
handleCancel() {
|
||||||
console.log('Autoplay: Cancelled by user');
|
|
||||||
try {
|
try {
|
||||||
this.stopCountdown();
|
this.stopCountdown();
|
||||||
this.onCancel();
|
this.onCancel();
|
||||||
|
|||||||
@ -14,12 +14,6 @@ class EndScreenOverlay extends Component {
|
|||||||
|
|
||||||
// Now set the instance property after super() completes
|
// Now set the instance property after super() completes
|
||||||
this.relatedVideos = options && options.relatedVideos ? options.relatedVideos : [];
|
this.relatedVideos = options && options.relatedVideos ? options.relatedVideos : [];
|
||||||
|
|
||||||
// console.log(
|
|
||||||
// 'EndScreenOverlay created with',
|
|
||||||
// this.relatedVideos.length,
|
|
||||||
// 'related videos'
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
createEl() {
|
createEl() {
|
||||||
@ -30,12 +24,6 @@ class EndScreenOverlay extends Component {
|
|||||||
const maxVideos = this.getMaxVideosForScreen();
|
const maxVideos = this.getMaxVideosForScreen();
|
||||||
const videosToShow = relatedVideos.slice(0, maxVideos);
|
const videosToShow = relatedVideos.slice(0, maxVideos);
|
||||||
|
|
||||||
// console.log(
|
|
||||||
// 'Creating end screen with',
|
|
||||||
// videosToShow.length,
|
|
||||||
// 'related videos'
|
|
||||||
// );
|
|
||||||
|
|
||||||
const overlay = super.createEl('div', {
|
const overlay = super.createEl('div', {
|
||||||
className: 'vjs-end-screen-overlay',
|
className: 'vjs-end-screen-overlay',
|
||||||
});
|
});
|
||||||
@ -88,7 +76,7 @@ class EndScreenOverlay extends Component {
|
|||||||
onerror: () => {
|
onerror: () => {
|
||||||
// Fallback to placeholder if image fails to load
|
// Fallback to placeholder if image fails to load
|
||||||
thumbnail.src = this.getPlaceholderImage(video.title);
|
thumbnail.src = this.getPlaceholderImage(video.title);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const overlay = videojs.dom.createEl('div', {
|
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
|
// 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
|
// For now, we'll use a simple colored placeholder based on the title
|
||||||
const colors = [
|
const colors = [
|
||||||
'#FF6B6B', '#4ECDC4', '#45B7D1', '#96CEB4', '#FFEAA7',
|
'#FF6B6B',
|
||||||
'#DDA0DD', '#98D8C8', '#F7DC6F', '#BB8FCE', '#85C1E9'
|
'#4ECDC4',
|
||||||
|
'#45B7D1',
|
||||||
|
'#96CEB4',
|
||||||
|
'#FFEAA7',
|
||||||
|
'#DDA0DD',
|
||||||
|
'#98D8C8',
|
||||||
|
'#F7DC6F',
|
||||||
|
'#BB8FCE',
|
||||||
|
'#85C1E9',
|
||||||
];
|
];
|
||||||
|
|
||||||
// Use title hash to consistently assign colors
|
// Use title hash to consistently assign colors
|
||||||
@ -221,7 +217,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'Bro Code',
|
author: 'Bro Code',
|
||||||
views: '2.1M views',
|
views: '2.1M views',
|
||||||
duration: 1800,
|
duration: 1800,
|
||||||
thumbnail: 'https://img.youtube.com/vi/dGcsHMXbSOA/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/dGcsHMXbSOA/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample2',
|
id: 'sample2',
|
||||||
@ -229,7 +225,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'Tech Tutorials',
|
author: 'Tech Tutorials',
|
||||||
views: '850K views',
|
views: '850K views',
|
||||||
duration: 1200,
|
duration: 1200,
|
||||||
thumbnail: 'https://img.youtube.com/vi/WZQc7RUAg18/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/WZQc7RUAg18/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample3',
|
id: 'sample3',
|
||||||
@ -237,7 +233,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'Web Dev Academy',
|
author: 'Web Dev Academy',
|
||||||
views: '1.2M views',
|
views: '1.2M views',
|
||||||
duration: 2400,
|
duration: 2400,
|
||||||
thumbnail: 'https://img.youtube.com/vi/0xMQfnTU6oo/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/0xMQfnTU6oo/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample4',
|
id: 'sample4',
|
||||||
@ -245,7 +241,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'Code Master',
|
author: 'Code Master',
|
||||||
views: '650K views',
|
views: '650K views',
|
||||||
duration: 3600,
|
duration: 3600,
|
||||||
thumbnail: 'https://img.youtube.com/vi/fBNz6F-Cowg/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/fBNz6F-Cowg/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample5',
|
id: 'sample5',
|
||||||
@ -253,7 +249,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'Frontend Pro',
|
author: 'Frontend Pro',
|
||||||
views: '980K views',
|
views: '980K views',
|
||||||
duration: 2800,
|
duration: 2800,
|
||||||
thumbnail: 'https://img.youtube.com/vi/qZXt1Aom3Cs/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/qZXt1Aom3Cs/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample6',
|
id: 'sample6',
|
||||||
@ -261,7 +257,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'Data Academy',
|
author: 'Data Academy',
|
||||||
views: '1.5M views',
|
views: '1.5M views',
|
||||||
duration: 4200,
|
duration: 4200,
|
||||||
thumbnail: 'https://img.youtube.com/vi/ua-CiDNNj30/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/ua-CiDNNj30/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample7',
|
id: 'sample7',
|
||||||
@ -269,7 +265,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'TypeScript Expert',
|
author: 'TypeScript Expert',
|
||||||
views: '720K views',
|
views: '720K views',
|
||||||
duration: 2100,
|
duration: 2100,
|
||||||
thumbnail: 'https://img.youtube.com/vi/BwuLxPH8IDs/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/BwuLxPH8IDs/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample8',
|
id: 'sample8',
|
||||||
@ -277,7 +273,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'Database Pro',
|
author: 'Database Pro',
|
||||||
views: '890K views',
|
views: '890K views',
|
||||||
duration: 1800,
|
duration: 1800,
|
||||||
thumbnail: 'https://img.youtube.com/vi/-56x56UppqQ/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/-56x56UppqQ/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample9',
|
id: 'sample9',
|
||||||
@ -285,7 +281,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'DevOps Master',
|
author: 'DevOps Master',
|
||||||
views: '1.1M views',
|
views: '1.1M views',
|
||||||
duration: 3200,
|
duration: 3200,
|
||||||
thumbnail: 'https://img.youtube.com/vi/pTFZFxd4hOI/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/pTFZFxd4hOI/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample10',
|
id: 'sample10',
|
||||||
@ -293,7 +289,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'Cloud Expert',
|
author: 'Cloud Expert',
|
||||||
views: '1.3M views',
|
views: '1.3M views',
|
||||||
duration: 4500,
|
duration: 4500,
|
||||||
thumbnail: 'https://img.youtube.com/vi/ITcXLS3h2qU/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/ITcXLS3h2qU/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample11',
|
id: 'sample11',
|
||||||
@ -301,7 +297,7 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'API Specialist',
|
author: 'API Specialist',
|
||||||
views: '680K views',
|
views: '680K views',
|
||||||
duration: 2600,
|
duration: 2600,
|
||||||
thumbnail: 'https://img.youtube.com/vi/ed8SzALpx1Q/maxresdefault.jpg'
|
thumbnail: 'https://img.youtube.com/vi/ed8SzALpx1Q/maxresdefault.jpg',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'sample12',
|
id: 'sample12',
|
||||||
@ -309,8 +305,8 @@ class EndScreenOverlay extends Component {
|
|||||||
author: 'AI Academy',
|
author: 'AI Academy',
|
||||||
views: '2.3M views',
|
views: '2.3M views',
|
||||||
duration: 5400,
|
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
|
// Environment-based development mode configuration
|
||||||
const isDevMode = import.meta.env.VITE_DEV_MODE === 'true' || window.location.hostname.includes('vercel.app');
|
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
|
// Safely access window.MEDIA_DATA with fallback using useMemo
|
||||||
const mediaData = useMemo(
|
const mediaData = useMemo(
|
||||||
() =>
|
() =>
|
||||||
@ -40,7 +37,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
poster_url:
|
poster_url:
|
||||||
'https://demo.mediacms.io/media/original/thumbnails/user/markos/7dedcb56bde9463dbc0766768a99be0f_C8E5GFY.20250605_110647.mp4.jpg',
|
'https://demo.mediacms.io/media/original/thumbnails/user/markos/7dedcb56bde9463dbc0766768a99be0f_C8E5GFY.20250605_110647.mp4.jpg',
|
||||||
chapter_data: [
|
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:24.295', endTime: '00:00:48.590', chapterTitle: 'A2 of Marine Life' },
|
||||||
{
|
{
|
||||||
startTime: '00:00:48.590',
|
startTime: '00:00:48.590',
|
||||||
@ -1052,6 +1049,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
siteUrl: '',
|
siteUrl: '',
|
||||||
nextLink: 'https://demo.mediacms.io/view?m=YjGJafibO',
|
nextLink: 'https://demo.mediacms.io/view?m=YjGJafibO',
|
||||||
urlAutoplay: true,
|
urlAutoplay: true,
|
||||||
|
urlMuted: false,
|
||||||
},
|
},
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
@ -1083,14 +1081,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
return hours * 3600 + minutes * 60 + seconds;
|
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
|
// Convert chapters data from backend format to required format with memoization
|
||||||
const convertChaptersData = useMemo(() => {
|
const convertChaptersData = useMemo(() => {
|
||||||
return (rawChaptersData) => {
|
return (rawChaptersData) => {
|
||||||
@ -1098,8 +1088,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('Converting raw chapters data:', rawChaptersData);
|
|
||||||
|
|
||||||
const convertedData = rawChaptersData.map((chapter) => ({
|
const convertedData = rawChaptersData.map((chapter) => ({
|
||||||
startTime: convertTimeStringToSeconds(chapter.startTime),
|
startTime: convertTimeStringToSeconds(chapter.startTime),
|
||||||
endTime: convertTimeStringToSeconds(chapter.endTime),
|
endTime: convertTimeStringToSeconds(chapter.endTime),
|
||||||
@ -1178,13 +1166,20 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
return 'video/mp4';
|
return 'video/mp4';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Get user's quality preference for dependency tracking
|
||||||
|
const userQualityPreference = userPreferences.current.getQualityPreference();
|
||||||
|
|
||||||
// Get video data from mediaData
|
// Get video data from mediaData
|
||||||
const currentVideo = useMemo(() => {
|
const currentVideo = useMemo(() => {
|
||||||
// Get video sources based on available data
|
// Get video sources based on available data and user preferences
|
||||||
const getVideoSources = () => {
|
const getVideoSources = () => {
|
||||||
|
// Use the extracted quality preference
|
||||||
|
const userQuality = userQualityPreference;
|
||||||
|
|
||||||
// Check if HLS info is available and not empty
|
// Check if HLS info is available and not empty
|
||||||
if (mediaData.data?.hls_info && mediaData.data.hls_info.master_file) {
|
if (mediaData.data?.hls_info) {
|
||||||
// Use master file as the primary source (auto quality)
|
// If user prefers auto quality or master file doesn't exist for specific quality
|
||||||
|
if (userQuality === 'auto' && mediaData.data.hls_info.master_file) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
src: mediaData.siteUrl + mediaData.data.hls_info.master_file,
|
src: mediaData.siteUrl + mediaData.data.hls_info.master_file,
|
||||||
@ -1194,10 +1189,57 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
// Fallback to encoded qualities if available
|
||||||
if (mediaData.data?.encodings_info) {
|
if (mediaData.data?.encodings_info) {
|
||||||
const sources = [];
|
|
||||||
const encodings = mediaData.data.encodings_info;
|
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
|
// Get available qualities dynamically from encodings_info
|
||||||
const availableQualities = Object.keys(encodings)
|
const availableQualities = Object.keys(encodings)
|
||||||
@ -1231,14 +1273,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
|
|
||||||
// Default sample video
|
// Default sample video
|
||||||
return [
|
return [
|
||||||
/* {
|
|
||||||
src: '/videos/sample-video.mp4',
|
|
||||||
type: 'video/mp4',
|
|
||||||
}, */
|
|
||||||
{
|
{
|
||||||
|
src: '/videos/sample-video-white.mp4',
|
||||||
|
type: 'video/mp4',
|
||||||
|
},
|
||||||
|
/* {
|
||||||
src: '/videos/sample-video.mp3',
|
src: '/videos/sample-video.mp3',
|
||||||
type: 'audio/mpeg',
|
type: 'audio/mpeg',
|
||||||
},
|
}, */
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1250,9 +1292,10 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
related_media: mediaData.data?.related_media || [],
|
related_media: mediaData.data?.related_media || [],
|
||||||
nextLink: mediaData?.nextLink || null,
|
nextLink: mediaData?.nextLink || null,
|
||||||
urlAutoplay: mediaData?.urlAutoplay || true,
|
urlAutoplay: mediaData?.urlAutoplay || true,
|
||||||
|
urlMuted: mediaData?.urlMuted || false,
|
||||||
sources: getVideoSources(),
|
sources: getVideoSources(),
|
||||||
};
|
};
|
||||||
}, [mediaData]);
|
}, [mediaData, userQualityPreference]);
|
||||||
|
|
||||||
// Compute available qualities. Prefer JSON (mediaData.data.qualities), otherwise build from encodings_info or current source.
|
// Compute available qualities. Prefer JSON (mediaData.data.qualities), otherwise build from encodings_info or current source.
|
||||||
const availableQualities = useMemo(() => {
|
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
|
// Function to navigate to next video
|
||||||
const goToNextVideo = () => {
|
const goToNextVideo = () => {
|
||||||
console.log('Next video functionality disabled for single video mode');
|
|
||||||
|
|
||||||
if (mediaData.onClickNextCallback && typeof mediaData.onClickNextCallback === 'function') {
|
if (mediaData.onClickNextCallback && typeof mediaData.onClickNextCallback === 'function') {
|
||||||
mediaData.onClickNextCallback();
|
mediaData.onClickNextCallback();
|
||||||
}
|
}
|
||||||
@ -1464,7 +1501,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
if (videoRef.current && !playerRef.current) {
|
if (videoRef.current && !playerRef.current) {
|
||||||
// Check if element is already a Video.js player
|
// Check if element is already a Video.js player
|
||||||
if (videoRef.current.player) {
|
if (videoRef.current.player) {
|
||||||
// console.log('Video.js already initialized on this element');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1478,8 +1514,8 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
controls: true,
|
controls: true,
|
||||||
|
|
||||||
// Player dimensions - removed for responsive design
|
// Player dimensions - removed for responsive design
|
||||||
// Autoplay behavior: Use 'muted' to comply with browser policies
|
// Autoplay behavior: Try unmuted first, fallback to muted if needed
|
||||||
autoplay: 'muted', // Auto-start muted to comply with browser policies (true/false, play, muted, any)
|
autoplay: true, // Try unmuted autoplay first (true/false, play, muted, any)
|
||||||
|
|
||||||
// Start video over when it ends
|
// Start video over when it ends
|
||||||
loop: false,
|
loop: false,
|
||||||
@ -1741,9 +1777,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
/* playerRef.current.on('ready', () => {
|
|
||||||
console.log('Video.js player ready');
|
|
||||||
}); */
|
|
||||||
playerRef.current.ready(() => {
|
playerRef.current.ready(() => {
|
||||||
// Apply user preferences to player
|
// Apply user preferences to player
|
||||||
userPreferences.current.applyToPlayer(playerRef.current);
|
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
|
||||||
if (mediaData?.urlAutoplay) {
|
const hasUserInteracted = () => {
|
||||||
playerRef.current.play();
|
// 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 {
|
} else {
|
||||||
// Auto-start video on page load/reload (muted to comply with browser policies)
|
// Show notification for manual interaction
|
||||||
playerRef.current.play().catch((error) => {
|
setTimeout(() => {
|
||||||
console.log('ℹ️ Browser prevented autoplay (normal behavior):', error.message);
|
if (playerRef.current && !playerRef.current.isDisposed()) {
|
||||||
// Fallback: ensure video is ready to play when user interacts
|
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) {
|
||||||
|
// Explicit autoplay requested via URL parameter
|
||||||
|
handleAutoplay();
|
||||||
|
} else {
|
||||||
|
// Auto-start video on page load/reload with fallback strategy
|
||||||
|
handleAutoplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupMobilePlayPause = () => {
|
const setupMobilePlayPause = () => {
|
||||||
@ -1857,18 +1964,28 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
const progressControl = controlBar.getChild('progressControl');
|
const progressControl = controlBar.getChild('progressControl');
|
||||||
const seekBar = progressControl.getChild('seekBar');
|
const seekBar = progressControl.getChild('seekBar');
|
||||||
const chaptersButton = controlBar.getChild('chaptersButton');
|
const chaptersButton = controlBar.getChild('chaptersButton');
|
||||||
const fullscreenToggle = controlBar.getChild('fullscreenToggle');
|
|
||||||
|
|
||||||
// Auto-play video when navigating from next button
|
// Auto-play video when navigating from next button
|
||||||
const urlParams = new URLSearchParams(window.location.search);
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
const hasVideoParam = urlParams.get('m');
|
const hasVideoParam = urlParams.get('m');
|
||||||
if (hasVideoParam) {
|
if (hasVideoParam) {
|
||||||
// Small delay to ensure everything is loaded
|
// Small delay to ensure everything is loaded
|
||||||
setTimeout(() => {
|
setTimeout(async () => {
|
||||||
if (playerRef.current && !playerRef.current.isDisposed()) {
|
if (playerRef.current && !playerRef.current.isDisposed()) {
|
||||||
playerRef.current.play().catch((error) => {
|
try {
|
||||||
console.log('ℹ️ Browser prevented autoplay (normal behavior):', error.message);
|
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);
|
}, 100);
|
||||||
}
|
}
|
||||||
@ -1918,7 +2035,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
const cue = new (window.VTTCue || window.TextTrackCue)(
|
const cue = new (window.VTTCue || window.TextTrackCue)(
|
||||||
chapter.startTime,
|
chapter.startTime,
|
||||||
chapter.endTime,
|
chapter.endTime,
|
||||||
chapter.text
|
chapter.chapterTitle
|
||||||
);
|
);
|
||||||
chaptersTrack.addCue(cue);
|
chaptersTrack.addCue(cue);
|
||||||
});
|
});
|
||||||
@ -1965,9 +2082,11 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
const autoplayToggleButton = new AutoplayToggleButton(playerRef.current, {
|
const autoplayToggleButton = new AutoplayToggleButton(playerRef.current, {
|
||||||
userPreferences: userPreferences.current,
|
userPreferences: userPreferences.current,
|
||||||
});
|
});
|
||||||
// Add it after the play button
|
// Add it before the chapters button (or at a suitable position)
|
||||||
const fullscreenToggleIndex = controlBar.children().indexOf(fullscreenToggle);
|
const chaptersButtonIndex = controlBar.children().indexOf(chaptersButton);
|
||||||
controlBar.addChild(autoplayToggleButton, {}, fullscreenToggleIndex - 1);
|
const insertIndex =
|
||||||
|
chaptersButtonIndex > 0 ? chaptersButtonIndex : controlBar.children().length - 3;
|
||||||
|
controlBar.addChild(autoplayToggleButton, {}, insertIndex);
|
||||||
|
|
||||||
// Store reference for later use
|
// Store reference for later use
|
||||||
customComponents.current.autoplayToggleButton = autoplayToggleButton;
|
customComponents.current.autoplayToggleButton = autoplayToggleButton;
|
||||||
@ -1975,62 +2094,13 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
// Force update icon after adding to DOM to ensure correct display
|
// Force update icon after adding to DOM to ensure correct display
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
autoplayToggleButton.updateIcon();
|
autoplayToggleButton.updateIcon();
|
||||||
console.log('✓ Autoplay toggle button icon updated after DOM insertion');
|
|
||||||
}, 100);
|
}, 100);
|
||||||
|
|
||||||
console.log('✓ Autoplay toggle button added successfully');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('✗ Failed to add autoplay toggle button:', error);
|
console.error('✗ Failed to add autoplay toggle button:', error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// END: Implement autoplay toggle button
|
// 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
|
// Make menus clickable instead of hover-only
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const setupClickableMenus = () => {
|
const setupClickableMenus = () => {
|
||||||
@ -2057,8 +2127,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
this.menu.show();
|
this.menu.show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log(`✓ Made ${buttonName} clickable`);
|
|
||||||
} else if (button) {
|
} else if (button) {
|
||||||
// For buttons without menuButton_ property
|
// For buttons without menuButton_ property
|
||||||
const buttonEl = button.el();
|
const buttonEl = button.el();
|
||||||
@ -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
|
// BEGIN: Add chapter markers and sprite preview to progress control
|
||||||
if (progressControl && seekBar) {
|
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
|
// Check if we have chapters
|
||||||
const hasChapters = chaptersData && chaptersData.length > 0;
|
const hasChapters = chaptersData && chaptersData.length > 0;
|
||||||
|
|
||||||
if (hasChapters) {
|
if (hasChapters) {
|
||||||
// Use original ChapterMarkers with sprite functionality when chapters exist
|
// 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, {
|
const chapterMarkers = new ChapterMarkers(playerRef.current, {
|
||||||
previewSprite: mediaData.previewSprite,
|
previewSprite: mediaData.previewSprite,
|
||||||
});
|
});
|
||||||
seekBar.addChild(chapterMarkers);
|
seekBar.addChild(chapterMarkers);
|
||||||
} else if (mediaData.previewSprite) {
|
} else if (mediaData.previewSprite) {
|
||||||
// Use separate SpritePreview component only when no chapters but sprite data exists
|
// 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, {
|
const spritePreview = new SpritePreview(playerRef.current, {
|
||||||
previewSprite: mediaData.previewSprite,
|
previewSprite: mediaData.previewSprite,
|
||||||
});
|
});
|
||||||
@ -2260,19 +2316,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
|
|
||||||
// Setup sprite preview hover functionality
|
// Setup sprite preview hover functionality
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('✓ Setting up sprite preview hover functionality');
|
|
||||||
spritePreview.setupProgressBarHover();
|
spritePreview.setupProgressBarHover();
|
||||||
}, 100);
|
}, 100);
|
||||||
} else {
|
|
||||||
console.log('✗ No chapters and no sprite data available');
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// END: Add chapter markers and sprite preview to progress control
|
// END: Add chapter markers and sprite preview to progress control
|
||||||
|
|
||||||
// BEGIN: Simple button layout fix - use CSS approach
|
// BEGIN: Simple button layout fix - use CSS approach
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Setting up simplified button layout...');
|
|
||||||
|
|
||||||
// Add a simple spacer div using DOM manipulation (simpler approach)
|
// Add a simple spacer div using DOM manipulation (simpler approach)
|
||||||
const spacerDiv = document.createElement('div');
|
const spacerDiv = document.createElement('div');
|
||||||
spacerDiv.className = 'vjs-spacer-control vjs-control';
|
spacerDiv.className = 'vjs-spacer-control vjs-control';
|
||||||
@ -2287,22 +2338,32 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
const durationEl = durationDisplay.el();
|
const durationEl = durationDisplay.el();
|
||||||
const nextSibling = durationEl.nextSibling;
|
const nextSibling = durationEl.nextSibling;
|
||||||
controlBarEl.insertBefore(spacerDiv, nextSibling);
|
controlBarEl.insertBefore(spacerDiv, nextSibling);
|
||||||
console.log('✓ Simple spacer added after duration display');
|
|
||||||
}
|
}
|
||||||
}, 300);
|
}, 300);
|
||||||
// END: Simple button layout fix
|
// END: Simple button layout fix
|
||||||
|
|
||||||
// BEGIN: Move chapters button after fullscreen toggle
|
// BEGIN: Move Picture-in-Picture and Fullscreen buttons to the very end
|
||||||
if (chaptersButton && fullscreenToggle) {
|
setTimeout(() => {
|
||||||
try {
|
try {
|
||||||
const fullscreenIndex = controlBar.children().indexOf(fullscreenToggle);
|
const pictureInPictureToggle = controlBar.getChild('pictureInPictureToggle');
|
||||||
controlBar.addChild(chaptersButton, {}, fullscreenIndex + 1);
|
const fullscreenToggle = controlBar.getChild('fullscreenToggle');
|
||||||
console.log('✓ Chapters button moved after fullscreen toggle');
|
|
||||||
|
// 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) {
|
} catch (e) {
|
||||||
console.log('✗ Failed to move chapters button:', e);
|
console.error('✗ Failed to move PiP/Fullscreen buttons to end:', e);
|
||||||
}
|
}
|
||||||
}
|
}, 100);
|
||||||
// END: Move chapters button after fullscreen toggle
|
// END: Move Picture-in-Picture and Fullscreen buttons to the very end
|
||||||
|
|
||||||
// BEGIN: Add Chapters Overlay Component
|
// BEGIN: Add Chapters Overlay Component
|
||||||
if (chaptersData && chaptersData.length > 0) {
|
if (chaptersData && chaptersData.length > 0) {
|
||||||
@ -2312,8 +2373,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
channelName: 'Chapter',
|
channelName: 'Chapter',
|
||||||
thumbnail: mediaData?.data?.thumbnail_url || mediaData?.data?.author_thumbnail || '',
|
thumbnail: mediaData?.data?.thumbnail_url || mediaData?.data?.author_thumbnail || '',
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
console.log('⚠ No chapters data available for overlay');
|
|
||||||
}
|
}
|
||||||
// END: Add Chapters Overlay Component
|
// END: Add Chapters Overlay Component
|
||||||
|
|
||||||
@ -2399,199 +2458,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// END: Add custom arrow key seek functionality
|
// 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
|
// Listen for next video event
|
||||||
playerRef.current.on('nextVideo', () => {
|
playerRef.current.on('nextVideo', () => {
|
||||||
console.log('Next video requested');
|
|
||||||
goToNextVideo();
|
goToNextVideo();
|
||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('play', () => {
|
playerRef.current.on('play', () => {
|
||||||
console.log('Video started playing');
|
|
||||||
// Only show play indicator if not changing quality
|
// Only show play indicator if not changing quality
|
||||||
if (!playerRef.current.isChangingQuality && customComponents.current.seekIndicator) {
|
if (!playerRef.current.isChangingQuality && customComponents.current.seekIndicator) {
|
||||||
customComponents.current.seekIndicator.show('play');
|
customComponents.current.seekIndicator.show('play');
|
||||||
@ -2599,7 +2473,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('pause', () => {
|
playerRef.current.on('pause', () => {
|
||||||
console.log('Video paused');
|
|
||||||
// Only show pause indicator if not changing quality
|
// Only show pause indicator if not changing quality
|
||||||
if (!playerRef.current.isChangingQuality && customComponents.current.seekIndicator) {
|
if (!playerRef.current.isChangingQuality && customComponents.current.seekIndicator) {
|
||||||
customComponents.current.seekIndicator.show('pause');
|
customComponents.current.seekIndicator.show('pause');
|
||||||
@ -2611,9 +2484,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
let autoplayCountdown = null;
|
let autoplayCountdown = null;
|
||||||
|
|
||||||
playerRef.current.on('ended', () => {
|
playerRef.current.on('ended', () => {
|
||||||
console.log('Video ended');
|
|
||||||
console.log('Available relatedVideos:', relatedVideos);
|
|
||||||
|
|
||||||
// Keep controls active after video ends
|
// Keep controls active after video ends
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (playerRef.current && !playerRef.current.isDisposed()) {
|
if (playerRef.current && !playerRef.current.isDisposed()) {
|
||||||
@ -2631,18 +2501,10 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
}
|
}
|
||||||
}, 50);
|
}, 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
|
// Check if autoplay is enabled and there's a next video
|
||||||
const isAutoplayEnabled = userPreferences.current.getAutoplayPreference();
|
const isAutoplayEnabled = userPreferences.current.getAutoplayPreference();
|
||||||
const hasNextVideo = mediaData.nextLink !== null;
|
const hasNextVideo = mediaData.nextLink !== null;
|
||||||
|
|
||||||
console.log('isAutoplayEnabled', isAutoplayEnabled);
|
|
||||||
console.log('hasNextVideo', hasNextVideo);
|
|
||||||
console.log('isEmbedPlayer', isEmbedPlayer);
|
|
||||||
|
|
||||||
if (!isEmbedPlayer && isAutoplayEnabled && hasNextVideo) {
|
if (!isEmbedPlayer && isAutoplayEnabled && hasNextVideo) {
|
||||||
// Get next video data for countdown display - find the next video in related videos
|
// Get next video data for countdown display - find the next video in related videos
|
||||||
let nextVideoData = {
|
let nextVideoData = {
|
||||||
@ -2680,11 +2542,9 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
nextVideoData: nextVideoData,
|
nextVideoData: nextVideoData,
|
||||||
countdownSeconds: 5,
|
countdownSeconds: 5,
|
||||||
onPlayNext: () => {
|
onPlayNext: () => {
|
||||||
console.log('Autoplay: Navigating to next video');
|
|
||||||
goToNextVideo();
|
goToNextVideo();
|
||||||
},
|
},
|
||||||
onCancel: () => {
|
onCancel: () => {
|
||||||
console.log('Autoplay: User cancelled, showing related videos');
|
|
||||||
// Hide countdown and show end screen instead
|
// Hide countdown and show end screen instead
|
||||||
if (autoplayCountdown) {
|
if (autoplayCountdown) {
|
||||||
playerRef.current.removeChild(autoplayCountdown);
|
playerRef.current.removeChild(autoplayCountdown);
|
||||||
@ -2705,7 +2565,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
function showEndScreen() {
|
function showEndScreen() {
|
||||||
// Prevent creating multiple end screens
|
// Prevent creating multiple end screens
|
||||||
if (endScreen) {
|
if (endScreen) {
|
||||||
console.log('End screen already exists, removing previous one');
|
|
||||||
playerRef.current.removeChild(endScreen);
|
playerRef.current.removeChild(endScreen);
|
||||||
endScreen = null;
|
endScreen = null;
|
||||||
}
|
}
|
||||||
@ -2753,35 +2612,23 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('error', (error) => {
|
playerRef.current.on('error', (error) => {
|
||||||
console.error('Video.js error:', error);
|
// console.error('Video.js error:', error);
|
||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('fullscreenchange', () => {
|
playerRef.current.on('fullscreenchange', () => {
|
||||||
console.log('Fullscreen changed:', playerRef.current.isFullscreen());
|
// console.log('Fullscreen changed:', playerRef.current.isFullscreen());
|
||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('volumechange', () => {
|
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', () => {
|
playerRef.current.on('ratechange', () => {
|
||||||
console.log('Playback rate changed:', playerRef.current.playbackRate());
|
// console.log('Playback rate changed:', playerRef.current.playbackRate());
|
||||||
});
|
});
|
||||||
|
|
||||||
playerRef.current.on('texttrackchange', () => {
|
playerRef.current.on('texttrackchange', () => {
|
||||||
console.log('Text track changed');
|
// 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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Focus the player element so keyboard controls work
|
// Focus the player element so keyboard controls work
|
||||||
@ -2793,7 +2640,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
const videoElement = playerRef.current.el();
|
const videoElement = playerRef.current.el();
|
||||||
videoElement.setAttribute('tabindex', '0');
|
videoElement.setAttribute('tabindex', '0');
|
||||||
videoElement.focus();
|
videoElement.focus();
|
||||||
console.log('Video player focused for keyboard controls');
|
|
||||||
|
|
||||||
// Add custom keyboard event handler for space key
|
// Add custom keyboard event handler for space key
|
||||||
const handleKeyPress = (event) => {
|
const handleKeyPress = (event) => {
|
||||||
@ -2828,15 +2674,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
document.removeEventListener('keydown', handleKeyPress);
|
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);
|
}, 0);
|
||||||
@ -2867,7 +2704,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
const videoElement = playerRef.current.el();
|
const videoElement = playerRef.current.el();
|
||||||
videoElement.setAttribute('tabindex', '0');
|
videoElement.setAttribute('tabindex', '0');
|
||||||
videoElement.focus();
|
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.css';
|
||||||
|
|
||||||
import VideoJS from './VideoJS.jsx';
|
import VideoJS from './VideoJS.jsx';
|
||||||
// import ChapterList from './components/chapter/ChapterList.jsx';
|
|
||||||
|
|
||||||
// Mount the components when the DOM is ready
|
// Mount the components when the DOM is ready
|
||||||
const mountComponents = () => {
|
const mountComponents = () => {
|
||||||
@ -16,7 +15,6 @@ const mountComponents = () => {
|
|||||||
<VideoJS videoId="video-main" />
|
<VideoJS videoId="video-main" />
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
console.log('Mounted main VideoJS player');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mount embed video player
|
// Mount embed video player
|
||||||
@ -28,7 +26,6 @@ const mountComponents = () => {
|
|||||||
<VideoJS videoId="video-embed" />
|
<VideoJS videoId="video-embed" />
|
||||||
</StrictMode>
|
</StrictMode>
|
||||||
);
|
);
|
||||||
console.log('Mounted embed VideoJS player');
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -37,7 +34,6 @@ window.triggerVideoJSMount = mountComponents;
|
|||||||
|
|
||||||
// Listen for custom events to trigger mounting
|
// Listen for custom events to trigger mounting
|
||||||
document.addEventListener('triggerVideoJSMount', () => {
|
document.addEventListener('triggerVideoJSMount', () => {
|
||||||
console.log('Received triggerVideoJSMount event, attempting to mount VideoJS components...');
|
|
||||||
mountComponents();
|
mountComponents();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -52,7 +48,6 @@ if (document.readyState === 'loading') {
|
|||||||
setInterval(() => {
|
setInterval(() => {
|
||||||
const embedContainer = document.getElementById('video-js-root-embed');
|
const embedContainer = document.getElementById('video-js-root-embed');
|
||||||
if (embedContainer && !embedContainer.hasChildNodes()) {
|
if (embedContainer && !embedContainer.hasChildNodes()) {
|
||||||
console.log('Found unmounted embed container during periodic check, mounting...');
|
|
||||||
mountComponents();
|
mountComponents();
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|||||||
@ -12,7 +12,7 @@ class UserPreferences {
|
|||||||
subtitleLanguage: null, // No subtitles by default
|
subtitleLanguage: null, // No subtitles by default
|
||||||
subtitleEnabled: false, // Subtitles off by default
|
subtitleEnabled: false, // Subtitles off by default
|
||||||
muted: false,
|
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 currentPrefs = this.getPreferences();
|
||||||
const updatedPrefs = { ...currentPrefs, ...preferences };
|
const updatedPrefs = { ...currentPrefs, ...preferences };
|
||||||
localStorage.setItem(this.storageKey, JSON.stringify(updatedPrefs));
|
localStorage.setItem(this.storageKey, JSON.stringify(updatedPrefs));
|
||||||
console.log('User preferences saved:', updatedPrefs);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error saving user preferences to localStorage:', error);
|
console.warn('Error saving user preferences to localStorage:', error);
|
||||||
}
|
}
|
||||||
@ -68,21 +67,13 @@ class UserPreferences {
|
|||||||
setPreference(key, value, forceSet = false) {
|
setPreference(key, value, forceSet = false) {
|
||||||
// Add special logging for subtitle language changes
|
// Add special logging for subtitle language changes
|
||||||
if (key === 'subtitleLanguage') {
|
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
|
// Block subtitle language changes during restoration, but allow forced sets
|
||||||
if (this.isRestoringSubtitles) {
|
if (this.isRestoringSubtitles) {
|
||||||
console.log('🚫 BLOCKED: Subtitle language change during restoration');
|
|
||||||
return; // Don't save during restoration
|
return; // Don't save during restoration
|
||||||
}
|
}
|
||||||
|
|
||||||
// Allow forced sets even if auto-save is disabled (for direct user clicks)
|
// Allow forced sets even if auto-save is disabled (for direct user clicks)
|
||||||
if (this.subtitleAutoSaveDisabled && !forceSet) {
|
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
|
return; // Don't save if disabled unless forced
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,7 +88,6 @@ class UserPreferences {
|
|||||||
resetPreferences() {
|
resetPreferences() {
|
||||||
try {
|
try {
|
||||||
localStorage.removeItem(this.storageKey);
|
localStorage.removeItem(this.storageKey);
|
||||||
console.log('User preferences reset to defaults');
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn('Error resetting user preferences:', error);
|
console.warn('Error resetting user preferences:', error);
|
||||||
}
|
}
|
||||||
@ -112,12 +102,10 @@ class UserPreferences {
|
|||||||
|
|
||||||
// DISABLE subtitle auto-save completely during initial load
|
// DISABLE subtitle auto-save completely during initial load
|
||||||
this.subtitleAutoSaveDisabled = true;
|
this.subtitleAutoSaveDisabled = true;
|
||||||
console.log('🔒 Subtitle auto-save DISABLED during initial load');
|
|
||||||
|
|
||||||
// Re-enable after 3 seconds to ensure everything has settled
|
// Re-enable after 3 seconds to ensure everything has settled
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.subtitleAutoSaveDisabled = false;
|
this.subtitleAutoSaveDisabled = false;
|
||||||
console.log('🔓 Subtitle auto-save RE-ENABLED after initial load period');
|
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
// Apply volume and mute state
|
// Apply volume and mute state
|
||||||
@ -133,11 +121,6 @@ class UserPreferences {
|
|||||||
if (typeof prefs.playbackRate === 'number' && prefs.playbackRate > 0) {
|
if (typeof prefs.playbackRate === 'number' && prefs.playbackRate > 0) {
|
||||||
player.playbackRate(prefs.playbackRate);
|
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', () => {
|
player.on('texttrackchange', () => {
|
||||||
// Skip saving if we're currently restoring subtitles
|
// Skip saving if we're currently restoring subtitles
|
||||||
if (this.isRestoringSubtitles) {
|
if (this.isRestoringSubtitles) {
|
||||||
console.log('Skipping subtitle save - currently restoring preferences');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,24 +155,16 @@ class UserPreferences {
|
|||||||
const track = textTracks[i];
|
const track = textTracks[i];
|
||||||
if (track.kind === 'subtitles' && track.mode === 'showing') {
|
if (track.kind === 'subtitles' && track.mode === 'showing') {
|
||||||
activeLanguage = track.language;
|
activeLanguage = track.language;
|
||||||
console.log('Active subtitle language detected:', activeLanguage);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no subtitles are active, save null
|
|
||||||
if (!activeLanguage) {
|
|
||||||
console.log('No subtitles active, saving null');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.setPreference('subtitleLanguage', activeLanguage);
|
this.setPreference('subtitleLanguage', activeLanguage);
|
||||||
}, 100);
|
}, 100);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Also hook into subtitle menu clicks directly
|
// Also hook into subtitle menu clicks directly
|
||||||
this.setupSubtitleMenuListeners(player);
|
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
|
// Wait for the control bar to be ready
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const controlBar = player.getChild('controlBar');
|
const controlBar = player.getChild('controlBar');
|
||||||
console.log('=== Searching for subtitle controls ===');
|
|
||||||
|
|
||||||
// Check all possible subtitle button names
|
// Check all possible subtitle button names
|
||||||
const possibleNames = ['subtitlesButton', 'captionsButton', 'subsCapsButton', 'textTrackButton'];
|
const possibleNames = ['subtitlesButton', 'captionsButton', 'subsCapsButton', 'textTrackButton'];
|
||||||
@ -210,7 +183,6 @@ class UserPreferences {
|
|||||||
for (const name of possibleNames) {
|
for (const name of possibleNames) {
|
||||||
const button = controlBar.getChild(name);
|
const button = controlBar.getChild(name);
|
||||||
if (button) {
|
if (button) {
|
||||||
console.log(`Found subtitle button: ${name}`);
|
|
||||||
subtitlesButton = button;
|
subtitlesButton = button;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -218,26 +190,21 @@ class UserPreferences {
|
|||||||
|
|
||||||
// Also try to find by scanning all children
|
// Also try to find by scanning all children
|
||||||
if (!subtitlesButton) {
|
if (!subtitlesButton) {
|
||||||
console.log('Scanning all control bar children...');
|
|
||||||
const children = controlBar.children();
|
const children = controlBar.children();
|
||||||
children.forEach((child, index) => {
|
children.forEach((child) => {
|
||||||
const name = child.name_ || child.constructor.name || 'Unknown';
|
const name = child.name_ || child.constructor.name || 'Unknown';
|
||||||
console.log(`Child ${index}: ${name}`);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
name.toLowerCase().includes('subtitle') ||
|
name.toLowerCase().includes('subtitle') ||
|
||||||
name.toLowerCase().includes('caption') ||
|
name.toLowerCase().includes('caption') ||
|
||||||
name.toLowerCase().includes('text')
|
name.toLowerCase().includes('text')
|
||||||
) {
|
) {
|
||||||
console.log(`Potential subtitle button found: ${name}`);
|
|
||||||
subtitlesButton = child;
|
subtitlesButton = child;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (subtitlesButton) {
|
if (subtitlesButton) {
|
||||||
console.log('Found subtitles button, setting up menu listeners');
|
|
||||||
|
|
||||||
// Wait a bit more for the menu to be created
|
// Wait a bit more for the menu to be created
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.attachMenuItemListeners(player, subtitlesButton);
|
this.attachMenuItemListeners(player, subtitlesButton);
|
||||||
@ -248,8 +215,6 @@ class UserPreferences {
|
|||||||
this.attachMenuItemListeners(player, subtitlesButton);
|
this.attachMenuItemListeners(player, subtitlesButton);
|
||||||
}, 2000);
|
}, 2000);
|
||||||
} else {
|
} else {
|
||||||
console.log('No subtitles button found after exhaustive search');
|
|
||||||
|
|
||||||
// Try alternative approach - listen to DOM changes
|
// Try alternative approach - listen to DOM changes
|
||||||
this.setupDOMBasedListeners(player);
|
this.setupDOMBasedListeners(player);
|
||||||
}
|
}
|
||||||
@ -261,8 +226,6 @@ class UserPreferences {
|
|||||||
* @param {Object} player - Video.js player instance
|
* @param {Object} player - Video.js player instance
|
||||||
*/
|
*/
|
||||||
setupDOMBasedListeners(player) {
|
setupDOMBasedListeners(player) {
|
||||||
console.log('Setting up DOM-based subtitle listeners as fallback');
|
|
||||||
|
|
||||||
// Wait for DOM to be ready
|
// Wait for DOM to be ready
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
const playerEl = player.el();
|
const playerEl = player.el();
|
||||||
@ -277,8 +240,6 @@ class UserPreferences {
|
|||||||
target.closest('.vjs-captions-menu-item') ||
|
target.closest('.vjs-captions-menu-item') ||
|
||||||
(target.closest('.vjs-menu-item') && target.textContent.toLowerCase().includes('subtitle'))
|
(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
|
// Extract language from the clicked item
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.detectActiveSubtitleFromDOM(player, true); // Force set for user clicks
|
this.detectActiveSubtitleFromDOM(player, true); // Force set for user clicks
|
||||||
@ -287,14 +248,11 @@ class UserPreferences {
|
|||||||
|
|
||||||
// Also handle "captions off" clicks
|
// Also handle "captions off" clicks
|
||||||
if (target.closest('.vjs-menu-item') && target.textContent.toLowerCase().includes('off')) {
|
if (target.closest('.vjs-menu-item') && target.textContent.toLowerCase().includes('off')) {
|
||||||
console.log('Captions off clicked via DOM listener');
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
|
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
|
||||||
}, 200);
|
}, 200);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('DOM-based subtitle listeners attached');
|
|
||||||
}
|
}
|
||||||
}, 1500);
|
}, 1500);
|
||||||
}
|
}
|
||||||
@ -307,7 +265,6 @@ class UserPreferences {
|
|||||||
detectActiveSubtitleFromDOM(player, forceSet = false) {
|
detectActiveSubtitleFromDOM(player, forceSet = false) {
|
||||||
// Skip saving if we're currently restoring subtitles
|
// Skip saving if we're currently restoring subtitles
|
||||||
if (this.isRestoringSubtitles) {
|
if (this.isRestoringSubtitles) {
|
||||||
console.log('Skipping DOM subtitle save - currently restoring preferences');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -318,7 +275,6 @@ class UserPreferences {
|
|||||||
const track = textTracks[i];
|
const track = textTracks[i];
|
||||||
if (track.kind === 'subtitles' && track.mode === 'showing') {
|
if (track.kind === 'subtitles' && track.mode === 'showing') {
|
||||||
activeLanguage = track.language;
|
activeLanguage = track.language;
|
||||||
console.log('DOM detection - Active subtitle language:', activeLanguage, track.label);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -335,49 +291,37 @@ class UserPreferences {
|
|||||||
try {
|
try {
|
||||||
const menu = subtitlesButton.menu;
|
const menu = subtitlesButton.menu;
|
||||||
if (menu && menu.children_) {
|
if (menu && menu.children_) {
|
||||||
console.log('Found subtitle menu with', menu.children_.length, 'items');
|
menu.children_.forEach((menuItem) => {
|
||||||
|
|
||||||
menu.children_.forEach((menuItem, index) => {
|
|
||||||
if (menuItem.track) {
|
if (menuItem.track) {
|
||||||
const track = menuItem.track;
|
const track = menuItem.track;
|
||||||
console.log(`Menu item ${index}: ${track.label} (${track.language})`);
|
|
||||||
|
|
||||||
// Override the handleClick method
|
// Override the handleClick method
|
||||||
const originalHandleClick = menuItem.handleClick.bind(menuItem);
|
const originalHandleClick = menuItem.handleClick.bind(menuItem);
|
||||||
menuItem.handleClick = () => {
|
menuItem.handleClick = () => {
|
||||||
console.log('Subtitle menu item clicked:', track.label, track.language);
|
|
||||||
|
|
||||||
// Call original click handler
|
// Call original click handler
|
||||||
originalHandleClick();
|
originalHandleClick();
|
||||||
|
|
||||||
// Save the preference after a short delay
|
// Save the preference after a short delay
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (track.mode === 'showing') {
|
if (track.mode === 'showing') {
|
||||||
console.log('Saving subtitle preference:', track.language);
|
|
||||||
this.setPreference('subtitleLanguage', track.language, true); // Force set for user clicks
|
this.setPreference('subtitleLanguage', track.language, true); // Force set for user clicks
|
||||||
} else {
|
} else {
|
||||||
console.log('Subtitle disabled, saving null');
|
|
||||||
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
|
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
} else if (menuItem.label && menuItem.label.toLowerCase().includes('off')) {
|
} else if (menuItem.label && menuItem.label.toLowerCase().includes('off')) {
|
||||||
// Handle "captions off" option
|
// Handle "captions off" option
|
||||||
console.log('Found captions off menu item');
|
|
||||||
const originalHandleClick = menuItem.handleClick.bind(menuItem);
|
const originalHandleClick = menuItem.handleClick.bind(menuItem);
|
||||||
menuItem.handleClick = () => {
|
menuItem.handleClick = () => {
|
||||||
console.log('Captions off clicked');
|
|
||||||
originalHandleClick();
|
originalHandleClick();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
console.log('Saving subtitle preference: null (off)');
|
|
||||||
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
|
this.setPreference('subtitleLanguage', null, true); // Force set for user clicks
|
||||||
}, 100);
|
}, 100);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
console.log('Could not find subtitle menu or menu items');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error setting up subtitle menu listeners:', error);
|
console.error('Error setting up subtitle menu listeners:', error);
|
||||||
@ -395,20 +339,9 @@ class UserPreferences {
|
|||||||
if (savedLanguage) {
|
if (savedLanguage) {
|
||||||
// Set flag to prevent auto-save during restoration
|
// Set flag to prevent auto-save during restoration
|
||||||
this.isRestoringSubtitles = true;
|
this.isRestoringSubtitles = true;
|
||||||
console.log('isRestoringSubtitles', this.isRestoringSubtitles);
|
|
||||||
|
|
||||||
// Multiple attempts with increasing delays to ensure text tracks are loaded
|
// Multiple attempts with increasing delays to ensure text tracks are loaded
|
||||||
const attemptToApplySubtitles = (attempt = 1) => {
|
const attemptToApplySubtitles = (attempt = 1) => {
|
||||||
const textTracks = player.textTracks();
|
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
|
// First, disable all subtitle tracks
|
||||||
for (let i = 0; i < textTracks.length; i++) {
|
for (let i = 0; i < textTracks.length; i++) {
|
||||||
@ -432,7 +365,6 @@ class UserPreferences {
|
|||||||
const track = textTracks[i];
|
const track = textTracks[i];
|
||||||
if (track.kind === 'subtitles' && matchesLang(track, savedLanguage)) {
|
if (track.kind === 'subtitles' && matchesLang(track, savedLanguage)) {
|
||||||
track.mode = 'showing';
|
track.mode = 'showing';
|
||||||
console.log('✓ Applied saved subtitle language:', savedLanguage, track.label);
|
|
||||||
found = true;
|
found = true;
|
||||||
|
|
||||||
// Also update the menu UI to reflect the selection
|
// Also update the menu UI to reflect the selection
|
||||||
@ -452,10 +384,7 @@ class UserPreferences {
|
|||||||
const track = textTracks[i];
|
const track = textTracks[i];
|
||||||
if (track.kind === 'subtitles') {
|
if (track.kind === 'subtitles') {
|
||||||
track.mode = 'showing';
|
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
|
// Save back the language we actually enabled for future precise matches
|
||||||
const langToSave = track.language || track.srclang || null;
|
const langToSave = track.language || track.srclang || null;
|
||||||
if (langToSave) this.setPreference('subtitleLanguage', langToSave, true);
|
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
|
// Clear the restoration flag after a longer delay to ensure all events have settled
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.isRestoringSubtitles = false;
|
this.isRestoringSubtitles = false;
|
||||||
console.log('✅ Subtitle restoration complete, auto-save re-enabled');
|
|
||||||
}, 600); // Increased to 3 seconds
|
}, 600); // Increased to 3 seconds
|
||||||
|
|
||||||
// If not found and we haven't tried too many times, try again
|
// If not found and we haven't tried too many times, try again
|
||||||
if (!found && attempt < 5) {
|
if (!found && attempt < 5) {
|
||||||
console.log(`Subtitle language ${savedLanguage} not found, retrying in ${attempt * 50}ms...`);
|
|
||||||
setTimeout(() => attemptToApplySubtitles(attempt + 1), attempt * 50);
|
setTimeout(() => attemptToApplySubtitles(attempt + 1), attempt * 50);
|
||||||
} else if (!found) {
|
} else if (!found) {
|
||||||
console.warn('Could not find subtitle track for language:', savedLanguage);
|
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
|
// Update custom settings menu to show "Off" as selected
|
||||||
this.updateCustomSettingsMenuUI(player);
|
this.updateCustomSettingsMenuUI(player);
|
||||||
} catch (e) {}
|
} catch (e) {
|
||||||
console.log('No subtitle auto-apply on load (disabled or no language).');
|
console.error('Error applying subtitle preference:', e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -538,13 +466,9 @@ class UserPreferences {
|
|||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
buttonEl.classList.add('vjs-subs-active');
|
buttonEl.classList.add('vjs-subs-active');
|
||||||
console.log('✓ Added vjs-subs-active class to subtitle button');
|
|
||||||
} else {
|
} else {
|
||||||
buttonEl.classList.remove('vjs-subs-active');
|
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) {
|
} catch (error) {
|
||||||
console.error('Error updating subtitle button visual state:', error);
|
console.error('Error updating subtitle button visual state:', error);
|
||||||
@ -569,7 +493,6 @@ class UserPreferences {
|
|||||||
if (menuItem.track) {
|
if (menuItem.track) {
|
||||||
if (menuItem.track === activeTrack) {
|
if (menuItem.track === activeTrack) {
|
||||||
menuItem.selected(true);
|
menuItem.selected(true);
|
||||||
console.log('Updated menu UI for:', menuItem.track.label);
|
|
||||||
} else {
|
} else {
|
||||||
menuItem.selected(false);
|
menuItem.selected(false);
|
||||||
}
|
}
|
||||||
@ -598,16 +521,11 @@ class UserPreferences {
|
|||||||
const customSettingsMenu = controlBar.getChild('CustomSettingsMenu');
|
const customSettingsMenu = controlBar.getChild('CustomSettingsMenu');
|
||||||
|
|
||||||
if (customSettingsMenu && customSettingsMenu.refreshSubtitlesSubmenu) {
|
if (customSettingsMenu && customSettingsMenu.refreshSubtitlesSubmenu) {
|
||||||
console.log('Updating custom settings menu UI...');
|
|
||||||
customSettingsMenu.refreshSubtitlesSubmenu();
|
customSettingsMenu.refreshSubtitlesSubmenu();
|
||||||
} else if (attempt < 5) {
|
} else if (attempt < 5) {
|
||||||
// Retry after a short delay if menu not found
|
// 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);
|
setTimeout(() => attemptUpdate(attempt + 1), attempt * 200);
|
||||||
} else {
|
|
||||||
console.log('Custom settings menu not found after multiple attempts');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating custom settings menu UI:', error);
|
console.error('Error updating custom settings menu UI:', error);
|
||||||
@ -639,12 +557,10 @@ class UserPreferences {
|
|||||||
* @param {string} language - Subtitle language code
|
* @param {string} language - Subtitle language code
|
||||||
*/
|
*/
|
||||||
forceSetSubtitleLanguage(language) {
|
forceSetSubtitleLanguage(language) {
|
||||||
console.log(`🚀 FORCE SAVING subtitle language: ${language}`);
|
|
||||||
const currentPrefs = this.getPreferences();
|
const currentPrefs = this.getPreferences();
|
||||||
const updatedPrefs = { ...currentPrefs, subtitleLanguage: language };
|
const updatedPrefs = { ...currentPrefs, subtitleLanguage: language };
|
||||||
try {
|
try {
|
||||||
localStorage.setItem(this.storageKey, JSON.stringify(updatedPrefs));
|
localStorage.setItem(this.storageKey, JSON.stringify(updatedPrefs));
|
||||||
console.log('✅ Force saved subtitle language:', language);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Error force saving subtitle language:', error);
|
console.error('❌ Error force saving subtitle language:', error);
|
||||||
}
|
}
|
||||||
@ -664,7 +580,6 @@ class UserPreferences {
|
|||||||
*/
|
*/
|
||||||
setAutoplayPreference(autoplay) {
|
setAutoplayPreference(autoplay) {
|
||||||
this.setPreference('autoplay', 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