@mux/mux-react-native-player 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/MuxReactNativePlayer.podspec +37 -0
  2. package/README.md +134 -0
  3. package/android/build.gradle +33 -0
  4. package/android/src/main/AndroidManifest.xml +1 -0
  5. package/android/src/main/java/com/mux/reactnativeplayer/MuxReactNativePlayerModule.kt +135 -0
  6. package/android/src/main/java/com/mux/reactnativeplayer/MuxVideoRecords.kt +174 -0
  7. package/android/src/main/java/com/mux/reactnativeplayer/MuxVideoView.kt +452 -0
  8. package/android/src/main/res/layout/mux_video_player_view.xml +6 -0
  9. package/assets/MuxRobot_02.gif +0 -0
  10. package/assets/MuxRobot_02@2x.gif +0 -0
  11. package/assets/MuxRobot_03.gif +0 -0
  12. package/assets/MuxRobot_03@2x.gif +0 -0
  13. package/assets/MuxRobot_04.gif +0 -0
  14. package/assets/MuxRobot_04@2x.gif +0 -0
  15. package/assets/MuxRobot_05.gif +0 -0
  16. package/assets/MuxRobot_05@2x.gif +0 -0
  17. package/build/MuxVideoControls.d.ts +21 -0
  18. package/build/MuxVideoControls.d.ts.map +1 -0
  19. package/build/MuxVideoControls.js +1032 -0
  20. package/build/MuxVideoPlayer.d.ts +59 -0
  21. package/build/MuxVideoPlayer.d.ts.map +1 -0
  22. package/build/MuxVideoPlayer.js +265 -0
  23. package/build/MuxVideoView.d.ts +39 -0
  24. package/build/MuxVideoView.d.ts.map +1 -0
  25. package/build/MuxVideoView.js +254 -0
  26. package/build/NativeMuxVideoView.d.ts +5 -0
  27. package/build/NativeMuxVideoView.d.ts.map +1 -0
  28. package/build/NativeMuxVideoView.js +4 -0
  29. package/build/index.d.ts +6 -0
  30. package/build/index.d.ts.map +1 -0
  31. package/build/index.js +3 -0
  32. package/build/normalizeSource.d.ts +7 -0
  33. package/build/normalizeSource.d.ts.map +1 -0
  34. package/build/normalizeSource.js +76 -0
  35. package/build/screenOrientation.d.ts +3 -0
  36. package/build/screenOrientation.d.ts.map +1 -0
  37. package/build/screenOrientation.js +38 -0
  38. package/build/types.d.ts +170 -0
  39. package/build/types.d.ts.map +1 -0
  40. package/build/types.js +1 -0
  41. package/expo-module.config.json +13 -0
  42. package/ios/MuxReactNativePlayerModule.swift +139 -0
  43. package/ios/MuxVideoRecords.swift +212 -0
  44. package/ios/MuxVideoView.swift +502 -0
  45. package/package.json +69 -0
  46. package/plugin/index.d.ts +11 -0
  47. package/plugin/index.js +1 -0
  48. package/plugin/withMuxReactNativePlayer.js +203 -0
  49. package/src/MuxVideoControls.tsx +1772 -0
  50. package/src/MuxVideoPlayer.ts +338 -0
  51. package/src/MuxVideoView.tsx +412 -0
  52. package/src/NativeMuxVideoView.ts +15 -0
  53. package/src/index.ts +32 -0
  54. package/src/normalizeSource.ts +101 -0
  55. package/src/screenOrientation.ts +46 -0
  56. package/src/types.ts +228 -0
