Merge branch 'audio-waveforms' into 'dev'
Audio waveforms See merge request keanuapp/keanuapp-weblite!321
This commit is contained in:
commit
10af030fce
3 changed files with 103 additions and 0 deletions
|
|
@ -6,15 +6,22 @@
|
|||
<div class="play-time">
|
||||
{{ currentTime }} / {{ totalTime }}
|
||||
</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"
|
||||
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>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import util from "../../plugins/utils";
|
||||
import AudioWaveformView from "./AudioWaveformView.vue";
|
||||
|
||||
export default {
|
||||
components: { AudioWaveformView },
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
|
|
|
|||
45
src/components/messages/AudioWaveformView.vue
Normal file
45
src/components/messages/AudioWaveformView.vue
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<template>
|
||||
<div v-html="content" class="waveform">
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
props: {
|
||||
event: {
|
||||
type: Object,
|
||||
default: function () {
|
||||
return null;
|
||||
},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
content() {
|
||||
return this.$sanitize(
|
||||
this.event ? this.event.getContent().formatted_body : "",
|
||||
{
|
||||
allowedTags: ["svg", "path"],
|
||||
allowedAttributes: {
|
||||
'svg': ["viewbox", "fill", "preserveaspectratio", "xmlns"],
|
||||
'path': ["d", "style"]
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import "@/assets/css/chat.scss";
|
||||
|
||||
.waveform svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
path {
|
||||
stroke: black !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -424,6 +424,9 @@ class Util {
|
|||
promise
|
||||
.then((response) => {
|
||||
messageContent.url = response.content_uri;
|
||||
return (msgtype == 'm.audio' ? this.generateWaveform(fileContents, messageContent) : true);
|
||||
})
|
||||
.then(() => {
|
||||
return this.sendMessage(matrixClient, roomId, "m.room.message", messageContent)
|
||||
})
|
||||
.then(result => {
|
||||
|
|
@ -478,6 +481,9 @@ class Util {
|
|||
return reject(response.error);
|
||||
}
|
||||
encryptedFile.url = response.content_uri;
|
||||
return (msgtype == 'm.audio' ? this.generateWaveform(fileContents, messageContent) : true);
|
||||
})
|
||||
.then(() => {
|
||||
return this.sendMessage(matrixClient, roomId, "m.room.message", messageContent)
|
||||
})
|
||||
.then(result => {
|
||||
|
|
@ -495,6 +501,51 @@ class Util {
|
|||
return uploadPromise;
|
||||
}
|
||||
|
||||
generateWaveform(data, messageContent) {
|
||||
if (!(window.AudioContext || window.webkitAudioContext)) {
|
||||
return; // No support
|
||||
}
|
||||
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
if (audioCtx) {
|
||||
return audioCtx.decodeAudioData(data)
|
||||
.then((audioBuffer) => {
|
||||
const rawData = audioBuffer.getChannelData(0); // TODO - currently using only 1 channel
|
||||
const samples = 1000; // Number of samples
|
||||
const blockSize = Math.floor(rawData.length / samples);
|
||||
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()));
|
||||
|
||||
|
||||
// Generate SVG of waveform
|
||||
let svg = `<svg viewBox="0 0 ${samples} 255" fill="none" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg">`;
|
||||
svg += `<path d="`;
|
||||
filteredData.forEach((d, i) => {
|
||||
const delta = d / 2;
|
||||
svg += `M${i} ${128 - delta}V${128 + delta}`;
|
||||
});
|
||||
svg += `" style="fill:none;stroke:green;stroke-width:1" />`;
|
||||
svg += "</svg>";
|
||||
|
||||
messageContent.format = "org.matrix.custom.html";
|
||||
messageContent.formatted_body = svg;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return what "mode" to use for the given room.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue