merged with next
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
// Audio recorder widget.
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { secondsToTime } from '../lib/strformat';
|
||||
import { base64ToIntArray } from '../lib/blob-helpers';
|
||||
@@ -13,9 +12,9 @@ const LINE_WIDTH = 3 * CANVAS_UPSCALING;
|
||||
// Spacing between two visualization bars.
|
||||
const SPACING = 2 * CANVAS_UPSCALING;
|
||||
// Color of histogram bars.
|
||||
const BAR_COLOR = '#bbb';
|
||||
const BAR_COLOR_DARK = '#888';
|
||||
const THUMB_COLOR = '#666';
|
||||
const BAR_COLOR = '#888A';
|
||||
const BAR_COLOR_DARK = '#666C';
|
||||
const THUMB_COLOR = '#444E';
|
||||
// Minimum number of amplitude bars to draw.
|
||||
const MIN_PREVIEW_LENGTH = 16;
|
||||
|
||||
@@ -24,7 +23,7 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
super(props);
|
||||
|
||||
let preview = base64ToIntArray(this.props.preview);
|
||||
if (Array.isArray(preview) && preview.length < MIN_PREVIEW_LENGTH) {
|
||||
if (!Array.isArray(preview) || preview.length < MIN_PREVIEW_LENGTH) {
|
||||
preview = null;
|
||||
}
|
||||
|
||||
@@ -57,9 +56,7 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
this.initAudio();
|
||||
}
|
||||
|
||||
if (this.state.preview) {
|
||||
this.initCanvas();
|
||||
}
|
||||
this.initCanvas();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@@ -79,7 +76,7 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
|
||||
if (this.props.preview != prevProps.preview) {
|
||||
let preview = base64ToIntArray(this.props.preview);
|
||||
if (Array.isArray(preview) && preview.length < MIN_PREVIEW_LENGTH) {
|
||||
if (!Array.isArray(preview) || preview.length < MIN_PREVIEW_LENGTH) {
|
||||
preview = null;
|
||||
}
|
||||
this.setState({preview: preview}, this.initCanvas);
|
||||
@@ -99,7 +96,7 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
}
|
||||
|
||||
initCanvas() {
|
||||
// Force canvas aspect ration to match one of the element + upscale canvas to reduce blurring.
|
||||
// Force canvas aspect ratio to match one of the element + upscale canvas to reduce blurring.
|
||||
this.canvasRef.current.width = this.canvasRef.current.offsetWidth * CANVAS_UPSCALING;
|
||||
this.canvasRef.current.height = this.canvasRef.current.offsetHeight * CANVAS_UPSCALING;
|
||||
|
||||
@@ -122,7 +119,7 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
this.canvasContext.lineWidth = LINE_WIDTH;
|
||||
|
||||
const drawFrame = () => {
|
||||
if (!this.canvasRef.current) {
|
||||
if (!this.canvasRef.current || !this.audioPlayer) {
|
||||
// The component is unmounted.
|
||||
return;
|
||||
}
|
||||
@@ -134,7 +131,7 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
window.requestAnimationFrame(drawFrame);
|
||||
}
|
||||
|
||||
// Current pplayback position.
|
||||
// Current playback position.
|
||||
const thumbAt = this.props.duration ?
|
||||
Math.max(0, Math.min(this.audioPlayer.currentTime * 1000 / this.props.duration, 1)) * (width - LINE_WIDTH * 2) : -1;
|
||||
|
||||
@@ -173,27 +170,36 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
|
||||
// Quick and dirty downsampling of the original preview bars into a smaller (or equal) number of bars we can display here.
|
||||
resampleBars(original) {
|
||||
const barCount = Math.min(original.length, ((this.canvasRef.current.width - SPACING) / (LINE_WIDTH + SPACING)) | 0);
|
||||
const factor = original.length / barCount;
|
||||
let amps = [];
|
||||
let max = -1;
|
||||
for (let i=0;i<barCount;i++) {
|
||||
let lo = (i * factor) | 0; // low bound;
|
||||
let hi = ((i + 1) * factor) | 0; // high bound;
|
||||
let amp = 0.0;
|
||||
for (let j=lo; j<hi; j++) {
|
||||
amp += original[j]
|
||||
}
|
||||
amps[i] = Math.max(0, amp / (hi - lo));
|
||||
max = Math.max(amps[i], max);
|
||||
const dstCount = ((this.canvasRef.current.width - SPACING) / (LINE_WIDTH + SPACING)) | 0;
|
||||
// Remove extra padding on the right due to fractional bar which is not drawn.
|
||||
this.effectiveWidth = dstCount * (LINE_WIDTH + SPACING) + SPACING;
|
||||
|
||||
if (!Array.isArray(original) || original.length == 0) {
|
||||
return Array.apply(null, Array(dstCount)).map(_ => 0.01);
|
||||
}
|
||||
|
||||
if (max > 0) {
|
||||
// Remove extra padding on the right due to fractional bar which is not drawn.
|
||||
this.effectiveWidth = barCount * (LINE_WIDTH + SPACING) + SPACING;
|
||||
return amps.map(a => a / max);
|
||||
const factor = original.length / dstCount;
|
||||
let amps = [];
|
||||
let maxAmp = -1;
|
||||
for (let i=0; i<dstCount; i++) {
|
||||
let lo = (i * factor) | 0; // low bound;
|
||||
let hi = ((i + 1) * factor) | 0; // high bound;
|
||||
if (hi == lo) {
|
||||
amps[i] = original[lo];
|
||||
} else {
|
||||
let amp = 0.0;
|
||||
for (let j=lo; j<hi; j++) {
|
||||
amp += original[j]
|
||||
}
|
||||
amps[i] = Math.max(0, amp / (hi - lo));
|
||||
}
|
||||
maxAmp = Math.max(amps[i], maxAmp);
|
||||
}
|
||||
return null;
|
||||
|
||||
if (maxAmp > 0) {
|
||||
return amps.map(a => a / maxAmp);
|
||||
}
|
||||
return Array.apply(null, Array(dstCount)).map(_ => 0.01);
|
||||
}
|
||||
|
||||
handlePlay(e) {
|
||||
@@ -211,7 +217,7 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
}
|
||||
|
||||
handleError(err) {
|
||||
console.log(err);
|
||||
console.error(err);
|
||||
}
|
||||
|
||||
handleSeek(e) {
|
||||
@@ -221,7 +227,7 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
const offset = (e.clientX - rect.left) / this.effectiveWidth * CANVAS_UPSCALING;
|
||||
this.audioPlayer.currentTime = this.props.duration * offset / 1000;
|
||||
this.setState({currentTime: secondsToTime(this.audioPlayer.currentTime, this.state.longMin)});
|
||||
if (!this.state.playing && this.state.preview) {
|
||||
if (!this.state.playing) {
|
||||
this.visualize();
|
||||
}
|
||||
}
|
||||
@@ -237,25 +243,14 @@ export default class AudioPlayer extends React.PureComponent {
|
||||
</a>);
|
||||
return (<div className="audio-player">{this.props.short ?
|
||||
<>
|
||||
<div>
|
||||
{this.state.preview ?
|
||||
<canvas className="playback" ref={this.canvasRef} onClick={this.handleSeek} /> :
|
||||
<div className="playback"> - </div>
|
||||
}
|
||||
</div>
|
||||
<canvas className="playback" ref={this.canvasRef} onClick={this.handleSeek} />
|
||||
{play}
|
||||
</>
|
||||
:
|
||||
<>
|
||||
{play}
|
||||
<div>
|
||||
{this.state.preview ?
|
||||
<canvas className="playback" ref={this.canvasRef} onClick={this.handleSeek} /> :
|
||||
<div className="playback">
|
||||
<FormattedMessage
|
||||
id="preview_unavailable" defaultMessage="unavailable" description="Message shown when media preview is not available" />
|
||||
</div>
|
||||
}
|
||||
<canvas className="playback" ref={this.canvasRef} onClick={this.handleSeek} />
|
||||
<div className="timer">{this.state.currentTime}/{this.state.duration}</div>
|
||||
</div>
|
||||
</>
|
||||
|
||||
@@ -8,6 +8,7 @@ import fixWebmDuration from 'fix-webm-duration';
|
||||
|
||||
import { intArrayToBase64 } from '../lib/blob-helpers.js'
|
||||
import { secondsToTime } from '../lib/strformat';
|
||||
import { MAX_DURATION, MIN_DURATION } from '../config.js';
|
||||
|
||||
// FFT resolution.
|
||||
const BUFFER_SIZE = 256;
|
||||
@@ -20,13 +21,9 @@ const SPACING = 2 * CANVAS_UPSCALING;
|
||||
// Duration represented by one visualization bar.
|
||||
const MILLIS_PER_BAR = 100;
|
||||
// Color of histogram bars
|
||||
const BAR_COLOR = '#bbb';
|
||||
const BAR_COLOR = '#BBBD';
|
||||
// Vertical scaling for visualization bars.
|
||||
const BAR_SCALE = 64.0;
|
||||
// Minimum duration of a recording in milliseconds.
|
||||
const MIN_DURATION = 200;
|
||||
// Maximum duration of a recording in milliseconds (10 min).
|
||||
const MAX_DURATION = 600000;
|
||||
// Number of bars in preview.
|
||||
const VISUALIZATION_BARS = 96;
|
||||
// Maximum number of samples per bar.
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
import React from 'react';
|
||||
import { FormattedMessage, injectIntl } from 'react-intl';
|
||||
import FormattedDuration from 'react-intl-formatted-duration';
|
||||
import { Drafty, Tinode } from 'tinode-sdk';
|
||||
|
||||
import Attachment from './attachment.jsx';
|
||||
@@ -11,6 +10,7 @@ import ReceivedMarker from './received-marker.jsx'
|
||||
|
||||
import { fullFormatter } from '../lib/formatters.js';
|
||||
import { isVideoCall, sanitizeUrl } from '../lib/utils.js';
|
||||
import { secondsToTime } from '../lib/strformat.js';
|
||||
|
||||
class BaseChatMessage extends React.PureComponent {
|
||||
constructor(props) {
|
||||
@@ -157,9 +157,7 @@ class BaseChatMessage extends React.PureComponent {
|
||||
defaultMessage="Outgoing Call" description="Outgoing call label" />;
|
||||
const isCallDropped = this.props.content == 'disconnected';
|
||||
content = <><i className="material-icons" style={{color: isCallDropped ? 'red' : 'green'}}>{direction}</i>{text}
|
||||
{ !isCallDropped && this.props.duration ?
|
||||
[' (', <FormattedDuration seconds={this.props.duration / 1000} format="{hours} {minutes} {seconds}" />, ')'] :
|
||||
null}
|
||||
{ !isCallDropped && this.props.duration ? [' (', secondsToTime(this.props.duration / 1000), ')'] : null}
|
||||
</>
|
||||
} else if (typeof content != 'string') {
|
||||
content = <><i className="material-icons gray">warning_amber</i> <i className="gray">
|
||||
|
||||
@@ -116,12 +116,12 @@ class ContactList extends React.Component {
|
||||
selected={selected}
|
||||
showOnline={this.props.showOnline && !isChannel}
|
||||
isChannel={isChannel}
|
||||
onSelected={this.props.onTopicSelected}
|
||||
showContextMenu={this.props.showContextMenu}
|
||||
isVerified={c.trusted && c.trusted.verified}
|
||||
isStaff={c.trusted && c.trusted.staff}
|
||||
isDangerous={c.trusted && c.trusted.danger}
|
||||
deleted={c._deleted}
|
||||
onSelected={this.props.onTopicSelected}
|
||||
item={key}
|
||||
index={contactNodes.length}
|
||||
key={key} />
|
||||
|
||||
@@ -84,7 +84,8 @@ class SendMessage extends React.PureComponent {
|
||||
|
||||
if (prevProps.topicName != this.props.topicName) {
|
||||
this.setState({message: '', audioRec: false, quote: null});
|
||||
} else if (prevProps.reply != this.props.reply) {
|
||||
}
|
||||
if (prevProps.reply != this.props.reply) {
|
||||
this.setState({quote: this.formatReply()});
|
||||
}
|
||||
}
|
||||
@@ -103,12 +104,8 @@ class SendMessage extends React.PureComponent {
|
||||
}
|
||||
// FIXME: handle large files too.
|
||||
if (filePasted(e,
|
||||
(bits, mime, width, height, fname) => {
|
||||
this.props.onAttachImage(mime, bits, width, height, fname);
|
||||
},
|
||||
(mime, bits, fname) => {
|
||||
this.props.onAttachFile(mime, bits, fname);
|
||||
},
|
||||
file => { this.props.onAttachImage(file); },
|
||||
file => { this.props.onAttachFile(file); },
|
||||
this.props.onError)) {
|
||||
|
||||
// If a file was pasted, don't paste base64 data into input field.
|
||||
@@ -196,17 +193,17 @@ class SendMessage extends React.PureComponent {
|
||||
formatMessage(messages[this.props.messagePrompt]) :
|
||||
formatMessage(messages.type_new_message));
|
||||
|
||||
const quote =
|
||||
const quote = this.state.quote ?
|
||||
(<div id="reply-quote-preview">
|
||||
<div className="cancel">
|
||||
<a href="#" onClick={(e) => {e.preventDefault(); this.props.onCancelReply();}}><i className="material-icons gray">close</i></a>
|
||||
</div>
|
||||
{this.state.quote}
|
||||
</div>);
|
||||
</div>) : null;
|
||||
const audioEnabled = this.state.audioAvailable && this.props.onAttachAudio;
|
||||
return (
|
||||
<div id="send-message-wrapper">
|
||||
{this.state.quote && !this.props.noInput ? quote : null}
|
||||
{!this.props.noInput ? quote : null}
|
||||
<div id="send-message-panel">
|
||||
{!this.props.disabled ?
|
||||
<>
|
||||
@@ -222,7 +219,7 @@ class SendMessage extends React.PureComponent {
|
||||
:
|
||||
null}
|
||||
{this.props.noInput ?
|
||||
(this.state.quote ? quote : <div className="hr thin" />) :
|
||||
(quote || <div className="hr thin" />) :
|
||||
(this.state.audioRec ?
|
||||
<AudioRecorder
|
||||
onDeleted={_ => this.setState({audioRec: false})}
|
||||
|
||||
Reference in New Issue
Block a user