3

This is how in my code, playing from url, looks like:

private func play() {
    let streamUrl = ...        
    let playerItem = AVPlayerItem(url: streamURL)
    radioPlayer = AVPlayer(playerItem: playerItem)
    radioPlayer.volume = 1.0
    do {
         try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, with: AVAudioSessionCategoryOptions.mixWithOthers)
         try AVAudioSession.sharedInstance().setActive(true)
         UIApplication.shared.beginReceivingRemoteControlEvents()
     } catch {
         print("Error deactivating audio session.")
     }
     radioPlayer.play()
     startListeningForStreamFail()
     stopStartButton.setImage(#imageLiteral(resourceName: "pause_btn"), for: .normal)
}

Like the code snippet explains above, after calling the .play() function, I'm calling startListeningForStreamFail(), which registers the current viewcontroller to two types of notifications, on main thread.

private func startListeningForStreamFail() {
    DispatchQueue.main.async { [weak self] in
        NotificationCenter.default.addObserver(self as Any, selector: #selector(self?.playerItemFailedToPlay), name: NSNotification.Name.AVPlayerItemPlaybackStalled, object: self?.radioPlayer?.currentItem)
        NotificationCenter.default.addObserver(self as Any, selector: #selector(self?.playerItemFailedToPlay), name: NSNotification.Name.AVPlayerItemFailedToPlayToEndTime, object: self?.radioPlayer?.currentItem)
    }
}

And the selector functions is this:

@objc private func playerItemFailedToPlay(notification: Notification) {
    print("playerItemFailedToPlay")
}

Because the stream right now works fine, I'm trying to test failure by addig some plus characters in it's url. But the playerItemFailedToPlay() functions does NOT get called, does NOT print anything.

Should this selector getting called, even if only the url was changed?

Any help would be appreciated. Thanks!

anitteb
  • 762
  • 10
  • 21
  • 2
    I built a testing project for you on [github](https://github.com/omiz/StreamTesterPlayer) try to test your link with it and please check the value of [NSAppTransportSecurity](https://stackoverflow.com/a/40299837/6689101) in your project – zombie Feb 19 '18 at 13:34
  • Thank you for the test project and for your patience! I've tested my url, if the url is correct it works fine, but with the "fake" url does not get any error. I'm wondering if I get the same error code if 1. the url is valid (but stream doesn't work) and if 2. the url isn't valid. – anitteb Feb 19 '18 at 14:05
  • 1
    I added a small trick to solve a fake url. please let me know if that works for you – zombie Feb 19 '18 at 14:13
  • In the `setup()` function if the url `!asset.isPlayable` I've changed the completion value from `nil` to `.unvalidURL`. And now it works. I've understood your logic, thank you very much! – anitteb Feb 19 '18 at 14:37

2 Answers2

7

I tried to build a project on Github for an easy check

I followed these steps:

  1. Adding NSAppTransportSecurity to info.plist as in this answer to allow http

  2. Trim the provided url to remove any spaces

    let urlString = urlString.trimmingCharacters(in: .whitespacesAndNewlines)

  3. Check if the string provides a valid link

    guard let url = URL(string: urlString) else { return complete(.unvalidURL) }

  4. Check if the link is playable

    AVAsset(url: url).isPlayable

If any of the previous steps was not successful then it means the url is not valid

I also added an observers for the errors after starting a playable link

NotificationCenter.default.addObserver(self, selector: #selector(itemFailedToPlayToEndTime(_:)), name: .AVPlayerItemFailedToPlayToEndTime, object: nil)
        
NotificationCenter.default.addObserver(self, selector: #selector(itemNewErrorLogEntry(_:)), name: .AVPlayerItemNewErrorLogEntry, object: nil)
        
NotificationCenter.default.addObserver(self, selector: #selector(itemPlaybackStalled(_:)), name: .AVPlayerItemPlaybackStalled, object: nil)

EDIT:

The part of AVAsset(url: url).isPlayable might only check if the path ends with an appropriate extension as an example mp3, mp4. I do not have access to check the actual code so I would only refer to the documentation

zombie
  • 5,069
  • 3
  • 25
  • 54
  • above notification observers do not get called....can someone provide any solution for this – Kedar Sukerkar Oct 01 '19 at 05:44
  • .isPlayable is utterly useless - all it does is check if the url ends in ".m3u8" or ".mp4". – Fattie Jun 09 '22 at 12:39
  • @Fattie thank you for your comment but I was only following the [documentation](https://developer.apple.com/documentation/avfoundation/avasset/1385974-isplayable) which says: `This property value is true if you can use the asset to create an AVPlayerItem.` – zombie Jun 09 '22 at 12:44
  • hi @zombie For sure - I wasn't criticizing you. Exactly as the documentation says, **the only thing .isPlayable does is check if the url ends in ".m3u8" or ".mp4"** (or other appropriate strings). It's just a string checker. It does not in any way check if the url is real, if the site actually exists on the internet, if the file is actually a video etc. "isPlayable" simply means "can I play that type of file extension" - that's all it does. it does not "go to the internet" in any way whatsoever, it's just a string checker. – Fattie Jun 09 '22 at 13:30
  • the "observer" part of your answer is critical and excellent BTW! :) – Fattie Jun 09 '22 at 13:31
0

Many of comments here says that .isPlayable it's just strings checker, but it's not.

In my case, I have 2 urls where video in one of them is broken, but both urls is correct.

After async loading of asset I have the next results:

asset=videoSource Optional("https://my.backend.com/7e84dd.mp4")
asset=isPlayable true

asset=videoSource Optional("https://my.backend.com/57f5d0.mp4")
asset=isPlayable false

So, its not just string checker

whalemare
  • 1,107
  • 1
  • 13
  • 30