From 4a2fc9c923e67ead60c7386be92961c6a3816cf9 Mon Sep 17 00:00:00 2001
From: Noah Petherbridge
Date: Fri, 31 Mar 2023 19:46:42 -0700
Subject: [PATCH] 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
---
pkg/handlers.go | 2 ++
pkg/messages.go | 18 +++++++++------
pkg/websocket.go | 48 ++++++++++++++++++++++++++--------------
web/static/css/chat.css | 5 +++++
web/static/js/BareRTC.js | 34 ++++++++++++++++++++++++++++
web/templates/chat.html | 33 +++++++++++++++++++++++----
6 files changed, 112 insertions(+), 28 deletions(-)
diff --git a/pkg/handlers.go b/pkg/handlers.go
index 0cc0483..5a47a9c 100644
--- a/pkg/handlers.go
+++ b/pkg/handlers.go
@@ -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
diff --git a/pkg/messages.go b/pkg/messages.go
index 98fc1b5..f3bb736 100644
--- a/pkg/messages.go
+++ b/pkg/messages.go
@@ -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"`
diff --git a/pkg/websocket.go b/pkg/websocket.go
index 2b357da..f9ca931 100644
--- a/pkg/websocket.go
+++ b/pkg/websocket.go
@@ -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"
diff --git a/web/static/css/chat.css b/web/static/css/chat.css
index 77ba40b..d19d46e 100644
--- a/web/static/css/chat.css
+++ b/web/static/css/chat.css
@@ -249,4 +249,9 @@ body {
position: absolute;
top: 14px;
left: 16px;
+}
+
+/* Cursors */
+.cursor-notallowed {
+ cursor: not-allowed;
}
\ No newline at end of file
diff --git a/web/static/js/BareRTC.js b/web/static/js/BareRTC.js
index a948529..97f7777 100644
--- a/web/static/js/BareRTC.js
+++ b/web/static/js/BareRTC.js
@@ -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(
+ `${user.username} 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();
},
diff --git a/web/templates/chat.html b/web/templates/chat.html
index bf794c3..2d7b004 100644
--- a/web/templates/chat.html
+++ b/web/templates/chat.html
@@ -228,8 +228,8 @@
see who is watching will be at the top of the page.
-
- If your camera will be featuring "NSFW" or sexual content, please mark it as such by
+
+ If your camera will be featuring "Explicit" or sexual content, please mark it as such by
clicking on the button or checking the box below to start with it enabled.