diff --git a/package-lock.json b/package-lock.json index 4d6a398..0736b51 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "0.0.0", "dependencies": { "floating-vue": "^2.0.0-beta.24", + "hark": "^1.2.3", "interactjs": "^1.10.18", "qrcodejs": "github:danielgjackson/qrcodejs", "vue": "^3.3.4", @@ -1260,6 +1261,14 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/hark": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/hark/-/hark-1.2.3.tgz", + "integrity": "sha512-u68vz9SCa38ESiFJSDjqK8XbXqWzyot7Cj6Y2b6jk2NJ+II3MY2dIrLMg/kjtIAun4Y1DHF/20hfx4rq1G5GMg==", + "dependencies": { + "wildemitter": "^1.2.0" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2056,6 +2065,11 @@ "node": ">= 8" } }, + "node_modules/wildemitter": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/wildemitter/-/wildemitter-1.2.1.tgz", + "integrity": "sha512-UMmSUoIQSir+XbBpTxOTS53uJ8s/lVhADCkEbhfRjUGFDPme/XGOb0sBWLx5sTz7Wx/2+TlAw1eK9O5lw5PiEw==" + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 9d1e26a..2021015 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "floating-vue": "^2.0.0-beta.24", + "hark": "^1.2.3", "interactjs": "^1.10.18", "qrcodejs": "github:danielgjackson/qrcodejs", "vue": "^3.3.4", diff --git a/public/static/css/chat.css b/public/static/css/chat.css index 450dfba..8d6af2d 100644 --- a/public/static/css/chat.css +++ b/public/static/css/chat.css @@ -241,11 +241,17 @@ img { width: 168px; height: 112px; background-color: black; + border: 1px solid black; margin: 3px; overflow: hidden; resize: both; } +/* A speaking webcam */ +.feed.is-speaking { + border: 1px solid #09F; +} + /* A popped-out video feed window */ div.feed.popped-out { position: absolute; diff --git a/src/App.vue b/src/App.vue index 9c25b56..61c3421 100644 --- a/src/App.vue +++ b/src/App.vue @@ -4,6 +4,7 @@ import FloatingVue from 'floating-vue'; import 'floating-vue/dist/style.css'; import { Mentionable } from 'vue-mention'; import EmojiPicker from 'vue3-emoji-picker'; +import hark from 'hark'; import AlertModal from './components/AlertModal.vue'; import LoginModal from './components/LoginModal.vue'; @@ -244,6 +245,7 @@ export default { muted: {}, // muted bool per username booted: {}, // booted bool per username poppedOut: {}, // popped-out video per username + speaking: {}, // speaking boolean per username // RTCPeerConnections per username. pc: {}, @@ -1760,6 +1762,9 @@ export default { this.WebRTC.muted[username] = true; $ref.muted = true; } + + // Set up the speech detector. + this.initSpeakingEvents(username, $ref); }); // Inform them they are being watched. @@ -1789,7 +1794,7 @@ export default { this.WebRTC.frozenStreamInterval[username] = setInterval(() => { if (videoTrack.muted) freezeDetected(); }, 3000); - }) + }); }; // ANSWERER: add our video to the connection so that the offerer (the one who @@ -2256,6 +2261,9 @@ export default { // Begin dark video detection. this.initDarkVideoDetection(); + + // Begin monitoring for speaking events. + this.initSpeakingEvents(this.username, this.webcam.elem); }).catch(err => { this.ChatClient(`Webcam error: ${err}

Please see the troubleshooting guide for help.`); }).finally(() => { @@ -2943,6 +2951,27 @@ export default { }) }, + // Webcam "is speaking" functions. + initSpeakingEvents(username, element) { + // element is the