Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Torch feature for Android and iOS #457

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.hardware.Camera;
import android.media.AudioAttributes;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
Expand All @@ -37,6 +38,7 @@
import com.twilio.video.AudioTrackPublication;
import com.twilio.video.BaseTrackStats;
import com.twilio.video.CameraCapturer;
import com.twilio.video.CameraParameterUpdater;
import com.twilio.video.ConnectOptions;
import com.twilio.video.LocalAudioTrack;
import com.twilio.video.LocalAudioTrackPublication;
Expand Down Expand Up @@ -81,6 +83,7 @@

import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_AUDIO_CHANGED;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CAMERA_SWITCHED;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CAMERA_FLASH_TOGGLED;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CONNECTED;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CONNECT_FAILURE;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_DISCONNECTED;
Expand Down Expand Up @@ -132,7 +135,8 @@ public class CustomTwilioVideoView extends View implements LifecycleEventListene
Events.ON_PARTICIPANT_DISABLED_AUDIO_TRACK,
Events.ON_STATS_RECEIVED,
Events.ON_NETWORK_QUALITY_LEVELS_CHANGED,
Events.ON_DOMINANT_SPEAKER_CHANGED
Events.ON_DOMINANT_SPEAKER_CHANGED,
Events.ON_CAMERA_FLASH_TOGGLED
})
public @interface Events {
String ON_CAMERA_SWITCHED = "onCameraSwitched";
Expand All @@ -157,6 +161,7 @@ public class CustomTwilioVideoView extends View implements LifecycleEventListene
String ON_STATS_RECEIVED = "onStatsReceived";
String ON_NETWORK_QUALITY_LEVELS_CHANGED = "onNetworkQualityLevelsChanged";
String ON_DOMINANT_SPEAKER_CHANGED = "onDominantSpeakerDidChange";
String ON_CAMERA_FLASH_TOGGLED = "onCameraFlashToggled";
}

private final ThemedReactContext themedReactContext;
Expand Down Expand Up @@ -565,6 +570,32 @@ public void switchCamera() {
}
}

private final CameraParameterUpdater flashToggler = parameters -> {
if (parameters.getFlashMode() != null) {
String flashMode = Camera.Parameters.FLASH_MODE_OFF.equals(parameters.getFlashMode()) ?
Camera.Parameters.FLASH_MODE_TORCH :
Camera.Parameters.FLASH_MODE_OFF;
parameters.setFlashMode(flashMode);
WritableMap event = new WritableNativeMap();
event.putBoolean("isFlashOn", flashMode == Camera.Parameters.FLASH_MODE_TORCH);
pushEvent(CustomTwilioVideoView.this, ON_CAMERA_FLASH_TOGGLED, event);
} else {
WritableMap event = new WritableNativeMap();
event.putString("error", "Flash is not supported in current camera mode");
pushEvent(CustomTwilioVideoView.this, ON_CAMERA_FLASH_TOGGLED, event);
}
};

public void toggleFlash() {
if (cameraCapturer != null) {
cameraCapturer.updateCameraParameters(flashToggler);
} else {
WritableMap event = new WritableNativeMap();
event.putString("error", "There's no camera available");
pushEvent(CustomTwilioVideoView.this, ON_CAMERA_FLASH_TOGGLED, event);
}
}

