Archived
2
0

live change resolution (WIP)

This commit is contained in:
Craig
2020-02-11 05:15:59 +00:00
parent 3d1341cfe1
commit 9e995233af
27 changed files with 747 additions and 127 deletions

View File

@ -0,0 +1,121 @@
<template>
<vue-context class="context" ref="context">
<template v-for="(conf, i) in configurations">
<li v-for="(rate, j) in conf.rates" :key="`${i}-${j}`" @click="screenSet(conf.width, conf.height, rate)">
<i class="fas fa-desktop"></i>
<span>{{ conf.width }}x{{ conf.height }}</span>
<small>{{ rate }}</small>
</li>
</template>
</vue-context>
</template>
<style lang="scss" scoped>
.context {
background-color: $background-floating;
background-clip: padding-box;
border-radius: 0.25rem;
display: block;
margin: 0;
padding: 5px;
min-width: 150px;
z-index: 1500;
position: fixed;
list-style: none;
box-sizing: border-box;
max-height: calc(100% - 50px);
overflow-y: auto;
color: $interactive-normal;
user-select: none;
box-shadow: $elevation-high;
scrollbar-width: thin;
scrollbar-color: $background-secondary transparent;
&::-webkit-scrollbar {
width: 8px;
}
&::-webkit-scrollbar-track {
background-color: transparent;
}
&::-webkit-scrollbar-thumb {
background-color: $background-secondary;
border: 2px solid $background-floating;
border-radius: 4px;
}
&::-webkit-scrollbar-thumb:hover {
background-color: $background-floating;
}
> li {
margin: 0;
position: relative;
align-content: center;
display: flex;
flex-direction: row;
padding: 8px 5px;
cursor: pointer;
i {
margin-right: 10px;
}
span {
flex-grow: 1;
}
small {
font-size: 0.7em;
justify-self: flex-end;
align-self: flex-end;
}
&:hover,
&:focus {
text-decoration: none;
background-color: $background-modifier-hover;
color: $interactive-hover;
}
&:focus {
outline: 0;
}
}
&:focus {
outline: 0;
}
}
</style>
<script lang="ts">
import { Component, Ref, Watch, Vue } from 'vue-property-decorator'
import { Member } from '~/neko/types'
// @ts-ignore
import { VueContext } from 'vue-context'
@Component({
name: 'neko-resolution',
components: {
'vue-context': VueContext,
},
})
export default class extends Vue {
@Ref('context') readonly context!: any
get configurations() {
return this.$accessor.video.configurations
}
open(event: MouseEvent) {
this.context.open(event)
}
screenSet(width: number, height: number, rate: number) {
this.$accessor.video.screenSet({ width, height, rate })
}
}
</script>

View File

