mime-chat/src/components/inputVisualizer/visualizerDetails/audioVisualizer.ts

147 lines
4.6 KiB
TypeScript

import { Ref } from '@/types/vueRef/index';
import AnimeFunc from "@/types/animejs/index";
import { inject, ref } from "vue";
interface AudioPath {
identifier: string;
d: string;
offset: number;
color: string;
draw: boolean;
}
export default function useStreamVisualizer(draw: Ref<boolean>) {
const audioContext = new AudioContext();
const analyzer = audioContext.createAnalyser();
analyzer.fftSize = 128;
const bufferLength = analyzer.frequencyBinCount;
const dataArray = new Uint8Array(bufferLength);
// imports animejs safely
let anime: AnimeFunc;
const animeInject: AnimeFunc | undefined = inject("animejs");
if (animeInject) anime = animeInject;
/*
* Draws MediaStream audioTrack as 'AudioPath' objects
*/
function drawStream(paths: Ref<any>, bubbleWidth: Ref<number>): void {
const xInitial = ref(10);
const maxAmplitude = 15;
const maxBubbleWidth = 300;
function shiftPathStream(pathList: NodeList): void {
for (let i = 0; i < pathList.length; i++) {
const stick = pathList[i] as HTMLElement;
const computedQuery = window.getComputedStyle(stick);
const matrix = new WebKitCSSMatrix(computedQuery.webkitTransform);
anime({
targets: stick,
translateX: [matrix.m41, matrix.m41 - 6],
})
}
}
function filterPathList(pathList: NodeList) {
for (let i = 0; i < pathList.length; i++) {
const stick = pathList[i] as HTMLElement;
const computedQuery = window.getComputedStyle(stick);
const matrix = new WebKitCSSMatrix(computedQuery.webkitTransform);
if (matrix.m41 < 10) {
anime({
targets: stick,
opacity: 0,
duration: 0
})
}
}
}
function drawNewPath(): void {
const calculateAmplitude = (): number => (
Math.min(Math.max(
(dataArray.reduce((a, b) => a + b) / bufferLength) * 0.45,
0), maxAmplitude)
)
const createAudioPath = (a: number, o: number): AudioPath => ({
identifier: `path_${o}`,
d: `M0,25v${-a}`,
offset: o,
color: '#000',
draw: true
})
const amplitude = calculateAmplitude();
const p: AudioPath = createAudioPath(amplitude, xInitial.value);
paths.value.push(p);
}
paths.value = [];
function drawPathStream(): void {
let streamPath;
if (!draw.value) {
window.clearTimeout(streamPath)
return;
}
setTimeout(() => {
requestAnimationFrame(drawPathStream);
analyzer.getByteFrequencyData(dataArray);
}, 1000 / 10)
/*
* To Draw pathStream
*/
if (bubbleWidth.value === 300) {
xInitial.value = bubbleWidth.value - 10;
} else if (bubbleWidth.value > 20) {
xInitial.value = bubbleWidth.value;
} else {
xInitial.value += 3;
}
streamPath = setTimeout(async () => {
const pathList = document.querySelectorAll('path');
filterPathList(pathList);
drawNewPath();
if (bubbleWidth.value === maxBubbleWidth) {
shiftPathStream(pathList);
}
}, 200)
}
drawPathStream();
}
function increaseBubbleWidth(bubbleRef: Ref<number>) {
const increase = () => {
let animation;
if (!draw.value) {
if (animation) {
cancelAnimationFrame(animation)
}
return;
}
animation = requestAnimationFrame(increase);
if (bubbleRef.value < 300) {
bubbleRef.value++;
}
}
increase();
}
function expandBubble(bubbleWidth: Ref<number>) {
increaseBubbleWidth(bubbleWidth);
}
function visualizeStreamAsPaths(s: MediaStream, paths: Ref<any>, bubbleWidth: Ref<number>): void {
const source = audioContext.createMediaStreamSource(s);
source.connect(analyzer);
drawStream(paths, bubbleWidth);
}
return {
visualizeStreamAsPaths,
expandBubble
}
}