Volume sliders, fullscreen video, misc tweaks

polling-api
Noah 2023-10-05 18:59:49 -07:00
parent 489f5b6aad
commit 2810169ce9
5 changed files with 240 additions and 83 deletions

14
package-lock.json generated
View File

@ -12,7 +12,8 @@
"interactjs": "^1.10.18",
"vue": "^3.3.4",
"vue-mention": "^2.0.0-alpha.3",
"vue3-emoji-picker": "^1.1.7"
"vue3-emoji-picker": "^1.1.7",
"vue3-slider": "^1.9.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.1",
@ -2023,6 +2024,17 @@
"vue": "^3.2.23"
}
},
"node_modules/vue3-slider": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/vue3-slider/-/vue3-slider-1.9.0.tgz",
"integrity": "sha512-S+ojp3A21FiOM389Xp/bdUKSzjxcsTaDwI4N1aWVPoNh9//E8M+dqs1Ufjh86LulPAWLCN0ZR6eaD8jLZZ574g==",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"vue": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",

View File

@ -14,7 +14,8 @@
"interactjs": "^1.10.18",
"vue": "^3.3.4",
"vue-mention": "^2.0.0-alpha.3",
"vue3-emoji-picker": "^1.1.7"
"vue3-emoji-picker": "^1.1.7",
"vue3-slider": "^1.9.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^4.3.1",

View File

