Skip to content

Commit

Permalink
Merge pull request #43 from sendbird/feat/add-resume-video-capturer
Browse files Browse the repository at this point in the history
[CLNP-5538] feat: add resumeVideoCapturer
  • Loading branch information
bang9 authored Oct 25, 2024
2 parents a8f9c17 + baa13dc commit b73f644
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -83,33 +83,6 @@ class CallsDirectCallModule(private val root: CallsModule): DirectCallModule {
call.setRemoteVideoView(view.getSurface())
}
}

override fun selectVideoDevice(type: String, identifier: String, device: ReadableMap, promise: Promise) {
CallsUtils.safeRun(promise) {
val from = "directCall/selectVideoDevice"
val call = CallsUtils.findDirectCall(identifier, from)
val deviceId = CallsUtils.safeGet { device.getString("deviceId") }

call.availableVideoDevices
.find {
it.deviceName === deviceId
}
?.let {
call.selectVideoDevice(it) { error ->
error
?.let {
promise.rejectCalls(error)
}
?: run {
promise.resolve(null)
}
}
}
?: run {
promise.reject(RNCallsInternalError(from, RNCallsInternalError.Type.NOT_FOUND_VIDEO_DEVICE))
}
}
}

override fun muteMicrophone(type: String, identifier: String) {
val from = "directCall/muteMicrophone"
Expand Down Expand Up @@ -182,4 +155,40 @@ class CallsDirectCallModule(private val root: CallsModule): DirectCallModule {
}
}
}

override fun selectVideoDevice(type: String, identifier: String, device: ReadableMap, promise: Promise) {
CallsUtils.safeRun(promise) {
val from = "directCall/selectVideoDevice"
val call = CallsUtils.findDirectCall(identifier, from)
val deviceId = CallsUtils.safeGet { device.getString("deviceId") }

call.availableVideoDevices
.find {
it.deviceName === deviceId
}
?.let {
call.selectVideoDevice(it) { error ->
error
?.let {
promise.rejectCalls(error)
}
?: run {
promise.resolve(null)
}
}
}
?: run {
promise.reject(RNCallsInternalError(from, RNCallsInternalError.Type.NOT_FOUND_VIDEO_DEVICE))
}
}
}

