Skip to content
Merged
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
@@ -0,0 +1,9 @@
{
"type": "patch",
"area": "fix",
"workstream": "BugFix",
"comment": "Fix issues with turning camera off and on when joining a call, as well fix microphone button to unmute and mute when joining",
"packageName": "@azure/communication-react",
"email": "94866715+dmceachernmsft@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"type": "patch",
"area": "fix",
"workstream": "BugFix",
"comment": "Fix issues with turning camera off and on when joining a call, as well fix microphone button to unmute and mute when joining",
"packageName": "@azure/communication-react",
"email": "94866715+dmceachernmsft@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,37 +173,48 @@ export const createDefaultCommonCallingHandlers = memoizeOne(
const onToggleCamera = async (options?: VideoStreamOptions): Promise<void> => {
const previewOn = _isPreviewOn(callClient.getState().deviceManager);

if (previewOn && call && call.state === 'Connecting') {
// This is to workaround: https://skype.visualstudio.com/SPOOL/_workitems/edit/3030558.
// The root cause of the issue is caused by never transitioning the unparented view to the
// call object when going from configuration page (disconnected call state) to connecting.
//
// Currently the only time the local video stream is moved from unparented view to the call
// object is when we transition from connecting -> call state. If the camera was on,
// inside the MediaGallery we trigger toggleCamera. This triggers onStartLocalVideo which
// destroys the unparentedView and creates a new stream in the call - so all looks well.
//
// However, if someone turns off their camera during the lobbyOrConnecting screen, the
// call.localVideoStreams will be empty (as the stream is currently stored in the unparented
// views and was never transitioned to the call object) and thus we incorrectly try to create
// a new video stream for the call object, instead of only stopping the unparented view.
//
// The correct fix for this is to ensure that callAgent.onStartCall is called with the
// localvideostream as a videoOption. That will mean call.onLocalVideoStreamsUpdated will
// be triggered when the call is in connecting state, which we can then transition the
// local video stream to the stateful call client and get into a clean state.
await onDisposeLocalStreamView();
return;
}
// the disposal of the unparented views is to workaround: https://skype.visualstudio.com/SPOOL/_workitems/edit/3030558.
// The root cause of the issue is caused by never transitioning the unparented view to the
// call object when going from configuration page (disconnected call state) to connecting.
//
// Currently the only time the local video stream is moved from unparented view to the call
// object is when we transition from connecting -> call state. If the camera was on,
// inside the MediaGallery we trigger toggleCamera. This triggers onStartLocalVideo which
// destroys the unparentedView and creates a new stream in the call - so all looks well.
//
// However, if someone turns off their camera during the lobbyOrConnecting screen, the
// call.localVideoStreams will be empty (as the stream is currently stored in the unparented
// views and was never transitioned to the call object) and thus we incorrectly try to create
// a new video stream for the call object, instead of only stopping the unparented view.
//
// The correct fix for this is to ensure that callAgent.onStartCall is called with the
// localvideostream as a videoOption. That will mean call.onLocalVideoStreamsUpdated will
// be triggered when the call is in connecting state, which we can then transition the
// local video stream to the stateful call client and get into a clean state.

if (call && (_isInCall(call.state) || _isInLobbyOrConnecting(call.state))) {
const stream = call.localVideoStreams.find((stream) => stream.mediaStreamType === 'Video');
if (stream) {
await onStopLocalVideo(stream);
const unparentedViews = callClient.getState().deviceManager.unparentedViews;
if (stream || unparentedViews.length > 0) {
unparentedViews &&
(await unparentedViews.forEach(
(view) => view.mediaStreamType === 'Video' && callClient.disposeView(undefined, undefined, view)
));
stream && (await onStopLocalVideo(stream));
} else {
await onStartLocalVideo();
}
} else {
/**
* This will create a unparented view to be used on the configuration page and the connecting screen
*
* If the device that the stream will come from is not on from permissions checks, then it will take time
* to create the stream since device is off. If we are turn the camera on immedietly on the configuration page we see it is
* fast but that is because the device is already primed to return a stream.
*
* On the connecting page the device has already turned off and the connecting window is so small we do not see the resulting
* unparented view from the code below.
*/
const selectedCamera = callClient.getState().deviceManager.selectedCamera;
if (selectedCamera) {
if (previewOn) {
Expand Down Expand Up @@ -292,7 +303,7 @@ export const createDefaultCommonCallingHandlers = memoizeOne(
};

const onToggleMicrophone = async (): Promise<void> => {
if (!call || !_isInCall(call.state)) {
if (!call || !(_isInCall(call.state) || _isInLobbyOrConnecting(call.state))) {
throw new Error(`Please invoke onToggleMicrophone after call is started`);
}
return call.isMuted ? await call.unmute() : await call.mute();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ export class AzureCommunicationCallAdapter<AgentType extends CallAgent | BetaTea
public async unmute(): Promise<void> {
return await this.asyncTeeErrorToEventEmitter(async () => {
this.context.setIsLocalMicrophoneEnabled(true);
if (_isInCall(this.call?.state) && this.call?.isMuted) {
if ((_isInCall(this.call?.state) || _isInLobbyOrConnecting(this.call?.state)) && this.call?.isMuted) {
await this.handlers.onToggleMicrophone();
}
});
Expand Down