How can I get access to ViewController from MPNowPlayingInfoCenter - ios

I'm working on an audio player and came across this situation:
I have a TrackDetailView that opens to play a track when I click on a TableView cell. Also I have implemented background playback and MPNowPlayingInfoCenter.
When I press Pause or Play button in MPNowPlayingInfoCenter, I want the button image to change on my TrackDetailView as well, but I just can't do it. I will be glad for any help.
Important note(!) TrackDetailView and MPNowPlayingInfoCenter are in different classes. When I put them in one class everything works without problems.
My code:
class TrackDetailView: UIView {
var audioPlayer = AudioPlayer()
...
#IBOutlet var playPauseButton: UIButton!
...
//Loading with view
func set() {
setupMediaPlayerNotificationView()
}
}
class AudioPlayer {
var trackDetailView: TrackDetailView?
func setupMediaPlayerNotificationView() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.addTarget { [unowned self] event in
if self.player.rate == 0.0 {
self.player.play()
self.trackDetailView?.playPauseButton.setImage(#imageLiteral(resourceName: "pause"), for: .normal)
}
return .commandFailed
}
commandCenter.pauseCommand.addTarget { [unowned self] event in
if self.player.rate == 1.0 {
self.player.pause()
self.trackDetailView?.playPauseButton.setImage(#imageLiteral(resourceName: "play"), for: .normal)
return .success
}
return .commandFailed
}
...
}
}
I think I have a problem with an instance of the TrackDetailView class.

You need to make sure that for this instance
var audioPlayer = AudioPlayer()
you set
audioPlayer.trackDetailView = self
e.x here
func set() {
audioPlayer.trackDetailView = self
audioPlayer.setupMediaPlayerNotificationView()
}

Related

Play/Pause button not updating in lockscreen always it will show pause button in Swift4 ios

I have a Audio player that can play from the iOS command center and lock screen. When I toggle a play/pause button in my app, it should update the play/pause button in the command center (MPRemoteCommandCenter) by updating the nowPlayingInfo (MPNowPlayingInfoCenter). But it's not updating.
But when i try to control from lockscreen Play/Pause its updating in my app toggle buttons this works fine.But similarly when i am controlling from inside my app play/pause button. its not updating play/pause button in lockscreen.Always its show pause button.
Iam setting Playbackrate 1.0 for play and 0.0 for pause.But still its not updating..
Can some please give me any suggestions or anything missing in this below code.
func setupLockScreenDisplay() {
var nowPlayingInfo = [String: Any]()
nowPlayingInfo[MPMediaItemPropertyTitle] = self.currentSongName
nowPlayingInfo[MPMediaItemPropertyPlaybackDuration] = self.currentAlbum == nil ? self.totalDurationTime : playerItem.asset.duration.seconds
nowPlayingInfo[MPNowPlayingInfoPropertyPlaybackRate] = isPlaying ? 1.0 : 0.0
nowPlayingInfo[MPNowPlayingInfoPropertyMediaType] = NSNumber(value: MPNowPlayingInfoMediaType.audio.rawValue)
nowPlayingInfo[MPNowPlayingInfoPropertyElapsedPlaybackTime] = self.currentAlbum == nil ? self.storyPlayer.currentTime : CMTimeGetSeconds(player.currentTime())
// Set the metadata
MPNowPlayingInfoCenter.default().nowPlayingInfo = nowPlayingInfo
MPNowPlayingInfoCenter.default().playbackState = .playing
}
below code for remote command below code is working fine..i can able to control from lockscreen
func setupRemoteCommandCenter() {
let commandCenter = MPRemoteCommandCenter.shared()
commandCenter.playCommand.isEnabled = true
commandCenter.playCommand.addTarget { event in
self.play()
return .success
}
commandCenter.pauseCommand.isEnabled = true
commandCenter.pauseCommand.addTarget { event in
self.pause()
return .success
}
commandCenter.nextTrackCommand.isEnabled = true
commandCenter.nextTrackCommand.addTarget { event in
self.nextPlay()
return .success
}
commandCenter.previousTrackCommand.isEnabled = true
commandCenter.previousTrackCommand.addTarget { event in
self.lastPlay()
return .success
}
commandCenter.togglePlayPauseCommand.isEnabled = true
}
Here code for inside play button
func play() {
if self.currentAlbum == nil {
self.storyPlayer.play()
} else {
self.player.play()
}
self.isPlaying = true
NotificationCenter.default.post(name: Notification.Name(rawValue: kPlayerManagerChangePlayingStateRsp), object: nil)
setupLockScreenDisplay()
setupRemoteCommandCenter()
MPNowPlayingInfoCenter.default().nowPlayingInfo![MPNowPlayingInfoPropertyPlaybackRate] = 1
MPNowPlayingInfoCenter.default().nowPlayingInfo![MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(player.currentTime())
}
code inside pause button
func pause() {
if self.currentAlbum == nil {
self.storyPlayer.pause()
} else {
self.player.pause()
}
self.isPlaying = false
NotificationCenter.default.post(name: Notification.Name(rawValue: kPlayerManagerChangePlayingStateRsp), object: nil)
setupLockScreenDisplay()
setupRemoteCommandCenter()
MPNowPlayingInfoCenter.default().nowPlayingInfo![MPNowPlayingInfoPropertyPlaybackRate] = 0
MPNowPlayingInfoCenter.default().nowPlayingInfo![MPNowPlayingInfoPropertyElapsedPlaybackTime] = CMTimeGetSeconds(player.currentTime())
}
add this code in your view did load func or app delegate
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers, .allowAirPlay])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
}
catch {
print(error)
}
and on your background modes in capabilities.

