iOS AVPlayer Subtitles / Captions

As of iOS 18, as far as I can tell, it appears there's still no AVPlayer options that allow users to toggle the caption / subtitle track on and off. Does anyone know of a way to do this with AVPlayer or with SwiftUI's VideoPlayer?

The following code reproduces this issue. It can be pasted into an app playground. This is a random video and a random vtt file I found on the internet.

import SwiftUI
import AVKit
import UIKit

struct ContentView: View {
    private let video = URL(string: "https://server15700.contentdm.oclc.org/dmwebservices/index.php?q=dmGetStreamingFile/p15700coll2/15.mp4/byte/json")!
    private let captions = URL(string: "https://gist.githubusercontent.com/samdutton/ca37f3adaf4e23679957b8083e061177/raw/e19399fbccbc069a2af4266e5120ae6bad62699a/sample.vtt")!

    @State private var player: AVPlayer?

    var body: some View {
        VStack {
            VideoPlayerView(player: player)
                .frame(maxWidth: .infinity, maxHeight: 200)
        }
        .task {
            // Captions won't work for some reason
            player = try? await loadPlayer(video: video, captions: captions)
        }
    }
}

private struct VideoPlayerView: UIViewControllerRepresentable {
    let player: AVPlayer?

    func makeUIViewController(context: Context) -> AVPlayerViewController {
        let controller = AVPlayerViewController()
        controller.player = player
        controller.modalPresentationStyle = .overFullScreen
        return controller
    }

    func updateUIViewController(_ uiViewController: AVPlayerViewController, context: Context) {
        uiViewController.player = player
    }
}

private func loadPlayer(video: URL, captions: URL?) async throws -> AVPlayer {
    let videoAsset = AVURLAsset(url: video)
    let videoPlusSubtitles = AVMutableComposition()
    try await videoPlusSubtitles.add(videoAsset, withMediaType: .video)
    try await videoPlusSubtitles.add(videoAsset, withMediaType: .audio)
    if let captions {
        let captionAsset = AVURLAsset(url: captions)
        // Must add as .text. .closedCaption and .subtitle don't work?
        try await videoPlusSubtitles.add(captionAsset, withMediaType: .text)
    }
    return await AVPlayer(playerItem: AVPlayerItem(asset: videoPlusSubtitles))
}

private extension AVMutableComposition {
    func add(_ asset: AVAsset, withMediaType mediaType: AVMediaType) async throws {
        let duration = try await asset.load(.duration)
        try await asset.loadTracks(withMediaType: mediaType).first.map { track in
            let newTrack = self.addMutableTrack(withMediaType: mediaType, preferredTrackID: kCMPersistentTrackID_Invalid)
            let range = CMTimeRangeMake(start: .zero, duration: duration)
            try newTrack?.insertTimeRange(range, of: track, at: .zero)
        }
    }
}
iOS AVPlayer Subtitles / Captions
 
 
Q