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 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 and supports autoplay, mute, and event handling. This is **Android** only package.
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 | Required | Description |
125
- | -------- | ----------- | -------- | ---------------------------------------------------- |
126
- | `uri` | `string` | Yes | The URI of the media to play (MP4, DASH, HLS, etc.). |
127
- | `drm` | `DRMConfig` | No | DRM configuration for protected content. |
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
- // Build the MediaItem, adding DRM configuration if a license URL is given
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
- // Set the media item on the player and prepare for playback
127
- exoPlayer?.setMediaItem(mediaItem)
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,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-media3-player",
3
- "version": "2.0.0",
3
+ "version": "2.1.1",
4
4
  "description": "React Native Media3 Player component for Android (ExoPlayer 3 integration).",
5
5
  "main": "index.js",
6
6
  "react-native": "index.js",
@@ -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