Add react-native-web support (#3958)
Co-authored-by: Kamil Moskała <91079590+moskalakamil@users.noreply.github.com>
This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								docs/bun.lockb
									
									
									
									
									
								
							
							
						
						
									
										
											BIN
										
									
								
								docs/bun.lockb
									
									
									
									
									
								
							
										
											Binary file not shown.
										
									
								
							@@ -103,7 +103,7 @@ Note: On Android, you must set the [reportBandwidth](#reportbandwidth) prop to e
 | 
			
		||||
 | 
			
		||||
### `onBuffer`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
Callback function that is called when the player buffers.
 | 
			
		||||
 | 
			
		||||
@@ -219,6 +219,9 @@ Payload: none
 | 
			
		||||
 | 
			
		||||
Callback function that is called when the media is loaded and ready to play.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
NOTE: tracks (`audioTracks`, `textTracks` & `videoTracks`) are not available on the web.
 | 
			
		||||
 | 
			
		||||
Payload:
 | 
			
		||||
 | 
			
		||||
| Property    | Type   | Description                                                                                                                                                                                                                                                                                                                                                        |
 | 
			
		||||
@@ -292,7 +295,7 @@ Example:
 | 
			
		||||
 | 
			
		||||
### `onPlaybackStateChanged`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
Callback function that is called when the playback state changes.
 | 
			
		||||
 | 
			
		||||
@@ -463,7 +466,7 @@ Payload: none
 | 
			
		||||
 | 
			
		||||
### `onSeek`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'Windows UWP']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'Windows UWP', 'web']} />
 | 
			
		||||
 | 
			
		||||
Callback function that is called when a seek completes.
 | 
			
		||||
 | 
			
		||||
@@ -604,7 +607,7 @@ Example:
 | 
			
		||||
 | 
			
		||||
### `onVolumeChange`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
Callback function that is called when the volume of player changes.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ This page shows the list of available methods
 | 
			
		||||
 | 
			
		||||
### `dismissFullscreenPlayer`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
`dismissFullscreenPlayer(): Promise<void>`
 | 
			
		||||
 | 
			
		||||
@@ -17,7 +17,7 @@ Take the player out of fullscreen mode.
 | 
			
		||||
 | 
			
		||||
### `pause`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
`pause(): Promise<void>`
 | 
			
		||||
 | 
			
		||||
@@ -25,7 +25,7 @@ Pause the video.
 | 
			
		||||
 | 
			
		||||
### `presentFullscreenPlayer`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
`presentFullscreenPlayer(): Promise<void>`
 | 
			
		||||
 | 
			
		||||
@@ -40,7 +40,7 @@ On Android, this puts the navigation controls in fullscreen mode. It is not a co
 | 
			
		||||
 | 
			
		||||
### `resume`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
`resume(): Promise<void>`
 | 
			
		||||
 | 
			
		||||
@@ -100,7 +100,7 @@ tolerance is the max distance in milliseconds from the seconds position that's a
 | 
			
		||||
 | 
			
		||||
### `setVolume`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
`setVolume(value): Promise<void>`
 | 
			
		||||
 | 
			
		||||
@@ -108,7 +108,7 @@ This function will change the volume exactly like [volume](./props#volume) prope
 | 
			
		||||
 | 
			
		||||
### `getCurrentPosition`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
`getCurrentPosition(): Promise<number>`
 | 
			
		||||
 | 
			
		||||
@@ -127,7 +127,7 @@ Changing source with this function will overide source provided as props.
 | 
			
		||||
 | 
			
		||||
### `setFullScreen`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
`setFullScreen(fullscreen): Promise<void>`
 | 
			
		||||
 | 
			
		||||
@@ -137,6 +137,13 @@ On iOS, this displays the video in a fullscreen view controller with controls.
 | 
			
		||||
 | 
			
		||||
On Android, this puts the navigation controls in fullscreen mode. It is not a complete fullscreen implementation, so you will still need to apply a style that makes the width and height match your screen dimensions to get a fullscreen video.
 | 
			
		||||
 | 
			
		||||
### `nativeHtmlVideoRef`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['web']} />
 | 
			
		||||
 | 
			
		||||
A ref to the underlying html video element. This can be used if you need to integrate a 3d party, web only video library (like hls.js, shaka, video.js...).
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
### Example Usage
 | 
			
		||||
 | 
			
		||||
```tsx
 | 
			
		||||
@@ -188,7 +195,7 @@ Possible values are:
 | 
			
		||||
 | 
			
		||||
### `isCodecSupported`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android']} />
 | 
			
		||||
<PlatformsList types={['Android', 'web']} />
 | 
			
		||||
 | 
			
		||||
Indicates whether the provided codec is supported level supported by device.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -131,7 +131,7 @@ When playing an HLS live stream with a `EXT-X-PROGRAM-DATE-TIME` tag configured,
 | 
			
		||||
 | 
			
		||||
### `controls`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
Determines whether to show player controls.
 | 
			
		||||
 | 
			
		||||
@@ -300,7 +300,7 @@ Whether this video view should be focusable with a non-touch input device, eg. r
 | 
			
		||||
 | 
			
		||||
### `fullscreen`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
Controls whether the player enters fullscreen on play.
 | 
			
		||||
See [presentFullscreenPlayer](#presentfullscreenplayer) for details.
 | 
			
		||||
@@ -316,7 +316,7 @@ If a preferred [fullscreenOrientation](#fullscreenorientation) is set, causes th
 | 
			
		||||
 | 
			
		||||
### `fullscreenOrientation`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['iOS', 'visionOS']} />
 | 
			
		||||
<PlatformsList types={['iOS', 'visionOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
- **all (default)** -
 | 
			
		||||
- **landscape**
 | 
			
		||||
@@ -709,6 +709,8 @@ The docs for this prop are incomplete and will be updated as each option is inve
 | 
			
		||||
 | 
			
		||||
> ⚠️ on iOS, you file name must not contain spaces eg. `my video.mp4` will not work, use `my-video.mp4` instead
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'visionOS', 'Windows UWP']} />
 | 
			
		||||
 | 
			
		||||
Example:
 | 
			
		||||
 | 
			
		||||
Pass directly the asset to play (deprecated)
 | 
			
		||||
@@ -820,7 +822,7 @@ Example:
 | 
			
		||||
 | 
			
		||||
#### Start playback at a specific point in time
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
Provide an optional `startPosition` for video. Value is in milliseconds. If the `cropStart` prop is applied, it will be applied from that point forward.
 | 
			
		||||
(If it is negative or undefined or null, it is ignored)
 | 
			
		||||
@@ -1048,7 +1050,7 @@ textTracks={[
 | 
			
		||||
 | 
			
		||||
### `showNotificationControls`
 | 
			
		||||
 | 
			
		||||
<PlatformsList types={['Android', 'iOS']} />
 | 
			
		||||
<PlatformsList types={['Android', 'iOS', 'web']} />
 | 
			
		||||
 | 
			
		||||
Controls whether to show media controls in the notification area.
 | 
			
		||||
For Android each Video component will have its own notification controls and for iOS only one notification control will be shown for the last Active Video component.
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@ It allows to stream video files (m3u, mpd, mp4, ...) inside your react native ap
 | 
			
		||||
- Exoplayer for android
 | 
			
		||||
- AVplayer for iOS, tvOS and visionOS
 | 
			
		||||
- Windows UWP for windows
 | 
			
		||||
- HTML5 for web
 | 
			
		||||
- Trick mode support
 | 
			
		||||
- Subtitles (embeded or side loaded)
 | 
			
		||||
- DRM support
 | 
			
		||||
 
 | 
			
		||||
@@ -181,3 +181,12 @@ Select RCTVideo-tvOS
 | 
			
		||||
Run `pod install` in the `visionos` directory of your project
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
<summary>web</summary>
 | 
			
		||||
 | 
			
		||||
Nothing to do, everything should work out of the box.
 | 
			
		||||
 | 
			
		||||
Note that only basic video support is present, no hls/dash or ads/drm for now.
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 
 | 
			
		||||
@@ -6,7 +6,7 @@ This directory contains examples for `react-native-video` - this is a guide that
 | 
			
		||||
 | 
			
		||||
- **[`bare`](#bare)** - Main example ([react-native-test-app](https://github.com/microsoft/react-native-test-app) - bare react-native app) that you can run on: iOS, Android, Windows, visionOS
 | 
			
		||||
 | 
			
		||||
- **[`expo`](#expo)** - Expo example that you can run on: iOS, Android, tvOS, web (support coming soon)
 | 
			
		||||
- **[`expo`](#expo)** - Expo example that you can run on: iOS, Android, tvOS, web
 | 
			
		||||
 | 
			
		||||
### Updating Examples Content
 | 
			
		||||
 | 
			
		||||
@@ -151,7 +151,9 @@ cd examples/expo && yarn install
 | 
			
		||||
> Setup for android is not complete yet. Please use bare app for android testing.
 | 
			
		||||
 | 
			
		||||
    - For Web:
 | 
			
		||||
      Support for web is coming soon.
 | 
			
		||||
      ```bash
 | 
			
		||||
      yarn web
 | 
			
		||||
      ```
 | 
			
		||||
 | 
			
		||||
If Metro Bundler is not running (or it did not start), you can start it by running:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,14 @@ export const srcAllPlatformList = [
 | 
			
		||||
    description: 'another bunny (can be saved)',
 | 
			
		||||
    uri: 'https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4',
 | 
			
		||||
    headers: {referer: 'www.github.com', 'User-Agent': 'react.native.video'},
 | 
			
		||||
    metadata: {
 | 
			
		||||
      title: 'Custom Title',
 | 
			
		||||
      subtitle: 'Custom Subtitle',
 | 
			
		||||
      artist: 'Custom Artist',
 | 
			
		||||
      description: 'Custom Description',
 | 
			
		||||
      imageUri:
 | 
			
		||||
        'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    description: 'sintel with subtitles',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import {StyleSheet} from 'react-native';
 | 
			
		||||
import {Platform, StyleSheet} from 'react-native';
 | 
			
		||||
 | 
			
		||||
const styles = StyleSheet.create({
 | 
			
		||||
  container: {
 | 
			
		||||
@@ -63,6 +63,7 @@ const styles = StyleSheet.create({
 | 
			
		||||
    borderRadius: 4,
 | 
			
		||||
    overflow: 'hidden',
 | 
			
		||||
    paddingBottom: 10,
 | 
			
		||||
    paddingTop: Platform.OS === 'web' ? 25 : 0,
 | 
			
		||||
  },
 | 
			
		||||
  rateControl: {
 | 
			
		||||
    flex: 1,
 | 
			
		||||
@@ -146,7 +147,7 @@ const styles = StyleSheet.create({
 | 
			
		||||
  },
 | 
			
		||||
  picker: {
 | 
			
		||||
    flex: 1,
 | 
			
		||||
    color: 'white',
 | 
			
		||||
    color: Platform.OS === 'web' ? 'black' : 'white',
 | 
			
		||||
    flexDirection: 'row',
 | 
			
		||||
    justifyContent: 'center',
 | 
			
		||||
    width: 100,
 | 
			
		||||
 
 | 
			
		||||
@@ -15,13 +15,15 @@
 | 
			
		||||
    "update-src": "echo 'Updating src from ../bare/src' && rm -r ./src && cp -r ../bare/src ./src && echo 'Updated src from ../bare/src'"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@expo/metro-runtime": "^3.2.3",
 | 
			
		||||
    "@react-native-picker/picker": "2.8.1",
 | 
			
		||||
    "expo": "~51.0.31",
 | 
			
		||||
    "expo-splash-screen": "~0.27.5",
 | 
			
		||||
    "expo-status-bar": "~1.12.1",
 | 
			
		||||
    "react": "18.2.0",
 | 
			
		||||
    "react-dom": "18.2.0",
 | 
			
		||||
    "react-native": "npm:react-native-tvos@~0.74.5-0"
 | 
			
		||||
    "react-native": "npm:react-native-tvos@~0.74.5-0",
 | 
			
		||||
    "react-native-web": "^0.19.13"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@babel/core": "^7.24.0",
 | 
			
		||||
 
 | 
			
		||||
@@ -78,6 +78,14 @@ export const srcAllPlatformList = [
 | 
			
		||||
    description: 'another bunny (can be saved)',
 | 
			
		||||
    uri: 'https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4',
 | 
			
		||||
    headers: {referer: 'www.github.com', 'User-Agent': 'react.native.video'},
 | 
			
		||||
    metadata: {
 | 
			
		||||
      title: 'Custom Title',
 | 
			
		||||
      subtitle: 'Custom Subtitle',
 | 
			
		||||
      artist: 'Custom Artist',
 | 
			
		||||
      description: 'Custom Description',
 | 
			
		||||
      imageUri:
 | 
			
		||||
        'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png',
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
  {
 | 
			
		||||
    description: 'sintel with subtitles',
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
import {StyleSheet} from 'react-native';
 | 
			
		||||
import {Platform, StyleSheet} from 'react-native';
 | 
			
		||||
 | 
			
		||||
const styles = StyleSheet.create({
 | 
			
		||||
  container: {
 | 
			
		||||
@@ -63,6 +63,7 @@ const styles = StyleSheet.create({
 | 
			
		||||
    borderRadius: 4,
 | 
			
		||||
    overflow: 'hidden',
 | 
			
		||||
    paddingBottom: 10,
 | 
			
		||||
    paddingTop: Platform.OS === 'web' ? 25 : 0,
 | 
			
		||||
  },
 | 
			
		||||
  rateControl: {
 | 
			
		||||
    flex: 1,
 | 
			
		||||
@@ -146,7 +147,7 @@ const styles = StyleSheet.create({
 | 
			
		||||
  },
 | 
			
		||||
  picker: {
 | 
			
		||||
    flex: 1,
 | 
			
		||||
    color: 'white',
 | 
			
		||||
    color: Platform.OS === 'web' ? 'black' : 'white',
 | 
			
		||||
    flexDirection: 'row',
 | 
			
		||||
    justifyContent: 'center',
 | 
			
		||||
    width: 100,
 | 
			
		||||
 
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
    "version": "6.7.0",
 | 
			
		||||
    "description": "A <Video /> element for react-native",
 | 
			
		||||
    "main": "lib/index",
 | 
			
		||||
    "source": "src/index",
 | 
			
		||||
    "source": "src/index.ts",
 | 
			
		||||
    "react-native": "src/index",
 | 
			
		||||
    "license": "MIT",
 | 
			
		||||
    "author": "Community Contributors",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										13
									
								
								shell.nix
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								shell.nix
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
			
		||||
{pkgs ? import <nixpkgs> {}}:
 | 
			
		||||
  pkgs.mkShell {
 | 
			
		||||
    packages = with pkgs; [
 | 
			
		||||
      nodejs-18_x
 | 
			
		||||
      nodePackages.yarn
 | 
			
		||||
      bun
 | 
			
		||||
      eslint_d
 | 
			
		||||
      prettierd
 | 
			
		||||
      jdk11
 | 
			
		||||
      (jdt-language-server.override { jdk = jdk11; })
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -45,8 +45,7 @@ import {
 | 
			
		||||
  resolveAssetSourceForVideo,
 | 
			
		||||
} from './utils';
 | 
			
		||||
import NativeVideoManager from './specs/NativeVideoManager';
 | 
			
		||||
import type {VideoSaveData} from './specs/NativeVideoManager';
 | 
			
		||||
import {CmcdMode, ViewType} from './types';
 | 
			
		||||
import {ViewType, type VideoSaveData, CmcdMode} from './types';
 | 
			
		||||
import type {
 | 
			
		||||
  OnLoadData,
 | 
			
		||||
  OnTextTracksData,
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										461
									
								
								src/Video.web.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										461
									
								
								src/Video.web.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,461 @@
 | 
			
		||||
import React, {
 | 
			
		||||
  forwardRef,
 | 
			
		||||
  useCallback,
 | 
			
		||||
  useEffect,
 | 
			
		||||
  useImperativeHandle,
 | 
			
		||||
  useRef,
 | 
			
		||||
  type RefObject,
 | 
			
		||||
  useState,
 | 
			
		||||
} from 'react';
 | 
			
		||||
import type {VideoRef, ReactVideoProps, VideoMetadata} from './types';
 | 
			
		||||
 | 
			
		||||
// stolen from https://stackoverflow.com/a/77278013/21726244
 | 
			
		||||
const isDeepEqual = <T,>(a: T, b: T): boolean => {
 | 
			
		||||
  if (a === b) {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const bothAreObjects =
 | 
			
		||||
    a && b && typeof a === 'object' && typeof b === 'object';
 | 
			
		||||
 | 
			
		||||
  return Boolean(
 | 
			
		||||
    bothAreObjects &&
 | 
			
		||||
      Object.keys(a).length === Object.keys(b).length &&
 | 
			
		||||
      Object.entries(a).every(([k, v]) => isDeepEqual(v, b[k as keyof T])),
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const Video = forwardRef<VideoRef, ReactVideoProps>(
 | 
			
		||||
  (
 | 
			
		||||
    {
 | 
			
		||||
      source,
 | 
			
		||||
      paused,
 | 
			
		||||
      muted,
 | 
			
		||||
      volume,
 | 
			
		||||
      rate,
 | 
			
		||||
      repeat,
 | 
			
		||||
      controls,
 | 
			
		||||
      showNotificationControls = false,
 | 
			
		||||
      poster,
 | 
			
		||||
      fullscreen,
 | 
			
		||||
      fullscreenAutorotate,
 | 
			
		||||
      fullscreenOrientation,
 | 
			
		||||
      onBuffer,
 | 
			
		||||
      onLoad,
 | 
			
		||||
      onProgress,
 | 
			
		||||
      onPlaybackRateChange,
 | 
			
		||||
      onError,
 | 
			
		||||
      onReadyForDisplay,
 | 
			
		||||
      onSeek,
 | 
			
		||||
      onVolumeChange,
 | 
			
		||||
      onEnd,
 | 
			
		||||
      onPlaybackStateChanged,
 | 
			
		||||
    },
 | 
			
		||||
    ref,
 | 
			
		||||
  ) => {
 | 
			
		||||
    const nativeRef = useRef<HTMLVideoElement>(null);
 | 
			
		||||
 | 
			
		||||
    const isSeeking = useRef(false);
 | 
			
		||||
    const seek = useCallback(
 | 
			
		||||
      async (time: number, _tolerance?: number) => {
 | 
			
		||||
        if (isNaN(time)) {
 | 
			
		||||
          throw new Error('Specified time is not a number');
 | 
			
		||||
        }
 | 
			
		||||
        if (!nativeRef.current) {
 | 
			
		||||
          console.warn('Video Component is not mounted');
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
        time = Math.max(0, Math.min(time, nativeRef.current.duration));
 | 
			
		||||
        nativeRef.current.currentTime = time;
 | 
			
		||||
        onSeek?.({seekTime: time, currentTime: nativeRef.current.currentTime});
 | 
			
		||||
      },
 | 
			
		||||
      [onSeek],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    const [src, setSource] = useState(source);
 | 
			
		||||
    const currentSourceProp = useRef(source);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      if (isDeepEqual(source, currentSourceProp.current)) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      currentSourceProp.current = source;
 | 
			
		||||
      setSource(source);
 | 
			
		||||
    }, [source]);
 | 
			
		||||
 | 
			
		||||
    const pause = useCallback(() => {
 | 
			
		||||
      if (!nativeRef.current) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      nativeRef.current.pause();
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const resume = useCallback(() => {
 | 
			
		||||
      if (!nativeRef.current) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      nativeRef.current.play();
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const setVolume = useCallback((vol: number) => {
 | 
			
		||||
      if (!nativeRef.current) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      nativeRef.current.volume = Math.max(0, Math.min(vol, 100)) / 100;
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const getCurrentPosition = useCallback(async () => {
 | 
			
		||||
      if (!nativeRef.current) {
 | 
			
		||||
        throw new Error('Video Component is not mounted');
 | 
			
		||||
      }
 | 
			
		||||
      return nativeRef.current.currentTime;
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    const unsupported = useCallback(() => {
 | 
			
		||||
      throw new Error('This is unsupported on the web');
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    // Stock this in a ref to not invalidate memoization when those changes.
 | 
			
		||||
    const fsPrefs = useRef({
 | 
			
		||||
      fullscreenAutorotate,
 | 
			
		||||
      fullscreenOrientation,
 | 
			
		||||
    });
 | 
			
		||||
    fsPrefs.current = {
 | 
			
		||||
      fullscreenOrientation,
 | 
			
		||||
      fullscreenAutorotate,
 | 
			
		||||
    };
 | 
			
		||||
    const setFullScreen = useCallback(
 | 
			
		||||
      async (
 | 
			
		||||
        newVal: boolean,
 | 
			
		||||
        orientation?: ReactVideoProps['fullscreenOrientation'],
 | 
			
		||||
        autorotate?: boolean,
 | 
			
		||||
      ) => {
 | 
			
		||||
        orientation ??= fsPrefs.current.fullscreenOrientation;
 | 
			
		||||
        autorotate ??= fsPrefs.current.fullscreenAutorotate;
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
          if (newVal) {
 | 
			
		||||
            await nativeRef.current?.requestFullscreen({
 | 
			
		||||
              navigationUI: 'hide',
 | 
			
		||||
            });
 | 
			
		||||
            if (orientation === 'all' || !orientation || autorotate) {
 | 
			
		||||
              screen.orientation.unlock();
 | 
			
		||||
            } else {
 | 
			
		||||
              await screen.orientation.lock(orientation);
 | 
			
		||||
            }
 | 
			
		||||
          } else {
 | 
			
		||||
            if (document.fullscreenElement) {
 | 
			
		||||
              await document.exitFullscreen();
 | 
			
		||||
            }
 | 
			
		||||
            screen.orientation.unlock();
 | 
			
		||||
          }
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
          // Changing fullscreen status without a button click is not allowed so it throws.
 | 
			
		||||
          // Some browsers also used to throw when locking screen orientation was not supported.
 | 
			
		||||
          console.error('Could not toggle fullscreen/screen lock status', e);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      [],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      setFullScreen(
 | 
			
		||||
        fullscreen || false,
 | 
			
		||||
        fullscreenOrientation,
 | 
			
		||||
        fullscreenAutorotate,
 | 
			
		||||
      );
 | 
			
		||||
    }, [
 | 
			
		||||
      setFullScreen,
 | 
			
		||||
      fullscreen,
 | 
			
		||||
      fullscreenAutorotate,
 | 
			
		||||
      fullscreenOrientation,
 | 
			
		||||
    ]);
 | 
			
		||||
 | 
			
		||||
    const presentFullscreenPlayer = useCallback(
 | 
			
		||||
      () => setFullScreen(true),
 | 
			
		||||
      [setFullScreen],
 | 
			
		||||
    );
 | 
			
		||||
    const dismissFullscreenPlayer = useCallback(
 | 
			
		||||
      () => setFullScreen(false),
 | 
			
		||||
      [setFullScreen],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    useImperativeHandle(
 | 
			
		||||
      ref,
 | 
			
		||||
      () => ({
 | 
			
		||||
        seek,
 | 
			
		||||
        setSource,
 | 
			
		||||
        pause,
 | 
			
		||||
        resume,
 | 
			
		||||
        setVolume,
 | 
			
		||||
        getCurrentPosition,
 | 
			
		||||
        presentFullscreenPlayer,
 | 
			
		||||
        dismissFullscreenPlayer,
 | 
			
		||||
        setFullScreen,
 | 
			
		||||
        save: unsupported,
 | 
			
		||||
        restoreUserInterfaceForPictureInPictureStopCompleted: unsupported,
 | 
			
		||||
        nativeHtmlVideoRef: nativeRef,
 | 
			
		||||
      }),
 | 
			
		||||
      [
 | 
			
		||||
        seek,
 | 
			
		||||
        setSource,
 | 
			
		||||
        pause,
 | 
			
		||||
        resume,
 | 
			
		||||
        unsupported,
 | 
			
		||||
        setVolume,
 | 
			
		||||
        getCurrentPosition,
 | 
			
		||||
        nativeRef,
 | 
			
		||||
        presentFullscreenPlayer,
 | 
			
		||||
        dismissFullscreenPlayer,
 | 
			
		||||
        setFullScreen,
 | 
			
		||||
      ],
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      if (paused) {
 | 
			
		||||
        pause();
 | 
			
		||||
      } else {
 | 
			
		||||
        resume();
 | 
			
		||||
      }
 | 
			
		||||
    }, [paused, pause, resume]);
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      if (volume === undefined || isNaN(volume)) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      setVolume(volume);
 | 
			
		||||
    }, [volume, setVolume]);
 | 
			
		||||
 | 
			
		||||
    // we use a ref to prevent triggerring the useEffect when the component rerender with a non-stable `onPlaybackStateChanged`.
 | 
			
		||||
    const playbackStateRef = useRef(onPlaybackStateChanged);
 | 
			
		||||
    playbackStateRef.current = onPlaybackStateChanged;
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      // Not sure about how to do this but we want to wait for nativeRef to be initialized
 | 
			
		||||
      setTimeout(() => {
 | 
			
		||||
        if (!nativeRef.current) {
 | 
			
		||||
          return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Set play state to the player's value (if autoplay is denied)
 | 
			
		||||
        // This is useful if our UI is in a play state but autoplay got denied so
 | 
			
		||||
        // the video is actually in a paused state.
 | 
			
		||||
        playbackStateRef.current?.({
 | 
			
		||||
          isPlaying: !nativeRef.current.paused,
 | 
			
		||||
          isSeeking: isSeeking.current,
 | 
			
		||||
        });
 | 
			
		||||
      }, 500);
 | 
			
		||||
    }, []);
 | 
			
		||||
 | 
			
		||||
    useEffect(() => {
 | 
			
		||||
      if (!nativeRef.current || rate === undefined) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      nativeRef.current.playbackRate = rate;
 | 
			
		||||
    }, [rate]);
 | 
			
		||||
 | 
			
		||||
    useMediaSession(src?.metadata, nativeRef, showNotificationControls);
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
      <video
 | 
			
		||||
        ref={nativeRef}
 | 
			
		||||
        src={src?.uri as string | undefined}
 | 
			
		||||
        muted={muted}
 | 
			
		||||
        autoPlay={!paused}
 | 
			
		||||
        controls={controls}
 | 
			
		||||
        loop={repeat}
 | 
			
		||||
        playsInline
 | 
			
		||||
        poster={
 | 
			
		||||
          typeof poster === 'object'
 | 
			
		||||
            ? typeof poster.source === 'object'
 | 
			
		||||
              ? poster.source.uri
 | 
			
		||||
              : undefined
 | 
			
		||||
            : poster
 | 
			
		||||
        }
 | 
			
		||||
        onCanPlay={() => onBuffer?.({isBuffering: false})}
 | 
			
		||||
        onWaiting={() => onBuffer?.({isBuffering: true})}
 | 
			
		||||
        onRateChange={() => {
 | 
			
		||||
          if (!nativeRef.current) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          onPlaybackRateChange?.({
 | 
			
		||||
            playbackRate: nativeRef.current?.playbackRate,
 | 
			
		||||
          });
 | 
			
		||||
        }}
 | 
			
		||||
        onDurationChange={() => {
 | 
			
		||||
          if (!nativeRef.current) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          onLoad?.({
 | 
			
		||||
            currentTime: nativeRef.current.currentTime,
 | 
			
		||||
            duration: nativeRef.current.duration,
 | 
			
		||||
            videoTracks: [],
 | 
			
		||||
            textTracks: [],
 | 
			
		||||
            audioTracks: [],
 | 
			
		||||
            naturalSize: {
 | 
			
		||||
              width: nativeRef.current.videoWidth,
 | 
			
		||||
              height: nativeRef.current.videoHeight,
 | 
			
		||||
              orientation: 'landscape',
 | 
			
		||||
            },
 | 
			
		||||
          });
 | 
			
		||||
        }}
 | 
			
		||||
        onTimeUpdate={() => {
 | 
			
		||||
          if (!nativeRef.current) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          onProgress?.({
 | 
			
		||||
            currentTime: nativeRef.current.currentTime,
 | 
			
		||||
            playableDuration: nativeRef.current.buffered.length
 | 
			
		||||
              ? nativeRef.current.buffered.end(
 | 
			
		||||
                  nativeRef.current.buffered.length - 1,
 | 
			
		||||
                )
 | 
			
		||||
              : 0,
 | 
			
		||||
            seekableDuration: 0,
 | 
			
		||||
          });
 | 
			
		||||
        }}
 | 
			
		||||
        onLoadedData={() => onReadyForDisplay?.()}
 | 
			
		||||
        onError={() => {
 | 
			
		||||
          if (!nativeRef.current?.error) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          onError?.({
 | 
			
		||||
            error: {
 | 
			
		||||
              errorString: nativeRef.current.error.message ?? 'Unknown error',
 | 
			
		||||
              code: nativeRef.current.error.code,
 | 
			
		||||
            },
 | 
			
		||||
          });
 | 
			
		||||
        }}
 | 
			
		||||
        onLoadedMetadata={() => {
 | 
			
		||||
          if (src?.startPosition) {
 | 
			
		||||
            seek(src.startPosition / 1000);
 | 
			
		||||
          }
 | 
			
		||||
        }}
 | 
			
		||||
        onPlay={() =>
 | 
			
		||||
          onPlaybackStateChanged?.({
 | 
			
		||||
            isPlaying: true,
 | 
			
		||||
            isSeeking: isSeeking.current,
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        onPause={() =>
 | 
			
		||||
          onPlaybackStateChanged?.({
 | 
			
		||||
            isPlaying: false,
 | 
			
		||||
            isSeeking: isSeeking.current,
 | 
			
		||||
          })
 | 
			
		||||
        }
 | 
			
		||||
        onSeeking={() => (isSeeking.current = true)}
 | 
			
		||||
        onSeeked={() => {
 | 
			
		||||
          // only trigger this if it's from UI seek.
 | 
			
		||||
          // if it was triggered via ref.seek(), onSeek has already been called
 | 
			
		||||
          if (isSeeking.current) {
 | 
			
		||||
            isSeeking.current = false;
 | 
			
		||||
            onSeek?.({
 | 
			
		||||
              seekTime: nativeRef.current!.currentTime,
 | 
			
		||||
              currentTime: nativeRef.current!.currentTime,
 | 
			
		||||
            });
 | 
			
		||||
          }
 | 
			
		||||
        }}
 | 
			
		||||
        onVolumeChange={() => {
 | 
			
		||||
          if (!nativeRef.current) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
          onVolumeChange?.({volume: nativeRef.current.volume});
 | 
			
		||||
        }}
 | 
			
		||||
        onEnded={onEnd}
 | 
			
		||||
        style={videoStyle}
 | 
			
		||||
      />
 | 
			
		||||
    );
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const videoStyle = {
 | 
			
		||||
  position: 'absolute',
 | 
			
		||||
  inset: 0,
 | 
			
		||||
  objectFit: 'contain',
 | 
			
		||||
  width: '100%',
 | 
			
		||||
  height: '100%',
 | 
			
		||||
} satisfies React.CSSProperties;
 | 
			
		||||
 | 
			
		||||
const useMediaSession = (
 | 
			
		||||
  metadata: VideoMetadata | undefined,
 | 
			
		||||
  nativeRef: RefObject<HTMLVideoElement>,
 | 
			
		||||
  showNotification: boolean,
 | 
			
		||||
) => {
 | 
			
		||||
  const isPlaying = !nativeRef.current?.paused ?? false;
 | 
			
		||||
  const progress = nativeRef.current?.currentTime ?? 0;
 | 
			
		||||
  const duration = Number.isFinite(nativeRef.current?.duration)
 | 
			
		||||
    ? nativeRef.current?.duration
 | 
			
		||||
    : undefined;
 | 
			
		||||
  const playbackRate = nativeRef.current?.playbackRate ?? 1;
 | 
			
		||||
 | 
			
		||||
  const enabled = 'mediaSession' in navigator && showNotification;
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      navigator.mediaSession.metadata = new MediaMetadata({
 | 
			
		||||
        title: metadata?.title,
 | 
			
		||||
        artist: metadata?.artist,
 | 
			
		||||
        artwork: metadata?.imageUri ? [{src: metadata.imageUri}] : undefined,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [enabled, metadata]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!enabled) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const seekTo = (time: number) => {
 | 
			
		||||
      if (nativeRef.current) {
 | 
			
		||||
        nativeRef.current.currentTime = time;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const seekRelative = (offset: number) => {
 | 
			
		||||
      if (nativeRef.current) {
 | 
			
		||||
        nativeRef.current.currentTime = nativeRef.current.currentTime + offset;
 | 
			
		||||
      }
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    const mediaActions: [
 | 
			
		||||
      MediaSessionAction,
 | 
			
		||||
      MediaSessionActionHandler | null,
 | 
			
		||||
    ][] = [
 | 
			
		||||
      ['play', () => nativeRef.current?.play()],
 | 
			
		||||
      ['pause', () => nativeRef.current?.pause()],
 | 
			
		||||
      [
 | 
			
		||||
        'seekbackward',
 | 
			
		||||
        (evt: MediaSessionActionDetails) =>
 | 
			
		||||
          seekRelative(evt.seekOffset ? -evt.seekOffset : -10),
 | 
			
		||||
      ],
 | 
			
		||||
      [
 | 
			
		||||
        'seekforward',
 | 
			
		||||
        (evt: MediaSessionActionDetails) =>
 | 
			
		||||
          seekRelative(evt.seekOffset ? evt.seekOffset : 10),
 | 
			
		||||
      ],
 | 
			
		||||
      ['seekto', (evt: MediaSessionActionDetails) => seekTo(evt.seekTime!)],
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    for (const [action, handler] of mediaActions) {
 | 
			
		||||
      try {
 | 
			
		||||
        navigator.mediaSession.setActionHandler(action, handler);
 | 
			
		||||
      } catch {
 | 
			
		||||
        // ignored
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [enabled, nativeRef]);
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      navigator.mediaSession.playbackState = isPlaying ? 'playing' : 'paused';
 | 
			
		||||
    }
 | 
			
		||||
  }, [isPlaying, enabled]);
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (enabled && duration !== undefined) {
 | 
			
		||||
      navigator.mediaSession.setPositionState({
 | 
			
		||||
        position: Math.min(progress, duration),
 | 
			
		||||
        duration,
 | 
			
		||||
        playbackRate: playbackRate,
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }, [progress, duration, playbackRate, enabled]);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Video.displayName = 'Video';
 | 
			
		||||
export default Video;
 | 
			
		||||
							
								
								
									
										34
									
								
								src/VideoDecoderProperties.web.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/VideoDecoderProperties.web.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,34 @@
 | 
			
		||||
/// <reference lib="dom" />
 | 
			
		||||
import type {VideoDecoderInfoModuleType} from './specs/NativeVideoDecoderInfoModule';
 | 
			
		||||
 | 
			
		||||
const canPlay = (codec: string): boolean => {
 | 
			
		||||
  // most chrome based browser (and safari I think) supports matroska but reports they do not.
 | 
			
		||||
  // for those browsers, only check the codecs and not the container.
 | 
			
		||||
  if (navigator.userAgent.search('Firefox') === -1) {
 | 
			
		||||
    codec = codec.replace('video/x-matroska', 'video/mp4');
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return !!MediaSource.isTypeSupported(codec);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const VideoDecoderProperties = {
 | 
			
		||||
  async getWidevineLevel() {
 | 
			
		||||
    return 0;
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async isCodecSupported(
 | 
			
		||||
    mimeType: string,
 | 
			
		||||
    _width: number,
 | 
			
		||||
    _height: number,
 | 
			
		||||
  ): Promise<'unsupported' | 'hardware' | 'software'> {
 | 
			
		||||
    // TODO: Figure out if we can get hardware support information
 | 
			
		||||
    return canPlay(mimeType) ? 'software' : 'unsupported';
 | 
			
		||||
  },
 | 
			
		||||
 | 
			
		||||
  async isHEVCSupported(): Promise<'unsupported' | 'hardware' | 'software'> {
 | 
			
		||||
    // Just a dummy vidoe mime type codec with HEVC to check.
 | 
			
		||||
    return canPlay('video/x-matroska; codecs="hvc1.1.4.L96.BO"')
 | 
			
		||||
      ? 'software'
 | 
			
		||||
      : 'unsupported';
 | 
			
		||||
  },
 | 
			
		||||
} satisfies VideoDecoderInfoModuleType;
 | 
			
		||||
@@ -1,6 +1,5 @@
 | 
			
		||||
import Video from './Video';
 | 
			
		||||
export {VideoDecoderProperties} from './VideoDecoderProperties';
 | 
			
		||||
export * from './types';
 | 
			
		||||
export type {VideoRef} from './Video';
 | 
			
		||||
export {Video};
 | 
			
		||||
export default Video;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ import {NativeModules} from 'react-native';
 | 
			
		||||
import type {Int32} from 'react-native/Libraries/Types/CodegenTypes';
 | 
			
		||||
 | 
			
		||||
// @TODO rename to "Spec" when applying new arch
 | 
			
		||||
interface VideoDecoderInfoModuleType {
 | 
			
		||||
export interface VideoDecoderInfoModuleType {
 | 
			
		||||
  getWidevineLevel: () => Promise<Int32>;
 | 
			
		||||
  isCodecSupported: (
 | 
			
		||||
    mimeType: string,
 | 
			
		||||
 
 | 
			
		||||
@@ -4,10 +4,7 @@ import type {
 | 
			
		||||
  Float,
 | 
			
		||||
  UnsafeObject,
 | 
			
		||||
} from 'react-native/Libraries/Types/CodegenTypes';
 | 
			
		||||
 | 
			
		||||
export type VideoSaveData = {
 | 
			
		||||
  uri: string;
 | 
			
		||||
};
 | 
			
		||||
import type {VideoSaveData} from '../types/video-ref';
 | 
			
		||||
 | 
			
		||||
// @TODO rename to "Spec" when applying new arch
 | 
			
		||||
export interface VideoManagerType {
 | 
			
		||||
 
 | 
			
		||||
@@ -285,12 +285,12 @@ type OnReceiveAdEventData = Readonly<{
 | 
			
		||||
 | 
			
		||||
export type OnVideoErrorData = Readonly<{
 | 
			
		||||
  error: Readonly<{
 | 
			
		||||
    errorString?: string; // android
 | 
			
		||||
    errorString?: string; // android | web
 | 
			
		||||
    errorException?: string; // android
 | 
			
		||||
    errorStackTrace?: string; // android
 | 
			
		||||
    errorCode?: string; // android
 | 
			
		||||
    error?: string; // ios
 | 
			
		||||
    code?: Int32; // ios
 | 
			
		||||
    code?: Int32; // ios | web
 | 
			
		||||
    localizedDescription?: string; // ios
 | 
			
		||||
    localizedFailureReason?: string; // ios
 | 
			
		||||
    localizedRecoverySuggestion?: string; // ios
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@ import type {
 | 
			
		||||
  OnVolumeChangeData,
 | 
			
		||||
} from '../specs/VideoNativeComponent';
 | 
			
		||||
 | 
			
		||||
export type * from '../specs/VideoNativeComponent';
 | 
			
		||||
 | 
			
		||||
export type AudioTrack = OnAudioTracksData['audioTracks'][number];
 | 
			
		||||
export type TextTrack = OnTextTracksData['textTracks'][number];
 | 
			
		||||
export type VideoTrack = OnVideoTracksData['videoTracks'][number];
 | 
			
		||||
 
 | 
			
		||||
@@ -7,4 +7,4 @@ export {default as ResizeMode} from './ResizeMode';
 | 
			
		||||
export {default as TextTrackType} from './TextTrackType';
 | 
			
		||||
export {default as ViewType} from './ViewType';
 | 
			
		||||
export * from './video';
 | 
			
		||||
export * from '../specs/VideoNativeComponent';
 | 
			
		||||
export * from './video-ref';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										23
									
								
								src/types/video-ref.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/types/video-ref.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,23 @@
 | 
			
		||||
import type {RefObject} from 'react';
 | 
			
		||||
import {ReactVideoSource} from './video';
 | 
			
		||||
 | 
			
		||||
export type VideoSaveData = {
 | 
			
		||||
  uri: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface VideoRef {
 | 
			
		||||
  seek: (time: number, tolerance?: number) => void;
 | 
			
		||||
  resume: () => void;
 | 
			
		||||
  pause: () => void;
 | 
			
		||||
  presentFullscreenPlayer: () => void;
 | 
			
		||||
  dismissFullscreenPlayer: () => void;
 | 
			
		||||
  restoreUserInterfaceForPictureInPictureStopCompleted: (
 | 
			
		||||
    restore: boolean,
 | 
			
		||||
  ) => void;
 | 
			
		||||
  save: (options: object) => Promise<VideoSaveData>;
 | 
			
		||||
  setVolume: (volume: number) => void;
 | 
			
		||||
  getCurrentPosition: () => Promise<number>;
 | 
			
		||||
  setFullScreen: (fullScreen: boolean) => void;
 | 
			
		||||
  setSource: (source?: ReactVideoSource) => void;
 | 
			
		||||
  nativeHtmlVideoRef?: RefObject<HTMLVideoElement>; // web only
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user