mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 07:28:53 -05:00
feat: Convert chapters data from backend format to required format with memoization
This commit is contained in:
parent
222c728909
commit
738d0d9e00
@ -39,10 +39,10 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
// COMMON
|
// COMMON
|
||||||
poster_url:
|
poster_url:
|
||||||
'https://demo.mediacms.io/media/original/thumbnails/user/markos/7dedcb56bde9463dbc0766768a99be0f_C8E5GFY.20250605_110647.mp4.jpg',
|
'https://demo.mediacms.io/media/original/thumbnails/user/markos/7dedcb56bde9463dbc0766768a99be0f_C8E5GFY.20250605_110647.mp4.jpg',
|
||||||
__chapter_data: [
|
chapter_data: [
|
||||||
{ startTime: 0, endTime: 4, text: 'A1 test' },
|
{ startTime: '00:00:00.000', endTime: '00:00:24.295', text: 'A1 test' },
|
||||||
{ startTime: 5, endTime: 10, text: 'A2 of Marine Life' },
|
{ startTime: '00:00:24.295', endTime: '00:00:48.590', text: 'A2 of Marine Life' },
|
||||||
{ startTime: 10, endTime: 15, text: 'A3 Reef Ecosystems' },
|
{ startTime: '00:00:48.590', endTime: '00:01:12.885', text: 'A3 Reef Ecosystems' },
|
||||||
],
|
],
|
||||||
related_media: [
|
related_media: [
|
||||||
{
|
{
|
||||||
@ -1057,37 +1057,91 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
// CONDITIONAL LOGIC:
|
// CONDITIONAL LOGIC:
|
||||||
// - When chaptersData has content: Uses original ChapterMarkers with sprite preview
|
// - When chaptersData has content: Uses original ChapterMarkers with sprite preview
|
||||||
// - When chaptersData is empty: Uses separate SpritePreview component
|
// - When chaptersData is empty: Uses separate SpritePreview component
|
||||||
// Toggle between these two lines to test both scenarios:
|
// Utility function to convert time string (HH:MM:SS.mmm) to seconds
|
||||||
const chaptersData =
|
const convertTimeStringToSeconds = (timeString) => {
|
||||||
mediaData?.data?.chapter_data && mediaData?.data?.chapter_data.length > 0
|
if (typeof timeString === 'number') {
|
||||||
? mediaData?.data?.chapter_data
|
return timeString; // Already in seconds
|
||||||
: isDevMode
|
}
|
||||||
|
|
||||||
|
if (typeof timeString !== 'string') {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = timeString.split(':');
|
||||||
|
if (parts.length !== 3) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const hours = parseInt(parts[0], 10) || 0;
|
||||||
|
const minutes = parseInt(parts[1], 10) || 0;
|
||||||
|
const seconds = parseFloat(parts[2]) || 0;
|
||||||
|
|
||||||
|
return hours * 3600 + minutes * 60 + seconds;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Test the conversion function
|
||||||
|
if (isDevMode) {
|
||||||
|
console.log('Testing time conversion:');
|
||||||
|
console.log('00:00:24.295 ->', convertTimeStringToSeconds('00:00:24.295')); // Should be 24.295
|
||||||
|
console.log('00:01:30.500 ->', convertTimeStringToSeconds('00:01:30.500')); // Should be 90.5
|
||||||
|
console.log('01:00:00.000 ->', convertTimeStringToSeconds('01:00:00.000')); // Should be 3600
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert chapters data from backend format to required format with memoization
|
||||||
|
const convertChaptersData = useMemo(() => {
|
||||||
|
return (rawChaptersData) => {
|
||||||
|
if (!rawChaptersData || !Array.isArray(rawChaptersData)) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Converting raw chapters data:', rawChaptersData);
|
||||||
|
|
||||||
|
const convertedData = rawChaptersData.map((chapter) => ({
|
||||||
|
startTime: convertTimeStringToSeconds(chapter.startTime),
|
||||||
|
endTime: convertTimeStringToSeconds(chapter.endTime),
|
||||||
|
text: chapter.text,
|
||||||
|
}));
|
||||||
|
|
||||||
|
return convertedData;
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Memoized chapters data conversion
|
||||||
|
const chaptersData = useMemo(() => {
|
||||||
|
if (mediaData?.data?.chapter_data && mediaData?.data?.chapter_data.length > 0) {
|
||||||
|
return convertChaptersData(mediaData?.data?.chapter_data);
|
||||||
|
}
|
||||||
|
return isDevMode
|
||||||
? [
|
? [
|
||||||
{ startTime: 0, endTime: 4, text: 'Introduction' },
|
{ startTime: '00:00:00.000', endTime: '00:00:04.000', text: 'Introduction' },
|
||||||
{ startTime: 5, endTime: 10, text: 'Overview of Marine Life' },
|
{ startTime: '00:00:05.000', endTime: '00:00:10.000', text: 'Overview of Marine Life' },
|
||||||
{ startTime: 10, endTime: 15, text: 'Coral Reef Ecosystems' },
|
{ startTime: '00:00:10.000', endTime: '00:00:15.000', text: 'Coral Reef Ecosystems' },
|
||||||
{ startTime: 15, endTime: 20, text: 'Deep Sea Creatures' },
|
{ startTime: '00:00:15.000', endTime: '00:00:20.000', text: 'Deep Sea Creatures' },
|
||||||
{ startTime: 20, endTime: 30, text: 'Ocean Conservation' },
|
{ startTime: '00:00:20.000', endTime: '00:00:30.000', text: 'Ocean Conservation' },
|
||||||
{ startTime: 240, endTime: 320, text: 'Ocean Conservation' },
|
{ startTime: '00:00:24.000', endTime: '00:00:32.000', text: 'Ocean Conservation' },
|
||||||
{ startTime: 320, endTime: 400, text: 'Climate Change Impact' },
|
{ startTime: '00:00:32.000', endTime: '00:00:40.000', text: 'Climate Change Impact' },
|
||||||
{ startTime: 400, endTime: 480, text: 'Marine Protected Areas' },
|
{ startTime: '00:00:40.000', endTime: '00:00:48.000', text: 'Marine Protected Areas' },
|
||||||
{ startTime: 480, endTime: 560, text: 'Sustainable Fishing' },
|
{ startTime: '00:00:48.000', endTime: '00:00:56.000', text: 'Sustainable Fishing' },
|
||||||
{ startTime: 560, endTime: 640, text: 'Research Methods' },
|
{ startTime: '00:00:56.000', endTime: '00:00:64.000', text: 'Research Methods' },
|
||||||
{ startTime: 640, endTime: 720, text: 'Future Challenges' },
|
{ startTime: '00:00:64.000', endTime: '00:00:72.000', text: 'Future Challenges' },
|
||||||
{ startTime: 720, endTime: 800, text: 'Conclusion' },
|
{ startTime: '00:00:72.000', endTime: '00:00:80.000', text: 'Conclusion' },
|
||||||
{ startTime: 800, endTime: 880, text: 'Marine Biodiversity Hotspots' },
|
{ startTime: '00:00:80.000', endTime: '00:00:88.000', text: 'Marine Biodiversity Hotspots' },
|
||||||
{ startTime: 880, endTime: 960, text: 'Underwater Photography' },
|
{ startTime: '00:00:88.000', endTime: '00:00:96.000', text: 'Underwater Photography' },
|
||||||
{ startTime: 960, endTime: 1040, text: 'Whale Migration Patterns' },
|
{ startTime: '00:00:96.000', endTime: '00:01:04.000', text: 'Whale Migration Patterns' },
|
||||||
{ startTime: 1040, endTime: 1120, text: 'Plastic Pollution Crisis' },
|
{ startTime: '00:01:04.000', endTime: '00:01:12.000', text: 'Plastic Pollution Crisis' },
|
||||||
{ startTime: 1120, endTime: 1200, text: 'Seagrass Meadows' },
|
{ startTime: '00:01:12.000', endTime: '00:01:20.000', text: 'Seagrass Meadows' },
|
||||||
{ startTime: 1200, endTime: 1280, text: 'Ocean Acidification' },
|
{ startTime: '00:01:20.000', endTime: '00:01:28.000', text: 'Ocean Acidification' },
|
||||||
{ startTime: 1280, endTime: 1360, text: 'Marine Archaeology' },
|
{ startTime: '00:01:28.000', endTime: '00:01:36.000', text: 'Marine Archaeology' },
|
||||||
{ startTime: 1360, endTime: 1440, text: 'Tidal Pool Ecosystems' },
|
{ startTime: '00:01:28.000', endTime: '00:01:36.000', text: 'Tidal Pool Ecosystems' },
|
||||||
{ startTime: 1440, endTime: 1520, text: 'Commercial Aquaculture' },
|
{ startTime: '00:01:36.000', endTime: '00:01:44.000', text: 'Commercial Aquaculture' },
|
||||||
{ startTime: 1520, endTime: 1600, text: 'Ocean Exploration Technology' },
|
{ startTime: '00:01:44.000', endTime: '00:01:52.000', text: 'Ocean Exploration Technology' },
|
||||||
]
|
].map((chapter) => ({
|
||||||
|
startTime: convertTimeStringToSeconds(chapter.startTime),
|
||||||
|
endTime: convertTimeStringToSeconds(chapter.endTime),
|
||||||
|
text: chapter.text,
|
||||||
|
}))
|
||||||
: [];
|
: [];
|
||||||
// const chaptersData = []; // NO CHAPTERS (uses separate SpritePreview)
|
}, [mediaData?.data?.chapter_data, isDevMode, convertChaptersData]);
|
||||||
|
|
||||||
// Helper function to determine MIME type based on file extension or media type
|
// Helper function to determine MIME type based on file extension or media type
|
||||||
const getMimeType = (url, mediaType) => {
|
const getMimeType = (url, mediaType) => {
|
||||||
@ -2246,7 +2300,6 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
}
|
}
|
||||||
// END: Move chapters button after fullscreen toggle
|
// END: Move chapters button after fullscreen toggle
|
||||||
|
|
||||||
console.log('chaptersData', chaptersData);
|
|
||||||
// BEGIN: Add Chapters Overlay Component
|
// BEGIN: Add Chapters Overlay Component
|
||||||
if (chaptersData && chaptersData.length > 0) {
|
if (chaptersData && chaptersData.length > 0) {
|
||||||
customComponents.current.chaptersOverlay = new CustomChaptersOverlay(playerRef.current, {
|
customComponents.current.chaptersOverlay = new CustomChaptersOverlay(playerRef.current, {
|
||||||
@ -2730,10 +2783,46 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
// Focus the player element so keyboard controls work
|
// Focus the player element so keyboard controls work
|
||||||
// This ensures spacebar can pause/play the video
|
// This ensures spacebar can pause/play the video
|
||||||
playerRef.current.ready(() => {
|
playerRef.current.ready(() => {
|
||||||
// Focus the player element
|
// Focus the player element and set up keyboard controls
|
||||||
if (playerRef.current.el()) {
|
if (playerRef.current.el()) {
|
||||||
playerRef.current.el().focus();
|
// Make the video element focusable
|
||||||
|
const videoElement = playerRef.current.el();
|
||||||
|
videoElement.setAttribute('tabindex', '0');
|
||||||
|
videoElement.focus();
|
||||||
console.log('Video player focused for keyboard controls');
|
console.log('Video player focused for keyboard controls');
|
||||||
|
|
||||||
|
// Add custom keyboard event handler for space key
|
||||||
|
const handleKeyPress = (event) => {
|
||||||
|
// Only handle space key when video element is focused or no other input is focused
|
||||||
|
if (event.code === 'Space' || event.key === ' ') {
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
const isInputFocused =
|
||||||
|
activeElement &&
|
||||||
|
(activeElement.tagName === 'INPUT' ||
|
||||||
|
activeElement.tagName === 'TEXTAREA' ||
|
||||||
|
activeElement.contentEditable === 'true');
|
||||||
|
|
||||||
|
// Only prevent default and control video if no input is focused
|
||||||
|
if (!isInputFocused) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (playerRef.current) {
|
||||||
|
if (playerRef.current.paused()) {
|
||||||
|
playerRef.current.play();
|
||||||
|
} else {
|
||||||
|
playerRef.current.pause();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add event listener to document for global space key handling
|
||||||
|
document.addEventListener('keydown', handleKeyPress);
|
||||||
|
|
||||||
|
// Store cleanup function
|
||||||
|
customComponents.current.cleanupKeyboardHandler = () => {
|
||||||
|
document.removeEventListener('keydown', handleKeyPress);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start playing the video immediately if autoplay is enabled
|
// Start playing the video immediately if autoplay is enabled
|
||||||
@ -2767,11 +2856,13 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Additional effect to ensure video gets focus for keyboard controls
|
// Additional effect to ensure video gets focus for keyboard controls on page load
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const focusVideo = () => {
|
const focusVideo = () => {
|
||||||
if (playerRef.current && playerRef.current.el()) {
|
if (playerRef.current && playerRef.current.el()) {
|
||||||
playerRef.current.el().focus();
|
const videoElement = playerRef.current.el();
|
||||||
|
videoElement.setAttribute('tabindex', '0');
|
||||||
|
videoElement.focus();
|
||||||
console.log('Video element focused for keyboard controls');
|
console.log('Video element focused for keyboard controls');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -2791,12 +2882,14 @@ function VideoJSPlayer({ videoId = 'default-video' }) {
|
|||||||
document.addEventListener('visibilitychange', handleVisibilityChange);
|
document.addEventListener('visibilitychange', handleVisibilityChange);
|
||||||
window.addEventListener('focus', handleWindowFocus);
|
window.addEventListener('focus', handleWindowFocus);
|
||||||
|
|
||||||
// Initial focus attempt
|
// Multiple attempts to ensure focus on page load
|
||||||
setTimeout(focusVideo, 500);
|
const focusAttempts = [100, 500, 1000, 2000];
|
||||||
|
const timeouts = focusAttempts.map((delay) => setTimeout(focusVideo, delay));
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
document.removeEventListener('visibilitychange', handleVisibilityChange);
|
||||||
window.removeEventListener('focus', handleWindowFocus);
|
window.removeEventListener('focus', handleWindowFocus);
|
||||||
|
timeouts.forEach(clearTimeout);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|||||||
@ -495,9 +495,6 @@
|
|||||||
color: #888;
|
color: #888;
|
||||||
}
|
}
|
||||||
|
|
||||||
.video-js {
|
|
||||||
background: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vjs-big-play-button {
|
.vjs-big-play-button {
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user