commonMain.com.bluebillywig.bbnativeshared.loggers.BlueBillywigLogger.kt Maven / Gradle / Ivy
package com.bluebillywig.bbnativeshared.loggers
import com.bluebillywig.bbnativeshared.Enums.EventName
import com.bluebillywig.bbnativeshared.Logger
import com.bluebillywig.bbnativeshared.Platform
import com.bluebillywig.bbnativeshared.interfaces.EventBusInterface
import com.bluebillywig.bbnativeshared.interfaces.EventListenerInterface
import com.bluebillywig.bbnativeshared.KMMTimer
import com.bluebillywig.bbnativeshared.model.*
import co.touchlab.stately.ensureNeverFrozen
import com.benasher44.uuid.uuid4
import com.soywiz.krypto.sha1
import io.ktor.client.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import io.ktor.http.*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.datetime.Clock
import kotlin.coroutines.CoroutineContext
import kotlin.math.*
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
open class BlueBillywigLogger:
EventListenerInterface, CoroutineScope {
override fun onEvent(eventType: EventName, data: Map?) {
// Logger.d(TAG + "onEvent", "GOT EVENT: $eventType")
when (eventType) {
EventName.bb_embed_loaded -> {
if (data != null && data["embedObject"] != null) this._ingestOpts(data["embedObject"] as EmbedObject?)
}
EventName.bb_project_loaded -> {
this._onProjectDataLoaded(eventType, data)
}
EventName.bb_mediaclip_loaded -> {
this._onClipDataLoaded(eventType, data)
}
EventName.bb_mediaclip_failed -> {
this._onClipDataFailed(eventType)
}
EventName.bb_view_started -> {
this._onViewStarted(eventType, data)
}
EventName.bb_view_finished -> {
this._onViewFinished(eventType)
}
EventName.bb_media_started -> {
this._onMediaStarted(eventType)
}
EventName.bb_media_finished -> {
this._onMediaFinished(eventType)
}
EventName.bb_media_paused -> {
this._onMediaPaused(eventType)
}
EventName.bb_media_resumed -> {
this._onMediaResumed(eventType)
}
EventName.bb_media_seeked -> {
this._onMediaSeeked(eventType, data)
}
EventName.bb_media_duration_change -> {
this._onMediaDurationChange(eventType, data)
}
EventName.bb_media_timeupdate -> {
this._onMediaTimeUpdate(eventType, data)
}
EventName.bb_adunit_initialized,
EventName.bb_adunit_failed,
EventName.bb_inview,
EventName.bb_outview -> {
this._onAdUnitEvent(eventType, data)
}
EventName.bb_adsystem_initialized,
EventName.bb_adsystem_loadstart,
EventName.bb_adsystem_blocked,
EventName.bb_adsystem_canplay,
EventName.bb_adsystem_started,
EventName.bb_adsystem_clicked,
EventName.bb_adsystem_skipped,
EventName.bb_adsystem_noad,
EventName.bb_adsystem_failed,
EventName.bb_adsystem_finished,
EventName.bb_adsystem_quartile -> {
this._onAdSystemEvent(eventType, data)
}
else -> {}
}
}
// resources
private var _eventBus: EventBusInterface? = eventBus
private var _progressTimer: KMMTimer? = null
private var _progressOnceTimer: KMMTimer? = null
private lateinit var mainDispatcher: CoroutineDispatcher
val job = Job()
override val coroutineContext: CoroutineContext
get() = job
constructor(eventBus: EventBusInterface?) : this(eventBus, Dispatchers.Main)
constructor(eventBus: EventBusInterface?, mainDispatcher: CoroutineDispatcher = Dispatchers.Main) {
this._eventBus = eventBus
this.mainDispatcher = mainDispatcher
this._debouncedEventListMap = mutableMapOf()
this._stopDebouncedEventList = false
_debouncedEventListMap.put("rs", MutableStateFlow(mutableMapOf()))
_debouncedEventListMap.put("se", MutableStateFlow(mutableMapOf()))
_debouncedEventListMap.forEach {
launch(mainDispatcher) {
_debouncedEventListMap[it.key]?.collect {
if (it.isNotEmpty()) {
_logEvent(it)
}
if (_stopDebouncedEventList) {
currentCoroutineContext().cancel()
}
}
}
}
}
companion object { // this is Kotlin's way to support static class methods; don't ask...
val TAG = "BlueBillywigLogger:"
private val client = HttpClient()
}
// settings
private var _playoutName: String = "default"
private var _publicationName: String = "default"
private var _disableCookies: String = "false"
private var _logProgressAsQuartiles: Boolean = false
private var _autoPlay: Boolean = false
private var _autoMute: Boolean = false
private var _autoLoop: Boolean = false
private val _uuid = uuid4().toString()
private var _playMode = "native"
private var _commercialPlayMode = "native"
private val _loggerUrl = "https://stats.bluebillywig.com"
// state
private var _enabled: Boolean = false
protected var _enabledQueue: MutableList> = mutableListOf()
private var _playerSessionId: String = this._generateSessionId()
private var _playerSessionStartLogged: Boolean = false
private var _sessionId = this._generateSessionId()
private var _creativeId: String? = null
private var _referrer: String = "in-app"
private var _realReferrer: String = "in-app"
private var _fullScreen: Boolean = false
private var _viewId: String? = null
private var _prevViewId: String? = null
private var _prevClipId: String? = null
private var _paused: Boolean = false
private var _startedClipId: String? = null // for detecting replay
private var _isReplay: Boolean = false
private var _projectXSTLogged: Boolean = false
private var _currentTime: Double = 0.0
private var _timeOffset: Long = 0
private var _serialNumber = 0
private var _timestampOffset = 0
private var _to: Long = 0
private var _to_now: Long = 0
private var _to_num: Long = 0
private var _quartile: Int = -1
private var _isInView: Boolean = false
private var _adUnitLoggedXiv: Boolean = false
private var _adUnitLoggedXov: Boolean = false
private var _lineItemLoggedXld: Boolean = false
private var _lineItemLoggedXst: Boolean = false
private var _creativeLoggedXit: Boolean = false
private var _creativeLoggedXit_pi: String? = null // pod index
private var _cuePointTuples: MutableMap>> = mutableMapOf()
private var _adSystemStarted: Boolean = false
// data
private var _clip: MediaClip? = null
private var _clip_isInitial: Boolean = true
private var _dvid: String? = null
private var _projectId: String? = null
private var _duration: Double = 0.0
private var _asset: MediaAsset? = null
private var _assetBandwidth: String? = null
private var _adUnitAdPosition: String? = null
private var _adUnitCode: String? = null
private var _lineItemCode: String? = null
private var _adProperties: Map? = null
private var _stopDebouncedEventList: Boolean
protected var _debouncedEventListMap: MutableMap>>
init {
ensureNeverFrozen() // NB!?!
this._eventBus?.addEventlistener(this)
}
fun __destruct() {
_stopDebouncedEventList = true
_debouncedEventListMap.forEach {
it.value.tryEmit(mutableMapOf())
}
_debouncedEventListMap.clear()
this._eventBus?.removeEventlistener(this)
this._eventBus = null
this._progressTimer?.cancel()
this._progressTimer = null
this._progressOnceTimer?.cancel()
this._progressOnceTimer = null
Dispatchers.Default.cancel()
mainDispatcher.cancel()
}
var eventBus: EventBusInterface?
get() = this._eventBus
set(value) {
this._eventBus?.removeEventlistener(this)
this._eventBus = value
this._eventBus?.addEventlistener(this)
}
fun enable() {
this._enabled = true
while (this._enabledQueue.count() > 0) {
val item = this._enabledQueue.removeFirst()
this._logEvent_enabled(item)
}
}
fun disable() {
this._enabled = false
}
fun handleAutoPause() {
var i = 0
while (i < this._enabledQueue.count()) {
val item = this._enabledQueue[i]
if (item["et"] === "Session" ||
item["ev"] === "it" ||
item["ev"] === "pf" ||
item["ev"] === "pw" ||
item["et"] === "Creative" ||
item["et"] === "AdUnit" ||
item["et"] === "LineItem") {
this._enabledQueue.removeAt(i) // NB no need to increment i!
this._logEvent_enabled(item)
} else {
i++
}
}
}
private fun _generateSessionId(): String {
val _uuid = this._uuid
val clipId = if (this._clip != null) this._clip!!.id ?: "0" else "0"
val now = Clock.System.now().toEpochMilliseconds().toString()
val hash = (_uuid + '/' + clipId + '/' + now).encodeToByteArray().sha1().hex
return hash
}
private fun _constructCreativeId(type: String, position: String, url: String): String {
val hash = (type + '/' + position + '/' + url).encodeToByteArray().sha1().hex
return hash;
}
private fun _onProjectDataLoaded(eventType: EventName, payload: Map?) {
// always reset vars (project xst start logging)
this._projectXSTLogged = false
this._projectId = null
this._sessionId = this._generateSessionId()
this._serialNumber = 0
if (this._clip != null && !this._clip_isInitial) this._prevClipId = this._clip!!.id
/* this._timelineIds = {}; */
this._currentTime = 0.0
this._timeOffset = 0
this._quartile = -1 // NB!
if (payload != null && payload["projectData"] != null) {
this._projectId = (payload["projectData"] as Project).id
val jsonParser = Json { ignoreUnknownKeys = true; isLenient = true; serializersModule = contentItemSerializersModule }
this._clip = jsonParser.decodeFromString("{\"id\":\"\", \"title\":\"\", \"projectId\":\""+this._projectId+"\", \"date\":{\"published\":\"\"}}")
this._clip_isInitial = false // ?!?
this._determineReferrer()
var aux = mutableMapOf("xu" to this._referrer, "xr" to this._realReferrer, "pt" to this._playoutName)
aux["aup"] = if (this._autoPlay) "1" else "0"
aux["aum"] = if (this._autoMute) "1" else "0"
aux["aul"] = if (this._autoLoop) "1" else "0"
if (!this._playerSessionStartLogged) {
this._playerSessionStartLogged = true
this._logExtensionEvent("xst", this._playerSessionId, "Session", null, null, aux) // no parent
}
this._logExtensionEvent("xit", this._projectId ?: "0", "Project", this._playerSessionId, "Session", aux)
}
}
// OBSOLETE; FOR TESTING ONLY
fun onClipDataLoaded(eventType: EventName, payload: Map?) {
this._onClipDataLoaded(eventType, payload)
}
private fun _onClipDataLoaded(eventType: EventName, payload: Map?) {
this._sessionId = this._generateSessionId()
this._serialNumber = 0
if (this._clip != null && !this._clip_isInitial) this._prevClipId = this._clip!!.id
/* this._timelineIds = {}; */
this._currentTime = 0.0
this._timeOffset = 0
this._quartile = -1 // NB!
if (payload != null && payload["clipData"] != null) {
this._clip = payload["clipData"] as MediaClip
this._clip_isInitial = false
if (this._projectId != null) this._clip!!.projectId = this._projectId // NB override, since 'loadedprojectdata' always precedes 'loadedclipdata'!
this._determineReferrer()
var aux = mutableMapOf("xu" to this._referrer, "xr" to this._realReferrer, "pt" to this._playoutName)
aux["aup"] = if (this._autoPlay) "1" else "0"
aux["aum"] = if (this._autoMute) "1" else "0"
aux["aul"] = if (this._autoLoop) "1" else "0"
if (!this._playerSessionStartLogged) {
this._playerSessionStartLogged = true
//console.log("[BlueBillywigStatisticsLogger] _onViewStarted going to log xst for session: " + this._playerSessionId);
this._logExtensionEvent("xst", this._playerSessionId, "Session", null, null, aux)
}
if (this._clip != null && this._clip!!.usetype != null) aux["ut"] = this._clip!!.usetype!!
this._logMediaClipEvent("it", aux)
} else {
this._clip = null
this._logErrorEvent("pf", mapOf("why" to "clip failed to open"))
}
this._progressTimer?.cancel()
}
private fun _onClipDataFailed(eventType: EventName) {
this._onClipDataLoaded(eventType, null)
}
private fun _onViewStarted(eventType: EventName, payload: Map?) {
Logger.d(TAG + "_onViewStarted", eventType.toString())
this._determineReferrer()
// hackje: just-in-time session init
if (!this._playerSessionStartLogged) {
this._playerSessionStartLogged = true
var aux2 = mutableMapOf("xu" to this._referrer, "xr" to this._realReferrer, "pt" to this._playoutName)
aux2["aup"] = if (this._autoPlay) "1" else "0"
aux2["aum"] = if (this._autoMute) "1" else "0"
aux2["aul"] = if (this._autoLoop) "1" else "0"
this._logExtensionEvent("xst", this._playerSessionId, "Session", null, null, aux2)
}
if (this._viewId != null) this._onViewFinished(eventType) // just to be sure
this._viewId = this._generateSessionId() // mwah
// Log xst for project (only once per project, see _onProjectDataLoaded)
if ( !this._projectXSTLogged && this._projectId != null ) {
this._logExtensionEvent("xst", this._projectId!!, "Project", this._viewId, "View")
this._projectXSTLogged = true
}
var aux = mutableMapOf("fs" to (if (this._fullScreen) "1" else "0"), "xu" to this._referrer, "xr" to this._realReferrer, "pt" to this._playoutName)
if (payload != null && payload.containsKey("initiator") && payload["initiator"] is String) {
val initiator_parts = (payload["initiator"] as String).split("/")
if (initiator_parts.count() > 0) aux["iet"] = initiator_parts[0]
if (initiator_parts.count() > 1) aux["iid"] = initiator_parts[1]
if (this._prevViewId != null) aux["pvid"] = this._prevViewId!!
if (this._prevClipId != null) aux["pcid"] = this._prevClipId!!
}
if (this._asset != null) {
if (this._asset!!.languageId != null) {
aux["ali"] = this._asset!!.languageId!! // asset language id
}
if (this._asset!!.languageIsocode != null) {
aux["alc"] = this._asset!!.languageIsocode!! // asset language code
}
}
if (this._clip != null && this._clip!!.usetype != null) aux["ut"] = this._clip!!.usetype!!
//console.log("[BlueBillywigStatisticsLogger] _onViewStarted going to log xst for view: " + this._viewId)
this._logExtensionEvent("xst", this._viewId ?: "null", "View", this._playerSessionId, "Session", aux )
}
private fun _onViewFinished(eventType: EventName) {
Logger.d(TAG + "_onViewFinished", eventType.toString())
this._progressTimer?.cancel()
this._logExtensionEvent("xfn", this._viewId ?: "null", "View", this._playerSessionId, "Session", mapOf("fs" to (if (this._fullScreen) "1" else "0")) )
this._prevViewId = this._viewId
this._viewId = null
}
private fun _onMediaStarted(eventType: EventName) {
Logger.d(TAG + "_onMediaStarted", eventType.toString())
this._paused = false
this._isReplay = false
if (this._clip != null && this._clip!!.id == this._startedClipId) { // replay
this._logMediaClipEvent("rp", mapOf("to" to "0"))
this._isReplay = true
}
if (this._clip != null) this._startedClipId = this._clip!!.id
// Stats expects a number for bandwidth
if (this._asset != null && this._asset!!.bandwidth != null && this._asset!!.bandwidth!!.toIntOrNull() != null) {
this._logMediaClipEvent("st", mapOf("to" to "0", "ab" to this._asset!!.bandwidth!!))
} else if (this._assetBandwidth != null && this._assetBandwidth!!.toIntOrNull() != null) { // if the "normal" bandwidth is not defined, check if the creative bandwidth is set.
this._logMediaClipEvent("st", mapOf("to" to "0", "ab" to this._assetBandwidth!!))
} else {
this._logMediaClipEvent("st", mapOf("to" to "0"))
}
this._progressTimer?.cancel()
val interval = this._determineProgressInterval() // in seconds
// always log one progress event after 2 seconds
if (interval > 2000) {
this._progressOnceTimer?.cancel()
this._progressOnceTimer = KMMTimer("ProgressOnceTimer", 2000, 2000, { this._progressOnceTimer?.cancel(); this._handleProgressTimerTick() }) // new
}
this._progressTimer = KMMTimer("ProgressTimer", (interval * 1000).toLong(), (interval * 1000).toLong(), { this._handleProgressTimerTick() }) // new
//Log xst for project (only once per project, see _onProjectDataLoaded)
if (!this._projectXSTLogged && this._projectId != null) {
this._logExtensionEvent("xst", this._projectId!!, "Project", this._viewId!!, "View")
this._projectXSTLogged = true
}
}
private fun _onMediaFinished(eventType: EventName) {
if (this._logProgressAsQuartiles) {
val wpa: String = if (this._paused) "true" else "false"
val quartile = 4
while (this._quartile < quartile) { // NB ensure all previous quartiles are also hit!
this._quartile++
val pct: String = (this._quartile * 25).toString()
this._logMediaClipEvent("pg", mapOf("pct" to pct, "pet" to "Session", "pid" to this._playerSessionId, "wpa" to wpa))
}
}
this._logMediaClipEvent("fn", mapOf("to" to this._getTimeOffset().toString()))
this._progressTimer?.cancel()
}
private fun _onMediaPaused(eventType: EventName) {
this._paused = true
this._logMediaClipEvent("pa", mapOf("to" to this._getTimeOffset().toString()))
this._progressTimer?.cancel()
val interval = 60.0 // in seconds
this._progressTimer = KMMTimer("ProgressTimer", (interval * 1000).toLong(), (interval * 1000).toLong(), { this._handleProgressTimerTick() })
}
private fun _onMediaResumed(eventType: EventName) {
this._paused = false
this._logMediaClipEvent("rs", mapOf("to" to this._getTimeOffset().toString()))
this._progressTimer?.cancel()
val interval = this._determineProgressInterval() // in seconds
this._progressTimer = KMMTimer("ProgressTimer", (interval * 1000).toLong(), (interval * 1000).toLong(), { this._handleProgressTimerTick() })
}
private fun _onMediaSeeked(eventType: EventName, payload: Map?) {
if (payload != null && payload["seekedPosition"] != null) {
val seekedPosition = (payload["seekedPosition"] as String).toDoubleOrNull()
if (seekedPosition != null && seekedPosition >= 0.0) { // numeric and non-negative
this._currentTime = seekedPosition
if (this._duration > 0.0 && this._currentTime > this._duration) this._currentTime = this._duration
this._timeOffset = (this._currentTime * 1000).roundToLong() // NB!
}
}
this._logMediaClipEvent("se", mapOf("to" to this._getTimeOffset().toString()))
}
private fun _onMediaDurationChange(eventType: EventName, payload: Map?) {
if (payload != null && payload["duration"] != null) {
val duration = (payload["seekedPosition"] as String).toDoubleOrNull()
if (duration != null && duration >= 0.0) { // numeric and non-negative
this._duration = duration
}
}
}
private fun _onMediaTimeUpdate(eventType: EventName, payload: Map?) {
if (payload != null && payload["currentTime"] != null) {
val currentTime = (payload["currentTime"] as String).toDoubleOrNull()
if (currentTime != null && currentTime >= 0.0) { // numeric and non-negative
this._currentTime = currentTime
if (this._duration > 0.0 && this._currentTime > this._duration) this._currentTime = this._duration
this._timeOffset = (this._currentTime * 1000).roundToLong() // NB!
}
}
// NB no logging here; done by progressTimer!
}
private fun _onAdUnitEvent(eventType: EventName, payload: Map?) {
val params: Map = if (payload != null) payload else mapOf()
val adPosition = params["adPosition"] as String?
if (!adPosition.isNullOrEmpty()) this._adUnitAdPosition = adPosition.toLowerCase()
val aux: MutableMap = mutableMapOf()
if (this._adUnitAdPosition != null) aux["ap"] = this._adUnitAdPosition!!
when (eventType) {
EventName.bb_adunit_initialized -> {
this._creativeId = null // NB?!?
this._adUnitLoggedXiv = false;
this._adUnitLoggedXov = false;
this._adUnitCode = params["adUnitCode"] as String?
if (this._adUnitCode != null) {
this._logExtensionEvent( "xit", this._adUnitCode!!, "AdUnit", this._viewId, "View", aux.toMap());
}
}
EventName.bb_adunit_failed -> {
this._creativeId = null // NB?!?
this._adUnitCode = params["adUnitCode"] as String?
if (this._adUnitCode != null) {
this._logExtensionEvent( "xpf", this._adUnitCode!!, "AdUnit", this._viewId, "View", aux.toMap());
}
}
EventName.bb_inview -> {
this._isInView = true;
}
EventName.bb_outview -> {
this._isInView = false;
}
else -> {}
}
if (this._adUnitCode != null) {
if (this._isInView) { // InView
if (!this._adUnitLoggedXiv) {
this._adUnitLoggedXiv = true;
this._logExtensionEvent("xiv", this._adUnitCode!!, "AdUnit", this._viewId, "View", aux);
}
} else { // OutView
if (!this._adUnitLoggedXov) {
this._adUnitLoggedXov = true;
this._logExtensionEvent("xov", this._adUnitCode!!, "AdUnit", this._viewId, "View", aux);
}
}
}
}
private fun _onAdSystemEvent(eventType: EventName, payload: Map?) {
val params: Map = if (payload != null) payload else mapOf()
val aux: MutableMap = mutableMapOf(
"at" to (params["adType"] as String? ?: ""),
"ap" to (params["adPosition"] as String? ?: "").toLowerCase(),
"ar" to (params["adRequestUrl"] as String? ?: ""))
when (eventType) {
EventName.bb_adsystem_initialized -> {
this._adProperties = null
this._lineItemLoggedXld = false
this._lineItemLoggedXst = false
this._creativeLoggedXit = false
this._creativeLoggedXit_pi = null
this._cuePointTuples = mutableMapOf()
// LineItem xit
this._lineItemCode = params["lineItemCode"] as String? // required
if (this._adUnitCode != null && this._lineItemCode != null) {
this._logExtensionEvent("xit", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", aux.toMap());
}
// Creative xit
// nothing; logged later in _onAdSystemCanPlay
}
EventName.bb_adsystem_loadstart -> {
if (params.containsKey("adProperties")) this._adProperties = params["adProperties"] as Map // else this._adProperties = null;
if (this._adProperties != null) {
aux["sdk"] = this._adProperties!!["sdk"] as String? ?: ""
}
// LineItem xls
if (params["lineItemCode"] != null) this._lineItemCode = params["lineItemCode"] as String?
if (this._adUnitCode != null && this._lineItemCode != null) {
this._logExtensionEvent("xls", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", aux.toMap());
}
// Creative xls
// nothing; logged later in _onAdSystemCanPlay
}
EventName.bb_adsystem_blocked -> {}
EventName.bb_adsystem_canplay -> {
if (params.containsKey("adProperties")) this._adProperties = params["adProperties"] as Map // else this._adProperties = null;
if (this._adProperties != null) {
val adProperties = this._adProperties!! // short-hand
aux["adi"] = adProperties["adId"] as String? ?: ""
aux["adt"] = adProperties["adTitle"] as String? ?: ""
aux["ads"] = adProperties["adSystem"] as String? ?: ""
aux["adl"] = adProperties["adLinear"] as String? ?: ""
aux["add"] = adProperties["duration"] as String? ?: ""
aux["sdk"] = adProperties["sdk"] as String? ?: ""
if (adProperties["adPodInfo"] != null) {
val adPodInfo = adProperties["adPodInfo"]!! as Map
aux["adp_ap"] = adPodInfo["adPosition"] as String? ?: ""
aux["adp_ib"] = if (adPodInfo["isBumper"] == true) "true" else "false"
aux["adp_md"] = adPodInfo["maxDuration"] as String? ?: ""
aux["adp_pi"] = adPodInfo["podIndex"] as String? ?: ""
aux["adp_to"] = adPodInfo["timeOffset"] as String? ?: ""
aux["adp_ta"] = adPodInfo["totalAds"] as String? ?: ""
}
}
// LineItem xld + xst
this._lineItemCode = params["lineItemCode"] as String? // required
if (this._adUnitCode != null && this._lineItemCode != null) {
val lineItemAux: MutableMap = mutableMapOf(
"at" to (params["adType"] as String? ?: ""),
"ap" to (params["adPosition"] as String? ?: "").toLowerCase(),
"ar" to (params["adRequestUrl"] as String? ?: ""))
if (this._adProperties != null) {
lineItemAux["sdk"] = this._adProperties!!["sdk"] as String? ?: ""
}
if (!this._lineItemLoggedXld) {
this._lineItemLoggedXld = true;
this._logExtensionEvent("xld", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", lineItemAux.toMap());
}
if (!this._lineItemLoggedXst) {
this._lineItemLoggedXst = true;
this._logExtensionEvent("xst", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", lineItemAux.toMap());
}
}
// Creative xit
if (this._adProperties != null && this._adProperties!!["adPodInfo"] != null) {
val adProperties = this._adProperties!! // short-hand
val adPodInfo = adProperties["adPodInfo"]!! as Map
if (this._creativeLoggedXit_pi != adPodInfo["podIndex"]) { // once per pod
this._creativeLoggedXit_pi = adPodInfo["podIndex"] as String?
val isBumper_str = if (adPodInfo["isBumper"] == true) "true" else "false"
val maxDuration = adPodInfo["maxDuration"] as String? ?: ""
val podIndex = adPodInfo["podIndex"] as String? ?: ""
val timeOffset = adPodInfo["timeOffset"] as String? ?: ""
val totalAds = adPodInfo["totalAds"] as String? ?: ""
var totalAds_int = 0;
try {
totalAds_int = totalAds.toInt()
} catch(er: Exception) {}
for (adPosition_int in 1..totalAds_int) {
val adPosition = "$adPosition_int"
val creativeId = this._constructCreativeId(adPosition, timeOffset, this._lineItemCode ?: "") // NB!
if (this._cuePointTuples["#"+timeOffset] == null) this._cuePointTuples["#"+timeOffset] = mutableListOf()
this._cuePointTuples["#"+timeOffset]?.add(Pair(adPosition, creativeId))
this._logCreativeXit(creativeId, aux["at"], aux["ap"], aux["ar"], aux["sdk"], isBumper_str, maxDuration, podIndex, totalAds, timeOffset, adPosition)
}
}
} else {
if (!this._creativeLoggedXit) {
this._creativeLoggedXit = true;
if (params["adType"] == "vms") { // VMS "creative" system
this._creativeId = params["vmsClipId"] as String? ?: "" // default?
} else { // Other ad systems
this._creativeId = this._constructCreativeId(params["adType"] as String? ?: "", params["adPosition"] as String? ?: "", this._lineItemCode ?: ""); // haha
}
val podIndex = if (aux["ap"] == "postroll") "-1" else (if (aux["ap"] == "preroll") "0" else "0")
val timeOffset = if (aux["ap"] == "postroll") "-1" else "0"
this._logCreativeXit(this._creativeId, aux["at"], aux["ap"], aux["ar"], aux["sdk"], "false", "0", podIndex, timeOffset, "1", "1")
}
}
// Creative xls + xld
aux["auc"] = this._adUnitCode ?: ""
aux["lic"] = this._lineItemCode ?: ""
if (this._adProperties != null && this._adProperties!!["adPodInfo"] != null) {
val adProperties = this._adProperties!! // short-hand
val adPodInfo = adProperties["adPodInfo"]!! as Map
val adPosition = adPodInfo["adPosition"] as String? ?: "" // NB?!?
val timeOffset = adPodInfo["timeOffset"] as String? ?: ""
if (this._cuePointTuples["#"+timeOffset] != null) {
for ((adPos, creativeId) in this._cuePointTuples["#"+timeOffset]!!) {
if (adPos == adPosition) this._creativeId = creativeId
}
}
}
if (this._creativeId != null) {
this._logExtensionEvent("xls", this._creativeId!!, "Creative", this._viewId, "View", aux); // obsolete?
this._logExtensionEvent("xld", this._creativeId!!, "Creative", this._viewId, "View", aux);
}
}
EventName.bb_adsystem_started -> {
if (params.containsKey("adProperties")) this._adProperties = params["adProperties"] as Map // else this._adProperties = null;
if (this._adProperties != null) {
aux["sdk"] = this._adProperties!!["sdk"] as String? ?: ""
}
this._adSystemStarted = true
// LineItem xst
if (params["lineItemCode"] != null) this._lineItemCode = params["lineItemCode"] as String?
if (this._adUnitCode != null && this._lineItemCode != null) {
val lineItemAux = aux.toMap() // copy
if (!this._lineItemLoggedXst) {
this._lineItemLoggedXst = true;
this._logExtensionEvent("xst", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", lineItemAux)
}
}
// Creative xst
// nothing; logged later in _onAdSystemQuartile case 0
}
EventName.bb_adsystem_clicked -> {
if (params["__pretend"] != null) aux["hmm"] = "1" // WTF?!?
// LineItem xcl
if (this._adUnitCode != null) {
if (params["lineItemCode"] != null) this._lineItemCode = params["lineItemCode"] as String?
val lineItemAux = aux.toMap() // copy
if (this._lineItemCode != null && !this._lineItemLoggedXst) {
this._lineItemLoggedXst = true
this._logExtensionEvent("xcl", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", lineItemAux)
}
aux["auc"] = this._adUnitCode!! // for Creative logging below
aux["lic"] = this._lineItemCode ?: "" // for Creative logging below
}
// Creative xcl
if (this._creativeId != null) {
this._logExtensionEvent( "xcl", this._creativeId!!, "Creative", this._viewId, "View", aux)
}
}
EventName.bb_adsystem_skipped -> {
this._adSystemStarted = false;
// LineItem xsk
if (this._adUnitCode != null) {
if (params["lineItemCode"] != null) this._lineItemCode = params["lineItemCode"] as String?
val lineItemAux = aux.toMap() // copy
if (this._lineItemCode != null) {
this._logExtensionEvent("xsk", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", lineItemAux)
}
aux["auc"] = this._adUnitCode!! // for Creative logging below
aux["lic"] = this._lineItemCode ?: "" // for Creative logging below
}
// Creative xsk
if (this._creativeId != null) {
this._logExtensionEvent( "xsk", this._creativeId!!, "Creative", this._viewId, "View", aux)
}
}
EventName.bb_adsystem_noad -> {
if (params.containsKey("adProperties")) this._adProperties = params["adProperties"] as Map // else this._adProperties = null;
if (this._adProperties != null) {
val adProperties = this._adProperties!! // short-hand
val errorCode = adProperties["errorCode"]
val errorMessage = adProperties["errorMessage"]
aux["why"] = if (errorMessage != null) "AdError $errorCode: $errorMessage" else "No ad found"
if (adProperties["adId"] != null) aux["adi"] = adProperties["adId"] as String
if (adProperties["adTitle"] != null) aux["adt"] = adProperties["adTitle"] as String
if (adProperties["adSystem"] != null) aux["ads"] = adProperties["adSystem"] as String
if (adProperties["adLinear"] != null) aux["adl"] = adProperties["adLinear"] as String
if (adProperties["duration"] != null) aux["add"] = adProperties["duration"] as String
if (adProperties["sdk"] != null) aux["sdk"] = adProperties["sdk"] as String
}
if (params["timeout"] != null) {
var timeout_double = 0.0
try {
timeout_double = (params["timeout"]!! as String).toDouble()
} catch(er: Exception) {}
aux["why"] = "timeout after " + (timeout_double/1000) + "s"
}
this._adSystemStarted = false;
// LineItem xpf
if (this._adUnitCode != null) {
if (params["lineItemCode"] != null) this._lineItemCode = params["lineItemCode"] as String?
val lineItemAux = aux.toMap() // copy
if (this._lineItemCode != null) {
this._logExtensionEvent("xpf", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", lineItemAux)
}
aux["auc"] = this._adUnitCode!! // for Creative logging below
aux["lic"] = this._lineItemCode ?: "" // for Creative logging below
}
// Creative xpf
if (this._creativeId == null) this._creativeId = this._constructCreativeId(params["adType"] as String? ?: "", params["adPosition"] as String? ?: "", this._lineItemCode ?: ""); // haha
if (this._adProperties != null && this._adProperties!!["adPodInfo"] != null) {
// nothing
} else {
if (!this._creativeLoggedXit) {
this._creativeLoggedXit = true
val podIndex = if (aux["ap"] == "postroll") "-1" else (if (aux["ap"] == "preroll") "0" else "0")
val timeOffset = if (aux["ap"] == "postroll") "-1" else "0"
this._logCreativeXit(this._creativeId, aux["at"], aux["ap"], aux["ar"], aux["sdk"], "false", "0", podIndex, timeOffset, "1", "1")
}
}
if (this._creativeId != null) {
this._logExtensionEvent( "xpf", this._creativeId!!, "Creative", this._viewId, "View", aux)
}
}
EventName.bb_adsystem_failed -> {
if (params.containsKey("adProperties")) this._adProperties = params["adProperties"] as Map // else this._adProperties = null;
if (this._adProperties != null) {
val adProperties = this._adProperties!! // short-hand
val errorCode = adProperties["errorCode"]
val errorMessage = adProperties["errorMessage"]
aux["why"] = if (errorMessage != null) "AdError $errorCode: $errorMessage" else "Ad system failed"
if (adProperties["adId"] != null) aux["adi"] = adProperties["adId"] as String
if (adProperties["adTitle"] != null) aux["adt"] = adProperties["adTitle"] as String
if (adProperties["adSystem"] != null) aux["ads"] = adProperties["adSystem"] as String
if (adProperties["adLinear"] != null) aux["adl"] = adProperties["adLinear"] as String
if (adProperties["duration"] != null) aux["add"] = adProperties["duration"] as String
if (adProperties["sdk"] != null) aux["sdk"] = adProperties["sdk"] as String
}
this._adSystemStarted = false;
// LineItem xpf
if (this._adUnitCode != null) {
if (params["lineItemCode"] != null) this._lineItemCode = params["lineItemCode"] as String?
val lineItemAux = aux.toMap() // copy
if (this._lineItemCode != null) {
this._logExtensionEvent("xpf", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", lineItemAux)
}
aux["auc"] = this._adUnitCode!! // for Creative logging below
aux["lic"] = this._lineItemCode ?: "" // for Creative logging below
}
// Creative xpf
if (this._creativeId == null) this._creativeId = this._constructCreativeId(params["adType"] as String? ?: "", params["adPosition"] as String? ?: "", this._lineItemCode ?: ""); // haha
if (this._adProperties != null && this._adProperties!!["adPodInfo"] != null) {
// nothing
} else {
if (!this._creativeLoggedXit) {
this._creativeLoggedXit = true
val podIndex = if (aux["ap"] == "postroll") "-1" else (if (aux["ap"] == "preroll") "0" else "0")
val timeOffset = if (aux["ap"] == "postroll") "-1" else "0"
this._logCreativeXit(this._creativeId, aux["at"], aux["ap"], aux["ar"], aux["sdk"], "false", "0", podIndex, timeOffset, "1", "1")
}
}
if (this._creativeId != null) {
this._logExtensionEvent( "xpf", this._creativeId!!, "Creative", this._viewId, "View", aux)
}
}
EventName.bb_adsystem_finished -> {
this._adSystemStarted = false;
// LineItem xfn
if (this._adUnitCode != null) {
if (params["lineItemCode"] != null) this._lineItemCode = params["lineItemCode"] as String?
val lineItemAux = aux.toMap() // copy
if (this._lineItemCode != null) {
this._logExtensionEvent("xfn", this._lineItemCode!!, "LineItem", this._adUnitCode, "AdUnit", lineItemAux)
}
}
// Creative xfn
// nothing; logged earlier in _onAdSystemQuartile case 4
}
EventName.bb_adsystem_quartile -> {
var quartile_int = 0
try {
quartile_int = (params["quartile"] as String? ?: "0").toInt()
} catch(er: Exception) {}
val percentage = quartile_int * 25
aux["pct"] = "$percentage"
// Creative xst/xpg/xfn
aux["auc"] = this._adUnitCode ?: ""
aux["lic"] = this._lineItemCode ?: ""
if (this._creativeId != null) {
when (quartile_int) {
0 -> {
this._logExtensionEvent("xst", this._creativeId!!, "Creative", this._viewId, "View", aux);
}
4 -> {
this._logExtensionEvent("xfn", this._creativeId!!, "Creative", this._viewId, "View", aux);
}
else -> {
this._logExtensionEvent("xpg", this._creativeId!!, "Creative", this._viewId, "View", aux);
}
}
}
}
else -> {}
}
}
private fun _logCreativeXit(creativeId: String?, at: String?, ap: String?, ar: String?, sdk: String?, isBumper_str: String = "false", maxDuration: String = "0", podIndex: String = "0", timeOffset: String = "0", totalAds: String = "1", adPosition: String = "1") {
val creativeXitAux: MutableMap = mutableMapOf(
"at" to (at as String? ?: ""),
"ap" to (ap as String? ?: ""),
"ar" to (ar as String? ?: ""),
"sdk" to (sdk as String? ?: ""),
"auc" to (this._adUnitCode ?: ""),
"lic" to (this._lineItemCode ?: ""),
"adp_ap" to adPosition,
"adp_ib" to isBumper_str,
"adp_md" to maxDuration,
"adp_pi" to podIndex,
"adp_to" to timeOffset,
"adp_ta" to totalAds
)
if (creativeId != null) {
this._logExtensionEvent("xit", creativeId!!, "Creative", this._viewId, "View", creativeXitAux)
}
}
private fun _determineReferrer() {
// nothing
}
// OBSOLETE; FOR TESTING ONLY
fun logMediaClipEvent(event: String, aux: Map = emptyMap()) {
this._logMediaClipEvent(event, aux)
}
private fun _logMediaClipEvent(event: String, aux: Map = emptyMap()) {
if (this._clip != null) {
Logger.d(TAG + "_logMediaClipEvent","Mediaclip initialized $event")
val now = Clock.System.now().toEpochMilliseconds()
val properties: MutableMap = mutableMapOf()
properties["vu"] = this._uuid
properties["pm"] = this._playMode
properties["sid"] = this._sessionId
//properties["prid"] = if (::projectId.isInitialized) this.projectId else this._clip.projectId ?: ""
properties["prid"] = if (this._projectId != null) this._projectId!! else (this._clip!!.projectId ?: "")
properties["ts"] = (now + this._timestampOffset++).toString()
properties["pp"] = this._publicationName
properties["pt"] = this._playoutName
properties["pv"] = "native ${Platform.platform} 7.x"
properties["ev"] = event
properties["id"] = "${this._clip!!.id}"
properties["ct"] = "${this._clip!!.title}"
properties["pd"] = "${this._clip!!.publisheddate}"
properties["vs"] = "n/a"
properties["rs"] = "bogusXbogus"// screen.width+"x"+screen.height
properties["fs"] = if (this._fullScreen) "1" else "0"
if (this._clip?.mediatype?.isNotEmpty() == true) properties["mt"] =
"${this._clip!!.mediatype}"
if (this._clip!!.mediatype_override?.isNotEmpty() == true) properties["mt"] =
"${this._clip!!.mediatype_override}"
if (this._clip!!.length?.isNotEmpty() == true) properties["du"] =
round(this._clip!!.length!!.toDouble() * 1000).toString() // this.duration
if (this._clip!!.sourcetype?.isNotEmpty() == true) properties["sot"] =
"${this._clip!!.sourcetype}"
if (this._dvid != null) properties["_dvid"] = this._dvid!!
for ((key, value) in aux) {
if (!properties.containsKey(key)) properties[key] = value
}
if (event.equals("se") || event.equals("rs")) {
// Commented debug statement, but keeping because it's easy to test with, but relatively heavy for production...
// Logger.d(TAG + "_logMediaClipEvent","Sending $event event to debouncer; $properties")
_debouncedEventListMap[event]?.tryEmit(properties)
} else {
this._logEvent(properties)
}
} else {
Logger.d(TAG + "_logMediaClipEvent","Mediaclip not initialized")
}
}
private fun _logExtensionEvent(event: String, id: String, entityType: String, pid: String?, pet: String?, aux: Map = emptyMap()) {
val now = Clock.System.now().toEpochMilliseconds()
val properties: MutableMap = mutableMapOf()
properties["pm"] = if (entityType == "Creative" || entityType == "AdUnit" || entityType == "LineItem") this._commercialPlayMode else this._playMode
properties["sid"] = this._sessionId
//properties["prid"] = if (::projectId.isInitialized) this.projectId else this._clip.projectId ?: ""
properties["prid"] = if (this._projectId != null) this._projectId!! else (this._clip!!.projectId ?: "")
properties["ts"] = (now + this._timestampOffset++).toString()
properties["pp"] = this._publicationName
properties["ev"] = event // should start with "x", denoting "extension"
properties["id"] = id
properties["et"] = entityType
properties["cid"] = this._clip?.id ?: "null"
properties["pid"] = pid ?: "null" // default conform javascript
properties["pet"] = pet ?: "null" // default conform javascript
// TODO Is there even a thing that can block ads?
// properties["abd"] = this.adBlockDetected
for ((key, value) in aux) {
if (!properties.containsKey(key)) properties[key] = value
}
this._logEvent( properties )
}
private fun _logErrorEvent(event: String, aux: Map = emptyMap()) {
var faked = false
if (this._clip == null) {
val jsonParser = Json { ignoreUnknownKeys = true; isLenient = true; serializersModule = contentItemSerializersModule }
this._clip = jsonParser.decodeFromString("{\"id\":0, \"projectId\":0, \"date\":{\"published\":\"\"}}")
faked = true
}
this._logMediaClipEvent(event, aux)
if (faked) {
this._clip = null
}
}
private fun _logEvent(properties: MutableMap) {
Logger.d(TAG + "_logEvent", "Is logging enabled? $_enabled")
if (this._enabled) {
this._logEvent_enabled(properties)
} else { // disabled
this._enabledQueue.add(properties)
}
}
private fun _logEvent_enabled(properties: MutableMap) {
properties["sn"] = "$_serialNumber" // why not _serialNumber.toString()?
var queryString = ""
for ((prop, value) in properties) {
queryString += if (queryString.isEmpty()) "?" else "&"
queryString += prop + "=" + value.encodeURLPath()
}
queryString = queryString.replace("-", "%2d")
queryString = queryString.replace("~~00~~", "%7E%7E00%7E%7E").replace("ad", "~~00~~")
queryString = queryString.replace("~~01~~", "%7E%7E01%7E%7E").replace("aD", "~~01~~")
queryString = queryString.replace("~~02~~", "%7E%7E02%7E%7E").replace("Ad", "~~02~~")
queryString = queryString.replace("~~03~~", "%7E%7E03%7E%7E").replace("AD", "~~03~~")
_serialNumber++
val url = _loggerUrl + queryString
launch(Dispatchers.Default) {
// Logger.d(TAG + "_logEvent_enabled", "Running query for: $url")
val success: Boolean = client.get {
url(Url(url))
}.status.isSuccess()
if (success) {
Logger.d(TAG + "_logEvent_enabled", "Sent successfully to url: $url")
}
}
}
private fun _determineProgressInterval(): Double {
// determines the interval of the amount of times the progress event is fired based on the mediaclip duration.
var interval: Double = 10.0
val clipDuration: Double = this._clip?.length?.toDouble() ?: 0.0
val interval_max = 10.0 // minimum of one log event once per 'max' seconds
if (clipDuration > 0) {
val ticks = 10 // every 10 percent (NB: 100 / 10)
val ticks_min = 5 // minimum of once every 20%
var interval_min: Double = min(clipDuration / ticks_min, 3.0) // maximum one log event once per 'min' seconds
if (this._isReplay) interval_min = 4.0 // no need for very specific events when replaying short clips
interval = if (this._logProgressAsQuartiles) (clipDuration / 40) else min(max(interval_min, (clipDuration / ticks)), interval_max)
} else {
interval = if (this._logProgressAsQuartiles) 1.0 else interval_max // default
}
return interval
}
private fun _handleProgressTimerTick() {
val to = this._getTimeOffset()
if (to == this._to) { // no change
val now = Clock.System.now().toEpochMilliseconds()
if (this._to_num <= 0) this._to_num = 1
if (this._to_now <= 0) this._to_now = now
if (this._to_num++ > 60) return // bail out if no change for 60 logs
if (now > (this._to_now + 3600000)) return // bail out if no change for 1 hour
} else { // change
this._to = to
this._to_num = 0
this._to_now = 0
}
val wpa: String = if (this._paused) "true" else "false"
if (this._logProgressAsQuartiles) {
val quartile: Int = if (this._duration > 0) floor(4 * to / (this._duration * 1000)).toInt() else 0
while (this._quartile < quartile) { // NB ensure all previous quartiles are also hit!
this._quartile++
val pct: String = (this._quartile * 25).toString()
this._logMediaClipEvent("pg", mapOf("pct" to pct, "pet" to "Session", "pid" to this._playerSessionId, "wpa" to wpa))
}
} else { // normal progress
this._logMediaClipEvent("pg", mapOf("to" to to.toString(), "pet" to "Session", "pid" to this._playerSessionId, "wpa" to wpa))
}
}
private fun _getTimeOffset(): Long {
if (this._timeOffset <= 0) this._timeOffset = 0
if (this._currentTime <= 0.0) this._currentTime = 0.0
this._timeOffset = min(this._timeOffset+60000, (this._currentTime * 1000).roundToLong())
return this._timeOffset
}
// OBSOLETE; FOR TESTING ONLY
fun ingestOpts(opts: EmbedObject?) {
this._ingestOpts(opts)
}
private fun _ingestOpts(opts: EmbedObject?) {
if (opts != null) {
// publication parameters
if (opts.publicationData != null) {
if (!opts.publicationData.name.isNullOrEmpty()) this._publicationName = opts.publicationData.name
}
// playout settings
if (opts.playoutData != null) {
if (opts.playoutData.name != null) this._playoutName = opts.playoutData.name
if (opts.playoutData.disableCookies != null) this._disableCookies = opts.playoutData.disableCookies
if (opts.playoutData.logProgressAsQuartiles != null) this._logProgressAsQuartiles = (opts.playoutData.logProgressAsQuartiles == "true")
if (opts.playoutData.autoPlay != null) this._autoPlay = (opts.playoutData.autoPlay == "true")
if (opts.playoutData.autoMute != null) this._autoMute = (opts.playoutData.autoMute == "true")
if (opts.playoutData.autoLoop != null) this._autoLoop = (opts.playoutData.autoLoop == "true")
}
// content
if (opts.clipData != null) {
this._clip = opts.clipData
this._clip_isInitial = true // flag to prevent prevClipId
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy