bitmovin-player-react-native 0.3.1 → 0.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/README.md +486 -24
- package/RNBitmovinPlayer.podspec +5 -1
- package/android/build.gradle +8 -5
- package/android/src/main/java/com/bitmovin/player/reactnative/AnalyticsModule.kt +154 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/DrmModule.kt +4 -5
- package/android/src/main/java/com/bitmovin/player/reactnative/PlayerModule.kt +41 -5
- package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerView.kt +318 -2
- package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewManager.kt +70 -9
- package/android/src/main/java/com/bitmovin/player/reactnative/RNPlayerViewPackage.kt +3 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/SourceModule.kt +4 -5
- package/android/src/main/java/com/bitmovin/player/reactnative/UuidModule.kt +3 -1
- package/android/src/main/java/com/bitmovin/player/reactnative/converter/JsonConverter.kt +389 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/extensions/Any.kt +27 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt +8 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReadableArray.kt +19 -2
- package/android/src/main/java/com/bitmovin/player/reactnative/extensions/String.kt +8 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/extensions/WritableMap.kt +19 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/ui/FullscreenHandlerBridge.kt +37 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/ui/FullscreenHandlerModule.kt +73 -0
- package/android/src/main/java/com/bitmovin/player/reactnative/ui/RNPictureInPictureHandler.kt +191 -0
- package/ios/AnalyticsModule.m +14 -0
- package/ios/AnalyticsModule.swift +180 -0
- package/ios/AudioSessionModule.m +10 -0
- package/ios/AudioSessionModule.swift +65 -0
- package/ios/Event+JSON.swift +134 -0
- package/ios/FullscreenHandlerBridge.swift +33 -0
- package/ios/FullscreenHandlerModule.m +9 -0
- package/ios/FullscreenHandlerModule.swift +71 -0
- package/ios/PlayerModule.m +6 -0
- package/ios/PlayerModule.swift +44 -0
- package/ios/RCTConvert+BitmovinPlayer.swift +382 -0
- package/ios/RNPlayerView+PlayerListener.swift +56 -0
- package/ios/RNPlayerView+UserInterfaceListener.swift +35 -0
- package/ios/RNPlayerView.swift +22 -0
- package/ios/RNPlayerViewManager.m +24 -1
- package/ios/RNPlayerViewManager.swift +23 -1
- package/lib/index.d.ts +1013 -150
- package/lib/index.js +251 -48
- package/lib/index.mjs +231 -34
- package/package.json +1 -1
- package/src/advertising.ts +155 -0
- package/src/analytics/collector.ts +97 -0
- package/src/analytics/config.ts +218 -0
- package/src/analytics/index.ts +2 -0
- package/src/audioSession.ts +47 -0
- package/src/components/PlayerView/events.ts +49 -3
- package/src/components/PlayerView/index.tsx +68 -11
- package/src/components/PlayerView/native.ts +4 -1
- package/src/events.ts +255 -0
- package/src/index.ts +4 -0
- package/src/media.ts +33 -0
- package/src/player.ts +57 -1
- package/src/source.ts +4 -0
- package/src/styleConfig.ts +87 -0
- package/src/ui/fullscreenhandler.ts +19 -0
- package/src/ui/fullscreenhandlerbridge.ts +59 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
package com.bitmovin.player.reactnative.extensions
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Reflection helper for dynamically getting a property by name from a java object.
|
|
5
|
+
* @param propertyName Property name.
|
|
6
|
+
* @return A mutable property reference that can be used to get/set the prop's value.
|
|
7
|
+
*/
|
|
8
|
+
@Suppress("UNCHECKED_CAST")
|
|
9
|
+
inline fun <reified T> Any?.getProperty(propertyName: String): T? = this?.let {
|
|
10
|
+
val getter = it::class.java.methods.firstOrNull { method ->
|
|
11
|
+
method.name == "get${propertyName.capitalized()}"
|
|
12
|
+
}
|
|
13
|
+
getter?.invoke(it) as? T
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Reflection helper for dynamically setting a property value by name to a java object.
|
|
18
|
+
* @param propertyName Property name.
|
|
19
|
+
* @param value Value that will be set for the specified `propertyName`.
|
|
20
|
+
*/
|
|
21
|
+
@Suppress("UNCHECKED_CAST")
|
|
22
|
+
inline fun <reified T> Any?.setProperty(propertyName: String, value: T) = this?.let {
|
|
23
|
+
val setter = it::class.java.methods.firstOrNull { method ->
|
|
24
|
+
method.name == "set${propertyName.capitalized()}"
|
|
25
|
+
}
|
|
26
|
+
setter?.invoke(it, value)
|
|
27
|
+
}
|
package/android/src/main/java/com/bitmovin/player/reactnative/extensions/ReactContextExtension.kt
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
package com.bitmovin.player.reactnative.extensions
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactContext
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
+
|
|
6
|
+
inline fun <reified T : ReactContextBaseJavaModule> ReactContext.getModule(): T? {
|
|
7
|
+
return getNativeModule(T::class.java)
|
|
8
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
package com.bitmovin.player.reactnative.extensions
|
|
2
2
|
|
|
3
|
-
import com.facebook.react.bridge
|
|
4
|
-
import com.facebook.react.bridge.ReadableMap
|
|
3
|
+
import com.facebook.react.bridge.*
|
|
5
4
|
|
|
6
5
|
inline fun <reified T> ReadableArray.toList(): List<T?> = (0 until size()).map { i ->
|
|
7
6
|
getDynamic(i).let {
|
|
@@ -12,7 +11,25 @@ inline fun <reified T> ReadableArray.toList(): List<T?> = (0 until size()).map {
|
|
|
12
11
|
Int::class -> it.asInt() as T
|
|
13
12
|
ReadableArray::class -> it.asArray() as T
|
|
14
13
|
ReadableMap::class -> it.asMap() as T
|
|
14
|
+
WritableArray::class -> it.asArray() as T
|
|
15
|
+
WritableMap::class -> it.asMap() as T
|
|
15
16
|
else -> null
|
|
16
17
|
}
|
|
17
18
|
}
|
|
18
19
|
}
|
|
20
|
+
|
|
21
|
+
inline fun <reified T> List<T>.toReadableArray(): ReadableArray = Arguments.createArray().apply {
|
|
22
|
+
forEach {
|
|
23
|
+
when (T::class) {
|
|
24
|
+
Boolean::class -> pushBoolean(it as Boolean)
|
|
25
|
+
String::class -> pushString(it as String)
|
|
26
|
+
Double::class -> pushDouble(it as Double)
|
|
27
|
+
Int::class -> pushInt(it as Int)
|
|
28
|
+
ReadableArray::class -> pushArray(it as ReadableArray)
|
|
29
|
+
ReadableMap::class -> pushMap(it as ReadableMap)
|
|
30
|
+
WritableArray::class -> pushArray(it as ReadableArray)
|
|
31
|
+
WritableMap::class -> pushMap(it as ReadableMap)
|
|
32
|
+
else -> pushNull()
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
package com.bitmovin.player.reactnative.extensions
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.WritableMap
|
|
4
|
+
|
|
5
|
+
fun WritableMap.putInt(key: String, i: Int?) {
|
|
6
|
+
if (i == null) {
|
|
7
|
+
putNull(key)
|
|
8
|
+
} else {
|
|
9
|
+
putInt(key, i)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
fun WritableMap.putDouble(key: String, d: Double?) {
|
|
14
|
+
if (d == null) {
|
|
15
|
+
putNull(key)
|
|
16
|
+
} else {
|
|
17
|
+
putDouble(key, d)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.bitmovin.player.reactnative.ui
|
|
2
|
+
|
|
3
|
+
import com.bitmovin.player.api.ui.FullscreenHandler
|
|
4
|
+
import com.bitmovin.player.reactnative.NativeId
|
|
5
|
+
import com.bitmovin.player.reactnative.extensions.getModule
|
|
6
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
7
|
+
|
|
8
|
+
class FullscreenHandlerBridge(
|
|
9
|
+
val context: ReactApplicationContext,
|
|
10
|
+
private val nativeId: NativeId
|
|
11
|
+
) : FullscreenHandler {
|
|
12
|
+
override var isFullscreen = false
|
|
13
|
+
|
|
14
|
+
override fun onDestroy() {
|
|
15
|
+
// Do nothing
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
override fun onFullscreenExitRequested() {
|
|
19
|
+
context
|
|
20
|
+
.getModule<FullscreenHandlerModule>()
|
|
21
|
+
?.requestExitFullscreen(nativeId)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
override fun onFullscreenRequested() {
|
|
25
|
+
context
|
|
26
|
+
.getModule<FullscreenHandlerModule>()
|
|
27
|
+
?.requestEnterFullscreen(nativeId)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
override fun onPause() {
|
|
31
|
+
// Do nothing
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
override fun onResume() {
|
|
35
|
+
// Do nothing
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package com.bitmovin.player.reactnative.ui
|
|
2
|
+
|
|
3
|
+
import com.bitmovin.player.reactnative.NativeId
|
|
4
|
+
import com.bitmovin.player.reactnative.Registry
|
|
5
|
+
import com.facebook.react.bridge.*
|
|
6
|
+
import com.facebook.react.module.annotations.ReactModule
|
|
7
|
+
import java.util.concurrent.locks.ReentrantLock
|
|
8
|
+
import kotlin.concurrent.withLock
|
|
9
|
+
|
|
10
|
+
private const val MODULE_NAME = "FullscreenHandlerModule"
|
|
11
|
+
|
|
12
|
+
@ReactModule(name = MODULE_NAME)
|
|
13
|
+
class FullscreenHandlerModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
|
|
14
|
+
override fun getName() = MODULE_NAME
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* In-memory mapping from `nativeId`s to `FullscreenHandler` instances.
|
|
18
|
+
*/
|
|
19
|
+
private val fullscreenHandler: Registry<FullscreenHandlerBridge> = mutableMapOf()
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Module's local lock object used to sync calls between Kotlin and JS.
|
|
23
|
+
*/
|
|
24
|
+
private val lock = ReentrantLock()
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Lock condition used to sync operations on the fullscreen handler.
|
|
28
|
+
*/
|
|
29
|
+
private val fullscreenChangedCondition = lock.newCondition()
|
|
30
|
+
|
|
31
|
+
fun getInstance(nativeId: NativeId?): FullscreenHandlerBridge? = fullscreenHandler[nativeId]
|
|
32
|
+
|
|
33
|
+
fun requestEnterFullscreen(nativeId: NativeId) {
|
|
34
|
+
context.catalystInstance.callFunction(
|
|
35
|
+
"FullscreenBridge-${nativeId}",
|
|
36
|
+
"enterFullscreen",
|
|
37
|
+
Arguments.createArray() as NativeArray
|
|
38
|
+
)
|
|
39
|
+
lock.withLock {
|
|
40
|
+
fullscreenChangedCondition.await()
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
fun requestExitFullscreen(nativeId: NativeId) {
|
|
45
|
+
context.catalystInstance.callFunction(
|
|
46
|
+
"FullscreenBridge-${nativeId}",
|
|
47
|
+
"exitFullscreen",
|
|
48
|
+
Arguments.createArray() as NativeArray
|
|
49
|
+
)
|
|
50
|
+
lock.withLock {
|
|
51
|
+
fullscreenChangedCondition.await()
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@ReactMethod(isBlockingSynchronousMethod = true)
|
|
56
|
+
fun onFullscreenChanged(nativeId: NativeId, isFullscreenEnabled: Boolean) {
|
|
57
|
+
fullscreenHandler[nativeId]?.isFullscreen = isFullscreenEnabled
|
|
58
|
+
lock.withLock {
|
|
59
|
+
fullscreenChangedCondition.signal()
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@ReactMethod
|
|
64
|
+
fun registerHandler(nativeId: NativeId) {
|
|
65
|
+
val fullscreenHandler = fullscreenHandler[nativeId] ?: FullscreenHandlerBridge(context, nativeId)
|
|
66
|
+
this.fullscreenHandler[nativeId] = fullscreenHandler
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@ReactMethod
|
|
70
|
+
fun destroy(nativeId: NativeId) {
|
|
71
|
+
fullscreenHandler.remove(nativeId)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
package com.bitmovin.player.reactnative.ui
|
|
2
|
+
|
|
3
|
+
import android.app.PictureInPictureParams
|
|
4
|
+
import android.content.pm.PackageManager
|
|
5
|
+
import android.content.res.Configuration
|
|
6
|
+
import android.graphics.Rect
|
|
7
|
+
import android.os.Build
|
|
8
|
+
import android.util.Rational
|
|
9
|
+
import androidx.annotation.RequiresApi
|
|
10
|
+
import androidx.appcompat.app.AppCompatActivity
|
|
11
|
+
import com.bitmovin.player.api.ui.PictureInPictureHandler
|
|
12
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Delegate object for `RNPictureInPictureHandler`. It delegates all view logic that needs
|
|
16
|
+
* to be performed during each PiP state to this object.
|
|
17
|
+
*/
|
|
18
|
+
interface RNPictureInPictureDelegate {
|
|
19
|
+
/**
|
|
20
|
+
* Called whenever the handler's `isInPictureInPictureMode` changes to `true`.
|
|
21
|
+
*/
|
|
22
|
+
fun onExitPictureInPicture()
|
|
23
|
+
/**
|
|
24
|
+
* Called whenever the handler's `isInPictureInPictureMode` changes to `false`.
|
|
25
|
+
*/
|
|
26
|
+
fun onEnterPictureInPicture()
|
|
27
|
+
/**
|
|
28
|
+
* Called whenever the activity's PiP mode state changes with the new resources configuration.
|
|
29
|
+
*/
|
|
30
|
+
fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration?)
|
|
31
|
+
/**
|
|
32
|
+
* Called whenever the handler needs to compute a new `sourceRectHint` for PiP params.
|
|
33
|
+
* The passed rect reference is expected to be fulfilled with the PlayerView's global visible
|
|
34
|
+
* rect.
|
|
35
|
+
*/
|
|
36
|
+
fun setSourceRectHint(sourceRectHint: Rect)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Custom PictureInPictureHandler` concrete implementation designed for React Native. It relies on
|
|
41
|
+
* React Native's application context to manage the application's PiP state. Can be subclassed in
|
|
42
|
+
* order to provide custom PiP capabilities.
|
|
43
|
+
*/
|
|
44
|
+
open class RNPictureInPictureHandler(val context: ReactApplicationContext): PictureInPictureHandler {
|
|
45
|
+
/**
|
|
46
|
+
* PiP delegate object that contains the view logic to be performed on each PiP state change.
|
|
47
|
+
*/
|
|
48
|
+
private var delegate: RNPictureInPictureDelegate? = null
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Whether the user has enabled PiP support via `isPictureInPictureEnabled` playback configuration in JS.
|
|
52
|
+
*/
|
|
53
|
+
var isPictureInPictureEnabled = false
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Whether this view is currently in PiP mode.
|
|
57
|
+
*/
|
|
58
|
+
var isInPictureInPictureMode = false
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Whether the current Android version supports PiP mode.
|
|
62
|
+
*/
|
|
63
|
+
private val isPictureInPictureSupported: Boolean
|
|
64
|
+
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
|
|
65
|
+
&& context.packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Whether the picture in picture feature is available and enabled.
|
|
69
|
+
*/
|
|
70
|
+
override val isPictureInPictureAvailable: Boolean
|
|
71
|
+
get() = isPictureInPictureEnabled && isPictureInPictureSupported
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Whether this view is currently in PiP mode. Required for PictureInPictureHandler interface.
|
|
75
|
+
*/
|
|
76
|
+
override val isPictureInPicture: Boolean
|
|
77
|
+
get() = isInPictureInPictureMode
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Current React activity computed property.
|
|
81
|
+
*/
|
|
82
|
+
private val currentActivity: AppCompatActivity?
|
|
83
|
+
get() {
|
|
84
|
+
if (context.hasCurrentActivity()) {
|
|
85
|
+
return context.currentActivity as AppCompatActivity
|
|
86
|
+
}
|
|
87
|
+
return null
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sets the new delegate object and update the activity's PiP parameters accordingly.
|
|
92
|
+
*/
|
|
93
|
+
open fun setDelegate(delegate: RNPictureInPictureDelegate?) {
|
|
94
|
+
this.delegate = delegate
|
|
95
|
+
// Update the activity's PiP params once the delegate has been set.
|
|
96
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPictureAvailable) {
|
|
97
|
+
applyPictureInPictureParams()
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Called whenever bitmovin's `PlayerView` needs to enter PiP mode.
|
|
103
|
+
*/
|
|
104
|
+
override fun enterPictureInPicture() {
|
|
105
|
+
if (isPictureInPictureAvailable) {
|
|
106
|
+
currentActivity?.let {
|
|
107
|
+
it.supportActionBar?.hide()
|
|
108
|
+
it.enterPictureInPictureMode()
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Called whenever bitmovin's `PlayerView` needs to exit PiP mode.
|
|
115
|
+
*/
|
|
116
|
+
override fun exitPictureInPicture() {
|
|
117
|
+
if (isPictureInPictureAvailable) {
|
|
118
|
+
currentActivity?.supportActionBar?.show()
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Called whenever the activity content resources have changed.
|
|
124
|
+
*/
|
|
125
|
+
open fun onConfigurationChanged(newConfig: Configuration?) {
|
|
126
|
+
// PiP mode is supported since Android 7.0
|
|
127
|
+
if (isPictureInPictureAvailable) {
|
|
128
|
+
handlePictureInPictureModeChanges(newConfig)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Checks whether the current activity `isInPictureInPictureMode` has changed since the last lifecycle
|
|
134
|
+
* configuration change.
|
|
135
|
+
*/
|
|
136
|
+
@RequiresApi(Build.VERSION_CODES.N)
|
|
137
|
+
private fun handlePictureInPictureModeChanges(newConfig: Configuration?) = currentActivity?.let {
|
|
138
|
+
if (isInPictureInPictureMode != it.isInPictureInPictureMode) {
|
|
139
|
+
delegate?.onPictureInPictureModeChanged(it.isInPictureInPictureMode, newConfig)
|
|
140
|
+
if (it.isInPictureInPictureMode) {
|
|
141
|
+
delegate?.onEnterPictureInPicture()
|
|
142
|
+
} else {
|
|
143
|
+
delegate?.onExitPictureInPicture()
|
|
144
|
+
}
|
|
145
|
+
isInPictureInPictureMode = it.isInPictureInPictureMode
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Applies Android recommended PiP params on the current activity for smoother transitions.
|
|
151
|
+
*
|
|
152
|
+
* You can read more about the recommended settings for PiP here:
|
|
153
|
+
* - https://developer.android.com/develop/ui/views/picture-in-picture#smoother-transition
|
|
154
|
+
* - https://developer.android.com/develop/ui/views/picture-in-picture#smoother-exit
|
|
155
|
+
*/
|
|
156
|
+
@RequiresApi(Build.VERSION_CODES.O)
|
|
157
|
+
private fun applyPictureInPictureParams() = currentActivity?.let {
|
|
158
|
+
// See also: https://developer.android.com/develop/ui/views/picture-in-picture#smoother-transition
|
|
159
|
+
val sourceRectHint = Rect()
|
|
160
|
+
delegate?.setSourceRectHint(sourceRectHint)
|
|
161
|
+
val ratio = Rational(16, 9)
|
|
162
|
+
val params = PictureInPictureParams.Builder()
|
|
163
|
+
.setAspectRatio(ratio)
|
|
164
|
+
.setSourceRectHint(sourceRectHint)
|
|
165
|
+
when {
|
|
166
|
+
// See also: https://developer.android.com/develop/ui/views/picture-in-picture#smoother-exit
|
|
167
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ->
|
|
168
|
+
params.setAutoEnterEnabled(true).setSeamlessResizeEnabled(true)
|
|
169
|
+
Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU ->
|
|
170
|
+
params.setExpandedAspectRatio(ratio)
|
|
171
|
+
}
|
|
172
|
+
it.setPictureInPictureParams(params.build())
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Update source rect hint on activity's PiP params.
|
|
177
|
+
*/
|
|
178
|
+
open fun updateSourceRectHint() {
|
|
179
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O || !isPictureInPictureAvailable) {
|
|
180
|
+
return
|
|
181
|
+
}
|
|
182
|
+
currentActivity?.let {
|
|
183
|
+
val sourceRectHint = Rect()
|
|
184
|
+
delegate?.setSourceRectHint(sourceRectHint)
|
|
185
|
+
it.setPictureInPictureParams(
|
|
186
|
+
PictureInPictureParams.Builder()
|
|
187
|
+
.setSourceRectHint(sourceRectHint)
|
|
188
|
+
.build())
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_REMAP_MODULE(AnalyticsModule, AnalyticsModule, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(initWithConfig:(NSString *)nativeId config:(nullable id)config)
|
|
6
|
+
RCT_EXTERN_METHOD(destroy:(NSString *)nativeId)
|
|
7
|
+
RCT_EXTERN_METHOD(attach:(NSString *)nativeId playerId:(NSString *)playerId)
|
|
8
|
+
RCT_EXTERN_METHOD(detach:(NSString *)nativeId)
|
|
9
|
+
RCT_EXTERN_METHOD(setCustomDataOnce:(NSString *)nativeId json:(nullable id)json)
|
|
10
|
+
RCT_EXTERN_METHOD(setCustomData:(NSString *)nativeId json:(nullable id)json)
|
|
11
|
+
RCT_EXTERN_METHOD(getCustomData:(NSString *)nativeId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
12
|
+
RCT_EXTERN_METHOD(getUserId:(NSString *)nativeId resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
|
|
13
|
+
|
|
14
|
+
@end
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import BitmovinPlayer
|
|
2
|
+
import BitmovinAnalyticsCollector
|
|
3
|
+
|
|
4
|
+
@objc(AnalyticsModule)
|
|
5
|
+
class AnalyticsModule: NSObject, RCTBridgeModule {
|
|
6
|
+
/// React bridge reference.
|
|
7
|
+
@objc var bridge: RCTBridge!
|
|
8
|
+
|
|
9
|
+
/// PlayerModule instance fetched from the bridge's registry
|
|
10
|
+
@objc var playerModule: PlayerModule? {
|
|
11
|
+
bridge.module(for: PlayerModule.self) as? PlayerModule
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/// In-memory mapping from `nativeId`s to `BitmovinPlayerCollector` instances.
|
|
15
|
+
private var collectors: Registry<BitmovinPlayerCollector> = [:]
|
|
16
|
+
|
|
17
|
+
/// JS module name.
|
|
18
|
+
static func moduleName() -> String! {
|
|
19
|
+
"AnalyticsModule"
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Module requires main thread initialization.
|
|
23
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
24
|
+
true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/// Use `UIManager.addBlock` to enqueue module methods on UI thread.
|
|
28
|
+
var methodQueue: DispatchQueue! {
|
|
29
|
+
bridge.uiManager.methodQueue
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
Retrieves a `BitmovinPlayerCollector` instance from the internal registry for the given `nativeId`.
|
|
34
|
+
- Parameter nativeId: Native Id of the collector instance.
|
|
35
|
+
- Returns: Collector instance associated with the `nativeId` or `nil`.
|
|
36
|
+
*/
|
|
37
|
+
@objc func retrieve(_ nativeId: NativeId) -> BitmovinPlayerCollector? {
|
|
38
|
+
collectors[nativeId]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
Creates a new `BitmovinPlayerCollector` instance inside the internal registry using the provided `config` object.
|
|
43
|
+
- Parameter nativeId: ID to associate with the `BitmovinPlayerCollector` instance.
|
|
44
|
+
- Parameter config: `BitmovinAnalyticsConfig` object received from JS.
|
|
45
|
+
*/
|
|
46
|
+
@objc(initWithConfig:config:)
|
|
47
|
+
func initWithConfig(_ nativeId: NativeId, config: Any?) {
|
|
48
|
+
bridge.uiManager.addUIBlock { [weak self] _, _ in
|
|
49
|
+
guard let analyticsConfig = RCTConvert.analyticsConfig(config) else {
|
|
50
|
+
return
|
|
51
|
+
}
|
|
52
|
+
self?.collectors[nativeId] = BitmovinPlayerCollector(config: analyticsConfig)
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
Detaches and removes the given `BitmovinPlayerCollector` from the internal registry.
|
|
58
|
+
- Parameter nativeId: Native Id of the collector instance.
|
|
59
|
+
*/
|
|
60
|
+
@objc(destroy:)
|
|
61
|
+
func destroy(_ nativeId: NativeId) {
|
|
62
|
+
bridge.uiManager.addUIBlock { [weak self] _, _ in
|
|
63
|
+
self?.collectors[nativeId]?.detachPlayer()
|
|
64
|
+
self?.collectors[nativeId] = nil
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
Attaches a `BitmovinPlayerCollector` to the `Player` instance with native Id equal to `playerId`.
|
|
70
|
+
- Parameter nativeId: Native Id of the collector instance.
|
|
71
|
+
- Parameter playerId: Native Id of the player instance.
|
|
72
|
+
*/
|
|
73
|
+
@objc(attach:playerId:)
|
|
74
|
+
func attach(_ nativeId: NativeId, playerId: NativeId) {
|
|
75
|
+
bridge.uiManager.addUIBlock { [weak self] _, _ in
|
|
76
|
+
guard
|
|
77
|
+
let collector = self?.collectors[nativeId],
|
|
78
|
+
let player = self?.playerModule?.retrieve(playerId)
|
|
79
|
+
else {
|
|
80
|
+
return
|
|
81
|
+
}
|
|
82
|
+
collector.attachPlayer(player: player)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
Detaches the player object from a `BitmovinPlayerCollector` instance.
|
|
88
|
+
- Parameter nativeId: Native Id of the collector instance.
|
|
89
|
+
*/
|
|
90
|
+
@objc(detach:)
|
|
91
|
+
func detach(_ nativeId: NativeId) {
|
|
92
|
+
bridge.uiManager.addUIBlock { [weak self] _, _ in
|
|
93
|
+
guard let collector = self?.collectors[nativeId] else {
|
|
94
|
+
return
|
|
95
|
+
}
|
|
96
|
+
collector.detachPlayer()
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
Updates the custom data config for a `BitmovinPlayerCollector` instance.
|
|
102
|
+
- Parameter nativeId: Native Id of the collector instance.
|
|
103
|
+
- Parameter json: Custom data config json.
|
|
104
|
+
*/
|
|
105
|
+
@objc(setCustomDataOnce:json:)
|
|
106
|
+
func setCustomDataOnce(_ nativeId: NativeId, json: Any?) {
|
|
107
|
+
bridge.uiManager.addUIBlock { [weak self] _, _ in
|
|
108
|
+
guard
|
|
109
|
+
let collector = self?.collectors[nativeId],
|
|
110
|
+
let customData = RCTConvert.analyticsCustomData(json)
|
|
111
|
+
else {
|
|
112
|
+
return
|
|
113
|
+
}
|
|
114
|
+
collector.setCustomDataOnce(customData: customData)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
Sets the custom data config for a `BitmovinPlayerCollector` instance.
|
|
120
|
+
- Parameter nativeId: Native Id of the collector instance.
|
|
121
|
+
- Parameter json: Custom data config json.
|
|
122
|
+
*/
|
|
123
|
+
@objc(setCustomData:json:)
|
|
124
|
+
func setCustomData(_ nativeId: NativeId, json: Any?) {
|
|
125
|
+
bridge.uiManager.addUIBlock { [weak self] _, _ in
|
|
126
|
+
guard
|
|
127
|
+
let collector = self?.collectors[nativeId],
|
|
128
|
+
let customData = RCTConvert.analyticsCustomData(json)
|
|
129
|
+
else {
|
|
130
|
+
return
|
|
131
|
+
}
|
|
132
|
+
collector.setCustomData(customData: customData)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
Gets the current custom data config for a `BitmovinPlayerCollector` instance.
|
|
138
|
+
- Parameter nativeId: Native Id of the the collector instance.
|
|
139
|
+
- Parameter resolver: JS promise resolver.
|
|
140
|
+
- Parameter rejecter: JS promise rejecter.
|
|
141
|
+
*/
|
|
142
|
+
@objc(getCustomData:resolver:rejecter:)
|
|
143
|
+
func getCustomData(
|
|
144
|
+
_ nativeId: NativeId,
|
|
145
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
146
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
147
|
+
) {
|
|
148
|
+
bridge.uiManager.addUIBlock { [weak self] _, _ in
|
|
149
|
+
guard
|
|
150
|
+
let collector = self?.collectors[nativeId],
|
|
151
|
+
let customData = RCTConvert.toJson(analyticsCustomData: collector.getCustomData())
|
|
152
|
+
else {
|
|
153
|
+
reject("[AnalyticsModule]", "Could not find analytics collector with ID (\(nativeId))", nil)
|
|
154
|
+
return
|
|
155
|
+
}
|
|
156
|
+
resolve(customData)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
Gets the current user Id for a `BitmovinPlayerCollector` instance.
|
|
162
|
+
- Parameter nativeId: Native Id of the the collector instance.
|
|
163
|
+
- Parameter resolver: JS promise resolver.
|
|
164
|
+
- Parameter rejecter: JS promise rejecter.
|
|
165
|
+
*/
|
|
166
|
+
@objc(getUserId:resolver:rejecter:)
|
|
167
|
+
func getUserId(
|
|
168
|
+
_ nativeId: NativeId,
|
|
169
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
170
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
171
|
+
) {
|
|
172
|
+
bridge.uiManager.addUIBlock { [weak self] _, _ in
|
|
173
|
+
guard let collector = self?.collectors[nativeId] else {
|
|
174
|
+
reject("[AnalyticsModule]", "Could not find analytics collector with ID (\(nativeId))", nil)
|
|
175
|
+
return
|
|
176
|
+
}
|
|
177
|
+
resolve(collector.getUserId())
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
#import <React/RCTBridgeModule.h>
|
|
2
|
+
|
|
3
|
+
@interface RCT_EXTERN_REMAP_MODULE(AudioSessionModule, AudioSessionModule, NSObject)
|
|
4
|
+
|
|
5
|
+
RCT_EXTERN_METHOD(
|
|
6
|
+
setCategory:(NSString *)category
|
|
7
|
+
resolver:(RCTPromiseResolveBlock)resolve
|
|
8
|
+
rejecter:(RCTPromiseRejectBlock)reject)
|
|
9
|
+
|
|
10
|
+
@end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import AVFAudio
|
|
2
|
+
|
|
3
|
+
@objc(AudioSessionModule)
|
|
4
|
+
class AudioSessionModule: NSObject, RCTBridgeModule {
|
|
5
|
+
// Run this module methods on main thread.
|
|
6
|
+
var methodQueue: DispatchQueue! {
|
|
7
|
+
DispatchQueue.main
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/// JS module name.
|
|
11
|
+
static func moduleName() -> String! {
|
|
12
|
+
"AudioSessionModule"
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Requires module initialization.
|
|
16
|
+
static func requiresMainQueueSetup() -> Bool {
|
|
17
|
+
true
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
Sets the audio session’s category.
|
|
22
|
+
- Parameter category: Category string.
|
|
23
|
+
- Parameter resolver: JS promise resolver block.
|
|
24
|
+
- Parameter rejecter: JS promise rejecter block.
|
|
25
|
+
*/
|
|
26
|
+
@objc func setCategory(
|
|
27
|
+
_ category: String,
|
|
28
|
+
resolver resolve: @escaping RCTPromiseResolveBlock,
|
|
29
|
+
rejecter reject: @escaping RCTPromiseRejectBlock
|
|
30
|
+
) {
|
|
31
|
+
if let category = parseCategory(category) {
|
|
32
|
+
do {
|
|
33
|
+
try AVAudioSession.sharedInstance().setCategory(category)
|
|
34
|
+
resolve(nil)
|
|
35
|
+
} catch {
|
|
36
|
+
reject("\((error as NSError).code)", error.localizedDescription, error as NSError)
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
let error = RCTErrorWithMessage("Unknown audio session category: \(category)") as NSError
|
|
40
|
+
reject("\(error.code)", error.localizedDescription, error)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
Parse any category string to an `AVAudioSession.Category` type.
|
|
46
|
+
*/
|
|
47
|
+
private func parseCategory(_ category: String) -> AVAudioSession.Category? {
|
|
48
|
+
switch (category) {
|
|
49
|
+
case "ambient":
|
|
50
|
+
return .ambient
|
|
51
|
+
case "multiRoute":
|
|
52
|
+
return .multiRoute
|
|
53
|
+
case "playAndRecord":
|
|
54
|
+
return .playAndRecord
|
|
55
|
+
case "playback":
|
|
56
|
+
return .playback
|
|
57
|
+
case "record":
|
|
58
|
+
return .record
|
|
59
|
+
case "soloAmbient":
|
|
60
|
+
return .soloAmbient
|
|
61
|
+
default:
|
|
62
|
+
return nil
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|