Start on voice recording
This commit is contained in:
parent
3aef5b6b3e
commit
fd86e753fe
7 changed files with 335 additions and 2 deletions
108
package-lock.json
generated
108
package-lock.json
generated
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "keanuapp-weblite",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"dependencies": {
|
||||
"aes-js": "^3.1.2",
|
||||
"axios": "^0.21.0",
|
||||
|
|
@ -16,6 +16,7 @@
|
|||
"json-web-key": "^0.4.0",
|
||||
"material-design-icons-iconfont": "^5.0.1",
|
||||
"matrix-js-sdk": "^9.4.1",
|
||||
"mic-recorder-to-mp3": "^2.2.2",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"qrcode": "^1.4.4",
|
||||
"raw-loader": "^4.0.2",
|
||||
|
|
@ -8121,6 +8122,14 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lamejs": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lamejs/-/lamejs-1.2.0.tgz",
|
||||
"integrity": "sha1-Aln4PbRmYUGntnG4yqY2nZUXfQg=",
|
||||
"dependencies": {
|
||||
"use-strict": "1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/launch-editor": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.2.1.tgz",
|
||||
|
|
@ -8490,6 +8499,17 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mic-recorder-to-mp3": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/mic-recorder-to-mp3/-/mic-recorder-to-mp3-2.2.2.tgz",
|
||||
"integrity": "sha512-xDkOaHbojW3bdKOGn9CI5dT+Mc0RrfczsX/Y1zGJp3FUB4zei5ZKFnNm7Nguc9v910wkd7T3csnCTq5EtCF3Zw==",
|
||||
"dependencies": {
|
||||
"lamejs": "^1.2.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"webrtc-adapter": ">=4.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
|
|
@ -11376,6 +11396,19 @@
|
|||
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
|
||||
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
|
||||
},
|
||||
"node_modules/rtcpeerconnection-shim": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
|
||||
"integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"sdp": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/run-async": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
|
|
@ -11508,6 +11541,12 @@
|
|||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/sdp": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
|
||||
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
|
|
@ -13280,6 +13319,11 @@
|
|||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-strict": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/use-strict/-/use-strict-1.0.1.tgz",
|
||||
"integrity": "sha1-C7gNlPSaSgUZK4Sox9NOlfGn46A="
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||
|
|
@ -14604,6 +14648,20 @@
|
|||
"webpack": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/webrtc-adapter": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.0.tgz",
|
||||
"integrity": "sha512-7Bp9OBnx642oJRkom1tNAbeJjUadAq2rh5xLL9YXPw5hVyt2h4hHr5bcoPYDs1stp/mZHSPSQA34YISdnr0DBQ==",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"rtcpeerconnection-shim": "^1.2.15",
|
||||
"sdp": "^2.12.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0",
|
||||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket-driver": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
|
||||
|
|
@ -21529,6 +21587,14 @@
|
|||
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
|
||||
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw=="
|
||||
},
|
||||
"lamejs": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lamejs/-/lamejs-1.2.0.tgz",
|
||||
"integrity": "sha1-Aln4PbRmYUGntnG4yqY2nZUXfQg=",
|
||||
"requires": {
|
||||
"use-strict": "1.0.1"
|
||||
}
|
||||
},
|
||||
"launch-editor": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/launch-editor/-/launch-editor-2.2.1.tgz",
|
||||
|
|
@ -21842,6 +21908,14 @@
|
|||
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
|
||||
"dev": true
|
||||
},
|
||||
"mic-recorder-to-mp3": {
|
||||
"version": "2.2.2",
|
||||
"resolved": "https://registry.npmjs.org/mic-recorder-to-mp3/-/mic-recorder-to-mp3-2.2.2.tgz",
|
||||
"integrity": "sha512-xDkOaHbojW3bdKOGn9CI5dT+Mc0RrfczsX/Y1zGJp3FUB4zei5ZKFnNm7Nguc9v910wkd7T3csnCTq5EtCF3Zw==",
|
||||
"requires": {
|
||||
"lamejs": "^1.2.0"
|
||||
}
|
||||
},
|
||||
"micromatch": {
|
||||
"version": "3.1.10",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
|
||||
|
|
@ -24192,6 +24266,15 @@
|
|||
"resolved": "https://registry.npmjs.org/roboto-fontface/-/roboto-fontface-0.10.0.tgz",
|
||||
"integrity": "sha512-OlwfYEgA2RdboZohpldlvJ1xngOins5d7ejqnIBWr9KaMxsnBqotpptRXTyfNRLnFpqzX6sTDt+X+a+6udnU8g=="
|
||||
},
|
||||
"rtcpeerconnection-shim": {
|
||||
"version": "1.2.15",
|
||||
"resolved": "https://registry.npmjs.org/rtcpeerconnection-shim/-/rtcpeerconnection-shim-1.2.15.tgz",
|
||||
"integrity": "sha512-C6DxhXt7bssQ1nHb154lqeL0SXz5Dx4RczXZu2Aa/L1NJFnEVDxFwCBo3fqtuljhHIGceg5JKBV4XJ0gW5JKyw==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"sdp": "^2.6.0"
|
||||
}
|
||||
},
|
||||
"run-async": {
|
||||
"version": "2.4.1",
|
||||
"resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz",
|
||||
|
|
@ -24280,6 +24363,12 @@
|
|||
"ajv-keywords": "^3.5.2"
|
||||
}
|
||||
},
|
||||
"sdp": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/sdp/-/sdp-2.12.0.tgz",
|
||||
"integrity": "sha512-jhXqQAQVM+8Xj5EjJGVweuEzgtGWb3tmEEpl3CLP3cStInSbVHSg0QWOGQzNq8pSID4JkpeV2mPqlMDLrm0/Vw==",
|
||||
"peer": true
|
||||
},
|
||||
"select": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz",
|
||||
|
|
@ -25742,6 +25831,11 @@
|
|||
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
|
||||
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
|
||||
},
|
||||
"use-strict": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/use-strict/-/use-strict-1.0.1.tgz",
|
||||
"integrity": "sha1-C7gNlPSaSgUZK4Sox9NOlfGn46A="
|
||||
},
|
||||
"util": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
|
||||
|
|
@ -26785,6 +26879,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"webrtc-adapter": {
|
||||
"version": "7.7.0",
|
||||
"resolved": "https://registry.npmjs.org/webrtc-adapter/-/webrtc-adapter-7.7.0.tgz",
|
||||
"integrity": "sha512-7Bp9OBnx642oJRkom1tNAbeJjUadAq2rh5xLL9YXPw5hVyt2h4hHr5bcoPYDs1stp/mZHSPSQA34YISdnr0DBQ==",
|
||||
"peer": true,
|
||||
"requires": {
|
||||
"rtcpeerconnection-shim": "^1.2.15",
|
||||
"sdp": "^2.12.0"
|
||||
}
|
||||
},
|
||||
"websocket-driver": {
|
||||
"version": "0.7.4",
|
||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
"json-web-key": "^0.4.0",
|
||||
"material-design-icons-iconfont": "^5.0.1",
|
||||
"matrix-js-sdk": "^9.4.1",
|
||||
"mic-recorder-to-mp3": "^2.2.2",
|
||||
"olm": "https://packages.matrix.org/npm/olm/olm-3.2.1.tgz",
|
||||
"qrcode": "^1.4.4",
|
||||
"raw-loader": "^4.0.2",
|
||||
|
|
|
|||
|
|
@ -597,4 +597,19 @@ $admin-fg: white;
|
|||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.voice-recorder {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
background-color: black;
|
||||
&.will-cancel {
|
||||
background-color: grey;
|
||||
}
|
||||
.recording-time {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +154,21 @@
|
|||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
>
|
||||
<v-btn
|
||||
fab
|
||||
small
|
||||
elevation="0"
|
||||
color="transparent"
|
||||
v-blur
|
||||
style="z-index:10"
|
||||
@mousedown.stop="startRecording"
|
||||
>
|
||||
<v-icon :color="showRecorder ? 'white' : 'black'">mic</v-icon>
|
||||
</v-btn>
|
||||
</v-col>
|
||||
<v-col
|
||||
class="input-area-button text-center flex-grow-0 flex-shrink-1"
|
||||
v-if="editedEvent || replyToEvent"
|
||||
|
|
@ -183,6 +198,7 @@
|
|||
</v-btn>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<VoiceRecorder :show="showRecorder" v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
|
||||
</v-container>
|
||||
|
||||
<div v-if="currentImageInput">
|
||||
|
|
@ -271,6 +287,7 @@ import DebugEvent from "./messages/DebugEvent.vue";
|
|||
import util from "../plugins/utils";
|
||||
import MessageOperations from "./messages/MessageOperations.vue";
|
||||
import ChatHeader from "./ChatHeader";
|
||||
import VoiceRecorder from "./VoiceRecorder";
|
||||
|
||||
const READ_RECEIPT_TIMEOUT = 5000; /* How long a message must have been visible before the read marker is updated */
|
||||
|
||||
|
|
@ -320,6 +337,7 @@ export default {
|
|||
RoomAvatarChanged,
|
||||
DebugEvent,
|
||||
MessageOperations,
|
||||
VoiceRecorder
|
||||
},
|
||||
|
||||
data() {
|
||||
|
|
@ -341,6 +359,7 @@ export default {
|
|||
showContextMenu: false,
|
||||
showContextMenuAnchor: null,
|
||||
initialLoadDone: false,
|
||||
showRecorder: false,
|
||||
|
||||
/**
|
||||
* Current chat container size. We need to keep track of this so that if and when
|
||||
|
|
@ -1061,6 +1080,19 @@ export default {
|
|||
dayForEvent(event) {
|
||||
return util.formatDay(event.getTs());
|
||||
},
|
||||
|
||||
startRecording() {
|
||||
this.showRecorder = true;
|
||||
},
|
||||
|
||||
onVoiceRecording(event) {
|
||||
util.sendImage(
|
||||
this.$matrix.matrixClient,
|
||||
this.roomId,
|
||||
event.file,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
|
|
|||
163
src/components/VoiceRecorder.vue
Normal file
163
src/components/VoiceRecorder.vue
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<template>
|
||||
<div
|
||||
v-show="show"
|
||||
:class="{ 'voice-recorder': true, 'will-cancel': willCancel }"
|
||||
ref="vr_root"
|
||||
>
|
||||
<v-container fluid fill-height>
|
||||
<v-row align="center" justify="center">
|
||||
<v-col class="text-center">
|
||||
<div class="recording-time">
|
||||
{{ recordingTime }}
|
||||
</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
const State = {
|
||||
INITIAL: "intial",
|
||||
RECORDING: "recording",
|
||||
RECORDED: "recorded",
|
||||
ERROR: "error",
|
||||
};
|
||||
import util from "../plugins/utils";
|
||||
|
||||
export default {
|
||||
name: "VoiceRecorder",
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: function () {
|
||||
return false;
|
||||
},
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
willCancel: false,
|
||||
states: State,
|
||||
state: State.INITIAL,
|
||||
recordStartedAt: null,
|
||||
recordingTime: null,
|
||||
recordTimer: null,
|
||||
};
|
||||
},
|
||||
watch: {
|
||||
show(val) {
|
||||
if (val) {
|
||||
// Add listeners
|
||||
document.addEventListener("mouseup", this.mouseUp, false);
|
||||
document.addEventListener("mousemove", this.mouseMove, false);
|
||||
this.startRecording();
|
||||
} else {
|
||||
// Remove listeners
|
||||
document.removeEventListener("mouseup", this.mouseUp, false);
|
||||
document.removeEventListener("mousemove", this.mouseMove, false);
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.stopRecordTimer();
|
||||
this.$emit("close");
|
||||
},
|
||||
mouseUp(ignoredEvent) {
|
||||
document.removeEventListener("mouseup", this.mouseUp, false);
|
||||
document.removeEventListener("mousemove", this.mouseMove, false);
|
||||
document.body.style.cursor = "";
|
||||
if (this.willCancel) {
|
||||
this.cancelRecording();
|
||||
} else {
|
||||
this.stopRecording();
|
||||
}
|
||||
this.close();
|
||||
},
|
||||
mouseMove(event) {
|
||||
document.body.style.cursor = "ns-resize";
|
||||
let rect = this.$refs.vr_root.getBoundingClientRect();
|
||||
this.willCancel = event.clientY < rect.top;
|
||||
console.log(
|
||||
"Cancel: " + this.willCancel + " " + event.clientY + " " + rect.top
|
||||
);
|
||||
|
||||
// let bottom = rect.bottom;
|
||||
// let mouseBottom = event.clientY;
|
||||
// let newHeight = Math.max(
|
||||
// 200,
|
||||
// Math.min(0.7 * window.innerHeight, bottom - mouseBottom)
|
||||
// );
|
||||
// this.$refs.chatPane.style.height = newHeight + "px";
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
},
|
||||
|
||||
startRecording() {
|
||||
const MicRecorder = require("mic-recorder-to-mp3");
|
||||
// Start recording. Browser will request permission to use your microphone.
|
||||
this.recorder = new MicRecorder({
|
||||
bitRate: 128,
|
||||
});
|
||||
this.recorder
|
||||
.start()
|
||||
.then(() => {
|
||||
this.state = State.RECORDING;
|
||||
this.recordStartedAt = Date.now();
|
||||
this.startRecordTimer();
|
||||
})
|
||||
.catch((e) => {
|
||||
console.error(e);
|
||||
//this.state = State.ERROR;
|
||||
});
|
||||
},
|
||||
cancelRecording() {
|
||||
this.state = State.INITIAL;
|
||||
this.recorder.stop();
|
||||
this.stopRecordTimer();
|
||||
},
|
||||
stopRecording() {
|
||||
this.state = State.RECORDED;
|
||||
this.stopRecordTimer();
|
||||
this.recorder
|
||||
.stop()
|
||||
.getMp3()
|
||||
.then(([buffer, blob]) => {
|
||||
// do what ever you want with buffer and blob
|
||||
// Example: Create a mp3 file and play
|
||||
const file = new File(buffer, util.formatRecordStartTime(this.recordStartedAt) + ".mp3", {
|
||||
type: blob.type,
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
this.$emit("file", {file: file});
|
||||
// const player = new Audio(URL.createObjectURL(file));
|
||||
// player.play();
|
||||
})
|
||||
.catch((e) => {
|
||||
alert("We could not retrieve your message");
|
||||
console.log(e);
|
||||
});
|
||||
},
|
||||
startRecordTimer() {
|
||||
this.stopRecordTimer();
|
||||
this.recordTimer = setInterval(() => {
|
||||
const now = Date.now();
|
||||
this.recordingTime = util.formatRecordDuration(
|
||||
now - this.recordStartedAt
|
||||
);
|
||||
}, 500);
|
||||
},
|
||||
stopRecordTimer() {
|
||||
if (this.recordTimer) {
|
||||
clearInterval(this.recordTimer);
|
||||
this.recordTimer = null;
|
||||
this.recordingTime = null;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
</style>
|
||||
|
|
@ -32,6 +32,13 @@ Vue.use((Vue) => {
|
|||
};
|
||||
});
|
||||
|
||||
// Register a global custom directive called `v-blur` that prevents focus
|
||||
Vue.directive('blur', {
|
||||
inserted: function (el) {
|
||||
el.onfocus = (ev) => ev.target.blur()
|
||||
}
|
||||
});
|
||||
|
||||
Vue.use(navigation, router);
|
||||
|
||||
new Vue({
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ var base64Url = require('json-web-key/lib/base64url');
|
|||
// Install extended localized format
|
||||
var localizedFormat = require('dayjs/plugin/localizedFormat')
|
||||
dayjs.extend(localizedFormat)
|
||||
var duration = require('dayjs/plugin/duration')
|
||||
dayjs.extend(duration);
|
||||
|
||||
class Util {
|
||||
getAttachment(matrixClient, event) {
|
||||
|
|
@ -443,6 +445,15 @@ class Util {
|
|||
return then.format('L');
|
||||
}
|
||||
}
|
||||
|
||||
formatRecordDuration(ms) {
|
||||
return dayjs.duration(ms).format("HH:mm:ss");
|
||||
}
|
||||
|
||||
formatRecordStartTime(timestamp) {
|
||||
var then = dayjs(timestamp);
|
||||
return then.format('lll');
|
||||
}
|
||||
}
|
||||
export default new Util();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue