react-native-media3-player 2.1.1 → 3.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
@@ -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 with support for autoplay, mute, event handling, and automatic stream type detection (DASH, HLS, MP4). This is an **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, automatic stream type detection (DASH, HLS, MP4), and client-side ad insertion (CSAI) via Google IMA. This is an **Android** only package.
4
4
 
5
5
  ---
6
6
 
@@ -11,6 +11,7 @@
11
11
  - [Props](#props)
12
12
  - [Stream Type Detection](#stream-type-detection)
13
13
  - [DRM Support (Widevine)](#drm-support-widevine)
14
+ - [IMA Ads Support (CSAI)](#ima-ads-support-csai)
14
15
  - [Events](#events)
15
16
  - [TypeScript Support](#typescript-support)
16
17
  - [Examples](#examples)
@@ -109,13 +110,43 @@ export default function App() {
109
110
  }
110
111
  ```
111
112
 
113
+ **With IMA Ads (Client-Side Ad Insertion):**
114
+
115
+ ```jsx
116
+ import React from 'react';
117
+ import {View} from 'react-native';
118
+ import Media3Player from 'react-native-media3-player';
119
+
120
+ export default function App() {
121
+ return (
122
+ <View style={{flex: 1}}>
123
+ <Media3Player
124
+ style={{width: '100%', height: 250}}
125
+ source={{
126
+ uri: 'https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
127
+ ads: {
128
+ adTagUrl:
129
+ 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpreonly&ciu_szs=300x250%2C728x90&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&correlator=',
130
+ },
131
+ }}
132
+ autoplay={true}
133
+ play={true}
134
+ onReady={() => console.log('Player is ready')}
135
+ onEnd={() => console.log('Video ended')}
136
+ onError={error => console.log('Player error:', error.message)}
137
+ />
138
+ </View>
139
+ );
140
+ }
141
+ ```
142
+
112
143
  ---
113
144
 
114
145
  ## Props
115
146
 
116
147
  | Prop | Type | Default | Description |
117
148
  | ---------- | ---------------------------- | -------------------------------- | ------------------------------------------------------------------------ |
118
- | `source` | `Source` | required | Video source object. Must include a valid `uri`. Supports optional `drm` config. |
149
+ | `source` | `Source` | required | Video source object. Must include a valid `uri`. Supports optional `drm` and `ads` config. |
119
150
  | `autoplay` | `boolean` | `false` | Automatically start playback when the video is ready. |
120
151
  | `play` | `boolean` | `false` | Controls whether the player is playing. Overrides autoplay. |
121
152
  | `mute` | `boolean` | `false` | Mutes or unmutes the video. |
@@ -128,6 +159,7 @@ export default function App() {
128
159
  | `uri` | `string` | Yes | The URI of the media to play (MP4, DASH, HLS, etc.). |
129
160
  | `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
161
  | `drm` | `DRMConfig` | No | DRM configuration for protected content. |
162
+ | `ads` | `AdsConfig` | No | IMA ads configuration for client-side ad insertion. |
131
163
 
132
164
  ### `DRMConfig` Object
133
165
 
@@ -136,6 +168,12 @@ export default function App() {
136
168
  | `licenseUrl` | `string` | Yes | The Widevine license server URL. |
137
169
  | `headers` | `Array<{ key: string, value: string }>` | No | Custom headers to include in the DRM license request. |
138
170
 
171
+ ### `AdsConfig` Object
172
+
173
+ | Property | Type | Required | Description |
174
+ | ---------- | -------- | -------- | ----------------------------------------------------------------- |
175
+ | `adTagUrl` | `string` | Yes | The VAST/VMAP ad tag URL for client-side ad insertion via IMA SDK. |
176
+
139
177
  ---
140
178
 
141
179
  ## Stream Type Detection
@@ -233,6 +271,58 @@ Some license servers require authentication tokens or custom headers. Pass them
233
271
 
234
272
  ---
235
273
 
274
+ ## IMA Ads Support (CSAI)
275
+
276
+ This library supports **Client-Side Ad Insertion (CSAI)** using the [Google IMA SDK](https://developers.google.com/interactive-media-ads) integrated via Media3's IMA extension. To enable ads, pass an `ads` object inside the `source` prop with a VAST or VMAP ad tag URL.
277
+
278
+ ### Basic Ad Playback
279
+
280
+ ```jsx
281
+ <Media3Player
282
+ source={{
283
+ uri: 'https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
284
+ ads: {
285
+ adTagUrl:
286
+ 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpreonly&ciu_szs=300x250%2C728x90&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&correlator=',
287
+ },
288
+ }}
289
+ autoplay
290
+ play
291
+ />
292
+ ```
293
+
294
+ ### Ads with DRM Content
295
+
296
+ You can combine ads with DRM-protected streams:
297
+
298
+ ```jsx
299
+ <Media3Player
300
+ source={{
301
+ uri: 'https://storage.googleapis.com/shaka-demo-assets/angel-one-widevine/dash.mpd',
302
+ type: 'dash',
303
+ drm: {
304
+ licenseUrl: 'https://cwip-shaka-proxy.appspot.com/no_auth',
305
+ },
306
+ ads: {
307
+ adTagUrl: 'https://your-ad-server.com/vmap-tag',
308
+ },
309
+ }}
310
+ autoplay
311
+ play
312
+ onError={e => console.error('Error:', e.message)}
313
+ />
314
+ ```
315
+
316
+ ### Notes
317
+
318
+ - Ads are supported on **Android only**.
319
+ - The `adTagUrl` should point to a valid VAST or VMAP XML endpoint.
320
+ - Both pre-roll and mid-roll ad configurations are supported via VMAP.
321
+ - The IMA ads loader is automatically initialized and released as needed when the ad tag URL changes.
322
+ - Ads play inline within the same `PlayerView` used for content playback.
323
+
324
+ ---
325
+
236
326
  ## Events
237
327
 
238
328
  | Event | Callback Signature | Description |
@@ -285,6 +375,23 @@ const drmProps: Media3PlayerProps = {
285
375
  };
286
376
  ```
287
377
 
378
+ **With IMA Ads:**
379
+
380
+ ```ts
381
+ const adsProps: Media3PlayerProps = {
382
+ source: {
383
+ uri: 'https://storage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4',
384
+ ads: {
385
+ adTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?...',
386
+ },
387
+ },
388
+ autoplay: true,
389
+ play: true,
390
+ style: {width: '100%', height: 250},
391
+ onError: err => console.log('Ad Error:', err.message),
392
+ };
393
+ ```
394
+
288
395
  ---
289
396
 
290
397
  ## Examples
@@ -371,6 +478,41 @@ const drmProps: Media3PlayerProps = {
371
478
  />
372
479
  ```
373
480
 
481
+ **With IMA Ads (VMAP):**
482
+
483
+ ```jsx
484
+ <Media3Player
485
+ source={{
486
+ uri: 'https://example.com/video.mp4',
487
+ ads: {
488
+ adTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?...',
489
+ },
490
+ }}
491
+ autoplay
492
+ play
493
+ />
494
+ ```
495
+
496
+ **Ads with DRM Stream:**
497
+
498
+ ```jsx
499
+ <Media3Player
500
+ source={{
501
+ uri: 'https://example.com/protected/manifest.mpd',
502
+ type: 'dash',
503
+ drm: {
504
+ licenseUrl: 'https://license.example.com/widevine',
505
+ },
506
+ ads: {
507
+ adTagUrl: 'https://pubads.g.doubleclick.net/gampad/ads?...',
508
+ },
509
+ }}
510
+ autoplay
511
+ play
512
+ onError={err => console.error('Error:', err.message)}
513
+ />
514
+ ```
515
+
374
516
  ---
375
517
 
376
518
  ## Contributing
@@ -65,4 +65,10 @@ dependencies {
65
65
 
66
66
  // Android core utilities and extensions (KTX)
67
67
  implementation "androidx.core:core-ktx:1.13.1"
68
+
69
+ // IMA Ads support for Media3
70
+ implementation "androidx.media3:media3-exoplayer-ima:$media3Version"
71
+
72
+ // Google IMA SDK
73
+ implementation "com.google.ads.interactivemedia.v3:interactivemedia:3.33.0"
68
74
  }
@@ -22,6 +22,9 @@ import androidx.media3.exoplayer.source.ProgressiveMediaSource
22
22
  import androidx.media3.exoplayer.dash.DashMediaSource
23
23
  import androidx.media3.exoplayer.hls.HlsMediaSource
24
24
  import androidx.media3.exoplayer.smoothstreaming.SsMediaSource
25
+ import androidx.media3.exoplayer.ima.ImaAdsLoader
26
+ import androidx.media3.exoplayer.source.ads.AdsMediaSource
27
+ import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
25
28
 
26
29
  /**
27
30
  * Custom view that wraps ExoPlayer and PlayerView, integrating with React Native.
@@ -39,6 +42,10 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
39
42
  private var play: Boolean = false
40
43
  // Mute state controlled by the JS prop
41
44
  private var mute: Boolean = false
45
+ // IMA Ads loader
46
+ private var adsLoader: ImaAdsLoader? = null
47
+ // Currently loaded ad tag URL
48
+ private var currentAdTagUrl: String? = null
42
49
 
43
50
  /**
44
51
  * Constructor: initialize the PlayerView and attach it to the layout.
@@ -52,6 +59,21 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
52
59
  addView(playerView)
53
60
  }
54
61
 
62
+ /**
63
+ * Initializes the IMA Ads loader and sets it to the player.
64
+ * @param adTagUrl The URL of the ad tag to load.
65
+ */
66
+ private fun initializeAdsLoader(adTagUrl: String) {
67
+ if (adsLoader == null || currentAdTagUrl != adTagUrl) {
68
+ adsLoader?.release()
69
+
70
+ adsLoader = ImaAdsLoader.Builder(context).build()
71
+ currentAdTagUrl = adTagUrl
72
+
73
+ adsLoader?.setPlayer(exoPlayer)
74
+ }
75
+ }
76
+
55
77
  /**
56
78
  * Initializes ExoPlayer and attaches a listener for player events.
57
79
  * Ensures only one instance exists.
@@ -61,6 +83,9 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
61
83
  exoPlayer = ExoPlayer.Builder(context).build()
62
84
  playerView.player = exoPlayer
63
85
 
86
+ // Set the IMA Ads loader to the player
87
+ adsLoader?.setPlayer(exoPlayer)
88
+
64
89
  // Listen for playback state changes and errors
65
90
  exoPlayer?.addListener(object : Player.Listener {
66
91
  override fun onPlaybackStateChanged(state: Int) {
@@ -219,7 +244,8 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
219
244
  uriString: String?,
220
245
  type: String?,
221
246
  licenseUrl: String?,
222
- headers: Map<String, String>?
247
+ headers: Map<String, String>?,
248
+ adTagUrl: String?
223
249
  ) {
224
250
  // Return early if no valid URI is provided
225
251
  if (uriString.isNullOrEmpty()) return
@@ -227,6 +253,15 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
227
253
  // Store the URI string for reference
228
254
  sourceUri = uriString
229
255
 
256
+
257
+ // Release and reset the adsLoader if the ad tag URL has changed -
258
+ // to prevent playing old ads or memory leaks.
259
+ if (currentAdTagUrl != adTagUrl) {
260
+ adsLoader?.setPlayer(null)
261
+ adsLoader?.release()
262
+ adsLoader = null
263
+ }
264
+
230
265
  // Ensure the ExoPlayer instance is initialized before use
231
266
  initializePlayer()
232
267
 
@@ -255,10 +290,33 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
255
290
  MediaItem.fromUri(uri)
256
291
  }
257
292
 
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)
293
+ // If an ad tag URL is provided, configure the player for ad playback using IMA:
294
+ // - Initialize the AdsLoader (only if not already done for the current ad tag)
295
+ // - Build a MediaItem that includes the ads configuration
296
+ // - Create a DefaultMediaSourceFactory with ads and ad view providers
297
+ // - Set the media source with ads on the player
298
+ if (!adTagUrl.isNullOrEmpty()) {
299
+ initializeAdsLoader(adTagUrl)
300
+
301
+ val mediaItemWithAds = mediaItem.buildUpon()
302
+ .setAdsConfiguration(
303
+ MediaItem.AdsConfiguration.Builder(Uri.parse(adTagUrl)).build()
304
+ )
305
+ .build()
306
+
307
+ val mediaSourceFactory = DefaultMediaSourceFactory(DefaultHttpDataSource.Factory())
308
+ .setAdsLoaderProvider { adsLoader }
309
+ .setAdViewProvider(playerView)
310
+
311
+ exoPlayer?.setMediaSource(
312
+ mediaSourceFactory.createMediaSource(mediaItemWithAds)
313
+ )
314
+ } else {
315
+ // If no ad tag URL, simply build and set the normal content media source
316
+ val mediaSource = buildMediaSource(uri, mediaItem, type)
317
+ exoPlayer?.setMediaSource(mediaSource)
318
+ }
319
+ // Prepare the player for playback (loads media and notifies listeners)
262
320
  exoPlayer?.prepare()
263
321
  }
264
322
 
@@ -300,10 +358,25 @@ class Media3PlayerView(context: Context) : FrameLayout(context) {
300
358
  * This should be called when the view is destroyed.
301
359
  */
302
360
  fun releasePlayer() {
361
+ // Release the IMA Ads loader
362
+ adsLoader?.setPlayer(null)
363
+ adsLoader?.release()
364
+ adsLoader = null
365
+
366
+ // Release the ExoPlayer
303
367
  exoPlayer?.release()
304
368
  exoPlayer = null
305
369
  }
306
370
 
371
+ /**
372
+ * Called when the view is detached from the window.
373
+ * Releases the player and cleans up resources.
374
+ */
375
+ override fun onDetachedFromWindow() {
376
+ super.onDetachedFromWindow()
377
+ releasePlayer()
378
+ }
379
+
307
380
  /**
308
381
  * Helper function to send events and optional data payloads to React Native JS side.
309
382
  * @param eventName Name of the event (e.g., "topReady", "topError")
@@ -57,6 +57,10 @@ class Media3PlayerViewManager :
57
57
  var headers: Map<String, String>? = null
58
58
  val drmMap = source?.getMap("drm")
59
59
 
60
+ // Extract the IMA Ads configuration from the source map.
61
+ val adsMap = source?.getMap("ads")
62
+ val adTagUrl = adsMap?.getString("adTagUrl")
63
+
60
64
  if (drmMap != null) {
61
65
  licenseUrl = drmMap.getString("licenseUrl")
62
66
  val headersArray = drmMap.getArray("headers")
@@ -74,7 +78,7 @@ class Media3PlayerViewManager :
74
78
  }
75
79
  }
76
80
  if (!uri.isNullOrEmpty()) {
77
- view.setSource(uri, type, licenseUrl, headers)
81
+ view.setSource(uri, type, licenseUrl, headers, adTagUrl)
78
82
  }
79
83
  }
80
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-media3-player",
3
- "version": "2.1.1",
3
+ "version": "3.0.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,7 +1,10 @@
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, WithDefault} from 'react-native/Libraries/Types/CodegenTypes';
4
+ import type {
5
+ DirectEventHandler,
6
+ WithDefault,
7
+ } from 'react-native/Libraries/Types/CodegenTypes';
5
8
  // Import codegenNativeComponent to register the native UI component for use in React Native
6
9
  import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
7
10
 
@@ -14,13 +17,20 @@ type DRMConfig = Readonly<{
14
17
  licenseUrl: string;
15
18
  headers?: ReadonlyArray<HeaderEntry>;
16
19
  }>;
20
+
21
+ type AdsConfig = Readonly<{
22
+ adTagUrl: string;
23
+ }>;
24
+
17
25
  // Source defines the media source for the player component.
18
26
  // - uri: The URI of the media to be played (required).
19
27
  // - drm: (Optional) DRM configuration for protected content.
28
+ // - ads: (Optional) IMA Ads configuration for ad playback.
20
29
  type Source = Readonly<{
21
30
  uri: string;
22
31
  type?: WithDefault<'dash' | 'hls' | 'mp4', 'mp4'>;
23
32
  drm?: DRMConfig;
33
+ ads?: AdsConfig;
24
34
  }>;
25
35
  // Event type emitted when the player is ready
26
36
  type OnReadyEvent = Readonly<{}>;