Skip to content

Commit 7914e51

Browse files
authored
feat: reset audio player progress when the track is fully played (#3066)
1 parent fdf0e15 commit 7914e51

9 files changed

Lines changed: 70 additions & 27 deletions

File tree

src/components/Attachment/__tests__/WaveProgressBar.test.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { act, render, screen } from '@testing-library/react';
2+
import { act, render, screen, waitFor } from '@testing-library/react';
33
import { fromPartial } from '@total-typescript/shoehorn';
44
import { WaveProgressBar } from '../../AudioPlayback';
55
import { ResizeObserverMock } from '../../../mock-builders/browser';
@@ -95,7 +95,9 @@ describe('WaveProgressBar', () => {
9595
'2px',
9696
),
9797
).toBeTruthy();
98-
expect(screen.getAllByTestId(AMPLITUDE_BAR_TEST_ID)).toHaveLength(7);
98+
await waitFor(() => {
99+
expect(screen.getAllByTestId(AMPLITUDE_BAR_TEST_ID)).toHaveLength(7);
100+
});
99101
});
100102

101103
it('does not recalculate the number of bars on root resize if ResizeObserver is unsupported', () => {

src/components/AudioPlayback/AudioPlayer.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ export class AudioPlayer {
526526

527527
stop = () => {
528528
this.pause();
529+
this.state.partialNext({ isPlaying: false });
529530
this.setSecondsElapsed(0);
530531
if (this.elementRef) this.elementRef.currentTime = 0;
531532
};
@@ -628,10 +629,7 @@ export class AudioPlayer {
628629
if (audioElement) {
629630
this.updateDurationFromElement(audioElement);
630631
}
631-
this.state.partialNext({
632-
isPlaying: false,
633-
secondsElapsed: audioElement?.duration ?? this.durationSeconds ?? 0,
634-
});
632+
this.stop();
635633
};
636634

637635
const handleError = (e: HTMLMediaElementEventMap['error']) => {

src/components/AudioPlayback/__tests__/AudioPlayer.test.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,24 @@ describe('AudioPlayer', () => {
269269
expect(player.elementRef.currentTime).toBe(0);
270270
});
271271

272+
it('ended event resets playback state to initial position', () => {
273+
const player = makePlayer();
274+
275+
player.play();
276+
player.state.partialNext({ isPlaying: true });
277+
player.setSecondsElapsed(50);
278+
279+
expect(player.state.getLatestValue().secondsElapsed).toBe(50);
280+
281+
player.elementRef.dispatchEvent(new Event('ended'));
282+
283+
const st = player.state.getLatestValue();
284+
expect(st.isPlaying).toBe(false);
285+
expect(st.secondsElapsed).toBe(0);
286+
expect(st.progressPercent).toBe(0);
287+
expect(player.elementRef.currentTime).toBe(0);
288+
});
289+
272290
it('togglePlay delegates to play() / pause()', async () => {
273291
const p = makePlayer();
274292

src/components/AudioPlayback/__tests__/WithAudioPlayback.test.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
185185
expect(st.progressPercent).toBeCloseTo(25, 5);
186186
});
187187

188-
it('subscriptions: sets isPlaying=false and secondsElapsed to duration on Event "ended"', () => {
188+
it('subscriptions: resets playback state on Event "ended"', () => {
189189
let player;
190190
renderWithProvider({
191191
allowConcurrentPlayback,
@@ -216,7 +216,8 @@ describe('WithAudioPlayback + useAudioPlayer', () => {
216216

217217
const st = player.state.getLatestValue();
218218
expect(st.isPlaying).toBe(false);
219-
expect(st.secondsElapsed).toBe(200);
219+
expect(st.secondsElapsed).toBe(0);
220+
expect(st.progressPercent).toBe(0);
220221
});
221222

222223
it('subscriptions: error with MediaError.code=4 logs and sets canPlayRecord=false', () => {

src/components/MediaRecorder/AudioRecorder/__tests__/AudioRecorder.test.tsx

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ import { AudioRecorder } from '../AudioRecorder';
3434
import { MediaRecordingState } from '../../classes';
3535
import { WithAudioPlayback } from '../../../AudioPlayback';
3636
import { ChatViewContext } from '../../../ChatView/ChatView';
37+
import type {
38+
AppSettingsAPIResponse,
39+
SendFileAPIResponse,
40+
} from '../../../../../../stream-chat-js/src';
3741

3842
const chatViewContextValue = {
3943
activeChatView: 'channels',
@@ -376,6 +380,38 @@ describe('MessageInput', () => {
376380
});
377381
expect(sendMessageSpy).not.toHaveBeenCalled();
378382
});
383+
384+
it('renders voice preview slot after stopping recording when multiple async messages are enabled', async () => {
385+
MediaRecorderMock.autoEmitDataOnStop = true;
386+
const {
387+
channels: [channel],
388+
client,
389+
} = await initClientWithChannels({
390+
channelsData: [{ channel: { own_capabilities: ['upload-file'] } }],
391+
});
392+
393+
vi.spyOn(client, 'getAppSettings').mockResolvedValue({} as AppSettingsAPIResponse);
394+
vi.spyOn(channel, 'sendFile').mockResolvedValue({
395+
file: fileObjectURL,
396+
} as SendFileAPIResponse);
397+
398+
await renderComponent({
399+
channelStateCtx: { channel },
400+
chatCtx: { client },
401+
props: { asyncMessagesMultiSendEnabled: true },
402+
});
403+
404+
fireEvent.click(screen.getByTestId(START_RECORDING_AUDIO_BUTTON_TEST_ID));
405+
await waitFor(() => {
406+
expect(screen.getByTestId(AUDIO_RECORDER_TEST_ID)).toBeInTheDocument();
407+
});
408+
409+
fireEvent.click(screen.getByTestId(AUDIO_RECORDER_STOP_BTN_TEST_ID));
410+
411+
await waitFor(() => {
412+
expect(screen.getByTestId('voice-preview-slot')).toBeInTheDocument();
413+
});
414+
});
379415
});
380416

381417
const recorderMock = {};

src/components/MediaRecorder/AudioRecorder/styling/AudioRecorder.scss

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
.str-chat__audio-recorder__recording-playback,
1515
.str-chat__audio-recorder__recording-preview {
1616
flex: 1;
17+
min-width: 0;
1718
display: flex;
1819
align-items: center;
1920
gap: var(--spacing-md);
@@ -101,13 +102,12 @@
101102

102103
.str-chat__wave-progress-bar__track-container,
103104
.str-chat__waveform-box-container {
104-
//flex: 1;
105+
flex: 1 1 auto;
105106
display: flex;
106107
align-items: center;
107-
width: 100%;
108+
width: auto;
108109
min-width: 0;
109110
//overflow-x: hidden;
110-
flex-shrink: 1;
111111
}
112112

113113
.str-chat__wave-progress-bar__track-container {
@@ -120,7 +120,7 @@
120120
.str-chat__wave-progress-bar__track {
121121
display: flex;
122122
min-width: 0;
123-
width: unset;
123+
width: 100%;
124124
align-items: center;
125125
flex-wrap: nowrap;
126126
height: 2rem;
@@ -139,8 +139,7 @@
139139
padding-inline: var(--spacing-xs);
140140

141141
.str-chat__wave-progress-bar__track {
142-
flex: unset;
143-
//width: max-content;
142+
flex: 1 1 auto;
144143
}
145144
}
146145

src/components/MessageComposer/MessageComposer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ const MessageComposerProvider = (props: PropsWithChildren<MessageComposerProps>)
9595

9696
useEffect(
9797
() => () => {
98-
messageComposer.createDraft();
98+
messageComposer.createDraft().finally(() => messageComposer.clear());
9999
},
100100
[messageComposer],
101101
);

src/components/TextareaComposer/TextareaComposer.tsx

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -267,13 +267,6 @@ export const TextareaComposer = ({
267267
textareaRef.current.focus();
268268
}, [attachments, focus, quotedMessage, textareaRef]);
269269

270-
useEffect(
271-
() => () => {
272-
messageComposer.clear();
273-
},
274-
[messageComposer],
275-
);
276-
277270
useLayoutEffect(() => {
278271
/**
279272
* It is important to perform set text and after that the range

src/context/ChannelListContext.tsx

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,10 @@ export const ChannelListContextProvider = ({
4141
</ChannelListContext.Provider>
4242
);
4343

44-
export const useChannelListContext = (componentName?: string) => {
44+
export const useChannelListContext = () => {
4545
const contextValue = useContext(ChannelListContext);
4646

4747
if (!contextValue) {
48-
console.warn(
49-
`The useChannelListContext hook was called outside of the ChannelListContext provider. Make sure this hook is called within the ChannelList component. The errored call is located in the ${componentName} component.`,
50-
);
51-
5248
return {} as ChannelListContextValue;
5349
}
5450

0 commit comments

Comments
 (0)