Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report video playback data for SSAI ads #74

Merged
merged 5 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
import android.os.Handler;
import android.util.Log;

import com.bitmovin.analytics.conviva.ssai.DefaultPlaybackStateProvider;
import com.bitmovin.analytics.conviva.ssai.DefaultPlaybackInfoProvider;
import com.bitmovin.analytics.conviva.ssai.DefaultSsaiApi;
import com.bitmovin.analytics.conviva.ssai.PlaybackInfoProvider;
import com.bitmovin.analytics.conviva.ssai.SsaiApi;
import com.bitmovin.player.api.Player;
import com.bitmovin.player.api.advertising.Ad;
Expand All @@ -17,7 +18,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.media.video.quality.VideoQuality;
import com.bitmovin.player.api.source.Source;
import com.bitmovin.player.api.source.SourceConfig;
import com.conviva.sdk.ConvivaAdAnalytics;
Expand All @@ -40,6 +40,7 @@ public class ConvivaAnalyticsIntegration {
private final ContentMetadataBuilder contentMetadataBuilder = new ContentMetadataBuilder();
private final ConvivaVideoAnalytics convivaVideoAnalytics;
private final ConvivaAdAnalytics convivaAdAnalytics;
private final PlaybackInfoProvider playbackInfoProvider;
private MetadataOverrides metadataOverrides;
private final DefaultSsaiApi ssai;

Expand Down Expand Up @@ -116,9 +117,10 @@ public ConvivaAnalyticsIntegration(Player player,
} else {
convivaAdAnalytics = adAnalytics;
}
playbackInfoProvider = new DefaultPlaybackInfoProvider(player);

if (ssai == null) {
this.ssai = new DefaultSsaiApi(convivaVideoAnalytics, convivaAdAnalytics, new DefaultPlaybackStateProvider(player));
this.ssai = new DefaultSsaiApi(convivaVideoAnalytics, convivaAdAnalytics, playbackInfoProvider);
} else {
this.ssai = ssai;
}
Expand Down Expand Up @@ -345,21 +347,24 @@ private void internalInitializeSession() {
}

private void updateSession() {
this.buildDynamicContentMetadata();

VideoQuality videoQuality = bitmovinPlayer.getPlaybackVideoData();
if (videoQuality != null) {
int bitrate = videoQuality.getBitrate() / 1000; // in kbps
convivaVideoAnalytics.reportPlaybackMetric(ConvivaSdkConstants.PLAYBACK.RESOLUTION, videoQuality.getHeight(), videoQuality.getWidth());
convivaVideoAnalytics.reportPlaybackMetric(ConvivaSdkConstants.PLAYBACK.BITRATE, bitrate);
convivaVideoAnalytics.reportPlaybackMetric(ConvivaSdkConstants.PLAYBACK.RENDERED_FRAMERATE, Math.round(videoQuality.getFrameRate()));
}
updatePlaybackVideoData();
buildDynamicContentMetadata();

if (isSessionActive) {
convivaVideoAnalytics.setContentInfo(contentMetadataBuilder.build());
}
}

private void updatePlaybackVideoData() {
HashMap<String, Object[]> playbackVideoData = playbackInfoProvider.getPlaybackVideoData();
for (Map.Entry<String, Object[]> entry : playbackVideoData.entrySet()) {
convivaVideoAnalytics.reportPlaybackMetric(entry.getKey(), entry.getValue());
if (ssai.isAdBreakActive()) {
convivaAdAnalytics.reportAdMetric(entry.getKey(), entry.getValue());
}
}
}

private void createContentMetadata() {
Source source = bitmovinPlayer.getSource();
if (source != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
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<String, Object[]> getPlaybackVideoData() {
HashMap<String, Object[]> 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;
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ public class DefaultSsaiApi implements SsaiApi {
private static final String TAG = "DefaultSsaiApi";
private final ConvivaVideoAnalytics convivaVideoAnalytics;
private final ConvivaAdAnalytics convivaAdAnalytics;
private final PlaybackStateProvider player;
private final PlaybackInfoProvider player;

public DefaultSsaiApi(ConvivaVideoAnalytics convivaVideoAnalytics, ConvivaAdAnalytics convivaAdAnalytics, PlaybackStateProvider player) {
public DefaultSsaiApi(ConvivaVideoAnalytics convivaVideoAnalytics, ConvivaAdAnalytics convivaAdAnalytics, PlaybackInfoProvider player) {
this.convivaVideoAnalytics = convivaVideoAnalytics;
this.convivaAdAnalytics = convivaAdAnalytics;
this.player = player;
Expand Down Expand Up @@ -72,7 +72,15 @@ public void reportAdStarted(AdInfo adInfo) {
}
Log.d(TAG, "Server side ad started");
convivaAdAnalytics.reportAdStarted(convertToConvivaAdInfo(adInfo, convivaVideoAnalytics.getMetadataInfo()));
reportInitialAdMetrics();
}

private void reportInitialAdMetrics() {
convivaAdAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.PLAYER_STATE, player.getPlayerState());
HashMap<String, Object[]> playbackVideoData = player.getPlaybackVideoData();
for (Map.Entry<String, Object[]> entry : playbackVideoData.entrySet()) {
convivaAdAnalytics.reportAdMetric(entry.getKey(), entry.getValue());
rolandkakonyi marked this conversation as resolved.
Show resolved Hide resolved
}
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import com.conviva.sdk.ConvivaSdkConstants;

public interface PlaybackStateProvider {
import java.util.HashMap;

public interface PlaybackInfoProvider {
ConvivaSdkConstants.PlayerState getPlayerState();

HashMap<String, Object[]> getPlaybackVideoData();
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import com.bitmovin.player.api.event.Event
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.media.video.quality.VideoQuality
import com.conviva.sdk.ConvivaAdAnalytics
import com.conviva.sdk.ConvivaSdkConstants
import com.conviva.sdk.ConvivaVideoAnalytics
Expand Down Expand Up @@ -103,6 +104,29 @@ class ConvivaAnalyticsIntegrationTest {
verify { adAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.PLAYER_STATE, ConvivaSdkConstants.PlayerState.PLAYING) }
}

@Test
fun `reports video playback quality changes to ad analytics during an SSAI ad break`() {
every { ssaiApi.isAdBreakActive } returns true
val newVideoQuality = VideoQuality(
id = "id",
label = "label",
bitrate = 1000,
codec = "codec",
frameRate = 10.3F,
width = 400,
height = 300,
)
every { mockedPlayer.playbackVideoData } returns newVideoQuality

player.listeners[PlayerEvent.VideoPlaybackQualityChanged::class]?.forEach { onEvent ->
onEvent(PlayerEvent.VideoPlaybackQualityChanged(null, newVideoQuality))
}

verify { adAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.BITRATE, 1) }
verify { adAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.RESOLUTION, 400, 300) }
verify { adAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.RENDERED_FRAMERATE, 10) }
}

companion object {
@JvmStatic
@BeforeClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,28 +29,38 @@ import strikt.assertions.isTrue
class DefaultSsaiApiTest {
private val videoAnalytics: ConvivaVideoAnalytics = mockk(relaxed = true)
private val adAnalytics: ConvivaAdAnalytics = mockk()
private val playbackStateProvider = mockk<PlaybackStateProvider>()
private val ssaiApi = DefaultSsaiApi(
videoAnalytics,
adAnalytics,
playbackStateProvider,
)
private val playbackInfoProvider = mockk<PlaybackInfoProvider>()
private lateinit var ssaiApi: DefaultSsaiApi

@Before
fun beforeTest() {
every { playbackStateProvider.playerState } returns ConvivaSdkConstants.PlayerState.PLAYING
every { playbackInfoProvider.playerState } returns ConvivaSdkConstants.PlayerState.PLAYING
every { playbackInfoProvider.playbackVideoData } returns hashMapOf<String, Array<Any>>(
ConvivaSdkConstants.PLAYBACK.BITRATE to arrayOf(1),
ConvivaSdkConstants.PLAYBACK.RESOLUTION to arrayOf(800, 1600),
ConvivaSdkConstants.PLAYBACK.RENDERED_FRAMERATE to arrayOf(60),
)
with(adAnalytics) {
every { reportAdMetric(ConvivaSdkConstants.PLAYBACK.PLAYER_STATE, any()) } just runs
every { reportAdMetric(ConvivaSdkConstants.PLAYBACK.RESOLUTION, any(), any()) } just runs
every { reportAdMetric(ConvivaSdkConstants.PLAYBACK.BITRATE, any()) } just runs
every { reportAdMetric(ConvivaSdkConstants.PLAYBACK.RENDERED_FRAMERATE, any()) } just runs
every { reportAdStarted(any()) } just runs
every { reportAdEnded() } just runs
every { reportAdSkipped() } just runs
every { setAdInfo(any()) } just runs
}

ssaiApi = DefaultSsaiApi(
videoAnalytics,
adAnalytics,
playbackInfoProvider,
)
}

@After
fun afterTest() {
clearMocks(videoAnalytics, adAnalytics, playbackStateProvider)
clearMocks(videoAnalytics, adAnalytics, playbackInfoProvider)
}


Expand Down Expand Up @@ -121,7 +131,7 @@ class DefaultSsaiApiTest {

@Test
fun `reports ad playback state playing to conviva when ad starts while paused`() {
every { playbackStateProvider.playerState } returns ConvivaSdkConstants.PlayerState.PAUSED
every { playbackInfoProvider.playerState } returns ConvivaSdkConstants.PlayerState.PAUSED

ssaiApi.reportAdBreakStarted()
ssaiApi.reportAdStarted(SsaiApi.AdInfo())
Expand All @@ -136,7 +146,7 @@ class DefaultSsaiApiTest {

@Test
fun `reports ad playback state buffering to conviva when ad starts while stalling`() {
every { playbackStateProvider.playerState } returns
every { playbackInfoProvider.playerState } returns
ConvivaSdkConstants.PlayerState.BUFFERING

ssaiApi.reportAdBreakStarted()
Expand All @@ -150,6 +160,18 @@ class DefaultSsaiApiTest {
}
}

@Test
fun `reports current playback video quality to conviva on ad start`() {
ssaiApi.reportAdBreakStarted()
ssaiApi.reportAdStarted(SsaiApi.AdInfo())

verify {
adAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.BITRATE, 1)
adAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.RESOLUTION, 800, 1600)
adAnalytics.reportAdMetric(ConvivaSdkConstants.PLAYBACK.RENDERED_FRAMERATE, 60)
}
}

@Test
fun `reports ad start with overwritten metadata when ad starts with additional metadata`() {
ssaiApi.reportAdBreakStarted()
Expand Down