AVAudioPlayer Number Of Loops only taking effect after being played through once - objective-c

I'm a little stumped by this weird occurrence:
I have a UIButton, which once tapped either sets a loop for an audio player, or resets it to 0 (no loop). Here is the method -
-(void)changeLoopValueForPlay:(int)tag toValue:(bool)value{
AVAudioPlayer *av = [self.playerArray objectAtIndex:tag];
if(value){
[av setNumberOfLoops:100];
[av prepareToPlay];
}
else{
[av setNumberOfLoops:0];
}
}
Now for some reason, the loop will only take effect after the player plays through the audio one time, meaning that the looping value doesn't take affect immediately, but the "numberOfLoops" value of the player is in fact set to 100 when I check its value before playing. I'm assuming this has something to do with the initialization or loading of the player, but I don't re-initialize it between those two plays (one without loop, the other with). Any idea why this is happening? If you want to see any other code please let me know.

This fixed the problem, however I feel as if this is a work-around instead of a direct solution. What I did is just create a new AVAudioPlayer with the numberOfLoops value set to whatever it is I wanted and replace that player with the existing player, instead of changing the value of the already existing player.

I workaround the issue by abandoning numberOfLoops altogether and doing my own logic instead.
First, set the delegate of the AVAudioPlayer:
self.audioPlayer.delegate = self;
Next, implement -audioPlayerDidFinishPlaying:successfully: of the delegate:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer*)player successfully:(BOOL)flag
{
if(flag && <#(bool)i_want_to_repeat_playing#>)
{
[self.audioPlayer play];
}
}
Just replace <#(bool)i_want_to_repeat_playing#> with your desired logic, e.g., check if a counter has reached a certain threshold.

Related

ReactiveCocoa: throttle never executes / not working

I try to subscribe to a signal with throttle, but it never executes.
I have a UISearchController (Attention: UISearchController from iOS8, not the older UISearchDisplayController, which works quiet better and has thousands of working tutorials and examples in the web) and want to make API-Requests while the user is typing.
To let the traffic being low, i don't want to start API-Requests with each key the user is pressing, but when the user stops for a while, say 500 ms after the last keypress.
Since we're unable to reference the TextField in the SearchBar of the UISearchController, we use the delegates from UISearchController:
To get the latest typed text of the Textfield in the Searchbar, I use this:
#pragma mark - UISearchResultsUpdating
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
NSString *searchText = searchController.searchBar.text;
// strip out all the leading and trailing spaces
NSString *strippedString = [searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if([strippedString isEqualToString:self.currentFilter]) {
return;
}
self.currentFilter = strippedString;
}
The property currentFilter keeps the current search string.
Also, i have a RACObserve on the currentFilter-Property to react on every change which is made to this property:
[RACObserve(self, currentFilter) subscribeNext:^(NSString* x) {
NSLog(#"Current Filter: %#", x);
// do api calls and everything else
}];
Now i want to throttle this signal. But when i implement the call to throttle, nothing happens. SubscribeNext will never be called:
[[RACObserve(self, currentFilter) throttle:500] subscribeNext:^(NSString* x) {
NSLog(#"%#", x); // will never be called
}];
How to achieve to throttle inputs in a searchbar? And what is wrong here?
UPDATE
i found a workaround besides ReactiveCocoa thanks to #malcomhall. I moved the code within the updateSearchResultsForSearchController-delegate method into a separate method and schedule it with performSelector and cancel this scheduler with cancelPreviousPerformRequestsWithTarget.
- (void)updateSearchResultsForSearchController:(UISearchController *)searchController {
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(useTextSearchQuery) object:nil];
[self performSelector:#selector(useTextSearchQuery) withObject:nil afterDelay:1.0];
}
Anyway, i want still understand how "throttle" from ReactiveCocoa is working and why not in this case :)
-throttle: accepts an NSTimeInterval, which is a floating-point specification of seconds, not milliseconds.
Given the code in the question, I expect you would see results after 500 seconds have elapsed.

AVQueuePlayer playback freezes when removing items from queue

I successfully use AVQueuePlayer to queue and play AVPlayerItems. However, the problems begin when I alter the players queue with calls to removeItem: method. My assumption was that I can freely modify this queue as long as I don't touch the currently played item, but it seems that each call to removeItem: freezes the playback for a moment.
Consider the following code snippet:
+ (void)setBranch:(BranchType)bType {
NSArray* items = [dynamicMusicPlayer items];
for (AVPlayerItem* item in [items copy]) {
if (item == dynamicMusicPlayer.currentItem) {
continue; // don't remove the currently played item
}
[dynamicMusicPlayer removeItem:item];
}
[self queueNextBlockFromBranchType:bType];
currentBranch = bType;
}
You can guess from this, that it's a dynamic music player that plays the music from different branches. So basically, when I comment the line where I remove items, it plays all ok, but obviously the branch is not changed as soon as I want it to. The freeze occurs exactly at the time the removing happens, so the currently played item is being interrupted, but the transition between the actual items is seamless. Also note that I never have more than 2 items in the queue, so in the loop I basically remove one item only.
So, my question is: is there any way to avoid this freeze? And what is causing the freeze on the first place?
Edit So for the people who encountered the same problem, the solution for me was to stop using AVFoundation and use OrigamiEngine instead.
Instead of removing the item in the loop, you could consider scheduling an NSOperation to do it later.
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do something here
}];
Ex:
+ (void)setBranch:(BranchType)bType {
NSArray* items = [dynamicMusicPlayer items];
for (AVPlayerItem* item in [items copy]) {
if (item == dynamicMusicPlayer.currentItem) {
continue; // don't remove the currently played item
}
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[dynamicMusicPlayer removeItem:item];
}];
}
[self queueNextBlockFromBranchType:bType];
currentBranch = bType;
}
I don't know if this will help, but it'll at least shift the actual item removal to a scheduled operation that the device can decide when to run.
This is a much hackier solution, but it might help.
Rather than removing the item from dynamicMusicPlayer immediately, you could mark it for removal by adding it to a separately maintained list of things to remove.
Then, when the AVQueuePlayer is about to advance to the next item, sweep through your list of deletable items and take them out. That way the blip occurs in the boundary between items rather than during playback.

