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:
parent
2a446b8799
commit
fe4de31e57
|
@ -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;
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue