Skip to content

Commit 1f97dc4

Browse files
committed
fix: audio progress and waveform progress bar
1 parent 22b4213 commit 1f97dc4

File tree

6 files changed

+133
-92
lines changed

6 files changed

+133
-92
lines changed

package/src/components/Attachment/AudioAttachment.tsx

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import duration from 'dayjs/plugin/duration';
66

77
import {
88
isVoiceRecordingAttachment,
9+
LocalMessage,
910
AudioAttachment as StreamAudioAttachment,
1011
VoiceRecordingAttachment as StreamVoiceRecordingAttachment,
1112
} from 'stream-chat';
@@ -26,16 +27,21 @@ import { getTrimmedAttachmentTitle } from '../../utils/getTrimmedAttachmentTitle
2627
import { ProgressControl } from '../ProgressControl/ProgressControl';
2728
import { WaveProgressBar } from '../ProgressControl/WaveProgressBar';
2829

30+
const ONE_HOUR_IN_MILLISECONDS = 3600 * 1000;
31+
const ONE_SECOND_IN_MILLISECONDS = 1000;
32+
2933
dayjs.extend(duration);
3034

3135
export type AudioAttachmentType = StreamAudioAttachment | StreamVoiceRecordingAttachment;
3236

3337
export type AudioAttachmentProps = {
3438
item: AudioAttachmentType;
39+
message?: LocalMessage;
3540
titleMaxLength?: number;
3641
hideProgressBar?: boolean;
3742
showSpeedSettings?: boolean;
3843
testID?: string;
44+
isPreview?: boolean;
3945
};
4046

4147
const audioPlayerSelector = (state: AudioPlayerState) => ({
@@ -56,15 +62,20 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
5662
const {
5763
hideProgressBar = false,
5864
item,
65+
message,
5966
showSpeedSettings = false,
6067
testID,
6168
titleMaxLength,
69+
isPreview = false,
6270
} = props;
6371
const isVoiceRecording = isVoiceRecordingAttachment(item);
6472

6573
const { audioPlayer, toggleAudio, playAudio, pauseAudio } = useAudioPlayerControl({
6674
duration: item.duration ?? 0,
6775
mimeType: item.mime_type ?? '',
76+
requester: isPreview
77+
? 'preview'
78+
: message?.id && `${message?.parent_id ?? message?.id}${message?.id}`,
6879
type: isVoiceRecording ? 'voiceRecording' : 'audio',
6980
uri: item.asset_url ?? '',
7081
});
@@ -80,21 +91,34 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
8091
}
8192
}, [audioPlayer]);
8293

94+
// When a audio attachment in preview is removed, we need to remove the player from the pool
95+
useEffect(
96+
() => () => {
97+
if (isPreview) {
98+
audioPlayer.onRemove();
99+
}
100+
},
101+
[audioPlayer, isPreview],
102+
);
103+
83104
/** This is for Native CLI Apps */
84105
const handleLoad = (payload: VideoPayloadData) => {
85-
// 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.
86-
audioPlayer.duration = payload.duration * 1000;
106+
// If the attachment is a voice recording, we rely on the duration from the attachment as the one from the react-native-video is incorrect.
107+
if (isVoiceRecording) {
108+
return;
109+
}
110+
audioPlayer.duration = payload.duration * ONE_SECOND_IN_MILLISECONDS;
87111
};
88112

89113
/** This is for Native CLI Apps */
90114
const handleProgress = (data: VideoProgressData) => {
91115
const { currentTime } = data;
92-
audioPlayer.position = currentTime * 1000;
116+
audioPlayer.position = currentTime * ONE_SECOND_IN_MILLISECONDS;
93117
};
94118

95119
/** This is for Native CLI Apps */
96120
const onSeek = (seekResponse: VideoSeekResponse) => {
97-
audioPlayer.position = seekResponse.currentTime * 1000;
121+
audioPlayer.position = seekResponse.currentTime * ONE_SECOND_IN_MILLISECONDS;
98122
};
99123

100124
const handlePlayPause = () => {
@@ -109,13 +133,13 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
109133
pauseAudio(audioPlayer.id);
110134
};
111135

