147 lines
4.6 KiB
TypeScript
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
|
|
}
|
|
}
|