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) { 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, bubbleWidth: Ref): 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) { 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) { increaseBubbleWidth(bubbleWidth); } function visualizeStreamAsPaths(s: MediaStream, paths: Ref, bubbleWidth: Ref): void { const source = audioContext.createMediaStreamSource(s); source.connect(analyzer); drawStream(paths, bubbleWidth); } return { visualizeStreamAsPaths, expandBubble } }