How to add WebVTT external subtitles to HTTP Live Stream on iOS client

We have videos encoded via bitmovin.com and provided as HTTP Live streams (Fairplay HLS), but the subtitles, although in WebVTT format, are displayed separately as direct URLs for the entire file, not separate segments and are not part of the H3 m3u8 list reproduction.

I am looking for a way that an external .vtt file, downloaded separately, can still be included in the HLS stream and be available as subtitles in AVPlayer.

I know that Apple's recommendation is to include VTT segmented subtitles in the HLS playlist, but now I can’t change the server implementation, so I want to clarify if it is even possible to provide subtitles for AVPlayer to play along with the HLS stream.

The only valid post on this topic claiming it is possible: Subtitles for AVPlayer / MPMoviePlayerController . However, the sample code loads the local mp4 file from the package, and I'm struggling to get it working in the m3u8 playlist via AVURLAsset. Actually, I am having a problem getting a video driver from a remote m3u8 stream since it asset.tracks(withMediaType: AVMediaTypeVideo)returns an empty array. Any ideas if this approach could work for a real HLS stream? Or is there another way to play individual WebVTT subtitles with an HLS stream without including them in the HLS playlist on the server? Thank.

func playFpsVideo(with asset: AVURLAsset, at context: UIViewController) {

    let composition = AVMutableComposition()

    // Video
    let videoTrack = composition.addMutableTrack(withMediaType: AVMediaTypeVideo, preferredTrackID: kCMPersistentTrackID_Invalid)

    do {

        let tracks = asset.tracks(withMediaType: AVMediaTypeVideo)

        // ==> The code breaks here, tracks is an empty array
        guard let track = tracks.first else {
            Log.error("Can't get first video track")
            return
        }

        try videoTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: track, at: kCMTimeZero)

    } catch {

        Log.error(error)
        return
    }


    // Subtitle, some test from the bundle..
    guard let subsUrl = Bundle.main.url(forResource: "subs", withExtension: "vtt") else {
        Log.error("Can't load subs.vtt from bundle")
        return
    }

    let subtitleAsset = AVURLAsset(url: subsUrl)

    let subtitleTrack = composition.addMutableTrack(withMediaType: AVMediaTypeText, preferredTrackID: kCMPersistentTrackID_Invalid)

    do {

        let subTracks = subtitleAsset.tracks(withMediaType: AVMediaTypeText)

        guard let subTrack = subTracks.first else {
            Log.error("Can't get first subs track")
            return
        }

        try subtitleTrack.insertTimeRange(CMTimeRangeMake(kCMTimeZero, asset.duration), of: subTrack, at: kCMTimeZero)

    } catch {

        Log.error(error)
        return
    }


    // Prepare item and play it
    let item = AVPlayerItem(asset: composition)

    let player = AVPlayer(playerItem: item)

    let playerViewController = AVPlayerViewController()
    playerViewController.player = player

    self.playerViewController = playerViewController

    context.present(playerViewController, animated: true) {
        playerViewController.player?.play()
    }
}
+5
source share

All Articles