react-native-theoplayer 9.9.0 → 9.10.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 (94) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +3 -0
  3. package/android/build.gradle +9 -7
  4. package/android/src/main/java/com/theoplayer/ReactTHEOplayerPackage.kt +3 -1
  5. package/android/src/main/java/com/theoplayer/drm/ContentProtectionAdapter.kt +32 -0
  6. package/android/src/main/java/com/theoplayer/presentation/FullscreenLayoutObserver.kt +41 -6
  7. package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +75 -67
  8. package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +11 -31
  9. package/android/src/main/java/com/theoplayer/theolive/EndpointAdapter.kt +2 -1
  10. package/android/src/main/java/com/theoplayer/theolive/THEOliveEventAdapter.kt +6 -2
  11. package/android/src/main/java/com/theoplayer/theolive/THEOliveModule.kt +49 -0
  12. package/ios/THEOplayerRCTBridge.m +4 -0
  13. package/ios/THEOplayerRCTMainEventHandler.swift +1 -1
  14. package/ios/THEOplayerRCTPlayerAPI.swift +14 -0
  15. package/ios/THEOplayerRCTTypeUtils.swift +13 -0
  16. package/ios/theolive/THEOplayerRCTTHEOliveAPI.swift +17 -0
  17. package/ios/theolive/THEOplayerRCTTHEOliveEventAdapter.swift +62 -0
  18. package/ios/theolive/THEOplayerRCTTHEOliveEventHandler.swift +6 -2
  19. package/lib/commonjs/api/event/TheoLiveEvent.js +6 -0
  20. package/lib/commonjs/api/event/TheoLiveEvent.js.map +1 -1
  21. package/lib/commonjs/api/player/PlayerEventMap.js.map +1 -1
  22. package/lib/commonjs/internal/THEOplayerView.js +8 -3
  23. package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
  24. package/lib/commonjs/internal/THEOplayerView.web.js +28 -3
  25. package/lib/commonjs/internal/THEOplayerView.web.js.map +1 -1
  26. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js +4 -2
  27. package/lib/commonjs/internal/adapter/THEOplayerAdapter.js.map +1 -1
  28. package/lib/commonjs/internal/adapter/WebEventForwarder.js +5 -0
  29. package/lib/commonjs/internal/adapter/WebEventForwarder.js.map +1 -1
  30. package/lib/commonjs/internal/adapter/event/PlayerEvents.js +9 -1
  31. package/lib/commonjs/internal/adapter/event/PlayerEvents.js.map +1 -1
  32. package/lib/commonjs/internal/adapter/event/native/NativeTheoLiveEvent.js +2 -0
  33. package/lib/commonjs/internal/adapter/event/native/NativeTheoLiveEvent.js.map +1 -1
  34. package/lib/commonjs/internal/adapter/theolive/TheoLiveNativeAdapter.js +9 -15
  35. package/lib/commonjs/internal/adapter/theolive/TheoLiveNativeAdapter.js.map +1 -1
  36. package/lib/commonjs/internal/adapter/theolive/TheoLiveWebAdapter.js +8 -0
  37. package/lib/commonjs/internal/adapter/theolive/TheoLiveWebAdapter.js.map +1 -1
  38. package/lib/commonjs/manifest.json +1 -1
  39. package/lib/module/api/event/TheoLiveEvent.js +7 -0
  40. package/lib/module/api/event/TheoLiveEvent.js.map +1 -1
  41. package/lib/module/api/player/PlayerEventMap.js.map +1 -1
  42. package/lib/module/internal/THEOplayerView.js +8 -3
  43. package/lib/module/internal/THEOplayerView.js.map +1 -1
  44. package/lib/module/internal/THEOplayerView.web.js +28 -3
  45. package/lib/module/internal/THEOplayerView.web.js.map +1 -1
  46. package/lib/module/internal/adapter/THEOplayerAdapter.js +4 -2
  47. package/lib/module/internal/adapter/THEOplayerAdapter.js.map +1 -1
  48. package/lib/module/internal/adapter/WebEventForwarder.js +6 -1
  49. package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
  50. package/lib/module/internal/adapter/event/PlayerEvents.js +7 -0
  51. package/lib/module/internal/adapter/event/PlayerEvents.js.map +1 -1
  52. package/lib/module/internal/adapter/event/native/NativeTheoLiveEvent.js +3 -1
  53. package/lib/module/internal/adapter/event/native/NativeTheoLiveEvent.js.map +1 -1
  54. package/lib/module/internal/adapter/theolive/TheoLiveNativeAdapter.js +10 -16
  55. package/lib/module/internal/adapter/theolive/TheoLiveNativeAdapter.js.map +1 -1
  56. package/lib/module/internal/adapter/theolive/TheoLiveWebAdapter.js +8 -0
  57. package/lib/module/internal/adapter/theolive/TheoLiveWebAdapter.js.map +1 -1
  58. package/lib/module/manifest.json +1 -1
  59. package/lib/typescript/api/event/TheoLiveEvent.d.ts +13 -3
  60. package/lib/typescript/api/event/TheoLiveEvent.d.ts.map +1 -1
  61. package/lib/typescript/api/player/PlayerEventMap.d.ts +3 -3
  62. package/lib/typescript/api/player/PlayerEventMap.d.ts.map +1 -1
  63. package/lib/typescript/api/theolive/TheoLiveAPI.d.ts +4 -0
  64. package/lib/typescript/api/theolive/TheoLiveAPI.d.ts.map +1 -1
  65. package/lib/typescript/api/theolive/TheoLiveEndpoint.d.ts +1 -0
  66. package/lib/typescript/api/theolive/TheoLiveEndpoint.d.ts.map +1 -1
  67. package/lib/typescript/internal/THEOplayerView.d.ts +1 -0
  68. package/lib/typescript/internal/THEOplayerView.d.ts.map +1 -1
  69. package/lib/typescript/internal/THEOplayerView.web.d.ts.map +1 -1
  70. package/lib/typescript/internal/adapter/THEOplayerAdapter.d.ts.map +1 -1
  71. package/lib/typescript/internal/adapter/WebEventForwarder.d.ts.map +1 -1
  72. package/lib/typescript/internal/adapter/event/PlayerEvents.d.ts +6 -1
  73. package/lib/typescript/internal/adapter/event/PlayerEvents.d.ts.map +1 -1
  74. package/lib/typescript/internal/adapter/event/native/NativeTheoLiveEvent.d.ts +5 -1
  75. package/lib/typescript/internal/adapter/event/native/NativeTheoLiveEvent.d.ts.map +1 -1
  76. package/lib/typescript/internal/adapter/theolive/TheoLiveNativeAdapter.d.ts +3 -0
  77. package/lib/typescript/internal/adapter/theolive/TheoLiveNativeAdapter.d.ts.map +1 -1
  78. package/lib/typescript/internal/adapter/theolive/TheoLiveWebAdapter.d.ts +2 -0
  79. package/lib/typescript/internal/adapter/theolive/TheoLiveWebAdapter.d.ts.map +1 -1
  80. package/package.json +2 -2
  81. package/react-native-theoplayer.podspec +7 -7
  82. package/src/api/event/TheoLiveEvent.ts +14 -3
  83. package/src/api/player/PlayerEventMap.ts +4 -2
  84. package/src/api/theolive/TheoLiveAPI.ts +4 -0
  85. package/src/api/theolive/TheoLiveEndpoint.ts +1 -0
  86. package/src/internal/THEOplayerView.tsx +10 -2
  87. package/src/internal/THEOplayerView.web.tsx +24 -2
  88. package/src/internal/adapter/THEOplayerAdapter.ts +6 -2
  89. package/src/internal/adapter/WebEventForwarder.ts +5 -1
  90. package/src/internal/adapter/event/PlayerEvents.ts +10 -0
  91. package/src/internal/adapter/event/native/NativeTheoLiveEvent.ts +14 -2
  92. package/src/internal/adapter/theolive/TheoLiveNativeAdapter.ts +13 -16
  93. package/src/internal/adapter/theolive/TheoLiveWebAdapter.ts +10 -0
  94. package/src/manifest.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,29 @@ 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