112-
const dragProgress = (progress: number) => {
113-
audioPlayer.progress = progress;
136+
const dragProgress = (currentProgress: number) => {
137+
audioPlayer.progress = currentProgress;
114138
};
115139

116-
const dragEnd = async (progress: number) => {
117-
const position = (progress * duration) / 1000;
118-
await audioPlayer.seek(position);
140+
const dragEnd = async (currentProgress: number) => {
141+
const positionInSeconds = (currentProgress * duration) / ONE_SECOND_IN_MILLISECONDS;
142+
await audioPlayer.seek(positionInSeconds);
119143
playAudio(audioPlayer.id);
120144
};
121145

@@ -142,15 +166,14 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
142166
},
143167
} = useTheme();
144168

145-
const positionInSeconds = position / 1000;
146169
const progressDuration = useMemo(
147170
() =>
148-
positionInSeconds
149-
? positionInSeconds / 3600 >= 1
150-
? dayjs.duration(positionInSeconds, 'second').format('HH:mm:ss')
151-
: dayjs.duration(positionInSeconds, 'second').format('mm:ss')
152-
: dayjs.duration(duration / 1000, 'second').format('mm:ss'),
153-
[duration, positionInSeconds],
171+
position
172+
? position / ONE_HOUR_IN_MILLISECONDS >= 1
173+
? dayjs.duration(position, 'milliseconds').format('HH:mm:ss')
174+
: dayjs.duration(position, 'milliseconds').format('mm:ss')
175+
: dayjs.duration(duration, 'milliseconds').format('mm:ss'),
176+
[duration, position],
154177
);
155178

156179
return (

package/src/components/Attachment/FileAttachmentGroup.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ import {
1717
import { useTheme } from '../../contexts/themeContext/ThemeContext';
1818
import { isSoundPackageAvailable } from '../../native';
1919

20-
export type FileAttachmentGroupPropsWithContext = Pick<MessageContextValue, 'files'> &
20+
export type FileAttachmentGroupPropsWithContext = Pick<MessageContextValue, 'files' | 'message'> &
2121
Pick<MessagesContextValue, 'Attachment' | 'AudioAttachment'> & {
2222
/**
23+
* @deprecated Use message instead
2324
* The unique id for the message with file attachments
2425
*/
2526
messageId: string;
@@ -30,7 +31,7 @@ export type FileAttachmentGroupPropsWithContext = Pick<MessageContextValue, 'fil
3031
};
3132

3233
const FileAttachmentGroupWithContext = (props: FileAttachmentGroupPropsWithContext) => {
33-
const { Attachment, AudioAttachment, files, messageId, styles: stylesProp = {} } = props;
34+
const { Attachment, AudioAttachment, files, message, styles: stylesProp = {} } = props;
3435

3536
const {
3637
theme: {
@@ -44,7 +45,7 @@ const FileAttachmentGroupWithContext = (props: FileAttachmentGroupPropsWithConte
4445
<View style={[styles.container, container, stylesProp.container]}>
4546
{files.map((file, index) => (
4647
<View
47-
key={`file-by-attachment-group-${messageId}-${index}`}
48+
key={`file-by-attachment-group-${message.id}-${index}`}
4849
style={[
4950
{ paddingBottom: index !== files.length - 1 ? 4 : 0 },
5051
stylesProp.attachmentContainer,
@@ -53,7 +54,7 @@ const FileAttachmentGroupWithContext = (props: FileAttachmentGroupPropsWithConte
5354
>
5455
{(isAudioAttachment(file) || isVoiceRecordingAttachment(file)) &&
5556
isSoundPackageAvailable() ? (
56-
<AudioAttachment item={file} showSpeedSettings={true} />
57+
<AudioAttachment item={file} message={message} showSpeedSettings={true} />
5758
) : (
5859
<Attachment attachment={file} />
5960
)}
@@ -67,8 +68,13 @@ const areEqual = (
6768
prevProps: FileAttachmentGroupPropsWithContext,
6869
nextProps: FileAttachmentGroupPropsWithContext,
6970
) => {
70-
const { files: prevFiles } = prevProps;
71-
const { files: nextFiles } = nextProps;
71+
const { files: prevFiles, message: prevMessage } = prevProps;
72+
const { files: nextFiles, message: nextMessage } = nextProps;
73+
74+
const messageEqual = prevMessage?.id === nextMessage?.id;
75+
if (!messageEqual) {
76+
return false;
77+
}
7278

7379
return prevFiles.length === nextFiles.length;
7480
};
@@ -86,7 +92,7 @@ export type FileAttachmentGroupProps = Partial<
8692
export const FileAttachmentGroup = (props: FileAttachmentGroupProps) => {
8793
const { files: propFiles, messageId } = props;
8894

89-
const { files: contextFiles } = useMessageContext();
95+
const { files: contextFiles, message } = useMessageContext();
9096

9197
const { Attachment = AttachmentDefault, AudioAttachment } = useMessagesContext();
9298

@@ -102,6 +108,7 @@ export const FileAttachmentGroup = (props: FileAttachmentGroupProps) => {
102108
Attachment,
103109
AudioAttachment,
104110
files,
111+
message,
105112
messageId,
106113
}}
107114
/>

package/src/components/MessageInput/components/AttachmentPreview/AudioAttachmentUploadPreview.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { DismissAttachmentUpload } from './DismissAttachmentUpload';
1010

1111
import { AudioAttachment } from '../../../../components/Attachment/AudioAttachment';
1212
import { useChatContext } from '../../../../contexts/chatContext/ChatContext';
13+
import { useMessageComposer } from '../../../../contexts/messageInputContext/hooks/useMessageComposer';
1314
import { UploadAttachmentPreviewProps } from '../../../../types/types';
1415
import { getIndicatorTypeForFileState, ProgressIndicatorTypes } from '../../../../utils/utils';
1516

@@ -28,14 +29,22 @@ export const AudioAttachmentUploadPreview = ({
2829
attachment.localMetadata.uploadState,
2930
enableOfflineSupport,
3031
);
32+
const messageComposer = useMessageComposer();
33+
const isDraft = messageComposer.draftId;
34+
const isEditing = messageComposer.editedMessage;
35+
const assetUrl =
36+
(isDraft || isEditing
37+
? attachment.asset_url
38+
: (attachment.localMetadata.file as FileReference).uri) ??
39+
(attachment.localMetadata.file as FileReference).uri;
3140

3241
const finalAttachment = useMemo(
3342
() => ({
3443
...attachment,
35-
asset_url: attachment.asset_url ?? (attachment.localMetadata.file as FileReference).uri,
44+
asset_url: assetUrl,
3645
id: attachment.localMetadata.id,
3746
}),
38-
[attachment],
47+
[attachment, assetUrl],
3948
);
4049

4150
const onRetryHandler = useCallback(() => {
@@ -55,6 +64,7 @@ export const AudioAttachmentUploadPreview = ({
5564
>
5665
<AudioAttachment
5766
hideProgressBar={true}
67+
isPreview={true}
5868
item={finalAttachment}
5969
showSpeedSettings={false}
6070
titleMaxLength={12}

package/src/components/ProgressControl/ProgressControl.tsx

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
1-
import React, { useState } from 'react';
1+
import React, { useRef, useState } from 'react';
22
import { StyleSheet, View } from 'react-native';
33
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
4-
import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
4+
import Animated, {
5+
runOnJS,
6+
useAnimatedReaction,
7+
useAnimatedStyle,
8+
useSharedValue,
9+
} from 'react-native-reanimated';
510

611
import { useTheme } from '../../contexts/themeContext/ThemeContext';
712

813
export type ProgressControlProps = {
914
/**
15+
* @deprecated unused prop.
1016
* The duration of the audio in seconds
1117
*/
1218
duration: number;
@@ -33,6 +39,7 @@ export type ProgressControlProps = {
3339
onPlayPause?: (status?: boolean) => void;
3440
/**
3541
* The function to be called when the user is dragging the progress bar
42+
* @deprecated This is not used anymore and is handled locally
3643
*/
3744
onProgressDrag?: (progress: number) => void;
3845
/**
@@ -67,51 +74,43 @@ const ProgressControlThumb = () => {
6774

6875
export const ProgressControl = (props: ProgressControlProps) => {
6976
const [widthInNumbers, setWidthInNumbers] = useState(0);
70-
const {
71-
filledColor: filledColorFromProp,
72-
onEndDrag,
73-
onPlayPause,
74-
onProgressDrag,
75-
onStartDrag,
76-
progress,
77-
testID,
78-
} = props;
77+
const { filledColor: filledColorFromProp, onEndDrag, onStartDrag, progress, testID } = props;
7978

80-
const progressValue = useSharedValue(progress);
79+
const state = useSharedValue(progress);
80+
const isSliding = useRef(false);
8181
const {
8282
theme: {
8383
colors: { grey_dark },
8484
progressControl: { container, filledColor: filledColorFromTheme, filledStyles, thumb },
8585
},
8686
} = useTheme();
8787

88+
useAnimatedReaction(
89+
() => progress,
90+
(newProgress) => {
91+
if (!isSliding.current) {
92+
state.value = newProgress;
93+
}
94+
},
95+
[progress, isSliding.current],
96+
);
97+
8898
const pan = Gesture.Pan()
8999
.maxPointers(1)
90-
.onStart((event) => {
91-
const currentProgress = (progressValue.value + event.x) / widthInNumbers;
92-
progressValue.value = Math.max(0, Math.min(currentProgress, 1));
100+
.onStart(() => {
101+
isSliding.current = true;
93102
if (onStartDrag) {
94-
runOnJS(onStartDrag)(progressValue.value);
95-
}
96-
if (onPlayPause) {
97-
runOnJS(onPlayPause)(true);
103+
runOnJS(onStartDrag)(state.value);
98104
}
99105
})
100106
.onUpdate((event) => {
101-
const currentProgress = (progressValue.value + event.x) / widthInNumbers;
102-
progressValue.value = Math.max(0, Math.min(currentProgress, 1));
103-
if (onProgressDrag) {
104-
runOnJS(onProgressDrag)(progressValue.value);
105-
}
107+
const newProgress = Math.max(0, Math.min(event.x / widthInNumbers, 1));
108+
state.value = newProgress;
106109
})
107-
.onEnd((event) => {
108-
const currentProgress = (progressValue.value + event.x) / widthInNumbers;
109-
progressValue.value = Math.max(0, Math.min(currentProgress, 1));
110+
.onEnd(() => {
111+
isSliding.current = false;
110112
if (onEndDrag) {
111-
runOnJS(onEndDrag)(progressValue.value);
112-
}
113-
if (onPlayPause) {
114-
runOnJS(onPlayPause)(false);
113+
runOnJS(onEndDrag)(state.value);
115114
}
116115
})
117116
.withTestId(testID);
@@ -120,16 +119,16 @@ export const ProgressControl = (props: ProgressControlProps) => {
120119

121120
const thumbStyles = useAnimatedStyle(
122121
() => ({
123-
transform: [{ translateX: progress * widthInNumbers - THUMB_WIDTH / 2 }],
122+
transform: [{ translateX: state.value * widthInNumbers - THUMB_WIDTH / 2 }],
124123
}),
125-
[progress],
124+
[widthInNumbers],
126125
);
127126

128127
const animatedFilledStyles = useAnimatedStyle(
129128
() => ({
130-
width: progress * widthInNumbers,
129+
width: state.value * widthInNumbers,
131130
}),
132-
[progress],
131+
[widthInNumbers],
133132
);
134133

135134
return (
@@ -151,7 +150,7 @@ export const ProgressControl = (props: ProgressControlProps) => {
151150
]}
152151
/>
153152
<Animated.View style={[thumbStyles, thumb]}>
154-
{onEndDrag || onProgressDrag ? <ProgressControlThumb /> : null}
153+
{onEndDrag ? <ProgressControlThumb /> : null}
155154
</Animated.View>
156155
</View>
157156
</GestureDetector>

0 commit comments

Comments
 (0)