react-native-theoplayer 10.3.0 → 10.5.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.
Files changed (81) hide show
  1. package/CHANGELOG.md +44 -3
  2. package/README.md +2 -2
  3. package/android/build.gradle +1 -1
  4. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  5. package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +44 -9
  6. package/android/src/main/java/com/theoplayer/audio/BackgroundAudioConfig.kt +7 -0
  7. package/android/src/main/java/com/theoplayer/audio/BackgroundAudioConfigAdapter.kt +12 -3
  8. package/android/src/main/java/com/theoplayer/drm/ContentProtectionAdapter.kt +1 -0
  9. package/android/src/main/java/com/theoplayer/media/MediaPlaybackService.kt +17 -34
  10. package/android/src/main/java/com/theoplayer/media/MediaSessionConfig.kt +10 -0
  11. package/android/src/main/java/com/theoplayer/media/MediaSessionConfigAdapter.kt +8 -0
  12. package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +8 -4
  13. package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +3 -0
  14. package/android/src/main/java/com/theoplayer/theoads/THEOadsEventAdapter.kt +1 -6
  15. package/android/src/main/java/com/theoplayer/theolive/EndpointAdapter.kt +16 -0
  16. package/ios/THEOplayerRCTDebug.swift +1 -1
  17. package/ios/THEOplayerRCTMainEventHandler.swift +0 -1
  18. package/ios/THEOplayerRCTMediaTrackEventHandler.swift +47 -28
  19. package/ios/THEOplayerRCTPlayerAPI.swift +1 -0
  20. package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +55 -62
  21. package/ios/THEOplayerRCTView+AppState.swift +33 -0
  22. package/ios/THEOplayerRCTView.swift +29 -1
  23. package/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift +19 -17
  24. package/ios/backgroundAudio/THEOplayerRCTNowPlayingManager.swift +3 -3
  25. package/ios/backgroundAudio/THEOplayerRCTRemoteCommandsManager.swift +26 -15
  26. package/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +8 -0
  27. package/ios/backgroundAudio/THEOplayerRCTView+MediaControlConfig.swift +8 -0
  28. package/ios/cache/THEOplayerRCTCacheAPI.swift +1 -2
  29. package/ios/pip/THEOplayerRCTPipControlsManager.swift +1 -1
  30. package/ios/pip/THEOplayerRCTPipManager.swift +38 -26
  31. package/ios/theoAds/THEOplayerRCTSourceDescriptionBuilder+TheoAds.swift +2 -0
  32. package/ios/theolive/THEOplayerRCTTHEOliveEventAdapter.swift +4 -0
  33. package/lib/commonjs/api/backgroundAudio/BackgroundAudioConfiguration.js.map +1 -1
  34. package/lib/commonjs/api/source/SourceDescription.js.map +1 -1
  35. package/lib/commonjs/internal/THEOplayerView.js +4 -1
  36. package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
  37. package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js +8 -3
  38. package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
  39. package/lib/commonjs/internal/adapter/WebEventForwarder.js +4 -1
  40. package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
  41. package/lib/commonjs/internal/adapter/event/native/NativeTheoAdsEvent.js +8 -2
  42. package/lib/commonjs/internal/adapter/event/native/NativeTheoAdsEvent.js.map +1 -1
  43. package/lib/commonjs/manifest.json +1 -1
  44. package/lib/module/api/backgroundAudio/BackgroundAudioConfiguration.js.map +1 -1
  45. package/lib/module/api/source/SourceDescription.js.map +1 -1
  46. package/lib/module/internal/THEOplayerView.js +4 -1
  47. package/lib/module/internal/THEOplayerView.js.map +1 -1
  48. package/lib/module/internal/adapter/THEOplayerWebAdapter.js +8 -3
  49. package/lib/module/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
  50. package/lib/module/internal/adapter/WebEventForwarder.js +5 -2
  51. package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
  52. package/lib/module/internal/adapter/event/native/NativeTheoAdsEvent.js +8 -2
  53. package/lib/module/internal/adapter/event/native/NativeTheoAdsEvent.js.map +1 -1
  54. package/lib/module/manifest.json +1 -1
  55. package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts +9 -0
  56. package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts.map +1 -1
  57. package/lib/typescript/api/media/MediaControlConfiguration.d.ts +17 -0
  58. package/lib/typescript/api/media/MediaControlConfiguration.d.ts.map +1 -1
  59. package/lib/typescript/api/source/SourceDescription.d.ts +6 -6
  60. package/lib/typescript/api/source/SourceDescription.d.ts.map +1 -1
  61. package/lib/typescript/api/source/drm/DRMConfiguration.d.ts +10 -0
  62. package/lib/typescript/api/source/drm/DRMConfiguration.d.ts.map +1 -1
  63. package/lib/typescript/api/theolive/TheoLiveEndpoint.d.ts +7 -0
  64. package/lib/typescript/api/theolive/TheoLiveEndpoint.d.ts.map +1 -1
  65. package/lib/typescript/internal/THEOplayerView.d.ts.map +1 -1
  66. package/lib/typescript/internal/adapter/THEOplayerWebAdapter.d.ts.map +1 -1
  67. package/lib/typescript/internal/adapter/WebEventForwarder.d.ts.map +1 -1
  68. package/lib/typescript/internal/adapter/event/native/NativeTheoAdsEvent.d.ts +1 -1
  69. package/lib/typescript/internal/adapter/event/native/NativeTheoAdsEvent.d.ts.map +1 -1
  70. package/package.json +5 -5
  71. package/react-native-theoplayer.podspec +7 -7
  72. package/src/api/backgroundAudio/BackgroundAudioConfiguration.ts +10 -0
  73. package/src/api/media/MediaControlConfiguration.ts +19 -0
  74. package/src/api/source/SourceDescription.ts +7 -7
  75. package/src/api/source/drm/DRMConfiguration.ts +9 -0
  76. package/src/api/theolive/TheoLiveEndpoint.ts +8 -0
  77. package/src/internal/THEOplayerView.tsx +4 -1
  78. package/src/internal/adapter/THEOplayerWebAdapter.ts +8 -3
  79. package/src/internal/adapter/WebEventForwarder.ts +45 -32
  80. package/src/internal/adapter/event/native/NativeTheoAdsEvent.ts +9 -4
  81. package/src/manifest.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,11 +5,43 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.1.0/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [10.5.0] - 25-11-24