AVPlayer auto play when enter to foreground from background using RxSwift

I have a video and play in ViewController.
But when I enter to background and then back to foreground, the video sometimes pause.
Have any idea let code add notification to know the user back to foreground and make the video auto playing.
Thanks.
extension UIViewController: AVPlayerViewControllerDelegate {
private func playVideo(url: URL, completeHandler: #escaping () -> Void) -> Void {
let player = AVPlayer(url: url)
let vc = AVPlayerViewController.init()
vc.videoGravity = "AVLayerVideoGravityResizeAspectFill"
vc.showsPlaybackControls = false
vc.player = player
// add child view controller
self.view.addSubview(vc.view)
self.addChildViewController(vc)
vc.didMove(toParentViewController: self)
vc.view.backgroundColor = UIColor.clear
// constraints
vc.view.snp.makeConstraints { (make) in
make.top.equalToSuperview()
make.bottom.equalToSuperview()
make.left.equalToSuperview()
make.right.equalToSuperview()
}
// end play
_ = NotificationCenter.default.rx
.notification(NSNotification.Name.AVPlayerItemDidPlayToEndTime)
.takeUntil(self.rx.deallocated)
.subscribe(onNext: { [weak self] _ in
for vc in self?.childViewControllers ?? [] {
if vc is AVPlayerViewController {
vc.view.removeFromSuperview()
vc.removeFromParentViewController()
}
}
completeHandler()
})
vc.player?.play()
}
}
Well, you have callbacks exactly for that purpose in UIApplicatonDelegate - https://developer.apple.com/documentation/uikit/uiapplicationdelegate
applicationDidEnterBackground(_:)
func applicationWillEnterForeground(UIApplication)
So you will have to pause/resume video from those callback methods.

Receive delegate method in different class

I'm developing a music player by using Jukebox library. It has a delegation which warn elements when songs duration/ current time etc. changed. In Jukebox example, all of codes are in ViewController. Therefore, for example when current time changes, it also changes sliders value. I want to create a player class which is usable by more than one ViewController. When I put delegate methods inside this model class. How can I reach them in ViewController classes?
First Screenshot is first ViewController which has delegates methods and initialize player.
MainViewController
I need to reach that delegate methods in secondViewController to update the UI.
class ViewController: UIViewController, JukeboxDelegate {
var jukebox : Jukebox!
override func viewDidLoad() {
super.viewDidLoad()
configureUI()
// begin receiving remote events
UIApplication.shared.beginReceivingRemoteControlEvents()
// configure jukebox
jukebox = Jukebox(delegate: self, items: [
JukeboxItem(URL: URL(string: "http://www.kissfm.ro/listen.pls")!),
JukeboxItem(URL: URL(string: "http://www.noiseaddicts.com/samples_1w72b820/2514.mp3")!),
JukeboxItem(URL: URL(string: "http://www.noiseaddicts.com/samples_1w72b820/2958.mp3")!)
])!
}
// MARK:- JukeboxDelegate -
func jukeboxDidLoadItem(_ jukebox: Jukebox, item: JukeboxItem) {
print("Jukebox did load: \(item.URL.lastPathComponent)")
}
func jukeboxPlaybackProgressDidChange(_ jukebox: Jukebox) {
if let currentTime = jukebox.currentItem?.currentTime, let duration = jukebox.currentItem?.meta.duration {
let value = Float(currentTime / duration)
slider.value = value
populateLabelWithTime(currentTimeLabel, time: currentTime)
populateLabelWithTime(durationLabel, time: duration)
} else {
resetUI()
}
}
func jukeboxStateDidChange(_ jukebox: Jukebox) {
UIView.animate(withDuration: 0.3, animations: { () -> Void in
self.indicator.alpha = jukebox.state == .loading ? 1 : 0
self.playPauseButton.alpha = jukebox.state == .loading ? 0 : 1
self.playPauseButton.isEnabled = jukebox.state == .loading ? false : true
})
if jukebox.state == .ready {
playPauseButton.setImage(UIImage(named: "playBtn"), for: UIControlState())
} else if jukebox.state == .loading {
playPauseButton.setImage(UIImage(named: "pauseBtn"), for: UIControlState())
} else {
volumeSlider.value = jukebox.volume
let imageName: String
switch jukebox.state {
case .playing, .loading:
imageName = "pauseBtn"
case .paused, .failed, .ready:
imageName = "playBtn"
}
playPauseButton.setImage(UIImage(named: imageName), for: UIControlState())
}
print("Jukebox state changed to \(jukebox.state)")
}
func jukeboxDidUpdateMetadata(_ jukebox: Jukebox, forItem: JukeboxItem) {
print("Item updated:\n\(forItem)")
}
If you want to use music playerview from anywhere
you should write player class all code for playing files should be in this and just need to pass array of list from anywhere and it can played.and use custom delegation for appropriate response in playerview
for UI make a viewcontroller and u can show it from any class so you can call from anywhere in the app

Do not trigger remoteControlReceived in UIView after load the view

I implement an audio player using AVAudioPlayer as the following.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
try? AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient, with: AVAudioSessionCategoryOptions.allowBluetooth)
try? AVAudioSession.sharedInstance().setActive(true)
if let path = Bundle.main.path(forResource: "LadyGaga-MillionReasons", ofType: "mp3") {
let url = URL(string:path)
self.audioPlayer = try? AVAudioPlayer(contentsOf: url!)
self.audioPlayer?.prepareToPlay()
}
self.becomeFirstResponder()
UIApplication.shared.beginReceivingRemoteControlEvents()
}
override var canBecomeFirstResponder: Bool{
get{
return true
}
}
override var canResignFirstResponder: Bool{
get{
return true
}
}
#IBAction func btnPlay_TouchUpInsde(_ sender: Any) {
if (self.audioPlayer?.isPlaying)! {
self.audioPlayer?.pause()
self.btnPlay.setTitle("Play", for: .normal)
}else{
self.audioPlayer?.play()
self.btnPlay.setTitle("Pause", for: .normal)
}
}
override func remoteControlReceived(with event: UIEvent?) {
if let e = event , e.type == .remoteControl {
if e.subtype == UIEventSubtype.remoteControlPause {
self.audioPlayer?.pause()
}else if(e.subtype == .remoteControlPlay){
self.audioPlayer?.play()
}else if(e.subtype == .remoteControlTogglePlayPause){
if self.audioPlayer!.isPlaying {
self.audioPlayer?.pause()
}else{
self.audioPlayer?.play()
}
}
}
}
}
The app is launched, then click the play button, the audio plays ok.
Then I pause the audio by a headset,and all works ok.
Another situation:
The app is launched, then I want to start the audio by a headset, it does not work.
It seems to that the view is not the first responder before I click the button, even I add self.becomeFirstResponder in init function.
Who know the why the app can not get the remoteControlReceived event when do not click the button.
I implement a sample. https://github.com/leogeng/AuidoTest.git

