bigbluebutton-Github/bigbluebutton-html5/imports/api/audio/client/bridge/base.js

178 lines
5.1 KiB
JavaScript
Raw Normal View History

import { Tracker } from 'meteor/tracker';
import VoiceCallStates from '/imports/api/voice-call-states';
import CallStateOptions from '/imports/api/voice-call-states/utils/callStates';
import logger from '/imports/startup/client/logger';
import Auth from '/imports/ui/services/auth';
import {
getAudioConstraints,
doGUM,
} from '/imports/api/audio/client/bridge/service';
const MEDIA = Meteor.settings.public.media;
const BASE_BRIDGE_NAME = 'base';
const CALL_TRANSFER_TIMEOUT = MEDIA.callTransferTimeout;
const TRANSFER_TONE = '1';
2017-07-24 22:15:46 +08:00
export default class BaseAudioBridge {
2017-10-12 05:04:10 +08:00
constructor(userData) {
this.userData = userData;
this.baseErrorCodes = {
INVALID_TARGET: 'INVALID_TARGET',
CONNECTION_ERROR: 'CONNECTION_ERROR',
REQUEST_TIMEOUT: 'REQUEST_TIMEOUT',
GENERIC_ERROR: 'GENERIC_ERROR',
2017-10-27 01:14:56 +08:00
MEDIA_ERROR: 'MEDIA_ERROR',
2018-06-27 21:56:03 +08:00
WEBRTC_NOT_SUPPORTED: 'WEBRTC_NOT_SUPPORTED',
2018-06-29 02:14:35 +08:00
ICE_NEGOTIATION_FAILED: 'ICE_NEGOTIATION_FAILED',
2017-10-12 05:04:10 +08:00
};
this.baseCallStates = {
started: 'started',
ended: 'ended',
failed: 'failed',
reconnecting: 'reconnecting',
autoplayBlocked: 'autoplayBlocked',
2017-10-12 05:04:10 +08:00
};
this.bridgeName = BASE_BRIDGE_NAME;
2017-07-24 22:15:46 +08:00
}
getPeerConnection() {
console.error('The Bridge must implement getPeerConnection');
}
2017-07-24 22:15:46 +08:00
exitAudio() {
2017-10-12 05:04:10 +08:00
console.error('The Bridge must implement exitAudio');
2017-07-24 22:15:46 +08:00
}
2017-09-29 21:38:10 +08:00
joinAudio() {
2017-10-12 05:04:10 +08:00
console.error('The Bridge must implement joinAudio');
2017-07-24 22:15:46 +08:00
}
changeInputDevice() {
console.error('The Bridge must implement changeInputDevice');
}
setInputStream() {
console.error('The Bridge must implement setInputStream');
}
sendDtmf() {
console.error('The Bridge must implement sendDtmf');
}
set inputDeviceId (deviceId) {
this._inputDeviceId = deviceId;
}
get inputDeviceId () {
return this._inputDeviceId;
}
/**
* Change the input device with the given deviceId, without renegotiating
* peer.
* A new MediaStream object is created for the given deviceId. This object
* is returned by the resolved promise.
* @param {String} deviceId The id of the device to be set as input
* @return {Promise} A promise that is resolved with the MediaStream
* object after changing the input device.
*/
async liveChangeInputDevice(deviceId) {
let newStream;
let backupStream;
try {
const constraints = {
audio: getAudioConstraints({ deviceId }),
};
// Backup stream (current one) in case the switch fails
if (this.inputStream && this.inputStream.active) {
backupStream = this.inputStream ? this.inputStream.clone() : null;
this.inputStream.getAudioTracks().forEach((track) => track.stop());
}
newStream = await doGUM(constraints);
await this.setInputStream(newStream);
if (backupStream && backupStream.active) {
backupStream.getAudioTracks().forEach((track) => track.stop());
backupStream = null;
}
return newStream;
} catch (error) {
// Device change failed. Clean up the tentative new stream to avoid lingering
// stuff, then try to rollback to the previous input stream.
if (newStream && typeof newStream.getAudioTracks === 'function') {
newStream.getAudioTracks().forEach((t) => t.stop());
newStream = null;
}
// Rollback to backup stream
if (backupStream && backupStream.active) {
this.setInputStream(backupStream).catch((rollbackError) => {
logger.error({
logCode: 'audio_changeinputdevice_rollback_failure',
extraInfo: {
bridgeName: this.bridgeName,
deviceId,
errorName: rollbackError.name,
errorMessage: rollbackError.message,
},
}, 'Microphone device change rollback failed - the device may become silent');
backupStream.getAudioTracks().forEach((track) => track.stop());
backupStream = null;
});
}
throw error;
}
}
trackTransferState(transferCallback) {
return new Promise((resolve, reject) => {
let trackerControl = null;
const timeout = setTimeout(() => {
trackerControl.stop();
logger.warn({ logCode: 'audio_transfer_timed_out' },
'Timeout on transferring from echo test to conference');
this.callback({
status: this.baseCallStates.failed,
error: 1008,
bridgeError: 'Timeout on call transfer',
bridge: this.bridgeName,
});
this.exitAudio();
reject(this.baseErrorCodes.REQUEST_TIMEOUT);
}, CALL_TRANSFER_TIMEOUT);
this.sendDtmf(TRANSFER_TONE);
Tracker.autorun((c) => {
trackerControl = c;
const selector = { meetingId: Auth.meetingID, userId: Auth.userID };
const query = VoiceCallStates.find(selector);
query.observeChanges({
changed: (id, fields) => {
if (fields.callState === CallStateOptions.IN_CONFERENCE) {
clearTimeout(timeout);
transferCallback();
c.stop();
resolve();
}
},
});
});
});
}
2017-07-24 22:15:46 +08:00
}