@simba-dev/react-native-media-player

中文文档
3.0.0发布于 2026/11/02MITTypeScriptCommonJS

React Native video & audio player powered by libmpv — Picture-in-Picture, MediaSession, foreground service, and a customizable TypeScript UI. Built for Android. Designed as a drop-in replacement for react-native-video / react-native-track-player / expo-av

原始使用文档
文档目录29 节
  1. Contents
  2. What this is
  3. What this is not
  4. Requirements
  5. Installation
  6. The native binaries
  7. Consumer setup
  8. Picture-in-Picture
  9. Background audio
  10. Quick start
  11. Play something
  12. Render the picture
  13. Drive it
  14. Optional adapter: resume positions
  15. Controls
  16. Configuration
  17. Picture-in-Picture
  18. Background audio
  19. Theming
  20. API reference
  21. Components
  22. Hooks
  23. Commands
  24. Bridge
  25. State
  26. Troubleshooting
  27. Limitations
  28. Contributing
  29. License

@simba-dev/react-native-media-player

React Native video and audio playback for Android, backed by libmpv. One engine for both lanes: a MediaSession and a foreground service for audio, Picture-in-Picture for video.

The library renders the picture and exposes state and commands. It draws no controls of its own — your app supplies the chrome.

Simba Player hero image — Android video player UI mockup

npm version GitHub release License: MIT npm downloads GitHub tags Sponsor Buy Me a Coffee CI Release

Releases  ·  CHANGELOG  ·  npm package  ·  Tags

Contents

  1. What this is
  2. What this is not
  3. Requirements
  4. Installation
  5. Consumer setup
  6. Quick start
  7. Controls
  8. Configuration
  9. Picture-in-Picture
  10. Background audio
  11. Theming
  12. API reference
  13. Troubleshooting
  14. Limitations
  15. Contributing
  16. License

What this is

A playback engine for React Native on Android, backed by libmpv. One engine serves both audio and video, and the two lanes meet in the same bridge:

  • One Activity. The library declares no Activity. Playback happens inside the launcher Activity your app already has; the video picture is <SimbaVideo /> in your React tree.
  • One engine for audio and video. Audio files play through the same libmpv instance with no surface attached, so there is no second player to maintain.
  • MediaSession and foreground service. Title, artist and album reach the lock screen and the media notification; play, pause, stop, next, previous and seek are advertised to the session, so headset buttons and Bluetooth controls work.
  • Picture-in-Picture. Manual entry on demand, and OS-level auto-enter on leave from Android 12.
  • Typed surface. Config, state, commands and hooks are all typed.

What this is not

  • Not a drop-in replacement for react-native-video, expo-av or react-native-track-player. Those expose a component you hand a source to. This one has a different shape: a provider, a <SimbaVideo /> picture, and usePlayer() for state and commands. There is no <Video source={…} /> here, and no adapter will make one.
  • Not a control surface. Since 2.0.0 the library ships no controls at all — see Controls.
  • Not iOS. react-native.config.js sets ios: null; there is no iOS implementation.
  • Not a legacy-bridge library. The native package is a TurboReactPackage, so the New Architecture is required (newArchEnabled=true).
  • Not a "clone the repo and build it" package. The prebuilt libmpv binaries are downloaded at install time — see Installation.

Requirements

React Native>= 0.76.0 (peer dependency)
React>= 18.0.0 (peer dependency)
AndroidAPI 24+ (Android 7.0). PiP entry calls Activity.enterPictureInPictureMode(), which was added in API 24. compileSdk and targetSdk are inherited from your app's rootProject.ext — the library does not pin them.
JDK17. The library's Kotlin jvmTarget is 17.
New ArchitectureRequired. PlayerPackage extends TurboReactPackage.

Installation

npm install @simba-dev/react-native-media-player

Autolinking handles the rest: the shipped react-native.config.js tells React Native's CLI where the Android module lives, so com.simba.player.PlayerPackage is registered in your MainApplication.kt on the next build.

The native binaries

The postinstall script (scripts/download-native.js) downloads the prebuilt native libraries (libmpv and friends) from the matching GitHub release, verifies the archive's SHA-256 and extracts it into android/src/main/jniLibs/<abi>/ inside the installed package. Four ABIs are published: arm64-v8a, armeabi-v7a, x86, x86_64.

The script is idempotent — if the binaries are already in place it exits without a network call, so re-running npm install is safe. Set SKIP_DOWNLOAD_NATIVE=1 to skip the download entirely (this is what the release workflow does, because CI builds the binaries itself).

Consumer setup

