send "recording" notifications when audio is being recorded

This commit is contained in:
or-else
2022-12-10 18:17:03 -08:00
parent 6aca7e527e
commit 692e6e1d8f
14 changed files with 74 additions and 35 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
import { PACKAGE_VERSION } from './version.js'; import { PACKAGE_VERSION } from './version.js';
// Name of this application, used in the User-Agent. // Name of this application, used in the User-Agent.
export const APP_NAME = 'TinodeWeb/' + (PACKAGE_VERSION || '0.17'); export const APP_NAME = 'TinodeWeb/' + (PACKAGE_VERSION || '0.21');
// API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own // API key. Use https://github.com/tinode/chat/tree/master/keygen to generate your own
export const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K'; export const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K';
+7 -2
View File
@@ -743,10 +743,14 @@ class MessagesView extends React.Component {
this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic); this.props.onChangePermissions(this.state.topic, DEFAULT_P2P_ACCESS_MODE, this.state.topic);
} }
sendKeyPress() { sendKeyPress(audio) {
const topic = this.props.tinode.getTopic(this.state.topic); const topic = this.props.tinode.getTopic(this.state.topic);
if (topic.isSubscribed()) { if (topic.isSubscribed()) {
topic.noteKeyPress(); if (audio) {
topic.noteRecording(true);
} else {
topic.noteKeyPress();
}
} }
} }
@@ -1335,6 +1339,7 @@ class MessagesView extends React.Component {
reply={this.state.reply} reply={this.state.reply}
initMessage={this.state.contentToEdit} initMessage={this.state.contentToEdit}
onKeyPress={this.sendKeyPress} onKeyPress={this.sendKeyPress}
onRecordingProgress={this.sendKeyPress}
onSendMessage={this.sendMessage} onSendMessage={this.sendMessage}
onAttachFile={this.props.forwardMessage ? null : this.handleAttachFile} onAttachFile={this.props.forwardMessage ? null : this.handleAttachFile}
onAttachImage={this.props.forwardMessage ? null : this.handleAttachImage} onAttachImage={this.props.forwardMessage ? null : this.handleAttachImage}
+16 -3
View File
@@ -10,7 +10,7 @@ import fixWebmDuration from 'webm-duration-fix';
import { intArrayToBase64 } from '../lib/blob-helpers.js' import { intArrayToBase64 } from '../lib/blob-helpers.js'
import { secondsToTime } from '../lib/strformat'; import { secondsToTime } from '../lib/strformat';
import { MAX_DURATION, MIN_DURATION } from '../config.js'; import { KEYPRESS_DELAY, MAX_DURATION, MIN_DURATION } from '../config.js';
// FFT resolution. // FFT resolution.
const BUFFER_SIZE = 256; const BUFFER_SIZE = 256;
@@ -86,6 +86,9 @@ class AudioRecorder extends React.PureComponent {
this.startedOn = null; this.startedOn = null;
this.viewBuffer = []; this.viewBuffer = [];
this.canvasRef = React.createRef(); this.canvasRef = React.createRef();
// Timestamp for sending "recording" notifications.
this.recordingTimestamp = 0;
} }
componentDidMount() { componentDidMount() {
@@ -192,6 +195,13 @@ class AudioRecorder extends React.PureComponent {
} }
// Actually draw the bars on canvas. // Actually draw the bars on canvas.
this.canvasContext.stroke(); this.canvasContext.stroke();
// Send notification, if needed.
const now = new Date().getTime();
if (now - this.recordingTimestamp > KEYPRESS_DELAY) {
this.props.onRecordingProgress();
this.recordingTimestamp = now;
}
} }
drawFrame(); drawFrame();
@@ -262,7 +272,7 @@ class AudioRecorder extends React.PureComponent {
this.mediaRecorder.onstop = _ => { this.mediaRecorder.onstop = _ => {
if (this.durationMillis > MIN_DURATION) { if (this.durationMillis > MIN_DURATION) {
this.getRecording(this.mediaRecorder.mimeType, this.durationMillis) this.getRecording(this.mediaRecorder.mimeType)
.then(result => this.props.onFinished(result.url, result.preview, this.durationMillis)); .then(result => this.props.onFinished(result.url, result.preview, this.durationMillis));
} else { } else {
this.props.onDeleted(); this.props.onDeleted();
@@ -289,11 +299,14 @@ class AudioRecorder extends React.PureComponent {
this.startedOn = Date.now(); this.startedOn = Date.now();
this.mediaRecorder.start(); this.mediaRecorder.start();
this.visualize(); this.visualize();
this.props.onRecordingProgress();
this.recordingTimestamp = this.startedOn;
} }
// Obtain data in a form sutable for sending or playing back. // Obtain data in a form sutable for sending or playing back.
// If duration is valid, apply fix for Chrome's WebM duration bug. // If duration is valid, apply fix for Chrome's WebM duration bug.
getRecording(mimeType, duration) { getRecording(mimeType) {
mimeType = mimeType || AUDIO_MIME_TYPE; mimeType = mimeType || AUDIO_MIME_TYPE;
let blob = new Blob(this.audioChunks, {type: mimeType}); let blob = new Blob(this.audioChunks, {type: mimeType});
return fixWebmDuration(blob, mimeType) return fixWebmDuration(blob, mimeType)
+8 -7
View File
@@ -68,10 +68,11 @@ class SendMessage extends React.PureComponent {
message: '', message: '',
audioRec: false, audioRec: false,
audioAvailable: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia), audioAvailable: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia),
// Make initial keypress time as if it happened 5001 milliseconds in the past.
keypressTimestamp: new Date().getTime() - KEYPRESS_DELAY - 1
}; };
// Timestamp when the previous key press was sent to the server.
this.keypressTimestamp = 0;
this.handlePasteEvent = this.handlePasteEvent.bind(this); this.handlePasteEvent = this.handlePasteEvent.bind(this);
this.handleAttachImage = this.handleAttachImage.bind(this); this.handleAttachImage = this.handleAttachImage.bind(this);
this.handleAttachFile = this.handleAttachFile.bind(this); this.handleAttachFile = this.handleAttachFile.bind(this);
@@ -194,15 +195,14 @@ class SendMessage extends React.PureComponent {
} }
handleMessageTyping(e) { handleMessageTyping(e) {
const newState = {message: e.target.value}; this.setState({message: e.target.value});
if (this.props.onKeyPress) { if (this.props.onKeyPress) {
const now = new Date().getTime(); const now = new Date().getTime();
if (now - this.state.keypressTimestamp > KEYPRESS_DELAY) { if (now - this.keypressTimestamp > KEYPRESS_DELAY) {
this.props.onKeyPress(); this.props.onKeyPress();
newState.keypressTimestamp = now; this.keypressTimestamp = now;
} }
} }
this.setState(newState);
} }
handleQuoteClick(e) { handleQuoteClick(e) {
@@ -255,6 +255,7 @@ class SendMessage extends React.PureComponent {
(this.state.audioRec ? (this.state.audioRec ?
(<Suspense fallback={<div>Loading...</div>}> (<Suspense fallback={<div>Loading...</div>}>
<AudioRecorder <AudioRecorder
onRecordingProgress={_ => this.props.onKeyPress(true)}
onDeleted={_ => this.setState({audioRec: false})} onDeleted={_ => this.setState({audioRec: false})}
onFinished={this.handleAttachAudio}/> onFinished={this.handleAttachAudio}/>
</Suspense>) : </Suspense>) :
@@ -262,7 +263,7 @@ class SendMessage extends React.PureComponent {
value={this.state.message} value={this.state.message}
onChange={this.handleMessageTyping} onChange={this.handleMessageTyping}
onKeyPress={this.handleKeyPress} onKeyPress={this.handleKeyPress}
ref={(ref) => {this.messageEditArea = ref;}} ref={ref => {this.messageEditArea = ref;}}
autoFocus />)} autoFocus />)}
{this.state.message || !audioEnabled ? {this.state.message || !audioEnabled ?
<a href="#" onClick={this.handleSend} title={formatMessage(messages.icon_title_send)}> <a href="#" onClick={this.handleSend} title={formatMessage(messages.icon_title_send)}>
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+15 -10
View File
@@ -2196,7 +2196,7 @@ __webpack_require__.r(__webpack_exports__);
/* harmony export */ }); /* harmony export */ });
/* harmony import */ var _version_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./version.js */ "./src/version.js"); /* harmony import */ var _version_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./version.js */ "./src/version.js");
const APP_NAME = 'TinodeWeb/' + (_version_js__WEBPACK_IMPORTED_MODULE_0__.PACKAGE_VERSION || '0.17'); const APP_NAME = 'TinodeWeb/' + (_version_js__WEBPACK_IMPORTED_MODULE_0__.PACKAGE_VERSION || '0.21');
const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K'; const API_KEY = 'AQEAAAABAAD_rAp4DJh05a1HAwFT3A6K';
const KNOWN_HOSTS = { const KNOWN_HOSTS = {
hosted: 'web.tinode.co', hosted: 'web.tinode.co',
@@ -5984,10 +5984,14 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
e.preventDefault(); e.preventDefault();
this.props.onChangePermissions(this.state.topic, _config_js__WEBPACK_IMPORTED_MODULE_16__.DEFAULT_P2P_ACCESS_MODE, this.state.topic); this.props.onChangePermissions(this.state.topic, _config_js__WEBPACK_IMPORTED_MODULE_16__.DEFAULT_P2P_ACCESS_MODE, this.state.topic);
} }
sendKeyPress() { sendKeyPress(audio) {
const topic = this.props.tinode.getTopic(this.state.topic); const topic = this.props.tinode.getTopic(this.state.topic);
if (topic.isSubscribed()) { if (topic.isSubscribed()) {
topic.noteKeyPress(); if (audio) {
topic.noteRecording(true);
} else {
topic.noteKeyPress();
}
} }
} }
sendMessage(msg, uploadCompletionPromise, uploader) { sendMessage(msg, uploadCompletionPromise, uploader) {
@@ -6527,6 +6531,7 @@ class MessagesView extends (react__WEBPACK_IMPORTED_MODULE_0___default().Compone
reply: this.state.reply, reply: this.state.reply,
initMessage: this.state.contentToEdit, initMessage: this.state.contentToEdit,
onKeyPress: this.sendKeyPress, onKeyPress: this.sendKeyPress,
onRecordingProgress: this.sendKeyPress,
onSendMessage: this.sendMessage, onSendMessage: this.sendMessage,
onAttachFile: this.props.forwardMessage ? null : this.handleAttachFile, onAttachFile: this.props.forwardMessage ? null : this.handleAttachFile,
onAttachImage: this.props.forwardMessage ? null : this.handleAttachImage, onAttachImage: this.props.forwardMessage ? null : this.handleAttachImage,
@@ -14678,9 +14683,9 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
quote: null, quote: null,
message: '', message: '',
audioRec: false, audioRec: false,
audioAvailable: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia), audioAvailable: !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
keypressTimestamp: new Date().getTime() - _config_js__WEBPACK_IMPORTED_MODULE_3__.KEYPRESS_DELAY - 1
}; };
this.keypressTimestamp = 0;
this.handlePasteEvent = this.handlePasteEvent.bind(this); this.handlePasteEvent = this.handlePasteEvent.bind(this);
this.handleAttachImage = this.handleAttachImage.bind(this); this.handleAttachImage = this.handleAttachImage.bind(this);
this.handleAttachFile = this.handleAttachFile.bind(this); this.handleAttachFile = this.handleAttachFile.bind(this);
@@ -14790,17 +14795,16 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
} }
} }
handleMessageTyping(e) { handleMessageTyping(e) {
const newState = { this.setState({
message: e.target.value message: e.target.value
}; });
if (this.props.onKeyPress) { if (this.props.onKeyPress) {
const now = new Date().getTime(); const now = new Date().getTime();
if (now - this.state.keypressTimestamp > _config_js__WEBPACK_IMPORTED_MODULE_3__.KEYPRESS_DELAY) { if (now - this.keypressTimestamp > _config_js__WEBPACK_IMPORTED_MODULE_3__.KEYPRESS_DELAY) {
this.props.onKeyPress(); this.props.onKeyPress();
newState.keypressTimestamp = now; this.keypressTimestamp = now;
} }
} }
this.setState(newState);
} }
handleQuoteClick(e) { handleQuoteClick(e) {
e.preventDefault(); e.preventDefault();
@@ -14857,6 +14861,7 @@ class SendMessage extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureComp
}) : this.state.audioRec ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react__WEBPACK_IMPORTED_MODULE_0__.Suspense, { }) : this.state.audioRec ? react__WEBPACK_IMPORTED_MODULE_0___default().createElement(react__WEBPACK_IMPORTED_MODULE_0__.Suspense, {
fallback: react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, "Loading...") fallback: react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", null, "Loading...")
}, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(AudioRecorder, { }, react__WEBPACK_IMPORTED_MODULE_0___default().createElement(AudioRecorder, {
onRecordingProgress: _ => this.props.onKeyPress(true),
onDeleted: _ => this.setState({ onDeleted: _ => this.setState({
audioRec: false audioRec: false
}), }),
+1 -1
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+10 -2
View File
@@ -93,6 +93,7 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
this.startedOn = null; this.startedOn = null;
this.viewBuffer = []; this.viewBuffer = [];
this.canvasRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef(); this.canvasRef = react__WEBPACK_IMPORTED_MODULE_0___default().createRef();
this.recordingTimestamp = 0;
} }
componentDidMount() { componentDidMount() {
this.stream = null; this.stream = null;
@@ -174,6 +175,11 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
this.canvasContext.lineTo(x, height * 0.5 + y * 0.5); this.canvasContext.lineTo(x, height * 0.5 + y * 0.5);
} }
this.canvasContext.stroke(); this.canvasContext.stroke();
const now = new Date().getTime();
if (now - this.recordingTimestamp > _config_js__WEBPACK_IMPORTED_MODULE_6__.KEYPRESS_DELAY) {
this.props.onRecordingProgress();
this.recordingTimestamp = now;
}
}; };
drawFrame(); drawFrame();
} }
@@ -242,7 +248,7 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
this.audioInput.connect(this.analyser); this.audioInput.connect(this.analyser);
this.mediaRecorder.onstop = _ => { this.mediaRecorder.onstop = _ => {
if (this.durationMillis > _config_js__WEBPACK_IMPORTED_MODULE_6__.MIN_DURATION) { if (this.durationMillis > _config_js__WEBPACK_IMPORTED_MODULE_6__.MIN_DURATION) {
this.getRecording(this.mediaRecorder.mimeType, this.durationMillis).then(result => this.props.onFinished(result.url, result.preview, this.durationMillis)); this.getRecording(this.mediaRecorder.mimeType).then(result => this.props.onFinished(result.url, result.preview, this.durationMillis));
} else { } else {
this.props.onDeleted(); this.props.onDeleted();
} }
@@ -265,8 +271,10 @@ class AudioRecorder extends (react__WEBPACK_IMPORTED_MODULE_0___default().PureCo
this.startedOn = Date.now(); this.startedOn = Date.now();
this.mediaRecorder.start(); this.mediaRecorder.start();
this.visualize(); this.visualize();
this.props.onRecordingProgress();
this.recordingTimestamp = this.startedOn;
} }
getRecording(mimeType, duration) { getRecording(mimeType) {
mimeType = mimeType || AUDIO_MIME_TYPE; mimeType = mimeType || AUDIO_MIME_TYPE;
let blob = new Blob(this.audioChunks, { let blob = new Blob(this.audioChunks, {
type: mimeType type: mimeType
File diff suppressed because one or more lines are too long
+10 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long