Skip to content

Commit df5162e

Browse files
authored
fix(📚): Improve documentation on useVideo (#2463)
1 parent 04ddb02 commit df5162e

File tree

2 files changed

+88
-116
lines changed

2 files changed

+88
-116
lines changed

‎docs/docs/video.md

+82-91
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ slug: /video
77

88
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`.
99

10-
## Requirements
10+
### Requirements
1111

1212
- **Reanimated** version 3 or higher.
1313
- **Android:** API level 26 or higher.
14-
- **Video URL:** Must be a local path. We recommend using it in combination with [expo-asset](https://docs.expo.dev/versions/latest/sdk/asset/) to download the video.
1514

1615
## Example
1716

1817
Here is an example of how to use the video support in React Native Skia. This example demonstrates how to load and display video frames within a canvas, applying a color matrix for visual effects. Tapping the screen will pause and play the video.
1918

19+
The video can be a remote (`http://...`) or local URL (`file://`), as well as a [video from the bundle](#using-assets).
20+
2021
```tsx twoslash
2122
import React from "react";
2223
import {
@@ -29,16 +30,11 @@ import {
2930
import { Pressable, useWindowDimensions } from "react-native";
3031
import { useSharedValue } from "react-native-reanimated";
3132

32-
interface VideoExampleProps {
33-
localVideoFile: string;
34-
}
35-
36-
// The URL needs to be a local path; we usually use expo-asset for that.
37-
export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
33+
export const VideoExample = () => {
3834
const paused = useSharedValue(false);
3935
const { width, height } = useWindowDimensions();
4036
const { currentFrame } = useVideo(
41-
require(localVideoFile),
37+
"https://bit.ly/skia-video",
4238
{
4339
paused,
4440
}
@@ -71,36 +67,70 @@ export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
7167
};
7268
```
7369

74-
## Using expo-asset
70+
## Returned Values
71+
72+
The `useVideo` hook returns `currentFrame`, which contains the current video frame, as well as `currentTime`, `rotation`, and `size`.
73+
74+
## Playback Options
75+
76+
The following table describes the playback options available for the `useVideo` hook:
77+
78+
| Option | Description |
79+
|---------------|----------------------------------------------------------------------------------------------|
80+
| `seek` | Allows seeking to a specific point in the video in milliseconds. Default is `null`. |
81+
| `paused` | Indicates whether the video is paused. |
82+
| `looping` | Indicates whether the video should loop. |
83+
| `volume` | A value from 0 to 1 representing the volume level (0 is muted, 1 is the maximum volume). |
7584

76-
Below is an example of how to use [expo-asset](https://docs.expo.dev/versions/latest/sdk/asset/) to load the video.
85+
In the example below, every time we tap on the video, we set the video seek at 2 seconds.
7786

7887
```tsx twoslash
79-
import { useVideo } from "@shopify/react-native-skia";
80-
import { useAssets } from "expo-asset";
88+
import React from "react";
89+
import {
90+
Canvas,
91+
Fill,
92+
Image,
93+
useVideo
94+
} from "@shopify/react-native-skia";
95+
import { Pressable, useWindowDimensions } from "react-native";
96+
import { useSharedValue } from "react-native-reanimated";
8197

82-
// Example usage:
83-
// const video = useVideoFromAsset(require("./BigBuckBunny.mp4"));
84-
export const useVideoFromAsset = (
85-
mod: number,
86-
options?: Parameters<typeof useVideo>[1]
87-
) => {
88-
const [assets, error] = useAssets([mod]);
89-
if (error) {
90-
throw error;
91-
}
92-
return useVideo(assets ? assets[0].localUri : null, options);
98+
export const VideoExample = () => {
99+
const seek = useSharedValue<null | number>(null);
100+
// Set this value to true to pause the video
101+
const paused = useSharedValue(false);
102+
const { width, height } = useWindowDimensions();
103+
const {currentFrame, currentTime} = useVideo(
104+
"https://bit.ly/skia-video",
105+
{
106+
seek,
107+
paused,
108+
looping: true
109+
}
110+
);
111+
return (
112+
<Pressable
113+
style={{ flex: 1 }}
114+
onPress={() => (seek.value = 2000)}
115+
>
116+
<Canvas style={{ flex: 1 }}>
117+
<Image
118+
image={currentFrame}
119+
x={0}
120+
y={0}
121+
width={width}
122+
height={height}
123+
fit="cover"
124+
/>
125+
</Canvas>
126+
</Pressable>
127+
);
93128
};
94129
```
95130

96-
## Returned Values
97-
98-
The `useVideo` hook returns `currentFrame` which contains the current video frame, as well as `currentTime`, `rotation`, and `size`.
99-
100131
## Rotated Video
101132

102-
`rotation` can either be `0`, `90`, `180`, or `270`.
103-
We provide a `fitbox` function that can help rotating and scaling the video.
133+
The `rotation` property can be `0`, `90`, `180`, or `270`. We provide a `fitbox` function that can help with rotating and scaling the video.
104134

105135
```tsx twoslash
106136
import React from "react";
@@ -114,15 +144,10 @@ import {
114144
import { Pressable, useWindowDimensions } from "react-native";
115145
import { useSharedValue } from "react-native-reanimated";
116146

117-
interface VideoExampleProps {
118-
localVideoFile: string;
119-
}
120-
121-
// The URL needs to be a local path; we usually use expo-asset for that.
122-
export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
147+
export const VideoExample = () => {
123148
const paused = useSharedValue(false);
124149
const { width, height } = useWindowDimensions();
125-
const { currentFrame, rotation, size } = useVideo(require(localVideoFile));
150+
const { currentFrame, rotation, size } = useVideo("https://bit.ly/skia-video");
126151
const src = rect(0, 0, size.width, size.height);
127152
const dst = rect(0, 0, width, height)
128153
const transform = fitbox("cover", src, dst, rotation);
@@ -142,62 +167,28 @@ export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
142167
};
143168
```
144169

170+
## Using Assets
145171

146-
## Playback Options
147-
148-
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.
149-
150-
`looping` indicates whether the video should be looped or not.
151-
152-
`playbackSpeed` indicates the playback speed of the video (default is 1).
153-
154-
In the example below, every time we tap on the video, we set the video to 2 seconds.
172+
Below is an example where we use [expo-asset](https://docs.expo.dev/versions/latest/sdk/asset/) to load a video file from the bundle.
155173

156174
```tsx twoslash
157-
import React from "react";
158-
import {
159-
Canvas,
160-
Fill,
161-
Image,
162-
useVideo
163-
} from "@shopify/react-native-skia";
164-
import { Pressable, useWindowDimensions } from "react-native";
165-
import { useSharedValue } from "react-native-reanimated";
166-
167-
interface VideoExampleProps {
168-
localVideoFile: string;
169-
}
175+
import { useVideo } from "@shopify/react-native-skia";
176+
import { useAssets } from "expo-asset";
170177

171-
export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
172-
const seek = useSharedValue<null | number>(null);
173-
// Set this value to true to pause the video
174-
const paused = useSharedValue(false);
175-
const { width, height } = useWindowDimensions();
176-
const {currentFrame, currentTime} = useVideo(
177-
require(localVideoFile),
178-
{
179-
seek,
180-
paused,
181-
looping: true,
182-
playbackSpeed: 1
183-
}
184-
);
185-
return (
186-
<Pressable
187-
style={{ flex: 1 }}
188-
onPress={() => (seek.value = 2000)}
189-
>
190-
<Canvas style={{ flex: 1 }}>
191-
<Image
192-
image={currentFrame}
193-
x={0}
194-
y={0}
195-
width={width}
196-
height={height}
197-
fit="cover"
198-
/>
199-
</Canvas>
200-
</Pressable>
201-
);
178+
// Example usage:
179+
// const video = useVideoFromAsset(require("./BigBuckBunny.mp4"));
180+
export const useVideoFromAsset = (
181+
mod: number,
182+
options?: Parameters<typeof useVideo>[1]
183+
) => {
184+
const [assets, error] = useAssets([mod]);
185+
if (error) {
186+
throw error;
187+
}
188+
return useVideo(assets ? assets[0].localUri : null, options);
202189
};
203-
```
190+
```
191+
192+
## Video Encoding
193+
194+
To encode videos from Skia images, you can use ffmpeg or also look into [react-native-skia-video](https://github.com/AzzappApp/react-native-skia-video).

‎package/src/external/reanimated/useVideo.ts

+6-25
Original file line numberDiff line numberDiff line change
@@ -7,29 +7,16 @@ import { Platform } from "../../Platform";
77

88
import Rea from "./ReanimatedProxy";
99

10-
export type Animated<T> = SharedValue<T> | T;
11-
// TODO: Move to useVideo.ts
12-
export interface PlaybackOptions {
13-
playbackSpeed: Animated<number>;
10+
type Animated<T> = SharedValue<T> | T;
11+
12+
interface PlaybackOptions {
1413
looping: Animated<boolean>;
1514
paused: Animated<boolean>;
1615
seek: Animated<number | null>;
1716
volume: Animated<number>;
1817
}
1918

20-
type Materialized<T> = {
21-
[K in keyof T]: T[K] extends Animated<infer U> ? U : T[K];
22-
};
23-
24-
export type MaterializedPlaybackOptions = Materialized<
25-
Omit<PlaybackOptions, "seek">
26-
>;
27-
28-
// TODO: move
29-
export const setFrame = (
30-
video: Video,
31-
currentFrame: SharedValue<SkImage | null>
32-
) => {
19+
const setFrame = (video: Video, currentFrame: SharedValue<SkImage | null>) => {
3320
"worklet";
3421
const img = video.nextImage();
3522
if (img) {
@@ -45,7 +32,6 @@ export const setFrame = (
4532
};
4633

4734
const defaultOptions = {
48-
playbackSpeed: 1,
4935
looping: true,
5036
paused: false,
5137
seek: null,
@@ -76,16 +62,15 @@ export const useVideo = (
7662
const looping = useOption(userOptions?.looping ?? defaultOptions.looping);
7763
const seek = useOption(userOptions?.seek ?? defaultOptions.seek);
7864
const volume = useOption(userOptions?.volume ?? defaultOptions.volume);
79-
const playbackSpeed = useOption(
80-
userOptions?.playbackSpeed ?? defaultOptions.playbackSpeed
81-
);
8265
const currentFrame = Rea.useSharedValue<null | SkImage>(null);
8366
const currentTime = Rea.useSharedValue(0);
8467
const lastTimestamp = Rea.useSharedValue(-1);
8568
const duration = useMemo(() => video?.duration() ?? 0, [video]);
8669
const framerate = useMemo(() => video?.framerate() ?? 0, [video]);
8770
const size = useMemo(() => video?.size() ?? { width: 0, height: 0 }, [video]);
8871
const rotation = useMemo(() => video?.rotation() ?? 0, [video]);
72+
const frameDuration = 1000 / framerate;
73+
const currentFrameDuration = Math.floor(frameDuration);
8974
Rea.useAnimatedReaction(
9075
() => isPaused.value,
9176
(paused) => {
@@ -127,10 +112,6 @@ export const useVideo = (
127112
}
128113
const delta = currentTimestamp - lastTimestamp.value;
129114

130-
const frameDuration = 1000 / framerate;
131-
const currentFrameDuration = Math.floor(
132-
frameDuration / playbackSpeed.value
133-
);
134115
const isOver = currentTime.value + delta > duration;
135116
if (isOver && looping.value) {
136117
seek.value = 0;

0 commit comments

Comments
 (0)