How to adjust the volume of 2 AVPlayers independently? - ios

I'm working on an app that plays two different tracks from the users iPod library.
I need to be able to adjust the volume of the two tracks independently.
Looking around online I found a suggestion to adjust the AVMutableAudioMix on the playerItems that are active in each of the two AVPlayers.
I've subclassed AVPlayer to include some functionality that I need to emulate a musicPlayer like the iPod.
Below is the method inside my AVPlayer subclass that I would like to use to adjust that players playerItem volume.
This method is being called by my viewController when a sliderValue is changed.
My result is that the volume does not change.
Does anyone know if this approach can work?
It seems to me that if it doesn't my only choice is to dig into Core Audio.
Thanks.
-(void)setPlayerItemVolume:(float)itemVolume
{
self.volume = itemVolume;
AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
AVPlayerItem *currentPlayerItem = [self currentItem];
CMTime currentTime = [currentPlayerItem currentTime];
[audioInputParams setVolume:itemVolume atTime:currentTime];
//[audioInputParams setTrackID:[currentPlayerItem trackID]];
//[allAudioParams addObject:audioInputParams];
NSArray *allAudioParams = [[NSArray alloc]initWithObjects:audioInputParams, nil];
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
[audioMix setInputParameters:allAudioParams];
[currentPlayerItem setAudioMix:audioMix];
}

iOS 7 introduces 2 new properties on AVPlayer: 'volume' and 'muted'. You should be able to use those to independently change audio levels.

Related

How to set System volume programmatically in iOS

i am getting System volume from following code
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(volumeChanged:)
name:#"AVSystemController_SystemVolumeDidChangeNotification"
object:nil];
- (void) volumeChanged:(NSNotification *)
{
float volume = [[[notification userInfo]
objectForKey:#"AVSystemController_AudioVolumeNotificationParameter"]
floatValue];
// here volume is the .....
}
but how to set system or devices volume from xcode (Objective C)
It's not possible to programmatically set the system volume without calling private methods/using private APIs.
You would have to call _commitVolumeChange which is private API and will definitely get your app rejected.
Further reading here.
Maybe this will help you (if you can get access to AVPlayer object and want to change the volume of your playing Asset), I use such code for modifying volume in my AVPlayer while playing video.
- (void)setVolume:(CGFloat)volumeToSet forPlayer:(AVPlayer *)avPlayer {
AVURLAsset *asset = (AVURLAsset *) [[avPlayer currentItem] asset];
NSArray *audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
NSMutableArray *allAudioParams = [NSMutableArray array];
for (AVAssetTrack *track in audioTracks) {
AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
[audioInputParams setVolume:volumeToSet atTime:kCMTimeZero];
[audioInputParams setTrackID:[track trackID]];
[allAudioParams addObject:audioInputParams];
}
AVMutableAudioMix *audioZeroMix = [AVMutableAudioMix audioMix];
[audioZeroMix setInputParameters:allAudioParams];
[[avPlayer currentItem] setAudioMix:audioZeroMix];
}
More details about this solution here Adjusting the volume of a playing AVPlayer
If you would like to change device's volume look here, this is workaround and may broke in some iOS versions, but works now in iOS 7, iOS 8, iOS 9 (tested it).
iOS 9: How to change volume programmatically without showing system sound bar popup?

Change AVPlayer Volume

So I am streaming a video file with audio using AVPlayer and have a UIWebView overlayed over the video that has controls for play/pause, seek, ect. I also have a slider for volume that I am trying to implement but so far I haven't had any luck. Here is my code the change the volume:
- (void)setPlayerVolume:(float)volume
{
NSArray *tracks = [self.player.currentItem.asset tracksWithMediaType:AVMediaTypeAudio];
NSMutableArray *audio = [NSMutableArray array];
for (AVAssetTrack *track in tracks) {
AVMutableAudioMixInputParameters *input = [AVMutableAudioMixInputParameters audioMixInputParameters];
[input setVolume:volume atTime:kCMTimeZero];
[input setTrackID:track.trackID];
[audio addObject:input];
}
AVMutableAudioMix *mix = [AVMutableAudioMix audioMix];
[mix setInputParameters:audio];
[self.player.currentItem setAudioMix:mix];
}
I have been reading up, and it seems like the only solution to use MPVolumeView. Are there any other solutions?
Thanks for any help, its greatly appreciated.

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];
}

How can i set volume of AVPLayer when it's playing radio stream

