Umasankar Buddi
Umasankar Buddi

Reputation: 297

Unable to get video tracks from AVURLAsset for HLS videos(.m3u8 format) for AVPlayer

I am developing a custom video player to stream HLS videos from a server. I can successfully play HLS videos using AVPlayerItem and AVPlayer.

After that I want to add subtitle tracks and audio tracks for my video player. So I used AVMutableComposition to do so. So now the issue is when I am creating AVURLAsset for HLS Videos, I am unable to get video tracks from AVURLAsset. It is always giving me 0 tracks. I tried loadValuesAsynchronously of AVURLAsset and I tried adding KVO for tracks of AVPlayerItem. But none of these produced any positive results.

I am using the following code.

func playVideo() {
    let videoAsset = AVURLAsset(url: videoURL!)
    let composition = AVMutableComposition()
    // Video
    let videoTrack = composition.addMutableTrack(withMediaType: .video, preferredTrackID: kCMPersistentTrackID_Invalid)
    do {
        let tracks = videoAsset.tracks(withMediaType: .video)
        guard let track = tracks.first else {
            print("Can't get first video track")
            return
        }
        try videoTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: track, at: kCMTimeZero)
    } catch {
        print(error)
        return
    }
    guard let subtitlesUrl = Bundle.main.url(forResource: "en", withExtension: "vtt") else {
        print("Can't load en.vtt from bundle")
        return
    }
    //Subtitles
    let subtitleAsset = AVURLAsset(url: subtitlesUrl)
    let subtitleTrack = composition.addMutableTrack(withMediaType: .text, preferredTrackID: kCMPersistentTrackID_Invalid)
    do {
        let subTracks = subtitleAsset.tracks(withMediaType: AVMediaType.text)
        guard let subTrack = subTracks.first else {
            print("Can't get first subtitles track")
            return
        }
        try subtitleTrack?.insertTimeRange(CMTimeRangeMake(kCMTimeZero, videoAsset.duration), of: subTrack, at: kCMTimeZero)
    } catch {
        print(error)
        return
    }
    // Prepare item and play it
    let item = AVPlayerItem(asset: composition)
    self.player = AVPlayer(playerItem: item)
    self.playerLayer = AVPlayerLayer.init()
    self.playerLayer.frame = self.bounds
    self.playerLayer.contentsGravity = kCAGravityResizeAspect
    self.playerLayer.player = player
    self.layer.addSublayer(self.playerLayer)
    self.player.addObserver(self, forKeyPath: "currentItem.loadedTimeRanges", options: .new, context: nil)
    self.player.play()
}

This procedure working well for .mp4 videos but not for HLS Videos(.m3u8). Does anyone have some working solution for this?

or

How can we get tracks from HLS videos using AVURLAsset? If this is not possible then how can I achieve a similar result?

Upvotes: 9

Views: 2547

Answers (3)

Youcine Premium
Youcine Premium

Reputation: 1

If you're having trouble getting video tracks from AVURLAsset for HLS videos (.m3u8 format) in AVPlayer, here are some possible reasons and solutions:

1. HLS Video Track Handling

Unlike MP4 files, HLS streams don’t always expose video tracks in the same way.

2. Protected/Encrypted Content

If the stream is DRM-protected, you may not be able to access tracks directly.

3. Network or CORS Issues

Make sure the .m3u8 file is accessible and properly formatted.

4. Incorrect Asset Loading

AVURLAsset needs to be loaded asynchronously before accessing tracks.

Upvotes: -1

Mike Post
Mike Post

Reputation: 11

I didn't have the exact same problem as you. But I got around a similar problem (querying for HDR) by instead of querying the tracks on the AVURLAsset, I queried the tracks on the AVPlayerItem.

Set up an observer on the item status:

player?.observe(\AVPlayer.currentItem?.status,
                                         options: [.new, .initial], changeHandler: { [weak self] player, _ in
                                            DispatchQueue.main.async {
                                                self?.observedItemStatus(from: player)
                                            }
                                         })

Then query the AVMediaType of your choice (in your case text).

func observedItemStatus(from avPlayer: AVPlayer) {

    guard let currentItem = avPlayer.currentItem else { return }

    // ideally execute code based on currentItem.status...for the brevity of this example I won't.

    let hasLegibleMedia = currentItem.tracks.first(where: {
        $0.assetTrack?.mediaType == AVMediaType.text
    })?.assetTrack.hasMediaCharacteristic(.legible)
}

Alternatively if you need more than just a Bool, you could do a loop to access the assetTrack you really want.

Upvotes: 1

For HLS video tracks(withMediaType: .video) will return an empty array.

Use this instead: player.currentItem.presentationSize.width and player.currentItem.presentationSize.height.

Pls let me know if it works.

Upvotes: 2

Related Questions