0

I am working on tvOS app using swift. I am using AVPlayerViewController to play video following Apple demo app(UIKit Catalog (tvOS): Creating and customising UIKit Controls). Everything is working fine, but app enter background to foreground during video play then forward and backward not working and same issue is generating if user can goto sleep mode. This issue is also generating in Apple demo app.

I am not understand why this problem is generating. Please suggest me how to resolve it.

    import UIKit
    import Foundation
    import AVKit

    class ViewController: UIViewController ,AVPlayerViewControllerDelegate{
        let playerController = AVPlayerViewController()
        var playerObj:AVPlayer!
        var asset:AVAsset!
        var playerItem:AVPlayerItem!
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
         self.playVideo("http://p.events-delivery.apple.com.edgesuite.net/1509pijnedfvopihbefvpijlkjb/m3u8/hls_vod_mvp.m3u8")
        }
  override func viewWillAppear(animated: Bool) {
         playerController.delegate = self
    }
        //MARK:- Play Video Method.
        func playVideo(videoURL:String) {
            print("video URL========\(videoURL)")

            self.asset = AVAsset(URL: NSURL(string: videoURL)!) as AVAsset
            self.playerItem = AVPlayerItem(asset: self.asset)
            self.playerObj = AVPlayer(playerItem: self.playerItem)
            self.playerController.player = self.playerObj
            playerController.view.frame = CGRectMake(0.0, 0.0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height)
            playerController.delegate = self
            self.view.addSubview(playerController.view)
            self.playerController.showsPlaybackControls = true
            self.playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: nil)
            self.playerController.player!.play()
            //self.playerController.player!.play()


        }
        override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>){

            if object as? NSObject == playerItem && (keyPath! as NSString).isEqualToString("status") {

                if (playerItem?.status == AVPlayerItemStatus.ReadyToPlay) {
                      print("Player Ready to play")
                }
                if (playerItem?.status == AVPlayerItemStatus.Failed) || (playerItem?.status == AVPlayerItemStatus.Unknown) {

                        print("Player getting play error")
                }
            }
        }


        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }


    }
Vipulk617
  • 799
  • 1
  • 7
  • 23
  • The amount of code context you gave isn't enough to really come up with a viable solution for you either because I cannot determine the full context. From my experience with AVPlayer, I recommend making this a class variable but it seems as though you make this a method variable instead. If that is the case, then it's likely your function is not holding onto the AVPlayer whenever the method terminates. Apple even suggests that you create a class variable for AVPlayer in order to "hold onto" the AVPlayer reference for the lifecycle of your application. – TimD Jun 29 '16 at 13:23
  • I have been tried with class variable but problem is same. – Vipulk617 Jun 29 '16 at 13:36
  • I still would venture to say that your class is releasing the delegate for receiving forward and back messages. This could mean your class is being unallocated when the application enters the background. Usually to solve this I create a singleton class to manage my audio/video playback so that I can guarantee that my app holds strongly onto my AVPlayer for the whole life cycle of the application. – TimD Jun 29 '16 at 13:38
  • I would suggest posting your entire UIViewController (or whatever ViewController you use) code so that I can see the entire context of your code. As I stated before, based on the tiny snippet you gave, it's hard to determine what exactly is going on. – TimD Jun 29 '16 at 13:41
  • Take a look at this documentation. https://developer.apple.com/library/ios/documentation/AVKit/Reference/AVPlayerViewControllerDelegate_Protocol/index.html#//apple_ref/doc/uid/TP40016157 "On Apple TV, you can implement methods related to playback navigation and interstitial content." According to the docs, playback navigation is handled in the avplayerviewcontroller delegate. I might suggest that in your viewWillAppear method that you set the delegate = self to just guarantee that your class is actually the delegate again in case it "let go" of it for some reason – TimD Jun 29 '16 at 13:48
  • I have updated class code.Please check it in question. This issue is in the real device not in simulator. – Vipulk617 Jun 29 '16 at 14:03
  • same issue in apple demo app https://developer.apple.com/library/tvos/samplecode/UICatalogFortvOS/Introduction/Intro.html – Vipulk617 Jun 29 '16 at 14:05
  • okay so in the example they are setting an IBAction and setting the player view controller delegate to self. This value is set ONCE and only ONCE when the user initiates playback. So if you background the app the view controller has actually disappeared and has been RELEASED because it's no longer being referenced so the delegate is no longer assigned to that player view controller. Thus when the view appears again the player delegate is not assigned to the player view controller. As I suggested earlier, you should assign the delegate again inside viewWillAppear – TimD Jun 29 '16 at 14:10
  • You are doing the same thing in your code as well. You are assigning the player delegate only once. Whenever your application gets backgrounded, it's likely the view controller is being deallocated and released thus the delegate method is also being released. Try instead to place the delegate in view Will appear or view did appear that way you can guarantee that your delegate is always set. Side note, DO NOT put the delegate method in viewDidLoad as this is only called once. viewDidLoad method is not guaranteed to be called multiple times' – TimD Jun 29 '16 at 14:13
  • Also, you are attempting to "play" a video right when a view is loaded in your viewdidLoad method. again, this is not the correct place to call that function because your view controller has "loaded" but has yet to be displayed. When a view has loaded that doesn't mean it has been "rendered" yet. Just because a view has been loaded does not mean that it has been displayed yet and it doesn't guarantee that the view controller will ever be displayed either it just means that iOS has loaded the view in memory and it's ready to be displayed. – TimD Jun 29 '16 at 14:16
  • Can you send me a demo app in my mail id vipul@fusionitechnologies.com – Vipulk617 Jun 29 '16 at 14:18
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/115975/discussion-between-vipulk617-and-user1171911). – Vipulk617 Jun 29 '16 at 14:25

