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