Message: Voice-To-Text for Video Message (#5201)
This commit is contained in:
parent
c91287635e
commit
a1923f203c
@ -28,6 +28,7 @@ import type {
|
|||||||
import type { UniversalMessage } from './messages';
|
import type { UniversalMessage } from './messages';
|
||||||
|
|
||||||
import { SUPPORTED_PHOTO_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES, VIDEO_WEBM_TYPE } from '../../../config';
|
import { SUPPORTED_PHOTO_CONTENT_TYPES, SUPPORTED_VIDEO_CONTENT_TYPES, VIDEO_WEBM_TYPE } from '../../../config';
|
||||||
|
import { generateWaveform } from '../../../util/generateWaveform';
|
||||||
import { pick } from '../../../util/iteratees';
|
import { pick } from '../../../util/iteratees';
|
||||||
import {
|
import {
|
||||||
addMediaToLocalDb, addStoryToLocalDb, type MediaRepairContext, serializeBytes,
|
addMediaToLocalDb, addStoryToLocalDb, type MediaRepairContext, serializeBytes,
|
||||||
@ -217,6 +218,8 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo
|
|||||||
nosound,
|
nosound,
|
||||||
} = videoAttr;
|
} = videoAttr;
|
||||||
|
|
||||||
|
const waveform = isRound ? generateWaveform(duration) : undefined;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
mediaType: 'video',
|
mediaType: 'video',
|
||||||
id: String(id),
|
id: String(id),
|
||||||
@ -233,6 +236,7 @@ export function buildVideoFromDocument(document: GramJs.Document, isSpoiler?: bo
|
|||||||
isSpoiler,
|
isSpoiler,
|
||||||
hasVideoPreview,
|
hasVideoPreview,
|
||||||
previewPhotoSizes,
|
previewPhotoSizes,
|
||||||
|
waveform,
|
||||||
...(nosound && { noSound: true }),
|
...(nosound && { noSound: true }),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -116,6 +116,7 @@ export interface ApiVideo {
|
|||||||
previewBlobUrl?: string;
|
previewBlobUrl?: string;
|
||||||
size: number;
|
size: number;
|
||||||
noSound?: boolean;
|
noSound?: boolean;
|
||||||
|
waveform?: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ApiAudio {
|
export interface ApiAudio {
|
||||||
|
|||||||
@ -4,7 +4,9 @@ import React, {
|
|||||||
} from '../../lib/teact/teact';
|
} from '../../lib/teact/teact';
|
||||||
import { getActions } from '../../global';
|
import { getActions } from '../../global';
|
||||||
|
|
||||||
import type { ApiAudio, ApiMessage, ApiVoice } from '../../api/types';
|
import type {
|
||||||
|
ApiAudio, ApiMessage, ApiVideo, ApiVoice,
|
||||||
|
} from '../../api/types';
|
||||||
import type { BufferedRange } from '../../hooks/useBuffering';
|
import type { BufferedRange } from '../../hooks/useBuffering';
|
||||||
import type { OldLangFn } from '../../hooks/useOldLang';
|
import type { OldLangFn } from '../../hooks/useOldLang';
|
||||||
import type { ISettings } from '../../types';
|
import type { ISettings } from '../../types';
|
||||||
@ -118,6 +120,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
} = message;
|
} = message;
|
||||||
const audio = contentAudio || getMessageWebPageAudio(message);
|
const audio = contentAudio || getMessageWebPageAudio(message);
|
||||||
const media = (voice || video || audio)!;
|
const media = (voice || video || audio)!;
|
||||||
|
const mediaSource = (voice || video);
|
||||||
const isVoice = Boolean(voice || video);
|
const isVoice = Boolean(voice || video);
|
||||||
const isSeeking = useRef<boolean>(false);
|
const isSeeking = useRef<boolean>(false);
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
@ -186,7 +189,7 @@ const Audio: FC<OwnProps> = ({
|
|||||||
|
|
||||||
const waveformCanvasRef = useWaveformCanvas(
|
const waveformCanvasRef = useWaveformCanvas(
|
||||||
theme,
|
theme,
|
||||||
voice,
|
mediaSource,
|
||||||
(isMediaUnread && !isOwn && !isReverse) ? 1 : playProgress,
|
(isMediaUnread && !isOwn && !isReverse) ? 1 : playProgress,
|
||||||
isOwn,
|
isOwn,
|
||||||
!noAvatars,
|
!noAvatars,
|
||||||
@ -462,10 +465,10 @@ const Audio: FC<OwnProps> = ({
|
|||||||
transferProgress,
|
transferProgress,
|
||||||
onDateClick ? handleDateClick : undefined,
|
onDateClick ? handleDateClick : undefined,
|
||||||
)}
|
)}
|
||||||
{origin === AudioOrigin.SharedMedia && (voice || video) && renderWithTitle()}
|
{origin === AudioOrigin.SharedMedia && mediaSource && renderWithTitle()}
|
||||||
{(origin === AudioOrigin.Inline || isInOneTimeModal) && voice && (
|
{(origin === AudioOrigin.Inline || isInOneTimeModal || isTranscribed) && mediaSource && (
|
||||||
renderVoice(
|
renderVoice(
|
||||||
voice,
|
mediaSource,
|
||||||
seekerRef,
|
seekerRef,
|
||||||
waveformCanvasRef,
|
waveformCanvasRef,
|
||||||
hasTtl ? reversePlayProgress : playProgress,
|
hasTtl ? reversePlayProgress : playProgress,
|
||||||
@ -553,7 +556,7 @@ function renderAudio(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function renderVoice(
|
function renderVoice(
|
||||||
voice: ApiVoice,
|
media: ApiVoice | ApiVideo,
|
||||||
seekerRef: React.Ref<HTMLDivElement>,
|
seekerRef: React.Ref<HTMLDivElement>,
|
||||||
waveformCanvasRef: React.Ref<HTMLCanvasElement>,
|
waveformCanvasRef: React.Ref<HTMLCanvasElement>,
|
||||||
playProgress: number,
|
playProgress: number,
|
||||||
@ -604,7 +607,7 @@ function renderVoice(
|
|||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
rx="6"
|
rx="6"
|
||||||
ry="6"
|
ry="6"
|
||||||
stroke="var(--accent-color)"
|
stroke="white"
|
||||||
stroke-dashoffset="1"
|
stroke-dashoffset="1"
|
||||||
stroke-dasharray="32,68"
|
stroke-dasharray="32,68"
|
||||||
/>
|
/>
|
||||||
@ -618,7 +621,7 @@ function renderVoice(
|
|||||||
dir="auto"
|
dir="auto"
|
||||||
>
|
>
|
||||||
{playProgress === 0 || playProgress === 1
|
{playProgress === 0 || playProgress === 1
|
||||||
? formatMediaDuration(voice.duration) : formatMediaDuration(voice.duration * playProgress)}
|
? formatMediaDuration(media!.duration) : formatMediaDuration(media!.duration * playProgress)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -626,7 +629,7 @@ function renderVoice(
|
|||||||
|
|
||||||
function useWaveformCanvas(
|
function useWaveformCanvas(
|
||||||
theme: ISettings['theme'],
|
theme: ISettings['theme'],
|
||||||
voice?: ApiVoice,
|
media?: ApiVoice | ApiVideo,
|
||||||
playProgress = 0,
|
playProgress = 0,
|
||||||
isOwn = false,
|
isOwn = false,
|
||||||
withAvatar = false,
|
withAvatar = false,
|
||||||
@ -637,11 +640,11 @@ function useWaveformCanvas(
|
|||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
const { data: spikes, peak } = useMemo(() => {
|
const { data: spikes, peak } = useMemo(() => {
|
||||||
if (!voice) {
|
if (!media) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { waveform, duration } = voice;
|
const { waveform, duration } = media;
|
||||||
if (!waveform) {
|
if (!waveform) {
|
||||||
return {
|
return {
|
||||||
data: new Array(Math.min(duration, MAX_EMPTY_WAVEFORM_POINTS)).fill(0),
|
data: new Array(Math.min(duration, MAX_EMPTY_WAVEFORM_POINTS)).fill(0),
|
||||||
@ -655,7 +658,7 @@ function useWaveformCanvas(
|
|||||||
const decodedWaveform = decodeWaveform(new Uint8Array(waveform));
|
const decodedWaveform = decodeWaveform(new Uint8Array(waveform));
|
||||||
|
|
||||||
return interpolateArray(decodedWaveform, spikesCount);
|
return interpolateArray(decodedWaveform, spikesCount);
|
||||||
}, [isMobile, voice, withAvatar]) || {};
|
}, [isMobile, media, withAvatar]) || {};
|
||||||
|
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
const canvas = canvasRef.current;
|
const canvas = canvasRef.current;
|
||||||
|
|||||||
@ -115,7 +115,6 @@ import {
|
|||||||
calculateDimensionsForMessageMedia,
|
calculateDimensionsForMessageMedia,
|
||||||
getStickerDimensions,
|
getStickerDimensions,
|
||||||
REM,
|
REM,
|
||||||
ROUND_VIDEO_DIMENSIONS_PX,
|
|
||||||
} from '../../common/helpers/mediaDimensions';
|
} from '../../common/helpers/mediaDimensions';
|
||||||
import { getPeerColorClass } from '../../common/helpers/peerColor';
|
import { getPeerColorClass } from '../../common/helpers/peerColor';
|
||||||
import renderText from '../../common/helpers/renderText';
|
import renderText from '../../common/helpers/renderText';
|
||||||
@ -520,9 +519,11 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const messageReplyInfo = getMessageReplyInfo(message);
|
const messageReplyInfo = getMessageReplyInfo(message);
|
||||||
const storyReplyInfo = getStoryReplyInfo(message);
|
const storyReplyInfo = getStoryReplyInfo(message);
|
||||||
|
|
||||||
|
const withVoiceTranscription = Boolean(!isTranscriptionHidden && (isTranscriptionError || transcribedText));
|
||||||
|
|
||||||
const hasStoryReply = Boolean(storyReplyInfo);
|
const hasStoryReply = Boolean(storyReplyInfo);
|
||||||
const hasThread = Boolean(repliesThreadInfo) && messageListType === 'thread';
|
const hasThread = Boolean(repliesThreadInfo) && messageListType === 'thread';
|
||||||
const isCustomShape = getMessageCustomShape(message);
|
const isCustomShape = !withVoiceTranscription && getMessageCustomShape(message);
|
||||||
const hasAnimatedEmoji = isCustomShape && (animatedEmoji || animatedCustomEmoji);
|
const hasAnimatedEmoji = isCustomShape && (animatedEmoji || animatedCustomEmoji);
|
||||||
const hasReactions = reactionMessage?.reactions && !areReactionsEmpty(reactionMessage.reactions);
|
const hasReactions = reactionMessage?.reactions && !areReactionsEmpty(reactionMessage.reactions);
|
||||||
const asForwarded = (
|
const asForwarded = (
|
||||||
@ -558,8 +559,6 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
&& forwardInfo.fromMessageId
|
&& forwardInfo.fromMessageId
|
||||||
));
|
));
|
||||||
|
|
||||||
const noUserColors = isOwn && !isCustomShape;
|
|
||||||
|
|
||||||
const hasFactCheck = Boolean(factCheck?.text);
|
const hasFactCheck = Boolean(factCheck?.text);
|
||||||
|
|
||||||
const hasForwardedCustomShape = asForwarded && isCustomShape;
|
const hasForwardedCustomShape = asForwarded && isCustomShape;
|
||||||
@ -575,7 +574,8 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const messageSender = canShowSender ? sender : undefined;
|
const messageSender = canShowSender ? sender : undefined;
|
||||||
const withVoiceTranscription = Boolean(!isTranscriptionHidden && (isTranscriptionError || transcribedText));
|
|
||||||
|
const noUserColors = isOwn && !isCustomShape;
|
||||||
|
|
||||||
const shouldPreferOriginSender = forwardInfo
|
const shouldPreferOriginSender = forwardInfo
|
||||||
&& (isChatWithSelf || isRepliesChat || isAnonymousForwards || !messageSender);
|
&& (isChatWithSelf || isRepliesChat || isAnonymousForwards || !messageSender);
|
||||||
@ -760,7 +760,7 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
const withQuickReactionButton = !isTouchScreen && !phoneCall && !isInSelectMode && defaultReaction
|
const withQuickReactionButton = !isTouchScreen && !phoneCall && !isInSelectMode && defaultReaction
|
||||||
&& !isInDocumentGroupNotLast && !isStoryMention && !hasTtl;
|
&& !isInDocumentGroupNotLast && !isStoryMention && !hasTtl;
|
||||||
|
|
||||||
const hasOutsideReactions = hasReactions
|
const hasOutsideReactions = !withVoiceTranscription && hasReactions
|
||||||
&& (isCustomShape || ((photo || video || storyData || (location?.mediaType === 'geo')) && !hasText));
|
&& (isCustomShape || ((photo || video || storyData || (location?.mediaType === 'geo')) && !hasText));
|
||||||
|
|
||||||
const contentClassName = buildContentClassName(message, album, {
|
const contentClassName = buildContentClassName(message, album, {
|
||||||
@ -904,20 +904,11 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
|
|
||||||
if (!isAlbum && (photo || video || invoice?.extendedMedia)) {
|
if (!isAlbum && (photo || video || invoice?.extendedMedia)) {
|
||||||
let width: number | undefined;
|
let width: number | undefined;
|
||||||
if (photo) {
|
if (photo || video) {
|
||||||
width = calculateMediaDimensions({
|
const media = (photo || video);
|
||||||
media: photo,
|
if (media && !isRoundVideo) {
|
||||||
isOwn,
|
|
||||||
asForwarded,
|
|
||||||
noAvatars,
|
|
||||||
isMobile,
|
|
||||||
}).width;
|
|
||||||
} else if (video) {
|
|
||||||
if (isRoundVideo) {
|
|
||||||
width = ROUND_VIDEO_DIMENSIONS_PX;
|
|
||||||
} else {
|
|
||||||
width = calculateMediaDimensions({
|
width = calculateMediaDimensions({
|
||||||
media: video,
|
media,
|
||||||
isOwn,
|
isOwn,
|
||||||
asForwarded,
|
asForwarded,
|
||||||
noAvatars,
|
noAvatars,
|
||||||
@ -1193,16 +1184,22 @@ const Message: FC<OwnProps & StateProps> = ({
|
|||||||
chatId={chatId}
|
chatId={chatId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isAlbum && isRoundVideo && (
|
{!isAlbum && isRoundVideo && !withVoiceTranscription && (
|
||||||
<RoundVideo
|
<RoundVideo
|
||||||
message={message}
|
message={message}
|
||||||
observeIntersection={observeIntersectionForLoading}
|
observeIntersection={observeIntersectionForLoading}
|
||||||
canAutoLoad={canAutoLoadMedia}
|
canAutoLoad={canAutoLoadMedia}
|
||||||
isDownloading={isDownloading}
|
isDownloading={isDownloading}
|
||||||
onReadMedia={shouldReadMedia ? handleReadMedia : undefined}
|
onReadMedia={shouldReadMedia ? handleReadMedia : undefined}
|
||||||
|
onHideTranscription={setTranscriptionHidden}
|
||||||
|
isTranscriptionError={isTranscriptionError}
|
||||||
|
isTranscribed={Boolean(transcribedText)}
|
||||||
|
canTranscribe={canTranscribeVoice && !hasTtl}
|
||||||
|
isTranscriptionHidden={isTranscriptionHidden}
|
||||||
|
isTranscribing={isTranscribing}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(audio || voice) && (
|
{(audio || voice || withVoiceTranscription) && (
|
||||||
<Audio
|
<Audio
|
||||||
theme={theme}
|
theme={theme}
|
||||||
message={message}
|
message={message}
|
||||||
|
|||||||
@ -102,7 +102,7 @@
|
|||||||
right: 0.25rem;
|
right: 0.25rem;
|
||||||
left: auto;
|
left: auto;
|
||||||
height: 1.125rem;
|
height: 1.125rem;
|
||||||
padding: 0 0.3125rem 0 0.375rem;
|
padding: 0 0.3125rem 0 0.6875rem;
|
||||||
|
|
||||||
.MessageOutgoingStatus .icon {
|
.MessageOutgoingStatus .icon {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
|||||||
@ -67,4 +67,31 @@
|
|||||||
z-index: var(--z-badge);
|
z-index: var(--z-badge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-svg {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
width: 1.875rem;
|
||||||
|
height: 1.3125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-rect {
|
||||||
|
animation: 1s linear loader-rectangle infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.transcribe-button {
|
||||||
|
position: absolute;
|
||||||
|
width: 1.875rem;
|
||||||
|
height: 1.3125rem;
|
||||||
|
bottom: 1.625rem;
|
||||||
|
right: 0.1875rem;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background: var(--pattern-color);
|
||||||
|
&:hover {
|
||||||
|
background: var(--pattern-color) !important;
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { FC } from '../../../lib/teact/teact';
|
import type { FC } from '../../../lib/teact/teact';
|
||||||
import React, {
|
import React, {
|
||||||
useEffect, useLayoutEffect, useRef, useSignal, useState,
|
useEffect, useLayoutEffect,
|
||||||
|
useRef, useSignal, useState,
|
||||||
} from '../../../lib/teact/teact';
|
} from '../../../lib/teact/teact';
|
||||||
import { getActions } from '../../../global';
|
import { getActions } from '../../../global';
|
||||||
|
|
||||||
@ -47,6 +48,12 @@ type OwnProps = {
|
|||||||
observeIntersection?: ObserveFn;
|
observeIntersection?: ObserveFn;
|
||||||
onStop?: NoneToVoidFunction;
|
onStop?: NoneToVoidFunction;
|
||||||
onReadMedia?: NoneToVoidFunction;
|
onReadMedia?: NoneToVoidFunction;
|
||||||
|
onHideTranscription?: (isHidden: boolean) => void;
|
||||||
|
isTranscriptionError?: boolean;
|
||||||
|
canTranscribe?: boolean;
|
||||||
|
isTranscribed?: boolean;
|
||||||
|
isTranscriptionHidden?: boolean;
|
||||||
|
isTranscribing?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const PROGRESS_CENTER = ROUND_VIDEO_DIMENSIONS_PX / 2;
|
const PROGRESS_CENTER = ROUND_VIDEO_DIMENSIONS_PX / 2;
|
||||||
@ -65,6 +72,12 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
observeIntersection,
|
observeIntersection,
|
||||||
onStop,
|
onStop,
|
||||||
onReadMedia,
|
onReadMedia,
|
||||||
|
isTranscriptionError,
|
||||||
|
isTranscribed,
|
||||||
|
canTranscribe,
|
||||||
|
onHideTranscription,
|
||||||
|
isTranscriptionHidden,
|
||||||
|
isTranscribing,
|
||||||
}) => {
|
}) => {
|
||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
@ -73,12 +86,12 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
// eslint-disable-next-line no-null/no-null
|
// eslint-disable-next-line no-null/no-null
|
||||||
const circleRef = useRef<SVGCircleElement>(null);
|
const circleRef = useRef<SVGCircleElement>(null);
|
||||||
|
|
||||||
const video = message.content.video!;
|
const { cancelMediaDownload, openOneTimeMediaModal, transcribeAudio } = getActions();
|
||||||
|
|
||||||
const { cancelMediaDownload, openOneTimeMediaModal } = getActions();
|
|
||||||
|
|
||||||
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
const isIntersecting = useIsIntersecting(ref, observeIntersection);
|
||||||
|
|
||||||
|
const video = message.content.video!;
|
||||||
|
|
||||||
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
|
const [isLoadAllowed, setIsLoadAllowed] = useState(canAutoLoad);
|
||||||
const shouldLoad = Boolean(isLoadAllowed && isIntersecting);
|
const shouldLoad = Boolean(isLoadAllowed && isIntersecting);
|
||||||
const { mediaData, loadProgress } = useMediaWithLoadProgress(
|
const { mediaData, loadProgress } = useMediaWithLoadProgress(
|
||||||
@ -181,7 +194,11 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
togglePlaying();
|
togglePlaying();
|
||||||
}, [isInOneTimeModal]);
|
}, [isInOneTimeModal]);
|
||||||
|
|
||||||
const handleClick = useLastCallback(() => {
|
const handleClick = useLastCallback((event) => {
|
||||||
|
if (event.target.closest('.transcribe-button')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!mediaData) {
|
if (!mediaData) {
|
||||||
setIsLoadAllowed((isAllowed) => !isAllowed);
|
setIsLoadAllowed((isAllowed) => !isAllowed);
|
||||||
|
|
||||||
@ -207,6 +224,10 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
setProgress(playerEl.currentTime / playerEl.duration);
|
setProgress(playerEl.currentTime / playerEl.duration);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const handleTranscribe = useLastCallback(() => {
|
||||||
|
transcribeAudio({ chatId: message.chatId, messageId: message.id });
|
||||||
|
});
|
||||||
|
|
||||||
function renderPlayWrapper() {
|
function renderPlayWrapper() {
|
||||||
return (
|
return (
|
||||||
<div className="play-wrapper">
|
<div className="play-wrapper">
|
||||||
@ -224,6 +245,14 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleButtonClick = useLastCallback(() => {
|
||||||
|
if ((isTranscribed || isTranscriptionError) && onHideTranscription) {
|
||||||
|
onHideTranscription(!isTranscriptionHidden);
|
||||||
|
} else if (!isTranscribing) {
|
||||||
|
handleTranscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref}
|
ref={ref}
|
||||||
@ -298,6 +327,31 @@ const RoundVideo: FC<OwnProps> = ({
|
|||||||
{(!isActivated || playerRef.current!.paused) && <Icon name="muted" />}
|
{(!isActivated || playerRef.current!.paused) && <Icon name="muted" />}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
{canTranscribe && (
|
||||||
|
<Button
|
||||||
|
onClick={handleButtonClick}
|
||||||
|
className="transcribe-button"
|
||||||
|
>
|
||||||
|
{isTranscribed || isTranscriptionError ? <Icon name="down" /> : <Icon name="transcribe" />}
|
||||||
|
{isTranscribing && (
|
||||||
|
<svg viewBox="0 0 32 24" className="loading-svg">
|
||||||
|
<rect
|
||||||
|
className="loading-rect"
|
||||||
|
fill="transparent"
|
||||||
|
width="32"
|
||||||
|
height="24"
|
||||||
|
stroke-width="3"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
rx="6"
|
||||||
|
ry="6"
|
||||||
|
stroke="white"
|
||||||
|
stroke-dashoffset="1"
|
||||||
|
stroke-dasharray="32,68"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -930,6 +930,12 @@
|
|||||||
.Message:not(.own) & {
|
.Message:not(.own) & {
|
||||||
--hover-color: var(--color-reply-hover);
|
--hover-color: var(--color-reply-hover);
|
||||||
--active-color: var(--color-reply-active);
|
--active-color: var(--color-reply-active);
|
||||||
|
|
||||||
|
&.has-subheader {
|
||||||
|
.MessageMeta {
|
||||||
|
right: 5.6875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.Message.own & {
|
.Message.own & {
|
||||||
@ -949,9 +955,20 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.has-subheader {
|
||||||
|
.RoundVideo .transcribe-button {
|
||||||
|
right: 0.8125rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.message-content.voice,
|
||||||
|
.message-content.video {
|
||||||
|
width: min-content;
|
||||||
|
}
|
||||||
|
|
||||||
.forwarded-message {
|
.forwarded-message {
|
||||||
.message-content.contact &,
|
.message-content.contact &,
|
||||||
.message-content.poll &,
|
.message-content.poll &,
|
||||||
|
|||||||
@ -98,6 +98,7 @@ export function buildContentClassName(
|
|||||||
|
|
||||||
if (isCustomShape) {
|
if (isCustomShape) {
|
||||||
classNames.push('custom-shape');
|
classNames.push('custom-shape');
|
||||||
|
|
||||||
if (isRoundVideo) {
|
if (isRoundVideo) {
|
||||||
classNames.push('round');
|
classNames.push('round');
|
||||||
}
|
}
|
||||||
@ -106,8 +107,10 @@ export function buildContentClassName(
|
|||||||
classNames.push('has-comment-counter');
|
classNames.push('has-comment-counter');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isMedia) {
|
if (isMedia && !withVoiceTranscription) {
|
||||||
classNames.push('media');
|
classNames.push('media');
|
||||||
|
} else if (video) {
|
||||||
|
classNames.push('video');
|
||||||
} else if (audio) {
|
} else if (audio) {
|
||||||
classNames.push('audio');
|
classNames.push('audio');
|
||||||
} else if (voice) {
|
} else if (voice) {
|
||||||
@ -178,7 +181,8 @@ export function buildContentClassName(
|
|||||||
classNames.push('has-background');
|
classNames.push('has-background');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasSubheader || asForwarded || isViaBot || !isMediaWithNoText || forceSenderName || hasFactCheck) {
|
if (hasSubheader || asForwarded || isViaBot || !isMediaWithNoText
|
||||||
|
|| forceSenderName || hasFactCheck || withVoiceTranscription) {
|
||||||
classNames.push('has-solid-background');
|
classNames.push('has-solid-background');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
6
src/util/generateWaveform.ts
Normal file
6
src/util/generateWaveform.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import { MAX_EMPTY_WAVEFORM_POINTS } from '../components/common/helpers/waveform';
|
||||||
|
|
||||||
|
export function generateWaveform(duration: number) {
|
||||||
|
const arr = Math.min(Math.round(duration), MAX_EMPTY_WAVEFORM_POINTS);
|
||||||
|
return Array.from({ length: arr }, () => Math.floor(Math.random() * 256));
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user