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.VideoActive = msg.VideoActive
sub.VideoMutual = msg.VideoMutual
sub.VideoMutualOpen = msg.VideoMutualOpen
sub.VideoNSFW = msg.NSFW sub.VideoNSFW = msg.NSFW
sub.ChatStatus = msg.ChatStatus sub.ChatStatus = msg.ChatStatus

View File

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

View File

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

View File

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

View File

@ -91,6 +91,8 @@ const app = Vue.createApp({
stream: null, // MediaStream object stream: null, // MediaStream object
muted: false, // our outgoing mic is muted, not by default muted: false, // our outgoing mic is muted, not by default
nsfw: false, // user has flagged their camera to be NSFW 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. // Who all is watching me? map of users.
watching: {}, watching: {},
@ -331,6 +333,8 @@ const app = Vue.createApp({
this.ws.conn.send(JSON.stringify({ this.ws.conn.send(JSON.stringify({
action: "me", action: "me",
videoActive: this.webcam.active, videoActive: this.webcam.active,
videoMutual: this.webcam.mutual,
videoMutualOpen: this.webcam.mutualOpen,
status: this.status, status: this.status,
nsfw: this.webcam.nsfw, 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 we are the offerer, begin the connection.
if (isOfferer) { if (isOfferer) {
pc.createOffer({ pc.createOffer({
@ -953,6 +966,14 @@ const app = Vue.createApp({
return; 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); this.sendOpen(user.username);
// Responsive CSS -> go to chat panel to see the camera // Responsive CSS -> go to chat panel to see the camera
@ -995,6 +1016,16 @@ const app = Vue.createApp({
// Inform backend we have closed it. // Inform backend we have closed it.
this.sendWatch(username, false); 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. // Show who watches our video.
showViewers() { showViewers() {
@ -1052,6 +1083,9 @@ const app = Vue.createApp({
this.closeVideo(username, "answerer"); this.closeVideo(username, "answerer");
} }
// Hang up on mutual cameras.
this.unMutualVideo();
// Tell backend our camera state. // Tell backend our camera state.
this.sendMe(); 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. <i class="fa fa-eye"></i> see who is watching will be at the top of the page.
</p> </p>
<p class="block"> <p class="block mb-1">
If your camera will be featuring "<abbr title="Not Safe For Work">NSFW</abbr>" or sexual content, please mark it as such by 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. clicking on the <i class="fa fa-fire has-text-danger"></i> button or checking the box below to start with it enabled.
</p> </p>
@ -237,7 +237,28 @@
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" <input type="checkbox"
v-model="webcam.nsfw"> 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> </label>
</div> </div>
@ -778,8 +799,12 @@
:class="{ :class="{
'is-danger is-outlined': u.videoActive && u.nsfw, 'is-danger is-outlined': u.videoActive && u.nsfw,
'is-info 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)"> @click="openVideo(u)">
<i class="fa fa-video"></i> <i class="fa fa-video"></i>
</button> </button>