Record audio & video in browser - javascript

Hi i need to record audio and video content on frontend that is streamed to the browser from server
I've found some info about MediaStreams so i did it and it seems that i don't record my html video and audio output but a camera output
<audio id="audio" autoplay="true"></audio>
<video id="video" autoplay="true" playsinline="true" controls></video>
var constraints = {
audio: true,
video: true,
};
navigator.mediaDevices.getUserMedia(constraints)
.then(function(mediaStreamObj) {
let start = document.getElementById('btnStart');
let stop = document.getElementById('btnStop');
let vidSave = document.getElementById('vid2');
let mediaRecorder = new MediaRecorder(mediaStreamObj);
let chunks = [];
start.addEventListener('click', (ev)=>{
mediaRecorder.start();
console.log(mediaRecorder.state);
})
stop.addEventListener('click', (ev)=>{
mediaRecorder.stop();
console.log(mediaRecorder.state);
});
mediaRecorder.ondataavailable = function(ev) {
chunks.push(ev.data);
}
mediaRecorder.onstop = (ev)=>{
let blob = new Blob(chunks, { 'type' : 'video/mp4;' });
chunks = [];
let videoURL = window.URL.createObjectURL(blob);
vidSave.src = videoURL;
}
})
.catch(function(err) {
console.log(err.name, err.message);
});
}
any help is appreciated because i want to do this in clean way but for now only option i see is to record whole browser window

