I have the following code that no longer works in iOS 7 for changing the sessionPreset of a capture session while it is still running (but not capturing video). This worked on iOS 6.x, but on 7 the video preview freezes.
I've tried removing begin/commit configuration, as well as locking the input device and unlocking after. I've also tried calling beginSession again after the commitConfiguration, but this causes the device to start capturing video.
Is this a bug or am I doing something wrong?
- (void)setVideoCaptureSessionPreset:(NSString *)videoCaptureSessionPreset
{
if (_captureSession.sessionPreset == videoCaptureSessionPreset) return;
if (![_captureSession canSetSessionPreset:videoCaptureSessionPreset])
{
TFLog(#"%s Device cannot set preset to: %#", __PRETTY_FUNCTION__, videoCaptureSessionPreset);
return;
}
[_captureSession beginConfiguration];
_captureSession.sessionPreset = videoCaptureSessionPreset;
[_captureSession commitConfiguration];
}
Edit: Not only does this cause the video preview to freeze, it causes the entire phone lock up (background no longer displayed on home screen, app cannot restart, and eventually the entire phone has to be hard-reset).
In iOS 7 and up, the solution is to use the new AVCaptureDevice activeFormat API and set the session preset to AVCaptureSessionPresetInputPriority. When you change the active format, you have to wrap your startRunning in the lockForConfiguration like so:
if ([_videoCaptureDevice lockForConfiguration:nil])
{
_videoCaptureDevice.activeFormat = format
[_captureSession startRunning];
[_videoCaptureDevice unlockForConfiguration];
}
Related
I have created a camera using AVCaptureSession. I have configured that for both Photo and Video recording modes.
Camera and App is running fine. Also I allowed background music play (If user play song using Music App in iPhone) while open camera or recording video. It is also working fine. (Attached image 2)
I allowed background Music play with the help of this code
AVAudioSession *session1 = [AVAudioSession sharedInstance];
[session1 setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers|AVAudioSessionCategoryOptionDefaultToSpeaker|AVAudioSessionCategoryOptionAllowBluetooth error:nil];
[session1 setActive:YES error:nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
Now if i receive a call, minimize phone call screen by tapping on Home button and open app and want to open camera screen to capture image / record video, It opens but freeze with a image (Attached image(1)).
Now my requirement is, i want to capture image / record video while on phone call. I looked for another apps, and Snapchat is doing same, and i am able to record video while i am on call.
please help me, how can i achieve this.
You need to use the AVCaptureSessionWasInterruptedNotification and AVCaptureSessionInterruptionEndedNotification callbacks and disconnect the audio capture while the session is interrupted:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(sessionWasInterrupted:) name:AVCaptureSessionWasInterruptedNotification object:self.session];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(sessionInterruptionEnded:) name:AVCaptureSessionInterruptionEndedNotification object:self.session];
// note that self.session is an AVCaptureSession
-
- (void)sessionWasInterrupted:(NSNotification *)notification {
NSLog(#"session was interrupted");
AVCaptureDevice *device = [[self audioInput] device];
if ([device hasMediaType:AVMediaTypeAudio]) {
[[self session] removeInput:[self audioInput]];
[self setAudioInput:nil];
}
}
- (void)sessionInterruptionEnded:(NSNotification *)notification {
NSLog(#"session interuption ended");
}
// note that [self audioInput] is a getter for an AVCaptureDeviceInput
This will allow the camera to continue running and allows it to capture stills / silent video
Now as for how to reconnect the audio after the call ends.. let me know if you figure it out, seemed impossible as of iOS 10: Callback when phone call ends? (to resume AVCaptureSession)
I'm running a session with AVCaptureSessionPresetMediumto process the frames, when I find what I need I want to capture a stills image with AVCaptureSessionPresetPhoto. I'm changing the sessionPreset with the code
dispatch_async(_captureSessionQueue, ^{
[_captureSession beginConfiguration];
if ([_captureSession canSetSessionPreset:AVCaptureSessionPresetPhoto])
{
_captureSession.sessionPreset = AVCaptureSessionPresetPhoto;
}
[_captureSession commitConfiguration];
});
When this code is called the screen(AVCaptureVideoPreviewLayer) "blinks".
I can't use highResolutionStillImageOutputEnabled because I need to support iOS 7 and devices lower than iPhone 6. Does any one have an idea why this blink happens?
I am working on iOS app which will be compatible with iOS 6/7 and stream audio .mp3 files from a website.
I have already gotten this to work using the following code:
-(NSString*)documentsFolder
{
NSString* dataPath = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents"];
if (![[NSFileManager defaultManager] fileExistsAtPath:dataPath])
[[NSFileManager defaultManager] createDirectoryAtPath:dataPath withIntermediateDirectories:NO attributes:nil error:NULL];
return dataPath;
}
-(NSString*)createURLFile:(NSString*)songURL
{
NSString* M3U_FILE = #"song.m3u";
NSString* path = [NSString stringWithFormat:#"%#",[[self documentsFolder] stringByAppendingPathComponent:M3U_FILE]];
if([[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil])
{
NSFileHandle* outFile = [NSFileHandle fileHandleForWritingAtPath:path];
if(outFile != nil)
{
NSData* buffer = [songURL dataUsingEncoding:NSUTF8StringEncoding];
[outFile writeData:buffer];
return path;
}
}
return nil;
}
- (void)createStreamer
{
// Remove any previous references.
[[NSNotificationCenter defaultCenter] removeObserver:self];
// Create a new player.
NSString* fileURL = [self createURLFile:self.aSong.songpath];
self.songPlayer = [[AVPlayer alloc]initWithURL:[NSURL fileURLWithPath:fileURL]];
NSAssert(self.songPlayer != nil, #"NIL AVPlayer Created!!!");
// Observer for when the song ends...
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[self.songPlayer currentItem]];
[[UIApplication sharedApplication] setIdleTimerDisabled: YES];
}
I store the url for the .mp3 file in a local .m3u file and use that to load up the AVPlayer. In earlier versions of iOS, I was told that the AVPlayer would load the song first and then play it, not stream it immediately. While this does not appear to be true in iOS 6/7 (the song starts streaming almost immediately), the .m3u file was being created in case there were any problems created by not having it done this way.
With this, a loop is monitoring the status of the AVPlayer and after a few seconds, the audio starts to play out the phone without a problem.
For testing purposes, I set up an MPVolumeView on the page which plays songs:
MPVolumeView *volumeView = [[[MPVolumeView alloc] initWithFrame:CGRectMake(0, 0, 310, 20)] autorelease];
volumeView.center = CGPointMake(160,62);
[volumeView sizeToFit];
[self.view addSubview:volumeView];
The reason for this is that the volume slider will also show an indicator if the bluetooth is connected as an audio output source and allow me to change the audio route between the phone and the bluetooth device. So far, so good.
I connected my phone to my Jawbox Jambone via bluetooth, start the AVPlayer on a song, and the song comes out of the Jawbox as expected. The volume control has the small "rectangle with arrow" indicating that I can switch the audio output and indeed, while the song is playing, I can switch between the phone and the Jawbox. Happiness.
The problem occurs when I try to connect it to a car. I have two experiences with this:
The car is already paired with the phone for making/receiving calls. The phone even indicates it is paired when I get into the car. But when I use the same code to play the same audio files, they only come out of the phone. The volume slider does not show the "bluetooth route" indicator at all (like it does not recognize the car as a audio output route).
In another car, the audio was streaming from another app (some radio streaming app). The other app was stopped and this one started. The audio started playing for the same song tested above, but stopped after a second or two. Again, there was no indicator on the volume slider that the bluetooth was connected at this point.
Can somebody explain to me why the audio could stream fine out to one bluetooth device but not to another?
Have I missed something (an entitlement?) in the profile for my app that will allow it to stream audio via bluetooth to a car?
There is this project at GIT.
Play iOS project is a streaming client for Play that runs on your iPhone/iPad. It supports background audio as well as the media keys when backgrounded.
It supports:
Streams shoutcast stream
Displays currently playing track
Background audio
Lock screen album art & play controls
AirPlay (along with Bluetooth) streaming. Supports sending metadata
and album art
You can download the project here.
I have not tested this on CAR bluetooth audio player though. Hope it may be of any help to you.
In the first example, your car may simply be a remote player. You would need to register for remote events like this (consider using an AVAudioPlayer instead of an AVPlayer also)
Setup the AudioSession to recognize a bluetooth audio route:
- (BOOL)prepareAudioSession {
// deactivate existing session
NSError *setCategoryError = nil;
NSError *activationError = nil;
BOOL success = [[AVAudioSession sharedInstance] setActive:NO error: nil];
if (!success) {
NSLog(#"deactivationError");
}
// set audio session category AVAudioSessionCategoryPlayAndRecord options AVAudioSessionCategoryOptionAllowBluetooth
success = [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback withOptions:AVAudioSessionCategoryOptionAllowBluetooth error:&setCategoryError];
if (!success)
{
NSLog(#"setCategoryError %#",setCategoryError);
}
// activate audio session
success = [[AVAudioSession sharedInstance] setActive:YES error: &activationError];
if (!success) {
NSLog(#"activationError");
}
return success;
}
You can check the routes:
AVAudioSessionRouteDescription *mAVASRD = audioSession.currentRoute;
NSLog(#"the array is %#",mAVASRD.outputs);
for (int ctr = 0; ctr < [mAVASRD.outputs count]; ctr++)
{
AVAudioSessionPortDescription *myPortDescription = [mAVASRD.outputs objectAtIndex:ctr];
NSLog(#"the type is %#",myPortDescription.portType);
NSLog(#"the name is %#",myPortDescription.portName);
NSLog(#"the UID is %#",myPortDescription.UID);
NSLog(#"the data sources are %#",myPortDescription.dataSources);
}
Then initialize your AVAudioPlayer and turn on RemoteControlEvents (you can use the console in your car to send play/pause/etc)
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
then implement something like the delegate method for AVAudioPlayer in this stack overflow question to capture the received events and react accordingly in your code:
AVAudioPlayer on Lock Screen
In the scenario 2, when you moved one app to the background (the radio streaming app) and started your app, the likely culprit for the issue is the same cause - your app has to recognize the bluetooth route for audio.
By the way for phone calls and Siri, the iOS uses a different Bluetooth channel that the default for remote control (which is the one I am describing for your car).
When you setup this route and remote control events, you also get a bonus byproduct - your app will be controllable from the lock screen. Check out this technical note from Apple to configure your app to play in the background as well if that is also something you need to do when the screen locks: Technical QA document QA1668
Finally, for added integration via your bluetooth route, look at MPNowPlayingInfoCenter - put the title artist artwork and other good stuff on the lock screen and on most bluetooth screens in the car that are displaying that information.
I'm pretty sure MPVolumeView can only address Bluetooth devices which conform to the newer low power consumption Bluetooth spec... (Bluetooth Low Energy or BLE)...
I know the phone app doesn't use MPVolumeView, probably this other audio player doesn't either.. You may need to look into CoreBluetooth and implement your own :( good luck. There may be a solution on github
A bluetooth speaker designed as a speaker will be no problem.
However, a car will usually be a "phone" bluetooth speaker and will only accept a "phone" type of communications.
My guess would be that you would have to trick it by setting up a "phone audio" connection and having the incoming audio transfer into the void, and the outgoing music stream as a phone signal.
Mind you, signal quality might degrade and there probaly won't be a fix for that.
For some reason the first time I open the UIImagePickerController in camera mode on my app it comes up blank. I have to close and reopen that view to get the camera feed to start working. I'm using the standard code that works in iOS 6 perfectly for camera capture. From the sample below I'm firing the capturePhoto: method. Anyone else running into this jenkiness with the iOS 7 camera? I checked the Apple dev forums but its near impossible to find answers there.
- (IBAction)capturePhoto:(id)sender {
[self doImagePickerForType:UIImagePickerControllerSourceTypeCamera];
}
- (void)doImagePickerForType:(UIImagePickerControllerSourceType)type {
if (!_imagePicker) {
_imagePicker = [[UIImagePickerController alloc] init];
_imagePicker.mediaTypes = #[(NSString*)kUTTypeImage];
_imagePicker.delegate = self;
}
_imagePicker.sourceType = type;
[self presentViewController:_imagePicker animated:YES completion:nil];
}
I'm also using UIImagePickerController and ran into the same issue with a blank screen. I'd like to expand a little on what klaudz mentioned regarding iOS 7 authorization for the camera.
Reference:
https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVCaptureDevice_Class/Reference/Reference.html
"Recording audio always requires explicit permission from the user; recording video also requires user permission on devices sold in certain regions."
Here is some code fragments you can start with to check to see if you have permission for the camera and request it if your app hadn't previously requested it. If you are denied due to an earlier request, your app may need to put up a notice to the user to go into settings to manually enable access as klaudz pointed out.
iOS 7 example
NSString *mediaType = AVMediaTypeVideo; // Or AVMediaTypeAudio
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:mediaType];
// This status is normally not visible—the AVCaptureDevice class methods for discovering devices do not return devices the user is restricted from accessing.
if(authStatus == AVAuthorizationStatusRestricted){
NSLog(#"Restricted");
}
// The user has explicitly denied permission for media capture.
else if(authStatus == AVAuthorizationStatusDenied){
NSLog(#"Denied");
}
// The user has explicitly granted permission for media capture, or explicit user permission is not necessary for the media type in question.
else if(authStatus == AVAuthorizationStatusAuthorized){
NSLog(#"Authorized");
}
// Explicit user permission is required for media capture, but the user has not yet granted or denied such permission.
else if(authStatus == AVAuthorizationStatusNotDetermined){
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
// Make sure we execute our code on the main thread so we can update the UI immediately.
//
// See documentation for ABAddressBookRequestAccessWithCompletion where it says
// "The completion handler is called on an arbitrary queue."
//
// Though there is no similar mention for requestAccessForMediaType, it appears it does
// the same thing.
//
dispatch_async(dispatch_get_main_queue(), ^{
if(granted){
// UI updates as needed
NSLog(#"Granted access to %#", mediaType);
}
else {
// UI updates as needed
NSLog(#"Not granted access to %#", mediaType);
}
});
}];
}
else {
NSLog(#"Unknown authorization status");
}
In iOS 7, an app could access the camera before getting authorize of the user.
When an app accesses the camera the first time, iOS show an alert view to ask user.
Users could also set the authorize in Settings--Privacy--Camera--[Your app's name].
The camera will stay in a black blank view if the switch is off.
If you call the camera by using AVCaptureDeviceInput, you can check like:
NSError *inputError = nil;
AVCaptureDeviceInput *captureInput =
[AVCaptureDeviceInput deviceInputWithDevice:inputDevice error:&inputError];
if (inputError &&
inputError.code == AVErrorApplicationIsNotAuthorizedToUseDevice)
{
// not authorized
}
If you call by using UIImagePickerController, I am still looking for a way to check whether got the authorize.
I tried these two methods:
[UIImagePickerController isSourceTypeAvailable:]
[UIImagePickerController isCameraDeviceAvailable:]
but they did't work that they all returned YES.
UPDATE
Thanks for Scott's expanding. [AVCaptureDevice authorizationStatusForMediaType:] is a better way to check.
AVAuthorizationStatus authStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (authStatus == AVAuthorizationStatusAuthorized) {
// successful
} else {
// failed, such as
// AVAuthorizationStatusNotDetermined
// AVAuthorizationStatusRestricted
// AVAuthorizationStatusNotDetermined
}
But remember to check the iOS version, because [AVCaptureDevice authorizationStatusForMediaType:] and AVAuthorizationStatus are available above iOS 7.
if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 7.0) {
// code for AVCaptureDevice auth checking
}
I experienced the exact same problem, and tried every solution on the Internet with no luck. But finally I found out it was the background thread prevented the camera preview to show up. If you happen to have background thread running while trying to open the camera as I do, try to block the background thread and see what happens. Hope you can get around it.
I came across this control AQPhotoPicker. It's quite easy to use, and hopefully it will help you
I've created my own custom controls for use with the MPMoviePlayerController. So far everything works except the mute button control.
I've configured the AVAudioSession using the following code before I create my instance of the MPMoviePlayerController.
NSError *modeError = nil;
[self.audioSession setMode:AVAudioSessionModeMoviePlayback error:&modeError];
if (modeError != nil) {
NSLog(#"Error setting mode for AVAudioSession: %#", modeError);
}
NSError *categoryError = nil;
[self.audioSession setCategory:AVAudioSessionCategoryPlayback error:&categoryError];
if (categoryError != nil) {
NSLog(#"Error setting category for AVAudioSession: %#", categoryError);
}
Then in my mute button callback method I have the following code:
NSError *activeError = nil;
[self.audioSession setActive:NO error:&activeError];
if (activeError != nil) {
NSLog(#"Error setting inactive state for AVAudioSession: %#", activeError);
}
When clicking the Mute button I get the following unuseful error:
Error Domain=NSOSStatusErrorDomain Code=560030580 "The operation couldn’t be completed. (OSStatus error 560030580.)"
I am linking to the AVFoundation framework.
This is really starting to bug me as I can't for the life of me work out a way to reduce or mute the playback audio of my application.
I don't want to change the system global volume just the application level volume as defined by the AVAudioSession AVAudioSessionCategoryPlayback category.
It seems that you can set the volume of the AVAudioPlayer but not the MPMoviePlayerController. I've seen other posts here on SO that say just create an instance of AVAudioPlayer and set the volume but this just causes my app to crash and I expect it has something to do with the fact I'm not using the initWithContentsOfURL:error: or initWithData:error: and instead using `init'.
Any help would be appreciated.
After speaking to an Apple technician it turns out that it's not possible to control or mute the audio using MPMoviePlayerController.
Instead you have to create your own controller using AVFoundations AVPlayer class.
Once you're using that it's a matter of creating a custom audio mix and setting the volume level. It actually works very well.
Sample code:
AVURLAsset * asset = [AVURLAsset URLAssetWithURL:[self localMovieURL] options:nil];
NSArray *audioTracks = [asset tracksWithMediaType:AVMediaTypeAudio];
// Mute all the audio tracks
NSMutableArray * allAudioParams = [NSMutableArray array];
for (AVAssetTrack *track in audioTracks) {
AVMutableAudioMixInputParameters *audioInputParams =[AVMutableAudioMixInputParameters audioMixInputParameters];
[audioInputParams setVolume:0.0 atTime:kCMTimeZero ];
[audioInputParams setTrackID:[track trackID]];
[allAudioParams addObject:audioInputParams];
}
AVMutableAudioMix * audioZeroMix = [AVMutableAudioMix audioMix];
[audioZeroMix setInputParameters:allAudioParams];
// Create a player item
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithAsset:asset];
[playerItem setAudioMix:audioZeroMix]; // Mute the player item
// Create a new Player, and set the player to use the player item
// with the muted audio mix
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
self.mPlayer = player;
[mPlayer play];
I've written an MPMoviePlayerController replacement class that adds support for volume level. I will upload the to github shortly and add the link in this post.
I know this is an old post, but I managed to find a way to successfully control the volume of the MPMoviePlayerController control in iOS6 & iOS7, using an MPVolumeView. One gotcha is that it does NOT work in the simulator, only on the physical device. For just controlling the volume, adding a hidden MPVolumeView will work fine. However if you use a hidden one, the native OS volume display that appears when you change the volume using the physical device volume buttons will still appear centre screen. If you want to prevent this, make sure your MPVolumeView is not hidden. Instead, you can give it a very low alpha transparency and place it behind other views, so the user can't see it.
Here's the code i've used:
MPVolumeView *volumeView = [[MPVolumeView alloc]initWithFrame:CGRectZero];
[volumeView setShowsVolumeSlider:YES];
[volumeView setShowsRouteButton:NO];
// control must be VISIBLE if you want to prevent default OS volume display
// from appearing when you change the volume level
[volumeView setHidden:NO];
volumeView.alpha = 0.1f;
volumeView.userInteractionEnabled = NO;
// to hide from view just insert behind all other views
[self.view insertSubview:volumeView atIndex:0];
This allows you to control the volume by calling:
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0];
But I was still getting the native OS volume display appearing the first time I would try to change the volume - on subsequent loads it did not show this display, so figuring it was something to do with the stage in the viewcontroller life cycle, I moved it from my viewDidLoad method to the viewDidAppear method - it worked - the volume muted and the native volume display did not appear, but I now was able to hear a split second of audio before the video started playing. So I hooked into the playback state did change delegate of the MPMoviePlayerController. In viewDidload I added:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(videoPlaybackStateDidChange:)
name:MPMoviePlayerPlaybackStateDidChangeNotification object:nil];
And the delegate callback method:
-(void)videoPlaybackStateDidChange:(NSNotification *)aNotification
{
// Note, this doesn't work in simulator (even in iOS7), only on actual device!
if ([moviePlayerController playbackState] == MPMoviePlaybackStatePlaying)
{
[[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0];
}
}
This muted the audio of the video before it started playing, but after the viewDidLoad in the life cycle, so no native OS volume muted display.
In my app, I retrieved and stored the current volume level before muting (using [MPMusicPlayerController applicationMusicPlayer].volume property), and then restored the volume to this level when the view controller was closed, meaning the user would be unaware that their device volume level was modified and reverted.
Also, if your MPMoviePlayerController is using a non-standard audio route in iOS7, calling [[MPMusicPlayerController applicationMusicPlayer] setVolume:0.0] may not work for you - in this case you can loop through the subviews of your MPVolumeView control until you find a view which subclasses UISlider. You can then call [sliderView setValue:0 animated:NO] which should work for you. This method isn't using any private Apple APIs so shouldn't get your app rejected - after all there are so many legitimate reasons why you would offer this functionality, and it was possible in iOS6 without having to go to these lengths! In fact, I was bamboozled to discover that Apple had removed the functionality to set the volume on MPMoviePlayerController in iOS7 in the first place.. enforced migration to AVPlayer?
Update: My iPad app has now been approved using this method and is live on the app store.