Play multiple audio files in sequence as one - ios

I'm trying to implement an Audio Player to play multiple files as if they were a single one. However, the initial buffer time should be only the first part's duration, and the other files should be loaded in sequence.
For example:
File1:
Part1 - 0:35s
Part2 - 0:47s
Part3 - 0:07s
The File1 should be played as if it had 1:29, but we'd only wait (at most) until Part1 is loaded to start playing.
I've had a look at AVAsset, but it doesn't seem to solve the problem. I also thought of implementing it using AVAudioPlayer and doing all the logic myself.
Has anyone had this issue before?
Thanks!

You can use AVQueuePlayer for achieving this. You need to create AVPlayerItem using your files url and then need to initialize the AVQueuePlayer using it's queuePlayerWithItems: method.
AVPlayerItem *firstItem = [[AVPlayerItem alloc] initWithURL:firstPartURL];
AVPlayerItem *secondItem = [[AVPlayerItem alloc] initWithURL:secondPartURL];
AVPlayerItem *thirdItem = [[AVPlayerItem alloc] initWithURL:thirdPartURL];
NSArray *itemsToPlay = [NSArray arrayWithObjects:firstItem, secondItem, thirdItem, nil];
AVQueuePlayer *qPlayer = [AVQueuePlayer queuePlayerWithItems:itemsToPlay];
[qPlayer play];

Related

If I have an instance of JWPlayerController how can I find out the file that is currently playing

