This repository has been archived on 2024-12-15. You can view files and clone it, but cannot push or open issues or pull requests.
seepur/resources/scripts/applications/home/ws/call.manager.ts

246 lines
8.1 KiB
TypeScript
Raw Permalink Normal View History

2020-05-01 05:55:26 +00:00
import WebSocketService from "./websocket.service";
2020-04-12 23:33:24 +00:00
import { EventEmitter } from "events";
2020-05-01 05:55:26 +00:00
let singleton: CallManager = null;
2020-04-12 23:33:24 +00:00
export default class CallManager {
2020-04-12 14:25:42 +00:00
private inCall: boolean;
2020-04-14 00:56:04 +00:00
public peerId: number;
2020-04-12 14:25:42 +00:00
private localStream: MediaStream;
private remoteStream: MediaStream;
2020-04-12 23:33:24 +00:00
private signalingChannel;
private emitter = new EventEmitter();
2020-04-12 14:25:42 +00:00
private pc: RTCPeerConnection;
2020-04-14 15:06:09 +00:00
public child;
public peer = { avatar: '' };
2020-04-14 15:06:09 +00:00
public isHost = false;
public books = [];
public currentActivity = null;
2020-05-01 05:55:26 +00:00
public callId;
private userId: number
constructor(private ws: WebSocketService) {
2020-04-12 14:25:42 +00:00
this.inCall = false;
this.peerId = null;
2020-04-12 14:25:42 +00:00
this.pc = null;
this.remoteStream = new MediaStream();
}
2020-04-14 00:56:04 +00:00
2020-05-01 05:55:26 +00:00
async connectToCall(userId: number, mediaConstraints: MediaStreamConstraints, callId: number): Promise<boolean> {
2020-04-12 14:25:42 +00:00
if (this.inCall) throw new Error('Already connected to call');
2020-05-01 05:55:26 +00:00
this.callId = callId;
this.userId = userId;
2020-04-12 23:33:24 +00:00
console.log('connecting to call');
await this.getUserMedia(mediaConstraints);
this.signalingChannel = this.ws.subscribe(`call:${this.callId}`);
const signalingChannel = this.signalingChannel;
2020-04-12 14:25:42 +00:00
const self = this;
return new Promise((resolve, reject) => {
2020-04-12 23:33:24 +00:00
signalingChannel.on('close', self.close.bind(self));
signalingChannel.on('call:start', self.onCallStart.bind(self));
signalingChannel.on('call:standby', self.onCallStandby.bind(self));
signalingChannel.on('wrtc:sdp:offer', self.onRemoteOffer.bind(self));
signalingChannel.on('wrtc:sdp:answer', self.onRemoteAnswer.bind(self));
signalingChannel.on('wrtc:ice', self.onRemoteIce.bind(self));
2020-04-14 15:06:09 +00:00
signalingChannel.on('book:action:flip-page', self.onActionBookFlip.bind(self));
signalingChannel.on('call:host:changed', self.onRemoteHostChanged.bind(self));
signalingChannel.on('call:view:lobby', self.onRemoteViewLobby.bind(self));
signalingChannel.on('call:view:book', self.onRemoteViewBook.bind(self));
2020-04-12 23:33:24 +00:00
signalingChannel.on('error', (e) => {
2020-04-12 14:25:42 +00:00
console.error(e);
resolve(false)
});
2020-04-12 23:33:24 +00:00
signalingChannel.on('ready', () => {
console.log('in Ready');
self.inCall = true;
2020-04-12 14:25:42 +00:00
resolve(true)
});
});
}
2020-04-12 23:33:24 +00:00
on(event: ECallEvents, callback: (...args) => void) {
this.emitter.on(event, callback);
}
2020-05-01 05:55:26 +00:00
removeListener(event: ECallEvents, callback: (...args) => void) {
this.emitter.removeListener(event, callback);
}
2020-04-12 23:33:24 +00:00
private emit(event: ECallEvents, data: any) {
this.emitter.emit(event, data);
}
2020-04-12 14:25:42 +00:00
private send(event: string, payload: { [key: string]: any }) {
console.log(`Sending event: ${event}`);
2020-04-12 23:33:24 +00:00
this.signalingChannel.emit(event, {
userId: this.userId,
peerId: this.peerId,
2020-04-12 14:25:42 +00:00
...payload
})
}
async onCallStart(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number, books: any[] }) {
2020-04-12 23:33:24 +00:00
console.log('onCallStart');
2020-04-12 14:25:42 +00:00
console.log(payload);
this.peerId = payload.peerId;
this.books = payload.books;
2020-05-01 05:55:26 +00:00
this.isHost = this.userId === payload.hostId;
2020-04-12 14:25:42 +00:00
this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
2020-04-14 15:06:09 +00:00
this.child = payload.child;
payload.users.forEach(u => {
if (u.id === this.peerId) this.peer = u;
2020-04-14 15:06:09 +00:00
});
this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId: payload.hostId });
2020-04-12 14:25:42 +00:00
console.log('Created PeerConnection');
2020-04-12 23:33:24 +00:00
console.log('adding tracks to pc');
this.localStream.getTracks().forEach(t => this.pc.addTrack(t, this.localStream));
2020-04-12 14:25:42 +00:00
this.setupPeerConnectionListeners();
const sdp = await this.pc.createOffer();
await this.pc.setLocalDescription(sdp);
2020-04-12 23:33:24 +00:00
console.log('Local description Set', sdp.sdp);
2020-04-12 14:25:42 +00:00
this.send('wrtc:sdp:offer', {
sdp
});
return true;
}
async onCallStandby(payload: { iceServers: RTCIceServer[], peerId: number, users: any[], child: any, hostId: number, books: any[] }) {
2020-04-12 23:33:24 +00:00
console.log('onCallStandby');
2020-04-12 14:25:42 +00:00
console.log(payload);
this.peerId = payload.peerId;
this.books = payload.books;
2020-05-01 05:55:26 +00:00
this.isHost = this.userId === payload.hostId;
2020-04-12 14:25:42 +00:00
this.pc = new RTCPeerConnection({ iceServers: payload.iceServers });
2020-04-14 15:06:09 +00:00
this.child = payload.child;
payload.users.forEach(u => {
if (u.id === this.peerId) this.peer = u;
2020-04-14 15:06:09 +00:00
});
this.emit(ECallEvents.CALL_HOST_CHANGED, { hostId: payload.hostId });
2020-04-12 14:25:42 +00:00
console.log('Created PeerConnection');
2020-04-12 23:33:24 +00:00
console.log('adding tracks to pc');
this.localStream.getTracks().forEach(t => this.pc.addTrack(t, this.localStream));
2020-04-12 14:25:42 +00:00
this.setupPeerConnectionListeners();
return true;
}
public selectBook(index: number, remote: boolean) {
this.currentActivity = this.books[index];
console.log('-------> Selected Book ', index, 'bookId:', this.currentActivity.id);
if (!remote) this.send('call:view:book', { bookId: this.currentActivity.id });
}
public backToLobby() {
console.log('-------> backToLobby');
2020-05-01 05:55:26 +00:00
this.emitter.removeAllListeners(ECallEvents.ACTION_BOOK_FLIP);
this.send('call:view:lobby', {});
}
2020-04-12 14:25:42 +00:00
setupPeerConnectionListeners() {
this.pc.addEventListener("icecandidate", this.onLocalIce.bind(this));
this.pc.addEventListener('connectionstatechange', event => {
console.log(`PC Connection state: ${this.pc.connectionState}`);
// if (this.pc.connectionState === 'connected') {
// // Peers connected!
// }
});
this.pc.addEventListener('iceconnectionstatechange', event => {
console.log('iceconnectionstatechange');
console.log(this.pc.iceConnectionState);
})
this.pc.addEventListener('track', async (event) => {
console.log('On remote track!');
this.remoteStream.addTrack(event.track);
});
this.pc.addEventListener('icegatheringstatechange', event => {
console.log('icegatheringstatechange', this.pc.iceGatheringState);
});
}
onLocalIce(event) {
if (event.candidate) {
console.log('Sending candidate');
this.send('wrtc:ice', {
2020-04-12 23:33:24 +00:00
ice: event.candidate,
2020-04-12 14:25:42 +00:00
});
}
}
async onRemoteOffer(payload) {
const offer = new RTCSessionDescription(payload.sdp);
await this.pc.setRemoteDescription(offer);
2020-04-12 23:33:24 +00:00
console.log('Remote offer Set', offer.sdp);
2020-04-12 14:25:42 +00:00
const sdp = await this.pc.createAnswer();
this.send('wrtc:sdp:answer', {
sdp
});
await this.pc.setLocalDescription(sdp);
2020-04-12 23:33:24 +00:00
console.log('Local answer Set', sdp.sdp);
2020-04-12 14:25:42 +00:00
return true;
}
async onRemoteAnswer(payload) {
const answer = new RTCSessionDescription(payload.sdp);
await this.pc.setRemoteDescription(answer);
2020-04-12 23:33:24 +00:00
console.log('Remote answer Set', answer.sdp);
2020-04-12 14:25:42 +00:00
return true;
}
async onRemoteIce(payload) {
const ice = payload.ice;
await this.pc.addIceCandidate(ice);
return true;
}
2020-04-12 23:33:24 +00:00
async getUserMedia(constraints: MediaStreamConstraints = { video: true, audio: true }) {
2020-04-12 14:25:42 +00:00
if (this.localStream) return this.localStream;
this.localStream = await navigator.mediaDevices.getUserMedia(constraints);
return this.localStream;
}
getRemoteStream() {
return this.remoteStream;
}
2020-04-14 00:56:04 +00:00
onActionBookFlip(payload) {
this.emit(ECallEvents.ACTION_BOOK_FLIP, payload);
};
2020-04-14 15:06:09 +00:00
changeHost() {
this.send('call:host:changed', {});
2020-04-14 15:06:09 +00:00
}
onRemoteHostChanged(payload) {
2020-05-01 05:55:26 +00:00
this.isHost = this.userId === payload.hostId;
2020-04-14 15:06:09 +00:00
this.emit(ECallEvents.CALL_HOST_CHANGED, payload);
}
onRemoteViewLobby(payload) {
2020-05-01 05:55:26 +00:00
this.emitter.removeAllListeners(ECallEvents.ACTION_BOOK_FLIP);
this.emit(ECallEvents.CALL_VIEW_LOBBY, null);
}
onRemoteViewBook(payload) {
this.emit(ECallEvents.CALL_VIEW_BOOK, payload);
}
2020-04-12 14:25:42 +00:00
close() {
2020-04-12 23:33:24 +00:00
if (!this.inCall) return;
2020-05-01 17:24:22 +00:00
console.log('Closing...');
2020-04-12 23:33:24 +00:00
this.emit(ECallEvents.CLOSE, this.callId);
if (this.signalingChannel) this.signalingChannel.close();
this.signalingChannel = null;
if (this.pc) this.pc.close();
if (this.localStream) this.localStream.getTracks().forEach(t => t.stop());
this.localStream = null;
this.remoteStream = null;
2020-05-01 05:55:26 +00:00
this.emitter.removeAllListeners();
2020-04-12 14:25:42 +00:00
this.inCall = false;
2020-05-01 05:55:26 +00:00
singleton = null;
2020-04-12 14:25:42 +00:00
}
2020-04-14 00:56:04 +00:00
2020-04-12 14:25:42 +00:00
}
2020-04-12 23:33:24 +00:00
export enum ECallEvents {
CLOSE = 'CLOSE',
2020-04-14 00:56:04 +00:00
REMOTE_STREAM = 'REMOTE_STREAM',
2020-04-14 15:06:09 +00:00
ACTION_BOOK_FLIP = 'ACTION_BOOK_FLIP',
CALL_HOST_CHANGED = "CALL_HOST_CHANGED",
CALL_VIEW_LOBBY = 'CALL_VIEW_LOBBY',
CALL_VIEW_BOOK = 'CALL_VIEW_BOOK',
2020-04-12 23:33:24 +00:00
}