Skip to content

Commit 93b1581

Browse files
committed
fix: make api better
1 parent cccdd38 commit 93b1581

File tree

7 files changed

+85
-74
lines changed

7 files changed

+85
-74
lines changed

package/src/components/Attachment/AudioAttachment.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
9999
} = props;
100100
const isVoiceRecording = isVoiceRecordingAttachment(item);
101101

102-
const { audioPlayer, toggleAudio, playAudio, pauseAudio } = useAudioPlayerControl({
102+
const audioPlayer = useAudioPlayerControl({
103103
duration: item.duration ?? 0,
104104
mimeType: item.mime_type ?? '',
105105
requester: isPreview
@@ -151,15 +151,15 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
151151
};
152152

153153
const handlePlayPause = () => {
154-
toggleAudio(audioPlayer.id);
154+
audioPlayer.togglePlayPause();
155155
};
156156

157157
const handleEnd = async () => {
158158
await audioPlayer.stop();
159159
};
160160

161161
const dragStart = () => {
162-
pauseAudio(audioPlayer.id);
162+
audioPlayer.pause();
163163
};
164164

165165
const dragProgress = (currentProgress: number) => {
@@ -169,7 +169,7 @@ export const AudioAttachment = (props: AudioAttachmentProps) => {
169169
const dragEnd = async (currentProgress: number) => {
170170
const positionInSeconds = (currentProgress * duration) / ONE_SECOND_IN_MILLISECONDS;
171171
await audioPlayer.seek(positionInSeconds);
172-
playAudio(audioPlayer.id);
172+
audioPlayer.play();
173173
};
174174

175175
const onSpeedChangeHandler = async () => {

package/src/components/Channel/Channel.tsx

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -495,7 +495,7 @@ export type ChannelPropsWithContext = Pick<ChannelContextValue, 'channel'> &
495495
* If true, multiple audio players will be allowed to play simultaneously
496496
* @default true
497497
*/
498-
playMultipleAudio?: boolean;
498+
allowConcurrentAudioPlayback?: boolean;
499499
stateUpdateThrottleInterval?: number;
500500
/**
501501
* Tells if channel is rendering a thread list
@@ -515,6 +515,7 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
515515
additionalKeyboardAvoidingViewProps,
516516
additionalPressableProps,
517517
additionalTextInputProps,
518+
allowConcurrentAudioPlayback = false,
518519
allowThreadMessagesInChannel = true,
519520
asyncMessagesLockDistance = 50,
520521
asyncMessagesMinimumPressDuration = 500,
@@ -691,7 +692,6 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
691692
openPollCreationDialog,
692693
overrideOwnCapabilities,
693694
PollContent,
694-
playMultipleAudio = true,
695695
ReactionListBottom = ReactionListBottomDefault,
696696
reactionListPosition = 'top',
697697
ReactionListTop = ReactionListTopDefault,
@@ -1669,9 +1669,9 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
16691669

16701670
const audioPlayerPoolProviderProps = useMemo<AudioPlayerPoolContextProps>(
16711671
() => ({
1672-
playMultipleAudio,
1672+
allowConcurrentAudioPlayback,
16731673
}),
1674-
[playMultipleAudio],
1674+
[allowConcurrentAudioPlayback],
16751675
);
16761676

16771677
const attachmentPickerProps = useMemo(
@@ -2008,20 +2008,20 @@ const ChannelWithContext = (props: PropsWithChildren<ChannelPropsWithContext>) =
20082008
<TypingProvider value={typingContext}>
20092009
<PaginatedMessageListProvider value={messageListContext}>
20102010
<MessagesProvider value={messagesContext}>
2011-
<AudioPlayerPoolProvider props={audioPlayerPoolProviderProps}>
2012-
<ThreadProvider value={threadContext}>
2013-
<AttachmentPickerProvider value={attachmentPickerContext}>
2014-
<MessageComposerProvider value={messageComposerContext}>
2015-
<MessageInputProvider value={inputMessageInputContext}>
2011+
<ThreadProvider value={threadContext}>
2012+
<AttachmentPickerProvider value={attachmentPickerContext}>
2013+
<MessageComposerProvider value={messageComposerContext}>
2014+
<MessageInputProvider value={inputMessageInputContext}>
2015+
<AudioPlayerPoolProvider props={audioPlayerPoolProviderProps}>
20162016
<View style={{ height: '100%' }}>{children}</View>
20172017
{!disableAttachmentPicker && (
20182018
<AttachmentPicker ref={bottomSheetRef} {...attachmentPickerProps} />
20192019
)}
2020-
</MessageInputProvider>
2021-
</MessageComposerProvider>
2022-
</AttachmentPickerProvider>
2023-
</ThreadProvider>
2024-
</AudioPlayerPoolProvider>
2020+
</AudioPlayerPoolProvider>
2021+
</MessageInputProvider>
2022+
</MessageComposerProvider>
2023+
</AttachmentPickerProvider>
2024+
</ThreadProvider>
20252025
</MessagesProvider>
20262026
</PaginatedMessageListProvider>
20272027
</TypingProvider>

package/src/components/MessageInput/components/AudioRecorder/AudioRecordingPreview.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ const audioPlayerSelector = (state: AudioPlayerState) => ({
6565
export const AudioRecordingPreview = (props: AudioRecordingPreviewProps) => {
6666
const { recordingDuration, uri, waveformData } = props;
6767

68-
const { audioPlayer, toggleAudio } = useAudioPlayerControl({
68+
const audioPlayer = useAudioPlayerControl({
6969
duration: recordingDuration / ONE_SECOND_IN_MILLISECONDS,
7070
mimeType: 'audio/aac',
7171
// This is a temporary flag to manage audio player for voice recording in preview as the one in message list uses react-native-video.
@@ -104,7 +104,7 @@ export const AudioRecordingPreview = (props: AudioRecordingPreviewProps) => {
104104
} = useTheme();
105105

106106
const handlePlayPause = () => {
107-
toggleAudio(audioPlayer.id);
107+
audioPlayer.togglePlayPause();
108108
};
109109

110110
const progressDuration = useMemo(

package/src/contexts/audioPlayerPoolContext/AudioPlayerPoolContext.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { AudioPlayerPool } from '../../state-store/audio-player-pool';
44
import { DEFAULT_BASE_CONTEXT_VALUE } from '../utils/defaultBaseContextValue';
55

66
export type AudioPlayerPoolContextProps = {
7-
playMultipleAudio: boolean;
7+
allowConcurrentAudioPlayback: boolean;
88
};
99

1010
export type AudioPlayerPoolContextValue = {
@@ -16,12 +16,12 @@ export const AudioPlayerPoolContext = createContext<AudioPlayerPoolContextValue>
1616
);
1717

1818
export const AudioPlayerPoolProvider = ({
19-
props,
19+
props: { allowConcurrentAudioPlayback },
2020
children,
2121
}: PropsWithChildren<{ props: AudioPlayerPoolContextProps }>) => {
2222
const audioPlayerPool = useMemo(
23-
() => new AudioPlayerPool({ multipleAudioPlayers: props.playMultipleAudio }),
24-
[props.playMultipleAudio],
23+
() => new AudioPlayerPool({ allowConcurrentAudioPlayback }),
24+
[allowConcurrentAudioPlayback],
2525
);
2626
const audioPlayerPoolContextValue = useMemo(() => ({ audioPlayerPool }), [audioPlayerPool]);
2727

package/src/hooks/useAudioPlayerControl.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useCallback, useMemo } from 'react';
1+
import { useMemo } from 'react';
22

33
import { useAudioPlayerPoolContext } from '../contexts/audioPlayerPoolContext/AudioPlayerPoolContext';
44
import { AudioPlayerOptions } from '../state-store/audio-player';
@@ -55,10 +55,5 @@ export const useAudioPlayerControl = ({
5555
[audioPlayerPool, duration, id, mimeType, playbackRates, previewVoiceRecording, type, uri],
5656
);
5757

58-
return {
59-
audioPlayer,
60-
pauseAudio: useCallback((id: string) => audioPlayerPool?.pause(id), [audioPlayerPool]),
61-
playAudio: useCallback((id: string) => audioPlayerPool?.play(id), [audioPlayerPool]),
62-
toggleAudio: useCallback((id: string) => audioPlayerPool?.toggle(id), [audioPlayerPool]),
63-
};
58+
return audioPlayer;
6459
};
Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,75 @@
11
import { AudioPlayer, AudioPlayerOptions } from './audio-player';
22

33
export type AudioPlayerPoolOptions = {
4-
multipleAudioPlayers: boolean;
4+
allowConcurrentAudioPlayback: boolean;
55
};
66

77
export class AudioPlayerPool {
8-
audioPlayers: Map<string, AudioPlayer>;
9-
multipleAudioPlayers: boolean;
10-
constructor({ multipleAudioPlayers }: AudioPlayerPoolOptions) {
11-
this.audioPlayers = new Map<string, AudioPlayer>();
12-
this.multipleAudioPlayers = multipleAudioPlayers ?? false;
8+
pool: Map<string, AudioPlayer>;
9+
allowConcurrentAudioPlayback: boolean;
10+
private currentlyPlayingId: string | null = null;
11+
12+
constructor({ allowConcurrentAudioPlayback }: AudioPlayerPoolOptions) {
13+
this.pool = new Map<string, AudioPlayer>();
14+
this.allowConcurrentAudioPlayback = allowConcurrentAudioPlayback ?? false;
1315
}
1416

15-
getPlayers() {
16-
return Array.from(this.audioPlayers.values());
17+
get players() {
18+
return Array.from(this.pool.values());
1719
}
1820

1921
getOrAddPlayer(params: AudioPlayerOptions) {
20-
const player = this.audioPlayers.get(params.id);
22+
const player = this.pool.get(params.id);
2123
if (player) {
2224
return player;
2325
}
2426
const newPlayer = new AudioPlayer(params);
27+
newPlayer.pool = this;
2528

26-
this.audioPlayers.set(params.id, newPlayer);
29+
this.pool.set(params.id, newPlayer);
2730
return newPlayer;
2831
}
2932

3033
removePlayer(id: string) {
31-
const player = this.audioPlayers.get(id);
34+
const player = this.pool.get(id);
3235
if (!player) return;
3336
player.onRemove();
34-
this.audioPlayers.delete(id);
37+
this.pool.delete(id);
38+
39+
// Clear tracking if this was the currently playing player
40+
if (!this.allowConcurrentAudioPlayback && this.currentlyPlayingId === id) {
41+
this.currentlyPlayingId = null;
42+
}
43+
}
44+
45+
deregister(id: string) {
46+
if (this.pool.has(id)) {
47+
this.pool.delete(id);
48+
}
3549
}
3650

3751
clear() {
38-
for (const player of this.audioPlayers.values()) {
52+
for (const player of this.pool.values()) {
3953
this.removePlayer(player.id);
4054
}
55+
this.currentlyPlayingId = null;
4156
}
4257

43-
play(id: string) {
44-
const targetPlayer = this.audioPlayers.get(id);
45-
if (!targetPlayer) return;
58+
requestPlay(id: string) {
59+
if (this.allowConcurrentAudioPlayback) return;
4660

47-
if (!this.multipleAudioPlayers) {
48-
for (const [playerId, player] of this.audioPlayers) {
49-
if (playerId !== id && player.isPlaying) {
50-
// eslint-disable-next-line no-underscore-dangle
51-
player._pauseInternal();
52-
}
61+
if (this.currentlyPlayingId && this.currentlyPlayingId !== id) {
62+
const currentPlayer = this.pool.get(this.currentlyPlayingId);
63+
if (currentPlayer && currentPlayer.isPlaying) {
64+
currentPlayer.pause();
5365
}
5466
}
55-
// eslint-disable-next-line no-underscore-dangle
56-
targetPlayer._playInternal();
57-
}
58-
59-
pause(id: string) {
60-
const targetPlayer = this.audioPlayers.get(id);
61-
if (!targetPlayer) return;
62-
// eslint-disable-next-line no-underscore-dangle
63-
targetPlayer._pauseInternal();
67+
this.currentlyPlayingId = id;
6468
}
6569

66-
toggle(id: string) {
67-
const targetPlayer = this.audioPlayers.get(id);
68-
if (!targetPlayer) return;
69-
if (targetPlayer.isPlaying) {
70-
this.pause(id);
71-
} else {
72-
this.play(id);
70+
notifyPaused(id: string) {
71+
if (this.currentlyPlayingId === id) {
72+
this.currentlyPlayingId = null;
7373
}
7474
}
7575
}

package/src/state-store/audio-player.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { StateStore } from 'stream-chat';
22

3+
import { AudioPlayerPool } from './audio-player-pool';
4+
35
import { AVPlaybackStatusToSet, NativeHandlers, PlaybackStatus, SoundReturnType } from '../native';
46

57
export type AudioDescriptor = {
@@ -47,6 +49,8 @@ export class AudioPlayer {
4749
private _id: string;
4850
private type: 'voiceRecording' | 'audio';
4951
private isExpoCLI: boolean;
52+
private _pool: AudioPlayerPool | null = null;
53+
5054
/**
5155
* This is a temporary flag to manage audio player for voice recording in preview as the one in message list uses react-native-video.
5256
* We can get rid of this when we migrate to the react-native-nitro-sound everywhere.
@@ -171,6 +175,10 @@ export class AudioPlayer {
171175
}
172176

173177
// Setters
178+
set pool(pool: AudioPlayerPool) {
179+
this._pool = pool;
180+
}
181+
174182
set duration(duration: number) {
175183
this.state.partialNext({
176184
duration,
@@ -219,11 +227,15 @@ export class AudioPlayer {
219227
}
220228
}
221229

222-
_playInternal() {
230+
play() {
223231
if (this.isPlaying) {
224232
return;
225233
}
226234

235+
if (this._pool) {
236+
this._pool.requestPlay(this.id);
237+
}
238+
227239
if (this.previewVoiceRecording) {
228240
if (NativeHandlers.Audio?.resumePlayer) {
229241
NativeHandlers.Audio.resumePlayer();
@@ -252,7 +264,7 @@ export class AudioPlayer {
252264
});
253265
}
254266

255-
_pauseInternal() {
267+
pause() {
256268
if (!this.isPlaying) {
257269
return;
258270
}
@@ -282,13 +294,17 @@ export class AudioPlayer {
282294
this.state.partialNext({
283295
isPlaying: false,
284296
});
297+
298+
if (this._pool) {
299+
this._pool.notifyPaused(this.id);
300+
}
285301
}
286302

287303
togglePlayPause() {
288304
if (this.isPlaying) {
289-
this._pauseInternal();
305+
this.pause();
290306
} else {
291-
this._playInternal();
307+
this.play();
292308
}
293309
}
294310

@@ -325,7 +341,7 @@ export class AudioPlayer {
325341
async stop() {
326342
// First seek to 0 to stop the audio and then pause it
327343
await this.seek(0);
328-
this._pauseInternal();
344+
this.pause();
329345
}
330346

331347
onRemove() {

0 commit comments

Comments
 (0)