react-native-theoplayer 9.8.0 → 9.9.1

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 (93) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/android/build.gradle +9 -7
  3. package/android/src/main/java/com/theoplayer/ReactTHEOplayerPackage.kt +3 -1
  4. package/android/src/main/java/com/theoplayer/drm/ContentProtectionAdapter.kt +32 -0
  5. package/android/src/main/java/com/theoplayer/presentation/PresentationManager.kt +63 -64
  6. package/android/src/main/java/com/theoplayer/source/SourceAdapter.kt +12 -30
  7. package/android/src/main/java/com/theoplayer/theolive/EndpointAdapter.kt +2 -1
  8. package/android/src/main/java/com/theoplayer/theolive/THEOliveEventAdapter.kt +6 -2
  9. package/android/src/main/java/com/theoplayer/theolive/THEOliveModule.kt +49 -0
  10. package/ios/THEOplayerRCTBridge.m +5 -0
  11. package/ios/THEOplayerRCTDeviceEventHandler.swift +38 -0
  12. package/ios/THEOplayerRCTPlayerAPI.swift +14 -0
  13. package/ios/THEOplayerRCTSourceDescriptionBuilder.swift +1 -0
  14. package/ios/THEOplayerRCTTypeUtils.swift +13 -0
  15. package/ios/THEOplayerRCTView.swift +11 -0
  16. package/ios/theoAds/THEOplayerRCTSourceDescriptionBuilder+TheoAds.swift +4 -1
  17. package/ios/theolive/THEOplayerRCTTHEOliveAPI.swift +21 -4
  18. package/ios/theolive/THEOplayerRCTTHEOliveEventAdapter.swift +62 -0
  19. package/ios/theolive/THEOplayerRCTTHEOliveEventHandler.swift +6 -2
  20. package/lib/commonjs/api/event/TheoAdsEvent.js +1 -1
  21. package/lib/commonjs/api/event/TheoLiveEvent.js +7 -1
  22. package/lib/commonjs/api/event/TheoLiveEvent.js.map +1 -1
  23. package/lib/commonjs/api/source/ads/TheoAdDescription.js.map +1 -1
  24. package/lib/commonjs/internal/THEOplayerView.js +12 -1
  25. package/lib/commonjs/internal/THEOplayerView.js.map +1 -1
  26. package/lib/commonjs/internal/THEOplayerView.web.js +28 -3
  27. package/lib/commonjs/internal/THEOplayerView.web.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/TheoAdsEvent.js +1 -1
  40. package/lib/module/api/event/TheoLiveEvent.js +8 -1
  41. package/lib/module/api/event/TheoLiveEvent.js.map +1 -1
  42. package/lib/module/api/source/ads/TheoAdDescription.js.map +1 -1
  43. package/lib/module/internal/THEOplayerView.js +12 -1
  44. package/lib/module/internal/THEOplayerView.js.map +1 -1
  45. package/lib/module/internal/THEOplayerView.web.js +28 -3
  46. package/lib/module/internal/THEOplayerView.web.js.map +1 -1
  47. package/lib/module/internal/adapter/WebEventForwarder.js +6 -1
  48. package/lib/module/internal/adapter/WebEventForwarder.js.map +1 -1
  49. package/lib/module/internal/adapter/event/PlayerEvents.js +7 -0
  50. package/lib/module/internal/adapter/event/PlayerEvents.js.map +1 -1
  51. package/lib/module/internal/adapter/event/native/NativeTheoLiveEvent.js +3 -1
  52. package/lib/module/internal/adapter/event/native/NativeTheoLiveEvent.js.map +1 -1
  53. package/lib/module/internal/adapter/theolive/TheoLiveNativeAdapter.js +10 -16
  54. package/lib/module/internal/adapter/theolive/TheoLiveNativeAdapter.js.map +1 -1
  55. package/lib/module/internal/adapter/theolive/TheoLiveWebAdapter.js +8 -0
  56. package/lib/module/internal/adapter/theolive/TheoLiveWebAdapter.js.map +1 -1
  57. package/lib/module/manifest.json +1 -1
  58. package/lib/typescript/api/event/TheoAdsEvent.d.ts +1 -1
  59. package/lib/typescript/api/event/TheoLiveEvent.d.ts +15 -5
  60. package/lib/typescript/api/event/TheoLiveEvent.d.ts.map +1 -1
  61. package/lib/typescript/api/source/ads/TheoAdDescription.d.ts +4 -0
  62. package/lib/typescript/api/source/ads/TheoAdDescription.d.ts.map +1 -1
  63. package/lib/typescript/api/theolive/TheoLiveAPI.d.ts +9 -3
  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/WebEventForwarder.d.ts.map +1 -1
  71. package/lib/typescript/internal/adapter/event/PlayerEvents.d.ts +6 -1
  72. package/lib/typescript/internal/adapter/event/PlayerEvents.d.ts.map +1 -1
  73. package/lib/typescript/internal/adapter/event/native/NativeTheoLiveEvent.d.ts +5 -1
  74. package/lib/typescript/internal/adapter/event/native/NativeTheoLiveEvent.d.ts.map +1 -1
  75. package/lib/typescript/internal/adapter/theolive/TheoLiveNativeAdapter.d.ts +3 -0
  76. package/lib/typescript/internal/adapter/theolive/TheoLiveNativeAdapter.d.ts.map +1 -1
  77. package/lib/typescript/internal/adapter/theolive/TheoLiveWebAdapter.d.ts +2 -0
  78. package/lib/typescript/internal/adapter/theolive/TheoLiveWebAdapter.d.ts.map +1 -1
  79. package/package.json +2 -2
  80. package/react-native-theoplayer.podspec +7 -7
  81. package/src/api/event/TheoAdsEvent.ts +1 -1
  82. package/src/api/event/TheoLiveEvent.ts +16 -5
  83. package/src/api/source/ads/TheoAdDescription.ts +5 -0
  84. package/src/api/theolive/TheoLiveAPI.ts +9 -3
  85. package/src/api/theolive/TheoLiveEndpoint.ts +1 -0
  86. package/src/internal/THEOplayerView.tsx +15 -2
  87. package/src/internal/THEOplayerView.web.tsx +24 -2
  88. package/src/internal/adapter/WebEventForwarder.ts +5 -1
  89. package/src/internal/adapter/event/PlayerEvents.ts +10 -0
  90. package/src/internal/adapter/event/native/NativeTheoLiveEvent.ts +14 -2
  91. package/src/internal/adapter/theolive/TheoLiveNativeAdapter.ts +13 -16
  92. package/src/internal/adapter/theolive/TheoLiveWebAdapter.ts +10 -0
  93. package/src/manifest.json +1 -1