@ -28,7 +28,11 @@
</div>
<div ref="aspect" class="player-aspect" />
</div>
<i v-if="!fullscreen" @click.stop.prevent="requestFullscreen" class="expand fas fa-expand"></i>
<ul v-if="!fullscreen" class="video-menu">
<li><i @click.stop.prevent="requestFullscreen" class="fas fa-expand"></i></li>
<li v-if="admin"><i @click.stop.prevent="onResolution" class="fas fa-cog"></i></li>
</ul>
<neko-resolution ref="resolution" />
</div>
</div>
</template>
@ -44,19 +48,26 @@
justify-content: center;
align-items: center;
.expand {
.video-menu {
position: absolute;
right: 20px;
top: 15px;
width: 30px;
height: 30px;
background: rgba($color: #fff, $alpha: 0.2);
border-radius: 5px;
line-height: 30px;
font-size: 16px;
text-align: center;
color: rgba($color: #fff, $alpha: 0.6);
cursor: pointer;
li {
margin: 0 0 10px 0;
i {
width: 30px;
height: 30px;
background: rgba($color: #fff, $alpha: 0.2);
border-radius: 5px;
line-height: 30px;
font-size: 16px;
text-align: center;
color: rgba($color: #fff, $alpha: 0.6);
cursor: pointer;
}
}
}
.player-container {
@ -129,11 +140,13 @@
import ResizeObserver from 'resize-observer-polyfill'
import Emote from './emote.vue'
import Resolution from './resolution.vue'
@Component({
name: 'neko-video',
components: {
'neko-emote': Emote,
'neko-resolution': Resolution,
},
})
export default class extends Vue {
@ -143,11 +156,16 @@
@Ref('aspect') readonly _aspect!: HTMLElement
@Ref('player') readonly _player!: HTMLElement
@Ref('video') readonly _video!: HTMLVideoElement
@Ref('resolution') readonly _resolution!: any
private observer = new ResizeObserver(this.onResise.bind(this))
private focused = false
private fullscreen = false
get admin() {
return this.$accessor.user.admin
}
get connected() {
return this.$accessor.connected
}
@ -200,6 +218,38 @@
return this.$accessor.remote.clipboard
}
get width() {
return this.$accessor.video.width
}
get height() {
return this.$accessor.video.height
}
get rate() {
return this.$accessor.video.rate
}
get vertical() {
return this.$accessor.video.vertical
}
get horizontal() {
return this.$accessor.video.horizontal
}
@Watch('width')
onWidthChanged(width: number) {
const { videoWidth, videoHeight } = this._video
console.log({ videoWidth, videoHeight })
this.onResise()
}
@Watch('height')
onHeightChanged(height: number) {
this.onResise()
}
@Watch('volume')
onVolumeChanged(volume: number) {
if (this._video) {
@ -292,8 +342,6 @@
this._video
.play()
.then(() => {
const { videoWidth, videoHeight } = this._video
this.$accessor.video.setResolution({ width: videoWidth, height: videoHeight })
this.onResise()
})
.catch(err => console.log(err))
@ -418,8 +466,6 @@
}
onResise() {
const { horizontal, vertical } = this.$accessor.video
let height = 0
if (!this.fullscreen) {
const { offsetWidth, offsetHeight } = this._component
@ -431,8 +477,12 @@
height = offsetHeight
}
this._container.style.maxWidth = `${(horizontal / vertical) * height}px`
this._aspect.style.paddingBottom = `${(vertical / horizontal) * 100}%`
this._container.style.maxWidth = `${(this.horizontal / this.vertical) * height}px`
this._aspect.style.paddingBottom = `${(this.vertical / this.horizontal) * 100}%`
}
onResolution(event: MouseEvent) {
this._resolution.open(event)
}
}
</script>

View File

@ -36,6 +36,11 @@ export const EVENT = {
MESSAGE: 'chat/message',
EMOTE: 'chat/emote',
},
SCREEN: {
CONFIGURATIONS: 'screen/configurations',
RESOLUTION: 'screen/resolution',
SET: 'screen/set',
},
ADMIN: {
BAN: 'admin/ban',
KICK: 'admin/kick',
@ -58,6 +63,7 @@ export type WebSocketEvents =
| MemberEvents
| SignalEvents
| ChatEvents
| ScreenEvents
| AdminEvents
export type ControlEvents =
@ -72,6 +78,8 @@ export type IdentityEvents = typeof EVENT.IDENTITY.PROVIDE | typeof EVENT.IDENTI
export type MemberEvents = typeof EVENT.MEMBER.LIST | typeof EVENT.MEMBER.CONNECTED | typeof EVENT.MEMBER.DISCONNECTED
export type SignalEvents = typeof EVENT.SIGNAL.ANSWER | typeof EVENT.SIGNAL.PROVIDE
export type ChatEvents = typeof EVENT.CHAT.MESSAGE | typeof EVENT.CHAT.EMOTE
export type ScreenEvents = typeof EVENT.SCREEN.CONFIGURATIONS | typeof EVENT.SCREEN.RESOLUTION | typeof EVENT.SCREEN.SET
export type AdminEvents =
| typeof EVENT.ADMIN.BAN
| typeof EVENT.ADMIN.KICK

View File

@ -15,9 +15,11 @@ import {
ControlTargetPayload,
ChatPayload,
EmotePayload,
ControlClipboardPayload,
ScreenConfigurationsPayload,
ScreenResolutionPayload,
AdminPayload,
AdminTargetPayload,
ControlClipboardPayload,
} from './messages'
interface NekoEvents extends BaseEvents {}
@ -285,6 +287,33 @@ export class NekoClient extends BaseClient implements EventEmitter<NekoEvents> {
this.$accessor.chat.newEmote({ type: emote })
}
/////////////////////////////
// Screen Events
/////////////////////////////
protected [EVENT.SCREEN.CONFIGURATIONS]({ configurations }: ScreenConfigurationsPayload) {
this.$accessor.video.setConfigurations(configurations)
}
protected [EVENT.SCREEN.RESOLUTION]({ id, width, height, rate }: ScreenResolutionPayload) {
this.$accessor.video.setResolution({ width, height, rate })
if (!id) {
return
}
const member = this.member(id)
if (!member || member.ignored) {
return
}
this.$accessor.chat.newMessage({
id,
content: `chaned the resolution to ${width}x${height}@${rate}`,
type: 'event',
created: new Date(),
})
}
/////////////////////////////
// Admin Events
/////////////////////////////

View File

@ -7,9 +7,10 @@ import {
MemberEvents,
SignalEvents,
ChatEvents,
ScreenEvents,
AdminEvents,
} from './events'
import { Member } from './types'
import { Member, ScreenConfigurations } from './types'
export type WebSocketMessages =
| WebSocketMessage
@ -19,6 +20,8 @@ export type WebSocketMessages =
| MembeConnectMessage
| MembeDisconnectMessage
| ControlMessage
| ScreenResolutionMessage
| ScreenConfigurationsMessage
| ChatMessage
export type WebSocketPayloads =
@ -31,6 +34,8 @@ export type WebSocketPayloads =
| ChatPayload
| ChatSendPayload
| EmojiSendPayload
| ScreenResolutionPayload
| ScreenConfigurationsPayload
| AdminPayload
export interface WebSocketMessage {
@ -145,6 +150,28 @@ export interface EmojiSendPayload {
emote: string
}
/*
SCREEN PAYLOADS
*/
export interface ScreenResolutionMessage extends WebSocketMessage, ScreenResolutionPayload {
event: ScreenEvents
}
export interface ScreenResolutionPayload {
id?: string
width: number
height: number
rate: number
}
export interface ScreenConfigurationsMessage extends WebSocketMessage, ScreenConfigurationsPayload {
event: ScreenEvents
}
export interface ScreenConfigurationsPayload {
configurations: ScreenConfigurations
}
/*
ADMIN PAYLOADS
*/

View File

@ -6,3 +6,13 @@ export interface Member {
connected?: boolean
ignored?: boolean
}
export interface ScreenConfigurations {
[index: number]: ScreenConfiguration
}
export interface ScreenConfiguration {
width: string
height: string
rates: { [index: number]: number }
}

View File

@ -1,5 +1,8 @@
import { getterTree, mutationTree, actionTree } from 'typed-vuex'
import { get, set } from '~/utils/localstorage'
import { EVENT } from '~/neko/events'
import { ScreenConfigurations } from '~/neko/types'
import { accessor } from '~/store'
export const namespaced = true
@ -7,8 +10,10 @@ export const state = () => ({
index: -1,
tracks: [] as MediaStreamTrack[],
streams: [] as MediaStream[],
configurations: {} as ScreenConfigurations,
width: 1280,
height: 720,
rate: 30,
horizontal: 16,
vertical: 9,
volume: get<number>('volume', 100),
@ -54,9 +59,10 @@ export const mutations = mutationTree(state, {
state.playable = playable
},
setResolution(state, { width, height }: { width: number; height: number }) {
setResolution(state, { width, height, rate }: { width: number; height: number; rate: number }) {
state.width = width
state.height = height
state.rate = rate
if ((height == 0 && width == 0) || (height == 0 && width != 0) || (height != 0 && width == 0)) {
return
@ -92,6 +98,10 @@ export const mutations = mutationTree(state, {
state.vertical = height / gcd
},
setConfigurations(state, configurations: ScreenConfigurations) {
state.configurations = configurations
},
setVolume(state, volume: number) {
state.volume = volume
set('volume', volume)
@ -115,11 +125,42 @@ export const mutations = mutationTree(state, {
state.index = -1
state.tracks = []
state.streams = []
state.configurations = []
state.width = 1280
state.height = 720
state.rate = 30
state.horizontal = 16
state.vertical = 9
state.playing = false
state.playable = false
},
})
export const actions = actionTree(
{ state, getters, mutations },
{
screenConfiguations({ state }) {
if (!accessor.connected || !accessor.user.admin) {
return
}
$client.sendMessage(EVENT.SCREEN.CONFIGURATIONS)
},
screenGet({ state }) {
if (!accessor.connected) {
return
}
$client.sendMessage(EVENT.SCREEN.RESOLUTION)
},
screenSet({ state }, { width, height, rate }: { width: number; height: number; rate: number }) {
if (!accessor.connected || !accessor.user.admin) {
return
}
$client.sendMessage(EVENT.SCREEN.SET, { width, height, rate })
},
},
)