diff --git a/pkg/logging.go b/pkg/logging.go index cefd316..a0583ff 100644 --- a/pkg/logging.go +++ b/pkg/logging.go @@ -87,8 +87,20 @@ func LogChannel(s *Server, channel string, username string, msg messages.Message ) } +// Tear down log files for subscribers. +func (s *Subscriber) teardownLogs() { + if s.logfh == nil { + return + } + + for username, fh := range s.logfh { + log.Error("TeardownLogs(%s/%s)", s.Username, username) + fh.Close() + } +} + // Initialize a logging directory. -func initLogFile(sub LogCacheable, components ...string) (io.Writer, error) { +func initLogFile(sub LogCacheable, components ...string) (io.WriteCloser, error) { // Initialize the logfh cache? var logfh = sub.GetLogFilehandleCache() @@ -126,19 +138,19 @@ func initLogFile(sub LogCacheable, components ...string) (io.Writer, error) { // Interface for objects that hold log filehandle caches. type LogCacheable interface { - GetLogFilehandleCache() map[string]io.Writer + GetLogFilehandleCache() map[string]io.WriteCloser } // Implementations of LogCacheable. -func (sub *Subscriber) GetLogFilehandleCache() map[string]io.Writer { +func (sub *Subscriber) GetLogFilehandleCache() map[string]io.WriteCloser { if sub.logfh == nil { - sub.logfh = map[string]io.Writer{} + sub.logfh = map[string]io.WriteCloser{} } return sub.logfh } -func (s *Server) GetLogFilehandleCache() map[string]io.Writer { +func (s *Server) GetLogFilehandleCache() map[string]io.WriteCloser { if s.logfh == nil { - s.logfh = map[string]io.Writer{} + s.logfh = map[string]io.WriteCloser{} } return s.logfh } diff --git a/pkg/server.go b/pkg/server.go index 0dfaf64..1511c2a 100644 --- a/pkg/server.go +++ b/pkg/server.go @@ -19,7 +19,7 @@ type Server struct { subscribers map[*Subscriber]struct{} // Cached filehandles for channel logging. - logfh map[string]io.Writer + logfh map[string]io.WriteCloser } // NewServer initializes the Server. diff --git a/pkg/websocket.go b/pkg/websocket.go index 424a782..e2ad059 100644 --- a/pkg/websocket.go +++ b/pkg/websocket.go @@ -48,7 +48,7 @@ type Subscriber struct { // Logging. log bool - logfh map[string]io.Writer + logfh map[string]io.WriteCloser } // ReadLoop spawns a goroutine that reads from the websocket connection. @@ -288,6 +288,9 @@ func (s *Server) DeleteSubscriber(sub *Subscriber) { sub.cancel() } + // Clean up any log files. + sub.teardownLogs() + s.subscribersMu.Lock() delete(s.subscribers, sub) s.subscribersMu.Unlock() diff --git a/src/App.vue b/src/App.vue index 590b777..66732c8 100644 --- a/src/App.vue +++ b/src/App.vue @@ -445,7 +445,7 @@ export default { } } }, - "webcam.rememberExpresslyClosed": function() { + "webcam.rememberExpresslyClosed": function () { LocalStorage.set('rememberExpresslyClosed', this.webcam.rememberExpresslyClosed); }, @@ -899,6 +899,28 @@ export default { return; } + // DEBUGGING: print WebRTC statistics + if (this.message.toLowerCase().indexOf("/debug-webrtc") === 0) { + let lines = [ + "WebRTC PeerConnections:" + ]; + for (let username of Object.keys(this.WebRTC.pc)) { + let pc = this.WebRTC.pc[username]; + let line = `${username}: `; + if (pc.offerer != undefined) { + line += "offerer; "; + } + if (pc.answerer != undefined) { + line += "answerer; "; + } + lines.push(line); + } + + this.ChatClient(lines.join("
")); + this.message = ""; + return; + } + // console.debug("Send message: %s", this.message); this.ws.conn.send(JSON.stringify({ action: "message", @@ -1633,6 +1655,7 @@ export default { // The user has closed our video feed. delete (this.webcam.watching[msg.username]); this.playSound("Unwatch"); + this.cleanupPeerConnections(); }, sendWatch(username, watching) { // Send the watch or unwatch message to backend. @@ -2108,7 +2131,7 @@ export default { // 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]); + 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; @@ -2174,20 +2197,38 @@ export default { delete (this.WebRTC.streams[username]); delete (this.WebRTC.muted[username]); delete (this.WebRTC.poppedOut[username]); + + // Should we close the WebRTC PeerConnection? If they were watching our video back, closing + // the connection MAY cause our video to freeze on their side: if we have the "auto-open my viewer's + // camera" option set, and our viewer sent their video on their open request, and they still have + // our camera open, do not close the connection so we don't freeze their side of the video. if (this.WebRTC.pc[username] != undefined && this.WebRTC.pc[username].offerer != undefined) { - this.WebRTC.pc[username].offerer.close(); - delete (this.WebRTC.pc[username]); + if (this.webcam.mutualOpen && this.isWatchingMe(username)) { + console.log(`OFFERER(${username}): Close video locally only: do not hang up the connection.`); + } else { + this.WebRTC.pc[username].offerer.close(); + delete (this.WebRTC.pc[username]); + } } // Inform backend we have closed it. this.sendWatch(username, false); + this.cleanupPeerConnections(); return; } else if (name === "answerer") { - // We have turned off our camera, kick off viewers. + // Should we close the WebRTC PeerConnection? If they were watching our video back, closing + // the connection MAY cause our video to freeze on their side: if we have the "auto-open my viewer's + // camera" option set, and our viewer sent their video on their open request, and they still have + // our camera open, do not close the connection so we don't freeze their side of the video. if (this.WebRTC.pc[username] != undefined && this.WebRTC.pc[username].answerer != undefined) { - this.WebRTC.pc[username].answerer.close(); - delete (this.WebRTC.pc[username]); + if (this.webcam.mutualOpen && this.isWatchingMe(username)) { + console.log(`ANSWERER(${username}): Close video locally only: do not hang up the connection.`); + } else { + this.WebRTC.pc[username].answerer.close(); + delete (this.WebRTC.pc[username]); + } } + this.cleanupPeerConnections(); return; } @@ -2215,6 +2256,7 @@ export default { // Inform backend we have closed it. this.sendWatch(username, false); + this.cleanupPeerConnections(); }, expresslyCloseVideo(username, name) { // Like closeVideo but communicates the user's intent to expressly @@ -2230,6 +2272,43 @@ export default { this.closeVideo(username, "offerer"); } }, + cleanupPeerConnections() { + // Helper function to check and clean up WebRTC PeerConnections. + // + // This is fired on Unwatch and CloseVideo events, to double check + // which videos the local user has open + who online is watching our + // video, to close out any lingering WebRTC connections. + for (let username of Object.keys(this.WebRTC.pc)) { + let pc = this.WebRTC.pc[username]; + + // Is their video on our screen? + if (this.WebRTC.streams[username] != undefined) { + continue; + } + + // Are they watching us? + if (this.isWatchingMe(username)) { + continue; + } + + // Are they an admin that we booted? + if (this.isBootedAdmin(username)) { + continue; + } + + // The WebRTC connections should be closed out. + if (pc.answerer != undefined) { + console.log("Clean up WebRTC answerer connection with " + username); + pc.answerer.close(); + delete (this.WebRTC.pc[username]); + } + if (pc.offerer != undefined) { + console.log("Clean up WebRTC offerer connection with " + username); + pc.offerer.close(); + delete (this.WebRTC.pc[username]); + } + } + }, muteAllVideos() { // Mute the mic of all open videos. let count = 0; @@ -2357,7 +2436,7 @@ export default { } this.sendUnboot(username); - delete(this.WebRTC.booted[username]); + delete (this.WebRTC.booted[username]); return; } @@ -2379,7 +2458,7 @@ export default { } // Remove them from our list. - delete(this.webcam.watching[username]); + delete (this.webcam.watching[username]); this.ChatClient( `You have booted ${username} off your camera. They will no longer be able ` + @@ -2612,8 +2691,8 @@ export default { } // Were we at mentioned in this message? - if (message.indexOf("@"+this.username) > -1) { - let re = new RegExp("@"+this.username+"\\b", "ig"); + if (message.indexOf("@" + this.username) > -1) { + let re = new RegExp("@" + this.username + "\\b", "ig"); message = message.replace(re, `@${this.username}`); } @@ -3097,7 +3176,8 @@ export default {
@@ -3325,8 +3405,7 @@ export default {
@@ -3338,8 +3417,7 @@ export default {
@@ -3483,8 +3561,8 @@ export default {
+ and we are able to enumerate their device names, we can show them here before they + go on this time.-->
@@ -3525,35 +3603,20 @@ export default {
- - + - + -