I've an AVPlayer embedded inside a UIViewController. I've added AVPlayerItemDidPlayToEndTime notification to the UIViewController so that I can restart my AVPlayer once it finished playing currentItem
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object:player.currentItem)
#objc private func playerDidFinishPlaying(_ notification: Notification) {
guard let url = URL(string: self.video.alt_content) else { return }
let item = self.getAssetToPlay(url: url)
item.seek(to: .zero, toleranceBefore: .zero, toleranceAfter: .zero)
self.player.replaceCurrentItem(with: item)
player.play()
}
private func getAssetToPlay(url: URL) -> AVPlayerItem {
let asset = AVURLAsset(url: url)
let item = AVPlayerItem(asset: asset)
return item
}
But this notification is also called from AppDelegate's applicationDidBecomeActive, like when my app comes from background. And so rather than playing AVPlayer from current time, code restarts the player in playerDidFinishPlaying.
I need a way so that when app comes from background it starts playing where it was left. And on completely finishing currentItem, it restarts the AVPlayerItem
So the problem was very stupid to be honest. It was due to the fact that I added the Observer before initializing the AVPlayer. Once I corrected the code, it no longer clashes with applicationDidBecomeActive
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 an array of URLs that I then turn into an array of AVPlayerItems and use AVQueuePlayer to loop through the videos- usually 1-7 videos at a time. However when it stops I am not sure how to start it again to play the same array of videos until the user switches to a different view controller.
in viewDidLoad this creates the array of playerItems
//creates playerItems to play videos in a queue
postURLs?.forEach{(url) in
let asset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: asset)
playerItems.append(playerItem)
}
public func playVideo() {
player = AVQueuePlayer(items: playerItems)
player.seek(to: CMTime.init(value: 0, timescale: 1))
playerLayer = AVPlayerLayer(player:player)
playerLayer.frame = self.lifieView.frame
lifieView.layer.addSublayer(playerLayer)
player.play()
//restart video maybe? Tested but did not work - hits function
NotificationCenter.default.addObserver(
forName: .AVPlayerItemDidPlayToEndTime,
object: nil,
queue: nil) { [weak self] _ in self?.restart2() }
}
//this is test function to restart (works with AVPlayer with single video)
private func restart2(){
player.seek(to: CMTime.zero)
player.play()
}
I got it working after much research and testing.
What I did was change the restart function to first remove all items from the player, then go through the array of playerItems and add them back into the queue- then have the player start back at the beginning.
func restartPlayer(){
player.removeAllItems()
playerItems.forEach{
player.insert($0, after:nil)
}
player.seek(to: .zero)
}
I am trying to play video playlist in loop and also play individual video clip from playlist in loop in AVQueuePlayer using AVPlayerItem, but i am unable to find the solution for same below is the code that i have tried so far
General
var player : AVQueuePlayer?
var playerLayer: AVPlayerLayer?
var playerItem: [AVPlayerItem] = []
func playAtIndex(index:Int){
for i in index ..< playerItem.count {
let obj = playerItem[i]
if (self.player?.canInsert(obj, after: nil))! {
obj.seek(to: .zero, completionHandler: nil)
self.player?.insert(obj, after: nil)
}
}
}
Initialise video player
self.player = AVQueuePlayer.init(items: self.playerItem)
self.playerLayer = AVPlayerLayer(player: self.player)
self.playerLayer?.frame = self.view!.bounds
self.playerLayer?.videoGravity = AVLayerVideoGravity.resizeAspect
self.view!.layer.addSublayer(self.playerLayer!)
self.player?.play()
code done so far for looping playlist, this works but some of the video from the loop does not play sometimes.
self.playAtIndex(index: 0)
code done for looping individual video clip in playlist, but does not work
let playerItem: AVPlayerItem = note.object as! AVPlayerItem // here we get current item
playerItem.seek(to: CMTime.zero, completionHandler: nil)
self.player?.play()
Any help will be great.!!
To loop the playerItems in AVQueuePlayer, you need to add a an observer to the notification - AVPlayerItemDidPlayToEndTimeNotification
A notification that's posted when the item has played to its end time.
NotificationCenter.default.addObserver(self, selector: #selector(playerEndedPlaying), name: Notification.Name("AVPlayerItemDidPlayToEndTimeNotification"), object: nil)
The method playerEndedPlaying(_:) will be called whenever the notification is fired.
#objc func playerEndedPlaying(_ notification: Notification) {
DispatchQueue.main.async {[weak self] in
if let playerItem = notification.object as? AVPlayerItem {
self?.player?.remove(playerItem)
playerItem.seek(to: .zero, completionHandler: nil)
self?.player?.insert(playerItem, after: nil)
if playerItem == self?.playerItems?.last {
self?.pauseVideo()
}
}
}
}
The above method will be called every time a playerItem in the AVQueuePlayer ends playing.
AVQueuePlayer's insert(_:after:) is called where each playerItem is appended to the queue.
afterItem
The player item that the newly inserted player item should
follow in the queue. Pass nil to append the item to the queue.
Loop is identified using playerItem == self?.playerItems?.last. You can add your custom handling here. I've paused the player once all the videos end playing.
Problem : In collectionview cell which has player
if I play two Video simultaneously and seek first Video to end then AVPlayerItemDidPlayToEndTime fired for two times and both videos restarted
In collection view cell I have
override func awakeFromNib() {
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem, queue: .main, using: {[weak self] (notification) in
if self?.player != nil {
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
})
}
and one play button action which play the video.
In cell I have slider to seek.
Any Help would be appreciated
Make sure that player and player?.currentItem are not equal to nil when you're registering for notifications. To me, it seems like one of them was nil and you're basically subscribing to all of the .AVPlayerItemDidPlayToEndTime notifications (since object is nil).
To avoid that, subscribe to the notifications right after assigning AVAsset to the player.
Swift 5.1
Pass the item as your object:
// Stored property
let player = AVPlayer(url: videoUrl)
// When you are adding your video layer
let playerLayer = AVPlayerLayer(player: player)
NotificationCenter.default.addObserver(self, selector: #selector(playerDidFinishPlaying), name: .AVPlayerItemDidPlayToEndTime, object: player.currentItem)
// add layer to view
Then when you get that notification, here's how you can get the currentItem:
// Grab the item from the notification object and ensure its the same item as the current players item
if let item = notification.object as? AVPlayerItem,
let currentItem = player.currentItem,
item == currentItem {
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
// remove player from view or do whatever you need to do here
}
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