I play audio using AVPlayer in the background and foreground from AppDelegate.After adding observer to check the play, a crash occurs when app goes to foreground from background while audio is playing
Crash : "an instance of class AVPlayerItem was deallocated while key value observers were still registered with it"
Many threads explains to remove observer so that we could avoid the crash.But thats in the case of moving to different viewControllers.I want to keep the audio playing in foreground and background.
Can someone help me to solve this?
This is my code:
case AudioLocation:{
audioUrl = [NSURL URLWithString:[responseDictionary
objectForKey:#"result"]];
if(!([[[NSUserDefaults
standardUserDefaults]objectForKey:#"audioURL"] isEqual:
[responseDictionary objectForKey:#"result"]]))
{
playerItem = [AVPlayerItem playerItemWithURL:audioUrl];
player = [AVPlayer playerWithPlayerItem:playerItem];
player = [AVPlayer playerWithURL:audioUrl];
player.automaticallyWaitsToMinimizeStalling = false;
[player.currentItem addObserver:self forKeyPath:#"status"
options:0 context:nil];
[player addObserver:self forKeyPath:#"rate" options:0
context:nil];
[player play];
isPlaying = YES;
}
-(void)observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
if ([keyPath isEqualToString:#"status"]) {
if (player.status == AVPlayerStatusFailed) {
}
}else if ([keyPath isEqualToString:#"rate"]) {
if (player.rate == 0 && //if player rate dropped to 0
CMTIME_COMPARE_INLINE(player.currentItem.currentTime, >,
kCMTimeZero) &&
CMTIME_COMPARE_INLINE(player.currentItem.currentTime, <,
player.currentItem.duration) &&isPlaying)
[self handleStalled];
}
}
-(void)handleStalled {
NSLog(#"Handle stalled. Available: %lf", [self availableDuration]);
if (player.currentItem.playbackLikelyToKeepUp || //
[self availableDuration] -
CMTimeGetSeconds(player.currentItem.currentTime) > 10.0) {
[player play];
} else {
[self performSelector:#selector(handleStalled) withObject:nil
afterDelay:0.5]; //try again
}
}
- (NSTimeInterval) availableDuration
{
NSArray *loadedTimeRanges = [[player currentItem] loadedTimeRanges];
CMTimeRange timeRange = [[loadedTimeRanges objectAtIndex:0]
CMTimeRangeValue];
Float64 startSeconds = CMTimeGetSeconds(timeRange.start);
Float64 durationSeconds = CMTimeGetSeconds(timeRange.duration);
NSTimeInterval result = startSeconds + durationSeconds;
return result;
}
I'm trying to play a .mp3 from a url on an AVPlayer.
url = [NSURL URLWithString:#"https://dl.dropboxusercontent.com/s/jcq74691pet09d9/Chumbawamba%20-%20Tubthumping%20HD.mp3?dl=0"];
AVPlayerItem *item = [[AVPlayerItem alloc]initWithURL:url];
AVPlayer *player = [[AVPlayer alloc]initWithPlayerItem:item];
[player play];
I've already imported the AVFoundation.h and AVFoundation.m and the url link also works as a direct/hot link.
Can't get it to work and I don't know why.
// *** create AVAsset using URL ***
AVAsset *asset = [AVAsset assetWithURL:[NSURL URLWithString:#"https://dl.dropboxusercontent.com/s/jcq74691pet09d9/Chumbawamba%20-%20Tubthumping%20HD.mp3?dl=0"]];
// *** Create AVPlayerItem using AVAsset ***
AVPlayerItem *playerItem = [[AVPlayerItem alloc] initWithAsset:asset];
// *** Initialise AVPlayer ***
avPlayer = [AVPlayer playerWithPlayerItem:playerItem];
// *** Add AVPlayer to ViewController ***
AVPlayerLayer *avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:avPlayer];
avPlayerLayer.frame = CGRectMake(0, 0, 320, 480);
[avPlayerLayer setBackgroundColor:[UIColor grayColor].CGColor];
[self.view.layer addSublayer:avPlayerLayer];
// *** Start Playback ***
[avPlayer play];
// *** Register for playback end notification ***
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(playerItemDidReachEnd:)
name:AVPlayerItemDidPlayToEndTimeNotification
object:[avPlayer currentItem]];
// *** Register observer for events of AVPlayer status ***
[avPlayer addObserver:self forKeyPath:#"status" options:0 context:nil];
Add following Observer method to observe playback status of AVPlayer
// *** Observe status event and perform actions ***
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (object == avPlayer && [keyPath isEqualToString:#"status"]) {
if (avPlayer.status == AVPlayerStatusFailed) {
NSLog(#"AVPlayer Failed");
} else if (avPlayer.status == AVPlayerStatusReadyToPlay) {
NSLog(#"AVPlayerStatusReadyToPlay");
[avPlayer play];
} else if (avPlayer.status == AVPlayerItemStatusUnknown) {
NSLog(#"AVPlayer Unknown");
}
}
}
Try like this:
NSURL *myurl = [NSURL URLWithString:#"https://dl.dropboxusercontent.com/s/jcq74691pet09d9/Chumbawamba%20-%20Tubthumping%20HD.mp3?dl=0"];
NSData *data = [NSData dataWithContentsOfURL:myurl];
audioPlayer = [[AVAudioPlayer alloc] initWithData:data error:NULL];
audioPlayer.delegate = self;
[audioPlayer play];
you can write Delegate method also:
-(void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
[audioPlayer stop];
NSLog(#"Finished Playing");
}
- (void)audioPlayerDecodeErrorDidOccur:(AVAudioPlayer *)player error:(NSError *)error
{
NSLog(#"Something wrong");
}
I have a music player app in which I am checking for internet connectivity when the app is running. When the connectivity is gone, I am showing an alert message and stopping the song. In case if the connectivity is back, I am again playing the song without user doing anything in it, but the problem is I am unable to hide the alert when the song is played back again. Here is the code:
#import "FirstViewController.h"
CM_EXPORT const CMTime kCMTimeZero;
#interface FirstViewController ()
#end
#implementation FirstViewController
#synthesize metadatas;
#synthesize toggleButton;
#synthesize slider;
#synthesize mpVolumeView = _mpVolumeView;
#synthesize viewVolume;
- (void)viewDidLoad
{
toggleIsOn=TRUE;
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:self.viewVolume.bounds] ;
[self.viewVolume addSubview:volumeView];
[volumeView sizeToFit];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
-(IBAction)playButtonPressed:(id)sender
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:TRUE forKey:#"FirstPlay"];
[defaults setBool:YES forKey:#"alertShown"];
if(toggleIsOn){
toggleIsOn=!toggleIsOn;
player = nil;
NSString *stringurl = #"";
stringurl = #"http://majestic.wavestreamer.com:6221/listen.pls";
NSURL *url = [NSURL URLWithString:stringurl];
asset = [AVURLAsset URLAssetWithURL:url options:nil];
playerItem = [AVPlayerItem playerItemWithAsset:asset];
player = [AVPlayer playerWithPlayerItem:playerItem];
player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
[playerItem addObserver:self forKeyPath:#"timedMetadata" options:NSKeyValueObservingOptionNew context:nil];
[playerItem addObserver:self forKeyPath:#"status" options:NSKeyValueObservingOptionNew context:nil];
[player play];
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self selector:#selector(audioSessionInterrupted:) name:AVAudioSessionInterruptionNotification object:nil];
[self.toggleButton setImage:[UIImage imageNamed:#"reload.png"] forState:UIControlStateNormal];
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
[[AVAudioSession sharedInstance] setActive: YES error: nil];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
}
else {
[self.toggleButton setImage:[UIImage imageNamed:#"playMusic.png"] forState:UIControlStateNormal];
self->player.rate=0.0;
toggleIsOn=!toggleIsOn;
}
}
- (void)audioSessionInterrupted:(NSNotification *)notification
{
NSNumber *interruptionType = [[notification userInfo] objectForKey:AVAudioSessionInterruptionTypeKey];
NSNumber *interruptionOption = [[notification userInfo] objectForKey:AVAudioSessionInterruptionOptionKey];
switch (interruptionType.unsignedIntegerValue) {
case AVAudioSessionInterruptionTypeBegan:{
// [self.toggleButton setImage:[UIImage imageNamed:#"playMusic.png"] forState:UIControlStateNormal];
// • Audio has stopped, already inactive
// • Change state of UI, etc., to reflect non-playing state
} break;
case AVAudioSessionInterruptionTypeEnded:{
// • Make session active
// • Update user interface
// • AVAudioSessionInterruptionOptionShouldResume option
if (interruptionOption.unsignedIntegerValue == AVAudioSessionInterruptionOptionShouldResume) {
// Here you should continue playback.
[player play];
}
} break;
default:
break;
}
}
- (void)audioPlayerBeginInterruption:(AVAudioPlayer *)audioPlayer
{
[player pause];
}
-(void)audioRecorderEndInterruption:(AVAudioRecorder *)audioPlayer
{
[player play];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:TRUE forKey:#"alertShown"];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(checkNetworkStatus:) name:kReachabilityChangedNotification object:nil];
internetReachable = [Reachability reachabilityForInternetConnection];
[internetReachable startNotifier];
// check if a pathway to a random host exists
hostReachable = [Reachability reachabilityWithHostName:#"www.apple.com"];
[hostReachable startNotifier];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[UIApplication sharedApplication] beginReceivingRemoteControlEvents];
[self becomeFirstResponder];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[UIApplication sharedApplication] endReceivingRemoteControlEvents];
[self resignFirstResponder];
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context {
[playerItem removeObserver:self forKeyPath:keyPath];
if ([keyPath isEqualToString:#"status"]) {
AVPlayerItem *pItem = (AVPlayerItem *)object;
if (pItem.status == AVPlayerItemStatusReadyToPlay)
{
metadatas.text = #"";
}
}
if ([keyPath isEqualToString:#"timedMetadata"]) {
for (AVAssetTrack *track in playerItem.tracks) {
for (AVPlayerItemTrack *item in player.currentItem.tracks) {
if ([item.assetTrack.mediaType isEqual:AVMediaTypeAudio]) {
NSArray *meta = [playerItem timedMetadata];
for (AVMetadataItem *metaItem in meta) {
NSString *source = metaItem.stringValue;
metadatas.text = source;
}
}
}
}
}
[self.toggleButton setImage:[UIImage imageNamed:toggleIsOn ? #"playMusic.png" :#"stop.png"] forState:UIControlStateNormal];
}
-(IBAction)fbButtonPressed:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://www.facebook.com"];
if (![[UIApplication sharedApplication] openURL:url])
NSLog(#"%#%#",#"Failed to open url:",[url description]);
}
-(IBAction)inButtonPressed:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://www.linkedin.com"];
if (![[UIApplication sharedApplication] openURL:url])
NSLog(#"%#%#",#"Failed to open url:",[url description]);
}
-(IBAction)tweetButtonPressed:(id)sender
{
NSURL *url = [NSURL URLWithString:#"http://www.twitter.com"];
if (![[UIApplication sharedApplication] openURL:url])
NSLog(#"%#%#",#"Failed to open url:",[url description]);
}
-(IBAction) sliderChanged:(id)sender
{
}
-(void) checkNetworkStatus:(NSNotification *)notice
{
// called after network status changes
NetworkStatus internetStatus = [internetReachable currentReachabilityStatus];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
switch (internetStatus)
{
case NotReachable:
{
NSLog(#"The internet is down.");
NSLog(#"%d",[defaults boolForKey:#"alertShown"]);
BOOL isAlertShown = [defaults boolForKey:#"alertShown"];
if(isAlertShown)
{
[self showAlert];
}
break;
}
case ReachableViaWiFi:
{
NSLog(#"The internet is working via WIFI.");
BOOL isFirstTimePlayed = [defaults boolForKey:#"FirstPlay"];
if(isFirstTimePlayed)
{
[self playButtonPressed:nil];
}
break;
}
case ReachableViaWWAN:
{
NSLog(#"The internet is working via WWAN.");
[self playButtonPressed:nil];
break;
}
}
}
-(void)showAlert
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:FALSE forKey:#"alertShown"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle: #"Alert" message: #"You have lost data connectivity. Please wait while we try to establish the connection again." delegate: nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
- (NSUInteger)supportedInterfaceOrientations{
return UIInterfaceOrientationMaskPortrait;
}
#end
In your method showAlert, you have a reference to the UIAlertView object, alert. Simply save that reference in a property so that you can access it later.
#interface FirstViewController ()
#property (nonatomic, strong) UIAlertView* internetConnectivityAlertView;
#end
-(void)showAlert
{
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setBool:FALSE forKey:#"alertShown"];
self.internetConnectivityAlertView = [[UIAlertView alloc] initWithTitle:#"Alert"
message:#"You have lost data connectivity. Please wait while we try to establish the connection again."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[self.internetConnectivityAlertView show];
}
When you do get internet connectivity, simply call
if(self.internetConnectivityAlertView)
{
[self.internetConnectivityAlertView dismissWithClickedButtonIndex:0 animated:YES];
self.internetConnectivityAlertView = nil;
}
to dismiss the alert.
See the API Documentation for UIAlertView.
I wanted to download image from internet from particular link
Downloading this image take place in other class which is Singleton class name moreAppInternet.m
My moreAppInterNet.m file is as follow
//finishedImgDling is bool
//mutableData is NSMutableData
//urlConnection is NSURLConnection
//appimage is UIImage
//imageUrl is NSString Containing Link
+(id)sharedManager
{
static moreAppInterNet *sharedMyManager = nil;
#synchronized(self) {
if (sharedMyManager == nil)
sharedMyManager = [[self alloc] init];
}
return sharedMyManager;
}
-(void)downloadImageFromUrl
{
finishedImgDling = NO;
[self.mutableData setLength:0];
self.urlConnection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.imageUrl]] delegate:self];
while(!finishedImgDling) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
if (connection == self.urlConnection)
{
NSLog(#"imageDling");
}
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
if (connection == self.urlConnection)
{
if (self.mutableData == nil)
{
self.mutableData = [NSMutableData data];
}
[self.mutableData appendData:data];
}
}
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
if (connection == self.urlConnection)
{
NSLog(#"image Downloaded");
finishedImgDling = YES;
[self setUrlConnection:nil];
[self storeImage];
}
}
-(void)storeImage
{
[self setAppimage:nil];
self.appimage = [UIImage imageWithData:self.mutableData];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *path =[documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"appImage.png"]];
[fileManager createFileAtPath:path contents:UIImagePNGRepresentation(self.appimage) attributes:nil];
NSLog(#"downloading and storing complete");
}
and my other ViewController is as follow from where I call that function
-(void)viewDidLoad
{
[moreAppInterNet sharedManager];
[self downloadImage];
}
-(void)downloadImage
{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(download_Image_from_other_class) object:nil];
NSOperationQueue *que = [[NSOperationQueue alloc] init];
[que addObserver:self forKeyPath:#"operations" options:0 context:NULL];
[que addOperation:operation];
[operation release];
self.dlingQue = que;
[que release];
}
-(void)download_Image_from_other_class
{
[[moreAppInterNet sharedManager] downloadImageFromUrl];
}
-(void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:#"operations"] && object == self.dlingQue)
{
if ([self.dlingQue operationCount] == 0)
{
self.imgView = [[moreAppInterNet sharedManager] appimage];
//it takes lots of time to display image in UIImageView
//Even though Above line get executed
}
}
else {
[super observeValueForKeyPath:keyPath ofObject:object
change:change context:context];
}
}
The Problem is that self.imgView takes Lots of time to load the image approx. 5sec even though its downloaded completely.
The library I use for networking is AFNetworking
I think your code downloads the image again when you request it.
You need to check if you have the file in downloadImageFromUrl.
Probably, you need more robust library to handle file downloading.
Do you know any way / method to take a photo in iOS and saving it to camera Roll only with a simple button pressure without showing any preview?
I already know how to show the camera view but it show the preview of the image and the user need to click the take photo button to take the photo.
In few Words: the user click the button, the picture is taken, without previews nor double checks to take / save the photo.
I already found the takePicture method of UIIMagePickerController Class http://developer.apple.com/library/ios/documentation/uikit/reference/UIImagePickerController_Class/UIImagePickerController/UIImagePickerController.html#//apple_ref/occ/instm/UIImagePickerController/takePicture
Set the showsCameraControls-Property to NO.
poc = [[UIImagePickerController alloc] init];
[poc setTitle:#"Take a photo."];
[poc setDelegate:self];
[poc setSourceType:UIImagePickerControllerSourceTypeCamera];
poc.showsCameraControls = NO;
You also have to add your own Controls as a custom view on the top of poc.view. But that is very simple and you can add your own UI-style by that way.
You receive the image-data as usually within the imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:
To take the photo, you call
[poc takePicture];
from your custom button.
Hope, that works for you.
Assuming you want a point-and-shoot method, you can create an AVSession and just call the UIImageWriteToSavedPhotosAlbum method.
Here is a link that goes into that exact process: http://www.musicalgeometry.com/?p=1297
It's also worth noting that your users need to have given the app access to their camera roll or you may experience issues saving the images.
You need to design your own custom preview according to your size, on capture button pressed and call buttonPressed method and do stuff what you want
(void)buttonPressed:(UIButton *)sender {
NSLog(#" Capture Clicked");
[self.imagePicker takePicture];
//[NSTimer scheduledTimerWithTimeInterval:3.0f target:self
selector:#selector(timerFired:) userInfo:nil repeats:NO];
}
following is code that will take photo without showing preview screen. when i tried the accepted answer, which uses UIImagePickerController, the preview screen showed, then auto disappeared. with the code below, user taps 'takePhoto' button, and the devices takes photo with zero change to UI (in my app, i add a green check mark next to take photo button). the code below is from apple https://developer.apple.com/LIBRARY/IOS/samplecode/AVCam/Introduction/Intro.html with the 'extra functions' (that do not relate to taking still photo) commented out. thank you incmiko for suggesting this code in your answer iOS take photo from camera without modalViewController.
updating code, 26 mar 2015:
to trigger snap photo:
[self snapStillImage:sender];
in .h file:
#import <AVFoundation/AVFoundation.h>
#import <AssetsLibrary/AssetsLibrary.h>
// include code below in header file, after #import and before #interface
// avfoundation copy paste code
static void * CapturingStillImageContext = &CapturingStillImageContext;
static void * RecordingContext = &RecordingContext;
static void * SessionRunningAndDeviceAuthorizedContext = &SessionRunningAndDeviceAuthorizedContext;
// avfoundation, include code below after #interface
// avf - Session management.
#property (nonatomic) dispatch_queue_t sessionQueue; // Communicate with the session and other session objects on this queue.
#property (nonatomic) AVCaptureSession *session;
#property (nonatomic) AVCaptureDeviceInput *videoDeviceInput;
#property (nonatomic) AVCaptureMovieFileOutput *movieFileOutput;
#property (nonatomic) AVCaptureStillImageOutput *stillImageOutput;
// avf - Utilities.
#property (nonatomic) UIBackgroundTaskIdentifier backgroundRecordingID;
#property (nonatomic, getter = isDeviceAuthorized) BOOL deviceAuthorized;
#property (nonatomic, readonly, getter = isSessionRunningAndDeviceAuthorized) BOOL sessionRunningAndDeviceAuthorized;
#property (nonatomic) BOOL lockInterfaceRotation;
#property (nonatomic) id runtimeErrorHandlingObserver;
in .m file:
#pragma mark - AV Foundation
- (BOOL)isSessionRunningAndDeviceAuthorized
{
return [[self session] isRunning] && [self isDeviceAuthorized];
}
+ (NSSet *)keyPathsForValuesAffectingSessionRunningAndDeviceAuthorized
{
return [NSSet setWithObjects:#"session.running", #"deviceAuthorized", nil];
}
// call following method from viewDidLoad
- (void)CreateAVCaptureSession
{
// Create the AVCaptureSession
AVCaptureSession *session = [[AVCaptureSession alloc] init];
[self setSession:session];
// Check for device authorization
[self checkDeviceAuthorizationStatus];
// In general it is not safe to mutate an AVCaptureSession or any of its inputs, outputs, or connections from multiple threads at the same time.
// Why not do all of this on the main queue?
// -[AVCaptureSession startRunning] is a blocking call which can take a long time. We dispatch session setup to the sessionQueue so that the main queue isn't blocked (which keeps the UI responsive).
dispatch_queue_t sessionQueue = dispatch_queue_create("session queue", DISPATCH_QUEUE_SERIAL);
[self setSessionQueue:sessionQueue];
dispatch_async(sessionQueue, ^{
[self setBackgroundRecordingID:UIBackgroundTaskInvalid];
NSError *error = nil;
AVCaptureDevice *videoDevice = [ViewController deviceWithMediaType:AVMediaTypeVideo preferringPosition:AVCaptureDevicePositionFront];
AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];
if (error)
{
NSLog(#"%#", error);
}
if ([session canAddInput:videoDeviceInput])
{
[session addInput:videoDeviceInput];
[self setVideoDeviceInput:videoDeviceInput];
dispatch_async(dispatch_get_main_queue(), ^{
// Why are we dispatching this to the main queue?
// Because AVCaptureVideoPreviewLayer is the backing layer for AVCamPreviewView and UIView can only be manipulated on main thread.
// Note: As an exception to the above rule, it is not necessary to serialize video orientation changes on the AVCaptureVideoPreviewLayer’s connection with other session manipulation.
});
}
/* AVCaptureDevice *audioDevice = [[AVCaptureDevice devicesWithMediaType:AVMediaTypeAudio] firstObject];
AVCaptureDeviceInput *audioDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:audioDevice error:&error];
if (error)
{
NSLog(#"%#", error);
}
if ([session canAddInput:audioDeviceInput])
{
[session addInput:audioDeviceInput];
}
*/
AVCaptureMovieFileOutput *movieFileOutput = [[AVCaptureMovieFileOutput alloc] init];
if ([session canAddOutput:movieFileOutput])
{
[session addOutput:movieFileOutput];
AVCaptureConnection *connection = [movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
if ([connection isVideoStabilizationSupported])
[connection setEnablesVideoStabilizationWhenAvailable:YES];
[self setMovieFileOutput:movieFileOutput];
}
AVCaptureStillImageOutput *stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
if ([session canAddOutput:stillImageOutput])
{
[stillImageOutput setOutputSettings:#{AVVideoCodecKey : AVVideoCodecJPEG}];
[session addOutput:stillImageOutput];
[self setStillImageOutput:stillImageOutput];
}
});
}
// call method below from viewWilAppear
- (void)AVFoundationStartSession
{
dispatch_async([self sessionQueue], ^{
[self addObserver:self forKeyPath:#"sessionRunningAndDeviceAuthorized" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:SessionRunningAndDeviceAuthorizedContext];
[self addObserver:self forKeyPath:#"stillImageOutput.capturingStillImage" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:CapturingStillImageContext];
[self addObserver:self forKeyPath:#"movieFileOutput.recording" options:(NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew) context:RecordingContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(subjectAreaDidChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
__weak ViewController *weakSelf = self;
[self setRuntimeErrorHandlingObserver:[[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification object:[self session] queue:nil usingBlock:^(NSNotification *note) {
ViewController *strongSelf = weakSelf;
dispatch_async([strongSelf sessionQueue], ^{
// Manually restarting the session since it must have been stopped due to an error.
[[strongSelf session] startRunning];
});
}]];
[[self session] startRunning];
});
}
// call method below from viewDidDisappear
- (void)AVFoundationStopSession
{
dispatch_async([self sessionQueue], ^{
[[self session] stopRunning];
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:[[self videoDeviceInput] device]];
[[NSNotificationCenter defaultCenter] removeObserver:[self runtimeErrorHandlingObserver]];
[self removeObserver:self forKeyPath:#"sessionRunningAndDeviceAuthorized" context:SessionRunningAndDeviceAuthorizedContext];
[self removeObserver:self forKeyPath:#"stillImageOutput.capturingStillImage" context:CapturingStillImageContext];
[self removeObserver:self forKeyPath:#"movieFileOutput.recording" context:RecordingContext];
});
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
- (BOOL)shouldAutorotate
{
// Disable autorotation of the interface when recording is in progress.
return ![self lockInterfaceRotation];
}
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
// [[(AVCaptureVideoPreviewLayer *)[[self previewView] layer] connection] setVideoOrientation:(AVCaptureVideoOrientation)toInterfaceOrientation];
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if (context == CapturingStillImageContext)
{
BOOL isCapturingStillImage = [change[NSKeyValueChangeNewKey] boolValue];
if (isCapturingStillImage)
{
[self runStillImageCaptureAnimation];
}
}
else if (context == RecordingContext)
{
BOOL isRecording = [change[NSKeyValueChangeNewKey] boolValue];
dispatch_async(dispatch_get_main_queue(), ^{
if (isRecording)
{
// [[self cameraButton] setEnabled:NO];
// [[self recordButton] setTitle:NSLocalizedString(#"Stop", #"Recording button stop title") forState:UIControlStateNormal];
// [[self recordButton] setEnabled:YES];
}
else
{
// [[self cameraButton] setEnabled:YES];
// [[self recordButton] setTitle:NSLocalizedString(#"Record", #"Recording button record title") forState:UIControlStateNormal];
// [[self recordButton] setEnabled:YES];
}
});
}
else if (context == SessionRunningAndDeviceAuthorizedContext)
{
BOOL isRunning = [change[NSKeyValueChangeNewKey] boolValue];
dispatch_async(dispatch_get_main_queue(), ^{
if (isRunning)
{
// [[self cameraButton] setEnabled:YES];
// [[self recordButton] setEnabled:YES];
// [[self stillButton] setEnabled:YES];
}
else
{
// [[self cameraButton] setEnabled:NO];
// [[self recordButton] setEnabled:NO];
// [[self stillButton] setEnabled:NO];
}
});
}
else
{
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark Actions
- (IBAction)toggleMovieRecording:(id)sender
{
// [[self recordButton] setEnabled:NO];
dispatch_async([self sessionQueue], ^{
if (![[self movieFileOutput] isRecording])
{
[self setLockInterfaceRotation:YES];
if ([[UIDevice currentDevice] isMultitaskingSupported])
{
// Setup background task. This is needed because the captureOutput:didFinishRecordingToOutputFileAtURL: callback is not received until AVCam returns to the foreground unless you request background execution time. This also ensures that there will be time to write the file to the assets library when AVCam is backgrounded. To conclude this background execution, -endBackgroundTask is called in -recorder:recordingDidFinishToOutputFileURL:error: after the recorded file has been saved.
[self setBackgroundRecordingID:[[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:nil]];
}
// Update the orientation on the movie file output video connection before starting recording.
// [[[self movieFileOutput] connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[(AVCaptureVideoPreviewLayer *)[[self previewView] layer] connection] videoOrientation]];
// Turning OFF flash for video recording
[ViewController setFlashMode:AVCaptureFlashModeOff forDevice:[[self videoDeviceInput] device]];
// Start recording to a temporary file.
NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[#"movie" stringByAppendingPathExtension:#"mov"]];
[[self movieFileOutput] startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self];
}
else
{
[[self movieFileOutput] stopRecording];
}
});
}
- (IBAction)changeCamera:(id)sender
{
// [[self cameraButton] setEnabled:NO];
// [[self recordButton] setEnabled:NO];
// [[self stillButton] setEnabled:NO];
dispatch_async([self sessionQueue], ^{
AVCaptureDevice *currentVideoDevice = [[self videoDeviceInput] device];
AVCaptureDevicePosition preferredPosition = AVCaptureDevicePositionUnspecified;
AVCaptureDevicePosition currentPosition = [currentVideoDevice position];
switch (currentPosition)
{
case AVCaptureDevicePositionUnspecified:
preferredPosition = AVCaptureDevicePositionBack;
break;
case AVCaptureDevicePositionBack:
preferredPosition = AVCaptureDevicePositionFront;
break;
case AVCaptureDevicePositionFront:
preferredPosition = AVCaptureDevicePositionBack;
break;
}
AVCaptureDevice *videoDevice = [ViewController deviceWithMediaType:AVMediaTypeVideo preferringPosition:preferredPosition];
AVCaptureDeviceInput *videoDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:nil];
[[self session] beginConfiguration];
[[self session] removeInput:[self videoDeviceInput]];
if ([[self session] canAddInput:videoDeviceInput])
{
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVCaptureDeviceSubjectAreaDidChangeNotification object:currentVideoDevice];
[ViewController setFlashMode:AVCaptureFlashModeAuto forDevice:videoDevice];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(subjectAreaDidChange:) name:AVCaptureDeviceSubjectAreaDidChangeNotification object:videoDevice];
[[self session] addInput:videoDeviceInput];
[self setVideoDeviceInput:videoDeviceInput];
}
else
{
[[self session] addInput:[self videoDeviceInput]];
}
[[self session] commitConfiguration];
dispatch_async(dispatch_get_main_queue(), ^{
// [[self cameraButton] setEnabled:YES];
// [[self recordButton] setEnabled:YES];
// [[self stillButton] setEnabled:YES];
});
});
}
- (IBAction)snapStillImage:(id)sender
{
dispatch_async([self sessionQueue], ^{
// Update the orientation on the still image output video connection before capturing.
// [[[self stillImageOutput] connectionWithMediaType:AVMediaTypeVideo] setVideoOrientation:[[(AVCaptureVideoPreviewLayer *)[[self previewView] layer] connection] videoOrientation]];
// Flash set to Auto for Still Capture
[ViewController setFlashMode:AVCaptureFlashModeAuto forDevice:[[self videoDeviceInput] device]];
// Capture a still image.
[[self stillImageOutput] captureStillImageAsynchronouslyFromConnection:[[self stillImageOutput] connectionWithMediaType:AVMediaTypeVideo] completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
if (imageDataSampleBuffer)
{
NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
UIImage *image = [[UIImage alloc] initWithData:imageData];
// do someting good with saved image
[self saveImageToParse:image];
}
}];
});
}
- (IBAction)focusAndExposeTap:(UIGestureRecognizer *)gestureRecognizer
{
// CGPoint devicePoint = [(AVCaptureVideoPreviewLayer *)[[self previewView] layer] captureDevicePointOfInterestForPoint:[gestureRecognizer locationInView:[gestureRecognizer view]]];
// [self focusWithMode:AVCaptureFocusModeAutoFocus exposeWithMode:AVCaptureExposureModeAutoExpose atDevicePoint:devicePoint monitorSubjectAreaChange:YES];
}
- (void)subjectAreaDidChange:(NSNotification *)notification
{
CGPoint devicePoint = CGPointMake(.5, .5);
[self focusWithMode:AVCaptureFocusModeContinuousAutoFocus exposeWithMode:AVCaptureExposureModeContinuousAutoExposure atDevicePoint:devicePoint monitorSubjectAreaChange:NO];
}
#pragma mark File Output Delegate
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error
{
if (error)
NSLog(#"%#", error);
[self setLockInterfaceRotation:NO];
// Note the backgroundRecordingID for use in the ALAssetsLibrary completion handler to end the background task associated with this recording. This allows a new recording to be started, associated with a new UIBackgroundTaskIdentifier, once the movie file output's -isRecording is back to NO — which happens sometime after this method returns.
UIBackgroundTaskIdentifier backgroundRecordingID = [self backgroundRecordingID];
[self setBackgroundRecordingID:UIBackgroundTaskInvalid];
[[[ALAssetsLibrary alloc] init] writeVideoAtPathToSavedPhotosAlbum:outputFileURL completionBlock:^(NSURL *assetURL, NSError *error) {
if (error)
NSLog(#"%#", error);
[[NSFileManager defaultManager] removeItemAtURL:outputFileURL error:nil];
if (backgroundRecordingID != UIBackgroundTaskInvalid)
[[UIApplication sharedApplication] endBackgroundTask:backgroundRecordingID];
}];
}
#pragma mark Device Configuration
- (void)focusWithMode:(AVCaptureFocusMode)focusMode exposeWithMode:(AVCaptureExposureMode)exposureMode atDevicePoint:(CGPoint)point monitorSubjectAreaChange:(BOOL)monitorSubjectAreaChange
{
dispatch_async([self sessionQueue], ^{
AVCaptureDevice *device = [[self videoDeviceInput] device];
NSError *error = nil;
if ([device lockForConfiguration:&error])
{
if ([device isFocusPointOfInterestSupported] && [device isFocusModeSupported:focusMode])
{
[device setFocusMode:focusMode];
[device setFocusPointOfInterest:point];
}
if ([device isExposurePointOfInterestSupported] && [device isExposureModeSupported:exposureMode])
{
[device setExposureMode:exposureMode];
[device setExposurePointOfInterest:point];
}
[device setSubjectAreaChangeMonitoringEnabled:monitorSubjectAreaChange];
[device unlockForConfiguration];
}
else
{
NSLog(#"%#", error);
}
});
}
+ (void)setFlashMode:(AVCaptureFlashMode)flashMode forDevice:(AVCaptureDevice *)device
{
if ([device hasFlash] && [device isFlashModeSupported:flashMode])
{
NSError *error = nil;
if ([device lockForConfiguration:&error])
{
[device setFlashMode:flashMode];
[device unlockForConfiguration];
}
else
{
NSLog(#"%#", error);
}
}
}
+ (AVCaptureDevice *)deviceWithMediaType:(NSString *)mediaType preferringPosition:(AVCaptureDevicePosition)position
{
NSArray *devices = [AVCaptureDevice devicesWithMediaType:mediaType];
AVCaptureDevice *captureDevice = [devices firstObject];
for (AVCaptureDevice *device in devices)
{
if ([device position] == position)
{
captureDevice = device;
break;
}
}
return captureDevice;
}
#pragma mark UI
- (void)runStillImageCaptureAnimation
{
/*
dispatch_async(dispatch_get_main_queue(), ^{
[[[self previewView] layer] setOpacity:0.0];
[UIView animateWithDuration:.25 animations:^{
[[[self previewView] layer] setOpacity:1.0];
}];
});
*/
}
- (void)checkDeviceAuthorizationStatus
{
NSString *mediaType = AVMediaTypeVideo;
[AVCaptureDevice requestAccessForMediaType:mediaType completionHandler:^(BOOL granted) {
if (granted)
{
//Granted access to mediaType
[self setDeviceAuthorized:YES];
}
else
{
//Not granted access to mediaType
dispatch_async(dispatch_get_main_queue(), ^{
[[[UIAlertView alloc] initWithTitle:#"AVCam!"
message:#"AVCam doesn't have permission to use Camera, please change privacy settings"
delegate:self
cancelButtonTitle:#"OK"
otherButtonTitles:nil] show];
[self setDeviceAuthorized:NO];
});
}
}];
}