mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 07:28:53 -05:00
feat: Timestamp click functionality, URL timestamps, embed functionality
This commit is contained in:
parent
e3291a5d75
commit
b96134bd9f
@ -32,6 +32,7 @@ const VideoJSEmbed = ({
|
|||||||
inEmbed,
|
inEmbed,
|
||||||
hasTheaterMode,
|
hasTheaterMode,
|
||||||
hasNextLink,
|
hasNextLink,
|
||||||
|
nextLink,
|
||||||
hasPreviousLink,
|
hasPreviousLink,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
onClickNextCallback,
|
onClickNextCallback,
|
||||||
@ -41,14 +42,30 @@ const VideoJSEmbed = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const containerRef = useRef(null);
|
const containerRef = useRef(null);
|
||||||
const assetsLoadedRef = useRef(false);
|
const assetsLoadedRef = useRef(false);
|
||||||
|
const playerInstanceRef = useRef(null);
|
||||||
|
const inEmbedRef = useRef(inEmbed);
|
||||||
|
|
||||||
|
// Helper function to get URL parameters
|
||||||
|
const getUrlParameter = (name) => {
|
||||||
|
const urlParams = new URLSearchParams(window.location.search);
|
||||||
|
return urlParams.get(name);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
// Update the ref whenever inEmbed changes
|
||||||
|
inEmbedRef.current = inEmbed;
|
||||||
|
|
||||||
// Set the global MEDIA_DATA that the video js expects
|
// Set the global MEDIA_DATA that the video js expects
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== 'undefined') {
|
||||||
|
// Get URL parameters for autoplay, muted, and timestamp
|
||||||
|
const urlTimestamp = getUrlParameter('t');
|
||||||
|
const urlAutoplay = getUrlParameter('autoplay');
|
||||||
|
const urlMuted = getUrlParameter('muted');
|
||||||
|
|
||||||
window.MEDIA_DATA = {
|
window.MEDIA_DATA = {
|
||||||
data: data || {}, // TODO: Check if this is needed
|
data: data || {}, // TODO: Check if this is needed
|
||||||
playerVolume: playerVolume || 0.5,
|
playerVolume: playerVolume || 0.5,
|
||||||
playerSoundMuted: playerSoundMuted || false,
|
playerSoundMuted: playerSoundMuted || (urlMuted === '1'),
|
||||||
videoQuality: videoQuality || 'auto',
|
videoQuality: videoQuality || 'auto',
|
||||||
videoPlaybackSpeed: videoPlaybackSpeed || 1,
|
videoPlaybackSpeed: videoPlaybackSpeed || 1,
|
||||||
inTheaterMode: inTheaterMode || false,
|
inTheaterMode: inTheaterMode || false,
|
||||||
@ -60,16 +77,28 @@ const VideoJSEmbed = ({
|
|||||||
poster: poster || '',
|
poster: poster || '',
|
||||||
previewSprite: previewSprite || null,
|
previewSprite: previewSprite || null,
|
||||||
subtitlesInfo: subtitlesInfo || [],
|
subtitlesInfo: subtitlesInfo || [],
|
||||||
enableAutoplay: enableAutoplay || false,
|
enableAutoplay: enableAutoplay || (urlAutoplay === '1'),
|
||||||
inEmbed: inEmbed || false,
|
inEmbed: inEmbed || false,
|
||||||
hasTheaterMode: hasTheaterMode || false,
|
hasTheaterMode: hasTheaterMode || false,
|
||||||
hasNextLink: hasNextLink || false,
|
hasNextLink: hasNextLink || false,
|
||||||
|
nextLink: nextLink || null,
|
||||||
hasPreviousLink: hasPreviousLink || false,
|
hasPreviousLink: hasPreviousLink || false,
|
||||||
errorMessage: errorMessage || '',
|
errorMessage: errorMessage || '',
|
||||||
|
// URL parameters
|
||||||
|
urlTimestamp: urlTimestamp ? parseInt(urlTimestamp, 10) : null,
|
||||||
|
urlAutoplay: urlAutoplay === '1',
|
||||||
|
urlMuted: urlMuted === '1',
|
||||||
onClickNextCallback: onClickNextCallback || null,
|
onClickNextCallback: onClickNextCallback || null,
|
||||||
onClickPreviousCallback: onClickPreviousCallback || null,
|
onClickPreviousCallback: onClickPreviousCallback || null,
|
||||||
onStateUpdateCallback: onStateUpdateCallback || null,
|
onStateUpdateCallback: onStateUpdateCallback || null,
|
||||||
onPlayerInitCallback: onPlayerInitCallback || null,
|
onPlayerInitCallback: (instance, elem) => {
|
||||||
|
// Store the player instance for timestamp functionality
|
||||||
|
playerInstanceRef.current = instance;
|
||||||
|
// Call the original callback if provided
|
||||||
|
if (onPlayerInitCallback) {
|
||||||
|
onPlayerInitCallback(instance, elem);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +107,89 @@ const VideoJSEmbed = ({
|
|||||||
loadVideoJSAssets();
|
loadVideoJSAssets();
|
||||||
assetsLoadedRef.current = true;
|
assetsLoadedRef.current = true;
|
||||||
}
|
}
|
||||||
}, [data, siteUrl]);
|
}, [data, siteUrl, inEmbed]);
|
||||||
|
|
||||||
|
// New effect to manually trigger VideoJS mounting for embed players
|
||||||
|
useEffect(() => {
|
||||||
|
if (inEmbed && containerRef.current) {
|
||||||
|
// Small delay to ensure DOM is fully ready, then trigger VideoJS mounting
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
// Try to trigger the VideoJS mount by dispatching a custom event
|
||||||
|
const event = new CustomEvent('triggerVideoJSMount', {
|
||||||
|
detail: { targetId: 'video-js-root-embed' }
|
||||||
|
});
|
||||||
|
document.dispatchEvent(event);
|
||||||
|
|
||||||
|
// Also try to trigger by calling the global function if it exists
|
||||||
|
if (typeof window !== 'undefined' && window.triggerVideoJSMount) {
|
||||||
|
window.triggerVideoJSMount();
|
||||||
|
}
|
||||||
|
}, 100);
|
||||||
|
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}, [inEmbed, containerRef.current]);
|
||||||
|
|
||||||
|
// Set up timestamp click functionality
|
||||||
|
useEffect(() => {
|
||||||
|
const handleTimestampClick = (e) => {
|
||||||
|
if (e.target.classList.contains('video-timestamp')) {
|
||||||
|
e.preventDefault();
|
||||||
|
const timestamp = parseInt(e.target.dataset.timestamp, 10);
|
||||||
|
|
||||||
|
// Try to get the player from multiple sources
|
||||||
|
let player = null;
|
||||||
|
|
||||||
|
// First try: from our stored instance
|
||||||
|
if (playerInstanceRef.current && playerInstanceRef.current.player) {
|
||||||
|
player = playerInstanceRef.current.player;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Second try: from global window.videojsPlayers
|
||||||
|
if (!player && typeof window !== 'undefined' && window.videojsPlayers) {
|
||||||
|
const videoId = inEmbedRef.current ? 'video-embed' : 'video-main';
|
||||||
|
player = window.videojsPlayers[videoId];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third try: from the global videojs function looking for existing players
|
||||||
|
if (!player && typeof window !== 'undefined' && window.videojs) {
|
||||||
|
const videoElement = document.querySelector(inEmbedRef.current ? '#video-embed' : '#video-main');
|
||||||
|
if (videoElement && videoElement.player) {
|
||||||
|
player = videoElement.player;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we found a player, seek to the timestamp
|
||||||
|
if (player) {
|
||||||
|
if (timestamp >= 0 && timestamp < player.duration()) {
|
||||||
|
player.currentTime(timestamp);
|
||||||
|
} else if (timestamp >= 0) {
|
||||||
|
player.play();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scroll to the video player with smooth behavior
|
||||||
|
const videoElement = document.querySelector(inEmbedRef.current ? '#video-embed' : '#video-main');
|
||||||
|
if (videoElement) {
|
||||||
|
videoElement.scrollIntoView({
|
||||||
|
behavior: 'smooth',
|
||||||
|
block: 'center',
|
||||||
|
inline: 'nearest'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('VideoJS player not found for timestamp navigation');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add the event listener to the document for timestamp clicks
|
||||||
|
document.addEventListener('click', handleTimestampClick);
|
||||||
|
|
||||||
|
// Cleanup function
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('click', handleTimestampClick);
|
||||||
|
};
|
||||||
|
}, []); // Empty dependency array so this effect only runs once
|
||||||
|
|
||||||
const loadVideoJSAssets = () => {
|
const loadVideoJSAssets = () => {
|
||||||
// Check if assets are already loaded
|
// Check if assets are already loaded
|
||||||
@ -103,7 +214,7 @@ const VideoJSEmbed = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="video-js-wrapper" ref={containerRef}>
|
<div className="video-js-wrapper" ref={containerRef}>
|
||||||
<div id="video-js-root" />
|
{inEmbed ? <div id="video-js-root-embed" className="video-js-root-embed" /> : <div id="video-js-root-main" className="video-js-root-main" />}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import React, { useRef, useState, useEffect, useContext } from 'react';
|
import React, { useRef, useState, useEffect, useContext } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { LinksContext } from '../../utils/contexts/';
|
import { LinksContext, SiteConsumer } from '../../utils/contexts/';
|
||||||
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
import { PageStore, MediaPageStore } from '../../utils/stores/';
|
||||||
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
import { PageActions, MediaPageActions } from '../../utils/actions/';
|
||||||
import { CircleIconButton, MaterialIcon, NumericInputWithUnit } from '../_shared/';
|
import { CircleIconButton, MaterialIcon, NumericInputWithUnit } from '../_shared/';
|
||||||
@ -135,7 +135,9 @@ export function MediaShareEmbed(props) {
|
|||||||
<div className="share-embed-inner">
|
<div className="share-embed-inner">
|
||||||
<div className="on-left">
|
<div className="on-left">
|
||||||
<div className="media-embed-wrap">
|
<div className="media-embed-wrap">
|
||||||
<VideoViewer data={MediaPageStore.get('media-data')} inEmbed={true} />
|
<SiteConsumer>
|
||||||
|
{(site) => <VideoViewer data={MediaPageStore.get('media-data')} siteUrl={site.url} inEmbed={true} />}
|
||||||
|
</SiteConsumer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user