Skip to content

Google Cast for Video Workouts beta ​

This guide explains how to integrate Google Cast in an iOS StreamingWorkoutController flow, following the same architecture used in the SampleApp.

Official prerequisites ​

Before wiring TrainingKit callbacks, align with Google Cast iOS sender requirements:

  • Use iOS 15+ target baseline.
  • Avoid -Ofast optimization for Cast sender builds; use -Os or standard release optimizations.
  • Call Cast SDK APIs on the main thread.
  • Include Local Network and Bonjour declarations in Info.plist to enable discovery on iOS 14+.

Official references:

1. Add Google Cast SDK dependency ​

The sample app uses a local Swift Package workaround (GoogleCastSPM) that wraps Google Cast as a binary target.

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 = false

If your app needs persistent background casting behavior, also review GCKCastOptions.suspendSessionsWhenBackgrounded in Google Cast official docs.

4. Attach Cast UI and remote manager in StreamingWorkoutController ​

In your StreamingWorkoutController subclass, add a cast button and assign a RemoteManager implementation:

swift
import GoogleCast

final class SampleStreamingWorkoutController: StreamingWorkoutController {
    private let castButton = GCKUICastButton()

    override func viewDidLoad() {
        super.viewDidLoad()

        castButton.tintColor = .white
        leftItems = [UIBarButtonItem(customView: 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 TrainingKit RemoteManager and maps TrainingKit playback calls to Google Cast commands.

Key responsibilities:

  • Expose currentTime via remoteMediaClient.approximateStreamPosition()
  • map Google Cast player state to RemoteMediaPlayerState
  • implement startMedia(at:with:), playMedia(), pauseMedia(promptTitle:promptCaption:), seekMedia(to:), and disconnect()
  • build GCKMediaInformation from TrainingKit Video + VideoMetadata
  • pass TrainingKit 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 TrainingKit remote delegate ​

The sample manager implements:

  • GCKSessionManagerListener to detect session start/resume/end and attach channel/listeners
  • GCKRemoteMediaClientListener to push media status updates back into TrainingKit

Important callback forwarding calls include:

  • delegate?.remoteSessionDidStart()
  • delegate?.remoteSessionDidResume()
  • delegate?.remoteSessionDidEnd(at:)
  • delegate?.remoteSessionCanStartMedia()
  • delegate?.remoteSessionDidUpdateMediaStatus(time:hasMedia:state:)

This is what keeps StreamingWorkoutController 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.

Source references ​