diff --git a/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx b/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx index 0f1053a760..0556e70e2f 100644 --- a/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx +++ b/package/src/components/MessageInput/__tests__/AttachmentUploadPreviewList.test.tsx @@ -652,5 +652,29 @@ describe('AttachmentUploadPreviewList', () => { expect(screen.queryAllByTestId('image-attachment-upload-preview')).toHaveLength(1); }); }); + + it('shows the duration a sent video carries in custom.duration', async () => { + const custom = { duration: 125000, mime_type: 'video/mp4' }; + + act(() => { + channel.messageComposer.initState({ + composition: generateMessage({ + attachments: [ + generateVideoAttachmentBase({ + asset_url: 'https://video-attachment.mp4', + custom, + thumb_url: 'https://video-attachment-thumb.png', + }), + ], + }), + }); + }); + + renderComponent({ channel, client, props: {} }); + + await waitFor(() => { + expect(screen.getByText('2m')).toBeTruthy(); + }); + }); }); }); diff --git a/package/src/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.tsx b/package/src/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.tsx index 7a74540a12..77e862d119 100644 --- a/package/src/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.tsx +++ b/package/src/components/MessageInput/components/AttachmentPreview/VideoAttachmentUploadPreview.tsx @@ -29,7 +29,13 @@ export const VideoAttachmentUploadPreview = ({ // `localMetadata.file` is `File | FileReference`; only the RN `FileReference` carries a // `duration` (and a required `uri` to discriminate on), so read it through a guard. const localFile = attachment.localMetadata.file; - const duration = localFile && 'uri' in localFile ? localFile.duration : undefined; + const { custom } = attachment; + const duration = + localFile && 'uri' in localFile + ? localFile.duration + : custom && 'duration' in custom && typeof custom.duration === 'number' + ? custom.duration + : undefined; return previewUri ? ( <> diff --git a/package/src/middlewares/__tests__/attachments.test.ts b/package/src/middlewares/__tests__/attachments.test.ts index 395fd00635..51927cdf99 100644 --- a/package/src/middlewares/__tests__/attachments.test.ts +++ b/package/src/middlewares/__tests__/attachments.test.ts @@ -81,6 +81,29 @@ describe('setupVideoAttachmentPreviewMiddleware', () => { expect(state.attachment.localMetadata.previewUri).toBeUndefined(); }); + it('sends the picked file duration along as custom.duration', () => { + const { next } = runPrepare( + localVideoAttachment({ + custom: { mime_type: 'video/mp4' }, + localMetadata: { + file: { duration: 125000, name: 'clip.mp4', uri: 'file://local/clip.mp4' }, + id: 'clip', + uploadState: 'uploading', + }, + }), + ); + + const state = next.mock.calls[0][0] as { attachment: LocalAttachment }; + expect(state.attachment.custom).toEqual({ duration: 125000, mime_type: 'video/mp4' }); + }); + + it('leaves custom untouched when the picked file has no duration', () => { + const { next } = runPrepare(localVideoAttachment({ custom: { mime_type: 'video/mp4' } })); + + const state = next.mock.calls[0][0] as { attachment: LocalAttachment }; + expect(state.attachment.custom).toEqual({ mime_type: 'video/mp4' }); + }); + it('forwards anything that is not a local video', () => { const image = runPrepare( localVideoAttachment({ image_url: 'file://local/photo.jpg', type: 'image' }), diff --git a/package/src/middlewares/attachments.ts b/package/src/middlewares/attachments.ts index 41d5e7c1cb..00dea363a8 100644 --- a/package/src/middlewares/attachments.ts +++ b/package/src/middlewares/attachments.ts @@ -6,7 +6,9 @@ import { /** * Sets a local video's `localMetadata.previewUri` to its `thumb_url`, because the picked video - * file itself cannot be rendered as a preview (other attachments preview the file URI). + * file itself cannot be rendered as a preview (other attachments preview the file URI). Also sends + * the picked file's duration along as `custom.duration`, since the local file is gone once the + * message is sent (e.g. when editing it). */ const createVideoAttachmentPreviewMiddleware = (): AttachmentPreUploadMiddleware => ({ id: 'stream-io/message-composer-ui-middleware/video-attachment-preview', @@ -18,10 +20,16 @@ const createVideoAttachmentPreviewMiddleware = (): AttachmentPreUploadMiddleware return forward(); } + // Only the RN `FileReference` carries a `duration` (and a required `uri` to discriminate on). + const { file } = attachment.localMetadata; + const duration = file && 'uri' in file ? file.duration : undefined; + const custom = duration ? { ...attachment.custom, duration } : attachment.custom; + return next({ ...state, attachment: { ...attachment, + custom, localMetadata: { ...attachment.localMetadata, previewUri: attachment.thumb_url,