allow selecting audio mode when recording voice message
This commit is contained in:
9
package-lock.json
generated
9
package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
76
src/frontend/components/messages/AddAudioButton.vue
Normal file
76
src/frontend/components/messages/AddAudioButton.vue
Normal 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>
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
@@ -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');
|
||||
|
||||
Reference in New Issue
Block a user