Apple Music Songs - MPMusicPlayerController giving wrong playbackState - ios

As Apple said in iOS 9.3 we can Access Apple Music Library. I am playing it from my application by MPMusicPlayerController.
I am getting wrong playbackState. For Ex. If song continue playing - so it should return status MPMusicPlaybackStatePlaying but gettting other enum values. My code is
if ([[MPMusicPlayerController systemMusicPlayer] playbackState]==MPMusicPlaybackStatePlaying)
{
}
else
{
NSLog(#"playbackState %ld",(long)[[MPMusicPlayerController systemMusicPlayer] playbackState]);
}
As apple saying here we have following possible vales -
Values for the playbackState property.
Declaration
Objective-C
enum {
MPMusicPlaybackStateStopped,
MPMusicPlaybackStatePlaying,
MPMusicPlaybackStatePaused,
MPMusicPlaybackStateInterrupted,
MPMusicPlaybackStateSeekingForward,
MPMusicPlaybackStateSeekingBackward
};
typedef NSInteger MPMusicPlaybackState;
How will I get the correct state of current playing song . Any Idea, If I mistaken something please let me know. Thanks

It is insane but this works:
_ = systemMusicPlayer.currentPlaybackRate
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in
print(self?.systemMusicPlayer.playbackState)
}

I've also faced this issue. So the workaround is: every n seconds check the [[MPMusicPlayerController systemMusicPlayer] currentPlaybackRate] property. 1 corresponds to "playing" and 0 to "paused" (or stopped).

Related

MPMusicPlayer Error Domain=MPErrorDomain Code=4?

I am having issue with Music player, most of the songs gives Error
Error Domain=MPErrorDomain Code=4
The testing device has Apple music subscription and the tracks gives error on the app they are working fine in Apple music app!
Here is the code:
let applicationMusicPlayer = MPMusicPlayerController.systemMusicPlayer()
applicationMusicPlayer.setQueueWithStoreIDs([ID])
if #available(iOS 10.1, *)
{
applicationMusicPlayer.prepareToPlay { (error) in
if (error != nil)
{
print("[MUSIC PLAYER] Error preparing : \(String(describing: error))")
return
}else
{
self.start_timer();
self.applicationMusicPlayer.play()
}
}
}else
//Play directly ios below version 10.1
{
self.applicationMusicPlayer.play()
}
}
But what I've tried, when the track gives this error, I went to Apple music player and played it from there its worked, then I came back to my app and play it from my app its worked also fine, so I need to go to Apple music app to play tracks not playing in my app to make them work in my app! That's so weird any idea why?
PS: the testing device has Apple music subscription
I had some similar problems when adding songs to a playlist, solved it by using:
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
// Code
}
i would play around with waiting a bit before or after preparing.
5 seconds may be too much, but you can start from there

Certain songs not playing through MPMusicPlayerController

I'm creating an app that can playback songs from Apple Music using the MPMusicPlayerController for people with a subscription.
I've run into a problem where certain songs fail to play whilst others work fine and I'm not sure why this would be the case.
The ID for a song that wouldn't work is 627511704. I've double checked some songs that don't work using the iTunes search API just to confirm that they do actually exist on the store which they did.
Here's the code where I attempt to queue the MPMusicPlayerController with an ID
func playWithAppleMusic(track: Track) {
guard let id = track.appleMusicID else { return }
self.appleMusicPlayer = MPMusicPlayerController.applicationMusicPlayer()
self.appleMusicPlayer?.prepareToPlay()
self.appleMusicPlayer?.setQueueWithStoreIDs([String(id)])
self.appleMusicPlayer?.currentPlaybackTime = 0
self.appleMusicPlayer?.repeatMode = .none
self.appleMusicPlayer?.shuffleMode = .off
self.appleMusicPlayer?.play()
self.startTimer()
}
I have also tried using an instance of MPMusicPlayerController.systemMusicPlayer() instead but that also did not work and wouldn't queue the song in the Apple Music app either.
Anybody know if I am doing something wrong here or is it just Apple's API being buggy again?
Thanks

