From 16ea02a8795f1b6c329069380621cb3b7ece17ee Mon Sep 17 00:00:00 2001 From: Lukas Knoch-Girstmair Date: Fri, 16 Aug 2024 08:01:47 +0200 Subject: [PATCH 01/25] Initial tests --- .../conviva/BitmovinPlayerHelper.java | 31 -- .../conviva/ConvivaAnalyticsIntegration.java | 307 +++++++++--------- .../ssai/DefaultPlaybackInfoProvider.java | 40 --- .../conviva/ssai/DefaultPlayerAdapter.java | 100 ++++++ .../conviva/ssai/DefaultSsaiApi.java | 28 +- .../conviva/ssai/PlaybackInfoProvider.java | 11 - .../analytics/conviva/ssai/PlayerAdapter.java | 34 ++ .../conviva/ssai/WithEventEmitter.java | 8 + .../ConvivaAnalyticsIntegrationTest.kt | 2 + .../conviva/ssai/DefaultSsaiApiTest.kt | 19 +- 10 files changed, 336 insertions(+), 244 deletions(-) delete mode 100644 conviva/src/main/java/com/bitmovin/analytics/conviva/BitmovinPlayerHelper.java delete mode 100644 conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultPlaybackInfoProvider.java create mode 100644 conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultPlayerAdapter.java delete mode 100644 conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/PlaybackInfoProvider.java create mode 100644 conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/PlayerAdapter.java create mode 100644 conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/WithEventEmitter.java diff --git a/conviva/src/main/java/com/bitmovin/analytics/conviva/BitmovinPlayerHelper.java b/conviva/src/main/java/com/bitmovin/analytics/conviva/BitmovinPlayerHelper.java deleted file mode 100644 index c1c4b6e..0000000 --- a/conviva/src/main/java/com/bitmovin/analytics/conviva/BitmovinPlayerHelper.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.bitmovin.analytics.conviva; - -import com.bitmovin.player.api.Player; - -class BitmovinPlayerHelper { - private final Player player; - - BitmovinPlayerHelper(Player player) { - this.player = player; - } - - String getSdkVersionString() { - return Player.getSdkVersion(); - } - - String getStreamType() { - if (player.getSource() == null) { - return null; - } else { - return player.getSource().getConfig().getType().name(); - } - } - - String getStreamUrl() { - if (player.getSource() == null) { - return null; - } else { - return player.getSource().getConfig().getUrl(); - } - } -} diff --git a/conviva/src/main/java/com/bitmovin/analytics/conviva/ConvivaAnalyticsIntegration.java b/conviva/src/main/java/com/bitmovin/analytics/conviva/ConvivaAnalyticsIntegration.java index 2c5d86f..90cc59b 100644 --- a/conviva/src/main/java/com/bitmovin/analytics/conviva/ConvivaAnalyticsIntegration.java +++ b/conviva/src/main/java/com/bitmovin/analytics/conviva/ConvivaAnalyticsIntegration.java @@ -4,9 +4,12 @@ import android.os.Handler; import android.util.Log; -import com.bitmovin.analytics.conviva.ssai.DefaultPlaybackInfoProvider; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bitmovin.analytics.conviva.ssai.DefaultPlayerAdapter; import com.bitmovin.analytics.conviva.ssai.DefaultSsaiApi; -import com.bitmovin.analytics.conviva.ssai.PlaybackInfoProvider; +import com.bitmovin.analytics.conviva.ssai.PlayerAdapter; import com.bitmovin.analytics.conviva.ssai.SsaiApi; import com.bitmovin.player.api.Player; import com.bitmovin.player.api.advertising.Ad; @@ -18,8 +21,6 @@ import com.bitmovin.player.api.event.EventListener; import com.bitmovin.player.api.event.PlayerEvent; import com.bitmovin.player.api.event.SourceEvent; -import com.bitmovin.player.api.source.Source; -import com.bitmovin.player.api.source.SourceConfig; import com.conviva.sdk.ConvivaAdAnalytics; import com.conviva.sdk.ConvivaAnalytics; import com.conviva.sdk.ConvivaExperienceAnalytics; @@ -33,22 +34,27 @@ public class ConvivaAnalyticsIntegration { public static final String STREAM_TYPE = "streamType"; public static final String INTEGRATION_VERSION = "integrationVersion"; - private static final String TAG = "ConvivaAnalyticsInt"; - private final Player bitmovinPlayer; + @NonNull private final ContentMetadataBuilder contentMetadataBuilder = new ContentMetadataBuilder(); + @NonNull private final ConvivaVideoAnalytics convivaVideoAnalytics; + @NonNull private final ConvivaAdAnalytics convivaAdAnalytics; - private final PlaybackInfoProvider playbackInfoProvider; - private MetadataOverrides metadataOverrides; - private final DefaultSsaiApi ssai; + /** + * The playerAdapter is used to get the current state of the player in a decoupled way. + * Can be `null` if the player is initialized late. + */ + @Nullable + private PlayerAdapter playerAdapter; - // Wrapper to extract bitmovinPlayer helper methods - private final BitmovinPlayerHelper playerHelper; + @Nullable + private MetadataOverrides metadataOverrides; + @NonNull + private final DefaultSsaiApi ssai; - // Helper private Boolean isSessionActive = false; private Boolean isBumper = false; private Boolean isBackgrounded = false; @@ -94,8 +100,7 @@ public ConvivaAnalyticsIntegration(Player player, ConvivaAdAnalytics adAnalytics, DefaultSsaiApi ssai ) { - this.bitmovinPlayer = player; - this.playerHelper = new BitmovinPlayerHelper(player); + Map settings = new HashMap<>(); if (config.getGatewayUrl() != null || config.isDebugLoggingEnabled()) { if (config.getGatewayUrl() != null) { @@ -117,15 +122,15 @@ public ConvivaAnalyticsIntegration(Player player, } else { convivaAdAnalytics = adAnalytics; } - playbackInfoProvider = new DefaultPlaybackInfoProvider(player); if (ssai == null) { - this.ssai = new DefaultSsaiApi(convivaVideoAnalytics, convivaAdAnalytics, playbackInfoProvider); + this.ssai = new DefaultSsaiApi(convivaVideoAnalytics, convivaAdAnalytics); } else { this.ssai = ssai; } - - attachBitmovinEventListeners(); + if (player != null) { + setPlayer(player); + } setUpAdAnalyticsCallback(); } @@ -133,8 +138,8 @@ private void setUpAdAnalyticsCallback() { convivaAdAnalytics.setCallback(new ConvivaExperienceAnalytics.ICallback() { @Override public void update() { - if (isAdActive()) { - convivaAdAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.PLAY_HEAD_TIME, ((long) (bitmovinPlayer.getCurrentTime() * 1000))); + if (isAdActive() && playerAdapter != null) { + convivaAdAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.PLAY_HEAD_TIME, playerAdapter.getPlayHeadTimeMillis()); } } @@ -145,7 +150,7 @@ public void update(String s) { } private boolean isAdActive() { - return bitmovinPlayer.isAd() || ssai.isAdBreakActive(); + return (playerAdapter != null && playerAdapter.isAd()) || ssai.isAdBreakActive(); } // region public methods @@ -178,14 +183,15 @@ public void sendCustomPlaybackEvent(String name, Map attributes) * no longer ensure that the session is managed at the correct time. Additional: Since some metadata attributes * relies on the players source we can't ensure that all metadata attributes are present at session creation. * Therefore it could be that there will be a 'ContentMetadata created late' issue after conviva validation. - *

- * If no source was loaded this method will throw an error. + *

+ * If no no player with loaded source is available, `assetName` must be set via `updateContentMetadata` before + * calling this method. */ public void initializeSession() throws ConvivaAnalyticsException { - if ((bitmovinPlayer.getSource() == null || bitmovinPlayer.getSource().getConfig().getTitle() == null) + if ((playerAdapter == null || playerAdapter.getStreamTitle() == null) && this.contentMetadataBuilder.getAssetName() == null) { throw new ConvivaAnalyticsException( - "AssetName is missing. Load player source (with Title) first or set assetName via updateContentMetadata" + "AssetName is missing. Load player source (with Title) and attach player first or set assetName via updateContentMetadata" ); } internalInitializeSession(); @@ -227,7 +233,9 @@ public void release() { public void release(Boolean releaseConvivaSdk) { convivaAdAnalytics.release(); convivaVideoAnalytics.release(); - detachBitmovinEventListeners(); + if (playerAdapter != null) { + detachBitmovinEventListeners(playerAdapter); + } if (releaseConvivaSdk) { ConvivaAnalytics.release(); } @@ -327,17 +335,17 @@ private void setupPlayerStateManager() { convivaVideoAnalytics.reportPlaybackMetric(ConvivaSdkConstants.PLAYBACK.PLAYER_STATE, ConvivaSdkConstants.PlayerState.STOPPED); Map playerInfo = new HashMap<>(); playerInfo.put(ConvivaSdkConstants.FRAMEWORK_NAME, "Bitmovin Player Android"); - playerInfo.put(ConvivaSdkConstants.FRAMEWORK_VERSION, playerHelper.getSdkVersionString()); + playerInfo.put(ConvivaSdkConstants.FRAMEWORK_VERSION, Player.getSdkVersion()); convivaVideoAnalytics.setPlayerInfo(playerInfo); convivaAdAnalytics.setAdPlayerInfo(playerInfo); } private void internalInitializeSession() { - if (isSessionActive) { - return; - } + if (isSessionActive) return; + Log.d(TAG, "internalInitializeSession"); createContentMetadata(); + convivaVideoAnalytics.reportPlaybackRequested(contentMetadataBuilder.build()); setupPlayerStateManager(); if (metadataOverrides != null) { @@ -347,16 +355,18 @@ private void internalInitializeSession() { } private void updateSession() { - updatePlaybackVideoData(); - buildDynamicContentMetadata(); + if (playerAdapter != null) { + updatePlaybackVideoData(playerAdapter); + buildDynamicContentMetadata(playerAdapter); + } if (isSessionActive) { convivaVideoAnalytics.setContentInfo(contentMetadataBuilder.build()); } } - private void updatePlaybackVideoData() { - HashMap playbackVideoData = playbackInfoProvider.getPlaybackVideoData(); + private void updatePlaybackVideoData(@NonNull PlayerAdapter playerAdapter) { + HashMap playbackVideoData = playerAdapter.getPlaybackVideoData(); for (Map.Entry entry : playbackVideoData.entrySet()) { convivaVideoAnalytics.reportPlaybackMetric(entry.getKey(), entry.getValue()); if (ssai.isAdBreakActive()) { @@ -366,33 +376,39 @@ private void updatePlaybackVideoData() { } private void createContentMetadata() { - Source source = bitmovinPlayer.getSource(); - if (source != null) { - SourceConfig sourceConfig = source.getConfig(); - String overriddenAssetName = metadataOverrides != null ? metadataOverrides.getAssetName() : null; + String overriddenAssetName = metadataOverrides != null ? metadataOverrides.getAssetName() : null; + if (overriddenAssetName != null) { + contentMetadataBuilder.setAssetName(overriddenAssetName); + } else { + if (playerAdapter != null && playerAdapter.getStreamTitle() != null) { + contentMetadataBuilder.setAssetName(playerAdapter.getStreamTitle()); + } else { + Log.w(TAG, "No asset name provided for content metadata."); + } + } - contentMetadataBuilder.setAssetName(overriddenAssetName != null ? overriddenAssetName : sourceConfig.getTitle()); + if (playerAdapter != null) { + buildDynamicContentMetadata(playerAdapter); } - this.buildDynamicContentMetadata(); } - private void buildDynamicContentMetadata() { + private void buildDynamicContentMetadata(PlayerAdapter playerAdapter) { // Build custom tags here, though this is static metadata but // streamType could be missing at time of session initialization // as source information could be unavailable at that time Map customInternTags = new HashMap<>(); - customInternTags.put(STREAM_TYPE, playerHelper.getStreamType()); + customInternTags.put(STREAM_TYPE, playerAdapter.getStreamType()); customInternTags.put(INTEGRATION_VERSION, BuildConfig.VERSION_NAME); contentMetadataBuilder.setCustom(customInternTags); - if (bitmovinPlayer.isLive()) { + if (playerAdapter.isLive()) { contentMetadataBuilder.setStreamType(ConvivaSdkConstants.StreamType.LIVE); } else { contentMetadataBuilder.setStreamType(ConvivaSdkConstants.StreamType.VOD); - contentMetadataBuilder.setDuration((int) bitmovinPlayer.getDuration()); + contentMetadataBuilder.setDuration(((int) playerAdapter.getDuration())); } - contentMetadataBuilder.setStreamUrl(playerHelper.getStreamUrl()); + contentMetadataBuilder.setStreamUrl(playerAdapter.getStreamUrl()); } private void internalEndSession() { @@ -407,81 +423,84 @@ private void internalEndSession() { } // endregion - private void attachBitmovinEventListeners() { - bitmovinPlayer.on(SourceEvent.Unloaded.class, onSourceUnloadedListener); - bitmovinPlayer.on(PlayerEvent.Error.class, onPlayerErrorListener); - bitmovinPlayer.on(SourceEvent.Error.class, onSourceErrorListener); - bitmovinPlayer.on(PlayerEvent.Warning.class, onPlayerWarningListener); - bitmovinPlayer.on(SourceEvent.Warning.class, onSourceWarningListener); - - bitmovinPlayer.on(PlayerEvent.Muted.class, onMutedListener); - bitmovinPlayer.on(PlayerEvent.Unmuted.class, onUnmutedListener); - - // Playback state events - bitmovinPlayer.on(PlayerEvent.Play.class, onPlayListener); - bitmovinPlayer.on(PlayerEvent.Playing.class, onPlayingListener); - bitmovinPlayer.on(PlayerEvent.Paused.class, onPausedListener); - bitmovinPlayer.on(PlayerEvent.StallEnded.class, onStallEndedListener); - bitmovinPlayer.on(PlayerEvent.StallStarted.class, onStallStartedListener); - bitmovinPlayer.on(PlayerEvent.PlaybackFinished.class, onPlaybackFinishedListener); - - // Seek events - bitmovinPlayer.on(PlayerEvent.Seeked.class, onSeekedListener); - bitmovinPlayer.on(PlayerEvent.Seek.class, onSeekListener); - - // Timeshift events - bitmovinPlayer.on(PlayerEvent.TimeShift.class, onTimeShiftListener); - bitmovinPlayer.on(PlayerEvent.TimeShifted.class, onTimeShiftedListener); - - // Ad events - bitmovinPlayer.on(PlayerEvent.AdBreakStarted.class, onAdBreakStarted); - bitmovinPlayer.on(PlayerEvent.AdBreakFinished.class, onAdBreakFinished); - bitmovinPlayer.on(PlayerEvent.AdStarted.class, onAdStartedListener); - bitmovinPlayer.on(PlayerEvent.AdFinished.class, onAdFinishedListener); - bitmovinPlayer.on(PlayerEvent.AdSkipped.class, onAdSkippedListener); - bitmovinPlayer.on(PlayerEvent.AdError.class, onAdErrorListener); - bitmovinPlayer.on(PlayerEvent.TimeChanged.class, onTimeChangedListener); - - bitmovinPlayer.on(PlayerEvent.VideoPlaybackQualityChanged.class, onVideoPlaybackQualityChangedListener); - } - - private void detachBitmovinEventListeners() { - bitmovinPlayer.off(SourceEvent.Unloaded.class, onSourceUnloadedListener); - bitmovinPlayer.off(PlayerEvent.Error.class, onPlayerErrorListener); - bitmovinPlayer.off(SourceEvent.Error.class, onSourceErrorListener); - bitmovinPlayer.off(PlayerEvent.Warning.class, onPlayerWarningListener); - bitmovinPlayer.off(SourceEvent.Warning.class, onSourceWarningListener); - - bitmovinPlayer.off(PlayerEvent.Muted.class, onMutedListener); - bitmovinPlayer.off(PlayerEvent.Unmuted.class, onUnmutedListener); - - // Playback state events - bitmovinPlayer.off(PlayerEvent.Play.class, onPlayListener); - bitmovinPlayer.off(PlayerEvent.Playing.class, onPlayingListener); - bitmovinPlayer.off(PlayerEvent.Paused.class, onPausedListener); - bitmovinPlayer.off(PlayerEvent.StallEnded.class, onStallEndedListener); - bitmovinPlayer.off(PlayerEvent.StallStarted.class, onStallStartedListener); - bitmovinPlayer.off(PlayerEvent.PlaybackFinished.class, onPlaybackFinishedListener); - - // Seek events - bitmovinPlayer.off(PlayerEvent.Seeked.class, onSeekedListener); - bitmovinPlayer.off(PlayerEvent.Seek.class, onSeekListener); - - // Timeshift events - bitmovinPlayer.off(PlayerEvent.TimeShift.class, onTimeShiftListener); - bitmovinPlayer.off(PlayerEvent.TimeShifted.class, onTimeShiftedListener); - - // Ad events - bitmovinPlayer.off(PlayerEvent.AdBreakStarted.class, onAdBreakStarted); - bitmovinPlayer.off(PlayerEvent.AdBreakFinished.class, onAdBreakFinished); - bitmovinPlayer.off(PlayerEvent.AdStarted.class, onAdStartedListener); - bitmovinPlayer.off(PlayerEvent.AdFinished.class, onAdFinishedListener); - bitmovinPlayer.off(PlayerEvent.AdSkipped.class, onAdSkippedListener); - bitmovinPlayer.off(PlayerEvent.AdError.class, onAdErrorListener); - bitmovinPlayer.off(PlayerEvent.TimeChanged.class, onTimeChangedListener); - - bitmovinPlayer.off(PlayerEvent.VideoPlaybackQualityChanged.class, - onVideoPlaybackQualityChangedListener); + private void attachBitmovinEventListeners(PlayerAdapter playerAdapter) { + playerAdapter.withEventEmitter(eventEmitter -> { + eventEmitter.on(SourceEvent.Unloaded.class, onSourceUnloadedListener); + eventEmitter.on(PlayerEvent.Error.class, onPlayerErrorListener); + eventEmitter.on(SourceEvent.Error.class, onSourceErrorListener); + eventEmitter.on(PlayerEvent.Warning.class, onPlayerWarningListener); + eventEmitter.on(SourceEvent.Warning.class, onSourceWarningListener); + + eventEmitter.on(PlayerEvent.Muted.class, onMutedListener); + eventEmitter.on(PlayerEvent.Unmuted.class, onUnmutedListener); + + // Playback state events + eventEmitter.on(PlayerEvent.Play.class, onPlayListener); + eventEmitter.on(PlayerEvent.Playing.class, onPlayingListener); + eventEmitter.on(PlayerEvent.Paused.class, onPausedListener); + eventEmitter.on(PlayerEvent.StallEnded.class, onStallEndedListener); + eventEmitter.on(PlayerEvent.StallStarted.class, onStallStartedListener); + eventEmitter.on(PlayerEvent.PlaybackFinished.class, onPlaybackFinishedListener); + + // Seek events + eventEmitter.on(PlayerEvent.Seeked.class, onSeekedListener); + eventEmitter.on(PlayerEvent.Seek.class, onSeekListener); + + // Time shift events + eventEmitter.on(PlayerEvent.TimeShift.class, onTimeShiftListener); + eventEmitter.on(PlayerEvent.TimeShifted.class, onTimeShiftedListener); + + // Ad events + eventEmitter.on(PlayerEvent.AdBreakStarted.class, onAdBreakStarted); + eventEmitter.on(PlayerEvent.AdBreakFinished.class, onAdBreakFinished); + eventEmitter.on(PlayerEvent.AdStarted.class, onAdStartedListener); + eventEmitter.on(PlayerEvent.AdFinished.class, onAdFinishedListener); + eventEmitter.on(PlayerEvent.AdSkipped.class, onAdSkippedListener); + eventEmitter.on(PlayerEvent.AdError.class, onAdErrorListener); + eventEmitter.on(PlayerEvent.TimeChanged.class, onTimeChangedListener); + + eventEmitter.on(PlayerEvent.VideoPlaybackQualityChanged.class, onVideoPlaybackQualityChangedListener); + }); + } + + private void detachBitmovinEventListeners(PlayerAdapter playerAdapter) { + playerAdapter.withEventEmitter(bitmovinPlayer -> { + bitmovinPlayer.off(SourceEvent.Unloaded.class, onSourceUnloadedListener); + bitmovinPlayer.off(PlayerEvent.Error.class, onPlayerErrorListener); + bitmovinPlayer.off(SourceEvent.Error.class, onSourceErrorListener); + bitmovinPlayer.off(PlayerEvent.Warning.class, onPlayerWarningListener); + bitmovinPlayer.off(SourceEvent.Warning.class, onSourceWarningListener); + + bitmovinPlayer.off(PlayerEvent.Muted.class, onMutedListener); + bitmovinPlayer.off(PlayerEvent.Unmuted.class, onUnmutedListener); + + // Playback state events + bitmovinPlayer.off(PlayerEvent.Play.class, onPlayListener); + bitmovinPlayer.off(PlayerEvent.Playing.class, onPlayingListener); + bitmovinPlayer.off(PlayerEvent.Paused.class, onPausedListener); + bitmovinPlayer.off(PlayerEvent.StallEnded.class, onStallEndedListener); + bitmovinPlayer.off(PlayerEvent.StallStarted.class, onStallStartedListener); + bitmovinPlayer.off(PlayerEvent.PlaybackFinished.class, onPlaybackFinishedListener); + + // Seek events + bitmovinPlayer.off(PlayerEvent.Seeked.class, onSeekedListener); + bitmovinPlayer.off(PlayerEvent.Seek.class, onSeekListener); + + // Timeshift events + bitmovinPlayer.off(PlayerEvent.TimeShift.class, onTimeShiftListener); + bitmovinPlayer.off(PlayerEvent.TimeShifted.class, onTimeShiftedListener); + + // Ad events + bitmovinPlayer.off(PlayerEvent.AdBreakStarted.class, onAdBreakStarted); + bitmovinPlayer.off(PlayerEvent.AdBreakFinished.class, onAdBreakFinished); + bitmovinPlayer.off(PlayerEvent.AdStarted.class, onAdStartedListener); + bitmovinPlayer.off(PlayerEvent.AdFinished.class, onAdFinishedListener); + bitmovinPlayer.off(PlayerEvent.AdSkipped.class, onAdSkippedListener); + bitmovinPlayer.off(PlayerEvent.AdError.class, onAdErrorListener); + bitmovinPlayer.off(PlayerEvent.TimeChanged.class, onTimeChangedListener); + + bitmovinPlayer.off(PlayerEvent.VideoPlaybackQualityChanged.class, onVideoPlaybackQualityChangedListener); + }); } private synchronized void transitionState(ConvivaSdkConstants.PlayerState state) { @@ -513,12 +532,9 @@ public void onEvent(PlayerEvent.Error event) { } }; - private final EventListener onSourceErrorListener = new EventListener() { - @Override - public void onEvent(SourceEvent.Error event) { - Log.d(TAG, "[Source Event] Error"); - handleError(String.format("%s - %s", event.getCode(), event.getMessage())); - } + private final EventListener onSourceErrorListener = event -> { + Log.d(TAG, "[Source Event] Error"); + handleError(String.format("%s - %s", event.getCode(), event.getMessage())); }; private void handleError(String message) { @@ -564,7 +580,6 @@ public void onEvent(SourceEvent.Warning warningEvent) { ensureConvivaSessionIsCreatedAndInitialized(); updateSession(); }; - private final EventListener onPlayingListener = playingEvent -> { Log.d(TAG, "[Player Event] Playing"); contentMetadataBuilder.setPlaybackStarted(true); @@ -605,7 +620,7 @@ public void onEvent(PlayerEvent.StallEnded stallEndedEvent) { new Handler().postDelayed(() -> { Log.d(TAG, "[Player Event] StallEnded"); ConvivaSdkConstants.PlayerState state = ConvivaSdkConstants.PlayerState.PLAYING; - if (bitmovinPlayer.isPaused()) { + if (playerAdapter.isPaused()) { state = ConvivaSdkConstants.PlayerState.PAUSED; } transitionState(state); @@ -651,7 +666,7 @@ public void setSeekEnd() { // Notify of seek buffering complete at this stage. Log.d(TAG, "[Player Event] Update state after buffering"); ConvivaSdkConstants.PlayerState state = ConvivaSdkConstants.PlayerState.PAUSED; - if (bitmovinPlayer.isPlaying()) { + if (playerAdapter != null && playerAdapter.isPlaying()) { state = ConvivaSdkConstants.PlayerState.PLAYING; } transitionState(state); @@ -722,7 +737,7 @@ private Map adStartedToAdInfo(PlayerEvent.AdStarted adStartedEve adInfo.put(ConvivaSdkConstants.FRAMEWORK_VERSION, imaSdkVersion); } else { adInfo.put(ConvivaSdkConstants.FRAMEWORK_NAME, "Bitmovin"); - adInfo.put(ConvivaSdkConstants.FRAMEWORK_VERSION, playerHelper.getSdkVersionString()); + adInfo.put(ConvivaSdkConstants.FRAMEWORK_VERSION, Player.getSdkVersion()); } adInfo.put("c3.ad.position", getAdPosition(adStartedEvent.getTimeOffset())); adInfo.put(ConvivaSdkConstants.DURATION, adStartedEvent.getDuration()); @@ -775,7 +790,7 @@ private ConvivaSdkConstants.AdPosition getAdPosition(double timeOffset) { ConvivaSdkConstants.AdPosition adPosition = ConvivaSdkConstants.AdPosition.MIDROLL; if (timeOffset == 0.0) { adPosition = ConvivaSdkConstants.AdPosition.PREROLL; - } else if (timeOffset == bitmovinPlayer.getDuration()) { + } else if (playerAdapter != null && timeOffset == playerAdapter.getDuration()) { adPosition = ConvivaSdkConstants.AdPosition.POSTROLL; } return adPosition; @@ -815,24 +830,24 @@ public void onEvent(PlayerEvent.AdError adError) { private final EventListener onTimeChangedListener = new EventListener() { @Override public void onEvent(PlayerEvent.TimeChanged timeChangedEvent) { - if (bitmovinPlayer.isLive()) { - double playerTimeshiftMax = bitmovinPlayer.getMaxTimeShift(); - double playerTimeshift = bitmovinPlayer.getTimeShift(); - long playerDurationMs = -(Math.round(playerTimeshiftMax * 1000)); - long playerPositionMs = playerDurationMs - -(Math.round(playerTimeshift * 1000)); - reportPlayHeadTime(playerPositionMs); - } else { - double currentTime = bitmovinPlayer.getCurrentTime(); - long playerDurationMs = (long) (currentTime * 1000); - reportPlayHeadTime(playerDurationMs); + if (isSessionActive) { + convivaVideoAnalytics.reportPlaybackMetric(ConvivaSdkConstants.PLAYBACK.PLAY_HEAD_TIME, playerAdapter.getPlayHeadTimeMillis()); } } }; - private void reportPlayHeadTime(long playerDurationMs) { - if (isSessionActive) { - convivaVideoAnalytics.reportPlaybackMetric(ConvivaSdkConstants.PLAYBACK.PLAY_HEAD_TIME, playerDurationMs); + /** + * Late initialize the player when it is not present at the time of the integration creation. + * Must be called before the source is loaded into the player. + */ + public void setPlayer(@NonNull Player player) { + playerAdapter = new DefaultPlayerAdapter(player); + if (player.getSource() != null) { + Log.w(TAG, "Player already has a source loaded. Please call setPlayer before loading the source."); } + + attachBitmovinEventListeners(playerAdapter); + ssai.setPlayerAdapter(playerAdapter); + updateSession(); } - // endregion -} +} \ No newline at end of file diff --git a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultPlaybackInfoProvider.java b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultPlaybackInfoProvider.java deleted file mode 100644 index 0a43d83..0000000 --- a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultPlaybackInfoProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.bitmovin.analytics.conviva.ssai; - -import com.bitmovin.player.api.Player; -import com.bitmovin.player.api.media.video.quality.VideoQuality; -import com.conviva.sdk.ConvivaSdkConstants; - -import java.util.HashMap; - -public class DefaultPlaybackInfoProvider implements PlaybackInfoProvider { - private final Player player; - - public DefaultPlaybackInfoProvider(Player player) { - this.player = player; - } - - @Override - public ConvivaSdkConstants.PlayerState getPlayerState() { - ConvivaSdkConstants.PlayerState state; - if (player.isPaused()) { - state = ConvivaSdkConstants.PlayerState.PAUSED; - } else if (player.isStalled()) { - state = ConvivaSdkConstants.PlayerState.BUFFERING; - } else { - state = ConvivaSdkConstants.PlayerState.PLAYING; - } - return state; - } - - @Override - public HashMap getPlaybackVideoData() { - HashMap videoData = new HashMap<>(); - VideoQuality playbackVideoData = player.getPlaybackVideoData(); - if (playbackVideoData != null) { - videoData.put(ConvivaSdkConstants.PLAYBACK.RESOLUTION, new Object[]{playbackVideoData.getWidth(), playbackVideoData.getHeight()}); - videoData.put(ConvivaSdkConstants.PLAYBACK.BITRATE, new Object[]{playbackVideoData.getBitrate() / 1000}); - videoData.put(ConvivaSdkConstants.PLAYBACK.RENDERED_FRAMERATE, new Object[]{Math.round(playbackVideoData.getFrameRate())}); - } - return videoData; - } -} diff --git a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultPlayerAdapter.java b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultPlayerAdapter.java new file mode 100644 index 0000000..7cd4fe7 --- /dev/null +++ b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultPlayerAdapter.java @@ -0,0 +1,100 @@ +package com.bitmovin.analytics.conviva.ssai; + +import com.bitmovin.player.api.Player; +import com.bitmovin.player.api.media.video.quality.VideoQuality; +import com.conviva.sdk.ConvivaSdkConstants; + +import java.util.HashMap; + +public class DefaultPlayerAdapter implements PlayerAdapter { + private final Player player; + + public DefaultPlayerAdapter(Player player) { + this.player = player; + } + + @Override + public ConvivaSdkConstants.PlayerState getPlayerState() { + ConvivaSdkConstants.PlayerState state; + if (player.isPaused()) { + state = ConvivaSdkConstants.PlayerState.PAUSED; + } else if (player.isStalled()) { + state = ConvivaSdkConstants.PlayerState.BUFFERING; + } else { + state = ConvivaSdkConstants.PlayerState.PLAYING; + } + return state; + } + + @Override + public HashMap getPlaybackVideoData() { + HashMap videoData = new HashMap<>(); + VideoQuality playbackVideoData = player.getPlaybackVideoData(); + if (playbackVideoData != null) { + videoData.put(ConvivaSdkConstants.PLAYBACK.RESOLUTION, new Object[]{playbackVideoData.getWidth(), playbackVideoData.getHeight()}); + videoData.put(ConvivaSdkConstants.PLAYBACK.BITRATE, new Object[]{playbackVideoData.getBitrate() / 1000}); + videoData.put(ConvivaSdkConstants.PLAYBACK.RENDERED_FRAMERATE, new Object[]{Math.round(playbackVideoData.getFrameRate())}); + } + return videoData; + } + + @Override + public String getStreamTitle() { + return player.getSource() == null ? null : player.getSource().getConfig().getTitle(); + } + + @Override + public String getStreamType() { + return player.getSource() == null ? null : player.getSource().getConfig().getType().name(); + } + + @Override + public String getStreamUrl() { + return player.getSource() == null ? null : player.getSource().getConfig().getUrl(); + } + + + @Override + public boolean isAd() { + return player.isAd(); + } + + @Override + public boolean isLive() { + return player.isLive(); + } + + @Override + public boolean isPaused() { + return player.isPaused(); + } + + @Override + public boolean isPlaying() { + return player.isPlaying(); + } + + @Override + public double getDuration() { + return player.getDuration(); + } + + @Override + public long getPlayHeadTimeMillis() { + if (player.isLive()) { + double playerTimeShiftMax = player.getMaxTimeShift(); + double playerTimeShift = player.getTimeShift(); + long playerDurationMs = -(Math.round(playerTimeShiftMax * 1000)); + return playerDurationMs - -(Math.round(playerTimeShift * 1000)); + + } else { + double currentTime = player.getCurrentTime(); + return (long) (currentTime * 1000); + } + } + + @Override + public void withEventEmitter(WithEventEmitter withEventEmitter) { + withEventEmitter.call(player); + } +} diff --git a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultSsaiApi.java b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultSsaiApi.java index b43b0bd..861406c 100644 --- a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultSsaiApi.java +++ b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/DefaultSsaiApi.java @@ -2,6 +2,9 @@ import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.bitmovin.analytics.conviva.ConvivaAnalyticsIntegration; import com.conviva.sdk.ConvivaAdAnalytics; import com.conviva.sdk.ConvivaSdkConstants; @@ -15,12 +18,19 @@ public class DefaultSsaiApi implements SsaiApi { private static final String TAG = "DefaultSsaiApi"; private final ConvivaVideoAnalytics convivaVideoAnalytics; private final ConvivaAdAnalytics convivaAdAnalytics; - private final PlaybackInfoProvider player; + @Nullable + private PlayerAdapter playerAdapter; - public DefaultSsaiApi(ConvivaVideoAnalytics convivaVideoAnalytics, ConvivaAdAnalytics convivaAdAnalytics, PlaybackInfoProvider player) { + public DefaultSsaiApi( + ConvivaVideoAnalytics convivaVideoAnalytics, + ConvivaAdAnalytics convivaAdAnalytics + ) { this.convivaVideoAnalytics = convivaVideoAnalytics; this.convivaAdAnalytics = convivaAdAnalytics; - this.player = player; + } + + public void setPlayerAdapter(@NonNull PlayerAdapter playerAdapter) { + this.playerAdapter = playerAdapter; } private boolean isAdBreakActive = false; @@ -70,14 +80,18 @@ public void reportAdStarted(AdInfo adInfo) { Log.d(TAG, "No server side ad break active"); return; } + if (playerAdapter == null) { + Log.d(TAG, "Player not yet set. Cannot report ad started."); + return; + } Log.d(TAG, "Server side ad started"); convivaAdAnalytics.reportAdStarted(convertToConvivaAdInfo(adInfo, convivaVideoAnalytics.getMetadataInfo())); - reportInitialAdMetrics(); + reportInitialAdMetrics(playerAdapter); } - private void reportInitialAdMetrics() { - convivaAdAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.PLAYER_STATE, player.getPlayerState()); - HashMap playbackVideoData = player.getPlaybackVideoData(); + private void reportInitialAdMetrics(@NonNull PlayerAdapter playerAdapter) { + convivaAdAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.PLAYER_STATE, playerAdapter.getPlayerState()); + HashMap playbackVideoData = playerAdapter.getPlaybackVideoData(); for (Map.Entry entry : playbackVideoData.entrySet()) { convivaAdAnalytics.reportAdMetric(entry.getKey(), entry.getValue()); } diff --git a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/PlaybackInfoProvider.java b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/PlaybackInfoProvider.java deleted file mode 100644 index 43ddd39..0000000 --- a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/PlaybackInfoProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.bitmovin.analytics.conviva.ssai; - -import com.conviva.sdk.ConvivaSdkConstants; - -import java.util.HashMap; - -public interface PlaybackInfoProvider { - ConvivaSdkConstants.PlayerState getPlayerState(); - - HashMap getPlaybackVideoData(); -} diff --git a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/PlayerAdapter.java b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/PlayerAdapter.java new file mode 100644 index 0000000..9a74f21 --- /dev/null +++ b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/PlayerAdapter.java @@ -0,0 +1,34 @@ +package com.bitmovin.analytics.conviva.ssai; + +import com.conviva.sdk.ConvivaSdkConstants; + +import java.util.HashMap; + +public interface PlayerAdapter { + ConvivaSdkConstants.PlayerState getPlayerState(); + + HashMap getPlaybackVideoData(); + + boolean isAd(); + + String getStreamTitle(); + + String getStreamType(); + + String getStreamUrl(); + + long getPlayHeadTimeMillis(); + + + boolean isLive(); + + double getDuration(); + + + void withEventEmitter(WithEventEmitter withEventEmitter); + + boolean isPaused(); + + boolean isPlaying(); + +} diff --git a/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/WithEventEmitter.java b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/WithEventEmitter.java new file mode 100644 index 0000000..6db0f61 --- /dev/null +++ b/conviva/src/main/java/com/bitmovin/analytics/conviva/ssai/WithEventEmitter.java @@ -0,0 +1,8 @@ +package com.bitmovin.analytics.conviva.ssai; + +import com.bitmovin.player.api.event.Event; +import com.bitmovin.player.api.event.JavaEventEmitter; + +public interface WithEventEmitter { + void call(JavaEventEmitter player); +} diff --git a/conviva/src/test/kotlin/com/bitmovin/analytics/conviva/ConvivaAnalyticsIntegrationTest.kt b/conviva/src/test/kotlin/com/bitmovin/analytics/conviva/ConvivaAnalyticsIntegrationTest.kt index 2ce3ec1..61bf069 100644 --- a/conviva/src/test/kotlin/com/bitmovin/analytics/conviva/ConvivaAnalyticsIntegrationTest.kt +++ b/conviva/src/test/kotlin/com/bitmovin/analytics/conviva/ConvivaAnalyticsIntegrationTest.kt @@ -49,6 +49,7 @@ class ConvivaAnalyticsIntegrationTest { with(ssaiApi) { every { isAdBreakActive } returns false every { reset() } just runs + every { setPlayerAdapter(any()) } just runs } convivaAnalyticsIntegration = ConvivaAnalyticsIntegration( @@ -136,6 +137,7 @@ class ConvivaAnalyticsIntegrationTest { every { Log.d(any(), any()) } returns 0 every { Log.i(any(), any()) } returns 0 every { Log.e(any(), any()) } returns 0 + every { Log.w(any(), any()) } returns 0 mockkConstructor(Handler::class) every { anyConstructed().postDelayed(any(), any()) } answers { diff --git a/conviva/src/test/kotlin/com/bitmovin/analytics/conviva/ssai/DefaultSsaiApiTest.kt b/conviva/src/test/kotlin/com/bitmovin/analytics/conviva/ssai/DefaultSsaiApiTest.kt index f3c7049..bcfcac6 100644 --- a/conviva/src/test/kotlin/com/bitmovin/analytics/conviva/ssai/DefaultSsaiApiTest.kt +++ b/conviva/src/test/kotlin/com/bitmovin/analytics/conviva/ssai/DefaultSsaiApiTest.kt @@ -29,13 +29,13 @@ import strikt.assertions.isTrue class DefaultSsaiApiTest { private val videoAnalytics: ConvivaVideoAnalytics = mockk(relaxed = true) private val adAnalytics: ConvivaAdAnalytics = mockk() - private val playbackInfoProvider = mockk() + private val playerAdapter = mockk() private lateinit var ssaiApi: DefaultSsaiApi @Before fun beforeTest() { - every { playbackInfoProvider.playerState } returns ConvivaSdkConstants.PlayerState.PLAYING - every { playbackInfoProvider.playbackVideoData } returns hashMapOf>( + every { playerAdapter.playerState } returns ConvivaSdkConstants.PlayerState.PLAYING + every { playerAdapter.playbackVideoData } returns hashMapOf>( ConvivaSdkConstants.PLAYBACK.BITRATE to arrayOf(1), ConvivaSdkConstants.PLAYBACK.RESOLUTION to arrayOf(800, 1600), ConvivaSdkConstants.PLAYBACK.RENDERED_FRAMERATE to arrayOf(60), @@ -54,18 +54,18 @@ class DefaultSsaiApiTest { ssaiApi = DefaultSsaiApi( videoAnalytics, adAnalytics, - playbackInfoProvider, ) + ssaiApi.setPlayerAdapter(playerAdapter) } @After fun afterTest() { - clearMocks(videoAnalytics, adAnalytics, playbackInfoProvider) + clearMocks(videoAnalytics, adAnalytics, playerAdapter) } @Test - fun `reports server side ad break start to conviva after ad break started`() { + fun `reports server side ad break start to conviva after ad break started`() { ssaiApi.reportAdBreakStarted() verify { videoAnalytics.reportAdBreakStarted( @@ -131,7 +131,7 @@ class DefaultSsaiApiTest { @Test fun `reports ad playback state playing to conviva when ad starts while paused`() { - every { playbackInfoProvider.playerState } returns ConvivaSdkConstants.PlayerState.PAUSED + every { playerAdapter.playerState } returns ConvivaSdkConstants.PlayerState.PAUSED ssaiApi.reportAdBreakStarted() ssaiApi.reportAdStarted(SsaiApi.AdInfo()) @@ -146,7 +146,7 @@ class DefaultSsaiApiTest { @Test fun `reports ad playback state buffering to conviva when ad starts while stalling`() { - every { playbackInfoProvider.playerState } returns + every { playerAdapter.playerState } returns ConvivaSdkConstants.PlayerState.BUFFERING ssaiApi.reportAdBreakStarted() @@ -205,7 +205,7 @@ class DefaultSsaiApiTest { adAnalytics.reportAdStarted(capture(slot)) } - expectThat(slot){ + expectThat(slot) { get { captured[ConvivaSdkConstants.ASSET_NAME] }.isEqualTo("overwrittenTitle") get { captured["streamType"] }.isEqualTo("mainContentStreamType") } @@ -294,6 +294,7 @@ class DefaultSsaiApiTest { every { Log.d(any(), any()) } returns 0 every { Log.i(any(), any()) } returns 0 every { Log.e(any(), any()) } returns 0 + every { Log.w(any(), any()) } returns 0 } @JvmStatic From b6bd68faa5ebfe8cb2a33703d32fd5160e70dfa7 Mon Sep 17 00:00:00 2001 From: David Steinacher Date: Fri, 23 Aug 2024 14:44:19 +0200 Subject: [PATCH 02/25] update sample to test late player attaching --- .../convivaanalyticsexample/MainActivity.java | 143 +++++++++++++----- .../src/main/res/layout/activity_main.xml | 24 ++- .../src/main/res/values/strings.xml | 1 + 3 files changed, 122 insertions(+), 46 deletions(-) diff --git a/ConvivaExampleApp/src/main/java/com/bitmovin/analytics/convivaanalyticsexample/MainActivity.java b/ConvivaExampleApp/src/main/java/com/bitmovin/analytics/convivaanalyticsexample/MainActivity.java index 1e39ce9..0db6ea4 100644 --- a/ConvivaExampleApp/src/main/java/com/bitmovin/analytics/convivaanalyticsexample/MainActivity.java +++ b/ConvivaExampleApp/src/main/java/com/bitmovin/analytics/convivaanalyticsexample/MainActivity.java @@ -1,14 +1,18 @@ package com.bitmovin.analytics.convivaanalyticsexample; import android.os.Bundle; + +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.LinearLayout; import android.widget.Switch; +import com.bitmovin.analytics.conviva.ConvivaAnalyticsException; import com.bitmovin.analytics.conviva.ConvivaAnalyticsIntegration; import com.bitmovin.analytics.conviva.ConvivaConfig; import com.bitmovin.analytics.conviva.MetadataOverrides; @@ -27,6 +31,8 @@ import java.util.Map; public class MainActivity extends AppCompatActivity implements View.OnClickListener { + private static final String TAG = "Example"; + // UI private Button pauseTrackingButton; private Button resumeTrackingButton; @@ -41,7 +47,9 @@ public class MainActivity extends AppCompatActivity implements View.OnClickListe private ConvivaAnalyticsIntegration convivaAnalyticsIntegration; // Player + @Nullable private Player bitmovinPlayer; + @Nullable private PlayerView bitmovinPlayerView; @Override @@ -54,10 +62,12 @@ protected void onCreate(Bundle savedInstanceState) { resumeTrackingButton.setOnClickListener(this); releaseButton = findViewById(R.id.release_button); releaseButton.setOnClickListener(this); - createButton = findViewById(R.id.create_button); + createButton = findViewById(R.id.create_player_button); createButton.setOnClickListener(this); sendCustomEventButton = findViewById(R.id.custom_event_button); sendCustomEventButton.setOnClickListener(this); + startSessionButton = findViewById(R.id.start_session_button); + startSessionButton.setOnClickListener(this); includeAdsSwitch = findViewById(R.id.include_ads_switch); this.setupBitmovinPlayer(); @@ -73,40 +83,12 @@ protected void setupBitmovinPlayer() { LinearLayout playerUIView = this.findViewById(R.id.bitmovinPlayerUIView); playerUIView.addView(bitmovinPlayerView); - // Create your ConvivaConfig object - ConvivaConfig convivaConfig = new ConvivaConfig(); - - // Set only in debug mode - if (gatewayUrl != null) { - convivaConfig.setGatewayUrl(gatewayUrl); + if (convivaAnalyticsIntegration == null) { + convivaAnalyticsIntegration = setupConvivaAnalytics(bitmovinPlayer); + } else { + convivaAnalyticsIntegration.attachPlayer(bitmovinPlayer); } - // Add optional parameters - convivaConfig.setDebugLoggingEnabled(true); - - // Create ConvivaAnalytics - convivaAnalyticsIntegration = new ConvivaAnalyticsIntegration( - bitmovinPlayer, - customerKey, - getApplicationContext(), - convivaConfig); - - MetadataOverrides metadata = new MetadataOverrides(); - metadata.setApplicationName("Bitmovin Android Conviva integration example app"); - metadata.setViewerId("awesomeViewerId"); - - Map standardTags = new HashMap<>(); - standardTags.put("c3.cm.contentType", "VOD"); - metadata.setAdditionalStandardTags(standardTags); - - Map customTags = new HashMap<>(); - customTags.put("custom_tag", "Episode"); - metadata.setCustom(customTags); - - metadata.setImaSdkVersion("3.31.0"); - - convivaAnalyticsIntegration.updateContentMetadata(metadata); - // load source using the created source configuration bitmovinPlayer.load(buildSourceConfiguration()); } @@ -118,6 +100,8 @@ private PlayerConfig buildPlayerConfiguration() { playerConfiguration.setAdvertisingConfig(buildAdConfiguration()); } + playerConfiguration.getPlaybackConfig().setAutoplayEnabled(true); + return playerConfiguration; } @@ -145,32 +129,113 @@ private AdvertisingConfig buildAdConfiguration() { @Override protected void onResume() { super.onResume(); + if (bitmovinPlayerView != null) { bitmovinPlayerView.onResume(); + } convivaAnalyticsIntegration.reportAppForegrounded(); } @Override protected void onPause() { convivaAnalyticsIntegration.reportAppBackgrounded(); - bitmovinPlayerView.onStop(); + if (bitmovinPlayerView != null) { + bitmovinPlayerView.onStop(); + } super.onPause(); } @Override protected void onDestroy() { - bitmovinPlayerView.onDestroy(); + if (bitmovinPlayerView != null) { + bitmovinPlayerView.onDestroy(); + } convivaAnalyticsIntegration.release(); super.onDestroy(); } private void tearDownPlayer() { convivaAnalyticsIntegration.release(); - ViewGroup parent = (ViewGroup) bitmovinPlayerView.getParent(); + ViewGroup parent = null; + if (bitmovinPlayerView != null) { + parent = (ViewGroup) bitmovinPlayerView.getParent(); + } if (parent != null) { parent.removeView(bitmovinPlayerView); } - bitmovinPlayer.destroy(); - bitmovinPlayerView.onDestroy(); + if (bitmovinPlayer != null) { + bitmovinPlayer.destroy(); + } + if (bitmovinPlayerView != null) { + bitmovinPlayerView.onDestroy(); + } + bitmovinPlayer = null; + } + + private void startSession() { + ConvivaAnalyticsIntegration convivaAnalyticsIntegration = setupConvivaAnalytics(bitmovinPlayer); + convivaAnalyticsIntegration.updateContentMetadata(buildMetadataOverrides("Art of Motion")); + try { + convivaAnalyticsIntegration.initializeSession(); + + this.convivaAnalyticsIntegration = convivaAnalyticsIntegration; + } catch (ConvivaAnalyticsException e) { + Log.d(TAG, "ConvivaAnalytics initialization failed with error: " + e); + } + } + + private ConvivaAnalyticsIntegration setupConvivaAnalytics(@Nullable Player player) { + // Create your ConvivaConfig object + ConvivaConfig convivaConfig = new ConvivaConfig(); + + // Set only in debug mode + if (gatewayUrl != null) { + convivaConfig.setGatewayUrl(gatewayUrl); + } + + ConvivaAnalyticsIntegration convivaAnalyticsIntegration = null; + // Add optional parameters + convivaConfig.setDebugLoggingEnabled(true); + + if (player != null) { + convivaAnalyticsIntegration = new ConvivaAnalyticsIntegration( + player, + customerKey, + getApplicationContext(), + convivaConfig + ); + } else { + convivaAnalyticsIntegration = new ConvivaAnalyticsIntegration( + customerKey, + getApplicationContext(), + convivaConfig + ); + } + + convivaAnalyticsIntegration.updateContentMetadata(buildMetadataOverrides(null)); + + return convivaAnalyticsIntegration; + } + + private MetadataOverrides buildMetadataOverrides(@Nullable String assetName) { + MetadataOverrides metadata = new MetadataOverrides(); + metadata.setApplicationName("Bitmovin Android Conviva integration example app"); + metadata.setViewerId("awesomeViewerId"); + + Map standardTags = new HashMap<>(); + standardTags.put("c3.cm.contentType", "VOD"); + metadata.setAdditionalStandardTags(standardTags); + + Map customTags = new HashMap<>(); + customTags.put("custom_tag", "Episode"); + metadata.setCustom(customTags); + + metadata.setImaSdkVersion("3.31.0"); + + if (assetName != null) { + metadata.setAssetName(assetName); + } + + return metadata; } @Override @@ -187,6 +252,8 @@ public void onClick(View v) { this.convivaAnalyticsIntegration.pauseTracking(false); } else if (v == resumeTrackingButton) { this.convivaAnalyticsIntegration.resumeTracking(); + } else if (v == startSessionButton) { + this.startSession(); } } } diff --git a/ConvivaExampleApp/src/main/res/layout/activity_main.xml b/ConvivaExampleApp/src/main/res/layout/activity_main.xml index 8d1a73a..58de572 100644 --- a/ConvivaExampleApp/src/main/res/layout/activity_main.xml +++ b/ConvivaExampleApp/src/main/res/layout/activity_main.xml @@ -7,6 +7,18 @@ android:keepScreenOn="true" tools:context=".MainActivity"> +