2021-06-19 09:15:29 +12:00
|
|
|
import Vue from 'vue'
|
2021-06-17 19:22:02 +12:00
|
|
|
import EventEmitter from 'eventemitter3'
|
2021-06-20 05:36:59 +12:00
|
|
|
import { Logger } from '../utils/logger'
|
2021-06-20 04:51:14 +12:00
|
|
|
import * as EVENT from '../types/events'
|
2021-06-17 19:22:02 +12:00
|
|
|
|
|
|
|
import { NekoWebSocket } from './websocket'
|
|
|
|
import { NekoWebRTC, WebRTCStats } from './webrtc'
|
2021-06-19 09:15:29 +12:00
|
|
|
import { Connection } from '../types/state'
|
2021-06-17 19:22:02 +12:00
|
|
|
|
|
|
|
export interface NekoConnectionEvents {
|
2021-06-20 04:44:43 +12:00
|
|
|
disconnect: (error?: Error) => void
|
2021-06-17 19:22:02 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
|
2021-06-18 10:31:03 +12:00
|
|
|
private _url: string
|
|
|
|
private _token: string
|
2021-06-19 09:15:29 +12:00
|
|
|
private _state: Connection
|
2021-06-20 05:36:59 +12:00
|
|
|
private _log: Logger
|
|
|
|
private _shouldReconnect = false
|
2021-06-17 19:22:02 +12:00
|
|
|
|
2021-06-18 10:31:03 +12:00
|
|
|
public websocket = new NekoWebSocket()
|
|
|
|
public webrtc = new NekoWebRTC()
|
2021-06-17 19:22:02 +12:00
|
|
|
|
2021-06-19 09:15:29 +12:00
|
|
|
constructor(state: Connection) {
|
2021-06-17 19:22:02 +12:00
|
|
|
super()
|
|
|
|
|
2021-06-18 10:31:03 +12:00
|
|
|
this._url = ''
|
|
|
|
this._token = ''
|
2021-06-20 05:36:59 +12:00
|
|
|
this._log = new Logger('connection')
|
2021-06-19 09:15:29 +12:00
|
|
|
this._state = state
|
|
|
|
|
|
|
|
// initial state
|
|
|
|
Vue.set(this._state, 'type', 'webrtc')
|
2021-06-20 04:36:48 +12:00
|
|
|
|
2021-06-19 09:15:29 +12:00
|
|
|
// websocket
|
|
|
|
this.websocket.on('connecting', () => {
|
2021-06-20 04:36:48 +12:00
|
|
|
if (this._state.status !== 'connecting') {
|
|
|
|
Vue.set(this._state, 'status', 'connecting')
|
|
|
|
}
|
2021-06-19 09:15:29 +12:00
|
|
|
})
|
|
|
|
this.websocket.on('connected', () => {
|
2021-06-20 05:36:59 +12:00
|
|
|
if (this.websocket.connected && this.webrtc.connected) {
|
2021-06-20 04:36:48 +12:00
|
|
|
Vue.set(this._state, 'status', 'connected')
|
|
|
|
}
|
2021-06-19 09:15:29 +12:00
|
|
|
})
|
|
|
|
this.websocket.on('disconnected', () => {
|
2021-06-20 04:36:48 +12:00
|
|
|
if (this._state.status !== 'disconnected') {
|
|
|
|
Vue.set(this._state, 'status', 'disconnected')
|
|
|
|
}
|
2021-06-20 05:36:59 +12:00
|
|
|
|
|
|
|
this._websocketReconnect()
|
2021-06-19 09:15:29 +12:00
|
|
|
})
|
|
|
|
|
|
|
|
// webrtc
|
|
|
|
this.webrtc.on('connecting', () => {
|
2021-06-20 04:36:48 +12:00
|
|
|
if (this._state.status !== 'connecting') {
|
|
|
|
Vue.set(this._state, 'status', 'connecting')
|
|
|
|
}
|
2021-06-19 09:15:29 +12:00
|
|
|
})
|
|
|
|
this.webrtc.on('connected', () => {
|
2021-06-20 05:36:59 +12:00
|
|
|
if (this.websocket.connected && this.webrtc.connected) {
|
2021-06-20 04:36:48 +12:00
|
|
|
Vue.set(this._state, 'status', 'connected')
|
|
|
|
}
|
2021-06-19 09:15:29 +12:00
|
|
|
})
|
|
|
|
this.webrtc.on('disconnected', () => {
|
2021-06-20 04:36:48 +12:00
|
|
|
if (this._state.status !== 'disconnected') {
|
|
|
|
Vue.set(this._state, 'status', 'disconnected')
|
|
|
|
}
|
2021-06-20 05:36:59 +12:00
|
|
|
|
|
|
|
this._webrtcReconnect()
|
2021-06-19 09:15:29 +12:00
|
|
|
})
|
2021-06-20 04:51:14 +12:00
|
|
|
|
|
|
|
let webrtcCongestion: number = 0
|
|
|
|
this.webrtc.on('stats', (stats: WebRTCStats) => {
|
|
|
|
Vue.set(this._state.webrtc, 'stats', stats)
|
|
|
|
|
|
|
|
// if automatic quality adjusting is turned off
|
|
|
|
if (!this._state.webrtc.auto) return
|
|
|
|
|
|
|
|
// if there are no or just one quality, no switching can be done
|
|
|
|
if (this._state.webrtc.videos.length <= 1) return
|
|
|
|
|
|
|
|
// current quality is not known
|
|
|
|
if (this._state.webrtc.video == null) return
|
|
|
|
|
|
|
|
// check if video is not playing
|
|
|
|
if (stats.fps) {
|
|
|
|
webrtcCongestion = 0
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// try to downgrade quality if it happend many times
|
|
|
|
if (++webrtcCongestion >= 3) {
|
|
|
|
const index = this._state.webrtc.videos.indexOf(this._state.webrtc.video)
|
|
|
|
|
|
|
|
// edge case: current quality is not in qualities list
|
|
|
|
if (index === -1) return
|
|
|
|
|
|
|
|
// current quality is the lowest one
|
|
|
|
if (index + 1 == this._state.webrtc.videos.length) return
|
|
|
|
|
|
|
|
// downgrade video quality
|
|
|
|
this.setVideo(this._state.webrtc.videos[index + 1])
|
|
|
|
webrtcCongestion = 0
|
|
|
|
}
|
|
|
|
})
|
2021-06-18 10:31:03 +12:00
|
|
|
}
|
2021-06-17 19:22:02 +12:00
|
|
|
|
2021-06-18 10:31:03 +12:00
|
|
|
public setUrl(url: string) {
|
|
|
|
this._url = url.replace(/^http/, 'ws').replace(/\/+$/, '') + '/api/ws'
|
|
|
|
}
|
2021-06-17 19:22:02 +12:00
|
|
|
|
2021-06-18 10:31:03 +12:00
|
|
|
public setToken(token: string) {
|
|
|
|
this._token = token
|
2021-06-17 19:22:02 +12:00
|
|
|
}
|
|
|
|
|
2021-06-20 04:51:14 +12:00
|
|
|
public setVideo(video: string) {
|
|
|
|
if (!this._state.webrtc.videos.includes(video)) {
|
|
|
|
throw new Error('video id not found')
|
|
|
|
}
|
|
|
|
|
|
|
|
this.websocket.send(EVENT.SIGNAL_VIDEO, { video: video })
|
|
|
|
}
|
|
|
|
|
2021-06-20 05:36:59 +12:00
|
|
|
public async connect(video?: string): Promise<void> {
|
|
|
|
await this._websocketConnect()
|
|
|
|
|
|
|
|
if (video && !this._state.webrtc.videos.includes(video)) {
|
|
|
|
throw new Error('video id not found')
|
|
|
|
}
|
2021-06-18 10:31:03 +12:00
|
|
|
|
2021-06-20 05:36:59 +12:00
|
|
|
this._webrtcConnect(video)
|
2021-06-20 06:21:32 +12:00
|
|
|
|
|
|
|
this._shouldReconnect = true
|
2021-06-17 19:22:02 +12:00
|
|
|
}
|
|
|
|
|
2021-06-18 10:31:03 +12:00
|
|
|
public disconnect() {
|
2021-06-20 05:36:59 +12:00
|
|
|
this._shouldReconnect = false
|
|
|
|
|
2021-06-20 04:44:43 +12:00
|
|
|
this.webrtc.disconnect()
|
2021-06-18 10:31:03 +12:00
|
|
|
this.websocket.disconnect()
|
2021-06-20 05:36:59 +12:00
|
|
|
|
2021-06-20 04:36:48 +12:00
|
|
|
Vue.set(this._state, 'status', 'disconnected')
|
2021-06-20 04:44:43 +12:00
|
|
|
this.emit('disconnect')
|
2021-06-17 19:22:02 +12:00
|
|
|
}
|
2021-06-20 05:36:59 +12:00
|
|
|
|
|
|
|
async _websocketConnect() {
|
|
|
|
let url = this._url
|
|
|
|
if (this._token) {
|
|
|
|
url += '?token=' + encodeURIComponent(this._token)
|
|
|
|
}
|
|
|
|
|
|
|
|
await this.websocket.connect(url)
|
|
|
|
}
|
|
|
|
|
|
|
|
_websocketIsReconnecting = false
|
|
|
|
_websocketReconnect() {
|
|
|
|
if (this._websocketIsReconnecting) {
|
2021-06-20 06:20:46 +12:00
|
|
|
this._log.debug(`websocket reconnection already in progress`)
|
2021-06-20 05:36:59 +12:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-06-20 06:20:46 +12:00
|
|
|
this._log.debug(`starting websocket reconnection`)
|
|
|
|
|
2021-06-20 05:36:59 +12:00
|
|
|
setTimeout(async () => {
|
|
|
|
while (this._shouldReconnect) {
|
|
|
|
try {
|
|
|
|
await this._websocketConnect()
|
|
|
|
this._webrtcReconnect()
|
|
|
|
break
|
|
|
|
} catch (e) {
|
2021-06-20 06:20:46 +12:00
|
|
|
this._log.debug(`websocket reconnection failed`, e)
|
2021-06-20 05:36:59 +12:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._websocketIsReconnecting = false
|
2021-06-20 06:20:46 +12:00
|
|
|
this._log.debug(`websocket reconnection finished`)
|
2021-06-20 05:36:59 +12:00
|
|
|
}, 0)
|
|
|
|
}
|
|
|
|
|
|
|
|
_webrtcConnect(video?: string) {
|
|
|
|
if (video && !this._state.webrtc.videos.includes(video)) {
|
|
|
|
throw new Error('video id not found')
|
|
|
|
}
|
|
|
|
|
|
|
|
this.websocket.send(EVENT.SIGNAL_REQUEST, { video: video })
|
|
|
|
}
|
|
|
|
|
|
|
|
_webrtcReconnTimer?: number
|
|
|
|
_webrtcReconnect() {
|
|
|
|
if (this._webrtcReconnTimer) {
|
2021-06-20 06:20:46 +12:00
|
|
|
this._log.debug(`webrtc reconnection already in progress`)
|
2021-06-20 05:36:59 +12:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const lastVideo = this._state.webrtc.video ?? undefined
|
2021-06-20 06:20:46 +12:00
|
|
|
this._log.debug(`starting webrtc reconnection`)
|
2021-06-20 05:36:59 +12:00
|
|
|
|
|
|
|
const reconnFunc = async () => {
|
|
|
|
if (!this._shouldReconnect || !this.websocket.connected || this.webrtc.connected) {
|
|
|
|
clearInterval(this._webrtcReconnTimer)
|
|
|
|
this._webrtcReconnTimer = undefined
|
2021-06-20 06:20:46 +12:00
|
|
|
this._log.debug(`webrtc reconnection finished`)
|
2021-06-20 05:36:59 +12:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
this._webrtcConnect(lastVideo)
|
2021-06-20 06:20:46 +12:00
|
|
|
} catch (e) {
|
|
|
|
this._log.debug(`webrtc reconnection failed`, e)
|
|
|
|
}
|
2021-06-20 05:36:59 +12:00
|
|
|
}
|
|
|
|
|
|
|
|
this._webrtcReconnTimer = window.setInterval(reconnFunc, 1000)
|
|
|
|
reconnFunc()
|
|
|
|
}
|
2021-06-17 19:22:02 +12:00
|
|
|
}
|