I am using AVQueuePlayer to playback a batch of audio files. I'd like to insert a pause in between items. Any idea how to do that?
Here the method that adds items into the queue:
func addItemToAudioQueue (file:String, last:Bool) {
let urlPath = Bundle.main.path(forResource: file, ofType: "mp3")
let fileURL = NSURL(fileURLWithPath:urlPath!)
let playerItem = AVPlayerItem(url:fileURL as URL)
if last {
NotificationCenter.default.addObserver(self, selector: #selector(self.playerDidFinishPlaying(sender:)), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
}
queuePlayer.insert(playerItem, after: nil)
}
Add an observer for the notification named AVPlayerItemDidPlayToEndTimeNotification that will be fired every time an AVPlayerItem is finished playing, i.e.
NotificationCenter.default.addObserver(self, selector: #selector(playerEndedPlaying), name: Notification.Name("AVPlayerItemDidPlayToEndTimeNotification"), object: nil)
AVPlayerItemDidPlayToEndTimeNotification
A notification that's posted
when the item has played to its end time.
Next, when the playerEndedPlaying method is called after the notification is fired, pause() the AVQueuePlayer initially and then play() it again after 5 seconds like so,
#objc func playerEndedPlaying(_ notification: Notification) {
self.player?.pause()
DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {[weak self] in
self?.player?.play()
}
}
Related
I've created a button to play audio (streaming from a link) with AVPlayer in Swift. How to stop the audio automatically when the audio time is finished?
Here is some of my code :
var player : AVPlayer?
url = URL(string: "https://cdn.islamic.network/quran/audio/128/ar.alafasy/162.mp3")
let playerItem:AVPlayerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem: playerItem)
player?.play()
For example, in this URL (https://cdn.islamic.network/quran/audio/128/ar.alafasy/162.mp3), the audio length is 19 seconds. So after 19 seconds I play the audio, the audio must stop.
Thank you :)
You can use the NSNotificationCenter Observer
NSNotificationCenter.defaultCenter().addObserver(self, selector: "playerDidFinishPlaying:", name: AVPlayerItemDidPlayToEndTimeNotification, object: item)
player?.play()
and stop player in this function
func playerDidFinishPlaying(note: NSNotification) {
// Your code here
player?.pause()
player?.replaceCurrentItem(with: nil)
}
There are 2 pages(ViewController) in Main.storyboard: HomeViewController and DetailViewController
I gave a storyboard ID for the DetailViewController:DetailPage
I have added a button into first page to take user to the second page.
I also have added a back button into second page to take user to the first page back.
I need to show a video in the background in all pages.
So I have added an UIView component in all pages to show a video.
I gave 0,0,0,0 constraint values for these 2 UIView components in each pages.
First let me share source codes and then I would like to ask my questions.
HomeViewController.swift file
import UIKit
import AVFoundation
class HomeViewController: UIViewController {
private var player: AVPlayer!
#IBOutlet weak var outlet4TheVideoUiView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
configureVideoIssue()
}
override func viewWillAppear(_ animated: Bool) {
configureVideoIssue()
}
func configureVideoIssue()
{
// BACKGROUND VIDEO SCOPE STARTS
let path = URL(fileURLWithPath: Bundle.main.path(forResource: "clouds", ofType: "mp4")!)
let player = AVPlayer(url: path)
self.player = player
let newLayer = AVPlayerLayer(player: player)
newLayer.frame = self.view.bounds
outlet4TheVideoUiView.frame = self.view.bounds
outlet4TheVideoUiView.layer.addSublayer(newLayer)
newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
player.play()
// video bitince tekrar oynatmak için
player.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
NotificationCenter.default.addObserver(self, selector: #selector(self.videoDidPlayToEnd(notification:)),
name: NSNotification.Name(rawValue: "AVPlayerItemDidPlayToEndTimeNotification"), object: player.currentItem)
NotificationCenter.default.addObserver(self, selector: #selector(enteredBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(enteredForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
// BACKGROUND VIDEO SCOPE ENDS
}
#objc func enteredBackground() {
player.pause()
}
#objc func enteredForeground() {
player.play()
}
#objc func videoDidPlayToEnd(notification: Notification)
{
let player: AVPlayerItem = notification.object as! AVPlayerItem
player.seek(to: .zero, completionHandler: nil)
}
#IBAction func linkBtnClick(_ sender: UIButton) {
let controller = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "DetailPage") as! DetailViewController
controller.modalPresentationStyle = .fullScreen
//present(controller, animated: true, completion: nil)
show(controller, sender: nil)
}
}
DetailViewController.swift file
import UIKit
import AVFoundation
class DetailViewController: UIViewController {
private var player: AVPlayer!
#IBOutlet weak var outlet4TheVideoUiView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
configureVideoIssue()
// Do any additional setup after loading the view.
}
func configureVideoIssue()
{
// BACKGROUND VIDEO SCOPE STARTS
let path = URL(fileURLWithPath: Bundle.main.path(forResource: "clouds", ofType: "mp4")!)
let player = AVPlayer(url: path)
self.player = player
let newLayer = AVPlayerLayer(player: player)
newLayer.frame = self.view.bounds
outlet4TheVideoUiView.frame = self.view.bounds
outlet4TheVideoUiView.layer.addSublayer(newLayer)
newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
player.play()
// video bitince tekrar oynatmak için
player.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none
NotificationCenter.default.addObserver(self, selector: #selector(self.videoDidPlayToEnd(notification:)),
name: NSNotification.Name(rawValue: "AVPlayerItemDidPlayToEndTimeNotification"), object: player.currentItem)
NotificationCenter.default.addObserver(self, selector: #selector(enteredBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(enteredForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
// BACKGROUND VIDEO SCOPE ENDS
}
#objc func enteredBackground() {
player.pause()
}
#objc func enteredForeground() {
player.play()
}
#objc func videoDidPlayToEnd(notification: Notification)
{
let player: AVPlayerItem = notification.object as! AVPlayerItem
player.seek(to: .zero, completionHandler: nil)
}
#IBAction func btnBackClick(_ sender: UIButton) {
dismiss(animated: true, completion: nil)
}
}
First Question: I am sure that it shouldn't be like this because same codes are written in both ViewControllers. So how can I avoid this? Any suggestions? I should use same video ui view object as a background for all view controllers. Does it make sense? If it does not make sense. What should I do? For example, I use asp.net user control component in Visual Studio for this scenario. I create one video component(user control) and I can use this component for all pages as a background.
Second Question: (if we can't find a solution to the first question)Let's assume that app user sees Homepage right now. And let's assume that user clicks button to see secondpage(detail page)after 10th second. Button click action takes user to the second page but video starts from the first second. Video should continue from 10th second or 11th second. How can I do this?
Third Question: Second page comes from the bottom after i click button in first page. Can second page comes with fading first page out animation and appearing second page animation?
Important details: Video should stop after app is minimized and should continue after app is maximized by app user. And other important detail is: buttons are not inside of the video view. User can see buttons above the video with this way. Let me show layers for the components in order with an image:
One way that you can do this is set your view on the rootWindow and then set all your views' background color to clear.
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
let window=self.window!
backgroundVideoView.bounds = window.bounds
window.addSubview(backgroundVideoView)
playVideo()
return true
}
Here, backgroundVideoView refers to the view which will be playing your video.
Although, I must warn you, this will consume a lot of memory but will get you the desired behavior.
Here is how I create my AVPlayerViewController in my custom view:
private func configureAVPlayer(completion: #escaping () -> ()) {
DispatchQueue.global(qos: .background).async {
self.avPlayer = AVPlayer()
self.avPlayerController = AVPlayerViewController()
guard self.avPlayer != nil && self.avPlayerController != nil else {
return
}
self.avPlayerController!.showsPlaybackControls = false
self.avPlayerController!.player = self.avPlayer!
self.avPlayerController!.videoGravity = AVLayerVideoGravity.resizeAspectFill.rawValue
self.avPlayerController!.view.clipsToBounds = true
self.avPlayerController!.view.layer.cornerRadius = 10.0
DispatchQueue.main.async(execute: {
self.avPlayerController!.view.frame = self.imageView.frame
self.mediaView.addSubview(self.avPlayerController!.view)
self.mediaView.sendSubview(toBack: self.avPlayerController!.view)
self.parentController.addChildViewController(self.avPlayerController!)
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: self.avPlayer!.currentItem, queue: .main) { _ in
print("Replaying")
self.avPlayer?.seek(to: kCMTimeZero)
self.avPlayer?.play()
}
completion()
})
}
}
When my view disappears, I want to remove my AVPlayerViewController and it's observer, so I use:
override func removeFromSuperview() {
avPlayerController?.player?.pause()
avPlayerController?.view.removeFromSuperview()
avPlayerController?.removeFromParentViewController()
avPlayerController = nil
NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: self.avPlayer!.currentItem)
}
However, the next time I create this view, and call configureAVPlayer, the completion for my observer gets called twice and I hear the audio of my video twice, aka two videos playing at once.
Am I not removing the observer correctly?
I'm trying to call a function when a song finishes playing in a Swift playground. This is the code I'm using:
NotificationCenter.default.addObserver(self, selector: Selector(("playerDidFinishPlaying:")), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: winMusic)
However the function isn't called when the song finishes playing, and I don't know why? It's obviously not the same as in an iOS app.
var winMusic = NSURL(fileURLWithPath: Bundle.main.path(forResource: "win", ofType: "mp3")!)
var winPlayer: AVAudioPlayer? = nil
class Responder : NSObject {
func playerDidFinishPlaying() {
print("test")
/* Nothing printed here */
}
func action(sender: UIButton) {
NotificationCenter.default.addObserver(self, selector: #selector(Responder.playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: winMusic)
NotificationCenter.default.post(name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: nil)
winPlayer!.play()
}
}
An AVAudioPlayer does not post an AVPlayerItemDidPlayToEndTime notification. You need to give the player a delegate. See https://developer.apple.com/reference/avfoundation/avaudioplayerdelegate/1389160-audioplayerdidfinishplaying
Set your winPlayer delegate as,
winPlayer?.delegate = self
and implement the method
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if flag == true {
// song finished successfully here
}
}
where you will place the delegate is up to you.
So I can video to play when you click on the thumbnail but if I attach the ad to it, the ad will play over the video. how do I wait to play the ad after?
func playVideo(){
player = AVPlayer(URL: NSURL(string: String(vid))!)
player?.play()
if(ALInterstitialAd.isReadyForDisplay()){
ALInterstitialAd.show()
}
}
Did you try to register for AVPlayerItemDidPlayToEndTimeNotification?
Just add
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(YourViewController.itemDidFinishPlaying(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: nil)
when you play your video and implement the itemDidFinishPlaying: method when you need to present your advertising. Something like this:
func playVideo(){
player = AVPlayer(URL: NSURL(string: String(vid))!)
player?.play()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(YourViewController.itemDidFinishPlaying(_:)), name: AVPlayerItemDidPlayToEndTimeNotification, object: nil)
}
func itemDidFinishPlaying(notification : NSNotification){
if(ALInterstitialAd.isReadyForDisplay()){
ALInterstitialAd.show()
}
NSNotificationCenter.defaultCenter().removeObserver(self, name: AVPlayerItemDidPlayToEndTimeNotification, object: nil)
}