Skip to content

Commit

Permalink
feat: upgrade analytics api v3 and the bundle analytics-player
Browse files Browse the repository at this point in the history
  • Loading branch information
matamegger committed Sep 12, 2023
1 parent ff48f0e commit 30f6e6f
Show file tree
Hide file tree
Showing 19 changed files with 300 additions and 680 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,19 @@
- `Player.getAudioTrack` and `Player.getSubtitleTrack` APIs to get currently selected audio and subtitle tracks
- `SourceConfig.description` property to allow setting a description for the source
- `Player.getThumbnail` and `Source.getThumbnail` APIs to get thumbnail images
- `DefaultMetadata` for configuration of the bundled analytics collector
- `Player.analytics` to access the `AnalyticsApi` and interact with the bundled analytics collector
- `SourceConfig.analyticsSourceMetadata` for extended configuration of the bundled analytics collector

### Changed

- Update Bitmovin's native Android SDK version to `3.43.0`
- `AnalyticsConfig` properties to match the bitmovin analytics v3 API

### Removed

- `AnalyticsCollector` in favor of the bundled analytics functionality
- `CdnProvider`, as the property on the `AnalyticsConfig` is now a `string`

## [0.10.0] (2023-09-04)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.bitmovin.player.reactnative

import android.util.Log
import com.bitmovin.analytics.bitmovin.player.api.IBitmovinPlayerCollector
import com.bitmovin.player.api.analytics.AnalyticsApi.Companion.analytics
import com.bitmovin.player.reactnative.converter.JsonConverter
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule
Expand All @@ -11,95 +10,11 @@ private const val MODULE_NAME = "AnalyticsModule"

