@@ -7,95 +7,125 @@ import {
77 PlayButton ,
88 WaveProgressBar ,
99} from './components' ;
10- import { useAudioController } from './hooks/useAudioController' ;
1110import { displayDuration } from './utils' ;
1211import { FileIcon } from '../ReactFileUtilities' ;
13- import { useTranslationContext } from '../../context' ;
12+ import { useMessageContext , useTranslationContext } from '../../context' ;
13+ import { type AudioPlayerState , useAudioPlayer } from '../AudioPlayback/' ;
14+ import { useStateStore } from '../../store' ;
15+ import type { AudioPlayer } from '../AudioPlayback/AudioPlayer' ;
1416
1517const rootClassName = 'str-chat__message-attachment__voice-recording-widget' ;
1618
17- export type VoiceRecordingPlayerProps = Pick < VoiceRecordingProps , 'attachment' > & {
18- /** An array of fractional numeric values of playback speed to override the defaults (1.0, 1.5, 2.0) */
19- playbackRates ?: number [ ] ;
20- } ;
21-
22- export const VoiceRecordingPlayer = ( {
23- attachment,
24- playbackRates,
25- } : VoiceRecordingPlayerProps ) => {
26- const { t } = useTranslationContext ( 'VoiceRecordingPlayer' ) ;
27- const {
28- asset_url,
29- duration = 0 ,
30- mime_type,
31- title = t ( 'Voice message' ) ,
32- waveform_data,
33- } = attachment ;
19+ const audioPlayerStateSelector = ( state : AudioPlayerState ) => ( {
20+ canPlayRecord : state . canPlayRecord ,
21+ isPlaying : state . isPlaying ,
22+ playbackRate : state . currentPlaybackRate ,
23+ progress : state . progressPercent ,
24+ secondsElapsed : state . secondsElapsed ,
25+ } ) ;
3426
35- const {
36- audioRef,
37- increasePlaybackRate,
38- isPlaying,
39- playbackRate,
40- progress,
41- secondsElapsed,
42- seek,
43- togglePlay,
44- } = useAudioController ( {
45- durationSeconds : duration ?? 0 ,
46- mimeType : mime_type ,
47- playbackRates,
48- } ) ;
27+ type VoiceRecordingPlayerUIProps = {
28+ audioPlayer : AudioPlayer ;
29+ } ;
4930
50- if ( ! asset_url ) return null ;
31+ // todo: finish creating a BaseAudioPlayer derived from VoiceRecordingPlayerUI and AudioAttachmentUI
32+ const VoiceRecordingPlayerUI = ( { audioPlayer } : VoiceRecordingPlayerUIProps ) => {
33+ const { canPlayRecord, isPlaying, playbackRate, progress, secondsElapsed } =
34+ useStateStore ( audioPlayer ?. state , audioPlayerStateSelector ) ?? { } ;
5135
52- const displayedDuration = secondsElapsed || duration ;
36+ const displayedDuration = secondsElapsed || audioPlayer . durationSeconds ;
5337
5438 return (
5539 < div className = { rootClassName } data-testid = 'voice-recording-widget' >
56- < audio ref = { audioRef } >
57- < source data-testid = 'audio-source' src = { asset_url } type = { mime_type } />
58- </ audio >
59- < PlayButton isPlaying = { isPlaying } onClick = { togglePlay } />
40+ < PlayButton isPlaying = { ! ! isPlaying } onClick = { audioPlayer . togglePlay } />
6041 < div className = 'str-chat__message-attachment__voice-recording-widget__metadata' >
6142 < div
6243 className = 'str-chat__message-attachment__voice-recording-widget__title'
6344 data-testid = 'voice-recording-title'
64- title = { title }
45+ title = { audioPlayer . title }
6546 >
66- { title }
47+ { audioPlayer . title }
6748 </ div >
6849 < div className = 'str-chat__message-attachment__voice-recording-widget__audio-state' >
6950 < div className = 'str-chat__message-attachment__voice-recording-widget__timer' >
70- { attachment . duration ? (
51+ { audioPlayer . durationSeconds ? (
7152 displayDuration ( displayedDuration )
7253 ) : (
7354 < FileSizeIndicator
74- fileSize = { attachment . file_size }
55+ fileSize = { audioPlayer . fileSize }
7556 maximumFractionDigits = { 0 }
7657 />
7758 ) }
7859 </ div >
7960 < WaveProgressBar
8061 progress = { progress }
81- seek = { seek }
82- waveformData = { waveform_data || [ ] }
62+ seek = { audioPlayer . seek }
63+ waveformData = { audioPlayer . waveformData || [ ] }
8364 />
8465 </ div >
8566 </ div >
8667 < div className = 'str-chat__message-attachment__voice-recording-widget__right-section' >
8768 { isPlaying ? (
88- < PlaybackRateButton disabled = { ! audioRef . current } onClick = { increasePlaybackRate } >
89- { playbackRate . toFixed ( 1 ) } x
69+ < PlaybackRateButton
70+ disabled = { ! canPlayRecord }
71+ onClick = { audioPlayer . increasePlaybackRate }
72+ >
73+ { playbackRate ?. toFixed ( 1 ) } x
9074 </ PlaybackRateButton >
9175 ) : (
92- < FileIcon big = { true } mimeType = { mime_type } size = { 40 } />
76+ < FileIcon big = { true } mimeType = { audioPlayer . mimeType } size = { 40 } />
9377 ) }
9478 </ div >
9579 </ div >
9680 ) ;
9781} ;
9882
83+ export type VoiceRecordingPlayerProps = Pick < VoiceRecordingProps , 'attachment' > & {
84+ /** An array of fractional numeric values of playback speed to override the defaults (1.0, 1.5, 2.0) */
85+ playbackRates ?: number [ ] ;
86+ } ;
87+
88+ export const VoiceRecordingPlayer = ( {
89+ attachment,
90+ playbackRates,
91+ } : VoiceRecordingPlayerProps ) => {
92+ const { t } = useTranslationContext ( ) ;
93+ const {
94+ asset_url,
95+ duration = 0 ,
96+ file_size,
97+ mime_type,
98+ title = t ( 'Voice message' ) ,
99+ waveform_data,
100+ } = attachment ;
101+
102+ /**
103+ * Introducing message context. This could be breaking change, therefore the fallback to {} is provided.
104+ * If this component is used outside the message context, then there will be no audio player namespacing
105+ * => scrolling away from the message in virtualized ML would create a new AudioPlayer instance.
106+ *
107+ * Edge case: the requester (message) has multiple attachments with the same assetURL - does not happen
108+ * with the default SDK components, but can be done with custom API calls.In this case all the Audio
109+ * widgets will share the state.
110+ */
111+ const { message, threadList } = useMessageContext ( ) ?? { } ;
112+
113+ const audioPlayer = useAudioPlayer ( {
114+ durationSeconds : duration ?? 0 ,
115+ fileSize : file_size ,
116+ mimeType : mime_type ,
117+ playbackRates,
118+ requester :
119+ message ?. id &&
120+ `${ threadList ? ( message . parent_id ?? message . id ) : '' } ${ message . id } ` ,
121+ src : asset_url ,
122+ title,
123+ waveformData : waveform_data ,
124+ } ) ;
125+
126+ return audioPlayer ? < VoiceRecordingPlayerUI audioPlayer = { audioPlayer } /> : null ;
127+ } ;
128+
99129export type QuotedVoiceRecordingProps = Pick < VoiceRecordingProps , 'attachment' > ;
100130
101131export const QuotedVoiceRecording = ( { attachment } : QuotedVoiceRecordingProps ) => {
0 commit comments