Waveform dividing across fullwidth

pull/1102/head
Vincent 5 years ago
parent 56ae7055cb
commit 74142fab28

@ -898,9 +898,15 @@
} }
} }
}, },
"audioPermissionNeeded": { "audioPermissionNeededTitle": {
"message": "message":
"To send audio messages, allow Session to access your microphone.", "Sending audio messages requires microphone access",
"description":
"Shown if the user attempts to send an audio message without audio permssions turned on"
},
"audioPermissionNeededDescription": {
"message":
"Give Session microphone permissions in your settings",
"description": "description":
"Shown if the user attempts to send an audio message without audio permssions turned on" "Shown if the user attempts to send an audio message without audio permssions turned on"
}, },

@ -2273,7 +2273,6 @@
}, },
async markRead(newestUnreadDate, providedOptions) { async markRead(newestUnreadDate, providedOptions) {
console.log(`[vince][unread] Marking messages as read!!`); console.log(`[vince][unread] Marking messages as read!!`);
const options = providedOptions || {}; const options = providedOptions || {};

@ -257,10 +257,13 @@ $composition-container-height: 60px;
} }
&--visualisation { &--visualisation {
margin-top: -900px;
z-index: 1000; z-index: 1000;
height: 400px;
width: 100%; width: 100%;
padding: 0px $session-margin-lg;
display: flex;
align-items: center;
justify-content: center;
max-width: 850px;
} }
&--delete { &--delete {
@ -282,3 +285,12 @@ $composition-container-height: 60px;
} }
} }
} }
.freq-band-item{
width: 5px;
margin: 0px 3px;
border-radius: 15px;
display: inline-block;
background-color: #AFAFAF;
transition: height 0.05s;
}

