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
Related
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'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
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.
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
I am writing an app that need display different video according to the selection of the user. When the user select a video, the function playVideo will be called. And after the video finish playing, then the videoView will be hidden again.
My code is as follows:
var player: AVPlayer?
func playVideo(String: videoFile) {
self.videoView.isHidden = false
let videoURL: NSURL = Bundle.main.url(forResource: videoFile, withExtension: "mp4")! as NSURL
self.player = AVPlayer(url: videoURL as URL)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.videoView.frame
self.videoView.layer.addSublayer(playerLayer)
let duration : Int64 = 0
let preferredTimeScale : Int32 = 1
let seekTime : CMTime = CMTimeMake(duration, preferredTimeScale)
self.player?.seek(to: seekTime)
self.player?.play()
NotificationCenter.default.addObserver(self, selector: #selector(self.playerItemDidReachEnd), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: player?.currentItem)
}
#objc func playerItemDidReachEnd()
{
self.player?.pause()
self.videoView.isHidden = true
NotificationCenter.default.removeObserver(self)
}
However, with the code above, i have several question:
How to delete / deallocate the player gracefully? If just using my current code, will it consume lots of memory?
Every time, when the user press a button, the function playVideo will be called, and the corresponding player will be created and play. Is this the right way to do so? Is there any other method or more efficient way or elegant way to do so?
I did try to replace the code on creation of the player by the following, but it fails to play the video.
let playerItem: AVPlayerItem = AVPlayerItem(url: videoURL as URL)
self.player? = AVPlayer(playerItem: playerItem)
Thank you