Субтитры для AVPlayer/MPMoviePlayerController

Я использую видео формат m3u8 для потоковой передачи видео, и теперь мне нужно отображать субтитры для того же.

Я искал в документации Apple и обнаружил, что могу добиться этого с помощью closedCaptionDisplayEnabled собственностью AVPlayer,

Мне интересно узнать какой должен быть формат субтитров? Подойдет ли формат.srt?

Также я могу добиться того же, используя MPMoviePlayerController?

Любая помощь приветствуется.

6 ответов

Обновление 30.10.2008: Стоит проверить этот ответ инженером Apple (спасибо allenlinli за указание на это). Он предлагает решение, включающее AVAssetResourceLoaderDelegate, Я не пробовал сам, но это может быть лучшее решение, чем мое ниже.


Оригинальный ответ:

Кажется, что ссылка на ваши файлы WebVTT в описании потоковой передачи m3u8 является официально поддерживаемым способом. Добавление их "по факту", по-видимому, официально не поддерживается (см. Это заявление инженера Apple (внизу страницы)).

Это - однако - не означает, что вы не можете заставить его работать;-). С помощью этой замечательной презентации и примера проекта (ZIP) Криса Адамсона, этой статьи на форумах разработчиков Apple и этого учебного пособия Рэя Вендерлиха Абдул Азим я смог заставить его работать. Это модифицированная версия образца кода Абдула Азима и проекта Криса.

Обратите внимание, как вам нужно использовать AVMediaTypeText вместо AVMediaTypeSubtitle, Это похоже на ошибку в 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]];

Это Swift-версия ответа @JohannesFahrenkrug. Надеюсь, это кому-нибудь пригодится:

    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)

Как AVPlayer, так и MPMoviePlayerController могут отображать субтитры.

Разница, по-видимому, заключается в том, что с помощью AVPlayer вы управляете отображением субтитров с помощью свойства closedCaptionDisplayEnabled.

С помощью MPMoviePlayerController пользователь управляет отображением субтитров с помощью переключателя в приложении "Настройка". Вы не получаете программный контроль над этим в приложении.

Произошла ошибка звука при добавлении субтитров. Обновленная версия, как показано ниже;

Примечание. В AVMutableComposition можно вставлять только локальные элементы (активы), удаленные элементы (например, видеопотоки HTTP) не будут работать до iOS 15.

Файлы видео и субтитров

Вставка потока HTTP в AVMutableComposition

Свифт 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()
    }
}

Если вы хотите работать с серверным видео и субтитрами, вам нужно использовать это.

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()
                }
            }
        }
    }
}

Приложение WWDC 2013 для iOS использует файлы WebVTT для своих субтитров (спасибо Николасу Райли за это). По какой-то причине, есть около 50 (точное количество варьируется) для каждого сеанса.

Я понятия не имею, поддерживается ли WebVTT на уровне AVFoundation или MPMoviePlayer, или приложение загружает и анализирует файлы субтитров самостоятельно.

Быстрый поиск "webvtt m3u8" обнаружил ссылку на этот HTTP Live Streaming Draft, что говорит о том, что вы можете выполнить эту работу, просто сославшись на файлы WebVTT в вашем плейлисте m3u8. У меня нет примера для вас, так как я не смог угадать URL-адрес m3u8 для сеанса WWDC.

Если кому-то нужна последняя версия Swift и собственное представление SwiftUI (AVplayerController, завернутый для SwiftUI), вот изменения, спасибо @perpeer:

      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 await videoTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.load(.duration)),
                                            of: videoTrackItem,
                                            at: .zero)
        }
        if let audioTrackItem = try await videoAsset.loadTracks(withMediaType: .audio).first {
            try await audioTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.load(.duration)),
                                            of: audioTrackItem,
                                            at: .zero)
        }
        if let subtitleTrackItem = try await subtitleAsset.loadTracks(withMediaType: .text).first {
            try await subtitleTrack?.insertTimeRange(CMTimeRangeMake(start: .zero, duration: videoAsset.load(.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()
            }
        }
    }

Пробовал с локальным и удаленным URL-адресом, все работает, главное, чтобы ваш подзаголовок был с расширением .vtt.

Другие вопросы по тегам