I'm trying to get when you press the play / pause button of my earpods make an action. For this I am first trying to achieve change a text label ...
According to look at various websites, they performed this same but it does not work. Is there anything missing me add?
I have the following code in viewController.swift
override func viewDidLoad() {
super.viewDidLoad()
canBecomeFirstResponder()
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBOutlet weak var Cosa: UILabel!
func play() {
Cosa.text = "Play"
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override func remoteControlReceivedWithEvent(event: UIEvent?) {
guard let event = event else {
print("no event\n")
return
}
guard event.type == UIEventType.RemoteControl else {
print("received other event type\n")
return
}
switch event.subtype {
case UIEventSubtype.RemoteControlPlay:
print("received remote play\n")
play()
case UIEventSubtype.RemoteControlPause:
print("received remote pause\n")
case UIEventSubtype.RemoteControlTogglePlayPause:
print("received toggle\n")
default:
print("received \(event.subtype) which we did not process\n")
}
}
For any RemoteControlEvents you need to have the audio playing.
Preparing Your App for Remote Control Events
To receive remote control events, do the following:
Register handlers for each action you support. Use the shared
MPRemoteCommandCenter object to register handlers for different types
of events
Begin
playing audio. Your app must be the “Now Playing” app. An app does not
receive remote control events until it begins playing audio.
https://developer.apple.com/library/content/documentation/EventHandling/Conceptual/EventHandlingiPhoneOS/Remote-ControlEvents/Remote-ControlEvents.html
Also you can refer the below link for more clarification and Demo project
Receive remote control events without audio
Related
I'm new to Swift development and I'm trying to play/pause on lock screen multiple audio files using:
remoteControlReceived
But it is only allowing me to add it once meaning I can only control one audio file but I'd like 2 other audio files to have play/pause enabled on lock screen.
Below is the code I'm using:
// Playback controls
override func remoteControlReceived(with event: UIEvent?) {
if let event = event {
if event.type == .remoteControl {
switch event.subtype {
case .remoteControlPlay: audioPlayer.play()
case . remoteControlPause: audioPlayer.pause()
default: print("Done")
}
}
}
}
override func remoteControlReceived0(with event: UIEvent?) {
if let event = event {
if event.type == .remoteControl {
switch event.subtype {
case .remoteControlPlay: audioPlayer1.play()
case . remoteControlPause: audioPlayer1.pause()
default: print("Done")
}
}
}
}
func remoteControlReceived(with event: UIEvent?) {
if let event = event {
if event.type == .remoteControl {
switch event.subtype {
case .remoteControlPlay: audioPlayer2.play()
case . remoteControlPause: audioPlayer2.pause()
default: print("Done")
}
}
}
}
but it is only allowing me to add it once meaning I can only control one audio file but I'd like 2 other audio files to have play/pause enabled on lock screen
You can’t. It references only the now playing item.
I want to know how to get the state of my player (AVPlayer) (buffering, playing, stopped, error) and update the ui according to those states (including the player on the lock screen). How exactly should I do it?
I have a label that may contain:
"Buffering...", "Playing", "Stopped" or "Error".
Basically, I have the following:
MediaPlayer:
import Foundation
import AVFoundation
class MediaPlayer {
static let sharedInstance = MediaPlayer()
fileprivate var player = AVPlayer(url: URL(string: "my_hls_stream_url_here")!)
fileprivate var isPlaying = false
func play() {
player.play()
isPlaying = true
}
func pause() {
player.pause()
isPlaying = false
}
func toggle() {
if isPlaying == true {
pause()
} else {
play()
}
}
func currentlyPlaying() -> Bool {
return isPlaying
}
}
PlayerViewController:
class PlayerViewController: UIViewController {
#IBOutlet weak var label: UILabel!
#IBAction func playStopButtonAction(_ sender: UIButton) {
MediaPlayer.sharedInstance.toggle()
}
override func viewDidLoad() {
super.viewDidLoad()
label.text = "Disconnected"
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
print("Audio session ok\n")
} catch {
print("Error: Audio session.\n")
}
// Show only play/pause button on the lock screen
if #available(iOS 9.1, *) {
let center = MPRemoteCommandCenter.shared()
[center.previousTrackCommand, center.nextTrackCommand, center.seekForwardCommand, center.seekBackwardCommand, center.skipForwardCommand, center.skipBackwardCommand, center.ratingCommand, center.changePlaybackRateCommand, center.likeCommand, center.dislikeCommand, center.bookmarkCommand, center.changePlaybackPositionCommand].forEach {
$0.isEnabled = false
}
center.togglePlayPauseCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
MediaPlayer.sharedInstance.toggle()
return MPRemoteCommandHandlerStatus.success
}
center.playCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
MediaPlayer.sharedInstance.play()
return MPRemoteCommandHandlerStatus.success
}
center.pauseCommand.addTarget { (commandEvent) -> MPRemoteCommandHandlerStatus in
MediaPlayer.sharedInstance.pause()
return MPRemoteCommandHandlerStatus.success
}
} else {
// Fallback on earlier versions
print("Error (MPRemoteCommandCenter)")
}
}
override func remoteControlReceived(with event: UIEvent?) {
guard let event = event else {
print("No event\n")
return
}
guard event.type == UIEventType.remoteControl else {
print("Another event received\n")
return
}
switch event.subtype {
case UIEventSubtype.remoteControlPlay:
print("'Play' event received\n")
case UIEventSubtype.remoteControlPause:
print("'Pause' event received\n")
case UIEventSubtype.remoteControlTogglePlayPause:
print("'Toggle' event received\n")
default:
print("\(event.subtype)\n")
}
}
}
I think you could use the timeControlStatus property of AVPlayer. According to the doc it can be paused, waitingToPlayAtSpecifiedRate which is basically what you call buffering or playing.
If you really need the error state, you could observe the error property or whether the status property is set to failed.
A simple KVO observer on these properties would do the trick.
A place to start could be through using the AVPlayer's "status" property. It is an enumeration that contains the following values (this is taken directly from the documentation):
'unknown': Indicates that the status of the player is not yet known because it has not tried to load new media resources for playback.
'readyToPlay': Indicates that the player is ready to play AVPlayerItem instances.
'failed': Indicates that the player can no longer play AVPlayerItem instances because of an error.
As to how you could tell if the content is actually playing, you could just use boolean checks as it seems you have partially implemented. For pausing and stopping, you could just keep the file loaded for pause, and delete the file for stop that way you could differentiate the two.
For buffering, if the enum is not unknown or readyToPlay then that theoretically should mean that there is a file being attached but is not quite ready to play (i.e. buffering).
I am trying to allow the user to pick a song from their library and play it across different view controllers. I am able to pick songs currently and play them, but I don't know how to play them in the background. Thanks!
class ViewController: UIViewController,
MPMediaPickerControllerDelegate, AVAudioPlayerDelegate {
var myMusicPlayer: MPMusicPlayerController?
var mediaPicker: MPMediaPickerController?
var backgroundMusicPlayer:AVAudioPlayer = AVAudioPlayer()
#IBAction func musicBtn(sender: AnyObject) {
displayMediaPickerAndPlayItem()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
func musicPlayerStateChanged(notification: NSNotification){
print("Player State Changed")
/* Let's get the state of the player */
let stateAsObject =
notification.userInfo!["MPMusicPlayerControllerPlaybackStateKey"]
as? NSNumber
if let state = stateAsObject{
/* Make your decision based on the state of the player */
switch MPMusicPlaybackState(rawValue: state.integerValue)!{
case .Stopped:
/* Here the media player has stopped playing the queue. */
print("Stopped")
case .Playing:
/* The media player is playing the queue. Perhaps you
can reduce some processing that your application
that is using to give more processing power
to the media player */
print("Paused")
case .Paused:
/* The media playback is paused here. You might want
to indicate by showing graphics to the user */
print("Paused")
case .Interrupted:
/* An interruption stopped the playback of the media queue */
print("Interrupted")
case .SeekingForward:
/* The user is seeking forward in the queue */
print("Seeking Forward")
case .SeekingBackward:
/* The user is seeking backward in the queue */
print("Seeking Backward")
}
}
}
func nowPlayingItemIsChanged(notification: NSNotification){
print("Playing Item Is Changed")
let key = "MPMusicPlayerControllerNowPlayingItemPersistentIDKey"
let persistentID =
notification.userInfo![key] as? NSString
if let id = persistentID{
/* Do something with Persistent ID */
print("Persistent ID = \(id)")
}
}
func volumeIsChanged(notification: NSNotification){
print("Volume Is Changed")
/* The userInfo dictionary of this notification is normally empty */
}
func mediaPicker(mediaPicker: MPMediaPickerController,
didPickMediaItems mediaItemCollection: MPMediaItemCollection){
print("Media Picker returned")
/* Instantiate the music player */
myMusicPlayer = MPMusicPlayerController()
if let player = myMusicPlayer{
player.beginGeneratingPlaybackNotifications()
/* Get notified when the state of the playback changes */
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "musicPlayerStateChanged:",
name: MPMusicPlayerControllerPlaybackStateDidChangeNotification,
object: nil)
/* Get notified when the playback moves from one item
to the other. In this recipe, we are only going to allow
our user to pick one music file */
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "nowPlayingItemIsChanged:",
name: MPMusicPlayerControllerNowPlayingItemDidChangeNotification,
object: nil)
/* And also get notified when the volume of the
music player is changed */
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "volumeIsChanged:",
name: MPMusicPlayerControllerVolumeDidChangeNotification,
object: nil)
/* Start playing the items in the collection */
player.setQueueWithItemCollection(mediaItemCollection)
player.play()
/* Finally dismiss the media picker controller */
mediaPicker.dismissViewControllerAnimated(true, completion: nil)
}
}
func mediaPickerDidCancel(mediaPicker: MPMediaPickerController) {
/* The media picker was cancelled */
print("Media Picker was cancelled")
mediaPicker.dismissViewControllerAnimated(true, completion: nil)
}
func stopPlayingAudio(){
NSNotificationCenter.defaultCenter().removeObserver(self)
if let player = myMusicPlayer{
player.stop()
}
}
func displayMediaPickerAndPlayItem(){
mediaPicker = MPMediaPickerController(mediaTypes: .AnyAudio)
if let picker = mediaPicker{
print("Successfully instantiated a media picker")
picker.delegate = self
picker.allowsPickingMultipleItems = true
picker.showsCloudItems = true
picker.prompt = "Pick a song please..."
view.addSubview(picker.view)
presentViewController(picker, animated: true, completion: nil)
} else {
print("Could not instantiate a media picker")
}
}
}
If you want to play "across different view controllers" then do not put the AVAudioPlayer in one view controller. If you do that, it will be destroyed when you leave that view controller.
Put the audio player somewhere that will survive no matter what, like your App Delegate.
You can use singleton to achieve it like this:
import AVFoundation
class MusicHelper {
static let sharedHelper = MusicHelper()
var audioPlayer: AVAudioPlayer?
func playBackgroundMusic() {
let pickedSong = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("userPickedSong", ofType: "mp3")!)
do {
audioPlayer = try AVAudioPlayer(contentsOfURL:aSound)
audioPlayer!.numberOfLoops = -1
audioPlayer!.prepareToPlay()
audioPlayer!.play()
} catch {
print("Cannot play the file")
}
}
}
And you can use it like this:
MusicHelper.sharedHelper.playBackgroundMusic()
This is just one example how you can do it. Actually there are more and there are already answers on StackOverflow. But with the way I showed you can call it in any class.
I'm implementing a low-latency drum set with TheAmazingAudioEngine framework. I have a scene with a single button and a viewController with the methods below. This code works very well if I touch the button slowly. But if I touch it many times in a short period of time --- 10 times per second, for instance ---, the sound is not played in some touches, without error messages. The audio sample is short (less than 2 seconds).
Why does it happen? What is wrong in my implementation?
I choose TheAmazingAudioEngine instead of AVAudioPlayer to get low-latency between the touch and the sound.
override func viewDidLoad() {
super.viewDidLoad()
// Enable multiple touch for the button
for v in view.subviews {
if v.isKindOfClass(UIButton) {
v.multipleTouchEnabled = true
}
}
// Init audio
audioController = AEAudioController(audioDescription: AEAudioController.nonInterleavedFloatStereoAudioDescription())
audioURL = NSBundle.mainBundle().URLForResource("shortSound", withExtension: "wav")!
do {
try audioController?.start()
} catch {
print("AudioController start Error")
}
}
override func viewDidDisappear(animated: Bool) {
super.viewDidDisappear(animated)
audioController?.stop()
}
#IBAction func playSound(sender: UIButton) {
do {
let player = try AEAudioFilePlayer(URL: audioURL)
player.removeUponFinish = true
audioController?.addChannels([player])
} catch {
print("Player start Error")
}
}
I am making a little app for fun to test my learning and I've gotten it for the most part but there is a little caveat to the solution I've come up with.
The app should play a horn sound everytime you click the screen. But this should allow you to continually press it in quick succession and the horn sound plays each time. It should also stop the previous horn sound as well so there are no overlapping sounds.
Here is what I've managed so far
import UIKit
import AVFoundation
class ViewController: UIViewController {
var horn:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
prepareAudios()
}
#IBAction func buttonPressed(sender: AnyObject) {
horn.stop()
horn.currentTime = 0
horn.play()
}
func prepareAudios() {
var path = NSBundle.mainBundle().pathForResource("horn", ofType: "mp3")
horn = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!), error: nil)
horn.prepareToPlay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
And it works, sort of. It stops the sound and sets it back to the beginning, but if you click really quickly it skips and doesn't play sometimes. I want to try ti get around this but I'm not finding much on google. Is there a better solution that will allow me to play this sound like this really quickly?
UPDATE
As per the answer from Duncan, I tried to create two instances of the sound to play and switch between them both but I can't seem to get them to start the sound right when the screen is touched each time it's touched in quick succession. Here is the code I have tried
class ViewController: UIViewController {
var horn1:AVAudioPlayer = AVAudioPlayer()
var horn2:AVAudioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
prepareAudios()
}
var bool = true
#IBAction func butonPressed(sender: AnyObject) {
if(bool){
horn1.play()
horn2.stop()
horn2.currentTime = 0
bool = false
}else{
horn2.play()
horn1.stop()
horn1.currentTime = 0
bool = true
}
}
func prepareAudios() {
var path = NSBundle.mainBundle().pathForResource("horn", ofType: "mp3")
horn1 = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!), error: nil)
horn2 = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!), error: nil)
horn1.prepareToPlay()
horn2.prepareToPlay()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
On each press of the button there is a slight delay before the sound plays. And I want the sound to start over immediately. I want the horn to be able to sound like in this video
https://www.youtube.com/watch?v=Ks5bzvT-D6I
it's so much simpler than that! if you want the audio to start exactly after the button is pressed, you should change the event to "Touch Down" when you're connecting the button to your code. That's it! and for the button this is enough :
#IBAction func buttonPressed(sender: AnyObject) {
horn.currentTime = 0
horn.play()
}
Try creating 2 instances of your horn player, and calling prepareToPlay each time. Then when you click your button, stop the one that's playing, start the new one, and call prepareToPlay on the one that you stopped in order to get it ready. You'll need to build some program logic to implement that.