Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(📚): Improve documentation on useVideo #2463

Merged
merged 35 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
f7f6e86
Add getSize on iOS
wcandillon Jun 4, 2024
1531b35
:wrench:
wcandillon Jun 5, 2024
99fe665
:wrench:
wcandillon Jun 5, 2024
7cb0988
:wrench:
wcandillon Jun 5, 2024
c16f64f
:wrench:
wcandillon Jun 5, 2024
ec4a47a
:wrench:
wcandillon Jun 5, 2024
ffaf65d
:wrench:
wcandillon Jun 5, 2024
9a04dcb
:wrench:
wcandillon Jun 5, 2024
0baafc4
Add utility to rotate/scale a video frame
wcandillon Jun 5, 2024
d2063d5
:books:
wcandillon Jun 5, 2024
d31c0a8
:green_heart:
wcandillon Jun 5, 2024
5f5b70f
:green_heart:
wcandillon Jun 5, 2024
f52718f
:wrench:
wcandillon Jun 5, 2024
72666a2
:wrench:
wcandillon Jun 5, 2024
99334fa
:wrench:
wcandillon Jun 6, 2024
96be66d
:wrench:
wcandillon Jun 6, 2024
503a583
:green_heart:
wcandillon Jun 6, 2024
054961c
:wrench:
wcandillon Jun 6, 2024
3c9582a
Add java impl
wcandillon Jun 6, 2024
609a303
:wrench:
wcandillon Jun 6, 2024
377846b
:wrench:
wcandillon Jun 6, 2024
dddf9c4
Merge branch 'rotation' into adio
wcandillon Jun 6, 2024
0944f3f
Update documentation
wcandillon Jun 6, 2024
a0ac4ca
:green_heart:
wcandillon Jun 6, 2024
626ebcb
Remove bogus change
wcandillon Jun 6, 2024
6718406
Merge branch 'main' into adio
wcandillon Jun 6, 2024
334e23f
remove bogus file
wcandillon Jun 6, 2024
c2d9128
Remove bogus files
wcandillon Jun 6, 2024
eda8d7e
:wrench:
wcandillon Jun 6, 2024
e91d087
Update docs
wcandillon Jun 6, 2024
985ab72
:wrench:
wcandillon Jun 6, 2024
5f0c526
:wrench:
wcandillon Jun 6, 2024
32de048
Merge branch 'main' into streaming
wcandillon Jun 6, 2024
310a073
:wrench:
wcandillon Jun 6, 2024
91d5ea5
:green_heart:
wcandillon Jun 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 82 additions & 91 deletions docs/docs/video.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,17 @@ 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`.

## Requirements
### Requirements

- **Reanimated** version 3 or higher.
- **Android:** API level 26 or higher.
- **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.

## Example

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.

The video can be a remote (`http://...`) or local URL (`file://`), as well as a [video from the bundle](#using-assets).

```tsx twoslash
import React from "react";
import {
Expand All @@ -29,16 +30,11 @@ import {
import { Pressable, useWindowDimensions } from "react-native";
import { useSharedValue } from "react-native-reanimated";

interface VideoExampleProps {
localVideoFile: string;
}

// The URL needs to be a local path; we usually use expo-asset for that.
export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
export const VideoExample = () => {
const paused = useSharedValue(false);
const { width, height } = useWindowDimensions();
const { currentFrame } = useVideo(
require(localVideoFile),
"https://bit.ly/skia-video",
{
paused,
}
Expand Down Expand Up @@ -71,36 +67,70 @@ export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
};
```

## Using expo-asset
## Returned Values

The `useVideo` hook returns `currentFrame`, which contains the current video frame, as well as `currentTime`, `rotation`, and `size`.

## Playback Options

The following table describes the playback options available for the `useVideo` hook:

| Option | Description |
|---------------|----------------------------------------------------------------------------------------------|
| `seek` | Allows seeking to a specific point in the video in milliseconds. Default is `null`. |
| `paused` | Indicates whether the video is paused. |
| `looping` | Indicates whether the video should loop. |
| `volume` | A value from 0 to 1 representing the volume level (0 is muted, 1 is the maximum volume). |

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

```tsx twoslash
import { useVideo } from "@shopify/react-native-skia";
import { useAssets } from "expo-asset";
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";

// Example usage:
// const video = useVideoFromAsset(require("./BigBuckBunny.mp4"));
export const useVideoFromAsset = (
mod: number,
options?: Parameters<typeof useVideo>[1]
) => {
const [assets, error] = useAssets([mod]);
if (error) {
throw error;
}
return useVideo(assets ? assets[0].localUri : null, options);
export const VideoExample = () => {
const seek = useSharedValue<null | number>(null);
// Set this value to true to pause the video
const paused = useSharedValue(false);
const { width, height } = useWindowDimensions();
const {currentFrame, currentTime} = useVideo(
"https://bit.ly/skia-video",
{
seek,
paused,
looping: true
}
);
return (
<Pressable
style={{ flex: 1 }}
onPress={() => (seek.value = 2000)}
>
<Canvas style={{ flex: 1 }}>
<Image
image={currentFrame}
x={0}
y={0}
width={width}
height={height}
fit="cover"
/>
</Canvas>
</Pressable>
);
};
```

