mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-12-09 05:32:31 -05:00
feat: Major Upgrade to Video.js v8 — Chapters Functionality, Fixes and Improvements
This commit is contained in:
committed by
GitHub
parent
b39072c8ae
commit
a5e6e7b9ca
264
frontend-tools/video-js/src/components/markers/SpritePreview.js
Normal file
264
frontend-tools/video-js/src/components/markers/SpritePreview.js
Normal file
@@ -0,0 +1,264 @@
|
||||
import videojs from 'video.js';
|
||||
import './SpritePreview.css';
|
||||
|
||||
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() {
|
||||
// Check if device is touch-enabled (tablet/mobile)
|
||||
const isTouchDevice =
|
||||
this.options_.isTouchDevice ||
|
||||
'ontouchstart' in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
navigator.msMaxTouchPoints > 0;
|
||||
|
||||
// Skip tooltip setup on touch devices
|
||||
if (isTouchDevice) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Try to get progress control from control bar first, then from moved location
|
||||
let progressControl = this.player().getChild('controlBar').getChild('progressControl');
|
||||
|
||||
// If not found in control bar, it might have been moved to a wrapper
|
||||
if (!progressControl) {
|
||||
// Look for moved progress control in custom components
|
||||
const customComponents = this.player().customComponents || {};
|
||||
progressControl = customComponents.movedProgressControl;
|
||||
}
|
||||
|
||||
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) {
|
||||
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',
|
||||
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';
|
||||
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;
|
||||
|
||||
// Calculate total frames based on video duration vs frame interval
|
||||
const videoDuration = this.player().duration();
|
||||
if (!videoDuration) return;
|
||||
|
||||
const maxFrames = Math.ceil(videoDuration / frameInterval);
|
||||
let frameIndex = Math.floor(currentTime / frameInterval);
|
||||
|
||||
// Clamp frameIndex to available frames to prevent showing empty areas
|
||||
frameIndex = Math.min(frameIndex, maxFrames - 1);
|
||||
frameIndex = Math.max(frameIndex, 0);
|
||||
|
||||
// Frames are arranged vertically (1 column, multiple rows)
|
||||
const frameRow = frameIndex;
|
||||
const frameCol = 0;
|
||||
|
||||
// Calculate background position (negative values to shift the sprite)
|
||||
const xPos = -(frameCol * width);
|
||||
const yPos = -(frameRow * height);
|
||||
|
||||
// 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';
|
||||
}
|
||||
|
||||
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
|
||||
let progressControl = this.player().getChild('controlBar')?.getChild('progressControl');
|
||||
|
||||
// If not found in control bar, it might have been moved to a wrapper
|
||||
if (!progressControl) {
|
||||
const customComponents = this.player().customComponents || {};
|
||||
progressControl = customComponents.movedProgressControl;
|
||||
}
|
||||
|
||||
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;
|
||||
Reference in New Issue
Block a user