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

5GMS Consumption reporting: Add support to Media Session Handler #38

Merged
merged 13 commits into from
Sep 26, 2023
Merged
1 change: 1 addition & 0 deletions .idea/gradle.xml

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

Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ 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.ConsumptionReporting
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 com.fivegmag.a5gmsmediasessionhandler.network.ConsumptionReportingApi

import okhttp3.Headers
import okhttp3.ResponseBody
import okhttp3.OkHttpClient
import retrofit2.Call
import retrofit2.Response
Expand All @@ -32,9 +36,12 @@ import retrofit2.converter.gson.GsonConverterFactory
import java.util.Timer
import java.util.TimerTask

import kotlin.random.Random

const val TAG = "5GMS Media Session Handler"

const val SamplePercentage_100 = 100;

/**
* Create a bound service when you want to interact with the service from activities and other components in your application
* or to expose some of your application's functionality to other applications through interprocess communication (IPC).
Expand All @@ -46,6 +53,14 @@ class MediaSessionHandlerMessengerService() : Service() {
*/
private lateinit var mMessenger: Messenger
private var clientsSessionData = HashMap<Int, ClientSessionModel>()

private lateinit var serviceAccessInformationApi: ServiceAccessInformationApi
//private lateinit var currentServiceAccessInformation: ServiceAccessInformation
private var currentServiceAccessInformation: ServiceAccessInformation? = null

private lateinit var consumptionReportingApi: ConsumptionReportingApi
private lateinit var serverAddressesForConsumpReport: String

private val headerInterceptor = HeaderInterceptor()
private val utils = Utils()
private val okHttpClient = OkHttpClient()
Expand Down Expand Up @@ -74,6 +89,7 @@ class MediaSessionHandlerMessengerService() : Service() {
)

SessionHandlerMessageTypes.SET_M5_ENDPOINT -> setM5Endpoint(msg)
SessionHandlerMessageTypes.CONSUMPTION_REPORTING_MESSAGE -> reportConsumption(msg)
else -> super.handleMessage(msg)
}
}
Expand Down Expand Up @@ -123,6 +139,15 @@ class MediaSessionHandlerMessengerService() : Service() {
val resource =
handleServiceAccessResponse(response, sendingUid, provisioningSessionId)

// initialize Retrofit for ConsumptionReporting
currentServiceAccessInformation = resource
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are maintaining multiple concurrent sessions in the Media Session Handler. This means that multiple applications can be connected to the same Media Session Handler (see handling in handleServiceAccessResponse). There is no need to define a variable currentServiceAccessInformation, you can use resourcedirectly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are maintaining multiple concurrent sessions in the Media Session Handler. This means that multiple applications can be connected to the same Media Session Handler (see handling in handleServiceAccessResponse). There is no need to define a variable currentServiceAccessInformation, you can use resourcedirectly.

Because in some Consumption reporting related methods such as IsConsumptionReportingActivated and NeedReportConsumption, clientConsumptionReportingConfiguration of ServiceAccessInformation is need. So I need to save it as a member of the class MediaSessionHandlerMessengerService. I can only use resource directly only in handleStartPlaybackByServiceListEntryMessage.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is what clientsSessionData can be used for:

clientsSessionData[sendingUid]?.serviceAccessInformation

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Got it, thanks. Done

if(currentServiceAccessInformation!!.clientConsumptionReportingConfiguration.serverAddresses.isNotEmpty())
{
val serverAddressesForConsumpReport: String = currentServiceAccessInformation!!.clientConsumptionReportingConfiguration.serverAddresses[0];
Log.i(TAG, ">>>shilin: clientConsumptionReportingConfiguration serverAddresses0: $serverAddressesForConsumpReport.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

initializeRetrofitForConsumpReport(serverAddressesForConsumpReport)
}

// Trigger the playback by providing all available entry points
val msgResponse: Message = Message.obtain(
null,
Expand All @@ -144,6 +169,7 @@ class MediaSessionHandlerMessengerService() : Service() {
}

override fun onFailure(call: Call<ServiceAccessInformation?>, t: Throwable) {
Log.i(TAG, ">>>shilin: debug onFailure")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

call.cancel()
}
})
Expand Down Expand Up @@ -302,5 +328,128 @@ class MediaSessionHandlerMessengerService() : Service() {
return mMessenger.binder
}

/* Refer to TS26.512 Clause 4.7.4
The Service Access Information indicating whether Consumption Reporting is provisioned for downlink streaming
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shorten this description

sessions is described in clause 11.2.3. When the clientConsumptionReportingConfiguration.samplePercentage
value is 100, the Media Session Handler shall activate the consumption reporting procedure.
If the samplePercentage is less than 100, the Media Session Handler shall generate a random number
which is uniformly distributed in the range of 0 to 100, and the Media Session Handler shall activate
the consumption report procedure when the generated random number is of a lower value than
the samplePercentage value.The Service Access Information indicating whether Consumption Reporting
is provisioned for downlink streaming sessions is described in clause 11.2.3. When the
clientConsumptionReportingConfiguration.samplePercentage value is 100, the Media Session Handler
shall activate the consumption reporting procedure. If the samplePercentage is less than 100,
the Media Session Handler shall generate a random number which is uniformly distributed in
the range of 0 to 100, and the Media Session Handler shall activate the consumption report
procedure when the generated random number is of a lower value than the samplePercentage value.
The Service Access Information indicating whether Consumption Reporting is provisioned for downlink streaming sessions is described in clause 11.2.3. When the clientConsumptionReportingConfiguration.samplePercentage value is 100, the Media Session Handler shall activate the consumption reporting procedure. If the samplePercentage is less than 100, the Media Session Handler shall generate a random number which is uniformly distributed in the range of 0 to 100, and the Media Session Handler shall activate the consumption report procedure when the generated random number is of a lower value than the samplePercentage value.
*/
private fun IsConsumptionReportingActivated(): Boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename function to isConsumptionReportingActivated

//if(currentServiceAccessInformation.isInitialized) {
if(currentServiceAccessInformation == null) {
return false
}

var samplePercentage: UInt = currentServiceAccessInformation!!.clientConsumptionReportingConfiguration.samplePercentage;
if(samplePercentage > SamplePercentage_100.toUInt())
{
Log.i(TAG, "shilin>>>Invaild samplePercentage $samplePercentage in currentServiceAccessInformation")
return false;
}

if(SamplePercentage_100.toUInt() == samplePercentage)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use this function to check if reporting should be activated or not according to samplePercentage:

      private fun shouldReportAccordingToSamplePercentage(samplePercentage: Float?): Boolean {
            if (samplePercentage != null && samplePercentage <= 0) {
                return false
            }

            if (samplePercentage == null || samplePercentage >= 100.0) {
                return true
            }

            return utils.generateRandomFloat() < samplePercentage

        }

{
return true;
}

val myRandomValue = Random.nextInt(0, SamplePercentage_100)
if(myRandomValue.toUInt() >= samplePercentage)
{
Log.i(TAG, "shilin>>>IsConsumptionReportingActivated true:myRandomValue[$myRandomValue],samplePercentage[$samplePercentage]")
return true;
}
else
{
Log.i(TAG, "shilin>>>IsConsumptionReportingActivated false:myRandomValue[$myRandomValue],samplePercentage[$samplePercentage]")
return false;
}
}

/* Refer to TS26.512 Clause 4.7.4
TS26.501 Clause 5.6.3*/
private fun NeedReportConsumption(): Boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rename function to needReportConsumption

// check IsConsumptionReportingActivated
if (!IsConsumptionReportingActivated())
{
Log.i(TAG, "shilin>>>ReportConsumption not Activated")
return false;
}

// Start/stop of consumption of a downlink streaming session
// triggered when PlayerStates.PLAYING & PlayerStates.ENDED
/*if( Start || stop)
{
return true;
}*/

// check clientConsumptionReportingConfiguration.reportingInterval, start a timer
// reportConsumptionTimer()

// check clientConsumptionReportingConfiguration.locationReporting
// check clientConsumptionReportingConfiguration.accessReporting
if(currentServiceAccessInformation!!.clientConsumptionReportingConfiguration.locationReporting
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is consumption reporting only send if locationReporting or accessReporting is true?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just follow clause 4.7.4, as below:
If the consumption reporting procedure is activated, the Media Session Handler shall submit a consumption report to the 5GMSd AF when any of the following conditions occur:

  • Start of consumption of a downlink streaming session;
  • Stop of consumption of a downlink streaming session;
  • Upon determining the need to report ongoing 5GMS consumption at periodic intervals determined by the clientConsumptionReportingConfiguration.reportingInterval property.
    - Upon determining a location change, if the clientConsumptionReportingConfiguration.locationReporting property is set to True.
    - Upon determining an access network change (e.g. unicast to eMBMS, or vice versa), if the clientConsumptionReportingConfiguration.accessReporting property is set to True.

Did I misunderstand it?

|| currentServiceAccessInformation!!.clientConsumptionReportingConfiguration.accessReporting)
{
return true;
}

return false;
}

private fun reportConsumption(msg: Message) {
if (!NeedReportConsumption())
{
Log.i(TAG, "shilin>>>Not need ReportConsumption")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove all shilin from the logs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

return
}

val bundle: Bundle = msg.data
bundle.classLoader = ConsumptionReporting::class.java.classLoader
//val data: String = bundle.getString("ConsumptionData", "")
val dataReporting: ConsumptionReporting? = bundle.getParcelable("consumptionData")

Log.i(TAG, "reportConsumption ClientId: ${dataReporting?.reportingClientId}.")
Toast.makeText(
applicationContext,
"MSH recv Consumption-ID: ${dataReporting?.reportingClientId}",
Toast.LENGTH_LONG
).show()

// call m5 report consumption to AF - TS26.512 Clause 4.7.4
val aspId: String = "2";
val call: Call<ResponseBody>? = consumptionReportingApi.postConsumptionReporting(aspId,dataReporting?.reportingClientId);

call?.enqueue(object : retrofit2.Callback<ResponseBody> {
override fun onResponse(
call: Call<ResponseBody?>,
response: Response<ResponseBody?>
) {
Log.i(TAG, "shilin>>resp from AF:"+ response.body()?.string())
//System.out.println(">>>>>>>>>>>>"+response.body());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}

override fun onFailure(call: Call<ResponseBody?>, t: Throwable) {
Log.i(TAG, "shilin>>onFailure")
call.cancel()
}
})
}

private fun initializeRetrofitForConsumpReport(url: String) {
val m5RetrofitConsump: Retrofit = Retrofit.Builder()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

retrofitBuilder is already initialized you can use

    val retrofit = retrofitBuilder
                        .baseUrl(m5BaseUrl)
                        .build()

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

.baseUrl(url)
//.addConverterFactory(GsonConverterFactory.create())
.build()
consumptionReportingApi = m5RetrofitConsump.create(ConsumptionReportingApi::class.java)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be bound to a specific client is as there are multiple applications connected to the same Media Session Handler. As an example for the Service Access Information:

clientsSessionData[msg.sendingUid]?.serviceAccessInformationApi = retrofit.create(ServiceAccessInformationApi::class.java)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,18 @@ https://drive.google.com/file/d/1cinCiA778IErENZ3JN52VFW-1ffHpx7Z/view

package com.fivegmag.a5gmsmediasessionhandler.network

import com.fivegmag.a5gmscommonlibrary.models.ConsumptionReporting
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.Path
import retrofit2.http.Field;
import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.POST

interface ConsumptionReportingApi {
@FormUrlEncoded
@POST("consumption-reporting/{aspId}")
fun postConsumptionReporting(@Path("aspId") aspId: String?, @Field("data") data: String?): Call<ResponseBody>?
//fun postConsumptionReporting(@Path("aspId") aspId: String?, @Field("data") data: String ): Call<String>?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}