fix playback on load
This commit is contained in:
@@ -57,14 +57,15 @@ export default function VideoEditor() {
|
||||
}, []);
|
||||
|
||||
function togglePlayPause() {
|
||||
const video = videoPlaybackRef.current?.video;
|
||||
const playback = videoPlaybackRef.current;
|
||||
const video = playback?.video;
|
||||
console.log('🎮 Toggle play/pause:', { hasVideo: !!video, isPlaying, action: isPlaying ? 'pause' : 'play' });
|
||||
if (!video) return;
|
||||
|
||||
if (!playback || !video) return;
|
||||
|
||||
if (isPlaying) {
|
||||
video.pause();
|
||||
playback.pause();
|
||||
} else {
|
||||
video.play().catch(err => console.error('❌ Video play failed:', err));
|
||||
playback.play().catch(err => console.error('❌ Video play failed:', err));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,8 @@ export interface VideoPlaybackRef {
|
||||
app: PIXI.Application | null;
|
||||
videoSprite: PIXI.Sprite | null;
|
||||
videoContainer: PIXI.Container | null;
|
||||
play: () => Promise<void>;
|
||||
pause: () => void;
|
||||
}
|
||||
|
||||
const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
||||
@@ -75,6 +77,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
||||
const maskGraphicsRef = useRef<PIXI.Graphics | null>(null);
|
||||
const isPlayingRef = useRef(isPlaying);
|
||||
const isSeekingRef = useRef(false);
|
||||
const allowPlaybackRef = useRef(false);
|
||||
|
||||
const clampFocusToStage = useCallback((focus: ZoomFocus, depth: ZoomDepth) => {
|
||||
return clampFocusToStageUtil(focus, depth, stageSizeRef.current);
|
||||
@@ -152,6 +155,28 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
||||
app: appRef.current,
|
||||
videoSprite: videoSpriteRef.current,
|
||||
videoContainer: videoContainerRef.current,
|
||||
play: async () => {
|
||||
const video = videoRef.current;
|
||||
if (!video) {
|
||||
allowPlaybackRef.current = false;
|
||||
return;
|
||||
}
|
||||
allowPlaybackRef.current = true;
|
||||
try {
|
||||
await video.play();
|
||||
} catch (error) {
|
||||
allowPlaybackRef.current = false;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
pause: () => {
|
||||
const video = videoRef.current;
|
||||
allowPlaybackRef.current = false;
|
||||
if (!video) {
|
||||
return;
|
||||
}
|
||||
video.pause();
|
||||
},
|
||||
}));
|
||||
|
||||
const updateFocusFromClientPoint = (clientX: number, clientY: number) => {
|
||||
@@ -330,6 +355,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
||||
if (!video) return;
|
||||
video.pause();
|
||||
video.currentTime = 0;
|
||||
allowPlaybackRef.current = false;
|
||||
}, [videoPath]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -343,6 +369,12 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
||||
if (video.videoWidth === 0 || video.videoHeight === 0) return;
|
||||
|
||||
const source = PIXI.VideoSource.from(video);
|
||||
if ('autoPlay' in source) {
|
||||
(source as { autoPlay?: boolean }).autoPlay = false;
|
||||
}
|
||||
if ('autoUpdate' in source) {
|
||||
(source as { autoUpdate?: boolean }).autoUpdate = true;
|
||||
}
|
||||
const videoTexture = PIXI.Texture.from(source);
|
||||
|
||||
const videoSprite = new PIXI.Sprite(videoTexture);
|
||||
@@ -374,6 +406,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
||||
video,
|
||||
isSeekingRef,
|
||||
isPlayingRef,
|
||||
allowPlaybackRef,
|
||||
currentTimeRef,
|
||||
timeUpdateAnimationRef,
|
||||
onPlayStateChange,
|
||||
@@ -524,6 +557,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(({
|
||||
onDurationChange(video.duration);
|
||||
video.currentTime = 0;
|
||||
video.pause();
|
||||
allowPlaybackRef.current = false;
|
||||
currentTimeRef.current = 0;
|
||||
setVideoReady(true);
|
||||
};
|
||||
|
||||
@@ -17,6 +17,7 @@ interface LayoutResult {
|
||||
baseScale: number;
|
||||
baseOffset: { x: number; y: number };
|
||||
maskRect: { x: number; y: number; width: number; height: number };
|
||||
cropOffset: { x: number; y: number };
|
||||
}
|
||||
|
||||
export function layoutVideoContent(params: LayoutParams): LayoutResult | null {
|
||||
@@ -90,10 +91,14 @@ export function layoutVideoContent(params: LayoutParams): LayoutResult | null {
|
||||
maskGraphics.fill({ color: 0xffffff });
|
||||
|
||||
return {
|
||||
stageSize: { width, height },
|
||||
stageSize: { width: croppedDisplayWidth, height: croppedDisplayHeight },
|
||||
videoSize: { width: croppedVideoWidth, height: croppedVideoHeight },
|
||||
baseScale: scale,
|
||||
baseOffset: { x: spriteX, y: spriteY },
|
||||
maskRect: { x: maskX, y: maskY, width: croppedDisplayWidth, height: croppedDisplayHeight },
|
||||
cropOffset: {
|
||||
x: crop.x * videoWidth,
|
||||
y: crop.y * videoHeight,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ interface VideoEventHandlersParams {
|
||||
video: HTMLVideoElement;
|
||||
isSeekingRef: React.MutableRefObject<boolean>;
|
||||
isPlayingRef: React.MutableRefObject<boolean>;
|
||||
allowPlaybackRef: React.MutableRefObject<boolean>;
|
||||
currentTimeRef: React.MutableRefObject<number>;
|
||||
timeUpdateAnimationRef: React.MutableRefObject<number | null>;
|
||||
onPlayStateChange: (playing: boolean) => void;
|
||||
@@ -13,6 +14,7 @@ export function createVideoEventHandlers(params: VideoEventHandlersParams) {
|
||||
video,
|
||||
isSeekingRef,
|
||||
isPlayingRef,
|
||||
allowPlaybackRef,
|
||||
currentTimeRef,
|
||||
timeUpdateAnimationRef,
|
||||
onPlayStateChange,
|
||||
@@ -38,12 +40,20 @@ export function createVideoEventHandlers(params: VideoEventHandlersParams) {
|
||||
video.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!allowPlaybackRef.current) {
|
||||
video.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
allowPlaybackRef.current = false;
|
||||
isPlayingRef.current = true;
|
||||
onPlayStateChange(true);
|
||||
updateTime();
|
||||
};
|
||||
|
||||
const handlePause = () => {
|
||||
allowPlaybackRef.current = false;
|
||||
isPlayingRef.current = false;
|
||||
if (timeUpdateAnimationRef.current) {
|
||||
cancelAnimationFrame(timeUpdateAnimationRef.current);
|
||||
|
||||
@@ -9,6 +9,7 @@ interface TransformParams {
|
||||
baseScale: number;
|
||||
baseOffset: { x: number; y: number };
|
||||
baseMask: { x: number; y: number; width: number; height: number };
|
||||
cropOffset: { x: number; y: number };
|
||||
zoomScale: number;
|
||||
focusX: number;
|
||||
focusY: number;
|
||||
@@ -26,6 +27,7 @@ export function applyZoomTransform(params: TransformParams) {
|
||||
baseScale,
|
||||
baseOffset,
|
||||
baseMask,
|
||||
cropOffset,
|
||||
zoomScale,
|
||||
focusX,
|
||||
focusY,
|
||||
@@ -45,10 +47,10 @@ export function applyZoomTransform(params: TransformParams) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusStagePxX = focusX * stageSize.width;
|
||||
const focusStagePxY = focusY * stageSize.height;
|
||||
const stageCenterX = stageSize.width / 2;
|
||||
const stageCenterY = stageSize.height / 2;
|
||||
const focusStagePxX = baseMask.x + focusX * stageSize.width;
|
||||
const focusStagePxY = baseMask.y + focusY * stageSize.height;
|
||||
const stageCenterX = baseMask.x + stageSize.width / 2;
|
||||
const stageCenterY = baseMask.y + stageSize.height / 2;
|
||||
|
||||
const actualScale = baseScale * zoomScale;
|
||||
videoSprite.scale.set(actualScale);
|
||||
@@ -62,8 +64,28 @@ export function applyZoomTransform(params: TransformParams) {
|
||||
const focusInVideoSpaceY = focusStagePxY - baseVideoY;
|
||||
|
||||
// Position formula: stageCenterX - focusInVideoSpace * zoomScale
|
||||
const newVideoX = stageCenterX - focusInVideoSpaceX * zoomScale;
|
||||
const newVideoY = stageCenterY - focusInVideoSpaceY * zoomScale;
|
||||
let newVideoX = stageCenterX - focusInVideoSpaceX * zoomScale;
|
||||
let newVideoY = stageCenterY - focusInVideoSpaceY * zoomScale;
|
||||
|
||||
const cropStartX = cropOffset.x;
|
||||
const cropStartY = cropOffset.y;
|
||||
const cropEndX = cropOffset.x + videoSize.width;
|
||||
const cropEndY = cropOffset.y + videoSize.height;
|
||||
const maskWidth = baseMask.width;
|
||||
const maskHeight = baseMask.height;
|
||||
|
||||
const minVideoX = baseMaskX + maskWidth - cropEndX * actualScale;
|
||||
const maxVideoX = baseMaskX - cropStartX * actualScale;
|
||||
const minVideoY = baseMaskY + maskHeight - cropEndY * actualScale;
|
||||
const maxVideoY = baseMaskY - cropStartY * actualScale;
|
||||
|
||||
if (minVideoX <= maxVideoX) {
|
||||
newVideoX = Math.max(minVideoX, Math.min(maxVideoX, newVideoX));
|
||||
}
|
||||
|
||||
if (minVideoY <= maxVideoY) {
|
||||
newVideoY = Math.max(minVideoY, Math.min(maxVideoY, newVideoY));
|
||||
}
|
||||
|
||||
videoSprite.position.set(newVideoX, newVideoY);
|
||||
|
||||
@@ -73,11 +95,10 @@ export function applyZoomTransform(params: TransformParams) {
|
||||
blurFilter.blur = motionBlur;
|
||||
}
|
||||
|
||||
const maskWidth = baseMask.width * zoomScale;
|
||||
const maskHeight = baseMask.height * zoomScale;
|
||||
const maskX = baseMaskX + (newVideoX - baseVideoX);
|
||||
const maskY = baseMaskY + (newVideoY - baseVideoY);
|
||||
const maskX = baseMaskX;
|
||||
const maskY = baseMaskY;
|
||||
const radius = Math.min(maskWidth, maskHeight) * 0.02;
|
||||
|
||||
maskGraphics.clear();
|
||||
maskGraphics.roundRect(maskX, maskY, maskWidth, maskHeight, radius);
|
||||
maskGraphics.fill({ color: 0xffffff });
|
||||
|
||||
Reference in New Issue
Block a user