Skip to content

Instantly share code, notes, and snippets.

@vctls
Last active September 21, 2021 17:45
Show Gist options
  • Save vctls/9e17a920490d11f7d12e0bc660ea721d to your computer and use it in GitHub Desktop.
Save vctls/9e17a920490d11f7d12e0bc660ea721d to your computer and use it in GitHub Desktop.
Adds a button next to the composer field to record voice messages.
// ==UserScript==
// @name Element Voice Messages
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Adds a button to record voice messages. Click the button to start recording, click again to stop.
// @author vctls
// @match https://app.element.io/
// @grant none
// ==/UserScript==
/*
As of 2021-05-14, Element.io does not have voice messages.
But the attachment button contains a hidden file input, which if changed
automatically triggers the Upload modal.
This script uses the MediaRecorder interface to record audio blobs,
which are then put into the hidden input using a DataTransfer object.
An <audio> html element is appended to the upload modal to preview the recorded audio.
*/
// Create an audio element to preview recorded audio.
const audio = document.createElement('audio');
audio.controls = true;
function setupRecorder(composer) {
if (!navigator.mediaDevices.getUserMedia) {
console.log('getUserMedia not supported');
return;
}
const button = document.createElement('div');
button.id = 'recordButton';
button.className = 'mx_AccessibleButton mx_MessageComposer_button mx_MessageComposer_record';
button.role = 'button';
// Add some style.
const style = document.createElement('style');
style.appendChild(document.createTextNode(
'#recordButton::before{background-color: indianred; border-radius: 20px; border-style: double;}' +
'.recording{animation: blinker 1s linear infinite;}' +
'.recording:before{background-color:red !important;}' +
'@keyframes blinker {50% {opacity: 0;}}'
));
document.getElementsByTagName('head')[0].appendChild(style);
const constraints = {audio: true};
let chunks = [];
let onSuccess = function (stream) {
const mediaRecorder = new MediaRecorder(stream);
button.onclick = function () {
if (mediaRecorder.state === 'recording') {
mediaRecorder.stop();
} else {
mediaRecorder.start();
// Set two minutes security timeout.
setTimeout(function () {
mediaRecorder.stop();
}, 120000);
}
}
mediaRecorder.onstart = function (e) {
document.getElementById('recordButton').classList.add('recording');
};
mediaRecorder.onstop = function (e) {
document.getElementById('recordButton').classList.remove('recording');
const blob = new Blob(chunks, {'type': 'audio/ogg; codecs=opus'});
chunks = [];
audio.src = window.URL.createObjectURL(blob);
// Put blob in file input.
let file = new File([blob], "audio.opus",{type:"audio/ogg", lastModified:new Date().getTime()});
let container = new DataTransfer();
container.items.add(file);
let fileInput = document.querySelector('.mx_MessageComposer_upload input[type="file"]');
fileInput.files = container.files;
// Trigger input change.
let event = document.createEvent("UIEvents");
event.initUIEvent("change", true, true);
fileInput.dispatchEvent(event);
}
mediaRecorder.ondataavailable = function (e) {
chunks.push(e.data);
}
}
let onError = function (err) {
console.log('The following error occured: ' + err);
}
navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError);
composer.append(button);
}
function check(changes, observer) {
const composer = document.querySelector('.mx_MessageComposer_row');
if (composer !== null && !composer.hasButton) {
setupRecorder(composer);
composer.hasButton = true;
}
const modalContent = document.getElementById('mx_Dialog_content');
if (
modalContent !== null
&& !modalContent.hasAudio
&& modalContent.textContent.includes('audio.opus')
) {
console.log('Appending audio clip');
modalContent.append(audio);
modalContent.hasAudio = true;
}
}
(function () {
'use strict';
(new MutationObserver(check)).observe(document, {childList: true, subtree: true});
})();
@vctls
Copy link
Author

vctls commented May 14, 2021

Element.io doesn't have a dedicated voice message button yet, although it already supports sending and playing inline audio.
Update: Element.io now supports voice messages natively, so this script is definitely obsolete.


This script adds a record button that uses the recent MediaStream API to record audio directly in the browser, which is then sent using the existing attachment feature.

Just a proof of concept. It will probably become obsolete soon, when the feature is implemented in Element.
Tested on Firefox 89 with GreaseMonkey.
https://www.youtube.com/watch?v=I8-bsGZPerg

Also available on GreasyFork:
https://greasyfork.org/en/scripts/426488-element-voice-messages

Apparently some browser versions show microphone activity all the time when this is loaded.
There are probably better ways of handling the permission.

(btw I'm not a JS dev. It probably shows, a bit.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment