diff --git a/frontend-tools/video-js/src/VideoJSNew.jsx b/frontend-tools/video-js/src/VideoJSNew.jsx deleted file mode 100644 index 98643e9e..00000000 --- a/frontend-tools/video-js/src/VideoJSNew.jsx +++ /dev/null @@ -1,8 +0,0 @@ -import React from 'react'; -import { VideoJSPlayerNew } from './components'; - -function VideoJSNew({ videoId = 'default-video', ...props }) { - return ; -} - -export default VideoJSNew; diff --git a/frontend-tools/video-js/src/components/index.js b/frontend-tools/video-js/src/components/index.js index e6cbaacf..7428eb6c 100644 --- a/frontend-tools/video-js/src/components/index.js +++ b/frontend-tools/video-js/src/components/index.js @@ -1,6 +1,5 @@ // Export all Video.js components export { default as VideoJSPlayer } from './video-player/VideoJSPlayer'; -export { default as VideoJSPlayerNew } from './video-player/VideoJSPlayerNew'; export { default as EndScreenOverlay } from './overlays/EndScreenOverlay'; export { default as AutoplayCountdownOverlay } from './overlays/AutoplayCountdownOverlay'; export { default as ChapterMarkers } from './markers/ChapterMarkers'; diff --git a/frontend-tools/video-js/src/components/video-player/VideoJSPlayerNew.css b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.css similarity index 100% rename from frontend-tools/video-js/src/components/video-player/VideoJSPlayerNew.css rename to frontend-tools/video-js/src/components/video-player/VideoJSPlayer.css 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 7d675ede..3db6d1d9 100644 --- a/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx +++ b/frontend-tools/video-js/src/components/video-player/VideoJSPlayer.jsx @@ -1,9 +1,13 @@ import React, { useEffect, useRef, useMemo } from 'react'; import videojs from 'video.js'; import 'video.js/dist/video-js.css'; +//import 'video.js/dist/video-js.css'; // import '../../VideoJS.css'; -import '../../styles/embed.css'; -//import '../controls/SubtitlesButton.css'; +// import '../../styles/embed.css'; +// import '../controls/SubtitlesButton.css'; +import './VideoJSPlayer.css'; +import './VideoJSPlayerRoundedCorners.css'; +import '../controls/ButtonTooltips.css'; // Import the separated components import EndScreenOverlay from '../overlays/EndScreenOverlay'; @@ -18,12 +22,157 @@ import CustomChaptersOverlay from '../controls/CustomChaptersOverlay'; import CustomSettingsMenu from '../controls/CustomSettingsMenu'; import SeekIndicator from '../controls/SeekIndicator'; import UserPreferences from '../../utils/UserPreferences'; +import TestButton from '../controls/TestButton'; +import { AutoplayHandler } from '../../utils/AutoplayHandler'; +import { OrientationHandler } from '../../utils/OrientationHandler'; +import { EndScreenHandler } from '../../utils/EndScreenHandler'; +import KeyboardHandler from '../../utils/KeyboardHandler'; +import PlaybackEventHandler from '../../utils/PlaybackEventHandler'; + +// Function to enable tooltips for all standard VideoJS buttons +const enableStandardButtonTooltips = (player) => { + // Wait a bit for all components to be initialized + setTimeout(() => { + const controlBar = player.getChild('controlBar'); + if (!controlBar) return; + + // Define tooltip mappings for standard VideoJS buttons + const buttonTooltips = { + playToggle: () => (player.paused() ? 'Play' : 'Pause'), + // muteToggle: () => (player.muted() ? 'Unmute' : 'Mute'), // Removed - no tooltip for mute/volume + // volumePanel: 'Volume', // Removed - no tooltip for volume + fullscreenToggle: () => (player.isFullscreen() ? 'Exit fullscreen' : 'Fullscreen'), + pictureInPictureToggle: 'Picture-in-picture', + subtitlesButton: 'Subtitles/CC', + chaptersButton: 'Chapters', + audioTrackButton: 'Audio tracks', + playbackRateMenuButton: 'Playback speed', + // currentTimeDisplay: 'Current time', // Removed - no tooltip for time + // durationDisplay: 'Duration', // Removed - no tooltip for duration + }; + + // Apply tooltips to each button + Object.keys(buttonTooltips).forEach((buttonName) => { + const button = controlBar.getChild(buttonName); + if (button && button.el()) { + const buttonEl = button.el(); + const tooltipText = + typeof buttonTooltips[buttonName] === 'function' + ? buttonTooltips[buttonName]() + : buttonTooltips[buttonName]; + + buttonEl.setAttribute('title', tooltipText); + buttonEl.setAttribute('aria-label', tooltipText); + + // Add touch tooltip support for mobile devices + addTouchTooltipSupport(buttonEl); + + // For dynamic tooltips (play/pause, fullscreen), update on state change + if (buttonName === 'playToggle') { + player.on('play', () => { + buttonEl.setAttribute('title', 'Pause'); + buttonEl.setAttribute('aria-label', 'Pause'); + }); + player.on('pause', () => { + buttonEl.setAttribute('title', 'Play'); + buttonEl.setAttribute('aria-label', 'Play'); + }); + } else if (buttonName === 'fullscreenToggle') { + player.on('fullscreenchange', () => { + const tooltip = player.isFullscreen() ? 'Exit fullscreen' : 'Fullscreen'; + buttonEl.setAttribute('title', tooltip); + buttonEl.setAttribute('aria-label', tooltip); + }); + } + } + }); + + // Remove title attributes from volume-related elements to prevent blank tooltips + const removeVolumeTooltips = () => { + const volumeElements = [ + controlBar.getChild('volumePanel'), + controlBar.getChild('muteToggle'), + controlBar.getChild('volumeControl'), + ]; + + volumeElements.forEach((element) => { + if (element && element.el()) { + const el = element.el(); + el.removeAttribute('title'); + el.removeAttribute('aria-label'); + + // Also remove from any child elements + const childElements = el.querySelectorAll('*'); + childElements.forEach((child) => { + child.removeAttribute('title'); + }); + } + }); + }; + + // Run immediately and also after a short delay + removeVolumeTooltips(); + setTimeout(removeVolumeTooltips, 100); + }, 500); // Delay to ensure all components are ready +}; + +// Helper function to add touch tooltip support +const addTouchTooltipSupport = (element) => { + // Check if device is touch-enabled + const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0; + + // Only add touch tooltip support on actual touch devices + if (!isTouchDevice) { + return; + } + + let touchStartTime = 0; + let tooltipTimeout = null; + + // Touch start + element.addEventListener( + 'touchstart', + () => { + touchStartTime = Date.now(); + }, + { passive: true } + ); + + // Touch end + element.addEventListener( + 'touchend', + () => { + const touchDuration = Date.now() - touchStartTime; + + // Only show tooltip for quick taps (not swipes) + if (touchDuration < 300) { + // Don't prevent default for most buttons to maintain functionality + + // Show tooltip briefly + element.classList.add('touch-tooltip-active'); + + // Clear any existing timeout + if (tooltipTimeout) { + clearTimeout(tooltipTimeout); + } + + // Hide tooltip after delay + tooltipTimeout = setTimeout(() => { + element.classList.remove('touch-tooltip-active'); + }, 2000); + } + }, + { passive: true } + ); +}; function VideoJSPlayer({ videoId = 'default-video' }) { const videoRef = useRef(null); const playerRef = useRef(null); // Track the player instance const userPreferences = useRef(new UserPreferences()); // User preferences instance const customComponents = useRef({}); // Store custom components for cleanup + const keyboardHandler = useRef(null); // Keyboard handler instance + const playbackEventHandler = useRef(null); // Playback event handler instance // Check if this is an embed player (disable next video and autoplay features) const isEmbedPlayer = videoId === 'video-embed'; @@ -47,10 +196,10 @@ function VideoJSPlayer({ videoId = 'default-video' }) { author_name: 'Markos Gogoulos', author_profile: '/user/markos/', author_thumbnail: '/media/userlogos/user.jpg', - url: 'https://videojs.mediacms.io/view?m=meivs1H3R', + url: 'https://demo.mediacms.io/view?m=elygiagorgechania', poster_url: - '/media/original/thumbnails/user/markos/d6ae9093cb1648529432f38ee1198200_6BfyhyM.video.mp4.jpg', - + '/media/original/thumbnails/user/markos/c1ab03cab3bb46b5854a5e217cfe3013_3mGZ15f.VID_20230813_144422.mp4.jpg', + ___chapter_data: [], chapter_data: [ { startTime: '00:00:00.000', @@ -970,80 +1119,85 @@ function VideoJSPlayer({ videoId = 'default-video' }) { // VIDEO media_type: 'video', - original_media_url: '/media/original/user/markos/d6ae9093cb1648529432f38ee1198200.video.mp4', + original_media_url: + '/media/original/user/markos/f371a6b2c157451d924bc4f612bf2667.Pexels_Videos_2079217_1.mp4', + hls_info: { - master_file: '/media/hls/d6ae9093cb1648529432f38ee1198200/master.m3u8', - '144_iframe': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-1/iframes.m3u8', - '240_iframe': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-2/iframes.m3u8', - '360_iframe': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-3/iframes.m3u8', - '480_iframe': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-4/iframes.m3u8', - '720_iframe': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-5/iframes.m3u8', - '1080_iframe': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-6/iframes.m3u8', - '144_playlist': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-1/stream.m3u8', - '240_playlist': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-2/stream.m3u8', - '360_playlist': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-3/stream.m3u8', - '480_playlist': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-4/stream.m3u8', - '720_playlist': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-5/stream.m3u8', - '1080_playlist': '/media/hls/d6ae9093cb1648529432f38ee1198200/media-6/stream.m3u8', + master_file: '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/master.m3u8', + '240_iframe': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-1/iframes.m3u8', + '480_iframe': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-2/iframes.m3u8', + '1080_iframe': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-3/iframes.m3u8', + '360_iframe': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-4/iframes.m3u8', + '720_iframe': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-5/iframes.m3u8', + '240_playlist': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-1/stream.m3u8', + '480_playlist': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-2/stream.m3u8', + '1080_playlist': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-3/stream.m3u8', + '360_playlist': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-4/stream.m3u8', + '720_playlist': '/media/hls/c1ab03cab3bb46b5854a5e217cfe3013/media-5/stream.m3u8', }, + + /* hls_info: { + master_file: '/media/hls/f371a6b2c157451d924bc4f612bf2667/master.m3u8', + '1080_iframe': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-1/iframes.m3u8', + '720_iframe': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-2/iframes.m3u8', + '480_iframe': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-3/iframes.m3u8', + '360_iframe': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-4/iframes.m3u8', + '240_iframe': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-5/iframes.m3u8', + '1080_playlist': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-1/stream.m3u8', + '720_playlist': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-2/stream.m3u8', + '480_playlist': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-3/stream.m3u8', + '360_playlist': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-4/stream.m3u8', + '240_playlist': '/media/hls/f371a6b2c157451d924bc4f612bf2667/media-5/stream.m3u8', + }, */ encodings_info: { - 144: { - h264: { - title: 'h264-144', - url: '/media/encoded/23/markos/d6ae9093cb1648529432f38ee1198200.d6ae9093cb1648529432f38ee1198200.video.mp4.mp4', - progress: 100, - size: '0.3MB', - encoding_id: 1, - status: 'success', - }, - }, + 144: {}, 240: { h264: { title: 'h264-240', - url: '/media/encoded/2/markos/d6ae9093cb1648529432f38ee1198200.d6ae9093cb1648529432f38ee1198200.video.mp4.mp4', + url: '/media/encoded/2/markos/c1ab03cab3bb46b5854a5e217cfe3013.c1ab03cab3bb46b5854a5e217cfe3013.VID_20230813_144422.mp4.mp4', progress: 100, - size: '0.6MB', - encoding_id: 2, + size: '2.2MB', + encoding_id: 4940, status: 'success', }, }, 360: { h264: { title: 'h264-360', - url: '/media/encoded/3/markos/d6ae9093cb1648529432f38ee1198200.d6ae9093cb1648529432f38ee1198200.video.mp4.mp4', + url: '/media/encoded/3/markos/c1ab03cab3bb46b5854a5e217cfe3013.c1ab03cab3bb46b5854a5e217cfe3013.VID_20230813_144422.mp4.mp4', progress: 100, - size: '0.8MB', - encoding_id: 3, + size: '3.3MB', + encoding_id: 4941, status: 'success', }, }, 480: { h264: { title: 'h264-480', - url: '/media/encoded/13/markos/d6ae9093cb1648529432f38ee1198200.d6ae9093cb1648529432f38ee1198200.video.mp4.mp4', + url: '/media/encoded/13/markos/c1ab03cab3bb46b5854a5e217cfe3013.c1ab03cab3bb46b5854a5e217cfe3013.VID_20230813_144422.mp4.mp4', progress: 100, - size: '1.5MB', - encoding_id: 4, + size: '6.0MB', + encoding_id: 4942, status: 'success', }, }, 720: { h264: { title: 'h264-720', - url: '/media/encoded/10/markos/d6ae9093cb1648529432f38ee1198200.d6ae9093cb1648529432f38ee1198200.video.mp4.mp4', + url: '/media/encoded/10/markos/c1ab03cab3bb46b5854a5e217cfe3013.c1ab03cab3bb46b5854a5e217cfe3013.VID_20230813_144422.mp4.mp4', progress: 100, - size: '3.5MB', - encoding_id: 5, + size: '17.8MB', + encoding_id: 4943, status: 'success', }, }, 1080: { h264: { title: 'h264-1080', - url: '/media/encoded/7/markos/d6ae9093cb1648529432f38ee1198200.d6ae9093cb1648529432f38ee1198200.video.mp4.mp4', + url: '/media/encoded/7/markos/c1ab03cab3bb46b5854a5e217cfe3013.c1ab03cab3bb46b5854a5e217cfe3013.VID_20230813_144422.mp4.mp4', progress: 100, - size: '6.4MB', - encoding_id: 6, + size: '37.2MB', + encoding_id: 4944, status: 'success', }, }, @@ -1060,14 +1214,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) { }, // other - useRoundedCorners: false, + useRoundedCorners: true, isPlayList: false, previewSprite: { - url: 'https://videojs.mediacms.io/media/original/thumbnails/user/markos/d6ae9093cb1648529432f38ee1198200.video.mp4sprites.jpg', + url: 'https://demo.mediacms.io/media/original/thumbnails/user/markos/c1ab03cab3bb46b5854a5e217cfe3013.VID_20230813_144422.mp4sprites.jpg', frame: { width: 160, height: 90, seconds: 10 }, }, - siteUrl: 'https://videojs.mediacms.io', - nextLink: 'https://videojs.mediacms.io/view?m=YjGJafibO', + siteUrl: 'https://demo.mediacms.io', + nextLink: 'https://demo.mediacms.io/view?m=elygiagorgechania', urlAutoplay: true, urlMuted: false, }, @@ -1593,960 +1747,1025 @@ function VideoJSPlayer({ videoId = 'default-video' }) { return; } - const timer = setTimeout(() => { - // Double-check that we still don't have a player and element exists - if (!playerRef.current && videoRef.current && !videoRef.current.player) { - playerRef.current = videojs(videoRef.current, { - // ===== STANDARD