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 .
Related
I am trying to understand how timestamping works for an AUv3 MIDI plug-in of type "aumi", where the plug-in sends MIDI events to a host. I cache the MIDIOutputEventBlockand the transportStateBlock properties into _outputEventBlock and _transportStateBlock in the allocateRenderResourcesAndReturnError method and use them in the internalRenderBlockmethod:
- (AUInternalRenderBlock)internalRenderBlock {
// Capture in locals to avoid Obj-C member lookups. If "self" is captured in render, we're doing it wrong. See sample code.
return ^AUAudioUnitStatus(AudioUnitRenderActionFlags *actionFlags, const AudioTimeStamp *timestamp, AVAudioFrameCount frameCount, NSInteger outputBusNumber, AudioBufferList *outputData, const AURenderEvent *realtimeEventListHead, AURenderPullInputBlock pullInputBlock) {
// Transport State
if (_transportStateBlock) {
AUHostTransportStateFlags transportStateFlags;
_transportStateBlock(&transportStateFlags, nil, nil, nil);
if (transportStateFlags & AUHostTransportStateMoving) {
if (!playedOnce) {
// NOTE On!
unsigned char dataOn[] = {0x90,69,96};
_outputEventBlock(timestamp->mSampleTime, 0, 3, dataOn);
playedOnce = YES;
// NOTE Off!
unsigned char dataOff[] = {0x80,69,0};
_outputEventBlock(timestamp->mSampleTime+96000, 0, 3, dataOff);
}
}
else {
playedOnce = NO;
}
}
return noErr;
};
}
What this code is meant to do is to play the A4 note in a synthesizer at the host for 2 seconds (the sampling rate is 48KHz). What I get is a click sound. Experimenting some, I have tried delaying the start of the note on MIDI event by offsetting the _outputEventBlock AUEventSampleTime, but it plays the click sound as soon as the play button is pressed on the host.
Now, if I change the code to generate the note off MIDI event when the _transportStateFlags are indicating the state is "not moving" instead, then the note plays as soon as the play button is pressed and stops when the pause button is pressed, which would be the correct behavior. This tells me that my understanding of the AUEventSampleTime property in MIDIOutputEventBlock is flawed and that it cannot be used to schedule MIDI events for the host by adding offsets to it.
I see that there is another property scheduleMIDIEventBlock, and tried using this property instead but when I use it, there isn't any sound played.
Any clarification of how this all works would be greatly appreciated.
I'm implementing NSProgress support in a library, and I wrote some unit tests to test that everything's working correctly. While ideally I'd like to be able to pass some additional metadata (userInfo keys not used by NSProgress itself, but for users of my API to consume), for now I'm just trying to get localizedDescription and localizedAdditionalDescription to work like the documentation says they should. Since the method I'm testing extracts files from an archive, I set the kind to NSProgressKindFile and set the various keys associated with file operations (e.g. NSProgressFileCompletedCountKey).
I expect when I observe changes to localizedDescription with KVO, that I'll see updates like this:
Processing “Test File A.txt”
Processing “Test File B.jpg”
Processing “Test File C.m4a”
When I stop at a breakpoint and po the localizedDescription on the worker NSProgress instance (childProgress below), that is in fact what I see. But when my tests run, all they see is the following, implying it's not seeing any of the userInfo keys I set:
0% completed
0% completed
53% completed
100% completed
100% completed
It looks like the userInfo keys I set on a child NSProgress instance are not getting passed on to its parent, even though fractionCompleted does. Am I doing something wrong?
I give some abstract code snippets below, but you can also download the commit with these changes from GitHub. If you'd like to reproduce this behavior, run the -[ProgressReportingTests testProgressReporting_ExtractFiles_Description] and -[ProgressReportingTests testProgressReporting_ExtractFiles_AdditionalDescription] test cases.
In my test case class:
static void *ProgressContext = &ProgressContext;
...
- (void)testProgressReporting {
NSProgress *parentProgress = [NSProgress progressWithTotalUnitCount:1];
[parentProgress becomeCurrentWithPendingUnitCount:1];
[parentProgress addObserver:self
forKeyPath:NSStringFromSelector(#selector(localizedDescription))
options:NSKeyValueObservingOptionInitial
context:ProgressContext];
MyAPIClass *apiObject = // initialize
[apiObject doLongRunningThing];
[parentProgress resignCurrent];
[parentProgress removeObserver:self
forKeyPath:NSStringFromSelector(#selector(localizedDescription))];
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSKeyValueChangeKey,id> *)change
context:(void *)context
{
if (context == ProgressContext) {
// Should refer to parentProgress from above
NSProgress *notificationProgress = object;
[self.descriptionArray addObject:notificationProgress.localizedDescription];
}
}
Then, in my class under test:
- (void) doLongRunningThing {
...
NSProgress *childProgress = [NSProgress progressWithTotalUnitCount:/* bytes calculated above */];
progress.kind = NSProgressKindFile;
[childProgress setUserInfoObject:#0
forKey:NSProgressFileCompletedCountKey];
[childProgress setUserInfoObject:#(/*array count from above*/)
forKey:NSProgressFileTotalCountKey];
int counter = 0;
for /* Long-running loop */ {
[childProgress setUserInfoObject: // a file URL
forKey:NSProgressFileURLKey];
// Do stuff
[childProgress setUserInfoObject:#(++counter)
forKey:NSProgressFileCompletedCountKey];
childProgress.completedUnitCount += myIncrement;
}
}
At the time I increment childProgress.completedUnitCount, this is what the userInfo looks like in the debugger. The fields I set are all represented:
> po childProgress.userInfo
{
NSProgressFileCompletedCountKey = 2,
NSProgressFileTotalCountKey = 3,
NSProgressFileURLKey = "file:///...Test%20File%20B.jpg"; // chunk elided from URL
}
When each KVO notification comes back, this is how notificationProgress.userInfo looks:
> po notificationProgress.userInfo
{
}
I wanted to comment on #clarus's answer, but SO won't let me do readable formatting in a comment. TL;DR - their take has always been my understanding and it's something that bit me when I started working with NSProgress a few years back.
For stuff like this, I like to check the Swift Foundation code for implementation hints. It's maybe not 100% authoritative if stuff's not done yet, but I like seeing the general thinking.
If you look at the implementation of setUserInfoObject(: forKey:), you can see that the implementation simply sets the user info dict without propagating anything up to the parent.
Conversely, updates that impact the child's fraction completed explicitly call back to the (private) _parent property to indicate its state should update in response to a child change.
That private _updateChild(: from: to: portion:) only seems concerned updating the fraction completed and not anything related to the user info dictionary.
Ok, I had a chance to look at the code again with more coffee in my system and more time on my hands. I'm actually seeing it working.
In your testProgressReporting_ExtractFiles_AdditionalDescription method, I changed the code to this:
NSProgress *extractFilesProgress = [NSProgress progressWithTotalUnitCount:1];
[extractFilesProgress setUserInfoObject:#10 forKey:NSProgressEstimatedTimeRemainingKey];
[extractFilesProgress setUserInfoObject:#"Test" forKey:#"TestKey"];
And then in observeValueForKeyPath, I printed these objects:
po progress.userInfo {
NSProgressEstimatedTimeRemainingKey = 10;
TestKey = Test;
}
po progress.localizedAdditionalDescription
0 of 1 — About 10 seconds remaining
You can see the key-values I added, and the localizedAdditionalDescription was created based on those entries (notice the time remaining). So, this all looks like it's working correctly.
I think one point of confusion might be around the NSProgress properties and their effect on the key-values in the userInfo dict. Setting the properties doesn't add key-values to the userInfo dict, and setting the key-values doesn't set the properties. For example, setting the progress kind doesn't add the NSProgressFileOperationKindKey to the userInfo dict. The value in the userInfo dict, if present, is more of an override of the property that's only used when creating the localizedAdditionalDescription.
You can also see the custom key-value I added. So, this all looks like it's working right. Can you point me to something that still looks off?
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.
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).
In my application, I use the AVPlayer to read some streams (m3u8 file), with HLS protocol. I need to know how many times, during a streaming session, the client switches bitrate.
Let's assume the client's bandwidth is increasing. So the client will switch to a higher bitrate segment.
Can the AVPlayer detect this switch ?
Thanks.
I have had a similar problem recently. The solution felt a bit hacky but it worked as far as I saw. First I set up an observer for new Access Log notifications:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleAVPlayerAccess:)
name:AVPlayerItemNewAccessLogEntryNotification
object:nil];
Which calls this function. It can probably be optimised but here is the basic idea:
- (void)handleAVPlayerAccess:(NSNotification *)notif {
AVPlayerItemAccessLog *accessLog = [((AVPlayerItem *)notif.object) accessLog];
AVPlayerItemAccessLogEvent *lastEvent = accessLog.events.lastObject;
float lastEventNumber = lastEvent.indicatedBitrate;
if (lastEventNumber != self.lastBitRate) {
//Here is where you can increment a variable to keep track of the number of times you switch your bit rate.
NSLog(#"Switch indicatedBitrate from: %f to: %f", self.lastBitRate, lastEventNumber);
self.lastBitRate = lastEventNumber;
}
}
Every time there is a new entry to the access log, it checks the last indicated bitrate from the most recent entry (the lastObject in the access log for the player item). It compares this indicated bitrate with a property that stored the the bitrate from that last change.
BoardProgrammer's solution works great! In my case, I needed the indicated bitrate to detect when the content quality switched from SD to HD. Here is the Swift 3 version.
// Add observer.
NotificationCenter.default.addObserver(self,
selector: #selector(handleAVPlayerAccess),
name: NSNotification.Name.AVPlayerItemNewAccessLogEntry,
object: nil)
// Handle notification.
func handleAVPlayerAccess(notification: Notification) {
guard let playerItem = notification.object as? AVPlayerItem,
let lastEvent = playerItem.accessLog()?.events.last else {
return
}
let indicatedBitrate = lastEvent.indicatedBitrate
// Use bitrate to determine bandwidth decrease or increase.
}