diff --git a/app/src/main/assets/config.propertiesTimer.xml b/app/src/main/assets/config.propertiesTimer.xml new file mode 100644 index 0000000..45ff2ea --- /dev/null +++ b/app/src/main/assets/config.propertiesTimer.xml @@ -0,0 +1,4 @@ + + + 9 + \ No newline at end of file diff --git a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt index b6905c8..394a05f 100644 --- a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt +++ b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt @@ -10,7 +10,6 @@ https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view package com.fivegmag.a5gmsmediasessionhandler import android.app.Service -import android.content.Context import android.content.Intent import android.os.* import android.util.Log @@ -19,16 +18,15 @@ import com.fivegmag.a5gmscommonlibrary.helpers.PlayerStates import com.fivegmag.a5gmscommonlibrary.helpers.SessionHandlerMessageTypes import com.fivegmag.a5gmscommonlibrary.helpers.Utils import com.fivegmag.a5gmscommonlibrary.models.ClientConsumptionReportingConfiguration -import com.fivegmag.a5gmscommonlibrary.models.EntryPoint -import com.fivegmag.a5gmscommonlibrary.models.PlaybackConsumptionReportingConfiguration import com.fivegmag.a5gmscommonlibrary.models.PlaybackRequest import com.fivegmag.a5gmscommonlibrary.models.ServiceAccessInformation import com.fivegmag.a5gmscommonlibrary.models.ServiceListEntry +import com.fivegmag.a5gmscommonlibrary.models.EntryPoint +import com.fivegmag.a5gmscommonlibrary.models.PlaybackConsumptionReportingConfiguration import com.fivegmag.a5gmsmediasessionhandler.models.ClientSessionModel +import com.fivegmag.a5gmsmediasessionhandler.network.ConsumptionReportingApi import com.fivegmag.a5gmsmediasessionhandler.network.HeaderInterceptor import com.fivegmag.a5gmsmediasessionhandler.network.ServiceAccessInformationApi -import com.fivegmag.a5gmsmediasessionhandler.network.ConsumptionReportingApi - import okhttp3.Headers import okhttp3.MediaType import okhttp3.OkHttpClient @@ -37,10 +35,15 @@ import retrofit2.Call import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory - -import java.util.Timer +import java.lang.Long.min +import java.text.SimpleDateFormat import java.util.TimerTask +import java.util.TimeZone +import java.util.Timer +import java.util.Date +import java.util.Properties +import kotlin.math.abs const val TAG = "5GMS Media Session Handler" @@ -49,6 +52,7 @@ const val TAG = "5GMS Media Session Handler" * or to expose some of your application's functionality to other applications through interprocess communication (IPC). */ class MediaSessionHandlerMessengerService() : Service() { + private var defaultServiceAccessInformationTimerVal : Long = 0 /** * Target we publish for clients to send messages to IncomingHandler. @@ -68,10 +72,7 @@ class MediaSessionHandlerMessengerService() : Service() { /** * Handler of incoming messages from clients. */ - inner class IncomingHandler( - context: Context, - private val applicationContext: Context = context.applicationContext - ) : Handler() { + inner class IncomingHandler : Handler() { override fun handleMessage(msg: Message) { when (msg.what) { @@ -132,7 +133,7 @@ class MediaSessionHandlerMessengerService() : Service() { private fun initializeConsumptionReportingTimer(clientId: Int, delay: Long? = null) { setConsumptionReportingEndpoint(clientId) - // Do not start the consumption reporting timer if we dont have an endpoint + // Do not start the consumption reporting timer if we don't have an endpoint if (clientsSessionData[clientId]?.consumptionReportingApi == null) { return } @@ -153,7 +154,7 @@ class MediaSessionHandlerMessengerService() : Service() { bundle.classLoader = ServiceListEntry::class.java.classLoader val serviceListEntry: ServiceListEntry? = bundle.getParcelable("serviceListEntry") val responseMessenger: Messenger = msg.replyTo - val sendingUid = msg.sendingUid; + val sendingUid = msg.sendingUid resetClientSessionData(sendingUid) @@ -169,7 +170,6 @@ class MediaSessionHandlerMessengerService() : Service() { call: Call, response: Response ) { - val resource = handleServiceAccessResponse(response, sendingUid, provisioningSessionId) @@ -309,17 +309,17 @@ class MediaSessionHandlerMessengerService() : Service() { } // if sample percentage is set to 0 or no server addresses are available stop consumption reporting - if (updatedClientConsumptionReportingConfiguration.samplePercentage!! <= 0) { + if (updatedClientConsumptionReportingConfiguration.samplePercentage <= 0) { stopConsumptionReportingTimer(clientId) } // if sample percentage is set to 100 start consumption reporting - if (updatedClientConsumptionReportingConfiguration.samplePercentage!! >= 100) { + if (updatedClientConsumptionReportingConfiguration.samplePercentage >= 100) { startConsumptionReportingTimer(clientId) } // if sample percentage was previously zero and is now higher than zero evaluate again - if (updatedClientConsumptionReportingConfiguration.samplePercentage!! > 0 && previousClientConsumptionReportingConfiguration.samplePercentage <= 0 && shouldReportAccordingToSamplePercentage( + if (updatedClientConsumptionReportingConfiguration.samplePercentage > 0 && previousClientConsumptionReportingConfiguration.samplePercentage <= 0 && shouldReportAccordingToSamplePercentage( updatedClientConsumptionReportingConfiguration.samplePercentage ) ) { @@ -376,56 +376,118 @@ class MediaSessionHandlerMessengerService() : Service() { sendingUid: Int, provisioningSessionId: String ) { - val cacheControlHeader = headers.get("cache-control") ?: return - val cacheControlHeaderItems = cacheControlHeader.split(',') - val maxAgeHeader = cacheControlHeaderItems.filter { it.trim().startsWith("max-age=") } + // Get MaxAge. If an age header is present, that value shall be subtracted from the value defined in max-age. + var periodByMaxAgeHeader = getMaxAge(headers) - if (maxAgeHeader.isEmpty()) { - return + val ageHeader = headers.get("Age") + if(null != ageHeader && -1 != periodByMaxAgeHeader.toInt()) { + periodByMaxAgeHeader -= ageHeader.toLong() } + + // get Expires + val periodByExpiresHeader = getExpires(headers) + + // get RefreshTimerValue and start timer + val periodInSec: Long = getRefreshTimerValue(periodByMaxAgeHeader, periodByExpiresHeader,defaultServiceAccessInformationTimerVal) - val maxAgeValue = maxAgeHeader[0].trim().substring(8).toLong() val timer = Timer() clientsSessionData[sendingUid]?.serviceAccessInformationRequestTimer = timer timer.schedule( - object : TimerTask() { - override fun run() { - val call: Call? = - clientsSessionData[sendingUid]?.serviceAccessInformationApi?.fetchServiceAccessInformation( - provisioningSessionId, - clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders?.get( - "etag" - ), - clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders?.get( - "last-modified" + object : TimerTask() { + override fun run() { + val call: Call? = + clientsSessionData[sendingUid]?.serviceAccessInformationApi?.fetchServiceAccessInformation( + provisioningSessionId, + clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders?.get( + "etag" + ), + clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders?.get( + "last-modified" + ) ) - ) - call?.enqueue(object : retrofit2.Callback { - override fun onResponse( - call: Call, - response: Response - ) { + call?.enqueue(object : retrofit2.Callback { + override fun onResponse( + call: Call, + response: Response + ) { + handleServiceAccessResponse( + response, + sendingUid, + provisioningSessionId + ) + } + + override fun onFailure( + call: Call, + t: Throwable + ) { + call.cancel() + } + }) + } + }, + periodInSec * 1000 + ) + } - handleServiceAccessResponse( - response, - sendingUid, - provisioningSessionId - ) - } - - override fun onFailure( - call: Call, - t: Throwable - ) { - call.cancel() - } - }) - } - }, - maxAgeValue * 1000 - ) + /** + * For the cacheControlHeader and expiresHeader: + * 1.In case both max-age and expires are present the values shall be translated to absolute times, compared and the earlier value shall be used. + * 2.In case only one of the headers is present, use it. + * 3.In case none of the headers are defined, we use a static configurable refresh value like 10 minutes. + */ + private fun getRefreshTimerValue( + periodByMaxAgeHeader: Long, + periodByExpiresHeader: Long, + defaultPeriodInSec: Long + ): Long { + var periodInSec: Long = defaultPeriodInSec + if (-1 != periodByMaxAgeHeader.toInt() + && -1 != periodByExpiresHeader.toInt() + ) { + periodInSec = min(periodByMaxAgeHeader, periodByExpiresHeader) + } else if (-1 != periodByMaxAgeHeader.toInt()) { + periodInSec = periodByMaxAgeHeader + } else if (-1 != periodByExpiresHeader.toInt()) { + periodInSec = periodByExpiresHeader + } + + return periodInSec + } + + private fun getExpires(headers: Headers): Long { + var periodByExpiresHeader : Long = -1 + val dateInExpHeader = headers.get("Expires") + if (null != dateInExpHeader) { + val dateFormat = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z") + dateFormat.timeZone = TimeZone.getTimeZone("UTC") + + val currentDate = dateFormat.format(Date(System.currentTimeMillis())) + + val currentFormattedDate = dateFormat.parse(currentDate) + val expHeaderFormattedDate = dateFormat.parse(dateInExpHeader) + val difference = abs(currentFormattedDate!!.time - expHeaderFormattedDate!!.time) + periodByExpiresHeader = difference / 1000 + } + + return periodByExpiresHeader + } + + private fun getMaxAge(headers: Headers): Long { + var periodByMaxAgeHeader : Long = -1 + val cacheControlHeader = headers.get("cache-control") + if (null != cacheControlHeader) { + val cacheControlHeaderItems = cacheControlHeader.split(',') + val maxAgeHeader = cacheControlHeaderItems.filter { it.trim().startsWith("max-age=") } + + if (maxAgeHeader.isNotEmpty()) { + periodByMaxAgeHeader = maxAgeHeader[0].trim().substring(8).toLong() + } + } + + return periodByMaxAgeHeader } private fun setConsumptionReportingEndpoint(clientId: Int) { @@ -454,7 +516,6 @@ class MediaSessionHandlerMessengerService() : Service() { } private fun startConsumptionReportingTimer(clientId: Int, delay: Long? = null) { - // Endpoint not set if (clientsSessionData[clientId]?.consumptionReportingApi == null) { setConsumptionReportingEndpoint(clientId) @@ -591,7 +652,11 @@ class MediaSessionHandlerMessengerService() : Service() { try { val bundle: Bundle = msg.data val m5BaseUrl: String? = bundle.getString("m5BaseUrl") - Log.i(TAG, "Setting M5 endpoint to $m5BaseUrl") + + val configPropertiesDefualtTimer : Properties = Utils().loadConfiguration(this.assets,"config.propertiesTimer.xml") + defaultServiceAccessInformationTimerVal = configPropertiesDefualtTimer.getProperty("defaultServiceAccessInformationTimerVal").toLong() + Log.i(TAG, "Setting M5 endpoint to $m5BaseUrl, defaultServiceAccessInformationTimerVal is $defaultServiceAccessInformationTimerVal") + if (m5BaseUrl != null) { val retrofit = retrofitBuilder .baseUrl(m5BaseUrl) @@ -616,7 +681,7 @@ class MediaSessionHandlerMessengerService() : Service() { */ override fun onBind(intent: Intent): IBinder? { Log.i("MediaSessionHandler-New", "Service bound new") - mMessenger = Messenger(IncomingHandler(this)) + mMessenger = Messenger(IncomingHandler()) return mMessenger.binder } } \ No newline at end of file