diff --git a/pkg/handlers.go b/pkg/handlers.go index 5a47a9c..0617d55 100644 --- a/pkg/handlers.go +++ b/pkg/handlers.go @@ -5,7 +5,6 @@ import ( "fmt" "path/filepath" "strings" - "time" "git.kirsle.net/apps/barertc/pkg/config" "git.kirsle.net/apps/barertc/pkg/jwt" @@ -54,21 +53,7 @@ func (s *Server) OnLogin(sub *Subscriber, msg Message) { } // Ensure the username is unique, or rename it. - var duplicate bool - for _, other := range s.IterSubscribers() { - if other.ID != sub.ID && other.Username == msg.Username { - duplicate = true - break - } - } - - if duplicate { - // Give them one that is unique. - msg.Username = fmt.Sprintf("%s %d", - msg.Username, - time.Now().Nanosecond(), - ) - } + msg.Username = s.UniqueUsername(msg.Username) // Use their username. sub.Username = msg.Username diff --git a/pkg/jwt/jwt.go b/pkg/jwt/jwt.go index 97327e8..bef805a 100644 --- a/pkg/jwt/jwt.go +++ b/pkg/jwt/jwt.go @@ -15,6 +15,7 @@ type Claims struct { IsAdmin bool `json:"op"` Avatar string `json:"img"` ProfileURL string `json:"url"` + Nick string `json:"nick"` // Standard claims. Notes: // subject = username diff --git a/pkg/messages.go b/pkg/messages.go index f3bb736..78068c9 100644 --- a/pkg/messages.go +++ b/pkg/messages.go @@ -69,6 +69,7 @@ const ( // WhoList is a member entry in the chat room. type WhoList struct { Username string `json:"username"` + Nickname string `json:"nickname,omitempty"` VideoActive bool `json:"videoActive,omitempty"` VideoMutual bool `json:"videoMutual,omitempty"` VideoMutualOpen bool `json:"videoMutualOpen,omitempty"` diff --git a/pkg/websocket.go b/pkg/websocket.go index fec6e37..1893627 100644 --- a/pkg/websocket.go +++ b/pkg/websocket.go @@ -259,6 +259,31 @@ func (s *Server) IterSubscribers(isLocked ...bool) []*Subscriber { return result } +// UniqueUsername ensures a username will be unique or renames it. +func (s *Server) UniqueUsername(username string) string { + var ( + subs = s.IterSubscribers() + usernames = map[string]interface{}{} + origUsername = username + counter = 2 + ) + for _, sub := range subs { + usernames[sub.Username] = nil + } + + // Check until unique. + for { + if _, ok := usernames[username]; ok { + username = fmt.Sprintf("%s %d", origUsername, counter) + counter++ + } else { + break + } + } + + return username +} + // Broadcast a message to the chat room. func (s *Server) Broadcast(msg Message) { if len(msg.Message) < 1024 { @@ -358,6 +383,7 @@ func (s *Server) SendWhoList() { who.Operator = user.JWTClaims.IsAdmin who.Avatar = user.JWTClaims.Avatar who.ProfileURL = user.JWTClaims.ProfileURL + who.Nickname = user.JWTClaims.Nick } users = append(users, who) } diff --git a/web/static/js/BareRTC.js b/web/static/js/BareRTC.js index a0c00c1..3ceebc6 100644 --- a/web/static/js/BareRTC.js +++ b/web/static/js/BareRTC.js @@ -107,6 +107,12 @@ const app = Vue.createApp({ [ "x3", "3x larger videos" ], [ "x4", "4x larger videos (not recommended)" ], ], + + // Available cameras and microphones for the Settings modal. + videoDevices: [], + videoDeviceID: null, + audioDevices: [], + audioDeviceID: null, }, // WebRTC sessions with other users. @@ -865,6 +871,17 @@ const app = Vue.createApp({ } return null; }, + nicknameForUsername(username) { + if (!username) return; + username = username.replace(/^@/, ""); + if (this.whoMap[username] != undefined && this.whoMap[username].profileURL) { + let nick = this.whoMap[username].nickname; + if (nick) { + return nick; + } + } + return username; + }, leaveDM() { // Validate we're in a DM currently. if (this.channel.indexOf("@") !== 0) return; @@ -946,12 +963,47 @@ const app = Vue.createApp({ // Tell backend the camera is ready. this.sendMe(); + + // Record the selected device IDs. + this.webcam.videoDeviceID = stream.getVideoTracks()[0].getSettings().deviceId; + this.webcam.audioDeviceID = stream.getAudioTracks()[0].getSettings().deviceId; + console.log("device IDs:", this.webcam.videoDeviceID, this.webcam.audioDeviceID); + + // Collect video and audio devices to let the user change them in their settings. + this.getDevices(); }).catch(err => { this.ChatClient(`Webcam error: ${err}`); }).finally(() => { this.webcam.busy = false; }) }, + getDevices() { + // Collect video and audio devices. + if (!navigator.mediaDevices?.enumerateDevices) { + console.log("enumerateDevices() not supported."); + return; + } + + navigator.mediaDevices.enumerateDevices().then(devices => { + this.webcam.videoDevices = []; + this.webcam.audioDevices = []; + devices.forEach(device => { + if (device.kind === 'videoinput') { + this.webcam.videoDevices.push({ + id: device.deviceId, + label: device.label, + }); + } else if (device.kind === 'audioinput') { + this.webcam.audioDevices.push({ + id: device.deviceId, + label: device.label, + }); + } + }) + }).catch(err => { + this.ChatClient(`Error listing your cameras and microphones: ${err.name}: ${err.message}`); + }) + }, // Begin connecting to someone else's webcam. openVideo(user, force) { diff --git a/web/templates/chat.html b/web/templates/chat.html index 6e6be80..6709f32 100644 --- a/web/templates/chat.html +++ b/web/templates/chat.html @@ -114,8 +114,102 @@ -

Sounds

+ + +

Sounds

+ +
+
+ +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+
+ +
+
+ +
+
+
+ +
+
+ +
+ +
+
+
+ +
+
+
+ +