public void toggleVideo(boolean enabled) {
isVideoEnabled = enabled;
if (localVideoTrack != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_AUDIO_CHANGED;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CAMERA_SWITCHED;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CAMERA_FLASH_TOGGLED;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CONNECTED;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_CONNECT_FAILURE;
import static com.twiliorn.library.CustomTwilioVideoView.Events.ON_DISCONNECTED;
Expand Down Expand Up @@ -59,6 +60,7 @@ public class CustomTwilioVideoViewManager extends SimpleViewManager<CustomTwilio
private static final int SEND_STRING = 12;
private static final int PUBLISH_VIDEO = 13;
private static final int PUBLISH_AUDIO = 14;
private static final int TOGGLE_FLASH = 15;

@Override
public String getName() {
Expand Down Expand Up @@ -127,6 +129,9 @@ public void receiveCommand(CustomTwilioVideoView view, int commandId, @Nullable
case PUBLISH_AUDIO:
view.publishLocalAudio(args.getBoolean(0));
break;
case TOGGLE_FLASH:
view.toggleFlash();
break;
}
}

Expand All @@ -143,6 +148,10 @@ public Map getExportedCustomDirectEventTypeConstants() {
ON_PARTICIPANT_CONNECTED, MapBuilder.of("registrationName", ON_PARTICIPANT_CONNECTED)
);

map.putAll(MapBuilder.of(
ON_CAMERA_FLASH_TOGGLED, MapBuilder.of("registrationName", ON_CAMERA_FLASH_TOGGLED)
));

map.putAll(MapBuilder.of(
ON_PARTICIPANT_DISCONNECTED, MapBuilder.of("registrationName", ON_PARTICIPANT_DISCONNECTED),
ON_DATATRACK_MESSAGE_RECEIVED, MapBuilder.of("registrationName", ON_DATATRACK_MESSAGE_RECEIVED),
Expand Down Expand Up @@ -177,6 +186,7 @@ public Map<String, Integer> getCommandsMap() {
.put("connectToRoom", CONNECT_TO_ROOM)
.put("disconnect", DISCONNECT)
.put("switchCamera", SWITCH_CAMERA)
.put("toggleFlash", TOGGLE_FLASH)
.put("toggleVideo", TOGGLE_VIDEO)
.put("toggleSound", TOGGLE_SOUND)
.put("getStats", GET_STATS)
Expand Down
2 changes: 2 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ onParticipantDisabledAudioTrack | func | no | | Called when an audio track has
onStatsReceived | func | no | | Callback that is called when stats are received (after calling getStats)
onNetworkQualityLevelsChanged | func | no | | Callback that is called when network quality levels are changed (only if enableNetworkQualityReporting in connect is set to true)
onDominantSpeakerDidChange | func | no | | Called when dominant speaker changes @param {{ participant, room }} dominant participant and room
onCameraFlashToggled | func | no | | Called when dominant speaker changes @param {{ isFlashOn, error }} dominant participant
-----

**src/TwilioVideo.ios.js**
Expand Down Expand Up @@ -70,6 +71,7 @@ onCameraDidStopRunning | func | no | | Called when the camera has stopped runin
onStatsReceived | func | no | | Called when stats are received (after calling getStats)
onNetworkQualityLevelsChanged | func | no | | Called when the network quality levels of a participant have changed (only if enableNetworkQualityReporting is set to True when connecting)
onDominantSpeakerDidChange | func | no | | Called when dominant speaker changes @param {{ participant, room }} dominant participant
onCameraFlashToggled | func | no | | Called when dominant speaker changes @param {{ isFlashOn, error }} dominant participant
-----

**src/TwilioVideoLocalView.android.js**
Expand Down
4 changes: 4 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,12 @@ declare module "react-native-twilio-video-webrtc" {

export type NetworkLevelChangeEventCb = (p: NetworkLevelChangeEventArgs) => void;

export type CameraToggled = (p: { isFlashOn: boolean, error?: string}) => void;

export type TwilioVideoProps = ViewProps & {
onCameraDidStart?: () => void;
onCameraDidStopRunning?: (err: any) => void;
onCameraFlashToggled?: CameraToggled;
onCameraWasInterrupted?: () => void;
onParticipantAddedAudioTrack?: TrackEventCb;
onParticipantAddedVideoTrack?: TrackEventCb;
Expand Down Expand Up @@ -125,6 +128,7 @@ declare module "react-native-twilio-video-webrtc" {
connect: (options: iOSConnectParams | androidConnectParams) => void;
disconnect: () => void;
flipCamera: () => void;
toggleFlash: () => void;
toggleSoundSetup: (speaker: boolean) => void;
getStats: () => void;
publishLocalAudio: () => void;
Expand Down
27 changes: 26 additions & 1 deletion ios/RCTTWVideoModule.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
static NSString* cameraDidStopRunning = @"cameraDidStopRunning";
static NSString* statsReceived = @"statsReceived";
static NSString* networkQualityLevelsChanged = @"networkQualityLevelsChanged";
static NSString* onCameraFlashToggled = @"onCameraFlashToggled";

static const CMVideoDimensions kRCTTWVideoAppCameraSourceDimensions = (CMVideoDimensions){900, 720};

Expand Down Expand Up @@ -111,7 +112,8 @@ - (dispatch_queue_t)methodQueue {
cameraInterruptionEnded,
statsReceived,
networkQualityLevelsChanged,
dominantSpeakerDidChange
dominantSpeakerDidChange,
onCameraFlashToggled
];
}

Expand Down Expand Up @@ -275,6 +277,29 @@ - (bool)_setLocalVideoEnabled:(bool)enabled {
}
}

RCT_EXPORT_METHOD(toggleFlash) {
NSMutableDictionary *body = [[NSMutableDictionary alloc] init];
if (self.camera) {
AVCaptureDevice *device = self.camera.device;
if ([device hasTorch]){
[device lockForConfiguration:nil];
if (!device.torchActive) {
[device setTorchMode:AVCaptureTorchModeOn];
} else {
[device setTorchMode:AVCaptureTorchModeOff];
}
BOOL isFlashOn = device.torchActive;
[device unlockForConfiguration];
[body addEntriesFromDictionary:@{ @"isFlashOn" : [NSNumber numberWithBool:!isFlashOn] }];
} else {
[body addEntriesFromDictionary:@{ @"error" : @"Flash is not supported in current camera mode" }];
}
} else {
[body addEntriesFromDictionary:@{ @"error" : @"There's no camera available" }];
}
[self sendEventCheckingListenerWithName:onCameraFlashToggled body:body];
}

RCT_EXPORT_METHOD(toggleSoundSetup:(BOOL)speaker) {
NSError *error = nil;
kTVIDefaultAVAudioSessionConfigurationBlock();
Expand Down
17 changes: 14 additions & 3 deletions src/TwilioVideo.android.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,12 @@ const propTypes = {
* Called when dominant speaker changes
* @param {{ participant, room }} dominant participant and room
*/
onDominantSpeakerDidChange: PropTypes.func
onDominantSpeakerDidChange: PropTypes.func,
/**
* Called when torch is done attempting to toggle
* @param {{ error, isFlashOn }}
*/
onCameraFlashToggled: PropTypes.func,
}

const nativeEvents = {
Expand All @@ -160,7 +165,8 @@ const nativeEvents = {
toggleBluetoothHeadset: 11,
sendString: 12,
publishVideo: 13,
publishAudio: 14
publishAudio: 14,
toggleFlash: 15
}

class CustomTwilioVideoView extends Component {
Expand Down Expand Up @@ -218,6 +224,10 @@ class CustomTwilioVideoView extends Component {
this.runCommand(nativeEvents.switchCamera, [])
}

toggleFlash() {
this.runCommand(nativeEvents.toggleFlash, []);
}

setLocalVideoEnabled (enabled) {
this.runCommand(nativeEvents.toggleVideo, [enabled])
return Promise.resolve(enabled)
Expand Down Expand Up @@ -287,7 +297,8 @@ class CustomTwilioVideoView extends Component {
'onParticipantDisabledAudioTrack',
'onStatsReceived',
'onNetworkQualityLevelsChanged',
'onDominantSpeakerDidChange'
'onDominantSpeakerDidChange',
'onCameraFlashToggled'
].reduce((wrappedEvents, eventName) => {
if (this.props[eventName]) {
return {
Expand Down
17 changes: 17 additions & 0 deletions src/TwilioVideo.ios.js
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,11 @@ export default class TwilioVideo extends Component {
* @param {{ participant, room }} dominant participant
*/
onDominantSpeakerDidChange: PropTypes.func,
/**
* Called when torch is done attempting to toggle
* @param {{ error, isFlashOn }}
*/
onCameraFlashToggled: PropTypes.func,
...View.propTypes
}

Expand Down Expand Up @@ -208,6 +213,13 @@ export default class TwilioVideo extends Component {
TWVideoModule.flipCamera()
}

/**
* Toggle Torch functionality
*/
toggleFlash () {
TWVideoModule.toggleFlash()
}

/**
* Toggle audio setup from speaker (default) and headset
*/
Expand Down Expand Up @@ -415,6 +427,11 @@ export default class TwilioVideo extends Component {
if (this.props.onDominantSpeakerDidChange) {
this.props.onDominantSpeakerDidChange(data)
}
}),
this._eventEmitter.addListener('onCameraFlashToggled', data => {
if (this.props.onCameraFlashToggled) {
this.props.onCameraFlashToggled(data)
}
})
]
}
Expand Down