neko/src/component/internal/connection.ts

246 lines
6.2 KiB
TypeScript
Raw Normal View History

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'
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
2021-06-21 09:56:34 +12:00
const websocketTimer = 1000
const webrtcTimer = 10000
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-24 07:24:34 +12:00
private _activated = false
private _url = ''
private _token = ''
private _log = new Logger('connection')
2021-06-19 09:15:29 +12:00
private _state: Connection
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-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('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', () => {
if (this._state.status === 'connected') {
2021-06-20 04:36:48 +12:00
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('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', () => {
if (this._state.status === 'connected') {
2021-06-20 04:36:48 +12:00
Vue.set(this._state, 'status', 'disconnected')
}
2021-06-20 05:36:59 +12:00
this._webrtcReconnect()
2021-06-19 09:15:29 +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
}
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> {
2021-06-21 09:56:34 +12:00
try {
await this._websocketConnect()
2021-06-20 05:36:59 +12:00
2021-06-21 09:56:34 +12:00
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-21 09:56:34 +12:00
await this._webrtcConnect(video)
2021-06-24 07:24:34 +12:00
this._activated = true
2021-06-21 09:56:34 +12:00
} catch (e) {
this.disconnect()
throw e
}
2021-06-17 19:22:02 +12:00
}
2021-06-18 10:31:03 +12:00
public disconnect() {
2021-06-24 07:24:34 +12:00
this._activated = false
2021-06-20 05:36:59 +12:00
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
2021-06-24 07:24:34 +12:00
public get activated() {
return this._activated
}
2021-06-20 05:36:59 +12:00
async _websocketConnect() {
2021-06-21 09:56:34 +12:00
Vue.set(this._state, 'status', 'connecting')
2021-06-20 05:36:59 +12:00
let url = this._url
if (this._token) {
url += '?token=' + encodeURIComponent(this._token)
}
2021-06-21 09:56:34 +12:00
this.websocket.connect(url)
await new Promise<void>((res, rej) => {
const timeout = window.setTimeout(() => {
this.websocket.disconnect()
rej(new Error('timeouted'))
}, websocketTimer)
this.websocket.once('connected', () => {
window.clearTimeout(timeout)
res()
})
this.websocket.once('disconnected', (reason) => {
window.clearTimeout(timeout)
rej(reason)
})
})
2021-06-20 05:36:59 +12:00
}
_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 () => {
2021-06-24 07:24:34 +12:00
while (this.activated) {
2021-06-20 05:36:59 +12:00
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)
}
2021-06-21 09:56:34 +12:00
async _webrtcConnect(video?: string) {
2021-06-20 05:36:59 +12:00
if (video && !this._state.webrtc.videos.includes(video)) {
throw new Error('video id not found')
}
2021-06-21 09:56:34 +12:00
Vue.set(this._state, 'status', 'connecting')
2021-06-20 05:36:59 +12:00
this.websocket.send(EVENT.SIGNAL_REQUEST, { video: video })
2021-06-21 09:56:34 +12:00
await new Promise<void>((res, rej) => {
const timeout = window.setTimeout(() => {
this.webrtc.disconnect()
rej(new Error('timeouted'))
}, webrtcTimer)
this.webrtc.once('connected', () => {
window.clearTimeout(timeout)
res()
})
this.webrtc.once('disconnected', (reason) => {
window.clearTimeout(timeout)
rej(reason)
})
})
2021-06-20 05:36:59 +12:00
}
2021-06-21 09:56:34 +12:00
_webrtcIsReconnecting = false
2021-06-20 05:36:59 +12:00
_webrtcReconnect() {
2021-06-21 09:56:34 +12:00
if (this._webrtcIsReconnecting) {
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
2021-06-21 09:56:34 +12:00
setTimeout(async () => {
2021-06-24 07:24:34 +12:00
while (this.activated && this.websocket.connected) {
2021-06-21 09:56:34 +12:00
try {
await this._webrtcConnect(lastVideo)
break
} catch (e) {
this._log.debug(`webrtc reconnection failed`, e)
}
2021-06-20 06:20:46 +12:00
}
2021-06-20 05:36:59 +12:00
2021-06-21 09:56:34 +12:00
this._webrtcIsReconnecting = false
this._log.debug(`webrtc reconnection finished`)
}, 0)
2021-06-20 05:36:59 +12:00
}
2021-06-17 19:22:02 +12:00
}