mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-10-06 22:32:41 +07:00
202 lines
6.4 KiB
JavaScript
202 lines
6.4 KiB
JavaScript
// components/controls/CustomChaptersOverlay.js
|
||
import videojs from 'video.js';
|
||
|
||
// Get the Component base class from Video.js
|
||
const Component = videojs.getComponent('Component');
|
||
|
||
class CustomChaptersOverlay extends Component {
|
||
constructor(player, options) {
|
||
super(player, options);
|
||
|
||
this.chaptersData = options.chaptersData || [];
|
||
this.overlay = null;
|
||
this.chaptersList = null;
|
||
|
||
// Bind methods
|
||
this.createOverlay = this.createOverlay.bind(this);
|
||
this.updateCurrentChapter = this.updateCurrentChapter.bind(this);
|
||
this.toggleOverlay = this.toggleOverlay.bind(this);
|
||
|
||
// Initialize after player is ready
|
||
this.player().ready(() => {
|
||
this.createOverlay();
|
||
this.setupChaptersButton();
|
||
});
|
||
}
|
||
|
||
createOverlay() {
|
||
if (!this.chaptersData || this.chaptersData.length === 0) {
|
||
console.log('⚠ No chapters data available for overlay');
|
||
return;
|
||
}
|
||
|
||
const playerEl = this.player().el();
|
||
|
||
// Create overlay element
|
||
this.overlay = document.createElement('div');
|
||
this.overlay.className = 'custom-chapters-overlay';
|
||
this.overlay.style.cssText = `
|
||
position: absolute;
|
||
top: 0;
|
||
right: 0;
|
||
width: 300px;
|
||
height: 100%;
|
||
background: linear-gradient(180deg, rgba(20, 20, 30, 0.95) 0%, rgba(40, 40, 50, 0.95) 100%);
|
||
color: white;
|
||
z-index: 1000;
|
||
display: none;
|
||
overflow-y: auto;
|
||
box-shadow: -4px 0 20px rgba(0, 0, 0, 0.5);
|
||
`;
|
||
|
||
// Create header
|
||
const header = document.createElement('div');
|
||
header.style.cssText = `
|
||
background: rgba(0, 0, 0, 0.8);
|
||
padding: 20px;
|
||
text-align: center;
|
||
font-weight: bold;
|
||
font-size: 14px;
|
||
letter-spacing: 2px;
|
||
border-bottom: 2px solid #4a90e2;
|
||
position: sticky;
|
||
top: 0;
|
||
`;
|
||
header.textContent = 'CHAPTERS';
|
||
this.overlay.appendChild(header);
|
||
|
||
// Create close button
|
||
const closeBtn = document.createElement('div');
|
||
closeBtn.style.cssText = `
|
||
position: absolute;
|
||
top: 15px;
|
||
right: 15px;
|
||
width: 25px;
|
||
height: 25px;
|
||
background: rgba(0, 0, 0, 0.6);
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
font-size: 16px;
|
||
z-index: 10;
|
||
`;
|
||
closeBtn.textContent = '×';
|
||
closeBtn.onclick = () => {
|
||
this.overlay.style.display = 'none';
|
||
};
|
||
this.overlay.appendChild(closeBtn);
|
||
|
||
// Create chapters list
|
||
this.chaptersList = document.createElement('div');
|
||
this.chaptersList.style.cssText = `
|
||
padding: 10px 0;
|
||
`;
|
||
|
||
// Add chapters from data
|
||
this.chaptersData.forEach((chapter) => {
|
||
const chapterItem = document.createElement('div');
|
||
chapterItem.style.cssText = `
|
||
padding: 15px 20px;
|
||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||
cursor: pointer;
|
||
transition: background 0.2s ease;
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
`;
|
||
chapterItem.textContent = chapter.text;
|
||
|
||
// Add hover effect
|
||
chapterItem.onmouseenter = () => {
|
||
chapterItem.style.background = 'rgba(74, 144, 226, 0.2)';
|
||
};
|
||
chapterItem.onmouseleave = () => {
|
||
chapterItem.style.background = 'transparent';
|
||
};
|
||
|
||
// Add click handler
|
||
chapterItem.onclick = () => {
|
||
this.player().currentTime(chapter.startTime);
|
||
this.overlay.style.display = 'none';
|
||
|
||
// Update active state
|
||
this.chaptersList.querySelectorAll('div').forEach((item) => {
|
||
item.style.background = 'transparent';
|
||
item.style.fontWeight = 'normal';
|
||
});
|
||
chapterItem.style.background = 'rgba(74, 144, 226, 0.4)';
|
||
chapterItem.style.fontWeight = 'bold';
|
||
};
|
||
|
||
this.chaptersList.appendChild(chapterItem);
|
||
});
|
||
|
||
this.overlay.appendChild(this.chaptersList);
|
||
|
||
// Add to player
|
||
playerEl.appendChild(this.overlay);
|
||
|
||
// Set up time update listener
|
||
this.player().on('timeupdate', this.updateCurrentChapter);
|
||
|
||
console.log('✓ Custom chapters overlay created');
|
||
}
|
||
|
||
setupChaptersButton() {
|
||
const chaptersButton = this.player().getChild('controlBar').getChild('chaptersButton');
|
||
if (chaptersButton) {
|
||
// Override the click handler
|
||
chaptersButton.off('click'); // Remove default handler
|
||
chaptersButton.on('click', this.toggleOverlay);
|
||
}
|
||
}
|
||
|
||
toggleOverlay() {
|
||
if (!this.overlay) return;
|
||
|
||
if (this.overlay.style.display === 'none' || !this.overlay.style.display) {
|
||
this.overlay.style.display = 'block';
|
||
} else {
|
||
this.overlay.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
updateCurrentChapter() {
|
||
if (!this.chaptersList || !this.chaptersData) return;
|
||
|
||
const currentTime = this.player().currentTime();
|
||
const chapterItems = this.chaptersList.querySelectorAll('div');
|
||
|
||
chapterItems.forEach((item, index) => {
|
||
const chapter = this.chaptersData[index];
|
||
const isPlaying =
|
||
currentTime >= chapter.startTime &&
|
||
(index === this.chaptersData.length - 1 || currentTime < this.chaptersData[index + 1].startTime);
|
||
|
||
if (isPlaying) {
|
||
item.style.borderLeft = '4px solid #10b981';
|
||
item.style.paddingLeft = '16px';
|
||
} else {
|
||
item.style.borderLeft = 'none';
|
||
item.style.paddingLeft = '20px';
|
||
}
|
||
});
|
||
}
|
||
|
||
dispose() {
|
||
if (this.overlay) {
|
||
this.overlay.remove();
|
||
}
|
||
super.dispose();
|
||
}
|
||
}
|
||
|
||
// Set component name for Video.js
|
||
CustomChaptersOverlay.prototype.controlText_ = 'Chapters Overlay';
|
||
|
||
// Register the component with Video.js
|
||
videojs.registerComponent('CustomChaptersOverlay', CustomChaptersOverlay);
|
||
|
||
export default CustomChaptersOverlay;
|