react-native-media3-player 1.0.0 → 2.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.
package/README.md CHANGED
@@ -1,18 +1,21 @@
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
 
7
7
  ## Table of Contents
8
- - [Installation](#installation)
9
- - [Usage](#usage)
10
- - [Props](#props)
11
- - [Events](#events)
12
- - [TypeScript Support](#typescript-support)
13
- - [Examples](#examples)
14
- - [Screenshots](#screenshots)
15
- - [Contributing](#contributing)
8
+
9
+ - [Installation](#installation)
10
+ - [Usage](#usage)
11
+ - [Props](#props)
12
+ - [Stream Type Detection](#stream-type-detection)
13
+ - [DRM Support (Widevine)](#drm-support-widevine)
14
+ - [Events](#events)
15
+ - [TypeScript Support](#typescript-support)
16
+ - [Examples](#examples)
17
+ - [Screenshots](#screenshots)
18
+ - [Contributing](#contributing)
16
19
  - [License](#license)
17
20
 
18
21
  ---
@@ -20,38 +23,86 @@
20
23
  ## Installation
21
24
 
22
25
  **Install via npm:**
26
+
23
27
  ```bash
24
28
  npm install react-native-media3-player
25
29
  ```
26
30
 
27
31
  **Or yarn:**
32
+
28
33
  ```bash
29
34
  yarn add react-native-media3-player
30
35
  ```
31
36
 
32
- > Requires **React Native >= 0.70**
37
+ > Requires **React Native >= 0.70**
38
+
39
+ ---
40
+
41
+ **Note:** To use a specific Media3 version, add or update the following in your **android/build.gradle** (Project-level) file:
42
+
43
+ ```groovy
44
+ buildscript {
45
+ ext {
46
+ media3Version = "1.4.1" // Set your desired Media3 version
47
+ }
48
+ }
49
+ ```
50
+
51
+ Be sure to sync your project after making this change.
33
52
 
34
53
  ---
35
54
 
36
55
  ## Usage
37
56
 
57
+ **Basic (non-DRM):**
58
+
38
59
  ```jsx
39
60
  import React from 'react';
40
- import { View } from 'react-native';
61
+ import {View} from 'react-native';
41
62
  import Media3Player from 'react-native-media3-player';
42
63
 
43
64
  export default function App() {
44
65
  return (
45
- <View style={{ flex: 1 }}>
66
+ <View style={{flex: 1}}>
46
67
  <Media3Player
47
- style={{ width: '100%', height: 250 }}
48
- source={{ uri: 'https://example.com/video.mp4' }}
68
+ style={{width: '100%', height: 250}}
69
+ source={{uri: 'https://example.com/video.mp4'}}
49
70
  autoplay={true}
50
71
  play={true}
51
72
  mute={false}
52
73
  onReady={() => console.log('Player is ready')}
53
74
  onEnd={() => console.log('Video ended')}
54
- onError={(error) => console.log('Player error:', error.message)}
75
+ onError={error => console.log('Player error:', error.message)}
76
+ />
77
+ </View>
78
+ );
79
+ }
80
+ ```
81
+
82
+ **DRM-protected content (Widevine):**
83
+
84
+ ```jsx
85
+ import React from 'react';
86
+ import {View} from 'react-native';
87
+ import Media3Player from 'react-native-media3-player';
88
+
89
+ export default function App() {
90
+ return (
91
+ <View style={{flex: 1}}>
92
+ <Media3Player
93
+ style={{width: '100%', height: 250}}
94
+ source={{
95
+ uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd',
96
+ type: 'dash',
97
+ drm: {
98
+ licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
99
+ },
100
+ }}
101
+ autoplay={true}
102
+ play={true}
103
+ onReady={() => console.log('Player is ready')}
104
+ onEnd={() => console.log('Video ended')}
105
+ onError={error => console.log('Player error:', error.message)}
55
106
  />
56
107
  </View>
57
108
  );
@@ -62,23 +113,133 @@ export default function App() {
62
113
 
63
114
  ## Props
64
115
 
65
- | Prop | Type | Default | Description |
66
- |------------|-----------------------------|----------|-------------|
67
- | `source` | `{ uri: string }` | required | Video source object. Must include a valid URI. |
68
- | `autoplay` | `boolean` | `false` | Automatically start playback when the video is ready. |
69
- | `play` | `boolean` | `false` | Controls whether the player is playing. Overrides autoplay. |
70
- | `mute` | `boolean` | `false` | Mutes or unmutes the video. |
71
- | `style` | `ViewStyle` or `ViewStyle[]` | `{ width: '100%', height: 250 }` | Styling for the player container. |
116
+ | Prop | Type | Default | Description |
117
+ | ---------- | ---------------------------- | -------------------------------- | ------------------------------------------------------------------------ |
118
+ | `source` | `Source` | required | Video source object. Must include a valid `uri`. Supports optional `drm` config. |
119
+ | `autoplay` | `boolean` | `false` | Automatically start playback when the video is ready. |
120
+ | `play` | `boolean` | `false` | Controls whether the player is playing. Overrides autoplay. |
121
+ | `mute` | `boolean` | `false` | Mutes or unmutes the video. |
122
+ | `style` | `ViewStyle` or `ViewStyle[]` | `{ width: '100%', height: 250 }` | Styling for the player container. |
123
+
124
+ ### `Source` Object
125
+
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. |
131
+
132
+ ### `DRMConfig` Object
133
+
134
+ | Property | Type | Required | Description |
135
+ | ------------ | --------------------------------- | -------- | ----------------------------------------------------- |
136
+ | `licenseUrl` | `string` | Yes | The Widevine license server URL. |
137
+ | `headers` | `Array<{ key: string, value: string }>` | No | Custom headers to include in the DRM license request. |
138
+
139
+ ---
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
+
186
+ ## DRM Support (Widevine)
187
+
188
+ This library supports **Widevine DRM** for playing protected DASH streams on Android. To enable DRM, pass a `drm` object inside the `source` prop.
189
+
190
+ ### Basic DRM Playback
191
+
192
+ ```jsx
193
+ <Media3Player
194
+ source={{
195
+ uri: 'https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd',
196
+ drm: {
197
+ licenseUrl: 'https://proxy.uat.widevine.com/proxy?provider=widevine_test',
198
+ },
199
+ }}
200
+ autoplay
201
+ play
202
+ />
203
+ ```
204
+
205
+ ### DRM with Custom License Headers
206
+
207
+ Some license servers require authentication tokens or custom headers. Pass them as an array of `{ key, value }` objects:
208
+
209
+ ```jsx
210
+ <Media3Player
211
+ source={{
212
+ uri: 'https://example.com/protected-stream/manifest.mpd',
213
+ drm: {
214
+ licenseUrl: 'https://license.example.com/widevine',
215
+ headers: [
216
+ {key: 'Authorization', value: 'Bearer <your-token>'},
217
+ {key: 'X-Custom-Header', value: 'custom-value'},
218
+ ],
219
+ },
220
+ }}
221
+ autoplay
222
+ play
223
+ onError={e => console.error('DRM error:', e.message)}
224
+ />
225
+ ```
226
+
227
+ ### Notes
228
+
229
+ - DRM is currently supported on **Android only** (Widevine L1/L3 depending on device).
230
+ - The `source.uri` should point to a DASH (`.mpd`) stream encrypted with Widevine.
231
+ - Multi-session DRM is enabled by default for streams that require it.
232
+ - If the license request fails, the `onError` callback will fire with the error details.
72
233
 
73
234
  ---
74
235
 
75
236
  ## Events
76
237
 
77
- | Event | Callback Signature | Description |
78
- |------------|----------------------------------|-------------|
79
- | `onReady` | `() => void` | Fired when the player is ready to play. |
80
- | `onEnd` | `() => void` | Fired when the video reaches the end. |
81
- | `onError` | `(error: { message: string }) => void` | Fired when the player encounters an error. Error object includes a `message` property. |
238
+ | Event | Callback Signature | Description |
239
+ | --------- | -------------------------------------- | -------------------------------------------------------------------------------------- |
240
+ | `onReady` | `() => void` | Fired when the player is ready to play. |
241
+ | `onEnd` | `() => void` | Fired when the video reaches the end. |
242
+ | `onError` | `(error: { message: string }) => void` | Fired when the player encounters an error. Error object includes a `message` property. |
82
243
 
83
244
  ---
84
245
 
@@ -88,18 +249,39 @@ This library includes TypeScript definitions. Example:
88
249
 
89
250
  ```ts
90
251
  import React from 'react';
91
- import { ViewStyle } from 'react-native';
92
- import Media3Player, { Media3PlayerProps } from 'react-native-media3-player';
252
+ import {ViewStyle} from 'react-native';
253
+ import Media3Player, {Media3PlayerProps} from 'react-native-media3-player';
93
254
 
94
255
  const props: Media3PlayerProps = {
95
- source: { uri: 'https://example.com/video.mp4' },
256
+ source: {uri: 'https://example.com/video.mp4'},
96
257
  autoplay: true,
97
258
  play: true,
98
259
  mute: false,
99
- style: { width: '100%', height: 250 },
260
+ style: {width: '100%', height: 250},
100
261
  onReady: () => console.log('Ready'),
101
262
  onEnd: () => console.log('End'),
102
- onError: (err) => console.log(err.message),
263
+ onError: err => console.log(err.message),
264
+ };
265
+ ```
266
+
267
+ **With DRM and explicit stream type:**
268
+
269
+ ```ts
270
+ const drmProps: Media3PlayerProps = {
271
+ source: {
272
+ uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd',
273
+ type: 'dash',
274
+ drm: {
275
+ licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
276
+ headers: [
277
+ {key: 'Authorization', value: 'Bearer my-token'},
278
+ ],
279
+ },
280
+ },
281
+ autoplay: true,
282
+ play: true,
283
+ style: {width: '100%', height: 250},
284
+ onError: err => console.log('DRM Error:', err.message),
103
285
  };
104
286
  ```
105
287
 
@@ -108,26 +290,84 @@ const props: Media3PlayerProps = {
108
290
  ## Examples
109
291
 
110
292
  **Basic Player:**
293
+
111
294
  ```jsx
112
- <Media3Player source={{ uri: 'https://example.com/video.mp4' }} />
295
+ <Media3Player source={{uri: 'https://example.com/video.mp4'}} />
113
296
  ```
114
297
 
115
298
  **Autoplay & Mute:**
299
+
116
300
  ```jsx
117
- <Media3Player
118
- source={{ uri: 'https://example.com/video.mp4' }}
119
- autoplay
120
- mute
121
- />
301
+ <Media3Player source={{uri: 'https://example.com/video.mp4'}} autoplay mute />
122
302
  ```
123
303
 
124
304
  **Event Handling:**
305
+
125
306
  ```jsx
126
307
  <Media3Player
127
- source={{ uri: 'https://example.com/video.mp4' }}
308
+ source={{uri: 'https://example.com/video.mp4'}}
128
309
  onReady={() => console.log('Ready')}
129
310
  onEnd={() => console.log('End')}
130
- onError={(err) => console.error(err.message)}
311
+ onError={err => console.error(err.message)}
312
+ />
313
+ ```
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
+
338
+ **Widevine DRM Stream:**
339
+
340
+ ```jsx
341
+ <Media3Player
342
+ source={{
343
+ uri: 'https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd',
344
+ type: 'dash',
345
+ drm: {
346
+ licenseUrl: 'https://proxy.uat.widevine.com/proxy?provider=widevine_test',
347
+ },
348
+ }}
349
+ autoplay
350
+ play
351
+ />
352
+ ```
353
+
354
+ **DRM with License Headers:**
355
+
356
+ ```jsx
357
+ <Media3Player
358
+ source={{
359
+ uri: 'https://example.com/protected/manifest.mpd',
360
+ type: 'dash',
361
+ drm: {
362
+ licenseUrl: 'https://license.example.com/widevine',
363
+ headers: [
364
+ {key: 'Authorization', value: 'Bearer my-token'},
365
+ ],
366
+ },
367
+ }}
368
+ autoplay
369
+ play
370
+ onError={err => console.error('DRM Error:', err.message)}
131
371
  />
132
372
  ```
133
373
 
@@ -135,13 +375,13 @@ const props: Media3PlayerProps = {
135
375
 
136
376
  ## Contributing
137
377
 
138
- We welcome contributions!
378
+ We welcome contributions!
139
379
 
140
- 1. Fork the repo
141
- 2. Create a branch (`git checkout -b feature/new-feature`)
142
- 3. Commit your changes (`git commit -m 'Add feature'`)
143
- 4. Push to the branch (`git push origin feature/new-feature`)
144
- 5. Open a Pull Request
380
+ 1. Fork the repo
381
+ 2. Create a branch (`git checkout -b feature/new-feature`)
382
+ 3. Commit your changes (`git commit -m 'Add feature'`)
383
+ 4. Push to the branch (`git push origin feature/new-feature`)
384
+ 5. Open a Pull Request
145
385
 
146
386
  > Please follow [Semantic Versioning](https://semver.org/) for commits.
147
387
 
@@ -150,4 +390,3 @@ We welcome contributions!
150
390
  ## License
151
391
 
152
392
  MIT © [Mohammad Rehan](https://github.com/mdRehan991)
153
-
@@ -5,6 +5,11 @@ apply plugin: "org.jetbrains.kotlin.android"
5
5
  // Apply the React Native plugin to enable React Native integration
6
6
  apply plugin: "com.facebook.react"
7
7
 
8
+ // Use app-defined version if available, otherwise fallback
9
+ def media3Version = rootProject.ext.has("media3Version")
10
+ ? rootProject.ext.media3Version
11
+ : "1.4.1"
12
+
8
13
  android {
9
14
  // Set the namespace for the generated R class and manifest
10
15
  namespace "com.rnmedia3playerdemo"
@@ -46,9 +51,17 @@ dependencies {
46
51
  implementation "org.jetbrains.kotlin:kotlin-stdlib"
47
52
 
48
53
  // AndroidX Media3 ExoPlayer, UI components, and Common classes
49
- implementation "androidx.media3:media3-exoplayer:1.4.1"
50
- implementation "androidx.media3:media3-ui:1.4.1"
51
- implementation "androidx.media3:media3-common:1.4.1"
54
+ implementation "androidx.media3:media3-exoplayer:$media3Version"
55
+ implementation "androidx.media3:media3-ui:$media3Version"
56
+ implementation "androidx.media3:media3-common:$media3Version"
57
+
58
+ // streaming formats
59
+ implementation "androidx.media3:media3-exoplayer-dash:$media3Version"
60
+ implementation "androidx.media3:media3-exoplayer-hls:$media3Version"
61
+ implementation "androidx.media3:media3-exoplayer-smoothstreaming:$media3Version"
62
+
63
+ // OkHttp data source for Media3 (DRM/auth/advanced networking)
64
+ implementation "androidx.media3:media3-datasource-okhttp:$media3Version"
52
65
 
53
66
  // Android core utilities and extensions (KTX)
54
67
  implementation "androidx.core:core-ktx:1.13.1"
@@ -14,6 +14,14 @@ import com.facebook.react.bridge.ReactContext
14
14
  import com.facebook.react.bridge.WritableMap
15
15
  import com.facebook.react.uimanager.UIManagerHelper
16
16
  import com.facebook.react.uimanager.events.Event
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
17
25
 
18
26
  /**
19
27
  * Custom view that wraps ExoPlayer and PlayerView, integrating with React Native.
@@ -83,19 +91,174 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
83
91
  }
84
92
 
85
93
  /**
86
- * Sets the video source from a URI string and prepares the player.
87
- * @param uriString Video source URI as a string
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
88
102
  */
89
- fun setSource(uriString: String?) {
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
+
210
+ /**
211
+ * Sets the video source (with optional DRM support) and prepares the player.
212
+ * Called from the ViewManager when the JS "source" prop changes.
213
+ *
214
+ * @param uriString The URI of the media source to play.
215
+ * @param licenseUrl If provided, enables DRM playback with this license URL.
216
+ * @param headers Optional headers to add to DRM license requests.
217
+ */
218
+ fun setSource(
219
+ uriString: String?,
220
+ type: String?,
221
+ licenseUrl: String?,
222
+ headers: Map<String, String>?
223
+ ) {
224
+ // Return early if no valid URI is provided
90
225
  if (uriString.isNullOrEmpty()) return
226
+
227
+ // Store the URI string for reference
91
228
  sourceUri = uriString
92
229
 
93
- // Ensure player is initialized
230
+ // Ensure the ExoPlayer instance is initialized before use
94
231
  initializePlayer()
95
232
 
96
- // Set and prepare the media item for playback
97
- val item = MediaItem.fromUri(Uri.parse(uriString))
98
- exoPlayer?.setMediaItem(item)
233
+ // Parse the URI string into a Uri object
234
+ val uri = Uri.parse(uriString)
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.
239
+ val mediaItem =
240
+ if (!licenseUrl.isNullOrEmpty()) {
241
+ val drmBuilder = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
242
+ .setLicenseUri(licenseUrl) // set the license URL
243
+ .setMultiSession(true) // allow multiple DRM sessions if the stream requires them
244
+
245
+ headers?.let {
246
+ drmBuilder.setLicenseRequestHeaders(it)
247
+ }
248
+
249
+ MediaItem.Builder()
250
+ .setUri(uri)
251
+ .setDrmConfiguration(drmBuilder.build())
252
+ .build()
253
+ } else {
254
+ // No DRM: simple MediaItem from URI
255
+ MediaItem.fromUri(uri)
256
+ }
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)
99
262
  exoPlayer?.prepare()
100
263
  }
101
264
 
@@ -45,14 +45,36 @@ class Media3PlayerViewManager :
45
45
  }
46
46
 
47
47
  /**
48
- * Sets the "source" property on the Media3PlayerView.
49
- * Reads the "uri" string from the passed ReadableMap and loads it into the native view.
48
+ * Handles the "source" prop for Media3PlayerView.
49
+ * Extracts the "uri", optional DRM license URL, and headers from the ReadableMap and forwards
50
+ * them to the view for loading the media source and DRM configuration.
50
51
  */
51
52
  @ReactProp(name = "source")
52
53
  override fun setSource(view: Media3PlayerView, source: ReadableMap?) {
53
54
  val uri = source?.getString("uri")
55
+ val type = source?.getString("type")
56
+ var licenseUrl: String? = null
57
+ var headers: Map<String, String>? = null
58
+ val drmMap = source?.getMap("drm")
59
+
60
+ if (drmMap != null) {
61
+ licenseUrl = drmMap.getString("licenseUrl")
62
+ val headersArray = drmMap.getArray("headers")
63
+ if (headersArray != null) {
64
+ val map = mutableMapOf<String, String>()
65
+ for (i in 0 until headersArray.size()) {
66
+ val entry = headersArray.getMap(i)
67
+ val key = entry?.getString("key")
68
+ val value = entry?.getString("value")
69
+ if (key != null && value != null) {
70
+ map[key] = value
71
+ }
72
+ }
73
+ headers = map
74
+ }
75
+ }
54
76
  if (!uri.isNullOrEmpty()) {
55
- view.setSource(uri)
77
+ view.setSource(uri, type, licenseUrl, headers)
56
78
  }
57
79
  }
58
80
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-media3-player",
3
- "version": "1.0.0",
3
+ "version": "2.1.0",
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,10 +1,27 @@
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
 
8
+ type HeaderEntry = Readonly<{
9
+ key: string;
10
+ value: string;
11
+ }>;
12
+
13
+ type DRMConfig = Readonly<{
14
+ licenseUrl: string;
15
+ headers?: ReadonlyArray<HeaderEntry>;
16
+ }>;
17
+ // Source defines the media source for the player component.
18
+ // - uri: The URI of the media to be played (required).
19
+ // - drm: (Optional) DRM configuration for protected content.
20
+ type Source = Readonly<{
21
+ uri: string;
22
+ type?: WithDefault<'dash' | 'hls' | 'mp4', 'mp4'>;
23
+ drm?: DRMConfig;
24
+ }>;
8
25
  // Event type emitted when the player is ready
9
26
  type OnReadyEvent = Readonly<{}>;
10
27
  // Event type emitted when playback reaches the end
@@ -17,7 +34,7 @@ type OnErrorEvent = Readonly<{
17
34
  // Props supported by the native Media3PlayerView component
18
35
  export interface NativeProps extends ViewProps {
19
36
  // Source URI for the media to play
20
- source?: Readonly<{uri: string}>;
37
+ source?: Source;
21
38
  // Whether playback should start automatically when ready
22
39
  autoplay?: boolean;
23
40
  // Whether playback should currently be running (true = play, false = pause)