+ ## [9.10.0] - 25-08-19
9
+
10
+ ### Fixed
11
+
12
+ - Fixed an issue on Android where transitioning into picture-in-picture presentation mode with the `THEOplayer_reparent_on_PiP` flag enabled would sometimes break the lay-out.
13
+ - Fixed an issue where the player's `currentTime` would not be immediately updated when dispatching `seeking` and `seeked` events.
14
+ - Fixed an issue on iOS where a listener for `seeked` events was not properly created.
15
+
16
+ ## [9.9.1] - 25-07-31
17
+
18
+ ### Fixed
19
+
20
+ - Fixed an issue on Web in case the `aspect-ratio` CSS property is not supported on older browsers.
21
+ - Prevented passing infinite or NaN for intializationDelay in TheoAdDescription, on the Android bridge.
22
+ - Fixed and issue on Android where transitioning automatically into PiP presentation mode would fail on Android 16+.
23
+ - Fixed sources without an explicit `type` not being correctly recognized on Android when the URL contains query parameters.
24
+
25
+ ### Added
26
+
27
+ - Added support for Clearkey DRM on Android.
28
+ - Added `endpoint` property to THEOlive `EndpointLoaded` event.
29
+ - Added support for `ABRStrategyConfiguration` on iOS.
30
+
8
31
  ## [9.9.0] - 25-07-14
9
32
 
10
33
  ### Added
