Various fixes regarding red cameras

* When an admin has marked your camera 'red' for you, the Explicit button at the
  top of the page now will require *two* clicks if the user wants to set their
  camera back to blue:

  On first click, they will get a red ChatClient message explaining that their
  cam was most-recently marked red due to a server action (such as a moderator
  flagging their cam for them). If they really mean to mark their camera blue,
  they are instructed to click it a second time to confirm.
* This behavior only occurs when the most recent NSFW setting was dictated by
  the server (e.g. a 'me' event disagreed with the page's local NSFW setting).
  The flag is cleared any time the user themself toggles the flag, or when the
  first ChatClient warning after a server event is shown.
* The explicit camera settings in the broadcast modal have been rearranged and
  reworded.
* Add an 'advanced' webcam feature to automatically broadcast in the future on
  page load. The option is only available in the Chat Settings 'Camera' tab
  after you are already broadcasting (or rather: when a list of video devices
  have become available to the page, indicating the user has possibly granted
  permission already).
This commit is contained in:
Noah 2024-09-21 15:48:47 -07:00
parent 971a6d800d
commit 3a7204178c
3 changed files with 99 additions and 28 deletions

View File

@ -202,6 +202,9 @@ export default {
audioDevices: [], audioDevices: [],
audioDeviceID: null, audioDeviceID: null,
// Advanced: automatically share your webcam when the page loads.
autoshare: false,
// After we get a device selected, remember it (by name) so that we // After we get a device selected, remember it (by name) so that we
// might hopefully re-select it by default IF we are able to enumerate // might hopefully re-select it by default IF we are able to enumerate
// devices before they go on camera the first time. // devices before they go on camera the first time.
@ -484,6 +487,7 @@ export default {
// Webcam preferences that the user can edit while live. // Webcam preferences that the user can edit while live.
"webcam.nsfw": function () { "webcam.nsfw": function () {
this.webcam.wasServerNSFW = false;
LocalStorage.set('videoExplicit', this.webcam.nsfw); LocalStorage.set('videoExplicit', this.webcam.nsfw);
if (this.webcam.active) { if (this.webcam.active) {
this.sendMe(); this.sendMe();
@ -528,6 +532,9 @@ export default {
"webcam.autoMuteWebcams": function () { "webcam.autoMuteWebcams": function () {
LocalStorage.set('autoMuteWebcams', this.webcam.autoMuteWebcams); LocalStorage.set('autoMuteWebcams', this.webcam.autoMuteWebcams);
}, },
"webcam.autoshare": function () {
LocalStorage.set('videoAutoShare', this.webcam.autoshare);
},
// Misc preference watches // Misc preference watches
"prefs.joinMessages": function () { "prefs.joinMessages": function () {
@ -709,7 +716,7 @@ export default {
if (!this.webcam.active) return 0; // unset all flags if not active now if (!this.webcam.active) return 0; // unset all flags if not active now
if (this.webcam.active) status |= this.VideoFlag.Active; if (this.webcam.active) status |= this.VideoFlag.Active;
if (this.webcam.muted) status |= this.VideoFlag.Muted; if (this.webcam.muted) status |= this.VideoFlag.Muted;
if (this.webcam.nsfw) status |= this.VideoFlag.NSFW; if (this.webcam.nsfw && this.config.permitNSFW) status |= this.VideoFlag.NSFW;
if (this.webcam.mutual) status |= this.VideoFlag.MutualRequired; if (this.webcam.mutual) status |= this.VideoFlag.MutualRequired;
if (this.webcam.mutualOpen) status |= this.VideoFlag.MutualOpen; if (this.webcam.mutualOpen) status |= this.VideoFlag.MutualOpen;
if (this.webcam.nonExplicit) status |= this.VideoFlag.NonExplicit; if (this.webcam.nonExplicit) status |= this.VideoFlag.NonExplicit;
@ -929,10 +936,13 @@ export default {
if (settings.videoAutoMute === true) { if (settings.videoAutoMute === true) {
this.webcam.autoMute = true; this.webcam.autoMute = true;
} }
if (settings.videoAutoShare === true) {
this.webcam.autoshare = true;
}
if (settings.videoVipOnly === true) { if (settings.videoVipOnly === true) {
this.webcam.vipOnly = true; this.webcam.vipOnly = true;
} }
if (settings.videoExplicit === true) { if (settings.videoExplicit === true && this.config.permitNSFW) {
this.webcam.nsfw = true; this.webcam.nsfw = true;
} }
if (settings.videoNonExplicit === true) { if (settings.videoNonExplicit === true) {
@ -1229,6 +1239,9 @@ export default {
let theirNSFW = (msg.video & this.VideoFlag.NSFW) > 0; let theirNSFW = (msg.video & this.VideoFlag.NSFW) > 0;
if (this.webcam.active && myNSFW != theirNSFW && theirNSFW) { if (this.webcam.active && myNSFW != theirNSFW && theirNSFW) {
this.webcam.nsfw = theirNSFW; this.webcam.nsfw = theirNSFW;
window.requestAnimationFrame(() => {
this.webcam.wasServerNSFW = true;
});
} }
// Note: Me events only come when we join the server or a moderator has // Note: Me events only come when we join the server or a moderator has
@ -1571,6 +1584,7 @@ export default {
jwt: this.jwt, jwt: this.jwt,
prefs: this.prefs, prefs: this.prefs,
onLoggedIn: this.onLoggedIn,
onWho: this.onWho, onWho: this.onWho,
onMe: this.onMe, onMe: this.onMe,
onMessage: this.onMessage, onMessage: this.onMessage,
@ -1613,6 +1627,14 @@ export default {
}, 1000); }, 1000);
}); });
}, },
onLoggedIn() {
// Called after the first 'me' is received from the chat server, e.g. once per login.
// Do we auto-broadcast our camera?
if (this.webcam.autoshare) {
this.startVideo({ force: true });
}
},
/** /**
* WebRTC concerns. * WebRTC concerns.
@ -2132,8 +2154,8 @@ export default {
this.getDevices(); this.getDevices();
} }
// If we are running in PermitNSFW mode, show the user the modal. // Show the broadcast settings modal the first time.
if (this.config.permitNSFW && !force) { if (!force) {
this.nsfwModalCast.visible = true; this.nsfwModalCast.visible = true;
return; return;
} }
@ -2297,6 +2319,24 @@ export default {
LocalStorage.set('preferredDeviceNames', this.webcam.preferredDeviceNames); LocalStorage.set('preferredDeviceNames', this.webcam.preferredDeviceNames);
}, },
// The 'Explicit' button at the top of the page: toggle the user's NSFW setting but
// with some smarts in case the user was just marked NSFW by an admin.
topNavExplicitButtonClicked() {
if (this.webcam.wasServerNSFW) {
this.webcam.wasServerNSFW = false;
this.ChatClient(
`Notice: your webcam was already marked as "Explicit" recently by the chat server.<br><br>` +
`If you were recently notified that a chat moderator has marked your camera as 'explicit' (red) for you, then ` +
`you do not need to do anything: your camera is marked Explicit already. Please leave it as Explicit if you are ` +
`being sexual on camera.<br><br>` +
`If you really mean to <strong>remove</strong> the Explicit label (and turn your camera 'blue'), then click on the ` +
`Explicit button at the top of the page one more time to confirm.`,
);
return;
}
this.webcam.nsfw = !this.webcam.nsfw;
},
// Replace your video/audio streams for your watchers (on camera changes) // Replace your video/audio streams for your watchers (on camera changes)
updateWebRTCStreams() { updateWebRTCStreams() {
console.log("Re-negotiating video and audio channels to your watchers."); console.log("Re-negotiating video and audio channels to your watchers.");
@ -4037,17 +4077,18 @@ export default {
</p> </p>
<p class="block mb-1" v-if="config.permitNSFW"> <p class="block mb-1" v-if="config.permitNSFW">
<label class="label">Explicit</label> <label class="label">Explicit webcam options</label>
</p> </p>
<div class="field mb-1" v-if="config.permitNSFW"> <div class="field mb-1" v-if="config.permitNSFW">
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" v-model="webcam.nsfw"> <input type="checkbox" v-model="webcam.nsfw">
Mark my camera as featuring Explicit content Mark my camera as featuring <i class="fa fa-fire has-text-danger mr-2"></i>
<span class="has-text-danger">Explicit</span> or sexual content
</label> </label>
</div> </div>
<div class="field"> <div class="field" v-if="config.permitNSFW">
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" v-model="webcam.nonExplicit"> <input type="checkbox" v-model="webcam.nonExplicit">
I prefer not to see Explicit cameras from other chatters I prefer not to see Explicit cameras from other chatters
@ -4138,6 +4179,22 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<p class="block mb-1" v-if="webcam.videoDevices.length > 0">
<label class="label">Miscellaneous</label>
</p>
<div class="field mb-1" v-if="webcam.videoDevices.length > 0">
<label class="checkbox">
<input type="checkbox" v-model="webcam.autoshare">
Automatically go on camera when I log onto the chat room
</label>
<p class="help">
Note: be sure that your web browser has <strong>remembered</strong> your webcam and mic
permission! This option can automatically share your webcam when you log onto chat again
from this device.
</p>
</div>
</div> </div>
<!-- Misc preferences --> <!-- Misc preferences -->
@ -4283,23 +4340,35 @@ export default {
</label> </label>
</div> </div>
<p class="block mb-1"> <p v-if="config.permitNSFW" class="block mb-1">
If your camera will be featuring "<abbr title="Not Safe For Work">Explicit</abbr>" or sexual <label class="label">'Explicit' webcam options</label>
content, please
mark it as such by clicking on the "<small><i class="fa fa-fire mr-1 has-text-danger"></i>
Explicit</small>"
button at the top of the page, or check the box below to start with it enabled.
</p> </p>
<div class="field"> <div class="field" v-if="config.permitNSFW">
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" v-model="webcam.nsfw"> <input type="checkbox" v-model="webcam.nsfw">
Check this box if your webcam will <em>definitely</em> be Explicit. 😈 Mark my camera as featuring <i class="fa fa-fire has-text-danger mr-2"></i>
<span class="has-text-danger">Explicit</span> or sexual content
</label> </label>
<p class="help">
You can toggle this at any time by clicking on the '<i class="fa fa-fire"></i> Explicit'
button at the top of the page.
</p>
</div>
<div class="field" v-if="config.permitNSFW">
<label class="checkbox">
<input type="checkbox" v-model="webcam.nonExplicit">
I prefer not to see Explicit cameras from other chatters
</label>
<p class="help">
Close, and don't automatically open, other peoples' cameras when they toggle
to become explicit.
</p>
</div> </div>
<p class="block mb-1"> <p class="block mb-1">
<label class="label">Mutual webcam options:</label> <label class="label">Mutual webcam options</label>
</p> </p>
<div class="field mb-1"> <div class="field mb-1">
@ -4316,17 +4385,6 @@ export default {
</label> </label>
</div> </div>
<div class="field">
<label class="checkbox">
<input type="checkbox" v-model="webcam.nonExplicit">
I prefer not to see Explicit cameras from other chatters
</label>
<p class="help">
Don't auto-open explicit cameras when they open mine; and automatically
close a camera I am watching if it toggles to become explicit.
</p>
</div>
<div class="field" v-if="isVIP"> <div class="field" v-if="isVIP">
<label class="checkbox"> <label class="checkbox">
<input type="checkbox" v-model="webcam.vipOnly"> <input type="checkbox" v-model="webcam.vipOnly">
@ -4452,7 +4510,7 @@ export default {
:class="{ :class="{
'is-outlined is-dark': !webcam.nsfw, 'is-outlined is-dark': !webcam.nsfw,
'is-danger': webcam.nsfw 'is-danger': webcam.nsfw
}" @click.prevent="webcam.nsfw = !webcam.nsfw" }" @click.prevent="topNavExplicitButtonClicked()"
title="Toggle the NSFW setting for your camera broadcast"> title="Toggle the NSFW setting for your camera broadcast">
<i class="fa fa-fire mr-1" :class="{ 'has-text-danger': !webcam.nsfw }"></i> Explicit <i class="fa fa-fire mr-1" :class="{ 'has-text-danger': !webcam.nsfw }"></i> Explicit
</button> </button>

View File

@ -32,6 +32,7 @@ class ChatClient {
onCut, onCut,
// Misc function registrations for callback. // Misc function registrations for callback.
onLoggedIn, // connection is fully established (first 'me' echo from server).
onNewJWT, // new JWT token from ping response onNewJWT, // new JWT token from ping response
bulkMuteUsers, // Upload our blocklist on connect. bulkMuteUsers, // Upload our blocklist on connect.
focusMessageBox, // Tell caller to focus the message entry box. focusMessageBox, // Tell caller to focus the message entry box.
@ -62,11 +63,15 @@ class ChatClient {
this.onBlock = onBlock; this.onBlock = onBlock;
this.onCut = onCut; this.onCut = onCut;
this.onLoggedIn = onLoggedIn;
this.onNewJWT = onNewJWT; this.onNewJWT = onNewJWT;
this.bulkMuteUsers = bulkMuteUsers; this.bulkMuteUsers = bulkMuteUsers;
this.focusMessageBox = focusMessageBox; this.focusMessageBox = focusMessageBox;
this.pushHistory = pushHistory; this.pushHistory = pushHistory;
// Received the first 'me' echo from server (to call onLoggedIn once per connection)
this.firstMe = false;
// WebSocket connection. // WebSocket connection.
this.ws = { this.ws = {
conn: null, conn: null,
@ -159,6 +164,13 @@ class ChatClient {
break; break;
case "me": case "me":
this.onMe(msg); this.onMe(msg);
// The first me?
if (!this.firstMe) {
this.firstMe = true;
this.onLoggedIn();
}
break; break;
case "message": case "message":
this.onMessage(msg); this.onMessage(msg);

View File

@ -18,6 +18,7 @@ const keys = {
'videoNonExplicit': Boolean, // user prefers not to see explicit 'videoNonExplicit': Boolean, // user prefers not to see explicit
'rememberExpresslyClosed': Boolean, 'rememberExpresslyClosed': Boolean,
'autoMuteWebcams': Boolean, // automatically mute other peoples' webcam audio feeds 'autoMuteWebcams': Boolean, // automatically mute other peoples' webcam audio feeds
'videoAutoShare': Boolean, // automatically share your webcam on page load
// Booleans // Booleans
'usePolling': Boolean, // use the polling API instead of WebSocket 'usePolling': Boolean, // use the polling API instead of WebSocket