16

I am using m3u8 video format for streaming the video and now I need to display subtitles for the same.

I searched in Apple Documentation and found that I can achieve this by using the closedCaptionDisplayEnabled property of AVPlayer.

I am interested to know what should be the format of subtitles? Will the .srt format do?

Also can I achieve the same using MPMoviePlayerController?

Any help is appreciated.

Till
  • 27,559
  • 13
  • 88
  • 122
Vasu N
  • 344
  • 1
  • 2
  • 9
  • See http://stackoverflow.com/questions/11312118/show-subtitles-with-an-avfoundation-avplayer-on-os-x and http://stackoverflow.com/questions/5662635/show-subtitles-in-a-mpmovieplayerviewcontroller-by-default – Till Jul 18 '12 at 21:07
  • The Apple documentation suffers from exact wording on this point. It says only that `closedCaptionDisplayEnabled` “[i]ndicates whether the player uses closed captioning”. It does not say anything about subtitles. In my testing (on OS X), that property has no effect on subtitles; it *only* works on closed captions. (You can test this yourself by recording closed-captioned television using EyeTV, then exporting it to iPhone and MPEG-2 muxed formats, then converting the latter with HandBrake. The EyeTV-to-iPhone export will have a CC track; the HandBrake rip will have a subtitles track.) – Peter Hosey Jul 27 '13 at 08:09

5 Answers5

35

Update 03/06/2020: On GitHub, jbweimar has created a sample project that uses the AVAssetResourceLoaderDelegate approach that looks very promising: https://github.com/jbweimar/external-webvtt-example


Update 10/30/2018: It's worth checking this answer by an Apple engineer (Thanks to @allenlini for pointing it out). He suggests a solution involving AVAssetResourceLoaderDelegate. I haven't tried it myself, but it might be a better solution than mine below.


Original Answer:

It seems as if referencing your WebVTT files in your m3u8 streaming description is the officially supported way. Adding them "after the fact" seems to not be officially supported (See this statement by an Apple engineer (bottom of the page)).

That - however - does not mean that you can't get it to work ;-). With the help of this great presentation and sample project (ZIP) by Chris Adamson, this post on the Apple Developer Forums and this Ray Wenderlich tutorial by Abdul Azeem, I was able to get it to work. This is a modified version of Abdul Azeem's sample code and Chris' sample project.

Note how you need to use AVMediaTypeText instead of AVMediaTypeSubtitle. This seems to be a bug in iOS.

