Appearance
Video Workouts
Video workouts are launched with StreamingWorkoutController (or a subclass) using the same GraphQL + token flow as classic workouts.
1. Detect video session and launch
swift
if response.data?.publicWorkoutSession.asWorkoutVideoSession != nil {
let controller = try await SampleStreamingWorkoutController(data: data, token: token)
controller.modalPresentationStyle = .fullScreen
present(controller, animated: true)
}2. Handle async save in a video controller subclass
swift
final class SampleStreamingWorkoutController: StreamingWorkoutController {
private var savePublisher: AnyPublisher<Void, Never>?
override func saveSession(state: SaveWorkoutState) {
super.saveSession(state: state)
savePublisher = Future<Void, Never> { promise in
// Replace with your backend persistence.
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
promise(.success(()))
}
}.eraseToAnyPublisher()
}
}StreamingWorkoutController uses the same completion contract as classic workouts. Override saveSession(state:) to persist completed workout state, quitWorkout() to react when the user exits before completion, and trackEvent(_:properties:) to forward TrainingKit analytics events. Always call super in these overrides so TrainingKit can complete its internal save and teardown behavior.
3. Add cast controls with a remote manager
swift
override func viewDidLoad() {
super.viewDidLoad()
let castButton = GCKUICastButton()
castButton.tintColor = .white
topLeftStack.addArrangedSubview(castButton)
let manager = GoogleCastManager(video: video, metadata: metadata)
remoteManager = manager
}When Cast support is enabled, the remote manager coordinates playback state between TrainingKit and the cast session.
For complete production setup (Info.plist permissions, GCKCastContext bootstrap, session listeners, and RemoteManager implementation), see Google Cast for Video Workouts.
