Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/shopify/react-native-skia/llms.txt

Use this file to discover all available pages before exploring further.

React Native Skia enables rich multimedia experiences by allowing you to load video frames as images. Video frames can be used anywhere a Skia image is accepted: Image, ImageShader, and Atlas components.

Requirements

  • React Native Reanimated version 3 or higher
  • Android API level 26 or higher
  • Web Fully supported

useVideo Hook

The useVideo hook loads video files and provides the current frame as a shared value.

Basic Example

import { Canvas, Image, useVideo } from "@shopify/react-native-skia";
import { useWindowDimensions } from "react-native";
import { useSharedValue } from "react-native-reanimated";

const VideoExample = () => {
  const { width, height } = useWindowDimensions();
  const { currentFrame } = useVideo("https://bit.ly/skia-video");
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Image
        image={currentFrame}
        x={0}
        y={0}
        width={width}
        height={height}
        fit="cover"
      />
    </Canvas>
  );
};

Returned Values

The useVideo hook returns an object with:
PropertyTypeDescription
currentFrameSharedValue<SkImage | null>Current video frame or null if not loaded
currentTimeSharedValue<number>Current playback position in milliseconds
durationnumberTotal video duration in milliseconds
frameratenumberVideo frame rate (frames per second)
rotation0 | 90 | 180 | 270Video rotation in degrees
size{ width: number, height: number }Video dimensions

Using Returned Values

import { Canvas, Image, useVideo, Text } from "@shopify/react-native-skia";
import { useDerivedValue } from "react-native-reanimated";

const VideoWithInfo = () => {
  const { currentFrame, currentTime, duration, framerate, size } = useVideo(
    "https://bit.ly/skia-video"
  );
  
  const timeText = useDerivedValue(
    () => `${Math.floor(currentTime.value / 1000)}s / ${Math.floor(duration / 1000)}s`
  );
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Image
        image={currentFrame}
        x={0}
        y={0}
        width={size.width}
        height={size.height}
        fit="contain"
      />
      <Text x={10} y={30} text={timeText} />
    </Canvas>
  );
};

Playback Options

Control video playback with options:
OptionTypeDescription
pausedSharedValue<boolean>Controls pause state
loopingSharedValue<boolean>Enables looping
seekSharedValue<number | null>Seek to specific time in milliseconds
volumeSharedValue<number>Volume level (0-1, where 0 is muted)

Pause and Play

import { Canvas, Image, useVideo } from "@shopify/react-native-skia";
import { Pressable } from "react-native";
import { useSharedValue } from "react-native-reanimated";

const PauseableVideo = () => {
  const paused = useSharedValue(false);
  const { currentFrame } = useVideo(
    "https://bit.ly/skia-video",
    { paused }
  );
  
  return (
    <Pressable
      style={{ flex: 1 }}
      onPress={() => {
        paused.value = !paused.value;
      }}
    >
      <Canvas style={{ flex: 1 }}>
        <Image
          image={currentFrame}
          x={0}
          y={0}
          width={400}
          height={300}
          fit="cover"
        />
      </Canvas>
    </Pressable>
  );
};

Looping Video

import { useSharedValue } from "react-native-reanimated";

const LoopingVideo = () => {
  const looping = useSharedValue(true);
  const { currentFrame } = useVideo(
    "https://bit.ly/skia-video",
    { looping }
  );
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Image image={currentFrame} x={0} y={0} width={400} height={300} />
    </Canvas>
  );
};

Seeking

import { Canvas, Image, useVideo } from "@shopify/react-native-skia";
import { Pressable } from "react-native";
import { useSharedValue } from "react-native-reanimated";

const SeekableVideo = () => {
  const seek = useSharedValue<number | null>(null);
  const { currentFrame, currentTime } = useVideo(
    "https://bit.ly/skia-video",
    { seek, looping: true }
  );
  
  return (
    <Pressable
      style={{ flex: 1 }}
      onPress={() => {
        // Seek to 2 seconds
        seek.value = 2000;
      }}
    >
      <Canvas style={{ flex: 1 }}>
        <Image
          image={currentFrame}
          x={0}
          y={0}
          width={400}
          height={300}
          fit="cover"
        />
      </Canvas>
    </Pressable>
  );
};

Volume Control

import { useSharedValue } from "react-native-reanimated";
import { Slider } from "react-native";

const VideoWithVolume = () => {
  const volume = useSharedValue(1.0);
  const { currentFrame } = useVideo(
    "https://bit.ly/skia-video",
    { volume }
  );
  
  return (
    <>
      <Canvas style={{ flex: 1 }}>
        <Image image={currentFrame} x={0} y={0} width={400} height={300} />
      </Canvas>
      <Slider
        value={volume.value}
        onValueChange={(value) => {
          volume.value = value;
        }}
        minimumValue={0}
        maximumValue={1}
      />
    </>
  );
};