Impossible to stop AVPlayer

I am currently testing the use of AVPlayer with audio streaming url, using Swift. There are play() and pause() methods, but the problem is that, pausing only, the stream remains cached in the device.
Here is my test code :
import UIKit
import AVFoundation
class ViewController: UIViewController {
let player = AVPlayer(URL: NSURL(string: "http://streaming.radio.rtl.fr/rtl-1-48-192")!)
#IBOutlet weak var btnPlay: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnPress(sender: AnyObject) {
if (btnPlay.titleLabel?.text == "Play") {
initPlayer()
btnPlay.setTitle("Stop", forState: UIControlState.Normal)
} else {
stopPlayer()
btnPlay.setTitle("Play", forState: UIControlState.Normal)
}
}
func initPlayer() {
player.play()
}
func stopPlayer() {
// player.currentItem = nil // Last thing I tried, but generate an error
player.pause()
}
}
Here are the issues when trying somethings :
player = nil : "Cannot assign a value of type 'NilLiteralCOnvertible' to a value of type 'AVPlayer'"
player.currentItem = nil : "Cannot assign to property: 'currentItem' is a get-only property"
I tried everything, even through AVQueuePlayer without any effective result. (obviously, since I only have one item in my case).
How to stop AVPlayer or destroy his instance ?
From this post I found the best solution to completely stop AVPlayer before you leave or start a new player:
videoPlayer.replaceCurrentItemWithPlayerItem(nil)
[Update] For SWIFT 3:
player.replaceCurrentItem(with: nil)
If you declare player as an optional variable, you can then set the player to nil to deallocate it.
Silly example but it shows what happens:
import UIKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var btnPlay: UIButton!
var player:AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnPress(sender: AnyObject) {
if (btnPlay.titleLabel?.text == "Play") {
initPlayer()
btnPlay.setTitle("Stop", forState: UIControlState.Normal)
} else {
stopPlayer()
btnPlay.setTitle("Play", forState: UIControlState.Normal)
}
}
func initPlayer() {
if let play = player {
print("playing")
play.play()
} else {
print("player allocated")
player = AVPlayer(URL: NSURL(string: "http://streaming.radio.rtl.fr/rtl-1-48-192")!)
print("playing")
player!.play()
}
}
func stopPlayer() {
if let play = player {
print("stopped")
play.pause()
player = nil
print("player deallocated")
} else {
print("player was already deallocated")
}
}
}
SWIFT 3 Version:
player.replaceCurrentItem(with: nil)

Resources