2

My app uses AVPlayer to play my videos however they do not play every time. I just get a black screen on some videos. However the videos that don't play aren't the same every time. I don't understand why one minute it works and then the next it doesn't. I am using the HCVimeoVideoExtractor in order to pull the videos from Vimeo.

import UIKit
import HCVimeoVideoExtractor
import Foundation
import AVKit
import AVFoundation

class Arms : UITableViewController{

    @IBOutlet var Tableview: UITableView!

    //video outlet
    let playerController = AVPlayerViewController()

    //@IBOutlet var mypic: UIImageView!

    let sectionName = ["Day 0","Day 1","Day 2","Day 3","Day 4","Day 5","Day 6","7"]

    let foodTitleArray = ["1 and a Half Cable Bicep Curl", "Barbell Bicep Curls", "Cable Bicep Curl","Dumbbell Bicep Curl","Bench Press Close Grip","Across The Body Tricep Extension","EZ Bar Curl", "Dumbbell Hammer Curl", "Low Pulley Cable Curl", "Preacher Curl Machine", "Skull Crusher", "Straight Bar Tricep Extension", "Rope Tricep Extension"]

    let foodImageArray = ["Bicep Curl 1 and half Cable", "Bicep Curl Barbell", "Bicep Curl Cable","Bicep Curl Dumbbell","Close Grip Bench Press","Cross body tricep extension","EZ Bar Curl", "Hammer Curl Dumbbell", "Low Cable Pulley Curl", "Preacher Curl Machine", "Skull Crusher", "Tricep extension Cable", "Tricep Extension rope"]


    var rowselected = Int()


    //-----vids-----
    //video links
    var calledVideo: String?

    let vids = ["https://vimeo.com/12345","https://vimeo.com/12345","https://vimeo.com/12345","https://vimeo.com/12345","https://vimeo.com/12345","https://vimeo.com/12345","https://vimeo.com/12345","https://vimeo.com/12345","https://vimeo.com/12345","https://vimeo.com/12345", "https://vimeo.com/12345", "https://vimeo.com/12345", "https://vimeo.com/12345"]
    //-----vids-----


    override func viewDidLoad() {
        super.viewDidLoad()
    }


    @IBAction func Back(_ sender: Any) {

        NotificationCenter.default.removeObserver(self)

        self.dismiss(animated: true, completion: nil)
    }


    //setting up tableview rows
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        //return sectionName.count
        return foodTitleArray.count
    }


    //what cell is & how it looks
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = Tableview.dequeueReusableCell(withIdentifier: "cellarms") as! UITableViewCell1


        // cell.foodImage.image = UIImage(named: foodImageArray[indexPath.row] + ".jpg") //this is origionally how memory images are used added to cell. this way caches the images building memory.
        cell.foodTitle.text = foodTitleArray[indexPath.row] //this is how to the uitableviews titles are added to cells

        //shows the images in cells without storing image in cache
        let bundlePath = Bundle.main.path(forResource: foodImageArray[indexPath.row], ofType: "jpg")
        let image = UIImage(contentsOfFile: bundlePath!)
        cell.foodImage.image = image

        return (cell)
    }


    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {

        rowselected = indexPath.row

        //indexpath.row is the code to get a number of the row selected this number then is selected from vids array and sets calledvideo to this number.
        print(indexPath.row)
        let number = indexPath.row
        calledVideo = vids[number]
        print(calledVideo)
        geturl()


        //making desleected row not highlighted
        tableView.deselectRow(at: indexPath, animated: true)
    }

    //Getting url info for video by using exractor
    func geturl() {
        if let url = URL(string: calledVideo!) {
            HCVimeoVideoExtractor.fetchVideoURLFrom(url: url, completion: { ( video:HCVimeoVideo?, error:Error?) -> Void in
                if let err = error {
                    DispatchQueue.main.async() {
                        let alert = UIAlertController(title: "Error", message: err.localizedDescription, preferredStyle: .alert)
                        alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
                        self.present(alert, animated: true, completion: nil)
                    }
                    return
                }


                guard let vid = video else {
                    print("Invalid video object")
                    return
                }



                //play video
                let player = AVPlayer(url: vid.videoURL[.Quality360p]!)
                self.playerController.player = player
                self.present(self.playerController, animated: true) {
                    player.play()
                }
            })   
        }
    }
}

its a different video that gets cancelled every time on me. it seem to be totally random. Below is one example of an error that occurred on me when a video was cancelled:

Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLStringKey=https://gcs-vimeo.akamaized.net/exp=1581445783~acl=%2Fvimeo-prod-skyfire-std-us%2F01%2F3120%2F14%2F365604762%2F1507711789.mp4~hmac=ac26b711525132b93ce5b4c33db5f04efc84feab53f3c07ec324d15962a9f5dd/vimeo-prod-skyfire-std-us/01/3120/14/365604762/1507711789.mp4?source=1, NSErrorFailingURLKey=https://gcs-vimeo.akamaized.net/exp=1581445783~acl=%2Fvimeo-prod-skyfire-std-us%2F01%2F3120%2F14%2F365604762%2F1507711789.mp4~hmac=ac26b711525132b93ce5b4c33db5f04efc84feab53f3c07ec324d15962a9f5dd/vimeo-prod-skyfire-std-us/01/3120/14/365604762/1507711789.mp4?source=1, _NSURLErrorRelatedURLSessionTaskErrorKey=(
    "LocalDataTask <A7D7517F-1F77-495B-80A1-2F61FE5B723D>.<1>"
), _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <A7D7517F-1F77-495B-80A1-2F61FE5B723D>.<1>, NSLocalizedDescription=cancelled} [-999]
Robin Schmidt
  • 1,153
  • 8
  • 15
