Changpeng Duan 4 jaren geleden
bovenliggende
commit
2184ba38ca
3 gewijzigde bestanden met toevoegingen van 743 en 0 verwijderingen
  1. 590 0
      src/static/grpc/RoomClient.js
  2. 148 0
      src/static/grpc/mediasoupclient.min.js
  3. 5 0
      src/static/grpc/socket.io.js

+ 590 - 0
src/static/grpc/RoomClient.js

@@ -0,0 +1,590 @@
+const mediaType = {
+    audio: 'audioType',
+    video: 'videoType',
+    screen: 'screenType'
+}
+const _EVENTS = {
+    exitRoom: 'exitRoom',
+    openRoom: 'openRoom',
+    startVideo: 'startVideo',
+    stopVideo: 'stopVideo',
+    startAudio: 'startAudio',
+    stopAudio: 'stopAudio',
+    startScreen: 'startScreen',
+    stopScreen: 'stopScreen'
+}
+let producer = null;
+
+class RoomClient {
+
+    constructor(localMediaEl, remoteVideoEl, remoteAudioEl, mediasoupClient, socket, room_id, name, successCallback, that) {
+        this.name = name
+        this.localMediaEl = localMediaEl
+        this.remoteVideoEl = remoteVideoEl
+        this.remoteAudioEl = remoteAudioEl
+        this.mediasoupClient = mediasoupClient
+        this.socket = socket
+        this.producerTransport = null
+        this.consumerTransport = null
+        this.device = null
+        this.room_id = room_id
+
+        this.consumers = new Map()
+        this.producers = new Map()
+        /**
+         * map that contains a mediatype as key and producer_id as value
+         */
+        this.producerLabel = new Map()
+
+        this._isOpen = false
+        this.eventListeners = new Map()
+        Object.keys(_EVENTS).forEach(function (evt) {
+            this.eventListeners.set(evt, [])
+        }.bind(this))
+        this.createRoom(room_id).then(async function () {
+            await this.join(name, room_id)
+            this.initSockets()
+            this._isOpen = true
+            successCallback()
+        }.bind(this))
+    }
+
+    ////////// INIT /////////
+
+    async createRoom(room_id) {
+        await this.socket.request('createRoom', {
+            room_id
+        }).catch(err => {
+            console.log(err)
+        })
+    }
+
+    async join(name, room_id) {
+        this.socket.request('join', {
+            name,
+            room_id
+        }).then(async function (e) {
+            const data = await this.socket.request('getRouterRtpCapabilities');
+            let device = await this.loadDevice(data)
+            this.device = device
+            await this.initTransports(device)
+            this.socket.emit('getProducers')
+        }.bind(this)).catch(e => {
+            console.log(e)
+        })
+    }
+
+    async loadDevice(routerRtpCapabilities) {
+        let device
+        try {
+            device = new this.mediasoupClient.Device();
+        } catch (error) {
+            if (error.name === 'UnsupportedError') {
+                console.error('browser not supported');
+            }
+            console.error(error)
+        }
+        await device.load({
+            routerRtpCapabilities
+        })
+        return device
+
+    }
+
+    async initTransports(device) {
+
+        // init producerTransport
+        {
+            const data = await this.socket.request('createWebRtcTransport', {
+                forceTcp: false,
+                rtpCapabilities: device.rtpCapabilities,
+            })
+            if (data.error) {
+                console.error(data.error);
+                return;
+            }
+
+            this.producerTransport = device.createSendTransport(data);
+
+            this.producerTransport.on('connect', async function ({
+                                                                     dtlsParameters
+                                                                 }, callback, errback) {
+                this.socket.request('connectTransport', {
+                    dtlsParameters,
+                    transport_id: data.id
+                })
+                    .then(callback)
+                    .catch(errback)
+            }.bind(this));
+
+            this.producerTransport.on('produce', async function ({
+                                                                     kind,
+                                                                     rtpParameters
+                                                                 }, callback, errback) {
+                try {
+                    const {
+                        producer_id
+                    } = await this.socket.request('produce', {
+                        producerTransportId: this.producerTransport.id,
+                        kind,
+                        rtpParameters,
+                    });
+                    callback({
+                        id: producer_id
+                    });
+                } catch (err) {
+                    errback(err);
+                }
+            }.bind(this))
+
+            this.producerTransport.on('connectionstatechange', function (state) {
+                switch (state) {
+                    case 'connecting':
+
+                        break;
+
+                    case 'connected':
+                        //localVideo.srcObject = stream
+                        break;
+
+                    case 'failed':
+                        this.producerTransport.close();
+                        break;
+
+                    default:
+                        break;
+                }
+            }.bind(this));
+        }
+
+        // init consumerTransport
+        {
+            const data = await this.socket.request('createWebRtcTransport', {
+                forceTcp: false,
+            });
+            if (data.error) {
+                console.error(data.error);
+                return;
+            }
+            // only one needed
+            this.consumerTransport = device.createRecvTransport(data);
+            this.consumerTransport.on('connect', function ({
+                                                               dtlsParameters
+                                                           }, callback, errback) {
+                this.socket.request('connectTransport', {
+                    transport_id: this.consumerTransport.id,
+                    dtlsParameters
+                })
+                    .then(callback)
+                    .catch(errback);
+            }.bind(this));
+
+            this.consumerTransport.on('connectionstatechange', async function (state) {
+                console.log('state', state);
+                switch (state) {
+                    case 'connecting':
+                        break;
+
+                    case 'connected':
+                        //remoteVideo.srcObject = await stream;
+                        //await socket.request('resume');
+                        break;
+
+                    case 'failed':
+                        this.consumerTransport.close();
+                        break;
+
+                    default:
+                        break;
+                }
+            }.bind(this));
+        }
+
+    }
+
+    initSockets() {
+        this.socket.on('consumerClosed', function ({
+                                                       consumer_id
+                                                   }) {
+            console.log('closing consumer:', consumer_id)
+            this.removeConsumer(consumer_id)
+        }.bind(this))
+
+        /**
+         * data: [ {
+         *  producer_id:
+         *  producer_socket_id:
+         * }]
+         */
+        console.log('=====producers======');
+        this.socket.on('newProducers', async function (data) {
+            console.log(123);
+            console.log(data);
+            for (let {
+                producer_id
+            } of data) {
+                await this.consume(producer_id)
+            }
+        }.bind(this))
+
+        this.socket.on('disconnect', function () {
+            this.exit(true)
+        }.bind(this))
+
+
+    }
+
+
+    //////// MAIN FUNCTIONS /////////////
+
+
+    async produce(type, deviceId = null) {
+        let mediaConstraints = {}
+        let audio = false
+        let screen = false
+        switch (type) {
+            case mediaType.audio:
+                mediaConstraints = {
+                    audio: {
+                        deviceId: deviceId
+                    },
+                    video: false
+                }
+                audio = true
+                break
+            case mediaType.video:
+                mediaConstraints = {
+                    audio: false,
+                    video: {
+                        width: {
+                            min: 640,
+                            ideal: 1920
+                        },
+                        height: {
+                            min: 400,
+                            ideal: 1080
+                        },
+                        deviceId: deviceId
+                        /*aspectRatio: {
+                            ideal: 1.7777777778
+                        }*/
+                    }
+                }
+                break
+            case mediaType.screen:
+                mediaConstraints = false
+                screen = true
+                break;
+            default:
+                return
+                break;
+        }
+        if (!this.device.canProduce('video') && !audio) {
+            console.error('cannot produce video');
+            return;
+        }
+        if (this.producerLabel.has(type)) {
+            console.log('producer already exists for this type ' + type)
+            return
+        }
+        console.log('mediacontraints:', mediaConstraints)
+        let stream;
+        try {
+            stream = screen ? await navigator.mediaDevices.getDisplayMedia() : await navigator.mediaDevices.getUserMedia(mediaConstraints)
+            console.log(navigator.mediaDevices.getSupportedConstraints())
+
+
+            const track = audio ? stream.getAudioTracks()[0] : stream.getVideoTracks()[0]
+            const params = {
+                track
+            };
+            if (!audio && !screen) {
+                params.encodings = [{
+                    rid: 'r0',
+                    maxBitrate: 100000,
+                    //scaleResolutionDownBy: 10.0,
+                    scalabilityMode: 'S1T3'
+                },
+                    {
+                        rid: 'r1',
+                        maxBitrate: 300000,
+                        scalabilityMode: 'S1T3'
+                    },
+                    {
+                        rid: 'r2',
+                        maxBitrate: 900000,
+                        scalabilityMode: 'S1T3'
+                    }
+                ];
+                params.codecOptions = {
+                    videoGoogleStartBitrate: 1000
+                };
+            }
+            producer = await this.producerTransport.produce(params)
+
+            console.log('producer', producer)
+
+            this.producers.set(producer.id, producer)
+
+            let elem
+            if (!audio) {
+                elem = document.createElement('video')
+                elem.srcObject = stream
+                elem.id = producer.id
+                elem.playsinline = false
+                elem.autoplay = true
+                elem.className = "vid"
+                this.localMediaEl.appendChild(elem)
+
+            }
+
+            producer.on('trackended', () => {
+                this.closeProducer(type)
+            })
+
+            producer.on('transportclose', () => {
+                console.log('producer transport close')
+                if (!audio) {
+                    elem.srcObject.getTracks().forEach(function (track) {
+                        track.stop()
+                    })
+                    elem.parentNode.removeChild(elem)
+                }
+                this.producers.delete(producer.id)
+
+            })
+
+            producer.on('close', () => {
+                console.log('closing producer')
+                if (!audio) {
+                    elem.srcObject.getTracks().forEach(function (track) {
+                        track.stop()
+                    })
+                    elem.parentNode.removeChild(elem)
+                }
+                this.producers.delete(producer.id)
+
+            })
+
+            this.producerLabel.set(type, producer.id)
+
+            switch (type) {
+                case mediaType.audio:
+                    this.event(_EVENTS.startAudio)
+                    break
+                case mediaType.video:
+                    this.event(_EVENTS.startVideo)
+                    break
+                case mediaType.screen:
+                    this.event(_EVENTS.startScreen)
+                    break;
+                default:
+                    return
+                    break;
+            }
+        } catch (err) {
+            console.log(err)
+        }
+    }
+
+    async consume(producer_id) {
+        console.log(123);
+        //let info = await roomInfo()
+
+        this.getConsumeStream(producer_id).then(function ({
+                                                              consumer,
+                                                              stream,
+                                                              kind
+                                                          }) {
+
+            this.consumers.set(consumer.id, consumer)
+            let elem;
+            if (kind === 'video') {
+                console.log(stream);
+                elem = document.createElement('video')
+                elem.srcObject = stream
+                elem.id = consumer.id
+                elem.playsinline = false
+                elem.autoplay = true
+                elem.className = "vid"
+                this.remoteVideoEl.appendChild(elem)
+            } else {
+                elem = document.createElement('audio')
+                elem.srcObject = stream
+                elem.id = consumer.id
+                elem.playsinline = false
+                elem.autoplay = true
+                this.remoteAudioEl.appendChild(elem)
+            }
+
+            consumer.on('trackended', function () {
+                this.removeConsumer(consumer.id)
+            }.bind(this))
+            consumer.on('transportclose', function () {
+                this.removeConsumer(consumer.id)
+            }.bind(this))
+
+
+        }.bind(this))
+    }
+
+    async getConsumeStream(producerId) {
+        const {
+            rtpCapabilities
+        } = this.device
+        const data = await this.socket.request('consume', {
+            rtpCapabilities,
+            consumerTransportId: this.consumerTransport.id, // might be 
+            producerId
+        });
+        const {
+            id,
+            kind,
+            rtpParameters,
+        } = data;
+
+        let codecOptions = {};
+        const consumer = await this.consumerTransport.consume({
+            id,
+            producerId,
+            kind,
+            rtpParameters,
+            codecOptions,
+        })
+        const stream = new MediaStream();
+        stream.addTrack(consumer.track);
+        return {
+            consumer,
+            stream,
+            kind
+        }
+    }
+
+    closeProducer(type) {
+        if (!this.producerLabel.has(type)) {
+            console.log('there is no producer for this type ' + type)
+            return
+        }
+        let producer_id = this.producerLabel.get(type)
+        console.log(producer_id)
+        this.socket.emit('producerClosed', {
+            producer_id
+        })
+        this.producers.get(producer_id).close()
+        this.producers.delete(producer_id)
+        this.producerLabel.delete(type)
+
+        if (type !== mediaType.audio) {
+            let elem = document.getElementById(producer_id)
+            elem.srcObject.getTracks().forEach(function (track) {
+                track.stop()
+            })
+            elem.parentNode.removeChild(elem)
+        }
+
+        switch (type) {
+            case mediaType.audio:
+                this.event(_EVENTS.stopAudio)
+                break
+            case mediaType.video:
+                this.event(_EVENTS.stopVideo)
+                break
+            case mediaType.screen:
+                this.event(_EVENTS.stopScreen)
+                break;
+            default:
+                return
+                break;
+        }
+
+    }
+
+    pauseProducer(type) {
+        if (!this.producerLabel.has(type)) {
+            console.log('there is no producer for this type ' + type)
+            return
+        }
+        let producer_id = this.producerLabel.get(type)
+        this.producers.get(producer_id).pause()
+
+    }
+
+    resumeProducer(type) {
+        if (!this.producerLabel.has(type)) {
+            console.log('there is no producer for this type ' + type)
+            return
+        }
+        let producer_id = this.producerLabel.get(type)
+        this.producers.get(producer_id).resume()
+
+    }
+
+    removeConsumer(consumer_id) {
+        let elem = document.getElementById(consumer_id)
+        elem.srcObject.getTracks().forEach(function (track) {
+            track.stop()
+        })
+        elem.parentNode.removeChild(elem)
+
+        this.consumers.delete(consumer_id)
+    }
+
+    exit(offline = false) {
+
+        let clean = function () {
+            this._isOpen = false
+            this.consumerTransport.close()
+            this.producerTransport.close()
+            this.socket.off('disconnect')
+            this.socket.off('newProducers')
+            this.socket.off('consumerClosed')
+        }.bind(this)
+
+        if (!offline) {
+            this.socket.request('exitRoom').then(e => console.log(e)).catch(e => console.warn(e)).finally(function () {
+                clean()
+            }.bind(this))
+        } else {
+            clean()
+        }
+
+        this.event(_EVENTS.exitRoom)
+
+    }
+
+    ///////  HELPERS //////////
+
+    async roomInfo() {
+        let info = await socket.request('getMyRoomInfo')
+        return info
+    }
+
+    static get mediaType() {
+        return mediaType
+    }
+
+    event(evt) {
+        if (this.eventListeners.has(evt)) {
+            this.eventListeners.get(evt).forEach(callback => callback())
+        }
+    }
+
+    on(evt, callback) {
+        console.log('evt', evt);
+        console.log('callback', callback);
+        this.eventListeners.get(evt).push(callback)
+    }
+
+
+    //////// GETTERS ////////
+
+    isOpen() {
+        return this._isOpen
+    }
+
+    static get EVENTS() {
+        return _EVENTS
+    }
+}
+
+module.exports = RoomClient;

File diff suppressed because it is too large
+ 148 - 0
src/static/grpc/mediasoupclient.min.js


File diff suppressed because it is too large
+ 5 - 0
src/static/grpc/socket.io.js


Some files were not shown because too many files changed in this diff