diff --git a/apps/web/src/components/views/rooms/MessageComposer.tsx b/apps/web/src/components/views/rooms/MessageComposer.tsx index eea76b7..3483c28 100644 --- a/apps/web/src/components/views/rooms/MessageComposer.tsx +++ b/apps/web/src/components/views/rooms/MessageComposer.tsx @@ -629,6 +629,9 @@ export class MessageComposer extends React.Component { key="controls_video_record" ref={this.videoRecordingButton} onRecordingStateChange={this.onVideoRecordingStateChange} + relation={this.props.relation} + replyToEvent={this.props.replyToEvent} + room={this.props.room} />, ); } else if (this.context.tombstone) { diff --git a/apps/web/src/components/views/rooms/VideoRecordComposerTile.tsx b/apps/web/src/components/views/rooms/VideoRecordComposerTile.tsx index c5f0adc..851c64e 100644 --- a/apps/web/src/components/views/rooms/VideoRecordComposerTile.tsx +++ b/apps/web/src/components/views/rooms/VideoRecordComposerTile.tsx @@ -6,22 +6,39 @@ Please see LICENSE files in the repository root for full details. */ import React, { type ReactNode } from "react"; +import { type IEventRelation, type MatrixEvent, type Room } from "matrix-js-sdk/src/matrix"; import { logger } from "matrix-js-sdk/src/logger"; import { DeleteIcon, SendSolidIcon, StopSolidIcon } from "@vector-im/compound-design-tokens/assets/web/icons"; import { _t } from "../../../languageHandler"; +import { MatrixClientPeg } from "../../../MatrixClientPeg"; +import ContentMessages from "../../../ContentMessages"; import AccessibleButton from "../elements/AccessibleButton"; interface IProps { onRecordingStateChange: (haveVideoRecording: boolean) => void; + relation?: IEventRelation; + replyToEvent?: MatrixEvent; + room: Room; } interface IState { error?: string; isRecording: boolean; + isSending: boolean; previewUrl?: string; } +const PREFERRED_VIDEO_MIME_TYPES = ["video/webm;codecs=vp8,opus", "video/webm"]; + +function preferredVideoMimeType(): string | undefined { + for (const mimeType of PREFERRED_VIDEO_MIME_TYPES) { + if (MediaRecorder.isTypeSupported(mimeType)) { + return mimeType; + } + } +} + export function isVideoMessageRecorderSupported(): boolean { return ( typeof window !== "undefined" && @@ -37,6 +54,7 @@ export function isVideoMessageRecorderSupported(): boolean { export default class VideoRecordComposerTile extends React.PureComponent { private chunks: Blob[] = []; private mediaRecorder?: MediaRecorder; + private recordedFile?: File; private stream?: MediaStream; private isRequestingMedia = false; private isUnmounted = false; @@ -47,6 +65,7 @@ export default class VideoRecordComposerTile extends React.PureComponent { if (ev.data.size > 0) { @@ -104,13 +125,18 @@ export default class VideoRecordComposerTile extends React.PureComponent { + const mimeType = this.mediaRecorder?.mimeType; const shouldDiscardRecording = this.shouldDiscardRecording; const hasRecording = this.chunks.length > 0 && !shouldDiscardRecording; @@ -142,29 +169,63 @@ export default class VideoRecordComposerTile extends React.PureComponent => { + if (!this.recordedFile || this.state.isSending) return; + + this.setState({ isSending: true }); + + try { + await ContentMessages.sharedInstance().sendContentToRoom( + this.recordedFile, + this.props.room.roomId, + this.props.relation, + MatrixClientPeg.safeGet(), + this.props.replyToEvent, + ); + this.clearRecording(); + this.props.onRecordingStateChange(false); + } catch (e) { + logger.error("Error sending video message recording:", e); + if (!this.isUnmounted) { + this.setState({ error: _t("composer|video_message_error"), isSending: false }); + } + } + }; + private onCancel = (): void => { this.shouldDiscardRecording = true; this.disposeRecording(); - this.setState({ error: undefined, isRecording: false, previewUrl: undefined }); + this.setState({ error: undefined, isRecording: false, isSending: false, previewUrl: undefined }); this.props.onRecordingStateChange(false); }; + private clearRecording(): void { + this.chunks = []; + this.recordedFile = undefined; + this.revokePreviewUrl(); + this.setState({ error: undefined, isRecording: false, isSending: false, previewUrl: undefined }); + } + private disposeRecording(): void { this.chunks = []; + this.recordedFile = undefined; this.stopStream(); this.revokePreviewUrl(); @@ -209,8 +270,8 @@ export default class VideoRecordComposerTile extends React.PureComponent