Loading Videos

From Network

const { currentFrame } = useVideo("https://example.com/video.mp4");

From Local File

const { currentFrame } = useVideo("file:///path/to/video.mp4");

From Assets with Expo

import { useVideo } from "@shopify/react-native-skia";
import { useAssets } from "expo-asset";

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);
};

// Usage
const VideoFromAsset = () => {
  const { currentFrame } = useVideoFromAsset(
    require("./assets/BigBuckBunny.mp4")
  );
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Image image={currentFrame} x={0} y={0} width={400} height={300} />
    </Canvas>
  );
};

Handling Rotation

Videos can have rotation metadata. Use the fitbox function to handle rotation:
import { Canvas, Image, useVideo, fitbox, rect } from "@shopify/react-native-skia";
import { useWindowDimensions } from "react-native";

const RotatedVideo = () => {
  const { width, height } = useWindowDimensions();
  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);
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Image
        image={currentFrame}
        x={0}
        y={0}
        width={width}
        height={height}
        fit="none"
        transform={transform}
      />
    </Canvas>
  );
};

Using with Shaders

Video frames can be used as shader inputs:
import {
  Canvas,
  Fill,
  ImageShader,
  ColorMatrix,
  useVideo,
} from "@shopify/react-native-skia";
import { useWindowDimensions } from "react-native";

const VideoWithShader = () => {
  const { width, height } = useWindowDimensions();
  const { currentFrame } = useVideo("https://bit.ly/skia-video");
  
  return (
    <Canvas style={{ flex: 1 }}>
      <Fill>
        <ImageShader
          image={currentFrame}
          x={0}
          y={0}
          width={width}
          height={height}
          fit="cover"
        />
        <ColorMatrix
          matrix={[
            0.95, 0, 0, 0, 0.05,
            0.65, 0, 0, 0, 0.15,
            0.15, 0, 0, 0, 0.5,
            0, 0, 0, 1, 0,
          ]}
        />
      </Fill>
    </Canvas>
  );
};

Complete Player Example

import { Canvas, Image, useVideo } from "@shopify/react-native-skia";
import { View, Pressable, Text, StyleSheet } from "react-native";
import { useSharedValue } from "react-native-reanimated";
import { useState } from "react";

const VideoPlayer = () => {
  const [isPlaying, setIsPlaying] = useState(true);
  const paused = useSharedValue(false);
  const looping = useSharedValue(true);
  
  const { currentFrame, currentTime, duration, size } = useVideo(
    "https://bit.ly/skia-video",
    { paused, looping }
  );
  
  const togglePlay = () => {
    paused.value = !paused.value;
    setIsPlaying(!isPlaying);
  };
  
  return (
    <View style={styles.container}>
      <Canvas style={styles.canvas}>
        <Image
          image={currentFrame}
          x={0}
          y={0}
          width={size.width}
          height={size.height}
          fit="contain"
        />
      </Canvas>
      <View style={styles.controls}>
        <Pressable style={styles.button} onPress={togglePlay}>
          <Text>{isPlaying ? "Pause" : "Play"}</Text>
        </Pressable>
        <Text>
          {Math.floor(currentTime.value / 1000)}s / {Math.floor(duration / 1000)}s
        </Text>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  canvas: {
    flex: 1,
  },
  controls: {
    flexDirection: "row",
    justifyContent: "space-between",
    padding: 10,
    backgroundColor: "#000",
  },
  button: {
    padding: 10,
    backgroundColor: "#007AFF",
    borderRadius: 5,
  },
});

Performance Tips

  • Use appropriate video resolution for your use case
  • Consider using lower frame rates for better performance
  • Limit the number of simultaneous videos
  • Pause videos when not visible
  • Use hardware acceleration when available

Video Encoding

To encode videos from Skia images:

Supported Formats

Supported formats vary by platform: iOS:
  • MP4
  • MOV
  • M4V
Android:
  • MP4
  • 3GP
  • WebM
  • MKV
Web:
  • MP4
  • WebM
  • OGG

Troubleshooting

Video Not Playing

  • Check that the video URL is accessible
  • Verify the video format is supported
  • Ensure Reanimated is properly installed
  • Check Android API level (26+)

Poor Performance

  • Reduce video resolution
  • Lower frame rate
  • Use hardware-accelerated formats
  • Limit number of simultaneous videos

Audio Issues

  • Check volume settings
  • Verify audio is present in source file
  • Test with different video files

See Also