iOS Swift App AVPlayerController dismiss not working on external display - ios

I built an app to display several short movies for an exhibition, the app is fully functional on the iPad, but on external display the video will stay at the last frame.
We want to use the iPad as external control for the display, but users should always see the video selection gui when no video is played
That is the snippet to start a video
let playerItem = AVPlayerItem(URL: NSURL(fileURLWithPath: videoPath!))
let player = AVPlayer(playerItem: playerItem)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(VideoCollectionViewController.playerDidReachEnd(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: playerItem)
playerController.player = player
self.presentViewController(playerController, animated: true) {
self.playerController.player!.play()
}
And here is the function called when it reaches the end.
func playerDidReachEnd(notification: NSNotification) {
NSNotificationCenter.defaultCenter().removeObserver(self)
playerController.player?.currentItem
playerController.dismissViewControllerAnimated(true, completion: nil)
}
It does also not fall back to the app screen if the video is stopped using the "Done" button of the player
Is there any trick to make it dismiss the AVPlayerController so it would fall back to my app directly on every display?

I totally forgot to post my answer here, in case anybody else might come to that problem.
The following code is to be placed in the playerDidReachEnd, not much different, but it removed the player from all screens.
playerController.dismissViewControllerAnimated(true, completion: nil)
playerController.view.removeFromSuperview()
self.presentedViewController?.dismissViewControllerAnimated(true, completion: nil)

Related

Play a video from url and validate if whole video was watched

I have this json and I have to play a video
What is the best way to play video and validate or check if user watched whole video?Video could be (Tiktok - Vimeo - Dayli Motion) video but no Youtube Video
I tried to use AVPlayer but it doesn't work :
let videoURL = NSURL(string: "https://vimeo.com/601097657")
let player = AVPlayer(url: videoURL! as URL)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
I think the possible solution it could be a webView but I'm not sure if its possible to validate if user watched whole video
AVPlayer sends notifications on occasions like that.
Simply subscribe to notifications you need. In your case you need
NSNotification.Name.AVPlayerItemDidPlayToEndTime
implementing this would look something like this:
NotificationCenter.default.addObserver(self,
selector: #selector(itemDidPlayToEnd),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: nil)
And implement a selector for handling notification:
#objc private func itemDidPlayToEnd() {
// do smth
}
This is working for me.
class Player: AVPlayerViewController {
init(url: URL) {
super.init(nibName: nil, bundle: nil)
player = AVPlayer(url: url)
player?.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: 1), queue: DispatchQueue.main, using: { time in
if self.player?.currentItem?.status == .readyToPlay {
let currenTime = CMTimeGetSeconds((self.player?.currentTime())!)
let secs = Int(currenTime)
print(NSString(format: "%02d:%02d", secs/60, secs%60) as String)
}
})
}
This will print out how much of the video they have watched in seconds, which you could then check if this is equal to the total amount of time the video is.
Obviously you would have to implement methods to check if they have skipped part of the video or not, but that's for another question.
Check out more info Time watched. and Total Video Time

Dismissing AVPlayerViewController doesn't 'kill' the object - it's persisting

