Sort the WhoList + Mutual Video options

* The who list now sorts alphabetically instead of random
* New user controls when they share video:
  * Require users to also be sharing before they open ours
  * We auto-open a viewer's video when they open ours
ipad-testing
Noah 2023-03-31 19:46:42 -07:00
parent 3f6e2193c8
commit 4a2fc9c923
6 changed files with 112 additions and 28 deletions

View File

@ -261,6 +261,8 @@ func (s *Server) OnMe(sub *Subscriber, msg Message) {
}
sub.VideoActive = msg.VideoActive
sub.VideoMutual = msg.VideoMutual
sub.VideoMutualOpen = msg.VideoMutualOpen
sub.VideoNSFW = msg.NSFW
sub.ChatStatus = msg.ChatStatus

View File

@ -20,9 +20,11 @@ type Message struct {
WhoList []WhoList `json:"whoList,omitempty"`
// Sent on `me` actions along with Username
VideoActive bool `json:"videoActive,omitempty"` // user tells us their cam status
ChatStatus string `json:"status,omitempty"` // online vs. away
NSFW bool `json:"nsfw,omitempty"` // user tags their video NSFW
VideoActive bool `json:"videoActive,omitempty"` // user tells us their cam status
VideoMutual bool `json:"videoMutual,omitempty"` // user wants mutual viewers
VideoMutualOpen bool `json:"videoMutualOpen,omitempty"`
ChatStatus string `json:"status,omitempty"` // online vs. away
NSFW bool `json:"nsfw,omitempty"` // user tags their video NSFW
// Sent on `open` actions along with the (other) Username.
OpenSecret string `json:"openSecret,omitempty"`
@ -66,10 +68,12 @@ const (
// WhoList is a member entry in the chat room.
type WhoList struct {
Username string `json:"username"`
VideoActive bool `json:"videoActive,omitempty"`
NSFW bool `json:"nsfw,omitempty"`
Status string `json:"status"`
Username string `json:"username"`
VideoActive bool `json:"videoActive,omitempty"`
VideoMutual bool `json:"videoMutual,omitempty"`
VideoMutualOpen bool `json:"videoMutualOpen,omitempty"`
NSFW bool `json:"nsfw,omitempty"`
Status string `json:"status"`
// JWT auth extra settings.
Operator bool `json:"op"`

View File

@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"net/http"
"sort"
"strings"
"sync"
"time"
@ -20,18 +21,20 @@ import (
// Subscriber represents a connected WebSocket session.
type Subscriber struct {
// User properties
ID int // ID assigned by server
Username string
VideoActive bool
VideoNSFW bool
ChatStatus string
JWTClaims *jwt.Claims
authenticated bool // has passed the login step
conn *websocket.Conn
ctx context.Context
cancel context.CancelFunc
messages chan []byte
closeSlow func()
ID int // ID assigned by server
Username string
VideoActive bool
VideoMutual bool
VideoMutualOpen bool
VideoNSFW bool
ChatStatus string
JWTClaims *jwt.Claims
authenticated bool // has passed the login step
conn *websocket.Conn
ctx context.Context
cancel context.CancelFunc
messages chan []byte
closeSlow func()
muteMu sync.RWMutex
booted map[string]struct{} // usernames booted off your camera
@ -308,8 +311,16 @@ func (s *Server) SendTo(username string, msg Message) error {
func (s *Server) SendWhoList() {
var (
subscribers = s.IterSubscribers()
usernames = []string{} // distinct and sorted usernames
userSub = map[string]*Subscriber{}
)
for _, sub := range subscribers {
usernames = append(usernames, sub.Username)
userSub[sub.Username] = sub
}
sort.Strings(usernames)
// Build the WhoList for each subscriber.
// TODO: it's the only way to fake videoActive for booted user views.
for _, sub := range subscribers {
@ -318,16 +329,19 @@ func (s *Server) SendWhoList() {
}
var users = []WhoList{}
for _, user := range subscribers {
for _, un := range usernames {
user := userSub[un]
if user.ChatStatus == "hidden" {
continue
}
who := WhoList{
Username: user.Username,
Status: user.ChatStatus,
VideoActive: user.VideoActive,
NSFW: user.VideoNSFW,
Username: user.Username,
Status: user.ChatStatus,
VideoActive: user.VideoActive,
VideoMutual: user.VideoMutual,
VideoMutualOpen: user.VideoMutualOpen,
NSFW: user.VideoNSFW,
}
// If this person had booted us, force their camera to "off"

View File

@ -249,4 +249,9 @@ body {
position: absolute;
top: 14px;
left: 16px;
}
/* Cursors */
.cursor-notallowed {
cursor: not-allowed;
}

View File

@ -91,6 +91,8 @@ const app = Vue.createApp({
stream: null, // MediaStream object
muted: false, // our outgoing mic is muted, not by default
nsfw: false, // user has flagged their camera to be NSFW
mutual: false, // user wants viewers to share their own videos
mutualOpen: false, // user wants to open video mutually
// Who all is watching me? map of users.
watching: {},
@ -331,6 +333,8 @@ const app = Vue.createApp({
this.ws.conn.send(JSON.stringify({
action: "me",
videoActive: this.webcam.active,
videoMutual: this.webcam.mutual,
videoMutualOpen: this.webcam.mutualOpen,
status: this.status,
nsfw: this.webcam.nsfw,
}));
@ -682,6 +686,15 @@ const app = Vue.createApp({
});
}
// If we are the offerer, and this member wants to auto-open our camera
// then add our own stream to the connection.
if (isOfferer && this.whoMap[username].videoMutualOpen && this.webcam.active) {
let stream = this.webcam.stream;
stream.getTracks().forEach(track => {
pc.addTrack(track, stream)
});
}
// If we are the offerer, begin the connection.
if (isOfferer) {
pc.createOffer({
@ -953,6 +966,14 @@ const app = Vue.createApp({
return;
}
// If this user requests mutual viewership...
if (user.videoMutual && !this.webcam.active) {
this.ChatClient(
`<strong>${user.username}</strong> has requested that you should share your own camera too before opening theirs.`
);
return;
}
this.sendOpen(user.username);
// Responsive CSS -> go to chat panel to see the camera
@ -995,6 +1016,16 @@ const app = Vue.createApp({
// Inform backend we have closed it.
this.sendWatch(username, false);
},
unMutualVideo() {
// If we had our camera on to watch a video of someone who wants mutual cameras,
// and then we turn ours off: we should unfollow the ones with mutual video.
for (let row of this.whoList) {
let username = row.username;
if (row.videoMutual && this.WebRTC.pc[username] != undefined) {
this.closeVideo(username);
}
}
},
// Show who watches our video.
showViewers() {
@ -1052,6 +1083,9 @@ const app = Vue.createApp({
this.closeVideo(username, "answerer");
}
// Hang up on mutual cameras.
this.unMutualVideo();
// Tell backend our camera state.
this.sendMe();
},

View File

@ -228,8 +228,8 @@
<i class="fa fa-eye"></i> see who is watching will be at the top of the page.
</p>
<p class="block">
If your camera will be featuring "<abbr title="Not Safe For Work">NSFW</abbr>" or sexual content, please mark it as such by
<p class="block mb-1">
If your camera will be featuring "<abbr title="Not Safe For Work">Explicit</abbr>" or sexual content, please mark it as such by
clicking on the <i class="fa fa-fire has-text-danger"></i> button or checking the box below to start with it enabled.
</p>
@ -237,7 +237,28 @@
<label class="checkbox">
<input type="checkbox"
v-model="webcam.nsfw">
Check this box if your webcam will <em>definitely</em> be NSFW. 😈
Check this box if your webcam will <em>definitely</em> be Explicit. 😈
</label>
</div>
<p class="block mb-1">
<label class="label">Mutual webcam options:</label>
</p>
<div class="field mb-1">
<label class="checkbox">
<input type="checkbox"
v-model="webcam.mutual">
People must be sharing their own camera before they can open mine
</label>
</div>
<div class="field">
<label class="checkbox">
<input type="checkbox"
:disabled="!webcam.mutual"
v-model="webcam.mutualOpen">
When someone opens my camera, I also open their camera automatically
</label>
</div>
@ -778,8 +799,12 @@
:class="{
'is-danger is-outlined': u.videoActive && u.nsfw,
'is-info is-outlined': u.videoActive && !u.nsfw,
'cursor-notallowed': u.videoActive && u.videoMutual && !webcam.active,
}"
title="Open video stream"
:title="`Open video stream` +
(u.videoActive && u.videoMutual ? '; mutual video sharing required' : '') +
(u.videoActive && u.videoMutualOpen ? '; will auto-open your video' : '')"
@click="openVideo(u)">
<i class="fa fa-video"></i>
</button>