Skip to content

Commit 8f8ca7e

Browse files
committed
feat: add useActiveAudioPlayer hook
1 parent cf6a67e commit 8f8ca7e

24 files changed

+265
-121
lines changed

src/components/Attachment/Audio.tsx

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,43 @@ import React from 'react';
22
import type { Attachment } from 'stream-chat';
33

44
import { DownloadButton, FileSizeIndicator, PlayButton, ProgressBar } from './components';
5-
import { useAudioPlayer } from '../AudioPlayer/WithAudioPlayback';
6-
import type { AudioPlayerState } from '../AudioPlayer/AudioPlayer';
5+
import { type AudioPlayerState, useAudioPlayer } from '../AudioPlayback';
76
import { useStateStore } from '../../store';
87
import { useMessageContext } from '../../context';
8+
import type { AudioPlayer } from '../AudioPlayback/AudioPlayer';
9+
10+
type AudioAttachmentUIProps = {
11+
audioPlayer: AudioPlayer;
12+
};
13+
14+
// todo: finish creating a BaseAudioPlayer derived from VoiceRecordingPlayerUI and AudioAttachmentUI
15+
const AudioAttachmentUI = ({ audioPlayer }: AudioAttachmentUIProps) => {
16+
const dataTestId = 'audio-widget';
17+
const rootClassName = 'str-chat__message-attachment-audio-widget';
18+
19+
const { isPlaying, progress } =
20+
useStateStore(audioPlayer?.state, audioPlayerStateSelector) ?? {};
21+
22+
return (
23+
<div className={rootClassName} data-testid={dataTestId}>
24+
<div className='str-chat__message-attachment-audio-widget--play-controls'>
25+
<PlayButton isPlaying={!!isPlaying} onClick={audioPlayer.togglePlay} />
26+
</div>
27+
<div className='str-chat__message-attachment-audio-widget--text'>
28+
<div className='str-chat__message-attachment-audio-widget--text-first-row'>
29+
<div className='str-chat__message-attachment-audio-widget--title'>
30+
{audioPlayer.title}
31+
</div>
32+
<DownloadButton assetUrl={audioPlayer.src} />
33+
</div>
34+
<div className='str-chat__message-attachment-audio-widget--text-second-row'>
35+
<FileSizeIndicator fileSize={audioPlayer.fileSize} />
36+
<ProgressBar onClick={audioPlayer.seek} progress={progress ?? 0} />
37+
</div>
38+
</div>
39+
</div>
40+
);
41+
};
942

