/* * Wire * Copyright (C) 2021 Wire Swiss GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * */ import React, {useEffect, useRef, useState} from 'react'; import cx from 'classnames'; import {interpolate} from 'Util/ArrayUtil'; import {clamp} from 'Util/NumberUtil'; import {createUuid} from 'Util/uuid'; import {FileAsset} from '../../../../../../entity/message/FileAsset'; export interface AudioSeekBarProps { asset: FileAsset; audioElement: HTMLAudioElement; disabled: boolean; } const AudioSeekBar: React.FC = ({asset, audioElement, disabled}) => { const [svgWidth, setSvgWidth] = useState(0); const [path, setPath] = useState(''); const [loudness, setLoudness] = useState([]); const [position, setPosition] = useState(0); const svgNode = useRef(null); const [clipId] = useState(`clip-${createUuid()}`); useEffect(() => { window.addEventListener('resize', updateSvgWidth); return () => window.removeEventListener('resize', updateSvgWidth); }, []); useEffect(() => { const loudness = asset.meta?.loudness; if (loudness) { setLoudness(Array.from(loudness).map(level => level / 256)); } }, [asset]); useEffect(() => { audioElement?.addEventListener('ended', onAudioEnded); audioElement?.addEventListener('timeupdate', onTimeUpdate); return () => { audioElement?.removeEventListener('ended', onAudioEnded); audioElement?.removeEventListener('timeupdate', onTimeUpdate); }; }, [audioElement]); useEffect(() => updateSvgWidth(), [svgNode.current]); useEffect(() => { if (!svgWidth) { return setPath(''); } const numberOfLevelsFitOnScreen = Math.floor(svgWidth / 3); const singleWidth = 1 / numberOfLevelsFitOnScreen; const barWidth = (singleWidth / 3) * 2; const scaledLoudness = interpolate(loudness, numberOfLevelsFitOnScreen); const newPath = scaledLoudness .map((loudness, index) => { const x = index * singleWidth; const y = 0.5 - loudness / 2; return `M${x},${y}h${barWidth}V${1 - y}H${x}z`; }) .join(''); setPath(newPath); }, [svgWidth]); const updateSvgWidth = () => setSvgWidth(svgNode.current?.clientWidth ?? 0); const onLevelClick = (event: React.MouseEvent) => { if (!svgNode.current) { return; } const mouse_x = (event.pageX ?? event.clientX) - svgNode.current.getBoundingClientRect().left; const calculatedTime = (audioElement.duration * mouse_x) / svgNode.current.clientWidth; const currentTime = isNaN(calculatedTime) ? 0 : calculatedTime; audioElement.currentTime = clamp(currentTime, 0, audioElement.duration); onTimeUpdate(); }; const onAudioEnded = () => setPosition(0); const onTimeUpdate = () => { if (audioElement.duration) { setPosition(audioElement.currentTime / audioElement.duration); } }; return ( ); }; export {AudioSeekBar};