I'm calling a function to attempt to turn on my device's flash:
private func flashOn(device:AVCaptureDevice)
{
print("flashOn called");
do {
try device.lockForConfiguration()
// line below returns warning 'flashMode' was deprecated in iOS 10.0: Use AVCapturePhotoSettings.flashMode instead.
device.flashMode = AVCaptureDevice.FlashMode.auto
device.unlockForConfiguration()
} catch {
// handle error
print("flash on error");
}
}
Setting device.flashMode to AVCaptureDevice.FlashMode.auto brings up the warning "'flashMode' was deprecated in iOS 10.0: Use AVCapturePhotoSettings.flashMode instead.". Even though it is just a warning, it does not enable the flash when testing my app, so I change that line to:
device.flashMode = AVCaptureDevice.FlashMode.auto
So I set the line to this, like it suggests:
AVCapturePhotoSettings.flashMode = AVCaptureDevice.FlashMode.auto
And I get the error "Instance member 'flashMode' cannot be used on type 'AVCapturePhotoSettings'"
So I have no idea how to set the flash in Xcode version 9 using Swift 4.0. All the answers I've found in Stack Overflow have been for previous versions.
I've been facing the same problem. Unfortunately, many useful methods got deprecated in iOS10 and 11. Here is how I managed to resolve it:
AVCapturePhotoSettings object is unique and cannot be reused, so you need to get new settings every time using this method:
/// the current flash mode
private var flashMode: AVCaptureDevice.FlashMode = .auto
/// Get settings
///
/// - Parameters:
/// - camera: the camera
/// - flashMode: the current flash mode
/// - Returns: AVCapturePhotoSettings
private func getSettings(camera: AVCaptureDevice, flashMode: AVCaptureDevice.FlashMode) -> AVCapturePhotoSettings {
let settings = AVCapturePhotoSettings()
if camera.hasFlash {
settings.flashMode = flashMode
}
return settings
}
As you can see, lockConfiguration is not needed.
Then simply use it while capturing photo:
#IBAction func captureButtonPressed(_ sender: UIButton) {
let settings = getSettings(camera: camera, flashMode: flashMode)
photoOutput.capturePhoto(with: settings, delegate: self)
}
Hope it will help.
For objective C:
- (IBAction)turnTorchOn: (UIButton *) sender {
sender.selected = !sender.selected;
BOOL on;
if (sender.selected) {
on = true;
} else {
on = false;
}
// check if flashlight available
Class captureDeviceClass = NSClassFromString(#"AVCaptureDevice");
if (captureDeviceClass != nil) {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCapturePhotoSettings *photosettings = [AVCapturePhotoSettings photoSettings];
if ([device hasTorch] && [device hasFlash]) {
[device lockForConfiguration:nil];
if (on) {
[device setTorchMode:AVCaptureTorchModeOn];
photosettings.flashMode = AVCaptureFlashModeOn;
//torchIsOn = YES; //define as a variable/property if you need to know status
} else {
[device setTorchMode:AVCaptureTorchModeOff];
photosettings.flashMode = AVCaptureFlashModeOn;
//torchIsOn = NO;
}
[device unlockForConfiguration];
}
}
}
Related
I have created an iOS app where I can take videos and photos and save them to my gallery on my iPhone. My problem is that I want to keep recording while taking a video before saving. It is an option recently added to WhatsApp or other apps, but I can't figure out how to do it.
-(BOOL)toggleCamera
{
BOOL success=NO;
if (self.cameraCount>1)
{
NSError* error;
AVCaptureDeviceInput* newVedioInput;
AVCaptureDevicePosition position=[self.videoInput.device position];
if (position==AVCaptureDevicePositionBack)
{
newVedioInput=[[AVCaptureDeviceInput alloc] initWithDevice:self.frontFacingCamera error:&error];
}
else if (position==AVCaptureDevicePositionFront)
{
newVedioInput=[[AVCaptureDeviceInput alloc] initWithDevice:self.backFacingCamera error:&error];
}
else
{
return NO;
}
if (newVedioInput!=nil)
{
[self.session beginConfiguration];
[self.session removeInput:self.videoInput];
if ([self.session canAddInput:newVedioInput])
{
[self.session addInput:newVedioInput];
self.videoInput=newVedioInput;
}
else
{
[self.session addInput:self.videoInput];
}
[self.session commitConfiguration];
success=YES;
}
else if (error)
{
if ([self.delegate respondsToSelector:#selector(recordManager:didFailWithError:)])
{
[self.delegate recordManager:self didFailWithError:error];
}
}
}
return success;
}
When I do this, the session changes and the video is saved before flipping the camera and that is not what I need. Can anyone help?
You don't need to change the output file.
You need to reconfigure the AVCaptureDeviceInput input of the AVCaptureSession by enclosing the configuration code in beginConfiguration() and commitConfiguration() method.
Dispatch the below work to session's private queue using it's sync
method. eg: sessionQueue.sync{}
self.session.beginConfiguration()
if let videoInput = self.videoInput {
self.session.removeInput(videoInput)
}
addVideoInput() //adding `AVCaptureDeviceInput` again
configureSessionQuality() //Configuring session preset here.
// Fix initial frame having incorrect orientation
let connection = self.videoOutput?.connection(with: .video)
if connection?.isVideoOrientationSupported == true {
connection?.videoOrientation = self.orientation
}
//Fix preview mirroring
if self.cameraLocation == .front {
if connection?.isVideoMirroringSupported == true {
connection?.isVideoMirrored = true
}
}
self.session.commitConfiguration()
//Change zoom scale if needed.
do {
try self.captureDevice?.lockForConfiguration()
self.captureDevice?.videoZoomFactor = zoomScale
self.captureDevice?.unlockForConfiguration()
} catch {
print(error)
}
I am using the above approach in my production code. It is working
perfectly fine
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 5 years ago.
Improve this question
As far as I know , apple native framework doesn't have APIs for converting voice to text and we have to go for third party framework to do that and it has so many drawbacks like user has to microphone to convert from voice to text.
But I can find lots of information for converting text to voice but not the other way
Couldn't find any clear information about this and mostly it has so many uncertain things.
If someone could shed some light it'd be really great !
For Objective C, I wrote a Speech Converter class a while back to convert voice to text.
Step 1: Create A Speech Convertor class
Create a new Cocoa Class and subclass it from NSObject.
Name it let's say ATSpeechRecognizer.
In ATSpeechRecognizer.h:
#import <Foundation/Foundation.h>
#import <Speech/Speech.h>
#import <AVFoundation/AVFoundation.h>
typedef NS_ENUM(NSInteger, ATSpeechRecognizerState) {
ATSpeechRecognizerStateRunning,
ATSpeechRecognizerStateStopped
};
#protocol ATSpeechDelegate<NSObject>
#required
/*This method relays parsed text from Speech to the delegate responder class*/
-(void)convertedSpeechToText:(NSString *) parsedText;
/*This method relays change in Speech recognition ability to delegate responder class*/
-(void) speechRecAvailabilityChanged:(BOOL) status;
/*This method relays error messages to delegate responder class*/
-(void) sendErrorInfoToViewController:(NSString *) errorMessage;
#optional
/*This method relays info regarding whether speech rec is running or stopped to delegate responder class. State with be either ATSpeechRecognizerStateRunning or ATSpeechRecognizerStateStopped. You may or may not implement this method*/
-(void) changeStateIndicator:(ATSpeechRecognizerState) state;
#end
#interface ATSpeechRecognizer : NSObject <SFSpeechRecognizerDelegate>
+ (ATSpeechRecognizer *)sharedObject;
/*Delegate to communicate with requesting VCs*/
#property (weak, nonatomic) id<ATSpeechDelegate> delegate;
/*Class Methods*/
-(void) toggleRecording;
-(void) activateSpeechRecognizerWithLocaleIdentifier:(NSString *) localeIdentifier andBlock:(void (^)(BOOL isAuthorized))successBlock;
#end
And in ATSpeechRecognizer.m:
#import "ATSpeechRecognizer.h"
#interface ATSpeechRecognizer ()
/*This object handles the speech recognition requests. It provides an audio input to the speech recognizer.*/
#property SFSpeechAudioBufferRecognitionRequest *speechAudioRecRequest;
/*The recognition task where it gives you the result of the recognition request. Having this object is handy as you can cancel or stop the task. */
#property SFSpeechRecognitionTask *speechRecogTask;
/*This is your Speech recognizer*/
#property SFSpeechRecognizer *speechRecognizer;
/*This is your audio engine. It is responsible for providing your audio input.*/
#property AVAudioEngine *audioEngine;
#end
#implementation ATSpeechRecognizer
#pragma mark - Constants
//Error Messages
#define kErrorMessageAuthorize #"You declined the permission to perform speech Permission. Please authorize the operation in your device settings."
#define kErrorMessageRestricted #"Speech recognition isn't available on this OS version. Please upgrade to iOS 10 or later."
#define kErrorMessageNotDetermined #"Speech recognition isn't authorized yet"
#define kErrorMessageAudioInputNotFound #"This device has no audio input node"
#define kErrorMessageRequestFailed #"Unable to create an SFSpeechAudioBufferRecognitionRequest object"
#define kErrorMessageAudioRecordingFailed #"Unable to start Audio recording due to failure in Recording Engine"
#pragma mark - Singleton methods
+ (ATSpeechRecognizer *)sharedObject {
static ATSpeechRecognizer *sharedClass = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedClass = [[self alloc] init];
});
return sharedClass;
}
- (id)init {
if (self = [super init]) {
}
return self;
}
#pragma mark - Recognition methods
-(void) activateSpeechRecognizerWithLocaleIdentifier:(NSString *) localeIdentifier andBlock:(void (^)(BOOL isAuthorized))successBlock{
//enter Described language here
if([localeIdentifier length]>0){
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:localeIdentifier];
_speechRecognizer = [[SFSpeechRecognizer alloc] initWithLocale:locale];
_speechRecognizer.delegate = self;
_audioEngine = [[AVAudioEngine alloc] init];
[self getSpeechRecognizerAuthenticationStatusWithSuccessBlock:^(BOOL isAuthorized) {
successBlock(isAuthorized);
}];
}
else{
successBlock(NO);
}
}
/*Microphone usage Must be authorized in the info.plist*/
-(void) toggleRecording{
if(_audioEngine.isRunning){
[self stopAudioEngine];
}
else{
[self startAudioEngine];
}
}
#pragma mark - Internal Methods
/*
In case different buttons are used for recording and stopping, these methods should be called indiviually. Otherwise use -(void) toggleRecording.
*/
-(void) startAudioEngine{
if([self isDelegateValidForSelector:NSStringFromSelector(#selector(changeStateIndicator:))]){
[_delegate changeStateIndicator:ATSpeechRecognizerStateRunning];
}
[self startRecordingSpeech];
}
-(void) stopAudioEngine{
if([self isDelegateValidForSelector:NSStringFromSelector(#selector(changeStateIndicator:))]){
[_delegate changeStateIndicator:ATSpeechRecognizerStateStopped];
}
[_audioEngine stop];
[_speechAudioRecRequest endAudio];
self.speechRecogTask = nil;
self.speechAudioRecRequest = nil;
}
/*
All the voice data is transmitted to Apple’s backend for processing. Therefore, it is mandatory to get the user’s authorization. Speech Recognition Must be authorized in the info.plist
*/
-(void) getSpeechRecognizerAuthenticationStatusWithSuccessBlock:(void (^)(BOOL isAuthorized))successBlock{
[SFSpeechRecognizer requestAuthorization:^(SFSpeechRecognizerAuthorizationStatus status) {
switch (status) {
case SFSpeechRecognizerAuthorizationStatusAuthorized:
successBlock(YES);
break;
case SFSpeechRecognizerAuthorizationStatusDenied:
[self sendErrorMessageToDelegate:kErrorMessageAuthorize];
successBlock(NO);
case SFSpeechRecognizerAuthorizationStatusRestricted:
[self sendErrorMessageToDelegate:kErrorMessageRestricted];
successBlock(NO);
case SFSpeechRecognizerAuthorizationStatusNotDetermined:
[self sendErrorMessageToDelegate:kErrorMessageNotDetermined];
successBlock(NO);
break;
default:
break;
}
}];
}
-(void) startRecordingSpeech{
/*
Check if the Task is running. If yes, Cancel it and start anew
*/
if(_speechRecogTask!=nil){
[_speechRecogTask cancel];
_speechRecogTask = nil;
}
/*
Prepare for the audio recording. Here we set the category of the session as recording, the mode as measurement, and activate it
*/
AVAudioSession *audioSession = [AVAudioSession sharedInstance];
#try {
[audioSession setCategory:AVAudioSessionCategoryRecord error:nil];
[audioSession setMode:AVAudioSessionModeMeasurement error:nil];
[audioSession setActive:YES error:nil];
} #catch (NSException *exception) {
[self sendErrorMessageToDelegate:exception.reason];
}
/*
Instantiate the recognitionRequest. Here we create the SFSpeechAudioBufferRecognitionRequest object. Later, we use it to pass our audio data to Apple’s servers.
*/
#try {
_speechAudioRecRequest = [[SFSpeechAudioBufferRecognitionRequest alloc] init];
} #catch (NSException *exception) {
[self sendErrorMessageToDelegate:kErrorMessageRequestFailed];
}
/*
Check if the audioEngine (your device) has an audio input for recording.
*/
if(_audioEngine.inputNode!=nil){
AVAudioInputNode *inputNode = _audioEngine.inputNode;
/*If true, partial (non-final) results for each utterance will be reported.
Default is true*/
_speechAudioRecRequest.shouldReportPartialResults = YES;
/*Start the recognition by calling the recognitionTask method of our speechRecognizer. This function has a completion handler. This completion handler will be called every time the recognition engine has received input, has refined its current recognition, or has been canceled or stopped, and will return a final transcript.*/
_speechRecogTask = [_speechRecognizer recognitionTaskWithRequest:_speechAudioRecRequest resultHandler:^(SFSpeechRecognitionResult * _Nullable result, NSError * _Nullable error) {
BOOL isFinal = NO;
if(result!=nil){
if([self isDelegateValidForSelector:NSStringFromSelector(#selector(convertedSpeechToText:))]){
[_delegate convertedSpeechToText:[[result bestTranscription] formattedString]];
}
isFinal = [result isFinal]; //True if the hypotheses will not change; speech processing is complete.
}
//If Error of Completed, end it.
if(error!=nil || isFinal){
[_audioEngine stop];
[inputNode removeTapOnBus:0];
self.speechRecogTask = nil;
self.speechAudioRecRequest = nil;
if(error!=nil){
[self stopAudioEngine];
[self sendErrorMessageToDelegate:[NSString stringWithFormat:#"%li - %#",error.code, error.localizedDescription]];
}
}
}];
/* Add an audio input to the recognitionRequest. Note that it is ok to add the audio input after starting the recognitionTask. The Speech Framework will start recognizing as soon as an audio input has been added.*/
AVAudioFormat *recordingFormat = [inputNode outputFormatForBus:0];
[inputNode installTapOnBus:0 bufferSize:1024 format:recordingFormat block:^(AVAudioPCMBuffer * _Nonnull buffer, AVAudioTime * _Nonnull when) {
[self.speechAudioRecRequest appendAudioPCMBuffer:buffer];
}];
/*Prepare and start the audioEngine.*/
[_audioEngine prepare];
#try {
[_audioEngine startAndReturnError:nil];
} #catch (NSException *exception) {
[self sendErrorMessageToDelegate:kErrorMessageAudioRecordingFailed];
}
}
else{
[self sendErrorMessageToDelegate:kErrorMessageAudioInputNotFound];
}
}
-(BOOL) isDelegateValidForSelector:(NSString*)selectorName{
if(_delegate!=nil && [_delegate respondsToSelector:NSSelectorFromString(selectorName)]){
return YES;
}
return NO;
}
-(void) sendErrorMessageToDelegate:(NSString*) errorMessage{
if([self isDelegateValidForSelector:NSStringFromSelector(#selector(sendErrorInfoToViewController:))]){
[_delegate sendErrorInfoToViewController:errorMessage];
}
}
#pragma mark - Speech Recognizer Delegate Methods
-(void) speechRecognizer:(SFSpeechRecognizer *)speechRecognizer availabilityDidChange:(BOOL)available{
if(!available){
[self stopAudioEngine];
}
[_delegate speechRecAvailabilityChanged:available];
}
And that's it. Now you can use this class anywhere in any project you want to convert voice to text. Just be sure to read the guidance comments if you feel confused about how it works.
Step 2: Set up the ATSpeechRecognizer Class in your VC
Import ATSpeechRecognizer in your View Controller and set up the delegate like this:
#import "ATSpeechRecognizer.h"
#interface ViewController : UIViewController <ATSpeechDelegate>{
BOOL isRecAllowed;
}
Use the following method on VC viewDidLoad to set it up and running:
-(void) setUpSpeechRecognizerService{
[ATSpeechRecognizer sharedObject].delegate = self;
[[ATSpeechRecognizer sharedObject] activateSpeechRecognizerWithLocaleIdentifier:#"en-US" andBlock:^(BOOL isAuthorized) {
isRecAllowed = isAuthorized; /*Is operation allowed or not?*/
}];
}
Now set up delegate methods:
#pragma mark - Speech Recog Delegates
-(void) convertedSpeechToText:(NSString *)parsedText{
if(parsedText!=nil){
_txtView.text = parsedText; //You got Text from voice. Use it as you want
}
}
-(void) speechRecAvailabilityChanged:(BOOL)status{
isRecAllowed = status; //Status of Conversion ability has changed. Use Status flag to allow/stop operations
}
-(void) changeStateIndicator:(ATSpeechRecognizerState) state{
if(state==ATSpeechRecognizerStateStopped){
//Speech Recognizer is Stopped
_lblState.text = #"Stopped";
}
else{
//Speech Recognizer is running
_lblState.text = #"Running";
}
_txtView.text = #"";
}
-(void) sendErrorInfoToViewController:(NSString *)errorMessage{
[self showPopUpForErrorMessage:errorMessage]; /*Some error occured. Show it to user*/
}
To Start Conversion of Voice to Text:
- (IBAction)btnRecordTapped:(id)sender {
if(!isRecAllowed){
[self showPopUpForErrorMessage:#"Speech recognition is either not authorized or available for this device. Please authorize the operation or upgrade to latest iOS. If you have done all this, check your internet connectivity"];
}
else{
[[ATSpeechRecognizer sharedObject] toggleRecording]; /*If speech Recognizer is running, it will turn it off. if it is off, it will set it on*/
/*
If you want to do it mannually, use startAudioEngine method and stopAudioEngine method to explicitly perform those operations instead of toggleRecording
*/
}
}
And that's it. All the further explanation you need is in code comments. Ping me if you need further explanation.
Here is the full code for the same:
import UIKit
import Speech
public class ViewController: UIViewController, SFSpeechRecognizerDelegate {
// MARK: Properties
private let speechRecognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))!
private var recognitionRequest: SFSpeechAudioBufferRecognitionRequest?
private var recognitionTask: SFSpeechRecognitionTask?
private let audioEngine = AVAudioEngine()
#IBOutlet var textView : UITextView!
#IBOutlet var recordButton : UIButton!
// MARK: UIViewController
public override func viewDidLoad() {
super.viewDidLoad()
// Disable the record buttons until authorization has been granted.
recordButton.isEnabled = false
}
override public func viewDidAppear(_ animated: Bool) {
speechRecognizer.delegate = self
SFSpeechRecognizer.requestAuthorization { authStatus in
/*
The callback may not be called on the main thread. Add an
operation to the main queue to update the record button's state.
*/
OperationQueue.main.addOperation {
switch authStatus {
case .authorized:
self.recordButton.isEnabled = true
case .denied:
self.recordButton.isEnabled = false
self.recordButton.setTitle("User denied access to speech recognition", for: .disabled)
case .restricted:
self.recordButton.isEnabled = false
self.recordButton.setTitle("Speech recognition restricted on this device", for: .disabled)
case .notDetermined:
self.recordButton.isEnabled = false
self.recordButton.setTitle("Speech recognition not yet authorized", for: .disabled)
}
}
}
}
private func startRecording() throws {
// Cancel the previous task if it's running.
if let recognitionTask = recognitionTask {
recognitionTask.cancel()
self.recognitionTask = nil
}
let audioSession = AVAudioSession.sharedInstance()
try audioSession.setCategory(AVAudioSessionCategoryRecord)
try audioSession.setMode(AVAudioSessionModeMeasurement)
try audioSession.setActive(true, with: .notifyOthersOnDeactivation)
recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
guard let inputNode = audioEngine.inputNode else { fatalError("Audio engine has no input node") }
guard let recognitionRequest = recognitionRequest else { fatalError("Unable to created a SFSpeechAudioBufferRecognitionRequest object") }
// Configure request so that results are returned before audio recording is finished
recognitionRequest.shouldReportPartialResults = true
// A recognition task represents a speech recognition session.
// We keep a reference to the task so that it can be cancelled.
recognitionTask = speechRecognizer.recognitionTask(with: recognitionRequest) { result, error in
var isFinal = false
if let result = result {
self.textView.text = result.bestTranscription.formattedString
isFinal = result.isFinal
}
if error != nil || isFinal {
self.audioEngine.stop()
inputNode.removeTap(onBus: 0)
self.recognitionRequest = nil
self.recognitionTask = nil
self.recordButton.isEnabled = true
self.recordButton.setTitle("Start Recording", for: [])
}
}
let recordingFormat = inputNode.outputFormat(forBus: 0)
inputNode.installTap(onBus: 0, bufferSize: 1024, format: recordingFormat) { (buffer: AVAudioPCMBuffer, when: AVAudioTime) in
self.recognitionRequest?.append(buffer)
}
audioEngine.prepare()
try audioEngine.start()
textView.text = "(Go ahead, I'm listening)"
}
// MARK: SFSpeechRecognizerDelegate
public func speechRecognizer(_ speechRecognizer: SFSpeechRecognizer, availabilityDidChange available: Bool) {
if available {
recordButton.isEnabled = true
recordButton.setTitle("Start Recording", for: [])
} else {
recordButton.isEnabled = false
recordButton.setTitle("Recognition not available", for: .disabled)
}
}
// MARK: Interface Builder actions
#IBAction func recordButtonTapped() {
if audioEngine.isRunning {
audioEngine.stop()
recognitionRequest?.endAudio()
recordButton.isEnabled = false
recordButton.setTitle("Stopping", for: .disabled)
} else {
try! startRecording()
recordButton.setTitle("Stop recording", for: [])
}
}
}
WebRTC video by default uses Front Camera, which works fine. However, i need to switch it to back camera, and i have not been able to find any code to do that.
Which part do i need to edit?
Is it the localView or localVideoTrack or capturer?
Swift 3.0
Peer connection can have only one 'RTCVideoTrack' for sending video stream.
At first, for change camera front/back you must remove current video track on peer connection.
After then, you create new 'RTCVideoTrack' on camera which you need, and set this for peer connection.
I used this methods.
func swapCameraToFront() {
let localStream: RTCMediaStream? = peerConnection?.localStreams.first as? RTCMediaStream
localStream?.removeVideoTrack(localStream?.videoTracks.first as! RTCVideoTrack)
let localVideoTrack: RTCVideoTrack? = createLocalVideoTrack()
if localVideoTrack != nil {
localStream?.addVideoTrack(localVideoTrack)
delegate?.appClient(self, didReceiveLocalVideoTrack: localVideoTrack!)
}
peerConnection?.remove(localStream)
peerConnection?.add(localStream)
}
func swapCameraToBack() {
let localStream: RTCMediaStream? = peerConnection?.localStreams.first as? RTCMediaStream
localStream?.removeVideoTrack(localStream?.videoTracks.first as! RTCVideoTrack)
let localVideoTrack: RTCVideoTrack? = createLocalVideoTrackBackCamera()
if localVideoTrack != nil {
localStream?.addVideoTrack(localVideoTrack)
delegate?.appClient(self, didReceiveLocalVideoTrack: localVideoTrack!)
}
peerConnection?.remove(localStream)
peerConnection?.add(localStream)
}
As of now I only have the answer in Objective C language in regard to Ankit's comment below. I will convert it into Swift after some time.
You can check the below code
- (RTCVideoTrack *)createLocalVideoTrack {
RTCVideoTrack *localVideoTrack = nil;
NSString *cameraID = nil;
for (AVCaptureDevice *captureDevice in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
if (captureDevice.position == AVCaptureDevicePositionFront) {
cameraID = [captureDevice localizedName]; break;
}
}
RTCVideoCapturer *capturer = [RTCVideoCapturer capturerWithDeviceName:cameraID];
RTCMediaConstraints *mediaConstraints = [self defaultMediaStreamConstraints];
RTCVideoSource *videoSource = [_factory videoSourceWithCapturer:capturer constraints:mediaConstraints];
localVideoTrack = [_factory videoTrackWithID:#"ARDAMSv0" source:videoSource];
return localVideoTrack;
}
- (RTCVideoTrack *)createLocalVideoTrackBackCamera {
RTCVideoTrack *localVideoTrack = nil;
//AVCaptureDevicePositionFront
NSString *cameraID = nil;
for (AVCaptureDevice *captureDevice in [AVCaptureDevice devicesWithMediaType:AVMediaTypeVideo]) {
if (captureDevice.position == AVCaptureDevicePositionBack) {
cameraID = [captureDevice localizedName];
break;
}
}
RTCVideoCapturer *capturer = [RTCVideoCapturer capturerWithDeviceName:cameraID];
RTCMediaConstraints *mediaConstraints = [self defaultMediaStreamConstraints];
RTCVideoSource *videoSource = [_factory videoSourceWithCapturer:capturer constraints:mediaConstraints];
localVideoTrack = [_factory videoTrackWithID:#"ARDAMSv0" source:videoSource];
return localVideoTrack;
}
If you decide to use official Google build here the explanation:
First, you must configure your camera before call start, best place to do that in ARDVideoCallViewDelegate in method didCreateLocalCapturer
- (void)startCapture:(void (^)(BOOL succeeded))completionHandler {
AVCaptureDevicePosition position = _usingFrontCamera ? AVCaptureDevicePositionFront : AVCaptureDevicePositionBack;
__weak AVCaptureDevice *device = [self findDeviceForPosition:position];
if ([device lockForConfiguration:nil]) {
if ([device isFocusPointOfInterestSupported]) {
[device setFocusModeLockedWithLensPosition:0.9 completionHandler: nil];
}
}
AVCaptureDeviceFormat *format = [self selectFormatForDevice:device];
if (format == nil) {
RTCLogError(#"No valid formats for device %#", device);
NSAssert(NO, #"");
return;
}
NSInteger fps = [self selectFpsForFormat:format];
[_capturer startCaptureWithDevice: device
format: format
fps:fps completionHandler:^(NSError * error) {
NSLog(#"%#",error);
if (error == nil) {
completionHandler(true);
}
}];
}
Don't forget enabling capture device is asynchronous, sometime better to use completion to be sure everything done as expected.
I am not sure which chrome version you are using for webrtc but with v54 and above there is "bool" property called "useBackCamera" in RTCAVFoundationVideoSource class. You can make use of this property to switch between front/back camera.
Swift 4.0 & 'GoogleWebRTC' : '1.1.20913'
RTCAVFoundationVideoSource class has a property named useBackCamera that can be used for switching the camera used.
#interface RTCAVFoundationVideoSource : RTCVideoSource
- (instancetype)init NS_UNAVAILABLE;
/**
* Calling this function will cause frames to be scaled down to the
* requested resolution. Also, frames will be cropped to match the
* requested aspect ratio, and frames will be dropped to match the
* requested fps. The requested aspect ratio is orientation agnostic and
* will be adjusted to maintain the input orientation, so it doesn't
* matter if e.g. 1280x720 or 720x1280 is requested.
*/
- (void)adaptOutputFormatToWidth:(int)width height:(int)height fps:(int)fps;
/** Returns whether rear-facing camera is available for use. */
#property(nonatomic, readonly) BOOL canUseBackCamera;
/** Switches the camera being used (either front or back). */
#property(nonatomic, assign) BOOL useBackCamera;
/** Returns the active capture session. */
#property(nonatomic, readonly) AVCaptureSession *captureSession;
Below is the implementation for switching camera.
var useBackCamera: Bool = false
func switchCamera() {
useBackCamera = !useBackCamera
self.switchCamera(useBackCamera: useBackCamera)
}
private func switchCamera(useBackCamera: Bool) -> Void {
let localStream = peerConnection?.localStreams.first
if let videoTrack = localStream?.videoTracks.first {
localStream?.removeVideoTrack(videoTrack)
}
let localVideoTrack = createLocalVideoTrack(useBackCamera: useBackCamera)
localStream?.addVideoTrack(localVideoTrack)
self.delegate?.webRTCClientDidAddLocal(videoTrack: localVideoTrack)
if let ls = localStream {
peerConnection?.remove(ls)
peerConnection?.add(ls)
}
}
func createLocalVideoTrack(useBackCamera: Bool) -> RTCVideoTrack {
let videoSource = self.factory.avFoundationVideoSource(with: self.constraints)
videoSource.useBackCamera = useBackCamera
let videoTrack = self.factory.videoTrack(with: videoSource, trackId: "video")
return videoTrack
}
In the current version of WebRTC, RTCAVFoundationVideoSource has been deprecated and replaced with a
generic RTCVideoSource combined with an RTCVideoCapturer implementation.
In order to switch the camera I'm doing this:
- (void)switchCameraToPosition:(AVCaptureDevicePosition)position completionHandler:(void (^)(void))completionHandler {
if (self.cameraPosition != position) {
RTCMediaStream *localStream = self.peerConnection.localStreams.firstObject;
[localStream removeVideoTrack:self.localVideoTrack];
//[self.peerConnection removeStream:localStream];
self.localVideoTrack = [self createVideoTrack];
[self startCaptureLocalVideoWithPosition:position completionHandler:^{
[localStream addVideoTrack:self.localVideoTrack];
//[self.peerConnection addStream:localStream];
if (completionHandler) {
completionHandler();
}
}];
self.cameraPosition = position;
}
}
Take a look at the commented lines, If you start adding/removing the stream from the peer connection it will cause a delay in the video connection.
I'm using GoogleWebRTC-1.1.25102
As we know that secure Enclave is a coprocessor fabricated in the Apple A7 and its available in A7 and later on but its use publicly in iOS 9 kSecAttrTokenIDSecureEnclave but how do we check either some device support secure enclave or not ?
Thanks
I didnt find any so I made my own check:
+ (BOOL) isDeviceOkForSecureEnclave
{
double OSVersionNumber = floor(NSFoundationVersionNumber);
UIUserInterfaceIdiom deviceType = [[UIDevice currentDevice] userInterfaceIdiom];
BOOL isOSForSecureEnclave = OSVersionNumber > NSFoundationVersionNumber_iOS_8_4 ? YES:NO;
//iOS 9 and up are ready for SE
BOOL isDeviceModelForSecureEnclave = NO;
switch (deviceType) {
case UIUserInterfaceIdiomPhone:
//iPhone
isDeviceModelForSecureEnclave = [self isPhoneForSE];
break;
case UIUserInterfaceIdiomPad:
//iPad
isDeviceModelForSecureEnclave = [self isPadForSE];
break;
default:
isDeviceModelForSecureEnclave = false;
break;
}
return (isOSForSecureEnclave && isDeviceModelForSecureEnclave) ? YES:NO;
}
/**
The arrays are models that we know not having SE in hardware, so if the current device is on the list it means it dosent have SE
*/
+ (BOOL) isPhoneForSE
{
NSString *thisPlatform = [self platform];
NSArray * oldModels = [NSArray arrayWithObjects:
#"x86_64",
#"iPhone1,1",
#"iPhone1,2",
#"iPhone2,1",
#"iPhone3,1",
#"iPhone3,3",
#"iPhone4,1",
#"iPhone5,1",
#"iPhone5,2",
#"iPhone5,3",
#"iPhone5,4", nil];
BOOL isInList = [oldModels containsObject: thisPlatform];
return !isInList;
}
+ (BOOL) isPadForSE
{
//iPad Mini 2 is the earliest with SE // "iPad4,4"
NSString *thisPlatform = [self platform];
NSArray * oldModels = [NSArray arrayWithObjects:
#"x86_64",
#"#iPad",
#"#iPad1,0",
#"#iPad1,1",
#"iPad2,1",
#"iPad2,2",
#"iPad2,3",
#"iPad2,4",
#"iPad2,5",
#"iPad2,6",
#"iPad2,7",
#"iPad3,1",
#"iPad3,2",
#"iPad3,3",
#"iPad3,4",
#"iPad3,5",
#"iPad3,6",nil];
BOOL isInList = [oldModels containsObject: thisPlatform];
return !isInList;
}
+ (NSString *)platform
{
size_t size;
sysctlbyname("hw.machine", NULL, &size, NULL, 0);
char *machine = malloc(size);
sysctlbyname("hw.machine", machine, &size, NULL, 0);
NSString *platform = [NSString stringWithUTF8String:machine];
free(machine);
return platform;
}
#end
TO Check Touch ID
- (BOOL)canAuthenticateByTouchId {
if ([LAContext class]) {
return [[[LAContext alloc] init] canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:nil];
}
return YES;
}
You can also find for detecting Secure Enclave here you find
Above solution has no problem but it seems like hack, so I am adding another solution in Swift 4.
To check Secure Enclave availability
enum Device {
//To check that device has secure enclave or not
public static var hasSecureEnclave: Bool {
return !isSimulator && hasBiometrics
}
//To Check that this is this simulator
public static var isSimulator: Bool {
return TARGET_OS_SIMULATOR == 1
}
//Check that this device has Biometrics features available
private static var hasBiometrics: Bool {
//Local Authentication Context
let localAuthContext = LAContext()
var error: NSError?
/// Policies can have certain requirements which, when not satisfied, would always cause
/// the policy evaluation to fail - e.g. a passcode set, a fingerprint
/// enrolled with Touch ID or a face set up with Face ID. This method allows easy checking
/// for such conditions.
var isValidPolicy = localAuthContext.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
guard isValidPolicy == true else {
if #available(iOS 11, *) {
if error!.code != LAError.biometryNotAvailable.rawValue {
isValidPolicy = true
} else{
isValidPolicy = false
}
}
else {
if error!.code != LAError.touchIDNotAvailable.rawValue {
isValidPolicy = true
}else{
isValidPolicy = false
}
}
return isValidPolicy
}
return isValidPolicy
}
}
To check that touch id available or not
let hasTouchID = LAContext().canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error)
if(hasTouchID || (error?.code != LAError.touchIDNotAvailable.rawValue)) {
print("Touch Id Available in device")
}
If you want solution in Objective C, then refer this link.
Solution in Objective C.
I had the problem to check if I am connected to an airplay device and if it is connected via mirroring or streaming. But the check needs to be done before the video started.
airPlayVideoActive only return YES if the video already started.
This is my solution
- (BOOL)isAudioSessionUsingAirplayOutputRoute
{
/**
* I found no other way to check if there is a connection to an airplay device
* airPlayVideoActive is NO as long as the video hasn't started
* and this method is true as soon as the device is connected to an airplay device
*/
AVAudioSession* audioSession = [AVAudioSession sharedInstance];
AVAudioSessionRouteDescription* currentRoute = audioSession.currentRoute;
for (AVAudioSessionPortDescription* outputPort in currentRoute.outputs){
if ([outputPort.portType isEqualToString:AVAudioSessionPortAirPlay])
return YES;
}
return NO;
}
To check if the airplay connection is mirroring you just have to check the screens count.
if ([[UIScreen screens] count] < 2)) {
//streaming
}
else {
//mirroring
}
If there is a better solution, let me know
Swift version:
var isAudioSessionUsingAirplayOutputRoute: Bool {
let audioSession = AVAudioSession.sharedInstance()
let currentRoute = audioSession.currentRoute
for outputPort in currentRoute.outputs {
if outputPort.portType == AVAudioSessionPortAirPlay {
return true
}
}
return false
}
And checking the screen count:
if UIScreen.screens.count < 2 {
//streaming
} else {
//mirroring
}
If you are using AVPlayer, it has property isExternalPlaybackActive which can help you
For the poor souls on Objc
[[NSNotificationCenter defaultCenter]
addObserver:self
selector: #selector(deviceChanged:)
name:AVAudioSessionRouteChangeNotification
object:[AVAudioSession sharedInstance]];
- (void)deviceChanged:(NSNotification *)sender {
NSLog(#"Enters here when connect or disconnect from Airplay");
}