diff --git a/src/App.vue b/src/App.vue
index 8ab2abf..735662e 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -219,6 +219,18 @@ export default {
// a camera, the icon changes to a spinner for a few seconds to see
// whether the video goes on to open.
openTimeouts: {}, // map usernames to timeouts
+
+ // Map of usernames whose cameras were expressly closed by us.
+ // Example: we have mutualOpen enabled, someone opens our cam
+ // so we open theirs back, and then we expressly 'X' out and
+ // close their camera. If they close and re-open ours, we don't
+ // want to auto-open their cam back because we had previously
+ // closed out of it manually.
+ //
+ // Usernames are added when we 'X' out of their video,
+ // and usernames are removed when we expressly re-open their
+ // video (e.g., by clicking their Who List video button).
+ expresslyClosed: {}, // bool per username
},
// Chat history.
@@ -1390,6 +1402,16 @@ export default {
pc.ontrack = event => {
const stream = event.streams[0];
+ // Had we expressly closed this user's cam before? e.g.: if we have auto-open their
+ // video enabled, and we 'X' out, and they reopen ours - we may be receiving their
+ // video right now. If we had expressly closed it, do not accept their video
+ // and hang up the connection.
+ if (this.WebRTC.expresslyClosed[username]) {
+ if (!isOfferer) {
+ return;
+ }
+ }
+
// We've received a video! If we had an "open camera spinner timeout",
// clear it before it expires.
if (this.WebRTC.openTimeouts[username] != undefined) {
@@ -1457,7 +1479,12 @@ export default {
// is also the only way that iPads/iPhones/Safari browsers can make a call
// (two-way video is the only option for them; send-only/receive-only channels seem
// not to work in Safari).
- if (isOfferer && (this.whoMap[username].video & this.VideoFlag.MutualOpen) && this.webcam.active) {
+ if (isOfferer &&
+ (this.whoMap[username].video & this.VideoFlag.MutualOpen) // They auto-open us
+ && this.webcam.active // Our camera is active (to add it)
+ && !this.isBooted(username) // We had not booted them off ours before
+ && !this.isMutedUser(username) // We had not muted them before
+ ) {
let stream = this.webcam.stream;
stream.getTracks().forEach(track => {
pc.addTrack(track, stream)
@@ -1503,7 +1530,7 @@ export default {
// Add the new ICE candidate.
pc.addIceCandidate(candidate).catch(e => {
- this.ChatClient(`addIceCandidate: ${e}`);
+ console.error(`addIceCandidate: ${e}`);
});
},
onSDP(msg) {
@@ -2002,11 +2029,17 @@ export default {
}
// If we have muted the target, we shouldn't view their video.
- if (this.isMutedUser(user.username)) {
+ if (this.isMutedUser(user.username) && !this.isOp) {
this.ChatClient(`You have muted ${user.username} and so should not see their camera.`);
return;
}
+ // If we have booted the target off our cam, we shouldn't view their video.
+ if (this.isBooted(user.username) && !this.isOp) {
+ this.ChatClient(`You had kicked ${user.username} off your camera and so it wouldn't be right to still watch their camera.`);
+ return;
+ }
+
// Is the target user NSFW? Go thru the modal.
let dontShowAgain = LocalStorage.get("skip-nsfw-modal") === true;
if ((user.video & this.VideoFlag.NSFW) && !dontShowAgain && !force) {
@@ -2015,6 +2048,10 @@ export default {
return;
}
+ // If the local user had expressly closed this user's camera before, forget
+ // this action because the user now is expressly OPENING this camera on purpose.
+ delete(this.WebRTC.expresslyClosed[user.username]);
+
// Debounce so we don't spam too much for the same user.
if (this.WebRTC.debounceOpens[user.username]) return;
this.WebRTC.debounceOpens[user.username] = true;
@@ -2028,7 +2065,7 @@ export default {
}
// If this user requests mutual viewership...
- if (this.isVideoNotAllowed(user)) {
+ if (this.isVideoNotAllowed(user) && !this.isOp) {
this.ChatClient(
`${user.username} has requested that you should share your own camera too before opening theirs.`
);
@@ -2121,6 +2158,14 @@ export default {
// Inform backend we have closed it.
this.sendWatch(username, false);
},
+ expresslyCloseVideo(username, name) {
+ // Like closeVideo but communicates the user's intent to expressly
+ // close the video for a user. e.g. they clicked the 'X' icon to
+ // close it out. As opposed to a user went off camera and were closed
+ // out automatically.
+ this.WebRTC.expresslyClosed[username] = true;
+ return this.closeVideo(username, name);
+ },
closeOpenVideos() {
// Close all videos open of other users.
for (let username of Object.keys(this.WebRTC.streams)) {
@@ -2218,7 +2263,7 @@ export default {
}
// We have muted them and it wouldn't be appropriate to still watch their video but not get their messages.
- if (this.isMutedUser(user.username)) {
+ if (this.isMutedUser(user.username) || this.isBooted(user.username)) {
return true;
}
@@ -2270,9 +2315,9 @@ export default {
this.sendBoot(username);
this.WebRTC.booted[username] = true;
- // Close the WebRTC peer connection.
+ // Close the WebRTC peer connections.
if (this.WebRTC.pc[username] != undefined) {
- this.closeVideo(username, "answerer");
+ this.closeVideo(username);
}
// Remove them from our list.
@@ -3653,7 +3698,7 @@ export default {
@reopen-video="openVideoByUsername"
@mute-video="muteVideo"
@popout="popoutVideo"
- @close-video="closeVideo"
+ @close-video="expresslyCloseVideo"
@set-volume="setVideoVolume">
@@ -3952,6 +3997,7 @@ export default {
:website-url="config.website"
:is-dnd="isUsernameDND(u.username)"
:is-muted="isMutedUser(u.username)"
+ :is-booted="isBooted(u.username)"
:is-op="isOp"
:is-video-not-allowed="isVideoNotAllowed(u)"
:video-icon-class="webcamIconClass(u)"
@@ -3973,6 +4019,7 @@ export default {
:website-url="config.website"
:is-dnd="isUsernameDND(username)"
:is-muted="isMutedUser(username)"
+ :is-booted="isBooted(u.username)"
:is-op="isOp"
:is-video-not-allowed="isVideoNotAllowed(u)"
:video-icon-class="webcamIconClass(u)"
diff --git a/src/components/WhoListRow.vue b/src/components/WhoListRow.vue
index b9df7f4..e534b2e 100644
--- a/src/components/WhoListRow.vue
+++ b/src/components/WhoListRow.vue
@@ -8,6 +8,7 @@ export default {
websiteUrl: String, // Base URL to website (for profile/avatar URLs)
isDnd: Boolean, // user is not accepting DMs
isMuted: Boolean, // user is muted by current user
+ isBooted: Boolean, // user is booted by current user
vipConfig: Object, // VIP config settings for BareRTC
isOp: Boolean, // current user is operator (can always DM)
isVideoNotAllowed: Boolean, // whether opening this camera is not allowed