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.
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)
}
}
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 MediaTrack
s while loading media.
CastPlatformAsset and CastAsset
CastPlatformAsset
encapsulates all the information required by receiver to authorize the content and start the playback.
Property Name | Type | Mandatory? | Description |
---|---|---|---|
mediaID | String | Yes | The unique id of the content to cast. |
consumptionType | ConsumptionType | Yes | The ConsumptionType of the content. It can be ConsumptionType.LIVE or ConsumptionType.VOD . |
catalogType | String | Yes | The catalog type of the content. |
mediaType | MediaType | Yes | The MediaType of the content. It can be MediaType.DASH , MediaType.SMOOTH_STREAMING or MediaType.HLS . |
drmScheme | DrmType | No | The DrmType of the content. Default value for the Android platform is DrmType.WIDEVINE . |
playbackMode | PlaybackMode? | No | One of the PlaybackMode values i.e. PlaybackMode.LIVE , PlaybackMode.RESTART or PlaybackMode.CATCHUP . This is mandatory only for the live playback. |
eventId | String? | No | The unique identifier of the live event/channel. |
startTime | String? | No | The live event start time in ISO 8601 format(in milliseconds). This is mandatory when playing in restart and catchup modes, otherwise not required. |
endTime | String? | No | The live event end time in ISO 8601 format(in milliseconds). This is mandatory when playing in catchup mode, otherwise not required.. |
initialPlaybackPositionMs | Long | No | The 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 Name | Type | Mandatory? | Description |
---|---|---|---|
userAuthToken | String | Yes | The user authentication token. |
platformAsset | CastPlatformAsset | Yes | The CastPlatformAsset object associated with the playback. |
headers | Map<String, String>? | No | The Key-Value pair sent as HTTP Request Header. |
flAnalyticsData | Map<String, Any>? | No | Any additional analytics attributes that has to be shared with chromecast receiver. |
videoAnalyticsData | Map<String, Any>? | No | Any additional video analytics metadata that has to be shared with chromecast receiver. |
customAdMetadata | Map<String, Any>? | No | Any 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.