Microphone timer

pull/1102/head
Vincent 6 years ago
parent 8d8abf1e43
commit 2dc5885c88

@ -876,6 +876,10 @@
"description": "description":
"Shown in toast if user clicks on quote references messages not loaded in view, but in database" "Shown in toast if user clicks on quote references messages not loaded in view, but in database"
}, },
"recording": {
"message": "Recording",
"description": "Shown on SessionRecording when recording is active."
},
"voiceNoteMustBeOnlyAttachment": { "voiceNoteMustBeOnlyAttachment": {
"message": "message":
"A voice note must be the only attachment included in a message.", "A voice note must be the only attachment included in a message.",

@ -942,6 +942,7 @@
.toString(36) .toString(36)
.substring(3); .substring(3);
window.toasts = new Map(); window.toasts = new Map();
window.pushToast = options => { window.pushToast = options => {
// Setting toasts with the same ID can be used to prevent identical // Setting toasts with the same ID can be used to prevent identical

@ -63,6 +63,7 @@
"@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6", "@journeyapps/sqlcipher": "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6",
"@sindresorhus/is": "0.8.0", "@sindresorhus/is": "0.8.0",
"@types/dompurify": "^2.0.0", "@types/dompurify": "^2.0.0",
"@types/moment": "^2.13.0",
"@types/rc-slider": "^8.6.5", "@types/rc-slider": "^8.6.5",
"@types/react-mic": "^12.4.1", "@types/react-mic": "^12.4.1",
"backbone": "1.3.3", "backbone": "1.3.3",

@ -79,6 +79,7 @@ window.CONSTANTS = {
// at which more messages should be loaded // at which more messages should be loaded
MESSAGE_CONTAINER_BUFFER_OFFSET_PX: 30, MESSAGE_CONTAINER_BUFFER_OFFSET_PX: 30,
MESSAGE_FETCH_INTERVAL: 1, MESSAGE_FETCH_INTERVAL: 1,
MAX_VOICE_MESSAGE_DURATION: 600,
}; };
window.versionInfo = { window.versionInfo = {

@ -266,11 +266,10 @@ $composition-container-height: 60px;
canvas { canvas {
width: 100%; width: 100%;
padding: 0px $session-margin-lg; padding: 0px $session-margin-lg;
max-width: 700px;
} }
} }
&--delete { &--status {
display: flex; display: flex;
justify-content: center; justify-content: center;
position: absolute; position: absolute;
@ -278,7 +277,7 @@ $composition-container-height: 60px;
right: 0; right: 0;
bottom: $composition-container-height + $session-margin-md; bottom: $composition-container-height + $session-margin-md;
.session-button.danger-alt{ .session-button{
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
@ -286,6 +285,29 @@ $composition-container-height: 60px;
font-weight: 300; font-weight: 300;
font-family: "SF Pro Text"; font-family: "SF Pro Text";
&.primary {
cursor: default;
user-select: none;
&:hover{
filter: brightness(100%);
}
}
}
}
&--timer {
display: inline-flex;
align-items: center;
font-family: "SF Pro Text";
font-weight: bold;
font-size: 14px;
&-light{
height: $session-margin-sm;
width: $session-margin-sm;
border-radius: 50%;
background-color: $session-color-danger-alt;
margin-left: $session-margin-sm;
} }
} }
} }

@ -1,5 +1,5 @@
import React from 'react'; import React from 'react';
import moment from 'moment';
import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { SessionButton, SessionButtonType, SessionButtonColor } from '../SessionButton'; import { SessionButton, SessionButtonType, SessionButtonColor } from '../SessionButton';
@ -17,6 +17,9 @@ interface State {
actionHover: boolean; actionHover: boolean;
mediaSetting?: boolean; mediaSetting?: boolean;
volumeArray?: Array<number>; volumeArray?: Array<number>;
startTimestamp: number;
nowTimestamp: number;
updateTimerInterval: NodeJS.Timeout;
} }
export class SessionRecording extends React.Component<Props, State> { export class SessionRecording extends React.Component<Props, State> {
@ -26,15 +29,6 @@ export class SessionRecording extends React.Component<Props, State> {
constructor(props: any) { constructor(props: any) {
super(props); super(props);
this.state = {
recordDuration: 0,
isRecording: true,
isPaused: false,
actionHover: false,
mediaSetting: undefined,
volumeArray: undefined,
};
this.handleHoverActions = this.handleHoverActions.bind(this); this.handleHoverActions = this.handleHoverActions.bind(this);
this.handleUnhoverActions = this.handleUnhoverActions.bind(this); this.handleUnhoverActions = this.handleUnhoverActions.bind(this);
@ -44,25 +38,52 @@ export class SessionRecording extends React.Component<Props, State> {
this.onSendVoiceMessage = this.onSendVoiceMessage.bind(this); this.onSendVoiceMessage = this.onSendVoiceMessage.bind(this);
this.onDeleteVoiceMessage = this.onDeleteVoiceMessage.bind(this); this.onDeleteVoiceMessage = this.onDeleteVoiceMessage.bind(this);
this.timerUpdate = this.timerUpdate.bind(this);
this.onStream = this.onStream.bind(this); this.onStream = this.onStream.bind(this);
this.visualisationRef = React.createRef(); this.visualisationRef = React.createRef();
this.visualisationCanvas = React.createRef(); this.visualisationCanvas = React.createRef();
const now = moment().unix();
const updateTimerInterval = setInterval(this.timerUpdate, 1000);
this.state = {
recordDuration: 0,
isRecording: true,
isPaused: false,
actionHover: false,
mediaSetting: undefined,
volumeArray: undefined,
startTimestamp: now,
nowTimestamp: now,
updateTimerInterval: updateTimerInterval,
};
} }
public async componentWillMount(){ public async componentWillMount(){
// This turns on the microphone on the system. Later we need to turn it off. // This turns on the microphone on the system. Later we need to turn it off.
this.initiateStream(); this.initiateStream();
} }
public componentWillUnmount(){
clearInterval(this.state.updateTimerInterval);
}
render() { render() {
const actionPause = (this.state.actionHover && this.state.isRecording); const actionPause = (this.state.actionHover && this.state.isRecording);
const actionPlay = (!this.state.isRecording || this.state.isPaused); const actionPlay = (!this.state.isRecording || this.state.isPaused);
const actionDefault = !actionPause && !actionPlay; const actionDefault = !actionPause && !actionPlay;
const { isRecording, startTimestamp, nowTimestamp } = this.state;
const elapsedTime = nowTimestamp - startTimestamp;
const displayTimeString = moment(elapsedTime).format('mm:ss');
console.log(`[vince][time] Elapsed time: `, this.state.nowTimestamp - this.state.startTimestamp);
console.log(`[vince][time] Elapsed time calculated: `, elapsedTime);
return ( return (
<div className="session-recording"> <div className="session-recording">
@ -104,23 +125,43 @@ export class SessionRecording extends React.Component<Props, State> {
</div> </div>
<div className="send-message-button"> { isRecording ? (
<SessionIconButton <div className="session-recording--timer">
iconType={SessionIconType.Send} { displayTimeString }
iconSize={SessionIconSize.Large} <div className="session-recording--timer-light">
iconColor={'#FFFFFF'}
iconRotation={90} </div>
onClick={this.onSendVoiceMessage} </div>
/> ) : (
</div> <div className="send-message-button">
<SessionIconButton
iconType={SessionIconType.Send}
iconSize={SessionIconSize.Large}
iconColor={'#FFFFFF'}
iconRotation={90}
onClick={this.onSendVoiceMessage}
/>
</div>
)}
<div className="session-recording--delete">
<SessionButton <div className="session-recording--status">
text={window.i18n('delete')} { isRecording ? (
<SessionButton
text={window.i18n('recording')}
buttonType={SessionButtonType.Brand} buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.DangerAlt} buttonColor={SessionButtonColor.Primary}
onClick={this.onDeleteVoiceMessage} />
/> ) : (
<SessionButton
text={window.i18n('delete')}
buttonType={SessionButtonType.Brand}
buttonColor={SessionButtonColor.DangerAlt}
onClick={this.onDeleteVoiceMessage}
/>
)}
</div> </div>
</div> </div>
); );
@ -140,6 +181,12 @@ export class SessionRecording extends React.Component<Props, State> {
} }
} }
private timerUpdate(){
this.setState({
nowTimestamp: moment().unix()
});
}
private handleUnhoverActions() { private handleUnhoverActions() {
if (this.state.isRecording && this.state.actionHover) { if (this.state.isRecording && this.state.actionHover) {
this.setState({ this.setState({
@ -246,7 +293,6 @@ export class SessionRecording extends React.Component<Props, State> {
volumeArray = volumeArray.slice(0, numBars); volumeArray = volumeArray.slice(0, numBars);
console.log(`[vince][mic] Width: `, VISUALISATION_WIDTH); console.log(`[vince][mic] Width: `, VISUALISATION_WIDTH);
canvas && (canvas.height = CANVAS_HEIGHT); canvas && (canvas.height = CANVAS_HEIGHT);
canvas && (canvas.width = CANVAS_WIDTH); canvas && (canvas.width = CANVAS_WIDTH);
const canvasContext = canvas && (canvas.getContext(`2d`)); const canvasContext = canvas && (canvas.getContext(`2d`));
@ -286,6 +332,8 @@ export class SessionRecording extends React.Component<Props, State> {
} }
private onStreamError(error: any) { private onStreamError(error: any) {
return error; return error;
} }
@ -306,4 +354,3 @@ export class SessionRecording extends React.Component<Props, State> {
} }

@ -281,6 +281,13 @@
resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.0.0.tgz#a3014921991066193f6c8e47290d4d598dfd19e6" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-5.0.0.tgz#a3014921991066193f6c8e47290d4d598dfd19e6"
integrity sha512-ZS0vBV7Jn5Z/Q4T3VXauEKMDCV8nWOtJJg90OsDylkYJiQwcWtKuLzohWzrthBkerUF7DLMmJcwOPEP0i/AOXw== integrity sha512-ZS0vBV7Jn5Z/Q4T3VXauEKMDCV8nWOtJJg90OsDylkYJiQwcWtKuLzohWzrthBkerUF7DLMmJcwOPEP0i/AOXw==
"@types/moment@^2.13.0":
version "2.13.0"
resolved "https://registry.yarnpkg.com/@types/moment/-/moment-2.13.0.tgz#604ebd189bc3bc34a1548689404e61a2a4aac896"
integrity sha1-YE69GJvDvDShVIaJQE5hoqSqyJY=
dependencies:
moment "*"
"@types/node@*": "@types/node@*":
version "13.1.6" version "13.1.6"
resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.6.tgz#076028d0b0400be8105b89a0a55550c86684ffec" resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.6.tgz#076028d0b0400be8105b89a0a55550c86684ffec"
@ -6609,16 +6616,16 @@ modify-filename@^1.1.0:
resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1" resolved "https://registry.yarnpkg.com/modify-filename/-/modify-filename-1.1.0.tgz#9a2dec83806fbb2d975f22beec859ca26b393aa1"
integrity sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE= integrity sha1-mi3sg4Bvuy2XXyK+7IWcoms5OqE=
moment@*, moment@^2.10.6:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
moment@2.21.0: moment@2.21.0:
version "2.21.0" version "2.21.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a" resolved "https://registry.yarnpkg.com/moment/-/moment-2.21.0.tgz#2a114b51d2a6ec9e6d83cf803f838a878d8a023a"
integrity sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ== integrity sha512-TCZ36BjURTeFTM/CwRcViQlfkMvL1/vFISuNLO5GkcVm1+QHfbSiNqZuWeMFjj1/3+uAjXswgRk30j1kkLYJBQ==
moment@^2.10.6:
version "2.24.0"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.24.0.tgz#0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b"
integrity sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==
move-concurrently@^1.0.1: move-concurrently@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92"

Loading…
Cancel
Save