123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631 |
- import Logger from '@jitsi/logger';
- import { merge } from 'lodash-es';
-
- import JitsiConference from './JitsiConference';
- import * as JitsiConferenceErrors from './JitsiConferenceErrors';
- import * as JitsiConferenceEvents from './JitsiConferenceEvents';
- import JitsiConnection from './JitsiConnection';
- import * as JitsiConnectionErrors from './JitsiConnectionErrors';
- import { JitsiConnectionEvents } from './JitsiConnectionEvents';
- import JitsiMediaDevices from './JitsiMediaDevices';
- import * as JitsiMediaDevicesEvents from './JitsiMediaDevicesEvents';
- import JitsiTrackError from './JitsiTrackError';
- import * as JitsiTrackErrors from './JitsiTrackErrors';
- import * as JitsiTrackEvents from './JitsiTrackEvents';
- import * as JitsiTranscriptionStatus from './JitsiTranscriptionStatus';
- import JitsiLocalTrack from './modules/RTC/JitsiLocalTrack';
- import RTC from './modules/RTC/RTC';
- import RTCStats from './modules/RTCStats/RTCStats';
- import * as RTCStatsEvents from './modules/RTCStats/RTCStatsEvents';
- import browser from './modules/browser';
- import NetworkInfo from './modules/connectivity/NetworkInfo';
- import { TrackStreamingStatus } from './modules/connectivity/TrackStreamingStatus';
- import getActiveAudioDevice from './modules/detection/ActiveDeviceDetector';
- import * as DetectionEvents from './modules/detection/DetectionEvents';
- import TrackVADEmitter from './modules/detection/TrackVADEmitter';
- import ProxyConnectionService
- from './modules/proxyconnection/ProxyConnectionService';
- import recordingConstants from './modules/recording/recordingConstants';
- import Settings from './modules/settings/Settings';
- import LocalStatsCollector from './modules/statistics/LocalStatsCollector';
- import runPreCallTest, { IIceServer, IPreCallResult } from './modules/statistics/PreCallTest';
- import Statistics from './modules/statistics/statistics';
- import Deferred from './modules/util/Deferred';
- import ScriptUtil from './modules/util/ScriptUtil';
- import * as VideoSIPGWConstants from './modules/videosipgw/VideoSIPGWConstants';
- import AudioMixer from './modules/webaudio/AudioMixer';
- import { MediaType } from './service/RTC/MediaType';
- import { VideoType } from './service/RTC/VideoType';
- import * as ConnectionQualityEvents
- from './service/connectivity/ConnectionQualityEvents';
- import * as E2ePingEvents from './service/e2eping/E2ePingEvents';
- import { createGetUserMediaEvent } from './service/statistics/AnalyticsEvents';
- import { COMMIT_HASH } from './version';
-
- const logger = Logger.getLogger('JitsiMeetJS');
-
- // Settin the default log levels to info early so that we avoid overriding a log level set externally.
- Logger.setLogLevel(Logger.levels.INFO);
-
- /**
- * Indicates whether GUM has been executed or not.
- */
- let hasGUMExecuted = false;
-
- /**
- * Extracts from an 'options' objects with a specific format (TODO what IS the
- * format?) the attributes which are to be logged in analytics events.
- *
- * @param options gum options (???)
- * @returns {*} the attributes to attach to analytics events.
- */
- function getAnalyticsAttributesFromOptions(options) {
- const attributes: any = {};
-
- /* eslint-disable */
-
- attributes.audio_requested = options.devices.includes(MediaType.AUDIO);
- attributes.video_requested = options.devices.includes(MediaType.VIDEO);
- attributes.screen_sharing_requested = options.devices.includes(VideoType.DESKTOP);
-
- if (attributes.video_requested) {
- attributes.resolution = options.resolution;
- }
-
- /* eslint-enable */
-
- return attributes;
- }
-
- interface ICreateLocalTrackOptions {
- cameraDeviceId?: string;
- devices?: any[];
- fireSlowPromiseEvent?: boolean;
- micDeviceId?: string;
- resolution?: string;
- }
-
- type desktopSharingSourceType = 'screen' | 'window';
-
- interface IJitsiMeetJSOptions {
- analytics?: {
- rtcstatsLogFlushSizeBytes?: number;
- rtcstatsStoreLogs?: boolean;
- };
- desktopSharingSources?: Array<desktopSharingSourceType>;
- enableAnalyticsLogging?: boolean;
- enableWindowOnErrorHandler?: boolean;
- externalStorage?: Storage;
- flags?: {
- runInLiteMode?: boolean;
- ssrcRewritingEnabled?: boolean;
- };
- }
-
- interface ICreateLocalTrackFromMediaStreamOptions {
- mediaType: MediaType;
- sourceType: string;
- stream: MediaStream;
- track: any;
- videoType?: VideoType;
- }
-
- export interface IJoinConferenceOptions {
- conferenceOptions?: any;
- connectionOptions?: any;
- jaas?: {
- release?: boolean;
- useStaging?: boolean;
- };
- tracks?: JitsiLocalTrack[];
- }
-
- /**
- * The public API of the Jitsi Meet library (a.k.a. {@code JitsiMeetJS}).
- */
- export default {
-
- version: COMMIT_HASH,
-
- JitsiConnection,
-
- /**
- * {@code ProxyConnectionService} is used to connect a remote peer to a
- * local Jitsi participant without going through a Jitsi conference. It is
- * currently used for room integration development, specifically wireless
- * screensharing. Its API is experimental and will likely change; usage of
- * it is advised against.
- */
- ProxyConnectionService,
-
- constants: {
- recording: recordingConstants,
- sipVideoGW: VideoSIPGWConstants,
- transcriptionStatus: JitsiTranscriptionStatus,
- trackStreamingStatus: TrackStreamingStatus
- },
- events: {
- conference: JitsiConferenceEvents,
- connection: JitsiConnectionEvents,
- detection: DetectionEvents,
- track: JitsiTrackEvents,
- mediaDevices: JitsiMediaDevicesEvents,
- connectionQuality: ConnectionQualityEvents,
- e2eping: E2ePingEvents,
- rtcstats: RTCStatsEvents
- },
- errors: {
- conference: JitsiConferenceErrors,
- connection: JitsiConnectionErrors,
- track: JitsiTrackErrors
- },
- errorTypes: {
- JitsiTrackError
- },
- logLevels: Logger.levels,
- mediaDevices: JitsiMediaDevices as unknown,
- analytics: Statistics.analytics as unknown,
- init(options: IJitsiMeetJSOptions = {}) {
- // Start capturing and sending logs to the rtcstats server as soon as possible.
- const {
- analytics: {
- rtcstatsStoreLogs,
- rtcstatsLogFlushSizeBytes
- } = {}
- } = options;
-
- if (rtcstatsStoreLogs) {
- Logger.addGlobalTransport(RTCStats.getDefaultLogCollector(rtcstatsLogFlushSizeBytes));
- }
-
- // @ts-ignore
- logger.info(`This appears to be ${browser.getName()}, ver: ${browser.getVersion()}`);
-
- JitsiMediaDevices.init();
- Settings.init(options.externalStorage);
- Statistics.init(options);
-
- // Initialize global window.connectionTimes
- // FIXME do not use 'window'
- if (!window.connectionTimes) {
- window.connectionTimes = {};
- }
-
- if (options.enableAnalyticsLogging !== true) {
- logger.warn('Analytics disabled, disposing.');
- this.analytics.dispose();
- }
-
- return RTC.init(options);
- },
-
- /**
- * Returns whether the desktop sharing is enabled or not.
- *
- * @returns {boolean}
- */
- isDesktopSharingEnabled() {
- return RTC.isDesktopSharingEnabled();
- },
-
- /**
- * Returns whether the current execution environment supports WebRTC (for
- * use within this library).
- *
- * @returns {boolean} {@code true} if WebRTC is supported in the current
- * execution environment (for use within this library); {@code false},
- * otherwise.
- */
- isWebRtcSupported() {
- return RTC.isWebRtcSupported();
- },
-
- setLogLevel(level) {
- Logger.setLogLevel(level);
- },
-
- /**
- * Expose rtcstats to the public API.
- */
- rtcstats: {
- /**
- * Checks if the rtcstats trace is available.
- * The trace is the abstraction for the underlying rtcstats websocket connection.
- *
- * @returns {boolean} <tt>true</tt> if the rtcstats trace is available or
- * <tt>false</tt> otherwise.
- */
- isTraceAvailable() {
- return RTCStats.isTraceAvailable();
- },
-
- /**
- * Sends identity data to the rtcstats server. This data is used
- * to identify the specifics of a particular client, it can be any object
- * and will show in the generated rtcstats dump under "identity" entries.
- *
- * @param {Object} identityData - Identity data to send.
- * @returns {void}
- */
- sendIdentityEntry(identityData) {
- RTCStats.sendIdentity(identityData);
- },
-
- /**
- * Sends a stats entry to rtcstats server.
- * @param {string} statsType - The type of stats to send.
- * @param {Object} data - The stats data to send.
- */
- sendStatsEntry(statsType, data) {
- RTCStats.sendStatsEntry(statsType, null, data);
- },
-
- /**
- * Events generated by rtcstats, such as PeerConnections state,
- * and websocket connection state.
- *
- * @param {RTCStatsEvents} event - The event name.
- * @param {function} handler - The event handler.
- */
- on(event, handler) {
- RTCStats.events.on(event, handler);
- }
- },
-
- /**
- * Sets the log level to the <tt>Logger</tt> instance with given id.
- *
- * @param {Logger.levels} level the logging level to be set
- * @param {string} id the logger id to which new logging level will be set.
- * Usually it's the name of the JavaScript source file including the path
- * ex. "modules/xmpp/ChatRoom.js"
- */
- setLogLevelById(level, id) {
- Logger.setLogLevelById(level, id);
- },
-
- /**
- * Registers new global logger transport to the library logging framework.
- *
- * @param globalTransport
- * @see Logger.addGlobalTransport
- */
- addGlobalLogTransport(globalTransport) {
- Logger.addGlobalTransport(globalTransport);
- },
-
- /**
- * Removes global logging transport from the library logging framework.
- *
- * @param globalTransport
- * @see Logger.removeGlobalTransport
- */
- removeGlobalLogTransport(globalTransport) {
- Logger.removeGlobalTransport(globalTransport);
- },
-
- /**
- * Sets global options which will be used by all loggers. Changing these
- * works even after other loggers are created.
- *
- * @param options
- * @see Logger.setGlobalOptions
- */
- setGlobalLogOptions(options) {
- Logger.setGlobalOptions(options);
- },
-
- /**
- * Creates local media tracks.
- *
- * @param options Object with properties / settings specifying the tracks
- * which should be created. should be created or some additional
- * configurations about resolution for example.
- * @param {Array} options.effects optional effects array for the track
- * @param {Array} options.devices the devices that will be requested
- * @param {string} options.resolution resolution constraints
- * @param {string} options.cameraDeviceId
- * @param {string} options.micDeviceId
- *
- * @returns {Promise.<{Array.<JitsiTrack>}, JitsiConferenceError>} A promise
- * that returns an array of created JitsiTracks if resolved, or a
- * JitsiConferenceError if rejected.
- */
- createLocalTracks(options: ICreateLocalTrackOptions = {}) {
- let isFirstGUM = false;
- const startTS = window.performance.now();
-
- if (!window.connectionTimes) {
- window.connectionTimes = {};
- }
-
- if (!hasGUMExecuted) {
- hasGUMExecuted = true;
- isFirstGUM = true;
- window.connectionTimes['firstObtainPermissions.start'] = startTS;
- }
- window.connectionTimes['obtainPermissions.start'] = startTS;
-
- return RTC.obtainAudioAndVideoPermissions(options)
- .then(tracks => {
- const endTS = window.performance.now();
-
- window.connectionTimes['obtainPermissions.end'] = endTS;
-
- if (isFirstGUM) {
- window.connectionTimes['firstObtainPermissions.end'] = endTS;
- }
-
- Statistics.sendAnalytics(
- createGetUserMediaEvent(
- 'success',
- getAnalyticsAttributesFromOptions(options)));
-
- if (this.isCollectingLocalStats()) {
- for (let i = 0; i < tracks.length; i++) {
- const track = tracks[i];
-
- if (track.getType() === MediaType.AUDIO) {
- Statistics.startLocalStats(track,
- track.setAudioLevel.bind(track));
- }
- }
- }
-
- // set real device ids
- const currentlyAvailableMediaDevices
- = RTC.getCurrentlyAvailableMediaDevices();
-
- if (currentlyAvailableMediaDevices) {
- for (let i = 0; i < tracks.length; i++) {
- const track = tracks[i];
-
- track._setRealDeviceIdFromDeviceList(
- currentlyAvailableMediaDevices);
- }
- }
-
- return tracks;
- })
- .catch(error => {
- if (error.name === JitsiTrackErrors.SCREENSHARING_USER_CANCELED) {
- Statistics.sendAnalytics(
- createGetUserMediaEvent(
- 'warning',
- {
- reason: 'extension install user canceled'
- }));
- } else if (error.name === JitsiTrackErrors.NOT_FOUND) {
- const attributes
- = getAnalyticsAttributesFromOptions(options);
-
- attributes.reason = 'device not found';
- attributes.devices = error.gum.devices.join('.');
- Statistics.sendAnalytics(
- createGetUserMediaEvent('error', attributes));
- } else {
- const attributes
- = getAnalyticsAttributesFromOptions(options);
-
- attributes.reason = error.name;
- Statistics.sendAnalytics(
- createGetUserMediaEvent('error', attributes));
- }
-
- const endTS = window.performance.now();
-
- window.connectionTimes['obtainPermissions.end'] = endTS;
-
- if (isFirstGUM) {
- window.connectionTimes['firstObtainPermissions.end'] = endTS;
- }
-
- return Promise.reject(error);
- });
- },
-
- /**
- * Manually create JitsiLocalTrack's from the provided track info, by exposing the RTC method
- *
- * @param {Array<ICreateLocalTrackFromMediaStreamOptions>} tracksInfo - array of track information
- * @returns {Array<JitsiLocalTrack>} - created local tracks
- */
- createLocalTracksFromMediaStreams(tracksInfo: ICreateLocalTrackFromMediaStreamOptions[]) {
- return RTC.createLocalTracks(tracksInfo.map(trackInfo => {
- const tracks = trackInfo.stream.getTracks()
- .filter(track => track.kind === trackInfo.mediaType);
-
- if (!tracks || tracks.length === 0) {
- // eslint-disable-next-line @typescript-eslint/only-throw-error
- throw new JitsiTrackError(JitsiTrackErrors.TRACK_NO_STREAM_TRACKS_FOUND, null, null);
- }
-
- if (tracks.length > 1) {
- // eslint-disable-next-line @typescript-eslint/only-throw-error
- throw new JitsiTrackError(JitsiTrackErrors.TRACK_TOO_MANY_TRACKS_IN_STREAM, null, null);
- }
-
- trackInfo.track = tracks[0];
-
- return trackInfo;
- }));
- },
-
- /**
- * Create a TrackVADEmitter service that connects an audio track to an VAD (voice activity detection) processor in
- * order to obtain VAD scores for individual PCM audio samples.
- * @param {string} localAudioDeviceId - The target local audio device.
- * @param {number} sampleRate - Sample rate at which the emitter will operate. Possible values 256, 512, 1024,
- * 4096, 8192, 16384. Passing other values will default to closes neighbor.
- * I.e. Providing a value of 4096 means that the emitter will process 4096 PCM samples at a time, higher values mean
- * longer calls, lowers values mean more calls but shorter.
- * @param {Object} vadProcessor - VAD Processors that does the actual compute on a PCM sample.The processor needs
- * to implement the following functions:
- * - <tt>getSampleLength()</tt> - Returns the sample size accepted by calculateAudioFrameVAD.
- * - <tt>getRequiredPCMFrequency()</tt> - Returns the PCM frequency at which the processor operates.
- * i.e. (16KHz, 44.1 KHz etc.)
- * - <tt>calculateAudioFrameVAD(pcmSample)</tt> - Process a 32 float pcm sample of getSampleLength size.
- * @returns {Promise<TrackVADEmitter>}
- */
- createTrackVADEmitter(localAudioDeviceId, sampleRate, vadProcessor) {
- return TrackVADEmitter.create(localAudioDeviceId, sampleRate, vadProcessor);
- },
-
- /**
- * Create AudioMixer, which is essentially a wrapper over web audio ChannelMergerNode. It essentially allows the
- * user to mix multiple MediaStreams into a single one.
- *
- * @returns {AudioMixer}
- */
- createAudioMixer() {
- return new AudioMixer();
- },
-
- /**
- * Go through all audio devices on the system and return one that is active, i.e. has audio signal.
- *
- * @returns Promise<Object> - Object containing information about the found device.
- */
- getActiveAudioDevice() {
- return getActiveAudioDevice();
- },
-
- /**
- * Checks if the current environment supports having multiple audio
- * input devices in use simultaneously.
- *
- * @returns {boolean} True if multiple audio input devices can be used.
- */
- isMultipleAudioInputSupported() {
- return this.mediaDevices.isMultipleAudioInputSupported();
- },
-
- /**
- * Checks if local tracks can collect stats and collection is enabled.
- *
- * @param {boolean} True if stats are being collected for local tracks.
- */
- isCollectingLocalStats() {
- return Statistics.audioLevelsEnabled && LocalStatsCollector.isLocalStatsSupported();
- },
-
- /**
- * Simple way to create a {JitsiConference}. All options are optional and sane defaults
- * will be chosen for JaaS users.
- *
- * @param roomName - The name of the conference.
- * @param appId - The application id (also known as tenant).
- * @param token - The token (JWT) to use for authentication.
- * @param options - The options to use for joining the conference.
- */
- async joinConference(
- roomName: string,
- appId: string = '',
- token: string | null = null,
- options: IJoinConferenceOptions = {}): Promise<JitsiConference> {
- const d = new Deferred();
- let connectionOptions = options.connectionOptions ?? {};
-
- // Provide a solid default config in case of JaaS.
- if (appId.startsWith('vpaas-magic-cookie')) {
- // Initialize RTCStats logging.
- Logger.addGlobalTransport(RTCStats.getDefaultLogCollector());
-
- const useStage = options.jaas?.useStaging ?? false;
- const jaasDomain = useStage ? 'staging.8x8.vc' : '8x8.vc';
- const opts = {
- hosts: {
- domain: jaasDomain,
- muc: `conference.${appId}.${jaasDomain}`
- },
- conferenceRequestUrl: `https://${jaasDomain}/${appId}/conference-request/v1?room=${roomName}`,
- serviceUrl: `wss://${jaasDomain}/${appId}/xmpp-websocket?room=${roomName}`,
- websocketKeepAliveUrl: `https://${jaasDomain}/${appId}/_unlock?room=${roomName}`,
- analytics: {
- rtcstatsEnabled: true,
- rtcstatsEndpoint: `wss://rtcstats-server-${useStage ? 'pilot' : '8x8'}.jitsi.net/`,
- rtcstatsSendSdp: true,
- },
- };
-
- connectionOptions = merge(connectionOptions, opts);
- }
-
- const conn = new JitsiConnection(appId, token, connectionOptions);
-
- function cleanupListeners() {
- conn.removeEventListener(
- JitsiConnectionEvents.CONNECTION_ESTABLISHED, onConnectionEstablished);
- conn.removeEventListener(
- JitsiConnectionEvents.CONNECTION_FAILED, onConnectionFailed);
- }
-
- function onConnectionEstablished() {
- cleanupListeners();
-
- const conf = conn.initJitsiConference(roomName, options.conferenceOptions ?? {});
-
- d.resolve(conf);
-
- // Make sure this runs after the promise was resolved so that local track events can be
- // listened to.
- queueMicrotask(() => {
- for (const track of options.tracks || []) {
- conf.addTrack(track);
- }
-
- conf.join();
-
- // Default to receiving 720p for everyone.
- conf.setReceiverConstraints({
- lastN: -1,
- defaultConstraints: { maxHeight: 720 },
- });
- });
- }
-
- function onConnectionFailed(error: string) {
- cleanupListeners();
- d.reject(error);
- }
-
- conn.addEventListener(
- JitsiConnectionEvents.CONNECTION_ESTABLISHED, onConnectionEstablished);
- conn.addEventListener(
- JitsiConnectionEvents.CONNECTION_FAILED, onConnectionFailed);
- conn.connect({ name: roomName });
-
- return d as unknown as Promise<JitsiConference>;
- },
-
- /**
- * Informs lib-jitsi-meet about the current network status.
- *
- * @param {object} state - The network info state.
- * @param {boolean} state.isOnline - {@code true} if the internet connectivity is online or {@code false}
- * otherwise.
- */
- setNetworkInfo({ isOnline }) {
- NetworkInfo.updateNetworkInfo({ isOnline });
- },
-
- /**
- * Run a pre-call test to check the network conditions.
- *
- * @param {IIceServer} iceServers - The ICE servers to use for the test,
- * @returns {Promise<PreCallResult | any>} - A Promise that resolves with the test results or rejects with an error
- * message.
- */
- runPreCallTest(iceServers: IIceServer[]): Promise<IPreCallResult | any> {
- return runPreCallTest(iceServers);
- },
-
- /**
- * Represents a hub/namespace for utility functionality which may be of
- * interest to lib-jitsi-meet clients.
- */
- util: {
- ScriptUtil,
- browser
- }
- };
|