react-native-media3-player 2.0.0 → 2.1.1
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.
- package/README.md +80 -6
- package/android/src/main/java/com/rnmedia3playerdemo/Media3PlayerView.kt +137 -3
- package/android/src/main/java/com/rnmedia3playerdemo/Media3PlayerViewManager.kt +2 -1
- package/package.json +1 -1
- package/src/Media3PlayerNativeComponent.ts +2 -1
- package/android/src/main/res/mipmap-hdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-hdpi/ic_launcher_round.png +0 -0
- package/android/src/main/res/mipmap-mdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-mdpi/ic_launcher_round.png +0 -0
- package/android/src/main/res/mipmap-xhdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xhdpi/ic_launcher_round.png +0 -0
- package/android/src/main/res/mipmap-xxhdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png +0 -0
- package/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png +0 -0
- package/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png +0 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 📽️ Media3Player
|
|
2
2
|
|
|
3
|
-
> A lightweight, high-performance React Native video player powered by Android Media3 (ExoPlayer). Fully customizable
|
|
3
|
+
> A lightweight, high-performance React Native video player powered by Android Media3 (ExoPlayer). Fully customizable with support for autoplay, mute, event handling, and automatic stream type detection (DASH, HLS, MP4). This is an **Android** only package.
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
- [Installation](#installation)
|
|
10
10
|
- [Usage](#usage)
|
|
11
11
|
- [Props](#props)
|
|
12
|
+
- [Stream Type Detection](#stream-type-detection)
|
|
12
13
|
- [DRM Support (Widevine)](#drm-support-widevine)
|
|
13
14
|
- [Events](#events)
|
|
14
15
|
- [TypeScript Support](#typescript-support)
|
|
@@ -92,6 +93,7 @@ export default function App() {
|
|
|
92
93
|
style={{width: '100%', height: 250}}
|
|
93
94
|
source={{
|
|
94
95
|
uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd',
|
|
96
|
+
type: 'dash',
|
|
95
97
|
drm: {
|
|
96
98
|
licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
|
|
97
99
|
},
|
|
@@ -121,10 +123,11 @@ export default function App() {
|
|
|
121
123
|
|
|
122
124
|
### `Source` Object
|
|
123
125
|
|
|
124
|
-
| Property | Type
|
|
125
|
-
| -------- |
|
|
126
|
-
| `uri` | `string`
|
|
127
|
-
| `
|
|
126
|
+
| Property | Type | Required | Description |
|
|
127
|
+
| -------- | ---------------------------- | -------- | ------------------------------------------------------------------------------------------------------------ |
|
|
128
|
+
| `uri` | `string` | Yes | The URI of the media to play (MP4, DASH, HLS, etc.). |
|
|
129
|
+
| `type` | `'dash' \| 'hls' \| 'mp4'` | No | Explicitly set the stream type. Defaults to `'mp4'`. If omitted, the player auto-detects the type from the URI. |
|
|
130
|
+
| `drm` | `DRMConfig` | No | DRM configuration for protected content. |
|
|
128
131
|
|
|
129
132
|
### `DRMConfig` Object
|
|
130
133
|
|
|
@@ -135,6 +138,51 @@ export default function App() {
|
|
|
135
138
|
|
|
136
139
|
---
|
|
137
140
|
|
|
141
|
+
## Stream Type Detection
|
|
142
|
+
|
|
143
|
+
The player supports **automatic stream type detection** based on the media URI. It inspects the file extension and known manifest patterns (`.mpd` for DASH, `.m3u8` for HLS) to choose the correct media source.
|
|
144
|
+
|
|
145
|
+
You can also **explicitly set the stream type** using the `type` property in the `source` prop. This is useful when the URI does not contain a recognizable extension (e.g., signed URLs or token-based endpoints).
|
|
146
|
+
|
|
147
|
+
### Supported Stream Types
|
|
148
|
+
|
|
149
|
+
| `type` Value | Format | Description |
|
|
150
|
+
| ------------ | ------------------- | ------------------------------------- |
|
|
151
|
+
| `'dash'` | DASH (`.mpd`) | Dynamic Adaptive Streaming over HTTP |
|
|
152
|
+
| `'hls'` | HLS (`.m3u8`) | HTTP Live Streaming |
|
|
153
|
+
| `'mp4'` | Progressive (`.mp4`)| Standard progressive HTTP download |
|
|
154
|
+
|
|
155
|
+
### Auto-Detection (Recommended)
|
|
156
|
+
|
|
157
|
+
If the URI contains a known extension, you can omit `type` entirely:
|
|
158
|
+
|
|
159
|
+
```jsx
|
|
160
|
+
<Media3Player
|
|
161
|
+
source={{uri: 'https://example.com/stream/manifest.mpd'}}
|
|
162
|
+
autoplay
|
|
163
|
+
play
|
|
164
|
+
/>
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
The player will automatically detect this as a DASH stream.
|
|
168
|
+
|
|
169
|
+
### Explicit Type Override
|
|
170
|
+
|
|
171
|
+
Use `type` when the URI does not have a recognizable extension:
|
|
172
|
+
|
|
173
|
+
```jsx
|
|
174
|
+
<Media3Player
|
|
175
|
+
source={{
|
|
176
|
+
uri: 'https://cdn.example.com/stream?token=abc123',
|
|
177
|
+
type: 'hls',
|
|
178
|
+
}}
|
|
179
|
+
autoplay
|
|
180
|
+
play
|
|
181
|
+
/>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
138
186
|
## DRM Support (Widevine)
|
|
139
187
|
|
|
140
188
|
This library supports **Widevine DRM** for playing protected DASH streams on Android. To enable DRM, pass a `drm` object inside the `source` prop.
|
|
@@ -216,12 +264,13 @@ const props: Media3PlayerProps = {
|
|
|
216
264
|
};
|
|
217
265
|
```
|
|
218
266
|
|
|
219
|
-
**With DRM:**
|
|
267
|
+
**With DRM and explicit stream type:**
|
|
220
268
|
|
|
221
269
|
```ts
|
|
222
270
|
const drmProps: Media3PlayerProps = {
|
|
223
271
|
source: {
|
|
224
272
|
uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd',
|
|
273
|
+
type: 'dash',
|
|
225
274
|
drm: {
|
|
226
275
|
licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
|
|
227
276
|
headers: [
|
|
@@ -263,12 +312,36 @@ const drmProps: Media3PlayerProps = {
|
|
|
263
312
|
/>
|
|
264
313
|
```
|
|
265
314
|
|
|
315
|
+
**HLS Stream:**
|
|
316
|
+
|
|
317
|
+
```jsx
|
|
318
|
+
<Media3Player
|
|
319
|
+
source={{
|
|
320
|
+
uri: 'https://example.com/live/stream.m3u8',
|
|
321
|
+
type: 'hls',
|
|
322
|
+
}}
|
|
323
|
+
autoplay
|
|
324
|
+
play
|
|
325
|
+
/>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
**DASH Stream (auto-detected):**
|
|
329
|
+
|
|
330
|
+
```jsx
|
|
331
|
+
<Media3Player
|
|
332
|
+
source={{uri: 'https://example.com/video/manifest.mpd'}}
|
|
333
|
+
autoplay
|
|
334
|
+
play
|
|
335
|
+
/>
|
|
336
|
+
```
|
|
337
|
+
|
|
266
338
|
**Widevine DRM Stream:**
|
|
267
339
|
|
|
268
340
|
```jsx
|
|
269
341
|
<Media3Player
|
|
270
342
|
source={{
|
|
271
343
|
uri: 'https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd',
|
|
344
|
+
type: 'dash',
|
|
272
345
|
drm: {
|
|
273
346
|
licenseUrl: 'https://proxy.uat.widevine.com/proxy?provider=widevine_test',
|
|
274
347
|
},
|
|
@@ -284,6 +357,7 @@ const drmProps: Media3PlayerProps = {
|
|
|
284
357
|
<Media3Player
|
|
285
358
|
source={{
|
|
286
359
|
uri: 'https://example.com/protected/manifest.mpd',
|
|
360
|
+
type: 'dash',
|
|
287
361
|
drm: {
|
|
288
362
|
licenseUrl: 'https://license.example.com/widevine',
|
|
289
363
|
headers: [
|
|
@@ -15,6 +15,13 @@ import com.facebook.react.bridge.WritableMap
|
|
|
15
15
|
import com.facebook.react.uimanager.UIManagerHelper
|
|
16
16
|
import com.facebook.react.uimanager.events.Event
|
|
17
17
|
import androidx.media3.common.C
|
|
18
|
+
import androidx.media3.common.util.Util
|
|
19
|
+
import androidx.media3.datasource.DefaultHttpDataSource
|
|
20
|
+
import androidx.media3.exoplayer.source.MediaSource
|
|
21
|
+
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
|
22
|
+
import androidx.media3.exoplayer.dash.DashMediaSource
|
|
23
|
+
import androidx.media3.exoplayer.hls.HlsMediaSource
|
|
24
|
+
import androidx.media3.exoplayer.smoothstreaming.SsMediaSource
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
27
|
* Custom view that wraps ExoPlayer and PlayerView, integrating with React Native.
|
|
@@ -83,6 +90,123 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
|
|
|
83
90
|
}
|
|
84
91
|
}
|
|
85
92
|
|
|
93
|
+
/**
|
|
94
|
+
* Maps a media type string from JavaScript to one of Media3's content type constants.
|
|
95
|
+
*
|
|
96
|
+
* This function interprets the "type" property provided from React Native JS props ("dash", "hls", "mp4")
|
|
97
|
+
* and translates it to the corresponding Media3 content type constant used by ExoPlayer.
|
|
98
|
+
* Returns null if the string is null, empty, or does not match a known type.
|
|
99
|
+
*
|
|
100
|
+
* @param type Optional string type from JS ("dash", "hls", "mp4")
|
|
101
|
+
* @return Media3 content type constant (C.CONTENT_TYPE_DASH, etc.), or null if unrecognized
|
|
102
|
+
*/
|
|
103
|
+
private fun mapTypeFromJS(type: String?): Int? {
|
|
104
|
+
return when (type?.lowercase()) {
|
|
105
|
+
"dash" -> C.CONTENT_TYPE_DASH
|
|
106
|
+
"hls" -> C.CONTENT_TYPE_HLS
|
|
107
|
+
"mp4" -> C.CONTENT_TYPE_OTHER
|
|
108
|
+
else -> null
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Infers the content type of a given URI for media playback.
|
|
114
|
+
*
|
|
115
|
+
* This method first uses Media3's built-in Util.inferContentType to try
|
|
116
|
+
* to detect the type (DASH, HLS, SmoothStreaming, or Other) based on file extension or URI.
|
|
117
|
+
* If this detection fails (returns CONTENT_TYPE_OTHER), it falls back to checking
|
|
118
|
+
* for well-known streaming manifest extensions (.mpd for DASH, .m3u8 for HLS)
|
|
119
|
+
* in the URI string. This fallback is important for cases where extensions are
|
|
120
|
+
* not present (e.g. signed URLs or tokens).
|
|
121
|
+
*
|
|
122
|
+
* @param uri The Uri of the media source.
|
|
123
|
+
* @return The detected content type as one of C.CONTENT_TYPE_* constants.
|
|
124
|
+
*/
|
|
125
|
+
private fun inferContentTypeSafe(uri: Uri): Int {
|
|
126
|
+
|
|
127
|
+
// Use Media3's built-in Util.inferContentType to try to detect the type.
|
|
128
|
+
val detectedType = Util.inferContentType(uri)
|
|
129
|
+
|
|
130
|
+
// If Media3 was able to determine the type, return it early.
|
|
131
|
+
if (detectedType != C.CONTENT_TYPE_OTHER) {
|
|
132
|
+
return detectedType
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Convert the Uri to a string
|
|
136
|
+
val url = uri.toString()
|
|
137
|
+
|
|
138
|
+
// Fallback: manually check for known manifest extensions in the URL
|
|
139
|
+
// This helps handle sources where the content type can't be inferred automatically.
|
|
140
|
+
return when {
|
|
141
|
+
url.contains(".mpd", ignoreCase = true) -> C.CONTENT_TYPE_DASH
|
|
142
|
+
url.contains(".m3u8", ignoreCase = true) -> C.CONTENT_TYPE_HLS
|
|
143
|
+
else -> C.CONTENT_TYPE_OTHER
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Builds a MediaSource instance required by ExoPlayer based on the content type of the given URI.
|
|
149
|
+
* Handles DASH, HLS, SmoothStreaming, and generic progressive streams.
|
|
150
|
+
*
|
|
151
|
+
* @param uri The Uri of the media to play.
|
|
152
|
+
* @param mediaItem The MediaItem (with possible DRM/config) for playback.
|
|
153
|
+
* @return The constructed MediaSource for the ExoPlayer.
|
|
154
|
+
*/
|
|
155
|
+
private fun buildMediaSource(
|
|
156
|
+
uri: Uri,
|
|
157
|
+
mediaItem: MediaItem,
|
|
158
|
+
type: String?
|
|
159
|
+
): MediaSource {
|
|
160
|
+
|
|
161
|
+
// Create a DefaultHttpDataSourceFactory for the MediaSource.
|
|
162
|
+
// This is used to fetch the media content from the network.
|
|
163
|
+
val dataSourceFactory = DefaultHttpDataSource.Factory()
|
|
164
|
+
|
|
165
|
+
// Check if a type was explicitly passed from JS (e.g., "hls", "dash", "mp4") and map to Media3 type constant.
|
|
166
|
+
val overrideType = mapTypeFromJS(type)
|
|
167
|
+
// Otherwise, infer the content type from the URI (file extension or stream manifest).
|
|
168
|
+
val detectedType = inferContentTypeSafe(uri)
|
|
169
|
+
// Use the JS override type if available, else fall back to detected type.
|
|
170
|
+
val finalType = overrideType ?: detectedType
|
|
171
|
+
|
|
172
|
+
// Log the content type detection process for debugging purposes.
|
|
173
|
+
Log.d(
|
|
174
|
+
"Media3Player",
|
|
175
|
+
"Type → override: $overrideType detected: $detectedType final: $finalType url: $uri"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
// Determine content type and return the appropriate MediaSource.
|
|
179
|
+
// This is used to create the appropriate MediaSource for the ExoPlayer.
|
|
180
|
+
return when (finalType) {
|
|
181
|
+
// DASH (MPD) stream
|
|
182
|
+
C.CONTENT_TYPE_DASH -> {
|
|
183
|
+
DashMediaSource.Factory(dataSourceFactory)
|
|
184
|
+
.createMediaSource(mediaItem)
|
|
185
|
+
}
|
|
186
|
+
// HLS (M3U8) stream
|
|
187
|
+
C.CONTENT_TYPE_HLS -> {
|
|
188
|
+
HlsMediaSource.Factory(dataSourceFactory)
|
|
189
|
+
.createMediaSource(mediaItem)
|
|
190
|
+
}
|
|
191
|
+
// SmoothStreaming (ISM) stream
|
|
192
|
+
C.CONTENT_TYPE_SS -> {
|
|
193
|
+
SsMediaSource.Factory(dataSourceFactory)
|
|
194
|
+
.createMediaSource(mediaItem)
|
|
195
|
+
}
|
|
196
|
+
// Progressive HTTP file (MP4, MP3, etc.)
|
|
197
|
+
C.CONTENT_TYPE_OTHER -> {
|
|
198
|
+
ProgressiveMediaSource.Factory(dataSourceFactory)
|
|
199
|
+
.createMediaSource(mediaItem)
|
|
200
|
+
}
|
|
201
|
+
// Fallback to progressive for unknown types
|
|
202
|
+
else -> {
|
|
203
|
+
ProgressiveMediaSource.Factory(dataSourceFactory)
|
|
204
|
+
.createMediaSource(mediaItem)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
|
|
86
210
|
/**
|
|
87
211
|
* Sets the video source (with optional DRM support) and prepares the player.
|
|
88
212
|
* Called from the ViewManager when the JS "source" prop changes.
|
|
@@ -93,18 +217,25 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
|
|
|
93
217
|
*/
|
|
94
218
|
fun setSource(
|
|
95
219
|
uriString: String?,
|
|
220
|
+
type: String?,
|
|
96
221
|
licenseUrl: String?,
|
|
97
222
|
headers: Map<String, String>?
|
|
98
223
|
) {
|
|
99
224
|
// Return early if no valid URI is provided
|
|
100
225
|
if (uriString.isNullOrEmpty()) return
|
|
226
|
+
|
|
227
|
+
// Store the URI string for reference
|
|
101
228
|
sourceUri = uriString
|
|
102
229
|
|
|
103
230
|
// Ensure the ExoPlayer instance is initialized before use
|
|
104
231
|
initializePlayer()
|
|
105
232
|
|
|
233
|
+
// Parse the URI string into a Uri object
|
|
106
234
|
val uri = Uri.parse(uriString)
|
|
107
|
-
|
|
235
|
+
|
|
236
|
+
// Build the MediaItem, adding DRM configuration if a license URL is given.
|
|
237
|
+
// This includes setting the license URL and multi-session support for DRM streams.
|
|
238
|
+
// If no license URL is provided, a simple MediaItem is created from the URI.
|
|
108
239
|
val mediaItem =
|
|
109
240
|
if (!licenseUrl.isNullOrEmpty()) {
|
|
110
241
|
val drmBuilder = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
|
|
@@ -123,8 +254,11 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
|
|
|
123
254
|
// No DRM: simple MediaItem from URI
|
|
124
255
|
MediaItem.fromUri(uri)
|
|
125
256
|
}
|
|
126
|
-
|
|
127
|
-
|
|
257
|
+
|
|
258
|
+
// Build the appropriate MediaSource (handles progressive, DASH/HLS/SmoothStreaming)
|
|
259
|
+
// and assign it to ExoPlayer. Prepare ExoPlayer for playback.
|
|
260
|
+
val mediaSource = buildMediaSource(uri, mediaItem, type)
|
|
261
|
+
exoPlayer?.setMediaSource(mediaSource)
|
|
128
262
|
exoPlayer?.prepare()
|
|
129
263
|
}
|
|
130
264
|
|
|
@@ -52,6 +52,7 @@ class Media3PlayerViewManager :
|
|
|
52
52
|
@ReactProp(name = "source")
|
|
53
53
|
override fun setSource(view: Media3PlayerView, source: ReadableMap?) {
|
|
54
54
|
val uri = source?.getString("uri")
|
|
55
|
+
val type = source?.getString("type")
|
|
55
56
|
var licenseUrl: String? = null
|
|
56
57
|
var headers: Map<String, String>? = null
|
|
57
58
|
val drmMap = source?.getMap("drm")
|
|
@@ -73,7 +74,7 @@ class Media3PlayerViewManager :
|
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
if (!uri.isNullOrEmpty()) {
|
|
76
|
-
view.setSource(uri, licenseUrl, headers)
|
|
77
|
+
view.setSource(uri, type, licenseUrl, headers)
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
80
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Import the base ViewProps type from React Native to extend the native component's props
|
|
2
2
|
import type {ViewProps} from 'react-native';
|
|
3
3
|
// Import the DirectEventHandler type used for native-to-JS event callback typings
|
|
4
|
-
import type {DirectEventHandler} from 'react-native/Libraries/Types/CodegenTypes';
|
|
4
|
+
import type {DirectEventHandler, WithDefault} from 'react-native/Libraries/Types/CodegenTypes';
|
|
5
5
|
// Import codegenNativeComponent to register the native UI component for use in React Native
|
|
6
6
|
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
|
|
7
7
|
|
|
@@ -19,6 +19,7 @@ type DRMConfig = Readonly<{
|
|
|
19
19
|
// - drm: (Optional) DRM configuration for protected content.
|
|
20
20
|
type Source = Readonly<{
|
|
21
21
|
uri: string;
|
|
22
|
+
type?: WithDefault<'dash' | 'hls' | 'mp4', 'mp4'>;
|
|
22
23
|
drm?: DRMConfig;
|
|
23
24
|
}>;
|
|
24
25
|
// Event type emitted when the player is ready
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|