How to get animation to work at exact points during playback of a music file?

Question:
In Swift code, apart from using an NSTimer, how can I get animations
to start at exact points during playback of a music file played using AVFoundation?
Background
I have a method that plays a music file using AVFoundation (below). I also have UIView animations that I want to start at exact points during the music file being played.
One way I could achieve this is using an NSTimer, but that has the potential to get out of sync or not be exact enough.
Is there a method that I can tap into AVFoundation accessing the music file's time elapsed (time counter), so when certain points during the music playback arrive, animations start?
Is there an event / notification that AVFoundation triggers that gives a constant stream of time elapsed since the music file has started playing?
For example
At 0:52.50 (52 seconds and 1/2), call startAnimation1(), at 1:20.75 (1 minute, 20 seconds and 3/4), call startAnimation2(), and so on?
switch musicPlayingTimeElapsed {
case 0:52.50:
startAnimation1()
case 1:20.75:
startAnimation2()
default:
()
}
Playing music using AVFoundation
import AVFoundation
var myMusic : AVAudioPlayer?
func playMusic() {
if let musicFile = self.setupAudioPlayerWithFile("fileName", type:"mp3") {
self.myMusic = musicFile
}
myMusic?.play()
}
func setupAudioPlayerWithFile(file:NSString, type:NSString) -> AVAudioPlayer? {
let path = NSBundle.mainBundle().pathForResource(file as String, ofType: type as String)
let url = NSURL.fileURLWithPath(path!)
var audioPlayer:AVAudioPlayer?
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: url)
} catch {
print("AVAudioPlayer not available")
}
return audioPlayer
}
If you use AVPlayer instead of AVAudioPlayer, you can use the (TBH slightly awkward) addBoundaryTimeObserverForTimes method:
let times = [
NSValue(CMTime:CMTimeMake(...)),
NSValue(CMTime:CMTimeMake(...)),
NSValue(CMTime:CMTimeMake(...)),
// etc
];
var observer: AnyObject? = nil // instance variable
self.observer = self.player.addBoundaryTimeObserverForTimes(times, queue: nil) {
switch self.player.currentTime() {
case 0:52.50:
startAnimation1()
case 1:20.75:
startAnimation2()
default:
break
}
}
// call this to stop observer
self.player.removeTimeObserver(self.observer)
The way I solve this is to divide the music up into separate segments beforehand. I then use one of two approaches:
I play the segments one at a time, each in its own audio player. The audio player's delegate is notified when a segment finishes, and so starting the next segment — along with accompanying action — is up to me.
Alternatively, I queue up all the segments onto an AVQueuePlayer. I then use KVO on the queue player's currentItem. Thus, I am notified exactly when we move to a new segment.
You might try using Key Value Observing to observe the duration property of your sound as it plays. When the duration reaches your time thresholds you'd trigger each animation. You'd need to make the time thresholds match times >= the trigger time, since you will likely not get a perfect match with your desired time.
I don't know how well that would work however. First, I'm not sure if the sound player's duration is KVO-compliant.
Next, KVO is somewhat resource-intensive, and if your KVO listener gets called thousands of times a second it might bog things down. It would at least be worth a try.

iOS MPMoviePlayerController can't judge if paused or automatically completed playing

Have an issue, MPMoviePlayerPlaybackDidFinishNotification sends me playbackState as MPMoviePlaybackStatePaused for both scenarios whether I pause or moviePlayer automatically finishes playing.
MPMoviePlaybackStateStopped is happening only if I do a manual stop i.e. [moviePlayer stop]
Any idea how to differ with automatically finished or paused scenario.
Thanks in advance
From the documentation
The following key may be found in the userInfo dictionary of a
MPMoviePlayerPlaybackDidFinishNotification notification.
Swift
let MPMoviePlayerPlaybackDidFinishReasonUserInfoKey: String
OBJECTIVE-C
NSString *const
MPMoviePlayerPlaybackDidFinishReasonUserInfoKey;
Then
The value of this key is an NSNumber containing an integer value that
represents one of the “MPMovieFinishReason” constants.
And MPMovieFinishReason is Enum there you have PlaybackEnded and UserExited
Declaration
SWIFT
enum MPMovieFinishReason : Int {
case PlaybackEnded
case PlaybackError
case UserExited
}
OBJECTIVE-C
enum {
MPMovieFinishReasonPlaybackEnded,
MPMovieFinishReasonPlaybackError,
MPMovieFinishReasonUserExited
};
typedef NSInteger MPMovieFinishReason;
Here is the documentation .

Using Spotify/background music with camera open

I have an app that needs to have:
Background music playing while using the app (eg. spotify)
Background music playing while watching movie from AVPlayer
Stop the music when recording a video
Like Snapchat, the camera-viewcontroller is part of a "swipeview" and therefore always on.
However, when opening and closing the app, the music makes a short "crack" noise/sound that ruins the music.
I recorded it here:
https://soundcloud.com/morten-stulen/hacky-sound-ios
(3 occurrences)
I use these settings for changing the AVAudiosession in the appdelegate didFinishLaunchingWithOptions:
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayAndRecord,withOptions:
[AVAudioSessionCategoryOptions.MixWithOthers,
AVAudioSessionCategoryOptions.DefaultToSpeaker])
try AVAudioSession.sharedInstance().setActive(true)
} catch {
print("error")
}
I use the LLSimpleCamera control for video recording and I've set the session there to:
_session.automaticallyConfiguresApplicationAudioSession = NO;
It seems others have the same problem with other camera libraries as well:
https://github.com/rFlex/SCRecorder/issues/127
https://github.com/rFlex/SCRecorder/issues/224
This guy removed the audioDeviceInput, but I kinda need that for recording video.
https://github.com/omergul123/LLSimpleCamera/issues/48
I also tried with Apple's code "AvCam", and I still have the same issue. How does Snapchat do this?!
Any help would be greatly appreciated, and I'll gladly provide more info or code!
I do something similar to what you're wanting, but without the camera aspect, but I think this will do what you want. My app allows background audio that will mix with non-fullscreen video/audio. When the user plays an audio file or a full screen video file, I stop the background audio completely.
The reason I do SoloAmbient then Playback is because I allow my audio to be played in the background when the device is locked. Going SoloAmbient will stop all background music playing and then switching to Playback lets my audio play in the app as well as in the background.
This is why you see a call to a method that sets the lock screen information in the Unload method. In this case, it is nulling it out so that there is no lock screen info.
In AppDelegate.swift
//MARK: Audio Session Mixing
func allowBackgroundAudio()
{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, withOptions: .MixWithOthers)
} catch {
NSLog("AVAudioSession SetCategory - Playback:MixWithOthers failed")
}
}
func preventBackgroundAudio()
{
do {
//Ask for Solo Ambient to prevent any background audio playing, then change to normal Playback so we can play while locked
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategorySoloAmbient)
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
} catch {
NSLog("AVAudioSession SetCategory - SoloAmbient failed")
}
}
When I want to stop background audio, for example when playing an audio track that should be alone, I do the following:
In MyAudioPlayer.swift
func playUrl(url: NSURL?, backgroundImageUrl: NSURL?, title: String, subtitle: String)
{
ForgeHelper.appDelegate().preventBackgroundAudio()
if _mediaPlayer == nil {
self._mediaPlayer = MediaPlayer()
_mediaPlayer!.delegate = self
}
//... Code removed for brevity
}
And when I'm done with my media playing, I do this:
private func unloadMediaPlayer()
{
if _mediaPlayer != nil {
_mediaPlayer!.unload()
self._mediaPlayer = nil
}
_controlView.updateForProgress(0, duration: 0, animate: false)
ForgeHelper.appDelegate().allowBackgroundAudio()
setLockScreenInfo()
}
Hope this helps you out!

Resources