diff --git a/app/build.gradle b/app/build.gradle
index 405b4e34..36e0eeaa 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -56,6 +56,9 @@ dependencies {
implementation libs.androidx.compose.ui.tooling.preview
debugImplementation libs.androidx.compose.ui.tooling
+ implementation "ch.acra:acra-http:5.7.0"
+ implementation fileTree(dir: './libs/GemiusSDK_2.0.8.aar', include: ['*.aar', '*.jar'], exclude: [])
+
implementation "com.theoplayer.theoplayer-sdk-android:core:$sdkVersion"
implementation "com.theoplayer.theoplayer-sdk-android:integration-ads-ima:$sdkVersion"
implementation libs.theoplayer.android.ui
@@ -69,6 +72,8 @@ dependencies {
implementation project(':connectors:analytics:comscore')
implementation libs.comscore
+ implementation project(":connectors:analytics:gemius")
+
implementation project(':connectors:yospace')
implementation libs.yospace
diff --git a/app/libs/.gitignore b/app/libs/.gitignore
new file mode 100644
index 00000000..6d81fcdb
--- /dev/null
+++ b/app/libs/.gitignore
@@ -0,0 +1 @@
+*.aar
diff --git a/app/src/main/java/com/theoplayer/android/connector/MainActivity.kt b/app/src/main/java/com/theoplayer/android/connector/MainActivity.kt
index 29778b9e..35e81eb5 100644
--- a/app/src/main/java/com/theoplayer/android/connector/MainActivity.kt
+++ b/app/src/main/java/com/theoplayer/android/connector/MainActivity.kt
@@ -35,6 +35,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Dialog
+import com.gemius.sdk.stream.ProgramData
import com.theoplayer.android.api.THEOplayerConfig
import com.theoplayer.android.api.THEOplayerView
import com.theoplayer.android.api.ads.LinearAd
@@ -50,7 +51,9 @@ import com.theoplayer.android.connector.analytics.comscore.ComscoreMediaType
import com.theoplayer.android.connector.analytics.comscore.ComscoreMetaData
import com.theoplayer.android.connector.analytics.conviva.ConvivaConfiguration
import com.theoplayer.android.connector.analytics.conviva.ConvivaConnector
+import com.theoplayer.android.connector.analytics.gemius.GemiusConfiguration
import com.theoplayer.android.connector.analytics.nielsen.NielsenConnector
+import com.theoplayer.android.connector.analytics.gemius.GemiusConnector
import com.theoplayer.android.connector.uplynk.SkippedAdStrategy
import com.theoplayer.android.connector.uplynk.UplynkConfiguration
import com.theoplayer.android.connector.uplynk.UplynkConnector
@@ -74,6 +77,7 @@ class MainActivity : ComponentActivity() {
private lateinit var comscoreConnector: ComscoreConnector
private lateinit var yospaceConnector: YospaceConnector
private lateinit var uplynkConnector: UplynkConnector
+ private lateinit var gemiusConnector: GemiusConnector
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -85,6 +89,7 @@ class MainActivity : ComponentActivity() {
setupNielsen()
setupYospace()
setupUplynk()
+ setupGemius()
setupAdListeners()
setContent {
@@ -184,6 +189,26 @@ class MainActivity : ComponentActivity() {
)
}
+ private fun setupGemius() {
+ val gemiusId = "your_gemius_id"
+ val hitCollectorHost = "your_hit_collector_host"
+ val gemiusConfiguration = GemiusConfiguration(
+ "Demo",
+ "1.0",
+ hitCollectorHost,
+ gemiusId,
+ true,
+ null
+ )
+ gemiusConnector = GemiusConnector(this,gemiusConfiguration,theoplayerView)
+ val programData = ProgramData()
+ programData.name = "testasset1"
+ programData.programGenre = 1
+ programData.programSeason = "1"
+ programData.programProducer = "Someone"
+ gemiusConnector.update("test asset 1", programData)
+ }
+
private fun setupNielsen() {
val appId = "your_nielsen_app_id"
nielsenConnector = NielsenConnector(applicationContext, theoplayerView.player, appId, true)
diff --git a/connectors/analytics/gemius/.gitignore b/connectors/analytics/gemius/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/connectors/analytics/gemius/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/connectors/analytics/gemius/README.md b/connectors/analytics/gemius/README.md
new file mode 100644
index 00000000..e69de29b
diff --git a/connectors/analytics/gemius/build.gradle b/connectors/analytics/gemius/build.gradle
new file mode 100644
index 00000000..23ee7420
--- /dev/null
+++ b/connectors/analytics/gemius/build.gradle
@@ -0,0 +1,54 @@
+plugins {
+ alias(libs.plugins.android.library)
+ alias(libs.plugins.kotlin.android)
+}
+
+android {
+ namespace 'com.theoplayer.android.connector.analytics.gemius'
+ compileSdk 35
+
+ defaultConfig {
+ minSdk 21
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles "consumer-rules.pro"
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ kotlinOptions {
+ jvmTarget = '11'
+ }
+}
+
+// The Gemius SDK location should be set by the app and passed in a gemiusSdkDir property.
+def gemiusSdkDir = rootProject.properties['gemiusSdkDir']
+if (!gemiusSdkDir) {
+ logger.warn("⚠️ WARNING: gemiusSdkDir not set.")
+} else if (!rootProject.file(gemiusSdkDir).exists()) {
+ logger.warn("⚠️ WARNING: gemiusSdkDir does not exist at: ${gemiusSdkDir}")
+} else {
+ dependencies {
+ compileOnly files("${rootProject.file(gemiusSdkDir)}/GemiusSDK_2.0.8.aar")
+ }
+}
+
+dependencies {
+
+ implementation libs.androidx.core.ktx
+ implementation libs.androidx.appcompat
+ implementation libs.material
+ compileOnly "com.theoplayer.theoplayer-sdk-android:core:$sdkVersion"
+ compileOnly "com.theoplayer.theoplayer-sdk-android:integration-ads-ima:$sdkVersion"
+ testImplementation libs.junit
+ androidTestImplementation libs.androidx.test.ext.junit
+ androidTestImplementation libs.androidx.test.espresso.core
+}
\ No newline at end of file
diff --git a/connectors/analytics/gemius/consumer-rules.pro b/connectors/analytics/gemius/consumer-rules.pro
new file mode 100644
index 00000000..e69de29b
diff --git a/connectors/analytics/gemius/proguard-rules.pro b/connectors/analytics/gemius/proguard-rules.pro
new file mode 100644
index 00000000..481bb434
--- /dev/null
+++ b/connectors/analytics/gemius/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/connectors/analytics/gemius/src/main/AndroidManifest.xml b/connectors/analytics/gemius/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..a5918e68
--- /dev/null
+++ b/connectors/analytics/gemius/src/main/AndroidManifest.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusAdapter.kt b/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusAdapter.kt
new file mode 100644
index 00000000..6d629998
--- /dev/null
+++ b/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusAdapter.kt
@@ -0,0 +1,404 @@
+package com.theoplayer.android.connector.analytics.gemius
+
+import android.content.Context
+import android.util.Log
+import com.gemius.sdk.Config
+import com.gemius.sdk.stream.AdData
+import com.gemius.sdk.stream.EventAdData
+import com.gemius.sdk.stream.EventProgramData
+import com.theoplayer.android.api.THEOplayerView
+import com.gemius.sdk.stream.Player
+import com.gemius.sdk.stream.PlayerData
+import com.gemius.sdk.stream.ProgramData
+import com.theoplayer.android.api.ads.Ad
+import com.theoplayer.android.api.ads.LinearAd
+import com.theoplayer.android.api.event.EventListener
+import com.theoplayer.android.api.event.ads.AdBeginEvent
+import com.theoplayer.android.api.event.ads.AdBreakBeginEvent
+import com.theoplayer.android.api.event.ads.AdBreakEndEvent
+import com.theoplayer.android.api.event.ads.AdEndEvent
+import com.theoplayer.android.api.event.ads.AdSkipEvent
+import com.theoplayer.android.api.event.ads.AdsEventTypes
+import com.theoplayer.android.api.event.player.EndedEvent
+import com.theoplayer.android.api.event.player.ErrorEvent
+import com.theoplayer.android.api.event.player.PauseEvent
+import com.theoplayer.android.api.event.player.PlayerEventTypes
+import com.theoplayer.android.api.event.player.PlayingEvent
+import com.theoplayer.android.api.event.player.SeekingEvent
+import com.theoplayer.android.api.event.player.SourceChangeEvent
+import com.theoplayer.android.api.event.player.VolumeChangeEvent
+import com.theoplayer.android.api.event.player.WaitingEvent
+import com.theoplayer.android.api.event.track.mediatrack.audio.QualityChangedEvent
+import com.theoplayer.android.api.event.track.mediatrack.video.VideoTrackEventTypes
+import com.theoplayer.android.api.event.track.mediatrack.video.list.AddTrackEvent
+import com.theoplayer.android.api.event.track.mediatrack.video.list.RemoveTrackEvent
+import com.theoplayer.android.api.event.track.mediatrack.video.list.VideoTrackListEventTypes
+import com.theoplayer.android.api.player.track.mediatrack.quality.VideoQuality
+import com.theoplayer.android.connector.analytics.gemius.Utils.describeEvent
+
+const val PLAYER_ID = "THEOplayer"
+const val TAG = "GemiusConnector"
+
+const val GEMIUS_SDK_LOGS = true
+const val PLAYER_EVENTS_LOGS = true
+const val INTEGRATION_LOGS = true
+
+class GemiusAdapter(
+ context: Context,
+ private val configuration: GemiusConfiguration,
+ private val playerView: THEOplayerView,
+ private val adProcessor: AdProcessor?
+) {
+ private val gemiusPlayer: Player?
+
+ private var programId: String? = null
+ private var programData: ProgramData? = null
+
+ private var partCount: Int = 1
+ private var adCount: Int = 1
+ private var currentAd: Ad? = null
+
+ private val onSourceChange: EventListener
+ private val onFirstPlaying: EventListener
+// private val onPlay: EventListener
+ private val onPause: EventListener
+ private val onWaiting: EventListener
+ private val onSeeking: EventListener
+ private val onError: EventListener
+ private val onEnded: EventListener
+ private val onVolumeChange: EventListener
+
+ private val onAddVideoTrack: EventListener
+ private val onRemoveVideoTrack: EventListener
+ private val onVideoQualityChanged: EventListener>
+
+ private val onAdBreakBegin: EventListener
+ private val onAdBegin: EventListener
+ private val onAdEnd: EventListener
+ private val onAdSkip: EventListener
+ private val onAdBreakEnded: EventListener
+
+ init {
+ val playerData = PlayerData()
+ playerData.resolution = "${playerView.width}x${playerView.height}"
+ playerData.volume = computeVolume()
+ gemiusPlayer = Player(PLAYER_ID, configuration.hitCollectorHost, configuration.gemiusId, playerData)
+ gemiusPlayer.setContext(context)
+ Config.setLoggingEnabled(configuration.debug && GEMIUS_SDK_LOGS)
+
+ onSourceChange = EventListener { event -> handleSourceChange(event) }
+ onFirstPlaying = EventListener { event -> handleFirstPlaying(event) }
+// onPlay = EventListener { event -> handlePlay(event) }
+ onPause = EventListener { event -> handlePause(event) }
+ onWaiting = EventListener { event -> handleWaiting(event) }
+ onSeeking = EventListener { event -> handleSeeking(event) }
+ onError = EventListener { event -> handleError(event) }
+ onEnded = EventListener { event -> handleEnded(event) }
+ onVolumeChange = EventListener { event -> handleVolumeChange(event) }
+ onAddVideoTrack = EventListener { event -> handleAddVideoTrack(event) }
+ onRemoveVideoTrack = EventListener { event -> handleRemoveVideoTrack(event) }
+ onVideoQualityChanged = EventListener { event -> handleVideoQualityChanged(event) }
+ onAdBreakBegin = EventListener { event -> handleAdBreakBegin(event) }
+ onAdBegin = EventListener { event -> handleAdBegin(event) }
+ onAdEnd = EventListener { event -> handleAdEnd(event) }
+ onAdSkip = EventListener { event -> handleAdSkip(event) }
+ onAdBreakEnded = EventListener { event -> handleAdBreakEnded(event) }
+
+ addEventListeners()
+ }
+
+ fun update(programId: String, programData: ProgramData) {
+ this.programId = programId
+ this.programData = programData
+ }
+
+ fun destroy() {
+ removeEventListeners()
+ }
+
+ private fun computeVolume(): Int {
+ return if (playerView.player.isMuted) -1 else (playerView.player.volume * 100).toInt()
+ }
+
+ private fun addEventListeners() {
+ playerView.player.addEventListener(PlayerEventTypes.SOURCECHANGE, onSourceChange)
+ playerView.player.addEventListener(PlayerEventTypes.PLAYING, onFirstPlaying)
+// playerView.player.addEventListener(PlayerEventTypes.PLAY, onPlay)
+ playerView.player.addEventListener(PlayerEventTypes.PAUSE, onPause)
+ playerView.player.addEventListener(PlayerEventTypes.WAITING, onWaiting)
+ playerView.player.addEventListener(PlayerEventTypes.SEEKING, onSeeking)
+ playerView.player.addEventListener(PlayerEventTypes.ERROR, onError)
+ playerView.player.addEventListener(PlayerEventTypes.ENDED, onEnded)
+ playerView.player.addEventListener(PlayerEventTypes.VOLUMECHANGE, onVolumeChange)
+ playerView.player.videoTracks.addEventListener(VideoTrackListEventTypes.ADDTRACK, onAddVideoTrack)
+ playerView.player.videoTracks.addEventListener(VideoTrackListEventTypes.REMOVETRACK, onRemoveVideoTrack)
+ playerView.player.ads.addEventListener(AdsEventTypes.AD_BREAK_BEGIN, onAdBreakBegin)
+ playerView.player.ads.addEventListener(AdsEventTypes.AD_BEGIN, onAdBegin)
+ playerView.player.ads.addEventListener(AdsEventTypes.AD_END, onAdEnd)
+ playerView.player.ads.addEventListener(AdsEventTypes.AD_SKIP, onAdSkip)
+ playerView.player.ads.addEventListener(AdsEventTypes.AD_BREAK_END, onAdBreakEnded)
+ }
+
+ private fun removeEventListeners() {
+ playerView.player.removeEventListener(PlayerEventTypes.SOURCECHANGE, onSourceChange)
+ playerView.player.removeEventListener(PlayerEventTypes.PLAYING, onFirstPlaying)
+// playerView.player.removeEventListener(PlayerEventTypes.PLAY, onPlay)
+ playerView.player.removeEventListener(PlayerEventTypes.PAUSE, onPause)
+ playerView.player.removeEventListener(PlayerEventTypes.WAITING, onWaiting)
+ playerView.player.removeEventListener(PlayerEventTypes.SEEKING, onSeeking)
+ playerView.player.removeEventListener(PlayerEventTypes.ERROR, onError)
+ playerView.player.removeEventListener(PlayerEventTypes.ENDED, onEnded)
+ playerView.player.removeEventListener(PlayerEventTypes.VOLUMECHANGE, onVolumeChange)
+ playerView.player.videoTracks.removeEventListener(VideoTrackListEventTypes.ADDTRACK, onAddVideoTrack)
+ playerView.player.videoTracks.removeEventListener(VideoTrackListEventTypes.REMOVETRACK, onRemoveVideoTrack)
+ playerView.player.ads.removeEventListener(AdsEventTypes.AD_BREAK_BEGIN, onAdBreakBegin)
+ playerView.player.ads.removeEventListener(AdsEventTypes.AD_BEGIN, onAdBegin)
+ playerView.player.ads.removeEventListener(AdsEventTypes.AD_END, onAdEnd)
+ playerView.player.ads.removeEventListener(AdsEventTypes.AD_SKIP, onAdSkip)
+ playerView.player.ads.removeEventListener(AdsEventTypes.AD_BREAK_END, onAdBreakEnded)
+ }
+
+ private fun handleSourceChange(event: SourceChangeEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: source = ${event.source.toString()}")
+ }
+ partCount = 1
+ currentAd = null
+ val programId = this.programId ?: return
+ val programData = this.programData ?: return
+
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: NEW PROGRAM - id: $programId - data: $programData")
+ }
+ gemiusPlayer?.newProgram(programId,programData)
+ playerView.player.removeEventListener(PlayerEventTypes.PLAYING,onFirstPlaying)
+ playerView.player.addEventListener(PlayerEventTypes.PLAYING,onFirstPlaying)
+ }
+ private fun handleFirstPlaying(event: PlayingEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: currentTime = ${event.currentTime}")
+ }
+ val computedVolume = computeVolume()
+ val programId = programId ?: return
+ currentAd?.let { ad ->
+ val adId = ad.id
+ val adBreak = ad.adBreak ?: return
+ val offset = adBreak.timeOffset
+ val adEventData = EventAdData()
+ adEventData.volume = computedVolume
+ adEventData.breakSize = adBreak.ads.size
+ if (ad is LinearAd) adEventData.adDuration = ad.duration
+ adEventData.adPosition = adCount
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: AD EVENT (${describeEvent(Player.EventType.PLAY)}) - programId: $programId - adId: $adId - offset: $offset")
+ }
+ gemiusPlayer?.adEvent(programId, adId, offset, Player.EventType.PLAY, adEventData)
+ } ?: run {
+ if (hasPrerollScheduled()) return
+ val player = playerView.player
+ val currentQuality = player.videoTracks.first { track -> track.isEnabled }.activeQuality
+ val programEventData = EventProgramData()
+ programEventData.volume = computedVolume
+ programEventData.programDuration = player.duration.toInt()
+ programEventData.partID = partCount
+ programEventData.autoPlay = player.isAutoplay
+ if (currentQuality!= null) programEventData.quality = "${currentQuality.width}x${currentQuality.height}"
+ val currentTime = player.currentTime.toInt()
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: PROGRAM EVENT (${describeEvent(Player.EventType.PLAY)}) - programId: $programId - offset: $currentTime")
+ }
+ gemiusPlayer?.programEvent(programId, currentTime, Player.EventType.PLAY, programEventData)
+ }
+ }
+ private fun handlePause(event: PauseEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: currentTime = ${event.currentTime}")
+ }
+ reportBasicEvent(Player.EventType.PAUSE)
+ }
+ private fun handleWaiting(event: WaitingEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: currentTime = ${event.currentTime}")
+ }
+ reportBasicEvent(Player.EventType.BUFFER)
+ }
+ private fun handleSeeking(event: SeekingEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: currentTime = ${event.currentTime}")
+ }
+ reportBasicEvent(Player.EventType.SEEK)
+ }
+ private fun handleError(event: ErrorEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ val errorObject = event.errorObject
+ Log.d(TAG, "Player Event: ${event.type}: error = ${errorObject.code}: ${errorObject.message}")
+ }
+ reportBasicEvent(Player.EventType.COMPLETE)
+ }
+ private fun handleEnded(event: EndedEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: currentTime = ${event.currentTime}")
+ }
+ reportBasicEvent(Player.EventType.COMPLETE)
+ }
+ private fun handleVolumeChange(event: VolumeChangeEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: volume = ${event.volume}")
+ }
+ val computedVolume = computeVolume()
+ val programId = programId ?: return
+ currentAd?.let { ad ->
+ val adBreak = ad.adBreak ?: return
+ val adId = ad.id
+ val offset = adBreak.timeOffset
+ val adEventData = EventAdData()
+ adEventData.volume = computedVolume
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: AD EVENT (${describeEvent(Player.EventType.CHANGE_VOL)}) - programId: $programId - adId: $adId - offset: $offset")
+ }
+ gemiusPlayer?.adEvent(programId, ad.id, offset, Player.EventType.CHANGE_VOL, adEventData)
+ } ?: run {
+ val programEventData = EventProgramData()
+ programEventData.volume = computedVolume
+ val currentTime = playerView.player.currentTime.toInt()
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: PROGRAM EVENT (${describeEvent(Player.EventType.CHANGE_VOL)}) - programId: $programId - offset: $currentTime")
+ }
+ gemiusPlayer?.programEvent(programId, currentTime, Player.EventType.CHANGE_VOL, programEventData)
+ }
+ }
+ private fun handleAddVideoTrack(event: AddTrackEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event (Video Track): ${event.type}")
+ }
+ val track = event.track
+ track.addEventListener(VideoTrackEventTypes.ACTIVEQUALITYCHANGEDEVENT, onVideoQualityChanged)
+ }
+ private fun handleRemoveVideoTrack(event: RemoveTrackEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}")
+ }
+ val track = event.track
+ track.removeEventListener(VideoTrackEventTypes.ACTIVEQUALITYCHANGEDEVENT, onVideoQualityChanged)
+ }
+ private fun handleVideoQualityChanged(event: QualityChangedEvent<*,*>) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}")
+ }
+ val programId = programId ?: return
+ val activeQuality = (event.quality as? VideoQuality) ?: return
+ val height = activeQuality.height
+ val width = activeQuality.width
+ currentAd?.let { ad ->
+ val adBreak = ad.adBreak ?: return
+ val adId = ad.id
+ val offset = adBreak.timeOffset
+ val adEventData = EventAdData()
+ adEventData.quality = "${width}x${height}"
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: AD EVENT (${describeEvent(Player.EventType.CHANGE_QUAL)}) - programId: $programId - adId: $adId - offset: $offset")
+ }
+ gemiusPlayer?.adEvent(programId,ad.id,adBreak.timeOffset,Player.EventType.CHANGE_QUAL,adEventData)
+ } ?: run {
+ val programEventData = EventProgramData()
+ programEventData.quality = "${width}x${height}"
+ val currentTime = playerView.player.currentTime.toInt()
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: PROGRAM EVENT (${describeEvent(Player.EventType.CHANGE_QUAL)}) - programId: $programId - offset: $currentTime")
+ }
+ gemiusPlayer?.programEvent(programId,currentTime,Player.EventType.CHANGE_QUAL,programEventData)
+ }
+ }
+ private fun handleAdBreakBegin(event: AdBreakBeginEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: offset = ${event.adBreak.timeOffset}")
+ }
+ reportBasicEvent(Player.EventType.BREAK)
+ playerView.player.removeEventListener(PlayerEventTypes.PLAYING, onFirstPlaying)
+ playerView.player.addEventListener(PlayerEventTypes.PLAYING, onFirstPlaying)
+ }
+ private fun handleAdBegin(event: AdBeginEvent) {
+ val ad = event.ad
+ currentAd = ad
+ val adId = ad?.id ?: return
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: id = $adId")
+ }
+ val adData = buildAdData(ad)
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: NEW AD - id: $adId - data: $adData")
+ }
+ gemiusPlayer?.newAd(adId,adData)
+ }
+ private fun handleAdEnd(event: AdEndEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: id = ${event.ad?.id}")
+ }
+ reportBasicEvent(Player.EventType.COMPLETE)
+ reportBasicEvent(Player.EventType.CLOSE)
+ adCount++
+ currentAd = null
+ playerView.player.removeEventListener(PlayerEventTypes.PLAYING, onFirstPlaying)
+ playerView.player.addEventListener(PlayerEventTypes.PLAYING, onFirstPlaying)
+ }
+ private fun handleAdSkip(event: AdSkipEvent) {
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: id = ${event.ad?.id}")
+ }
+ reportBasicEvent(Player.EventType.SKIP)
+ }
+ private fun handleAdBreakEnded(event: AdBreakEndEvent) {
+ val offset = event.adBreak.timeOffset
+ if (configuration.debug && PLAYER_EVENTS_LOGS) {
+ Log.d(TAG, "Player Event: ${event.type}: offset = $offset")
+ }
+ adCount = 1
+ if (offset > 0) partCount++
+ val programId = programId ?: return
+ val programData = programData ?: return
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: NEW PROGRAM - id: $programId - data: $programData")
+ }
+ gemiusPlayer?.newProgram(programId, programData)
+ playerView.player.removeEventListener(PlayerEventTypes.PLAYING, onFirstPlaying)
+ if (offset == 0) playerView.player.addEventListener(PlayerEventTypes.PLAYING, onFirstPlaying)
+ }
+ private fun reportBasicEvent(eventType: Player.EventType) {
+ val programId = programId ?: return
+ currentAd?.let { ad ->
+ val offset = ad.adBreak?.timeOffset ?: return
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: AD EVENT (${describeEvent(eventType)}) - id: ${ad.id} - offset: $offset")
+ }
+ // docs mention null can be passed but interface prohibits
+ gemiusPlayer?.adEvent(programId,ad.id, offset, eventType, EventAdData())
+ } ?: run {
+ val currentTime = playerView.player.currentTime.toInt()
+ if (configuration.debug && INTEGRATION_LOGS) {
+ Log.d(TAG, "Integration: PROGRAM EVENT (${describeEvent(eventType)}) - id: $programId - offset: $currentTime")
+ }
+ // docs mention null can be passed but interface prohibits
+ gemiusPlayer?.programEvent(programId, currentTime, eventType, EventProgramData())
+ }
+ }
+
+ private fun buildAdData(ad: Ad): AdData {
+ val adData = AdData()
+ val linearAd = ad as? LinearAd ?: return adData
+ adProcessor?.let { adProcessor ->
+ return adProcessor.apply(ad)
+ } ?: run {
+ adData.name = linearAd.id
+ adData.adType = AdData.AdType.BREAK
+ adData.adFormat = 1 // 1 = VIDEO ; 2 = AUDIO
+ adData.duration = linearAd.duration
+ adData.quality = "${playerView.player.videoWidth}x${playerView.player.videoHeight}"
+ adData.resolution = "${playerView.width}x${playerView.height}"
+ return adData
+ }
+ }
+
+ private fun hasPrerollScheduled(): Boolean {
+ return playerView.player.ads.scheduledAds.any { it.adBreak?.timeOffset == 0 }
+ }
+}
\ No newline at end of file
diff --git a/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusConfiguration.kt b/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusConfiguration.kt
new file mode 100644
index 00000000..5d0c4b10
--- /dev/null
+++ b/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusConfiguration.kt
@@ -0,0 +1,10 @@
+package com.theoplayer.android.connector.analytics.gemius
+
+data class GemiusConfiguration (
+ val applicationName: String,
+ val applicationVersion: String,
+ val hitCollectorHost: String,
+ val gemiusId: String,
+ val debug: Boolean,
+ val adProcessor: AdProcessor?
+)
\ No newline at end of file
diff --git a/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusConnector.kt b/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusConnector.kt
new file mode 100644
index 00000000..cc3c4b27
--- /dev/null
+++ b/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/GemiusConnector.kt
@@ -0,0 +1,27 @@
+package com.theoplayer.android.connector.analytics.gemius
+
+import android.content.Context
+import com.gemius.sdk.stream.AdData
+import com.gemius.sdk.stream.ProgramData
+import com.theoplayer.android.api.THEOplayerView
+import com.theoplayer.android.api.ads.Ad
+
+interface AdProcessor {
+ fun apply(input: Ad): AdData
+}
+
+class GemiusConnector(
+ context: Context,
+ configuration: GemiusConfiguration,
+ playerView: THEOplayerView,
+) {
+ private val gemiusAdapter = GemiusAdapter(context,configuration,playerView, configuration.adProcessor)
+
+ fun update(programId: String, programData: ProgramData) {
+ gemiusAdapter.update(programId, programData)
+ }
+
+ fun destroy() {
+ gemiusAdapter.destroy()
+ }
+}
\ No newline at end of file
diff --git a/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/Utils.kt b/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/Utils.kt
new file mode 100644
index 00000000..83faea0a
--- /dev/null
+++ b/connectors/analytics/gemius/src/main/java/com/theoplayer/android/connector/analytics/gemius/Utils.kt
@@ -0,0 +1,23 @@
+package com.theoplayer.android.connector.analytics.gemius
+
+import com.gemius.sdk.stream.Player
+
+object Utils {
+ fun describeEvent(eventType: Player.EventType): String = when (eventType) {
+ Player.EventType.SKIP -> "SKIP"
+ Player.EventType.PLAY -> "PLAY"
+ Player.EventType.PAUSE -> "PAUSE"
+ Player.EventType.STOP -> "STOP"
+ Player.EventType.CLOSE -> "CLOSE"
+ Player.EventType.BUFFER -> "BUFFER"
+ Player.EventType.BREAK -> "BREAK"
+ Player.EventType.SEEK -> "SEEK"
+ Player.EventType.COMPLETE -> "COMPLETE"
+ Player.EventType.NEXT -> "NEXT"
+ Player.EventType.PREV -> "PREV"
+ Player.EventType.CHANGE_VOL -> "CHANGE_VOL"
+ Player.EventType.CHANGE_QUAL -> "CHANGE_QUAL"
+ Player.EventType.CHANGE_RES -> "CHANGE_RES"
+ else -> "UNKNOWN EVENTTYPE"
+ }
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 22f41cdc..da0a1706 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -26,3 +26,5 @@ android.nonFinalResIds=true
groupId=com.theoplayer.android-connector
sdkVersion=9.8.2
connectorVersion=9.8.2
+
+gemiusSdkDir=./app/libs
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 99b80574..407011ad 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -24,6 +24,7 @@ conviva = "4.0.43"
nielsen = "9.2.0.0"
comscore = "6.10.0"
yospace = "3.6.7"
+material = "1.12.0"
[libraries]
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "androidxAppCompat" }
@@ -52,6 +53,7 @@ conviva = { group = "com.conviva.sdk", name = "conviva-core-sdk", version.ref =
nielsen = { group = "com.nielsenappsdk.global", name = "ad", version.ref = "nielsen" }
comscore = { group = "com.comscore", name = "android-analytics", version.ref = "comscore" }
yospace = { group = "com.yospace", name = "admanagement-sdk", version.ref = "yospace" }
+material = { group = "com.google.android.material", name = "material", version.ref = "material" }
[plugins]
android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
diff --git a/settings.gradle b/settings.gradle
index dcdf50c3..7aafcda8 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -28,6 +28,8 @@ include ':app'
include ':connectors:analytics:comscore'
include ':connectors:analytics:conviva'
include ':connectors:analytics:nielsen'
+include ':connectors:analytics:gemius'
include ':connectors:mediasession'
include ':connectors:yospace'
include ':connectors:uplynk'
+include ':connectors:analytics:gemius'