Video freeze detection + Other tweaks
* Color code usernames on video windows to be blue or red depending on their local cam explicit setting * Attempt to detect freezes on RTCPeerConnection videos by registering a video onmute handler. If a freeze is detected, show a cyan mountain icon by their name. Clicking the icon will re-connect their video. * Update the video buttons on the Who List to always re-connect video instead of toggling it opened and closed. The X buttons on videos are now how you close a video.
This commit is contained in:
parent
37360211e7
commit
b3d4b375ed
|
@ -28,6 +28,14 @@ body {
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Color coded webcam usernames per camera setting */
|
||||||
|
.has-text-camera-blue {
|
||||||
|
color: #00ccff !important;
|
||||||
|
}
|
||||||
|
.has-text-camera-red {
|
||||||
|
color: #ff9999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/************************
|
/************************
|
||||||
* Main CSS Grid Layout *
|
* Main CSS Grid Layout *
|
||||||
************************/
|
************************/
|
||||||
|
|
|
@ -161,6 +161,13 @@ const app = Vue.createApp({
|
||||||
|
|
||||||
// RTCPeerConnections per username.
|
// RTCPeerConnections per username.
|
||||||
pc: {},
|
pc: {},
|
||||||
|
|
||||||
|
// Video stream freeze detection.
|
||||||
|
frozenStreamInterval: {}, // map usernames to intervals
|
||||||
|
frozenStreamDetected: {}, // map usernames to bools
|
||||||
|
|
||||||
|
// Debounce connection attempts since now every click = try to connect.
|
||||||
|
debounceOpens: {}, // map usernames to bools
|
||||||
},
|
},
|
||||||
|
|
||||||
// Chat history.
|
// Chat history.
|
||||||
|
@ -398,6 +405,7 @@ const app = Vue.createApp({
|
||||||
myVideoFlag() {
|
myVideoFlag() {
|
||||||
// Compute the current user's video status flags.
|
// Compute the current user's video status flags.
|
||||||
let status = 0;
|
let status = 0;
|
||||||
|
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) status |= this.VideoFlag.NSFW;
|
||||||
|
@ -507,6 +515,16 @@ const app = Vue.createApp({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DEBUGGING: fake set the freeze indicator.
|
||||||
|
let match = this.message.match(/^\/freeze (.+?)$/i);
|
||||||
|
if (match) {
|
||||||
|
let username = match[1];
|
||||||
|
this.WebRTC.frozenStreamDetected[username] = true;
|
||||||
|
this.ChatClient(`DEBUG: Marked ${username} stream as frozen.`);
|
||||||
|
this.message = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// console.debug("Send message: %s", this.message);
|
// console.debug("Send message: %s", this.message);
|
||||||
this.ws.conn.send(JSON.stringify({
|
this.ws.conn.send(JSON.stringify({
|
||||||
action: "message",
|
action: "message",
|
||||||
|
@ -987,6 +1005,28 @@ const app = Vue.createApp({
|
||||||
|
|
||||||
// Inform them they are being watched.
|
// Inform them they are being watched.
|
||||||
this.sendWatch(username, true);
|
this.sendWatch(username, true);
|
||||||
|
|
||||||
|
// Set a mute video handler to detect freezes.
|
||||||
|
stream.getVideoTracks().forEach(videoTrack => {
|
||||||
|
let freezeDetected = () => {
|
||||||
|
console.log("FREEZE DETECTED:", username);
|
||||||
|
// Wait 3 seconds to see if the stream has recovered on its own
|
||||||
|
setTimeout(() => {
|
||||||
|
// Flag it as likely frozen.
|
||||||
|
if (videoTrack.muted) {
|
||||||
|
this.WebRTC.frozenStreamDetected[username] = true;
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Apply onmute handler for", username);
|
||||||
|
videoTrack.onmute = freezeDetected;
|
||||||
|
|
||||||
|
// Double check for frozen streams on an interval.
|
||||||
|
this.WebRTC.frozenStreamInterval[username] = setInterval(() => {
|
||||||
|
if (videoTrack.muted) freezeDetected();
|
||||||
|
}, 3000);
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// If we were already broadcasting video, send our stream to
|
// If we were already broadcasting video, send our stream to
|
||||||
|
@ -1212,6 +1252,14 @@ const app = Vue.createApp({
|
||||||
}
|
}
|
||||||
return username;
|
return username;
|
||||||
},
|
},
|
||||||
|
isUsernameCamNSFW(username) {
|
||||||
|
// returns true if the username is broadcasting and NSFW, false otherwise.
|
||||||
|
// (used for the color coding of their nickname on their video box - so assumed they are broadcasting)
|
||||||
|
if (this.whoMap[username] != undefined && this.whoMap[username].video & this.VideoFlag.NSFW) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
leaveDM() {
|
leaveDM() {
|
||||||
// Validate we're in a DM currently.
|
// Validate we're in a DM currently.
|
||||||
if (this.channel.indexOf("@") !== 0) return;
|
if (this.channel.indexOf("@") !== 0) return;
|
||||||
|
@ -1407,7 +1455,21 @@ const app = Vue.createApp({
|
||||||
},
|
},
|
||||||
|
|
||||||
// Begin connecting to someone else's webcam.
|
// Begin connecting to someone else's webcam.
|
||||||
|
openVideoByUsername(username, force) {
|
||||||
|
if (this.whoMap[username] != undefined) {
|
||||||
|
this.openVideo(this.whoMap[username], force);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.ChatClient("Couldn't open video by username: not found.");
|
||||||
|
},
|
||||||
openVideo(user, force) {
|
openVideo(user, force) {
|
||||||
|
// 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;
|
||||||
|
setTimeout(() => {
|
||||||
|
delete(this.WebRTC.debounceOpens[user.username]);
|
||||||
|
}, 5000);
|
||||||
|
|
||||||
if (user.username === this.username) {
|
if (user.username === this.username) {
|
||||||
this.ChatClient("You can already see your own webcam.");
|
this.ChatClient("You can already see your own webcam.");
|
||||||
return;
|
return;
|
||||||
|
@ -1434,7 +1496,6 @@ const app = Vue.createApp({
|
||||||
// Camera is already open? Then disconnect the connection.
|
// Camera is already open? Then disconnect the connection.
|
||||||
if (this.WebRTC.pc[user.username] != undefined && this.WebRTC.pc[user.username].offerer != undefined) {
|
if (this.WebRTC.pc[user.username] != undefined && this.WebRTC.pc[user.username].offerer != undefined) {
|
||||||
this.closeVideo(user.username, "offerer");
|
this.closeVideo(user.username, "offerer");
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this user requests mutual viewership...
|
// If this user requests mutual viewership...
|
||||||
|
@ -1466,6 +1527,13 @@ const app = Vue.createApp({
|
||||||
delete (this.WebRTC.pc[username]);
|
delete (this.WebRTC.pc[username]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up any lingering camera freeze states.
|
||||||
|
delete (this.WebRTC.frozenStreamDetected[username]);
|
||||||
|
if (this.WebRTC.frozenStreamInterval[username]) {
|
||||||
|
clearInterval(this.WebRTC.frozenStreamInterval);
|
||||||
|
delete(this.WebRTC.frozenStreamInterval[username]);
|
||||||
|
}
|
||||||
|
|
||||||
// Inform backend we have closed it.
|
// Inform backend we have closed it.
|
||||||
this.sendWatch(username, false);
|
this.sendWatch(username, false);
|
||||||
return;
|
return;
|
||||||
|
@ -1493,6 +1561,13 @@ const app = Vue.createApp({
|
||||||
delete (this.WebRTC.poppedOut[username]);
|
delete (this.WebRTC.poppedOut[username]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up any lingering camera freeze states.
|
||||||
|
delete (this.WebRTC.frozenStreamDetected[username]);
|
||||||
|
if (this.WebRTC.frozenStreamInterval[username]) {
|
||||||
|
clearInterval(this.WebRTC.frozenStreamInterval);
|
||||||
|
delete(this.WebRTC.frozenStreamInterval[username]);
|
||||||
|
}
|
||||||
|
|
||||||
// Inform backend we have closed it.
|
// Inform backend we have closed it.
|
||||||
this.sendWatch(username, false);
|
this.sendWatch(username, false);
|
||||||
},
|
},
|
||||||
|
|
|
@ -591,7 +591,9 @@
|
||||||
autoplay muted>
|
autoplay muted>
|
||||||
</video>
|
</video>
|
||||||
|
|
||||||
<div class="caption">
|
<div class="caption"
|
||||||
|
:class="{'has-text-camera-blue': !webcam.nsfw,
|
||||||
|
'has-text-camera-red': webcam.nsfw}">
|
||||||
<i class="fa fa-microphone-slash mr-1 has-text-grey"
|
<i class="fa fa-microphone-slash mr-1 has-text-grey"
|
||||||
v-if="webcam.muted"></i>
|
v-if="webcam.muted"></i>
|
||||||
[[username]]
|
[[username]]
|
||||||
|
@ -631,19 +633,28 @@
|
||||||
:id="'videofeed-'+username"
|
:id="'videofeed-'+username"
|
||||||
autoplay>
|
autoplay>
|
||||||
</video>
|
</video>
|
||||||
<div class="caption">
|
<div class="caption"
|
||||||
|
:class="{'has-text-camera-blue': !isUsernameCamNSFW(username),
|
||||||
|
'has-text-camera-red': isUsernameCamNSFW(username)}">
|
||||||
<i class="fa fa-microphone-slash mr-1 has-text-grey"
|
<i class="fa fa-microphone-slash mr-1 has-text-grey"
|
||||||
v-if="isSourceMuted(username)"></i>
|
v-if="isSourceMuted(username)"></i>
|
||||||
[[username]]
|
[[username]]
|
||||||
<i class="fa fa-people-arrows ml-1 has-text-grey is-size-7"
|
<i class="fa fa-people-arrows ml-1 has-text-grey is-size-7"
|
||||||
:title="username+' is watching your camera too'"
|
:title="username+' is watching your camera too'"
|
||||||
v-if="isWatchingMe(username)"></i>
|
v-if="isWatchingMe(username)"></i>
|
||||||
|
|
||||||
|
<!-- Frozen stream detection -->
|
||||||
|
<a class="fa fa-mountain ml-1" href="#"
|
||||||
|
v-if="WebRTC.frozenStreamDetected[username]"
|
||||||
|
style="color: #00FFFF"
|
||||||
|
@click.prevent="openVideoByUsername(username, true)"
|
||||||
|
title="Frozen video detected!"></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="close">
|
<div class="close">
|
||||||
<a href="#"
|
<a href="#"
|
||||||
class="has-text-danger"
|
class="has-text-danger"
|
||||||
title="Close video"
|
title="Close video"
|
||||||
@click="closeVideo(username, 'offerer')">
|
@click.prevent="closeVideo(username, 'offerer')">
|
||||||
<i class="fa fa-close"></i>
|
<i class="fa fa-close"></i>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user