mirror of
https://github.com/m1k1o/neko.git
synced 2024-07-24 14:40:50 +12:00
refactor state & public methods.
This commit is contained in:
parent
6177ffc242
commit
619ac3350b
104
src/app.vue
104
src/app.vue
@ -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() {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user