Well, I'm guessing you're aiming for a webcam-like setup, so this works, on a server, keep in mind, you can't download video/audio unless you're hosting HTML through a server. It won't work while testing on a file though:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body {
padding: 0;
margin: 0;
}
svg:not(:root) {
display: block;
}
.playable-code {
background-color: #f4f7f8;
border: none;
border-left: 6px solid #558abb;
border-width: medium medium medium 6px;
color: #4d4e53;
height: 100px;
width: 90%;
padding: 10px 10px 0;
}
.playable-canvas {
border: 1px solid #4d4e53;
border-radius: 2px;
}
.playable-buttons {
text-align: right;
width: 90%;
padding: 5px 10px 5px 26px;
}
</style>
<style type="text/css">
body {
font: 14px "Open Sans", "Arial", sans-serif;
}
video {
margin-top: 2px;
border: 1px solid black;
}
.button {
cursor: pointer;
display: block;
width: 160px;
border: 1px solid black;
font-size: 16px;
text-align: center;
padding-top: 2px;
padding-bottom: 4px;
color: white;
background-color: darkgreen;
text-decoration: none;
}
h2 {
margin-bottom: 4px;
}
.left {
margin-right: 10px;
float: left;
width: 160px;
padding: 0px;
}
.right {
margin-left: 10px;
float: left;
width: 160px;
padding: 0px;
}
.bottom {
clear: both;
padding-top: 10px;
}
</style>
<title>Recording a media element - Example - code sample</title>
</head>
<body>
<p>Click the "Start" button to begin video recording for a few seconds. You can stop
the video by clicking the creatively-named "Stop" button. The "Download"
button will download the received data (although it's in a raw, unwrapped form
that isn't very useful).
</p>
<br>
<div class="left">
<div id="startButton" class="button">
Start
</div>
<h2>Preview</h2>
<video id="preview" width="160" height="120" autoplay muted></video>
</div>
<div class="right">
<div id="stopButton" class="button">
Stop
</div>
<h2>Recording</h2>
<video id="recording" width="160" height="120" controls></video>
<a id="downloadButton" class="button">
Download
</a>
</div>
<div class="bottom">
<pre id="log"></pre>
</div>
<script>
let preview = document.getElementById("preview");
let recording = document.getElementById("recording");
let startButton = document.getElementById("startButton");
let stopButton = document.getElementById("stopButton");
let downloadButton = document.getElementById("downloadButton");
let logElement = document.getElementById("log");
let recordingTimeMS = 5000;
function log(msg) {
logElement.innerHTML += msg + "\n";
}
function wait(delayInMS) {
return new Promise(resolve => setTimeout(resolve, delayInMS));
}
function startRecording(stream, lengthInMS) {
let recorder = new MediaRecorder(stream);
let data = [];
recorder.ondataavailable = event => data.push(event.data);
recorder.start();
log(recorder.state + " for " + (lengthInMS/1000) + " seconds...");
let stopped = new Promise((resolve, reject) => {
recorder.onstop = resolve;
recorder.onerror = event => reject(event.name);
});
let recorded = wait(lengthInMS).then(
() => recorder.state == "recording" && recorder.stop()
);
return Promise.all([
stopped,
recorded
])
.then(() => data);
}
function stop(stream) {
stream.getTracks().forEach(track => track.stop());
}
startButton.addEventListener("click", function() {
navigator.mediaDevices.getUserMedia({
video: true,
audio: true
}).then(stream => {
preview.srcObject = stream;
downloadButton.href = stream;
preview.captureStream = preview.captureStream || preview.mozCaptureStream;
return new Promise(resolve => preview.onplaying = resolve);
}).then(() => startRecording(preview.captureStream(), recordingTimeMS))
.then (recordedChunks => {
let recordedBlob = new Blob(recordedChunks, { type: "video/webm" });
recording.src = URL.createObjectURL(recordedBlob);
downloadButton.href = recording.src;
downloadButton.download = "RecordedVideo.webm";
log("Successfully recorded " + recordedBlob.size + " bytes of " +
recordedBlob.type + " media.");
})
.catch(log);
}, false);
stopButton.addEventListener("click", function() {
stop(preview.srcObject);
}, false);
</script>
</body>
</html>
This will work if you're hosting on a server, like I said, so assuming you are, this is your code. If you haven't setup a server yet, consider using Python and Flask, my personal favourites.
The above code is from MDN Web Docs by the way. This is where you can find the full tutorial and code: https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API/Recording_a_media_element.
Thanks and I hope this helps you, if not, please let me know!

Related

How to select and manipulate the dynamically created html element with javascript?

I am pretty new to js, and I am building a color scheme generator as a solo project.
I am now stuck on select the html element that created from dynamically.
I tried to select both label and input element below, using document.getElementByClassName but it gives me 'undefined'
I wanna select both label and input elements and add an click eventListner so that they can copy the result color code from that elements.
<label for='${resultColor}' class='copy-label'>Click to copy!</label>
<input class='result-code' id='${resultColor}' type="text" value='${resultColor}'/>`
const colorPickerModes = [ 'monochrome', 'monochrome-dark', 'monochrome-light', 'analogic', 'complement', 'analogic-complement', 'triad quad']
const colorPickerForm = document.getElementById("colorPick-form");
const colorPickerInput = document.getElementById("colorPicker");
const colorPickerModeDropDown = document.getElementById("colorPick-mode");
const resultColorDiv = document.getElementById("result-color-div");
const resultColorCodeDiv = document.getElementById("result-code-div");
let colorPicked = "";
let modePicked = "";
let resultColorDivHtml =''
let resultCodeDivHtml=''
let colorSchemeSetStrings = [];
let resultColorSchemeSet = [];
fetchToRender()
renderDropDownList();
//listen when user change the color input and save that data in global variable
colorPickerInput.addEventListener(
"change",
(event) => {
//to remove # from the color hex code data we got from the user
colorPicked = event.target.value.slice(1, 7);
},
false
);
//listen when user change the scheme mode dropdownlist value and save that data in global variable
colorPickerModeDropDown.addEventListener('change', (event)=>{
modePicked =
colorPickerModeDropDown.options[colorPickerModeDropDown.selectedIndex].text;
})
//whe user click submit btn get data from user's input
colorPickerForm.addEventListener("submit", (event) => {
event.preventDefault();
// To get options in dropdown list
modePicked =
colorPickerModeDropDown.options[colorPickerModeDropDown.selectedIndex].text;
fetchToRender()
});
//when first load, and when user request a new set of color scheme
function fetchToRender(){
if (!colorPicked) {
//initialize the color and mode value if user is not selected anything
colorPicked = colorPickerInput.value.slice(1, 7);
modePicked = colorPickerModes[0]
}
fetch(
`https://www.thecolorapi.com/scheme?hex=${colorPicked}&mode=${modePicked}`
)
.then((res) => res.json())
.then((data) => {
let colorSchemeSetArray = data.colors;
for (let i = 0; i < 5; i++) {
colorSchemeSetStrings.push(colorSchemeSetArray[i]);
}
// to store each object's hex value
for (let i = 0; i < colorSchemeSetStrings.length; i++) {
resultColorSchemeSet.push(colorSchemeSetStrings[i].hex.value);
}
renderColor();
colorSchemeSetStrings = []
resultColorSchemeSet = [];
});
}
function renderColor(){
//to store result of color scheme set object
resultColorDivHtml = resultColorSchemeSet.map((resultColorItem) => {
return `<div class="result-color"
style="background-color: ${resultColorItem};"></div>`;
}).join('')
resultCodeDivHtml = resultColorSchemeSet
.map((resultColor) => {
return `
<label for='${resultColor}' class='copy-label'>
Click to copy!</label>
<input class='result-code' id='${resultColor}'
type="text" value='${resultColor}'/>`;
})
.join("");
resultColorDiv.innerHTML = resultColorDivHtml;
resultColorCodeDiv.innerHTML = resultCodeDivHtml;
}
function renderDropDownList() {
const colorPickerModeOptionsHtml = colorPickerModes
.map((colorPickerMode) => {
return `<option class='colorSchemeOptions' value="#">${colorPickerMode}</option>`;
})
.join("");
colorPickerModeDropDown.innerHTML = colorPickerModeOptionsHtml;
}
* {
box-sizing: border-box;
}
body {
font-size: 1.1rem;
font-family: "Ubuntu", sans-serif;
text-align: center;
margin: 0;
}
/*------Layout------*/
#container {
margin: 0 auto;
width: 80%;
}
#form-div {
width: 100%;
height:10vh;
margin: 0 auto;
}
#colorPick-form {
display: flex;
width: 100%;
height:6vh;
justify-content: space-between;
}
#colorPick-form > * {
margin: 1rem;
height: inherit;
border: 1px lightgray solid;
font-family: "Ubuntu", sans-serif;
}
#colorPick-form > #colorPicker {
width: 14%;
height: inherit;
}
#colorPick-form > #colorPick-mode {
width: 45%;
padding-left: 0.5rem;
}
#colorPick-form > #btn-getNewScheme {
width: 26%;
}
#main {
display: flex;
flex-direction:column;
width:100%;
margin: .8em auto 0;
height: 75vh;
border:lightgray 1px solid;
}
#result-color-div {
width:100%;
height:90%;
display:flex;
}
#result-color-div > *{
width:calc(100%/5);
}
#result-code-div {
width:100%;
height:10%;
display:flex;
}
.copy-label{
width:10%;
display:flex;
padding:0.5em;
font-size:0.8rem;
align-items: center;
cursor: pointer;
background-color: #4CAF50;
color: white;
}
#result-code-div .result-code{
width:calc(90%/5);
text-align: center;
border:none;
cursor: pointer;
}
.result-code:hover, .result-code:focus, .copy-label:hover, .copy-label:focus{
font-weight:700;
}
/*------Button------*/
#btn-getNewScheme {
background-image: linear-gradient(
to right,
#614385 0%,
#516395 51%,
#614385 100%
);
}
#btn-getNewScheme {
padding:0.8rem 1.5rem;
transition: 0.5s;
font-weight: 700;
background-size: 200% auto;
color: white;
box-shadow: 0 0 20px #eee;
border-radius: 5px;
display: block;
cursor: pointer;
}
#btn-getNewScheme:hover,
#btn-getNewScheme:focus {
background-position: right center; /* change the direction of the change here */
color: #fff;
text-decoration: none;
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Ubuntu:wght#300;400;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="index.css">
<title>Color Scheme Generator</title>
</head>
<body>
<div id="container">
<div>
<header><h1 class="site-title">🦎 Color Scheme Generator 🦎</h1></header>
</div>
<div id="form-div">
<form id="colorPick-form" method="get" >
<input id="colorPicker" type="color" />
<select name="colorPick-mode" id="colorPick-mode">
</select>
<button type='submit' id="btn-getNewScheme">Get Color Scheme</button>
</form>
</div>
<div id="main">
<div id="result-color-div">
</div>
<div id="result-code-div">
</div>
</div>
<script src="index.js" type="module"></script>
</body>
</html>
I think the problem is rendering timing. So you need to add event listener below the code where set innerHTML.
function renderColor() {
// to store result of color scheme set object
resultColorDivHtml = resultColorSchemeSet
.map((resultColorItem) => {
return `<div class="result-color" style="background-color: ${resultColorItem};"></div>`;
})
.join("");
resultCodeDivHtml = resultColorSchemeSet
.map((resultColor) => {
return `
<label for='${resultColor}' class='copy-label'>Click to copy!</label>
<input class='result-code' id='${resultColor}' type="text" value='${resultColor}'/>
`;
})
.join("");
resultColorDiv.innerHTML = resultColorDivHtml;
resultColorCodeDiv.innerHTML = resultCodeDivHtml;
// here! add event listener
const labels = document.getElementsByClassName("result-code");
Object.entries(labels).forEach(([key, label]) => {
label.addEventListener("click", (event) =>
alert(`copy color: ${event.target.value}`)
);
});
}
const resultColorCodeDiv=document.getElementById("resultColorCodeDiv")
const resultColorDiv=document.getElementById("resultColorDiv")
resultColorSchemeSet=[
{color:"red", code: "#ff0000"},
{color:"green", code: "#00ff00"},
{color:"blue", code: "#0000ff"}]
function renderColor(){
//to store result of color scheme set object
resultColorDivHtml = resultColorSchemeSet.map((resultColorItem) => {
return `<div class="result-color" style="background-color: ${resultColorItem.color};"></div>`
}).join('')
resultCodeDivHtml = resultColorSchemeSet
.map((resultColor) => {
return `
<label for='${resultColor.code}' class='copy-label'>Click to copy!</label>
<input class='result-code' id='${resultColor.code}' type="text" value='${resultColor.code}'/>`
})
.join("")
resultColorDiv.innerHTML = resultColorDivHtml
resultColorCodeDiv.innerHTML = resultCodeDivHtml
addListener(document.querySelectorAll(".result-color"))
addListener(document.querySelectorAll(".result-code"))
}
renderColor()
function addListener(elements){
for(const element of elements){
element.addEventListener("click" , ()=>{
// add copy logic here
console.log("hello")
})
}
}
<body>
<div id="resultColorDiv"></div>
<div id="resultColorCodeDiv"></div>
</body>