override fun resumeVideoCapturer(type: String, identifier: String) {
val from ="directCall/resumeVideoCapturer"
RNCallsLogger.d("[DirectCallModule] $from ($identifier)")

CallsUtils.safeRun {
CallsUtils.findDirectCall(identifier, from).resumeVideoCapturer()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,4 +129,13 @@ class CallsGroupCallModule: GroupCallModule {
// NOOP
promise.resolve(null)
}

override fun resumeVideoCapturer(type: String, identifier: String) {
val from = "groupCall/resumeVideoCapturer"
RNCallsLogger.d("[GroupCallModule] $from ($identifier)")

CallsUtils.safeRun {
CallsUtils.findRoom(identifier, from).localParticipant?.resumeVideoCapturer()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ class CallsModule(val reactContext: ReactApplicationContext) : CallsModuleStruct
override fun unmuteMicrophone(type: String, identifier: String) = getControllableModule(type).unmuteMicrophone(type, identifier)
override fun switchCamera(type: String, identifier: String, promise: Promise) = getControllableModule(type).switchCamera(type, identifier, promise)
override fun selectAudioDevice(type: String, identifier: String, device: String, promise: Promise) = getControllableModule(type).selectAudioDevice(type, identifier, device, promise)
override fun selectVideoDevice(type: String, identifier: String, device: ReadableMap, promise: Promise)= getControllableModule(type).selectVideoDevice(type, identifier, device, promise)
override fun selectVideoDevice(type: String, identifier: String, device: ReadableMap, promise: Promise) = getControllableModule(type).selectVideoDevice(type, identifier, device, promise)
override fun resumeVideoCapturer(type: String, identifier: String) = getControllableModule(type).resumeVideoCapturer(type, identifier)

/** DirectCall module interface **/
override fun accept(callId: String, options: ReadableMap, holdActiveCall: Boolean, promise: Promise) = directCallModule.accept(callId, options, holdActiveCall, promise)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ interface MediaDeviceControl {
fun switchCamera(type: String, identifier: String, promise: Promise)
fun selectAudioDevice(type: String, identifier: String, device: String, promise: Promise)
fun selectVideoDevice(type: String, identifier: String, device: ReadableMap, promise: Promise)
fun resumeVideoCapturer(type: String, identifier: String)
}
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class RNSendbirdCallsModule(private val reactContext: ReactApplicationContext) :
override fun selectAudioDevice(type: String, identifier: String, device: String, promise: Promise) = module.selectAudioDevice(type, identifier, device, promise)
@ReactMethod
override fun selectVideoDevice(type: String, identifier: String, device: ReadableMap, promise: Promise) = module.selectVideoDevice(type, identifier, device, promise)
@ReactMethod
override fun resumeVideoCapturer(type: String, identifier: String) = module.resumeVideoCapturer(type, identifier)

/** DirectCall **/
@ReactMethod
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { FC, useEffect, useRef } from 'react';
import { Animated, Easing, StyleSheet, View, useWindowDimensions } from 'react-native';
import { Animated, Easing, Platform, StyleSheet, View, useWindowDimensions } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { DirectCall, DirectCallVideoView } from '@sendbird/calls-react-native';
Expand Down Expand Up @@ -31,6 +31,14 @@ const DirectCallVideoContentView: FC<CallStatusProps> = ({ call, status }) => {
}
}, [status]);

// In Android, if you have granted permission after accepting the call,
// you have to call `call.android_resumeVideoCapturer()`.
useEffect(() => {
if (Platform.OS === 'android') {
call.android_resumeVideoCapturer();
}
}, []);

return (
<View style={{ flex: 1 }}>
<DirectCallVideoView
Expand Down
10 changes: 9 additions & 1 deletion sample/src/group-call/components/GroupCallVideoStreamView.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { FC, useEffect, useState } from 'react';
import { Image, StyleSheet, View } from 'react-native';
import { Image, Platform, StyleSheet, View } from 'react-native';

import {
GroupCallVideoView,
Expand Down Expand Up @@ -64,6 +64,14 @@ const GroupCallVideoStreamView: FC<GroupCallVideoStreamViewProps> = ({ room, lay
}
}, [layoutSize.width, layoutSize.height, rowCol.row, rowCol.column]);

// In Android, if you have granted permission after accepting the call,
// you have to call `room.localParticipant.android_resumeVideoCapturer()`.
useEffect(() => {
if (Platform.OS === 'android' && room.localParticipant) {
room.localParticipant.android_resumeVideoCapturer();
}
}, [room.localParticipant]);

return (
<View
style={[
Expand Down
12 changes: 12 additions & 0 deletions src/libs/DirectCall.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,21 @@ export class DirectCall implements DirectCallProperties, DirectCallMethods {
* @since 1.0.0
*/
public android_selectAudioDevice = async (device: AudioDevice) => {
if (Platform.OS !== 'android') return;
await this._binder.nativeModule.selectAudioDevice(ControllableModuleType.DIRECT_CALL, this.callId, device);
};

/**
* Connects the device camera and Sendbird Calls SDK to stream video.
*
* @platform Android
* @since 1.1.3
* */
public android_resumeVideoCapturer = () => {
if (Platform.OS !== 'android') return;
this._binder.nativeModule.resumeVideoCapturer(ControllableModuleType.DIRECT_CALL, this.callId);
};

/**
* Mutes the audio of local user.
* Will trigger {@link DirectCallListener.onRemoteAudioSettingsChanged} method of the remote user.
Expand Down
13 changes: 13 additions & 0 deletions src/libs/Participant.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Platform } from 'react-native';

import type { LocalParticipantMethods, ParticipantProperties, RoomListener } from '../types';
import { ControllableModuleType } from '../types';
import type NativeBinder from './NativeBinder';
Expand Down Expand Up @@ -158,4 +160,15 @@ export class LocalParticipant extends Participant implements LocalParticipantMet
public switchCamera = () => {
return this._binder.nativeModule.switchCamera(ControllableModuleType.GROUP_CALL, this._roomId);
};

/**
* Connects the device camera and Sendbird Calls SDK to stream video for local participant.
*
* @platform Android
* @since 1.1.3
* */
public android_resumeVideoCapturer = () => {
if (Platform.OS !== 'android') return;
return this._binder.nativeModule.resumeVideoCapturer(ControllableModuleType.GROUP_CALL, this._roomId);
};
}
5 changes: 3 additions & 2 deletions src/libs/Room.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,8 @@ export class Room implements RoomProperties, GroupCallMethods {
* @platform Android
* @since 1.0.0
*/
public android_selectAudioDevice = (device: AudioDevice) => {
return this._binder.nativeModule.selectAudioDevice(ControllableModuleType.GROUP_CALL, this.roomId, device);
public android_selectAudioDevice = async (device: AudioDevice) => {
if (Platform.OS !== 'android') return;
await this._binder.nativeModule.selectAudioDevice(ControllableModuleType.GROUP_CALL, this.roomId, device);
};
}
6 changes: 5 additions & 1 deletion src/types/Call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,11 @@ export interface DirectCallProperties {

/** DirectCall */
type JSDirectCallModule = AsJSDirectCall<NativeDirectCallModule>;
type JSDirectCallMediaDeviceControl = AsJSInterface<JSMediaDeviceControl, 'android', 'selectAudioDevice'>;
type JSDirectCallMediaDeviceControl = AsJSInterface<
JSMediaDeviceControl,
'android',
'selectAudioDevice' | 'resumeVideoCapturer'
>;

export interface DirectCallMethods extends JSDirectCallModule, JSDirectCallMediaDeviceControl {
addListener(listener: Partial<DirectCallListener>): () => void;
Expand Down
2 changes: 2 additions & 0 deletions src/types/NativeModule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ export interface NativeMediaDeviceControl {
selectVideoDevice(type: ControllableModuleType, identifier: string, device: VideoDevice): Promise<void>;
/** @platform Android **/
selectAudioDevice(type: ControllableModuleType, identifier: string, device: AudioDevice): Promise<void>;
/** @platform Android **/
resumeVideoCapturer(type: ControllableModuleType, identifier: string): void;
}

export interface NativeDirectCallModule {
Expand Down
10 changes: 7 additions & 3 deletions src/types/Participant.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { User } from './User';
import { JSMediaDeviceControl } from './index';
import type { AsJSInterface, JSMediaDeviceControl } from './index';

export interface ParticipantProperties {
participantId: string;
Expand All @@ -18,10 +18,14 @@ export interface ParticipantProperties {

type JSLocalParticipantMediaDeviceControl = Pick<
JSMediaDeviceControl,
'muteMicrophone' | 'unmuteMicrophone' | 'switchCamera' | 'startVideo' | 'stopVideo'
'muteMicrophone' | 'unmuteMicrophone' | 'switchCamera' | 'startVideo' | 'stopVideo' | 'resumeVideoCapturer'
>;

export type LocalParticipantMethods = JSLocalParticipantMediaDeviceControl;
export type LocalParticipantMethods = AsJSInterface<
JSLocalParticipantMediaDeviceControl,
'android',
'resumeVideoCapturer'
>;

export enum ParticipantState {
ENTERED = 'ENTERED',
Expand Down

0 comments on commit b73f644

Please sign in to comment.