npm package: @ohmi/react-native-video
Description: A <Video /> element for react-native
Latest version: 5.2.1-0.2.51

> 模板版本：v0.2.2

<p align="center">
  <h1 align="center"> <code>@ohmi/react-native-video</code> </h1>
</p>
<p align="center">
    <a href="https://github.com/react-native-video/react-native-video/tree/support/5.2.X">
        <img src="https://img.shields.io/badge/platforms-android%20|%20ios%20|%20harmony%20-lightgrey.svg" alt="Supported platforms" />
    </a>
    <a href="https://github.com/react-native-video/react-native-video/blob/support/5.2.X/LICENSE">
        <img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License" />
    </a>
</p>

> [!TIP] [Gitee 地址](https://gitee.com/kunyuan-hongke/react-native-video)

## 安装与使用

请到三方库的 Releases
发布地址查看配套的版本信息：[@ohmi/react-native-video Releases](https://gitee.com/kunyuan-hongke/react-native-video/releases)
。对于未发布到npm的旧版本，请参考[安装指南](https://gitee.com/react-native-oh-library/usage-docs/blob/master/zh-cn/tgz-usage.md)安装tgz包。

进入到工程目录并输入以下命令：

<!-- tabs:start -->

#### **npm**

```bash
npm install @ohmi/react-native-video
```

#### **yarn**

```bash
yarn add @ohmi/react-native-video
```

<!-- tabs:end -->

下面的代码展示了这个库的基本使用场景：

> [!WARNING] 使用时 import 的库名不变。

```js
import React, {useState, useRef} from "react";
import {View, ScrollView, StyleSheet, Text, TextInput} from "react-native";
import RNCVideo from "react-native-video";

function RNCVideoDemo() {
    const [muted, setMuted] = useState(true);
    const [paused, setPaused] = useState(false);
    const [repeat, setRepeat] = useState(true);
    const [controls, setControls] = useState(false);
    const [disableFocus, setDisableFocus] = useState(false);
    const [uri, setUri] = useState(
        "https://res.vmallres.com//uomcdn/CN/cms/202210/C75C7E20060F3E909F2998E13C3ABC03.mp4"
    );
    const [txt, setTxt] = useState("empty");
    const [resizeMode, setResizeMode] = useState("none");
    const [posterResizeMode, setPosterResizeMode] = useState("cover");
    const [seekSec, setSeekSec] = useState(5000);

    const [onVideoLoad, setOnVideoLoad] = useState("onVideoLoad");
    const [onVideoLoadStart, setOnVideoLoadStart] = useState("onVideoLoadStart");
    const [onVideoError, setOnVideoError] = useState("onVideoError");
    const [onVideoProgress, setOnVideoProgress] = useState("onVideoProgress");
    const [onVideoEnd, setOnVideoEnd] = useState("onVideoEnd");
    const [onVideoBuffer, setOnVideoBuffer] = useState("onVideoBuffer");
    const [onPlaybackStalled, setOnPlaybackStalled] = useState("onPlaybackStalled");
    const [onPlaybackResume, setOnPlaybackResume] = useState("onPlaybackResume");

    const videoRef = React.useRef < typeof RNCVideo > ();

    const toggleMuted = () => {
        setMuted((prevMuted) => !prevMuted);
    };

    const toggleControls = () => {
        setControls((prevControls) => !prevControls);
    };

    const togglePaused = () => {
        setPaused((prevPaused) => !prevPaused);
    };

    const toggleRepeat = () => {
        setRepeat((prevRepeat) => !prevRepeat);
    };

    const toggleDisableFocus = () => {
        setDisableFocus((prevDisableFocus) => !prevDisableFocus);
    };

    const firstVideo = () => {
        setUri((prevRepeat) => "https://vjs.zencdn.net/v/oceans.mp4");
    };

    const secondVideo = () => {
        // setUri((prevRepeat) => 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4');
        setUri(
            (prevRepeat) =>
                "https://res.vmallres.com//uomcdn/CN/cms/202210/C75C7E20060F3E909F2998E13C3ABC03.mp4"
        );
    };

    const changeResizeMode = (resizeMode) => {
        setResizeMode((prevResizeMode) => resizeMode);
    };

    return (
        <ScrollView style={styles.container}>
            <View style={styles.container}>
                <Text style={styles.title}>网络视频demo</Text>
                <Text style={styles.labelB}>{onVideoLoad}</Text>
                <Text style={styles.label}>{onVideoError}</Text>
                <Text style={styles.label}>{onVideoLoadStart}</Text>
                <Text style={styles.labelB}>{onVideoProgress}</Text>
                <Text style={styles.label}>{onVideoEnd}</Text>
                <Text style={styles.label}>{onVideoBuffer}</Text>
                <Text style={styles.label}>{onPlaybackStalled}</Text>
                <Text style={styles.label}>{onPlaybackResume}</Text>
                <Text style={styles.title}>update source</Text>
                <View
                    style={{
                        flexDirection: "row",
                        height: 40,
                        padding: 0,
                    }}
                >
                    <Text
                        style={{backgroundColor: "blue", flex: 0.25}}
                        onPress={() => {
                            setUri(
                                "https://res.vmallres.com//uomcdn/CN/cms/202210/C75C7E20060F3E909F2998E13C3ABC03.mp4"
                            );
                            setPosterResizeMode("stretch");
                        }}
                    >
                        切换net:vmallres
                    </Text>
                    <Text
                        style={{backgroundColor: "red", flex: 0.25}}
                        onPress={() => {
                            setUri("https://vjs.zencdn.net/v/oceans.mp4");
                            setPosterResizeMode("contain");
                        }}
                    >
                        切换net:oceans
                    </Text>
                </View>
                <Text style={styles.title}>set resizeMode</Text>
                <View
                    style={{
                        flexDirection: "row",
                        height: 40,
                        padding: 0,
                    }}
                >
                    <Text
                        style={{backgroundColor: "blue", flex: 0.25}}
                        onPress={() => {
                            setResizeMode("none");
                        }}
                    >
                        none
                    </Text>
                    <Text
                        style={{backgroundColor: "red", flex: 0.25}}
                        onPress={() => {
                            setResizeMode("contain");
                        }}
                    >
                        contain
                    </Text>
                    <Text
                        style={{backgroundColor: "yellow", flex: 0.25}}
                        onPress={() => {
                            setResizeMode("stretch");
                        }}
                    >
                        stretch
                    </Text>
                    <Text
                        style={{backgroundColor: "green", flex: 0.25}}
                        onPress={() => {
                            setResizeMode("cover");
                        }}
                    >
                        cover
                    </Text>
                </View>
                <View
                    style={{
                        flexDirection: "row",
                        flexWrap: "wrap",
                        padding: 0,
                    }}
                >
                    <Text style={styles.title}>操作</Text>
                    <TextInput
                        style={styles.prop_input}
                        placeholder="input seek sec number:"
                        multiline={false}
                        maxLength={30}
                        keyboardType="numeric"
                        onChangeText={(text) => {
                            const newText = text.replace(/[^\d]+/, "");
                            setSeekSec(Number(newText));
                        }}
                        autoFocus={false}
                    />
                </View>
                <View
                    style={{
                        flexDirection: "row",
                        flexWrap: "wrap",
                        padding: 0,
                    }}
                >
                    <Text
                        style={styles.button_b}
                        onPress={() => {
                            videoRef.current?.seek(seekSec);
                        }}
                    >
                        seek:{seekSec.toString()}
                    </Text>
                    <Text
                        style={styles.button_b}
                        onPress={() => {
                            togglePaused();
                        }}
                    >
                        paused:{paused.toString()}
                    </Text>
                    <Text
                        style={styles.button_b}
                        onPress={() => {
                            toggleMuted();
                        }}
                    >
                        muted:{muted.toString()}
                    </Text>
                    <Text style={styles.button_b} onPress={() => {
                        toggleControls()
                    }}>controls:{controls.toString()}</Text>
                    <Text
                        style={styles.button_b}
                        onPress={() => {
                            toggleRepeat();
                        }}
                    >
                        repeat:{repeat.toString()}
                    </Text>
                    <Text
                        style={styles.button_b}
                        onPress={() => {
                            toggleDisableFocus();
                        }}
                    >
                        disableFocus:{disableFocus.toString()}
                    </Text>
                    <Text style={styles.button_b}>
                        ReSizeMode:{resizeMode.toString()}
                    </Text>
                </View>
                <RNCVideo
                    style={styles.video}
                    ref={videoRef}
                    source={{uri: uri, isNetwork: true}}
                    paused={paused}
                    muted={muted}
                    resizeMode={resizeMode}
                    controls={controls}
                    repeat={repeat}
                    volume={1}
                    disableFocus={disableFocus}
                    poster={
                        "https://res.vmallres.com/pimages/uomcdn/CN/pms/202304/sbom/4002010007801/group/800_800_9B1356F1330EADDCB20D35D2AE1F46E0.jpg"
                    }
                    posterResizeMode={posterResizeMode}
                    onLoad={(e) => {
                        setOnVideoLoad(
                            "onVideoLoad currentTime =" +
                            e.currentPosition +
                            "s duration =" +
                            e.duration +
                            "s width =" +
                            e.naturalSize.width +
                            " orientation =" +
                            e.naturalSize.orientation
                        );
                        setOnVideoError("onVideoError error = ok");
                    }}
                    onLoadStart={(e) => {
                        setOnVideoLoadStart(
                            "onVideoLoadStart isNetwork =" +
                            e.isNetwork +
                            " type=" +
                            e.type +
                            " uri=" +
                            e.uri
                        );
                    }}
                    onProgress={(e) => {
                        setOnVideoProgress(
                            "onVideoProgress currentTime =" +
                            e.currentTime +
                            " playableDuration=" +
                            e.playableDuration +
                            " seekableDuration=" +
                            e.seekableDuration
                        );
                    }}
                    onError={(e) => {
                        setOnVideoError("onVideoError error =" + e.error);
                    }}
                    onEnd={() => {
                        setOnVideoEnd("onVideoEnd completed");
                    }}
                    onBuffer={(e) => {
                        setOnVideoBuffer("onVideoBuffer :" + e.isBuffering);
                    }}
                    onPlaybackStalled={() => {
                        setOnPlaybackStalled("onPlaybackStalled : true");
                        setOnPlaybackResume("onPlaybackResume :false");
                    }}
                    onPlaybackResume={() => {
                        setOnPlaybackStalled("onPlaybackStalled :false");
                        setOnPlaybackResume("onPlaybackResume :true");
                    }}
                    onReadyForDisplay={() => {
                        console.log(`onReadyForDisplay :setShowPoster(false)`);
                    }}
                />
            </View>
        </ScrollView>
    );
}

const styles = StyleSheet.create({
    video: {
        width: "100%",
        height: 260,
    },
    container: {
        width: "100%",
        height: "100%",
        backgroundColor: "#f8f8f8",
    },
    title: {
        color: "white",
        width: "30%", // hack
        height: 30, // hack
    },
    label: {
        color: "gray",
        width: "100%", // hack
        minHeight: 20,
    },
    labelB: {
        color: "gray",
        width: "100%", // hack
        minHeight: 40,
    },
    button: {
        width: 160,
        height: 36,
        backgroundColor: "hsl(190, 50%, 70%)",
        paddingHorizontal: 16,
        paddingVertical: 8,
        borderRadius: 8,
    },
    buttonText: {
        width: "100%",
        height: "100%",
        fontWeight: "bold",
    },
    button_b: {
        paddingHorizontal: 8,
        paddingVertical: 6,
        borderRadius: 4,
        backgroundColor: "oldlace",
        alignSelf: "flex-start",
        marginHorizontal: "1%",
        marginBottom: 6,
        minWidth: "25%",
        minHeight: 20,
        textAlign: "center",
    },
    prop_input: {
        width: 160,
        height: 40,
        borderWidth: 1,
        backgroundColor: "white",
        color: "black",
        borderRadius: 8,
    },
});

export default RNCVideoDemo;
```

## Link

目前 HarmonyOS 暂不支持 AutoLink，所以 Link 步骤需要手动配置。

首先需要使用 DevEco Studio 打开项目里的 HarmonyOS 工程 `harmony`

### 1.在工程根目录的 `oh-package.json5` 添加 overrides 字段

```json
{
  ...
  "overrides": {
    "@rnoh/react-native-openharmony": "./react_native_openharmony"
  }
}
```

### 2.引入原生端代码

目前有两种方法：

1. 通过 har 包引入（在 IDE 完善相关功能后该方法会被遗弃，目前首选此方法）；
2. 直接链接源码。

方法一：通过 har 包引入（推荐）

> [!TIP] har 包位于三方库安装路径的 `harmony` 文件夹下。

打开 `entry/oh-package.json5`，添加以下依赖

```json
"dependencies": {
...
"@rnoh/react-native-openharmony": "file:../react_native_openharmony",
"@ohmi/react-native-video": "file:../../node_modules/@ohmi/react-native-video/harmony/rn_video.har"
}
```

点击右上角的 `sync` 按钮

或者在终端执行：

```bash
cd entry
ohpm install
```

方法二：直接链接源码

> [!TIP] 如需使用直接链接源码，请参考[直接链接源码说明](https://gitee.com/react-native-oh-library/usage-docs/blob/master/zh-cn/link-source-code.md)

### 3.配置 CMakeLists 和引入 RNCVideoPackage

打开 `entry/src/main/cpp/CMakeLists.txt`，添加：

```diff
project(rnapp)
cmake_minimum_required(VERSION 3.4.1)
set(CMAKE_SKIP_BUILD_RPATH TRUE)
set(RNOH_APP_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
set(NODE_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../node_modules")
+ set(OH_MODULES "${CMAKE_CURRENT_SOURCE_DIR}/../../../oh_modules")
set(RNOH_CPP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../react-native-harmony/harmony/cpp")
set(LOG_VERBOSITY_LEVEL 1)
set(CMAKE_ASM_FLAGS "-Wno-error=unused-command-line-argument -Qunused-arguments")
set(CMAKE_CXX_FLAGS "-fstack-protector-strong -Wl,-z,relro,-z,now,-z,noexecstack -s -fPIE -pie")
set(WITH_HITRACE_SYSTRACE 1) # for other CMakeLists.txt files to use
add_compile_definitions(WITH_HITRACE_SYSTRACE)

add_subdirectory("${RNOH_CPP_DIR}" ./rn)

# RNOH_BEGIN: manual_package_linking_1
add_subdirectory("../../../../sample_package/src/main/cpp" ./sample-package)
+ add_subdirectory("${OH_MODULES}/@ohmi/react-native-video/src/main/cpp" ./video)
# RNOH_BEGIN: manual_package_linking_1

file(GLOB GENERATED_CPP_FILES "./generated/*.cpp")

add_library(rnoh_app SHARED
    ${GENERATED_CPP_FILES}
    "./PackageProvider.cpp"
    "${RNOH_CPP_DIR}/RNOHAppNapiBridge.cpp"
)
target_link_libraries(rnoh_app PUBLIC rnoh)

# RNOH_BEGIN: manual_package_linking_2
target_link_libraries(rnoh_app PUBLIC rnoh_sample_package)
+ target_link_libraries(rnoh_app PUBLIC rnoh_video)
# RNOH_END: manual_package_linking_2
```

打开 `entry/src/main/cpp/PackageProvider.cpp`，添加：

```diff
#include "RNOH/PackageProvider.h"
#include "generated/RNOHGeneratedPackage.h"
#include "SamplePackage.h"
+ #include "RNCVideoPackage.h"

using namespace rnoh;

std::vector<std::shared_ptr<Package>> PackageProvider::getPackages(Package::Context ctx) {
    return {
      std::make_shared<RNOHGeneratedPackage>(ctx),
      std::make_shared<SamplePackage>(ctx),
+     std::make_shared<RNCVideoPackage>(ctx)
    };
}
```

### 4.在 ArkTs 侧引入 RNCVideo 组件

找到 **function buildCustomComponent()**，一般位于 `entry/src/main/ets/pages/index.ets` 或
`entry/src/main/ets/rn/LoadBundle.ets`，添加：

```diff
  ...
+ import { RNCVideo, RNC_VIDEO_TYPE } from "@ohmi/react-native-video"

@Builder
function buildCustomRNComponent(ctx: ComponentBuilderContext) {
  ...
+ if (ctx.componentName === RNC_VIDEO_TYPE) {
+   RNCVideo({
+     ctx: ctx.rnComponentContext,
+     tag: ctx.tag
+   })
+ }
 ...
}
...
```

> [!TIP] 本库使用了混合方案，需要添加组件名。

在`entry/src/main/ets/pages/index.ets` 或 `entry/src/main/ets/rn/LoadBundle.ets` 找到常量 `arkTsComponentNames`
在其数组里添加组件名

```diff
const arkTsComponentNames: Array<string> = [
  ...
+ RNC_VIDEO_TYPE
  ];
```

### 5.在 ArkTs 侧引入 RNCVideoPackage

打开 `entry/src/main/ets/RNPackagesFactory.ts`，添加：

```diff
  ...
+ import { RNCVideoPackage } from '@ohmi/react-native-video/ts';

export function createRNPackages(ctx: RNPackageContext): RNPackage[] {
  return [
    new SamplePackage(ctx),
+   new RNCVideoPackage(ctx)
  ];
}
```

### 6.运行

点击右上角的 `sync` 按钮

或者在终端执行：

```bash
cd entry
ohpm install
```

然后编译、运行即可。

## 兼容性

要使用此库，需要使用正确的 React-Native 和 RNOH 版本。另外，还需要使用配套的 DevEco Studio 和 手机 ROM。

请到三方库相应的 Releases 发布地址查看 Release
配套的版本信息：[@ohmi/react-native-video Releases](https://gitee.com/kunyuan-hongke/react-native-video/releases)

## 权限与要求

- 由于此库涉及后台播放，需要配置后台运行权限，权限需配置在entry/src/main目录下module.json5文件中。具体权限配置见文档：[程序访问控制](https://gitee.com/openharmony/docs/blob/master/zh-cn/application-dev/security/AccessToken/Readme-CN.md#/openharmony/docs/blob/master/zh-cn/application-dev/security/AccessToken/app-permission-mgmt-overview.md)。

- 此库后台播放功能需要添加权限：ohos.permission.KEEP_BACKGROUND_RUNNING，还需配置后台播放模式：

  ```
  "abilities": [
     {
      "backgroundModes": ["audioPlayback"]
     }
  ]
  ```

  

  

## 属性

详情请查看[react-native-video 官方文档](https://github.com/react-native-video/react-native-video/blob/support/5.2.X/README.md)

> [!TIP] "Platform"列表示该属性在原三方库上支持的平台。

> [!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性；no 则表示不支持；partially 表示部分支持。使用方法跨平台一致，效果对标
> iOS 或 Android 的效果。

| Name                                      | 描述                                                         | Type    | Required | Platform                                                 | HarmonyOS Support |
| ----------------------------------------- | ------------------------------------------------------------ | :------ | -------- | -------------------------------------------------------- | ----------------- |
| `disableFocus`                            | 确定视频音频是否应覆盖 Android 和 HarmonyOS 设备中的背景音乐/音频。<br/>**false (默认)** - 不覆盖背景音乐/音频 | boolean | No       | Android Exoplayer                                        | yes               |
| `muted`                                   | 控制音频是否静音。<br/>**false (默认)** - 不静音             | boolean | No       | All                                                      | yes               |
| `paused`                                  | 控制媒体是否暂停。<br/>**false (默认)** - 不暂停媒体         | boolean | No       | All                                                      | yes               |
| `repeat`                                  | 确定是否在视频结束时重复播放。<br/>**false (默认)** - 不重复播放 | boolean | No       | All                                                      | yes               |
| `resizeMode`                              | 确定当帧与原始视频尺寸不匹配时如何调整视频大小。<br/>**"none" (默认)** - 不应用调整大小 | string  | No       | Android ExoPlayer, Android MediaPlayer, iOS, Windows UWP | yes               |
| `volume`                                  | 调整音量。<br/>**1.0 (默认)** - 以全音量播放                 | number  | No       | All                                                      | yes               |
| `poster`                                  | 在视频加载时显示的图像<br/>值：包含海报 URL 的字符串，例如 "<https://baconmockup.com/300/200/>" | string  | No       | All                                                      | yes               |
| `posterResizeMode`                        | 确定当帧与原始视频尺寸不匹配时如何调整海报图像大小。<br/>**"contain" (默认)** - 按比例缩放图像（保持图像的宽高比），使得图像的宽度和高度都小于或等于视图的相应维度（减去内边距）。 | string  | No       | All                                                      | yes               |
| `allowsExternalPlayback`                  | 指示播放器是否允许切换到外部播放模式，例如 AirPlay 或 HDMI。 | boolean | No       | iOS                                                      | No                |
| `audioOnly`                               | 指示播放器是否仅播放音频轨道，并且显示海报而不是视频轨道。   | boolean | No       | All                                                      | No                |
| `onPlaybackStalled`                       | 缓冲开始时的回调，控制加载视图的显示                         | boolean | No       | Android                                                  | No                |
| `onPlaybackResume`                        | 缓存结束时的回调，控制加载视图的隐藏                         | boolean | No       | Android                                                  | No                |
| `automaticallyWaitsToMinimizeStalling`    | 布尔值，指示播放器是否应自动延迟播放以最小化卡顿。适用于链接到 iOS 10.0 及更高版本的客户端 | boolean | No       | iOS                                                      | No                |
| `bufferConfig`                            | 调整缓冲设置。此属性接受包含以下一个或多个属性的对象。       | object  | No       | Android                                                  | No                |
| `controls`                                | 确定是否显示播放器控件。                                     | boolean | No       | All                                                      | yes               |
| `currentPlaybackTime`                     | 当播放带有 EXT-X-PROGRAM-DATE-TIME 标签配置的 HLS 实时流时，此属性将包含以毫秒为单位的纪元值。 | string  | No       | All                                                      | No                |
| `filter`                                  | 添加视频滤镜                                                 | string  | No       | iOS                                                      | No                |
| `filterEnabled`                           | 启用视频滤镜。                                               | string  | No       | iOS                                                      | No                |
| `fullscreen`                              | 控制播放时是否进入全屏模式。                                 | boolean | No       | iOS                                                      | No                |
| `fullscreenAutorotate`                    | 如果设置了首选全屏方向，则会导致视频旋转到该方向，但允许用户持有的屏幕方向旋转。默认为 TRUE。 | boolean | No       | iOS                                                      | No                |
| `fullscreenOrientation`                   | 设置全屏方向。                                               | string  | No       | iOS                                                      | No                |
| `headers`                                 | 将标头传递给 HTTP 客户端。可用于授权。标头必须是源对象的一部分。 | object  | No       | Android                                                  | No                |
| `hideShutterView`                         | 控制是否启用 ExoPlayer 快门视图（加载时的黑屏）。            | boolean | No       | Android                                                  | No                |
| `id`                                      | 设置 DOM id 元素，以便可以在 Web 平台上使用 document.getElementById。接受字符串值。 | string  | No       | All                                                      | No                |
| `ignoreSilentSwitch`                      | 控制 iOS 静音开关的行为                                      | string  | No       | iOS                                                      | No                |
| `maxBitRate`                              | 设置网络带宽消耗的期望限制（以每秒比特数为单位），当播放列表中有多个视频流可用时。 | number  | No       | All                                                      | No                |
| `minLoadRetryCount`                       | 设置在失败并报告错误给应用程序之前最少重试加载数据的次数。有助于恢复短暂的互联网故障。 | number  | No       | Android                                                  | No                |
| `mixWithOthers`                           | 控制音频与其他应用程序的混合方式。                           | string  | No       | iOS                                                      | No                |
| `pictureInPicture`                        | 确定媒体是否作为画中画播放。                                 | boolean | No       | iOS                                                      | No                |
| `playInBackground`                        | 确定媒体是否在应用程序处于后台时继续播放。这允许客户继续收听音频。 | boolean | No       | All                                                      | yes               |
| `playWhenInactive`                        | 确定媒体是否在通知或控制中心位于视频前面时继续播放。         | boolean | No       | iOS                                                      | No                |
| `preferredForwardBufferDuration`          | 播放器在网络中缓冲媒体的时间长度，以防止播放中断。设置 AVPlayerItem 的 preferredForwardBufferDuration 实例属性。 | number  | No       | iOS                                                      | No                |
| `preventsDisplaySleepDuringVideoPlayback` | 控制在播放视频时是否允许显示器休眠。默认不允许显示器休眠。   | boolean | No       | All                                                      | No                |
| `progressUpdateInterval`                  | onProgress 事件之间的延迟，以毫秒为单位。                    | number  | No       | iOS                                                      | No                |
| `rate`                                    | 媒体播放的速度。                                             | number  | No       | All                                                      | yes               |
| `reportBandwidth`                         | 确定是否生成 onBandwidthUpdate 事件。这是由于 ExoPlayer 上这些事件的频率较高而需要的。 | boolean | No       | Android                                                  | No                |
| `selectedAudioTrack`                      | 配置要播放的音频轨道（如果有）。                             | object  | No       | All                                                      | No                |
| `selectedTextTrack`                       | 配置要显示的文本轨道（字幕或隐藏字幕）（如果有）。           | object  | No       | All                                                      | No                |
| `selectedVideoTrack`                      | 配置要播放的视频轨道。默认情况下，播放器使用自适应比特率流技术，根据可用带宽自动选择其认为性能最佳的流。 | object  | No       | Android                                                  | No                |
| `stereoPan`                               | 调整左右音频通道的平衡。接受 –1.0 到 1.0 之间的任何值。      | number  | No       | Android                                                  | No                |
| `textTracks`                              | 加载一个或多个“旁白”文本轨道。此属性接受代表每个轨道的对象数组。 | object  | No       | All                                                      | No                |
| `trackId`                                 | 配置视频流的标识符，以将播放上下文链接到发出的事件。         | string  | No       | Android                                                  | No                |
| `useTextureView`                          | 控制是否输出到 TextureView 或 SurfaceView。                  | boolean | No       | Android                                                  | No                |

## 事件回调

> [!TIP] "Platform"列表示该属性在原三方库上支持的平台。

> [!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性；no 则表示不支持；partially 表示部分支持。使用方法跨平台一致，效果对标
> iOS 或 Android 的效果。

| Name                                            | 描述                                                         | Type     | Required | Platform                                         | HarmonyOS Support |
| ----------------------------------------------- | ------------------------------------------------------------ | :------- | -------- | ------------------------------------------------ | ----------------- |
| `onLoad`                                        | 媒体加载并准备好播放时调用的回调函数。                       | function | No       | All                                              | yes               |
| `onLoadStart`                                   | 媒体开始加载时调用的回调函数。                               | function | No       | All                                              | yes               |
| `onReadyForDisplay`                             | 第一帧视频准备好显示时调用的回调函数。这就是移除海报的时候。 | function | No       | Android ExoPlayer, Android MediaPlayer, iOS, Web | yes               |
| `onProgress`                                    | 每隔 progressUpdateInterval 秒调用一次的回调函数，提供当前播放位置的信息。 | function | No       | All                                              | yes               |
| `onEnd`                                         | 播放器到达媒体结尾时调用的回调函数。                         | function | No       | All                                              | yes               |
| `onError`                                       | 播放器遇到播放错误时调用的回调函数。                         | function | No       | All                                              | yes               |
| `onBuffer`                                      | 播放器缓冲时调用的回调函数。                                 | function | No       | Android, iOS                                     | yes               |
| `onPlaybackStalled`                             | MediaPlayer MEDIA_INFO_BUFFERING_START 的回调函数            | function | No       | Android MediaPlayer                              | yes               |
| `onPlaybackResume`                              | MediaPlayer MEDIA_INFO_BUFFERING_END 的回调函数              | function | No       | Android MediaPlayer                              | yes               |
| `onAudioBecomingNoisy`                          | 当音频因音频输出变化即将变得“嘈杂”时调用的回调函数。通常是在音频输出从外部来源（如耳机）切换回内部扬声器时调用。在这种情况下，最好暂停媒体以免扬声器突然开始大声播放声音。 | function | No       | All                                              | No                |
| `onBandwidthUpdate`                             | 当可用带宽发生变化时调用的回调函数。                         | function | No       | Android                                          | No                |
| `onExternalPlaybackChange`                      | 当前播放视频的外部播放模式发生更改时调用的回调函数。主要用于连接/断开 Apple TV 时——在连接/断开时调用。 | function | No       | iOS                                              | No                |
| `onFullscreenPlayerWillPresent`                 | 播放器即将进入全屏模式时调用的回调函数。                     | function | No       | All                                              | No                |
| `onFullscreenPlayerDidPresent`                  | 播放器已进入全屏模式时调用的回调函数。                       | function | No       | All                                              | No                |
| `onFullscreenPlayerWillDismiss`                 | 播放器即将退出全屏模式时调用的回调函数。                     | function | No       | All                                              | No                |
| `onFullscreenPlayerDidDismiss`                  | 播放器已退出全屏模式时调用的回调函数。                       | function | No       | All                                              | No                |
| `onPictureInPictureStatusChanged`               | 当画中画变为活动或非活动状态时调用的回调函数。               | function | No       | IOS                                              | No                |
| `onPlaybackRateChange`                          | 播放速率更改时调用的回调函数——无论是暂停还是开始/恢复。      | function | No       | All                                              | yes               |
| `onSeek`                                        | 寻找完成时调用的回调函数。                                   | function | No       | All                                              | yes               |
| `onRestoreUserInterfaceForPictureInPictureStop` | 对应于 Apple 的 restoreUserInterfaceForPictureInPictureStopWithCompletionHandler。在此函数中调用 restoreUserInterfaceForPictureInPictureStopCompleted 以完成用户界面恢复。 | function | No       | iOS                                              | No                |
| `onTimedMetadata`                               | 当定时元数据可用时调用的回调函数                             | function | No       | All                                              | No                |

## 静态方法

> [!TIP] "Platform"列表示该属性在原三方库上支持的平台。

> [!TIP] "HarmonyOS Support"列为 yes 表示 HarmonyOS 平台支持该属性；no 则表示不支持；partially 表示部分支持。使用方法跨平台一致，效果对标
> iOS 或 Android 的效果。

| Name                                            | 描述                                                                                                                | Type     | Required | Platform | HarmonyOS Support |
|-------------------------------------------------|-------------------------------------------------------------------------------------------------------------------|----------|----------|----------|-------------------|
| `seek()`                                        | 跳转到由秒表示的指定位置。seconds 是一个浮点值。                                                                                      | function | No       | All      | yes               |
| `dismissFullscreenPlayer()`                     | 退出全屏模式。                                                                                                           | function | No       | All      | No                |
| `presentFullscreenPlayer()`                     | 进入全屏模式。                                                                                                           | function | No       | All      | No                |
| `save()`                                        | 使用当前滤镜属性将视频保存到照片中。返回 promise。                                                                                     | function | No       | iOS      | No                |
| `restoreUserInterfaceForPictureInPictureStop()` | 对应于 Apple 的恢复用户界面 ForPictureInPictureStop 完成处理程序。重要提示：必须在调用 onRestoreUserInterfaceForPictureInPictureStop 后调用此函数。 | function | No       | iOS      | No                |

## 遗留问题

- [x] source 暂时只支持在线 URL
  资源问题： [issue#34](https://github.com/react-native-oh-library/react-native-video/issues/34)。
- [x] react-native-video 部分属性和方法未实现 HarmonyOS
  化： [issue#60](https://github.com/react-native-oh-library/react-native-video/issues/60)。

## 其他

## 开源协议

本项目基于 [The MIT License (MIT)](https://github.com/TheWidlarzGroup/react-native-video/blob/master/LICENSE)
，请自由地享受和参与开源。