Control slideshow with current time of AVAudioPlayer - objective-c

I use a AVAudioPlayer to play a MP3 file. While the player is running I would like to control a slideshow with the current time of the player. Of course I could do this with a NSTimer which is fired when the play starts but that doesn't sound like a sweet solution. How can I ad a observer to the currentTime Value of the AVAudioPlayer?
Thanks,
Philip

The cleanest way to do this is to set up a audio player yourself using AVPlayer and set up a time-passed observation using -[AVPlayer addBoundaryTimeObserverForTimes:queue:usingBlock:].
If you’re stuck with the AVAudioPlayer I believe you can use Key-Value Observing to listen for the currentTime change:
static NSString *someObservationContext = #"something";
[audioPlayer addObserver:self forKeyPath:#"currentTime" options:0 context:&someObservationContext];
and then implement
- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == &someObservationContext) {
NSTimeInterval t = [(AVAudioPlayer *)object currentTime];
// do your thing
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
Be sure to call -[NSObject removeObserver:forKeyPath:] to remove the observation hook before you tear down the AVAudioPlayer.

Related

KVO. ObserveValueForKeyPAth is not called

I am not if this will work properly
[[cr1.crossRoad.trafficLights
objectForKey: [NSNumber numberWithInt:pedestrianTL]]
addObserver:view
forKeyPath:#"colorState"
options:NSKeyValueObservingOptionNew
context:nil];
The project I'm developing doesn't work properly. This way I was trying to add an observer to change the view with after every change happening to the cell of the colorState array.
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
[self refreshState:object];
}
The program never enters this method though I change values of the colorState cells. Maybe the problem is with me trying to observe array but not actually what it contains?
The problem was that I was trying to observe an array which was not possible that way.

iOS: addObserver and superview query

I have a uiview named subview1. I add this as subview to a couple of other views depending on certain situations. Now I have the following code
[subView1 addObserver:self forKeyPath:#"superview" options:NSKeyValueObservingOptionNew context:nil];
My problem is the obserValueForKeypath function is never called
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context{
if (self.subView1 == (UIView*)object) {
if ([keyPath isEqualToString:#"superview"]) {
NSLog(#"superview changed %#",change);
}
}
}
Am i doing something wrong here.
Just check that if it is going into first if block , the problem might be there.Also check if you have declared property for variable for which you are setting the observer if it is in different class.

How to target the parent object when using performSelectorOnMainThread?

I have a custom NSOperation object that is instantiated from a UITableViewController subclass (MusicTableVC). The NSOperation object is supposed to populate an NSarray from a URL in the background so the UI doesn't freeze up, but then I need to send that array back to main thread so the MusicTableVC instance can do stuff with it.
I know I need to use performSelectorOnMainThread: to send the array back to the MusicTableVC but to do that I need a pointer to the instance of MusicTableVC.
I was thinking about creating an init method in the NSOperation e.g. initWithParent to pass on a pointer to self and use that but maybe I'm missing something?
#synthesize parent;
- (id)initWithParent:(MusicTableVC*) musicTableViewController
{
if(self = [super init])
{
self.parent = musicTableViewController;
}
return self;
}
-(void) main
}
[parent performSelectorOnMainThread:#selector(arrayFinishedLoading:)
withObject:playlist
waitUntilDone:YES];
}
I think you would do better with blocks and Grand Central Dispatch:
dispatch_async(dispatch_get_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0), ^{
// This is called in background, not blocking the UI
[self populateArrayFromURL];
dispatch_async(dispatch_get_main_queue(), ^{
// This is called on the main thread
[self reportStuffDone];
});
});
that is one way of doing it, but a more common pattern is to have the parent of the NSOperation observe its state, and do something with the results when it is complete. so when you create your operation in the view controller, do something like this:
NSOperation *myOp = [[NSOperation alloc] init];
[myOp addObserver:self forKeyPath:#"isFinished" options:NSKeyValueObservingOptionsNew context:NULL];
then add the KVO callback method:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
NSOperation *myOp = (NSOperation *)object;
[myOp removeObserver:self forKeyPath:keyPath];
dispatch_async(dispatch_get_main_queue(), ^{
// reload the table with the results here
});
}
Your way is fine, I would call it initWithDelegate and define a protocol thou. Just pass the delegate and then the operation is finished just ket it know if it succeded or not.
Recently I've been switching from useless delegates to GCD, so instead I would make something like initWithSuccessBlock and then dispatch it to the main queue. Attention that, if you decide to use this you would have to make sure the block was copied.

