refactor state & public methods.

This commit is contained in:
Miroslav Šedivý 2020-11-10 19:57:52 +01:00
parent 6177ffc242
commit 619ac3350b
3 changed files with 209 additions and 109 deletions

View File

@ -32,20 +32,67 @@
<td>{{ neko.state.video.playable }}</td>
</tr>
<tr class="ok">
<th>video.playing</th>
<td><input type="checkbox" v-model="neko.state.video.playing" /></td>
<th rowspan="2">video.playing</th>
<td>{{ neko.state.video.playing }}</td>
</tr>
<tr class="ok">
<th>video.volume</th>
<td><input type="range" min="0" max="1" v-model="neko.state.video.volume" step="0.01" /></td>
<td>
<button v-if="!neko.state.video.playing" @click="neko.play()">play</button>
<button v-else @click="neko.pause()">pause</button>
</td>
</tr>
<tr class="ok">
<th>control.scroll.inverse</th>
<td><input type="checkbox" v-model="neko.state.control.scroll.inverse" /></td>
<th rowspan="2">video.volume</th>
<td>{{ neko.state.video.volume }}</td>
</tr>
<tr class="ok">
<th>control.scroll.sensitivity</th>
<td><input type="number" v-model="neko.state.control.scroll.sensitivity" /></td>
<td>
<input
type="range"
min="0"
max="1"
:value="neko.state.video.volume"
@input="neko.setVolume(parseInt($event))"
step="0.01"
/>
</td>
</tr>
<tr class="ok">
<th rowspan="2">video.fullscreen</th>
<td>{{ neko.state.video.fullscreen }}</td>
</tr>
<tr class="ok">
<td>
<button v-if="!neko.state.video.fullscreen" @click="neko.requestFullscreen()">request</button>
<button v-else @click="neko.exitFullscreen()">exit</button>
</td>
</tr>
<tr class="ok">
<th rowspan="2">control.scroll.inverse</th>
<td>{{ neko.state.control.scroll.inverse }}</td>
</tr>
<tr class="ok">
<td>
<button v-if="!neko.state.control.scroll.inverse" @click="neko.setScrollInverse(true)">switch to inverse</button>
<button v-else @click="neko.setScrollInverse(false)">switch to normal</button>
</td>
</tr>
<tr class="ok">
<th rowspan="2">control.scroll.sensitivity</th>
<td>{{ neko.state.control.scroll.sensitivity }}</td>
</tr>
<tr class="ok">
<td>
<input
type="number"
:value="neko.state.control.scroll.sensitivity"
@input="neko.setScrollSensitivity(parseInt($event.target.value))"
/>
</td>
</tr>
<tr>
<th>control.clipboard.data</th>
<td>{{ neko.state.control.clipboard.data }}</td>
</tr>
<tr>
<th>control.host</th>
@ -64,13 +111,16 @@
<td>{{ neko.state.screen.size.rate }}</td>
</tr>
<tr class="ok">
<th>screen.configurations</th>
<th rowspan="2">screen.configurations</th>
<td>Total {{ neko.state.screen.configurations.length }} configurations.</td>
</tr>
<tr class="ok">
<td>
<select
:value="Object.values(neko.state.screen.size).join()"
@input="
a = String($event.target.value).split(',')
neko.screen.size(parseInt(a[0]), parseInt(a[1]), parseInt(a[2]))
neko.setScreenSize(parseInt(a[0]), parseInt(a[1]), parseInt(a[2]))
"
>
<option
@ -83,10 +133,6 @@
</select>
</td>
</tr>
<tr class="ok">
<th>screen.fullscreen</th>
<td><input type="checkbox" v-model="neko.state.screen.fullscreen" /></td>
</tr>
<tr class="ok">
<th>member.id</th>
<td>{{ neko.state.member.id }}</td>
@ -104,9 +150,15 @@
<td>{{ neko.state.member.is_watching }}</td>
</tr>
<tr class="ok">
<th>member.is_controlling</th>
<th rowspan="2">member.is_controlling</th>
<td>{{ neko.state.member.is_controlling }}</td>
</tr>
<tr class="ok">
<td>
<button v-if="!neko.state.member.is_controlling" @click="neko.controlRequest()">request control</button>
<button v-else @click="neko.controlRelease()">release control</button>
</td>
</tr>
<tr>
<th>member.can_watch</th>
<td>{{ neko.state.member.can_watch }}</td>
@ -126,15 +178,17 @@
</table>
</div>
<div>
<button @click="connect()">Connect</button>
<button @click="disconnect()">Disonnect</button>
<div v-if="loaded && !neko.connected">
<input type="text" placeholder="URL" v-model="url" />
<input type="text" placeholder="Password" v-model="pass" />
<input type="text" placeholder="Display Name" v-model="name" />
<button @click="connect()">Connect</button>
</div>
<button v-if="loaded && neko.connected" @click="disconnect()">Disonnect</button>
<template v-if="loaded && neko.connected">
<button v-if="!is_controlling" @click="neko.control.request()">request control</button>
<button v-else @click="neko.control.release()">release control</button>
<button v-if="neko.state.video.playing" @click="neko.state.video.playing = false">pause stream</button>
<button v-else @click="neko.state.video.playing = true">play stream</button><br />
<button v-if="!is_controlling" @click="neko.controlRequest()">request control</button>
<button v-else @click="neko.controlRelease()">release control</button>
</template>
<div ref="container" style="width: 1280px; height: 720px; border: 2px solid red">
@ -182,8 +236,12 @@
return this.neko.state.member.is_controlling
}
url: string = 'ws://192.168.1.20:3000/'
pass: string = 'admin'
name: string = 'test'
connect() {
this.neko.connect('ws://192.168.1.20:3000/', 'admin', 'test')
this.neko.connect(this.url, this.pass, this.name)
}
disconnect() {

View File

@ -52,12 +52,6 @@
import NekoState from '~/types/state'
import Overlay from './overlay.vue'
export interface NekoEvents {
connecting: () => void
connected: () => void
disconnected: (error?: Error) => void
}
@Component({
name: 'neko-canvas',
components: {
@ -67,12 +61,15 @@
export default class extends Vue {
@Ref('component') readonly _component!: HTMLElement
@Ref('container') readonly _container!: HTMLElement
@Ref('video') readonly video!: HTMLVideoElement
@Ref('video') readonly _video!: HTMLVideoElement
private websocket = new NekoWebSocket()
private webrtc = new NekoWebRTC()
private observer = new ResizeObserver(this.onResize.bind(this))
websocket = new NekoWebSocket()
webrtc = new NekoWebRTC()
observer = new ResizeObserver(this.onResize.bind(this))
/////////////////////////////
// Public state
/////////////////////////////
public state = {
connection: {
websocket: 'disconnected',
@ -86,12 +83,16 @@
playable: false,
playing: false,
volume: 0,
fullscreen: false,
},
control: {
scroll: {
inverse: true,
sensitivity: 1,
},
clipboard: {
data: null,
},
host: null,
},
screen: {
@ -101,7 +102,6 @@
rate: 30,
},
configurations: [],
fullscreen: false,
},
member: {
id: null,
@ -116,64 +116,18 @@
members: [],
} as NekoState
public events = new NekoMessages(this.websocket, this.state)
public get connected() {
return this.state.connection.websocket == 'connected' && this.state.connection.webrtc == 'connected'
}
@Watch('state.video.playing')
onVideoPlayingChanged(play: boolean) {
if (this.video.paused && play) {
this.video.play()
}
if (!this.video.paused && !play) {
this.video.pause()
}
// TODO: check if user has tab focused and send via websocket
Vue.set(this.state.member, 'is_watching', play)
}
@Watch('state.video.volume')
onVideoVolumeChanged(value: number) {
if (value < 0 || value > 1) {
throw new Error('Out of range. Value must be between 0 and 1.')
}
this.video.volume = value
}
@Watch('state.screen.size')
onScreenSizeChanged() {
this.onResize()
}
@Watch('state.screen.fullscreen')
onScreenFullscreenChanged() {
if (document.fullscreenElement !== null) {
document.exitFullscreen()
} else {
this._component.requestFullscreen()
}
}
public control = {
request: () => {
this.websocket.send('control/request')
},
release: () => {
this.websocket.send('control/release')
},
}
public screen = {
size: (width: number, height: number, rate: number) => {
this.websocket.send('screen/set', { width, height, rate })
},
}
/////////////////////////////
// Public events
/////////////////////////////
public events = new NekoMessages(this.websocket, this.state)
/////////////////////////////
// Public methods
/////////////////////////////
public connect(url: string, password: string, name: string) {
if (this.connected) {
throw new Error('client already connected')
@ -189,32 +143,91 @@
}
this.websocket.disconnect()
// TODO: reset state
Vue.set(this.state.member, 'is_controlling', false)
}
private mounted() {
// update canvas on resize
public play() {
this._video.play()
}
public pause() {
this._video.pause()
}
public setVolume(value: number) {
if (value < 0 || value > 1) {
throw new Error('Out of range. Value must be between 0 and 1.')
}
this._video.volume = value
}
public requestFullscreen() {
this._component.requestFullscreen()
}
public exitFullscreen() {
document.exitFullscreen()
}
public setScrollInverse(value: boolean = true) {
Vue.set(this.state.control.scroll, 'inverse', value)
}
public setScrollSensitivity(value: number) {
Vue.set(this.state.control.scroll, 'sensitivity', value)
}
public setClipboardData(value: number) {
// TODO: Via REST API.
}
public controlRequest() {
// TODO: Via REST API.
this.websocket.send('control/request')
}
public controlRelease() {
// TODO: Via REST API.
this.websocket.send('control/release')
}
public controlTake() {
// TODO: Via REST API.
}
public controlGive(id: string) {
// TODO: Via REST API.
}
public controlReset() {
// TODO: Via REST API.
}
public setScreenSize(width: number, height: number, rate: number) {
// TODO: Via REST API.
this.websocket.send('screen/set', { width, height, rate })
}
/////////////////////////////
// Component lifecycle
/////////////////////////////
mounted() {
// component size change
this.observer.observe(this._component)
// change host
// host change
this.events.on('control.host', (id: string | null) => {
Vue.set(this.state.member, 'is_controlling', id != null && id === this.state.member.id)
})
// hardcoded webrtc for now
Vue.set(this.state.connection, 'type', 'webrtc')
Vue.set(this.state.connection, 'can_watch', this.webrtc.supported)
Vue.set(this.state.connection, 'can_control', this.webrtc.supported)
// fullscreen change
this._component.addEventListener('fullscreenchange', () => {
Vue.set(this.state.screen, 'fullscreen', document.fullscreenElement !== null)
Vue.set(this.state.video, 'fullscreen', document.fullscreenElement !== null)
this.onResize()
})
// video
VideoRegister(this.video, this.state.video)
// video events
VideoRegister(this._video, this.state.video)
// websocket
this.websocket.on('message', async (event: string, payload: any) => {
@ -241,6 +254,18 @@
Vue.set(this.state.connection, 'websocket', 'disconnected')
this.events.emit('system.websocket', 'disconnected')
this.webrtc.disconnect()
// TODO: reset state
Vue.set(this.state, 'member', {
id: null,
name: null,
is_admin: false,
is_watching: false,
is_controlling: false,
can_watch: false,
can_control: false,
clipboard_access: false,
})
})
// webrtc
@ -249,14 +274,14 @@
if (track.kind === 'audio') return
// create stream
if ('srcObject' in this.video) {
this.video.srcObject = streams[0]
if ('srcObject' in this._video) {
this._video.srcObject = streams[0]
} else {
// @ts-ignore
this.video.src = window.URL.createObjectURL(streams[0]) // for older browsers
this._video.src = window.URL.createObjectURL(streams[0]) // for older browsers
}
this.video.play()
this._video.play()
})
this.webrtc.on('connecting', () => {
Vue.set(this.state.connection, 'webrtc', 'connecting')
@ -270,17 +295,29 @@
Vue.set(this.state.connection, 'webrtc', 'disconnected')
this.events.emit('system.webrtc', 'disconnected')
// @ts-ignore
if (this.video) this.video.src = null
this._video.src = null
})
// hardcoded webrtc for now
Vue.set(this.state.connection, 'type', 'webrtc')
Vue.set(this.state.connection, 'can_watch', this.webrtc.supported)
Vue.set(this.state.connection, 'can_control', this.webrtc.supported)
}
private beforeDestroy() {
beforeDestroy() {
this.observer.disconnect()
this.webrtc.disconnect()
this.websocket.disconnect()
}
private onResize() {
@Watch('state.video.playing')
onVideoPlayingChanged(play: boolean) {
// TODO: check if user has tab focused and send via websocket
Vue.set(this.state.member, 'is_watching', play)
}
@Watch('state.screen.size')
onResize() {
const { width, height } = this.state.screen.size
const screen_ratio = width / height

View File

@ -26,6 +26,7 @@ export interface Video {
playable: boolean
playing: boolean
volume: number
fullscreen: boolean
}
/////////////////////////////
@ -33,6 +34,7 @@ export interface Video {
/////////////////////////////
export interface Control {
scroll: Scroll
clipboard: Clipboard
host: Member | null
}
@ -41,13 +43,16 @@ export interface Scroll {
sensitivity: number
}
export interface Clipboard {
data: string | null
}
/////////////////////////////
// Screen
/////////////////////////////
export interface Screen {
size: ScreenSize
configurations: ScreenSize[]
fullscreen: boolean
}
export interface ScreenSize {