// Copyright 2021-2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only import React, { useRef, useEffect, useState } from 'react'; import classNames from 'classnames'; import { noop } from 'lodash'; import type { LocalizerType } from '../../types/Util'; import type { AttachmentType } from '../../types/Attachment'; import { isDownloaded } from '../../types/Attachment'; import type { DirectionType, MessageStatusType } from './Message'; import type { ComputePeaksResult } from '../GlobalAudioContext'; import { MessageMetadata } from './MessageMetadata'; import * as log from '../../logging/log'; import type { ActiveAudioPlayerStateType } from '../../state/ducks/audioPlayer'; export type OwnProps = Readonly<{ active: ActiveAudioPlayerStateType | undefined; renderingContext: string; 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; conversationId: string; played: boolean; showMessageDetail: (id: string) => void; status?: MessageStatusType; textPending?: boolean; timestamp: number; buttonRef: React.RefObject; kickOffAttachmentDownload(): void; onCorrupted(): void; onFirstPlayed(): void; computePeaks(url: string, barCount: number): Promise; }>; export type DispatchProps = Readonly<{ loadAndPlayMessageAudio: ( id: string, url: string, context: string, position: number, isConsecutive: boolean ) => void; setCurrentTime: (currentTime: number) => void; setPlaybackRate: (conversationId: string, rate: number) => void; setIsPlaying: (value: boolean) => void; }>; export type Props = OwnProps & DispatchProps; type ButtonProps = { i18n: LocalizerType; buttonRef: React.RefObject; mod: string; label: string; onClick: () => void; }; 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 REWIND_BAR_COUNT = 2; // Increments for keyboard audio seek (in seconds) const SMALL_INCREMENT = 1; const BIG_INCREMENT = 5; const PLAYBACK_RATES = [1, 1.5, 2, 0.5]; // Utils const timeToText = (time: number): string => { const hours = Math.floor(time / 3600); let minutes = Math.floor((time % 3600) / 60).toString(); let seconds = Math.floor(time % 60).toString(); if (hours !== 0 && minutes.length < 2) { minutes = `0${minutes}`; } if (seconds.length < 2) { seconds = `0${seconds}`; } return hours ? `${hours}:${minutes}:${seconds}` : `${minutes}:${seconds}`; }; const Button: React.FC = props => { const { i18n, buttonRef, mod, label, onClick } = props; // Clicking button toggle playback const onButtonClick = (event: React.MouseEvent) => { event.stopPropagation(); event.preventDefault(); onClick(); }; // Keyboard playback toggle const onButtonKeyDown = (event: React.KeyboardEvent) => { if (event.key !== 'Enter' && event.key !== 'Space') { return; } event.stopPropagation(); event.preventDefault(); onClick(); }; return ( )} {!withContentBelow && !collapseMetadata && ( )} ); return (
{button} {waveform}
{metadata}
); };