How to call function when song finished in a Swift Playground - swift

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.

Related

How to rewrite code for detect when AVPlayer is closed inside WKWebKit

I am need detect when AVPlayer was closed.
When I am open website with with video content inside webkit and press on player AVPlayer will be opened and I am can detect this using code below:
override func viewDidLoad() {
super.viewDidLoad()
webkit.load(request)
NotificationCenter.default.addObserver(self, selector: #selector(windowDidBecomeVisibleNotification(notif:)), name: NSNotification.Name("UIWindowDidBecomeVisibleNotification"), object: nil)
}
#objc func windowDidBecomeVisibleNotification(notif: Notification) {
if let isWindow = notif.object as? UIWindow {
if (isWindow !== self.view.window) {
print("New window did open, check what is the currect URL")
}
}
}
But I can't understand how rewrite this code if I am wan't opposite action when user close this player.
I am was try rewrite this code but my attempts is fails.
Need create two notificationcented observer for detect maximize and minimize avplayer
override func viewDidLoad() {
super.viewDidLoad()
// listen for videos playing in fullscreen
NotificationCenter.default.addObserver(self, selector: #selector(onDidEnterFullscreen(_:)), name: UIWindow.didBecomeVisibleNotification, object: view.window)
// listen for videos stopping to play in fullscreen
NotificationCenter.default.addObserver(self, selector: #selector(onDidLeaveFullscreen(_:)), name: UIWindow.didBecomeHiddenNotification, object: view.window)
}
#objc func onDidEnterFullscreen(_ notification: Notification) {
print("Enter Fullscreen")
}
#objc func onDidLeaveFullscreen(_ notification: Notification) {
print("Leave Fullscreen")
}

How can I show same video as a background in all View Controllers without any interruption during the pages transition?

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.

AvFoundation framework, AvPlayer pause method causes a crash after app minimized

I am showing mp4 video file in the background with UIView component and AVFoundation framework.
But it gives an error after app is minimized by app user. Because player.pause() method causes a crash as you see. Here is the error: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
import UIKit
import AVFoundation
import AVKit
class ViewController: UIViewController {
private var player: AVPlayer!
#IBOutlet weak var videoUiViewOutlet: UIView!
override func viewDidLoad() {
super.viewDidLoad()
self.setupView()
}
private func setupView()
{
let path = URL(fileURLWithPath: Bundle.main.path(forResource: "clouds", ofType: "mp4")!)
let player = AVPlayer(url: path)
let newLayer = AVPlayerLayer(player: player)
newLayer.frame = self.videoUiViewOutlet.frame
self.videoUiViewOutlet.layer.addSublayer(newLayer)
newLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
player.play()
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)
}
#objc func videoDidPlayToEnd(notification: Notification)
{
let player: AVPlayerItem = notification.object as! AVPlayerItem
player.seek(to: .zero, completionHandler: nil)
}
#objc func enteredBackground() {
print("scope: enteredBackground")
player.pause()
}
#objc func enteredForeground() {
print("scope: enteredForeground")
player.play()
}
}
How can I solve this problem? Video should pause after app is minimized. And video should continue after app is maximized.
The issue is because you haven't assigned the value to player property of the class ViewController, i.e.
private var player: AVPlayer!
And since it is a forced unwrap optional, it'll have nil as its default value. And using it in enteredBackground() method will result in runtime exception.
Solution:
In setupView() method, replace
let player = AVPlayer(url: path)
with
self.player = AVPlayer(url: path)

How to insert a pause in between items in AVQueuePlayer?

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()
}
}

NotificationCenter not fired with network change

I want to detect network changes. For example when switching from wifi to 4G or back. But my code doesn't detect these changes. I get only some output when the app has started.
I have this code:
import ReachabilitySwift
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let reachability = Reachability()!
NotificationCenter.default.addObserver(self, selector: #selector(self.reachabilityChanged),name: ReachabilityChangedNotification,object: reachability)
do{
try reachability.startNotifier()
}catch{
print("could not start reachability notifier")
}
}
func reachabilityChanged(note: NSNotification) {
let reachability = note.object as! Reachability
if reachability.isReachable {
if reachability.isReachableViaWiFi {
print("Reachable via WiFi")
} else {
print("Reachable via Cellular")
}
} else {
print("Network not reachable")
}
}
Did I have implemented something wrong?
The action method takes a parameter so the selector must be
#selector(reachabilityChanged(_:)) // self is not needed
And the method itself is supposed to be
func reachabilityChanged(_ notification: Notification) {
Selector is wrong
change this
NotificationCenter.default.addObserver(self, selector: #selector(self.reachabilityChanged),name: ReachabilityChangedNotification,object: reachability)
to
NotificationCenter.default.addObserver(self, selector: #selector(reachabilityChanged(_:),name: ReachabilityChangedNotification,object: reachability)
I hope this will work.
and also
func reachabilityChanged(note: NSNotification) {
to
func reachabilityChanged(note: NSNotification) {
name is wrong. Change
name: ReachabilityChangedNotification
to
name: NSNotification.Name.reachabilityChanged
or to
name: .reachabilityChanged
It will work.