diff --git a/package-lock.json b/package-lock.json
index 0342882..ac506f4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,7 @@
"version": "1.9.0",
"license": "MIT",
"dependencies": {
+ "click-outside-vue3": "^4.0.1",
"electron-prompt": "^1.7.0",
"mitt": "^3.0.1",
"vue-router": "^4.4.2"
@@ -1939,6 +1940,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/click-outside-vue3": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/click-outside-vue3/-/click-outside-vue3-4.0.1.tgz",
+ "integrity": "sha512-sbplNecrup5oGqA3o4bo8XmvHRT6q9fvw21Z67aDbTqB9M6LF7CuYLTlLvNtOgKU6W3zst5H5zJuEh4auqA34g==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
diff --git a/package.json b/package.json
index 0208b22..b75cb20 100644
--- a/package.json
+++ b/package.json
@@ -11,8 +11,8 @@
"dist": "npm run build && python setup.py build && electron-builder --publish=never"
},
"license": "MIT",
- "engines" : {
- "node" : ">=18"
+ "engines": {
+ "node": ">=18"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.1.2",
@@ -94,6 +94,7 @@
}
},
"dependencies": {
+ "click-outside-vue3": "^4.0.1",
"electron-prompt": "^1.7.0",
"mitt": "^3.0.1",
"vue-router": "^4.4.2"
diff --git a/src/frontend/components/messages/AddAudioButton.vue b/src/frontend/components/messages/AddAudioButton.vue
new file mode 100644
index 0000000..940345e
--- /dev/null
+++ b/src/frontend/components/messages/AddAudioButton.vue
@@ -0,0 +1,76 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/frontend/components/messages/ConversationViewer.vue b/src/frontend/components/messages/ConversationViewer.vue
index babf28c..2c93efe 100644
--- a/src/frontend/components/messages/ConversationViewer.vue
+++ b/src/frontend/components/messages/ConversationViewer.vue
@@ -310,20 +310,12 @@
-
-
+
+ Recording: {{ audioAttachmentRecordingDuration }}
+
@@ -363,9 +355,13 @@ import Utils from "../../js/Utils";
import DialogUtils from "../../js/DialogUtils";
import NotificationUtils from "../../js/NotificationUtils";
import WebSocketConnection from "../../js/WebSocketConnection";
+import AddAudioButton from "./AddAudioButton.vue";
export default {
name: 'ConversationViewer',
+ components: {
+ AddAudioButton,
+ },
props: {
myLxmfAddressHash: String,
selectedPeer: Object,
@@ -1018,7 +1014,7 @@ export default {
clearImageInput: function() {
this.$refs["image-input"].value = null;
},
- async startRecordingAudioAttachment() {
+ async startRecordingAudioAttachment(args) {
// do nothing if already recording
if(this.isRecordingAudioAttachment){
@@ -1030,22 +1026,36 @@ export default {
return;
}
- // start recording microphone
- this.audioAttachmentMicrophoneRecorder = new Codec2MicrophoneRecorder();
- this.audioAttachmentRecordingStartedAt = Date.now();
- this.isRecordingAudioAttachment = await this.audioAttachmentMicrophoneRecorder.start();
+ // handle selected codec
+ switch(args.codec){
+ case "codec2": {
- // update recording time in ui every second
- this.audioAttachmentRecordingDuration = Utils.formatMinutesSeconds(0);
- this.audioAttachmentRecordingTimer = setInterval(() => {
- const recordingDurationMillis = Date.now() - this.audioAttachmentRecordingStartedAt;
- const recordingDurationSeconds = recordingDurationMillis / 1000;
- this.audioAttachmentRecordingDuration = Utils.formatMinutesSeconds(recordingDurationSeconds);
- }, 1000);
+ // start recording microphone
+ this.audioAttachmentMicrophoneRecorder = new Codec2MicrophoneRecorder();
+ this.audioAttachmentMicrophoneRecorder.codec2Mode = args.mode;
+ this.audioAttachmentRecordingStartedAt = Date.now();
+ this.isRecordingAudioAttachment = await this.audioAttachmentMicrophoneRecorder.start();
- // alert if failed to start recording
- if(!this.isRecordingAudioAttachment){
- DialogUtils.alert("failed to start recording");
+ // update recording time in ui every second
+ this.audioAttachmentRecordingDuration = Utils.formatMinutesSeconds(0);
+ this.audioAttachmentRecordingTimer = setInterval(() => {
+ const recordingDurationMillis = Date.now() - this.audioAttachmentRecordingStartedAt;
+ const recordingDurationSeconds = recordingDurationMillis / 1000;
+ this.audioAttachmentRecordingDuration = Utils.formatMinutesSeconds(recordingDurationSeconds);
+ }, 1000);
+
+ // alert if failed to start recording
+ if(!this.isRecordingAudioAttachment){
+ DialogUtils.alert("failed to start recording");
+ }
+
+ break;
+
+ }
+ default: {
+ DialogUtils.alert(`Unhandled microphone recorder codec: ${args.codec}`);
+ break;
+ }
}
},
@@ -1069,7 +1079,8 @@ export default {
}
// decode codec2 audio back to wav so we can show a preview audio player before user sends it
- const decoded = await Codec2Lib.runDecode("1200", new Uint8Array(audio));
+ const codec2Mode = this.audioAttachmentMicrophoneRecorder.codec2Mode;
+ const decoded = await Codec2Lib.runDecode(codec2Mode, new Uint8Array(audio));
// convert decoded codec2 to wav audio and create a blob
const wavAudio = await Codec2Lib.rawToWav(decoded);
@@ -1077,9 +1088,26 @@ export default {
type: "audio/wav",
});
+ // determine audio mode
+ var audioMode = null;
+ switch(codec2Mode){
+ case "1200": {
+ audioMode = 0x04; // LXMF.AM_CODEC2_1200
+ break;
+ }
+ case "3200": {
+ audioMode = 0x09; // LXMF.AM_CODEC2_3200
+ break;
+ }
+ default: {
+ DialogUtils.alert(`Unhandled microphone recorder codec2Mode: ${codec2Mode}`);
+ return;
+ }
+ }
+
// update message audio attachment
this.newMessageAudio = {
- audio_mode: 0x04, // hardcoded to LXMF.AM_CODEC2_1200 for now
+ audio_mode: audioMode,
audio_blob: new Blob([audio]),
audio_wav_url: URL.createObjectURL(wavBlob),
};
diff --git a/src/frontend/main.js b/src/frontend/main.js
index 5979d09..6c2e067 100644
--- a/src/frontend/main.js
+++ b/src/frontend/main.js
@@ -1,5 +1,6 @@
import { createApp } from 'vue';
import { createRouter, createWebHashHistory } from 'vue-router';
+import vClickOutside from "click-outside-vue3";
import App from './components/App.vue';
import AboutPage from "./components/about/AboutPage.vue";
@@ -27,4 +28,5 @@ const router = createRouter({
createApp(App)
.use(router)
+ .use(vClickOutside)
.mount('#app');