All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.com.bluebillywig.bbnativeshared.loggers.BlueBillywigLogger.kt Maven / Gradle / Ivy

There is a newer version: 8.15.0
Show newest version
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