import React, { FC, useCallback, useEffect, useMemo, } from '../../lib/teact/teact'; import { getActions, withGlobal } from '../../global'; import { AudioOrigin } from '../../types'; import { ApiAudio, ApiChat, ApiMessage, ApiUser, } from '../../api/types'; import { IS_IOS, IS_SINGLE_COLUMN_LAYOUT, IS_TOUCH_ENV } from '../../util/environment'; import * as mediaLoader from '../../util/mediaLoader'; import { getMediaDuration, getMessageContent, getMessageMediaHash, getSenderTitle, isMessageLocal, } from '../../global/helpers'; import { selectChat, selectSender } from '../../global/selectors'; import buildClassName from '../../util/buildClassName'; import { makeTrackId } from '../../util/audioPlayer'; import { clearMediaSession } from '../../util/mediaSession'; import windowSize from '../../util/windowSize'; import useAudioPlayer from '../../hooks/useAudioPlayer'; import useLang from '../../hooks/useLang'; import useMessageMediaMetadata from '../../hooks/useMessageMediaMetadata'; import renderText from '../common/helpers/renderText'; import RippleEffect from '../ui/RippleEffect'; import Button from '../ui/Button'; import RangeSlider from '../ui/RangeSlider'; import './AudioPlayer.scss'; type OwnProps = { message: ApiMessage; origin?: AudioOrigin; className?: string; noUi?: boolean; }; type StateProps = { sender?: ApiChat | ApiUser; chat?: ApiChat; volume: number; playbackRate: number; isMuted: boolean; }; const FAST_PLAYBACK_RATE = 1.8; const AudioPlayer: FC = ({ message, className, noUi, sender, chat, volume, playbackRate, isMuted, }) => { const { setAudioPlayerVolume, setAudioPlayerPlaybackRate, setAudioPlayerMuted, focusMessage, closeAudioPlayer, } = getActions(); const lang = useLang(); const { audio, voice, video } = getMessageContent(message); const isVoice = Boolean(voice || video); const senderName = sender ? getSenderTitle(lang, sender) : undefined; const mediaData = mediaLoader.getFromMemory(getMessageMediaHash(message, 'inline')!) as (string | undefined); const mediaMetadata = useMessageMediaMetadata(message, sender, chat); const { playPause, stop, isPlaying, requestNextTrack, requestPreviousTrack, isFirst, isLast, setVolume, toggleMuted, setPlaybackRate, } = useAudioPlayer( makeTrackId(message), getMediaDuration(message)!, isVoice ? 'voice' : 'audio', mediaData, undefined, mediaMetadata, undefined, true, undefined, undefined, isMessageLocal(message), true, ); // Prevent refresh by accidentally rotating device when listening to a voice message const isVoicePlaying = isVoice && isPlaying; useEffect(() => { if (!isVoicePlaying) { return undefined; } windowSize.disableRefresh(); return () => { windowSize.enableRefresh(); }; }, [isVoicePlaying]); const handleClick = useCallback(() => { focusMessage({ chatId: message.chatId, messageId: message.id }); }, [focusMessage, message.chatId, message.id]); const handleClose = useCallback(() => { if (isPlaying) { playPause(); } closeAudioPlayer(); clearMediaSession(); stop(); }, [closeAudioPlayer, isPlaying, playPause, stop]); const handleVolumeChange = useCallback((value: number) => { setAudioPlayerVolume({ volume: value / 100 }); setVolume(value / 100); }, [setAudioPlayerVolume, setVolume]); const handleVolumeClick = useCallback(() => { if (IS_TOUCH_ENV && !IS_IOS) return; toggleMuted(); setAudioPlayerMuted({ isMuted: !isMuted }); }, [isMuted, setAudioPlayerMuted, toggleMuted]); const handlePlaybackClick = useCallback(() => { if (playbackRate === 1) { setPlaybackRate(FAST_PLAYBACK_RATE); setAudioPlayerPlaybackRate({ playbackRate: FAST_PLAYBACK_RATE }); } else { setPlaybackRate(1); setAudioPlayerPlaybackRate({ playbackRate: 1 }); } }, [playbackRate, setAudioPlayerPlaybackRate, setPlaybackRate]); const volumeIcon = useMemo(() => { if (volume === 0 || isMuted) return 'icon-muted'; if (volume < 0.3) return 'icon-volume-1'; if (volume < 0.6) return 'icon-volume-2'; return 'icon-volume-3'; }, [volume, isMuted]); if (noUi) { return undefined; } return (
{audio ? renderAudio(audio) : renderVoice(lang('AttachAudio'), senderName)}
{isVoice && ( )}
); }; function renderAudio(audio: ApiAudio) { const { title, performer, fileName } = audio; return ( <>
{renderText(title || fileName)}
{performer && (
{renderText(performer)}
)} ); } function renderVoice(subtitle: string, senderName?: string) { return ( <>
{senderName && renderText(senderName)}
{subtitle}
); } export default withGlobal( (global, { message }): StateProps => { const sender = selectSender(global, message); const chat = selectChat(global, message.chatId); const { volume, playbackRate, isMuted } = global.audioPlayer; return { sender, chat, volume, playbackRate, isMuted, }; }, )(AudioPlayer);