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.
Related
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
I have set up a function that creates an AVPlayerViewController instance in order to play an audio track (stored in our server).
func playAudio(_ url: URL) {
let avAssest = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: avAssest)
audioPlayer = AVPlayer(playerItem: playerItem)
let playerViewController = AVPlayerViewController()
playerViewController.player = audioPlayer
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
self.present(playerViewController, animated: true, completion: {
self.audioPlayer.play()
})
}
The player works fine while in the foreground. If I go to the lock screen, the audio continues to play. If I press the Pause button in the lock screen, the audio pauses. The problem is, if I then click play on the lock screen, the audio I was listening to resumes, but a second instance of the audio starts playing from the start, so now i have 2 instances of the audio playing simultaneously. If I pause again and press play again, yet another instance of the audio starts playing.
How can I fix this so the lock screen buttons only affect the original audio?
I'm working on Xcode 11, with Swift 4.2.
The problem cannot be reproduced based on the code you showed. Therefore it sounds like you have an extra reference somewhere to self.audioPlayer, and the problem is caused by code you did not reveal to us.
To test that theory, let's start by not making it a global. There's no need for that within the scope of the question.
Modify your code to look like this (note the changes to make this a local):
let avAssest = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: avAssest)
let audioPlayer = AVPlayer(playerItem: playerItem) // *
let playerViewController = AVPlayerViewController()
playerViewController.player = audioPlayer
try? AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
self.present(playerViewController, animated: true, completion: {
audioPlayer.play() // *
})
Now delete the audioPlayer property and, if your code fails to compile, comment out everything that refers to it until the code does compile. Test the app; all is well. Now go back and figure out where in the code you did not show us the problem is coming from.
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.
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)
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.