Generate base64 waveform in audio message body
This commit is contained in:
parent
254eb72870
commit
1d33657569
3 changed files with 125 additions and 0 deletions
|
|
@ -6,15 +6,22 @@
|
||||||
<div class="play-time">
|
<div class="play-time">
|
||||||
{{ currentTime }} / {{ totalTime }}
|
{{ currentTime }} / {{ totalTime }}
|
||||||
</div>
|
</div>
|
||||||
|
<div style="position:relative;flex: 1 1 100%">
|
||||||
<v-slider @change="seeked" :disabled="!info.url" color="currentColor" track-color="#cccccc" class="play-progress" :value="info.playPercent" min="0"
|
<v-slider @change="seeked" :disabled="!info.url" color="currentColor" track-color="#cccccc" class="play-progress" :value="info.playPercent" min="0"
|
||||||
max="100" />
|
max="100" />
|
||||||
|
<div style="position:absolute;left:18px;right:18px;top:0;bottom:0;height:100%">
|
||||||
|
<AudioWaveformView :event="event" style="width:100%;height:100%" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import util from "../../plugins/utils";
|
import util from "../../plugins/utils";
|
||||||
|
import AudioWaveformView from "./AudioWaveformView.vue";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
components: { AudioWaveformView },
|
||||||
props: {
|
props: {
|
||||||
event: {
|
event: {
|
||||||
type: Object,
|
type: Object,
|
||||||
|
|
|
||||||
79
src/components/messages/AudioWaveformView.vue
Normal file
79
src/components/messages/AudioWaveformView.vue
Normal file
|
|
@ -0,0 +1,79 @@
|
||||||
|
<template>
|
||||||
|
<canvas ref="waveform">
|
||||||
|
</canvas>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// import util from "../../plugins/utils";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
event: {
|
||||||
|
type: Object,
|
||||||
|
default: function () {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
};
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.drawWaveform(this.event);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
drawWaveform(event) {
|
||||||
|
const canvas = this.$refs.waveform;
|
||||||
|
if (canvas) {
|
||||||
|
const dpr = window.devicePixelRatio || 1;
|
||||||
|
canvas.width = canvas.offsetWidth * dpr;
|
||||||
|
canvas.height = (canvas.offsetHeight) * dpr;
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
||||||
|
const base64content = event ? event.getContent().formatted_body : undefined;
|
||||||
|
if (base64content) {
|
||||||
|
const data = atob(base64content).split('').map(function (c) { return c.charCodeAt(0); });
|
||||||
|
|
||||||
|
const drawLineSegment = (ctx, x, y0, data) => {
|
||||||
|
ctx.lineWidth = 1; // how thick the line is
|
||||||
|
ctx.strokeStyle = "#000"; // what color our line is
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x, y0 - data);
|
||||||
|
ctx.lineTo(x, y0 + data);
|
||||||
|
ctx.stroke();
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("W", canvas.width, canvas.height, canvas.offsetWidth, canvas.offsetHeight);
|
||||||
|
|
||||||
|
const width = canvas.width;
|
||||||
|
const height = canvas.height;
|
||||||
|
const samples = data.length;
|
||||||
|
for (let i = 0; i < width; i++) {
|
||||||
|
if (i % 4 == 2 || i % 4 == 3) continue;
|
||||||
|
const iSample = Math.floor(width == 0 ? 0 :(1000 * i) / width);
|
||||||
|
const sample = iSample < samples ? data[iSample] : 0;
|
||||||
|
drawLineSegment(ctx, i, height/2, (sample / 255) * height / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
event: {
|
||||||
|
immediate: false,
|
||||||
|
handler(event) {
|
||||||
|
this.drawWaveform(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
@import "@/assets/css/chat.scss";
|
||||||
|
</style>
|
||||||
|
|
@ -424,6 +424,9 @@ class Util {
|
||||||
promise
|
promise
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
messageContent.url = response.content_uri;
|
messageContent.url = response.content_uri;
|
||||||
|
return (msgtype == 'm.audio' ? this.generateWaveform(fileContents, messageContent) : true);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return this.sendMessage(matrixClient, roomId, "m.room.message", messageContent)
|
return this.sendMessage(matrixClient, roomId, "m.room.message", messageContent)
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
|
|
@ -478,6 +481,9 @@ class Util {
|
||||||
return reject(response.error);
|
return reject(response.error);
|
||||||
}
|
}
|
||||||
encryptedFile.url = response.content_uri;
|
encryptedFile.url = response.content_uri;
|
||||||
|
return (msgtype == 'm.audio' ? this.generateWaveform(fileContents, messageContent) : true);
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
return this.sendMessage(matrixClient, roomId, "m.room.message", messageContent)
|
return this.sendMessage(matrixClient, roomId, "m.room.message", messageContent)
|
||||||
})
|
})
|
||||||
.then(result => {
|
.then(result => {
|
||||||
|
|
@ -495,6 +501,39 @@ class Util {
|
||||||
return uploadPromise;
|
return uploadPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
generateWaveform(data, messageContent) {
|
||||||
|
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||||
|
if (audioCtx) {
|
||||||
|
return audioCtx.decodeAudioData(data)
|
||||||
|
.then((audioBuffer) => {
|
||||||
|
const rawData = audioBuffer.getChannelData(0); // We only need to work with one channel of data
|
||||||
|
const samples = 1000; // Number of samples we want to have in our final data set
|
||||||
|
const blockSize = Math.floor(rawData.length / samples); // the number of samples in each subdivision
|
||||||
|
let filteredData = [];
|
||||||
|
for (let i = 0; i < samples; i++) {
|
||||||
|
let blockStart = blockSize * i; // the location of the first sample in the block
|
||||||
|
let sum = 0;
|
||||||
|
for (let j = 0; j < blockSize; j++) {
|
||||||
|
sum = sum + Math.abs(rawData[blockStart + j]) // find the sum of all the samples in the block
|
||||||
|
}
|
||||||
|
filteredData.push(sum / blockSize); // divide the sum by the block size to get the average
|
||||||
|
}
|
||||||
|
|
||||||
|
// Normalize
|
||||||
|
const multiplier = Math.pow(Math.max(...filteredData), -1);
|
||||||
|
filteredData = filteredData.map(n => n * multiplier);
|
||||||
|
|
||||||
|
// Integerize
|
||||||
|
filteredData = filteredData.map(n => parseInt((n * 255).toFixed()));
|
||||||
|
|
||||||
|
const base64 = Buffer.from(filteredData).toString('base64'); //.replace(/=/g, '')
|
||||||
|
console.log("BASE64", base64);
|
||||||
|
messageContent.format = "org.matrix.custom.html";
|
||||||
|
messageContent.formatted_body = base64;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return what "mode" to use for the given room.
|
* Return what "mode" to use for the given room.
|
||||||
*
|
*
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue