Skip to main content

Chromecast

Chromecast on Quickplay platform qualifies as a device on its own and can perform device registration, content authorization and playback similar to any other conventional devices. The client sender facilitates sharing required information for device registration and content authorization to the receiver and the receiver processes the same for playback.

Overview

Casting a media using Quickplay platform requires the following to be performed in a sequence:

  • Initialize Cast Manager
  • Send User Device Information message to Receiver
  • Load Media onto Receiver

The above sequence of steps needs to be performed invariably for a new cast session inclusive of both Connect & Play and Play & Connect scenarios.

info

It is advised that the initialization of cast manager and sharing user device information be performed during application launch and load media be performed on demand.

Cast Manager

CastManager uses Google Cast Application Framework's (CAF) SessionManager instance to manage CastSession and coordinate all the Cast interactions. CastManager provides access to CastPlayer that helps in initiating and controlling playback on the Web Receiver Application, and enforces policies for accessing Quickplay Platform resources.

Initializing CastOptions

The Sender Application must implement the com.google.android.gms.cast.framework.OptionsProvider interface to supply CastOptions needed to initialize the CastContext singleton. The most important option is the Receiver Application ID, which is used to filter Cast Device discovery results and to launch the Receiver Application when a CastSession is started.


import android.content.Context
import com.google.android.gms.cast.framework.OptionsProvider

class CastOptionsProvider : OptionsProvider {

override fun getCastOptions(context: Context): CastOptions {
return CastOptions.Builder()
.setReceiverApplicationId(context.getString(R.string.app_receiver_id))
.build()
}

override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}

The OptionsProvider must then be declared within the "application" tag of the app's AndroidManifest.xml file:


<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.sample.cast.CastOptionsProvider" />

Initializing CastManager

Initializing the CastManager is the first and foremost step in setting Chromecast up. It is advised to perform initialization right at application launch. CastManager is using CAF's global singleton object, the CastContext, which coordinates all the Cast interactions. When the CastManager instance is created the Sender Application MUST also register CastManagerStateListener that provides notifications for the CastManager and CAF's CastSession events.


class CastSampleActivity : AppCompatActivity() {

private lateinit var castManager: CastManager
private var castManagerStateListener: CastManagerStateListener = object : CastManagerStateListener {
override fun onCastDeviceAvailable() {
//Handle as required
}
override fun onSessionStarted(castDeviceID: String) {
//Handle as required
}
override fun onSessionStartError(error: Error) {
//Handle as required
}
override fun onSessionSuspended() {
//Handle as required
}
override fun onSessionResumed(castDeviceID: String) {
//Handle as required
}
override fun onSessionEnded(error: Error) {
//Handle as required
}
override fun onDeviceRegistrationError(error: Error) {
//Handle as required
}
override fun onDeviceRegistered() {
//Handle as required
}
override fun onPlaybackAuthorisationError(error: Error) {
//Handle as required
}
override fun onPlaybackAuthorised() {
//Handle as required
}
override fun onCastManagerInitializationSuccess() {
//Handle as required
}
override fun onCastManagerInitializationFailure(error: Error) {
//Handle as required
}
override fun onCustomError(error: Error){
//Handle as required
}
}

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_cast_sample)
initViews()
castManager = CastManager(this) // pass context
castManager.registerCastManagerStateListener(castManagerStateListener)
}
}

note

In some cases, CastManager cannot be initialized because of incompatible device or Google Play Services version. Any failure / success in initialization of CastManager will be notified via the registered CastManagerStateListener.

Cast Session Management

For the CAF a CastSession combines the steps of connecting to a device, launching (or joining), connecting to the Receiver Application, and initializing a media control channel if appropriate. The media control channel is how the Cast framework sends and receives messages from the Receiver Media Player. The CastSession will be started automatically, when user clicks the Cast button and selects a device from the list, and will be stopped automatically when user disconnects. Reconnecting to a receiver session due to networking issues is also automatically handled by the Cast SDK.

Device Registration

The Sender Application requires CastPlayer instance to initiate content playback, but before casting any media, the Cast Device must be registered with Quickplay Platform. The status of Device Registration will be notified to the sender application via the attached CastManagerStateListener.

Send User Device Information

Once a session has been established successfully, one must share the User Device Information to the Receiver. This is a mandate step, so that the receiver can register the device with Quickplay platform and perform authorization while loading media.

The Sender Application should call CastManager.registerUserData to perform the user data, that includes the Cast Device ID, registration. The registration should be done just after CastManager notifies the Sender Application that the CastSession has be started. After the user data registration the Sender Application should proceed with loading media on the Receiver Application. If the user data registration failed then the Sender Application will be notified, with an error, when the first attempt is made to load media on the Receiver Application.


private var castManagerStateListener: CastManagerStateListener = object : CastManagerStateListener {
private fun defaultClient(): PlatformClient {
return object : PlatformClient {
override val id: String = "client-id"
override val type: String = "androidmobile"
}
}

private suspend fun acquireUserAuthorizationData(): Result<UserAuthorizationData, Error> =
suspendCancellableCoroutine { continuation ->
acquireUserAuthorizationDataInner(
Callback { userAuthData, error ->
userAuthData?.let {
continuation.resume(Result.Success(it))
} ?: error?.let {
continuation.resume(Result.Failure(it))
}
}
)
}

private fun acquireUserAuthorizationDataInner(
callback: Callback<UserAuthorizationData, Error>
) =
platformAuthorizer.userAuthorizationDelegate.fetchUserAuthorizationToken(
Callback { authData, error ->
check(authData != null || error != null) {
"Either UserAuthorizationData instance or Error instance should be non-null"
}
authData?.let {
callback.complete(it, null)
} ?: error?.let {
callback.complete(null, it)
}
}
)

override fun onSessionStarted(castDeviceID: String) {
val coroutineScope = CoroutineScope(Dispatchers.IO)
coroutineScope.launch {
when (val result = acquireUserAuthorizationData()) {
is Result.Success -> {
authToken = result.value.accessToken
val platformClient = defaultClient()
withContext(Dispatchers.Main) {
castManager.registerUserData(
platformClient,
castDeviceID,
authToken
)
}
}
is Result.Failure -> {
val error = result.value
logger.warn { "Failed to acquire user access token: $error" }
}
}
}
}
}

Load and start playback

The CastPlayer is analogous to the remote media player of the Receiver Application. The Sender Application can retrieve CastPlayer instance, from the CastManager, once the CastSession has been established. The Sender Application then should create a PlatformAsset which describes the media that needs to be authorized with Quickplay Platform and played upon successful authorization. The Sender Application should also provide MediaMetadata and, optionally, a collection of MediaTracks while loading media.

CastPlatformAsset and CastAsset

CastPlatformAsset encapsulates all the information required by receiver to authorize the content and start the playback.

Property NameTypeMandatory?Description
mediaIDStringYesThe unique id of the content to cast.
consumptionTypeConsumptionTypeYesThe ConsumptionType of the content. It can be ConsumptionType.LIVE or ConsumptionType.VOD.
catalogTypeStringYesThe catalog type of the content.
mediaTypeMediaTypeYesThe MediaType of the content. It can be MediaType.DASH, MediaType.SMOOTH_STREAMING or MediaType.HLS.
drmSchemeDrmTypeNoThe DrmType of the content. Default value for the Android platform is DrmType.WIDEVINE.
playbackModePlaybackMode?NoOne of the PlaybackMode values i.e. PlaybackMode.LIVE, PlaybackMode.RESTART or PlaybackMode.CATCHUP. This is mandatory only for the live playback.
eventIdString?NoThe unique identifier of the live event/channel.
startTimeString?NoThe live event start time in ISO 8601 format(in milliseconds). This is mandatory when playing in restart and catchup modes, otherwise not required.
endTimeString?NoThe live event end time in ISO 8601 format(in milliseconds). This is mandatory when playing in catchup mode, otherwise not required..
initialPlaybackPositionMsLongNoThe playback position for cast player to start playback from (in milliseconds). By default, the playback will start from 0 for VOD and live edge for live playbacks.
val castPlatformAsset = CastPlatformAsset(
"myMediaID", // pass preferred value
ConsumptionType.LIVE, // pass preferred value
"myCatalogType", // pass preferred value
MediaType.DASH, // pass preferred value
DrmType.WIDEVINE, // pass preferred value
PlaybackMode.RESTART, // pass preferred value. mandatory for any LIVE playback.
"myEventID", // pass preferred value
"9999-12-30T12:59:59Z", // program start time in yyyy-MM-dd'T'HH:mm:ss'Z' format. mandatory for RESTART and CATCHUP playbacks.
"9999-12-31T12:59:59Z", // program end time in yyyy-MM-dd'T'HH:mm:ss'Z' format. mandatory for RESTART and CATCHUP playbacks.
)

