mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
Refactor signaling for video and audio (#39)
* refactor webrtc video and audio. * do not reconnect if video is disabled. * export webrtc types.
This commit is contained in:
parent
52107f5934
commit
e58aecc49c
@ -1,6 +1,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import EventEmitter from 'eventemitter3'
|
import EventEmitter from 'eventemitter3'
|
||||||
import * as EVENT from '../types/events'
|
import * as EVENT from '../types/events'
|
||||||
|
import * as webrtcTypes from '../types/webrtc'
|
||||||
|
|
||||||
import { NekoWebSocket } from './websocket'
|
import { NekoWebSocket } from './websocket'
|
||||||
import { NekoLoggerFactory } from './logger'
|
import { NekoLoggerFactory } from './logger'
|
||||||
@ -24,6 +25,7 @@ export interface NekoConnectionEvents {
|
|||||||
export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
|
export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
|
||||||
private _open = false
|
private _open = false
|
||||||
private _closing = false
|
private _closing = false
|
||||||
|
private _peerRequest?: webrtcTypes.PeerRequest
|
||||||
|
|
||||||
public websocket = new NekoWebSocket()
|
public websocket = new NekoWebSocket()
|
||||||
public logger = new NekoLoggerFactory(this.websocket)
|
public logger = new NekoLoggerFactory(this.websocket)
|
||||||
@ -62,9 +64,16 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.websocket.connected && !this.webrtc.connected) {
|
if (this.websocket.connected && !this.webrtc.connected) {
|
||||||
|
// if custom peer request is set, send custom peer request
|
||||||
|
if (this._peerRequest) {
|
||||||
|
this.websocket.send(EVENT.SIGNAL_REQUEST, this._peerRequest)
|
||||||
|
this._peerRequest = undefined
|
||||||
|
} else {
|
||||||
|
// otherwise use reconnectors connect method
|
||||||
this._reconnector.webrtc.connect()
|
this._reconnector.webrtc.connect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._onDisconnectHandle = () => {
|
this._onDisconnectHandle = () => {
|
||||||
Vue.set(this._state.websocket, 'connected', this.websocket.connected)
|
Vue.set(this._state.websocket, 'connected', this.websocket.connected)
|
||||||
@ -109,10 +118,10 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
|
|||||||
|
|
||||||
this._webrtcCongestionControlHandle = (stats: WebRTCStats) => {
|
this._webrtcCongestionControlHandle = (stats: WebRTCStats) => {
|
||||||
// if automatic quality adjusting is turned off
|
// if automatic quality adjusting is turned off
|
||||||
if (this._state.webrtc.auto) return
|
if (this._state.webrtc.video.auto) return
|
||||||
|
|
||||||
// when connection is paused, 0fps and muted track is expected
|
// when connection is paused or video disabled, 0fps and muted track is expected
|
||||||
if (stats.paused) return
|
if (stats.paused || this._state.webrtc.video.disabled) return
|
||||||
|
|
||||||
// if automatic quality adjusting is turned off
|
// if automatic quality adjusting is turned off
|
||||||
if (!this._reconnector.webrtc.isOpen) return
|
if (!this._reconnector.webrtc.isOpen) return
|
||||||
@ -121,7 +130,7 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
|
|||||||
if (this._state.webrtc.videos.length <= 1) return
|
if (this._state.webrtc.videos.length <= 1) return
|
||||||
|
|
||||||
// current quality is not known
|
// current quality is not known
|
||||||
if (this._state.webrtc.video == null) return
|
if (this._state.webrtc.video.id == '') return
|
||||||
|
|
||||||
// check if video is not playing smoothly
|
// check if video is not playing smoothly
|
||||||
if (stats.fps && stats.packetLoss < WEBRTC_RECONN_MAX_LOSS && !stats.muted) {
|
if (stats.fps && stats.packetLoss < WEBRTC_RECONN_MAX_LOSS && !stats.muted) {
|
||||||
@ -142,7 +151,7 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
|
|||||||
|
|
||||||
webrtcCongestion = 0
|
webrtcCongestion = 0
|
||||||
|
|
||||||
const quality = this._webrtcQualityDowngrade(this._state.webrtc.video)
|
const quality = this._webrtcQualityDowngrade(this._state.webrtc.video.id)
|
||||||
|
|
||||||
// downgrade if lower video quality exists
|
// downgrade if lower video quality exists
|
||||||
if (quality && this.webrtc.connected) {
|
if (quality && this.webrtc.connected) {
|
||||||
@ -176,27 +185,13 @@ export class NekoConnection extends EventEmitter<NekoConnectionEvents> {
|
|||||||
return this.logger.new(scope)
|
return this.logger.new(scope)
|
||||||
}
|
}
|
||||||
|
|
||||||
public open(video?: string, auto?: boolean) {
|
public open(peerRequest?: webrtcTypes.PeerRequest) {
|
||||||
if (this._open) {
|
if (this._open) {
|
||||||
throw new Error('connection already open')
|
throw new Error('connection already open')
|
||||||
}
|
}
|
||||||
|
|
||||||
this._open = true
|
this._open = true
|
||||||
|
this._peerRequest = peerRequest
|
||||||
if (video) {
|
|
||||||
if (!this._state.webrtc.videos.includes(video)) {
|
|
||||||
throw new Error('video id not found')
|
|
||||||
}
|
|
||||||
|
|
||||||
Vue.set(this._state.webrtc, 'video', video)
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we didn't specify auto
|
|
||||||
if (typeof auto == 'undefined') {
|
|
||||||
// if we didn't specify video, set auto to true
|
|
||||||
auto = !video
|
|
||||||
}
|
|
||||||
Vue.set(this._state.webrtc, 'auto', auto)
|
|
||||||
|
|
||||||
Vue.set(this._state, 'status', 'connecting')
|
Vue.set(this._state, 'status', 'connecting')
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ export class NekoMessages extends EventEmitter<NekoEvents> {
|
|||||||
// Signal Events
|
// Signal Events
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
|
||||||
protected async [EVENT.SIGNAL_PROVIDE]({ sdp, iceservers }: message.SignalProvide) {
|
protected async [EVENT.SIGNAL_PROVIDE]({ sdp, iceservers, video, audio }: message.SignalProvide) {
|
||||||
this._localLog.debug(`EVENT.SIGNAL_PROVIDE`)
|
this._localLog.debug(`EVENT.SIGNAL_PROVIDE`)
|
||||||
|
|
||||||
// create WebRTC connection
|
// create WebRTC connection
|
||||||
@ -158,6 +158,9 @@ export class NekoMessages extends EventEmitter<NekoEvents> {
|
|||||||
|
|
||||||
// TODO: Return whole signal description (if answer / offer).
|
// TODO: Return whole signal description (if answer / offer).
|
||||||
this.emit('connection.webrtc.sdp', 'remote', sdp)
|
this.emit('connection.webrtc.sdp', 'remote', sdp)
|
||||||
|
|
||||||
|
this[EVENT.SIGNAL_VIDEO](video)
|
||||||
|
this[EVENT.SIGNAL_AUDIO](audio)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected async [EVENT.SIGNAL_OFFER]({ sdp }: message.SignalDescription) {
|
protected async [EVENT.SIGNAL_OFFER]({ sdp }: message.SignalDescription) {
|
||||||
@ -193,10 +196,16 @@ export class NekoMessages extends EventEmitter<NekoEvents> {
|
|||||||
this.emit('connection.webrtc.sdp.candidate', 'remote', candidate)
|
this.emit('connection.webrtc.sdp.candidate', 'remote', candidate)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.SIGNAL_VIDEO]({ video, auto }: message.SignalVideo) {
|
protected [EVENT.SIGNAL_VIDEO]({ disabled, id, auto }: message.SignalVideo) {
|
||||||
this._localLog.debug(`EVENT.SIGNAL_VIDEO`, { video, auto })
|
this._localLog.debug(`EVENT.SIGNAL_VIDEO`, { disabled, id, auto })
|
||||||
Vue.set(this._state.connection.webrtc, 'video', video)
|
Vue.set(this._state.connection.webrtc.video, 'disabled', disabled)
|
||||||
Vue.set(this._state.connection.webrtc, 'auto', !!auto)
|
Vue.set(this._state.connection.webrtc.video, 'id', id)
|
||||||
|
Vue.set(this._state.connection.webrtc.video, 'auto', auto)
|
||||||
|
}
|
||||||
|
|
||||||
|
protected [EVENT.SIGNAL_AUDIO]({ disabled }: message.SignalAudio) {
|
||||||
|
this._localLog.debug(`EVENT.SIGNAL_AUDIO`, { disabled })
|
||||||
|
Vue.set(this._state.connection.webrtc.audio, 'disabled', disabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
protected [EVENT.SIGNAL_CLOSE]() {
|
protected [EVENT.SIGNAL_CLOSE]() {
|
||||||
|
@ -37,9 +37,25 @@ export class WebrtcReconnector extends ReconnectorAbstract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this._websocket.connected) {
|
if (this._websocket.connected) {
|
||||||
|
// use requests from state to connect with selected values
|
||||||
|
|
||||||
|
let selector = null
|
||||||
|
if (this._state.webrtc.video.id) {
|
||||||
|
selector = {
|
||||||
|
id: this._state.webrtc.video.id,
|
||||||
|
type: 'exact',
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this._websocket.send(EVENT.SIGNAL_REQUEST, {
|
this._websocket.send(EVENT.SIGNAL_REQUEST, {
|
||||||
video: this._state.webrtc.video,
|
video: {
|
||||||
auto: this._state.webrtc.auto,
|
disabled: this._state.webrtc.video.disabled,
|
||||||
|
selector,
|
||||||
|
auto: this._state.webrtc.video.auto,
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
disabled: this._state.webrtc.audio.disabled,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
export * as ApiModels from './api/models'
|
export * as ApiModels from './api/models'
|
||||||
export * as StateModels from './types/state'
|
export * as StateModels from './types/state'
|
||||||
import * as EVENT from './types/events'
|
export * as webrtcTypes from './types/webrtc'
|
||||||
|
|
||||||
import { Configuration } from './api/configuration'
|
import { Configuration } from './api/configuration'
|
||||||
import { AxiosInstance } from 'axios'
|
import { AxiosInstance } from 'axios'
|
||||||
@ -89,6 +89,8 @@
|
|||||||
import { register as VideoRegister } from './internal/video'
|
import { register as VideoRegister } from './internal/video'
|
||||||
|
|
||||||
import { ReconnectorConfig } from './types/reconnector'
|
import { ReconnectorConfig } from './types/reconnector'
|
||||||
|
import * as EVENT from './types/events'
|
||||||
|
import * as webrtcTypes from './types/webrtc'
|
||||||
import NekoState from './types/state'
|
import NekoState from './types/state'
|
||||||
import { CursorDrawFunction, InactiveCursorDrawFunction, Dimension } from './types/cursors'
|
import { CursorDrawFunction, InactiveCursorDrawFunction, Dimension } from './types/cursors'
|
||||||
import Overlay from './overlay.vue'
|
import Overlay from './overlay.vue'
|
||||||
@ -169,8 +171,14 @@
|
|||||||
backoff_ms: 1500,
|
backoff_ms: 1500,
|
||||||
},
|
},
|
||||||
stats: null,
|
stats: null,
|
||||||
video: null,
|
video: {
|
||||||
|
disabled: false,
|
||||||
|
id: '',
|
||||||
auto: false,
|
auto: false,
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
disabled: false,
|
||||||
|
},
|
||||||
videos: [],
|
videos: [],
|
||||||
},
|
},
|
||||||
screencast: true, // TODO: Should get by API call.
|
screencast: true, // TODO: Should get by API call.
|
||||||
@ -383,7 +391,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public connect(video?: string, auto?: boolean) {
|
public connect(peerRequest?: webrtcTypes.PeerRequest) {
|
||||||
if (!this.state.authenticated) {
|
if (!this.state.authenticated) {
|
||||||
throw new Error('client not authenticated')
|
throw new Error('client not authenticated')
|
||||||
}
|
}
|
||||||
@ -392,7 +400,7 @@
|
|||||||
throw new Error('client is already connected')
|
throw new Error('client is already connected')
|
||||||
}
|
}
|
||||||
|
|
||||||
this.connection.open(video, auto)
|
this.connection.open(peerRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
public disconnect() {
|
public disconnect() {
|
||||||
@ -512,19 +520,12 @@
|
|||||||
this.connection.websocket.send(EVENT.SCREEN_SET, { width, height, rate })
|
this.connection.websocket.send(EVENT.SCREEN_SET, { width, height, rate })
|
||||||
}
|
}
|
||||||
|
|
||||||
public setWebRTCVideo(video?: string, auto?: boolean) {
|
public setWebRTCVideo(peerVideo: webrtcTypes.PeerVideoRequest) {
|
||||||
// if video has been set, check if it exists
|
this.connection.websocket.send(EVENT.SIGNAL_VIDEO, peerVideo)
|
||||||
if (video && !this.state.connection.webrtc.videos.includes(video)) {
|
|
||||||
throw new Error('video id not found')
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we didn't specify auto
|
public setWebRTCAudio(peerAudio: webrtcTypes.PeerAudioRequest) {
|
||||||
if (typeof auto == 'undefined') {
|
this.connection.websocket.send(EVENT.SIGNAL_AUDIO, peerAudio)
|
||||||
// if we didn't specify video, set auto to true
|
|
||||||
auto = !video
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.websocket.send(EVENT.SIGNAL_VIDEO, { video, auto })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender {
|
public addTrack(track: MediaStreamTrack, ...streams: MediaStream[]): RTCRtpSender {
|
||||||
@ -778,8 +779,10 @@
|
|||||||
|
|
||||||
// webrtc
|
// webrtc
|
||||||
Vue.set(this.state.connection.webrtc, 'stats', null)
|
Vue.set(this.state.connection.webrtc, 'stats', null)
|
||||||
Vue.set(this.state.connection.webrtc, 'video', null)
|
Vue.set(this.state.connection.webrtc.video, 'disabled', false)
|
||||||
Vue.set(this.state.connection.webrtc, 'auto', false)
|
Vue.set(this.state.connection.webrtc.video, 'id', '')
|
||||||
|
Vue.set(this.state.connection.webrtc.video, 'auto', false)
|
||||||
|
Vue.set(this.state.connection.webrtc.audio, 'disabled', false)
|
||||||
Vue.set(this.state.connection.webrtc, 'videos', [])
|
Vue.set(this.state.connection.webrtc, 'videos', [])
|
||||||
Vue.set(this.state.connection, 'type', 'none')
|
Vue.set(this.state.connection, 'type', 'none')
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ export const SIGNAL_ANSWER = 'signal/answer'
|
|||||||
export const SIGNAL_PROVIDE = 'signal/provide'
|
export const SIGNAL_PROVIDE = 'signal/provide'
|
||||||
export const SIGNAL_CANDIDATE = 'signal/candidate'
|
export const SIGNAL_CANDIDATE = 'signal/candidate'
|
||||||
export const SIGNAL_VIDEO = 'signal/video'
|
export const SIGNAL_VIDEO = 'signal/video'
|
||||||
|
export const SIGNAL_AUDIO = 'signal/audio'
|
||||||
export const SIGNAL_CLOSE = 'signal/close'
|
export const SIGNAL_CLOSE = 'signal/close'
|
||||||
|
|
||||||
export const SESSION_CREATED = 'session/created'
|
export const SESSION_CREATED = 'session/created'
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { ICEServer } from '../internal/webrtc'
|
import { ICEServer } from '../internal/webrtc'
|
||||||
import { Settings } from './state'
|
import { Settings } from './state'
|
||||||
|
import { PeerRequest, PeerVideo, PeerAudio } from './webrtc'
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// System
|
// System
|
||||||
@ -42,9 +43,13 @@ export interface SystemDisconnect {
|
|||||||
// Signal
|
// Signal
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
|
||||||
|
export type SignalRequest = PeerRequest
|
||||||
|
|
||||||
export interface SignalProvide {
|
export interface SignalProvide {
|
||||||
sdp: string
|
sdp: string
|
||||||
iceservers: ICEServer[]
|
iceservers: ICEServer[]
|
||||||
|
video: PeerVideo
|
||||||
|
audio: PeerAudio
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SignalCandidate = RTCIceCandidateInit
|
export type SignalCandidate = RTCIceCandidateInit
|
||||||
@ -53,10 +58,9 @@ export interface SignalDescription {
|
|||||||
sdp: string
|
sdp: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SignalVideo {
|
export type SignalVideo = PeerVideo
|
||||||
video: string
|
|
||||||
auto: boolean
|
export type SignalAudio = PeerAudio
|
||||||
}
|
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// Session
|
// Session
|
||||||
|
@ -38,8 +38,8 @@ export interface WebRTC {
|
|||||||
stable: boolean
|
stable: boolean
|
||||||
config: ReconnectorConfig
|
config: ReconnectorConfig
|
||||||
stats: WebRTCStats | null
|
stats: WebRTCStats | null
|
||||||
video: string | null
|
video: PeerVideo
|
||||||
auto: boolean
|
audio: PeerAudio
|
||||||
videos: string[]
|
videos: string[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,6 +47,10 @@ export interface ReconnectorConfig extends reconnectorTypes.ReconnectorConfig {}
|
|||||||
|
|
||||||
export interface WebRTCStats extends webrtcTypes.WebRTCStats {}
|
export interface WebRTCStats extends webrtcTypes.WebRTCStats {}
|
||||||
|
|
||||||
|
export interface PeerVideo extends webrtcTypes.PeerVideo {}
|
||||||
|
|
||||||
|
export interface PeerAudio extends webrtcTypes.PeerAudio {}
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// Video
|
// Video
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
|
@ -1,3 +1,36 @@
|
|||||||
|
export type StreamSelectorType = 'exact' | 'nearest' | 'lower' | 'higher'
|
||||||
|
|
||||||
|
export interface StreamSelector {
|
||||||
|
type: StreamSelectorType
|
||||||
|
id?: string
|
||||||
|
bitrate?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PeerRequest {
|
||||||
|
video?: PeerVideoRequest
|
||||||
|
audio?: PeerAudioRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PeerVideo {
|
||||||
|
disabled: boolean
|
||||||
|
id: string
|
||||||
|
auto: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PeerVideoRequest {
|
||||||
|
disabled?: boolean
|
||||||
|
selector?: StreamSelector
|
||||||
|
auto?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PeerAudio {
|
||||||
|
disabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PeerAudioRequest {
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export interface WebRTCStats {
|
export interface WebRTCStats {
|
||||||
paused: boolean
|
paused: boolean
|
||||||
bitrate: number
|
bitrate: number
|
||||||
|
@ -137,6 +137,13 @@
|
|||||||
>
|
>
|
||||||
webrtc is paused
|
webrtc is paused
|
||||||
</td>
|
</td>
|
||||||
|
<td
|
||||||
|
colspan="2"
|
||||||
|
style="background: darkviolet; text-align: center"
|
||||||
|
v-else-if="neko.state.connection.webrtc.video.disabled"
|
||||||
|
>
|
||||||
|
video is disabled
|
||||||
|
</td>
|
||||||
<td
|
<td
|
||||||
colspan="2"
|
colspan="2"
|
||||||
style="background: red; text-align: center"
|
style="background: red; text-align: center"
|
||||||
@ -158,15 +165,37 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<th>connection.webrtc.video</th>
|
<th title="connection.webrtc.video.disabled">connection.webrtc.video.disab..</th>
|
||||||
<td>{{ neko.state.connection.webrtc.video }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<th>connection.webrtc.auto</th>
|
|
||||||
<td>
|
<td>
|
||||||
<div class="space-between">
|
<div class="space-between">
|
||||||
<span>{{ neko.state.connection.webrtc.auto }}</span>
|
<span>{{ neko.state.connection.webrtc.video.disabled }}</span>
|
||||||
<button @click="neko.setWebRTCVideo(undefined, !neko.state.connection.webrtc.auto)">
|
<button @click="neko.setWebRTCVideo({ disabled: !neko.state.connection.webrtc.video.disabled })">
|
||||||
|
<i class="fas fa-toggle-on"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>connection.webrtc.video.id</th>
|
||||||
|
<td>{{ neko.state.connection.webrtc.video.id }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>connection.webrtc.video.auto</th>
|
||||||
|
<td>
|
||||||
|
<div class="space-between">
|
||||||
|
<span>{{ neko.state.connection.webrtc.video.auto }}</span>
|
||||||
|
<button @click="neko.setWebRTCVideo({ auto: !neko.state.connection.webrtc.video.auto })">
|
||||||
|
<i class="fas fa-toggle-on"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th title="connection.webrtc.audio.disabled">connection.webrtc.audio.disab..</th>
|
||||||
|
<td>
|
||||||
|
<div class="space-between">
|
||||||
|
<span>{{ neko.state.connection.webrtc.audio.disabled }}</span>
|
||||||
|
<button @click="neko.setWebRTCAudio({ disabled: !neko.state.connection.webrtc.audio.disabled })">
|
||||||
<i class="fas fa-toggle-on"></i>
|
<i class="fas fa-toggle-on"></i>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@ -178,7 +207,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<select :value="neko.state.connection.webrtc.video" @input="neko.setWebRTCVideo($event.target.value, false)">
|
<select
|
||||||
|
:value="neko.state.connection.webrtc.video.id"
|
||||||
|
@input="neko.setWebRTCVideo({ selector: { id: $event.target.value } })"
|
||||||
|
>
|
||||||
<option v-for="video in neko.state.connection.webrtc.videos" :key="video" :value="video">
|
<option v-for="video in neko.state.connection.webrtc.videos" :key="video" :value="video">
|
||||||
{{ video }}
|
{{ video }}
|
||||||
</option>
|
</option>
|
||||||
|
Loading…
Reference in New Issue
Block a user