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;
}
Related
I'm developing a iOS app using objective-c. When the application is launched a background music is played. The background music should continue playing when the user clicks help button. Also when the user goes back to the main screen from the help screen the background music should be continuously playing.
For me a new background music is getting played along with the old background music when I navigate from help to main menu. So, I am hearing two background music now.
Could anyone help me in solving this issue?
Regards,
Bharathi.
Your problem could be solved if you retained a reference to your audio player in your UIApplicationDelegate (or some other singleton that's kept around).
//in the .h
#property (nonatomic, strong) AVAudioPlayer *player;
//in the .m
- (void) playMusic
{
if (self.player == nil) {
NSString *path = [NSString stringWithFormat:#"%#/music.mp3", [[NSBundle mainBundle] resourcePath]];
NSURL *soundUrl = [NSURL fileURLWithPath:path];
self.player = [[AVAudioPlayer alloc] initWithContentsOfURL:soundUrl error:nil];
}
if (!self.player.isPlaying) {
[self.player play];
}
}
That way you can call it wherever you need with a:
[(MyAppDelegate*)[UIApplication sharedApplication].delegate playMusic];
Though it might be to your advantage to keep around a SoundsManager class as a singleton in order to handle all the sounds that you'll need to track if you're going to need more than just this one.
I have implemented MobileVLCKit in iOS by using MobileVLCKit framework. I have an issue,
When I declare the player #Interface the streaming and voice is working well.
#import <MobileVLCKit/MobileVLCKit.h>
#interface ViewController ()<VLCMediaPlayerDelegate>{
VLCMediaPlayer *vlcPlayer1
}
#end
But, declare the VLCMediaPlayer object at local function the video preview not displayed but, audio id playing.
- (void)viewDidLoad {
[super viewDidLoad];
VLCMediaPlayer *vlcPlayer1 = [[VLCMediaPlayer alloc] initWithOptions:nil];
vlcPlayer1.drawable = view;
media = [VLCMedia mediaWithURL:[NSURL URLWithString: UrlString]];
[vlcPlayer1 setMedia:media];
[vlcPlayer1 play];
}
How can I resolve the issue. Because, I need to create the view dynamically.
Try this:
[vlcplayer.media addOptions:#{ #"network-caching" : #300}];
If it doesn't work, replace 300 with a bigger value.
That may work.
So both of these questions/answers put me on the right path, but this is ultimately what worked for me.
NSURL* _uri = [NSURL URLWithString:uri];
NSArray* initOptions = [NSArray arrayWithObjects:#"--codec=avcodec", "--network-caching=10000", nil];
self.player = [[VLCMediaPlayer alloc] initWithOptions:initOptions];
self.player.media = [VLCMedia mediaWithURL:_uri];
It looks like the "addOptions" is valid, but my particular use case wasn't picking it up, and instead I had to actually initialize the VLCMediaPlayer with the options from the get go. Worked out nice because it actually fits much better with other JAVA/Android/CMD line VLC api's.
I've done a lot of reading and research in trying to come up with a solution to my problem and can't seem to make my code work the way I want, or the way it should. What I want to do is be able to have different buttons play different, short sounds, without overlapping each other if I decide to click another sound button before another sound button's audio is finished. I've successfully got to the point where they play their designated sound using SystemSoundID, but they still overlap when I click on multiple buttons.
Below is a sample of my code:
Interface file:
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController {
}
-(IBAction)Sound:(id)sender;
#end
Implementation file:
#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>
#import <UIKit/UIKit.h>
#implementation ViewController : UIViewController
- (IBAction)Sound:(id)sender
{
NSString *path = [[NSBundle mainBundle] pathForResource:#"Sound" ofType:#"mp3"];
AVAudioPlayer* theAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
[theAudio play];
}
- (void)viewDidLoad
{
[super viewDidLoad];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
#end
I would appreciate the help more than you know, as I've spent quite some time trying to get it to work the way it should. If you have any questions for me please let me know! Thank you very much.
-Michael
I have the understanding that when you play an AudioSystemSound that it will play until completion. This method of playing Audio isn't a highly functional one and only really supposed to be used for simple "clicks" and "dings" etc. For a more comprehensive audio suite of features perhaps you could look at using the AVFoundation library with AVAudioPlayer where you can call "stop" to cease playing the called sound.
https://developer.apple.com/av-foundation/
Edited addition to the above answer:
In your .h file make sure you include;
#import AVFoundation;
and then create an AV player with;
AVAudioPlayer *playAudio
In your .m file add this block of code below in your method that needs to play the sound. Of course adjust the song/audiofile name and format appropriately;
NSString *backgroundMusic = [[NSBundle mainBundle]
pathForResource:#"insertSongNameHere" ofType:#"mp3"];
playAudio = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:insertSongNameHere] error:NULL];
playAudio.numberOfLoops = -1;
[playAudio play];
Note: Number of loops being -1 means repeat forever. You can of course make that whatever number you like though. I hope that all helps, if not let me know and I'll see what I can do to assist!
I'm trying to add a custom click sound on all my buttons in my application. I've created a class category for UIButton with the following code in it:
NSURL *soundURL = [NSURL fileURLWithPath:[[NSBundle bundleWithIdentifier:#"com.apple.UIKit"] pathForResource:#"Tock" ofType:#"aiff"]];
NSError *error;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:soundURL error:&error];
if(audioPlayer == nil){
NSLog(#"%#", [error description]);
} else{
[audioPlayer play];
}
All this is executed in: - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
But from what I understand there is a common problem with ARC (wich I am using) here. The audioPlayer objects seems to be released before the sound is played. I've also had a look at AudioServicesPlaySystemSound();. But that's no alternative for me at the moment because I need a volume level controller in my application and I've found no way to change the system volume without leaving the app.
My question is: Is there any way to make a strong reference to my audioPlayer object in the UIButton class category? Or is there any other, maybe better way, to get this working?
When adding any object relationship in a category, it's always worth looking at associated objects. Use the objc_setAssociatedObject() and objc_getAssociatedObject() functions. Make sure to use a unique pointer value for the key, and in your case you'll definitely want to use one of the retain association policies to ensure your object's lifespan is appropriate.
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.