JWConfig * config = [JWConfig new];
config.file = #"https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8";
self.player = [[JWPlayerController alloc] initWithConfig:config];
[self.player play];
Later on, with the same player instance I can do something like this:
JWPlaylistItem * item = [JWPlaylistItem new];
item.file = #"http://qthttp.apple.com.edgesuite.net/1010qwoeiuryfg/sl.m3u8";
[self.player load:#[ item ]];
[self.player play];
If I have a reference to self.player how can I get the URL of the file that is currently playing?
I have tried:
self.player.config.file
This doesn't work, because it will return the original file from the initialization (bitdash-a.akamaihd.net...)
From the player instance I can't find a way to get a reference to the currently playing playlistItem
There is no way to do this with jw player on iOS. https://github.com/jwplayer/JWPlayer-iOS-SDK-cocoapod/issues/49
The SDK intends to add a getPlaylistItem method for this purpose

ios SystemSound will not respond to volume buttons

Ive used SystemSound in my app in order to play simple sound effects. In addition to this I play a musicvideo through the MPMoviePlayerController - now when I turn the volume up/down the music from the video responds as intended (lowering the volume up/down).
But the system sounds that are played does not respond to the volume. Im playing the system sounds when the user taps certain areas in the app. Heres a snippet of my code for that:
- (void)handleTap:(UITapGestureRecognizer *)recognizer {
SystemSoundID completeSound = nil;
//yellow folder in xcode doesnt need subdirectory param
//blue folder (true folder) will need to use subdirectory:#"dirname"
NSURL *sound_path = [[NSBundle mainBundle] URLForResource: target_sound_filename withExtension: #"wav"];
AudioServicesCreateSystemSoundID((__bridge CFURLRef)sound_path, &completeSound);
AudioServicesPlaySystemSound(completeSound);
}
PS. I doublechecked that my "Settings->Sounds->Ringer and Alerts->Change With Buttons" is set to ON (as I read on some other SO answers that leaving this option OFF will cause systemsound to not respond to the volume buttons)
Further the reason for using systemsound is that it gave the most accurate and responsive results when playing multiple sounds (like in a game).
Id prefer to not use OpenAL if possible (even through 3rd party sound libraries like Finch or CocosDenshion)
Any ideas?
Use the AVAudioPlayer class for playing sounds that are controlled by the user's volume settings (non-system sounds).
You can retain instances of AVAudioPlayer for each sound file that you use regularly and simply call the play method. Use prepareToPlay to preload the buffers.
Cheers to Marcus for suggesting that i could retain instances of AVAudioPlayer for each sound file and use prepareToPlay to preload the sounds. It might be to help for others looking for the same solution so here is how I did it (feel free to comment if anyone have suggestions for improvements)
//top of viewcontroller.m
#property (nonatomic, strong) NSMutableDictionary *audioPlayers;
#synthesize audioPlayers = _audioPlayers;
//on viewDidLoad
self.audioPlayers = [NSMutableDictionary new];
//creating the instances and adding them to the nsmutabledictonary in order to retain them
//soundFile is just a NSString containing the name of the wav file
NSString *soundFile = [[NSBundle mainBundle] pathForResource:s ofType:#"wav"];
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:soundFile] error:nil];
//audioPlayer.numberOfLoops = -1;
[audioPlayer prepareToPlay];
//add to dictonary with filename (omit extension) as key
[self.audioPlayers setObject:audioPlayer forKey:s];
//then i use the following to play the sound later on (i have it on a tap event)
//get pointer reference to the correct AVAudioPlayer instance for this sound, and play it
AVAudioPlayer *foo = [self.audioPlayers objectForKey:target_sound_filename];
[foo play];
//also im not sure how ARC will treat the strong property, im setting it to nil in dealloc atm.
-(void)dealloc {
self.audioPlayers = nil;
}

Playing sounds in sequence with SimpleAudioEngine

I'm building an iOS app with cocos2d 2, and I'm using SimpleAudioEngine to play some effects.
Is there a way to sequence multiple sounds to be played after the previous sound is complete?
For example in my code:
[[SimpleAudioEngine sharedEngine] playEffect:#"yay.wav"];
[[SimpleAudioEngine sharedEngine] playEffect:#"youDidIt.wav"];
When this code is run, yay.wav and youDidIt.wav play at the exact same time, over each other.
I want yay.wav to play and once complete, youDidIt.wav to play.
If not with SimpleAudioEngine, is there a way with AudioToolbox, or something else?
Thank you!
== UPDATE ==
I think I'm going to go with this method using AVFoundation:
AVPlayerItem *sound1 = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"yay" ofType:#"wav"]]];
AVPlayerItem *sound2 = [[AVPlayerItem alloc] initWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:#"YouDidIt" ofType:#"wav"]]];
AVQueuePlayer *player = [[AVQueuePlayer alloc] initWithItems: [NSArray arrayWithObjects:sound1, sound2, nil]];
[player play];
The easy way would be getting the track duration using -[CDSoundSource durationInSeconds] and then schedule the second effect playing after a proper delay:
[[SimpleAudioEngine sharedEngine] performSelector:#selector(playEffect:) withObject:#"youDidIt.wav" afterDelay:duration];
An easier way to get the audio duration would be patching SimpleAudioManager and add a method that queries its CDSoundEngine (a static global) for the audio duration:
- (float)soundDuration {
return [_engine bufferDurationInSeconds:_soundId];
}
The second approach would be polling on the status of the audio engine and wait for it to stop playing.
alGetSourcei(sourceId, AL_SOURCE_STATE, &state);
if (state == AL_PLAYING) {
...
The sourceId is the ALuint returned by playEffect.
It's simple, tested with Cocos2D 2.1:
Creates a property durationInSeconds on SimpleAudioEngine.h :
#property (readonly) float durationInSeconds;
Synthesize them :
#synthesize durationInSeconds;
Modify playEffect like this :
-(ALuint) playEffect:(NSString*) filePath pitch:(Float32) pitch pan:(Float32) pan gain:(Float32) gain {
int soundId = [bufferManager bufferForFile:filePath create:YES];
if (soundId != kCDNoBuffer) {
durationInSeconds = [soundEngine bufferDurationInSeconds:soundId];
return [soundEngine playSound:soundId sourceGroupId:0 pitch:pitch pan:pan gain:gain loop:false];
} else {
return CD_MUTE;
}
}
As you can see, I inserted durationInSeconds value directly from soundEngine bufferDurationInSeconds from the result of bufferManager.
Now, only ask for the value [SimpleAudioEngine sharedEngine].durationInSeconds and you've got the float duration in seconds of this sound.
Put a timer this seconds and after this, play the next iteration of your sound or put the flag OFF or something.

Unable to retrieve Shoutcast .pls stream

I'm trying to get a .pls stream from a shoutcast server to play in my ios app. So far i've been unsuccessful. I've red a lot of posts on stackoverflow but non of these were of any help.
Can anyone please explain to me, if its even possible, how to get .pls to stream?
all you need is to list the port of you radio, here is one working example:
in - (void)viewDidLoad
NSURL *vibes = [NSURL URLWithString:#"http://website.com:8002"];
vPlayer = [[AVPlayer alloc] initWithURL:vibes];
self.myViewVolume = [[MPVolumeView alloc] initWithFrame:CGRectMake(20, 330, 280, 50)];
[self.myViewVolume sizeToFit];
[self.view addSubview:self.myViewVolume];
you need to create an instance of AVPlayer in your .m file , here it is vPlayer
do not forget to add AVFoundation framework to you project, you can play and stop the stream with [player play] and [player stop]
One problem with AVPlayer is the lack of easy volume control, you can add one with mpViewVolume.
I am also working on radio app and by far AVPlayer is the best to play shoutcast streams.
#Walid Hussain
it worked for me using AVPlayer
link with AVFoundation.framework
//import needed
#import <AVFoundation/AVFoundation.h>
// declaration for the player
AVPlayer * radioPlayer;
// play
NSURL * url = [NSURL URLWithString:#"http://energy10.egihosting.com:9636"];
radioPlayer = [[AVPlayer playerWithURL:url] retain];
[radioPlayer play];

AVAUdioPlayer - changing current URL

In my audio player app, if I hit the fast forward button - the player must find the next song in the playlist and play it. What I do is I get the URL from the next song, and try to put it in the background (background is an instance of AVAudioPlayer*) url field, but that property is read only. So what I'm actually doing - I'm calling the initWithContentsOfURL method (again) to set the URL like this :
[self.background initWithContentsOfURL:
[[_playlist.collection objectAtIndex:currentIndex] songURL] error:nil];
Is this legit? I mean, the compiler tells me that the expression result is unused, but it actually works.
Is there another way of doing it? Thanks ;-)
For playing more than one URL, or effectively changing the URL, use the subclass AVQueuePlayer.
AVQueuePlayer is a subclass of AVPlayer you use to play a number of items in sequence.
Example:
NSString *fooVideoPath = [[NSBundle mainBundle] pathForResource:#"tommy" ofType:#"mov"];
NSString *barVideoPath = [[NSBundle mainBundle] pathForResource:#"pamela" ofType:#"mp4"];
AVPlayerItem *fooVideoItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:fooVideoPath]];
AVPlayerItem *barVideoItem = [AVPlayerItem playerItemWithURL:[NSURL fileURLWithPath:barVideoPath]];
self.queuePlayer = [AVQueuePlayer queuePlayerWithItems:[NSArray arrayWithObjects:fooVideoItem, barVideoItem,nil]];
[self.queuePlayer play];
// things happening...
[self.queuePlayer advanceToNextItem];
Have checked 4 Apple samples, they are using AVAudioPlayer to play one song only. However, your result looks very interesting and impressive! Please let us know, are you stopping the playback before reinitializing object with the same address, are you starting the new audio session ?
As for me, I'd not put the playback and app stability in a risk doing something not mentioned in the documentation but , but to be on the bright side would use the AVAudioPlayer class as it seems the most right, which gives us:
use the error variable to track the possible errors
stop the playing AVAudioPlayer instance, initialize a new instance of AVAudioPlayer and set it to the property letting an old-one to be deallocated automatically.
And you probably know yourself, that
self.background = [self.background initWithContentsOfURL::
will remove the warning for you.

Resources