Performance issues using an MPMusicPlayerController - can it be accessed in the background?

EDIT: Updating my own question with the answer I figured out months later. Short answer is no, MPMusicPlayerController forwards all calls to the main thread. But it uses a CPDistributedMessagingCenter to actually handle all operations, so one can very easily write a replacement controller that makes asynchronous calls (but it won't work for a sandboxed App Store app, as far as I know - and if it did, Apple would promptly reject it).
I'm making a simple app to control iPod playback, so I've been using an MPMusicPlayerController, which Apple states can only be used in the main thread. However, I've been experiencing some frustrating performance issues in the UI. Switching to the next or previous song is triggered by a swipe, which moves the entire display (of the song info) with it, and then updates the display for the next song when the switch is triggered. The trouble is that once the song has been changed, the UI hangs for up to a second while the song info is retrieved from the MPMusicPlayerController. I've tried most everything I can think of to optimize the code, but it seems to me that the only way to fix it is to move the MPMusicPlayerController code on to a background thread, despite Apple's instructions not to.
The code to update the display, for reference:
// Called when MPMusicPlayerControllerNowPlayingItemDidChangeNotification is received
- (void) nowPlayingDidChange {
if ([iPodMusicPlayer nowPlayingItem]) {
// Temp variables (necessary when updating the subview values in the background)
title = [[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyTitle];
artist = [[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyArtist];
album = [[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyAlbumTitle];
artwork = [[[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyArtwork] imageWithSize:CGSizeMake(VIEW_HEIGHT - (2*MARGINS), VIEW_HEIGHT - (2*MARGINS))];
length = [[[iPodMusicPlayer nowPlayingItem] valueForProperty:MPMediaItemPropertyPlaybackDuration] doubleValue];
if (updateViewInBackground)
[self performSelectorInBackground:#selector(updateSongInfo) withObject:nil];
else
[self updateSongInfo];
}
else
[self setSongInfoAsDefault];
}
- (void) updateSongInfo {
// Subviews of the UIScrollView that has performance issues
songTitle.text = title;
songArtist.text = artist;
songAlbum.text = album;
songLength.text = [self formatSongLength:length];
if (!artwork) {
if ([[UIScreen mainScreen] respondsToSelector:#selector(scale)] && [[UIScreen mainScreen] scale] == 2.00)
songArtwork.image = [UIImage imageWithContentsOfFile:
#"/System/Library/Frameworks/MediaPlayer.framework/noartplaceholder#2x.png"];
else
songArtwork.image = [UIImage imageWithContentsOfFile:
#"/System/Library/Frameworks/MediaPlayer.framework/noartplaceholder.png"];
}
else
songArtwork.image = artwork;
title = nil;
artist = nil;
album = nil;
artwork = nil;
length = 0.0;
}
Is there anything I'm missing here (ie. performance optimization when updating the UIScrollView subviews)? And if not, would it be such a bad idea to just use the MPMusicPlayerController in a background thread? I know that can lead to issues if something else is accessing the iPodMusicPlayer (shared instance of the iPod in MPMusicPlayerController), but are there any ways I could potentially work around that?
Also, this is a jailbreak tweak (a Notification Center widget), so I can make use of Apple's private frameworks if they would work better than, say, the MPMusicPlayerController class (which is fairly limited anyways) for my purposes. That also means, though, that my app will be running as a part of the SpringBoard process, so I want to be sure that my code is as safe and stable as possible (I experience 2 minute hangs whenever my code does something wrong, which I don't want happening when I release this). So if you have any suggestions, I'd really appreciate it! I can provide more code / info if necessary. Thanks!

Is there any way i can group three classes?

What I want to do is I have many classes first of all, they all have the same music throughout, as i said in the app delegates bool application did finish launching method. But in my last 3 classes, I want different music, fair enough, I put these lines of code:
[(Smart2AppDelegate *)[UIApplication sharedApplication].delegate pauseAudioPlayer];
[(Smart2AppDelegate *)[UIApplication sharedApplication].delegate newAudioPlayer];
And in my app delegate:
-(void)newAudioPlayer {
NSString *music = [[NSBundle mainBundle]
pathForResource:#"win" ofType:#"m4a"];
audio.delegate = self;
self.audio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:music] error:NULL];
[audio play];
audio.numberOfLoops = -1;
}
-(void)pauseAudioPlayer {
[audio pause];
}
So it works, whenever I go to that view, it changes music, lets call that view, view x. Now, from that view x I can go to and from only to 2 other views, e.g. I can go to the info page and there is a back button that leads back to that view x, and the same with a prize page. But when I go back to the view x, the music starts from the beginning, when in these three classes, i want them to all loop and not go from beginning because it sounds akward. The reason is simple it is because I put it in the viewDidLoad. But how can I do this, I was thinking of a way to actually group classes and put in the avaudioplayer method in there.
Here you have a possible approach:
Refactor out the music+sounds mechanisms into a separate class, i.e. something like Smart2AudioPlayer.
Public the necessary methods: play, pause, resume so you can use the audio player from anywhere.
In each viewDidLoad method, call the audio player and pass along a parameter to indicate who is the sender (who wants the sound to be played) and a preference indicating whether you wish to continue playing the current group song (see bellow) or start it all over again.
Implement the necessary logic in your audio player to allow certain groups of classes to be associated together. This way, when you are playing a song from a class that belongs to a group, and another class of the same group asks for the music to be played, you won't start the song again, you simply do nothing.
Hope this helps

AVAudioPlayer meter values still - 160

Trying to implement AVAudioplayer and get some metering data of the played music, but still getting value -160.
It looks easy to use, just enable Meter and then pickup data under a timer, but no results so far.
playerAV = [[AVAudioPlayer alloc] initWithContentsOfURL:outURL error:nil];
playerAV.delegate = self;
playerAV.meteringEnabled = YES;
[playerAV prepareToPlay];
[playerAV play];
NSLog(#"Peak left: %f Avg right: %f", [playerAV peakPowerForChannel:0],[playerAV averagePowerForChannel:1]);
any thoughts are welcome.
Are you calling updateMeters? The docs for both peakPowerForChannel: and averagePowerForChannel: say:
To obtain a current [...] value, you must call the updateMeters method before calling this method.
How do you know the url works, and the data at the end of it is the right format? Can you hear stuff coming from the speakers?
Are you somehow making two playerAV objects, one with the correct URL which plays, the other with a bad url which plays nothing, and is the one that the timer sees when it calls NSLog()?