kitchen800
  • 197
  • 1
  • 12
  • 36

3 Answers3

3

First, you should show an Alert when printing „invalid video object“, so the user gets this info too.

You try to stream videos. Sometimes this may start immediately, but mostly the player needs some time to buffer the first seconds before being able to play.

You need to observe the status of your players currentItem. This will show you what your player is doing, if it is loading, did fail to load, or is ready. When the status turns to „readyToPlay“ you can call playerController.play()

How to observe this property is shown here: https://stackoverflow.com/a/47787854

Edit: observer implementation
Declare this variable in your ViewController class:

var observer: NSKeyValueObservation? {
    willSet {
        guard let observer = observer else { return }
        observer.invalidate()
    }
}

Replace this code:

//play video
let player = AVPlayer(url: vid.videoURL[.Quality360p]!)
self.playerController.player = player
self.present(self.playerController, animated: true) {
    player.play()
}

with following:

let url = vid.videoURL[.Quality360p]!
// Create asset to be played
let asset = AVAsset(url: url)

let assetKeys = [
    "playable",
    "hasProtectedContent"
]
// Create a new AVPlayerItem with the asset and an
// array of asset keys to be automatically loaded
let playerItem = AVPlayerItem(asset: asset,
                          automaticallyLoadedAssetKeys: assetKeys)

// Associate the player item with the player
let player = AVPlayer(playerItem: playerItem)
self.playerController.player = player

// Register as an observer of the player item's status property
self.observer = playerItem.observe(\.status, options:  [.new, .old], changeHandler: { (playerItem, change) in
    if playerItem.status == .readyToPlay {
        self.present(self.playerController, animated: true) {
            player.play()
        }
    }
})
Robin Schmidt
  • 1,153
  • 8
  • 15
1

The code is not testable as-is, so just by mental analyses...

The modified part of code is following your //play video comment. It needs explicitly forward UI operations & plying to main thread:

//play video
DispatchQueue.main.async {
    let player = AVPlayer(url: vid.videoURL[.Quality360p]!)
    self.playerController.player = player
    self.present(self.playerController, animated: true) {
        player.play()
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690
  • Why would not having this result in some vids loading and others not? – kitchen800 Feb 06 '20 at 13:51
  • If extractor uses default global queue for processing it might sometime be in main thread and sometime not. – Asperi Feb 06 '20 at 13:55
  • didn't seem too work. Some still load and some don't – kitchen800 Feb 06 '20 at 18:39
  • 1
    @pete800, then it is not in the provided code, I believe... do you play them all simultaneously or one by one? Don't you think there might be some limitations or restrictions, memory or server-side. – Asperi Feb 06 '20 at 18:55
  • @pete800, I've created a test project with your provided code + my fix and tested it with some free Vimeo videos found in net... and all works, ok, almost - the only additional condition I added is for extracted videoURL quality, because those free videos were with different quality and that force unwrap throws exception. So... if you still have issues with my applied fix and you're sure Quality360p is always present in your video, then, again, the issue is not in the provided code. – Asperi Feb 11 '20 at 20:02
0

Your link is changed when you scrolled your tableview. Check your url after scroll. If everything is okay then try this code below.

For swift:

let url = URL(string: "https://vimeo.com/254597739")!
HCVimeoVideoExtractor.fetchVideoURLFrom(url: url, completion: { ( video:HCVimeoVideo?, error:Error?) -> Void in                
    if let err = error {                    
       print("Error = \(err.localizedDescription)")                    
       return
    }

    guard let vid = video else {
        print("Invalid video object")
        return
    }

    print("Title = \(vid.title), url = \(vid.videoURL), thumbnail = \(vid.thumbnailURL)")

    if let videoURL = vid.videoURL[.Quality540p] {
        let player = AVPlayer(url: videoURL)
        let playerController = AVPlayerViewController()
        playerController.player = player
        self.present(playerController, animated: true) {
            player.play()
        }
    }                            
})
Jamil Hasnine Tamim
  • 4,389
  • 27
  • 43
  • I couldn't seem to get your method to work. i have updated my answer with an example of the error that is presented when a video dent load for me. – kitchen800 Feb 11 '20 at 17:45
  • @pete800 I think it's `tableview` scrolling issue and your link is mismatching when you scrolling your item! – Jamil Hasnine Tamim Feb 12 '20 at 05:07
  • @pete800 If you have `section` and your link is generated from `section` click your number should be `indexPath.section`. Please attach an screenshot of your app. – Jamil Hasnine Tamim Feb 12 '20 at 05:11