The package's own manifest declares a foreground service and a PiP action receiver. It declares no Activity: playback happens in the Activity your app already has.

Picture-in-Picture

Your launcher Activity has to opt in:

<!-- 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" is not optional. An Activity that is not resizeable cannot enter Picture-in-Picture.

Background audio

<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" />

On Android 13+ (API 33) the app must hold POST_NOTIFICATIONS before the media notification is shown. commands.requestNotificationPermission() asks for it; on older versions it does nothing.

Quick start

The integration is 1 import + 1 wrapper + at most 1 optional adapter.

// 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> composes PlayerProvider (config plus live player state) and PlayerResumeProvider (the resume lookup). Its props are config, resumePolicy and children.

Play something

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' starts the foreground media service. type: 'video' loads into the mounted <SimbaVideo /> and resolves false when no surface is mounted — check commands.isSurfaceMounted() if you expected it to work and it did not.

Render the picture

import {SimbaVideo} from '@simba-dev/react-native-media-player';

<SimbaVideo style={{flex: 1}} resizeMode="contain" />;

resizeMode is 'contain' | 'cover' | 'stretch' and defaults to 'contain'.

Drive it

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() returns {state, commands}. usePlayerProgress() returns the progress object directly (positionMs, durationMs, isBuffering, isSeeking, seekable, cacheRanges, cacheFill).

Optional adapter: resume positions

<SimbaPlayer resumePolicy={(id) => bookmarks.byId[id]?.positionMs}>
  <RootNavigator />
</SimbaPlayer>

resumePolicy receives the resumeId you pass to useOpenWithResume() and returns a saved position in milliseconds, or undefined when there is none.

Controls

The library draws no controls. That has been the contract since 2.0.0.

<DefaultControls> — the default control bundle the package used to ship — was removed in 2.0.0. The export no longer exists, and <PlayerRoot> no longer falls back to it. If you were using it, keep your own copy in your app or render your own controls; the library will not supply them.

There are two ways to have chrome, and you can mix them:

  1. Render your own component wherever you want it, reading usePlayer(), usePlayerProgress() and useTheme(). Nothing else is needed.
  2. Use the renderControls slot on <PlayerProvider>. The function returns the tree to draw over the picture, and <PlayerRoot> positions it in an absolutely positioned overlay:
<PlayerProvider config={config} renderControls={() => <MyControls />}>
  <PlayerRoot />
</PlayerProvider>

renderControls takes no arguments — read what you need from the hooks inside your component. The slot is JS-only: it is never sent to the native side. useRenderControls() returns the slot, or null when there is none.

<PlayerRoot> renders <PlayerSurface /> — a transparent flex: 1 layout placeholder — plus the overlay. The picture itself is <SimbaVideo />; <PlayerSurface> is not the video surface.

Configuration

config is a PlayerConfig. Every field is optional and merged with the defaults by resolvePlayerConfig.

SectionFieldsDefault
themeaccent, background, text, textSecondary, surface, icon?#FFD700, #121216, #FFFFFF, rgba(255,255,255,0.6), rgba(255,255,255,0.1)
pipenabled, autoEnterOnLeavetrue, true
hardwareDecoding'auto' | 'mediacodec' | 'no''auto'
notificationsenabled, channelIdtrue, 'simba_player_media'
audiobackgroundPlayback, respectAudioFocustrue, true
subtitlepreferredLanguages, fontSize[], 16
debugverboseLoggingfalse
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>;

The resolved config is pushed to the native bridge as JSON. The native side acts on the PiP section (it drives the auto-enter switch); the remaining sections are values your app reads back with usePlayerConfig() and applies itself. DEFAULT_PLAYER_CONFIG and DEFAULT_THEME are exported if you would rather spread and patch.

Picture-in-Picture

Manual entry:

commands.enterPip();          // chapter title and progress are optional arguments
commands.exitPip();
commands.exitPipAndFinish();

enterPip() calls enterPictureInPictureMode() on the current Activity, so the Activity has to declare android:supportsPictureInPicture="true" (see Consumer setup).

Auto-enter on leave is config.pip.autoEnterOnLeave (default true). It maps onto the Android 12 / API 31 platform feature PictureInPictureParams.Builder.setAutoEnterEnabled: the OS enters Picture-in-Picture for you when the user leaves the app. Below API 31 the platform has no such feature — the OS cannot auto-enter Picture-in-Picture there, and entry stays manual through commands.enterPip().

The two PiP flags combine: auto-enter needs pip.enabled and pip.autoEnterOnLeave to both be true.