How to make Animated Web Speech API UI in HTML

I have to create a animation Like Google.com Dekstop Mic shows (i.e. scaling of the mic border according to the loudness of voice). I have used the Web Speech API with reference from here (MDN) which shows how we can change the background colour of the webpage using our voice, it work's fine but I want to add Animation Like Google's site( mentioned above).I have searched a lot to find a way to achieve this animation but I was unable to find this. So I am asking here as this is the best place where I can get my answer :) Thanks a lot in advance for helping me out with this.
I'm not expert in this area but I followed the example in MDN and here is the result.
Beside the setup, the key point here is analyser.getByteFrequencyData which gives us the decibel levels.
In order to simplify the code, I took the highest decibel level in the array (Math.max.apply(null, dataArray)) but you can fine tuning it by average or any other calculation you like.
Demo
let audioCtx = new (window.AudioContext || window.webkitAudioContext)();
let distortion = audioCtx.createWaveShaper();
let gainNode = audioCtx.createGain();
let biquadFilter = audioCtx.createBiquadFilter();
let analyser = audioCtx.createAnalyser();
analyser.minDecibels = -90;
analyser.maxDecibels = -10;
analyser.fftSize = 256;
const mic = document.querySelector('.mic');
let isListening = false;
let tracks = [];
if (!navigator.mediaDevices.getUserMedia) {
alert('getUserMedia not supported on your browser!');
}
mic.addEventListener('click', async () => {
if (!isListening) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
isListening = true;
tracks = stream.getTracks();
source = audioCtx.createMediaStreamSource(stream);
source.connect(distortion);
distortion.connect(biquadFilter);
biquadFilter.connect(gainNode);
gainNode.connect(analyser);
analyser.connect(audioCtx.destination);
requestAnimationFrame(function log() {
let bufferLength = analyser.frequencyBinCount;
let dataArray = new Uint8Array(bufferLength);
analyser.getByteFrequencyData(dataArray);
const level = Math.max.apply(null, dataArray);
document.querySelector('#level span').textContent = level;
mic.style.setProperty('--border', `${level / 5}px`);
requestAnimationFrame(log);
});
} catch (err) {
console.log('The following gUM error occured: ' + err);
}
} else {
isListening = false;
tracks.forEach((track) => {
track.stop();
});
}
});
body {
margin: 0;
height: 100vh;
position: relative;
}
.content {
height: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 20px;
}
.mic {
background: #fff;
width: 50px;
height: 50px;
border: 1px solid #eee;
border-radius: 100%;
bottom: 0;
box-shadow: 0 2px 5px var(--border) rgb(0 0 0 / 10%);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
<html>
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" type="text/css" href="styles.css" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css"
integrity="sha512-Fo3rlrZj/k7ujTnHg4CGR2D7kSs0v4LLanw2qksYuRlEzO+tcaEPQogQ0KaoGN26/zrn20ImR1DfuLWnOo7aBA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
/>
</head>
<body>
<div class="content">
<div class="mic">
<i class="fas fa-microphone"></i>
</div>
<div id="level">Level: <span></span></div>
</div>
<script src="script.js"></script>
</body>
</html>

Detect and notify when download is complete

Let's say I am having a drive link to download a zip file. When I click on the link, the download starts in the browser. Once the download is completed, I want to send an email to notify the user. Is it Possible. I have a .net application(C#) and a page that shows all the drive links. Once a drive link is clicked and gets downloaded completely, I want to send the mail. Also if the download fails in between, I want to send the failed email. Can I do that?
I have updated the below reference code. Add a proper download url link and try this out.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style type="text/css">
body {
padding: 0;
margin: 0;
}
svg:not(:root) {
display: block;
}
.playable-code {
background-color: #f4f7f8;
border: none;
border-left: 6px solid #558abb;
border-width: medium medium medium 6px;
color: #4d4e53;
height: 100px;
width: 90%;
padding: 10px 10px 0;
}
.playable-canvas {
border: 1px solid #4d4e53;
border-radius: 2px;
}
.playable-buttons {
text-align: right;
width: 90%;
padding: 5px 10px 5px 26px;
}
</style>
<style type="text/css">
.event-log {
width: 25rem;
height: 4rem;
border: 1px solid black;
margin: .5rem;
padding: .2rem;
}
input {
width: 11rem;
margin: .5rem;
}
</style>
<title>XMLHttpRequest: progress event - Live_example - code sample</title>
</head>
<body>
<div class="controls">
<input class="xhr success" type="button" name="xhr" value="Click to start XHR (success)" />
<input class="xhr error" type="button" name="xhr" value="Click to start XHR (error)" />
<input class="xhr abort" type="button" name="xhr" value="Click to start XHR (abort)" />
</div>
<textarea readonly class="event-log"></textarea>
<script>
const xhrButtonSuccess = document.querySelector('.xhr.success');
const xhrButtonError = document.querySelector('.xhr.error');
const xhrButtonAbort = document.querySelector('.xhr.abort');
const log = document.querySelector('.event-log');
function handleEvent(e) {
if (e.type=='progress')
{log.textContent = log.textContent + `${e.type}: ${e.loaded} bytes transferred Received ${event.loaded} of ${event.total}\n`;
}
else if (e.type=='loadstart')
{
log.textContent = log.textContent + `${e.type}: started\n`;
}
else if (e.type=='error')
{
log.textContent = log.textContent + `${e.type}: error\n`;
}
else if (e.type=='loadend')
{
log.textContent = log.textContent + `${e.type}: completed\n`;
}
}
function addListeners(xhr) {
xhr.addEventListener('loadstart', handleEvent);
xhr.addEventListener('load', handleEvent);
xhr.addEventListener('loadend', handleEvent);
xhr.addEventListener('progress', handleEvent);
xhr.addEventListener('error', handleEvent);
xhr.addEventListener('abort', handleEvent);
}
function runXHR(url) {
log.textContent = '';
const xhr = new XMLHttpRequest();
var request = new XMLHttpRequest();
addListeners(request);
request.open('GET', url, true);
request.responseType = 'blob';
request.onload = function (e) {
var data = request.response;
var blobUrl = window.URL.createObjectURL(data);
var downloadLink = document.createElement('a');
downloadLink.href = blobUrl;
downloadLink.download ='download.zip';
downloadLink.click();
};
request.send();
return request
}
xhrButtonSuccess.addEventListener('click', () => {
runXHR('https://abbbbbc.com/download.zip');
});
xhrButtonError.addEventListener('click', () => {
runXHR('http://i-dont-exist');
});
xhrButtonAbort.addEventListener('click', () => {
runXHR('https://raw.githubusercontent.com/mdn/content/main/files/en-us/_wikihistory.json').abort();
});
</script>
</body>
</html>
The download link works for zip files as well.
Reference:https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/progress_event#live_example

Qr & Barcode Reader javascript documentation for html5

I want to make an input where it can be automatically filled in after scanning a QR or barcode scanner using a webcam or phone cam.
for the script I imagined something like this
<video autoplay = "true" id = "video-webcam">
</video>
<input type = "text" id = "scanresult">
<script>
external or internal script for scan qr or barcode. save result in variable = result
html DOM getElementById ('scanresult'). value (result);
</script>
I hope anyone can give me suggestions or feedback for my problem.
Thank you
Before it thank you for JaromandaX,
i have found script for barcode scanner on Html5 using webcam.
this is my index.html
<!DOCTYPE html>
<html>
<head>
<title>QR Code Scanner</title>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width; initial-scale=1.0; maximum-scale=1.0; user-scalable=0;" />
<link rel="stylesheet" href="style.css" />
<script src="https://rawgit.com/sitepoint-editors/jsqrcode/master/src/qr_packed.js"></script>
</head>
<body>
<div id="container">
<h1>QR Code Scanner</h1>
<a id="btn-scan-qr">
<img src="https://dab1nmslvvntp.cloudfront.net/wp-content/uploads/2017/07/1499401426qr_icon.svg">
<a/>
<canvas hidden="" id="qr-canvas"></canvas>
<div id="qr-result" hidden="">
<b>Data:</b> <span id="outputData"></span>
</div>
</div>
<script src="qrCodeScanner.js"></script>
</body>
</html>
qsCodeScanner.js
//const qrcode = window.qrcode;
const video = document.createElement("video");
const canvasElement = document.getElementById("qr-canvas");
const canvas = canvasElement.getContext("2d");
const qrResult = document.getElementById("qr-result");
const outputData = document.getElementById("outputData");
const btnScanQR = document.getElementById("btn-scan-qr");
let scanning = false;
qrcode.callback = res => {
if (res) {
outputData.innerText = res;
scanning = false;
video.srcObject.getTracks().forEach(track => {
track.stop();
});
qrResult.hidden = false;
canvasElement.hidden = true;
btnScanQR.hidden = false;
}
};
btnScanQR.onclick = () => {
navigator.mediaDevices
.getUserMedia({ video: { facingMode: "environment" } })
.then(function(stream) {
scanning = true;
qrResult.hidden = true;
btnScanQR.hidden = true;
canvasElement.hidden = false;
video.setAttribute("playsinline", true); // required to tell iOS safari we don't want fullscreen
video.srcObject = stream;
video.play();
tick();
scan();
});
};
function tick() {
canvasElement.height = video.videoHeight;
canvasElement.width = video.videoWidth;
canvas.drawImage(video, 0, 0, canvasElement.width, canvasElement.height);
scanning && requestAnimationFrame(tick);
}
function scan() {
try {
qrcode.decode();
} catch (e) {
setTimeout(scan, 300);
}
}
style.css
html {
height: 100%;
}
body {
font-family: sans-serif;
padding: 0 10px;
height: 100%;
background: black;
margin: 0;
}
h1 {
color: white;
margin: 0;
padding: 15px;
}
#container {
text-align: center;
margin: 0;
}
#qr-canvas {
margin: auto;
width: calc(100% - 20px);
max-width: 400px;
}
#btn-scan-qr {
cursor: pointer;
}
#btn-scan-qr img {
height: 10em;
padding: 15px;
margin: 15px;
background: white;
}
#qr-result {
font-size: 1.2em;
margin: 20px auto;
padding: 20px;
max-width: 700px;
background-color: white;
}
There are lots of better libraries out there for doing this, but I'd personally recommend QrScanner because of its simplicity and intuitiveness.
Your live QrCode Scanner would be like this...
scanner.html
<div id="holder">
<h3>Scan QR Code from Camera</h3>
<div class="make">
<video id="scan"></video>
</div>
<div>
<input type = "text" id = "scanresult"><br>
<button id="start">Start</button>
<button id="stop">Stop</button>
</div>
</div>
Then add a little CSS as:
style.css
#holder{
width: 30%;
margin:auto;
}
#holder .make {
width: 99%;
height: 30vh;
margin-bottom: 15px;
text-align: center;
}
video {
width: 99%;
margin:auto;
}
Then add your QrScanner code as:
<script type="module">
import QrScanner from "/path/to/qr-scanner.min.js";
QrScanner.WORKER_PATH = "/path/to/qr-scanner-worker.min.js";
// Scanner Object
const scanner = new QrScanner(
document.getElementById("scan"),
function(result){
document.getElementById("scanresult").value = result;
}
);
document.getElementById("start").onclick = e => scanner.start();
document.getElementById("stop").onclick = e => scanner.stop();
</script>
Then connect your camera and click start button...

