2020-05-15 00:36:03 +08:00
|
|
|
import logger from '/imports/startup/client/logger';
|
2020-12-10 06:00:54 +08:00
|
|
|
import { fetchWebRTCMappedStunTurnServers, getMappedFallbackStun } from '/imports/utils/fetchStunTurnServers';
|
|
|
|
import loadAndPlayMediaStream from '/imports/ui/services/bbb-webrtc-sfu/load-play';
|
2021-01-12 23:24:01 +08:00
|
|
|
import { SCREENSHARING_ERRORS } from './errors';
|
2024-06-14 21:30:48 +08:00
|
|
|
import getFromMeetingSettings, { getVoiceConf } from '/imports/ui/services/meeting-settings';
|
2020-05-15 00:36:03 +08:00
|
|
|
|
2021-01-12 23:24:01 +08:00
|
|
|
const HAS_DISPLAY_MEDIA = (typeof navigator.getDisplayMedia === 'function'
|
2020-05-15 00:36:03 +08:00
|
|
|
|| (navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function'));
|
2017-07-25 03:29:34 +08:00
|
|
|
|
2024-06-14 21:30:48 +08:00
|
|
|
const getConferenceBridge = () => getVoiceConf();
|
2017-07-25 03:29:34 +08:00
|
|
|
|
2021-01-12 23:24:01 +08:00
|
|
|
const normalizeGetDisplayMediaError = (error) => {
|
|
|
|
return SCREENSHARING_ERRORS[error.name] || SCREENSHARING_ERRORS.GetDisplayMediaGenericError;
|
|
|
|
};
|
|
|
|
|
2021-01-12 22:40:54 +08:00
|
|
|
const getBoundGDM = () => {
|
|
|
|
if (typeof navigator.getDisplayMedia === 'function') {
|
|
|
|
return navigator.getDisplayMedia.bind(navigator);
|
|
|
|
} else if (navigator.mediaDevices && typeof navigator.mediaDevices.getDisplayMedia === 'function') {
|
|
|
|
return navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-05-15 00:36:03 +08:00
|
|
|
const getScreenStream = async () => {
|
2024-07-03 01:58:58 +08:00
|
|
|
const {
|
|
|
|
constraints: GDM_CONSTRAINTS,
|
|
|
|
} = window.meetingClientSettings.public.kurento.screenshare;
|
|
|
|
|
2020-05-15 00:36:03 +08:00
|
|
|
const gDMCallback = (stream) => {
|
2020-12-10 06:00:54 +08:00
|
|
|
// Some older Chromium variants choke on gDM when audio: true by NOT generating
|
|
|
|
// a promise rejection AND not generating a valid input screen stream, need to
|
|
|
|
// work around that manually for now - prlanzarin
|
|
|
|
if (stream == null) {
|
2021-01-12 23:24:01 +08:00
|
|
|
return Promise.reject(SCREENSHARING_ERRORS.NotSupportedError);
|
2020-12-10 06:00:54 +08:00
|
|
|
}
|
|
|
|
|
2020-05-15 00:36:03 +08:00
|
|
|
if (typeof stream.getVideoTracks === 'function'
|
2021-01-12 22:40:54 +08:00
|
|
|
&& typeof GDM_CONSTRAINTS.video === 'object') {
|
2020-05-15 00:36:03 +08:00
|
|
|
stream.getVideoTracks().forEach(track => {
|
|
|
|
if (typeof track.applyConstraints === 'function') {
|
2021-01-12 22:40:54 +08:00
|
|
|
track.applyConstraints(GDM_CONSTRAINTS.video).catch(error => {
|
2020-05-15 00:36:03 +08:00
|
|
|
logger.warn({
|
|
|
|
logCode: 'screenshare_videoconstraint_failed',
|
|
|
|
extraInfo: { errorName: error.name, errorCode: error.code },
|
|
|
|
},
|
2020-12-10 06:00:54 +08:00
|
|
|
'Error applying screenshare video constraint');
|
2020-05-15 00:36:03 +08:00
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof stream.getAudioTracks === 'function'
|
2021-01-12 22:40:54 +08:00
|
|
|
&& typeof GDM_CONSTRAINTS.audio === 'object') {
|
2020-05-15 00:36:03 +08:00
|
|
|
stream.getAudioTracks().forEach(track => {
|
|
|
|
if (typeof track.applyConstraints === 'function') {
|
2021-01-12 22:40:54 +08:00
|
|
|
track.applyConstraints(GDM_CONSTRAINTS.audio).catch(error => {
|
2020-05-15 00:36:03 +08:00
|
|
|
logger.warn({
|
|
|
|
logCode: 'screenshare_audioconstraint_failed',
|
|
|
|
extraInfo: { errorName: error.name, errorCode: error.code },
|
|
|
|
}, 'Error applying screenshare audio constraint');
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.resolve(stream);
|
2020-05-26 01:32:24 +08:00
|
|
|
};
|
2020-05-15 00:36:03 +08:00
|
|
|
|
2021-01-12 22:40:54 +08:00
|
|
|
const getDisplayMedia = getBoundGDM();
|
2020-05-15 00:36:03 +08:00
|
|
|
|
2021-01-12 22:40:54 +08:00
|
|
|
if (typeof getDisplayMedia === 'function') {
|
|
|
|
return getDisplayMedia(GDM_CONSTRAINTS)
|
2020-05-26 01:32:24 +08:00
|
|
|
.then(gDMCallback)
|
2021-01-12 22:40:54 +08:00
|
|
|
.catch(error => {
|
2021-01-12 23:24:01 +08:00
|
|
|
const normalizedError = normalizeGetDisplayMediaError(error);
|
2020-05-26 01:32:24 +08:00
|
|
|
logger.error({
|
|
|
|
logCode: 'screenshare_getdisplaymedia_failed',
|
2021-01-12 23:24:01 +08:00
|
|
|
extraInfo: { errorCode: normalizedError.errorCode, errorMessage: normalizedError.errorMessage },
|
2020-05-26 01:32:24 +08:00
|
|
|
}, 'getDisplayMedia call failed');
|
2021-01-12 23:24:01 +08:00
|
|
|
return Promise.reject(normalizedError);
|
2020-05-26 01:32:24 +08:00
|
|
|
});
|
2020-12-10 06:00:54 +08:00
|
|
|
} else {
|
|
|
|
// getDisplayMedia isn't supported, error its way out
|
2021-01-12 23:24:01 +08:00
|
|
|
return Promise.reject(SCREENSHARING_ERRORS.NotSupportedError);
|
2020-05-26 01:32:24 +08:00
|
|
|
}
|
|
|
|
};
|
2020-05-15 00:36:03 +08:00
|
|
|
|
2020-12-10 06:00:54 +08:00
|
|
|
const getIceServers = (sessionToken) => {
|
|
|
|
return fetchWebRTCMappedStunTurnServers(sessionToken).catch(error => {
|
|
|
|
logger.error({
|
|
|
|
logCode: 'screenshare_fetchstunturninfo_error',
|
|
|
|
extraInfo: { error }
|
|
|
|
}, 'Screenshare bridge failed to fetch STUN/TURN info');
|
|
|
|
return getMappedFallbackStun();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2021-04-27 02:39:29 +08:00
|
|
|
const getMediaServerAdapter = () => {
|
2024-07-03 01:58:58 +08:00
|
|
|
const {
|
|
|
|
mediaServer: DEFAULT_SCREENSHARE_MEDIA_SERVER,
|
|
|
|
} = window.meetingClientSettings.public.kurento.screenshare;
|
2021-04-27 05:05:06 +08:00
|
|
|
return getFromMeetingSettings('media-server-screenshare', DEFAULT_SCREENSHARE_MEDIA_SERVER);
|
2021-04-27 02:39:29 +08:00
|
|
|
}
|
|
|
|
|
2020-12-10 06:00:54 +08:00
|
|
|
const getNextReconnectionInterval = (oldInterval) => {
|
2024-07-03 01:58:58 +08:00
|
|
|
const {
|
|
|
|
mediaTimeouts: MEDIA_TIMEOUTS,
|
|
|
|
} = window.meetingClientSettings.public.kurento.screenshare;
|
|
|
|
const {
|
|
|
|
maxTimeout: MAX_MEDIA_TIMEOUT,
|
|
|
|
timeoutIncreaseFactor: TIMEOUT_INCREASE_FACTOR,
|
|
|
|
baseReconnectionTimeout: BASE_RECONNECTION_TIMEOUT,
|
|
|
|
} = MEDIA_TIMEOUTS;
|
2020-12-10 06:00:54 +08:00
|
|
|
return Math.min(
|
2023-02-16 22:37:00 +08:00
|
|
|
(TIMEOUT_INCREASE_FACTOR * Math.max(oldInterval, BASE_RECONNECTION_TIMEOUT)),
|
2020-12-10 06:00:54 +08:00
|
|
|
MAX_MEDIA_TIMEOUT,
|
|
|
|
);
|
2020-05-15 00:36:03 +08:00
|
|
|
}
|
|
|
|
|
2020-12-10 06:00:54 +08:00
|
|
|
const streamHasAudioTrack = (stream) => {
|
|
|
|
return stream
|
|
|
|
&& typeof stream.getAudioTracks === 'function'
|
|
|
|
&& stream.getAudioTracks().length >= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
const dispatchAutoplayHandlingEvent = (mediaElement) => {
|
|
|
|
const tagFailedEvent = new CustomEvent('screensharePlayFailed',
|
|
|
|
{ detail: { mediaElement } });
|
|
|
|
window.dispatchEvent(tagFailedEvent);
|
|
|
|
}
|
|
|
|
|
|
|
|
const screenshareLoadAndPlayMediaStream = (stream, mediaElement, muted) => {
|
|
|
|
return loadAndPlayMediaStream(stream, mediaElement, muted).catch(error => {
|
|
|
|
// NotAllowedError equals autoplay issues, fire autoplay handling event.
|
|
|
|
// This will be handled in the screenshare react component.
|
|
|
|
if (error.name === 'NotAllowedError') {
|
|
|
|
logger.error({
|
|
|
|
logCode: 'screenshare_error_autoplay',
|
|
|
|
extraInfo: { errorName: error.name },
|
|
|
|
}, 'Screen share media play failed: autoplay error');
|
|
|
|
dispatchAutoplayHandlingEvent(mediaElement);
|
|
|
|
} else {
|
2021-01-12 23:24:01 +08:00
|
|
|
throw {
|
|
|
|
errorCode: SCREENSHARING_ERRORS.SCREENSHARE_PLAY_FAILED.errorCode,
|
|
|
|
errorMessage: error.message || SCREENSHARING_ERRORS.SCREENSHARE_PLAY_FAILED.errorMessage,
|
2020-12-10 06:00:54 +08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
2020-05-15 00:36:03 +08:00
|
|
|
|
2024-07-03 01:58:58 +08:00
|
|
|
class EXPORTED_CONFIGS {
|
|
|
|
static BASE_MEDIA_TIMEOUT() {
|
|
|
|
const {
|
|
|
|
mediaTimeouts: MEDIA_TIMEOUTS,
|
|
|
|
} = window.meetingClientSettings.public.kurento.screenshare;
|
|
|
|
const {
|
|
|
|
baseTimeout: BASE_MEDIA_TIMEOUT,
|
|
|
|
} = MEDIA_TIMEOUTS;
|
|
|
|
return BASE_MEDIA_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static BASE_RECONNECTION_TIMEOUT() {
|
|
|
|
const {
|
|
|
|
mediaTimeouts: MEDIA_TIMEOUTS,
|
|
|
|
} = window.meetingClientSettings.public.kurento.screenshare;
|
|
|
|
const {
|
|
|
|
baseReconnectionTimeout: BASE_RECONNECTION_TIMEOUT,
|
|
|
|
} = MEDIA_TIMEOUTS;
|
|
|
|
return BASE_RECONNECTION_TIMEOUT;
|
|
|
|
}
|
|
|
|
|
|
|
|
static MAX_CONN_ATTEMPTS() {
|
|
|
|
const {
|
|
|
|
mediaTimeouts: MEDIA_TIMEOUTS,
|
|
|
|
} = window.meetingClientSettings.public.kurento.screenshare;
|
|
|
|
const {
|
|
|
|
maxConnectionAttempts: MAX_CONN_ATTEMPTS,
|
|
|
|
} = MEDIA_TIMEOUTS;
|
|
|
|
return MAX_CONN_ATTEMPTS;
|
|
|
|
}
|
|
|
|
|
|
|
|
static BASE_BITRATE() {
|
|
|
|
const {
|
|
|
|
bitrate: BASE_BITRATE,
|
|
|
|
} = window.meetingClientSettings.public.kurento.screenshare;
|
|
|
|
return BASE_BITRATE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-07-25 03:29:34 +08:00
|
|
|
export default {
|
2021-01-12 23:24:01 +08:00
|
|
|
HAS_DISPLAY_MEDIA,
|
2017-07-25 03:29:34 +08:00
|
|
|
getConferenceBridge,
|
2020-05-15 00:36:03 +08:00
|
|
|
getScreenStream,
|
2020-12-10 06:00:54 +08:00
|
|
|
getIceServers,
|
|
|
|
getNextReconnectionInterval,
|
|
|
|
streamHasAudioTrack,
|
|
|
|
screenshareLoadAndPlayMediaStream,
|
2021-04-27 02:39:29 +08:00
|
|
|
getMediaServerAdapter,
|
2024-07-03 01:58:58 +08:00
|
|
|
BASE_MEDIA_TIMEOUT: EXPORTED_CONFIGS.BASE_MEDIA_TIMEOUT,
|
|
|
|
BASE_RECONNECTION_TIMEOUT: EXPORTED_CONFIGS.BASE_RECONNECTION_TIMEOUT,
|
|
|
|
MAX_CONN_ATTEMPTS: EXPORTED_CONFIGS.MAX_CONN_ATTEMPTS,
|
|
|
|
BASE_BITRATE: EXPORTED_CONFIGS.BASE_BITRATE,
|
2017-07-25 03:29:34 +08:00
|
|
|
};
|