feat(tracks): place local tracks in the redux store

- Add tracks to the redux store by intercepting where the
  tracks actually get used via conference.replaceTrack
- While the replace call is unique to web, the _dispose and
 _addTracks calls use existing native code implementations
- Between _dispose and addTracks is a call to update mute state.
  Without it, when changing devices or videoType while muted,
  the user will stay muted (whereas existing web behavior
  causes unmute). This is due to middelware calling
  _syncTrackMutedState to make the track mute if the user is
  currently muted.
- Move the rest of ConferenceEvents.TRACK_MUTE_CHANGED into
  middleware so the event is no longer used
- Note: This change does not guarantee the track state in the
  redux store will be 100% accurate, specifically the attribute
  videoStarted. Muted and videoType should be accurate.
This commit is contained in:
Leonard Kim 2017-06-20 15:09:34 -05:00 committed by Paweł Domas
parent 2a446b8799
commit fe4de31e57
3 changed files with 78 additions and 41 deletions

View File

@ -42,7 +42,11 @@ import {
participantRoleChanged, participantRoleChanged,
participantUpdated participantUpdated
} from './react/features/base/participants'; } from './react/features/base/participants';
import { trackAdded, trackRemoved } from './react/features/base/tracks'; import {
replaceLocalTrack,
trackAdded,
trackRemoved
} from './react/features/base/tracks';
import { import {
showDesktopPicker showDesktopPicker
} from './react/features/desktop-picker'; } from './react/features/desktop-picker';
@ -1023,16 +1027,9 @@ export default {
* @returns {Promise} * @returns {Promise}
*/ */
useVideoStream(newStream) { useVideoStream(newStream) {
return room.replaceTrack(localVideo, newStream) return APP.store.dispatch(
replaceLocalTrack(localVideo, newStream, room))
.then(() => { .then(() => {
// We call dispose after doing the replace because
// dispose will try and do a new o/a after the
// track removes itself. Doing it after means
// the JitsiLocalTrack::conference member is already
// cleared, so it won't try and do the o/a
if (localVideo) {
localVideo.dispose();
}
localVideo = newStream; localVideo = newStream;
if (newStream) { if (newStream) {
this.videoMuted = newStream.isMuted(); this.videoMuted = newStream.isMuted();
@ -1058,16 +1055,9 @@ export default {
* @returns {Promise} * @returns {Promise}
*/ */
useAudioStream(newStream) { useAudioStream(newStream) {
return room.replaceTrack(localAudio, newStream) return APP.store.dispatch(
replaceLocalTrack(localAudio, newStream, room))
.then(() => { .then(() => {
// We call dispose after doing the replace because
// dispose will try and do a new o/a after the
// track removes itself. Doing it after means
// the JitsiLocalTrack::conference member is already
// cleared, so it won't try and do the o/a
if (localAudio) {
localAudio.dispose();
}
localAudio = newStream; localAudio = newStream;
if (newStream) { if (newStream) {
this.audioMuted = newStream.isMuted(); this.audioMuted = newStream.isMuted();
@ -1338,24 +1328,6 @@ export default {
APP.store.dispatch(trackRemoved(track)); APP.store.dispatch(trackRemoved(track));
}); });
room.on(ConferenceEvents.TRACK_MUTE_CHANGED, (track) => {
if (!track || !track.isLocal()) {
return;
}
const handler = (track.getType() === "audio")?
APP.UI.setAudioMuted : APP.UI.setVideoMuted;
const mute = track.isMuted();
const id = APP.conference.getMyUserId();
if (track.getType() === "audio") {
this.audioMuted = mute;
} else {
this.videoMuted = mute;
}
handler(id , mute);
});
room.on(ConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, (id, lvl) => { room.on(ConferenceEvents.TRACK_AUDIO_LEVEL_CHANGED, (id, lvl) => {
if(this.isLocalId(id) && localAudio && localAudio.isMuted()) { if(this.isLocalId(id) && localAudio && localAudio.isMuted()) {
lvl = 0; lvl = 0;

View File

@ -4,7 +4,9 @@ import JitsiMeetJS, {
} from '../lib-jitsi-meet'; } from '../lib-jitsi-meet';
import { import {
CAMERA_FACING_MODE, CAMERA_FACING_MODE,
MEDIA_TYPE MEDIA_TYPE,
setAudioMuted,
setVideoMuted
} from '../media'; } from '../media';
import { getLocalParticipant } from '../participants'; import { getLocalParticipant } from '../participants';
@ -51,6 +53,61 @@ export function destroyLocalTracks() {
.map(t => t.jitsiTrack))); .map(t => t.jitsiTrack)));
} }
/**
* Replaces one track with another for one renegotiation instead of invoking
* two renegotations with a separate removeTrack and addTrack. Disposes the
* removed track as well.
*
* @param {JitsiLocalTrack|null} oldTrack - The track to dispose.
* @param {JitsiLocalTrack|null} newTrack - The track to use instead.
* @param {JitsiConference} [conference] - The conference from which to remove
* and add the tracks. If one is not provied, the conference in the redux store
* will be used.
* @returns {Function}
*/
export function replaceLocalTrack(oldTrack, newTrack, conference) {
return (dispatch, getState) => {
const currentConference = conference
|| getState()['features/base/conference'].conference;
return currentConference.replaceTrack(oldTrack, newTrack)
.then(() => {
// We call dispose after doing the replace because
// dispose will try and do a new o/a after the
// track removes itself. Doing it after means
// the JitsiLocalTrack::conference member is already
// cleared, so it won't try and do the o/a
const disposePromise = oldTrack
? dispatch(_disposeAndRemoveTracks([ oldTrack ]))
: Promise.resolve();
return disposePromise
.then(() => {
if (newTrack) {
// The mute state of the new track should be
// reflected in the app's mute state. For example,
// if the app is currently muted and changing to a
// new track that is not muted, the app's mute
// state should be falsey. As such, emit a mute
// event here to set up the app to reflect the
// track's mute state. If this is not done, the
// current mute state of the app will be reflected
// on the track, not vice-versa.
const muteAction = newTrack.isVideoTrack()
? setVideoMuted : setAudioMuted;
return dispatch(muteAction(newTrack.isMuted()));
}
})
.then(() => {
if (newTrack) {
return dispatch(_addTracks([ newTrack ]));
}
});
});
};
}
/** /**
* Create an action for when a new track has been signaled to be added to the * Create an action for when a new track has been signaled to be added to the
* conference. * conference.

View File

@ -113,13 +113,21 @@ MiddlewareRegistry.register(store => next => action => {
case TRACK_UPDATED: case TRACK_UPDATED:
// TODO Remove the below calls to APP.UI once components interested in // TODO Remove the below calls to APP.UI once components interested in
// track mute changes are moved into react. // track mute changes are moved into react.
if (typeof APP !== 'undefined' && !action.track.local) { if (typeof APP !== 'undefined') {
const { jitsiTrack } = action.track; const { jitsiTrack } = action.track;
const isMuted = jitsiTrack.isMuted(); const isMuted = jitsiTrack.isMuted();
const participantID = jitsiTrack.getParticipantId(); const participantID = jitsiTrack.getParticipantId();
const { videoType } = jitsiTrack; const isVideoTrack = jitsiTrack.isVideoTrack();
if (videoType) { if (jitsiTrack.isLocal()) {
if (isVideoTrack) {
APP.conference.videoMuted = isMuted;
} else {
APP.conference.audioMuted = isMuted;
}
}
if (isVideoTrack) {
APP.UI.setVideoMuted(participantID, isMuted); APP.UI.setVideoMuted(participantID, isMuted);
APP.UI.onPeerVideoTypeChanged( APP.UI.onPeerVideoTypeChanged(
participantID, jitsiTrack.videoType); participantID, jitsiTrack.videoType);