The Picture-in-Picture action row has three buttons: Play/Pause, Expand and Close. They arrive in JS as the onPipPlayPause, onPipExpand and onPipClose events, next to onPipModeChanged.

The media notification and lock screen are a separate surface. Its MediaSession advertises play, pause, play/pause, stop, skip-to-next, skip-to-previous and seek-to, with title, artist and album metadata.

Background audio

audio.backgroundPlayback defaults to true: audio keeps playing when the app is backgrounded, through the foreground media service. Video is the Picture-in-Picture lane instead — it does not keep playing in the background.

Theming

import {useTheme} from '@simba-dev/react-native-media-player';

const theme = useTheme();
// {accent, background, text, textSecondary, surface, icon?}

API reference

Components

ExportPropsNotes
<SimbaPlayer>config?, resumePolicy?, childrenThe one-import integration point. Composes PlayerProvider + PlayerResumeProvider. resumePolicy: (itemId: string) => number | undefined.
<PlayerProvider>config?, renderControls?, childrenResolved config, live player state, and the controls slot.
<PlayerRoot>launchParams?The surface placeholder plus the controls overlay. launchParams is a {uri, title, type, startPositionMs, requestId?} payload; it is loaded once per (uri, startPositionMs) pair.
<SimbaVideo>resizeMode? + standard view propsThe picture. resizeMode: 'contain' | 'cover' | 'stretch', default 'contain'.
<PlayerSurface>backgroundColor?A transparent flex: 1 layout placeholder. Not the video surface.

Hooks

ExportReturns
usePlayer(){state, commands}
usePlayerProgress()PlayerProgress — positionMs, durationMs, isBuffering, isSeeking, seekable, cacheRanges, cacheFill
usePlayerConfig()The resolved ResolvedPlayerConfig. Throws outside a <PlayerProvider>.
useTheme()The resolved PlayerTheme.
useRenderControls()The renderControls slot, or null.
usePlayerActivity(){openPlayer(opts)} → Promise<boolean>
useOpenWithResume()(opts & {resumeId?}) => Promise<boolean> — resolves the resume position through the nearest <SimbaPlayer>'s resumePolicy.
usePlayItem()({id, uri, title}, {type}) => Promise<boolean>
useOpenFromUrl()(uri: string) => Promise<boolean> — accepts content:// and file:// URIs, derives a title, classifies the type and opens it.
useOpenPlaylist(){openPlaylist(entries, opts?)} → Promise<boolean>
useLaunchPlayback()(launchParams) — loads a payload once per (uri, startPositionMs) pair.
useQueue() / useQueueLength() / useQueueItems() / useQueueItemsAs()The queue store and convenience selectors.
usePlaybackHistory() / usePlaybackHistoryAs()The playback history.
useQueueSelection() / useQueueSelectedIndices()The selection store.

openPlayer accepts {uri, title, type, startPositionMs?, artworkPath?, artist?, album?}. The artwork, artist and album fields apply to the audio lane only.

The queue store's actions are addToQueue, prependToQueue, removeFromQueue, removeFromQueueByIndex, reorderQueue, clearQueue, shuffleQueue, addToPlaybackHistory and clearPlaybackHistory.

captureFrame(uri, positionMs, options?) returns a Promise<string | null> — a JPEG written for that position in that file. The defaults are 640×360 at quality 80, exported as DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT and DEFAULT_FRAME_QUALITY.

Commands

commands is a stable object; every method resolves the bridge at call time.

GroupMethods
Transportplay, pause, togglePlayPause, stop, stopPlayback, next, previous
Seekingseek(positionMs), seekBy(deltaMs), skipForward(seconds), skipBackward(seconds), seekToChapter(index)
Volume and speedsetVolume(0..100), setMuted(boolean), toggleMute(), setSpeed(number), setLoopMode('none' | 'file' | 'playlist')
PlaylistloadFile(uri, title?), loadPlaylist(uris, startIndex?), playlistRemove(index), shuffle(), clear(), setShuffle(boolean)
TrackscycleTrack(type), setTrack(type, trackId)
Picture-in-PictureenterPip(chapterTitle?, progressPct?), exitPip(), exitPipAndFinish()
ScreensetKeepScreenOn(boolean), setOrientation(mode), setImmersive(boolean), setScreenBrightness(0..1), getScreenBrightness()
SurfacesetVideoBounds(x, y, width, height), fillVideoBounds(), setSurfaceVisible(boolean), isSurfaceMounted()
FilterssetAudioFilter(name, enabled), setVideoFilter(name, enabled)
NotificationsrequestNotificationPermission()
mpv propertiesgetProperty(name), setProperty(name, value), observeProperty(name), unobserveProperty(name)
Content URIsgrantPersistablePermission(uri), verifyContentUri(uri)