@ReactModule(name = MODULE_NAME)
class AnalyticsModule(private val context: ReactApplicationContext) : ReactContextBaseJavaModule(context) {
/**
* In-memory mapping from `nativeId`s to `BitmovinPlayerCollector` instances.
*/
private val collectors: Registry<IBitmovinPlayerCollector> = mutableMapOf()

/**
* JS exported module name.
*/
override fun getName() = MODULE_NAME

/**
* Fetches the `BitmovinPlayerCollector` instance associated with `nativeId` from the internal registry.
* @param nativeId `BitmovinPlayerCollector` instance ID.
* @return The associated `BitmovinPlayerCollector` instance or `null`.
*/
fun getCollector(nativeId: NativeId?): IBitmovinPlayerCollector? {
if (nativeId == null) {
return null
}
return collectors[nativeId]
}

/**
* Creates a new `BitmovinPlayerCollector` instance inside the internal registry using the provided `config` object.
* @param config `BitmovinAnalyticsConfig` object received from JS.
*/
@ReactMethod
fun initWithConfig(nativeId: NativeId, config: ReadableMap?) {
uiManager()?.addUIBlock { _ ->
JsonConverter.toAnalyticsConfig(config)?.let {
collectors[nativeId] = IBitmovinPlayerCollector.create(it, context)
}
}
}

/**
* Detaches and removes the given `BitmovinPlayerCollector` from the internal registry.
* @param nativeId Native Id of the collector instance.
*/
@ReactMethod
fun destroy(nativeId: NativeId) {
uiManager()?.addUIBlock {
if (collectors.containsKey(nativeId)) {
collectors[nativeId]?.detachPlayer()
collectors.remove(nativeId)
}
}
}

/**
* Attaches a `BitmovinPlayerCollector` to the `Player` instance with native Id equal to `playerId`.
* @param nativeId Native Id of the collector instance.
* @param playerId Native Id of the player instance.
*/
@ReactMethod
fun attach(nativeId: NativeId, playerId: NativeId) {
uiManager()?.addUIBlock { _ ->
playerModule()?.getPlayer(playerId)?.let {
collectors[nativeId]?.attachPlayer(it)
}
}
}

/**
* Detaches the player object from a `BitmovinPlayerCollector` instance.
* @param nativeId Native Id of the collector instance.
*/
@ReactMethod
fun detach(nativeId: NativeId) {
uiManager()?.addUIBlock { _ ->
collectors[nativeId]?.detachPlayer()
}
}

/**
* Updates the custom data config for a `BitmovinPlayerCollector` instance.
* @param nativeId Native Id of the collector instance.
* @param json Custom data config json.
*/
@Deprecated("Confusing API naming", replaceWith = ReplaceWith("sendCustomDataEvent(nativeId, json)"))
@ReactMethod
fun setCustomDataOnce(nativeId: NativeId, json: ReadableMap?) {
uiManager()?.addUIBlock { _ ->
JsonConverter.toAnalyticsCustomData(json)?.let {
collectors[nativeId]?.setCustomDataOnce(it)
}
}
}

/**
* Sends a sample with the provided custom data.
* Does not change the configured custom data of the collector or source.
Expand All @@ -110,114 +25,20 @@ class AnalyticsModule(private val context: ReactApplicationContext) : ReactConte
fun sendCustomDataEvent(nativeId: NativeId, json: ReadableMap?) {
uiManager()?.addUIBlock { _ ->
JsonConverter.toAnalyticsCustomData(json)?.let {
collectors[nativeId]?.sendCustomDataEvent(it)
}
}
}


/**
* Sets the custom data config for a `BitmovinPlayerCollector` instance.
* @param nativeId Native Id of the collector instance.
* @param json Custom data config json.
*/
@ReactMethod
fun setCustomData(nativeId: NativeId, playerId: NativeId?, json: ReadableMap?) {
uiManager()?.addUIBlock { _ ->
val source = playerModule()?.getPlayer(playerId)?.source
val collector = collectors[nativeId]
val customData = JsonConverter.toAnalyticsCustomData(json)
when {
source == null -> Log.d(
"[AnalyticsModule]", "Could not find source for player ($playerId)"
)
collector == null -> Log.d(
"[AnalyticsModule]", "Could not find analytics collector ($nativeId)"
)
customData == null -> Log.d(
"[AnalyticsModule]", "Could not convert custom data, thus they are not applied to the active source for the player ($playerId) with the collector ($nativeId)"
)
else -> collector.setCustomData(source, customData)
}
}
}

/**
* Gets the current custom data config for a `BitmovinPlayerCollector` instance.
* @param nativeId Native Id of the the collector instance.
* @param promise JS promise object.
*/
@ReactMethod
fun getCustomData(nativeId: NativeId, playerId: NativeId?, promise: Promise) {
uiManager()?.addUIBlock { _ ->
val source = playerModule()?.getPlayer(playerId)?.source
val collector = collectors[nativeId]
when {
source == null -> promise.reject(
"[AnalyticsModule]", "Could not find source for player ($playerId)"
)
collector == null -> promise.reject(
"[AnalyticsModule]", "Could not find analytics collector ($nativeId)"
)
else -> promise.resolve(JsonConverter.fromAnalyticsCustomData(collector.getCustomData(source)))
}
}
}

@ReactMethod
fun addSourceMetadata(nativeId: NativeId, playerId: NativeId?, json: ReadableMap?) {
uiManager()?.addUIBlock { _ ->
val source = playerModule()?.getPlayer(playerId)?.source
val collector = collectors[nativeId]
val sourceMetadata = JsonConverter.toAnalyticsSourceMetadata(json)
when {
source == null -> Log.d(
"[AnalyticsModule]", "Could not find source for player ($playerId)"
)
collector == null -> Log.d(
"[AnalyticsModule]", "Could not find analytics collector ($nativeId)"
)
sourceMetadata == null -> Log.d(
"[AnalyticsModule]", "Could not convert source metadata, thus they are not applied to the collector ($nativeId)"
)
else -> collector.addSourceMetadata(source, sourceMetadata)
}
}
}

/**
* Sets the source metadata for the current active source of the player associated to `playerId`.
*/
@ReactMethod
fun setSourceMetadata(nativeId: NativeId, playerId: NativeId?, json: ReadableMap?) {
uiManager()?.addUIBlock { _ ->
val source = playerModule()?.getPlayer(playerId)?.source
val collector = collectors[nativeId]
val sourceMetadata = JsonConverter.toAnalyticsSourceMetadata(json)
when {
source == null -> Log.d(
"[AnalyticsModule]", "Could not find source for player ($playerId)"
)
collector == null -> Log.d(
"[AnalyticsModule]", "Could not find analytics collector ($nativeId)"
)
sourceMetadata == null -> Log.d(
"[AnalyticsModule]", "Could not convert source metadata, thus they are not applied to the collector ($nativeId)"
)
else -> collector.setSourceMetadata(source, sourceMetadata)
playerModule()?.getPlayer(nativeId)?.analytics?.sendCustomDataEvent(it)
}
}
}

/**
* Gets the current user Id for a `BitmovinPlayerCollector` instance.
* @param nativeId Native Id of the the collector instance.
* Gets the current user Id for a player instance with analytics.
* @param nativeId Native Id of the the player instance.
* @param promise JS promise object.
*/
@ReactMethod
fun getUserId(nativeId: NativeId, promise: Promise) {
fun getUserId(playerId: NativeId, promise: Promise) {
uiManager()?.addUIBlock { _ ->
collectors[nativeId]?.let {
playerModule()?.getPlayer(playerId)?.analytics?.let {
promise.resolve(it.userId)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,15 +56,15 @@ class PlayerModule(private val context: ReactApplicationContext) : ReactContextB
* @param analyticsConfigJson `AnalyticsConfig` object received from JS.
*/
@ReactMethod
fun initWithConfig(nativeId: NativeId, playerConfigJson: ReadableMap?, analyticsConfigJson: ReadableMap?) {
fun initWithAnalyticsConfig(nativeId: NativeId, playerConfigJson: ReadableMap?, analyticsConfigJson: ReadableMap?) {
uiManager()?.addUIBlock {
if (players.containsKey(nativeId)) {
Log.d("[PlayerModule]", "Duplicate player creation for id $nativeId")
return@addUIBlock
}
val playerConfig = JsonConverter.toPlayerConfig(playerConfigJson)
val analyticsConfig = JsonConverter.toAnalyticsConfig(analyticsConfigJson)
val defaultMetadata = JsonConverter.toAnalyticsDefaultMetadata(analyticsConfigJson)
val defaultMetadata = JsonConverter.toAnalyticsDefaultMetadata(analyticsConfigJson?.getMap("defaultMetadata"))

players[nativeId] = if (analyticsConfig == null) {
Player.create(context, playerConfig)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package com.bitmovin.player.reactnative

import android.util.Log
import com.bitmovin.analytics.api.SourceMetadata
import com.bitmovin.player.api.analytics.create
import com.bitmovin.player.api.source.Source
import com.bitmovin.player.api.source.SourceConfig
import com.bitmovin.player.reactnative.converter.JsonConverter
import com.facebook.react.bridge.*
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.UIManagerModule

Expand Down Expand Up @@ -33,17 +41,24 @@ class SourceModule(private val context: ReactApplicationContext) : ReactContextB
}

/**
* Creates a new `Source` instance inside the internal sources using the provided `config` object.
* Creates a new `Source` instance inside the internal sources using the provided
* `config` and `analyticsSourceMetadata` object as well as an initialized DRM configuration ID.
* @param nativeId ID to be associated with the `Source` instance.
* @param drmNativeId ID of the DRM config to use.
* @param config `SourceConfig` object received from JS.
* @param analyticsSourceMetadata `SourceMetadata` object received from JS.
*/
@ReactMethod
fun initWithConfig(nativeId: NativeId, config: ReadableMap?) {
fun initWithAnalyticsConfig(
nativeId: NativeId,
drmNativeId: NativeId?,
config: ReadableMap?,
analyticsSourceMetadata: ReadableMap?
) {
uiManager()?.addUIBlock {
if (!sources.containsKey(nativeId)) {
JsonConverter.toSourceConfig(config)?.let {
sources[nativeId] = Source.create(it)
}
val sourceMetadata = JsonConverter.toAnalyticsSourceMetadata(analyticsSourceMetadata) ?: SourceMetadata()
initializeSource(nativeId, drmNativeId, config) { sourceConfig ->
Source.create(sourceConfig, sourceMetadata)
}
}
}
Expand All @@ -56,15 +71,36 @@ class SourceModule(private val context: ReactApplicationContext) : ReactContextB
* @param config `SourceConfig` object received from JS.
*/
@ReactMethod
fun initWithDrmConfig(nativeId: NativeId, drmNativeId: NativeId, config: ReadableMap?) {
fun initWithConfig(
nativeId: NativeId,
drmNativeId: NativeId?,
config: ReadableMap?,
) {
uiManager()?.addUIBlock {
val drmConfig = drmModule()?.getConfig(drmNativeId)
if (!sources.containsKey(nativeId) && drmConfig != null) {
JsonConverter.toSourceConfig(config)?.let {
it.drmConfig = drmConfig
sources[nativeId] = Source.create(it)
initializeSource(nativeId, drmNativeId, config) { sourceConfig ->
Source.create(sourceConfig)
}
}
}

private fun initializeSource(
nativeId: NativeId,
drmNativeId: NativeId?,
config: ReadableMap?,
action: (SourceConfig) -> Source
) {
val drmConfig = drmNativeId?.let { drmModule()?.getConfig(it) }
if (!sources.containsKey(nativeId)) {
val sourceConfig = JsonConverter.toSourceConfig(config)?.apply {
if (drmConfig != null) {
this.drmConfig = drmConfig
}
}
if (sourceConfig == null) {
Log.d("[SourceModule]", "Could not parse SourceConfig")
} else {
sources[nativeId] = action(sourceConfig)
}
}
}

Expand Down
Loading

0 comments on commit 30f6e6f

Please sign in to comment.