/* eslint-disable no-nested-ternary */
import logger from '../../logger';
import MESSAGES from '../../messages';
import {
isABoolean, isANumber, isAObj, hasAudioTrack, hasVideoTrack,
} from '../../utils/helpers';
import SkylinkSignalingServer from '../../server-communication/signaling-server/index';
import { localMediaMuted, peerUpdated, streamMuted } from '../../skylink-events';
import { dispatchEvent } from '../../utils/skylinkEventManager';
import PeerData from '../../peer-data/index';
import Skylink from '../../index';
import {
MEDIA_STATUS, MEDIA_INFO, MEDIA_STATE, TRACK_KIND,
} from '../../constants';
import PeerMedia from '../../peer-media/index';
const dispatchStreamMutedEvent = (room, streamId, isScreensharing) => {
const roomState = Skylink.getSkylinkState(room.id);
dispatchEvent(streamMuted({
isSelf: true,
peerId: roomState.user.sid,
peerInfo: PeerData.getUserInfo(room),
streamId,
isScreensharing,
}));
};
const dispatchPeerUpdatedEvent = (room) => {
const roomState = Skylink.getSkylinkState(room.id);
const isSelf = true;
const peerId = roomState.user.sid;
const peerInfo = PeerData.getCurrentSessionInfo(room);
dispatchEvent(peerUpdated({
isSelf,
peerId,
peerInfo,
}));
};
const getAudioTracks = stream => stream.getAudioTracks();
const getVideoTracks = stream => stream.getVideoTracks();
const dispatchLocalMediaMutedEvent = (hasToggledVideo, hasToggledAudio, stream, roomKey, isScreensharing = false) => {
const state = Skylink.getSkylinkState(roomKey);
if ((hasVideoTrack(stream) && hasToggledVideo) || (hasAudioTrack(stream) && hasToggledAudio)) {
dispatchEvent(localMediaMuted({
streamId: stream.id,
isScreensharing,
mediaStatus: state.streamsMediaStatus[stream.id],
}));
}
return true;
};
const retrieveOriginalActiveStreamId = (roomState, currentActiveStreamId, replacedStreamIds) => {
let originalActiveStreamId = currentActiveStreamId;
const { streams: { userMedia } } = roomState;
const pReplacedStreamIds = replacedStreamIds || Object.keys(userMedia).filter(streamId => userMedia[streamId].isReplaced);
if (pReplacedStreamIds.length === 0) {
return originalActiveStreamId;
}
if (pReplacedStreamIds.indexOf(originalActiveStreamId) > -1) {
pReplacedStreamIds.splice(pReplacedStreamIds.indexOf(originalActiveStreamId), 1);
}
if (pReplacedStreamIds.length > 1) {
for (let i = 0; i < pReplacedStreamIds.length; i += 1) {
if (userMedia[pReplacedStreamIds[i]].newStream && userMedia[pReplacedStreamIds[i]].newStream.id === originalActiveStreamId) {
originalActiveStreamId = pReplacedStreamIds[i];
retrieveOriginalActiveStreamId(roomState, originalActiveStreamId, pReplacedStreamIds);
break;
}
}
}
return pReplacedStreamIds[0];
};
const updateMediaInfo = (hasToggledVideo, hasToggledAudio, room, streamId) => {
const roomState = Skylink.getSkylinkState(room.id);
const originalStreamId = retrieveOriginalActiveStreamId(roomState, streamId);
const { streamsMutedSettings } = roomState;
if (hasToggledVideo) {
const mediaId = PeerMedia.retrieveMediaId(TRACK_KIND.VIDEO, originalStreamId);
PeerMedia.updatePeerMediaInfo(room, roomState.user.sid, mediaId, MEDIA_INFO.MEDIA_STATE, streamsMutedSettings[originalStreamId].videoMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.ACTIVE);
}
if (hasToggledAudio) {
const mediaId = PeerMedia.retrieveMediaId(TRACK_KIND.AUDIO, originalStreamId);
setTimeout(() => PeerMedia.updatePeerMediaInfo(room, roomState.user.sid, mediaId, MEDIA_INFO.MEDIA_STATE, streamsMutedSettings[originalStreamId].audioMuted ? MEDIA_STATE.MUTED : MEDIA_STATE.ACTIVE), hasToggledVideo ? 1050 : 0);
}
};
const sendSigMsgs = (hasToggledVideo, hasToggledAudio, room, streamId) => {
const roomState = Skylink.getSkylinkState(room.id);
const signaling = new SkylinkSignalingServer();
const originalStreamId = retrieveOriginalActiveStreamId(roomState, streamId);
if (hasToggledVideo) {
signaling.muteVideoEvent(room, originalStreamId);
}
if (hasToggledAudio) {
setTimeout(() => signaling.muteAudioEvent(room, originalStreamId), hasToggledVideo ? 1050 : 0);
}
};
// TODO: check if this is needed since Edge is not built on Chrome
// const muteForEdge = (roomState, streamId, hasToggledVideo, hasToggledAudio) => {
// const { peerConnections } = roomState;
// const peerIds = Object.keys(peerConnections);
// peerIds.forEach((peerId) => {
// const localStreams = peerConnections[peerId].getLocalStreams();
// if (streamId) {
// for (let i = 0; i < localStreams.length; i += 1) {
// if (streamId === localStreams[i].id) {
// muteFn(localStreams[i], roomState);
// dispatchLocalMediaMutedEvent(hasToggledVideo, hasToggledAudio, localStreams[i], roomState);
// sendMuteAudioAndMuteVideoSigMsg(hasToggledVideo, hasToggledAudio, roomState, streamId);
// break;
// }
// }
// } else {
// localStreams.forEach((stream, i) => {
// muteFn(stream, roomState);
// dispatchLocalMediaMutedEvent(hasToggledVideo, hasToggledAudio, stream, roomState);
// setTimeout(sendMuteAudioAndMuteVideoSigMsg, i === 0 ? 0 : 1000);
// });
// }
// });
// };
const muteFn = (stream, state) => {
const updatedState = state;
const { room } = updatedState;
const audioTracks = getAudioTracks(stream);
const videoTracks = getVideoTracks(stream);
updatedState.streamsMediaStatus[stream.id].audioMuted = MEDIA_STATUS.UNAVAILABLE;
updatedState.streamsMediaStatus[stream.id].videoMuted = MEDIA_STATUS.UNAVAILABLE;
audioTracks.forEach((audioTrack) => {
// eslint-disable-next-line no-param-reassign
audioTrack.enabled = !updatedState.streamsMutedSettings[stream.id].audioMuted;
updatedState.streamsMediaStatus[stream.id].audioMuted = updatedState.streamsMutedSettings[stream.id].audioMuted ? MEDIA_STATUS.MUTED : MEDIA_STATUS.ACTIVE;
});
videoTracks.forEach((videoTrack) => {
// eslint-disable-next-line no-param-reassign
videoTrack.enabled = !updatedState.streamsMutedSettings[stream.id].videoMuted;
updatedState.streamsMediaStatus[stream.id].videoMuted = updatedState.streamsMutedSettings[stream.id].videoMuted ? MEDIA_STATUS.MUTED : MEDIA_STATUS.ACTIVE;
});
Skylink.setSkylinkState(updatedState, room.id);
logger.log.DEBUG(MESSAGES.MEDIA_STREAM.UPDATE_MEDIA_STATUS, updatedState.streamsMediaStatus, stream.id);
};
const retrieveToggleState = (state, options, streamId) => {
const { streams, streamsMutedSettings } = state;
let hasToggledAudio = false;
let hasToggledVideo = false;
if (hasAudioTrack(streams.userMedia[streamId].stream) && streamsMutedSettings[streamId].audioMuted !== options.audioMuted) {
hasToggledAudio = true;
}
if (hasVideoTrack(streams.userMedia[streamId].stream) && streamsMutedSettings[streamId].videoMuted !== options.videoMuted) {
hasToggledVideo = true;
}
return {
hasToggledAudio,
hasToggledVideo,
};
};
const updateStreamsMutedSettings = (state, toggleState, streamId) => {
const updatedState = state;
const { room } = updatedState;
if (toggleState.hasToggledAudio) {
updatedState.streamsMutedSettings[streamId].audioMuted = !updatedState.streamsMutedSettings[streamId].audioMuted;
}
if (toggleState.hasToggledVideo) {
updatedState.streamsMutedSettings[streamId].videoMuted = !updatedState.streamsMutedSettings[streamId].videoMuted;
}
logger.log.DEBUG(MESSAGES.MEDIA_STREAM.UPDATE_MUTED_SETTINGS, updatedState.streamsMutedSettings, streamId);
Skylink.setSkylinkState(updatedState, room.id);
};
const startMuteEvents = (roomKey, streamId, options) => {
const state = Skylink.getSkylinkState(roomKey);
const { streams, room } = state;
const toggleState = retrieveToggleState(state, options, streamId);
const { hasToggledAudio, hasToggledVideo } = toggleState;
if (streams.userMedia) {
updateStreamsMutedSettings(state, toggleState, streamId);
muteFn(streams.userMedia[streamId].stream, state);
dispatchLocalMediaMutedEvent(hasToggledVideo, hasToggledAudio, streams.userMedia[streamId].stream, room.id);
dispatchPeerUpdatedEvent(room); // TODO: Currently peerUpdatedEvent will fire after each stream is updated. Suggest to refactor to have last stream trigger peerUpdatedEvent after a timeout since only one peerUpdatedEvent is needed
dispatchStreamMutedEvent(room, streamId);
sendSigMsgs(hasToggledVideo, hasToggledAudio, room, streamId);
updateMediaInfo(hasToggledVideo, hasToggledAudio, room, streamId);
}
if (streams.screenshare) {
if ((streamId && streams.screenshare.stream.id === streamId) || !streamId) {
updateStreamsMutedSettings(state, toggleState, streamId);
muteFn(streams.screenshare.stream, state);
dispatchLocalMediaMutedEvent(hasToggledVideo, hasToggledAudio, streams.screenshare.stream, room.id, true);
dispatchPeerUpdatedEvent(room);
dispatchStreamMutedEvent(room, streamId, true);
sendSigMsgs(hasToggledVideo, hasToggledAudio, room, streamId);
updateMediaInfo(hasToggledVideo, hasToggledAudio, room, streamId);
}
}
};
const retrieveMutedSetting = (mediaMutedOption) => {
switch (mediaMutedOption) {
case 1:
return false;
case 0:
return true;
default:
return true;
}
};
const isValidStreamId = (streamId, state) => {
const { streams } = state;
let isValid = false;
Object.keys(streams.userMedia).forEach((gumStreamId) => {
if (gumStreamId === streamId) {
isValid = true;
}
});
return isValid;
};
/**
* @param {SkylinkState} roomState
* @param {boolean} options
* @param {boolean} options.audioMuted
* @param {boolean} options.videoMuted
* @param {String} streamId
* @memberOf MediaStreamHelpers
* @fires streamMuted, peerUpdated, localMediaMuted
*/
const muteStreams = (roomState, options, streamId = null) => {
const {
streams, room,
} = roomState;
if (!isAObj(options)) {
logger.log.ERROR(MESSAGES.MEDIA_STREAM.ERRORS.INVALID_MUTE_OPTIONS, options);
return;
}
if (!streams.userMedia && !streams.screenshare) {
logger.log.WARN(MESSAGES.MEDIA_STREAM.ERRORS.NO_STREAM);
return;
}
if (streamId && !isValidStreamId(streamId, roomState)) {
logger.log.ERROR(MESSAGES.MEDIA_STREAM.ERRORS.INVALID_MUTE_OPTIONS, options);
return;
}
const fOptions = {
audioMuted: isABoolean(options.audioMuted) ? options.audioMuted : (isANumber(options.audioMuted) ? retrieveMutedSetting(options.audioMuted) : true),
videoMuted: isABoolean(options.videoMuted) ? options.videoMuted : (isANumber(options.videoMuted) ? retrieveMutedSetting(options.videoMuted) : true),
};
const streamIdsThatCanBeMuted = streamId ? [streamId] : Object.keys(streams.userMedia).filter(id => !streams.userMedia[id].isReplaced);
const streamIdsToMute = Object.values(streamIdsThatCanBeMuted).filter(sId => (retrieveToggleState(roomState, fOptions, sId).hasToggledAudio || retrieveToggleState(roomState, fOptions, sId).hasToggledVideo));
streamIdsToMute.forEach((streamIdToMute, i) => {
setTimeout(() => startMuteEvents(room.id, streamIdToMute, fOptions), i === 0 ? 0 : 1050);
// TODO: Implement peerUpdatedEvent timeout here?
});
};
export default muteStreams;