I am playing videos that are in my app bundle.
They are playing correctly.
However, when I call to dismiss the AVPlayerViewController, it visibly is removed from the view hierarchy but, if I turn off the iOS device and turn it back on again, on the lock screen there is a media control showing that video and a 'play' button.
If you touch play you only get the audio and no video.
My problem is I don't understand why the 'dismiss' is not completely 'killing' the player when I'm done with it.
Here is the presentation code:
let path = Bundle.main.path(forResource: filename, ofType: type)
let url = NSURL(fileURLWithPath: path!)
let player = AVPlayer(url: url as URL)
NotificationCenter.default.addObserver(self,
selector: #selector(VideoLibraryViewController.didFinishPlaying(notification:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: player.currentItem)
self.playerController = AVPlayerViewController()
self.playerController?.player = player
self.playerController?.allowsPictureInPicturePlayback = true
self.playerController?.showsPlaybackControls = YES
self.playerController?.delegate = self
self.playerController?.player?.play()
self.present(self.playerController!, animated: true, completion : nil)
Here is the dismissal code:
// Delegate can implement this method to be notified when Picture in Picture will start.
func playerViewController(_ playerViewController: AVPlayerViewController, willEndFullScreenPresentationWithAnimationCoordinator coordinator: UIViewControllerTransitionCoordinator)
{
self.playerController?.dismiss(animated: NO, completion: nil )
}
And here's what's remaining in the systemwide media player that is shown on the lock screen / control centre:
iOS 13 SDK ONLY: Here's the solution, but the answer is that despite dismissing the AVPlayerViewController, the AVPlayer object that it's knows about is persistent and that needs to be set to nil.
private func killVideoPlayer()
{
self.playerController?.player?.pause()
self.playerController?.player = nil
self.playerController?.dismiss(animated: YES, completion: { self.playerController = nil })
}
Previous SDK's, this still isn't working.
Neither is setting the AVAudioSession.active to false... ?!?! Still need a pre iOS 13 SDK solution.

AVPlayer play continuously

I have an array of object. Each object has a property url. Using this url I am playing audio on AVPlayer.
func playAudio(url: URL)
{
let player = AVPlayer(url: url )
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true)
{
do
{
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
}
catch _ as NSError {
}
playerViewController.player!.play()
}
NotificationCenter.default.addObserver(self, selector: #selector(CourseDetailViewController.moviePlayBackDidFinish(notification:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
}
I want to play the audios of all objects continuously. For which I have set
NotificationCenter.default.addObserver(self, selector: #selector(CourseDetailViewController.moviePlayBackDidFinish(notification:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
In CourseDetailViewController.moviePlayBackDidFinish I am again calling playAudio. I then took player as a global variable so that it is not initialised again. But there is no overload of AVPlayer where we can supply url to already existing AVPlayer. And if do let player = AVPlayer(url: url ) for next audio, it doesn't play because the previous AVPlayer instance is in the foreground.
This is the challenge I am facing in playing continuous audios. I would appreciate if there a better and neat way to do this.
I did the same in AVPlayer, but playing video continuously. I hope you can solve this issue exactly the same way. Add an observer to check if the player reached at the end of item. When ever each item reached at the end, notification will call.
Adding observer:
NotificationCenter.default.addObserver(self,
selector: #selector(playerItemDidReachEnd(notification:)),
name: NSNotification.Name.AVPlayerItemDidPlayToEndTime,
object: avPlayer.currentItem)
avPlayer.play()
Notification method:
func playerItemDidReachEnd(notification: Notification){
//TODO: Add your code here..
}
Did you look at AVQueuePlayer. This player is used to play a number of items in sequence. SDK Docs at link

How to handle Next and Previous button in AVPlayerViewController?

In my app i have used AVPlayerViewController to play audio file using URL.Now i want to handle Next and Previous button click event in AVPlayerViewController.
Here is code which i have tried :
func playSound(_ url: URL) {
let avPlayerItem = AVPlayerItem(url: url)
let avPlayer = AVPlayer(playerItem: avPlayerItem)
let player = AVPlayerViewController()
player.player = avPlayer
avPlayer.play()
NotificationCenter.default.addObserver(self, selector: #selector(self.donePlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)//NSNotification.Name.AVPlayerItemDidPlayToEndTime
self.present(player, animated: true, completion: nil)
}
self.playSound("Here your URL")
If any one know thenPlease let me know.
Thanks
check this demo project:project demo
where it is shown how to put a custom buttons on top of AVPlayer and they responds to clicks.
What was done:
Xib (uses auto layout) file created which hold custom buttons like next, replay and others.
Buttons were connected to the source which prints what needs to do. Like: play next or replay.
Was added observer which observe when video is finished to play.
When video is finished to play xib file with buttons will be shown.

View unresponsive after AVPlayerViewController dismissed - Swift

I have a parent view with a play button that instantiates an AVPlayerViewController object. If the user presses the player's default "Done" button, the player view disappears and the parent view works as expected. However, when playback finishes by reaching the end of the video, the player disappears but the parent view screen is unresponsive to button clicks. I don't understand because both scenarios are using the same callback to dismiss the player view.
Here is the code where I instantiate the AVPlayer and set up the two notifications for how the playback can be completed:
func playVideoLocally() {
// create player
let playerItem = AVPlayerItem(asset: AVAsset(URL: NSURL(string: media.url)!))
player = AVPlayer(playerItem: playerItem)
// create modal view with callback for done button
// This works fine
playerController = CustomAVPlayerViewController()
playerController.player = player
playerController.modalPresentationStyle = UIModalPresentationStyle.OverFullScreen
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "onLocalPlayCompletion:",
name: CustomAVPlayerViewController.viewWillDisappearNotification,
object: nil)
self.presentViewController(playerController, animated: true, completion: nil)
// create player completion callback
// This leads to unresponsive parent view
NSNotificationCenter.defaultCenter().addObserver(self, selector: "onLocalPlayCompletion:",
name: AVPlayerItemDidPlayToEndTimeNotification,
object: playerItem)
// play it
player.play()
}
Here is my onLocalPlayCompletion callback that both scenarios are calling:
func onLocalPlayCompletion(note: NSNotification) -> Void {
playerController.view.removeFromSuperview()
onCompletion()
}
I found the problem. The only reason why pressing "Done" wasn't affecting the parent view is because it's using a default AVPlayer action to dismiss itself. My attempt to dismiss the player from inside onLocalPlayCompletion(),
playerController.view.removeFromSuperview()
isn't correct, and in my case I replaced it with
self.dismissViewControllerAnimated(true, completion: nil)
which does what I want.

Resources