Appearance
Google Cast for Video Workouts beta
This guide explains how to integrate Google Cast in an iOS GoVideoController flow, following the same architecture used in the SampleApp.
Official prerequisites
Before wiring WorkoutKit callbacks, align with Google Cast iOS sender requirements:
- Use iOS 15+ target baseline.
- Avoid
-Ofastoptimization for Cast sender builds; use-Osor standard release optimizations. - Call Cast SDK APIs on the main thread.
- Include Local Network and Bonjour declarations in
Info.plistto enable discovery on iOS 14+.
Official references:
- https://developers.google.com/cast/docs/ios_sender
- https://developers.google.com/cast/docs/ios_sender/integrate
- https://developers.google.com/cast/docs/ios_sender/permissions_and_discovery
1. Add Google Cast SDK dependency
The sample app uses a local Swift Package workaround (GoogleCastSPM) that wraps Google Cast as a binary target.
- Workaround package: https://github.com/fysiki/workoutkit-ios-sdk/tree/main/demo/GoogleCastSPM
- Upstream note: https://github.com/fysiki/workoutkit-ios-sdk/blob/main/demo/GoogleCastSPM/README.md
In your own app, use either this workaround approach or your preferred official Google Cast SDK integration method.
2. Configure app discovery permissions
Google Cast discovery requires local network and Bonjour declarations in Info.plist.
xml
<key>NSLocalNetworkUsageDescription</key>
<string>Local network - usage description</string>
<key>NSBonjourServices</key>
<array>
<string>_googlecast._tcp</string>
<string>_ABCDEF123._googlecast._tcp</string>
</array>Replace ABCDEF123 with your receiver app ID.
3. Initialize the Cast context at app launch
Initialize GCKCastContext in AppDelegate (or app startup entry point):
swift
import GoogleCast
let options = GCKCastOptions(discoveryCriteria: GCKDiscoveryCriteria(applicationID: "ABCDEF123"))
options.physicalVolumeButtonsWillControlDeviceVolume = true
options.disableDiscoveryAutostart = false
let launchOptions = GCKLaunchOptions()
launchOptions.androidReceiverCompatible = true
options.launchOptions = launchOptions
GCKCastContext.setSharedInstanceWith(options)
GCKCastContext.sharedInstance().useDefaultExpandedMediaControls = falseIf your app needs persistent background casting behavior, also review GCKCastOptions.suspendSessionsWhenBackgrounded in Google Cast official docs.
4. Attach Cast UI and remote manager in GoVideoController
In your GoVideoController subclass, add a cast button and assign a RemoteManager implementation:
swift
import GoogleCast
final class MonVideoGoController: GoVideoController {
private let castButton = GCKUICastButton()
override func viewDidLoad() {
super.viewDidLoad()
castButton.tintColor = .white
topLeftStack.addArrangedSubview(castButton)
let manager = GoogleCastManager(video: video, metadata: metadata)
remoteManager = manager
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if let manager = remoteManager as? GCKSessionManagerListener {
GCKCastContext.sharedInstance().sessionManager.add(manager)
}
}
}5. Implement GoogleCastManager as a RemoteManager
The sample implementation subclasses WorkoutKit RemoteManager and maps WorkoutKit playback calls to Google Cast commands.
Key responsibilities:
- Expose
currentTimeviaremoteMediaClient.approximateStreamPosition() - map Google Cast player state to
RemoteMediaPlayerState - implement
startMedia(at:with:),playMedia(),pauseMedia(promptTitle:promptCaption:),seekMedia(to:), anddisconnect() - build
GCKMediaInformationfrom WorkoutKitVideo+VideoMetadata - pass WorkoutKit UI state through Cast custom payload data
swift
final class GoogleCastManager: RemoteManager {
override func startMedia(at time: CMTime, with mode: WorkoutStateMode) {
// Build GCKMediaInformation and call client.loadMedia(...)
}
override func playMedia() { /* client.play(...) */ }
override func pauseMedia(promptTitle: String? = nil, promptCaption: String? = nil) { /* client.pause(...) */ }
override func seekMedia(to time: CMTime) { /* client.seek(...) */ }
override func disconnect() { /* sessionManager.endSession() */ }
}6. Bridge Cast callbacks to WorkoutKit remote delegate
The sample manager implements:
GCKSessionManagerListenerto detect session start/resume/end and attach channel/listenersGCKRemoteMediaClientListenerto push media status updates back into WorkoutKit
Important callback forwarding calls include:
delegate?.remoteSessionDidStart()delegate?.remoteSessionDidResume()delegate?.remoteSessionDidEnd(at:)delegate?.remoteSessionCanStartMedia()delegate?.remoteSessionDidUpdateMediaStatus(time:hasMedia:state:)
This is what keeps GoVideoController and the cast receiver synchronized.
7. Production hardening checklist
- Replace placeholder receiver ID (
ABCDEF123) and namespace (urn:x-cast:com.yourapp.cast). - Ensure receiver app understands the custom payload fields sent by your manager.
- Remove listeners appropriately if you add symmetrical cleanup hooks (e.g. in
viewWillDisappear). - Keep all token/API concerns in host app code; the Cast manager should focus on media transport and state synchronization.
