From 7284ec61fdd09cd2dbe731adff4e136da7f686d2 Mon Sep 17 00:00:00 2001 From: William Candillon Date: Tue, 28 May 2024 16:21:18 +0200 Subject: [PATCH] =?UTF-8?q?fix(=F0=9F=93=BC):=20add=20seek=20playback=20op?= =?UTF-8?q?tion=20(#2448)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/docs/video.md | 71 +++++++++++++++++++-- package/src/external/reanimated/useVideo.ts | 15 +++++ 2 files changed, 82 insertions(+), 4 deletions(-) diff --git a/docs/docs/video.md b/docs/docs/video.md index fe4f9baabe..43ea4d5995 100644 --- a/docs/docs/video.md +++ b/docs/docs/video.md @@ -5,8 +5,7 @@ sidebar_label: Video slug: /video --- -React Native Skia provides a way to load video frames as images, enabling rich multimedia experiences within your applications. -A video frame can be used anywhere a Skia image is accepted: `Image`, `ImageShader`, and `Atlas`. +React Native Skia provides a way to load video frames as images, enabling rich multimedia experiences within your applications. A video frame can be used anywhere a Skia image is accepted: `Image`, `ImageShader`, and `Atlas`. ## Requirements @@ -35,7 +34,7 @@ interface VideoExampleProps { localVideoFile: string; } -// The URL needs to be a local path, we usually use expo-asset for that. +// The URL needs to be a local path; we usually use expo-asset for that. export const VideoExample = ({ localVideoFile }: VideoExampleProps) => { const paused = useSharedValue(false); const { width, height } = useWindowDimensions(); @@ -75,7 +74,7 @@ export const VideoExample = ({ localVideoFile }: VideoExampleProps) => { ## Using expo-asset -Below is an example where we use [expo-asset](https://docs.expo.dev/versions/latest/sdk/asset/) to load the video. +Below is an example of how to use [expo-asset](https://docs.expo.dev/versions/latest/sdk/asset/) to load the video. ```tsx twoslash import { useVideo } from "@shopify/react-native-skia"; @@ -93,4 +92,68 @@ export const useVideoFromAsset = ( } return useVideo(assets ? assets[0].localUri : null, options); }; +``` + +## Playback Options + +You can seek a video via the `seek` playback option. By default, the seek option is null. If you set a value in milliseconds, it will seek to that point in the video and then set the option value to null again. + +There is also the `currentTime` option, which is a Reanimated value that contains the current playback time of the video. + +`looping` indicates whether the video should be looped or not. + +`playbackSpeed` indicates the playback speed of the video (default is 1). + +In the example below, every time we tap on the video, we set the video to 2 seconds. + +```tsx twoslash +import React from "react"; +import { + Canvas, + Fill, + Image, + useVideo +} from "@shopify/react-native-skia"; +import { Pressable, useWindowDimensions } from "react-native"; +import { useSharedValue } from "react-native-reanimated"; + +interface VideoExampleProps { + localVideoFile: string; +} + +export const VideoExample = ({ localVideoFile }: VideoExampleProps) => { + const seek = useSharedValue(null); + // Set this value to true to pause the video + const paused = useSharedValue(false); + // Contains the current playback time of the video + const currentTime = useSharedValue(0); + const { width, height } = useWindowDimensions(); + const video = useVideo( + require(localVideoFile), + { + seek, + paused, + currentTime, + looping: true, + playbackSpeed: 1 + } + ); + return ( + (seek.value = 2000)} + > + + + + + ); +}; ``` \ No newline at end of file diff --git a/package/src/external/reanimated/useVideo.ts b/package/src/external/reanimated/useVideo.ts index 30c672ca92..05c923a225 100644 --- a/package/src/external/reanimated/useVideo.ts +++ b/package/src/external/reanimated/useVideo.ts @@ -18,12 +18,16 @@ export interface PlaybackOptions { playbackSpeed: Animated; looping: Animated; paused: Animated; + seek: Animated; + currentTime: Animated; } const defaultOptions = { playbackSpeed: 1, looping: true, paused: false, + seek: null, + currentTime: 0, }; const useOption = (value: Animated) => { @@ -42,6 +46,10 @@ export const useVideo = ( const video = useMemo(() => (source ? Skia.Video(source) : null), [source]); const isPaused = useOption(userOptions?.paused ?? defaultOptions.paused); const looping = useOption(userOptions?.looping ?? defaultOptions.looping); + const seek = useOption(userOptions?.seek ?? defaultOptions.seek); + const currentTime = useOption( + userOptions?.currentTime ?? defaultOptions.currentTime + ); const playbackSpeed = useOption( userOptions?.playbackSpeed ?? defaultOptions.playbackSpeed ); @@ -64,6 +72,12 @@ export const useVideo = ( if (!video) { return; } + if (seek.value !== null) { + video.seek(seek.value); + seek.value = null; + lastTimestamp.value = -1; + startTimestamp.value = -1; + } if (isPaused.value && lastTimestamp.value !== -1) { return; } @@ -76,6 +90,7 @@ export const useVideo = ( // Calculate the current time in the video const currentTimestamp = timestamp - startTimestamp.value; + currentTime.value = currentTimestamp; // Handle looping if (currentTimestamp > duration && looping.value) {