@simba-dev / react-native-media-player
React Native video and audio playback for Android,由 libmpv. 一个引擎用于两个通道:音频的MediaSession和前台服务,视频的Picture-in-Picture。
该库呈现图片并公开状态和命令。它不绘制自己的控件-你的应用程序提供的chrome。
目录
这是什么?
Android上的React Native播放引擎,由libmpv支持。一个引擎同时提供音频和音频 视频中,两条车道在同一座桥上相遇:
- 一个活动。 标准库没有声明Activity。播放发生在启动器活动中
你的应用已经有;视频图片为
<SimbaVideo />在React树中。 - 一个音频和视频引擎。 音频文件通过相同的libmpv实例播放,没有 表面连接,所以没有第二个播放器维护。
- MediaSession和前台服务。 标题,艺术家和专辑到达锁定屏幕和 媒体通知;播放、暂停、停止、下一个、上一个和查找都被通知到会话,所以 耳机按钮和蓝牙控制。
- 画中画。 手动输入的需求,和操作系统级别的自动输入休假从Android 12。
- 类型的表面。 配置、状态、命令和钩子都是类型化的。
这不是什么
- 不是现成的替代品
react-native-video,expo-av或者react-native-track-player. 它们暴露了您所提供的组件source出现。这个有一个不同的形状:提供者,一个<SimbaVideo />照片,usePlayer()用于状态和命令。没有<Video source={…} />这里,没有适配器可以创建一个。 - 不是一个控制面。 自2.0.0以来,库根本没有提供任何控件-参见 控制.
- 不是iOS。
react-native.config.js集合ios: null;没有iOS实现。 - 不是传统的桥式图书馆。 本机包是一个
TurboReactPackage所以新 建筑学是必需的(newArchEnabled=true)。 - 而不是一个“克隆并构建它”的包。 预构建的libmpv二进制文件下载于 安装时间-见 安装.
要求
| React Native | >= 0.76.0 (同行依赖) |
| 反应 | >= 18.0.0 (同行依赖) |
| 安卓 | API 24+ (Android 7.0)。PiP输入调用 Activity.enterPictureInPictureMode(),这是在API 24中添加的。 compileSdk 和 targetSdk 是从你的应用程序继承的 rootProject.ext -图书馆没有固定它们。 |
| JDK | 17. 图书馆的Kotlin jvmTarget 是17岁。 |
| 新架构 | 必需的。 PlayerPackage 扩展 TurboReactPackage. |
安装
npm install @simba-dev/react-native-media-player
自动链接处理其余的:发货 react-native.config.js 告诉React Native的CLI where
Android模块还存在,所以 com.simba.player.PlayerPackage 登记在你的
MainApplication.kt 在下一个构建中。
本地二进制文件
这个 postinstall 脚本(scripts/download-native.js)下载预构建的本机库
(libmpv和朋友)从匹配的GitHub版本,验证存档的SHA-256和提取
它变成 android/src/main/jniLibs/<abi>/ 在已安装的包中。发布了四项abi:
arm64-v8a, armeabi-v7a, x86, x86_64.
这个脚本是幂等的——如果二进制文件已经存在,它会在没有网络调用的情况下退出;
所以重新运行 npm install 是安全的。集 SKIP_DOWNLOAD_NATIVE=1 完全跳过下载
(这就是发布工作流所做的,因为CI自己构建二进制文件)。
消费者设置
包自己的清单声明了一个前台服务和一个PiP操作接收器。它宣称没有 Activity:回放发生在你的应用已经拥有的Activity中。
画中画
你的启动器活动必须选择加入:
<!-- android/app/src/main/AndroidManifest.xml -->
<activity
android:name=".MainActivity"
android:configChanges="orientation|screenSize|screenLayout|keyboardHidden|navigation"
android:launchMode="singleTask"
android:supportsPictureInPicture="true"
android:resizeableActivity="true"
android:theme="@style/AppTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
resizeableActivity="true" 不是可选的。未调整大小的活动无法进入
画中画。
背景音频
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
在Android 13+ (API 33)上,应用程序必须保持 POST_NOTIFICATIONS 之前的媒体通知是
显示。 commands.requestNotificationPermission() 要求得到它;在旧版本中,它什么也不做。
快速启动
积分是 1个导入+ 1个包装+最多1个可选适配器.
// App.tsx
import React from 'react';
import {SimbaPlayer} from '@simba-dev/react-native-media-player';
export default function App() {
return (
<SimbaPlayer config={{audio: {backgroundPlayback: true}}}>
<RootNavigator />
</SimbaPlayer>
);
}
<SimbaPlayer> 组成 PlayerProvider (配置加上玩家状态)和
PlayerResumeProvider (查找简历)。它的道具是 config, resumePolicy 和
children.
玩的东西
import {usePlayerActivity} from '@simba-dev/react-native-media-player';
const {openPlayer} = usePlayerActivity();
await openPlayer({
uri: 'https://example.com/movie.mkv',
title: 'Movie',
type: 'video',
});
type: 'audio' 启动前台媒体服务。 type: 'video' 装载到已安装的
<SimbaVideo /> 和解决 false 当没有安装表面时-检查 commands.isSurfaceMounted()
如果你期望它能起作用,但它没有。
渲染图片
import {SimbaVideo} from '@simba-dev/react-native-media-player';
<SimbaVideo style={{flex: 1}} resizeMode="contain" />;
resizeMode 是 'contain' | 'cover' | 'stretch' 默认为 'contain'.
驱动它
import {usePlayer, usePlayerProgress} from '@simba-dev/react-native-media-player';
function Controls() {
const {state, commands} = usePlayer();
const {positionMs, durationMs} = usePlayerProgress();
return (
<View>
<Text>
{Math.floor(positionMs / 1000)} / {Math.floor(durationMs / 1000)}
</Text>
<Button
title={state.isPlaying ? 'Pause' : 'Play'}
onPress={commands.togglePlayPause}
/>
</View>
);
}
usePlayer() 返回 {state, commands}. usePlayerProgress() 返回进度对象。
直接(positionMs, durationMs, isBuffering, isSeeking, seekable, cacheRanges,
cacheFill)。
可选适配器:简历位置
<SimbaPlayer resumePolicy={(id) => bookmarks.byId[id]?.positionMs}>
<RootNavigator />
</SimbaPlayer>
resumePolicy 接收 resumeId 你传给 useOpenWithResume() 并返回已保存的
位置以毫秒为单位,或者 undefined 当没有的时候。
控制
库不绘制控件。 这是自2.0.0以来的合约。
<DefaultControls> -该包用来发布的默认控制包是 删除在
2.0.0. 导出不再存在,并且 <PlayerRoot> 不再退回到它。如果你是
使用它,在你的应用中保留你自己的副本或渲染你自己的控件;图书馆不提供
他们。
有两种方法来获得铬,你可以混合它们:
- 渲染你自己的组件 无论你想要什么,阅读吧
usePlayer(),usePlayerProgress()和useTheme(). 不需要其他任何东西。 - 使用
renderControls槽 在<PlayerProvider>. 函数返回要绘制的树 在图片上,和<PlayerRoot>将其放置在一个绝对定位的覆盖层中:
<PlayerProvider config={config} renderControls={() => <MyControls />}>
<PlayerRoot />
</PlayerProvider>
renderControls 不带参数——从组件内部的钩子中读取你需要的内容。
槽位是JS-only:它永远不会发送到本机。 useRenderControls() 返回槽位,
或者 null 当没有的时候。
<PlayerRoot> 呈现 <PlayerSurface /> -透明的 flex: 1 布局占位符-加上
覆盖。图片本身是 <SimbaVideo />; <PlayerSurface> 不是视频面。
配置
config 是一个 PlayerConfig. 每个字段都是可选的,并与默认值by合并
resolvePlayerConfig.
| 部分 | 字段 | 默认的 |
|---|---|---|
theme | accent, background, text, textSecondary, surface, icon? | #FFD700, #121216, #FFFFFF, rgba(255,255,255,0.6), rgba(255,255,255,0.1) |
pip | enabled, autoEnterOnLeave | true, true |
hardwareDecoding | 'auto' | 'mediacodec' | 'no' | 'auto' |
notifications | enabled, channelId | true, 'simba_player_media' |
audio | backgroundPlayback, respectAudioFocus | true, true |
subtitle | preferredLanguages, fontSize | [], 16 |
debug | verboseLogging | false |
import {PlayerProvider} from '@simba-dev/react-native-media-player';
<PlayerProvider
config={{
theme: {accent: '#00FF88'},
pip: {enabled: true, autoEnterOnLeave: true},
audio: {backgroundPlayback: true, respectAudioFocus: true},
hardwareDecoding: 'auto',
}}>
<App />
</PlayerProvider>;
解析后的配置将作为JSON推送到本机桥。原生端作用于PiP
段(驱动自动进入开关);其余部分是应用程序读取回的值
与 usePlayerConfig() 并适用于自己。 DEFAULT_PLAYER_CONFIG 和 DEFAULT_THEME 是
如果你想扩展和修补,可以导出。
画中画
人工输入:
commands.enterPip(); // chapter title and progress are optional arguments
commands.exitPip();
commands.exitPipAndFinish();
enterPip() 调用 enterPictureInPictureMode() 在当前Activity上,所以Activity必须
声明 android:supportsPictureInPicture="true" (见 消费者设置)。
休假时自动输入 是 config.pip.autoEnterOnLeave (默认 true). 它映射到
Android 12 / API 31平台特性 PictureInPictureParams.Builder.setAutoEnterEnabled:
当用户离开应用程序时,操作系统会为你进入Picture-in-Picture。 API 31下的平台
没有这样的功能 -操作系统不能在那里自动输入Picture-in-Picture,输入仍然是手动的
通过 commands.enterPip().
两个PiP标志结合在一起:自动输入需求 pip.enabled 和 pip.autoEnterOnLeave 都是
真实的。
Picture-in-Picture操作行有三个按钮: 播放/暂停, 扩大 和 关闭.
它们在JS中是 onPipPlayPause, onPipExpand 和 onPipClose 事件,在
onPipModeChanged.
媒体通知和锁定屏幕是一个单独的界面。它的MediaSession广告播放, 暂停,播放/暂停,停止,跳转到下一个,跳转到上一个和查找,与标题,艺术家和专辑 元数据。
背景音频
audio.backgroundPlayback 默认为 true:当应用程序处于后台时,音频继续播放,
通过前台媒体服务。相反,视频是“画中画”通道——它没有
继续在后台播放。
主题
import {useTheme} from '@simba-dev/react-native-media-player';
const theme = useTheme();
// {accent, background, text, textSecondary, surface, icon?}
API参考
组件
| 出口 | 道具 | 笔记 |
|---|---|---|
<SimbaPlayer> | config?, resumePolicy?, children | 一个导入集成点。组成 PlayerProvider + PlayerResumeProvider. resumePolicy: (itemId: string) => number | undefined. |
<PlayerProvider> | config?, renderControls?, children | 解析配置,实时播放器状态和控制槽。 |
<PlayerRoot> | launchParams? | 表面占位符加上控件覆盖。 launchParams 是一个 {uri, title, type, startPositionMs, requestId?} 有效载荷;它每加载一次 (uri, startPositionMs) 对。 |
<SimbaVideo> | resizeMode? +标准视图道具 | 这幅画。 resizeMode: 'contain' | 'cover' | 'stretch'、默认 'contain'. |
<PlayerSurface> | backgroundColor? | 一个透明的 flex: 1 布局占位符。不是视频表面。 |
钩子
| 出口 | 返回 |
|---|---|
usePlayer() | {state, commands} |
usePlayerProgress() | PlayerProgress - - - - - - positionMs, durationMs, isBuffering, isSeeking, seekable, cacheRanges, cacheFill |
usePlayerConfig() | 解析后的 ResolvedPlayerConfig. 抛出一个 <PlayerProvider>. |
useTheme() | 解析后的 PlayerTheme. |
useRenderControls() | 这个 renderControls 槽,或 null. |
usePlayerActivity() | {openPlayer(opts)} → Promise<boolean> |
useOpenWithResume() | (opts & {resumeId?}) => Promise<boolean> -通过最近的地址解析简历位置 <SimbaPlayer>的 resumePolicy. |
usePlayItem() | ({id, uri, title}, {type}) => Promise<boolean> |
useOpenFromUrl() | (uri: string) => Promise<boolean> - - - - - -接受 content:// 和 file:// uri,派生标题,对类型进行分类并打开它。 |
useOpenPlaylist() | {openPlaylist(entries, opts?)} → Promise<boolean> |
useLaunchPlayback() | (launchParams) -每次加载一次有效载荷 (uri, startPositionMs) 对。 |
useQueue() / useQueueLength() / useQueueItems() / useQueueItemsAs() | 队列存储和便利选择器。 |
usePlaybackHistory() / usePlaybackHistoryAs() | 回放历史记录。 |
useQueueSelection() / useQueueSelectedIndices() | 选择存储。 |
openPlayer 接受 {uri, title, type, startPositionMs?, artworkPath?, artist?, album?}。这个
艺术作品,艺术家和专辑字段只适用于音频通道。
队列存储的操作如下 addToQueue, prependToQueue, removeFromQueue,
removeFromQueueByIndex, reorderQueue, clearQueue, shuffleQueue, addToPlaybackHistory 和
clearPlaybackHistory.
captureFrame(uri, positionMs, options?) 返回一个 Promise<string | null> -一个JPEG为
档案中的那个位置。默认值为640×360,质量为80,导出为
DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT 和 DEFAULT_FRAME_QUALITY.
命令
commands 是一个稳定的物体;每个方法都在调用时解析桥。
| 集团 | 方法 |
|---|---|
| 运输 | play, pause, togglePlayPause, stop, stopPlayback, next, previous |
| 寻求 | seek(positionMs), seekBy(deltaMs), skipForward(seconds), skipBackward(seconds), seekToChapter(index) |
| 音量和速度 | setVolume(0..100), setMuted(boolean), toggleMute(), setSpeed(number), setLoopMode('none' | 'file' | 'playlist') |
| 播放列表 | loadFile(uri, title?), loadPlaylist(uris, startIndex?), playlistRemove(index), shuffle(), clear(), setShuffle(boolean) |
| 跟踪 | cycleTrack(type), setTrack(type, trackId) |
| 画中画 | enterPip(chapterTitle?, progressPct?), exitPip(), exitPipAndFinish() |
| 屏幕 | setKeepScreenOn(boolean), setOrientation(mode), setImmersive(boolean), setScreenBrightness(0..1), getScreenBrightness() |
| 表面 | setVideoBounds(x, y, width, height), fillVideoBounds(), setSurfaceVisible(boolean), isSurfaceMounted() |
| 过滤器 | setAudioFilter(name, enabled), setVideoFilter(name, enabled) |
| 通知 | requestNotificationPermission() |
| 商务性质 | getProperty(name), setProperty(name, value), observeProperty(name), unobserveProperty(name) |
| 内容uri | grantPersistablePermission(uri), verifyContentUri(uri) |
桥
getMpvPlayerModule() 返回本机模块,或者在本机端为时返回无操作的替身
没有连线(玩笑,网页预览),所以消费者代码保持可渲染。
subscribePlayerEvent(name, handler) 订阅26个命名事件之一并返回一个
退订功能; removeAllListeners(), setDebugLogging() 和
dumpObservedProperties() 也被导出。事件名称的类型为 PlayerEventName.
状态
state 携带 isPlaying, isLoading, title, artist, album, positionMs, durationMs,
isBuffering, isSeeking, seekable, volume, isMuted, speed, loopMode, playlist,
currentIndex, tracks, chapters, currentChapter, videoParams, error 和 shuffle.
故障排除
openPlayer 解决了 false 看视频。 没有安装表面。渲染 <SimbaVideo />
启动前,检查 commands.isSurfaceMounted().
Picture-in-Picture不会启动。 活动缺失
android:supportsPictureInPicture="true" 或者 android:resizeableActivity="true". Auto-enter上
另留需API 31+ -低于此,自行输入PiP与 commands.enterPip().
Gradle失败了 libmpv.so not found at android/src/main/jniLibs/…. 的postinstall
下载未运行。重新运行 npm install 在app中,或者手动运行脚本; SKIP_DOWNLOAD_NATIVE=1
设置在您的环境或CI中的某个位置。
NativeModules.MpvPlayerModule 是 undefined 在运行时。 新架构是必需的。
检查 newArchEnabled=true 在应用程序中 android/gradle.properties,并且该应用程序是React
本机Gradle插件运行 autolinkLibrariesWithApp() -应用库自己的构建文件
com.facebook.react 对于管道。
Android 13+上没有媒体通知。 该应用程序尚未获得批准 POST_NOTIFICATIONS.
呼叫 commands.requestNotificationPermission().
限制
- Android。 没有iOS实现。
- 没有控制。 图书馆没有船;看到 控制.
- 自动输入图中图需要Android 12 (API 31)或更新版本。 低于这个值操作系统就不能 这样做,进入是手动的。
- 大多数配置部分都是在JavaScript中使用的。 本地端接收解析后的
配置为JSON并作用于PiP自动输入开关;其余的是应用程序读取回的值
通过
usePlayerConfig(). notifications.channelId不是天生的。 前台服务创建自己的服务 通知渠道,simba_player_media_playback.- 捆绑的本机二进制文件有自己的许可证。 它们是由mpv-android制造的 libmpv的默认版本是GPLv2+),所以将它们链接到 你的应用是你自己的授权决定。库自己的JavaScript和Kotlin代码是MIT。
贡献
从存储库根目录:
npm install # postinstall downloads the native binaries
npm run typecheck # tsc --noEmit
npm test # jest
npm run build # tsc
npm run lint # placeholder — no linter is configured yet
有一个 example/ 应用程序与 npm run android, npm start 和 npm run typecheck. 它的
源代码仍然以2.0.0之前的API为目标(它们导入已删除的 DefaultControls 和演示
屏幕不再存在),所以它目前不进行类型检查——它是一个正在进行的工作,而不是
参考实现。
释放是由 release.yml 工作流:它从固定库构建本地库
mpv-android release,将它们附加到GitHub release,并发布到npm。请勿发表于
的手。