diff --git a/conference.js b/conference.js index bb99581d8..52aaea994 100644 --- a/conference.js +++ b/conference.js @@ -752,6 +752,9 @@ export default { if (!this._localTracksInitialized) { this.videoMuted = mute; + return; + } else if (localVideo && localVideo.isMuted() === mute) { + // NO-OP return; } @@ -1994,7 +1997,23 @@ export default { ); APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => { - this.muteVideo(audioOnly); + + // FIXME On web video track is stored both in redux and in + // 'localVideo' field, video is attempted to be unmuted twice when + // turning off the audio only mode. This will crash the app with + // 'unmute operation is already in progress'. + // Because there's no logic in redux about creating new track in + // case unmute when not track exists the things have to go through + // muteVideo logic in such case. + const tracks = APP.store.getState()['features/base/tracks']; + const isTrackInRedux + = Boolean( + tracks.find( + track => track.jitsiTrack + && track.jitsiTrack.getType() === 'video')); + if (!isTrackInRedux) { + this.muteVideo(audioOnly); + } // Immediately update the UI by having remote videos and the large // video update themselves instead of waiting for some other event diff --git a/react/features/base/media/middleware.js b/react/features/base/media/middleware.js index 605eb0a61..47a5b7cdf 100644 --- a/react/features/base/media/middleware.js +++ b/react/features/base/media/middleware.js @@ -9,7 +9,7 @@ import { setCameraFacingMode, setVideoMuted } from './actions'; -import { CAMERA_FACING_MODE } from './constants'; +import { CAMERA_FACING_MODE, MEDIA_TYPE } from './constants'; /** * Middleware that captures CONFERENCE_LEFT action and restores initial state @@ -70,6 +70,15 @@ function _syncTrackMutedState(store, track) { // fired before track gets to state. if (track.muted !== muted) { track.muted = muted; - setTrackMuted(track.jitsiTrack, muted); + setTrackMuted(track.jitsiTrack, muted) + .catch(error => { + console.error(`setTrackMuted(${muted}) failed`, error); + const setMuted + = track.mediaType === MEDIA_TYPE.AUDIO + ? setAudioMuted : setVideoMuted; + + // Failed to sync muted state - dispatch rollback action + store.dispatch(setMuted(!muted)); + }); } } diff --git a/react/features/base/tracks/functions.js b/react/features/base/tracks/functions.js index f452b516d..b0b3ba28b 100644 --- a/react/features/base/tracks/functions.js +++ b/react/features/base/tracks/functions.js @@ -174,9 +174,5 @@ export function setTrackMuted(track, muted) { const f = muted ? 'mute' : 'unmute'; - return track[f]() - .catch(err => { - console.warn(`Track ${f} was rejected:`, err); - throw err; - }); + return track[f](); } diff --git a/react/features/base/tracks/middleware.js b/react/features/base/tracks/middleware.js index 388d0d44c..d44c63987 100644 --- a/react/features/base/tracks/middleware.js +++ b/react/features/base/tracks/middleware.js @@ -160,7 +160,19 @@ function _getLocalTrack(store, mediaType: MEDIA_TYPE) { function _setMuted(store, action, mediaType: MEDIA_TYPE) { const localTrack = _getLocalTrack(store, mediaType); - localTrack && setTrackMuted(localTrack.jitsiTrack, action.muted); + if (localTrack) { + setTrackMuted(localTrack.jitsiTrack, action.muted) + .catch(error => { + console.error(`setTrackMuted(${action.muted}) failed`, error); + + const setMuted + = mediaType === MEDIA_TYPE.AUDIO + ? setAudioMuted : setVideoMuted; + + // Failed to modify muted state - dispatch rollback action + store.dispatch(setMuted(!action.muted)); + }); + } } /**