AVPlayer audio volume over Airplay? - ios

Does anyone know of a way to adjust an AVPlayer track's volume when it is playing over Airplay? I have tried AVAudioMix and MPVolumeView but neither of them work. I have tried on iOS 5 and iOS 6 and am using the latest xcode 4.5.1. A simple example of this not working is Apple's AVPlayerTestApp which does a simple fade out using setVolumeRampFromStartVolume. This works fine on the device but doesn't if connected through Airplay.
In ViewDidLoad I load a track and start it playing (this is all taken from AVPlayerTestApp)
AVAudioSession *session = [AVAudioSession sharedInstance];
[session setActive:TRUE error:nil];
[session setCategory:AVAudioSessionCategoryPlayback error:nil];
self.mediaItems = [[[MPMediaQuery songsQuery] items] mutableCopy];
NSURL *anUrl = [[mediaItems objectAtIndex: 0] valueForProperty:MPMediaItemPropertyAssetURL];
AVAsset *asset = [AVURLAsset URLAssetWithURL:anUrl options:nil];
AVPlayerItem *myPlayerItem = [AVPlayerItem playerItemWithAsset:asset];
self.myPlayer1 = [[[AVPlayer alloc] initWithPlayerItem:myPlayerItem] retain];
[myPlayer1 play];
then I have a button which opens an alertview allowing user to set volume and switch on airplay
MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame: CGRectMake(10, 37, 260, 20)] autorelease];
UIAlertView *volumeAlert = [[UIAlertView alloc] initWithTitle:#"Volume" message:#"" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[volumeView sizeToFit];
[volumeAlert addSubview:volumeView];
[volumeAlert show];
[volumeAlert release];
then another button which fades out the currently playing track
AVAsset *asset = [myPlayer1.currentItem asset];
NSArray *keys = [NSArray arrayWithObject:#"tracks"];
[asset loadValuesAsynchronouslyForKeys:keys completionHandler:^(void) {
NSError *error = nil;
AVKeyValueStatus trackStatus = [asset statusOfValueForKey:#"tracks" error:&error];
CMTime currentTime;
switch (trackStatus) {
case AVKeyValueStatusLoaded:
if(myPlayer1)
{
NSArray *tracks = [asset tracksWithMediaType:AVMediaTypeAudio];
NSMutableArray * allAudioParams = [NSMutableArray array];
for (AVAssetTrack *t in tracks) {
AVMutableAudioMixInputParameters *params =[AVMutableAudioMixInputParameters audioMixInputParameters];
float fadeOutSeconds = 5;
currentTime = [myPlayer1 currentTime];
[params setVolumeRampFromStartVolume: 1.0 toEndVolume: 0.0 timeRange: CMTimeRangeMake(currentTime, CMTimeMakeWithSeconds(fadeOutSeconds, 1))];
[params setTrackID:[t trackID]];
[allAudioParams addObject:params];
}
AVMutableAudioMix * zeromix = [AVMutableAudioMix audioMix];
[zeromix setInputParameters:allAudioParams];
[myPlayer1.currentItem setAudioMix:zeromix];
}
break;
case AVKeyValueStatusFailed:
// error occured loading AVAsset
NSLog(#"error occured loading AVAsset");
break;
case AVKeyValueStatusCancelled:
// loading of the AVAsset was cancelled
NSLog(#"loading of the AVAsset was cancelled");
break;
default:
break;
}
}];
This works as expected on the device and fades the volume when the button is tapped. However, if Airplay is turned on the volume change doesn't get through. Using MPMoviePlayController I can do fades myself which work over Airplay but AVPlayer has less latency over Airplay so I would rather use that.
Any help much appreciated.

As Sam_899 didn't post an answer I'll have to do it. As far as I can work out, it is impossible to control the volume of avplayer over airplay unless the connection is made with mirroring turned on. As the only way to turn mirroring on is from outside your app the only option is to inform users how to turn it on themselves and hope they are happy with it! For those who don't know how to turn on mirroring over airplay; double click the home button, swipe left to the volume control, tap the airplay icon, choose the airplay device to connect to then switch mirroring on.

Related

MP3 Queue Player - Load in background thread?

I have an AVQueuePlayer that is used to play a list of MP3 songs from the internet (http). I need to also know which song is currently playing. The current problem is that loading the song causes a delay that blocks the main thread while waiting for the song to load (first song as well as sequential songs after the first has completed playback).
The following code blocks the main thread:
queuePlayer = [[AVQueuePlayer alloc] init];
[queuePlayer insertItem: [AVPlayerItem playerItemWithURL:url] afterItem: nil]; // etc.
[queuePlayer play]
I am looking for a way to create a playlist of MP3s where the next file to be played back is preloaded in the background.
I tried the following code:
NSArray* tracks = [NSArray arrayWithObjects:#"http://example.com/song1.mp3", #"http://example.com/song2.mp3", #"http://example.com/song3.mp3", nil];
for (NSString* trackName in tracks)
{
AVURLAsset* audioAsset = [[AVURLAsset alloc]initWithURL:[NSURL URLWithString:trackName]
options:nil];
AVMutableCompositionTrack* audioTrack = [_composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
NSError* error;
[audioTrack insertTimeRange:CMTimeRangeMake([_composition duration], audioAsset.duration)
ofTrack:[[audioAsset tracksWithMediaType:AVMediaTypeAudio]objectAtIndex:0]
atTime:kCMTimeZero
error:&error];
if (error)
{
NSLog(#"%#", [error localizedDescription]);
}
// Store the track IDs as track name -> track ID
[_audioMixTrackIDs setValue:[NSNumber numberWithInteger:audioTrack.trackID]
forKey:trackName];
}
_player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
[_player play];
The issue with this is that I am not sure how to detect when the next song starts playing. Also, the docs don't specify whether or not this will pre-load MP3 files or not.
I am looking for a solution that:
Plays MP3s by pre-loading them in the background prior to playback (ideally start loading the next song before the current song finishes, so it is ready for immediate playback once the current song finishes)
Allow me to view the current song playing.
AVFoundation has some classes designed to do exactly what you're looking for.
It looks like your current solution is to build a single AVPlayerItem that concatenates all of the MP3 files that you want to play. A better solution is to create an AVQueuePlayer with an array of the AVPlayerItem objects that you want to play.
NSArray* tracks = [NSArray arrayWithObjects:#"http://example.com/song1.mp3", #"http://example.com/song2.mp3", #"http://example.com/song3.mp3", nil];
NSMutableArray *playerItems = [[NSMutableArray alloc] init];
for (NSString* trackName in tracks)
{
NSURL *assetURL = [NSURL URLWithString:trackName];
if (!assetURL) {
continue;
}
AVURLAsset* audioAsset = [[AVURLAsset alloc] initWithURL:assetURL
options:nil];
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:audioAsset];
[playerItems addObject:playerItem];
}
_player = [[AVQueuePlayer alloc] initWithItems:playerItems];
[_player play];
In answer to your final wrap-up questions:
Yes, AVQueuePlayer DOES preload the next item in the playlist while it's playing the current one.
You can access the currentItem property to determine which AVPlayerItem is currently playing.

MPMoviePlayerController plays video but shows black screen in iOS 8

I am using the MPMoviePlayerController to play video stored in main bundle.
However sometime when I play video it plays the video but shows the black screen in that case seek bar also works properly.
At that time if I switch to fullscreen mode and get back to normal mode(or send app in background mode and bring it in foreground mode) then it plays(resumes) video properly(did not show the black screen).
Please refer the below screenshot of an App:
UPDATE:
Below is the code used to play video:
- (void)playVideo:(NSString *)filepath {
NSLog(#"get audiosession");
AVAudioSession *audiosession = [AVAudioSession sharedInstance];
[audiosession setCategory:AVAudioSessionCategoryAmbient error:nil ];
NSLog(#"finish audiosession");
appDelegate.currentCourseNoExt = [filepath stringByDeletingPathExtension];
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#.mp3",appDelegate.currentCourseNoExt]];
NSError *error;
NSLog(#"get avaudioplayer");
AVAudioPlayer *avPlayerTemp = [[AVAudioPlayer alloc]
initWithContentsOfURL:url
error:&error];
NSLog(#"finish avaudioplayer");
avPlayer = avPlayerTemp;
NSLog(#"copy avaudioplayer");
// get frame
CGRect frame = self.view.frame;
frame.origin.x = 0;
frame.origin.y = 0;
frame.size.height -= 0;
NSLog(#"self.view.frame - %#",NSStringFromCGRect(self.view.frame));
appDelegate.moviePlayer = [[MPMoviePlayerController alloc] initWithContentURL:[NSURL fileURLWithPath:filepath]];
appDelegate.moviePlayer.view.frame = frame;
appDelegate.moviePlayer.view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:appDelegate.moviePlayer.view];
[self.view sendSubviewToBack:(appDelegate.moviePlayer.view)];
imgBottomAdvertisement.frame = CGRectMake(0, appDelegate.moviePlayer.view.frame.size.height - imgBottomAdvertisement.frame.size.height, imgBottomAdvertisement.frame.size.width, imgBottomAdvertisement.frame.size.height);
[appDelegate.moviePlayer.backgroundView addSubview:imgBottomAdvertisement];
appDelegate.moviePlayer.currentPlaybackRate= 1.0;
appDelegate.moviePlayer.currentPlaybackTime = appDelegate.SMile.doubleValue;
[avPlayer setRate:0];
[appDelegate.moviePlayer play ];
[avPlayer play];
}
The above method is called from viewDidAppear method of ViewController.

Reduce / decrease background ipod sound in my App

I need to make an application in iOS who make different sound like "beep" during her fonctionnement.
I have implemented interaction to background ipod with MPMusicPlayerController.
The probleme:
I do not hear the "beep" due to the volume of music from ipod.
I need to reduce the volume of the ipod but NOT reduce the volume of my application.
The tomtom application make that when the speaker give information about the direction to take. But I do not know how.
I have try this code:
- (void)playSoundCountDown{
NSError *errorObject = nil;
[[AVAudioSession sharedInstance]setCategory:AVAudioSessionCategoryAmbient error:&errorObject];
if (errorObject) {
NSLog(#"Error setting category! %#",errorObject);
}
NSString *path = [[NSBundle mainBundle] pathForResource:#"countDownFiveToOne" ofType:#"caf"];
theSound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil];
theSound.delegate = self;// (AVAudioPlayer *theSound;)
[theSound setVolume:0.4f];
[theSound play];
float volumeDecrease = 0.2f;
MPMusicPlayerController* iPodMusicPlayer = [MPMusicPlayerController iPodMusicPlayer];
[iPodMusicPlayer setVolume:volumeDecrease];
}
But "setVolume" reduce the volume of all sound of the device, included the sound of my app...
I need your help please.
Thanks.
Instead of decreasing the volume, try enabling ducking:
- (void)playSoundCountDown{
NSError *errorObject = nil;
[[AVAudioSession sharedInstance]
setCategory: AVAudioSessionCategoryAmbient
withOptions: AVAudioSessionCategoryOptionDuckOthers
error: nil];
NSString *path = [[NSBundle mainBundle] pathForResource:#"countDownFiveToOne" ofType:#"caf"];
theSound = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil];
theSound.delegate = self;
[theSound setVolume:0.4f];
[theSound play];
}

AVQueuePlayer volume not changed

I wanted to change volume settings with a UISlider.
I used currentItem inside AVQueuePlayer.
ps0 is an AVQueuePlayer.
I have no error and same sound level when I use my UISlider:
- (IBAction)volumeSliderMoved:(UISlider *)sender
{
AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
AVPlayerItem *av = [ps0 currentItem];
CMTime currentTime = [av currentTime];
float volume = [sender value];
[audioInputParams setVolume:volume atTime:currentTime];
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
audioMix.inputParameters = [NSArray arrayWithObject:audioInputParams];
av.audioMix = audioMix;
[ps0 replaceCurrentItemWithPlayerItem: av];
}
EDITED :
I tried another solution from Apple to change volume settings.
As you can see in this solution, it create a new playerItem and a new player.
But my playerItem is a copy of the current one because I just want to change the sound (not the item). And it is automatically related to the old player.
When I try to use their solution. I have an error message saying:
An AVPlayerItem cannot be associated with more than one instance of AVPlayer
Any suggestion?
EDITED again
To change playback with AVQueuePlayer
I have an array with every mp3 name “textMissingTab”
I have an array with AVPlayerItem “soundItems”
Creation :
textMissingTab = [[NSArray alloc] initWithObjects:#"cat",#"mouse",#"dog",#"shark",#"dolphin", #"eagle", #"fish", #"wolf", #"rabbit", #"person", #"bird", nil];
for (NSString *text in textMissingTab)
{
NSURL *soundFileURL = [[NSURL alloc] initFileURLWithPath: [[NSBundle mainBundle] pathForResource:text ofType:#"mp3"]];
AVPlayerItem *item = [AVPlayerItem playerItemWithURL:soundFileURL];
[soundItems addObject:item];
}
Init :
NSURL *soundFileURL = [[NSURL alloc] initFileURLWithPath: [[NSBundle mainBundle] pathForResource:#"dog" ofType:#"mp3"]];
ps0 = [[AVQueuePlayer alloc] initWithURL:soundFileURL];
Change playItem : text is NSString
int index = [textMissingTab indexOfObject:text];
[ps0 setActionAtItemEnd:AVPlayerActionAtItemEndNone];
CMTime newTime = CMTimeMake(0, 1);
[ps0 seekToTime:newTime];
[ps0 setRate:1.0f];
[ps0 replaceCurrentItemWithPlayerItem:[soundItems objectAtIndex:(index)]];
[ps0 play];
I had several problems using AVQueuePlayer and changing playback parameters. If your goal is to use a slider for volume adjustment, I would replace the UISlider with a blank UIView in your storyboard and then instantiate an MPVolumeView within that view. This works reliably for me. Note that MPVolumeView does not show up on the simulator.
I think you are right tigloo. MPVolumeView is the best way to manage sound level.
It is explained in this tutorial :http://www.littlereddoor.co.uk/ios/controlling-system-output-volume-by-adding-an-mpvolumeview-object-to-an-ios-application/
"Everything works exactly as we would expect until the user hits the stumbling block that is created by the current ringer volume that is set on the device. The maximum value of the AVAudioPlayer property is 1.0, where 1.0 is equal to the current ringer volume setting of the device. Simply put, if the device ringer volume is set to 50% volume, then 100% of the AVAudioPlayer volume still only equates to the already set 50% ringer. The limitations of using this method speak for themselves."
I edited again my post to show how I manage some songs with AVQueuePlayer. This part of code is working.
AVAsset *asset;
NSArray *playerTracks;
NSMutableArray *playerParams;
AVMutableAudioMix *muteAudioMix;
for (int k=0; k<[[audio items] count]; k++)
{
//disable audio (this is the version when you have more than one video in the playlist: i write this version so it should be more useful)
asset = [AVURLAsset URLAssetWithURL:[NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:[soundfile objectAtIndex:k+([soundfile count]-[[audio items] count])] ofType:#"mp3"]] options:nil];
playerTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
playerParams = [NSMutableArray array];
for (AVAssetTrack *track in playerTracks) {
AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
[audioInputParams setVolume:1.0 atTime:kCMTimeZero];
[audioInputParams setTrackID:[track trackID]];
[playerParams addObject:audioInputParams];
}
muteAudioMix = [AVMutableAudioMix audioMix];
[muteAudioMix setInputParameters:playerParams];
[[[audio items] objectAtIndex:k] setAudioMix:muteAudioMix];
}

AVAudioPlayer sound file location not changing / updating

*This is my first stackoverflow question so apologies if I am doing something wrong.
I am trying to create a simple sound board app. I'm having trouble getting my AVAudioPlayer on xCode 4.1 (iOS 6.1.2) to update where the sound file to play is located. I wanted to avoid having multiple audio players (audioPlayer2, audioPlayer3, etc) so I am trying to use the same audio player but instead, update where the sound file is located. I only want one sound playing at a time so multiple sounds is obviously not an issue. Here is my code:
- (IBAction)play {
if (audioPlayer.playing == NO) {
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/k.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = 0;
//I added the next two lines to allow me to play AVAudio with MPiPodPlayer (which I found on this site, too) simultaneously.
AVAudioSession *audiosession = [AVAudioSession sharedInstance];
[audiosession setCategory:AVAudioSessionCategoryAmbient error:nil];
[audioPlayer play];
[start setTitle:#"Stop" forState:UIControlStateNormal];
}
else
if (audioPlayer.playing == YES) {
[audioPlayer stop];
[start setTitle:#"Start" forState:UIControlStateNormal];
}
}
- (IBAction)play2 {
if (audioPlayer.playing == NO) {
NSURL *url = [NSURL fileURLWithPath:[NSString stringWithFormat:#"%#/chicken.mp3", [[NSBundle mainBundle] resourcePath]]];
NSError *error;
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
audioPlayer.numberOfLoops = 0;
AVAudioSession *audiosession = [AVAudioSession sharedInstance];
[audiosession setCategory:AVAudioSessionCategoryAmbient error:nil];
[audioPlayer play];
[start2 setTitle:#"Stop" forState:UIControlStateNormal];
clicked = 1;
clicked2 = 0;
} else
if (audioPlayer.playing == YES) {
[audioPlayer stop];
[start2 setTitle:#"Start" forState:UIControlStateNormal];
clicked = 0;
}
}
My IBActions are linked to UIButtons and the 'start's are UIButton references. Whenever I click on 'start' it plays 'k.mp3' fine, however if I then click on 'start2' AFTER it starts to play the 'k.mp3' file, and not the chicken file. Whichever button I click on first is what the url is set to. This is my first iPhone OS application project so I realize there are probably some embarrassing coding mistakes in there (feel free to correct me). I'd like an answer that would be applicable to multiple buttons, even for some 30 buttons so I do not have to copy and paste stop audio player 1, 2, and 3 for each button.
To summarize: I have multiple buttons that play one sound each, I would like no more than one sound playing at a time; when I click on a button it plays 1 sound and all other audio players stop. I would prefer having only one AVAudioPlayer instance for simplicity. Thanks in advance!
Check if application control goes into - (IBAction)play2
And if control goes in it and it is still not working then you might try [audioPlayer release] before
audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:url error:&error];
that might do the trick for you

Resources