2017-10-04 22:36:09 +00:00
|
|
|
// @flow
|
|
|
|
|
2019-03-19 15:42:25 +00:00
|
|
|
import type { Dispatch } from 'redux';
|
|
|
|
|
2018-07-20 18:19:26 +00:00
|
|
|
import {
|
|
|
|
createSelectParticipantFailedEvent,
|
|
|
|
sendAnalytics
|
|
|
|
} from '../analytics';
|
2016-10-05 14:36:59 +00:00
|
|
|
import { _handleParticipantError } from '../base/conference';
|
2018-07-20 18:19:26 +00:00
|
|
|
import { MEDIA_TYPE } from '../base/media';
|
2018-08-08 18:48:23 +00:00
|
|
|
import { getParticipants } from '../base/participants';
|
2018-07-21 15:16:32 +00:00
|
|
|
import { reportError } from '../base/util';
|
2018-08-08 18:48:23 +00:00
|
|
|
import { shouldDisplayTileView } from '../video-layout';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2017-08-09 19:40:03 +00:00
|
|
|
import {
|
|
|
|
SELECT_LARGE_VIDEO_PARTICIPANT,
|
|
|
|
UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION
|
|
|
|
} from './actionTypes';
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2018-07-20 18:19:26 +00:00
|
|
|
declare var APP: Object;
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
/**
|
|
|
|
* Signals conference to select a participant.
|
|
|
|
*
|
|
|
|
* @returns {Function}
|
|
|
|
*/
|
|
|
|
export function selectParticipant() {
|
2019-03-19 15:42:25 +00:00
|
|
|
return (dispatch: Dispatch<any>, getState: Function) => {
|
2016-10-05 14:36:59 +00:00
|
|
|
const state = getState();
|
2017-10-04 22:36:09 +00:00
|
|
|
const { conference } = state['features/base/conference'];
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
if (conference) {
|
2018-08-08 18:48:23 +00:00
|
|
|
const ids = shouldDisplayTileView(state)
|
|
|
|
? getParticipants(state).map(participant => participant.id)
|
|
|
|
: [ state['features/large-video'].participantId ];
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
try {
|
2018-08-08 18:48:23 +00:00
|
|
|
conference.selectParticipants(ids);
|
2016-10-05 14:36:59 +00:00
|
|
|
} catch (err) {
|
|
|
|
_handleParticipantError(err);
|
2018-07-20 18:19:26 +00:00
|
|
|
|
|
|
|
sendAnalytics(createSelectParticipantFailedEvent(err));
|
|
|
|
|
2018-08-08 18:48:23 +00:00
|
|
|
reportError(
|
|
|
|
err, `Failed to select participants ${ids.toString()}`);
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Action to select the participant to be displayed in LargeVideo based on a
|
2018-11-08 12:25:02 +00:00
|
|
|
* variety of factors: If there is a dominant or pinned speaker, or if there are
|
2017-01-17 14:32:20 +00:00
|
|
|
* remote tracks, etc.
|
2016-10-05 14:36:59 +00:00
|
|
|
*
|
|
|
|
* @returns {Function}
|
|
|
|
*/
|
|
|
|
export function selectParticipantInLargeVideo() {
|
2019-03-19 15:42:25 +00:00
|
|
|
return (dispatch: Dispatch<any>, getState: Function) => {
|
2016-10-05 14:36:59 +00:00
|
|
|
const state = getState();
|
2016-11-04 18:28:47 +00:00
|
|
|
const participantId = _electParticipantInLargeVideo(state);
|
2017-01-17 14:44:50 +00:00
|
|
|
const largeVideo = state['features/large-video'];
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
if (participantId !== largeVideo.participantId) {
|
|
|
|
dispatch({
|
2017-01-11 18:14:00 +00:00
|
|
|
type: SELECT_LARGE_VIDEO_PARTICIPANT,
|
2016-10-05 14:36:59 +00:00
|
|
|
participantId
|
|
|
|
});
|
|
|
|
|
|
|
|
dispatch(selectParticipant());
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2017-08-09 19:40:03 +00:00
|
|
|
/**
|
|
|
|
* Updates the currently seen resolution of the video displayed on large video.
|
|
|
|
*
|
|
|
|
* @param {number} resolution - The current resolution (height) of the video.
|
|
|
|
* @returns {{
|
|
|
|
* type: UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION,
|
|
|
|
* resolution: number
|
|
|
|
* }}
|
|
|
|
*/
|
2017-10-04 22:36:09 +00:00
|
|
|
export function updateKnownLargeVideoResolution(resolution: number) {
|
2017-08-09 19:40:03 +00:00
|
|
|
return {
|
|
|
|
type: UPDATE_KNOWN_LARGE_VIDEO_RESOLUTION,
|
|
|
|
resolution
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2016-10-05 14:36:59 +00:00
|
|
|
/**
|
2017-10-06 19:43:58 +00:00
|
|
|
* Returns the most recent existing remote video track.
|
2016-10-05 14:36:59 +00:00
|
|
|
*
|
|
|
|
* @param {Track[]} tracks - All current tracks.
|
|
|
|
* @private
|
|
|
|
* @returns {(Track|undefined)}
|
|
|
|
*/
|
2017-10-06 19:43:58 +00:00
|
|
|
function _electLastVisibleRemoteVideo(tracks) {
|
2016-10-05 14:36:59 +00:00
|
|
|
// First we try to get most recent remote video track.
|
2016-11-04 18:28:47 +00:00
|
|
|
for (let i = tracks.length - 1; i >= 0; --i) {
|
|
|
|
const track = tracks[i];
|
|
|
|
|
|
|
|
if (!track.local && track.mediaType === MEDIA_TYPE.VIDEO) {
|
|
|
|
return track;
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-11-08 12:25:02 +00:00
|
|
|
* Returns the identifier of the participant who is to be on the stage and
|
2017-10-01 06:35:19 +00:00
|
|
|
* should be displayed in {@code LargeVideo}.
|
2016-10-05 14:36:59 +00:00
|
|
|
*
|
2016-11-04 18:28:47 +00:00
|
|
|
* @param {Object} state - The Redux state from which the participant to be
|
2017-10-01 06:35:19 +00:00
|
|
|
* displayed in {@code LargeVideo} is to be elected.
|
2016-10-05 14:36:59 +00:00
|
|
|
* @private
|
|
|
|
* @returns {(string|undefined)}
|
|
|
|
*/
|
2016-11-04 18:28:47 +00:00
|
|
|
function _electParticipantInLargeVideo(state) {
|
2017-10-09 15:03:02 +00:00
|
|
|
// 1. If a participant is pinned, they will be shown in the LargeVideo (
|
|
|
|
// regardless of whether they are local or remote).
|
2016-11-04 18:28:47 +00:00
|
|
|
const participants = state['features/base/participants'];
|
2016-10-05 14:36:59 +00:00
|
|
|
let participant = participants.find(p => p.pinned);
|
2017-10-06 19:43:58 +00:00
|
|
|
let id = participant && participant.id;
|
2016-10-05 14:36:59 +00:00
|
|
|
|
|
|
|
if (!id) {
|
2017-10-09 15:03:02 +00:00
|
|
|
// 2. No participant is pinned so get the dominant speaker. But the
|
|
|
|
// local participant won't be displayed in LargeVideo even if she is
|
|
|
|
// the dominant speaker.
|
2016-11-04 18:28:47 +00:00
|
|
|
participant = participants.find(p => p.dominantSpeaker && !p.local);
|
2017-10-06 19:43:58 +00:00
|
|
|
id = participant && participant.id;
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2016-11-04 18:28:47 +00:00
|
|
|
if (!id) {
|
2017-10-09 15:03:02 +00:00
|
|
|
// 3. There is no dominant speaker so select the remote participant
|
|
|
|
// who last had visible video.
|
2016-11-04 18:28:47 +00:00
|
|
|
const tracks = state['features/base/tracks'];
|
2017-10-06 19:43:58 +00:00
|
|
|
const videoTrack = _electLastVisibleRemoteVideo(tracks);
|
2016-10-05 14:36:59 +00:00
|
|
|
|
2016-11-04 18:28:47 +00:00
|
|
|
id = videoTrack && videoTrack.participantId;
|
2017-10-06 19:43:58 +00:00
|
|
|
|
|
|
|
if (!id) {
|
2017-10-09 15:03:02 +00:00
|
|
|
// 4. It's possible there is no participant with visible video.
|
|
|
|
// This can happen for a number of reasons:
|
|
|
|
// - there is only one participant (i.e. the local user),
|
|
|
|
// - other participants joined with video muted.
|
|
|
|
// As a last resort, pick the last participant who joined the
|
|
|
|
// conference (regardless of whether they are local or
|
|
|
|
// remote).
|
2019-06-06 12:00:15 +00:00
|
|
|
//
|
|
|
|
// HOWEVER: We don't want to show poltergeist or other bot type participants on stage
|
|
|
|
// automatically, because it's misleading (users may think they are already
|
|
|
|
// joined and maybe speaking).
|
|
|
|
for (let i = participants.length; i > 0 && !participant; i--) {
|
|
|
|
const p = participants[i - 1];
|
|
|
|
|
|
|
|
!p.botType && (participant = p);
|
|
|
|
}
|
|
|
|
|
2017-10-06 19:43:58 +00:00
|
|
|
id = participant && participant.id;
|
|
|
|
}
|
2016-11-04 18:28:47 +00:00
|
|
|
}
|
2016-10-05 14:36:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return id;
|
|
|
|
}
|