Skip to content

Commit b4264fc

Browse files
authored
Add video analytics
1 parent c733525 commit b4264fc

File tree

17 files changed

+670
-4
lines changed

17 files changed

+670
-4
lines changed

core/src/androidTest/java/com/cloudinary/android/AndroidJobStrategyTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ public void onReschedule(String requestId, ErrorInfo error) {
9595
.dispatch();
9696
Thread.sleep(1000);
9797
MediaManager.get().cancelRequest(requestId);
98-
Thread.sleep(3000);
98+
Thread.sleep(7000);
9999
Assert.assertTrue(success == 0);
100100
Assert.assertTrue(errors == 1);
101101
}

ui/build.gradle

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ dependencies {
4242

4343
implementation 'androidx.appcompat:appcompat:1.3.0'
4444
implementation 'com.google.android.material:material:1.4.0'
45-
4645
testImplementation 'junit:junit:4.12'
4746
androidTestImplementation 'androidx.test:runner:1.4.0'
4847
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package cloudinary.android.ui;
2+
3+
import org.junit.Test;
4+
5+
import java.util.HashMap;
6+
import java.util.Map;
7+
8+
import static org.junit.Assert.assertEquals;
9+
10+
import com.cloudinary.android.cldvideoplayer.analytics.models.EventNames;
11+
import com.cloudinary.android.cldvideoplayer.analytics.models.TrackingType;
12+
import com.cloudinary.android.cldvideoplayer.analytics.models.VideoEventJSONKeys;
13+
import com.cloudinary.android.cldvideoplayer.analytics.models.events.VideoLoadMetadata;
14+
import com.cloudinary.android.cldvideoplayer.analytics.models.events.VideoPauseEvent;
15+
import com.cloudinary.android.cldvideoplayer.analytics.models.events.VideoPlayEvent;
16+
import com.cloudinary.android.cldvideoplayer.analytics.models.events.VideoViewEnd;
17+
import com.cloudinary.android.cldvideoplayer.analytics.models.events.VideoViewStartEvent;
18+
19+
public class VideoEventTests {
20+
21+
@Test
22+
public void testVideoViewStartEventInitialization() {
23+
String videoUrl = "https://www.example.com/video.mp4";
24+
Map<String, String> trackingData = new HashMap<>();
25+
trackingData.put("cloudName", "exampleCloud");
26+
trackingData.put("publicId", "abc123");
27+
Map<String, Object> providedData = new HashMap<>();
28+
providedData.put("key", "value");
29+
30+
VideoViewStartEvent event = new VideoViewStartEvent(TrackingType.AUTO, videoUrl, trackingData, providedData);
31+
32+
assertEquals(event.eventName, EventNames.VIEW_START.getValue());
33+
assertEquals(event.eventDetails.get(VideoEventJSONKeys.TRACKING_TYPE.getValue()), TrackingType.AUTO.getValue());
34+
assertEquals(event.eventDetails.get(VideoEventJSONKeys.VIDEO_URL.getValue()), videoUrl);
35+
36+
Map<String, Object> customerData = (Map<String, Object>) event.eventDetails.get(VideoEventJSONKeys.CUSTOMER_DATA.getValue());
37+
Map<String, Object> videoData = (Map<String, Object>) customerData.get(VideoEventJSONKeys.VIDEO_DATA.getValue());
38+
assertEquals(videoData.get(VideoEventJSONKeys.CLOUD_NAME.getValue()), trackingData.get("cloudName"));
39+
assertEquals(videoData.get(VideoEventJSONKeys.PUBLIC_ID.getValue()), trackingData.get("publicId"));
40+
41+
Map<String, Object> providedDataObject = (Map<String, Object>) customerData.get(VideoEventJSONKeys.PROVIDED_DATA.getValue());
42+
assertEquals(providedDataObject.get("key"), providedData.get("key"));
43+
}
44+
45+
@Test
46+
public void testVideoLoadMetadataEventInitialization() {
47+
int duration = 120;
48+
49+
VideoLoadMetadata event = new VideoLoadMetadata(TrackingType.AUTO, duration, null);
50+
51+
assertEquals(event.eventName, EventNames.LOAD_METADATA.getValue());
52+
assertEquals(event.eventDetails.get(VideoEventJSONKeys.TRACKING_TYPE.getValue()), TrackingType.AUTO.getValue());
53+
assertEquals(event.eventDetails.get(VideoEventJSONKeys.VIDEO_DURATION.getValue()), duration);
54+
}
55+
56+
@Test
57+
public void testVideoViewEndEventInitialization() {
58+
Map<String, Object> providedData = new HashMap<>();
59+
providedData.put("key", "value");
60+
61+
VideoViewEnd event = new VideoViewEnd(TrackingType.AUTO, providedData);
62+
63+
assertEquals(event.eventName, EventNames.VIEW_END.getValue());
64+
assertEquals(event.eventDetails.get(VideoEventJSONKeys.TRACKING_TYPE.getValue()), TrackingType.AUTO.getValue());
65+
Map<String, Object> customerData = (Map<String, Object>) event.eventDetails.get(VideoEventJSONKeys.CUSTOMER_DATA.getValue());
66+
assertEquals(customerData.get(VideoEventJSONKeys.PROVIDED_DATA.getValue()), providedData);
67+
}
68+
69+
@Test
70+
public void testVideoPlayEventInitialization() {
71+
Map<String, Object> providedData = new HashMap<>();
72+
providedData.put("key", "value");
73+
74+
VideoPlayEvent event = new VideoPlayEvent(TrackingType.AUTO, providedData);
75+
76+
assertEquals(event.eventName, EventNames.PLAY.getValue());
77+
Map<String, Object> customerData = (Map<String, Object>) event.eventDetails.get(VideoEventJSONKeys.CUSTOMER_DATA.getValue());
78+
assertEquals(customerData.get(VideoEventJSONKeys.PROVIDED_DATA.getValue()), providedData);
79+
}
80+
81+
@Test
82+
public void testVideoPauseEventInitialization() {
83+
Map<String, Object> providedData = new HashMap<>();
84+
providedData.put("key", "value");
85+
86+
VideoPauseEvent event = new VideoPauseEvent(TrackingType.AUTO, providedData);
87+
88+
assertEquals(event.eventName, EventNames.PAUSE.getValue());
89+
Map<String, Object> customerData = (Map<String, Object>) event.eventDetails.get(VideoEventJSONKeys.CUSTOMER_DATA.getValue());
90+
assertEquals(customerData.get(VideoEventJSONKeys.PROVIDED_DATA.getValue()), providedData);
91+
}
92+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package cloudinary.android.ui;
2+
3+
4+
import static org.junit.Assert.assertEquals;
5+
6+
import android.content.Context;
7+
8+
import androidx.test.platform.app.InstrumentationRegistry;
9+
import androidx.test.runner.AndroidJUnit4;
10+
11+
import com.cloudinary.android.MediaManager;
12+
import com.cloudinary.android.cldvideoplayer.analytics.VideoEventsManager;
13+
import com.cloudinary.android.cldvideoplayer.analytics.models.TrackingType;
14+
import com.cloudinary.android.cldvideoplayer.analytics.models.events.VideoViewStartEvent;
15+
16+
import org.junit.BeforeClass;
17+
import org.junit.Test;
18+
import org.junit.runner.RunWith;
19+
import java.util.HashMap;
20+
import java.util.Map;
21+
22+
@RunWith(AndroidJUnit4.class)
23+
public class VideoEventsManagerTest {
24+
25+
private static VideoEventsManager videoEventsManager;
26+
27+
@BeforeClass
28+
public static void setUp() {
29+
Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
30+
if(MediaManager.get() == null) {
31+
MediaManager.init(context);
32+
}
33+
videoEventsManager = new VideoEventsManager(context);
34+
videoEventsManager.cloudName = "TestCloudName";
35+
videoEventsManager.publicId = "TestPublicId";
36+
}
37+
38+
@Test
39+
public void getUserId_NewUserIdGenerated() {
40+
String userId = videoEventsManager.getUserId();
41+
// Verify that a new user ID is generated and stored in SharedPreferences
42+
assertEquals(32, userId.length()); // Assuming user ID length is 32 characters
43+
}
44+
45+
@Test
46+
public void sendViewStartEvent_EventAddedToQueue() {
47+
// Create mock data
48+
String videoUrl = "https://example.com/video.mp4";
49+
Map<String, Object> providedData = new HashMap<>();
50+
providedData.put("key", "value");
51+
52+
// Trigger the method
53+
videoEventsManager.sendViewStartEvent(videoUrl, providedData);
54+
55+
// Verify that the event is added to the queue
56+
assertEquals(1, videoEventsManager.eventQueue.size());
57+
}
58+
59+
@Test
60+
public void sendEvents_WithNonEmptyQueue_SendsEventsAndClearsQueue() {
61+
// Populate the eventQueue with some mock events
62+
VideoViewStartEvent mockEvent1 = new VideoViewStartEvent(TrackingType.AUTO, "videoUrl", new HashMap<>(), new HashMap<>());
63+
VideoViewStartEvent mockEvent2 = new VideoViewStartEvent(TrackingType.MANUAL, "videoUrl2", new HashMap<>(), new HashMap<>());
64+
videoEventsManager.eventQueue.add(mockEvent1);
65+
videoEventsManager.eventQueue.add(mockEvent2);
66+
67+
int initialQueueSize = videoEventsManager.eventQueue.size();
68+
69+
videoEventsManager.sendEvents();
70+
71+
assertEquals(0, videoEventsManager.eventQueue.size());
72+
}
73+
}

ui/src/main/java/com/cloudinary/android/cldvideoplayer/CldVideoPlayer.java

Lines changed: 68 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
package com.cloudinary.android.cldvideoplayer;
22

33
import android.content.Context;
4-
import android.util.Log;
54

65
import androidx.media3.common.MediaItem;
6+
import androidx.media3.common.Player;
77
import androidx.media3.exoplayer.ExoPlayer;
88

9-
import com.cloudinary.Cloudinary;
109
import com.cloudinary.Transformation;
1110
import com.cloudinary.android.MediaManager;
11+
import com.cloudinary.android.cldvideoplayer.analytics.VideoEventsManager;
12+
import com.cloudinary.android.cldvideoplayer.analytics.models.AnalyticsType;
13+
import com.cloudinary.android.cldvideoplayer.analytics.models.TrackingType;
1214

1315
import java.net.URL;
1416

@@ -19,6 +21,11 @@ public class CldVideoPlayer {
1921

2022
String url;
2123

24+
VideoEventsManager videoEventsManager;
25+
26+
boolean viewStartSent = false;
27+
private boolean analytics = true;
28+
2229
public CldVideoPlayer(Context context, URL url) {
2330
this.url = url.toString();
2431
initPlayer(context, this.url);
@@ -48,17 +55,76 @@ private void initiliaze(Context context, String publicId, Transformation transfo
4855
}
4956

5057
private void initPlayer(Context context, String url) {
58+
videoEventsManager = new VideoEventsManager(context);
5159
player = new ExoPlayer.Builder(context).build();
5260
player.setMediaItem(MediaItem.fromUri(url));
61+
setListeners();
5362
player.prepare();
5463
}
5564

65+
private void setListeners() {
66+
player.addListener(new Player.Listener() {
67+
@Override
68+
public void onPlaybackStateChanged(@Player.State int state) {
69+
Player.Listener.super.onPlaybackStateChanged(state);
70+
if (state == Player.STATE_READY && !viewStartSent && analytics) {
71+
viewStartSent = true;
72+
videoEventsManager.sendViewStartEvent(url, null);
73+
int duration = (int) player.getDuration() / 1000;
74+
videoEventsManager.sendLoadMetadataEvent(duration, null);
75+
}
76+
}
77+
@Override
78+
public void onIsPlayingChanged(boolean isPlaying) {
79+
Player.Listener.super.onIsPlayingChanged(isPlaying);
80+
if(analytics) {
81+
if (isPlaying) {
82+
videoEventsManager.sendPlayEvent(null);
83+
} else {
84+
videoEventsManager.sendPauseEvent(null);
85+
}
86+
}
87+
}
88+
});
89+
}
90+
91+
public void releasePlayer() {
92+
if (player != null) {
93+
player.release();
94+
player = null;
95+
if(analytics) {
96+
videoEventsManager.sendViewEndEvent(null);
97+
videoEventsManager.sendEvents();
98+
}
99+
}
100+
}
101+
56102
public void play() {
57103
if (player != null) {
58104
player.play();
59105
}
60106
}
61107

108+
public void setAnalytics(AnalyticsType type, String cloudName, String publicId) {
109+
switch (type) {
110+
case AUTO:
111+
analytics = true;
112+
videoEventsManager.trackingType = TrackingType.AUTO;
113+
videoEventsManager.cloudName = (cloudName != null) ? cloudName : MediaManager.get().getCloudinary().config.cloudName;
114+
videoEventsManager.publicId = (publicId != null) ? cloudName : "";
115+
break;
116+
case MANUAL:
117+
analytics = true;
118+
videoEventsManager.trackingType = TrackingType.MANUAL;
119+
videoEventsManager.cloudName = (cloudName != null) ? cloudName : MediaManager.get().getCloudinary().config.cloudName;
120+
videoEventsManager.publicId = (publicId != null) ? cloudName : "";
121+
break;
122+
case DISABLED:
123+
analytics = false;
124+
break;
125+
}
126+
}
127+
62128
public void setPlayer(ExoPlayer player) {
63129
this.player = player;
64130
}

0 commit comments

Comments
 (0)