mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-09 08:58:53 -05:00
fix: Video.js (chaptersData conditions, separate sprite preview, add close icon in modal settings)
This commit is contained in:
parent
0eb9b5ce1b
commit
ca830788ca
@ -539,6 +539,12 @@ white-space: nowrap; text-overflow: ellipsis; height: 28px; overflow: hidden; di
|
|||||||
.chapter-close button{ background:transparent; color:#fff; border: 0; width: 40px; height: 40px; padding: 0; display: flex;
|
.chapter-close button{ background:transparent; color:#fff; border: 0; width: 40px; height: 40px; padding: 0; display: flex;
|
||||||
align-items: center; justify-content: center; border-radius:8px;}
|
align-items: center; justify-content: center; border-radius:8px;}
|
||||||
.chapter-close button:hover{ background:rgba(255,255,255,0.1);}
|
.chapter-close button:hover{ background:rgba(255,255,255,0.1);}
|
||||||
|
|
||||||
|
.settings-header{ display:flex; align-items:center; justify-content:space-between; position:relative;}
|
||||||
|
.settings-close-btn{ background:transparent; color:#fff; border: 0; width: 32px; height: 32px; padding: 0; display: flex;
|
||||||
|
align-items: center; justify-content: center; border-radius:6px; cursor:pointer;}
|
||||||
|
.settings-close-btn:hover{ background:rgba(255,255,255,0.1);}
|
||||||
|
|
||||||
.playlist-action-menu{ display:none; justify-content:space-between; gap:10px;}
|
.playlist-action-menu{ display:none; justify-content:space-between; gap:10px;}
|
||||||
.playlist-action-menu button{ background:transparent; border: 0; width: 40px; height: 40px; padding: 0; display: flex;
|
.playlist-action-menu button{ background:transparent; border: 0; width: 40px; height: 40px; padding: 0; display: flex;
|
||||||
align-items: center; justify-content: center; align-items:center; border-radius:100px; }
|
align-items: center; justify-content: center; align-items:center; border-radius:100px; }
|
||||||
@ -596,11 +602,16 @@ overflow:visible !important; clip: initial !important;}
|
|||||||
.vjs-chapter-floating-tooltip{ text-align:center; width:160px !important; max-width:100% !important ; height:auto;}
|
.vjs-chapter-floating-tooltip{ text-align:center; width:160px !important; max-width:100% !important ; height:auto;}
|
||||||
.chapter-image-sprite{width: 166px !important; max-width:100% !important; height: 96px; margin:0 auto 10px;
|
.chapter-image-sprite{width: 166px !important; max-width:100% !important; height: 96px; margin:0 auto 10px;
|
||||||
border-radius: 6px; border: 3px solid #FFF; }
|
border-radius: 6px; border: 3px solid #FFF; }
|
||||||
.vjs-chapter-floating-tooltip .chapter-title{ font-size:24px; margin:0 0 10px; font-weight:700; text-overflow:ellipsis; white-space:nowrap;
|
.vjs-chapter-floating-tooltip .chapter-title{ font-size:16px; margin:0 0 10px; font-weight:700; word-break: break-all; line-height:20px;}
|
||||||
height:30px; line-height:30px; overflow:hidden;}
|
|
||||||
.vjs-chapter-floating-tooltip .position-info,
|
.vjs-chapter-floating-tooltip .position-info,
|
||||||
.vjs-chapter-floating-tooltip .chapter-info{ font-size:15px; display:inline-block; margin:0 0 2px; line-height:normal; vertical-align:top; line-height:20px;}
|
.vjs-chapter-floating-tooltip .chapter-info{ font-size:15px; display:inline-block; margin:0 0 2px; line-height:normal; vertical-align:top; line-height:20px;}
|
||||||
|
|
||||||
|
/* Sprite Preview Tooltip Styles - Match chapter styling */
|
||||||
|
.vjs-sprite-preview-tooltip{ text-align:center; width:172px !important; max-width:100% !important ; height:auto;}
|
||||||
|
|
||||||
|
.vjs-sprite-preview-tooltip .sprite-image-preview{ width: 166px !important; max-width:100% !important; height: 96px; margin:0 auto;
|
||||||
|
border-radius: 6px; border: 3px solid #FFF; }
|
||||||
|
|
||||||
|
|
||||||
@media (pointer: coarse) {
|
@media (pointer: coarse) {
|
||||||
.video-js .vjs-volume-panel div.vjs-volume-control {width: auto; opacity: 1;}
|
.video-js .vjs-volume-panel div.vjs-volume-control {width: auto; opacity: 1;}
|
||||||
|
|||||||
@ -155,7 +155,15 @@ class CustomSettingsMenu extends Component {
|
|||||||
(currentQuality ? String(currentQuality) : "Auto");
|
(currentQuality ? String(currentQuality) : "Auto");
|
||||||
|
|
||||||
// Settings menu content - split into separate variables for maintainability
|
// Settings menu content - split into separate variables for maintainability
|
||||||
const settingsHeader = `<div class="settings-header">Settings</div>`;
|
const settingsHeader = `
|
||||||
|
<div class="settings-header">
|
||||||
|
<span>Settings</span>
|
||||||
|
<button class="settings-close-btn" aria-label="Close settings">
|
||||||
|
<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="currentColor"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
const playbackSpeedSection = `
|
const playbackSpeedSection = `
|
||||||
<div class="settings-item" data-setting="playback-speed">
|
<div class="settings-item" data-setting="playback-speed">
|
||||||
@ -527,6 +535,29 @@ class CustomSettingsMenu extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setupEventListeners() {
|
setupEventListeners() {
|
||||||
|
// Close button functionality
|
||||||
|
const closeButton = this.settingsOverlay.querySelector('.settings-close-btn');
|
||||||
|
if (closeButton) {
|
||||||
|
const closeFunction = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
this.settingsOverlay.classList.remove("show");
|
||||||
|
this.settingsOverlay.style.display = "none";
|
||||||
|
this.speedSubmenu.style.display = "none";
|
||||||
|
if (this.qualitySubmenu) this.qualitySubmenu.style.display = "none";
|
||||||
|
if (this.subtitlesSubmenu) this.subtitlesSubmenu.style.display = "none";
|
||||||
|
const btnEl = this.settingsButton?.el();
|
||||||
|
if (btnEl) {
|
||||||
|
btnEl.classList.remove("settings-clicked");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
closeButton.addEventListener('click', closeFunction);
|
||||||
|
closeButton.addEventListener('touchend', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
closeFunction(e);
|
||||||
|
}, { passive: false });
|
||||||
|
}
|
||||||
|
|
||||||
// Settings item clicks
|
// Settings item clicks
|
||||||
this.settingsOverlay.addEventListener("click", (e) => {
|
this.settingsOverlay.addEventListener("click", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|||||||
@ -218,7 +218,7 @@ class ChapterMarkers extends Component {
|
|||||||
// Update text content without rebuilding DOM
|
// Update text content without rebuilding DOM
|
||||||
this.chapterTitle.textContent = currentChapter.text;
|
this.chapterTitle.textContent = currentChapter.text;
|
||||||
this.chapterInfo.textContent = `Chapter: ${startTime} - ${endTime}`;
|
this.chapterInfo.textContent = `Chapter: ${startTime} - ${endTime}`;
|
||||||
this.positionInfo.textContent = `Position: ${timeAtPosition}`;
|
// this.positionInfo.textContent = `Position: ${timeAtPosition}`;
|
||||||
|
|
||||||
// Update sprite thumbnail
|
// Update sprite thumbnail
|
||||||
this.updateSpriteThumbnail(currentTime);
|
this.updateSpriteThumbnail(currentTime);
|
||||||
|
|||||||
246
frontend-tools/video-js/src/components/markers/SpritePreview.js
Normal file
246
frontend-tools/video-js/src/components/markers/SpritePreview.js
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
import videojs from 'video.js';
|
||||||
|
|
||||||
|
const Component = videojs.getComponent('Component');
|
||||||
|
|
||||||
|
// Sprite Preview Component for seekbar hover thumbnails (used when no chapters exist)
|
||||||
|
class SpritePreview extends Component {
|
||||||
|
constructor(player, options) {
|
||||||
|
super(player, options);
|
||||||
|
this.tooltip = null;
|
||||||
|
this.isHovering = false;
|
||||||
|
this.previewSprite = options.previewSprite || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
createEl() {
|
||||||
|
const el = super.createEl('div', {
|
||||||
|
className: 'vjs-sprite-preview-track',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Initialize tooltip as null - will be created when needed
|
||||||
|
this.tooltip = null;
|
||||||
|
|
||||||
|
return el;
|
||||||
|
}
|
||||||
|
|
||||||
|
setupProgressBarHover() {
|
||||||
|
const progressControl = this.player().getChild('controlBar').getChild('progressControl');
|
||||||
|
if (!progressControl) return;
|
||||||
|
|
||||||
|
const seekBar = progressControl.getChild('seekBar');
|
||||||
|
if (!seekBar) return;
|
||||||
|
|
||||||
|
const seekBarEl = seekBar.el();
|
||||||
|
|
||||||
|
// Only setup if we have sprite data
|
||||||
|
if (!this.previewSprite || !this.previewSprite.url) {
|
||||||
|
console.log('No sprite data available for preview:', this.previewSprite);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure tooltip is properly created and add to seekBar if not already added
|
||||||
|
if (!this.tooltip || !this.tooltip.nodeType) {
|
||||||
|
// Create tooltip if it's not a proper DOM node
|
||||||
|
this.tooltip = videojs.dom.createEl('div', {
|
||||||
|
className: 'vjs-sprite-preview-tooltip',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Style the floating tooltip
|
||||||
|
Object.assign(this.tooltip.style, {
|
||||||
|
position: 'absolute',
|
||||||
|
zIndex: '1000',
|
||||||
|
bottom: '45px',
|
||||||
|
transform: 'translateX(-50%)',
|
||||||
|
display: 'none',
|
||||||
|
minWidth: '172px', // Accommodate 166px image + 3px border on each side
|
||||||
|
maxWidth: '172px',
|
||||||
|
width: '172px',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Create stable DOM structure
|
||||||
|
this.spriteImage = videojs.dom.createEl('div', {
|
||||||
|
className: 'sprite-image-preview',
|
||||||
|
});
|
||||||
|
Object.assign(this.spriteImage.style, {
|
||||||
|
display: 'block',
|
||||||
|
overflow: 'hidden',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Append sprite image to tooltip (no time info)
|
||||||
|
this.tooltip.appendChild(this.spriteImage);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add tooltip to seekBar if not already added
|
||||||
|
if (!seekBarEl.querySelector('.vjs-sprite-preview-tooltip')) {
|
||||||
|
try {
|
||||||
|
seekBarEl.appendChild(this.tooltip);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Could not append sprite preview tooltip:', error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the progress control element for larger hover area
|
||||||
|
const progressControlEl = progressControl.el();
|
||||||
|
|
||||||
|
// Remove existing listeners to prevent duplicates
|
||||||
|
progressControlEl.removeEventListener('mouseenter', this.handleMouseEnter);
|
||||||
|
progressControlEl.removeEventListener('mouseleave', this.handleMouseLeave);
|
||||||
|
progressControlEl.removeEventListener('mousemove', this.handleMouseMove);
|
||||||
|
|
||||||
|
// Bind methods to preserve context
|
||||||
|
this.handleMouseEnter = () => {
|
||||||
|
this.isHovering = true;
|
||||||
|
this.tooltip.style.display = 'block';
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleMouseLeave = () => {
|
||||||
|
this.isHovering = false;
|
||||||
|
this.tooltip.style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleMouseMove = (e) => {
|
||||||
|
if (!this.isHovering) return;
|
||||||
|
this.updateSpriteTooltip(e, seekBarEl, progressControlEl);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event listeners to the entire progress control area
|
||||||
|
progressControlEl.addEventListener('mouseenter', this.handleMouseEnter);
|
||||||
|
progressControlEl.addEventListener('mouseleave', this.handleMouseLeave);
|
||||||
|
progressControlEl.addEventListener('mousemove', this.handleMouseMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSpriteTooltip(event, seekBarEl, progressControlEl) {
|
||||||
|
if (!this.tooltip || !this.isHovering) return;
|
||||||
|
|
||||||
|
const duration = this.player().duration();
|
||||||
|
if (!duration) return;
|
||||||
|
|
||||||
|
// Calculate time position based on mouse position relative to seekBar
|
||||||
|
const seekBarRect = seekBarEl.getBoundingClientRect();
|
||||||
|
const progressControlRect = progressControlEl.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Use seekBar for horizontal calculation but allow vertical tolerance
|
||||||
|
const offsetX = event.clientX - seekBarRect.left;
|
||||||
|
const percentage = Math.max(0, Math.min(1, offsetX / seekBarRect.width));
|
||||||
|
const currentTime = percentage * duration;
|
||||||
|
|
||||||
|
// Position tooltip relative to progress control area
|
||||||
|
const tooltipOffsetX = event.clientX - progressControlRect.left;
|
||||||
|
|
||||||
|
// Update sprite thumbnail
|
||||||
|
this.updateSpriteThumbnail(currentTime);
|
||||||
|
|
||||||
|
// Position tooltip with smart boundary detection
|
||||||
|
// Force tooltip to be visible momentarily to get accurate dimensions
|
||||||
|
this.tooltip.style.visibility = 'hidden';
|
||||||
|
this.tooltip.style.display = 'block';
|
||||||
|
|
||||||
|
const tooltipWidth = this.tooltip.offsetWidth || 172; // Fallback width matches our fixed width
|
||||||
|
const progressControlWidth = progressControlRect.width;
|
||||||
|
const halfTooltipWidth = tooltipWidth / 2;
|
||||||
|
|
||||||
|
// Calculate ideal position (where mouse is)
|
||||||
|
let idealLeft = tooltipOffsetX;
|
||||||
|
|
||||||
|
// Check and adjust boundaries
|
||||||
|
if (idealLeft - halfTooltipWidth < 0) {
|
||||||
|
// Too far left - align to left edge with small margin
|
||||||
|
idealLeft = halfTooltipWidth + 5;
|
||||||
|
} else if (idealLeft + halfTooltipWidth > progressControlWidth) {
|
||||||
|
// Too far right - align to right edge with small margin
|
||||||
|
idealLeft = progressControlWidth - halfTooltipWidth - 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply position and make visible
|
||||||
|
this.tooltip.style.left = `${idealLeft}px`;
|
||||||
|
this.tooltip.style.visibility = 'visible';
|
||||||
|
this.tooltip.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSpriteThumbnail(currentTime) {
|
||||||
|
if (!this.previewSprite || !this.previewSprite.url) {
|
||||||
|
// Hide image if no sprite data available
|
||||||
|
this.spriteImage.style.display = 'none';
|
||||||
|
console.log('No sprite data available:', this.previewSprite);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { url, frame } = this.previewSprite;
|
||||||
|
const { width, height } = frame;
|
||||||
|
|
||||||
|
// Calculate which frame to show based on current time
|
||||||
|
// Use sprite interval from frame data, fallback to 10 seconds
|
||||||
|
const frameInterval = frame.seconds || 10;
|
||||||
|
|
||||||
|
// Try to detect total frames based on video duration vs frame interval
|
||||||
|
const videoDuration = this.player().duration() || 45; // fallback duration
|
||||||
|
const calculatedMaxFrames = Math.ceil(videoDuration / frameInterval);
|
||||||
|
const maxFrames = Math.min(calculatedMaxFrames, 6); // Cap at 6 frames to be safe
|
||||||
|
|
||||||
|
let frameIndex = Math.floor(currentTime / frameInterval);
|
||||||
|
|
||||||
|
// Clamp frameIndex to available frames to prevent showing empty areas
|
||||||
|
frameIndex = Math.min(frameIndex, maxFrames - 1);
|
||||||
|
|
||||||
|
// Based on the sprite image, it appears to have frames arranged vertically
|
||||||
|
// Let's try a vertical layout first (1 column, multiple rows)
|
||||||
|
const frameRow = frameIndex; // Each frame is on its own row
|
||||||
|
const frameCol = 0; // Always first (and only) column
|
||||||
|
|
||||||
|
// Calculate background position (negative values to shift the sprite)
|
||||||
|
const xPos = -(frameCol * width);
|
||||||
|
const yPos = -(frameRow * height);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Sprite Preview - Time: ${currentTime}s, Duration: ${this.player().duration()}s, Interval: ${frameInterval}s, Frame: ${frameIndex}/${maxFrames - 1}, Row: ${frameRow}, Col: ${frameCol}, Pos: ${xPos}px ${yPos}px, URL: ${url}`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply sprite background
|
||||||
|
this.spriteImage.style.backgroundImage = `url("${url}")`;
|
||||||
|
this.spriteImage.style.backgroundPosition = `${xPos}px ${yPos}px`;
|
||||||
|
this.spriteImage.style.backgroundSize = 'auto';
|
||||||
|
this.spriteImage.style.backgroundRepeat = 'no-repeat';
|
||||||
|
// Use CSS-defined dimensions (166x96) to match chapter styling
|
||||||
|
this.spriteImage.style.width = '166px';
|
||||||
|
this.spriteImage.style.height = '96px';
|
||||||
|
|
||||||
|
// Ensure the image is visible
|
||||||
|
this.spriteImage.style.display = 'block';
|
||||||
|
|
||||||
|
// Fallback: if we're beyond frame 3 (30s+), try showing frame 2 instead (20-30s frame)
|
||||||
|
if (frameIndex >= 3 && currentTime > 30) {
|
||||||
|
const fallbackYPos = -(2 * height); // Frame 2 (20-30s range)
|
||||||
|
this.spriteImage.style.backgroundPosition = `${xPos}px ${fallbackYPos}px`;
|
||||||
|
console.log(`Fallback: Using frame 2 instead of frame ${frameIndex} for time ${currentTime}s`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatTime(seconds) {
|
||||||
|
const mins = Math.floor(seconds / 60);
|
||||||
|
const secs = Math.floor(seconds % 60);
|
||||||
|
return `${mins}:${secs.toString().padStart(2, '0')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose() {
|
||||||
|
// Clean up event listeners
|
||||||
|
const progressControl = this.player().getChild('controlBar')?.getChild('progressControl');
|
||||||
|
if (progressControl) {
|
||||||
|
const progressControlEl = progressControl.el();
|
||||||
|
progressControlEl.removeEventListener('mouseenter', this.handleMouseEnter);
|
||||||
|
progressControlEl.removeEventListener('mouseleave', this.handleMouseLeave);
|
||||||
|
progressControlEl.removeEventListener('mousemove', this.handleMouseMove);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove tooltip
|
||||||
|
if (this.tooltip && this.tooltip.parentNode) {
|
||||||
|
this.tooltip.parentNode.removeChild(this.tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register the sprite preview component
|
||||||
|
videojs.registerComponent('SpritePreview', SpritePreview);
|
||||||
|
|
||||||
|
export default SpritePreview;
|
||||||
@ -6,6 +6,7 @@ import 'video.js/dist/video-js.css';
|
|||||||
import EndScreenOverlay from '../overlays/EndScreenOverlay';
|
import EndScreenOverlay from '../overlays/EndScreenOverlay';
|
||||||
import AutoplayCountdownOverlay from '../overlays/AutoplayCountdownOverlay';
|
import AutoplayCountdownOverlay from '../overlays/AutoplayCountdownOverlay';
|
||||||
import ChapterMarkers from '../markers/ChapterMarkers';
|
import ChapterMarkers from '../markers/ChapterMarkers';
|
||||||
|
import SpritePreview from '../markers/SpritePreview';
|
||||||
import NextVideoButton from '../controls/NextVideoButton';
|
import NextVideoButton from '../controls/NextVideoButton';
|
||||||
import AutoplayToggleButton from '../controls/AutoplayToggleButton';
|
import AutoplayToggleButton from '../controls/AutoplayToggleButton';
|
||||||
import CustomRemainingTime from '../controls/CustomRemainingTime';
|
import CustomRemainingTime from '../controls/CustomRemainingTime';
|
||||||
@ -634,6 +635,10 @@ function VideoJSPlayer() {
|
|||||||
|
|
||||||
// Define chapters as JSON object
|
// Define chapters as JSON object
|
||||||
// Note: The sample-chapters.vtt file is no longer needed as chapters are now loaded from this JSON
|
// Note: The sample-chapters.vtt file is no longer needed as chapters are now loaded from this JSON
|
||||||
|
// CONDITIONAL LOGIC:
|
||||||
|
// - When chaptersData has content: Uses original ChapterMarkers with sprite preview
|
||||||
|
// - When chaptersData is empty: Uses separate SpritePreview component
|
||||||
|
// Toggle between these two lines to test both scenarios:
|
||||||
const chaptersData = mediaData?.data?.chapter_data && mediaData?.data?.chapter_data.length > 0 ? mediaData?.data?.chapter_data : isDevelopment ? [
|
const chaptersData = mediaData?.data?.chapter_data && mediaData?.data?.chapter_data.length > 0 ? mediaData?.data?.chapter_data : isDevelopment ? [
|
||||||
{ startTime: 0, endTime: 4, text: 'Introduction' },
|
{ startTime: 0, endTime: 4, text: 'Introduction' },
|
||||||
{ startTime: 5, endTime: 10, text: 'Overview of Marine Life' },
|
{ startTime: 5, endTime: 10, text: 'Overview of Marine Life' },
|
||||||
@ -658,7 +663,7 @@ function VideoJSPlayer() {
|
|||||||
{ startTime: 1440, endTime: 1520, text: 'Commercial Aquaculture' },
|
{ startTime: 1440, endTime: 1520, text: 'Commercial Aquaculture' },
|
||||||
{ startTime: 1520, endTime: 1600, text: 'Ocean Exploration Technology' },
|
{ startTime: 1520, endTime: 1600, text: 'Ocean Exploration Technology' },
|
||||||
] : [];
|
] : [];
|
||||||
// const chaptersData = [];
|
// const chaptersData = []; // NO CHAPTERS (uses separate SpritePreview)
|
||||||
|
|
||||||
// Get video data from mediaData
|
// Get video data from mediaData
|
||||||
const currentVideo = useMemo(
|
const currentVideo = useMemo(
|
||||||
@ -1531,14 +1536,40 @@ function VideoJSPlayer() {
|
|||||||
setupClickableMenus();
|
setupClickableMenus();
|
||||||
}, 1500);
|
}, 1500);
|
||||||
|
|
||||||
// BEGIN: Add chapter markers to progress control
|
// BEGIN: Add chapter markers and sprite preview to progress control
|
||||||
if (progressControl && seekBar) {
|
if (progressControl && seekBar) {
|
||||||
const chapterMarkers = new ChapterMarkers(playerRef.current, {
|
console.log('Setting up sprite preview and chapter markers...');
|
||||||
previewSprite: mediaData.previewSprite,
|
console.log('mediaData.previewSprite:', mediaData.previewSprite);
|
||||||
});
|
console.log('chaptersData:', chaptersData);
|
||||||
seekBar.addChild(chapterMarkers);
|
|
||||||
|
// Check if we have chapters
|
||||||
|
const hasChapters = chaptersData && chaptersData.length > 0;
|
||||||
|
|
||||||
|
if (hasChapters) {
|
||||||
|
// Use original ChapterMarkers with sprite functionality when chapters exist
|
||||||
|
console.log('✓ Adding ChapterMarkers component with sprite functionality (chapters exist)');
|
||||||
|
const chapterMarkers = new ChapterMarkers(playerRef.current, {
|
||||||
|
previewSprite: mediaData.previewSprite,
|
||||||
|
});
|
||||||
|
seekBar.addChild(chapterMarkers);
|
||||||
|
} else if (mediaData.previewSprite) {
|
||||||
|
// Use separate SpritePreview component only when no chapters but sprite data exists
|
||||||
|
console.log('✓ Adding SpritePreview component (no chapters, but sprite data available)');
|
||||||
|
const spritePreview = new SpritePreview(playerRef.current, {
|
||||||
|
previewSprite: mediaData.previewSprite,
|
||||||
|
});
|
||||||
|
seekBar.addChild(spritePreview);
|
||||||
|
|
||||||
|
// Setup sprite preview hover functionality
|
||||||
|
setTimeout(() => {
|
||||||
|
console.log('✓ Setting up sprite preview hover functionality');
|
||||||
|
spritePreview.setupProgressBarHover();
|
||||||
|
}, 100);
|
||||||
|
} else {
|
||||||
|
console.log('✗ No chapters and no sprite data available');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// END: Add chapter markers 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(() => {
|
||||||
|
|||||||
14
frontend/packages/player/package-lock.json
generated
14
frontend/packages/player/package-lock.json
generated
@ -2108,13 +2108,13 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.4.0",
|
"version": "24.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.0.tgz",
|
||||||
"integrity": "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ==",
|
"integrity": "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.11.0"
|
"undici-types": "~7.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/resolve": {
|
"node_modules/@types/resolve": {
|
||||||
@ -6352,9 +6352,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.11.0",
|
"version": "7.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||||
"integrity": "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA==",
|
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|||||||
14
frontend/packages/scripts/package-lock.json
generated
14
frontend/packages/scripts/package-lock.json
generated
@ -2594,12 +2594,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "24.4.0",
|
"version": "24.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.4.0.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.5.0.tgz",
|
||||||
"integrity": "sha512-gUuVEAK4/u6F9wRLznPUU4WGUacSEBDPoC2TrBkw3GAnOLHBL45QdfHOXp1kJ4ypBGLxTOB+t7NJLpKoC3gznQ==",
|
"integrity": "sha512-y1dMvuvJspJiPSDZUQ+WMBvF7dpnEqN4x9DDC9ie5Fs/HUZJA3wFp7EhHoVaKX/iI0cRoECV8X2jL8zi0xrHCg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~7.11.0"
|
"undici-types": "~7.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/parse-json": {
|
"node_modules/@types/parse-json": {
|
||||||
@ -13493,9 +13493,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "7.11.0",
|
"version": "7.12.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.12.0.tgz",
|
||||||
"integrity": "sha512-kt1ZriHTi7MU+Z/r9DOdAI3ONdaR3M3csEaRc6ewa4f4dTvX4cQCbJ4NkEn0ohE4hHtq85+PhPSTY+pO/1PwgA==",
|
"integrity": "sha512-goOacqME2GYyOZZfb5Lgtu+1IDmAlAEu5xnD3+xTzS10hT0vzpf0SPjkXwAw9Jm+4n/mQGDP3LO8CPbYROeBfQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/unicode-canonical-property-names-ecmascript": {
|
"node_modules/unicode-canonical-property-names-ecmascript": {
|
||||||
|
|||||||
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