@ -209,7 +209,7 @@ export class LeftPaneMessageSection extends React.Component<Props, any> {
); );
} }
public renderConversations() {\ public renderConversations() {
return ( return (
<div className="module-conversations-list-content"> <div className="module-conversations-list-content">
{this.state.shouldRenderMessageOnboarding ? ( {this.state.shouldRenderMessageOnboarding ? (

@ -18,6 +18,8 @@ interface State {
isRecording: boolean; isRecording: boolean;
mediaSetting: boolean | null; mediaSetting: boolean | null;
showEmojiPanel: boolean; showEmojiPanel: boolean;
attachments: Array<File>;
voiceRecording?: File;
} }
export class SessionCompositionBox extends React.Component<Props, State> { export class SessionCompositionBox extends React.Component<Props, State> {
@ -29,6 +31,8 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.state = { this.state = {
message: '', message: '',
attachments: [],
voiceRecording: undefined,
isRecording: false, isRecording: false,
mediaSetting: null, mediaSetting: null,
showEmojiPanel: false, showEmojiPanel: false,
@ -43,22 +47,22 @@ export class SessionCompositionBox extends React.Component<Props, State> {
this.renderCompositionView = this.renderCompositionView.bind(this); this.renderCompositionView = this.renderCompositionView.bind(this);
this.onKeyDown = this.onKeyDown.bind(this); this.onKeyDown = this.onKeyDown.bind(this);
this.onStartRecording = this.onStartRecording.bind(this); this.onStartedRecording = this.onStartedRecording.bind(this);
this.onStopRecording = this.onStopRecording.bind(this); this.onStoppedRecording = this.onStoppedRecording.bind(this);
this.onSendMessage = this.onSendMessage.bind(this); this.onSendMessage = this.onSendMessage.bind(this);
this.onChooseAttachment = this.onChooseAttachment.bind(this); this.onChooseAttachment = this.onChooseAttachment.bind(this);
} }
public componentWillReceiveProps(){
console.log(`[vince][info] Here are my composition props: `, this.props);
}
public async componentWillMount(){ public async componentWillMount(){
const mediaSetting = await window.getMediaPermissions(); const mediaSetting = await window.getMediaPermissions();
this.setState({mediaSetting}); this.setState({mediaSetting});
} }
public componentWillReceiveProps(){
console.log(`[vince][info] Here are my composition props: `, this.props);
}
render() { render() {
const { isRecording } = this.state; const { isRecording } = this.state;
@ -82,7 +86,8 @@ export class SessionCompositionBox extends React.Component<Props, State> {
private renderRecordingView() { private renderRecordingView() {
return ( return (
<SessionRecording <SessionRecording
onStoppedRecording={this.props.onStoppedRecording} onStartedRecording={this.onStartedRecording}
onStoppedRecording={this.onStoppedRecording}
/> />
); );
} }
@ -106,13 +111,12 @@ export class SessionCompositionBox extends React.Component<Props, State> {
type='file' type='file'
/> />
{ this.state.mediaSetting && (
<SessionIconButton <SessionIconButton
iconType={SessionIconType.Microphone} iconType={SessionIconType.Microphone}
iconSize={SessionIconSize.Huge} iconSize={SessionIconSize.Huge}
onClick={this.onStartRecording} onClick={this.onStartedRecording}
/> />
)}
<div className="send-message-input"> <div className="send-message-input">
<TextareaAutosize <TextareaAutosize
@ -168,24 +172,36 @@ export class SessionCompositionBox extends React.Component<Props, State> {
console.log(`[vince][msg] Message:`, messagePlaintext); console.log(`[vince][msg] Message:`, messagePlaintext);
console.log(`[vince][msg] Attachments:`, attachments); console.log(`[vince][msg] Attachments:`, attachments);
console.log(`[vince][msg] Voice message:`, this.state.voiceRecording);
if (false){ if (false){
this.props.sendMessage(); this.props.sendMessage();
} }
} }
private onStartRecording(){ private onStartedRecording(){
// Do stuff for component, then run callback to SessionConversation // Do stuff for component, then run callback to SessionConversation
this.setState({ const {mediaSetting} = this.state;
isRecording: true,
});
if (mediaSetting){
this.setState({ isRecording: true });
this.props.onStartedRecording(); this.props.onStartedRecording();
return;
} }
private onStopRecording() { window.pushToast({
// Do stuff for component, then run callback to SessionConversation id: window.generateID(),
title: window.i18n('audioPermissionNeededTitle'),
description: window.i18n('audioPermissionNeededDescription'),
type: 'info',
});
}
private onStoppedRecording() {
// Do stuff for component, then run callback to SessionConversation
this.setState({ isRecording: false });
this.props.onStoppedRecording(); this.props.onStoppedRecording();
} }

@ -21,6 +21,7 @@ interface State {
selectedMessages: Array<string>; selectedMessages: Array<string>;
isScrolledToBottom: boolean; isScrolledToBottom: boolean;
doneInitialScroll: boolean; doneInitialScroll: boolean;
displayScrollToBottomButton: boolean;
messageFetchTimestamp: number; messageFetchTimestamp: number;
isRecording: boolean; isRecording: boolean;
} }
@ -44,6 +45,7 @@ export class SessionConversation extends React.Component<any, State> {
selectedMessages: [], selectedMessages: [],
isScrolledToBottom: !unreadCount, isScrolledToBottom: !unreadCount,
doneInitialScroll: false, doneInitialScroll: false,
displayScrollToBottomButton: false,
messageFetchTimestamp: 0, messageFetchTimestamp: 0,
isRecording: false, isRecording: false,
}; };

@ -1,12 +1,13 @@
import React from 'react'; import React from 'react';
import {ReactMic} from 'react-mic';
import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon'; import { SessionIconButton, SessionIconSize, SessionIconType } from '../icon';
import { SessionButton, SessionButtonType, SessionButtonColor } from '../SessionButton'; import { SessionButton, SessionButtonType, SessionButtonColor } from '../SessionButton';
interface Props { interface Props {
onStoppedRecording: any; onStoppedRecording: any;
onStartedRecording: any;
} }
interface State { interface State {
@ -14,9 +15,13 @@ interface State {
isRecording: boolean; isRecording: boolean;
isPaused: boolean; isPaused: boolean;
actionHover: boolean; actionHover: boolean;
mediaSetting?: boolean;
volumeArray?: Array<number>;
} }
export class SessionRecording extends React.Component<Props, State> { export class SessionRecording extends React.Component<Props, State> {
private visualisationRef: React.RefObject<HTMLDivElement>;
private visualisationCanvas: React.RefObject<HTMLCanvasElement>;
constructor(props: any) { constructor(props: any) {
super(props); super(props);
@ -26,24 +31,34 @@ export class SessionRecording extends React.Component<Props, State> {
isRecording: true, isRecording: true,
isPaused: false, isPaused: false,
actionHover: 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);
this.onPlayRecording = this.onPlayRecording.bind(this); this.playRecording = this.playRecording.bind(this);
this.onStopRecording = this.onStopRecording.bind(this); this.stopRecording = this.stopRecording.bind(this);
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.onStream = this.onStream.bind(this);
this.visualisationRef = React.createRef();
this.visualisationCanvas = React.createRef();
} }
public componentWillReceiveProps(){ public async componentWillMount(){
console.log(`[vince][mic] Here are my composition props: `, this.props); // This turns on the microphone on the system. Later we need to turn it off.
this.initiateStream();
console.log(`[vince][mic] Permissions: `, navigator.getUserMedia({ audio: true }, () => null, error => alert(error)));
} }
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);
@ -62,14 +77,14 @@ export class SessionRecording extends React.Component<Props, State> {
iconSize={SessionIconSize.Medium} iconSize={SessionIconSize.Medium}
// FIXME VINCE: Globalise constants for JS Session Colors // FIXME VINCE: Globalise constants for JS Session Colors
iconColor={'#FF4538'} iconColor={'#FF4538'}
onClick={this.onStopRecording} onClick={this.stopRecording}
/> />
)} )}
{actionPlay && ( {actionPlay && (
<SessionIconButton <SessionIconButton
iconType={SessionIconType.Play} iconType={SessionIconType.Play}
iconSize={SessionIconSize.Medium} iconSize={SessionIconSize.Medium}
onClick={this.onPlayRecording} onClick={this.playRecording}
/> />
)} )}
@ -81,14 +96,12 @@ export class SessionRecording extends React.Component<Props, State> {
)} )}
</div> </div>
<ReactMic <div
record={this.state.isRecording} className="session-recording--visualisation"
className='session-recording--visualisation' ref={this.visualisationRef}
onStop={() => null} >
onData= {(data: any) => console.log(`[vince][mic] Data:`, data)} <canvas ref={this.visualisationCanvas}></canvas>
strokeColor={'#00F480'} </div>
backgroundColor={'blue'}
/>
<div className="send-message-button"> <div className="send-message-button">
@ -113,13 +126,18 @@ export class SessionRecording extends React.Component<Props, State> {
); );
} }
public blobToFile (data: any, fileName:string) {
const file = new File([data.blob], fileName);
console.log(`[vince][mic] File: `, file);
return file;
}
private handleHoverActions() { private handleHoverActions() {
if ((this.state.isRecording) && !this.state.actionHover) { if ((this.state.isRecording) && !this.state.actionHover) {
this.setState({ this.setState({
actionHover: true, actionHover: true,
}); });
} }
} }
private handleUnhoverActions() { private handleUnhoverActions() {
@ -130,7 +148,7 @@ export class SessionRecording extends React.Component<Props, State> {
} }
} }
private onStopRecording() { private stopRecording() {
console.log(`[vince][mic] Stopped recording`); console.log(`[vince][mic] Stopped recording`);
this.setState({ this.setState({
@ -139,7 +157,7 @@ export class SessionRecording extends React.Component<Props, State> {
}); });
} }
private onPlayRecording() { private playRecording() {
console.log(`[vince][mic] Playing recording`); console.log(`[vince][mic] Playing recording`);
this.setState({ this.setState({
@ -148,12 +166,167 @@ export class SessionRecording extends React.Component<Props, State> {
}); });
} }
private initSendVoiceRecording(){
return;
}
private onDeleteVoiceMessage() { private onDeleteVoiceMessage() {
this.onStopRecording(); //this.stopRecording();
this.props.onStoppedRecording(); this.setState({
isRecording: false,
isPaused: true,
}, () => this.props.onStoppedRecording());
} }
private onSendVoiceMessage() { private onSendVoiceMessage() {
console.log(`[vince][mic] Sending voice message`); console.log(`[vince][mic] Sending voice message`);
} }
private async initiateStream() {
navigator.getUserMedia({audio:true}, this.onStream, this.onStreamError);
//const mediaStreamSource = audioContext.createMediaStreamSource(stream);
//const meter = getMeter(audioContext);
//mediaStreamSource.connect(meter);
}
private onStream(stream: any) {
// AUDIO CONTEXT
const audioContext = new window.AudioContext();
const input = audioContext.createMediaStreamSource(stream);
const bufferSize = 8192;
const analyser = audioContext.createAnalyser();
analyser.smoothingTimeConstant = 0.3;
analyser.fftSize = 256;
const processor = audioContext.createScriptProcessor(bufferSize, 1, 1);
processor.onaudioprocess = () => {
// Array of volumes by frequency (not in Hz, arbitrary unit)
const freqTypedArray = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(freqTypedArray);
const freqArray = Array.from(freqTypedArray);
const VISUALISATION_WIDTH = this.visualisationRef.current?.clientWidth;
const maxVisualisationHeight = 30;
const minVisualisationHeight = 3;
// CANVAS CONTEXT
const drawCanvas = () => {
const canvas = this.visualisationCanvas.current;
const CANVAS_HEIGHT = 35;
const CANVAS_WIDTH = VISUALISATION_WIDTH || 600;
const barPadding = 3;
const barWidth = 4;
const numBars = Math.floor(CANVAS_WIDTH / (barPadding + barWidth));
const maxSumVal = Math.max(...freqArray) * numBars;
const sumReset = Math.floor(freqArray.length / numBars);
// This takes the while frequency spectrum and splits it into
// the number of bars required to take up the entire width
let sum = 0;
let barHeightArray = [];
for (let i = 0; i < freqArray.length; i++) {
sum += freqArray[i];
const initialHeight = maxVisualisationHeight * (sum / (maxSumVal));
const freqBarHeight = initialHeight > minVisualisationHeight
? initialHeight
: minVisualisationHeight;
if (i % sumReset === 0){
barHeightArray.push(freqBarHeight);
sum = 0;
continue;
}
} }
console.log(`[vince][mic] freqArray:`, freqArray);
console.log(`[vince][mic] Num bars`, numBars);
console.log(`[vince][mic] Max sum`, maxSumVal);
console.log(`[vince][mic] Barheight:`, barHeightArray);
// let barHeightArray = freqArray.map(n => {
// const maxVal = Math.max(...freqArray);
// const initialHeight = maxVisualisationHeight * (n / maxVal);
// const freqBarHeight = initialHeight > minVisualisationHeight
// ? initialHeight
// : minVisualisationHeight;
// return freqBarHeight;
// });
// // Create initial fake bars to improve appearance.
// // Gradually increasing wave rather than a wall at the beginning
// const frontLoadLen = Math.ceil(volumeArray.length / 10);
// const frontLoad = volumeArray.slice(0, frontLoadLen - 1).reverse().map(n => n * 0.80);
// volumeArray = [...frontLoad, ...volumeArray].slice(0, volumeArray.length - frontLoadLen - 1);
canvas && (canvas.height = CANVAS_HEIGHT);
canvas && (canvas.width = CANVAS_WIDTH);
const canvasContext = canvas && (canvas.getContext(`2d`));
for (var i = 0; i < barHeightArray.length; i++) {
const barHeight = Math.ceil(barHeightArray[i]);
const offset_x = Math.ceil(i * (barWidth + barPadding));
const offset_y = Math.ceil((CANVAS_HEIGHT / 2 ) - (barHeight / 2 ));
const radius = 15;
// FIXME VINCE - Globalise JS references to colors
canvasContext && (canvasContext.fillStyle = '#AFAFAF');
canvasContext && this.drawRoundedRect(
canvasContext,
offset_x,
offset_y,
barWidth,
barHeight,
radius,
);
}
}
requestAnimationFrame(drawCanvas);
}
// Get volume for visualisation
input.connect(analyser);
processor.connect(audioContext.destination);
console.log(`[vince][mic] Freq:`, analyser.frequencyBinCount);
//Start recording the stream
const media = new window.MediaRecorder(stream);
}
private onStreamError(error: any) {
return error;
}
private drawRoundedRect (ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) {
if (w < 2 * r) r = w / 2;
if (h < 2 * r) r = h / 2;
ctx.beginPath();
ctx.moveTo(x+r, y);
ctx.arcTo(x+w, y, x+w, y+h, r);
ctx.arcTo(x+w, y+h, x, y+h, r);
ctx.arcTo(x, y+h, x, y, r);
ctx.arcTo(x, y, x+w, y, r);
ctx.closePath();
ctx.fill();
}
}

10
ts/global.d.ts vendored

@ -1,4 +1,5 @@
interface Window { interface Window {
// Low level
CONSTANTS: any; CONSTANTS: any;
versionInfo: any; versionInfo: any;
@ -10,6 +11,11 @@ interface Window {
deleteAllData: any; deleteAllData: any;
clearLocalData: any; clearLocalData: any;
// Microphone
MediaRecorder: any;
AudioContext: any;
// Gets
getAccountManager: any; getAccountManager: any;
getMediaPermissions: any; getMediaPermissions: any;
getConversations: any; getConversations: any;
@ -35,9 +41,6 @@ interface Window {
Whisper: any; Whisper: any;
ConversationController: any; ConversationController: any;
// Following function needs to be written in background.js
// getMemberList: any;
onLogin: any; onLogin: any;
setPassword: any; setPassword: any;
textsecure: any; textsecure: any;
@ -57,6 +60,7 @@ interface Window {
deleteAccount: any; deleteAccount: any;
// Toggles
toggleTheme: any; toggleTheme: any;
toggleMenuBar: any; toggleMenuBar: any;
toggleSpellCheck: any; toggleSpellCheck: any;

Loading…
Cancel
Save