1 Answers1

0

try this out...probably not the "best" solution but it shows what I'm talking about regarding the view controller life cycle.

Side note, I would suggest that you always call super.viewWillAppear(animated) inside your overridden functions for the view controller.

import UIKit
import Foundation
import AVKit
import AVFoundation

class ViewController: UIViewController ,AVPlayerViewControllerDelegate{
    let playerController = AVPlayerViewController()
    var playerObj:AVPlayer = AVPlayer()
    var asset:AVAsset!
    var playerItem:AVPlayerItem!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
override func viewWillAppear(animated: Bool) {
    super.viewWillAppear(animated) // always call this
    playerController.delegate = self
    NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(setDelegateOnForground), name: "application_forgrounded", object: nil)
}

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated) // always call this
    // check the player rate if 0 means it's paused / not playing

if playerObj.rate == 0 { playVideo("http://p.events-delivery.apple.com.edgesuite.net/1509pijnedfvopihbefvpijlkjb/m3u8/hls_vod_mvp.m3u8") } } //MARK:- Play Video Method. func playVideo(videoURL:String) { print("video URL========(videoURL)") self.asset = AVAsset(URL: NSURL(string: videoURL)!) as AVAsset self.playerItem = AVPlayerItem(asset: self.asset) self.playerObj = AVPlayer(playerItem: self.playerItem) self.playerController.player = self.playerObj playerController.view.frame = CGRectMake(0.0, 0.0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height) self.view.addSubview(playerController.view) self.playerController.showsPlaybackControls = true self.playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: nil) self.playerController.player!.play() //self.playerController.player!.play()

}

func setDelegateOnForground() {
    playerController.delegate = self
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>){

    if object as? NSObject == playerItem && (keyPath! as NSString).isEqualToString("status") {

        if (playerItem?.status == AVPlayerItemStatus.ReadyToPlay) {
            print("Player Ready to play")
        }
        if (playerItem?.status == AVPlayerItemStatus.Failed) || (playerItem?.status == AVPlayerItemStatus.Unknown) {

            print("Player getting play error")
        }
    }
}


override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Dispose of any resources that can be recreated.
}

}

in your AppDelegate.swift in

    func applicationDidBecomeActive(application: UIApplication) {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    NSNotificationCenter.defaultCenter().postNotification(NSNotification(name: "application_forgrounded", object: nil))
}

this will guarantee to set your delegate

TimD
  • 1,090
  • 11
  • 22
  • This code is not working. If app enter background to foreground then viewWillAppear not call. – Vipulk617 Jun 29 '16 at 14:36
  • so after posting the correct version, it works for me. I can go back and forth in the video using the skipping functionality – TimD Jun 29 '16 at 15:02
  • can you send me demo app in my mail id vipul@fusionitechnologies.com – Sankalap Yaduraj Singh Jun 29 '16 at 15:04
  • Sent the zipped file. The code is the same as is here. – TimD Jun 29 '16 at 16:30
  • I have run demo app but not working. If app enter background to foreground and user tap on touch surface then pause thumbnail is not showing and not forward and backward using touch surface. This code is working in simulator but not in Apple tv. – Vipulk617 Jun 30 '16 at 04:43
  • Which version of tvOS you are using? – Vipulk617 Jun 30 '16 at 05:09
  • I am using tvOS 9.2. it works in simulator but not on physical device. I am not understand for this strange behaviour. – Vipulk617 Jun 30 '16 at 11:28
  • this may sound strange but delete the app on your apple tv. Then in Xcode, select product->clean. Then rebuild and deploy your app on your apple TV. Sometimes the compiler doesn't "pick up" the changes you make on some files and uses a cached version of it when compiling your code. – TimD Jun 30 '16 at 12:29
  • I have tried this but not got success. I have an one doubt Apple is not release tvOS 9.3, The latest version of tvOS 9.2.1 . – Vipulk617 Jun 30 '16 at 13:19
  • you are correct iOS 10.x is a beta software http://9to5mac.com/2016/05/03/ios-9-3-2-tvos-9-2-1-el-capitan-10-11-5-fourth-beta/ I mistyped and was thinking iOS 9.3. Sorry about that. the tvOS version I am using is the most recent release version. I have both Xcode 8 and 7 on my machine and get them confused sometimes. I've updated this post / comment to reflect that. Again, apologies for the mistype – TimD Jun 30 '16 at 13:52
  • Thanks for giving valuable time and quick response. I have solved my problem using two lines of code playerController.showsPlaybackControls = false; playerController.showsPlaybackControls = true; in UIApplicationWillEnterForegroundNotification target. But i am not sure it is right way or not. – Vipulk617 Jul 01 '16 at 10:35