diff --git a/frontend-tools/video-js/index-embed.html b/frontend-tools/video-js/index-embed.html new file mode 100644 index 00000000..0d926325 --- /dev/null +++ b/frontend-tools/video-js/index-embed.html @@ -0,0 +1,14 @@ + + + + + + + VideoJS + + + +
+ + + diff --git a/frontend-tools/video-js/index.html b/frontend-tools/video-js/index.html index 1af479c7..b7c4ab60 100644 --- a/frontend-tools/video-js/index.html +++ b/frontend-tools/video-js/index.html @@ -7,14 +7,8 @@ VideoJS -

Main Video Player

-
- -
- -

Embed Video Player

-
- +
+ diff --git a/frontend-tools/video-js/src/VideoJS.css b/frontend-tools/video-js/src/VideoJS.css index edd1dd7f..7a4fda3f 100644 --- a/frontend-tools/video-js/src/VideoJS.css +++ b/frontend-tools/video-js/src/VideoJS.css @@ -22,6 +22,20 @@ html { border-radius: 12px !important; } +/* Fullscreen video styles for embedded video player */ +.video-js-root-embed .video-js video { + width: 100vw !important; + height: 100vh !important; + object-fit: cover !important; + border-radius: 0 !important; +} +.video-js-root-embed .video-js .vjs-poster { + border-radius: 0 !important; + width: 100vw !important; + height: 100vh !important; + object-fit: cover !important; +} + .video-js div.vjs-control-bar { background: transparent !important; background-color: transparent !important; @@ -41,10 +55,32 @@ html { padding: 0 20px; box-sizing: border-box; } + +/* Fullscreen styles for embedded video player */ +.video-js-root-embed .video-container { + width: 100vw; + height: 100vh; + max-width: none; + margin: 0; + padding: 0; + box-sizing: border-box; + position: fixed; + top: 0; + left: 0; + z-index: 1000; +} .video-js.vjs-fluid { width: 100% !important; max-width: 100% !important; } + +/* Fullscreen fluid styles for embedded video player */ +.video-js-root-embed .video-js.vjs-fluid { + width: 100vw !important; + height: 100vh !important; + max-width: none !important; + max-height: none !important; +} .vjs-autoplay-toggle .vjs-autoplay-icon svg { width: 100%; height: 100%; @@ -735,6 +771,32 @@ button { height: 100% !important; border-radius: 12px; } + +/* Fullscreen video-js player styles for embedded video player */ +.video-js-root-embed .video-js { + width: 100vw !important; + height: 100vh !important; + border-radius: 0; + position: relative; +} + +/* Prevent page scrolling when embed is active */ +.video-js-root-embed { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 9999; + background: #000; +} + +html.video-js-root-embed-active, +body.video-js-root-embed-active { + margin: 0; + padding: 0; + overflow: hidden; +} .video-chapter { position: absolute; top: auto; diff --git a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx index 1fd1984b..39b12be4 100644 --- a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx +++ b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx @@ -1047,6 +1047,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) { }, siteUrl: '', nextLink: 'https://demo.mediacms.io/view?m=YjGJafibO', + urlAutoplay: true, }, [] ); @@ -1190,6 +1191,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) { previewSprite: mediaData?.previewSprite || {}, related_media: mediaData.data?.related_media || [], nextLink: mediaData?.nextLink || null, + urlAutoplay: mediaData?.urlAutoplay || true, sources: getVideoSources(), }; }, [mediaData]); @@ -1419,13 +1421,13 @@ function VideoJSPlayer({ videoId = 'default-video' }) { // Player dimensions - removed for responsive design // Autoplay behavior: Use 'muted' to comply with browser policies - autoplay: 'play', // Set to true/false to show poster image initially (true/false, play, muted, any) + autoplay: 'muted', // Auto-start muted to comply with browser policies (true/false, play, muted, any) // Start video over when it ends loop: false, - // Start video muted - muted: false, + // Start video muted (check URL parameter or default) + muted: mediaData.urlMuted || false, // Poster image URL displayed before video starts poster: currentVideo.poster, @@ -1691,6 +1693,54 @@ function VideoJSPlayer({ videoId = 'default-video' }) { // Set up auto-save for preference changes userPreferences.current.setupAutoSave(playerRef.current); + // Expose the player instance globally for timestamp functionality + if (typeof window !== 'undefined') { + if (!window.videojsPlayers) { + window.videojsPlayers = {}; + } + window.videojsPlayers[videoId] = playerRef.current; + } + + // Call the onPlayerInitCallback if provided via MEDIA_DATA + if (mediaData.onPlayerInitCallback && typeof mediaData.onPlayerInitCallback === 'function') { + mediaData.onPlayerInitCallback({ player: playerRef.current }, playerRef.current.el()); + } + + // Handle URL timestamp parameter + if (mediaData.urlTimestamp !== null && mediaData.urlTimestamp >= 0) { + const timestamp = mediaData.urlTimestamp; + + // Wait for video metadata to be loaded before seeking + if (playerRef.current.readyState() >= 1) { + // Metadata is already loaded, seek immediately + if (timestamp < playerRef.current.duration()) { + playerRef.current.currentTime(timestamp); + } else if (timestamp >= 0) { + playerRef.current.play(); + } + } else { + // Wait for metadata to load + playerRef.current.one('loadedmetadata', () => { + if (timestamp >= 0 && timestamp < playerRef.current.duration()) { + playerRef.current.currentTime(timestamp); + } else if (timestamp >= 0) { + playerRef.current.play(); + } + }); + } + } + + // Handle URL autoplay parameter or auto-start on page load + if (mediaData?.urlAutoplay) { + playerRef.current.play(); + } else { + // Auto-start video on page load/reload (muted to comply with browser policies) + playerRef.current.play().catch((error) => { + console.log('ℹ️ Browser prevented autoplay (normal behavior):', error.message); + // Fallback: ensure video is ready to play when user interacts + }); + } + const setupMobilePlayPause = () => { const playerEl = playerRef.current.el(); const videoEl = playerEl.querySelector('video'); @@ -2268,14 +2318,18 @@ function VideoJSPlayer({ videoId = 'default-video' }) { const newTime = Math.min(currentTime + seekAmount, duration); playerRef.current.currentTime(newTime); - customComponents.current.seekIndicator.show('forward', seekAmount); + if (customComponents.current.seekIndicator) { + customComponents.current.seekIndicator.show('forward', seekAmount); + } } else if (event.key === 'ArrowLeft' || event.keyCode === 37) { event.preventDefault(); const currentTime = playerRef.current.currentTime(); const newTime = Math.max(currentTime - seekAmount, 0); playerRef.current.currentTime(newTime); - customComponents.current.seekIndicator.show('backward', seekAmount); + if (customComponents.current.seekIndicator) { + customComponents.current.seekIndicator.show('backward', seekAmount); + } } }; @@ -2482,7 +2536,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) { playerRef.current.on('play', () => { console.log('Video started playing'); // Only show play indicator if not changing quality - if (!playerRef.current.isChangingQuality) { + if (!playerRef.current.isChangingQuality && customComponents.current.seekIndicator) { customComponents.current.seekIndicator.show('play'); } }); @@ -2490,7 +2544,7 @@ function VideoJSPlayer({ videoId = 'default-video' }) { playerRef.current.on('pause', () => { console.log('Video paused'); // Only show pause indicator if not changing quality - if (!playerRef.current.isChangingQuality) { + if (!playerRef.current.isChangingQuality && customComponents.current.seekIndicator) { customComponents.current.seekIndicator.show('pause'); } }); diff --git a/frontend-tools/video-js/src/main.jsx b/frontend-tools/video-js/src/main.jsx index da4140f6..c225f3af 100644 --- a/frontend-tools/video-js/src/main.jsx +++ b/frontend-tools/video-js/src/main.jsx @@ -9,29 +9,50 @@ import VideoJS from './VideoJS.jsx'; const mountComponents = () => { // Mount main video player const rootContainerMain = document.getElementById('video-js-root-main'); - if (rootContainerMain) { + if (rootContainerMain && !rootContainerMain.hasChildNodes()) { const rootMain = createRoot(rootContainerMain); rootMain.render( ); + console.log('Mounted main VideoJS player'); } // Mount embed video player const rootContainerEmbed = document.getElementById('video-js-root-embed'); - if (rootContainerEmbed) { + if (rootContainerEmbed && !rootContainerEmbed.hasChildNodes()) { const rootEmbed = createRoot(rootContainerEmbed); rootEmbed.render( ); + console.log('Mounted embed VideoJS player'); } }; +// Expose the mounting function globally for manual triggering +window.triggerVideoJSMount = mountComponents; + +// Listen for custom events to trigger mounting +document.addEventListener('triggerVideoJSMount', () => { + console.log('Received triggerVideoJSMount event, attempting to mount VideoJS components...'); + mountComponents(); +}); + +// Initial mount if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', mountComponents); } else { mountComponents(); } + +// Also periodically check for new containers (as a fallback) +setInterval(() => { + const embedContainer = document.getElementById('video-js-root-embed'); + if (embedContainer && !embedContainer.hasChildNodes()) { + console.log('Found unmounted embed container during periodic check, mounting...'); + mountComponents(); + } +}, 1000);