Skip to content

Commit f85bf00

Browse files
committed
push-to-talk support
(fixes element-hq/element-web#1358)
1 parent ebe7ec4 commit f85bf00

File tree

6 files changed

+106
-5
lines changed

6 files changed

+106
-5
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"linkifyjs": "^2.1.3",
6363
"lodash": "^4.13.1",
6464
"matrix-js-sdk": "matrix-org/matrix-js-sdk#develop",
65+
"msr": "^1.3.4",
6566
"optimist": "^0.6.1",
6667
"q": "^1.4.1",
6768
"react": "^15.4.0",

src/ContentMessages.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,8 @@ class ContentMessages {
274274
this.nextId = 0;
275275
}
276276

277-
sendContentToRoom(file, roomId, matrixClient) {
278-
const content = {
277+
sendContentToRoom(file, roomId, matrixClient, infoFunction: (Object) => Object = (o) => o) {
278+
let content = {
279279
body: file.name,
280280
info: {
281281
size: file.size,
@@ -343,6 +343,7 @@ class ContentMessages {
343343
dis.dispatch({action: 'upload_progress', upload: upload});
344344
}
345345
}).then(function(url) {
346+
content = infoFunction(content);
346347
return matrixClient.sendMessage(roomId, content);
347348
}, function(err) {
348349
error = err;

src/components/structures/RoomView.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -874,7 +874,7 @@ module.exports = React.createClass({
874874
this.setState({ draggingFile : false });
875875
},
876876

877-
uploadFile: function(file) {
877+
uploadFile: function(file, infoFunction: (Object) => Object = (o) => o) {
878878
var self = this;
879879

880880
if (MatrixClientPeg.get().isGuest()) {
@@ -887,7 +887,7 @@ module.exports = React.createClass({
887887
}
888888

889889
ContentMessages.sendContentToRoom(
890-
file, this.state.room.roomId, MatrixClientPeg.get()
890+
file, this.state.room.roomId, MatrixClientPeg.get(), infoFunction,
891891
).done(undefined, function(error) {
892892
var ErrorDialog = sdk.getComponent("dialogs.ErrorDialog");
893893
Modal.createDialog(ErrorDialog, {

src/components/views/messages/MAudioBody.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,13 @@ export default class MAudioBody extends React.Component {
9696

9797
const contentUrl = this._getContentUrl();
9898

99+
const shouldAutoPlay = MatrixClientPeg.get().credentials.userId !==
100+
this.props.mxEvent.sender.userId &&
101+
content.autoplay;
102+
99103
return (
100104
<span className="mx_MAudioBody">
101-
<audio src={contentUrl} controls />
105+
<audio src={contentUrl} controls autoPlay={shouldAutoPlay} />
102106
<MFileBody {...this.props} decryptedBlob={this.state.decryptedBlob} />
103107
</span>
104108
);
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//@flow
2+
3+
import React from 'react';
4+
5+
import sdk from '../../../index';
6+
import Modal from '../../../Modal';
7+
import ErrorDialog from '../dialogs/ErrorDialog';
8+
import MediaStreamRecorder from 'msr';
9+
10+
export default class AudioRecorder extends React.Component {
11+
static propTypes = {
12+
sendAudioBlob: React.PropTypes.func.isRequired,
13+
};
14+
15+
mediaStream: ?MediaStream = null;
16+
recorder: ?MediaStreamRecorder = null;
17+
blobs: Array<Blob> = [];
18+
19+
constructor(props: Object) {
20+
super(props);
21+
22+
this.state = {
23+
recording: false,
24+
};
25+
}
26+
27+
onRecordClicked = async () => {
28+
if(this.state.recording && this.mediaStream) {
29+
this.recorder.stop();
30+
window.ConcatenateBlobs(this.blobs, this.blobs[0].type, (blob) => {
31+
console.log(blob);
32+
this.props.sendAudioBlob(blob);
33+
this.blobs = [];
34+
this.recorder = null;
35+
this.mediaStream.getTracks().forEach(track => track.stop());
36+
this.mediaStream = null;
37+
});
38+
} else {
39+
try {
40+
const mediaStream = await navigator.mediaDevices.getUserMedia({
41+
audio: true,
42+
});
43+
this.onUserMediaSuccess(mediaStream);
44+
} catch (e) {
45+
this.onUserMediaFailure(e);
46+
}
47+
}
48+
};
49+
50+
onUserMediaFailure = () => {
51+
Modal.createDialog(ErrorDialog, {
52+
description: "Failed to start recording.",
53+
});
54+
};
55+
56+
onUserMediaSuccess = (mediaStream: MediaStream) => {
57+
this.mediaStream = mediaStream;
58+
this.recorder = new MediaStreamRecorder(this.mediaStream);
59+
this.recorder.recorderType = class extends MediaStreamRecorder.StereoAudioRecorder {
60+
constructor(mediaStream: MediaStream) {
61+
super(mediaStream);
62+
this.audioChannels = 1;
63+
}
64+
};
65+
this.recorder.ondataavailable = (blob) => {
66+
this.blobs.push(blob);
67+
};
68+
this.recorder.mimeType = 'audio/wav';
69+
this.recorder.start(100);
70+
this.setState({recording: true});
71+
};
72+
73+
render() {
74+
const TintableSvg = sdk.getComponent("elements.TintableSvg");
75+
return (
76+
<div key="controls_ptt" className="mx_MessageComposer_voicecall"
77+
onClick={this.onRecordClicked} title="Record & send message">
78+
<TintableSvg src="img/voice.svg" width="22" height="22"/>
79+
</div>
80+
);
81+
}
82+
}

src/components/views/rooms/MessageComposer.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ var sdk = require('../../../index');
2222
var dis = require('../../../dispatcher');
2323
import Autocomplete from './Autocomplete';
2424
import classNames from 'classnames';
25+
import AudioRecorder from './AudioRecorder';
2526

2627
import UserSettingsStore from '../../../UserSettingsStore';
2728

@@ -262,6 +263,17 @@ export default class MessageComposer extends React.Component {
262263
MatrixClientPeg.get().credentials.userId);
263264

264265
if (canSendMessages) {
266+
const sendAudioBlob = (blob) => {
267+
const file = new File([blob], `${+new Date()}.wav`, {
268+
type: blob.type,
269+
});
270+
this.props.uploadFile(file, (content) => {
271+
content.autoplay = true;
272+
return content;
273+
});
274+
};
275+
const pttButton = <AudioRecorder sendAudioBlob={sendAudioBlob} />;
276+
265277
// This also currently includes the call buttons. Really we should
266278
// check separately for whether we can call, but this is slightly
267279
// complex because of conference calls.
@@ -299,6 +311,7 @@ export default class MessageComposer extends React.Component {
299311
onContentChanged={this.onInputContentChanged}
300312
onInputStateChanged={this.onInputStateChanged} />,
301313
formattingButton,
314+
pttButton,
302315
uploadButton,
303316
hangupButton,
304317
callButton,

0 commit comments

Comments
 (0)