@ -21,7 +21,7 @@ type Server struct {
// NewServer initializes the Server.
func NewServer() *Server {
return &Server{
subscriberMessageBuffer: 16,
subscriberMessageBuffer: 32,
subscribers: make(map[*Subscriber]struct{}),
}
}

View File

@ -10,6 +10,7 @@ import ExplicitOpenModal from './components/ExplicitOpenModal.vue';
import ReportModal from './components/ReportModal.vue';
import MessageBox from './components/MessageBox.vue';
import WhoListRow from './components/WhoListRow.vue';
import VideoFeed from './components/VideoFeed.vue';
import LocalStorage from './lib/LocalStorage';
import VideoFlag from './lib/VideoFlag';
@ -48,6 +49,7 @@ export default {
ReportModal,
MessageBox,
WhoListRow,
VideoFeed,
},
data() {
return {
@ -2313,6 +2315,13 @@ export default {
$ref.muted = this.WebRTC.muted[username];
}
},
setVideoVolume(username, volume) {
// Set the volume on their video.
let $ref = document.getElementById(`videofeed-${username}`);
if ($ref) {
$ref.volume = volume / 100;
}
},
// Pop out a user's video.
popoutVideo(username) {
@ -3123,7 +3132,8 @@ export default {
<div class="select is-fullwidth">
<select v-model="webcam.videoDeviceID"
@change="startVideo({ changeCamera: true, force: true })">
<option v-for="(d, i) in webcam.videoDevices" :value="d.id">
<option v-for="(d, i) in webcam.videoDevices" :value="d.id"
v-bind:key="i">
{{ d.label || `Camera ${i}` }}
</option>
</select>
@ -3135,7 +3145,8 @@ export default {
<div class="select is-fullwidth">
<select v-model="webcam.audioDeviceID"
@change="startVideo({ changeCamera: true, force: true })">
<option v-for="(d, i) in webcam.audioDevices" :value="d.id">
<option v-for="(d, i) in webcam.audioDevices" :value="d.id"
v-bind:key="i">
{{ d.label || `Microphone ${i}` }}
</option>
</select>
@ -3448,7 +3459,7 @@ export default {
</ul>
<p class="menu-label">
Private Messages
Direct Messages
</p>
<ul class="menu-list">
@ -3482,6 +3493,21 @@ export default {
</ul>
</aside>
<!-- Close new DMs toggle -->
<div class="tag mt-2">
<label class="checkbox">
<input type="checkbox"
v-model="prefs.closeDMs"
:value="true">
Ignore unsolicited DMs
<a href="#"
onclick="alert('When checked, your DMs will be closed to new conversations. You may still initiate new DMs with others.'); return false"
class="fa fa-info-circle ml-2">
</a>
</label>
</div>
</div>
</div>
</div>
@ -3535,85 +3561,36 @@ export default {
<!-- Video Feeds-->
<!-- My video -->
<div class="feed" v-show="webcam.active" :class="{
'popped-out': WebRTC.poppedOut[username],
'popped-in': !WebRTC.poppedOut[username]
}">
<video class="feed" id="localVideo" autoplay muted>
</video>
<div class="caption" :class="{
'has-text-camera-blue': !webcam.nsfw,
'has-text-camera-red': webcam.nsfw
}">
<i class="fa fa-microphone-slash mr-1 has-text-grey" v-if="webcam.muted"></i>
{{ username }}
</div>
<div class="controls">
<!-- MY Mute button -->
<button type="button" v-if="webcam.active && !webcam.muted"
class="button is-small is-success is-outlined ml-1 px-2" @click="muteMe()">
<i class="fa fa-microphone"></i>
</button>
<button type="button" v-if="webcam.active && webcam.muted"
class="button is-small is-danger ml-1 px-2" @click="muteMe()">
<i class="fa fa-microphone-slash"></i>
</button>
<!-- Pop-out MY video -->
<button type="button" class="button is-small is-light is-outlined p-2 ml-2" title="Pop out"
@click="popoutVideo(username)">
<i class="fa fa-up-right-from-square"></i>
</button>
</div>
</div>
<VideoFeed
v-show="webcam.active"
:local-video="true"
:username="username"
:popped-out="WebRTC.poppedOut[username]"
:is-explicit="webcam.nsfw"
:is-muted="webcam.muted"
:is-source-muted="webcam.muted"
@mute-video="muteMe()"
@popout="popoutVideo"
@set-volume="setVideoVolume">
</VideoFeed>
<!-- Others' videos -->
<div class="feed" v-for="(stream, username) in WebRTC.streams" v-bind:key="username" :class="{
'popped-out': WebRTC.poppedOut[username],
'popped-in': !WebRTC.poppedOut[username]
}">
<video class="feed" :id="'videofeed-' + username" autoplay>
</video>
<div class="caption" :class="{
'has-text-camera-blue': !isUsernameCamNSFW(username),
'has-text-camera-red': isUsernameCamNSFW(username)
}">
<i class="fa fa-microphone-slash mr-1 has-text-grey" v-if="isSourceMuted(username)"></i>
{{ username }}
<i class="fa fa-people-arrows ml-1 has-text-grey is-size-7"
:title="username + ' is watching your camera too'" v-if="isWatchingMe(username)"></i>
<!-- Frozen stream detection -->
<a class="fa fa-mountain ml-1" href="#" v-if="WebRTC.frozenStreamDetected[username]"
style="color: #00FFFF" @click.prevent="openVideoByUsername(username, true)"
title="Frozen video detected!"></a>
</div>
<div class="close">
<a href="#" class="has-text-danger" title="Close video"
@click.prevent="closeVideo(username, 'offerer')">
<i class="fa fa-close"></i>
</a>
</div>
<div class="controls">
<!-- Mute button -->
<button type="button" v-if="isMuted(username)" class="button is-small is-danger p-2"
title="Unmute this video" @click="muteVideo(username)">
<i class="fa fa-volume-xmark"></i>
</button>
<button type="button" v-else class="button is-small is-success is-outlined p-2"
title="Mute this video" @click="muteVideo(username)">
<i class="fa fa-volume-high"></i>
</button>
<!-- Pop-out -->
<button type="button" class="button is-small is-light is-outlined p-2 ml-2" title="Pop out"
@click="popoutVideo(username)">
<i class="fa fa-up-right-from-square"></i>
</button>
</div>
</div>
<VideoFeed
v-for="(stream, username) in WebRTC.streams"
v-bind:key="username"
:username="username"
:popped-out="WebRTC.poppedOut[username]"
:is-explicit="isUsernameCamNSFW(username)"
:is-source-muted="isSourceMuted(username)"
:is-muted="isMuted(username)"
:is-watching-me="isWatchingMe(username)"
:is-frozen="WebRTC.frozenStreamDetected[username]"
@reopen-video="openVideoByUsername"
@mute-video="muteVideo"
@popout="popoutVideo"
@close-video="closeVideo"
@set-volume="setVideoVolume">
</VideoFeed>
<!-- Debugging - copy a lot of these to simulate more videos -->

View File

@ -0,0 +1,167 @@
<script>
import Slider from 'vue3-slider';
export default {
props: {
localVideo: Boolean, // is our local webcam (not other's camera)
poppedOut: Boolean, // Video is popped-out and draggable
username: String, // username related to this video
isExplicit: Boolean, // camera is marked Explicit
isMuted: Boolean, // camera is muted on our end
isSourceMuted: Boolean, // camera is muted on the broadcaster's end
isWatchingMe: Boolean, // other video is watching us back
isFrozen: Boolean, // video is detected as frozen
},
components: {
Slider,
},
data() {
return {
// Volume slider
volume: 100,
// Volume change debounce
volumeDebounce: null,
};
},
computed: {
videoID() {
return this.localVideo ? 'localVideo' : `videofeed-${this.username}`;
}
},
methods: {
closeVideo() {
// Note: closeVideo only available for OTHER peoples cameras.
// Closes the WebRTC connection as the offerer.
this.$emit('close-video', this.username, 'offerer');
},
reopenVideo() {
// Note: goes into openVideo(username, force)
this.$emit('reopen-video', this.username, true);
},
// Toggle the Mute button
muteVideo() {
this.$emit('mute-video', this.username);
},
popoutVideo() {
this.$emit('popout', this.username);
},
fullscreen() {
let $elem = document.getElementById(this.videoID);
if ($elem) {
if ($elem.requestFullscreen) {
$elem.requestFullscreen();
} else {
window.alert("Fullscreen not supported by your browser.");
}
}
},
volumeChanged() {
if (this.volumeDebounce !== null) {
clearTimeout(this.volumeDebounce);
}
this.volumeDebounce = setTimeout(() => {
this.$emit('set-volume', this.username, this.volume);
}, 200);
},
}
}
</script>
<template>
<div class="feed" :class="{
'popped-out': poppedOut,
'popped-in': !poppedOut,
}">
<video class="feed" :id="videoID" autoplay :muted="localVideo"></video>
<!-- Caption -->
<div class="caption" :class="{
'has-text-camera-blue': !isExplicit,
'has-text-camera-red': isExplicit,
}">
<i class="fa fa-microphone-slash mr-1 has-text-grey"
v-if="isSourceMuted"></i>
{{ username }}
<i class="fa fa-people-arrows ml-1 has-text-grey is-size-7"
:title="username + ' is watching your camera too'"
v-if="isWatchingMe"></i>
<!-- Frozen stream detection -->
<a class="fa fa-mountain ml-1" href="#" v-if="!localVideo && isFrozen"
style="color: #00FFFF" @click.prevent="reopenVideo()"
title="Frozen video detected!"></a>
</div>
<!-- Close button (others' videos only) -->
<div class="close" v-if="!localVideo">
<a href="#" class="has-text-danger" title="Close video"
@click.prevent="closeVideo()">
<i class="fa fa-close"></i>
</a>
</div>
<!-- Controls -->
<div class="controls">
<!-- Mute Button -->
<button type="button" v-if="!isMuted"
class="button is-small is-success is-outlined ml-1 px-2"
@click="muteVideo()">
<i class="fa"
:class="{'fa-microphone': localVideo,
'fa-volume-high': !localVideo}"></i>
</button>
<button type="button" v-else
class="button is-small is-danger ml-1 px-2"
@click="muteVideo()">
<i class="fa"
:class="{'fa-microphone-slash': localVideo,
'fa-volume-xmark': !localVideo}"></i>
</button>
<!-- Pop-out Video -->
<button type="button" class="button is-small is-light is-outlined p-2 ml-2"
title="Pop out"
@click="popoutVideo()">
<i class="fa fa-up-right-from-square"></i>
</button>
<!-- Full screen -->
<button type="button" class="button is-small is-light is-outlined p-2 ml-2"
title="Go full screen"
@click="fullscreen()">
<i class="fa fa-expand"></i>
</button>
</div>
<!-- Volume slider -->
<div class="volume-slider" v-if="!localVideo && !isMuted">
<Slider
v-model="volume"
color="#00FF00"
track-color="#006600"
:min="0"
:max="100"
:step="1"
:height="7"
orientation="vertical"
@change="volumeChanged">
</Slider>
</div>
</div>
</template>
<style scoped>
.volume-slider {
position: absolute;
left: 18px;
top: 30px;
bottom: 44px;
}
</style>