// 1 - Load video asset
AVAsset *videoAsset = [AVURLAsset assetWithURL:[[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"]];

// 2 - Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances.
AVMutableComposition *mixComposition = [[AVMutableComposition alloc] init];

// 3 - Video track
AVMutableCompositionTrack *videoTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeVideo
                                                                    preferredTrackID:kCMPersistentTrackID_Invalid];
[videoTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
                    ofTrack:[[videoAsset tracksWithMediaType:AVMediaTypeVideo] objectAtIndex:0]
                     atTime:kCMTimeZero error:nil];

// 4 - Subtitle track
AVURLAsset *subtitleAsset = [AVURLAsset assetWithURL:[[NSBundle mainBundle] URLForResource:@"subtitles" withExtension:@"vtt"]];

AVMutableCompositionTrack *subtitleTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeText
                                                                       preferredTrackID:kCMPersistentTrackID_Invalid];

[subtitleTrack insertTimeRange:CMTimeRangeMake(kCMTimeZero, videoAsset.duration)
                       ofTrack:[[subtitleAsset tracksWithMediaType:AVMediaTypeText] objectAtIndex:0]
                        atTime:kCMTimeZero error:nil];

// 5 - Set up player
AVPlayer *player = [AVPlayer playerWithPlayerItem: [AVPlayerItem playerItemWithAsset:mixComposition]];
Johannes Fahrenkrug
  • 42,912
  • 19
  • 126
  • 165
  • Thanks alot for superb post. Can you please mention how can we set the metadata. let titleMetadataItem = AVMutableMetadataItem() titleMetadataItem.locale = NSLocale.current titleMetadataItem.key = AVMetadataCommonKeyTitle as (NSCopying & NSObjectProtocol)? titleMetadataItem.keySpace = AVMetadataKeySpaceCommon titleMetadataItem.value = self.assetObject.title as (NSCopying & NSObjectProtocol)? – Taimur Ajmal Jan 15 '17 at 12:39
  • 1
    @Johannes Fahrenkrug: Is this solution working with real HLS stream from remote m3u8 playlist please? I am trying to do it in Swift with no luck. Can you please check my post: http://stackoverflow.com/questions/43161124/how-to-add-external-webvtt-subtitles-into-http-live-stream-on-ios-client. Thanks! – Martin Koles Apr 01 '17 at 21:05
  • @MartinKoles I think it should work. The best way to find out is just to try it :) Maybe you can post your results here! – Johannes Fahrenkrug Apr 03 '17 at 14:24
  • @JohannesFahrenkrug I have added my Swift 3 version of your code as a separate answer here, but I am getting problem to get the mutable media track from the composition. Maybe I am doing some wrong, can you please check? – Martin Koles Apr 04 '17 at 11:18
  • I want to play radio url with format .m3u8. how it is possible? – Deepak Saki Apr 18 '17 at 13:01
  • My understanding is that `AVComposition` does not work with any HTTP streaming assets, so this solution will not work. – Estel Jun 21 '17 at 14:16
  • 2
    Here is a nice article for post "Adding Subtitles From SEPARATE WebVTT File to AVAsset/PlayerItem" https://forums.developer.apple.com/thread/45060 – allenlinli May 29 '18 at 03:39
  • 1
    @allenlinli Wow, that's a great solution! Thanks for sharing!! – Johannes Fahrenkrug May 29 '18 at 13:47
  • @JohannesFahrenkrug this is a wonderful solution! Thanks! The only problem I seem to have is that when adding subs using `AVMediaTypeText` instead of `AVMediaTypeSubtitle`, you don't get to turn the subtitles on and off, or select between multiple languages in AVPlayerViewController. Anyone has any workaround for that? – m_katsifarakis Oct 30 '18 at 09:54
  • @m_katsifarakis Thank you, I wasn't able to find a solution for that. I haven't worked on this for a few years, so it's worth trying whether `AVMediaTypeSubtitle` works now. If not, another more involved solution could be to run an http server inside of your app in order to deliver a `m3u8` file. It might be crazy enough to work :) – Johannes Fahrenkrug Oct 30 '18 at 12:24
  • @JohannesFahrenkrug I am currently trying just that :) Preparing a custom m3u8 file. If that works out I will add my solution here. Best regards – m_katsifarakis Oct 31 '18 at 13:25
  • 1
    @m_katsifarakis Cool! Yes, please post an update! Also make sure to check out this link: https://forums.developer.apple.com/thread/45060#202064 – Johannes Fahrenkrug Oct 31 '18 at 15:48
  • @MartinKoles Did you get it working? I am stuck at the same point. Need to use subtitles (vtt) with HLS. Please post your solution/work around. – Pallav Trivedi Sep 17 '19 at 05:42
  • @PallavTrivedi My solution was to make vtt files available on the backend and properly reference them in the m3u8 file. It's working since then. – Martin Koles Sep 17 '19 at 07:44
6

This is the Swift version of @JohannesFahrenkrug answer. Hope this is useful for someone:

    let localVideoAsset = Bundle.main.url(forResource: "L1C1P1C3", withExtension: "mp4")

    //Create AVMutableComposition
    let videoPlusSubtitles = AVMutableComposition()

    //Adds video track
    let videoTrack = videoPlusSubtitles.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)

    try? videoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, localVideoAsset.duration),
                                of: localVideoAsset.tracks(withMediaType: .video)[0],
                                at: kCMTimeZero)

    //Adds subtitle track
    let subtitleAsset = AVURLAsset(url: Bundle.main.url(forResource: "L1C1P1C3", withExtension: ".mp4.vtt")!)

    let subtitleTrack = videoPlusSubtitles.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid)

    try? subtitleTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, localVideoAsset.duration),
                                        of: subtitleAsset.tracks(withMediaType: .text)[0],
                                        at: kCMTimeZero)
jmartinalonso
  • 2,324
  • 1
  • 19
  • 22
2

Both AVPlayer and MPMoviePlayerController can display subtitles.

The difference seems to be that with AVPlayer, you control whether or not the subtitles are displayed using the closedCaptionDisplayEnabled property.

With MPMoviePlayerController, the user controls whether or not subtitles are displayed using a switch in the Setting app. You get no programatic control of this in the app.

Dave Batton
  • 8,795
  • 1
  • 46
  • 50
  • 2
    `closedCaptionDisplayEnabled` toggles closed captions, not subtitles. They're two different things, and that property has no effect on subtitles. (One caveat: I have only tried this on OS X, not iOS.) – Peter Hosey Jul 27 '13 at 07:58
1

Audio error occurred when adding subtitles. Updated version as below;