AVPlayer replaceCurrentItemWithPlayerItem not working on iOS 4.3.3+

I have an audio player that I'm building using AVPlayer.
Currently, I keep the player instance around and when I need to swap tracks (either from a manual selection or the track has reached the end) I create a new AVPlayerItem and I call replaceCurrentItemWithPlayerItem with the new item.
According to the docs, replaceCurrentItemWithPlayerItem is an asynchronous operation, so I also observe the currentItem key path of the player. When that gets called, I tell my player to play.
Here is the relevant code:
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerItemStatusChangedContext];
if (!_player) {
_player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
[_player addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerStatusChangedContext];
[_player addObserver:self forKeyPath:#"currentItem" options:NSKeyValueObservingOptionNew context:CHStreamingAudioPlayer_PlayerCurrentItemChangedContext];
} else {
[_player replaceCurrentItemWithPlayerItem:playerItem];
}
And here is the key value observation callback:
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (context == CHStreamingAudioPlayer_PlayerCurrentItemChangedContext) {
NSLog(#"Player's current item changed! Change dictionary: %#", change);
if (_player.currentItem) {
[self play]; //<---- doesn't get called
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
On iOS 4.3.3 (and iOS 5) my key observation method is called but _player.currentItem is always nil. On 4.2.1 and 4.3.2 this property contains the actual value. This method is never called again. So in essence, replacing seems to always fail.
This seems like a bug, but perhaps I'm doing something wrong.
I had this issue with iOS 5 (including 5.0.1). It used to work fine on iOS 4.x.
There are two ways to workaround this, release and recreate your AVPlayer with the desired AVPlayerItems each time you need to swap tracks. Or, simply call replaceCurrentItemWithPlayerItem: on the main thread.
I tried both options and they worked fine.
Credits to: Apple Developer Forums
I've been experiencing similar problems. You probably got started from AVPlayerDemoPlaybackViewController from Apple sample code like me. Maybe the problem why currentItem is nil is because it's not loaded yet or ready for playback (my problem was I couldn't get the duration of the new AVPlayerItem).
You could try starting the playback when observed status of the currentItem is ReadyToPlay.
AVPlayerStatus status = [[change objectForKey:NSKeyValueChangeNewKey] integerValue];
switch (status) {
case AVPlayerStatusUnknown: {
NSLog(#"PLAYER StatusUnknown");
}
break;
case AVPlayerStatusReadyToPlay: {
NSLog(#"PLAYER ReadyToPlay");
[self play];
}
break;
case AVPlayerStatusFailed: {
AVPlayerItem *playerItem = (AVPlayerItem *)object;
[self handleError: [playerItem.error localizedDescription]];
}
break;
}
I don't know if this will wok for you, I didn't try this on lower or higher than 4.3.4 iPad, so I guess I'll run into complications soon.

question about KVO and the method "observeValueForKeyPath"

can you help me understand the observeValueForKeyPath method :
here it seems that observeValueForKeyPath is called because we changed the value "for the key : earthquakeList" , but let's say we have another key observed, like "earthquake_New_List",
how can i know that the first, or the second key observed, has changed, if we only have one callback method, the observeValueForKeyPath ?
[self addObserver:self forKeyPath:#"earthquakeList" options:0 context:NULL];
//...
- (void)insertEarthquakes:(NSArray *)earthquakes
{
// this will allow us as an observer to notified (see observeValueForKeyPath)
// so we can update our UITableView
//
[self willChangeValueForKey:#"earthquakeList"];
[self.earthquakeList addObjectsFromArray:earthquakes];
[self didChangeValueForKey:#"earthquakeList"];
}
// listen for changes to the earthquake list coming from our app delegate.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
[self.tableView reloadData];
}
Thanks
The keyPath parameter in your implementation of observeValueForKeyPath:ofObject:change:context: will tell you which key has changed. So you can do something like:
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
if ([keyPath isEqualToString:#"key1"]) {
// do something
} else if ([keyPath isEqualToString:#"key2"]) {
// do something else
}
}