I'm using AVPlayer for playing radio stream.
For initializing player i'm using next code:
self.asset = [AVAsset assetWithURL:self.url];
self.item = [AVPlayerItem playerItemWithAsset:self.asset];
self.player = [AVPlayer playerWithPlayerItem:self.item];
I've read this question Adjusting the volume of a playing AVPlayer
and i'm trying to set volume with slider value
- (void)setVolume:(float )volume
{
_volume = volume;
NSArray *audioTracks = self.asset.tracks;
NSMutableArray *allAudioParams = [NSMutableArray array];
for (AVAssetTrack *track in audioTracks) {
AVMutableAudioMixInputParameters *audioInputParams =
[AVMutableAudioMixInputParameters audioMixInputParameters];
[audioInputParams setVolume:volume atTime:kCMTimeZero];
[allAudioParams addObject:audioInputParams];
[audioInputParams setTrackID:[track trackID]];
}
AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
[audioMix setInputParameters:allAudioParams];
NSLog(#"%#", self.player.currentItem);
NSLog(#"%f", volume);
[self.player.currentItem setAudioMix:audioMix];
}
I'm also trying to set
[audioInputParams setVolume:volume atTime:self.player.currentTime];
but there are not any tracks in that array (self.asset.tracks),
and its have not any effect on volume. (also i can't get any metadata),
I can't use AVAudioPlayer for playing audio stream by url
An instance of the AVAudioPlayer class, called an audio player,
provides playback of audio data from a file or memory.
Apple recommends that you use this class for audio playback unless you
are playing audio captured from a network stream or require very low
I/O latency.
I'm thinking about using that code https://github.com/DigitalDJ/AudioStreamer/downloads
for playing radio stream, but i don't know how set volume with Audiotoolbox.
Please, help me! Thanks!
i have tried lots of solutions, but the best way to do that is to implement an instance ov MPVolumeVIew:
self.myViewVolume = [[MPVolumeView alloc] initWithFrame:CGRectMake(20, 330, 280, 50)];
[self.myViewVolume sizeToFit];
[self.view addSubview:self.myViewVolume];
You can add this to any UIView you want, just change the class to MPVolumeView
Do not forget to add MediaPlayer framework.
Here is what you have to do if you want to get metadata from your stream:
[playerItem addObserver:self forKeyPath:#"timedMetadata" options:NSKeyValueObservingOptionNew context:NULL];
With this you can simply add the information to any UILabel

tracks in AVComposition losing time when paused

I've created an AVMutableComposition that consists of a bunch of audio tracks that start at specific times. From there, following Apple recommendations, i turned it into an AVComposition before playing it with AVPlayer.
It all works fine playing this AVPlayer item, but if I pause it and then continue, all the tracks in the composition appear to slip back about 0.2 seconds relative to each other (i.e., they bunch up). Hitting pause and continuing several times compounds the effect and the overlap is more significant (basically if I hit it enough, I will end up with all 8 tracks playing simultaneously).
if (self.player.rate > 0.0) {
//if player is playing, pause
[self.player pause];
} else {
if (self.player) {
[self.player play];
return;
}
*/CODE CREATING COMPOSITION - missed out big chunk of code relating to finding the track and retrieving its position and scale/*
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES]
forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
AVURLAsset *sourceAsset = [[AVURLAsset alloc] initWithURL:url options:options];
//calculate times
NSNumber *time = [soundArray1 objectAtIndex:1]; //this is the time scale - e.g. 96 or 120 etc.
double timenow = [time doubleValue];
double insertTime = (240*y);
AVMutableCompositionTrack *track =
[composition addMutableTrackWithMediaType:AVMediaTypeAudio
preferredTrackID:kCMPersistentTrackID_Invalid];
//insert the audio track from the asset into the track added to the mutable composition
AVAssetTrack *myTrack = [[sourceAsset tracksWithMediaType:AVMediaTypeAudio] objectAtIndex:0];
CMTimeRange myTrackRange = myTrack.timeRange;
NSError *error = nil;
[track insertTimeRange:myTrackRange
ofTrack:myTrack
atTime:CMTimeMake(insertTime, timenow)
error:&error];
[sourceAsset release];
}
}
AVComposition *immutableSnapshotOfMyComposition = [composition copy];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:immutableSnapshotOfMyComposition];
self.player = [[AVPlayer alloc] initWithPlayerItem:playerItem];
NSLog(#"here");
[self.player play];
Thanks
OK, this feels a little hacky, but it definitely works if anybody is stuck. If someone has a better answer, do let me know!
Basically, I just save the player.currentTime of the track when I hit pause and remake the track when i hit play, just starting from the point at which i paused it. No discernible delay, but I'd still be happier without wasting this extra processing.
Make sure you properly release your player item after you hit pause, otherwise you'll end up with a giant stack of AVPlayers!
I have a solution that is a bit less hacky but still hacky.
The solution comes from the fact that I noticed that if you seeked on the player, the latency between audio and video introduced by pausing disappeared.
Hence: just save the player.currentTime just before pausing and, player seekToTime just before playing again. It works pretty well on iOS 6, haven't tested on other versions yet.

Resources