From 31d086fcc5e74a7372037bca7b67df9d5cc49e56 Mon Sep 17 00:00:00 2001 From: Oleksandr Kharchenko Date: Wed, 27 Nov 2024 17:48:03 +0200 Subject: [PATCH 1/3] [CORE-5217] Added Bitmovin analytics --- .../main/java/com/streamamg/PlaybackSDKManager.kt | 7 +++++++ .../configuration/PlayerInformationAPIService.kt | 5 +++-- .../streamamg/player/plugin/VideoPlayerPlugin.kt | 2 +- .../plugin/bitmovin/BitmovinVideoPlayerPlugin.kt | 10 ++++++++-- .../plugin/bitmovin/VideoPlayerViewModel.kt | 15 +++++++++++++-- .../com/streamamg/player/ui/PlaybackUIView.kt | 3 ++- 6 files changed, 34 insertions(+), 8 deletions(-) diff --git a/playback-sdk-android/src/main/java/com/streamamg/PlaybackSDKManager.kt b/playback-sdk-android/src/main/java/com/streamamg/PlaybackSDKManager.kt index 6f8e851..b955394 100644 --- a/playback-sdk-android/src/main/java/com/streamamg/PlaybackSDKManager.kt +++ b/playback-sdk-android/src/main/java/com/streamamg/PlaybackSDKManager.kt @@ -43,6 +43,7 @@ object PlaybackSDKManager { */ internal var baseURL = "https://api.playback.streamamg.com/v1" internal var bitmovinLicense: String = "" + internal var analyticsLicense: String? = null private var userAgent: String? = null val playbackSdkVersion = BuildConfig.SDK_VERSION @@ -94,17 +95,20 @@ object PlaybackSDKManager { * Composable function that loads and renders the player UI. * @param entryID The ID of the entry. * @param authorizationToken The authorization token. + * @param analyticsViewerId The user's id to be tracked in analytics * @param onError Callback for handling errors. Default is null. */ @Composable fun loadPlayer( entryID: String, authorizationToken: String?, + analyticsViewerId: String?, onError: ((PlaybackAPIError) -> Unit)? ) { PlaybackUIView( authorizationToken = authorizationToken, entryId = entryID, + analyticsViewerId = analyticsViewerId, userAgent = this.userAgent, onError = onError ) @@ -133,6 +137,9 @@ object PlaybackSDKManager { } val bitmovinLicense = playerInfo?.player?.bitmovin?.license + val analyticsLicense = playerInfo?.player?.bitmovin?.integrations?.mux?.envKey + + this@PlaybackSDKManager.analyticsLicense = analyticsLicense this@PlaybackSDKManager.bitmovinLicense = bitmovinLicense ?: run { completion(null, SDKError.MissingLicense) diff --git a/playback-sdk-android/src/main/java/com/streamamg/api/configuration/PlayerInformationAPIService.kt b/playback-sdk-android/src/main/java/com/streamamg/api/configuration/PlayerInformationAPIService.kt index 8233581..4959516 100644 --- a/playback-sdk-android/src/main/java/com/streamamg/api/configuration/PlayerInformationAPIService.kt +++ b/playback-sdk-android/src/main/java/com/streamamg/api/configuration/PlayerInformationAPIService.kt @@ -3,6 +3,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flowOn +import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import java.io.IOException @@ -80,8 +81,8 @@ internal data class Integrations( @Serializable internal data class Mux( - val playerName: String? = null, - val envKey: String? = null + @SerialName("player_name") val playerName: String? = null, + @SerialName("env_key") val envKey: String? = null ) @Serializable diff --git a/playback-sdk-android/src/main/java/com/streamamg/player/plugin/VideoPlayerPlugin.kt b/playback-sdk-android/src/main/java/com/streamamg/player/plugin/VideoPlayerPlugin.kt index be92746..5fb6e98 100644 --- a/playback-sdk-android/src/main/java/com/streamamg/player/plugin/VideoPlayerPlugin.kt +++ b/playback-sdk-android/src/main/java/com/streamamg/player/plugin/VideoPlayerPlugin.kt @@ -39,7 +39,7 @@ interface VideoPlayerPlugin { * @param hlsUrl The HLS URL of the video to be played. */ @Composable - fun PlayerView(hlsUrl: String) + fun PlayerView(hlsUrl: String, analyticsViewerId: String?) /** * Starts playback of the video. diff --git a/playback-sdk-android/src/main/java/com/streamamg/player/plugin/bitmovin/BitmovinVideoPlayerPlugin.kt b/playback-sdk-android/src/main/java/com/streamamg/player/plugin/bitmovin/BitmovinVideoPlayerPlugin.kt index 2181827..90b4528 100644 --- a/playback-sdk-android/src/main/java/com/streamamg/player/plugin/bitmovin/BitmovinVideoPlayerPlugin.kt +++ b/playback-sdk-android/src/main/java/com/streamamg/player/plugin/bitmovin/BitmovinVideoPlayerPlugin.kt @@ -28,8 +28,10 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.compose.viewModel +import com.bitmovin.analytics.api.AnalyticsConfig import com.bitmovin.player.PlayerView import com.bitmovin.player.api.Player +import com.bitmovin.player.api.PlayerConfig import com.bitmovin.player.api.ui.FullscreenHandler import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted @@ -49,6 +51,7 @@ class BitmovinVideoPlayerPlugin : VideoPlayerPlugin, LifecycleCleaner { override val version: String = "1.0" private var hlsUrl: String = "" + private var analyticsViewerId: String? = null private var playerConfig = VideoPlayerConfig() private var playerBind: Player? = null private val fullscreen = mutableStateOf(false) @@ -61,8 +64,10 @@ class BitmovinVideoPlayerPlugin : VideoPlayerPlugin, LifecycleCleaner { playerConfig.playbackConfig.fullscreenEnabled = config.playbackConfig.fullscreenEnabled } + + @Composable - override fun PlayerView(hlsUrl: String): Unit { + override fun PlayerView(hlsUrl: String, analyticsViewerId: String?): Unit { val context = LocalContext.current val isJetpackCompose = when (context) { is ComponentActivity -> true @@ -74,6 +79,7 @@ class BitmovinVideoPlayerPlugin : VideoPlayerPlugin, LifecycleCleaner { ViewModelProvider(it)[VideoPlayerViewModel::class.java] } ?: viewModel() + this.analyticsViewerId = analyticsViewerId this.hlsUrl = hlsUrl val currentLifecycle = LocalLifecycleOwner.current val lastHlsUrl = remember { mutableStateOf(hlsUrl) } @@ -91,7 +97,7 @@ class BitmovinVideoPlayerPlugin : VideoPlayerPlugin, LifecycleCleaner { } DisposableEffect(hlsUrl) { - playerViewModel?.initializePlayer(context, playerConfig, hlsUrl) + playerViewModel?.initializePlayer(context, playerConfig, hlsUrl, analyticsViewerId) playerBind = playerViewModel?.player onDispose { if (isJetpackCompose) { diff --git a/playback-sdk-android/src/main/java/com/streamamg/player/plugin/bitmovin/VideoPlayerViewModel.kt b/playback-sdk-android/src/main/java/com/streamamg/player/plugin/bitmovin/VideoPlayerViewModel.kt index 6d3e9ef..6984e6b 100644 --- a/playback-sdk-android/src/main/java/com/streamamg/player/plugin/bitmovin/VideoPlayerViewModel.kt +++ b/playback-sdk-android/src/main/java/com/streamamg/player/plugin/bitmovin/VideoPlayerViewModel.kt @@ -9,8 +9,11 @@ import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.viewModelScope +import com.bitmovin.analytics.api.AnalyticsConfig +import com.bitmovin.analytics.api.DefaultMetadata import com.bitmovin.player.api.Player import com.bitmovin.player.api.PlayerConfig +import com.bitmovin.player.api.analytics.AnalyticsPlayerConfig import com.bitmovin.player.api.event.Event import com.bitmovin.player.api.event.PlayerEvent import com.bitmovin.player.api.event.on @@ -50,14 +53,15 @@ class VideoPlayerViewModel : ViewModel() { } } - fun initializePlayer(context: Context, config: VideoPlayerConfig, videoUrl: String) { + fun initializePlayer(context: Context, config: VideoPlayerConfig, videoUrl: String, analyticsViewerId: String? = null) { this.config = config backgroundPlaybackEnabled = config.playbackConfig.backgroundPlaybackEnabled autoplayEnabled = config.playbackConfig.autoplayEnabled if (player == null) { val playerConfig = PlayerConfig(key = PlaybackSDKManager.bitmovinLicense) - player = Player(context, playerConfig) + val analyticsConfig = provideAnalyticsConfig(PlaybackSDKManager.analyticsLicense, analyticsViewerId) + player = Player(context, playerConfig, analyticsConfig) } unbindFromService(context) @@ -77,6 +81,13 @@ class VideoPlayerViewModel : ViewModel() { updateBackgroundService(context) } + private fun provideAnalyticsConfig(license: String? = null, analyticsViewerId: String?): AnalyticsPlayerConfig { + return license?.let { + val config = AnalyticsConfig.Builder(it).build() + AnalyticsPlayerConfig.Enabled(config, DefaultMetadata(customUserId = analyticsViewerId)) + } ?: AnalyticsPlayerConfig.Disabled + } + private fun loadVideo(videoUrl: String) { if (!urlsAreEqualExcludingKs(currentVideoUrl ?: "", videoUrl)) { val sourceConfig = SourceConfig.fromUrl(videoUrl) diff --git a/playback-sdk-android/src/main/java/com/streamamg/player/ui/PlaybackUIView.kt b/playback-sdk-android/src/main/java/com/streamamg/player/ui/PlaybackUIView.kt index adcad49..8beb9cf 100644 --- a/playback-sdk-android/src/main/java/com/streamamg/player/ui/PlaybackUIView.kt +++ b/playback-sdk-android/src/main/java/com/streamamg/player/ui/PlaybackUIView.kt @@ -24,6 +24,7 @@ object PlaybackUIView { fun PlaybackUIView( authorizationToken: String?, entryId: String, + analyticsViewerId: String?, userAgent: String?, onError: ((PlaybackAPIError) -> Unit)? ) { @@ -59,7 +60,7 @@ object PlaybackUIView { } else { videoURL?.let { url -> VideoPlayerPluginManager.selectedPlugin?.let { plugin -> - plugin.PlayerView(url) + plugin.PlayerView(url, analyticsViewerId) } } ?: run { // TODO: Handle null video URL (Error UI View) From 01bda05bdb610a0215b06077931c2cc9709741e4 Mon Sep 17 00:00:00 2001 From: Oleksandr Kharchenko Date: Tue, 3 Dec 2024 13:16:00 +0200 Subject: [PATCH 2/3] [CORE-5217] Optional analytics viewer id --- .../src/main/java/com/streamamg/PlaybackSDKManager.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playback-sdk-android/src/main/java/com/streamamg/PlaybackSDKManager.kt b/playback-sdk-android/src/main/java/com/streamamg/PlaybackSDKManager.kt index b955394..8b7fa05 100644 --- a/playback-sdk-android/src/main/java/com/streamamg/PlaybackSDKManager.kt +++ b/playback-sdk-android/src/main/java/com/streamamg/PlaybackSDKManager.kt @@ -102,7 +102,7 @@ object PlaybackSDKManager { fun loadPlayer( entryID: String, authorizationToken: String?, - analyticsViewerId: String?, + analyticsViewerId: String? = null, onError: ((PlaybackAPIError) -> Unit)? ) { PlaybackUIView( From 10f39167603098a5ce61696adae1d94532c497d8 Mon Sep 17 00:00:00 2001 From: Oleksandr Kharchenko Date: Tue, 3 Dec 2024 13:36:25 +0200 Subject: [PATCH 3/3] [CORE-5217] Optional analytics viewer id --- .../playback_sdk_android_app/CustomVideoPlayerPlugin.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/streamamg/playback_sdk_android_app/CustomVideoPlayerPlugin.kt b/app/src/main/java/com/streamamg/playback_sdk_android_app/CustomVideoPlayerPlugin.kt index 2d2f1d3..c8649eb 100644 --- a/app/src/main/java/com/streamamg/playback_sdk_android_app/CustomVideoPlayerPlugin.kt +++ b/app/src/main/java/com/streamamg/playback_sdk_android_app/CustomVideoPlayerPlugin.kt @@ -27,7 +27,7 @@ class NativeMediaPlayerPlugin : VideoPlayerPlugin { } @Composable - override fun PlayerView(hlsUrl: String): Unit { + override fun PlayerView(hlsUrl: String, analyticsViewerId: String?) { mediaPlayer = MediaPlayer() val textureView = rememberTextureView()