Voice messages

This commit is contained in:
N-Pex 2021-02-23 22:07:57 +01:00
parent fd86e753fe
commit 3146a0b35a
4 changed files with 303 additions and 99 deletions

View file

@ -137,7 +137,7 @@ $admin-fg: white;
max-height: 30vh;
overflow-x: hidden;
overflow-y: auto;
padding: 0 0 0 0px;
padding: 0 0 0px 20px;
margin: 6px 0;
font-family: 'Inter', sans-serif;
font-weight: 300;
@ -606,10 +606,45 @@ $admin-fg: white;
bottom: 0;
right: 0;
background-color: black;
&.will-cancel {
background-color: grey;
.will-cancel {
background-color: #ff3300;
}
.recording-time {
color: white;
}
.swipe-info {
color: white;
}
.locked {
background-color: black;
}
.error {
background-color: orange;
}
.voice-recorder-lock {
position: relative;
margin-left: 10px;
margin-right: 10px;
width: 20px;
height: 100%;
border: 1px solid black;
border-radius: 10px;
background-color: rgba(white, 0.3);
&.locked {
background-color: rgba(white, 0.8);
}
.thumb {
width: 16px;
height: 16px;
background-color: black;
border-radius: 8px;
position: absolute;
bottom: 1px;
left: 1px;
&.locked {
top: 1px;
bottom: unset;
}
}
}
}

View file

@ -111,6 +111,78 @@
</div>
</v-row>
<v-row class="input-area-inner align-center">
<v-col class="flex-grow-1 flex-shrink-1 ma-0 pa-0">
<v-textarea
height="undefined"
ref="messageInput"
full-width
auto-grow
rows="1"
v-model="currentInput"
no-resize
class="input-area-text"
placeholder="Your message..."
hide-details
background-color="white"
v-on:keydown.enter.prevent="
() => {
sendMessage();
}
"
/>
</v-col>
<v-col
class="input-area-button text-center flex-grow-0 flex-shrink-1"
v-if="editedEvent || replyToEvent"
>
<v-btn
fab
small
elevation="0"
color="black"
@click.stop="cancelEditReply"
>
<v-icon color="white">cancel</v-icon>
</v-btn>
</v-col>
<v-col
class="input-area-button text-center flex-grow-0 flex-shrink-1"
v-if="!currentInput || currentInput.length == 0"
>
<v-btn
ref="mic_button"
fab
small
elevation="0"
color="transparent"
v-blur
style="z-index:10"
@touchstart.native.stop="startRecording"
@mousedown.native.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-else
>
<v-btn
fab
small
elevation="0"
color="black"
@click.stop="sendMessage"
:disabled="sendButtonDisabled"
>
<v-icon color="white">{{
editedEvent ? "save" : "arrow_upward"
}}</v-icon>
</v-btn>
</v-col>
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1">
<label icon flat ref="attachmentLabel">
<v-btn
@ -133,72 +205,8 @@
</label>
</v-col>
<v-col class="flex-grow-1 flex-shrink-1 ma-0 pa-0">
<v-textarea
height="undefined"
ref="messageInput"
full-width
auto-grow
rows="1"
v-model="currentInput"
no-resize
class="input-area-text"
placeholder="Send message"
hide-details
background-color="white"
v-on:keydown.enter.prevent="
() => {
sendMessage();
}
"
/>
</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"
>
<v-btn
fab
small
elevation="0"
color="black"
@click.stop="cancelEditReply"
>
<v-icon color="white">cancel</v-icon>
</v-btn>
</v-col>
<v-col class="input-area-button text-center flex-grow-0 flex-shrink-1">
<v-btn
fab
small
elevation="0"
color="black"
@click.stop="sendMessage"
:disabled="sendButtonDisabled"
>
<v-icon color="white">{{
editedEvent ? "save" : "arrow_upward"
}}</v-icon>
</v-btn>
</v-col>
</v-row>
<VoiceRecorder :show="showRecorder" v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
<VoiceRecorder :micButtonRef="$refs.mic_button" :show="showRecorder" v-on:close="showRecorder = false" v-on:file="onVoiceRecording" />
</v-container>
<div v-if="currentImageInput">

View file

