mirror of
https://github.com/mediacms-io/mediacms.git
synced 2025-11-06 15:38:53 -05:00
chore: video-editor update with new prettier settings
This commit is contained in:
parent
eaf87e20d8
commit
074638e237
@ -1,5 +1,5 @@
|
|||||||
import { formatTime, formatLongTime } from "@/lib/timeUtils";
|
import { formatTime, formatLongTime } from '@/lib/timeUtils';
|
||||||
import "../styles/ClipSegments.css";
|
import '../styles/ClipSegments.css';
|
||||||
|
|
||||||
export interface Segment {
|
export interface Segment {
|
||||||
id: number;
|
id: number;
|
||||||
@ -20,8 +20,8 @@ const ClipSegments = ({ segments }: ClipSegmentsProps) => {
|
|||||||
// Handle delete segment click
|
// Handle delete segment click
|
||||||
const handleDeleteSegment = (segmentId: number) => {
|
const handleDeleteSegment = (segmentId: number) => {
|
||||||
// Create and dispatch the delete event
|
// Create and dispatch the delete event
|
||||||
const deleteEvent = new CustomEvent("delete-segment", {
|
const deleteEvent = new CustomEvent('delete-segment', {
|
||||||
detail: { segmentId }
|
detail: { segmentId },
|
||||||
});
|
});
|
||||||
document.dispatchEvent(deleteEvent);
|
document.dispatchEvent(deleteEvent);
|
||||||
};
|
};
|
||||||
@ -74,9 +74,7 @@ const ClipSegments = ({ segments }: ClipSegmentsProps) => {
|
|||||||
))}
|
))}
|
||||||
|
|
||||||
{sortedSegments.length === 0 && (
|
{sortedSegments.length === 0 && (
|
||||||
<div className="empty-message">
|
<div className="empty-message">No segments created yet. Use the split button to create segments.</div>
|
||||||
No segments created yet. Use the split button to create segments.
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import "../styles/EditingTools.css";
|
import '../styles/EditingTools.css';
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from 'react';
|
||||||
|
|
||||||
interface EditingToolsProps {
|
interface EditingToolsProps {
|
||||||
onSplit: () => void;
|
onSplit: () => void;
|
||||||
@ -24,7 +24,7 @@ const EditingTools = ({
|
|||||||
canUndo,
|
canUndo,
|
||||||
canRedo,
|
canRedo,
|
||||||
isPlaying = false,
|
isPlaying = false,
|
||||||
isPlayingSegments = false
|
isPlayingSegments = false,
|
||||||
}: EditingToolsProps) => {
|
}: EditingToolsProps) => {
|
||||||
const [isSmallScreen, setIsSmallScreen] = useState(false);
|
const [isSmallScreen, setIsSmallScreen] = useState(false);
|
||||||
|
|
||||||
@ -34,15 +34,15 @@ const EditingTools = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
checkScreenSize();
|
checkScreenSize();
|
||||||
window.addEventListener("resize", checkScreenSize);
|
window.addEventListener('resize', checkScreenSize);
|
||||||
return () => window.removeEventListener("resize", checkScreenSize);
|
return () => window.removeEventListener('resize', checkScreenSize);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Handle play button click with iOS fix
|
// Handle play button click with iOS fix
|
||||||
const handlePlay = () => {
|
const handlePlay = () => {
|
||||||
// Ensure lastSeekedPosition is used when play is clicked
|
// Ensure lastSeekedPosition is used when play is clicked
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
console.log("Play button clicked, current lastSeekedPosition:", window.lastSeekedPosition);
|
console.log('Play button clicked, current lastSeekedPosition:', window.lastSeekedPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the original handler
|
// Call the original handler
|
||||||
@ -59,9 +59,9 @@ const EditingTools = ({
|
|||||||
className={`button segments-button`}
|
className={`button segments-button`}
|
||||||
onClick={onPlaySegments}
|
onClick={onPlaySegments}
|
||||||
data-tooltip={
|
data-tooltip={
|
||||||
isPlayingSegments ? "Stop segments playback" : "Play segments in one continuous flow"
|
isPlayingSegments ? 'Stop segments playback' : 'Play segments in one continuous flow'
|
||||||
}
|
}
|
||||||
style={{ fontSize: "0.875rem" }}
|
style={{ fontSize: '0.875rem' }}
|
||||||
>
|
>
|
||||||
{isPlayingSegments ? (
|
{isPlayingSegments ? (
|
||||||
<>
|
<>
|
||||||
@ -133,10 +133,10 @@ const EditingTools = ({
|
|||||||
{/* Standard Play button (only shown when not in segments playback on small screens) */}
|
{/* Standard Play button (only shown when not in segments playback on small screens) */}
|
||||||
{(!isPlayingSegments || !isSmallScreen) && (
|
{(!isPlayingSegments || !isSmallScreen) && (
|
||||||
<button
|
<button
|
||||||
className={`button play-button ${isPlayingSegments ? "greyed-out" : ""}`}
|
className={`button play-button ${isPlayingSegments ? 'greyed-out' : ''}`}
|
||||||
onClick={handlePlay}
|
onClick={handlePlay}
|
||||||
data-tooltip={isPlaying ? "Pause video" : "Play full video"}
|
data-tooltip={isPlaying ? 'Pause video' : 'Play full video'}
|
||||||
style={{ fontSize: "0.875rem" }}
|
style={{ fontSize: '0.875rem' }}
|
||||||
disabled={isPlayingSegments}
|
disabled={isPlayingSegments}
|
||||||
>
|
>
|
||||||
{isPlaying && !isPlayingSegments ? (
|
{isPlaying && !isPlayingSegments ? (
|
||||||
@ -208,7 +208,7 @@ const EditingTools = ({
|
|||||||
<button
|
<button
|
||||||
className="button"
|
className="button"
|
||||||
aria-label="Undo"
|
aria-label="Undo"
|
||||||
data-tooltip={isPlayingSegments ? "Disabled during preview" : "Undo last action"}
|
data-tooltip={isPlayingSegments ? 'Disabled during preview' : 'Undo last action'}
|
||||||
disabled={!canUndo || isPlayingSegments}
|
disabled={!canUndo || isPlayingSegments}
|
||||||
onClick={onUndo}
|
onClick={onUndo}
|
||||||
>
|
>
|
||||||
@ -229,7 +229,7 @@ const EditingTools = ({
|
|||||||
<button
|
<button
|
||||||
className="button"
|
className="button"
|
||||||
aria-label="Redo"
|
aria-label="Redo"
|
||||||
data-tooltip={isPlayingSegments ? "Disabled during preview" : "Redo last undone action"}
|
data-tooltip={isPlayingSegments ? 'Disabled during preview' : 'Redo last undone action'}
|
||||||
disabled={!canRedo || isPlayingSegments}
|
disabled={!canRedo || isPlayingSegments}
|
||||||
onClick={onRedo}
|
onClick={onRedo}
|
||||||
>
|
>
|
||||||
@ -251,7 +251,7 @@ const EditingTools = ({
|
|||||||
<button
|
<button
|
||||||
className="button"
|
className="button"
|
||||||
onClick={onReset}
|
onClick={onReset}
|
||||||
data-tooltip={isPlayingSegments ? "Disabled during preview" : "Reset to full video"}
|
data-tooltip={isPlayingSegments ? 'Disabled during preview' : 'Reset to full video'}
|
||||||
disabled={isPlayingSegments}
|
disabled={isPlayingSegments}
|
||||||
>
|
>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from 'react';
|
||||||
import "../styles/IOSPlayPrompt.css";
|
import '../styles/IOSPlayPrompt.css';
|
||||||
|
|
||||||
interface MobilePlayPromptProps {
|
interface MobilePlayPromptProps {
|
||||||
videoRef: React.RefObject<HTMLVideoElement>;
|
videoRef: React.RefObject<HTMLVideoElement>;
|
||||||
@ -33,9 +33,9 @@ const MobilePlayPrompt: React.FC<MobilePlayPromptProps> = ({ videoRef, onPlay })
|
|||||||
setIsVisible(false);
|
setIsVisible(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
video.addEventListener("play", handlePlay);
|
video.addEventListener('play', handlePlay);
|
||||||
return () => {
|
return () => {
|
||||||
video.removeEventListener("play", handlePlay);
|
video.removeEventListener('play', handlePlay);
|
||||||
};
|
};
|
||||||
}, [videoRef]);
|
}, [videoRef]);
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState, useRef } from "react";
|
import { useEffect, useState, useRef } from 'react';
|
||||||
import { formatTime } from "@/lib/timeUtils";
|
import { formatTime } from '@/lib/timeUtils';
|
||||||
import "../styles/IOSVideoPlayer.css";
|
import '../styles/IOSVideoPlayer.css';
|
||||||
|
|
||||||
interface IOSVideoPlayerProps {
|
interface IOSVideoPlayerProps {
|
||||||
videoRef: React.RefObject<HTMLVideoElement>;
|
videoRef: React.RefObject<HTMLVideoElement>;
|
||||||
@ -9,7 +9,7 @@ interface IOSVideoPlayerProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps) => {
|
const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps) => {
|
||||||
const [videoUrl, setVideoUrl] = useState<string>("");
|
const [videoUrl, setVideoUrl] = useState<string>('');
|
||||||
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
|
const [iosVideoRef, setIosVideoRef] = useState<HTMLVideoElement | null>(null);
|
||||||
|
|
||||||
// Refs for hold-to-continue functionality
|
// Refs for hold-to-continue functionality
|
||||||
@ -26,14 +26,14 @@ const IOSVideoPlayer = ({ videoRef, currentTime, duration }: IOSVideoPlayerProps
|
|||||||
|
|
||||||
// Get the video source URL from the main player
|
// Get the video source URL from the main player
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (videoRef.current && videoRef.current.querySelector("source")) {
|
if (videoRef.current && videoRef.current.querySelector('source')) {
|
||||||
const source = videoRef.current.querySelector("source") as HTMLSourceElement;
|
const source = videoRef.current.querySelector('source') as HTMLSourceElement;
|
||||||
if (source && source.src) {
|
if (source && source.src) {
|
||||||
setVideoUrl(source.src);
|
setVideoUrl(source.src);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Fallback to sample video if needed
|
// Fallback to sample video if needed
|
||||||
setVideoUrl("/videos/sample-video-10m.mp4");
|
setVideoUrl('/videos/sample-video.mp4');
|
||||||
}
|
}
|
||||||
}, [videoRef]);
|
}, [videoRef]);
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from 'react';
|
||||||
import "../styles/Modal.css";
|
import '../styles/Modal.css';
|
||||||
|
|
||||||
interface ModalProps {
|
interface ModalProps {
|
||||||
isOpen: boolean;
|
isOpen: boolean;
|
||||||
@ -13,21 +13,21 @@ const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children, actions
|
|||||||
// Close modal when Escape key is pressed
|
// Close modal when Escape key is pressed
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEscapeKey = (event: KeyboardEvent) => {
|
const handleEscapeKey = (event: KeyboardEvent) => {
|
||||||
if (event.key === "Escape" && isOpen) {
|
if (event.key === 'Escape' && isOpen) {
|
||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("keydown", handleEscapeKey);
|
document.addEventListener('keydown', handleEscapeKey);
|
||||||
|
|
||||||
// Disable body scrolling when modal is open
|
// Disable body scrolling when modal is open
|
||||||
if (isOpen) {
|
if (isOpen) {
|
||||||
document.body.style.overflow = "hidden";
|
document.body.style.overflow = 'hidden';
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("keydown", handleEscapeKey);
|
document.removeEventListener('keydown', handleEscapeKey);
|
||||||
document.body.style.overflow = "";
|
document.body.style.overflow = '';
|
||||||
};
|
};
|
||||||
}, [isOpen, onClose]);
|
}, [isOpen, onClose]);
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -1,7 +1,7 @@
|
|||||||
import React, { useRef, useEffect, useState } from "react";
|
import React, { useRef, useEffect, useState } from 'react';
|
||||||
import { formatTime, formatDetailedTime } from "@/lib/timeUtils";
|
import { formatTime, formatDetailedTime } from '@/lib/timeUtils';
|
||||||
import logger from "../lib/logger";
|
import logger from '../lib/logger';
|
||||||
import "../styles/VideoPlayer.css";
|
import '../styles/VideoPlayer.css';
|
||||||
|
|
||||||
interface VideoPlayerProps {
|
interface VideoPlayerProps {
|
||||||
videoRef: React.RefObject<HTMLVideoElement>;
|
videoRef: React.RefObject<HTMLVideoElement>;
|
||||||
@ -22,7 +22,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
isMuted = false,
|
isMuted = false,
|
||||||
onPlayPause,
|
onPlayPause,
|
||||||
onSeek,
|
onSeek,
|
||||||
onToggleMute
|
onToggleMute,
|
||||||
}) => {
|
}) => {
|
||||||
const progressRef = useRef<HTMLDivElement>(null);
|
const progressRef = useRef<HTMLDivElement>(null);
|
||||||
const [isIOS, setIsIOS] = useState(false);
|
const [isIOS, setIsIOS] = useState(false);
|
||||||
@ -30,12 +30,13 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
const [lastPosition, setLastPosition] = useState<number | null>(null);
|
const [lastPosition, setLastPosition] = useState<number | null>(null);
|
||||||
const [isDraggingProgress, setIsDraggingProgress] = useState(false);
|
const [isDraggingProgress, setIsDraggingProgress] = useState(false);
|
||||||
const isDraggingProgressRef = useRef(false);
|
const isDraggingProgressRef = useRef(false);
|
||||||
const [tooltipPosition, setTooltipPosition] = useState({ x: 0 });
|
const [tooltipPosition, setTooltipPosition] = useState({
|
||||||
|
x: 0,
|
||||||
|
});
|
||||||
const [tooltipTime, setTooltipTime] = useState(0);
|
const [tooltipTime, setTooltipTime] = useState(0);
|
||||||
|
|
||||||
const sampleVideoUrl =
|
const sampleVideoUrl =
|
||||||
(typeof window !== "undefined" && (window as any).MEDIA_DATA?.videoUrl) ||
|
(typeof window !== 'undefined' && (window as any).MEDIA_DATA?.videoUrl) || '/videos/sample-video.mp4';
|
||||||
"/videos/sample-video-10m.mp4";
|
|
||||||
|
|
||||||
// Detect iOS device
|
// Detect iOS device
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -47,8 +48,8 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
setIsIOS(checkIOS());
|
setIsIOS(checkIOS());
|
||||||
|
|
||||||
// Check if video was previously initialized
|
// Check if video was previously initialized
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
const wasInitialized = localStorage.getItem("video_initialized") === "true";
|
const wasInitialized = localStorage.getItem('video_initialized') === 'true';
|
||||||
setHasInitialized(wasInitialized);
|
setHasInitialized(wasInitialized);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
@ -57,8 +58,8 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isPlaying && !hasInitialized) {
|
if (isPlaying && !hasInitialized) {
|
||||||
setHasInitialized(true);
|
setHasInitialized(true);
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
localStorage.setItem("video_initialized", "true");
|
localStorage.setItem('video_initialized', 'true');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [isPlaying, hasInitialized]);
|
}, [isPlaying, hasInitialized]);
|
||||||
@ -70,15 +71,15 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
|
|
||||||
// These attributes need to be set directly on the DOM element
|
// These attributes need to be set directly on the DOM element
|
||||||
// for iOS Safari to respect inline playback
|
// for iOS Safari to respect inline playback
|
||||||
video.setAttribute("playsinline", "true");
|
video.setAttribute('playsinline', 'true');
|
||||||
video.setAttribute("webkit-playsinline", "true");
|
video.setAttribute('webkit-playsinline', 'true');
|
||||||
video.setAttribute("x-webkit-airplay", "allow");
|
video.setAttribute('x-webkit-airplay', 'allow');
|
||||||
|
|
||||||
// Store the last known good position for iOS
|
// Store the last known good position for iOS
|
||||||
const handleTimeUpdate = () => {
|
const handleTimeUpdate = () => {
|
||||||
if (!isDraggingProgressRef.current) {
|
if (!isDraggingProgressRef.current) {
|
||||||
setLastPosition(video.currentTime);
|
setLastPosition(video.currentTime);
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
window.lastSeekedPosition = video.currentTime;
|
window.lastSeekedPosition = video.currentTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -86,25 +87,25 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
|
|
||||||
// Handle iOS-specific play/pause state
|
// Handle iOS-specific play/pause state
|
||||||
const handlePlay = () => {
|
const handlePlay = () => {
|
||||||
logger.debug("Video play event fired");
|
logger.debug('Video play event fired');
|
||||||
if (isIOS) {
|
if (isIOS) {
|
||||||
setHasInitialized(true);
|
setHasInitialized(true);
|
||||||
localStorage.setItem("video_initialized", "true");
|
localStorage.setItem('video_initialized', 'true');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePause = () => {
|
const handlePause = () => {
|
||||||
logger.debug("Video pause event fired");
|
logger.debug('Video pause event fired');
|
||||||
};
|
};
|
||||||
|
|
||||||
video.addEventListener("timeupdate", handleTimeUpdate);
|
video.addEventListener('timeupdate', handleTimeUpdate);
|
||||||
video.addEventListener("play", handlePlay);
|
video.addEventListener('play', handlePlay);
|
||||||
video.addEventListener("pause", handlePause);
|
video.addEventListener('pause', handlePause);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
video.removeEventListener("timeupdate", handleTimeUpdate);
|
video.removeEventListener('timeupdate', handleTimeUpdate);
|
||||||
video.removeEventListener("play", handlePlay);
|
video.removeEventListener('play', handlePlay);
|
||||||
video.removeEventListener("pause", handlePause);
|
video.removeEventListener('pause', handlePause);
|
||||||
};
|
};
|
||||||
}, [videoRef, isIOS, isDraggingProgressRef]);
|
}, [videoRef, isIOS, isDraggingProgressRef]);
|
||||||
|
|
||||||
@ -150,12 +151,12 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
const handleMouseUp = () => {
|
const handleMouseUp = () => {
|
||||||
setIsDraggingProgress(false);
|
setIsDraggingProgress(false);
|
||||||
isDraggingProgressRef.current = false;
|
isDraggingProgressRef.current = false;
|
||||||
document.removeEventListener("mousemove", handleMouseMove);
|
document.removeEventListener('mousemove', handleMouseMove);
|
||||||
document.removeEventListener("mouseup", handleMouseUp);
|
document.removeEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("mousemove", handleMouseMove);
|
document.addEventListener('mousemove', handleMouseMove);
|
||||||
document.addEventListener("mouseup", handleMouseUp);
|
document.addEventListener('mouseup', handleMouseUp);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle progress dragging for both mouse and touch events
|
// Handle progress dragging for both mouse and touch events
|
||||||
@ -167,14 +168,16 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
const seekTime = duration * clickPosition;
|
const seekTime = duration * clickPosition;
|
||||||
|
|
||||||
// Update tooltip position and time
|
// Update tooltip position and time
|
||||||
setTooltipPosition({ x: e.clientX });
|
setTooltipPosition({
|
||||||
|
x: e.clientX,
|
||||||
|
});
|
||||||
setTooltipTime(seekTime);
|
setTooltipTime(seekTime);
|
||||||
|
|
||||||
// Store position locally for iOS Safari - critical for timeline seeking
|
// Store position locally for iOS Safari - critical for timeline seeking
|
||||||
setLastPosition(seekTime);
|
setLastPosition(seekTime);
|
||||||
|
|
||||||
// Also store globally for integration with other components
|
// Also store globally for integration with other components
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
(window as any).lastSeekedPosition = seekTime;
|
(window as any).lastSeekedPosition = seekTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,14 +205,16 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
const handleTouchEnd = () => {
|
const handleTouchEnd = () => {
|
||||||
setIsDraggingProgress(false);
|
setIsDraggingProgress(false);
|
||||||
isDraggingProgressRef.current = false;
|
isDraggingProgressRef.current = false;
|
||||||
document.removeEventListener("touchmove", handleTouchMove);
|
document.removeEventListener('touchmove', handleTouchMove);
|
||||||
document.removeEventListener("touchend", handleTouchEnd);
|
document.removeEventListener('touchend', handleTouchEnd);
|
||||||
document.removeEventListener("touchcancel", handleTouchEnd);
|
document.removeEventListener('touchcancel', handleTouchEnd);
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("touchmove", handleTouchMove, { passive: false });
|
document.addEventListener('touchmove', handleTouchMove, {
|
||||||
document.addEventListener("touchend", handleTouchEnd);
|
passive: false,
|
||||||
document.addEventListener("touchcancel", handleTouchEnd);
|
});
|
||||||
|
document.addEventListener('touchend', handleTouchEnd);
|
||||||
|
document.addEventListener('touchcancel', handleTouchEnd);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle touch dragging on progress bar
|
// Handle touch dragging on progress bar
|
||||||
@ -217,7 +222,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
if (!progressRef.current) return;
|
if (!progressRef.current) return;
|
||||||
|
|
||||||
// Get the touch coordinates
|
// Get the touch coordinates
|
||||||
const touch = "touches" in e ? e.touches[0] : null;
|
const touch = 'touches' in e ? e.touches[0] : null;
|
||||||
if (!touch) return;
|
if (!touch) return;
|
||||||
|
|
||||||
e.preventDefault(); // Prevent scrolling while dragging
|
e.preventDefault(); // Prevent scrolling while dragging
|
||||||
@ -227,14 +232,16 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
const seekTime = duration * touchPosition;
|
const seekTime = duration * touchPosition;
|
||||||
|
|
||||||
// Update tooltip position and time
|
// Update tooltip position and time
|
||||||
setTooltipPosition({ x: touch.clientX });
|
setTooltipPosition({
|
||||||
|
x: touch.clientX,
|
||||||
|
});
|
||||||
setTooltipTime(seekTime);
|
setTooltipTime(seekTime);
|
||||||
|
|
||||||
// Store position for iOS Safari
|
// Store position for iOS Safari
|
||||||
setLastPosition(seekTime);
|
setLastPosition(seekTime);
|
||||||
|
|
||||||
// Also store globally for integration with other components
|
// Also store globally for integration with other components
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
(window as any).lastSeekedPosition = seekTime;
|
(window as any).lastSeekedPosition = seekTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +262,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
setLastPosition(seekTime);
|
setLastPosition(seekTime);
|
||||||
|
|
||||||
// Also store globally for integration with other components
|
// Also store globally for integration with other components
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
(window as any).lastSeekedPosition = seekTime;
|
(window as any).lastSeekedPosition = seekTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,7 +290,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
if (video.paused) {
|
if (video.paused) {
|
||||||
// For iOS Safari: Before playing, explicitly seek to the remembered position
|
// For iOS Safari: Before playing, explicitly seek to the remembered position
|
||||||
if (isIOS && lastPosition !== null && lastPosition > 0) {
|
if (isIOS && lastPosition !== null && lastPosition > 0) {
|
||||||
logger.debug("iOS: Explicitly setting position before play:", lastPosition);
|
logger.debug('iOS: Explicitly setting position before play:', lastPosition);
|
||||||
|
|
||||||
// First, seek to the position
|
// First, seek to the position
|
||||||
video.currentTime = lastPosition;
|
video.currentTime = lastPosition;
|
||||||
@ -296,13 +303,13 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
.play()
|
.play()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"iOS: Play started successfully at position:",
|
'iOS: Play started successfully at position:',
|
||||||
videoRef.current?.currentTime
|
videoRef.current?.currentTime
|
||||||
);
|
);
|
||||||
onPlayPause(); // Update parent state after successful play
|
onPlayPause(); // Update parent state after successful play
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("iOS: Error playing video:", err);
|
console.error('iOS: Error playing video:', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}, 50);
|
}, 50);
|
||||||
@ -311,11 +318,11 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
video
|
video
|
||||||
.play()
|
.play()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
logger.debug("Normal: Play started successfully");
|
logger.debug('Normal: Play started successfully');
|
||||||
onPlayPause(); // Update parent state after successful play
|
onPlayPause(); // Update parent state after successful play
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Error playing video:", err);
|
console.error('Error playing video:', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -350,7 +357,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Play/Pause Indicator (shows based on current state) */}
|
{/* Play/Pause Indicator (shows based on current state) */}
|
||||||
<div className={`play-pause-indicator ${isPlaying ? "pause-icon" : "play-icon"}`}></div>
|
<div className={`play-pause-indicator ${isPlaying ? 'pause-icon' : 'play-icon'}`}></div>
|
||||||
|
|
||||||
{/* Video Controls Overlay */}
|
{/* Video Controls Overlay */}
|
||||||
<div className="video-controls">
|
<div className="video-controls">
|
||||||
@ -363,13 +370,23 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
{/* Progress Bar with enhanced dragging */}
|
{/* Progress Bar with enhanced dragging */}
|
||||||
<div
|
<div
|
||||||
ref={progressRef}
|
ref={progressRef}
|
||||||
className={`video-progress ${isDraggingProgress ? "dragging" : ""}`}
|
className={`video-progress ${isDraggingProgress ? 'dragging' : ''}`}
|
||||||
onClick={handleProgressClick}
|
onClick={handleProgressClick}
|
||||||
onMouseDown={handleProgressDragStart}
|
onMouseDown={handleProgressDragStart}
|
||||||
onTouchStart={handleProgressTouchStart}
|
onTouchStart={handleProgressTouchStart}
|
||||||
>
|
>
|
||||||
<div className="video-progress-fill" style={{ width: `${progressPercentage}%` }}></div>
|
<div
|
||||||
<div className="video-scrubber" style={{ left: `${progressPercentage}%` }}></div>
|
className="video-progress-fill"
|
||||||
|
style={{
|
||||||
|
width: `${progressPercentage}%`,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<div
|
||||||
|
className="video-scrubber"
|
||||||
|
style={{
|
||||||
|
left: `${progressPercentage}%`,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
|
||||||
{/* Floating time tooltip when dragging */}
|
{/* Floating time tooltip when dragging */}
|
||||||
{isDraggingProgress && (
|
{isDraggingProgress && (
|
||||||
@ -377,7 +394,7 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
className="video-time-tooltip"
|
className="video-time-tooltip"
|
||||||
style={{
|
style={{
|
||||||
left: `${tooltipPosition.x}px`,
|
left: `${tooltipPosition.x}px`,
|
||||||
transform: "translateX(-50%)"
|
transform: 'translateX(-50%)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{formatDetailedTime(tooltipTime)}
|
{formatDetailedTime(tooltipTime)}
|
||||||
@ -391,9 +408,9 @@ const VideoPlayer: React.FC<VideoPlayerProps> = ({
|
|||||||
{onToggleMute && (
|
{onToggleMute && (
|
||||||
<button
|
<button
|
||||||
className="mute-button"
|
className="mute-button"
|
||||||
aria-label={isMuted ? "Unmute" : "Mute"}
|
aria-label={isMuted ? 'Unmute' : 'Mute'}
|
||||||
onClick={onToggleMute}
|
onClick={onToggleMute}
|
||||||
data-tooltip={isMuted ? "Unmute" : "Mute"}
|
data-tooltip={isMuted ? 'Unmute' : 'Mute'}
|
||||||
>
|
>
|
||||||
{isMuted ? (
|
{isMuted ? (
|
||||||
<svg
|
<svg
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from 'react';
|
||||||
import { generateThumbnail } from "@/lib/videoUtils";
|
import { generateThumbnail } from '@/lib/videoUtils';
|
||||||
import { formatDetailedTime } from "@/lib/timeUtils";
|
import { formatDetailedTime } from '@/lib/timeUtils';
|
||||||
import logger from "@/lib/logger";
|
import logger from '@/lib/logger';
|
||||||
import type { Segment } from "@/components/ClipSegments";
|
import type { Segment } from '@/components/ClipSegments';
|
||||||
|
|
||||||
// Represents a state of the editor for undo/redo
|
// Represents a state of the editor for undo/redo
|
||||||
interface EditorState {
|
interface EditorState {
|
||||||
@ -46,21 +46,18 @@ const useVideoTrimmer = () => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (history.length > 0) {
|
if (history.length > 0) {
|
||||||
// For debugging - moved to console.debug
|
// For debugging - moved to console.debug
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.debug(
|
console.debug(`History state updated: ${history.length} entries, position: ${historyPosition}`);
|
||||||
`History state updated: ${history.length} entries, position: ${historyPosition}`
|
|
||||||
);
|
|
||||||
// Log actions in history to help debug undo/redo
|
// Log actions in history to help debug undo/redo
|
||||||
const actions = history.map(
|
const actions = history.map(
|
||||||
(state, idx) =>
|
(state, idx) => `${idx}: ${state.action || 'unknown'} (segments: ${state.clipSegments.length})`
|
||||||
`${idx}: ${state.action || "unknown"} (segments: ${state.clipSegments.length})`
|
|
||||||
);
|
);
|
||||||
console.debug("History actions:", actions);
|
console.debug('History actions:', actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's at least one history entry and it wasn't a save operation, mark as having unsaved changes
|
// If there's at least one history entry and it wasn't a save operation, mark as having unsaved changes
|
||||||
const lastAction = history[historyPosition]?.action || "";
|
const lastAction = history[historyPosition]?.action || '';
|
||||||
if (lastAction !== "save" && lastAction !== "save_copy" && lastAction !== "save_segments") {
|
if (lastAction !== 'save' && lastAction !== 'save_copy' && lastAction !== 'save_segments') {
|
||||||
setHasUnsavedChanges(true);
|
setHasUnsavedChanges(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -72,7 +69,7 @@ const useVideoTrimmer = () => {
|
|||||||
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
if (hasUnsavedChanges) {
|
if (hasUnsavedChanges) {
|
||||||
// Standard way of showing a confirmation dialog before leaving
|
// Standard way of showing a confirmation dialog before leaving
|
||||||
const message = "Your edits will get lost if you leave the page. Do you want to continue?";
|
const message = 'Your edits will get lost if you leave the page. Do you want to continue?';
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.returnValue = message; // Chrome requires returnValue to be set
|
e.returnValue = message; // Chrome requires returnValue to be set
|
||||||
return message; // For other browsers
|
return message; // For other browsers
|
||||||
@ -80,11 +77,11 @@ const useVideoTrimmer = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add event listener
|
// Add event listener
|
||||||
window.addEventListener("beforeunload", handleBeforeUnload);
|
window.addEventListener('beforeunload', handleBeforeUnload);
|
||||||
|
|
||||||
// Clean up
|
// Clean up
|
||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("beforeunload", handleBeforeUnload);
|
window.removeEventListener('beforeunload', handleBeforeUnload);
|
||||||
};
|
};
|
||||||
}, [hasUnsavedChanges]);
|
}, [hasUnsavedChanges]);
|
||||||
|
|
||||||
@ -105,10 +102,10 @@ const useVideoTrimmer = () => {
|
|||||||
// Create an initial segment that spans the entire video
|
// Create an initial segment that spans the entire video
|
||||||
const initialSegment: Segment = {
|
const initialSegment: Segment = {
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "segment",
|
name: 'segment',
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
endTime: video.duration,
|
endTime: video.duration,
|
||||||
thumbnail: segmentThumbnail
|
thumbnail: segmentThumbnail,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize history state with the full-length segment
|
// Initialize history state with the full-length segment
|
||||||
@ -116,7 +113,7 @@ const useVideoTrimmer = () => {
|
|||||||
trimStart: 0,
|
trimStart: 0,
|
||||||
trimEnd: video.duration,
|
trimEnd: video.duration,
|
||||||
splitPoints: [],
|
splitPoints: [],
|
||||||
clipSegments: [initialSegment]
|
clipSegments: [initialSegment],
|
||||||
};
|
};
|
||||||
|
|
||||||
setHistory([initialState]);
|
setHistory([initialState]);
|
||||||
@ -159,19 +156,19 @@ const useVideoTrimmer = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Add event listeners
|
// Add event listeners
|
||||||
video.addEventListener("loadedmetadata", handleLoadedMetadata);
|
video.addEventListener('loadedmetadata', handleLoadedMetadata);
|
||||||
video.addEventListener("timeupdate", handleTimeUpdate);
|
video.addEventListener('timeupdate', handleTimeUpdate);
|
||||||
video.addEventListener("play", handlePlay);
|
video.addEventListener('play', handlePlay);
|
||||||
video.addEventListener("pause", handlePause);
|
video.addEventListener('pause', handlePause);
|
||||||
video.addEventListener("ended", handleEnded);
|
video.addEventListener('ended', handleEnded);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
// Remove event listeners
|
// Remove event listeners
|
||||||
video.removeEventListener("loadedmetadata", handleLoadedMetadata);
|
video.removeEventListener('loadedmetadata', handleLoadedMetadata);
|
||||||
video.removeEventListener("timeupdate", handleTimeUpdate);
|
video.removeEventListener('timeupdate', handleTimeUpdate);
|
||||||
video.removeEventListener("play", handlePlay);
|
video.removeEventListener('play', handlePlay);
|
||||||
video.removeEventListener("pause", handlePause);
|
video.removeEventListener('pause', handlePause);
|
||||||
video.removeEventListener("ended", handleEnded);
|
video.removeEventListener('ended', handleEnded);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -184,7 +181,7 @@ const useVideoTrimmer = () => {
|
|||||||
video.pause();
|
video.pause();
|
||||||
} else {
|
} else {
|
||||||
// iOS Safari fix: Use the last seeked position if available
|
// iOS Safari fix: Use the last seeked position if available
|
||||||
if (!isPlaying && typeof window !== "undefined" && window.lastSeekedPosition > 0) {
|
if (!isPlaying && typeof window !== 'undefined' && window.lastSeekedPosition > 0) {
|
||||||
// Only apply this if the video is not at the same position already
|
// Only apply this if the video is not at the same position already
|
||||||
// This avoids unnecessary seeking which might cause playback issues
|
// This avoids unnecessary seeking which might cause playback issues
|
||||||
if (Math.abs(video.currentTime - window.lastSeekedPosition) > 0.1) {
|
if (Math.abs(video.currentTime - window.lastSeekedPosition) > 0.1) {
|
||||||
@ -201,12 +198,12 @@ const useVideoTrimmer = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
// Play started successfully
|
// Play started successfully
|
||||||
// Reset the last seeked position after successfully starting playback
|
// Reset the last seeked position after successfully starting playback
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
window.lastSeekedPosition = 0;
|
window.lastSeekedPosition = 0;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Error starting playback:", err);
|
console.error('Error starting playback:', err);
|
||||||
setIsPlaying(false); // Reset state if play failed
|
setIsPlaying(false); // Reset state if play failed
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -226,7 +223,7 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Store the position in a global state accessible to iOS Safari
|
// Store the position in a global state accessible to iOS Safari
|
||||||
// This ensures when play is pressed later, it remembers the position
|
// This ensures when play is pressed later, it remembers the position
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
window.lastSeekedPosition = time;
|
window.lastSeekedPosition = time;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +236,7 @@ const useVideoTrimmer = () => {
|
|||||||
setIsPlaying(true); // Update state to reflect we're playing
|
setIsPlaying(true); // Update state to reflect we're playing
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Error resuming playback:", err);
|
console.error('Error resuming playback:', err);
|
||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -254,7 +251,7 @@ const useVideoTrimmer = () => {
|
|||||||
trimEnd,
|
trimEnd,
|
||||||
splitPoints: [...splitPoints],
|
splitPoints: [...splitPoints],
|
||||||
clipSegments: JSON.parse(JSON.stringify(clipSegments)), // Deep clone to avoid reference issues
|
clipSegments: JSON.parse(JSON.stringify(clipSegments)), // Deep clone to avoid reference issues
|
||||||
action: action || "manual_save" // Track the action that triggered this save
|
action: action || 'manual_save', // Track the action that triggered this save
|
||||||
};
|
};
|
||||||
|
|
||||||
// Check if state is significantly different from last saved state
|
// Check if state is significantly different from last saved state
|
||||||
@ -339,16 +336,16 @@ const useVideoTrimmer = () => {
|
|||||||
if (recordHistory) {
|
if (recordHistory) {
|
||||||
// Use a small timeout to ensure the state is updated
|
// Use a small timeout to ensure the state is updated
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
saveState(action || (isStart ? "adjust_trim_start" : "adjust_trim_end"));
|
saveState(action || (isStart ? 'adjust_trim_start' : 'adjust_trim_end'));
|
||||||
}, 10);
|
}, 10);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("update-trim", handleTrimUpdate as EventListener);
|
document.addEventListener('update-trim', handleTrimUpdate as EventListener);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("update-trim", handleTrimUpdate as EventListener);
|
document.removeEventListener('update-trim', handleTrimUpdate as EventListener);
|
||||||
};
|
};
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@ -360,11 +357,11 @@ const useVideoTrimmer = () => {
|
|||||||
// Default to true to ensure all segment changes are recorded
|
// Default to true to ensure all segment changes are recorded
|
||||||
const isSignificantChange = e.detail.recordHistory !== false;
|
const isSignificantChange = e.detail.recordHistory !== false;
|
||||||
// Get the action type if provided
|
// Get the action type if provided
|
||||||
const actionType = e.detail.action || "update_segments";
|
const actionType = e.detail.action || 'update_segments';
|
||||||
|
|
||||||
// Log the update details
|
// Log the update details
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`Updating segments with action: ${actionType}, recordHistory: ${isSignificantChange ? "true" : "false"}`
|
`Updating segments with action: ${actionType}, recordHistory: ${isSignificantChange ? 'true' : 'false'}`
|
||||||
);
|
);
|
||||||
|
|
||||||
// Update segment state immediately for UI feedback
|
// Update segment state immediately for UI feedback
|
||||||
@ -384,7 +381,7 @@ const useVideoTrimmer = () => {
|
|||||||
trimEnd,
|
trimEnd,
|
||||||
splitPoints: [...splitPoints],
|
splitPoints: [...splitPoints],
|
||||||
clipSegments: segmentsClone,
|
clipSegments: segmentsClone,
|
||||||
action: actionType // Store the action type in the state
|
action: actionType, // Store the action type in the state
|
||||||
};
|
};
|
||||||
|
|
||||||
// Get the current history position to ensure we're using the latest value
|
// Get the current history position to ensure we're using the latest value
|
||||||
@ -405,16 +402,12 @@ const useVideoTrimmer = () => {
|
|||||||
// Ensure the historyPosition is updated to the correct position
|
// Ensure the historyPosition is updated to the correct position
|
||||||
setHistoryPosition((prev) => {
|
setHistoryPosition((prev) => {
|
||||||
const newPosition = prev + 1;
|
const newPosition = prev + 1;
|
||||||
logger.debug(
|
logger.debug(`Saved state with action: ${actionType} to history position ${newPosition}`);
|
||||||
`Saved state with action: ${actionType} to history position ${newPosition}`
|
|
||||||
);
|
|
||||||
return newPosition;
|
return newPosition;
|
||||||
});
|
});
|
||||||
}, 20); // Slightly increased delay to ensure state updates are complete
|
}, 20); // Slightly increased delay to ensure state updates are complete
|
||||||
} else {
|
} else {
|
||||||
logger.debug(
|
logger.debug(`Skipped saving state to history for action: ${actionType} (recordHistory=false)`);
|
||||||
`Skipped saving state to history for action: ${actionType} (recordHistory=false)`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -423,8 +416,8 @@ const useVideoTrimmer = () => {
|
|||||||
const customEvent = e as CustomEvent;
|
const customEvent = e as CustomEvent;
|
||||||
if (
|
if (
|
||||||
customEvent.detail &&
|
customEvent.detail &&
|
||||||
typeof customEvent.detail.time === "number" &&
|
typeof customEvent.detail.time === 'number' &&
|
||||||
typeof customEvent.detail.segmentId === "number"
|
typeof customEvent.detail.segmentId === 'number'
|
||||||
) {
|
) {
|
||||||
// Get the time and segment ID from the event
|
// Get the time and segment ID from the event
|
||||||
const timeToSplit = customEvent.detail.time;
|
const timeToSplit = customEvent.detail.time;
|
||||||
@ -457,7 +450,7 @@ const useVideoTrimmer = () => {
|
|||||||
name: `${segmentToSplit.name}-A`,
|
name: `${segmentToSplit.name}-A`,
|
||||||
startTime: segmentToSplit.startTime,
|
startTime: segmentToSplit.startTime,
|
||||||
endTime: timeToSplit,
|
endTime: timeToSplit,
|
||||||
thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
|
thumbnail: '', // Empty placeholder - we'll use dynamic colors instead
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create second half of the split segment - no thumbnail needed
|
// Create second half of the split segment - no thumbnail needed
|
||||||
@ -466,7 +459,7 @@ const useVideoTrimmer = () => {
|
|||||||
name: `${segmentToSplit.name}-B`,
|
name: `${segmentToSplit.name}-B`,
|
||||||
startTime: timeToSplit,
|
startTime: timeToSplit,
|
||||||
endTime: segmentToSplit.endTime,
|
endTime: segmentToSplit.endTime,
|
||||||
thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
|
thumbnail: '', // Empty placeholder - we'll use dynamic colors instead
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add the new segments
|
// Add the new segments
|
||||||
@ -477,14 +470,14 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Update state
|
// Update state
|
||||||
setClipSegments(newSegments);
|
setClipSegments(newSegments);
|
||||||
saveState("split_segment");
|
saveState('split_segment');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle delete segment event
|
// Handle delete segment event
|
||||||
const handleDeleteSegment = async (e: Event) => {
|
const handleDeleteSegment = async (e: Event) => {
|
||||||
const customEvent = e as CustomEvent;
|
const customEvent = e as CustomEvent;
|
||||||
if (customEvent.detail && typeof customEvent.detail.segmentId === "number") {
|
if (customEvent.detail && typeof customEvent.detail.segmentId === 'number') {
|
||||||
const segmentId = customEvent.detail.segmentId;
|
const segmentId = customEvent.detail.segmentId;
|
||||||
|
|
||||||
// Find and remove the segment
|
// Find and remove the segment
|
||||||
@ -497,10 +490,10 @@ const useVideoTrimmer = () => {
|
|||||||
// No need to generate a thumbnail - we'll use dynamic colors
|
// No need to generate a thumbnail - we'll use dynamic colors
|
||||||
const defaultSegment: Segment = {
|
const defaultSegment: Segment = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
name: "segment",
|
name: 'segment',
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
endTime: videoRef.current.duration,
|
endTime: videoRef.current.duration,
|
||||||
thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
|
thumbnail: '', // Empty placeholder - we'll use dynamic colors instead
|
||||||
};
|
};
|
||||||
|
|
||||||
// Reset the trim points as well
|
// Reset the trim points as well
|
||||||
@ -512,32 +505,32 @@ const useVideoTrimmer = () => {
|
|||||||
// Just update the segments normally
|
// Just update the segments normally
|
||||||
setClipSegments(newSegments);
|
setClipSegments(newSegments);
|
||||||
}
|
}
|
||||||
saveState("delete_segment");
|
saveState('delete_segment');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("update-segments", handleUpdateSegments as EventListener);
|
document.addEventListener('update-segments', handleUpdateSegments as EventListener);
|
||||||
document.addEventListener("split-segment", handleSplitSegment as EventListener);
|
document.addEventListener('split-segment', handleSplitSegment as EventListener);
|
||||||
document.addEventListener("delete-segment", handleDeleteSegment as EventListener);
|
document.addEventListener('delete-segment', handleDeleteSegment as EventListener);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener("update-segments", handleUpdateSegments as EventListener);
|
document.removeEventListener('update-segments', handleUpdateSegments as EventListener);
|
||||||
document.removeEventListener("split-segment", handleSplitSegment as EventListener);
|
document.removeEventListener('split-segment', handleSplitSegment as EventListener);
|
||||||
document.removeEventListener("delete-segment", handleDeleteSegment as EventListener);
|
document.removeEventListener('delete-segment', handleDeleteSegment as EventListener);
|
||||||
};
|
};
|
||||||
}, [clipSegments, duration]);
|
}, [clipSegments, duration]);
|
||||||
|
|
||||||
// Handle trim start change
|
// Handle trim start change
|
||||||
const handleTrimStartChange = (time: number) => {
|
const handleTrimStartChange = (time: number) => {
|
||||||
setTrimStart(time);
|
setTrimStart(time);
|
||||||
saveState("adjust_trim_start");
|
saveState('adjust_trim_start');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle trim end change
|
// Handle trim end change
|
||||||
const handleTrimEndChange = (time: number) => {
|
const handleTrimEndChange = (time: number) => {
|
||||||
setTrimEnd(time);
|
setTrimEnd(time);
|
||||||
saveState("adjust_trim_end");
|
saveState('adjust_trim_end');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle split at current position
|
// Handle split at current position
|
||||||
@ -563,7 +556,7 @@ const useVideoTrimmer = () => {
|
|||||||
name: `Segment ${i + 1}`,
|
name: `Segment ${i + 1}`,
|
||||||
startTime,
|
startTime,
|
||||||
endTime,
|
endTime,
|
||||||
thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
|
thumbnail: '', // Empty placeholder - we'll use dynamic colors instead
|
||||||
});
|
});
|
||||||
|
|
||||||
startTime = endTime;
|
startTime = endTime;
|
||||||
@ -571,7 +564,7 @@ const useVideoTrimmer = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setClipSegments(newSegments);
|
setClipSegments(newSegments);
|
||||||
saveState("create_split_points");
|
saveState('create_split_points');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -587,14 +580,14 @@ const useVideoTrimmer = () => {
|
|||||||
// No need to generate thumbnails - we'll use dynamic colors
|
// No need to generate thumbnails - we'll use dynamic colors
|
||||||
const defaultSegment: Segment = {
|
const defaultSegment: Segment = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
name: "segment",
|
name: 'segment',
|
||||||
startTime: 0,
|
startTime: 0,
|
||||||
endTime: duration,
|
endTime: duration,
|
||||||
thumbnail: "" // Empty placeholder - we'll use dynamic colors instead
|
thumbnail: '', // Empty placeholder - we'll use dynamic colors instead
|
||||||
};
|
};
|
||||||
|
|
||||||
setClipSegments([defaultSegment]);
|
setClipSegments([defaultSegment]);
|
||||||
saveState("reset_all");
|
saveState('reset_all');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle undo
|
// Handle undo
|
||||||
@ -607,7 +600,7 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Log segment details to help debug
|
// Log segment details to help debug
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Segment details after undo:",
|
'Segment details after undo:',
|
||||||
previousState.clipSegments.map(
|
previousState.clipSegments.map(
|
||||||
(seg) =>
|
(seg) =>
|
||||||
`ID: ${seg.id}, Time: ${formatDetailedTime(seg.startTime)} - ${formatDetailedTime(seg.endTime)}`
|
`ID: ${seg.id}, Time: ${formatDetailedTime(seg.startTime)} - ${formatDetailedTime(seg.endTime)}`
|
||||||
@ -621,7 +614,7 @@ const useVideoTrimmer = () => {
|
|||||||
setClipSegments(JSON.parse(JSON.stringify(previousState.clipSegments)));
|
setClipSegments(JSON.parse(JSON.stringify(previousState.clipSegments)));
|
||||||
setHistoryPosition(historyPosition - 1);
|
setHistoryPosition(historyPosition - 1);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Cannot undo: at earliest history position");
|
logger.debug('Cannot undo: at earliest history position');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -635,7 +628,7 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Log segment details to help debug
|
// Log segment details to help debug
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Segment details after redo:",
|
'Segment details after redo:',
|
||||||
nextState.clipSegments.map(
|
nextState.clipSegments.map(
|
||||||
(seg) =>
|
(seg) =>
|
||||||
`ID: ${seg.id}, Time: ${formatDetailedTime(seg.startTime)} - ${formatDetailedTime(seg.endTime)}`
|
`ID: ${seg.id}, Time: ${formatDetailedTime(seg.startTime)} - ${formatDetailedTime(seg.endTime)}`
|
||||||
@ -649,7 +642,7 @@ const useVideoTrimmer = () => {
|
|||||||
setClipSegments(JSON.parse(JSON.stringify(nextState.clipSegments)));
|
setClipSegments(JSON.parse(JSON.stringify(nextState.clipSegments)));
|
||||||
setHistoryPosition(historyPosition + 1);
|
setHistoryPosition(historyPosition + 1);
|
||||||
} else {
|
} else {
|
||||||
logger.debug("Cannot redo: at latest history position");
|
logger.debug('Cannot redo: at latest history position');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -669,10 +662,10 @@ const useVideoTrimmer = () => {
|
|||||||
setIsPlaying(false);
|
setIsPlaying(false);
|
||||||
} else {
|
} else {
|
||||||
// iOS Safari fix: Check for lastSeekedPosition
|
// iOS Safari fix: Check for lastSeekedPosition
|
||||||
if (typeof window !== "undefined" && window.lastSeekedPosition > 0) {
|
if (typeof window !== 'undefined' && window.lastSeekedPosition > 0) {
|
||||||
// Only seek if the position is significantly different
|
// Only seek if the position is significantly different
|
||||||
if (Math.abs(video.currentTime - window.lastSeekedPosition) > 0.1) {
|
if (Math.abs(video.currentTime - window.lastSeekedPosition) > 0.1) {
|
||||||
console.log("handlePlay: Using lastSeekedPosition", window.lastSeekedPosition);
|
console.log('handlePlay: Using lastSeekedPosition', window.lastSeekedPosition);
|
||||||
video.currentTime = window.lastSeekedPosition;
|
video.currentTime = window.lastSeekedPosition;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -683,12 +676,12 @@ const useVideoTrimmer = () => {
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
setIsPlaying(true);
|
setIsPlaying(true);
|
||||||
// Reset lastSeekedPosition after successful play
|
// Reset lastSeekedPosition after successful play
|
||||||
if (typeof window !== "undefined") {
|
if (typeof window !== 'undefined') {
|
||||||
window.lastSeekedPosition = 0;
|
window.lastSeekedPosition = 0;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
console.error("Error playing video:", err);
|
console.error('Error playing video:', err);
|
||||||
setIsPlaying(false); // Reset state if play failed
|
setIsPlaying(false); // Reset state if play failed
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -710,28 +703,28 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Create the JSON data for saving
|
// Create the JSON data for saving
|
||||||
const saveData = {
|
const saveData = {
|
||||||
type: "save",
|
type: 'save',
|
||||||
segments: sortedSegments.map((segment) => ({
|
segments: sortedSegments.map((segment) => ({
|
||||||
startTime: formatDetailedTime(segment.startTime),
|
startTime: formatDetailedTime(segment.startTime),
|
||||||
endTime: formatDetailedTime(segment.endTime)
|
endTime: formatDetailedTime(segment.endTime),
|
||||||
}))
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Display JSON in alert (for demonstration purposes)
|
// Display JSON in alert (for demonstration purposes)
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.debug("Saving data:", saveData);
|
console.debug('Saving data:', saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark as saved - no unsaved changes
|
// Mark as saved - no unsaved changes
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
|
|
||||||
// Debug message
|
// Debug message
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.debug("Changes saved - reset unsaved changes flag");
|
console.debug('Changes saved - reset unsaved changes flag');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to history with special "save" action to mark saved state
|
// Save to history with special "save" action to mark saved state
|
||||||
saveState("save");
|
saveState('save');
|
||||||
|
|
||||||
// In a real implementation, this would make a POST request to save the data
|
// In a real implementation, this would make a POST request to save the data
|
||||||
// logger.debug("Save data:", saveData);
|
// logger.debug("Save data:", saveData);
|
||||||
@ -744,28 +737,28 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Create the JSON data for saving as a copy
|
// Create the JSON data for saving as a copy
|
||||||
const saveData = {
|
const saveData = {
|
||||||
type: "save_as_a_copy",
|
type: 'save_as_a_copy',
|
||||||
segments: sortedSegments.map((segment) => ({
|
segments: sortedSegments.map((segment) => ({
|
||||||
startTime: formatDetailedTime(segment.startTime),
|
startTime: formatDetailedTime(segment.startTime),
|
||||||
endTime: formatDetailedTime(segment.endTime)
|
endTime: formatDetailedTime(segment.endTime),
|
||||||
}))
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Display JSON in alert (for demonstration purposes)
|
// Display JSON in alert (for demonstration purposes)
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.debug("Saving data as copy:", saveData);
|
console.debug('Saving data as copy:', saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark as saved - no unsaved changes
|
// Mark as saved - no unsaved changes
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
|
|
||||||
// Debug message
|
// Debug message
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.debug("Changes saved as copy - reset unsaved changes flag");
|
console.debug('Changes saved as copy - reset unsaved changes flag');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save to history with special "save_copy" action to mark saved state
|
// Save to history with special "save_copy" action to mark saved state
|
||||||
saveState("save_copy");
|
saveState('save_copy');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle save segments individually action
|
// Handle save segments individually action
|
||||||
@ -775,27 +768,27 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Create the JSON data for saving individual segments
|
// Create the JSON data for saving individual segments
|
||||||
const saveData = {
|
const saveData = {
|
||||||
type: "save_segments",
|
type: 'save_segments',
|
||||||
segments: sortedSegments.map((segment) => ({
|
segments: sortedSegments.map((segment) => ({
|
||||||
name: segment.name,
|
name: segment.name,
|
||||||
startTime: formatDetailedTime(segment.startTime),
|
startTime: formatDetailedTime(segment.startTime),
|
||||||
endTime: formatDetailedTime(segment.endTime)
|
endTime: formatDetailedTime(segment.endTime),
|
||||||
}))
|
})),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Display JSON in alert (for demonstration purposes)
|
// Display JSON in alert (for demonstration purposes)
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.debug("Saving data as segments:", saveData);
|
console.debug('Saving data as segments:', saveData);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mark as saved - no unsaved changes
|
// Mark as saved - no unsaved changes
|
||||||
setHasUnsavedChanges(false);
|
setHasUnsavedChanges(false);
|
||||||
|
|
||||||
// Debug message
|
// Debug message
|
||||||
logger.debug("All segments saved individually - reset unsaved changes flag");
|
logger.debug('All segments saved individually - reset unsaved changes flag');
|
||||||
|
|
||||||
// Save to history with special "save_segments" action to mark saved state
|
// Save to history with special "save_segments" action to mark saved state
|
||||||
saveState("save_segments");
|
saveState('save_segments');
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handle seeking with mobile check
|
// Handle seeking with mobile check
|
||||||
@ -808,10 +801,8 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Check if device is mobile
|
// Check if device is mobile
|
||||||
const isMobile =
|
const isMobile =
|
||||||
typeof window !== "undefined" &&
|
typeof window !== 'undefined' &&
|
||||||
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(
|
/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini|Mobile|mobile|CriOS/i.test(navigator.userAgent);
|
||||||
navigator.userAgent
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add videoInitialized state
|
// Add videoInitialized state
|
||||||
const [videoInitialized, setVideoInitialized] = useState(false);
|
const [videoInitialized, setVideoInitialized] = useState(false);
|
||||||
@ -845,9 +836,9 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// If video is somehow paused, ensure it keeps playing
|
// If video is somehow paused, ensure it keeps playing
|
||||||
if (video.paused) {
|
if (video.paused) {
|
||||||
logger.debug("Ensuring playback continues to next segment");
|
logger.debug('Ensuring playback continues to next segment');
|
||||||
video.play().catch((err) => {
|
video.play().catch((err) => {
|
||||||
console.error("Error continuing segment playback:", err);
|
console.error('Error continuing segment playback:', err);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -855,12 +846,12 @@ const useVideoTrimmer = () => {
|
|||||||
video.pause();
|
video.pause();
|
||||||
setIsPlayingSegments(false);
|
setIsPlayingSegments(false);
|
||||||
setCurrentSegmentIndex(0);
|
setCurrentSegmentIndex(0);
|
||||||
video.removeEventListener("timeupdate", handleSegmentsPlayback);
|
video.removeEventListener('timeupdate', handleSegmentsPlayback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
video.addEventListener("timeupdate", handleSegmentsPlayback);
|
video.addEventListener('timeupdate', handleSegmentsPlayback);
|
||||||
|
|
||||||
// Start playing if not already playing
|
// Start playing if not already playing
|
||||||
if (video.paused && orderedSegments.length > 0) {
|
if (video.paused && orderedSegments.length > 0) {
|
||||||
@ -869,7 +860,7 @@ const useVideoTrimmer = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
video.removeEventListener("timeupdate", handleSegmentsPlayback);
|
video.removeEventListener('timeupdate', handleSegmentsPlayback);
|
||||||
};
|
};
|
||||||
}, [isPlayingSegments, currentSegmentIndex, clipSegments]);
|
}, [isPlayingSegments, currentSegmentIndex, clipSegments]);
|
||||||
|
|
||||||
@ -878,20 +869,15 @@ const useVideoTrimmer = () => {
|
|||||||
const handleSegmentIndexUpdate = (event: CustomEvent) => {
|
const handleSegmentIndexUpdate = (event: CustomEvent) => {
|
||||||
const { segmentIndex } = event.detail;
|
const { segmentIndex } = event.detail;
|
||||||
if (isPlayingSegments && segmentIndex !== currentSegmentIndex) {
|
if (isPlayingSegments && segmentIndex !== currentSegmentIndex) {
|
||||||
logger.debug(
|
logger.debug(`Updating current segment index from ${currentSegmentIndex} to ${segmentIndex}`);
|
||||||
`Updating current segment index from ${currentSegmentIndex} to ${segmentIndex}`
|
|
||||||
);
|
|
||||||
setCurrentSegmentIndex(segmentIndex);
|
setCurrentSegmentIndex(segmentIndex);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
document.addEventListener("update-segment-index", handleSegmentIndexUpdate as EventListener);
|
document.addEventListener('update-segment-index', handleSegmentIndexUpdate as EventListener);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
document.removeEventListener(
|
document.removeEventListener('update-segment-index', handleSegmentIndexUpdate as EventListener);
|
||||||
"update-segment-index",
|
|
||||||
handleSegmentIndexUpdate as EventListener
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
}, [isPlayingSegments, currentSegmentIndex]);
|
}, [isPlayingSegments, currentSegmentIndex]);
|
||||||
|
|
||||||
@ -920,11 +906,11 @@ const useVideoTrimmer = () => {
|
|||||||
|
|
||||||
// Start playback with proper error handling
|
// Start playback with proper error handling
|
||||||
video.play().catch((err) => {
|
video.play().catch((err) => {
|
||||||
console.error("Error starting segments playback:", err);
|
console.error('Error starting segments playback:', err);
|
||||||
setIsPlayingSegments(false);
|
setIsPlayingSegments(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.debug("Starting playback of all segments continuously");
|
logger.debug('Starting playback of all segments continuously');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -960,7 +946,7 @@ const useVideoTrimmer = () => {
|
|||||||
handleSaveSegments,
|
handleSaveSegments,
|
||||||
isMobile,
|
isMobile,
|
||||||
videoInitialized,
|
videoInitialized,
|
||||||
setVideoInitialized
|
setVideoInitialized,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,7 +7,7 @@ const logger = {
|
|||||||
* Logs debug messages only in development environment
|
* Logs debug messages only in development environment
|
||||||
*/
|
*/
|
||||||
debug: (...args: any[]) => {
|
debug: (...args: any[]) => {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === 'development') {
|
||||||
console.debug(...args);
|
console.debug(...args);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -25,7 +25,7 @@ const logger = {
|
|||||||
/**
|
/**
|
||||||
* Always logs info messages
|
* Always logs info messages
|
||||||
*/
|
*/
|
||||||
info: (...args: any[]) => console.info(...args)
|
info: (...args: any[]) => console.info(...args),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default logger;
|
export default logger;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { QueryClient, QueryFunction } from "@tanstack/react-query";
|
import { QueryClient, QueryFunction } from '@tanstack/react-query';
|
||||||
|
|
||||||
async function throwIfResNotOk(res: Response) {
|
async function throwIfResNotOk(res: Response) {
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
@ -7,31 +7,27 @@ async function throwIfResNotOk(res: Response) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function apiRequest(
|
export async function apiRequest(method: string, url: string, data?: unknown | undefined): Promise<Response> {
|
||||||
method: string,
|
|
||||||
url: string,
|
|
||||||
data?: unknown | undefined
|
|
||||||
): Promise<Response> {
|
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method,
|
method,
|
||||||
headers: data ? { "Content-Type": "application/json" } : {},
|
headers: data ? { 'Content-Type': 'application/json' } : {},
|
||||||
body: data ? JSON.stringify(data) : undefined,
|
body: data ? JSON.stringify(data) : undefined,
|
||||||
credentials: "include"
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
await throwIfResNotOk(res);
|
await throwIfResNotOk(res);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
type UnauthorizedBehavior = "returnNull" | "throw";
|
type UnauthorizedBehavior = 'returnNull' | 'throw';
|
||||||
export const getQueryFn: <T>(options: { on401: UnauthorizedBehavior }) => QueryFunction<T> =
|
export const getQueryFn: <T>(options: { on401: UnauthorizedBehavior }) => QueryFunction<T> =
|
||||||
({ on401: unauthorizedBehavior }) =>
|
({ on401: unauthorizedBehavior }) =>
|
||||||
async ({ queryKey }) => {
|
async ({ queryKey }) => {
|
||||||
const res = await fetch(queryKey[0] as string, {
|
const res = await fetch(queryKey[0] as string, {
|
||||||
credentials: "include"
|
credentials: 'include',
|
||||||
});
|
});
|
||||||
|
|
||||||
if (unauthorizedBehavior === "returnNull" && res.status === 401) {
|
if (unauthorizedBehavior === 'returnNull' && res.status === 401) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,14 +38,14 @@ export const getQueryFn: <T>(options: { on401: UnauthorizedBehavior }) => QueryF
|
|||||||
export const queryClient = new QueryClient({
|
export const queryClient = new QueryClient({
|
||||||
defaultOptions: {
|
defaultOptions: {
|
||||||
queries: {
|
queries: {
|
||||||
queryFn: getQueryFn({ on401: "throw" }),
|
queryFn: getQueryFn({ on401: 'throw' }),
|
||||||
refetchInterval: false,
|
refetchInterval: false,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
retry: false
|
retry: false,
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
retry: false
|
retry: false,
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -2,17 +2,17 @@
|
|||||||
* Format seconds to HH:MM:SS.mmm format with millisecond precision
|
* Format seconds to HH:MM:SS.mmm format with millisecond precision
|
||||||
*/
|
*/
|
||||||
export const formatDetailedTime = (seconds: number): string => {
|
export const formatDetailedTime = (seconds: number): string => {
|
||||||
if (isNaN(seconds)) return "00:00:00.000";
|
if (isNaN(seconds)) return '00:00:00.000';
|
||||||
|
|
||||||
const hours = Math.floor(seconds / 3600);
|
const hours = Math.floor(seconds / 3600);
|
||||||
const minutes = Math.floor((seconds % 3600) / 60);
|
const minutes = Math.floor((seconds % 3600) / 60);
|
||||||
const remainingSeconds = Math.floor(seconds % 60);
|
const remainingSeconds = Math.floor(seconds % 60);
|
||||||
const milliseconds = Math.round((seconds % 1) * 1000);
|
const milliseconds = Math.round((seconds % 1) * 1000);
|
||||||
|
|
||||||
const formattedHours = String(hours).padStart(2, "0");
|
const formattedHours = String(hours).padStart(2, '0');
|
||||||
const formattedMinutes = String(minutes).padStart(2, "0");
|
const formattedMinutes = String(minutes).padStart(2, '0');
|
||||||
const formattedSeconds = String(remainingSeconds).padStart(2, "0");
|
const formattedSeconds = String(remainingSeconds).padStart(2, '0');
|
||||||
const formattedMilliseconds = String(milliseconds).padStart(3, "0");
|
const formattedMilliseconds = String(milliseconds).padStart(3, '0');
|
||||||
|
|
||||||
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}.${formattedMilliseconds}`;
|
return `${formattedHours}:${formattedMinutes}:${formattedSeconds}.${formattedMilliseconds}`;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { clsx, type ClassValue } from "clsx";
|
import { clsx, type ClassValue } from 'clsx';
|
||||||
import { twMerge } from "tailwind-merge";
|
import { twMerge } from 'tailwind-merge';
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
export function cn(...inputs: ClassValue[]) {
|
||||||
return twMerge(clsx(inputs));
|
return twMerge(clsx(inputs));
|
||||||
|
|||||||
@ -20,17 +20,14 @@ export const generateSolidColor = (time: number, duration: number): string => {
|
|||||||
* Legacy function kept for compatibility
|
* Legacy function kept for compatibility
|
||||||
* Now returns a data URL for a solid color square instead of a video thumbnail
|
* Now returns a data URL for a solid color square instead of a video thumbnail
|
||||||
*/
|
*/
|
||||||
export const generateThumbnail = async (
|
export const generateThumbnail = async (videoElement: HTMLVideoElement, time: number): Promise<string> => {
|
||||||
videoElement: HTMLVideoElement,
|
|
||||||
time: number
|
|
||||||
): Promise<string> => {
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
// Create a small canvas for the solid color
|
// Create a small canvas for the solid color
|
||||||
const canvas = document.createElement("canvas");
|
const canvas = document.createElement('canvas');
|
||||||
canvas.width = 10; // Much smaller - we only need a color
|
canvas.width = 10; // Much smaller - we only need a color
|
||||||
canvas.height = 10;
|
canvas.height = 10;
|
||||||
|
|
||||||
const ctx = canvas.getContext("2d");
|
const ctx = canvas.getContext('2d');
|
||||||
if (ctx) {
|
if (ctx) {
|
||||||
// Get the solid color based on time
|
// Get the solid color based on time
|
||||||
const color = generateSolidColor(time, videoElement.duration);
|
const color = generateSolidColor(time, videoElement.duration);
|
||||||
@ -41,7 +38,7 @@ export const generateThumbnail = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Convert to data URL (much smaller now)
|
// Convert to data URL (much smaller now)
|
||||||
const dataUrl = canvas.toDataURL("image/png", 0.5);
|
const dataUrl = canvas.toDataURL('image/png', 0.5);
|
||||||
resolve(dataUrl);
|
resolve(dataUrl);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@ -22,16 +22,13 @@ const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|||||||
|
|
||||||
// For now, we'll use a mock API that returns a promise
|
// For now, we'll use a mock API that returns a promise
|
||||||
// This can be replaced with actual API calls later
|
// This can be replaced with actual API calls later
|
||||||
export const trimVideo = async (
|
export const trimVideo = async (mediaId: string, data: TrimVideoRequest): Promise<TrimVideoResponse> => {
|
||||||
mediaId: string,
|
|
||||||
data: TrimVideoRequest
|
|
||||||
): Promise<TrimVideoResponse> => {
|
|
||||||
try {
|
try {
|
||||||
// Attempt the real API call
|
// Attempt the real API call
|
||||||
const response = await fetch(`/api/v1/media/${mediaId}/trim_video`, {
|
const response = await fetch(`/api/v1/media/${mediaId}/trim_video`, {
|
||||||
method: "POST",
|
method: 'POST',
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(data)
|
body: JSON.stringify(data),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
@ -43,17 +40,17 @@ export const trimVideo = async (
|
|||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
return {
|
return {
|
||||||
status: 400,
|
status: 400,
|
||||||
error: errorData.error || "An error occurred during processing",
|
error: errorData.error || 'An error occurred during processing',
|
||||||
msg: "Video Processing Error",
|
msg: 'Video Processing Error',
|
||||||
url_redirect: ""
|
url_redirect: '',
|
||||||
};
|
};
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
// If can't parse response JSON, return generic error
|
// If can't parse response JSON, return generic error
|
||||||
return {
|
return {
|
||||||
status: 400,
|
status: 400,
|
||||||
error: "An error occurred during video processing",
|
error: 'An error occurred during video processing',
|
||||||
msg: "Video Processing Error",
|
msg: 'Video Processing Error',
|
||||||
url_redirect: ""
|
url_redirect: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else if (response.status !== 404) {
|
} else if (response.status !== 404) {
|
||||||
@ -63,17 +60,17 @@ export const trimVideo = async (
|
|||||||
const errorData = await response.json();
|
const errorData = await response.json();
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
error: errorData.error || "An error occurred during processing",
|
error: errorData.error || 'An error occurred during processing',
|
||||||
msg: "Video Processing Error",
|
msg: 'Video Processing Error',
|
||||||
url_redirect: ""
|
url_redirect: '',
|
||||||
};
|
};
|
||||||
} catch (parseError) {
|
} catch (parseError) {
|
||||||
// If can't parse response JSON, return generic error
|
// If can't parse response JSON, return generic error
|
||||||
return {
|
return {
|
||||||
status: response.status,
|
status: response.status,
|
||||||
error: "An error occurred during video processing",
|
error: 'An error occurred during video processing',
|
||||||
msg: "Video Processing Error",
|
msg: 'Video Processing Error',
|
||||||
url_redirect: ""
|
url_redirect: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -81,8 +78,8 @@ export const trimVideo = async (
|
|||||||
await delay(1500); // Simulate 1.5 second server delay
|
await delay(1500); // Simulate 1.5 second server delay
|
||||||
return {
|
return {
|
||||||
status: 200, // Mock success status
|
status: 200, // Mock success status
|
||||||
msg: "Video Processed Successfully", // Updated per requirements
|
msg: 'Video Processed Successfully', // Updated per requirements
|
||||||
url_redirect: `./view?m=${mediaId}`
|
url_redirect: `./view?m=${mediaId}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -91,17 +88,17 @@ export const trimVideo = async (
|
|||||||
const jsonResponse = await response.json();
|
const jsonResponse = await response.json();
|
||||||
return {
|
return {
|
||||||
status: 200,
|
status: 200,
|
||||||
msg: "Video Processed Successfully", // Ensure the success message is correct
|
msg: 'Video Processed Successfully', // Ensure the success message is correct
|
||||||
url_redirect: jsonResponse.url_redirect || `./view?m=${mediaId}`,
|
url_redirect: jsonResponse.url_redirect || `./view?m=${mediaId}`,
|
||||||
...jsonResponse
|
...jsonResponse,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// For any fetch errors, return mock success response with delay
|
// For any fetch errors, return mock success response with delay
|
||||||
await delay(1500); // Simulate 1.5 second server delay
|
await delay(1500); // Simulate 1.5 second server delay
|
||||||
return {
|
return {
|
||||||
status: 200, // Mock success status
|
status: 200, // Mock success status
|
||||||
msg: "Video Processed Successfully", // Consistent with requirements
|
msg: 'Video Processed Successfully', // Consistent with requirements
|
||||||
url_redirect: `./view?m=${mediaId}`
|
url_redirect: `./view?m=${mediaId}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
frontend/package-lock.json
generated
12
frontend/package-lock.json
generated
@ -3788,9 +3788,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/aproba": {
|
"node_modules/aproba": {
|
||||||
"version": "2.0.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz",
|
||||||
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
|
"integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
@ -12590,9 +12590,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nan": {
|
"node_modules/nan": {
|
||||||
"version": "2.22.2",
|
"version": "2.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/nan/-/nan-2.22.2.tgz",
|
"resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz",
|
||||||
"integrity": "sha512-DANghxFkS1plDdRsX0X9pm0Z6SJNN6gBdtXfanwoZ8hooC5gosGFSBGRYHUVPz1asKA/kMRqDRdHrluZ61SpBQ==",
|
"integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==",
|
||||||
"devOptional": true,
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|||||||
5595
frontend/yarn.lock
5595
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user