Bridge

getMpvPlayerModule() returns the native module, or a no-op stand-in when the native side is not wired (Jest, web previews) so consumer code stays renderable. subscribePlayerEvent(name, handler) subscribes to one of the 26 named events and returns an unsubscribe function; removeAllListeners(), setDebugLogging() and dumpObservedProperties() are also exported. The event names are typed as PlayerEventName.

State

state carries isPlaying, isLoading, title, artist, album, positionMs, durationMs, isBuffering, isSeeking, seekable, volume, isMuted, speed, loopMode, playlist, currentIndex, tracks, chapters, currentChapter, videoParams, error and shuffle.

Troubleshooting

openPlayer resolves false for a video. No surface is mounted. Render <SimbaVideo /> before launching, and check commands.isSurfaceMounted().

Picture-in-Picture does not start. The Activity is missing android:supportsPictureInPicture="true" or android:resizeableActivity="true". Auto-enter on leave additionally needs API 31+ — below that, enter PiP yourself with commands.enterPip().

Gradle fails with libmpv.so not found at android/src/main/jniLibs/…. The postinstall download did not run. Re-run npm install in the app, or run the script by hand; SKIP_DOWNLOAD_NATIVE=1 is set somewhere in your environment or CI.

NativeModules.MpvPlayerModule is undefined at runtime. The New Architecture is required. Check newArchEnabled=true in the app's android/gradle.properties, and that the app's React Native Gradle plugin runs autolinkLibrariesWithApp() — the library's own build file applies com.facebook.react for that pipeline.

No media notification on Android 13+. The app has not been granted POST_NOTIFICATIONS. Call commands.requestNotificationPermission().

Limitations

  • Android only. There is no iOS implementation.
  • No controls. The library ships none; see Controls.
  • Auto-enter Picture-in-Picture needs Android 12 (API 31) or newer. Below that the OS cannot do it and entry is manual.
  • Most config sections are consumed in JavaScript. The native side receives the resolved config as JSON and acts on the PiP auto-enter switch; the rest are values your app reads back through usePlayerConfig().
  • notifications.channelId is not read natively. The foreground service creates its own notification channel, simba_player_media_playback.
  • The bundled native binaries carry their own licence. They are built by the mpv-android project and are subject to its terms (libmpv's default build is GPLv2+), so linking them into your app is a licensing decision you own. The library's own JavaScript and Kotlin code is MIT.

Contributing

From the repository root:

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

There is an example/ app with npm run android, npm start and npm run typecheck. Its sources still target the pre-2.0.0 API (they import the removed DefaultControls and demo screens that no longer exist), so it does not currently typecheck — it is a work in progress, not a reference implementation.

Releases are cut by the release.yml workflow: it builds the native libraries from the pinned mpv-android release, attaches them to the GitHub Release, and publishes to npm. Do not publish by hand.

License

MIT — see LICENSE. The prebuilt native binaries are third-party builds of mpv and carry their own licence terms; see Limitations.

版本与发布记录

@simba-dev/react-native-media-player 共发布 63 个版本,首次发布于 2026/09/03;过去 12 个月发布了 63 个版本。

版本发布日期距今
3.0.0latest2026/11/021 天前
2.1.02026/10/304 天前
2.0.12026/10/304 天前
2.0.02026/10/294 天前
1.23.02026/10/276 天前
1.22.02026/10/2112 天前

其他发布通道:staging → 1.5.3。

关于 @simba-dev/react-native-media-player 的常见问题

@simba-dev/react-native-media-player 是什么?

React Native视频和音频播放器,由libmpv - Picture-in-Picture, MediaSession,前台服务和可定制的TypeScript UI提供支持。为Android打造。设计为一个插入式替代反应原生视频/反应原生轨道播放器/博览会av

如何安装 @simba-dev/react-native-media-player?

在项目目录运行 npm install @simba-dev/react-native-media-player,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:react(>=18.0.0)、react-native(>=0.76.0)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@simba-dev/react-native-media-player 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@simba-dev/react-native-media-player 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@simba-dev/react-native-media-player 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@simba-dev/react-native-media-player 可以商用吗?

@simba-dev/react-native-media-player 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@simba-dev/react-native-media-player 还在维护吗?

最新版本 3.0.0 发布于 2026/11/02(1 天前);至今共发布 63 个版本,其中过去 12 个月发布了 63 个;首次发布于 2026/09/03。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。