1043
export type AudioProps = {
1144
// fixme: rename og to attachment
@@ -34,38 +67,17 @@ const UnMemoizedAudio = (props: AudioProps) => {
3467
const { message, threadList } = useMessageContext() ?? {};
3568

3669
const audioPlayer = useAudioPlayer({
70+
fileSize: file_size,
3771
mimeType: mime_type,
3872
requester:
3973
message?.id &&
4074
`${threadList ? (message.parent_id ?? message.id) : ''}${message.id}`,
4175
src: asset_url,
76+
title,
77+
waveformData: props.og.waveform_data,
4278
});
4379

44-
const { isPlaying, progress } =
45-
useStateStore(audioPlayer?.state, audioPlayerStateSelector) ?? {};
46-
47-
if (!audioPlayer) return null;
48-
49-
const dataTestId = 'audio-widget';
50-
const rootClassName = 'str-chat__message-attachment-audio-widget';
51-
52-
return (
53-
<div className={rootClassName} data-testid={dataTestId}>
54-
<div className='str-chat__message-attachment-audio-widget--play-controls'>
55-
<PlayButton isPlaying={!!isPlaying} onClick={audioPlayer.togglePlay} />
56-
</div>
57-
<div className='str-chat__message-attachment-audio-widget--text'>
58-
<div className='str-chat__message-attachment-audio-widget--text-first-row'>
59-
<div className='str-chat__message-attachment-audio-widget--title'>{title}</div>
60-
<DownloadButton assetUrl={asset_url} />
61-
</div>
62-
<div className='str-chat__message-attachment-audio-widget--text-second-row'>
63-
<FileSizeIndicator fileSize={file_size} />
64-
<ProgressBar onClick={audioPlayer.seek} progress={progress ?? 0} />
65-
</div>
66-
</div>
67-
</div>
68-
);
80+
return audioPlayer ? <AudioAttachmentUI audioPlayer={audioPlayer} /> : null;
6981
};
7082

7183
/**

src/components/Attachment/Card.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ import { useTranslationContext } from '../../context/TranslationContext';
1212
import type { Attachment } from 'stream-chat';
1313
import type { RenderAttachmentProps } from './utils';
1414
import type { Dimensions } from '../../types/types';
15-
import { useAudioPlayer } from '../AudioPlayer/WithAudioPlayback';
15+
import { type AudioPlayerState, useAudioPlayer } from '../AudioPlayback';
1616
import { useStateStore } from '../../store';
17-
import type { AudioPlayerState } from '../AudioPlayer/AudioPlayer';
1817
import { useMessageContext } from '../../context';
1918

2019
const getHostFromURL = (url?: string | null) => {

src/components/Attachment/VoiceRecording.tsx

Lines changed: 59 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ import {
1010
import { displayDuration } from './utils';
1111
import { FileIcon } from '../ReactFileUtilities';
1212
import { useMessageContext, useTranslationContext } from '../../context';
13-
import { useAudioPlayer } from '../AudioPlayer/WithAudioPlayback';
13+
import { type AudioPlayerState, useAudioPlayer } from '../AudioPlayback/';
1414
import { useStateStore } from '../../store';
15-
import type { AudioPlayerState } from '../AudioPlayer/AudioPlayer';
15+
import type { AudioPlayer } from '../AudioPlayback/AudioPlayer';
1616

1717
const rootClassName = 'str-chat__message-attachment__voice-recording-widget';
1818

@@ -24,51 +24,16 @@ const audioPlayerStateSelector = (state: AudioPlayerState) => ({
2424
secondsElapsed: state.secondsElapsed,
2525
});
2626

27-
export type VoiceRecordingPlayerProps = Pick<VoiceRecordingProps, 'attachment'> & {
28-
/** An array of fractional numeric values of playback speed to override the defaults (1.0, 1.5, 2.0) */
29-
playbackRates?: number[];
27+
type VoiceRecordingPlayerUIProps = {
28+
audioPlayer: AudioPlayer;
3029
};
3130

32-
export const VoiceRecordingPlayer = ({
33-
attachment,
34-
playbackRates,
35-
}: VoiceRecordingPlayerProps) => {
36-
const { t } = useTranslationContext('VoiceRecordingPlayer');
37-
const {
38-
asset_url,
39-
duration = 0,
40-
mime_type,
41-
title = t('Voice message'),
42-
waveform_data,
43-
} = attachment;
44-
45-
/**
46-
* Introducing message context. This could be breaking change, therefore the fallback to {} is provided.
47-
* If this component is used outside the message context, then there will be no audio player namespacing
48-
* => scrolling away from the message in virtualized ML would create a new AudioPlayer instance.
49-
*
50-
* Edge case: the requester (message) has multiple attachments with the same assetURL - does not happen
51-
* with the default SDK components, but can be done with custom API calls.In this case all the Audio
52-
* widgets will share the state.
53-
*/
54-
const { message, threadList } = useMessageContext() ?? {};
55-
56-
const audioPlayer = useAudioPlayer({
57-
durationSeconds: duration ?? 0,
58-
mimeType: mime_type,
59-
playbackRates,
60-
requester:
61-
message?.id &&
62-
`${threadList ? (message.parent_id ?? message.id) : ''}${message.id}`,
63-
src: asset_url,
64-
});
65-
31+
// todo: finish creating a BaseAudioPlayer derived from VoiceRecordingPlayerUI and AudioAttachmentUI
32+
const VoiceRecordingPlayerUI = ({ audioPlayer }: VoiceRecordingPlayerUIProps) => {
6633
const { canPlayRecord, isPlaying, playbackRate, progress, secondsElapsed } =
6734
useStateStore(audioPlayer?.state, audioPlayerStateSelector) ?? {};
6835

69-
if (!audioPlayer) return null;
70-
71-
const displayedDuration = secondsElapsed || duration;
36+
const displayedDuration = secondsElapsed || audioPlayer.durationSeconds;
7237

7338
return (
7439
<div className={rootClassName} data-testid='voice-recording-widget'>
@@ -77,25 +42,25 @@ export const VoiceRecordingPlayer = ({
7742
<div
7843
className='str-chat__message-attachment__voice-recording-widget__title'
7944
data-testid='voice-recording-title'
80-
title={title}
45+
title={audioPlayer.title}
8146
>
82-
{title}
47+
{audioPlayer.title}
8348
</div>
8449
<div className='str-chat__message-attachment__voice-recording-widget__audio-state'>
8550
<div className='str-chat__message-attachment__voice-recording-widget__timer'>
86-
{attachment.duration ? (
51+
{audioPlayer.durationSeconds ? (
8752
displayDuration(displayedDuration)
8853
) : (
8954
<FileSizeIndicator
90-
fileSize={attachment.file_size}
55+
fileSize={audioPlayer.fileSize}
9156
maximumFractionDigits={0}
9257
/>
9358
)}
9459
</div>
9560
<WaveProgressBar
9661
progress={progress}
9762
seek={audioPlayer.seek}
98-
waveformData={waveform_data || []}
63+
waveformData={audioPlayer.waveformData || []}
9964
/>
10065
</div>
10166
</div>
@@ -108,13 +73,59 @@ export const VoiceRecordingPlayer = ({
10873
{playbackRate?.toFixed(1)}x
10974
</PlaybackRateButton>
11075
) : (
111-
<FileIcon big={true} mimeType={mime_type} size={40} />
76+
<FileIcon big={true} mimeType={audioPlayer.mimeType} size={40} />
11277
)}
11378
</div>
11479
</div>
11580
);
11681
};
11782

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+
118129
export type QuotedVoiceRecordingProps = Pick<VoiceRecordingProps, 'attachment'>;
119130

120131
export const QuotedVoiceRecording = ({ attachment }: QuotedVoiceRecordingProps) => {

src/components/Attachment/__tests__/Audio.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import '@testing-library/jest-dom';
55
import { Audio } from '../Audio';
66
import { generateAudioAttachment, generateMessage } from '../../../mock-builders';
77
import { prettifyFileSize } from '../../MessageInput/hooks/utils';
8-
import { WithAudioPlayback } from '../../AudioPlayer/WithAudioPlayback';
8+
import { WithAudioPlayback } from '../../AudioPlayback';
99
import { MessageProvider } from '../../../context';
1010

1111
jest.mock('../../../context/ChatContext', () => ({

src/components/Attachment/__tests__/Card.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
mockTranslationContext,
2525
useMockedApis,
2626
} from '../../../mock-builders';
27-
import { WithAudioPlayback } from '../../AudioPlayer';
27+
import { WithAudioPlayback } from '../../AudioPlayback';
2828

2929
let chatClient;
3030
let channel;

src/components/Attachment/__tests__/VoiceRecording.test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
import { VoiceRecording, VoiceRecordingPlayer } from '../VoiceRecording';
1010
import { ChatProvider, MessageProvider } from '../../../context';
1111
import { ResizeObserverMock } from '../../../mock-builders/browser';
12-
import { WithAudioPlayback } from '../../AudioPlayer';
12+
import { WithAudioPlayback } from '../../AudioPlayback';
1313

1414
const AUDIO_RECORDING_PLAYER_TEST_ID = 'voice-recording-widget';
1515
const QUOTED_AUDIO_RECORDING_TEST_ID = 'quoted-voice-recording-widget';

src/components/Attachment/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@ export * from './FileAttachment';
99
export * from './Geolocation';
1010
export * from './UnsupportedAttachment';
1111
export * from './utils';
12+
export * from './VoiceRecording';
1213
export { useAudioController } from './hooks/useAudioController';
1314
export * from '../Location/hooks/useLiveLocationSharingManager';

0 commit comments

Comments
 (0)