9
+
10
+ ### Fixed
11
+
12
+ - Fixed an issue on iOS where the scrim of an IMA ad was in a wrong position due to incorrect `safeAreaInsets`.
13
+
14
+ ### Added
15
+
16
+ - Added `stopOnBackground` property to `BackgroundAudioConfiguration` to control whether playback should stop when the app goes to the background.
17
+ - Added `millicastSrc` to `TheoLiveEndpoint` for Web and Android.
18
+ - Added support for configuring query parameters that are common to multiple key system configurations. The parameters defined in `contentProtection.queryParameters` will be merged with any query parameters that are explicitly defined on a key system configuration, whereby the latter takes precedence.
19
+
20
+ ### Changed
21
+
22
+ - Updated the active quality info extraction on iOS to use the activeQualityChange event data instead of player API.
23
+
24
+ ## [10.4.0] - 25-11-13
25
+
26
+ ### Fixed
27
+
28
+ - Fixed an issue on Android, where the app could crash when a `THEOads` error event was dispatched.
29
+ - Fixed an issue on Web, where the contents of the `reason` property of the THEOads `intenttofallback` event did not conform to its declared `PlayerError` type.
30
+
31
+ ### Added
32
+
33
+ - Pass `streamActivityMonitorId` property for `THEOAdDescription` on iOS and Android.
34
+ - Added `allowLivePlayPause` and `seekToLiveOnResume` properties to `PlayerConfiguration.mediaControl` to control pausing and resuming behavior on live streams from the lockscreen controls.
35
+
36
+ ### Changed
37
+
38
+ - Upgraded example app to React-Native v0.82.
39
+
8
40
  ## [10.3.0] - 25-10-27
9
41
 
10
42
  ### Fixed
11
43
 
12
- - Fixed an issue on web, where adbreak related AdEvents did no longer contain the adBreak info.
44
+ - Fixed an issue on Web, where adbreak related AdEvents did no longer contain the adBreak info.
13
45
 
14
46
  ### Changed
15
47
 
@@ -30,6 +62,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
30
62
 
31
63
  ## [10.2.0] - 25-10-20
32
64
 
