From a29d94dcbdff0d78fbf52b4f3b2bb658ba65a789 Mon Sep 17 00:00:00 2001 From: Yiannis Christodoulou Date: Mon, 6 Oct 2025 12:41:54 +0300 Subject: [PATCH] Remove VideoJSNew and consolidate player components Deleted VideoJSNew.jsx and removed VideoJSPlayerNew from exports. Merged VideoJSPlayerNew.css into VideoJSPlayer.css and updated VideoJSPlayer.jsx with new features, configuration, and sample data. Refactored and enhanced VideoJSPlayer to be the main player component, updating references and improving tooltip, autoplay, and control bar logic. --- frontend-tools/video-js/src/VideoJSNew.jsx | 8 - .../video-js/src/components/index.js | 1 - ...VideoJSPlayerNew.css => VideoJSPlayer.css} | 0 .../components/video-player/VideoJSPlayer.jsx | 3071 +++++++++-------- ...JSPlayerNew.jsx => VideoJSPlayer__OLD.jsx} | 3065 ++++++++-------- frontend-tools/video-js/src/main.jsx | 27 +- 6 files changed, 3071 insertions(+), 3101 deletions(-) delete mode 100644 frontend-tools/video-js/src/VideoJSNew.jsx rename frontend-tools/video-js/src/components/video-player/{VideoJSPlayerNew.css => VideoJSPlayer.css} (100%) rename frontend-tools/video-js/src/components/video-player/{VideoJSPlayerNew.jsx => VideoJSPlayer__OLD.jsx} (57%) 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