CastAsset binds additional information along with the CastPlatformAsset. This object should necessarily be passed to the loadMedia API that starts the playback.

Property NameTypeMandatory?Description
userAuthTokenStringYesThe user authentication token.
platformAssetCastPlatformAssetYesThe CastPlatformAsset object associated with the playback.
headersMap<String, String>?NoThe Key-Value pair sent as HTTP Request Header.
flAnalyticsDataMap<String, Any>?NoAny additional analytics attributes that has to be shared with chromecast receiver.
videoAnalyticsDataMap<String, Any>?NoAny additional video analytics metadata that has to be shared with chromecast receiver.
customAdMetadataMap<String, Any>?NoAny additional ad metadata that has to be shared with chromecast receiver.
// any additional custom metadata for analytics. below is the expected schema for New Relic:
val flAnalyticsData = mapOf(
"appBuild" to "testId",
"channelID" to "myChannelID",
"channelName" to "myChannelName",
"contentID" to "myContentID",
"contentName" to "myContentName",
"pageID" to "myContent",
"seriesName" to "mySeriesName",
"serviceID" to "myServiceID",
"silentLogin" to "true",
"tabName" to "myTabName"
)

// any additional custom metadata for video analytics. below is the expected schema for Conviva:
val videoAnalyticsData = mapOf(
"viewerId" to "userID / mobileNumber / emailId", // mandatory
"assetName" to "myAssetName", // mandatory
"tags" to mapOf("customTagKey" to "customTagValue") // optional key value pairs to provide additional information about the content.
)

// any additional custom metadata for ads. below is the expected schema:
val customAdMetadata = mapOf(
"rdId" to "myAndroidAdID", // mandatory - resettable device ID.
"idType" to "adid", // mandatory - the type of device identifier (always adid for Android).
"isLat" to "0" // mandatory - boolean flag to limit ad tracking (pass preferred value - 0 or 1)
)

val castAsset = CastAsset(
authToken, // user authentication token from UMS
castPlatformAsset, // corresponding castPlatformAsset object
headers, // any optional HTTP headers to be passed
flAnalyticsData, // optional custom metadata for analytics with New Relic
videoAnalyticsData, // optional custom metadata for video analytics with Conviva
customAdMetadata // optional custom metadata for ad supported content
)

After building the CastAsset and MediaMetadata objects along with a successful CastSession, the sender application can try to start loading using the CastPlayer.loadMedia API.

val mediaMetadata = MediaMetadata(
MediaMetadata.MEDIA_TYPE_MOVIE // use appropriate media type. Refer com.google.android.gms.cast.MediaMetadata.
)
castManager.castPlayer?.let {
it.loadMedia(castAsset, mediaMetadata)
}

The Receiver Application upon receiving this information attempts to authorize the asset with Quickplay Platform and plays the content.

Cast Player UI

The CastPlayer extends Player interface and provides the benefit of having the same APIs as conventional Player from FL Player library. The integrator has complete control over UI rendering and enjoys the luxury of integrating the Google CAF SDK directly and use the UI widgets from CAF SDK. Most of the integrators prefer using their own UI for the player which can double up as a remote Cast player and a local player. This makes the player integration a breeze as both players have the same interface.