65
+ ### Added
66
+
67
+ - React Native THEOplayer now supports [React Compiler](https://react.dev/learn/react-compiler).
68
+ - For Expo users: refer to the [Expo docs](https://docs.expo.dev/guides/react-compiler/) to set up React Compiler in your app.
69
+
33
70
  ### Fixed
34
71
 
35
72
  - Fixed an issue on iOS where the player crashed when it was destroyed while in fullscreen.
@@ -40,6 +77,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
40
77
 
41
78
  - Added `THEOplayer.theoLive` and deprecated `THEOplayer.theolive`, to be consistent with the THEOplayer SDKs for other platforms.
42
79
 
80
+ ### Changed
81
+
82
+ - Moved the `hlsDateRange` property from `SourceConfiguration` to its correct location in `TypedSource`.
83
+
43
84
  ## [10.1.0] - 25-10-06
44
85
 
45
86
  ### Added
@@ -114,11 +155,11 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
114
155
 
115
156
  - Deprecated the `BaseSource.integration` property in favor of `TypedSource.type`.
116
157
 
117
- ## Added
158
+ ### Added
118
159
 
119
160
  - Added support for `THEOlive` events.
120
161
 
121
- ## Fixed
162
+ ### Fixed
122
163
 
123
164
  - Fixed an issue on Android where, depending on the project structure, the Maven repository list would be incorrect.
124
165
 
package/README.md CHANGED
@@ -73,8 +73,8 @@ please reach out to us for support.
73
73
  <tbody>
74
74
  <tr>
75
75
  <td><strong>Streaming</strong></td>
76
- <td colspan="2">MPEG-DASH (fmp4, CMAF), HLS (TS, CMAF), Progressive MP4, MP3</td>
77
- <td>HLS (TS, CMAF), Progressive MP4, MP3</td>
76
+ <td colspan="2">MPEG-DASH (fmp4, CMAF), HLS (TS, CMAF), Progressive MP4, MP3, M4A</td>
77
+ <td>HLS (TS, CMAF), Progressive MP4, MP3, M4A</td>
78
78
  </tr>
79
79
  <tr>
80
80
  <td><strong>Content Protection</strong></td>
@@ -152,7 +152,7 @@ dependencies {
152
152
  }
153
153
 
154
154
  //noinspection GradleDynamicVersion
155
- implementation("com.facebook.react:react-native:+")
155
+ implementation("com.facebook.react:react-android:+")
156
156
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
157
157
  implementation("androidx.appcompat:appcompat:$appcompatVersion")
158
158
  implementation("androidx.core:core-ktx:$corektxVersion")
@@ -1,5 +1,5 @@
1
1
  distributionBase=GRADLE_USER_HOME
2
2
  distributionPath=wrapper/dists
3
- distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
3
+ distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
4
4
  zipStoreBase=GRADLE_USER_HOME
5
5
  zipStorePath=wrapper/dists
@@ -31,6 +31,7 @@ import com.theoplayer.android.api.millicast.MillicastIntegrationFactory
31
31
  import com.theoplayer.android.api.player.Player
32
32
  import com.theoplayer.android.api.player.RenderingTarget
33
33
  import com.theoplayer.android.connector.mediasession.MediaSessionConnector
34
+ import com.theoplayer.android.connector.mediasession.MediaSessionListener
34
35
  import com.theoplayer.audio.AudioBecomingNoisyManager
35
36
  import com.theoplayer.audio.AudioFocusManager
36
37
  import com.theoplayer.audio.BackgroundAudioConfig
@@ -50,6 +51,11 @@ private const val ALLOWED_PLAYBACK_ACTIONS = (
50
51
  PlaybackStateCompat.ACTION_REWIND or
51
52
  PlaybackStateCompat.ACTION_SET_PLAYBACK_SPEED)
52
53
 
54
+ private const val ALLOWED_PLAY_PAUSE_ACTIONS = (
55
+ PlaybackStateCompat.ACTION_PLAY_PAUSE or
56
+ PlaybackStateCompat.ACTION_PLAY or
57
+ PlaybackStateCompat.ACTION_PAUSE)
58
+
53
59
  class ReactTHEOplayerContext private constructor(
54
60
  private val reactContext: ThemedReactContext,
55
61
  private val configAdapter: PlayerConfigAdapter
@@ -82,14 +88,13 @@ class ReactTHEOplayerContext private constructor(
82
88
  get() = playerView.player
83
89
 
84
90
  private val uiModeManager by lazy {
85
- reactContext.getSystemService(Context.UI_MODE_SERVICE) as? UiModeManager
91
+ reactContext.getSystemService(Context.UI_MODE_SERVICE) as? UiModeManager
86
92
  }
87
93
 
88
94
  var daiIntegration: GoogleDaiIntegration? = null
89
95
  var imaIntegration: GoogleImaIntegration? = null
90
96
  private var theoAdsIntegration: TheoAdsIntegration? = null
91
97
  var castIntegration: CastIntegration? = null
92
- @Suppress("UnstableApiUsage")
93
98
  var wasPlayingOnHostPause: Boolean = false
94
99
  private var isHostPaused: Boolean = false
95
100
  private var millicastIntegration: MillicastIntegration? = null
@@ -131,6 +136,19 @@ class ReactTHEOplayerContext private constructor(
131
136
  }
132
137
  }
133
138
 
139
+ private val mediaSessionListener = object : MediaSessionListener() {
140
+ override fun onStop() {
141
+ binder?.stopForegroundService()
142
+ }
143
+
144
+ override fun onPlay() {
145
+ // Optionally seek to live, if configured.
146
+ if (mediaSessionConfig.seekToLiveOnResume && player.duration.isInfinite()) {
147
+ player.currentTime = Double.POSITIVE_INFINITY
148
+ }
149
+ }
150
+ }
151
+
134
152
  private fun applyBackgroundPlaybackConfig(
135
153
  config: BackgroundAudioConfig,
136
154
  prevConfig: BackgroundAudioConfig?
@@ -167,10 +185,12 @@ class ReactTHEOplayerContext private constructor(
167
185
  val isLive = player.duration.isInfinite()
168
186
  val isInAd = player.ads.isPlaying
169
187
  mediaSessionConnector?.enabledPlaybackActions = when {
170
- isInAd || isLive && !isTV -> 0
171
- isLive && isTV -> ALLOWED_PLAYBACK_ACTIONS xor
172
- PlaybackStateCompat.ACTION_FAST_FORWARD xor
173
- PlaybackStateCompat.ACTION_REWIND
188
+ // Allow trick-play for live events if configured
189
+ isLive && mediaSessionConfig.allowLivePlayPause -> ALLOWED_PLAY_PAUSE_ACTIONS
190
+ isLive && !mediaSessionConfig.allowLivePlayPause -> 0
191
+ // Do not allow playback actions during ad play-out
192
+ isInAd -> 0
193
+
174
194
  else -> ALLOWED_PLAYBACK_ACTIONS
175
195
  }
176
196
  }
@@ -263,9 +283,13 @@ class ReactTHEOplayerContext private constructor(
263
283
  }
264
284
  }
265
285
 
266
- private fun applyMediaSessionConfig(connector: MediaSessionConnector?, config: MediaSessionConfig) {
286
+ private fun applyMediaSessionConfig(
287
+ connector: MediaSessionConnector?,
288
+ config: MediaSessionConfig
289
+ ) {
267
290
  connector?.apply {
268
291
  debug = BuildConfig.LOG_MEDIASESSION_EVENTS
292
+ removeListener(mediaSessionListener)
269
293
 
270
294
  player = this@ReactTHEOplayerContext.player
271
295
 
@@ -279,10 +303,15 @@ class ReactTHEOplayerContext private constructor(
279
303
  // Pass metadata from source description
280
304
  setMediaSessionMetadata(player?.source)
281
305
 
306
+ // Do not let MediaButtons restart the player when media session is not active.
307
+ // https://developer.android.com/media/legacy/media-buttons#restarting-inactive-mediasessions
308
+ this.mediaSession.setMediaButtonReceiver(null)
309
+
282
310
  // Install a queue navigator, but only if we want to handle skip buttons.
283
311
  if (mediaSessionConfig.convertSkipToSeek) {
284
312
  queueNavigator = MediaQueueNavigator(mediaSessionConfig)
285
313
  }
314
+ addListener(mediaSessionListener)
286
315
  }
287
316
  }
288
317
 
@@ -339,8 +368,8 @@ class ReactTHEOplayerContext private constructor(
339
368
  if (BuildConfig.EXTENSION_MILLICAST) {
340
369
  millicastIntegration = MillicastIntegrationFactory.createMillicastIntegration()
341
370
  .also {
342
- playerView.player.addIntegration(it)
343
- }
371
+ playerView.player.addIntegration(it)
372
+ }
344
373
  }
345
374
  } catch (e: Exception) {
346
375
  Log.w(TAG, "Failed to configure Millicast integration ${e.message}")
@@ -429,6 +458,12 @@ class ReactTHEOplayerContext private constructor(
429
458
  // The player pauses and goes to the background, we can abandon audio focus.
430
459
  audioFocusManager?.abandonAudioFocus()
431
460
  }
461
+
462
+ // Optionally fully stop play-out, unless PiP mode is active.
463
+ if (backgroundAudioConfig.stopOnBackground
464
+ && reactContext.currentActivity?.isInPictureInPictureMode != true) {
465
+ player.stop()
466
+ }
432
467
  }
433
468
 
434
469
  /**
@@ -7,4 +7,11 @@ data class BackgroundAudioConfig(
7
7
  * @defaultValue `false`
8
8
  */
9
9
  val enabled: Boolean,
10
+
11
+ /**
12
+ * Set whether the player should stop play-out when the app goes to background.
13
+ *
14
+ * @defaultValue `false`
15
+ */
16
+ val stopOnBackground: Boolean = false
10
17
  )
@@ -4,12 +4,21 @@ import com.facebook.react.bridge.ReadableMap
4
4
 
5
5
  object BackgroundAudioConfigAdapter {
6
6
  private const val PROP_ENABLED = "enabled"
7
+ private const val PROP_STOP_ON_BACKGROUND = "stopOnBackground"
7
8
 
8
9
  private const val DEFAULT_ENABLED = false
10
+ private const val DEFAULT_STOP_ON_BACKGROUND = false
9
11
 
10
12
  fun fromProps(props: ReadableMap?): BackgroundAudioConfig {
11
- val enabled =
12
- if (props?.hasKey(PROP_ENABLED) == true) props.getBoolean(PROP_ENABLED) else DEFAULT_ENABLED
13
- return BackgroundAudioConfig(enabled)
13
+ val enabled = props?.hasKey(PROP_ENABLED)?.let {
14
+ props.getBoolean(PROP_ENABLED)
15
+ } ?: DEFAULT_ENABLED
16
+ val destroy = props?.hasKey(PROP_STOP_ON_BACKGROUND)?.let {
17
+ props.getBoolean(PROP_STOP_ON_BACKGROUND)
18
+ } ?: DEFAULT_STOP_ON_BACKGROUND
19
+ return BackgroundAudioConfig(
20
+ enabled = enabled,
21
+ stopOnBackground = destroy
22
+ )
14
23
  }
15
24
  }
@@ -104,6 +104,7 @@ object ContentProtectionAdapter {
104
104
  if (jsonConfig.has(PROP_INTEGRATION_PARAMETERS)) {
105
105
  integrationParameters(fromJSONObjectToMap(jsonConfig.getJSONObject(PROP_INTEGRATION_PARAMETERS)))
106
106
  }
107
+ queryParameters(fromJSONObjectToMap(jsonConfig.optJSONObject(PROP_QUERY_PARAMETERS)))
107
108
  }.build()
108
109
  }
109
110
 
@@ -13,11 +13,9 @@ import android.util.Log
13
13
  import androidx.core.app.ServiceCompat
14
14
  import androidx.core.content.ContextCompat
15
15
  import androidx.media.session.MediaButtonReceiver
16
- import com.theoplayer.BuildConfig
17
16
  import com.theoplayer.ReactTHEOplayerContext
18
17
  import com.theoplayer.android.api.player.Player
19
18
  import com.theoplayer.android.connector.mediasession.MediaSessionConnector
20
- import com.theoplayer.android.connector.mediasession.MediaSessionListener
21
19
 
22
20
  private const val STOP_SERVICE_IF_APP_REMOVED = true
23
21
 
@@ -39,7 +37,7 @@ class MediaPlaybackService : Service() {
39
37
 
40
38
  private lateinit var mediaSessionConnector: MediaSessionConnector
41
39
  private val mediaSession: MediaSessionCompat
42
- get() = mediaSessionConnector.mediaSession
40
+ get() = mediaSessionConnector.mediaSession
43
41
 
44
42
  inner class MediaPlaybackBinder : Binder() {
45
43
  private val service: MediaPlaybackService
@@ -119,7 +117,6 @@ class MediaPlaybackService : Service() {
119
117
 
120
118
  override fun onDestroy() {
121
119
  super.onDestroy()
122
- removeListeners()
123
120
  mediaSessionConnector.destroy()
124
121
  playerContext = null
125
122
  }
@@ -147,16 +144,7 @@ class MediaPlaybackService : Service() {
147
144
  }
148
145
 
149
146
  // Create a MediaSessionConnector.
150
- mediaSessionConnector = MediaSessionConnector(mediaSession).apply {
151
- debug = BuildConfig.LOG_MEDIASESSION_EVENTS
152
-
153
- // Set mediaSession active
154
- setActive(BuildConfig.EXTENSION_MEDIASESSION)
155
-
156
- // Do not let MediaButtons restart the player when media session is not active.
157
- // https://developer.android.com/media/legacy/media-buttons#restarting-inactive-mediasessions
158
- mediaSession.setMediaButtonReceiver(null)
159
- }
147
+ mediaSessionConnector = MediaSessionConnector(mediaSession)
160
148
  }
161
149
 
162
150
  private fun stopForegroundService() {
@@ -165,28 +153,10 @@ class MediaPlaybackService : Service() {
165
153
  }
166
154
 
167
155
  private fun connectPlayerContext(playerContext: ReactTHEOplayerContext) {
168
- if (this.playerContext != null) {
169
- removeListeners()
170
- }
171
156
  this.playerContext = playerContext
172
- addListeners()
173
157
  updateNotification()
174
158
  }
175
159
 
176
- private val mediaSessionListener = object : MediaSessionListener() {
177
- override fun onStop() {
178
- stopForegroundService()
179
- }
180
- }
181
-
182
- private fun addListeners() {
183
- mediaSessionConnector.addListener(mediaSessionListener)
184
- }
185
-
186
- private fun removeListeners() {
187
- mediaSessionConnector.removeListener(mediaSessionListener)
188
- }
189
-
190
160
  private fun updateNotification() {
191
161
  val player = player
192
162
  when {
@@ -204,9 +174,17 @@ class MediaPlaybackService : Service() {
204
174
  PlaybackStateCompat.STATE_PAUSED -> {
205
175
  // Fetch large icon asynchronously
206
176
  fetchImageFromMetadata(player?.source) { largeIcon ->
207
- notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build(playbackState, largeIcon, mediaSessionConfig.mediaSessionEnabled))
177
+ notificationManager.notify(
178
+ NOTIFICATION_ID,
179
+ notificationBuilder.build(
180
+ playbackState,
181
+ largeIcon,
182
+ mediaSessionConfig.mediaSessionEnabled
183
+ )
184
+ )
208
185
  }
209
186
  }
187
+
210
188
  PlaybackStateCompat.STATE_PLAYING -> {
211
189
  // When a service runs in the foreground, it must display a notification, ideally
212
190
  // with one or more transport controls. The notification should also include useful
@@ -220,6 +198,7 @@ class MediaPlaybackService : Service() {
220
198
  startForegroundWithPlaybackState(playbackState, largeIcon)
221
199
  }
222
200
  }
201
+
223
202
  PlaybackStateCompat.STATE_STOPPED -> {
224
203
  // Remove this service from foreground state, allowing it to be killed if more memory is
225
204
  // needed. This does not stop the service from running (for that you use stopSelf()
@@ -227,13 +206,17 @@ class MediaPlaybackService : Service() {
227
206
  // Also remove the notification.
228
207
  ServiceCompat.stopForeground(this, ServiceCompat.STOP_FOREGROUND_REMOVE)
229
208
  }
209
+
230
210
  else -> {
231
211
  // Ignore
232
212
  }
233
213
  }
234
214
  }
235
215
 
236
- private fun startForegroundWithPlaybackState(@PlaybackStateCompat.State playbackState: Int, largeIcon: Bitmap? = null) {
216
+ private fun startForegroundWithPlaybackState(
217
+ @PlaybackStateCompat.State playbackState: Int,
218
+ largeIcon: Bitmap? = null
219
+ ) {
237
220
  try {
238
221
  ServiceCompat.startForeground(
239
222
  this,
@@ -20,4 +20,14 @@ data class MediaSessionConfig (
20
20
  * Whether "skip track" events should be handled the same as "fast-forward/rewind".
21
21
  */
22
22
  var convertSkipToSeek: Boolean = false,
23
+
24
+ /**
25
+ * Whether to allow play/pause of live assets.
26
+ */
27
+ var allowLivePlayPause: Boolean = false,
28
+
29
+ /**
30
+ * Whether to seek to live when resuming a live stream.
31
+ */
32
+ var seekToLiveOnResume: Boolean = false,
23
33
  )
@@ -7,6 +7,8 @@ object MediaSessionConfigAdapter {
7
7
  private const val PROP_SKIP_FORWARD_INTERVAL = "skipForwardInterval"
8
8
  private const val PROP_SKIP_BACKWARD_INTERVAL = "skipBackwardInterval"
9
9
  private const val PROP_CONVERT_SKIP = "convertSkipToSeek"
10
+ private const val PROP_ALLOW_LIVE_PLAY_PAUSE = "allowLivePlayPause"
11
+ private const val PROP_SEEK_TO_LIVE_RESUME = "seekToLiveOnResume"
10
12
 
11
13
  fun fromProps(props: ReadableMap?): MediaSessionConfig {
12
14
  return MediaSessionConfig().apply {
@@ -22,6 +24,12 @@ object MediaSessionConfigAdapter {
22
24
  if (props?.hasKey(PROP_CONVERT_SKIP) == true) {
23
25
  convertSkipToSeek = props.getBoolean(PROP_CONVERT_SKIP)
24
26
  }
27
+ if (props?.hasKey(PROP_ALLOW_LIVE_PLAY_PAUSE) == true) {
28
+ allowLivePlayPause = props.getBoolean(PROP_ALLOW_LIVE_PLAY_PAUSE)
29
+ }
30
+ if (props?.hasKey(PROP_SEEK_TO_LIVE_RESUME) == true) {
31
+ seekToLiveOnResume = props.getBoolean(PROP_SEEK_TO_LIVE_RESUME)
32
+ }
25
33
  }
26
34
  }
27
35
  }
@@ -319,14 +319,18 @@ class PresentationManager(
319
319
  currentPresentationModeChangeContext = context
320
320
  eventEmitter.emitPresentationModeChange(presentationMode, prevPresentationMode, context)
321
321
 
322
- // Resume playing when going to PiP and player was playing
322
+ // Resume play-out when going to PiP and player was playing
323
323
  if (presentationMode == PresentationMode.PICTURE_IN_PICTURE && viewCtx.wasPlayingOnHostPause) {
324
324
  viewCtx.player.play()
325
325
  }
326
326
 
327
- // Apply background audio config when closing PiP window
328
- if (context?.pip == PresentationModeChangePipContext.CLOSED && !viewCtx.backgroundAudioConfig.enabled) {
329
- viewCtx.player.pause()
327
+ // When closing PiP window
328
+ if (context?.pip == PresentationModeChangePipContext.CLOSED) {
329
+ // Pause if background audio is not enabled
330
+ if (!viewCtx.backgroundAudioConfig.enabled) viewCtx.player.pause()
331
+
332
+ // Optionally fully stop play-out
333
+ if (viewCtx.backgroundAudioConfig.stopOnBackground) viewCtx.player.stop()
330
334
  }
331
335
  }
332
336
  }
@@ -69,6 +69,7 @@ private const val PROP_USE_ID3 = "useId3"
69
69
  private const val PROP_RETRIEVE_POD_ID_URI = "retrievePodIdURI"
70
70
  private const val PROP_INITIALIZATION_DELAY = "initializationDelay"
71
71
  private const val PROP_SSE_ENDPOINT = "sseEndpoint"
72
+ private const val PROP_STREAM_ACTIVITY_MONITOR_ID = "streamActivityMonitorId"
72
73
  private const val PROP_LATENCY_CONFIGURATION = "latencyConfiguration"
73
74
 
74
75
  private const val ERROR_IMA_NOT_ENABLED = "Google IMA support not enabled."
@@ -272,6 +273,7 @@ class SourceAdapter {
272
273
  fileName.endsWith(".m3u8") -> return SourceType.HLSX
273
274
  fileName.endsWith(".mp4") -> return SourceType.MP4
274
275
  fileName.endsWith(".mp3") -> return SourceType.MP3
276
+ fileName.endsWith(".m4a") -> return SourceType.M4A
275
277
  }
276
278
  }
277
279
  }
@@ -334,6 +336,7 @@ class SourceAdapter {
334
336
  retrievePodIdURI = jsonAdDescription.optString(PROP_RETRIEVE_POD_ID_URI).takeIf { it.isNotEmpty() },
335
337
  initializationDelay = jsonAdDescription.optDouble(PROP_INITIALIZATION_DELAY).takeIf { it.isFinite() },
336
338
  sseEndpoint = jsonAdDescription.optString(PROP_SSE_ENDPOINT).takeIf { it.isNotEmpty() },
339
+ streamActivityMonitorId = jsonAdDescription.optString(PROP_STREAM_ACTIVITY_MONITOR_ID).takeIf { it.isNotEmpty() },
337
340
  )
338
341
  }
339
342
 
@@ -5,7 +5,6 @@ import com.facebook.react.bridge.WritableMap
5
5
  import com.theoplayer.android.api.ads.theoads.TheoAdsIntegration
6
6
  import com.theoplayer.android.api.ads.theoads.event.InterstitialErrorEvent
7
7
  import com.theoplayer.android.api.ads.theoads.event.InterstitialEvent
8
- import com.theoplayer.android.api.ads.theoads.event.TheoAdsErrorEvent
9
8
  import com.theoplayer.android.api.ads.theoads.event.TheoAdsEvent
10
9
  import com.theoplayer.android.api.ads.theoads.event.TheoAdsEventTypes
11
10
  import com.theoplayer.android.api.event.EventListener
@@ -19,8 +18,7 @@ private val FORWARDED_EVENTS = listOf(
19
18
  TheoAdsEventTypes.INTERSTITIAL_BEGIN,
20
19
  TheoAdsEventTypes.INTERSTITIAL_END,
21
20
  TheoAdsEventTypes.INTERSTITIAL_UPDATE,
22
- TheoAdsEventTypes.INTERSTITIAL_ERROR,
23
- TheoAdsEventTypes.THEOADS_ERROR
21
+ TheoAdsEventTypes.INTERSTITIAL_ERROR
24
22
  )
25
23
 
26
24
  class THEOadsEventAdapter(private val api: TheoAdsIntegration, private val emitter: Emitter) {
@@ -44,9 +42,6 @@ class THEOadsEventAdapter(private val api: TheoAdsIntegration, private val emitt
44
42
  (event as? InterstitialEvent)?.let {
45
43
  putMap(EVENT_PROP_INTERSTITIAL, THEOadsAdapter.fromInterstitial(event.interstitial))
46
44
  }
47
- (event as? TheoAdsErrorEvent)?.let {
48
- putString(EVENT_PROP_MESSAGE, event.message)
49
- }
50
45
  (event as? InterstitialErrorEvent)?.let {
51
46
  putString(EVENT_PROP_MESSAGE, event.message)
52
47
  }
@@ -4,9 +4,15 @@ import com.facebook.react.bridge.Arguments
4
4
  import com.facebook.react.bridge.WritableMap
5
5
  import com.theoplayer.android.api.theolive.ContentProtectionConfiguration
6
6
  import com.theoplayer.android.api.theolive.Endpoint
7
+ import com.theoplayer.android.api.theolive.EndpointMillicastSource
7
8
  import com.theoplayer.android.api.theolive.FairPlayConfiguration
8
9
  import com.theoplayer.android.api.theolive.KeySystemConfiguration
9
10
 
11
+ private const val PROP_MILLICAST_SRC = "millicastSrc"
12
+ private const val PROP_MILLICAST_NAME = "name"
13
+ private const val PROP_MILLICAST_ACCOUNTID = "accountId"
14
+ private const val PROP_MILLICAST_SUBSCRIBER_TOKEN ="subscriberToken"
15
+ private const val PROP_MILLICAST_DIRECTOR_URL = "directorUrl"
10
16
  private const val PROP_HESP_SRC = "hespSrc"
11
17
  private const val PROP_HLS_SRC = "hlsSrc"
12
18
  private const val PROP_AD_SRC = "adSrc"
@@ -24,8 +30,18 @@ private const val PROP_CERTIFICATE_URL = "certificateUrl"
24
30
 
25
31
  object EndpointAdapter {
26
32
 
33
+ fun fromEndPointMillicastSource(millicastSource: EndpointMillicastSource): WritableMap {
34
+ return Arguments.createMap().apply {
35
+ putString(PROP_MILLICAST_NAME , millicastSource.name)
36
+ putString(PROP_MILLICAST_ACCOUNTID, millicastSource.accountId)
37
+ millicastSource.directorUrl?.let { putString(PROP_MILLICAST_DIRECTOR_URL, it) }
38
+ millicastSource.subscriberToken?.let { putString(PROP_MILLICAST_SUBSCRIBER_TOKEN, it) }
39
+ }
40
+ }
41
+
27
42
  fun fromEndpoint(endPoint: Endpoint): WritableMap {
28
43
  return Arguments.createMap().apply {
44
+ endPoint.millicastSrc?.let { putMap(PROP_MILLICAST_SRC, fromEndPointMillicastSource(it)) }
29
45
  endPoint.hespSrc?.let { putString(PROP_HESP_SRC, it) }
30
46
  endPoint.hlsSrc?.let { putString(PROP_HLS_SRC, it) }
31
47
  endPoint.adSrc?.let { putString(PROP_AD_SRC, it) }
@@ -15,7 +15,7 @@ let DEBUG_THEOPLAYER_INTERACTION = DEBUG && false
15
15
  // Debug flag to monitor contentProtection integration handling
16
16
  let DEBUG_CONTENT_PROTECTION_API = DEBUG && false
17
17
 
18
- // Debug flag to monitor all updates made on bridged properties
18
+ // Debug flag to monitor all updates made on bridged properties
19
19
  let DEBUG_VIEW = DEBUG && false
20
20
 
21
21
  // Debug flag to monitor correct SourceDescription buildup
@@ -277,7 +277,6 @@ public class THEOplayerRCTMainEventHandler {
277
277
  let welf = self,
278
278
  let forwardedLoadedMetadataEvent = self?.onNativeLoadedMetadata {
279
279
  let metadata = THEOplayerRCTTrackMetadataAggregator.aggregateTrackMetadata(player: wplayer, metadataTracksInfo: welf.loadedMetadataAndChapterTracksInfo)
280
- print(metadata)
281
280
  forwardedLoadedMetadataEvent(metadata)
282
281
  }
283
282
  }