Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds functionality to re-request the Service Access Information #31

Merged
merged 2 commits into from
Aug 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ android {
minSdk 29
targetSdk 33
versionCode 1
versionName "1.0.1"
versionName "1.0.2"

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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<Int>()
private var clientsSessionData = HashMap<Int, ClientSessionModel>()
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.
Expand All @@ -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) {
Expand All @@ -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<ServiceAccessInformation>? =
serviceAccessInformationApi.fetchServiceAccessInformation(provisioningSessionId)

clientsSessionData[msg.sendingUid]?.serviceAccessInformationApi?.fetchServiceAccessInformation(
provisioningSessionId,
null,
null
)
call?.enqueue(object : retrofit2.Callback<ServiceAccessInformation?> {
override fun onResponse(
call: Call<ServiceAccessInformation?>,
response: Response<ServiceAccessInformation?>
) {
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<EntryPoint>? = 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<ServiceAccessInformation?>, t: Throwable) {
Expand All @@ -132,14 +149,136 @@ class MediaSessionHandlerMessengerService() : Service() {
})
}

/**
* Starts the timer task to re-request and saves the current state of the Service Access Information
*
* @param response
* @param sendingUid
* @param provisioningSessionId
* @return
*/
private fun handleServiceAccessResponse(
response: Response<ServiceAccessInformation?>,
sendingUid: Int,
provisioningSessionId: String
): ServiceAccessInformation? {
val headers = response.headers()


// Save the ServiceAccessInformation if it has changed
val resource: ServiceAccessInformation? = response.body()
if (resource != null && response.code() != 304 && 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<ServiceAccessInformation>? =
clientsSessionData[sendingUid]?.serviceAccessInformationApi?.fetchServiceAccessInformation(
provisioningSessionId,
clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders?.get("etag"),
clientsSessionData[sendingUid]?.serviceAccessInformationResponseHeaders?.get("last-modified")
)

call?.enqueue(object : retrofit2.Callback<ServiceAccessInformation?> {
override fun onResponse(
call: Call<ServiceAccessInformation?>,
response: Response<ServiceAccessInformation?>
) {

handleServiceAccessResponse(
response,
sendingUid,
provisioningSessionId
)
}

override fun onFailure(
call: Call<ServiceAccessInformation?>,
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 {
val bundle: Bundle = msg.data
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) {
}
Expand All @@ -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
Expand All @@ -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
}


}
Original file line number Diff line number Diff line change
@@ -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
)
Original file line number Diff line number Diff line change
Expand Up @@ -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<ServiceAccessInformation>?

fun fetchServiceAccessInformation(
@Path("provisioningSessionId") provisioningSessionId: String?,
@Header("If-None-Match") ifNoneMatch: String?,
@Header("If-Modified-Since") ifModifiedSince: String?
): Call<ServiceAccessInformation>?
}