react-native-media3-player 0.0.1 → 2.0.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
@@ -5,14 +5,16 @@
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
+ - [DRM Support (Widevine)](#drm-support-widevine)
13
+ - [Events](#events)
14
+ - [TypeScript Support](#typescript-support)
15
+ - [Examples](#examples)
16
+ - [Screenshots](#screenshots)
17
+ - [Contributing](#contributing)
16
18
  - [License](#license)
17
19
 
18
20
  ---
@@ -20,38 +22,85 @@
20
22
  ## Installation
21
23
 
22
24
  **Install via npm:**
25
+
23
26
  ```bash
24
27
  npm install react-native-media3-player
25
28
  ```
26
29
 
27
30
  **Or yarn:**
31
+
28
32
  ```bash
29
33
  yarn add react-native-media3-player
30
34
  ```
31
35
 
32
- > Requires **React Native >= 0.70**
36
+ > Requires **React Native >= 0.70**
37
+
38
+ ---
39
+
40
+ **Note:** To use a specific Media3 version, add or update the following in your **android/build.gradle** (Project-level) file:
41
+
42
+ ```groovy
43
+ buildscript {
44
+ ext {
45
+ media3Version = "1.4.1" // Set your desired Media3 version
46
+ }
47
+ }
48
+ ```
49
+
50
+ Be sure to sync your project after making this change.
33
51
 
34
52
  ---
35
53
 
36
54
  ## Usage
37
55
 
56
+ **Basic (non-DRM):**
57
+
38
58
  ```jsx
39
59
  import React from 'react';
40
- import { View } from 'react-native';
60
+ import {View} from 'react-native';
41
61
  import Media3Player from 'react-native-media3-player';
42
62
 
43
63
  export default function App() {
44
64
  return (
45
- <View style={{ flex: 1 }}>
65
+ <View style={{flex: 1}}>
46
66
  <Media3Player
47
- style={{ width: '100%', height: 250 }}
48
- source={{ uri: 'https://example.com/video.mp4' }}
67
+ style={{width: '100%', height: 250}}
68
+ source={{uri: 'https://example.com/video.mp4'}}
49
69
  autoplay={true}
50
70
  play={true}
51
71
  mute={false}
52
72
  onReady={() => console.log('Player is ready')}
53
73
  onEnd={() => console.log('Video ended')}
54
- onError={(error) => console.log('Player error:', error.message)}
74
+ onError={error => console.log('Player error:', error.message)}
75
+ />
76
+ </View>
77
+ );
78
+ }
79
+ ```
80
+
81
+ **DRM-protected content (Widevine):**
82
+
83
+ ```jsx
84
+ import React from 'react';
85
+ import {View} from 'react-native';
86
+ import Media3Player from 'react-native-media3-player';
87
+
88
+ export default function App() {
89
+ return (
90
+ <View style={{flex: 1}}>
91
+ <Media3Player
92
+ style={{width: '100%', height: 250}}
93
+ source={{
94
+ uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd',
95
+ drm: {
96
+ licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
97
+ },
98
+ }}
99
+ autoplay={true}
100
+ play={true}
101
+ onReady={() => console.log('Player is ready')}
102
+ onEnd={() => console.log('Video ended')}
103
+ onError={error => console.log('Player error:', error.message)}
55
104
  />
56
105
  </View>
57
106
  );
@@ -62,23 +111,87 @@ export default function App() {
62
111
 
63
112
  ## Props
64
113
 
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. |
114
+ | Prop | Type | Default | Description |
115
+ | ---------- | ---------------------------- | -------------------------------- | ------------------------------------------------------------------------ |
116
+ | `source` | `Source` | required | Video source object. Must include a valid `uri`. Supports optional `drm` config. |
117
+ | `autoplay` | `boolean` | `false` | Automatically start playback when the video is ready. |
118
+ | `play` | `boolean` | `false` | Controls whether the player is playing. Overrides autoplay. |
119
+ | `mute` | `boolean` | `false` | Mutes or unmutes the video. |
120
+ | `style` | `ViewStyle` or `ViewStyle[]` | `{ width: '100%', height: 250 }` | Styling for the player container. |
121
+
122
+ ### `Source` Object
123
+
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. |
128
+
129
+ ### `DRMConfig` Object
130
+
131
+ | Property | Type | Required | Description |
132
+ | ------------ | --------------------------------- | -------- | ----------------------------------------------------- |
133
+ | `licenseUrl` | `string` | Yes | The Widevine license server URL. |
134
+ | `headers` | `Array<{ key: string, value: string }>` | No | Custom headers to include in the DRM license request. |
135
+
136
+ ---
137
+
138
+ ## DRM Support (Widevine)
139
+
140
+ This library supports **Widevine DRM** for playing protected DASH streams on Android. To enable DRM, pass a `drm` object inside the `source` prop.
141
+
142
+ ### Basic DRM Playback
143
+
144
+ ```jsx
145
+ <Media3Player
146
+ source={{
147
+ uri: 'https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd',
148
+ drm: {
149
+ licenseUrl: 'https://proxy.uat.widevine.com/proxy?provider=widevine_test',
150
+ },
151
+ }}
152
+ autoplay
153
+ play
154
+ />
155
+ ```
156
+
157
+ ### DRM with Custom License Headers
158
+
159
+ Some license servers require authentication tokens or custom headers. Pass them as an array of `{ key, value }` objects:
160
+
161
+ ```jsx
162
+ <Media3Player
163
+ source={{
164
+ uri: 'https://example.com/protected-stream/manifest.mpd',
165
+ drm: {
166
+ licenseUrl: 'https://license.example.com/widevine',
167
+ headers: [
168
+ {key: 'Authorization', value: 'Bearer <your-token>'},
169
+ {key: 'X-Custom-Header', value: 'custom-value'},
170
+ ],
171
+ },
172
+ }}
173
+ autoplay
174
+ play
175
+ onError={e => console.error('DRM error:', e.message)}
176
+ />
177
+ ```
178
+
179
+ ### Notes
180
+
181
+ - DRM is currently supported on **Android only** (Widevine L1/L3 depending on device).
182
+ - The `source.uri` should point to a DASH (`.mpd`) stream encrypted with Widevine.
183
+ - Multi-session DRM is enabled by default for streams that require it.
184
+ - If the license request fails, the `onError` callback will fire with the error details.
72
185
 
73
186
  ---
74
187
 
75
188
  ## Events
76
189
 
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. |
190
+ | Event | Callback Signature | Description |
191
+ | --------- | -------------------------------------- | -------------------------------------------------------------------------------------- |
192
+ | `onReady` | `() => void` | Fired when the player is ready to play. |
193
+ | `onEnd` | `() => void` | Fired when the video reaches the end. |
194
+ | `onError` | `(error: { message: string }) => void` | Fired when the player encounters an error. Error object includes a `message` property. |
82
195
 
83
196
  ---
84
197
 
@@ -88,18 +201,38 @@ This library includes TypeScript definitions. Example:
88
201
 
89
202
  ```ts
90
203
  import React from 'react';
91
- import { ViewStyle } from 'react-native';
92
- import Media3Player, { Media3PlayerProps } from 'react-native-media3-player';
204
+ import {ViewStyle} from 'react-native';
205
+ import Media3Player, {Media3PlayerProps} from 'react-native-media3-player';
93
206
 
94
207
  const props: Media3PlayerProps = {
95
- source: { uri: 'https://example.com/video.mp4' },
208
+ source: {uri: 'https://example.com/video.mp4'},
96
209
  autoplay: true,
97
210
  play: true,
98
211
  mute: false,
99
- style: { width: '100%', height: 250 },
212
+ style: {width: '100%', height: 250},
100
213
  onReady: () => console.log('Ready'),
101
214
  onEnd: () => console.log('End'),
102
- onError: (err) => console.log(err.message),
215
+ onError: err => console.log(err.message),
216
+ };
217
+ ```
218
+
219
+ **With DRM:**
220
+
221
+ ```ts
222
+ const drmProps: Media3PlayerProps = {
223
+ source: {
224
+ uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd',
225
+ drm: {
226
+ licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
227
+ headers: [
228
+ {key: 'Authorization', value: 'Bearer my-token'},
229
+ ],
230
+ },
231
+ },
232
+ autoplay: true,
233
+ play: true,
234
+ style: {width: '100%', height: 250},
235
+ onError: err => console.log('DRM Error:', err.message),
103
236
  };
104
237
  ```
105
238
 
@@ -108,26 +241,59 @@ const props: Media3PlayerProps = {
108
241
  ## Examples
109
242
 
110
243
  **Basic Player:**
244
+
111
245
  ```jsx
112
- <Media3Player source={{ uri: 'https://example.com/video.mp4' }} />
246
+ <Media3Player source={{uri: 'https://example.com/video.mp4'}} />
113
247
  ```
114
248
 
115
249
  **Autoplay & Mute:**
250
+
116
251
  ```jsx
117
- <Media3Player
118
- source={{ uri: 'https://example.com/video.mp4' }}
119
- autoplay
120
- mute
121
- />
252
+ <Media3Player source={{uri: 'https://example.com/video.mp4'}} autoplay mute />
122
253
  ```
123
254
 
124
255
  **Event Handling:**
256
+
125
257
  ```jsx
126
258
  <Media3Player
127
- source={{ uri: 'https://example.com/video.mp4' }}
259
+ source={{uri: 'https://example.com/video.mp4'}}
128
260
  onReady={() => console.log('Ready')}
129
261
  onEnd={() => console.log('End')}
130
- onError={(err) => console.error(err.message)}
262
+ onError={err => console.error(err.message)}
263
+ />
264
+ ```
265
+
266
+ **Widevine DRM Stream:**
267
+
268
+ ```jsx
269
+ <Media3Player
270
+ source={{
271
+ uri: 'https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd',
272
+ drm: {
273
+ licenseUrl: 'https://proxy.uat.widevine.com/proxy?provider=widevine_test',
274
+ },
275
+ }}
276
+ autoplay
277
+ play
278
+ />
279
+ ```
280
+
281
+ **DRM with License Headers:**
282
+
283
+ ```jsx
284
+ <Media3Player
285
+ source={{
286
+ uri: 'https://example.com/protected/manifest.mpd',
287
+ drm: {
288
+ licenseUrl: 'https://license.example.com/widevine',
289
+ headers: [
290
+ {key: 'Authorization', value: 'Bearer my-token'},
291
+ ],
292
+ },
293
+ }}
294
+ autoplay
295
+ play
296
+ onError={err => console.error('DRM Error:', err.message)}
131
297
  />
132
298
  ```
133
299
 
@@ -135,13 +301,13 @@ const props: Media3PlayerProps = {
135
301
 
136
302
  ## Contributing
137
303
 
138
- We welcome contributions!
304
+ We welcome contributions!
139
305
 
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
306
+ 1. Fork the repo
307
+ 2. Create a branch (`git checkout -b feature/new-feature`)
308
+ 3. Commit your changes (`git commit -m 'Add feature'`)
309
+ 4. Push to the branch (`git push origin feature/new-feature`)
310
+ 5. Open a Pull Request
145
311
 
146
312
  > Please follow [Semantic Versioning](https://semver.org/) for commits.
147
313
 
@@ -150,4 +316,3 @@ We welcome contributions!
150
316
  ## License
151
317
 
152
318
  MIT © [Mohammad Rehan](https://github.com/mdRehan991)
153
-
@@ -1,37 +1,68 @@
1
+ // Apply the Android library plugin
1
2
  apply plugin: "com.android.library"
3
+ // Apply the Kotlin Android plugin for Kotlin language support
2
4
  apply plugin: "org.jetbrains.kotlin.android"
5
+ // Apply the React Native plugin to enable React Native integration
6
+ apply plugin: "com.facebook.react"
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"
3
12
 
4
13
  android {
14
+ // Set the namespace for the generated R class and manifest
5
15
  namespace "com.rnmedia3playerdemo"
16
+
17
+ // Compile SDK version to build against
6
18
  compileSdkVersion 35
7
19
 
8
20
  defaultConfig {
21
+ // Minimum SDK version supported by the library
9
22
  minSdkVersion 24
23
+ // Target SDK version for compatibility and behaviors
10
24
  targetSdkVersion 34
11
25
  }
12
26
 
13
27
  compileOptions {
28
+ // Specify Java source and target compatibility
14
29
  sourceCompatibility JavaVersion.VERSION_17
15
30
  targetCompatibility JavaVersion.VERSION_17
16
31
  }
17
32
 
18
33
  kotlinOptions {
34
+ // Set the Kotlin JVM target version
19
35
  jvmTarget = "17"
20
36
  }
37
+
38
+ sourceSets {
39
+ main {
40
+ // Include codegen outputs (for TurboModules, Fabric, etc.) in the main sources
41
+ java.srcDirs += "${buildDir}/generated/source/codegen/java"
42
+ }
43
+ }
21
44
  }
22
45
 
23
46
  dependencies {
24
- // Required for React Native bridge
25
- implementation "com.facebook.react:react-native:+"
47
+ // Dependency for React Native bridge (provided by the app, NOT bundled in aar)
48
+ compileOnly "com.facebook.react:react-android"
49
+
50
+ // Kotlin standard library dependency
51
+ implementation "org.jetbrains.kotlin:kotlin-stdlib"
52
+
53
+ // AndroidX Media3 ExoPlayer, UI components, and Common classes
54
+ implementation "androidx.media3:media3-exoplayer:$media3Version"
55
+ implementation "androidx.media3:media3-ui:$media3Version"
56
+ implementation "androidx.media3:media3-common:$media3Version"
26
57
 
27
- // Kotlin standard lib
28
- implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.0"
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"
29
62
 
30
- // Media3 dependencies
31
- implementation "androidx.media3:media3-exoplayer:1.4.1"
32
- implementation "androidx.media3:media3-ui:1.4.1"
33
- implementation "androidx.media3:media3-common:1.4.1"
63
+ // OkHttp data source for Media3 (DRM/auth/advanced networking)
64
+ implementation "androidx.media3:media3-datasource-okhttp:$media3Version"
34
65
 
35
- // Useful Android extensions
66
+ // Android core utilities and extensions (KTX)
36
67
  implementation "androidx.core:core-ktx:1.13.1"
37
68
  }
@@ -2,13 +2,25 @@ package com.rnmedia3playerdemo
2
2
 
3
3
  import com.facebook.react.ReactPackage
4
4
  import com.facebook.react.bridge.NativeModule
5
- import com.facebook.react.uimanager.ViewManager
6
5
  import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
7
 
8
+ /**
9
+ * Media3Package integrates the custom Media3PlayerViewManager with React Native.
10
+ *
11
+ * This package does not register any NativeModules. It only provides the
12
+ * Media3PlayerViewManager for use as a native UI component from JavaScript.
13
+ */
8
14
  class Media3Package : ReactPackage {
9
- override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> = emptyList()
15
+ /**
16
+ * No native modules are exported by this package.
17
+ */
18
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
19
+ emptyList()
10
20
 
11
- override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
12
- return listOf(Media3PlayerViewManager(reactContext))
13
- }
21
+ /**
22
+ * Registers Media3PlayerViewManager as the only custom ViewManager for this package.
23
+ */
24
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
25
+ listOf(Media3PlayerViewManager())
14
26
  }
@@ -9,26 +9,33 @@ import androidx.media3.common.Player
9
9
  import androidx.media3.common.PlaybackException
10
10
  import androidx.media3.exoplayer.ExoPlayer
11
11
  import androidx.media3.ui.PlayerView
12
- import com.facebook.react.bridge.ReactContext
13
- import com.facebook.react.uimanager.events.RCTEventEmitter
14
12
  import com.facebook.react.bridge.Arguments
13
+ import com.facebook.react.bridge.ReactContext
14
+ import com.facebook.react.bridge.WritableMap
15
+ import com.facebook.react.uimanager.UIManagerHelper
16
+ import com.facebook.react.uimanager.events.Event
17
+ import androidx.media3.common.C
15
18
 
16
19
  /**
17
- * Media3PlayerView
18
- *
19
- * Handles ExoPlayer playback and exposes events to React Native:
20
- * - onReady
21
- * - onEnd
22
- * - onError
20
+ * Custom view that wraps ExoPlayer and PlayerView, integrating with React Native.
23
21
  */
24
22
  class Media3PlayerView(context: Context) : FrameLayout(context) {
23
+ // ExoPlayer instance to handle media playback
25
24
  private var exoPlayer: ExoPlayer? = null
25
+ // PlayerView to display video content
26
26
  private var playerView: PlayerView
27
+ // Currently loaded media source URI
27
28
  private var sourceUri: String? = null
29
+ // Should video autoplay when ready
28
30
  private var autoplay: Boolean = false
31
+ // Play state controlled by the JS prop
29
32
  private var play: Boolean = false
33
+ // Mute state controlled by the JS prop
30
34
  private var mute: Boolean = false
31
35
 
36
+ /**
37
+ * Constructor: initialize the PlayerView and attach it to the layout.
38
+ */
32
39
  init {
33
40
  playerView = PlayerView(context)
34
41
  playerView.layoutParams = LayoutParams(
@@ -38,16 +45,23 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
38
45
  addView(playerView)
39
46
  }
40
47
 
48
+ /**
49
+ * Initializes ExoPlayer and attaches a listener for player events.
50
+ * Ensures only one instance exists.
51
+ */
41
52
  private fun initializePlayer() {
42
53
  if (exoPlayer == null) {
43
54
  exoPlayer = ExoPlayer.Builder(context).build()
44
55
  playerView.player = exoPlayer
45
56
 
57
+ // Listen for playback state changes and errors
46
58
  exoPlayer?.addListener(object : Player.Listener {
47
59
  override fun onPlaybackStateChanged(state: Int) {
48
60
  when (state) {
49
61
  Player.STATE_READY -> {
50
- sendEvent("onReady")
62
+ // Notify React Native that player is ready
63
+ sendEvent("topReady")
64
+ // If autoplay is enabled, start playback when ready
51
65
  if (autoplay) {
52
66
  exoPlayer?.playWhenReady = true
53
67
  exoPlayer?.play()
@@ -55,62 +69,133 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
55
69
  }
56
70
  }
57
71
  Player.STATE_ENDED -> {
58
- sendEvent("onEnd")
72
+ // Notify React Native that playback has ended
73
+ sendEvent("topEnd")
59
74
  }
60
75
  }
61
76
  }
62
77
 
63
78
  override fun onPlayerError(error: PlaybackException) {
64
- sendEvent("onError", error.message ?: "Unknown error")
79
+ // Report playback errors to React Native
80
+ sendEvent("topError", error.message ?: "Unknown error")
65
81
  }
66
82
  })
67
83
  }
68
84
  }
69
85
 
70
- fun setSource(uriString: String?) {
86
+ /**
87
+ * Sets the video source (with optional DRM support) and prepares the player.
88
+ * Called from the ViewManager when the JS "source" prop changes.
89
+ *
90
+ * @param uriString The URI of the media source to play.
91
+ * @param licenseUrl If provided, enables DRM playback with this license URL.
92
+ * @param headers Optional headers to add to DRM license requests.
93
+ */
94
+ fun setSource(
95
+ uriString: String?,
96
+ licenseUrl: String?,
97
+ headers: Map<String, String>?
98
+ ) {
99
+ // Return early if no valid URI is provided
71
100
  if (uriString.isNullOrEmpty()) return
72
101
  sourceUri = uriString
73
102
 
103
+ // Ensure the ExoPlayer instance is initialized before use
74
104
  initializePlayer()
75
105
 
76
- val item = MediaItem.fromUri(Uri.parse(uriString))
77
- exoPlayer?.setMediaItem(item)
106
+ val uri = Uri.parse(uriString)
107
+ // Build the MediaItem, adding DRM configuration if a license URL is given
108
+ val mediaItem =
109
+ if (!licenseUrl.isNullOrEmpty()) {
110
+ val drmBuilder = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
111
+ .setLicenseUri(licenseUrl) // set the license URL
112
+ .setMultiSession(true) // allow multiple DRM sessions if the stream requires them
113
+
114
+ headers?.let {
115
+ drmBuilder.setLicenseRequestHeaders(it)
116
+ }
117
+
118
+ MediaItem.Builder()
119
+ .setUri(uri)
120
+ .setDrmConfiguration(drmBuilder.build())
121
+ .build()
122
+ } else {
123
+ // No DRM: simple MediaItem from URI
124
+ MediaItem.fromUri(uri)
125
+ }
126
+ // Set the media item on the player and prepare for playback
127
+ exoPlayer?.setMediaItem(mediaItem)
78
128
  exoPlayer?.prepare()
79
129
  }
80
130
 
131
+ /**
132
+ * Sets the autoplay flag to control whether playback starts when player is ready.
133
+ * @param value Boolean value from JS prop
134
+ */
81
135
  fun setAutoplay(value: Boolean) {
82
136
  autoplay = value
83
137
  }
84
138
 
139
+ /**
140
+ * Set the play state of the player.
141
+ * If true, the player will start/resume playback; otherwise, pause.
142
+ * @param value Boolean value from JS prop
143
+ */
85
144
  fun setPlay(value: Boolean) {
86
145
  play = value
87
- // if you want to disable autoplay with play (prop) -
88
- // if (exoPlayer?.playbackState == Player.STATE_READY)
89
- if (play) {
90
- exoPlayer?.play()
91
- } else {
92
- exoPlayer?.pause()
93
- }
94
- // }
146
+ // Optionally: Disabling autoplay if play is used directly
147
+ // Only play or pause if player is ready
148
+ if (play) {
149
+ exoPlayer?.play()
150
+ } else {
151
+ exoPlayer?.pause()
152
+ }
95
153
  }
96
154
 
155
+ /**
156
+ * Sets the mute state of the player.
157
+ * @param value Boolean indicating if the audio should be muted
158
+ */
97
159
  fun setMute(value: Boolean) {
98
160
  mute = value
99
161
  exoPlayer?.volume = if (mute) 0f else 1f
100
162
  }
101
163
 
164
+ /**
165
+ * Releases the player and cleans up resources.
166
+ * This should be called when the view is destroyed.
167
+ */
102
168
  fun releasePlayer() {
103
169
  exoPlayer?.release()
104
170
  exoPlayer = null
105
171
  }
106
172
 
107
- // 👇 helper function to send events to React Native
173
+ /**
174
+ * Helper function to send events and optional data payloads to React Native JS side.
175
+ * @param eventName Name of the event (e.g., "topReady", "topError")
176
+ * @param message Optional message string to pass in the event payload
177
+ */
108
178
  private fun sendEvent(eventName: String, message: String? = null) {
109
179
  val reactContext = context as? ReactContext ?: return
110
- val event = Arguments.createMap()
111
- message?.let { event.putString("message", it) }
112
- reactContext
113
- .getJSModule(RCTEventEmitter::class.java)
114
- .receiveEvent(id, eventName, event)
180
+ val surfaceId = UIManagerHelper.getSurfaceId(this)
181
+ val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
182
+ val payload = Arguments.createMap()
183
+ message?.let { payload.putString("message", it) }
184
+ eventDispatcher?.dispatchEvent(
185
+ Media3Event(surfaceId, id, eventName, payload)
186
+ )
187
+ }
188
+
189
+ /**
190
+ * Custom event class to bridge native events to React Native UIManager system.
191
+ */
192
+ private class Media3Event(
193
+ surfaceId: Int,
194
+ viewId: Int,
195
+ private val name: String,
196
+ private val payload: WritableMap
197
+ ) : Event<Media3Event>(surfaceId, viewId) {
198
+ override fun getEventName(): String = name
199
+ override fun getEventData(): WritableMap = payload
115
200
  }
116
201
  }
@@ -1,61 +1,127 @@
1
1
  package com.rnmedia3playerdemo
2
2
 
3
- import com.facebook.react.bridge.ReactApplicationContext
4
3
  import com.facebook.react.bridge.ReadableMap
4
+ import com.facebook.react.module.annotations.ReactModule
5
5
  import com.facebook.react.uimanager.SimpleViewManager
6
6
  import com.facebook.react.uimanager.ThemedReactContext
7
+ import com.facebook.react.uimanager.ViewManagerDelegate
7
8
  import com.facebook.react.uimanager.annotations.ReactProp
9
+ import com.facebook.react.viewmanagers.Media3PlayerViewManagerDelegate
10
+ import com.facebook.react.viewmanagers.Media3PlayerViewManagerInterface
8
11
 
9
12
  /**
10
- * Media3PlayerViewManager
11
- *
12
- * Exposes the native view to React Native.
13
- * Props: source { uri: string }, autoplay, play, mute
14
- * Events: onReady, onEnd, onError
13
+ * ViewManager for the custom Media3PlayerView.
14
+ * Responsible for creating the view, handling props from JS, and managing events.
15
15
  */
16
- class Media3PlayerViewManager(private val reactContext: ReactApplicationContext) :
17
- SimpleViewManager<Media3PlayerView>() {
16
+ @ReactModule(name = Media3PlayerViewManager.NAME)
17
+ class Media3PlayerViewManager :
18
+ SimpleViewManager<Media3PlayerView>(),
19
+ Media3PlayerViewManagerInterface<Media3PlayerView> {
18
20
 
19
- override fun getName(): String = "Media3PlayerView"
21
+ companion object {
22
+ // The name used to reference this view manager from JS
23
+ const val NAME = "Media3PlayerView"
24
+ }
25
+
26
+ // Delegate handles communication and prop mapping automatically when generated by Codegen
27
+ private val delegate = Media3PlayerViewManagerDelegate(this)
28
+
29
+ /**
30
+ * Returns the delegate instance for prop/event mapping.
31
+ */
32
+ override fun getDelegate(): ViewManagerDelegate<Media3PlayerView> = delegate
20
33
 
34
+ /**
35
+ * Returns the unique name for this ViewManager, used by React Native.
36
+ */
37
+ override fun getName(): String = NAME
38
+
39
+ /**
40
+ * Creates and returns a new Media3PlayerView instance for this ViewManager.
41
+ * @param reactContext ThemedReactContext provided by React Native
42
+ */
21
43
  override fun createViewInstance(reactContext: ThemedReactContext): Media3PlayerView {
22
44
  return Media3PlayerView(reactContext)
23
45
  }
24
46
 
47
+ /**
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.
51
+ */
25
52
  @ReactProp(name = "source")
26
- fun setSource(view: Media3PlayerView, source: ReadableMap?) {
27
- val uri: String? = source?.getString("uri")
53
+ override fun setSource(view: Media3PlayerView, source: ReadableMap?) {
54
+ val uri = source?.getString("uri")
55
+ var licenseUrl: String? = null
56
+ var headers: Map<String, String>? = null
57
+ val drmMap = source?.getMap("drm")
58
+
59
+ if (drmMap != null) {
60
+ licenseUrl = drmMap.getString("licenseUrl")
61
+ val headersArray = drmMap.getArray("headers")
62
+ if (headersArray != null) {
63
+ val map = mutableMapOf<String, String>()
64
+ for (i in 0 until headersArray.size()) {
65
+ val entry = headersArray.getMap(i)
66
+ val key = entry?.getString("key")
67
+ val value = entry?.getString("value")
68
+ if (key != null && value != null) {
69
+ map[key] = value
70
+ }
71
+ }
72
+ headers = map
73
+ }
74
+ }
28
75
  if (!uri.isNullOrEmpty()) {
29
- view.setSource(uri)
76
+ view.setSource(uri, licenseUrl, headers)
30
77
  }
31
78
  }
32
79
 
80
+ /**
81
+ * Sets the "autoplay" property on the Media3PlayerView.
82
+ * Controls whether playback should start automatically when ready.
83
+ */
33
84
  @ReactProp(name = "autoplay", defaultBoolean = false)
34
- fun setAutoplay(view: Media3PlayerView, autoplay: Boolean) {
35
- view.setAutoplay(autoplay)
85
+ override fun setAutoplay(view: Media3PlayerView, value: Boolean) {
86
+ view.setAutoplay(value)
36
87
  }
37
88
 
89
+ /**
90
+ * Sets the "play" property on the Media3PlayerView.
91
+ * Controls whether the player should be playing or paused.
92
+ */
38
93
  @ReactProp(name = "play", defaultBoolean = false)
39
- fun setPlay(view: Media3PlayerView, play: Boolean) {
40
- view.setPlay(play)
94
+ override fun setPlay(view: Media3PlayerView, value: Boolean) {
95
+ view.setPlay(value)
41
96
  }
42
97
 
98
+ /**
99
+ * Sets the "mute" property on the Media3PlayerView.
100
+ * Controls whether the audio should be muted.
101
+ */
43
102
  @ReactProp(name = "mute", defaultBoolean = false)
44
- fun setMute(view: Media3PlayerView, mute: Boolean) {
45
- view.setMute(mute)
103
+ override fun setMute(view: Media3PlayerView, value: Boolean) {
104
+ view.setMute(value)
46
105
  }
47
106
 
107
+ /**
108
+ * Called when the React Native view instance is about to be dropped/remounted.
109
+ * Ensures resources are released to avoid leaks.
110
+ */
48
111
  override fun onDropViewInstance(view: Media3PlayerView) {
49
112
  super.onDropViewInstance(view)
50
113
  view.releasePlayer()
51
114
  }
52
115
 
53
- // 👇 Expose custom events to JS
116
+ /**
117
+ * Exports native direct event types to React Native for event binding in JS.
118
+ * Maps native event names to the registration name expected by React.
119
+ */
54
120
  override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
55
121
  return mutableMapOf(
56
- "onReady" to mapOf("registrationName" to "onReady"),
57
- "onEnd" to mapOf("registrationName" to "onEnd"),
58
- "onError" to mapOf("registrationName" to "onError")
122
+ "topReady" to mapOf("registrationName" to "onReady"),
123
+ "topEnd" to mapOf("registrationName" to "onEnd"),
124
+ "topError" to mapOf("registrationName" to "onError")
59
125
  )
60
126
  }
61
127
  }
package/index.d.ts CHANGED
@@ -1,15 +1,16 @@
1
- import { ViewStyle } from 'react-native';
2
1
  import * as React from 'react';
2
+ import {ViewStyle} from 'react-native';
3
3
 
4
4
  export interface Media3PlayerProps {
5
5
  style?: ViewStyle | ViewStyle[];
6
- source: { uri: string };
6
+ source: {uri: string};
7
7
  autoplay?: boolean;
8
8
  play?: boolean;
9
9
  mute?: boolean;
10
10
  onReady?: () => void;
11
11
  onEnd?: () => void;
12
- onError?: (error: any) => void;
12
+ onError?: (event: {nativeEvent: {message: string}}) => void;
13
13
  }
14
14
 
15
- export default class Media3Player extends React.Component<Media3PlayerProps> {}
15
+ declare const Media3Player: React.FC<Media3PlayerProps>;
16
+ export default Media3Player;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "react-native-media3-player",
3
- "version": "0.0.1",
3
+ "version": "2.0.0",
4
4
  "description": "React Native Media3 Player component for Android (ExoPlayer 3 integration).",
5
5
  "main": "index.js",
6
+ "react-native": "index.js",
6
7
  "types": "index.d.ts",
7
8
  "keywords": [
8
9
  "react-native",
@@ -31,5 +32,13 @@
31
32
  "src",
32
33
  "index.js",
33
34
  "index.d.ts"
34
- ]
35
+ ],
36
+ "codegenConfig": {
37
+ "name": "RNMedia3PlayerSpec",
38
+ "type": "components",
39
+ "jsSrcsDir": "src",
40
+ "android": {
41
+ "javaPackageName": "com.rnmedia3playerdemo"
42
+ }
43
+ }
35
44
  }
@@ -1,20 +1,20 @@
1
1
  import React from 'react';
2
- import {requireNativeComponent, StyleSheet} from 'react-native';
3
-
4
- // Link to native view
5
- const NativeMedia3Player = requireNativeComponent('Media3PlayerView');
2
+ import {StyleSheet} from 'react-native';
3
+ // Import the native codegen-wrapped player view component
4
+ import NativeMedia3PlayerView from './Media3PlayerNativeComponent';
6
5
 
7
6
  /**
8
- * Media3Player
7
+ * Media3Player React component
9
8
  *
10
9
  * Props:
11
- * - source: { uri: string } ✅ required
12
- * - autoplay: boolean
13
- * - play: boolean
14
- * - mute: boolean
15
- * - onReady: function
16
- * - onEnd: function
17
- * - onError: function
10
+ * - style: (optional) additional style overrides
11
+ * - source: { uri: string } – required, the media URI to load
12
+ * - autoplay: (optional, default false) – whether playback should start automatically when ready
13
+ * - play: (optional, default false) – whether playback should currently be running (true = play, false = pause)
14
+ * - mute: (optional, default false) – whether audio should be muted
15
+ * - onReady: callback when player is ready
16
+ * - onEnd: callback when playback has ended
17
+ * - onError: callback when an error occurs during playback or loading
18
18
  */
19
19
  export default function Media3Player({
20
20
  style,
@@ -26,14 +26,16 @@ export default function Media3Player({
26
26
  onEnd,
27
27
  onError,
28
28
  }) {
29
+ // Validate that source.uri is provided; warn and render nothing if missing
29
30
  if (!source || !source.uri) {
30
- console.warn('Media3Player: "source" prop with a valid "uri" is required.');
31
+ console.warn('Media3Player: "source.uri" is required.');
31
32
  return null;
32
33
  }
33
34
 
35
+ // Render the native player view, passing all relevant props and composing the style
34
36
  return (
35
- <NativeMedia3Player
36
- style={[styles.default, style]} // ensure style is never undefined
37
+ <NativeMedia3PlayerView
38
+ style={[styles.default, style]}
37
39
  source={source}
38
40
  autoplay={autoplay}
39
41
  play={play}
@@ -45,10 +47,11 @@ export default function Media3Player({
45
47
  );
46
48
  }
47
49
 
50
+ // Default styling applied to the player view unless overridden via 'style' prop
48
51
  const styles = StyleSheet.create({
49
52
  default: {
50
- width: '100%',
51
- height: 250,
52
- backgroundColor: 'black',
53
+ width: '100%', // Occupy full width of parent
54
+ height: 250, // Fixed height for the player
55
+ backgroundColor: 'black', // Default background color
53
56
  },
54
57
  });
@@ -0,0 +1,53 @@
1
+ // Import the base ViewProps type from React Native to extend the native component's props
2
+ import type {ViewProps} from 'react-native';
3
+ // Import the DirectEventHandler type used for native-to-JS event callback typings
4
+ import type {DirectEventHandler} from 'react-native/Libraries/Types/CodegenTypes';
5
+ // Import codegenNativeComponent to register the native UI component for use in React Native
6
+ import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
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
+ drm?: DRMConfig;
23
+ }>;
24
+ // Event type emitted when the player is ready
25
+ type OnReadyEvent = Readonly<{}>;
26
+ // Event type emitted when playback reaches the end
27
+ type OnEndEvent = Readonly<{}>;
28
+ // Event type emitted when an error occurs during playback
29
+ type OnErrorEvent = Readonly<{
30
+ message: string; // Error message describing what went wrong
31
+ }>;
32
+
33
+ // Props supported by the native Media3PlayerView component
34
+ export interface NativeProps extends ViewProps {
35
+ // Source URI for the media to play
36
+ source?: Source;
37
+ // Whether playback should start automatically when ready
38
+ autoplay?: boolean;
39
+ // Whether playback should currently be running (true = play, false = pause)
40
+ play?: boolean;
41
+ // Whether audio should be muted
42
+ mute?: boolean;
43
+
44
+ // Callback invoked when the player is ready to play
45
+ onReady?: DirectEventHandler<OnReadyEvent>;
46
+ // Callback invoked when playback reaches the end of the media
47
+ onEnd?: DirectEventHandler<OnEndEvent>;
48
+ // Callback invoked when an error occurs in playback or loading
49
+ onError?: DirectEventHandler<OnErrorEvent>;
50
+ }
51
+
52
+ // Create and export the code-generated native component using the props interface
53
+ export default codegenNativeComponent<NativeProps>('Media3PlayerView');