fix(AudioOnly+web): crash when untoggle audio only

Because 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'. This commit will prevent from taking action from the web
world if the video track already exists and will make the redux side
rollback unmuted status in case unmute fails.
This commit is contained in:
paweldomas 2017-07-24 17:36:19 +02:00
parent 2525bb2805
commit 122a7f6346
4 changed files with 45 additions and 9 deletions

View File

@ -752,6 +752,9 @@ export default {
if (!this._localTracksInitialized) { if (!this._localTracksInitialized) {
this.videoMuted = mute; this.videoMuted = mute;
return;
} else if (localVideo && localVideo.isMuted() === mute) {
// NO-OP
return; return;
} }
@ -1994,7 +1997,23 @@ export default {
); );
APP.UI.addListener(UIEvents.TOGGLE_AUDIO_ONLY, audioOnly => { 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 // Immediately update the UI by having remote videos and the large
// video update themselves instead of waiting for some other event // video update themselves instead of waiting for some other event

View File

@ -9,7 +9,7 @@ import {
setCameraFacingMode, setCameraFacingMode,
setVideoMuted setVideoMuted
} from './actions'; } 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 * Middleware that captures CONFERENCE_LEFT action and restores initial state
@ -70,6 +70,15 @@ function _syncTrackMutedState(store, track) {
// fired before track gets to state. // fired before track gets to state.
if (track.muted !== muted) { if (track.muted !== muted) {
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));
});
} }
} }

View File

@ -174,9 +174,5 @@ export function setTrackMuted(track, muted) {
const f = muted ? 'mute' : 'unmute'; const f = muted ? 'mute' : 'unmute';
return track[f]() return track[f]();
.catch(err => {
console.warn(`Track ${f} was rejected:`, err);
throw err;
});
} }

View File

@ -160,7 +160,19 @@ function _getLocalTrack(store, mediaType: MEDIA_TYPE) {
function _setMuted(store, action, mediaType: MEDIA_TYPE) { function _setMuted(store, action, mediaType: MEDIA_TYPE) {
const localTrack = _getLocalTrack(store, mediaType); 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));
});
}
} }
/** /**