vertinho
Version:
Library to make conference apps and softphones through WebSockets with FreeSWITCH mod_verto.
320 lines (274 loc) • 8.29 kB
JavaScript
/**
* _ _ _
* | | (_) | |
* __ _____ _ __| |_ _ _ __ | |__ ___
* \ \ / / _ \ '__| __| | '_ \| '_ \ / _ \
* \ V / __/ | | |_| | | | | | | | (_) |
* \_/ \___|_| \__|_|_| |_|_| |_|\___/
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
* And see https://github.com/Mazuh/vertinho for the full license details.
*/
import 'webrtc-adapter';
import FSRTCPeerConnection from './FSRTCPeerConnection';
import {
activateMediaNode,
deactivateMediaNode,
traceMediaError,
} from './utils';
export default class VertoRTC {
constructor(options = {}) {
this.options = {
useVideo: null,
userData: null,
localVideo: null,
screenShare: false,
useCamera: 'any',
iceServers: false,
videoParams: {},
audioParams: {},
callbacks: {
onPeerStreaming: () => {},
onPeerStreamingError: () => {},
onICESDP: () => {},
},
mediaHandlers: {
playRemoteVideo: null,
stopRemoteVideo: null,
playLocalVideo: null,
stopLocalVideo: null,
},
...options,
};
this.mediaData = {
SDP: null,
profile: {},
candidateList: [],
};
if (this.options.useVideo && !this.options.screenShare) {
if (this.options.mediaHandlers.stopRemoteVideo) {
this.options.mediaHandlers.stopRemoteVideo();
} else {
deactivateMediaNode(this.options.useVideo);
}
}
}
useVideo(obj, local) {
if (obj) {
this.options.useVideo = obj;
this.options.localVideo = local;
} else {
this.options.useVideo = null;
this.options.localVideo = null;
}
if (this.options.useVideo) {
this.options.useVideo.style.display = 'none';
}
}
answer(sdp, onSuccess, onError) {
this.peer.addAnswerSDP({ sdp, type: 'answer' }, onSuccess, onError);
}
stopPeer() {
if (this.peer) {
this.peer.stop();
}
}
onRemoteStream(stream) {
const { options: { useAudio, useVideo } } = this;
const element = useVideo || useAudio;
if (element) {
if (this.options.mediaHandlers.playRemoteVideo) {
this.options.mediaHandlers.playRemoteVideo(stream);
} else {
activateMediaNode(element, stream);
}
}
}
stop() {
const { options: { useVideo, localVideo }, peer, localStream } = this;
if (useVideo) {
if (this.options.mediaHandlers.stopRemoteVideo) {
this.options.mediaHandlers.stopRemoteVideo();
} else {
deactivateMediaNode(useVideo);
}
}
if (localVideo) {
if (this.options.mediaHandlers.stopLocalVideo) {
this.options.mediaHandlers.stopLocalVideo();
} else {
deactivateMediaNode(localVideo);
}
}
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
}
if (peer) {
peer.stop();
}
}
getAudioConstraint() {
const { useMic, audioParams, screenShare } = this.options;
if (screenShare) {
return false;
}
if (useMic === 'none') {
return false;
}
if (useMic === 'any') {
return audioParams;
}
return {
...audioParams,
deviceId: { exact: useMic },
};
}
getVideoConstraint() {
const {
videoParams,
useCamera,
useVideo,
} = this.options;
if (!useVideo) {
return false;
}
if (this.options.screenShare) {
return this.getScreenConstraint();
}
if (useCamera === 'none') {
return false;
}
const dimensions = {};
const { minWidth, maxWidth } = videoParams;
if (minWidth !== undefined && maxWidth !== undefined) {
dimensions.width = { min: minWidth, max: maxWidth };
}
const { minHeight, maxHeight } = videoParams;
if (minHeight !== undefined && maxHeight !== undefined) {
dimensions.height = { min: minHeight, max: maxHeight };
}
if (useCamera === 'any') {
return { ...dimensions };
}
return { ...dimensions, deviceId: useCamera };
}
getScreenConstraint() {
const {
videoParams: screenParams,
useCamera: useScreen,
} = this.options;
const isFirefox = !!navigator.mozGetUserMedia;
if (isFirefox) {
const { minWidth, maxWidth } = screenParams;
const { minHeight, maxHeight } = screenParams;
return {
width: { min: minWidth, max: maxWidth },
height: { min: minHeight, max: maxHeight },
mediaSource: 'screen',
};
}
return {
mandatory: screenParams,
optional: useScreen ? [{ sourceId: useScreen }] : [],
};
}
getMediaParams() {
return {
audio: this.getAudioConstraint(),
video: this.getVideoConstraint(),
};
}
onICE(candidate) {
this.mediaData.candidate = candidate;
this.mediaData.candidateList.push(this.mediaData.candidate);
}
onICESDP(sdp) {
this.mediaData.SDP = sdp.sdp;
this.options.callbacks.onICESDP();
}
createAnswer({ useCamera, sdp }) {
const { options } = this;
const { useVideo, localVideo, iceServers } = options;
this.type = 'answer';
this.options.useCamera = useCamera;
if (useVideo && localVideo) {
const localVideoConstraints = { audio: false, video: true };
navigator.mediaDevices.getUserMedia(localVideoConstraints).then((stream) => {
if (this.options.mediaHandlers.playLocalVideo) {
this.options.mediaHandlers.playLocalVideo(stream);
} else {
activateMediaNode(localVideo, stream);
}
}).catch(error => traceMediaError(localVideoConstraints, error));
}
const mediaConstraints = this.getMediaParams();
navigator.mediaDevices.getUserMedia(mediaConstraints).then((stream) => {
this.peer = new FSRTCPeerConnection({
type: 'answer',
attachStream: stream,
offerSDP: { sdp, type: 'offer' },
constraints: this.getPeerConstraints(),
onPeerStreamingError: this.options.callbacks.onPeerStreamingError,
onICE: this.onICE.bind(this),
onRemoteStream: this.onRemoteStream.bind(this),
onICESDP: iceSdp => this.onICESDP(iceSdp),
onAnswerSDP: (answerSDP) => {
this.answer.SDP = answerSDP.sdp;
},
iceServers,
});
this.options.callbacks.onPeerStreaming(stream);
}).catch((error) => {
traceMediaError(mediaConstraints, error);
});
}
getPeerConstraints() {
return {
offerToReceiveAudio: this.options.useSpeak !== 'none',
offerToReceiveVideo: !!this.options.useVideo,
};
}
inviteRemotePeerConnection() {
this.type = 'offer';
const mediaConstraints = this.getMediaParams();
const screen = this.options.videoParams && this.options.screenShare;
const screenPeerConstraints = {
offerToReceiveVideo: false,
offerToReceiveAudio: false,
offerToSendAudio: false,
};
const handleStream = (stream) => {
this.localStream = stream;
this.peer = new FSRTCPeerConnection({
type: this.type,
attachStream: stream,
onICESDP: this.onICESDP.bind(this),
onPeerStreamingError: this.options.callbacks.onPeerStreamingError,
constraints: screen ? screenPeerConstraints : this.getPeerConstraints(),
iceServers: this.options.iceServers,
onICE: this.onICE.bind(this),
onRemoteStream: remoteStream => !screen && this.onRemoteStream(remoteStream),
onOfferSDP: (sdp) => {
this.mediaData.SDP = sdp.sdp;
},
});
this.options.callbacks.onPeerStreaming(stream);
if (this.options.mediaHandlers.playLocalVideo) {
this.options.mediaHandlers.playLocalVideo(stream);
} else {
activateMediaNode(this.options.localVideo, stream);
}
};
if (!mediaConstraints.audio && !mediaConstraints.video) {
handleStream(null);
} else {
navigator.mediaDevices.getUserMedia(mediaConstraints)
.then(handleStream)
.catch((error) => {
traceMediaError(mediaConstraints, error);
});
}
}
}