For Dummies · Beginner-Friendly · Step‑by‑Step
Integrating Chromecast (Google Cast) into your Android app can feel daunting, especially if you’re new to media streaming. In this guide, you’ll learn how to:
- Set up the Google Cast SDK in Kotlin
- Retrieve the total duration of your media file
- Implement a seek bar so users can skip forward or rewind
Let’s break it down into simple, actionable steps!
📋 Prerequisites
Before you begin, make sure you have:
- Android Studio (Arctic Fox or newer)
- Kotlin configured in your project
- Internet permission in
AndroidManifest.xml
:<uses-permission android:name="android.permission.INTERNET" />
- A basic MediaSessionCompat or ExoPlayer setup (optional, for local playback fallback).
🔧 1. Add Required Dependencies
Open your app-level build.gradle
and add:
dependencies {
implementation "com.google.android.gms:play-services-cast:22.1.0"
implementation "com.google.android.gms:play-services-cast-framework:22.1.0"
}
- play-services-cast: core Cast APIs
- cast-framework: UI widgets and lifecycle helpers
Sync your project after saving.
🚀 2. Initialize CastContext
Early
CastContext
is the entry point for all Cast operations. Initialize it in your custom Application
class:
class MyApp : Application() {
override fun onCreate() {
super.onCreate()
// This warms up the Cast SDK
CastContext.getSharedInstance(this)
}
}
Register it in AndroidManifest.xml
:
<application
android:name=".MyApp"
…>
<!-- other entries -->
</application>
🎛️ 3. Add a Cast Button to Your Toolbar
Let users discover available Cast devices:
- menu_main.xml
<item android:id="@+id/media_route_menu_item" android:title="@string/cast" app:actionProviderClass="androidx.mediarouter.app.MediaRouteActionProvider" app:showAsAction="always" />
- In Your Activity
override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.menu_main, menu) val mediaRouteItem = menu.findItem(R.id.media_route_menu_item) val provider = mediaRouteItem.actionProvider as MediaRouteActionProvider provider.routeSelector = CastContext.getSharedInstance(this).mergedSelector return true }
🛠️ 4. Listen for Cast Sessions & Get a RemoteMediaClient
In a service (e.g., CastService
) or your main Activity, set up a SessionManagerListener
:
private lateinit var sessionManager: SessionManager
private var remoteClient: RemoteMediaClient? = null
private val sessionListener = object : SessionManagerListener<CastSession> {
override fun onSessionStarted(session: CastSession, id: String) {
remoteClient = session.remoteMediaClient
remoteClient?.registerCallback(mediaCallback)
remoteClient?.addProgressListener(progressListener, 1000L)
}
override fun onSessionEnded(session: CastSession, error: Int) {
remoteClient?.unregisterCallback(mediaCallback)
remoteClient?.removeProgressListener(progressListener)
remoteClient = null
}
// other overrides can be empty…
}
// In onCreate or onStart:
sessionManager = CastContext.getSharedInstance(this).sessionManager
sessionManager.addSessionManagerListener(sessionListener, CastSession::class.java)
⏱️ 5. Retrieve Media Duration & Current Position
Use a ProgressListener to get real‑time updates:
private val _streamDuration = MutableLiveData<Long>()
val streamDuration: LiveData<Long> = _streamDuration
private val _streamPosition = MutableLiveData<Long>()
val streamPosition: LiveData<Long> = _streamPosition
private val progressListener = RemoteMediaClient.ProgressListener { posMs, durMs ->
_streamPosition.postValue(posMs)
_streamDuration.postValue(durMs)
}
posMs
= current position (ms)durMs
= total duration (ms)
Observe these LiveData in your UI to update a SeekBar and duration TextView:
viewModel.streamDuration.observe(this) { dur ->
seekBar.max = dur.toInt()
durationTextView.text = formatMs(dur)
}
viewModel.streamPosition.observe(this) { pos ->
seekBar.progress = pos.toInt()
positionTextView.text = formatMs(pos)
}
Helper to format ms → mm:ss:
fun formatMs(ms: Long): String {
val totalSec = ms / 1000
val min = totalSec / 60
val sec = totalSec % 60
return "%02d:%02d".format(min, sec)
}
🔀 6. Implement Seek (Skip)
When the user drags the SeekBar, call:
seekBar.setOnSeekBarChangeListener(object: SeekBar.OnSeekBarChangeListener {
override fun onProgressChanged(sb: SeekBar, progress: Int, fromUser: Boolean) {
if (fromUser) {
remoteClient?.seek(progress.toLong())
}
}
override fun onStartTrackingTouch(sb: SeekBar) {}
override fun onStopTrackingTouch(sb: SeekBar) {}
})
Tip:
.seek(positionMs: Long)
is easy and works, but you can also use the newerseek(MediaSeekOptions)
if you need more control.
✅ 7. (Optional) Pre‑Set Stream Duration
For VOD (fixed‑length files), you can speed up UI updates by telling Cast the duration up‑front:
val mediaInfo = MediaInfo.Builder(url)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType("audio/mp3")
.setStreamDuration(180_000L) // 3 minutes in ms
.setMetadata(metadata)
.build()
Cast will then show the total length immediately, without waiting for server headers.
🚩 Common Pitfalls & Tips
- Live/Radio streams have no end: duration will be 0. Skip setting
setStreamDuration
. - Permission errors? Make sure
INTERNET
is declared and your Cast button appears only on HTTPS streams. - Deprecated APIs: if
seek(MediaSeekOptions)
orProgressListener
imports aren’t found, bump yourplay-services-cast-framework
to the latest version and re‑sync.
🔚 Conclusion
You’ve now got a full Chromecast integration in Kotlin—complete with real‑time duration display and seek controls. Your users can cast audio/video, see how long the media is, and skip to any part they like.
Feel free to copy‑paste the code snippets, and tweak them to fit your app’s architecture. Happy coding!