Note: It is only possible to insert local items (assets) into an AVMutableComposition, remote items (like HTTP video streams) will not work before iOS 15.

Video and Subtitle files

Inserting an HTTP stream into a AVMutableComposition

Swift 5

func play() {
    guard let videoUrl = Bundle.main.url(forResource: "ElephantsDream", withExtension: "mp4")?.absoluteURL,
          let subtitleUrl = Bundle.main.url(forResource: "subtitles-en", withExtension: "vtt")?.absoluteURL else {
              return
          }


    let videoAsset = AVURLAsset(url: videoUrl)
    let subtitleAsset = AVURLAsset(url: subtitleUrl)

    // Create AVMutableComposition object. This object will hold your AVMutableCompositionTrack instances.
    let mixComposition = AVMutableComposition()

    let videoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
    let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
    let subtitleTrack = mixComposition.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid)

    do {
        try videoTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                        of: videoAsset.tracks(withMediaType: .video).first!,
                                        at: .zero)
        try audioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                        of: videoAsset.tracks(withMediaType: .audio).first!,
                                        at: .zero)
        try subtitleTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: subtitleAsset.duration),
                                           of: subtitleAsset.tracks(withMediaType: .text).first!, at: .zero)
    } catch let err {
        print(err.localizedDescription)
    }

    let player = AVPlayer(playerItem: AVPlayerItem(asset: mixComposition))
    let vc = AVPlayerViewController()
    vc.player = player
    present(vc, animated: true) {
        vc.player?.play()
    }
}

If you want to work with server-side video and subtitles you need to use like this.

iOS 15

func play() {
    if #available(iOS 15.0.0, *) {
        let videoUrl = URL(string: "https://wwww.acme.com/video/elephantsdream.mov")!
        let subtitleUrl = URL(string: "https://wwww.acme.com/subtitle/subtitles-en.vtt")!
        
        let videoAsset = AVURLAsset(url: videoUrl)
        let subtitleAsset = AVURLAsset(url: subtitleUrl)

        let mixComposition = AVMutableComposition()
        let videoTrack = mixComposition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
        let audioTrack = mixComposition.addMutableTrack(withMediaType: .audio, preferredTrackID: kCMPersistentTrackID_Invalid)
        let subtitleTrack = mixComposition.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid)

        Task {
            if let videoTrackItem = try await videoAsset.loadTracks(withMediaType: .video).first {
                try videoTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                                of: videoTrackItem,
                                                at: .zero)
            }
            if let audioTrackItem = try await videoAsset.loadTracks(withMediaType: .audio).first {
                try audioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                                of: audioTrackItem,
                                                at: .zero)
            }
            if let subtitleTrackItem = try await subtitleAsset.loadTracks(withMediaType: .text).first {
                try subtitleTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.duration),
                                                   of: subtitleTrackItem,
                                                   at: .zero)
            }
            DispatchQueue.main.async {
                let player = AVPlayer(playerItem: AVPlayerItem(asset: mixComposition))
                let vc = AVPlayerViewController()
                vc.player = player
                self.present(vc, animated: true) {
                    vc.player?.play()
                }
            }
        }
    }
}
perpeer
  • 51
  • 1
  • 5
  • that's not working for me, video and audio is ok but subtitle not showing(my subtitle is .srt), my video have two audio, you know how can i switch on my second audio? – Amin Rezaew Apr 25 '22 at 18:55
  • I added the subtitles as VTT, can you try that way? For multiple audio files, you can try adding from the list you got from the video like that; videoAsset.loadTracks(withMediaType: .audio).first – perpeer Apr 27 '22 at 00:50
  • this code worked when i changed my **SRT** subtitle to **VTT**, please edit and add this to your answer, thanks =) – Amin Rezaew May 01 '22 at 18:46
  • subtitle have black background you know how can i delete that? and is it possible to change subtitles color? – Amin Rezaew May 01 '22 at 18:50
0

The WWDC 2013 iOS app uses WebVTT files for its subtitles (credit to Nicholas Riley for discovering this). For whatever reason, there are about 50 (exact number varies) for each session.

I have no idea whether WebVTT is supported at the AVFoundation or MPMoviePlayer level, or whether the app downloads and parses the subtitles files itself.

A quick search for “webvtt m3u8” turned up a reference to this HTTP Live Streaming Draft, which suggests that you may be able to make this work by simply referencing the WebVTT files in your m3u8 playlist. I don't have an example for you, though, since I wasn't able to trivially guess the m3u8 URL for a WWDC session.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370