// Copyright 2021 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback } from 'react'; import type { RefObject } from 'react'; import classNames from 'classnames'; import { noop } from 'lodash'; import { animated, useSpring } from '@react-spring/web'; import type { LocalizerType } from '../../types/Util'; import type { AttachmentType } from '../../types/Attachment'; import type { PushPanelForConversationActionType } from '../../state/ducks/conversations'; import { isDownloaded } from '../../types/Attachment'; import type { DirectionType, MessageStatusType } from './Message'; import type { ComputePeaksResult } from '../VoiceNotesPlaybackContext'; import { MessageMetadata } from './MessageMetadata'; import * as log from '../../logging/log'; import type { ActiveAudioPlayerStateType } from '../../state/ducks/audioPlayer'; import { PlaybackRateButton } from '../PlaybackRateButton'; import { PlaybackButton } from '../PlaybackButton'; import { WaveformScrubber } from './WaveformScrubber'; import { useComputePeaks } from '../../hooks/useComputePeaks'; import { durationToPlaybackText } from '../../util/durationToPlaybackText'; import { shouldNeverBeCalled } from '../../util/shouldNeverBeCalled'; export type OwnProps = Readonly<{ active: | Pick< ActiveAudioPlayerStateType, 'currentTime' | 'duration' | 'playing' | 'playbackRate' > | undefined; buttonRef: RefObject; i18n: LocalizerType; attachment: AttachmentType; collapseMetadata: boolean; withContentAbove: boolean; withContentBelow: boolean; // Message properties. Many are needed for rendering metadata direction: DirectionType; expirationLength?: number; expirationTimestamp?: number; id: string; played: boolean; status?: MessageStatusType; textPending?: boolean; timestamp: number; kickOffAttachmentDownload(): void; onCorrupted(): void; computePeaks(url: string, barCount: number): Promise; onPlayMessage: (id: string, position: number) => void; }>; export type DispatchProps = Readonly<{ pushPanelForConversation: PushPanelForConversationActionType; setPosition: (positionAsRatio: number) => void; setPlaybackRate: (rate: number) => void; setIsPlaying: (value: boolean) => void; }>; export type Props = OwnProps & DispatchProps; enum State { NotDownloaded = 'NotDownloaded', Pending = 'Pending', Computing = 'Computing', Normal = 'Normal', } // Constants const CSS_BASE = 'module-message__audio-attachment'; const BAR_COUNT = 47; const BAR_NOT_DOWNLOADED_HEIGHT = 2; const BAR_MIN_HEIGHT = 4; const BAR_MAX_HEIGHT = 20; const SPRING_CONFIG = { mass: 0.5, tension: 350, friction: 20, velocity: 0.01, }; const DOT_DIV_WIDTH = 14; function PlayedDot({ played, onHide, }: { played: boolean; onHide: () => void; }) { const start = played ? 1 : 0; const end = played ? 0 : 1; const [animProps] = useSpring( { config: SPRING_CONFIG, from: { scale: start, opacity: start, width: start }, to: { scale: end, opacity: end, width: end * DOT_DIV_WIDTH }, onRest: () => { if (played) { onHide(); } }, }, [played] ); return (