package/README.md CHANGED
@@ -145,8 +145,11 @@ functionality. Currently, the following connectors are available:
145
145
  | Agama analytics | [![%40theoplayer/react-native-analytics-agama](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-agama?label=%40theoplayer/react-native-analytics-agama)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-agama) | [`Agama`](https://github.com/THEOplayer/react-native-connectors/tree/main/agama) |
146
146
  | Comscore analytics | [![%40theoplayer/react-native-analytics-comscore](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-comscore?label=%40theoplayer/react-native-analytics-comscore)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-comscore) | [`Comscore`](https://github.com/THEOplayer/react-native-connectors/tree/main/comscore) |
147
147
  | Conviva analytics | [![%40theoplayer/react-native-analytics-conviva](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-conviva?label=%40theoplayer/react-native-analytics-conviva)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-conviva) | [`Conviva`](https://github.com/THEOplayer/react-native-connectors/tree/main/conviva) |
148
+ | Engage | [![%40theoplayer/react-native-engage](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-engage?label=%40theoplayer/react-native-engage)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-engage) | [`Engage`](https://github.com/THEOplayer/react-native-connectors/tree/main/engage) |
149
+ | Gemius | [![%40theoplayer/react-native-analytics-gemius](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-gemius?label=%40theoplayer/react-native-analytics-gemius)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-gemius) | [`Gemius`](https://github.com/THEOplayer/react-native-connectors/tree/main/gemius) |
148
150
  | Mux analytics | [![%40theoplayer/react-native-analytics-mux](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-mux?label=%40theoplayer/react-native-analytics-mux)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-mux) | [`Mux`](https://github.com/THEOplayer/react-native-connectors/tree/main/mux) |
149
151
  | Nielsen analytics | [![%40theoplayer/react-native-analytics-nielsen](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-nielsen?label=%40theoplayer/react-native-analytics-nielsen)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-nielsen) | [`Nielsen`](https://github.com/THEOplayer/react-native-connectors/tree/main/nielsen) |
152
+ | Nielsen AdScript analytics | [![%40theoplayer/react-native-analytics-adscript](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-adscript?label=%40theoplayer/react-native-analytics-adscript)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-adscript) | [`AdScript`](https://github.com/THEOplayer/react-native-connectors/tree/main/adscript) |
150
153
  | Youbora analytics | [![%40theoplayer/react-native-analytics-youbora](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-analytics-youbora?label=%40theoplayer/react-native-analytics-youbora)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-analytics-youbora) | [`Youbora`](https://github.com/THEOplayer/react-native-connectors/tree/main/youbora) |
151
154
  | Yospace SSAI | [![%40theoplayer/react-native-yospace](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-yospace?label=%40theoplayer/react-native-yospace)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-yospace) | [`Yospace`](https://github.com/THEOplayer/react-native-connectors/tree/main/yospace) |
152
155
  | Content protection (DRM) | [![%40theoplayer/react-native-drm](https://img.shields.io/npm/v/%40theoplayer%2Freact-native-drm?label=%40theoplayer/react-native-drm)](https://www.npmjs.com/package/%40theoplayer%2Freact-native-drm) | [`DRM`](https://github.com/THEOplayer/react-native-theoplayer-drm) |
@@ -43,12 +43,12 @@ def enabledMediaSession = safeExtGet("THEOplayer_extensionMediaSession", 'true')
43
43
  def enabledMillicast = safeExtGet("THEOplayer_extensionMillicast", 'false').toBoolean()
44
44
 
45
45
  android {
46
- compileSdk safeExtGet('THEOplayer_compileSdkVersion', 34)
46
+ compileSdk safeExtGet('THEOplayer_compileSdkVersion', 35)
47
47
  namespace "com.theoplayer"
48
48
 
49
49
  defaultConfig {
50
50
  minSdkVersion safeExtGet('THEOplayer_minSdkVersion', 21)
51
- targetSdkVersion safeExtGet('THEOplayer_targetSdkVersion', 34)
51
+ targetSdkVersion safeExtGet('THEOplayer_targetSdkVersion', 35)
52
52
  versionCode 1
53
53
  versionName "1.0"
54
54
 
@@ -124,15 +124,16 @@ repositories {
124
124
  mavenLocal()
125
125
  }
126
126
 
127
- // The minimum supported THEOplayer version is 9.8.0
128
- def theoVersion = safeExtGet('THEOplayer_sdk', '[9.8.0, 10.0.0)')
127
+ // The minimum supported THEOplayer version is 9.9.0
128
+ def theoVersion = safeExtGet('THEOplayer_sdk', '[9.9.0, 10.0.0)')
129
129
  def theoMediaSessionVersion = safeExtGet('THEOplayer_mediasession', '[8.0.0, 10.0.0)')
130
130
  def theoAdsWrapperVersion = "9.0.0"
131
- def coroutinesVersion = safeExtGet('coroutinesVersion', '1.7.3')
131
+ def coroutinesVersion = safeExtGet('coroutinesVersion', '1.10.2')
132
132
  def appcompatVersion = safeExtGet('appcompatVersion', '1.7.1')
133
133
  def corektxVersion = safeExtGet('corektxVersion', '1.13.1')
134
- def gsonVersion = "2.12.1"
135
- def millicastVersion = safeExtGet('millicastVersion', '2.0.0')
134
+ def gsonVersion = safeExtGet('gsonVersion', '2.13.1')
135
+ def activityktxVersion = safeExtGet('activityktxVersion', '1.10.1')
136
+ def millicastVersion = safeExtGet('millicastVersion', '2.4.3')
136
137
 
137
138
  dependencies {
138
139
  def addOptiViewIntegration = { enabled, notation, additional = null ->
@@ -156,6 +157,7 @@ dependencies {
156
157
  implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion")
157
158
  implementation("androidx.appcompat:appcompat:$appcompatVersion")
158
159
  implementation("androidx.core:core-ktx:$corektxVersion")
160
+ implementation("androidx.activity:activity-ktx:$activityktxVersion")
159
161
  implementation("com.google.code.gson:gson:$gsonVersion")
160
162
 
161
163
  println("Using THEOplayer (${versionString(theoVersion)})")
@@ -11,6 +11,7 @@ import com.theoplayer.cast.CastModule
11
11
  import com.theoplayer.broadcast.EventBroadcastModule
12
12
  import com.theoplayer.playback.PlaybackSettingsModule
13
13
  import com.theoplayer.player.PlayerModule
14
+ import com.theoplayer.theolive.THEOliveModule
14
15
 
15
16
  class ReactTHEOplayerPackage : ReactPackage {
16
17
  override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
@@ -21,7 +22,8 @@ class ReactTHEOplayerPackage : ReactPackage {
21
22
  CastModule(reactContext),
22
23
  CacheModule(reactContext),
23
24
  EventBroadcastModule(reactContext),
24
- PlaybackSettingsModule(reactContext)
25
+ PlaybackSettingsModule(reactContext),
26
+ THEOliveModule(reactContext)
25
27
  )
26
28
  }
27
29
 
@@ -9,6 +9,7 @@ import com.google.gson.Gson
9
9
  import com.theoplayer.android.api.contentprotection.KeySystemId
10
10
  import com.theoplayer.android.api.contentprotection.Request
11
11
  import com.theoplayer.android.api.contentprotection.Response
12
+ import com.theoplayer.android.api.source.drm.ClearkeyKeySystemConfiguration
12
13
  import com.theoplayer.android.api.source.drm.DRMConfiguration
13
14
  import com.theoplayer.android.api.source.drm.DRMIntegrationId
14
15
  import com.theoplayer.android.api.source.drm.KeySystemConfiguration
@@ -27,6 +28,7 @@ const val PROP_KEYSYSTEM_ID: String = "keySystemId"
27
28
  const val PROP_DRM_CONFIG: String = "drmConfig"
28
29
  const val PROP_PLAYREADY: String = "playready"
29
30
  const val PROP_WIDEVINE: String = "widevine"
31
+ const val PROP_CLEARKEY: String = "clearkey"
30
32
  const val PROP_REQUEST_ID: String = "requestId"
31
33
  const val PROP_URL: String = "url"
32
34
  const val PROP_METHOD: String = "method"
@@ -42,6 +44,9 @@ const val PROP_LICENSE_TYPE_TEMPORARY: String = "temporary"
42
44
  const val PROP_LICENSE_TYPE_PERSISTENT: String = "persistent"
43
45
  const val PROP_QUERY_PARAMETERS: String = "queryParameters"
44
46
  const val PROP_CERTIFICATE: String = "certificate"
47
+ const val PROP_KEYS: String = "keys"
48
+ const val PROP_ID: String = "id"
49
+ const val PROP_VALUE: String = "value"
45
50
 
46
51
  object ContentProtectionAdapter {
47
52
 
@@ -93,6 +98,9 @@ object ContentProtectionAdapter {
93
98
  if (jsonConfig.has(PROP_PLAYREADY)) {
94
99
  playready(keySystemConfigurationFromJson(jsonConfig.getJSONObject(PROP_PLAYREADY)))
95
100
  }
101
+ if (jsonConfig.has(PROP_CLEARKEY)) {
102
+ clearkey(clearkeyKeySystemConfigurationFromJson(jsonConfig.getJSONObject(PROP_CLEARKEY)))
103
+ }
96
104
  if (jsonConfig.has(PROP_INTEGRATION_PARAMETERS)) {
97
105
  integrationParameters(fromJSONObjectToMap(jsonConfig.getJSONObject(PROP_INTEGRATION_PARAMETERS)))
98
106
  }
@@ -113,6 +121,30 @@ object ContentProtectionAdapter {
113
121
  }.build()
114
122
  }
115
123
 
124
+ private fun clearkeyKeySystemConfigurationFromJson(config: JSONObject): ClearkeyKeySystemConfiguration {
125
+ val jsonKeys = config.optJSONArray(PROP_KEYS)
126
+ val keys = mutableListOf<ClearkeyKeySystemConfiguration.ClearkeyDecryptionKey>()
127
+ if (jsonKeys != null) {
128
+ for (i in 0 until jsonKeys.length()) {
129
+ val jsonKey = jsonKeys.optJSONObject(i)
130
+ if (jsonKey != null) {
131
+ val id = jsonKey.optString(PROP_ID)
132
+ val value = jsonKey.optString(PROP_VALUE)
133
+ if (!id.isNullOrEmpty() && !value.isNullOrEmpty()) {
134
+ keys.add(ClearkeyKeySystemConfiguration.ClearkeyDecryptionKey(id, value))
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return ClearkeyKeySystemConfiguration.Builder(config.optString(PROP_LICENSE_ACQUISITION_URL))
140
+ .apply {
141
+ useCredentials(config.optBoolean(PROP_USE_CREDENTIALS))
142
+ headers(fromJSONObjectToMap(config.optJSONObject(PROP_HEADERS)))
143
+ queryParameters(fromJSONObjectToMap(config.optJSONObject(PROP_QUERY_PARAMETERS)))
144
+ keys(keys.toTypedArray())
145
+ }.build()
146
+ }
147
+
116
148
  private fun licenseTypeFromString(str: String?): LicenseType? {
117
149
  return when (str) {
118
150
  PROP_LICENSE_TYPE_PERSISTENT -> LicenseType.PERSISTENT
@@ -1,15 +1,20 @@
1
1
  package com.theoplayer.presentation
2
2
 
3
3
  import android.util.Log
4
+ import android.view.View
5
+ import android.view.ViewGroup
4
6
  import android.view.ViewTreeObserver
7
+ import androidx.core.view.children
5
8
  import com.facebook.react.views.view.ReactViewGroup
9
+ import com.theoplayer.ReactTHEOplayerView
6
10
 
7
- private val TAG = "FSLayoutObserver"
11
+ private const val TAG = "FSLayoutObserver"
8
12
 
9
13
  /**
10
14
  * FullScreenLayoutObserver makes sure that the React Native view does not get the layout
11
- * defined in React-Native during fullscreen presentation mode. We want to enforce fullscreen
15
+ * defined in ReactNative during fullscreen presentation mode. We want to enforce fullscreen
12
16
  * position & size.
17
+ * Similarly for picture-in-picture presentation mode, enforce a view that stretches the whole screen.
13
18
  */
14
19
  class FullScreenLayoutObserver {
15
20
  private var globalLayoutListener: ViewTreeObserver.OnGlobalLayoutListener? = null
@@ -20,13 +25,22 @@ class FullScreenLayoutObserver {
20
25
  Log.w(TAG, "A previously attached ViewGroup was not properly detached.")
21
26
  }
22
27
 
23
- viewGroup?.let {
28
+ viewGroup?.let { reactPlayerGroup ->
24
29
  globalLayoutListener = ViewTreeObserver.OnGlobalLayoutListener {
25
- it.post {
26
- it.layout(0, 0, viewGroup.width, viewGroup.height)
30
+ val root = getRootViewFrom(reactPlayerGroup)
31
+ reactPlayerGroup.post {
32
+ applyOnViewTree(reactPlayerGroup) { view ->
33
+ if (view == reactPlayerGroup || view is ReactTHEOplayerView) {
34
+ view.measure(
35
+ View.MeasureSpec.makeMeasureSpec(root.width, View.MeasureSpec.EXACTLY),
36
+ View.MeasureSpec.makeMeasureSpec(root.height, View.MeasureSpec.EXACTLY)
37
+ )
38
+ view.layout(0, 0, view.measuredWidth, view.measuredHeight)
39
+ }
40
+ }
27
41
  }
28
42
  }
29
- it.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
43
+ reactPlayerGroup.viewTreeObserver.addOnGlobalLayoutListener(globalLayoutListener)
30
44
  attached = viewGroup
31
45
  }
32
46
  }
@@ -37,3 +51,24 @@ class FullScreenLayoutObserver {
37
51
  globalLayoutListener = null
38
52
  }
39
53
  }
54
+
55
+ /**
56
+ * Find the view root, most likely the decorView
57
+ */
58
+ fun getRootViewFrom(view: View): View {
59
+ var current = view
60
+ while (current.parent is View) {
61
+ current = current.parent as View
62
+ }
63
+ return current
64
+ }
65
+
66
+ /**
67
+ * Conditionally apply an operation on each view in a hierarchy.
68
+ */
69
+ fun applyOnViewTree(view: View, doOp: (View) -> Unit) {
70
+ doOp(view)
71
+ if (view is ViewGroup) {
72
+ view.children.forEach { ch -> applyOnViewTree(ch, doOp) }
73
+ }
74
+ }
@@ -26,6 +26,12 @@ import com.theoplayer.android.api.error.ErrorCode
26
26
  import com.theoplayer.android.api.error.THEOplayerException
27
27
  import com.theoplayer.android.api.player.PresentationMode
28
28
 
29
+ const val IS_TRANSITION_INTO_PIP = "isTransitioningToPip"
30
+ const val IS_IN_PIP_MODE = "isInPictureInPictureMode"
31
+ const val ON_USER_LEAVE_HINT = "onUserLeaveHint"
32
+ const val ON_PIP_MODE_CHANGED = "onPictureInPictureModeChanged"
33
+
34
+ @Suppress("KotlinConstantConditions", "SimplifyBooleanWithConstants")
29
35
  @SuppressLint("UnspecifiedRegisterReceiverFlag")
30
36
  class PresentationManager(
31
37
  private val viewCtx: ReactTHEOplayerContext,
@@ -34,34 +40,28 @@ class PresentationManager(
34
40
  ) {
35
41
  private var supportsPip = false
36
42
  private var onUserLeaveHintReceiver: BroadcastReceiver? = null
43
+ private var onUserLeaveHintRunnable: Runnable = Runnable {
44
+ if (pipConfig.startsAutomatically == true) {
45
+ setPresentation(PresentationMode.PICTURE_IN_PICTURE)
46
+ }
47
+ }
37
48
  private var onPictureInPictureModeChanged: BroadcastReceiver? = null
38
49
  private val pipUtils: PipUtils = PipUtils(viewCtx, reactContext)
39
50
  private val fullScreenLayoutObserver = FullScreenLayoutObserver()
40
51
  private val playerGroupRestoreOptions by lazy {
41
52
  PlayerGroupRestoreOptions()
42
53
  }
43
-
44
54
  var currentPresentationMode: PresentationMode = PresentationMode.INLINE
45
55
  private set
46
56
  var currentPresentationModeChangeContext: PresentationModeChangeContext? = null
47
57
  private set
48
-
49
58
  var pipConfig: PipConfig = PipConfig()
50
59
 
51
60
  init {
52
- onUserLeaveHintReceiver = object : BroadcastReceiver() {
53
- override fun onReceive(context: Context?, intent: Intent?) {
54
- // Optionally into PiP mode when the app goes to background.
55
- if (pipConfig.startsAutomatically == true) {
56
- setPresentation(PresentationMode.PICTURE_IN_PICTURE)
57
- }
58
- }
59
- }
60
61
  onPictureInPictureModeChanged = object : BroadcastReceiver() {
61
62
  override fun onReceive(context: Context?, intent: Intent?) {
62
- val transitioningToPip = intent
63
- ?.getBooleanExtra("isTransitioningToPip", false) ?: false
64
- val inPip = intent?.getBooleanExtra("isInPictureInPictureMode", false) ?: false
63
+ val transitioningToPip = intent?.getBooleanExtra(IS_TRANSITION_INTO_PIP, false) ?: false
64
+ val inPip = intent?.getBooleanExtra(IS_IN_PIP_MODE, false) ?: false
65
65
  // Dispatch event on every PiP mode change
66
66
  when {
67
67
  transitioningToPip -> onEnterPip(true)
@@ -75,35 +75,50 @@ class PresentationManager(
75
75
  reactContext.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
76
76
  }
77
77
 
78
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
79
- reactContext.currentActivity?.registerReceiver(
80
- onUserLeaveHintReceiver, IntentFilter("onUserLeaveHint"), Context.RECEIVER_EXPORTED
81
- )
82
- } else {
83
- reactContext.currentActivity?.registerReceiver(
84
- onUserLeaveHintReceiver, IntentFilter("onUserLeaveHint")
85
- )
86
- }
78
+ reactContext.currentActivity?.let { activity ->
79
+ // On Android 16+, the broadcasted onUserLeaveHint comes too late to activate PiP presentation
80
+ // mode, and the activity will not go into PiP when the user backgrounds the app. In this case
81
+ // we rely on the newer addOnUserLeaveHintListener and ignore the broadcast event.
82
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
83
+ onUserLeaveHintReceiver = object : BroadcastReceiver() {
84
+ override fun onReceive(context: Context?, intent: Intent?) {
85
+ onUserLeaveHintRunnable.run()
86
+ }
87
+ }
88
+ } else {
89
+ (activity as? ComponentActivity)?.addOnUserLeaveHintListener(onUserLeaveHintRunnable)
90
+ }
87
91
 
88
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
89
- reactContext.currentActivity?.registerReceiver(
90
- onPictureInPictureModeChanged, IntentFilter("onPictureInPictureModeChanged"),
91
- Context.RECEIVER_EXPORTED
92
- )
93
- } else {
94
- reactContext.currentActivity?.registerReceiver(
95
- onPictureInPictureModeChanged, IntentFilter("onPictureInPictureModeChanged")
96
- )
92
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
93
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
94
+ activity.registerReceiver(
95
+ onUserLeaveHintReceiver,
96
+ IntentFilter(ON_USER_LEAVE_HINT),
97
+ Context.RECEIVER_EXPORTED
98
+ )
99
+ activity.registerReceiver(
100
+ onPictureInPictureModeChanged,
101
+ IntentFilter(ON_PIP_MODE_CHANGED),
102
+ Context.RECEIVER_EXPORTED
103
+ )
104
+ } else {
105
+ activity.registerReceiver(onUserLeaveHintReceiver, IntentFilter(ON_USER_LEAVE_HINT))
106
+ activity.registerReceiver(onPictureInPictureModeChanged, IntentFilter(ON_PIP_MODE_CHANGED))
107
+ }
97
108
  }
98
109
  }
99
110
 
100
111
  fun destroy() {
101
112
  try {
102
- reactContext.currentActivity?.unregisterReceiver(onUserLeaveHintReceiver)
103
- reactContext.currentActivity?.unregisterReceiver(onPictureInPictureModeChanged)
113
+ reactContext.currentActivity?.let { activity ->
114
+ activity.unregisterReceiver(onUserLeaveHintReceiver)
115
+ (activity as? ComponentActivity)?.addOnUserLeaveHintListener(onUserLeaveHintRunnable)
116
+ activity.unregisterReceiver(onPictureInPictureModeChanged)
117
+ }
118
+
104
119
  fullScreenLayoutObserver.remove()
105
120
  pipUtils.destroy()
106
- } catch (ignore: Exception) {
121
+ } catch (_: Exception) {
107
122
  }
108
123
  }
109
124
 
@@ -126,62 +141,60 @@ class PresentationManager(
126
141
 
127
142
  private fun enterPip() {
128
143
  // PiP not supported
129
- if (!supportsPip || Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
130
- return
131
- }
144
+ if (!supportsPip || Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
132
145
 
133
146
  // Already in right PiP state?
134
- if (currentPresentationMode == PresentationMode.PICTURE_IN_PICTURE) {
135
- return
136
- }
147
+ if (currentPresentationMode == PresentationMode.PICTURE_IN_PICTURE) return
137
148
 
138
149
  // Check to see whether this activity is in the process of finishing, either because you
139
150
  // called finish on it or someone else has requested that it finished.
140
- if (reactContext.currentActivity?.isFinishing == true) {
141
- return
142
- }
151
+ if (reactContext.currentActivity?.isFinishing == true) return
143
152
 
144
153
  // Check whether the special permission Picture-in-Picture is given.
145
- if (!hasPipPermission()) {
146
- return
147
- }
154
+ if (!hasPipPermission()) return
148
155
 
149
156
  try {
150
157
  pipUtils.enable()
151
158
  reactContext.currentActivity?.enterPictureInPictureMode(pipUtils.getPipParams())
152
- if (BuildConfig.REPARENT_ON_PIP && pipConfig.reparentPip == true) {
153
- reparentPlayerToRoot()
154
- }
155
159
  } catch (_: Exception) {
156
160
  onPipError()
157
161
  }
158
162
  }
159
163
 
164
+ /**
165
+ * Called when the transition into PiP either starts (transitioningToPip = true)
166
+ * or after it ends (transitioningToPip = false)
167
+ */
160
168
  private fun onEnterPip(transitioningToPip: Boolean = false) {
169
+ if (BuildConfig.REPARENT_ON_PIP && !transitioningToPip && pipConfig.reparentPip == true) {
170
+ reparentPlayerToRoot()
171
+ }
172
+
161
173
  updatePresentationMode(
162
174
  PresentationMode.PICTURE_IN_PICTURE,
163
- if (transitioningToPip)
164
- PresentationModeChangeContext(PresentationModeChangePipContext.TRANSITIONING_TO_PIP)
175
+ if (transitioningToPip) PresentationModeChangeContext(PresentationModeChangePipContext.TRANSITIONING_TO_PIP)
165
176
  else null
166
177
  )
167
178
  }
168
179
 
180
+ /**
181
+ * Called when the PiP exit transition starts.
182
+ */
169
183
  private fun onExitPip() {
170
184
  val pipCtx: PresentationModeChangePipContext =
171
- if ((reactContext.currentActivity as? ComponentActivity)
172
- ?.lifecycle?.currentState == Lifecycle.State.CREATED
173
- ) {
185
+ if ((reactContext.currentActivity as? ComponentActivity)?.lifecycle?.currentState == Lifecycle.State.CREATED) {
174
186
  PresentationModeChangePipContext.CLOSED
175
187
  } else {
176
188
  PresentationModeChangePipContext.RESTORED
177
189
  }
178
- if (BuildConfig.REPARENT_ON_PIP) {
190
+ if (BuildConfig.REPARENT_ON_PIP && pipConfig.reparentPip == true) {
179
191
  reparentPlayerToOriginal()
180
192
  }
181
193
  updatePresentationMode(PresentationMode.INLINE, PresentationModeChangeContext(pipCtx))
182
194
  pipUtils.disable()
183
195
  }
184
196
 
197
+ @Suppress("DEPRECATION")
185
198
  private fun hasPipPermission(): Boolean {
186
199
  val appOps = reactContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager?
187
200
  return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -192,7 +205,6 @@ class PresentationManager(
192
205
  reactContext.packageName
193
206
  ) == AppOpsManager.MODE_ALLOWED
194
207
  } else {
195
- @Suppress("DEPRECATION")
196
208
  appOps?.checkOpNoThrow(
197
209
  AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
198
210
  reactContext.applicationInfo.uid,
@@ -210,9 +222,7 @@ class PresentationManager(
210
222
  }
211
223
 
212
224
  private fun setFullscreen(fullscreen: Boolean) {
213
- if ((fullscreen && currentPresentationMode == PresentationMode.FULLSCREEN) ||
214
- (!fullscreen && currentPresentationMode == PresentationMode.INLINE)
215
- ) {
225
+ if ((fullscreen && currentPresentationMode == PresentationMode.FULLSCREEN) || (!fullscreen && currentPresentationMode == PresentationMode.INLINE)) {
216
226
  return
217
227
  }
218
228
  val activity = reactContext.currentActivity ?: return
@@ -254,8 +264,7 @@ class PresentationManager(
254
264
  val activity = reactContext.currentActivity ?: return null
255
265
  // Try to search in parents and as a fallback option from root to bottom using depth-first order
256
266
  return reactPlayerGroup?.getClosestParentOfType()
257
- ?: (activity.window.decorView.rootView as? ViewGroup)
258
- ?.getClosestParentOfType(false)
267
+ ?: (activity.window.decorView.rootView as? ViewGroup)?.getClosestParentOfType(false)
259
268
  }
260
269
 
261
270
  private fun reparentPlayerToRoot() {
@@ -281,7 +290,9 @@ class PresentationManager(
281
290
 
282
291
  // Re-parent the playerViewGroup from the root node to its original parent
283
292
  removeView(playerGroup)
284
- playerGroupRestoreOptions.parentNode?.addView(playerGroup, playerGroupRestoreOptions.childIndex ?: 0)
293
+ playerGroupRestoreOptions.parentNode?.addView(
294
+ playerGroup, playerGroupRestoreOptions.childIndex ?: 0
295
+ )
285
296
  playerGroupRestoreOptions.reset()
286
297
  }
287
298
  }
@@ -289,12 +300,9 @@ class PresentationManager(
289
300
  // endregion
290
301
 
291
302
  private fun updatePresentationMode(
292
- presentationMode: PresentationMode,
293
- context: PresentationModeChangeContext? = null
303
+ presentationMode: PresentationMode, context: PresentationModeChangeContext? = null
294
304
  ) {
295
- if (presentationMode == currentPresentationMode &&
296
- context == currentPresentationModeChangeContext
297
- ) {
305
+ if (presentationMode == currentPresentationMode && context == currentPresentationModeChangeContext) {
298
306
  return
299
307
  }
300
308
  val prevPresentationMode = currentPresentationMode
@@ -2,6 +2,7 @@ package com.theoplayer.source
2
2
 
3
3
  import android.text.TextUtils
4
4
  import android.util.Log
5
+ import androidx.core.net.toUri
5
6
  import com.google.gson.Gson
6
7
  import com.theoplayer.android.api.ads.theoads.TheoAdDescription
7
8
  import com.theoplayer.android.api.error.THEOplayerException
@@ -271,38 +272,17 @@ class SourceAdapter {
271
272
  private fun parseSourceType(jsonTypedSource: JSONObject): SourceType? {
272
273
  val type = jsonTypedSource.optString(PROP_TYPE)
273
274
  if (type.isNotEmpty()) {
274
- if ("application/dash+xml" == type) {
275
- return SourceType.DASH
276
- }
277
- if ("application/x-mpegurl" == type) {
278
- return SourceType.HLSX
279
- }
280
- if ("application/vnd.theo.hesp+json" == type) {
281
- return SourceType.HESP
282
- }
283
- if ("application/vnd.apple.mpegurl" == type) {
284
- return SourceType.HLS
285
- }
286
- if ("video/mp4" == type) {
287
- return SourceType.MP4
288
- }
289
- if ("audio/mpeg" == type) {
290
- return SourceType.MP3
291
- }
275
+ SourceType.entries.find { it.mimeType == type }?.let { return it }
292
276
  } else {
293
277
  // No type given, check for known extension.
294
- val src = jsonTypedSource.optString(PROP_SRC)
295
- if (src.endsWith(".mpd")) {
296
- return SourceType.DASH
297
- }
298
- if (src.endsWith(".m3u8")) {
299
- return SourceType.HLSX
300
- }
301
- if (src.endsWith(".mp4")) {
302
- return SourceType.MP4
303
- }
304
- if (src.endsWith(".mp3")) {
305
- return SourceType.MP3
278
+ val src = jsonTypedSource.optString(PROP_SRC).toUri()
279
+ src.lastPathSegment?.let { fileName ->
280
+ when {
281
+ fileName.endsWith(".mpd") -> return SourceType.DASH
282
+ fileName.endsWith(".m3u8") -> return SourceType.HLSX
283
+ fileName.endsWith(".mp4") -> return SourceType.MP4
284
+ fileName.endsWith(".mp3") -> return SourceType.MP3
285
+ }
306
286
  }
307
287
  }
308
288
  return null
@@ -366,7 +346,7 @@ class SourceAdapter {
366
346
  overrideLayout = parseOverrideLayout(jsonAdDescription.optString(PROP_OVERRIDE_LAYOUT)),
367
347
  useId3 = jsonAdDescription.optBoolean(PROP_USE_ID3, false),
368
348
  retrievePodIdURI = jsonAdDescription.optString(PROP_RETRIEVE_POD_ID_URI).takeIf { it.isNotEmpty() },
369
- initializationDelay = jsonAdDescription.optDouble(PROP_INITIALIZATION_DELAY),
349
+ initializationDelay = jsonAdDescription.optDouble(PROP_INITIALIZATION_DELAY).takeIf { it.isFinite() },
370
350
  sseEndpoint = jsonAdDescription.optString(PROP_SSE_ENDPOINT).takeIf { it.isNotEmpty() },
371
351
  )
372
352
  }
@@ -10,12 +10,12 @@ import com.theoplayer.android.api.theolive.KeySystemConfiguration
10
10
  private const val PROP_HESP_SRC = "hespSrc"
11
11
  private const val PROP_HLS_SRC = "hlsSrc"
12
12
  private const val PROP_AD_SRC = "adSrc"
13
+ private const val PROP_CDN = "cdn"
13
14
  private const val PROP_TARGET_LATENCY = "targetLatency"
14
15
  private const val PROP_WEIGHT = "weight"
15
16
  private const val PROP_PRIORITY = "priority"
16
17
  private const val PROP_CONTENT_PROTECTION = "contentProtection"
17
18
  private const val PROP_INTEGRATION = "integration"
18
-
19
19
  private const val PROP_WIDEVINE = "widevine"
20
20
  private const val PROP_PLAYREADY = "playready"
21
21
  private const val PROP_FAIRPLAY = "fairplay"
@@ -29,6 +29,7 @@ object EndpointAdapter {
29
29
  endPoint.hespSrc?.let { putString(PROP_HESP_SRC, it) }
30
30
  endPoint.hlsSrc?.let { putString(PROP_HLS_SRC, it) }
31
31
  endPoint.adSrc?.let { putString(PROP_AD_SRC, it) }
32
+ endPoint.cdn?.let { putString(PROP_CDN, it) }
32
33
  endPoint.targetLatency?.let { putDouble(PROP_TARGET_LATENCY, it) }
33
34
  putInt(PROP_WEIGHT, endPoint.weight)
34
35
  putInt(PROP_PRIORITY, endPoint.priority)
@@ -8,11 +8,12 @@ import com.theoplayer.android.api.event.player.theolive.EndpointLoadedEvent
8
8
  import com.theoplayer.android.api.event.player.theolive.IntentToFallbackEvent
9
9
  import com.theoplayer.android.api.event.player.theolive.TheoLiveEventTypes
10
10
  import com.theoplayer.android.api.player.theolive.TheoLive
11
+ import com.theoplayer.util.PayloadBuilder
11
12
 
12
13
  private const val EVENT_PROP_TYPE = "type"
13
-
14
14
  private const val EVENT_PROP_DISTRIBUTION_ID = "distributionId"
15
15
  private const val EVENT_PROP_ENDPOINT = "endpoint"
16
+ private const val EVENT_PROP_REASON = "reason"
16
17
 
17
18
  class THEOliveEventAdapter(private val theoLiveApi: TheoLive, private val emitter: Emitter) {
18
19
 
@@ -55,9 +56,12 @@ class THEOliveEventAdapter(private val theoLiveApi: TheoLive, private val emitte
55
56
  })
56
57
  }
57
58
 
58
- private fun onIntentOfFallback(_event: IntentToFallbackEvent) {
59
+ private fun onIntentOfFallback(event: IntentToFallbackEvent) {
59
60
  emitter.emit(Arguments.createMap().apply {
60
61
  putString(EVENT_PROP_TYPE, "intenttofallback")
62
+ event.reason?.let {
63
+ putMap(EVENT_PROP_REASON, PayloadBuilder().error(it.code.id.toString(), it.message).build())
64
+ }
61
65
  })
62
66
  }
63
67
  }