allow selecting audio mode when recording voice message

This commit is contained in:
liamcottle
2024-08-10 22:39:34 +12:00
parent b5dcac88b8
commit 42ca60474d
5 changed files with 149 additions and 33 deletions

9
package-lock.json generated
View File

@@ -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",

View File

@@ -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"

View File

@@ -0,0 +1,76 @@
<template>
<div class="inline-flex rounded-md shadow-sm">
<button v-if="isRecordingAudioAttachment" @click="stopRecordingAudioAttachment" type="button" class="my-auto mr-1 inline-flex items-center gap-x-1 rounded-md bg-red-500 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-red-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path d="M7 4a3 3 0 0 1 6 0v6a3 3 0 1 1-6 0V4Z" />
<path d="M5.5 9.643a.75.75 0 0 0-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-1.5v-1.546A6.001 6.001 0 0 0 16 10v-.357a.75.75 0 0 0-1.5 0V10a4.5 4.5 0 0 1-9 0v-.357Z" />
</svg>
<span class="ml-1">
<slot/>
</span>
</button>
<button v-else @click="showMenu" type="button" class="my-auto mr-1 inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path d="M7 4a3 3 0 0 1 6 0v6a3 3 0 1 1-6 0V4Z" />
<path d="M5.5 9.643a.75.75 0 0 0-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-1.5v-1.546A6.001 6.001 0 0 0 16 10v-.357a.75.75 0 0 0-1.5 0V10a4.5 4.5 0 0 1-9 0v-.357Z" />
</svg>
<span class="ml-1 hidden sm:inline-block">Add Voice</span>
</button>
<div class="relative block">
<Transition
enter-active-class="transition ease-out duration-100"
enter-from-class="transform opacity-0 scale-95"
enter-to-class="transform opacity-100 scale-100"
leave-active-class="transition ease-in duration-75"
leave-from-class="transform opacity-100 scale-100"
leave-to-class="transform opacity-0 scale-95">
<div v-if="isShowingMenu" v-click-outside="hideMenu" class="absolute bottom-0 -ml-11 sm:right-0 sm:ml-0 z-10 mb-10 w-56 rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
<div class="py-1">
<button @click="startRecordingCodec2('1200')" type="button" class="w-full block text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Codec2 (Low Quality)</button>
<button @click="startRecordingCodec2('3200')" type="button" class="w-full block text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Codec2 (Medium Quality)</button>
</div>
</div>
</Transition>
</div>
</div>
</template>
<script>
export default {
name: 'AddAudioButton',
props: {
isRecordingAudioAttachment: Boolean,
},
data() {
return {
isShowingMenu: false,
};
},
methods: {
showMenu() {
this.isShowingMenu = true;
},
hideMenu() {
this.isShowingMenu = false;
},
startRecordingAudioAttachment(args) {
this.isShowingMenu = false;
this.$emit("start-recording", args);
},
startRecordingCodec2(mode) {
this.startRecordingAudioAttachment({
codec: "codec2",
mode: mode,
});
},
stopRecordingAudioAttachment() {
this.isShowingMenu = false;
this.$emit("stop-recording");
},
},
}
</script>

View File

@@ -310,20 +310,12 @@
<!-- add audio -->
<div>
<button v-if="isRecordingAudioAttachment" @click="stopRecordingAudioAttachment" type="button" class="my-auto mr-1 inline-flex items-center gap-x-1 rounded-md bg-red-500 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-red-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-red-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path d="M7 4a3 3 0 0 1 6 0v6a3 3 0 1 1-6 0V4Z" />
<path d="M5.5 9.643a.75.75 0 0 0-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-1.5v-1.546A6.001 6.001 0 0 0 16 10v-.357a.75.75 0 0 0-1.5 0V10a4.5 4.5 0 0 1-9 0v-.357Z" />
</svg>
<span class="ml-1">Recording: {{ audioAttachmentRecordingDuration }}</span>
</button>
<button v-else @click="startRecordingAudioAttachment" type="button" class="my-auto mr-1 inline-flex items-center gap-x-1 rounded-md bg-gray-500 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-gray-400 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-gray-500">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" class="size-5">
<path d="M7 4a3 3 0 0 1 6 0v6a3 3 0 1 1-6 0V4Z" />
<path d="M5.5 9.643a.75.75 0 0 0-1.5 0V10c0 3.06 2.29 5.585 5.25 5.954V17.5h-1.5a.75.75 0 0 0 0 1.5h4.5a.75.75 0 0 0 0-1.5h-1.5v-1.546A6.001 6.001 0 0 0 16 10v-.357a.75.75 0 0 0-1.5 0V10a4.5 4.5 0 0 1-9 0v-.357Z" />
</svg>
<span class="ml-1 hidden sm:inline-block">Add Voice</span>
</button>
<AddAudioButton
:is-recording-audio-attachment="isRecordingAudioAttachment"
@start-recording="startRecordingAudioAttachment($event)"
@stop-recording="stopRecordingAudioAttachment">
<span>Recording: {{ audioAttachmentRecordingDuration }}</span>
</AddAudioButton>
</div>
<!-- send message -->
@@ -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,8 +1026,13 @@ export default {
return;
}
// handle selected codec
switch(args.codec){
case "codec2": {
// start recording microphone
this.audioAttachmentMicrophoneRecorder = new Codec2MicrophoneRecorder();
this.audioAttachmentMicrophoneRecorder.codec2Mode = args.mode;
this.audioAttachmentRecordingStartedAt = Date.now();
this.isRecordingAudioAttachment = await this.audioAttachmentMicrophoneRecorder.start();
@@ -1048,6 +1049,15 @@ export default {
DialogUtils.alert("failed to start recording");
}
break;
}
default: {
DialogUtils.alert(`Unhandled microphone recorder codec: ${args.codec}`);
break;
}
}
},
async stopRecordingAudioAttachment() {
@@ -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),
};

View File

@@ -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');