How can I make the speechRecognition and speechSynthesis API's work together?

I am currently working on an experiment using the Web Speech API. The idea is to use speech recognition and synthesis to improve the user's experience with HTML forms. Keep in mind that the code below is just a concept and has many issues. However, it seems that the speechRecognition API and the speechSynthesis API do not work well together. My desired result is that when an input or button receives focus, the label will be read using speechSynthesis and then it will listen to the user's answer using speechRecognition. That seems to be working as expected.
The issue is, that after the speechRecognition has started for the first time the speechSynthesis get's much lower in volume and somethings just fail (does not read). I have added some logic to abort the recognition on focus loss but this does not seem to work. Has anyone ever ran into this issue? Am I doing something wrong? Any help is welcome.
EDIT: StackOverflow does not ask for the user's mic permissions, please use this fiddle: https://jsfiddle.net/wzt0nfp3/ for a working demo.
const speechRecognition = !!window.SpeechRecognition || !!window.webkitSpeechRecognition ?
new(window.SpeechRecognition || window.webkitSpeechRecognition)() :
false;
const speechSynthesis =
"speechSynthesis" in window ? window.speechSynthesis : false;
if (!!speechRecognition && !!speechSynthesis) {
let voice = null;
speechRecognition.lang = "en-US";
speechRecognition.continuous = false;
speechRecognition.interimResults = false;
const state = {
speaking: false,
listening: false
};
function loadVoice() {
const voices = speechSynthesis.getVoices();
let defaultVoice = null;
let preferredVoice = null;
voices.forEach(voice => {
if (defaultVoice && preferredVoice) return;
if (voice.default) defaultVoice = voice;
if (voice.name.startsWith("Microsoft Jessa Online")) {
preferredVoice = voice;
}
});
voice = preferredVoice ? preferredVoice : defaultVoice;
}
loadVoice();
speechSynthesis.onvoiceschanged = loadVoice;
const abortRecognition = () => speechRecognition.abort();
const startRecognition = () => speechRecognition.start();
function speak(text) {
if (speechSynthesis.speaking) speechSynthesis.cancel();
if (text !== "") {
const utterThis = new SpeechSynthesisUtterance(text);
utterThis.lang = "en-US";
utterThis.voice = voice;
utterThis.volume = 1;
utterThis.pitch = 1;
utterThis.rate = 1;
utterThis.addEventListener("start", event => {
state.speaking = true;
console.log("Start Speaking.", state);
});
utterThis.addEventListener("error", event => {
state.speaking = false;
console.log("Error: " + event.error, state);
});
utterThis.addEventListener("end", event => {
startRecognition();
state.speaking = false;
console.log("Stop Speaking.", state);
});
speechSynthesis.speak(utterThis);
}
}
speechRecognition.addEventListener("start", event => {
state.listening = true;
console.log("Start Listening.", state);
});
speechRecognition.addEventListener("error", event => {
state.listening = false;
console.log("Error: " + event.error, state);
});
speechRecognition.addEventListener("end", event => {
state.listening = false;
console.log("Stop Listening.", state);
});
speechRecognition.addEventListener("result", event => {
if (typeof event.results === "undefined") return;
state.listening = false;
const capitalize = s => s.charAt(0).toUpperCase() + s.slice(1);
const transcript = capitalize(event.results[0][0].transcript.trim());
console.log("Transcript: " + transcript, state);
if (transcript !== "") {
const inputField = document.activeElement;
inputField.value = transcript;
}
});
document.querySelectorAll("input").forEach(input => {
input.addEventListener("blur", () => abortRecognition());
input.addEventListener("focus", e => {
speak(e.target.parentElement.textContent.trim());
});
});
document.querySelectorAll("textarea").forEach(textarea => {
textarea.addEventListener("blur", () => abortRecognition());
textarea.addEventListener("focus", e => {
speak(e.target.parentElement.textContent.trim());
});
});
document.querySelectorAll("button").forEach(button => {
button.addEventListener("blur", () => abortRecognition());
button.addEventListener("focus", e => {
speak(e.target.textContent.trim());
});
});
}
body {
font-family: sans-serif;
margin: 5% 0;
}
form {
display: flex;
flex-direction: column;
margin: 0 auto;
max-width: 600px;
width: 90%;
}
label {
align-items: center;
color: blue;
display: flex;
justify-content: space-between;
margin-bottom: 1em;
}
input,
textarea {
border: none;
border-bottom: 1px solid blue;
flex-grow: 1;
font-size: inherit;
margin-left: 1rem;
max-width: 350px;
padding: 0.5rem 0;
}
input:focus,
textarea:focus {
outline: none;
box-shadow: 0 1px 0 0 blue;
}
textarea {
resize: vertical;
}
button {
background-color: blue;
border-radius: 0;
border-radius: 3px;
border: none;
color: white;
font-size: inherit;
margin-top: 2rem;
padding: 0.75rem 5rem;
width: fit-content;
}
button:focus,
button:hover {
background-color: transparent;
border: 2px solid blue;
color: blue;
outline: none;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Web Speech API</title>
</head>
<body>
<form>
<label>My name is
<input type="text" placeholder="Your name" />
</label>
<label>My business is
<input type="text" placeholder="Company name" />
</label>
<label>You can email me at
<input type="email" placeholder="Email address" />
</label>
<label>You can call me at
<input type="tel" placeholder="Phone number" />
</label>
<label>My project is
<textarea rows="5" placeholder="Description of my project, budget, time constraints..."></textarea>
</label>
<button type="submit">Get in touch</button>
</form>
<script src="./index.js"></script>
</body>
</html>

Categories