add video
This commit is contained in:
@@ -10,6 +10,7 @@ import {
|
||||
Camera,
|
||||
Check,
|
||||
CheckCheck,
|
||||
Circle,
|
||||
FileText,
|
||||
ImageIcon,
|
||||
Loader2,
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
Trash2,
|
||||
UserPlus,
|
||||
Users,
|
||||
Video,
|
||||
Volume2,
|
||||
VolumeX,
|
||||
X
|
||||
@@ -58,6 +60,9 @@ import { InlineEditableTitle } from '@/components/chat/inline-editable-title';
|
||||
import { RepliedMessagePreview, scrollToChatMessage } from '@/components/chat/replied-message-preview';
|
||||
import { TypingIndicator } from '@/components/chat/typing-indicator';
|
||||
import { VoiceMessagePlayer } from '@/components/chat/voice-message-player';
|
||||
import { ChatVideoCaptureDialog, type ChatVideoCaptureMode } from '@/components/chat/chat-video-capture-dialog';
|
||||
import { ChatVideoNotePlayer } from '@/components/chat/chat-video-note-player';
|
||||
import { ChatVideoPlayer } from '@/components/chat/chat-video-player';
|
||||
import { NotificationBell } from '@/components/notifications/notification-bell';
|
||||
import { UserMenu } from '@/components/id/user-menu';
|
||||
import { UserAvatar } from '@/components/id/user-avatar';
|
||||
@@ -313,6 +318,8 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
const [chatToolsTab, setChatToolsTab] = useState<ChatToolsTab>('search');
|
||||
const [mediaDropVisible, setMediaDropVisible] = useState(false);
|
||||
const [lightboxIndex, setLightboxIndex] = useState<number | null>(null);
|
||||
const [videoCaptureOpen, setVideoCaptureOpen] = useState(false);
|
||||
const [videoCaptureMode, setVideoCaptureMode] = useState<ChatVideoCaptureMode>('note');
|
||||
|
||||
const mediaComposer = useChatMediaComposer();
|
||||
|
||||
@@ -326,6 +333,7 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
const mediaUrlsRef = useRef<Record<string, LoadedChatMedia>>({});
|
||||
const mediaLoadInFlightRef = useRef<Set<string>>(new Set());
|
||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||
const videoInputRef = useRef<HTMLInputElement>(null);
|
||||
const dragDepthRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -722,7 +730,15 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
|
||||
let meta = parseMessageMetadata(message.metadataJson);
|
||||
let fileName = meta.fileName || message.content || undefined;
|
||||
let mimeType = message.mimeType || (message.type === 'VOICE' ? 'audio/webm' : message.type === 'AUDIO' ? 'audio/mpeg' : undefined);
|
||||
let mimeType =
|
||||
message.mimeType ||
|
||||
(message.type === 'VOICE'
|
||||
? 'audio/webm'
|
||||
: message.type === 'AUDIO'
|
||||
? 'audio/mpeg'
|
||||
: message.type === 'VIDEO' || message.type === 'VIDEO_NOTE'
|
||||
? 'video/webm'
|
||||
: undefined);
|
||||
const query = new URLSearchParams({ storageKey });
|
||||
if (fileName) query.set('fileName', fileName);
|
||||
|
||||
@@ -1328,26 +1344,36 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadChatFile(file: File, options?: { voice?: boolean; durationMs?: number }) {
|
||||
async function uploadChatFile(file: File, options?: { voice?: boolean; videoNote?: boolean; durationMs?: number }) {
|
||||
if (!token || !activeRoomId || !activeRoom) return;
|
||||
if (!options?.voice) {
|
||||
const contentType = resolveChatContentType(file);
|
||||
const isDirectUpload = Boolean(options?.voice || options?.videoNote || contentType.startsWith('video/'));
|
||||
if (!isDirectUpload) {
|
||||
queueMediaFiles([file]);
|
||||
return;
|
||||
}
|
||||
setSending(true);
|
||||
try {
|
||||
const contentType = resolveChatContentType(file);
|
||||
const messageType = detectChatMessageType(file, { voice: options?.voice });
|
||||
const messageType = detectChatMessageType(file, { voice: options?.voice, videoNote: options?.videoNote });
|
||||
const metadata = {
|
||||
fileName: file.name,
|
||||
fileSize: file.size,
|
||||
...(options?.durationMs ? { durationMs: options.durationMs } : {})
|
||||
...(options?.durationMs ? { durationMs: options.durationMs } : {}),
|
||||
...(options?.videoNote ? { videoNote: true } : {})
|
||||
};
|
||||
|
||||
let uploadFile = file;
|
||||
let uploadContentType = contentType;
|
||||
let messageContent: string | undefined =
|
||||
messageType === 'VOICE' ? 'Голосовое сообщение' : messageType === 'FILE' ? file.name : undefined;
|
||||
messageType === 'VOICE'
|
||||
? 'Голосовое сообщение'
|
||||
: messageType === 'VIDEO'
|
||||
? 'Видео'
|
||||
: messageType === 'VIDEO_NOTE'
|
||||
? 'Видеосообщение'
|
||||
: messageType === 'FILE'
|
||||
? file.name
|
||||
: undefined;
|
||||
let isEncrypted = false;
|
||||
|
||||
if (activeRoomIsE2E) {
|
||||
@@ -2084,6 +2110,27 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (message.type === 'VIDEO') {
|
||||
const metaDuration = e2ePayload?.durationMs ?? parseMessageMetadata(message.metadataJson).durationMs;
|
||||
return (
|
||||
<ChatVideoPlayer
|
||||
src={media.blobUrl}
|
||||
durationMs={typeof metaDuration === 'number' ? metaDuration : undefined}
|
||||
fileName={fileName}
|
||||
variant={mine ? 'mine' : 'theirs'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (message.type === 'VIDEO_NOTE') {
|
||||
const metaDuration = e2ePayload?.durationMs ?? parseMessageMetadata(message.metadataJson).durationMs;
|
||||
return (
|
||||
<ChatVideoNotePlayer
|
||||
src={media.blobUrl}
|
||||
durationMs={typeof metaDuration === 'number' ? metaDuration : undefined}
|
||||
variant={mine ? 'mine' : 'theirs'}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
@@ -2333,16 +2380,42 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
{sending ? <Loader2 className="h-4 w-4 animate-spin" /> : <Send className="h-4 w-4" />}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
className={cn('h-11 w-11 rounded-full p-0', recording && 'bg-red-500 hover:bg-red-600')}
|
||||
onMouseDown={() => void startVoiceRecording()}
|
||||
onMouseUp={stopVoiceRecording}
|
||||
onMouseLeave={stopVoiceRecording}
|
||||
onTouchStart={() => void startVoiceRecording()}
|
||||
onTouchEnd={stopVoiceRecording}
|
||||
>
|
||||
<Mic className="h-4 w-4" />
|
||||
</Button>
|
||||
<>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-xl p-0"
|
||||
title="Записать кружок"
|
||||
onClick={() => {
|
||||
setVideoCaptureMode('note');
|
||||
setVideoCaptureOpen(true);
|
||||
}}
|
||||
>
|
||||
<Circle className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
size="sm"
|
||||
variant="ghost"
|
||||
className="h-10 w-10 rounded-xl p-0"
|
||||
title="Записать видео"
|
||||
onClick={() => {
|
||||
setVideoCaptureMode('video');
|
||||
setVideoCaptureOpen(true);
|
||||
}}
|
||||
>
|
||||
<Video className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
className={cn('h-11 w-11 rounded-full p-0', recording && 'bg-red-500 hover:bg-red-600')}
|
||||
onMouseDown={() => void startVoiceRecording()}
|
||||
onMouseUp={stopVoiceRecording}
|
||||
onMouseLeave={stopVoiceRecording}
|
||||
onTouchStart={() => void startVoiceRecording()}
|
||||
onTouchEnd={stopVoiceRecording}
|
||||
>
|
||||
<Mic className="h-4 w-4" />
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2355,6 +2428,18 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
)}
|
||||
</section>
|
||||
|
||||
<input
|
||||
ref={videoInputRef}
|
||||
type="file"
|
||||
className="hidden"
|
||||
accept="video/*"
|
||||
onChange={(event) => {
|
||||
const files = Array.from(event.target.files ?? []);
|
||||
files.forEach((file) => void uploadChatFile(file));
|
||||
event.target.value = '';
|
||||
}}
|
||||
/>
|
||||
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
@@ -2368,6 +2453,18 @@ export function FamilyGroupView({ groupId }: { groupId: string }) {
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChatVideoCaptureDialog
|
||||
open={videoCaptureOpen}
|
||||
mode={videoCaptureMode}
|
||||
onClose={() => setVideoCaptureOpen(false)}
|
||||
onCaptured={(file, durationMs) => {
|
||||
void uploadChatFile(file, {
|
||||
videoNote: videoCaptureMode === 'note',
|
||||
durationMs
|
||||
});
|
||||
}}
|
||||
/>
|
||||
|
||||
<ChatMediaComposeDialog
|
||||
open={mediaComposer.open}
|
||||
items={mediaComposer.items}
|
||||
|
||||
Reference in New Issue
Block a user