package/CHANGELOG.md CHANGED
@@ -5,6 +5,31 @@ 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.9.1] - 25-07-31
9
+
10
+ ### Fixed
11
+
12
+ - Fixed an issue on Web in case the `aspect-ratio` CSS property is not supported on older browsers.
13
+ - Prevented passing infinite or NaN for intializationDelay in TheoAdDescription, on the Android bridge.
14
+ - Fixed and issue on Android where transitioning automatically into PiP presentation mode would fail on Android 16+.
15
+ - Fixed sources without an explicit `type` not being correctly recognized on Android when the URL contains query parameters.
16
+
17
+ ### Added
18
+
19
+ - Added support for Clearkey DRM on Android.
20
+ - Added `endpoint` property to THEOlive `EndpointLoaded` event.
21
+ - Added support for ABRStrategyConfiguration on iOS
22
+
23
+ ## [9.9.0] - 25-07-14
24
+
25
+ ### Added
26
+
27
+ - Added initializationDelay to TheoAdsDecription, to delay THEOads session creation for more optimal ads monetisation.
28
+
29
+ ### Fixed
30
+
31
+ - Fixed an issue on iOS where on iPads the fullscreen dimensions were not correctly updated after the first device orientation change.
32
+
8
33
  ## [9.8.0] - 25-07-08
9
34
 
10
35
  ### Changed