## Returned Values

The `useVideo` hook returns `currentFrame` which contains the current video frame, as well as `currentTime`, `rotation`, and `size`.

## Rotated Video

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

```tsx twoslash
import React from "react";
Expand All @@ -114,15 +144,10 @@ import {
import { Pressable, useWindowDimensions } from "react-native";
import { useSharedValue } from "react-native-reanimated";

interface VideoExampleProps {
localVideoFile: string;
}

// The URL needs to be a local path; we usually use expo-asset for that.
export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
export const VideoExample = () => {
const paused = useSharedValue(false);
const { width, height } = useWindowDimensions();
const { currentFrame, rotation, size } = useVideo(require(localVideoFile));
const { currentFrame, rotation, size } = useVideo("https://bit.ly/skia-video");
const src = rect(0, 0, size.width, size.height);
const dst = rect(0, 0, width, height)
const transform = fitbox("cover", src, dst, rotation);
Expand All @@ -142,62 +167,28 @@ export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
};
```

## Using Assets

## 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.

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

```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;
}
import { useVideo } from "@shopify/react-native-skia";
import { useAssets } from "expo-asset";

export const VideoExample = ({ localVideoFile }: VideoExampleProps) => {
const seek = useSharedValue<null | number>(null);
// Set this value to true to pause the video
const paused = useSharedValue(false);
const { width, height } = useWindowDimensions();
const {currentFrame, currentTime} = useVideo(
require(localVideoFile),
{
seek,
paused,
looping: true,
playbackSpeed: 1
}
);
return (
<Pressable
style={{ flex: 1 }}
onPress={() => (seek.value = 2000)}
>
<Canvas style={{ flex: 1 }}>
<Image
image={currentFrame}
x={0}
y={0}
width={width}
height={height}
fit="cover"
/>
</Canvas>
</Pressable>
);
// Example usage:
// const video = useVideoFromAsset(require("./BigBuckBunny.mp4"));
export const useVideoFromAsset = (
mod: number,
options?: Parameters<typeof useVideo>[1]
) => {
const [assets, error] = useAssets([mod]);
if (error) {
throw error;
}
return useVideo(assets ? assets[0].localUri : null, options);
};
```
```

## Video Encoding

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).
31 changes: 6 additions & 25 deletions package/src/external/reanimated/useVideo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,29 +7,16 @@ import { Platform } from "../../Platform";

import Rea from "./ReanimatedProxy";

export type Animated<T> = SharedValue<T> | T;
// TODO: Move to useVideo.ts
export interface PlaybackOptions {
playbackSpeed: Animated<number>;
type Animated<T> = SharedValue<T> | T;

interface PlaybackOptions {
looping: Animated<boolean>;
paused: Animated<boolean>;
seek: Animated<number | null>;
volume: Animated<number>;
}

type Materialized<T> = {
[K in keyof T]: T[K] extends Animated<infer U> ? U : T[K];
};

export type MaterializedPlaybackOptions = Materialized<
Omit<PlaybackOptions, "seek">
>;

// TODO: move
export const setFrame = (
video: Video,
currentFrame: SharedValue<SkImage | null>
) => {
const setFrame = (video: Video, currentFrame: SharedValue<SkImage | null>) => {
"worklet";
const img = video.nextImage();
if (img) {
Expand All @@ -45,7 +32,6 @@ export const setFrame = (
};

const defaultOptions = {
playbackSpeed: 1,
looping: true,
paused: false,
seek: null,
Expand Down Expand Up @@ -76,16 +62,15 @@ export const useVideo = (
const looping = useOption(userOptions?.looping ?? defaultOptions.looping);
const seek = useOption(userOptions?.seek ?? defaultOptions.seek);
const volume = useOption(userOptions?.volume ?? defaultOptions.volume);
const playbackSpeed = useOption(
userOptions?.playbackSpeed ?? defaultOptions.playbackSpeed
);
const currentFrame = Rea.useSharedValue<null | SkImage>(null);
const currentTime = Rea.useSharedValue(0);
const lastTimestamp = Rea.useSharedValue(-1);
const duration = useMemo(() => video?.duration() ?? 0, [video]);
const framerate = useMemo(() => video?.framerate() ?? 0, [video]);
const size = useMemo(() => video?.size() ?? { width: 0, height: 0 }, [video]);
const rotation = useMemo(() => video?.rotation() ?? 0, [video]);
const frameDuration = 1000 / framerate;
const currentFrameDuration = Math.floor(frameDuration);
Rea.useAnimatedReaction(
() => isPaused.value,
(paused) => {
Expand Down Expand Up @@ -127,10 +112,6 @@ export const useVideo = (
}
const delta = currentTimestamp - lastTimestamp.value;

const frameDuration = 1000 / framerate;
const currentFrameDuration = Math.floor(
frameDuration / playbackSpeed.value
);
const isOver = currentTime.value + delta > duration;
if (isOver && looping.value) {
seek.value = 0;
Expand Down
Loading