AVPlayer unexpected behaviour after iOS and tvOS update to 17.0
Asked Answered
L

1

6

Main thread blocked by synchronous property query on not-yet-loaded property (assetProperty_MediaPlaybackValidation) for HTTP(S) asset. This could have been a problem if this asset were being read from a slow network.

I am getting this error while using AVPlayer for playing videos in iPad and Apple TV after iOS and tvOS update.

Video is playing 2x faster sometime and later it completely stops playing.

I tried to avoid calling play function in main thread.

Logging answered 3/10, 2023 at 16:51 Comment(1)
I removed the code which checks for AVAsset url is playable or not, then the error disappear. Still I am facing AVlayer running videos too fast than expected issue only sometimes even after I manually set player.rate = 1. If you got any idea please shareLogging
S
3

I was having the same problem and figured it out after digging through the documentation for a while. As far as I can tell, the problem is that the play method gets all of the metadata that you haven't yet fetched that is needed for playing the asset. In my case, it was a video.

This is a relevant piece of documentation from Apple. https://developer.apple.com/documentation/avfoundation/media_assets/retrieving_media_metadata

This is the actual function that I used to load the metadata. https://developer.apple.com/documentation/avfoundation/avasynchronouskeyvalueloading/3747326-load

Here is the video player that I have that loads the metadata asynchronously before attempting to play the asset. There can be a small delay in loading the video, which shows a black screen on the video player, but it does not block any UI, and the warning went away.

import SwiftUI
import AVKit

struct VideoPlayerView: View {
    
    private var videoURL : URL
    @State private var player: AVPlayer?
    
    @State private var isMuted: Bool = true
    
    var showMuteButton: Bool
    
    init(url: URL, showMuteButton: Bool = true) {
        self.videoURL = url
        self.showMuteButton = showMuteButton
    }
    
    
    var body: some View {
        VideoPlayer(player: player)
        
            .onAppear() {
                Task {
                    player = AVPlayer()
                    await loadPlayerItem(self.videoURL)
                    
                    player?.isMuted = true
                    self.isMuted = player?.isMuted ?? false
                    
                    player?.play()
                    NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main) { _ in
                        self.player?.seek(to: .zero)
                        self.player?.play()
                    }
                }//: Task
            }//: onAppear
            .onDisappear() {
                Task {
                    // Stop the player when the view disappears
                    player?.pause()
                    
                }
            }
            .onChange(of: videoURL) { oldValue, newValue in
                Task {
                    await loadPlayerItem(newValue)
                    
                }
            }
            .overlay(alignment: .topTrailing) {
                if showMuteButton {
                    Image(systemName: isMuted ? "speaker.slash.fill" : "speaker.wave.2.fill")
                        .font(.footnote)
                        .foregroundColor(.white)
                        .padding(8)
                        .background(Color.gray.opacity(0.3))
                        .clipShape(Circle())
                        .padding()
                        .onTapGesture {
                            player?.isMuted.toggle()
                            self.isMuted = player?.isMuted ?? false
                        }
                }
            }
            
    }
    
    
    func loadPlayerItem(_ videoURL: URL) async {
        
        let asset = AVAsset(url: videoURL)
        do {
            let (_, _, _) = try await asset.load(.tracks, .duration, .preferredTransform)
        } catch let error {
            print(error.localizedDescription)
        }
        
        let item = AVPlayerItem(asset: asset)
        player?.replaceCurrentItem(with: item)
        
    }
}

The key function is the "loadPlayerItem". You don't actually need the return values for the load function, but having loaded them caches them so that the play function has everything it needs to play the asset.

I hope this helps.

Saintly answered 19/12, 2023 at 21:47 Comment(4)
weird, i used your code.. now video loads much faster than before but warning still showing up.Malkamalkah
Which not-yet-loaded property is your error pointing to? It may be that you need to include something else in the load method.Saintly
Works fine, but is there any reason why you choose .tracks and .preferredTransform?Sibilate
John Lima, I can't remember exactly why I added those because I haven't looked at this in several months, but I vaguely remember that I needed to load those for the video to be playable without any additional loading. I am not positive that that is why I did it, but you can always try removing that and see how your performance is.Saintly

© 2022 - 2024 — McMap. All rights reserved.