@@ -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.7.0
128
- def theoVersion = safeExtGet('THEOplayer_sdk', '[9.7.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
@@ -26,7 +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
- @SuppressLint("UnspecifiedRegisterReceiverFlag")
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")
30
35
  class PresentationManager(
31
36
  private val viewCtx: ReactTHEOplayerContext,
32
37
  private val reactContext: ThemedReactContext,
@@ -34,34 +39,28 @@ class PresentationManager(
34
39
  ) {
35
40
  private var supportsPip = false
36
41
  private var onUserLeaveHintReceiver: BroadcastReceiver? = null
42
+ private var onUserLeaveHintRunnable: Runnable = Runnable {
43
+ if (pipConfig.startsAutomatically == true) {
44
+ setPresentation(PresentationMode.PICTURE_IN_PICTURE)
45
+ }
46
+ }
37
47
  private var onPictureInPictureModeChanged: BroadcastReceiver? = null
38
48
  private val pipUtils: PipUtils = PipUtils(viewCtx, reactContext)
39
49
  private val fullScreenLayoutObserver = FullScreenLayoutObserver()
40
50
  private val playerGroupRestoreOptions by lazy {
41
51
  PlayerGroupRestoreOptions()
42
52
  }
43
-
44
53
  var currentPresentationMode: PresentationMode = PresentationMode.INLINE
45
54
  private set
46
55
  var currentPresentationModeChangeContext: PresentationModeChangeContext? = null
47
56
  private set
48
-
49
57
  var pipConfig: PipConfig = PipConfig()
50
58
 
51
59
  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
60
  onPictureInPictureModeChanged = object : BroadcastReceiver() {
61
61
  override fun onReceive(context: Context?, intent: Intent?) {
62
- val transitioningToPip = intent
63
- ?.getBooleanExtra("isTransitioningToPip", false) ?: false
64
- val inPip = intent?.getBooleanExtra("isInPictureInPictureMode", false) ?: false
62
+ val transitioningToPip = intent?.getBooleanExtra(IS_TRANSITION_INTO_PIP, false) ?: false
63
+ val inPip = intent?.getBooleanExtra(IS_IN_PIP_MODE, false) ?: false
65
64
  // Dispatch event on every PiP mode change
66
65
  when {
67
66
  transitioningToPip -> onEnterPip(true)
@@ -75,35 +74,50 @@ class PresentationManager(
75
74
  reactContext.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
76
75
  }
77
76
 
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
- }
77
+ reactContext.currentActivity?.let { activity ->
78
+ // On Android 16+, the broadcasted onUserLeaveHint comes too late to activate PiP presentation
79
+ // mode, and the activity will not go into PiP when the user backgrounds the app. In this case
80
+ // we rely on the newer addOnUserLeaveHintListener and ignore the broadcast event.
81
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.VANILLA_ICE_CREAM) {
82
+ onUserLeaveHintReceiver = object : BroadcastReceiver() {
83
+ override fun onReceive(context: Context?, intent: Intent?) {
84
+ onUserLeaveHintRunnable.run()
85
+ }
86
+ }
87
+ } else {
88
+ (activity as? ComponentActivity)?.addOnUserLeaveHintListener(onUserLeaveHintRunnable)
89
+ }
87
90
 
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
- )
91
+ @SuppressLint("UnspecifiedRegisterReceiverFlag")
92
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
93
+ activity.registerReceiver(
94
+ onUserLeaveHintReceiver,
95
+ IntentFilter(ON_USER_LEAVE_HINT),
96
+ Context.RECEIVER_EXPORTED
97
+ )
98
+ activity.registerReceiver(
99
+ onPictureInPictureModeChanged,
100
+ IntentFilter(ON_PIP_MODE_CHANGED),
101
+ Context.RECEIVER_EXPORTED
102
+ )
103
+ } else {
104
+ activity.registerReceiver(onUserLeaveHintReceiver, IntentFilter(ON_USER_LEAVE_HINT))
105
+ activity.registerReceiver(onPictureInPictureModeChanged, IntentFilter(ON_PIP_MODE_CHANGED))
106
+ }
97
107
  }
98
108
  }
99
109
 
100
110
  fun destroy() {
101
111
  try {
102
- reactContext.currentActivity?.unregisterReceiver(onUserLeaveHintReceiver)
103
- reactContext.currentActivity?.unregisterReceiver(onPictureInPictureModeChanged)
112
+ reactContext.currentActivity?.let { activity ->
113
+ activity.unregisterReceiver(onUserLeaveHintReceiver)
114
+ (activity as? ComponentActivity)?.addOnUserLeaveHintListener(onUserLeaveHintRunnable)
115
+ activity.unregisterReceiver(onPictureInPictureModeChanged)
116
+ }
117
+
104
118
  fullScreenLayoutObserver.remove()
105
119
  pipUtils.destroy()
106
- } catch (ignore: Exception) {
120
+ } catch (_: Exception) {
107
121
  }
108
122
  }
109
123
 
@@ -126,25 +140,17 @@ class PresentationManager(
126
140
 
127
141
  private fun enterPip() {
128
142
  // PiP not supported
129
- if (!supportsPip || Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
130
- return
131
- }
143
+ if (!supportsPip || Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
132
144
 
133
145
  // Already in right PiP state?
134
- if (currentPresentationMode == PresentationMode.PICTURE_IN_PICTURE) {
135
- return
136
- }
146
+ if (currentPresentationMode == PresentationMode.PICTURE_IN_PICTURE) return
137
147
 
138
148
  // Check to see whether this activity is in the process of finishing, either because you
139
149
  // called finish on it or someone else has requested that it finished.
140
- if (reactContext.currentActivity?.isFinishing == true) {
141
- return
142
- }
150
+ if (reactContext.currentActivity?.isFinishing == true) return
143
151
 
144
152
  // Check whether the special permission Picture-in-Picture is given.
145
- if (!hasPipPermission()) {
146
- return
147
- }
153
+ if (!hasPipPermission()) return
148
154
 
149
155
  try {
150
156
  pipUtils.enable()
@@ -160,17 +166,14 @@ class PresentationManager(
160
166
  private fun onEnterPip(transitioningToPip: Boolean = false) {
161
167
  updatePresentationMode(
162
168
  PresentationMode.PICTURE_IN_PICTURE,
163
- if (transitioningToPip)
164
- PresentationModeChangeContext(PresentationModeChangePipContext.TRANSITIONING_TO_PIP)
169
+ if (transitioningToPip) PresentationModeChangeContext(PresentationModeChangePipContext.TRANSITIONING_TO_PIP)
165
170
  else null
166
171
  )
167
172
  }
168
173
 
169
174
  private fun onExitPip() {
170
175
  val pipCtx: PresentationModeChangePipContext =
171
- if ((reactContext.currentActivity as? ComponentActivity)
172
- ?.lifecycle?.currentState == Lifecycle.State.CREATED
173
- ) {
176
+ if ((reactContext.currentActivity as? ComponentActivity)?.lifecycle?.currentState == Lifecycle.State.CREATED) {
174
177
  PresentationModeChangePipContext.CLOSED
175
178
  } else {
176
179
  PresentationModeChangePipContext.RESTORED
@@ -182,6 +185,7 @@ class PresentationManager(
182
185
  pipUtils.disable()
183
186
  }
184
187
 
188
+ @Suppress("DEPRECATION")
185
189
  private fun hasPipPermission(): Boolean {
186
190
  val appOps = reactContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager?
187
191
  return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -192,7 +196,6 @@ class PresentationManager(
192
196
  reactContext.packageName
193
197
  ) == AppOpsManager.MODE_ALLOWED
194
198
  } else {
195
- @Suppress("DEPRECATION")
196
199
  appOps?.checkOpNoThrow(
197
200
  AppOpsManager.OPSTR_PICTURE_IN_PICTURE,
198
201
  reactContext.applicationInfo.uid,
@@ -210,9 +213,7 @@ class PresentationManager(
210
213
  }
211
214
 
212
215
  private fun setFullscreen(fullscreen: Boolean) {
213
- if ((fullscreen && currentPresentationMode == PresentationMode.FULLSCREEN) ||
214
- (!fullscreen && currentPresentationMode == PresentationMode.INLINE)
215
- ) {
216
+ if ((fullscreen && currentPresentationMode == PresentationMode.FULLSCREEN) || (!fullscreen && currentPresentationMode == PresentationMode.INLINE)) {
216
217
  return
217
218
  }
218
219
  val activity = reactContext.currentActivity ?: return
@@ -254,8 +255,7 @@ class PresentationManager(
254
255
  val activity = reactContext.currentActivity ?: return null
255
256
  // Try to search in parents and as a fallback option from root to bottom using depth-first order
256
257
  return reactPlayerGroup?.getClosestParentOfType()
257
- ?: (activity.window.decorView.rootView as? ViewGroup)
258
- ?.getClosestParentOfType(false)
258
+ ?: (activity.window.decorView.rootView as? ViewGroup)?.getClosestParentOfType(false)
259
259
  }
260
260
 
261
261
  private fun reparentPlayerToRoot() {
@@ -281,7 +281,9 @@ class PresentationManager(
281
281
 
282
282
  // Re-parent the playerViewGroup from the root node to its original parent
283
283
  removeView(playerGroup)
284
- playerGroupRestoreOptions.parentNode?.addView(playerGroup, playerGroupRestoreOptions.childIndex ?: 0)
284
+ playerGroupRestoreOptions.parentNode?.addView(
285
+ playerGroup, playerGroupRestoreOptions.childIndex ?: 0
286
+ )
285
287
  playerGroupRestoreOptions.reset()
286
288
  }
287
289
  }
@@ -289,12 +291,9 @@ class PresentationManager(
289
291
  // endregion
290
292
 
291
293
  private fun updatePresentationMode(
292
- presentationMode: PresentationMode,
293
- context: PresentationModeChangeContext? = null
294
+ presentationMode: PresentationMode, context: PresentationModeChangeContext? = null
294
295
  ) {
295
- if (presentationMode == currentPresentationMode &&
296
- context == currentPresentationModeChangeContext
297
- ) {
296
+ if (presentationMode == currentPresentationMode && context == currentPresentationModeChangeContext) {
298
297
  return
299
298
  }
300
299
  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
@@ -67,6 +68,7 @@ private const val PROP_OVERRIDE_LAYOUT = "overrideLayout"
67
68
  private const val PROP_NETWORK_CODE = "networkCode"
68
69
  private const val PROP_USE_ID3 = "useId3"
69
70
  private const val PROP_RETRIEVE_POD_ID_URI = "retrievePodIdURI"
71
+ private const val PROP_INITIALIZATION_DELAY = "initializationDelay"
70
72
  private const val PROP_SSE_ENDPOINT = "sseEndpoint"
71
73
  private const val PROP_LATENCY_CONFIGURATION = "latencyConfiguration"
72
74
  private const val PROP_PLAYBACK_PIPELINE = "playbackPipeline"
@@ -270,38 +272,17 @@ class SourceAdapter {
270
272
  private fun parseSourceType(jsonTypedSource: JSONObject): SourceType? {
271
273
  val type = jsonTypedSource.optString(PROP_TYPE)
272
274
  if (type.isNotEmpty()) {
273
- if ("application/dash+xml" == type) {
274
- return SourceType.DASH
275
- }
276
- if ("application/x-mpegurl" == type) {
277
- return SourceType.HLSX
278
- }
279
- if ("application/vnd.theo.hesp+json" == type) {
280
- return SourceType.HESP
281
- }
282
- if ("application/vnd.apple.mpegurl" == type) {
283
- return SourceType.HLS
284
- }
285
- if ("video/mp4" == type) {
286
- return SourceType.MP4
287
- }
288
- if ("audio/mpeg" == type) {
289
- return SourceType.MP3
290
- }
275
+ SourceType.entries.find { it.mimeType == type }?.let { return it }
291
276
  } else {
292
277
  // No type given, check for known extension.
293
- val src = jsonTypedSource.optString(PROP_SRC)
294
- if (src.endsWith(".mpd")) {
295
- return SourceType.DASH
296
- }
297
- if (src.endsWith(".m3u8")) {
298
- return SourceType.HLSX
299
- }
300
- if (src.endsWith(".mp4")) {
301
- return SourceType.MP4
302
- }
303
- if (src.endsWith(".mp3")) {
304
- 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
+ }
305
286
  }
306
287
  }
307
288
  return null
@@ -365,6 +346,7 @@ class SourceAdapter {
365
346
  overrideLayout = parseOverrideLayout(jsonAdDescription.optString(PROP_OVERRIDE_LAYOUT)),
366
347
  useId3 = jsonAdDescription.optBoolean(PROP_USE_ID3, false),
367
348
  retrievePodIdURI = jsonAdDescription.optString(PROP_RETRIEVE_POD_ID_URI).takeIf { it.isNotEmpty() },
349
+ initializationDelay = jsonAdDescription.optDouble(PROP_INITIALIZATION_DELAY).takeIf { it.isFinite() },
368
350
  sseEndpoint = jsonAdDescription.optString(PROP_SSE_ENDPOINT).takeIf { it.isNotEmpty() },
369
351
  )
370
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
  }
@@ -0,0 +1,49 @@
1
+ package com.theoplayer.theolive
2
+
3
+ import com.facebook.react.bridge.*
4
+ import com.theoplayer.*
5
+ import com.theoplayer.util.ViewResolver
6
+
7
+ private const val TAG = "THEORCTTHEOliveModule"
8
+
9
+ private const val PROP_ENGINE_LATENCY = "engineLatency"
10
+ private const val PROP_DISTRIBUTION_LATENCY = "distributionLatency"
11
+ private const val PROP_PLAYER_LATENCY = "playerLatency"
12
+ private const val PROP_THEOLIVE_LATENCY = "theoliveLatency"
13
+
14
+ @Suppress("unused")
15
+ class THEOliveModule(context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
16
+ private val viewResolver: ViewResolver = ViewResolver(context)
17
+
18
+ override fun getName(): String {
19
+ return TAG
20
+ }
21
+
22
+ @ReactMethod
23
+ fun currentLatency(tag: Int, promise: Promise) {
24
+ viewResolver.resolveViewByTag(tag) { view: ReactTHEOplayerView? ->
25
+ // NYI on Android SDK
26
+ promise.resolve(-1.0)
27
+ }
28
+ }
29
+
30
+ @ReactMethod
31
+ fun latencies(tag: Int, promise: Promise) {
32
+ viewResolver.resolveViewByTag(tag) { view: ReactTHEOplayerView? ->
33
+ // NYI on Android SDK
34
+ promise.resolve(Arguments.createMap().apply {
35
+ putDouble(PROP_ENGINE_LATENCY, -1.0)
36
+ putDouble(PROP_DISTRIBUTION_LATENCY, -1.0)
37
+ putDouble(PROP_PLAYER_LATENCY, -1.0)
38
+ putDouble(PROP_THEOLIVE_LATENCY, -1.0)
39
+ })
40
+ }
41
+ }
42
+
43
+ @ReactMethod
44
+ fun setAuthToken(tag: Int, token: String) {
45
+ viewResolver.resolveViewByTag(tag) { view: ReactTHEOplayerView? ->
46
+ view?.player?.theoLive?.authToken = token
47
+ }
48
+ }
49
+ }
@@ -43,6 +43,7 @@ RCT_EXPORT_VIEW_PROPERTY(onNativeTextTrackListEvent, RCTDirectEventBlock);
43
43
  RCT_EXPORT_VIEW_PROPERTY(onNativeTextTrackEvent, RCTDirectEventBlock);
44
44
  RCT_EXPORT_VIEW_PROPERTY(onNativeMediaTrackListEvent, RCTDirectEventBlock);
45
45
  RCT_EXPORT_VIEW_PROPERTY(onNativeMediaTrackEvent, RCTDirectEventBlock);
46
+ RCT_EXPORT_VIEW_PROPERTY(onNativeDeviceOrientationChanged, RCTDirectEventBlock);
46
47
  RCT_EXPORT_VIEW_PROPERTY(onNativePlayerReady, RCTDirectEventBlock);
47
48
  RCT_EXPORT_VIEW_PROPERTY(onNativePresentationModeChange, RCTDirectEventBlock);
48
49
  RCT_EXPORT_VIEW_PROPERTY(onNativeResize, RCTDirectEventBlock);
@@ -279,4 +280,8 @@ RCT_EXTERN_METHOD(latencies:(nonnull NSNumber *)node
279
280
  resolver:(RCTPromiseResolveBlock)resolve
280
281
  rejecter:(RCTPromiseRejectBlock)reject)
281
282
 
283
+ RCT_EXTERN_METHOD(setAuthToken:(nonnull NSNumber *)node
284
+ token:(nonnull NSString *)token)
285
+
282
286
  @end
287
+
@@ -0,0 +1,38 @@
1
+ // THEOplayerRCTDeviceEventHandler.swift
2
+
3
+ import Foundation
4
+ import THEOplayerSDK
5
+
6
+ public class THEOplayerRCTDeviceEventHandler {
7
+ // MARK: Events
8
+ var onNativeDeviceOrientationChanged: RCTDirectEventBlock?
9
+
10
+ init() {
11
+ #if os(iOS)
12
+ UIDevice.current.beginGeneratingDeviceOrientationNotifications()
13
+ NotificationCenter.default.addObserver(
14
+ self,
15
+ selector: #selector(self.handleOrientationChange),
16
+ name: UIDevice.orientationDidChangeNotification,
17
+ object: nil
18
+ )
19
+ #endif
20
+ }
21
+
22
+ func destroy() {
23
+ #if os(iOS)
24
+ NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil)
25
+ UIDevice.current.endGeneratingDeviceOrientationNotifications()
26
+ #endif
27
+ }
28
+
29
+ #if os(iOS)
30
+ @objc private func handleOrientationChange() {
31
+ DispatchQueue.main.async {
32
+ if let forwardedNativeOrientationChanged = self.onNativeDeviceOrientationChanged {
33
+ forwardedNativeOrientationChanged([:])
34
+ }
35
+ }
36
+ }
37
+ #endif
38
+ }
@@ -132,6 +132,20 @@ class THEOplayerRCTPlayerAPI: NSObject, RCTBridgeModule {
132
132
  player.abr.preferredMaximumResolution = CGSize(width: Double(width), height: Double(height))
133
133
  }
134
134
  }
135
+ if let configuredStrategy = abrConfig["strategy"] as? String {
136
+ player.abr.strategy = ABRStrategyConfiguration(type: THEOplayerRCTTypeUtils.abrStrategyFromString(configuredStrategy))
137
+ } else if let configuredStrategy = abrConfig["strategy"] as? [String:Any] {
138
+ if let type = configuredStrategy["type"] as? String {
139
+ let abrType = THEOplayerRCTTypeUtils.abrStrategyFromString(type)
140
+ var abrMetadata: ABRMetadata?
141
+ if let metadata = configuredStrategy["metadata"] as? [String:Any],
142
+ let bitrate = metadata["bitrate"] as? Double {
143
+ abrMetadata = ABRMetadata(bitrate: bitrate)
144
+ }
145
+ let strategy = ABRStrategyConfiguration(type: abrType, metadata: abrMetadata)
146
+ player.abr.strategy = strategy
147
+ }
148
+ }
135
149
  }
136
150
  }
137
151
  }
@@ -59,6 +59,7 @@ let SD_PROP_OVERRIDE_LAYOUT: String = "overrideLayout"
59
59
  let SD_PROP_OVERRIDE_AD_SRC: String = "overrideAdSrc"
60
60
  let SD_PROP_USE_ID3: String = "useId3"
61
61
  let SD_PROP_RETRIEVE_POD_ID_URI: String = "retrievePodIdURI"
62
+ let SD_PROP_INITIALIZATION_DELAY: String = "initializationDelay"
62
63
  let SD_PROP_HLS_DATE_RANGE: String = "hlsDateRange"
63
64
  let SD_PROP_CMCD: String = "cmcd"
64
65