Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor WebRTC class #13736

Merged
merged 13 commits into from
May 20, 2019
4 changes: 3 additions & 1 deletion app/notifications/server/lib/Notifications.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Meteor } from 'meteor/meteor';
import { DDPCommon } from 'meteor/ddp-common';

import { WEB_RTC_EVENTS } from '../../../webrtc';
import { Subscriptions, Rooms } from '../../../models';
import { settings } from '../../../settings';

Expand Down Expand Up @@ -176,9 +177,10 @@ const notifications = new Notifications();
notifications.streamRoom.allowWrite(function(eventName, username, typing, extraData) {
const [roomId, e] = eventName.split('/');

if (e === 'webrtc') {
if (isNaN(e) ? e === WEB_RTC_EVENTS.WEB_RTC : parseFloat(e) === WEB_RTC_EVENTS.WEB_RTC) {
return true;
}

if (e === 'typing') {
const key = settings.get('UI_Use_Real_Name') ? 'name' : 'username';
// typing from livechat widget
Expand Down
159 changes: 58 additions & 101 deletions app/webrtc/client/WebRTCClass.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import EventEmitter from 'wolfy87-eventemitter';
import { Meteor } from 'meteor/meteor';
import { Tracker } from 'meteor/tracker';
import { ReactiveVar } from 'meteor/reactive-var';
Expand All @@ -11,62 +12,37 @@ import { settings } from '../../settings';
import { modal } from '../../ui-utils';
import { ChatSubscription } from '../../models';

class WebRTCTransportClass {
import { WEB_RTC_EVENTS } from '..';

class WebRTCTransportClass extends EventEmitter {
constructor(webrtcInstance) {
super();
this.debug = false;
this.webrtcInstance = webrtcInstance;
this.callbacks = {};
Notifications.onRoom(this.webrtcInstance.room, 'webrtc', (type, data) => {
const { onRemoteStatus } = this.callbacks;
Notifications.onRoom(this.webrtcInstance.room, WEB_RTC_EVENTS.WEB_RTC, (type, data) => {
this.log('WebRTCTransportClass - onRoom', type, data);
switch (type) {
case 'status':
if (onRemoteStatus && onRemoteStatus.length) {
onRemoteStatus.forEach((fn) => fn(data));
}
}
this.emit(type, data);
});
}

log(...args) {
if (this.debug === true) {
console.log.apply(console, args);
console.log(...args);
}
}

onUserStream(type, data) {
if (data.room !== this.webrtcInstance.room) {
return;
}
this.log('WebRTCTransportClass - onUser', type, data);
const { onRemoteCall, onRemoteJoin, onRemoteCandidate, onRemoteDescription } = this.callbacks;

switch (type) {
case 'call':
if (onRemoteCall && onRemoteCall.length) {
onRemoteCall.forEach((fn) => fn(data));
}
break;
case 'join':
if (onRemoteJoin && onRemoteJoin.length) {
onRemoteJoin.forEach((fn) => fn(data));
}
break;
case 'candidate':
if (onRemoteCandidate && onRemoteCandidate.length) {
onRemoteCandidate.forEach((fn) => fn(data));
}
break;
case 'description':
if (onRemoteDescription && onRemoteDescription.length) {
onRemoteDescription.forEach((fn) => fn(data));
}
}
this.log('WebRTCTransportClass - onUser', type, data);
this.emit(type, data);
}

startCall(data) {
this.log('WebRTCTransportClass - startCall', this.webrtcInstance.room, this.webrtcInstance.selfId);
Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'call', {
Notifications.notifyUsersOfRoom(this.webrtcInstance.room, WEB_RTC_EVENTS.WEB_RTC, WEB_RTC_EVENTS.CALL, {
from: this.webrtcInstance.selfId,
room: this.webrtcInstance.room,
media: data.media,
Expand All @@ -77,14 +53,14 @@ class WebRTCTransportClass {
joinCall(data) {
this.log('WebRTCTransportClass - joinCall', this.webrtcInstance.room, this.webrtcInstance.selfId);
if (data.monitor === true) {
Notifications.notifyUser(data.to, 'webrtc', 'join', {
Notifications.notifyUser(data.to, WEB_RTC_EVENTS.WEB_RTC, WEB_RTC_EVENTS.JOIN, {
from: this.webrtcInstance.selfId,
room: this.webrtcInstance.room,
media: data.media,
monitor: data.monitor,
});
} else {
Notifications.notifyUsersOfRoom(this.webrtcInstance.room, 'webrtc', 'join', {
Notifications.notifyUsersOfRoom(this.webrtcInstance.room, WEB_RTC_EVENTS.WEB_RTC, WEB_RTC_EVENTS.JOIN, {
from: this.webrtcInstance.selfId,
room: this.webrtcInstance.room,
media: data.media,
Expand All @@ -97,60 +73,40 @@ class WebRTCTransportClass {
data.from = this.webrtcInstance.selfId;
data.room = this.webrtcInstance.room;
this.log('WebRTCTransportClass - sendCandidate', data);
Notifications.notifyUser(data.to, 'webrtc', 'candidate', data);
Notifications.notifyUser(data.to, WEB_RTC_EVENTS.WEB_RTC, WEB_RTC_EVENTS.CANDIDATE, data);
}

sendDescription(data) {
data.from = this.webrtcInstance.selfId;
data.room = this.webrtcInstance.room;
this.log('WebRTCTransportClass - sendDescription', data);
Notifications.notifyUser(data.to, 'webrtc', 'description', data);
Notifications.notifyUser(data.to, WEB_RTC_EVENTS.WEB_RTC, WEB_RTC_EVENTS.DESCRIPTION, data);
}

sendStatus(data) {
this.log('WebRTCTransportClass - sendStatus', data, this.webrtcInstance.room);
data.from = this.webrtcInstance.selfId;
Notifications.notifyRoom(this.webrtcInstance.room, 'webrtc', 'status', data);
Notifications.notifyRoom(this.webrtcInstance.room, WEB_RTC_EVENTS.WEB_RTC, WEB_RTC_EVENTS.STATUS, data);
}

onRemoteCall(fn) {
const { callbacks } = this;
if (callbacks.onRemoteCall == null) {
callbacks.onRemoteCall = [];
}
callbacks.onRemoteCall.push(fn);
return this.on(WEB_RTC_EVENTS.CALL, fn);
}

onRemoteJoin(fn) {
const { callbacks } = this;
if (callbacks.onRemoteJoin == null) {
callbacks.onRemoteJoin = [];
}
callbacks.onRemoteJoin.push(fn);
return this.on(WEB_RTC_EVENTS.JOIN, fn);
}

onRemoteCandidate(fn) {
const { callbacks } = this;
if (callbacks.onRemoteCandidate == null) {
callbacks.onRemoteCandidate = [];
}
callbacks.onRemoteCandidate.push(fn);
return this.on(WEB_RTC_EVENTS.CANDIDATE, fn);
}

onRemoteDescription(fn) {
const { callbacks } = this;
if (callbacks.onRemoteDescription == null) {
callbacks.onRemoteDescription = [];
}
callbacks.onRemoteDescription.push(fn);
return this.on(WEB_RTC_EVENTS.DESCRIPTION, fn);
}

onRemoteStatus(fn) {
const { callbacks } = this;
if (callbacks.onRemoteStatus == null) {
callbacks.onRemoteStatus = [];
}
callbacks.onRemoteStatus.push(fn);
return this.on(WEB_RTC_EVENTS.STATUS, fn);
}
}

Expand Down Expand Up @@ -211,8 +167,8 @@ class WebRTCClass {
} else if (userAgent.indexOf('safari') !== -1) {
this.navigator = 'safari';
}
const nav = this.navigator;
this.screenShareAvailable = nav === 'chrome' || nav === 'firefox' || nav === 'electron';

this.screenShareAvailable = ['chrome', 'firefox', 'electron'].includes(this.navigator);
this.media = {
video: false,
audio: true,
Expand All @@ -223,11 +179,17 @@ class WebRTCClass {
this.transport.onRemoteCandidate(this.onRemoteCandidate.bind(this));
this.transport.onRemoteDescription(this.onRemoteDescription.bind(this));
this.transport.onRemoteStatus(this.onRemoteStatus.bind(this));


Meteor.setInterval(this.checkPeerConnections.bind(this), 1000);

// Meteor.setInterval(this.broadcastStatus.bind(@), 1000);
}

onUserStream(...args) {
return this.transport.onUserStream(...args);
}

log(...args) {
if (this.debug === true) {
console.log.apply(console, args);
Expand All @@ -240,11 +202,13 @@ class WebRTCClass {

checkPeerConnections() {
const { peerConnections } = this;
Object.keys(peerConnections).forEach((id) => {
const peerConnection = peerConnections[id];
if (peerConnection.iceConnectionState !== 'connected' && peerConnection.iceConnectionState !== 'completed' && peerConnection.createdAt + 5000 < Date.now()) {
const date = Date.now();
Object.entries(peerConnections).some(([id, peerConnection]) => {
if (!['connected', 'completed'].includes(peerConnection.iceConnectionState) && peerConnection.createdAt + 5000 < date) {
this.stopPeerConnection(id);
return true;
}
return false;
});
}

Expand All @@ -253,9 +217,7 @@ class WebRTCClass {
const itemsById = {};
const { peerConnections } = this;

Object.keys(peerConnections).forEach((id) => {
const peerConnection = peerConnections[id];

Object.entries(peerConnections).forEach(([id, peerConnection]) => {
peerConnection.getRemoteStreams().forEach((remoteStream) => {
const item = {
id,
Expand Down Expand Up @@ -288,7 +250,7 @@ class WebRTCClass {
this.remoteItemsById.set(itemsById);
}

resetCallInProgress() {
resetCallInProgress = () => {
this.callInProgress.set(false);
}

Expand All @@ -298,11 +260,10 @@ class WebRTCClass {
}
const remoteConnections = [];
const { peerConnections } = this;
Object.keys(peerConnections).forEach((id) => {
const peerConnection = peerConnections[id];
Object.keys(peerConnections).entries(([id, { remoteMedia: media }]) => {
remoteConnections.push({
id,
media: peerConnection.remoteMedia,
media,
});
});

Expand All @@ -326,7 +287,7 @@ class WebRTCClass {
// this.log(onRemoteStatus, arguments);
this.callInProgress.set(true);
Meteor.clearTimeout(this.callInProgressTimeout);
this.callInProgressTimeout = Meteor.setTimeout(this.resetCallInProgress.bind(this), 2000);
this.callInProgressTimeout = Meteor.setTimeout(this.resetCallInProgress, 2000);
if (this.active !== true) {
return;
}
Expand Down Expand Up @@ -419,6 +380,10 @@ class WebRTCClass {
}
onSuccess(stream);
};
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
return navigator.mediaDevices.getUserMedia(media).then(onSuccessLocal).catch(onError);
}

navigator.getUserMedia(media, onSuccessLocal, onError);
}

Expand Down Expand Up @@ -538,10 +503,7 @@ class WebRTCClass {
this.videoEnabled.set(this.media.video === true);
this.audioEnabled.set(this.media.audio === true);
const { peerConnections } = this;
Object.keys(peerConnections).forEach((id) => {
const peerConnection = peerConnections[id];
peerConnection.addStream(stream);
});
Object.entries(peerConnections).forEach(([, peerConnection]) => peerConnection.addStream(stream));
callback(null, this.localStream);
};
const onError = (error) => {
Expand All @@ -556,7 +518,7 @@ class WebRTCClass {
@param id {String}
*/

stopPeerConnection(id) {
stopPeerConnection = (id) => {
const peerConnection = this.peerConnections[id];
if (peerConnection == null) {
return;
Expand All @@ -569,9 +531,7 @@ class WebRTCClass {
stopAllPeerConnections() {
const { peerConnections } = this;

Object.keys(peerConnections).forEach((id) => {
this.stopPeerConnection(id);
});
Object.keys(peerConnections).forEach(this.stopPeerConnection);

window.audioContext && window.audioContext.close();
}
Expand Down Expand Up @@ -754,16 +714,13 @@ class WebRTCClass {
}, (isConfirm) => {
if (isConfirm) {
FlowRouter.goToRoomById(data.room);
Meteor.defer(() => {
this.joinCall({
to: data.from,
monitor: data.monitor,
media: data.media,
});
return this.joinCall({
to: data.from,
monitor: data.monitor,
media: data.media,
});
} else {
this.stop();
}
this.stop();
});
}

Expand Down Expand Up @@ -960,8 +917,8 @@ const WebRTC = new class {
this.instancesByRoomId = {};
}

getInstanceByRoomId(roomId) {
const subscription = ChatSubscription.findOne({ rid: roomId });
getInstanceByRoomId(rid) {
const subscription = ChatSubscription.findOne({ rid });
if (!subscription) {
return;
}
Expand All @@ -979,22 +936,22 @@ const WebRTC = new class {
if (enabled === false) {
return;
}
if (this.instancesByRoomId[roomId] == null) {
this.instancesByRoomId[roomId] = new WebRTCClass(Meteor.userId(), roomId);
if (this.instancesByRoomId[rid] == null) {
this.instancesByRoomId[rid] = new WebRTCClass(Meteor.userId(), rid);
}
return this.instancesByRoomId[roomId];
return this.instancesByRoomId[rid];
}
}();

Meteor.startup(function() {
Tracker.autorun(function() {
if (Meteor.userId()) {
Notifications.onUser('webrtc', (type, data) => {
Notifications.onUser(WEB_RTC_EVENTS.WEB_RTC, (type, data) => {
if (data.room == null) {
return;
}
const webrtc = WebRTC.getInstanceByRoomId(data.room);
webrtc.transport.onUserStream(type, data);
webrtc.onUserStream(type, data);
});
}
});
Expand Down
5 changes: 1 addition & 4 deletions app/webrtc/client/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import './adapter';
import { WebRTC } from './WebRTCClass';

export {
WebRTC,
};
export * from './WebRTCClass';
8 changes: 8 additions & 0 deletions app/webrtc/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const WEB_RTC_EVENTS = {
WEB_RTC: 'webrtc',
STATUS: 'status',
CALL: 'call',
JOIN: 'join',
CANDIDATE: 'candidate',
DESCRIPTION: 'description',
};