diff --git a/frontend/src/static/js/components/VideoJS/VideoJSEmbed.jsx b/frontend/src/static/js/components/VideoJS/VideoJSEmbed.jsx index 74cb034e..7021fcf4 100644 --- a/frontend/src/static/js/components/VideoJS/VideoJSEmbed.jsx +++ b/frontend/src/static/js/components/VideoJS/VideoJSEmbed.jsx @@ -32,6 +32,7 @@ const VideoJSEmbed = ({ inEmbed, hasTheaterMode, hasNextLink, + nextLink, hasPreviousLink, errorMessage, onClickNextCallback, @@ -41,14 +42,30 @@ const VideoJSEmbed = ({ }) => { const containerRef = useRef(null); 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(() => { + // Update the ref whenever inEmbed changes + inEmbedRef.current = inEmbed; + // Set the global MEDIA_DATA that the video js expects 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 = { data: data || {}, // TODO: Check if this is needed playerVolume: playerVolume || 0.5, - playerSoundMuted: playerSoundMuted || false, + playerSoundMuted: playerSoundMuted || (urlMuted === '1'), videoQuality: videoQuality || 'auto', videoPlaybackSpeed: videoPlaybackSpeed || 1, inTheaterMode: inTheaterMode || false, @@ -60,16 +77,28 @@ const VideoJSEmbed = ({ poster: poster || '', previewSprite: previewSprite || null, subtitlesInfo: subtitlesInfo || [], - enableAutoplay: enableAutoplay || false, + enableAutoplay: enableAutoplay || (urlAutoplay === '1'), inEmbed: inEmbed || false, hasTheaterMode: hasTheaterMode || false, hasNextLink: hasNextLink || false, + nextLink: nextLink || null, hasPreviousLink: hasPreviousLink || false, errorMessage: errorMessage || '', + // URL parameters + urlTimestamp: urlTimestamp ? parseInt(urlTimestamp, 10) : null, + urlAutoplay: urlAutoplay === '1', + urlMuted: urlMuted === '1', onClickNextCallback: onClickNextCallback || null, onClickPreviousCallback: onClickPreviousCallback || 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(); 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 = () => { // Check if assets are already loaded @@ -103,7 +214,7 @@ const VideoJSEmbed = ({ return (
-
+ {inEmbed ?
:
}
); }; diff --git a/frontend/src/static/js/components/media-actions/MediaShareEmbed.jsx b/frontend/src/static/js/components/media-actions/MediaShareEmbed.jsx index 68c5f63d..c13eefdf 100644 --- a/frontend/src/static/js/components/media-actions/MediaShareEmbed.jsx +++ b/frontend/src/static/js/components/media-actions/MediaShareEmbed.jsx @@ -1,6 +1,6 @@ import React, { useRef, useState, useEffect, useContext } from 'react'; import PropTypes from 'prop-types'; -import { LinksContext } from '../../utils/contexts/'; +import { LinksContext, SiteConsumer } from '../../utils/contexts/'; import { PageStore, MediaPageStore } from '../../utils/stores/'; import { PageActions, MediaPageActions } from '../../utils/actions/'; import { CircleIconButton, MaterialIcon, NumericInputWithUnit } from '../_shared/'; @@ -135,7 +135,9 @@ export function MediaShareEmbed(props) {
- + + {(site) => } +