From 61b28f6998070439239bcd1f6eaece966dd21f03 Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Wed, 2 Aug 2023 16:03:34 +0200 Subject: [PATCH 1/2] Adds functionality to re-request the Service Access Information based on the max-age header. Moreover, sends previous "etag" and "last-modified" header values as "If-None-Match" and "If-Modified-Since" request headers. --- .idea/compiler.xml | 2 +- .idea/misc.xml | 3 +- app/build.gradle | 4 +- .../MediaSessionHandlerMessengerService.kt | 201 ++++++++++++++---- .../models/ClientSessionModel.kt | 15 ++ .../network/ServiceAccessInformationApi.kt | 9 +- 6 files changed, 185 insertions(+), 49 deletions(-) create mode 100644 app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/models/ClientSessionModel.kt diff --git a/.idea/compiler.xml b/.idea/compiler.xml index fb7f4a8..b589d56 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index bdd9278..773fe0f 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,7 +1,6 @@ - - + diff --git a/app/build.gradle b/app/build.gradle index c10b29f..e4ead25 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -12,7 +12,7 @@ android { minSdk 29 targetSdk 33 versionCode 1 - versionName "1.0.1" + versionName "1.0.2" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -43,7 +43,7 @@ dependencies { androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' // 5GMAG - implementation 'com.fivegmag:a5gmscommonlibrary:1.0.1' + implementation 'com.fivegmag:a5gmscommonlibrary:1.0.2' // Retrofit def retrofit_version = "2.9.0" diff --git a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt index 65534b6..784b4df 100644 --- a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt +++ b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt @@ -16,16 +16,21 @@ import android.os.* import android.util.Log import android.widget.Toast import com.fivegmag.a5gmscommonlibrary.helpers.SessionHandlerMessageTypes +import com.fivegmag.a5gmscommonlibrary.helpers.Utils import com.fivegmag.a5gmscommonlibrary.models.EntryPoint import com.fivegmag.a5gmscommonlibrary.models.ServiceAccessInformation import com.fivegmag.a5gmscommonlibrary.models.ServiceListEntry +import com.fivegmag.a5gmsmediasessionhandler.models.ClientSessionModel import com.fivegmag.a5gmsmediasessionhandler.network.HeaderInterceptor import com.fivegmag.a5gmsmediasessionhandler.network.ServiceAccessInformationApi +import okhttp3.Headers import okhttp3.OkHttpClient import retrofit2.Call import retrofit2.Response import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory +import java.util.Timer +import java.util.TimerTask const val TAG = "5GMS Media Session Handler" @@ -40,11 +45,16 @@ class MediaSessionHandlerMessengerService() : Service() { * Target we publish for clients to send messages to IncomingHandler. */ private lateinit var mMessenger: Messenger - private lateinit var serviceAccessInformationApi: ServiceAccessInformationApi - private lateinit var currentServiceAccessInformation: ServiceAccessInformation - - /** Keeps track of all current registered clients. */ - var mClients = ArrayList() + private var clientsSessionData = HashMap() + private val headerInterceptor = HeaderInterceptor() + private val utils = Utils() + private val okHttpClient = OkHttpClient() + .newBuilder() + .addInterceptor(headerInterceptor) + .build() + private val retrofitBuilder = Retrofit.Builder() + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) /** * Handler of incoming messages from clients. @@ -69,11 +79,11 @@ class MediaSessionHandlerMessengerService() : Service() { } private fun registerClient(msg: Message) { - mClients.add(msg.sendingUid) + clientsSessionData[msg.sendingUid] = ClientSessionModel(msg.replyTo) } private fun unregisterClient(msg: Message) { - mClients.remove(msg.sendingUid) + clientsSessionData.remove(msg.sendingUid) } private fun handleStatusMessage(msg: Message) { @@ -94,36 +104,43 @@ class MediaSessionHandlerMessengerService() : Service() { bundle.classLoader = ServiceListEntry::class.java.classLoader val serviceListEntry: ServiceListEntry? = bundle.getParcelable("serviceListEntry") val responseMessenger: Messenger = msg.replyTo + val sendingUid = msg.sendingUid; + resetClientSession(sendingUid) + val provisioningSessionId: String = serviceListEntry!!.provisioningSessionId val call: Call? = - serviceAccessInformationApi.fetchServiceAccessInformation(provisioningSessionId) - + clientsSessionData[msg.sendingUid]?.serviceAccessInformationApi?.fetchServiceAccessInformation( + provisioningSessionId, + null, + null + ) call?.enqueue(object : retrofit2.Callback { override fun onResponse( call: Call, response: Response ) { - val resource: ServiceAccessInformation? = response.body() - if (resource != null) { - currentServiceAccessInformation = resource - } + + val resource = + handleServiceAccessResponse(response, sendingUid, provisioningSessionId) + + // Trigger the playback by providing all available entry points val msgResponse: Message = Message.obtain( null, SessionHandlerMessageTypes.SESSION_HANDLER_TRIGGERS_PLAYBACK ) var finalEntryPoints: ArrayList? = serviceListEntry.entryPoints - if (finalEntryPoints == null || finalEntryPoints.size == 0) { + if (resource != null && (finalEntryPoints == null || finalEntryPoints.size == 0)) { finalEntryPoints = - currentServiceAccessInformation.streamingAccess.entryPoints + resource.streamingAccess.entryPoints } - - val bundle = Bundle() + val responseBundle = Bundle() if (finalEntryPoints != null && finalEntryPoints.size > 0) { - bundle.putParcelableArrayList("entryPoints", finalEntryPoints) - msgResponse.data = bundle + responseBundle.putParcelableArrayList("entryPoints", finalEntryPoints) + msgResponse.data = responseBundle responseMessenger.send(msgResponse) } + } override fun onFailure(call: Call, t: Throwable) { @@ -132,6 +149,124 @@ class MediaSessionHandlerMessengerService() : Service() { }) } + /** + * Starts the timer task to re-request and saves the current state of the Service Access Information e + * + * @param response + * @param sendingUid + * @param provisioningSessionId + * @return + */ + private fun handleServiceAccessResponse( + response: Response, + sendingUid: Int, + provisioningSessionId: String + ): ServiceAccessInformation? { + val headers = response.headers() + + + // Save the ServiceAccessInformation if it has changed + val resource: ServiceAccessInformation? = response.body() + if (resource != null && utils.hasResponseChanged( + headers, + clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders + ) + ) { + clientsSessionData[sendingUid]?.serviceAccessInformation = resource + } + + + // Start the re-requesting of the Service Access Information according to the max-age header + startServiceAccessInformationUpdateTimer( + headers, + sendingUid, + provisioningSessionId + ) + + // Save current headers + clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders = headers + + return clientsSessionData[sendingUid]?.serviceAccessInformation + } + + + /** + * Starts the timer task to re-request the the Service Access Information + * + * @param headers + * @param sendingUid + * @param provisioningSessionId + */ + private fun startServiceAccessInformationUpdateTimer( + headers: Headers, + sendingUid: Int, + provisioningSessionId: String + ) { + val cacheControlHeader = headers.get("cache-control") ?: return + val cacheControlHeaderItems = cacheControlHeader.split(','); + val maxAgeHeader = cacheControlHeaderItems.filter { it.trim().startsWith("max-age=") } + + if (maxAgeHeader.isEmpty()) { + return + } + + 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") + ) + + 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() + } + }) + } + }, + maxAgeValue * 1000 + ) + + } + + /** + * Reset a client session once a new playback session is started. Remove the ServiceAccessInformation + * for the corresponding client id and reset all metric reporting timers. + * + * @param clientId + */ + private fun resetClientSession(clientId: Int) { + if (clientsSessionData[clientId] != null) { + Log.i(TAG, "Resetting information for client $clientId") + clientsSessionData[clientId]?.serviceAccessInformation = null + clientsSessionData[clientId]?.serviceAccessInformationRequestTimer?.cancel() + clientsSessionData[clientId]?.serviceAccessInformationRequestTimer = null + clientsSessionData[clientId]?.serviceAccessInformationResponseHeaders = null + } + } + private fun setM5Endpoint(msg: Message) { try { @@ -139,7 +274,11 @@ class MediaSessionHandlerMessengerService() : Service() { val m5BaseUrl: String? = bundle.getString("m5BaseUrl") Log.i(TAG, "Setting M5 endpoint to $m5BaseUrl") if (m5BaseUrl != null) { - initializeRetrofitForServiceAccessInformation(m5BaseUrl) + val retrofit = retrofitBuilder + .baseUrl(m5BaseUrl) + .build() + clientsSessionData[msg.sendingUid]?.serviceAccessInformationApi = + retrofit.create(ServiceAccessInformationApi::class.java) } } catch (e: Exception) { } @@ -151,23 +290,6 @@ class MediaSessionHandlerMessengerService() : Service() { } - private fun initializeRetrofitForServiceAccessInformation(url: String) { - val headerInterceptor = HeaderInterceptor() - val okHttpClient = OkHttpClient() - .newBuilder() - .addInterceptor(headerInterceptor) - .build() - - val retrofitServiceAccessInformation: Retrofit = Retrofit.Builder() - .client(okHttpClient) - .baseUrl(url) - .addConverterFactory(GsonConverterFactory.create()) - .build() - - serviceAccessInformationApi = - retrofitServiceAccessInformation.create(ServiceAccessInformationApi::class.java) - } - /** * When binding to the service, we return an interface to our messenger * for sending messages to the service. To create a bound service, you must define the interface that specifies @@ -176,12 +298,9 @@ class MediaSessionHandlerMessengerService() : Service() { */ override fun onBind(intent: Intent): IBinder? { Log.i("MediaSessionHandler-New", "Service bound new") - return initializeMessenger() - } - - private fun initializeMessenger(): IBinder? { mMessenger = Messenger(IncomingHandler(this)) return mMessenger.binder } + } \ No newline at end of file diff --git a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/models/ClientSessionModel.kt b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/models/ClientSessionModel.kt new file mode 100644 index 0000000..aa23232 --- /dev/null +++ b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/models/ClientSessionModel.kt @@ -0,0 +1,15 @@ +package com.fivegmag.a5gmsmediasessionhandler.models + +import android.os.Messenger +import com.fivegmag.a5gmscommonlibrary.models.ServiceAccessInformation +import com.fivegmag.a5gmsmediasessionhandler.network.ServiceAccessInformationApi +import okhttp3.Headers +import java.util.Timer + +data class ClientSessionModel( + var messenger: Messenger?, + var serviceAccessInformation: ServiceAccessInformation? = null, + var serviceAccessInformationApi: ServiceAccessInformationApi? = null, + var serviceAccessInformationResponseHeaders: Headers? = null, + var serviceAccessInformationRequestTimer: Timer? = null +) \ No newline at end of file diff --git a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/network/ServiceAccessInformationApi.kt b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/network/ServiceAccessInformationApi.kt index 7605688..3a21d4c 100644 --- a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/network/ServiceAccessInformationApi.kt +++ b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/network/ServiceAccessInformationApi.kt @@ -12,11 +12,14 @@ package com.fivegmag.a5gmsmediasessionhandler.network import com.fivegmag.a5gmscommonlibrary.models.ServiceAccessInformation import retrofit2.Call import retrofit2.http.GET +import retrofit2.http.Header import retrofit2.http.Path interface ServiceAccessInformationApi { - @GET("service-access-information/{provisioningSessionId}") - fun fetchServiceAccessInformation(@Path("provisioningSessionId") provisioningSessionId: String?): Call? - + fun fetchServiceAccessInformation( + @Path("provisioningSessionId") provisioningSessionId: String?, + @Header("If-None-Match") ifNoneMatch: String?, + @Header("If-Modified-Since") ifModifiedSince: String? + ): Call? } \ No newline at end of file From 8d521abd1ca87775a6493d84eb9e679be875b8e2 Mon Sep 17 00:00:00 2001 From: Daniel Silhavy Date: Thu, 3 Aug 2023 07:59:51 +0200 Subject: [PATCH 2/2] Check for response code when updating the ServiceAccessInformation. Should be different from 304 --- .../MediaSessionHandlerMessengerService.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt index 784b4df..d9192f8 100644 --- a/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt +++ b/app/src/main/java/com/fivegmag/a5gmsmediasessionhandler/MediaSessionHandlerMessengerService.kt @@ -150,7 +150,7 @@ class MediaSessionHandlerMessengerService() : Service() { } /** - * Starts the timer task to re-request and saves the current state of the Service Access Information e + * Starts the timer task to re-request and saves the current state of the Service Access Information * * @param response * @param sendingUid @@ -167,7 +167,7 @@ class MediaSessionHandlerMessengerService() : Service() { // Save the ServiceAccessInformation if it has changed val resource: ServiceAccessInformation? = response.body() - if (resource != null && utils.hasResponseChanged( + if (resource != null && response.code() != 304 && utils.hasResponseChanged( headers, clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders )