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.
- package/CHANGELOG.md +44 -3
- package/README.md +2 -2
- package/android/build.gradle +1 -1
- package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
- package/android/src/main/java/com/theoplayer/ReactTHEOplayerContext.kt +44 -9
- package/android/src/main/java/com/theoplayer/audio/BackgroundAudioConfig.kt +7 -0
- package/android/src/main/java/com/theoplayer/audio/BackgroundAudioConfigAdapter.kt +12 -3
- package/android/src/main/java/com/theoplayer/drm/ContentProtectionAdapter.kt +1 -0
- package/android/src/main/java/com/theoplayer/media/MediaPlaybackService.kt +17 -34
- package/android/src/main/java/com/theoplayer/media/MediaSessionConfig.kt +10 -0
- package/android/src/main/java/com/theoplayer/media/MediaSessionConfigAdapter.kt +8 -0
- package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +8 -4
- package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +3 -0
- package/android/src/main/java/com/theoplayer/theoads/THEOadsEventAdapter.kt +1 -6
- package/android/src/main/java/com/theoplayer/theolive/EndpointAdapter.kt +16 -0
- package/ios/THEOplayerRCTDebug.swift +1 -1
- package/ios/THEOplayerRCTMainEventHandler.swift +0 -1
- package/ios/THEOplayerRCTMediaTrackEventHandler.swift +47 -28
- package/ios/THEOplayerRCTPlayerAPI.swift +1 -0
- package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +55 -62
- package/ios/THEOplayerRCTView+AppState.swift +33 -0
- package/ios/THEOplayerRCTView.swift +29 -1
- package/ios/backgroundAudio/THEOplayerRCTBackgroundAudioManager.swift +19 -17
- package/ios/backgroundAudio/THEOplayerRCTNowPlayingManager.swift +3 -3
- package/ios/backgroundAudio/THEOplayerRCTRemoteCommandsManager.swift +26 -15
- package/ios/backgroundAudio/THEOplayerRCTView+BackgroundAudioConfig.swift +8 -0
- package/ios/backgroundAudio/THEOplayerRCTView+MediaControlConfig.swift +8 -0
- package/ios/cache/THEOplayerRCTCacheAPI.swift +1 -2
- package/ios/pip/THEOplayerRCTPipControlsManager.swift +1 -1
- package/ios/pip/THEOplayerRCTPipManager.swift +38 -26
- package/ios/theoAds/THEOplayerRCTSourceDescriptionBuilder+TheoAds.swift +2 -0
- package/ios/theolive/THEOplayerRCTTHEOliveEventAdapter.swift +4 -0
- package/lib/commonjs/api/backgroundAudio/BackgroundAudioConfiguration.js.map +1 -1
- package/lib/commonjs/api/source/SourceDescription.js.map +1 -1
- package/lib/commonjs/internal/THEOplayerView.js +4 -1
- package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
- package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js +8 -3
- package/lib/commonjs/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
- package/lib/commonjs/internal/adapter/WebEventForwarder.js +4 -1
- package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
- package/lib/commonjs/internal/adapter/event/native/NativeTheoAdsEvent.js +8 -2
- package/lib/commonjs/internal/adapter/event/native/NativeTheoAdsEvent.js.map +1 -1
- package/lib/commonjs/manifest.json +1 -1
- package/lib/module/api/backgroundAudio/BackgroundAudioConfiguration.js.map +1 -1
- package/lib/module/api/source/SourceDescription.js.map +1 -1
- package/lib/module/internal/THEOplayerView.js +4 -1
- package/lib/module/internal/THEOplayerView.js.map +1 -1
- package/lib/module/internal/adapter/THEOplayerWebAdapter.js +8 -3
- package/lib/module/internal/adapter/THEOplayerWebAdapter.js.map +1 -1
- package/lib/module/internal/adapter/WebEventForwarder.js +5 -2
- package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
- package/lib/module/internal/adapter/event/native/NativeTheoAdsEvent.js +8 -2
- package/lib/module/internal/adapter/event/native/NativeTheoAdsEvent.js.map +1 -1
- package/lib/module/manifest.json +1 -1
- package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts +9 -0
- package/lib/typescript/api/backgroundAudio/BackgroundAudioConfiguration.d.ts.map +1 -1
- package/lib/typescript/api/media/MediaControlConfiguration.d.ts +17 -0
- package/lib/typescript/api/media/MediaControlConfiguration.d.ts.map +1 -1
- package/lib/typescript/api/source/SourceDescription.d.ts +6 -6
- package/lib/typescript/api/source/SourceDescription.d.ts.map +1 -1
- package/lib/typescript/api/source/drm/DRMConfiguration.d.ts +10 -0
- package/lib/typescript/api/source/drm/DRMConfiguration.d.ts.map +1 -1
- package/lib/typescript/api/theolive/TheoLiveEndpoint.d.ts +7 -0
- package/lib/typescript/api/theolive/TheoLiveEndpoint.d.ts.map +1 -1
- package/lib/typescript/internal/THEOplayerView.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/THEOplayerWebAdapter.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/WebEventForwarder.d.ts.map +1 -1
- package/lib/typescript/internal/adapter/event/native/NativeTheoAdsEvent.d.ts +1 -1
- package/lib/typescript/internal/adapter/event/native/NativeTheoAdsEvent.d.ts.map +1 -1
- package/package.json +5 -5
- package/react-native-theoplayer.podspec +7 -7
- package/src/api/backgroundAudio/BackgroundAudioConfiguration.ts +10 -0
- package/src/api/media/MediaControlConfiguration.ts +19 -0
- package/src/api/source/SourceDescription.ts +7 -7
- package/src/api/source/drm/DRMConfiguration.ts +9 -0
- package/src/api/theolive/TheoLiveEndpoint.ts +8 -0
- package/src/internal/THEOplayerView.tsx +4 -1
- package/src/internal/adapter/THEOplayerWebAdapter.ts +8 -3
- package/src/internal/adapter/WebEventForwarder.ts +45 -32
- package/src/internal/adapter/event/native/NativeTheoAdsEvent.ts +9 -4
- 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
|
|
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
|
-
|
|
158
|
+
### Added
|
|
118
159
|
|
|
119
160
|
- Added support for `THEOlive` events.
|
|
120
161
|
|
|
121
|
-
|
|
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>
|
package/android/build.gradle
CHANGED
|
@@ -152,7 +152,7 @@ dependencies {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
//noinspection GradleDynamicVersion
|
|
155
|
-
implementation("com.facebook.react:react-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
171
|
-
isLive &&
|
|
172
|
-
|
|
173
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
13
|
-
|
|
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() =
|
|
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)
|
|
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(
|
|
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(
|
|
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
|
|
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
|
-
//
|
|
328
|
-
if (context?.pip == PresentationModeChangePipContext.CLOSED
|
|
329
|
-
|
|
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
|
|
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
|
}
|