@@ -0,0 +1,37 @@
1
+ require 'json'
2
+
3
+ package = JSON.parse(File.read(File.join(__dir__, 'package.json')))
4
+
5
+ Pod::Spec.new do |s|
6
+ s.name = 'MuxReactNativePlayer'
7
+ s.version = package['version']
8
+ s.summary = package['description']
9
+ s.description = 'React Native bindings for Mux Player Swift and Mux Player Android.'
10
+ s.license = package['license']
11
+ s.author = 'Mux, Inc.'
12
+ s.homepage = 'https://github.com/muxinc/mux-react-native-player'
13
+ s.source = { :git => 'https://github.com/muxinc/mux-react-native-player.git', :tag => "#{s.version}" }
14
+ s.platforms = { :ios => '15.0' }
15
+ s.swift_version = '5.9'
16
+ s.static_framework = true
17
+
18
+ s.source_files = 'ios/*.{h,m,mm,swift}'
19
+ s.dependency 'ExpoModulesCore'
20
+
21
+ if respond_to?(:spm_dependency, true)
22
+ spm_dependency(
23
+ s,
24
+ url: 'https://github.com/muxinc/mux-player-swift.git',
25
+ requirement: {
26
+ kind: 'upToNextMajorVersion',
27
+ minimumVersion: '1.5.0'
28
+ },
29
+ products: ['MuxPlayerSwift']
30
+ )
31
+ else
32
+ raise <<~MSG
33
+ MuxReactNativePlayer requires React Native's spm_dependency Podfile helper to consume MuxPlayerSwift.
34
+ Upgrade React Native to 0.75 or newer, or add MuxPlayerSwift to the app target manually and patch this podspec.
35
+ MSG
36
+ end
37
+ end
package/README.md ADDED
@@ -0,0 +1,134 @@
1
+ # Mux React Native Player
2
+
3
+ <p align="center">
4
+ <img src="assets/player_screenshot.png" alt="Mux React Native Player" width="320" />
5
+ </p>
6
+
7
+ A React native video player with built-in Mux Data and Mux Robots integrations. Native playback is delegated to:
8
+
9
+ - iOS: [`muxinc/mux-player-swift`](https://github.com/muxinc/mux-player-swift)
10
+ - Android: [`muxinc/mux-player-android`](https://github.com/muxinc/mux-player-android)
11
+
12
+ ## Requirements
13
+
14
+ - React Native 0.75+ with Expo Modules
15
+ - iOS 15+, Android minSdk 23+
16
+
17
+ ## Install
18
+
19
+ ```sh
20
+ npm install @mux/mux-react-native-player
21
+ ```
22
+
23
+ ```json
24
+ {
25
+ "expo": {
26
+ "plugins": [
27
+ [
28
+ "@mux/mux-react-native-player/plugin",
29
+ { "enablePictureInPicture": true }
30
+ ]
31
+ ]
32
+ }
33
+ }
34
+ ```
35
+
36
+ ```sh
37
+ npx expo prebuild
38
+ npx expo run:ios
39
+ npx expo run:android
40
+ ```
41
+
42
+ ## Usage
43
+
44
+ ```tsx
45
+ import { MuxVideoView, useMuxVideoPlayer } from "@mux/mux-react-native-player";
46
+
47
+ export default function Player() {
48
+ const player = useMuxVideoPlayer({
49
+ playbackId: "qxb01i6T202018GFS02vp9RIe01icTcDCjVzQpmaB00CUisJ4",
50
+ metadata: {
51
+ envKey: "YOUR_MUX_DATA_ENV_KEY",
52
+ playerName: "MuxReactNativePlayerExample",
53
+ videoTitle: "Mux playback in React Native",
54
+ viewerUserId: "user-123",
55
+ },
56
+ });
57
+
58
+ return (
59
+ <MuxVideoView
60
+ player={player}
61
+ controls="custom"
62
+ contentFit="contain"
63
+ style={{ width: "100%", aspectRatio: 16 / 9, backgroundColor: "black" }}
64
+ />
65
+ );
66
+ }
67
+ ```
68
+
69
+ `controls`: `"native"` for platform controls, `"custom"` for the shared cross-platform JS controls, `"none"` for the bare video surface.
70
+
71
+ ## Source Options
72
+
73
+ ```ts
74
+ type MuxVideoSource = {
75
+ playbackId: string;
76
+ assetId?: string;
77
+ playbackToken?: string;
78
+ drmToken?: string;
79
+ customDomain?: string;
80
+ minResolution?: "480p" | "540p" | "720p" | "1080p" | "1440p" | "2160p";
81
+ maxResolution?: "720p" | "1080p" | "1440p" | "2160p";
82
+ renditionOrder?: "default" | "desc";
83
+ clipping?: { assetStartTime?: number; assetEndTime?: number };
84
+ metadata?: {
85
+ envKey?: string;
86
+ playerName?: string;
87
+ videoTitle?: string;
88
+ videoId?: string;
89
+ viewerUserId?: string;
90
+ customData?: Record<string, string>;
91
+ };
92
+ };
93
+ ```
94
+
95
+ - `metadata.envKey` enables Mux Data.
96
+ - `drmToken` requires `playbackToken`.
97
+ - The source is mutable — pass it from state and the player will reload when it changes.
98
+
99
+ ## Player Commands
100
+
101
+ ```ts
102
+ await player.play();
103
+ await player.pause();
104
+ await player.seekTo(12);
105
+ await player.seekBy(10);
106
+ await player.replay();
107
+ await player.setMuted(true);
108
+ await player.setVolume(0.5);
109
+ await player.setLoop(true);
110
+ await player.setPlaybackRate(1.25);
111
+ player.replace({ playbackId: "NEW_PLAYBACK_ID" });
112
+ await player.release();
113
+ ```
114
+
115
+ ## Mux Robots UI
116
+
117
+ When using `controls="custom"`, you can add Mux Robots buttons (summary, chapters, key moments). Keep `MUX_TOKEN_ID` / `MUX_TOKEN_SECRET` on a backend and pass callbacks:
118
+
119
+ ```tsx
120
+ <MuxVideoView
121
+ player={player}
122
+ controls="custom"
123
+ robots={{
124
+ onSummarize: ({ assetId }) =>
125
+ fetchJson("/mux/robots/summarize", { assetId }),
126
+ onGenerateChapters: ({ assetId }) =>
127
+ fetchJson("/mux/robots/chapters", { assetId }),
128
+ onFindKeyMoments: ({ assetId }) =>
129
+ fetchJson("/mux/robots/key-moments", { assetId }),
130
+ }}
131
+ />
132
+ ```
133
+
134
+ Buttons appear only for actions with a callback. Chapters render as timeline markers; key moments render as highlighted ranges. Selecting either seeks to its start time.
@@ -0,0 +1,33 @@
1
+ apply plugin: 'com.android.library'
2
+ apply plugin: 'org.jetbrains.kotlin.android'
3
+ apply plugin: 'expo-module-gradle-plugin'
4
+
5
+ group = 'com.mux.reactnativeplayer'
6
+ version = '0.1.0'
7
+
8
+ def safeExtGet(prop, fallback) {
9
+ rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
10
+ }
11
+
12
+ android {
13
+ namespace 'com.mux.reactnativeplayer'
14
+ compileSdkVersion safeExtGet('compileSdkVersion', 36)
15
+
16
+ defaultConfig {
17
+ minSdkVersion safeExtGet('minSdkVersion', 23)
18
+ targetSdkVersion safeExtGet('targetSdkVersion', 36)
19
+ versionCode 1
20
+ versionName "0.1.0"
21
+ }
22
+ }
23
+
24
+ repositories {
25
+ google()
26
+ mavenCentral()
27
+ maven { url = uri("https://muxinc.jfrog.io/artifactory/default-maven-release-local") }
28
+ }
29
+
30
+ dependencies {
31
+ implementation project(':expo-modules-core')
32
+ implementation "com.mux.player:android:1.5.4"
33
+ }
@@ -0,0 +1 @@
1
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
@@ -0,0 +1,135 @@
1
+ package com.mux.reactnativeplayer
2
+
3
+ import android.app.Activity
4
+ import android.content.pm.ActivityInfo
5
+ import expo.modules.kotlin.modules.Module
6
+ import expo.modules.kotlin.modules.ModuleDefinition
7
+
8
+ class MuxReactNativePlayerModule : Module() {
9
+ override fun definition() = ModuleDefinition {
10
+ Name("MuxReactNativePlayer")
11
+
12
+ AsyncFunction("lockFullscreenLandscape") {
13
+ runOnActivity { activity ->
14
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
15
+ }
16
+ }
17
+
18
+ AsyncFunction("unlockFullscreenOrientation") {
19
+ runOnActivity { activity ->
20
+ activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
21
+ }
22
+ }
23
+
24
+ View(MuxVideoView::class) {
25
+ OnViewDestroys { view: MuxVideoView ->
26
+ view.release()
27
+ }
28
+
29
+ Events(
30
+ "onStatusChange",
31
+ "onPlayingChange",
32
+ "onTimeUpdate",
33
+ "onSourceLoad",
34
+ "onSourceError"
35
+ )
36
+
37
+ Prop("source") { view: MuxVideoView, source: MuxVideoSourceRecord? ->
38
+ view.setSource(source)
39
+ }
40
+
41
+ Prop("nativeControls", true) { view: MuxVideoView, enabled: Boolean ->
42
+ view.setNativeControls(enabled)
43
+ }
44
+
45
+ Prop("contentFit", "contain") { view: MuxVideoView, contentFit: String ->
46
+ view.setContentFit(contentFit)
47
+ }
48
+
49
+ Prop("allowsFullscreen", true) { _: MuxVideoView, _: Boolean ->
50
+ // Android Media3 PlayerView exposes fullscreen through app UI; accepted for API parity.
51
+ }
52
+
53
+ Prop("allowsPictureInPicture", false) { _: MuxVideoView, _: Boolean ->
54
+ // PiP requires host Activity integration and is intentionally left to a future pass.
55
+ }
56
+
57
+ Prop("timeUpdateEventInterval", 0.5) { view: MuxVideoView, interval: Double ->
58
+ view.setTimeUpdateEventInterval(interval)
59
+ }
60
+
61
+ Prop("startupBufferDuration", 0.0) { view: MuxVideoView, duration: Double ->
62
+ view.setStartupBufferDuration(duration)
63
+ }
64
+
65
+ Prop("playWhenReady", false) { view: MuxVideoView, playWhenReady: Boolean ->
66
+ view.setPlayWhenReady(playWhenReady)
67
+ }
68
+
69
+ Prop("muted", false) { view: MuxVideoView, muted: Boolean ->
70
+ view.setMuted(muted)
71
+ }
72
+
73
+ Prop("volume", 1.0) { view: MuxVideoView, volume: Double ->
74
+ view.setVolume(volume)
75
+ }
76
+
77
+ Prop("loop", false) { view: MuxVideoView, loop: Boolean ->
78
+ view.setLoop(loop)
79
+ }
80
+
81
+ Prop("playbackRate", 1.0) { view: MuxVideoView, rate: Double ->
82
+ view.setPlaybackRate(rate)
83
+ }
84
+
85
+ AsyncFunction("play") { view: MuxVideoView ->
86
+ view.play()
87
+ }
88
+
89
+ AsyncFunction("pause") { view: MuxVideoView ->
90
+ view.pause()
91
+ }
92
+
93
+ AsyncFunction("replay") { view: MuxVideoView ->
94
+ view.replay()
95
+ }
96
+
97
+ AsyncFunction("seekBy") { view: MuxVideoView, seconds: Double ->
98
+ view.seekBy(seconds)
99
+ }
100
+
101
+ AsyncFunction("seekTo") { view: MuxVideoView, seconds: Double ->
102
+ view.seekTo(seconds)
103
+ }
104
+
105
+ AsyncFunction("setMuted") { view: MuxVideoView, muted: Boolean ->
106
+ view.setMuted(muted)
107
+ }
108
+
109
+ AsyncFunction("setVolume") { view: MuxVideoView, volume: Double ->
110
+ view.setVolume(volume)
111
+ }
112
+
113
+ AsyncFunction("setLoop") { view: MuxVideoView, loop: Boolean ->
114
+ view.setLoop(loop)
115
+ }
116
+
117
+ AsyncFunction("setPlaybackRate") { view: MuxVideoView, rate: Double ->
118
+ view.setPlaybackRate(rate)
119
+ }
120
+
121
+ AsyncFunction("setCaptionTrack") { view: MuxVideoView, trackId: String? ->
122
+ view.setCaptionTrack(trackId)
123
+ }
124
+
125
+ AsyncFunction("release") { view: MuxVideoView ->
126
+ view.release()
127
+ }
128
+ }
129
+ }
130
+
131
+ private fun runOnActivity(block: (Activity) -> Unit) {
132
+ val activity = appContext.activityProvider?.currentActivity ?: return
133
+ activity.runOnUiThread { block(activity) }
134
+ }
135
+ }
@@ -0,0 +1,174 @@
1
+ package com.mux.reactnativeplayer
2
+
3
+ import androidx.media3.common.MediaItem
4
+ import androidx.media3.common.MediaMetadata
5
+ import com.mux.player.media.MediaItems
6
+ import com.mux.player.media.PlaybackResolution
7
+ import com.mux.player.media.RenditionOrder
8
+ import com.mux.stats.sdk.core.model.CustomData
9
+ import com.mux.stats.sdk.core.model.CustomerData
10
+ import com.mux.stats.sdk.core.model.CustomerPlayerData
11
+ import com.mux.stats.sdk.core.model.CustomerVideoData
12
+ import expo.modules.kotlin.records.Field
13
+ import expo.modules.kotlin.records.Record
14
+
15
+ class MuxVideoClippingRecord : Record {
16
+ @Field
17
+ var assetStartTime: Double? = null
18
+
19
+ @Field
20
+ var assetEndTime: Double? = null
21
+ }
22
+
23
+ class MuxVideoMetadataRecord : Record {
24
+ @Field
25
+ var envKey: String? = null
26
+
27
+ @Field
28
+ var playerName: String? = null
29
+
30
+ @Field
31
+ var playerVersion: String? = null
32
+
33
+ @Field
34
+ var videoTitle: String? = null
35
+
36
+ @Field
37
+ var videoId: String? = null
38
+
39
+ @Field
40
+ var videoSeries: String? = null
41
+
42
+ @Field
43
+ var viewerUserId: String? = null
44
+
45
+ @Field
46
+ var customData: Map<String, String>? = null
47
+ }
48
+
49
+ class MuxVideoSourceRecord : Record {
50
+ @Field
51
+ var playbackId: String = ""
52
+
53
+ @Field
54
+ var playbackToken: String? = null
55
+
56
+ @Field
57
+ var drmToken: String? = null
58
+
59
+ @Field
60
+ var customDomain: String? = null
61
+
62
+ @Field
63
+ var minResolution: String? = null
64
+
65
+ @Field
66
+ var maxResolution: String? = null
67
+
68
+ @Field
69
+ var renditionOrder: String = "default"
70
+
71
+ @Field
72
+ var clipping: MuxVideoClippingRecord? = null
73
+
74
+ @Field
75
+ var metadata: MuxVideoMetadataRecord? = null
76
+
77
+ val fingerprint: String
78
+ get() = listOf(
79
+ playbackId,
80
+ playbackToken.orEmpty(),
81
+ drmToken.orEmpty(),
82
+ customDomain.orEmpty(),
83
+ minResolution.orEmpty(),
84
+ maxResolution.orEmpty(),
85
+ renditionOrder,
86
+ clipping?.assetStartTime?.toString().orEmpty(),
87
+ clipping?.assetEndTime?.toString().orEmpty(),
88
+ metadata?.envKey.orEmpty(),
89
+ metadata?.playerName.orEmpty(),
90
+ metadata?.playerVersion.orEmpty(),
91
+ metadata?.videoTitle.orEmpty(),
92
+ metadata?.videoId.orEmpty(),
93
+ metadata?.videoSeries.orEmpty(),
94
+ metadata?.viewerUserId.orEmpty(),
95
+ metadata?.customData?.toSortedMap()?.entries?.joinToString { "${it.key}:${it.value}" }.orEmpty(),
96
+ ).joinToString("|")
97
+
98
+ fun toMediaItem(): MediaItem {
99
+ val builder = MediaItems.builderFromMuxPlaybackId(
100
+ playbackId = playbackId,
101
+ maxResolution = maxResolution.toPlaybackResolution(),
102
+ minResolution = minResolution.toPlaybackResolution(),
103
+ renditionOrder = renditionOrder.toRenditionOrder(),
104
+ assetStartTime = clipping?.assetStartTime,
105
+ assetEndTime = clipping?.assetEndTime,
106
+ domain = customDomain,
107
+ playbackToken = playbackToken,
108
+ drmToken = drmToken,
109
+ )
110
+
111
+ metadata?.videoTitle?.let { title ->
112
+ builder.setMediaMetadata(
113
+ MediaMetadata.Builder()
114
+ .setTitle(title)
115
+ .build()
116
+ )
117
+ }
118
+
119
+ return builder.build()
120
+ }
121
+
122
+ fun toCustomerData(): CustomerData {
123
+ val metadata = metadata
124
+ return CustomerData().apply {
125
+ customerPlayerData = CustomerPlayerData().apply {
126
+ environmentKey = metadata?.envKey
127
+ playerName = metadata?.playerName ?: "MuxReactNativePlayer"
128
+ playerVersion = metadata?.playerVersion
129
+ viewerUserId = metadata?.viewerUserId
130
+ }
131
+ customerVideoData = CustomerVideoData().apply {
132
+ videoTitle = metadata?.videoTitle
133
+ videoId = metadata?.videoId
134
+ videoSeries = metadata?.videoSeries
135
+ }
136
+ customData = CustomData().apply {
137
+ applyCustomData(metadata?.customData)
138
+ }
139
+ }
140
+ }
141
+ }
142
+
143
+ private fun String?.toPlaybackResolution(): PlaybackResolution? {
144
+ return when (this) {
145
+ "480p" -> PlaybackResolution.LD_480
146
+ "540p" -> PlaybackResolution.LD_540
147
+ "720p" -> PlaybackResolution.HD_720
148
+ "1080p" -> PlaybackResolution.FHD_1080
149
+ "1440p" -> PlaybackResolution.QHD_1440
150
+ "2160p" -> PlaybackResolution.FOUR_K_2160
151
+ else -> null
152
+ }
153
+ }
154
+
155
+ private fun String?.toRenditionOrder(): RenditionOrder? {
156
+ return when (this) {
157
+ "desc" -> RenditionOrder.Descending
158
+ "default" -> RenditionOrder.Default
159
+ else -> null
160
+ }
161
+ }
162
+
163
+ private fun CustomData.applyCustomData(values: Map<String, String>?) {
164
+ customData1 = values?.get("customData1")
165
+ customData2 = values?.get("customData2")
166
+ customData3 = values?.get("customData3")
167
+ customData4 = values?.get("customData4")
168
+ customData5 = values?.get("customData5")
169
+ customData6 = values?.get("customData6")
170
+ customData7 = values?.get("customData7")
171
+ customData8 = values?.get("customData8")
172
+ customData9 = values?.get("customData9")
173
+ customData10 = values?.get("customData10")
174
+ }