1- import React , { RefObject , useEffect , useMemo , useState } from 'react' ;
1+ import React , { RefObject , useEffect , useMemo } from 'react' ;
22import { I18nManager , Pressable , StyleSheet , Text , View } from 'react-native' ;
33
44import dayjs from 'dayjs' ;
55import duration from 'dayjs/plugin/duration' ;
66
7- import { AudioAttachment as StreamAudioAttachment } from 'stream-chat' ;
7+ import {
8+ isVoiceRecordingAttachment ,
9+ AudioAttachment as StreamAudioAttachment ,
10+ VoiceRecordingAttachment as StreamVoiceRecordingAttachment ,
11+ } from 'stream-chat' ;
812
913import { useTheme } from '../../contexts' ;
10- import { useAudioPlayer } from '../../hooks/useAudioPlayer' ;
14+ import { useStateStore } from '../../hooks' ;
15+ import { useAudioPlayerControl } from '../../hooks/useAudioPlayerControl' ;
1116import { Audio , Pause , Play } from '../../icons' ;
1217import {
1318 NativeHandlers ,
@@ -17,125 +22,100 @@ import {
1722 VideoProgressData ,
1823 VideoSeekResponse ,
1924} from '../../native' ;
20- import { AudioConfig , FileTypes } from '../../types/types ' ;
25+ import { AudioPlayerState } from '../../state-store/audio-player ' ;
2126import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle' ;
2227import { ProgressControl } from '../ProgressControl/ProgressControl' ;
2328import { WaveProgressBar } from '../ProgressControl/WaveProgressBar' ;
2429
2530dayjs . extend ( duration ) ;
2631
27- export type AudioAttachmentType = AudioConfig &
28- Pick < StreamAudioAttachment , 'waveform_data' | 'asset_url' | 'title' > & {
29- id : string ;
30- type : 'audio' | 'voiceRecording' ;
31- } ;
32+ export type AudioAttachmentType = StreamAudioAttachment | StreamVoiceRecordingAttachment ;
3233
3334export type AudioAttachmentProps = {
3435 item : AudioAttachmentType ;
35- onLoad : ( index : string , duration : number ) => void ;
36- onPlayPause : ( index : string , pausedStatus ?: boolean ) => void ;
37- onProgress : ( index : string , progress : number ) => void ;
3836 titleMaxLength ?: number ;
3937 hideProgressBar ?: boolean ;
4038 showSpeedSettings ?: boolean ;
4139 testID ?: string ;
4240} ;
4341
42+ const audioPlayerSelector = ( state : AudioPlayerState ) => ( {
43+ currentPlaybackRate : state . currentPlaybackRate ,
44+ duration : state . duration ,
45+ isPlaying : state . isPlaying ,
46+ position : state . position ,
47+ progress : state . progress ,
48+ } ) ;
49+
4450/**
4551 * AudioAttachment
4652 * UI Component to preview the audio files
4753 */
4854export const AudioAttachment = ( props : AudioAttachmentProps ) => {
49- const [ currentSpeed , setCurrentSpeed ] = useState < number > ( 1.0 ) ;
50- const [ audioFinished , setAudioFinished ] = useState ( false ) ;
5155 const soundRef = React . useRef < SoundReturnType | null > ( null ) ;
56+
5257 const {
5358 hideProgressBar = false ,
5459 item,
55- onLoad,
56- onPlayPause,
57- onProgress,
5860 showSpeedSettings = false ,
5961 testID,
6062 titleMaxLength,
6163 } = props ;
62- const { changeAudioSpeed, pauseAudio, playAudio, seekAudio } = useAudioPlayer ( { soundRef } ) ;
64+
65+ const { audioPlayer, toggleAudio, playAudio, pauseAudio } = useAudioPlayerControl ( {
66+ duration : item . duration ?? 0 ,
67+ mimeType : item . mime_type ?? '' ,
68+ playerRef : soundRef ,
69+ uri : item . asset_url ?? '' ,
70+ } ) ;
71+ const { duration, isPlaying, position, progress, currentPlaybackRate } = useStateStore (
72+ audioPlayer . state ,
73+ audioPlayerSelector ,
74+ ) ;
6375 const isExpoCLI = NativeHandlers . SDK === 'stream-chat-expo' ;
64- const isVoiceRecording = item . type === FileTypes . VoiceRecording ;
76+ const isVoiceRecording = isVoiceRecordingAttachment ( item ) ;
6577
6678 /** This is for Native CLI Apps */
6779 const handleLoad = ( payload : VideoPayloadData ) => {
6880 // The duration given by the rn-video is not same as the one of the voice recording, so we take the actual duration for voice recording.
6981 if ( isVoiceRecording && item . duration ) {
70- onLoad ( item . id , item . duration ) ;
82+ audioPlayer . duration = item . duration ;
7183 } else {
72- onLoad ( item . id , item . duration || payload . duration ) ;
84+ audioPlayer . duration = item . duration || payload . duration ;
7385 }
7486 } ;
7587
7688 /** This is for Native CLI Apps */
7789 const handleProgress = ( data : VideoProgressData ) => {
78- const { currentTime, seekableDuration } = data ;
79- // The duration given by the rn-video is not same as the one of the voice recording, so we take the actual duration for voice recording.
80- if ( isVoiceRecording && item . duration ) {
81- if ( currentTime < item . duration && ! audioFinished ) {
82- onProgress ( item . id , currentTime / item . duration ) ;
83- } else {
84- setAudioFinished ( true ) ;
85- }
86- } else {
87- if ( currentTime < seekableDuration && ! audioFinished ) {
88- onProgress ( item . id , currentTime / seekableDuration ) ;
89- } else {
90- setAudioFinished ( true ) ;
91- }
92- }
90+ const { currentTime } = data ;
91+ audioPlayer . position = currentTime ;
9392 } ;
9493
9594 /** This is for Native CLI Apps */
9695 const onSeek = ( seekResponse : VideoSeekResponse ) => {
97- setAudioFinished ( false ) ;
98- onProgress ( item . id , seekResponse . currentTime / ( item . duration as number ) ) ;
96+ audioPlayer . position = seekResponse . currentTime ;
9997 } ;
10098
101- const handlePlayPause = async ( ) => {
102- if ( item . paused ) {
103- if ( isExpoCLI ) {
104- await playAudio ( ) ;
105- }
106- onPlayPause ( item . id , false ) ;
107- } else {
108- if ( isExpoCLI ) {
109- await pauseAudio ( ) ;
110- }
111- onPlayPause ( item . id , true ) ;
112- }
99+ const handlePlayPause = ( ) => {
100+ toggleAudio ( audioPlayer . id ) ;
113101 } ;
114102
115103 const handleEnd = async ( ) => {
116- setAudioFinished ( false ) ;
117- await pauseAudio ( ) ;
118- onPlayPause ( item . id , true ) ;
119- await seekAudio ( 0 ) ;
104+ await audioPlayer . stop ( ) ;
120105 } ;
121106
122- const dragStart = async ( ) => {
123- if ( isExpoCLI ) {
124- await pauseAudio ( ) ;
125- }
126- onPlayPause ( item . id , true ) ;
107+ const dragStart = ( ) => {
108+ pauseAudio ( audioPlayer . id ) ;
127109 } ;
128110
129111 const dragProgress = ( progress : number ) => {
130- onProgress ( item . id , progress ) ;
112+ audioPlayer . progress = progress ;
131113 } ;
132114
133115 const dragEnd = async ( progress : number ) => {
134- await seekAudio ( progress * ( item . duration as number ) ) ;
135- if ( isExpoCLI ) {
136- await playAudio ( ) ;
137- }
138- onPlayPause ( item . id , false ) ;
116+ const position = isExpoCLI ? ( progress * duration ) / 1000 : progress * duration ;
117+ await audioPlayer . seek ( position ) ;
118+ playAudio ( audioPlayer . id ) ;
139119 } ;
140120
141121 /** For Expo CLI */
@@ -147,26 +127,28 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
147127 }
148128 } else {
149129 const { durationMillis, positionMillis } = playbackStatus ;
130+ // Update your UI for the loaded state
150131 // This is done for Expo CLI where we don't get file duration from file picker
151132 if ( item . duration === 0 ) {
152- onLoad ( item . id , durationMillis / 1000 ) ;
133+ audioPlayer . duration = durationMillis ;
153134 } else {
154135 // The duration given by the expo-av is not same as the one of the voice recording, so we take the actual duration for voice recording.
155136 if ( isVoiceRecording && item . duration ) {
156- onLoad ( item . id , item . duration ) ;
137+ audioPlayer . duration = item . duration * 1000 ;
157138 } else {
158- onLoad ( item . id , durationMillis / 1000 ) ;
139+ audioPlayer . duration = durationMillis ;
159140 }
160141 }
161- // Update your UI for the loaded state
142+
143+ // Update the position of the audio player when it is playing
162144 if ( playbackStatus . isPlaying ) {
163145 if ( isVoiceRecording && item . duration ) {
164146 if ( positionMillis <= item . duration * 1000 ) {
165- onProgress ( item . id , positionMillis / ( item . duration * 1000 ) ) ;
147+ audioPlayer . position = positionMillis ;
166148 }
167149 } else {
168150 if ( positionMillis <= durationMillis ) {
169- onProgress ( item . id , positionMillis / durationMillis ) ;
151+ audioPlayer . position = positionMillis ;
170152 }
171153 }
172154 } else {
@@ -177,10 +159,8 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
177159 // Update your UI for the buffering state
178160 }
179161
162+ // Update the UI when the audio is finished playing
180163 if ( playbackStatus . didJustFinish && ! playbackStatus . isLooping ) {
181- onProgress ( item . id , 1 ) ;
182- // The player has just finished playing and will stop. Maybe you want to play something else?
183- // status: opposite of pause,says i am playing
184164 handleEnd ( ) ;
185165 }
186166 }
@@ -214,41 +194,8 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
214194 // eslint-disable-next-line react-hooks/exhaustive-deps
215195 } , [ ] ) ;
216196
217- // This is needed for expo applications where the rerender doesn't occur on time thefore you need to update the state of the sound.
218- useEffect ( ( ) => {
219- const initalPlayPause = async ( ) => {
220- if ( ! isExpoCLI ) {
221- return ;
222- }
223- try {
224- if ( item . paused ) {
225- await pauseAudio ( ) ;
226- } else {
227- await playAudio ( ) ;
228- }
229- } catch ( e ) {
230- console . log ( 'An error has occurred while trying to interact with the audio. ' , e ) ;
231- }
232- } ;
233- // For expo CLI
234- if ( ! NativeHandlers . Sound ?. Player ) {
235- initalPlayPause ( ) ;
236- }
237- } , [ item . paused , isExpoCLI , pauseAudio , playAudio ] ) ;
238-
239197 const onSpeedChangeHandler = async ( ) => {
240- if ( currentSpeed === 2.0 ) {
241- setCurrentSpeed ( 1.0 ) ;
242- await changeAudioSpeed ( 1.0 ) ;
243- } else {
244- if ( currentSpeed === 1.0 ) {
245- setCurrentSpeed ( 1.5 ) ;
246- await changeAudioSpeed ( 1.5 ) ;
247- } else if ( currentSpeed === 1.5 ) {
248- setCurrentSpeed ( 2.0 ) ;
249- await changeAudioSpeed ( 2.0 ) ;
250- }
251- }
198+ await audioPlayer . changePlaybackRate ( ) ;
252199 } ;
253200
254201 const {
@@ -270,19 +217,15 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
270217 } ,
271218 } = useTheme ( ) ;
272219
273- const progressValueInSeconds = useMemo (
274- ( ) => ( item . duration as number ) * ( item . progress as number ) ,
275- [ item . duration , item . progress ] ,
276- ) ;
277-
220+ const positionInSeconds = isExpoCLI ? position / 1000 : position ;
278221 const progressDuration = useMemo (
279222 ( ) =>
280- progressValueInSeconds
281- ? progressValueInSeconds / 3600 >= 1
282- ? dayjs . duration ( progressValueInSeconds , 'second' ) . format ( 'HH:mm:ss' )
283- : dayjs . duration ( progressValueInSeconds , 'second' ) . format ( 'mm:ss' )
284- : dayjs . duration ( item . duration ?? 0 , 'second' ) . format ( 'mm:ss' ) ,
285- [ progressValueInSeconds , item . duration ] ,
223+ positionInSeconds
224+ ? positionInSeconds / 3600 >= 1
225+ ? dayjs . duration ( positionInSeconds , 'second' ) . format ( 'HH:mm:ss' )
226+ : dayjs . duration ( positionInSeconds , 'second' ) . format ( 'mm:ss' )
227+ : dayjs . duration ( isExpoCLI ? duration / 1000 : duration , 'second' ) . format ( 'mm:ss' ) ,
228+ [ duration , isExpoCLI , positionInSeconds ] ,
286229 ) ;
287230
288231 return (
@@ -308,7 +251,7 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
308251 playPauseButton ,
309252 ] }
310253 >
311- { item . paused ? (
254+ { ! isPlaying ? (
312255 < Play fill = { static_black } height = { 32 } width = { 32 } />
313256 ) : (
314257 < Pause fill = { static_black } height = { 32 } width = { 32 } />
@@ -328,7 +271,7 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
328271 filenameText ,
329272 ] }
330273 >
331- { item . type === FileTypes . VoiceRecording
274+ { isVoiceRecordingAttachment ( item )
332275 ? 'Recording'
333276 : getTrimmedAttachmentTitle ( item . title , titleMaxLength ) }
334277 </ Text >
@@ -344,17 +287,17 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
344287 onEndDrag = { dragEnd }
345288 onProgressDrag = { dragProgress }
346289 onStartDrag = { dragStart }
347- progress = { item . progress as number }
290+ progress = { progress }
348291 waveformData = { item . waveform_data }
349292 />
350293 ) : (
351294 < ProgressControl
352- duration = { item . duration as number }
295+ duration = { duration }
353296 filledColor = { accent_blue }
354297 onEndDrag = { dragEnd }
355298 onProgressDrag = { dragProgress }
356299 onStartDrag = { dragStart }
357- progress = { item . progress as number }
300+ progress = { progress }
358301 testID = 'progress-control'
359302 />
360303 ) }
@@ -367,8 +310,8 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
367310 onLoad = { handleLoad }
368311 onProgress = { handleProgress }
369312 onSeek = { onSeek }
370- paused = { item . paused }
371- rate = { currentSpeed }
313+ paused = { ! isPlaying }
314+ rate = { currentPlaybackRate }
372315 soundRef = { soundRef as RefObject < SoundReturnType > }
373316 testID = 'sound-player'
374317 uri = { item . asset_url }
@@ -377,7 +320,7 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
377320 </ View >
378321 { showSpeedSettings ? (
379322 < View style = { [ styles . rightContainer , rightContainer ] } >
380- { item . paused ? (
323+ { ! isPlaying ? (
381324 < Audio fill = { '#ffffff' } />
382325 ) : (
383326 < Pressable
@@ -390,7 +333,7 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
390333 >
391334 < Text
392335 style = { [ styles . speedChangeButtonText , speedChangeButtonText ] }
393- > { `x${ currentSpeed . toFixed ( 1 ) } ` } </ Text >
336+ > { `x${ currentPlaybackRate . toFixed ( 1 ) } ` } </ Text >
394337 </ Pressable >
395338 ) }
396339 </ View >
0 commit comments