@ -1,18 +1,79 @@
<template>
<div
v-show="show"
:class="{ 'voice-recorder': true, 'will-cancel': willCancel }"
ref="vr_root"
>
<div v-show="show" class="voice-recorder" ref="vrroot">
<v-container fluid fill-height>
<v-row align="center" justify="center">
<v-col class="text-center">
<v-row align="center">
<v-col cols="3">
<div class="recording-time">
{{ recordingTime }}
</div>
</v-col>
<v-col cols="6">
<div class="swipe-info">&lt;&lt; Swipe to cancel</div>
</v-col>
</v-row>
</v-container>
<transition name="fade" mode="out-in">
<div
v-if="willCancel"
class="will-cancel"
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0"
>
<v-container fluid fill-height>
<v-row align="center">
<v-col cols="3">
<v-icon color="white">delete_outline</v-icon>
</v-col>
<v-col cols="6">
<div class="swipe-info">Release to cancel</div>
</v-col>
</v-row>
</v-container>
</div>
</transition>
<transition name="fade" mode="out-in">
<div
v-if="recordingLocked"
class="locked"
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0"
>
<v-container fluid fill-height>
<v-row align="center">
<v-col cols="3">
<div class="recording-time">
{{ recordingTime }}
</div>
</v-col>
<v-col cols="3">
<v-btn @click.stop="cancelRecording" text class="swipe-info"
>Cancel</v-btn
>
</v-col>
<v-col cols="3">
<v-btn @click.stop="stopRecording" icon class="swipe-info"
><v-icon color="white">stop</v-icon></v-btn
>
</v-col>
</v-row>
</v-container>
</div>
</transition>
<div
v-if="state == states.ERROR"
class="error"
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0"
>
<v-container fluid fill-height>
<v-row align="center">
<v-col>
<div class="swipe-info">Failed to record audio</div>
</v-col>
</v-row>
</v-container>
</div>
<VoiceRecorderLock v-show="state == states.RECORDING" :style="lockButtonStyle" :isLocked="recordingLocked" />
</div>
</template>
<script>
@ -23,9 +84,13 @@ const State = {
ERROR: "error",
};
import util from "../plugins/utils";
import VoiceRecorderLock from "./VoiceRecorderLock";
export default {
name: "VoiceRecorder",
components: {
VoiceRecorderLock,
},
props: {
show: {
type: Boolean,
@ -33,31 +98,74 @@ export default {
return false;
},
},
micButtonRef: {
type: Object,
default: function () {
return null;
},
},
},
data() {
return {
willCancel: false,
/** Starting X coordinate of dragging operations */
startCoordinateX: null,
startCoordinateY: null,
states: State,
state: State.INITIAL,
recordStartedAt: null,
recordingTime: null,
recordTimer: null,
recordingLocked: false,
};
},
watch: {
show(val) {
if (val) {
// Add listeners
this.state = State.INITIAL;
document.addEventListener("mouseup", this.mouseUp, false);
document.addEventListener("mousemove", this.mouseMove, false);
document.addEventListener("touchend", this.mouseUp, false);
document.addEventListener("touchmove", this.mouseMove, false);
this.startRecording();
} else {
// Remove listeners
document.removeEventListener("mouseup", this.mouseUp, false);
document.removeEventListener("mousemove", this.mouseMove, false);
document.removeEventListener("touchend", this.mouseUp, false);
document.removeEventListener("touchmove", this.mouseMove, false);
this.startCoordinateX = null;
this.startCoordinateY = null;
this.willCancel = false;
this.startCoordinateX = null;
this.startCoordinateY = null;
this.recordingLocked = false;
}
},
},
computed: {
lockButtonStyle() {
/**
Calculate where to show the lock button (it should be at the same X-coord as the)
mic button (given as a reference!)
*/
var left = 0;
var width = 20;
if (this.micButtonRef) {
var r = this.micButtonRef.$el.getBoundingClientRect();
left = r.left;
width = r.right - r.left;
}
const s =
"position:absolute;top:-50px;left:" +
left +
"px;width:" +
width +
"px;height:40px";
return s;
},
},
methods: {
close() {
this.stopRecordTimer();
@ -66,29 +174,38 @@ export default {
mouseUp(ignoredEvent) {
document.removeEventListener("mouseup", this.mouseUp, false);
document.removeEventListener("mousemove", this.mouseMove, false);
document.body.style.cursor = "";
if (this.willCancel) {
this.cancelRecording();
document.removeEventListener("touchend", this.mouseUp, false);
document.removeEventListener("touchmove", this.mouseMove, false);
//document.body.style.cursor = "";
if (this.state == State.RECORDING) {
if (!this.recordingLocked) {
if (this.willCancel) {
this.cancelRecording();
} else {
this.stopRecording();
}
}
} else {
this.stopRecording();
this.cancelRecording();
}
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";
var x = event.clientX;
var y = event.clientY;
if (event.touches && event.touches.length > 0) {
x = event.touches[0].clientX;
y = event.touches[0].clientY;
}
if (!this.startCoordinateX) {
// First move, set it!
this.startCoordinateX = x;
this.startCoordinateY = y;
}
//document.body.style.cursor = "ns-resize";
this.willCancel = x < this.startCoordinateX - 30;
if (y < this.startCoordinateY - 30 && !this.willCancel) {
this.recordingLocked = true;
}
event.preventDefault();
event.stopPropagation();
},
@ -108,27 +225,34 @@ export default {
})
.catch((e) => {
console.error(e);
//this.state = State.ERROR;
this.state = State.ERROR;
});
},
cancelRecording() {
this.state = State.INITIAL;
this.recorder.stop();
this.stopRecordTimer();
this.close();
},
stopRecording() {
this.state = State.RECORDED;
this.stopRecordTimer();
this.close();
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(),
});
const file = new File(
buffer,
util.formatRecordStartTime(this.recordStartedAt) + ".mp3",
{
type: blob.type,
lastModified: Date.now(),
}
);
//console.log(file);
this.$emit("file", {file: file});
// const player = new Audio(URL.createObjectURL(file));
// player.play();
@ -160,4 +284,13 @@ export default {
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
opacity: 0;
}
</style>

View file

@ -0,0 +1,28 @@
<template>
<div>
<div
:class="{ 'voice-recorder-lock': true, 'locked': isLocked }"
ref="vr_lock"
>
<div :class="{ 'thumb': true, 'locked': isLocked }" />
</div>
</div>
</template>
<script>
export default {
name: "VoiceRecorderLock",
props: {
isLocked: {
type: Boolean,
default: function () {
return false;
},
},
},
};
</script>
<style lang="scss">
@import "@/assets/css/chat.scss";
</style>