wip: adding support for video recordings

This commit is contained in:
or-else
2022-12-12 16:48:15 -08:00
parent 0d33c0f0a8
commit a8d7e6be50
10 changed files with 441 additions and 63 deletions
+108 -8
View File
@@ -18,6 +18,7 @@ import LoadSpinner from '../widgets/load-spinner.jsx';
import LogoView from './logo-view.jsx';
import MetaMessage from '../widgets/meta-message.jsx';
import SendMessage from '../widgets/send-message.jsx';
import VideoPreview from '../widgets/video-preview.jsx';
import { DEFAULT_P2P_ACCESS_MODE, EDIT_PREVIEW_LENGTH, IMAGE_PREVIEW_DIM, KEYPRESS_DELAY,
MESSAGES_PAGE, MAX_EXTERN_ATTACHMENT_SIZE, MAX_IMAGE_DIM, MAX_INBAND_ATTACHMENT_SIZE,
@@ -104,6 +105,7 @@ class MessagesView extends React.Component {
this.sendMessage = this.sendMessage.bind(this);
this.retrySend = this.retrySend.bind(this);
this.sendImageAttachment = this.sendImageAttachment.bind(this);
this.sendVideoAttachment = this.sendVideoAttachment.bind(this);
this.sendFileAttachment = this.sendFileAttachment.bind(this);
this.sendAudioAttachment = this.sendAudioAttachment.bind(this);
this.sendKeyPress = this.sendKeyPress.bind(this);
@@ -123,7 +125,7 @@ class MessagesView extends React.Component {
this.handleNewChatAcceptance = this.handleNewChatAcceptance.bind(this);
this.handleEnablePeer = this.handleEnablePeer.bind(this);
this.handleAttachFile = this.handleAttachFile.bind(this);
this.handleAttachImage = this.handleAttachImage.bind(this);
this.handleAttachImageOrVideo = this.handleAttachImageOrVideo.bind(this);
this.handleCancelUpload = this.handleCancelUpload.bind(this);
this.postReadNotification = this.postReadNotification.bind(this);
this.clearNotificationQueue = this.clearNotificationQueue.bind(this);
@@ -680,7 +682,10 @@ class MessagesView extends React.Component {
if (this.state.imagePreview && this.state.imagePreview.url) {
URL.revokeObjectURL(this.state.imagePreview.url);
}
this.setState({ imagePostview: null, imagePreview: null, docPreview: null });
if (this.state.videoPreview && this.state.videoPreview.url) {
URL.revokeObjectURL(this.state.videoPreview.url);
}
this.setState({ imagePostview: null, imagePreview: null, docPreview: null, videoPreview: null });
}
handleFormResponse(action, text, data) {
@@ -855,7 +860,7 @@ class MessagesView extends React.Component {
this.props.onCallHangup(topic, seq);
}
// sendImageAttachment sends the image bits inband as Drafty message.
// sendImageAttachment sends the image bits as Drafty message.
sendImageAttachment(caption, blob) {
const mime = this.state.imagePreview.mime;
const width = this.state.imagePreview.width;
@@ -897,10 +902,10 @@ class MessagesView extends React.Component {
this.sendMessage(msg, uploadCompletionPromise, uploader);
})
.catch(err => this.props.onError(err, 'err'));
return;
return;
}
// Upload the image if it's too big to be send inband.
// Send the image inband if it's not too big.
blobToBase64(blob)
.then(b64 => {
let msg = Drafty.insertImage(null, 0, {
@@ -919,10 +924,94 @@ class MessagesView extends React.Component {
});
}
// handleAttachImage method is called when [Attach image] button is clicked: launch image preview.
handleAttachImage(file) {
// sendVideoAttachment sends the video bits as Drafty message.
sendVideoAttachment(caption, videoBlob, previewBlob, params) {
const mime = params.mime;
const width = params.width;
const height = params.height;
const fname = params.name;
const duration = params.duration;
// Server-provided limit reduced for base64 encoding and overhead.
const maxInbandAttachmentSize = (this.props.tinode.getServerParam('maxMessageSize',
MAX_INBAND_ATTACHMENT_SIZE) * 0.75 - 1024) | 0;
if (videoBlob.size > maxInbandAttachmentSize) {
console.log("1");
// Too large to send inband - uploading out of band and sending as a link.
const uploader = this.props.tinode.getLargeFileHelper();
if (!uploader) {
this.props.onError(this.props.intl.formatMessage(messages.cannot_initiate_upload));
return;
}
const uploadCompletionPromise = uploader.upload(videoBlob);
console.log("2");
// Make small preview to show while uploading.
imageScaled(previewBlob, IMAGE_PREVIEW_DIM, IMAGE_PREVIEW_DIM, -1, false)
// Convert tiny image into base64 for serialization and previewing.
.then(scaled => blobToBase64(scaled.blob))
.then(b64 => {
console.log("3");
let msg = Drafty.insertVideo(null, 0, {
mime: mime,
_tempPreview: b64.bits, // This preview will not be serialized.
width: width,
height: height,
duration: duration,
filename: fname,
size: videoBlob.size,
urlPromise: uploadCompletionPromise
});
console.log("4");
if (caption) {
msg = Drafty.appendLineBreak(msg);
msg = Drafty.append(msg, Drafty.parse(caption));
}
// Pass data and the uploader to the TinodeWeb.
this.sendMessage(msg, uploadCompletionPromise, uploader);
console.log("5");
})
.catch(err => /* this.props.onError(err, 'err') */ console.log(err));
return;
}
console.log("6");
// Send the video inband if it's small enough.
blobToBase64(videoBlob)
.then(b64 => {
let msg = Drafty.insertVideo(null, 0, {
mime: b64.mime,
preview: b64.bits, // Serializable preview
width: width,
height: height,
duration: duration,
filename: fname,
size: blob.size
});
if (caption) {
msg = Drafty.appendLineBreak(msg);
msg = Drafty.append(msg, Drafty.parse(caption));
}
this.sendMessage(msg);
});
}
// handleAttachImageOrVideo method is called when [Attach image or video] button is clicked: launch image or video preview.
handleAttachImageOrVideo(file) {
const maxExternAttachmentSize = this.props.tinode.getServerParam('maxFileUploadSize', MAX_EXTERN_ATTACHMENT_SIZE);
if (file.type.startsWith('video/')) {
this.setState({videoPreview: {
url: URL.createObjectURL(file),
blob: file,
name: file.name,
size: file.size,
mime: file.type
}});
return;
}
// Get image dimensions and size, optionally scale it down.
imageScaled(file, MAX_IMAGE_DIM, MAX_IMAGE_DIM, maxExternAttachmentSize, false)
.then(scaled => {
@@ -1089,6 +1178,17 @@ class MessagesView extends React.Component {
onClose={this.handleClosePreview}
onSendMessage={this.sendImageAttachment} />
);
} else if (this.state.videoPreview) {
// Preview video.
component2 = (
<VideoPreview
content={this.state.videoPreview}
tinode={this.props.tinode}
reply={this.state.reply}
onCancelReply={this.handleCancelReply}
onClose={this.handleClosePreview}
onSendMessage={this.sendVideoAttachment} />
);
} else if (this.state.imagePostview) {
// Expand received image.
component2 = (
@@ -1341,7 +1441,7 @@ class MessagesView extends React.Component {
onRecordingProgress={this.sendKeyPress}
onSendMessage={this.sendMessage}
onAttachFile={this.props.forwardMessage ? null : this.handleAttachFile}
onAttachImage={this.props.forwardMessage ? null : this.handleAttachImage}
onAttachImage={this.props.forwardMessage ? null : this.handleAttachImageOrVideo}
onAttachAudio={this.props.forwardMessage ? null : this.sendAudioAttachment}
onError={this.props.onError}
onQuoteClick={this.handleQuoteClick}
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react';
import { FormattedMessage } from 'react-intl';
import SendMessage from '../widgets/send-message.jsx';
import SendMessage from './send-message.jsx';
import { REM_SIZE } from '../config.js';
import { fitImageSize } from '../lib/blob-helpers.js';
+1 -1
View File
@@ -277,7 +277,7 @@ class SendMessage extends React.PureComponent {
}
<input type="file" ref={ref => {this.attachFile = ref}}
onChange={this.handleAttachFile} style={{display: 'none'}} />
<input type="file" ref={ref => {this.attachImage = ref}} accept="image/*"
<input type="file" ref={ref => {this.attachImage = ref}} accept="image/*, video/*"
onChange={this.handleAttachImage} style={{display: 'none'}} />
</>
:
+60
View File
@@ -0,0 +1,60 @@
import React from 'react';
import SendMessage from './send-message.jsx';
export default class VideoPreview extends React.PureComponent {
constructor(props) {
super(props);
this.videoRef = React.createRef();
this.handleSendVideo = this.handleSendVideo.bind(this);
}
handleSendVideo(caption) {
this.props.onClose();
const params = {
width: this.videoRef.current.videoWidth,
height: this.videoRef.current.videoHeight,
duration: (this.videoRef.current.duration * 1000) | 0,
mime: this.props.content.mime,
name: this.props.content.name
}
// Capture screen from a video.
const canvas = document.createElement('canvas');
canvas.width = params.width;
canvas.height = params.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(this.videoRef.current, 0, 0, canvas.width, canvas.height);
ctx.canvas.toBlob(
preview => this.props.onSendMessage(caption, this.props.content.blob, preview, params),
'image/jpeg', 0.75
);
}
render() {
if (!this.props.content) {
return null;
}
return (
<div id="image-preview">
<div id="image-preview-caption-panel">
<span>{this.props.content.name}</span>
<a href="#" onClick={e => {e.preventDefault(); this.props.onClose();}}><i className="material-icons gray">close</i></a>
</div>
<div id="image-preview-container">
<video controls ref={this.videoRef} src={this.props.content.url} className="image-preview" alt={this.props.content.name} />
</div>
<SendMessage
messagePrompt="add_image_caption"
acceptBlank={true}
tinode={this.props.tinode}
reply={this.props.reply}
onCancelReply={this.props.onCancelReply}
onSendMessage={this.handleSendVideo}
onError={this.props.onError} />
</div>
);
}
};