<html>
<head>
<title>Using Screen Capture API - RJM Programming - June, 2022 ... thanks to https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API/Using_Screen_Capture and https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API</title>
<style>
body {
padding: 0;
margin: 0;
background: linear-gradient(rgba(255,255,255,0.9),rgba(255,255,255,0.9)),url('./screen_capture_api_test.gif') right;
background-size: contain;
background-repeat: no-repeat;
}
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;
}
td { vertical-align: top; }
#video {
border: 1px solid #999;
width: 98%;
max-width: 860px;
}
.error {
color: red;
}
.warn {
color: orange;
}
.info {
color: darkgreen;
}
button {
vertical-align: top;
}
</style>
<script type='text/javascript'>
// Options for getDisplayMedia()
var displayMediaOptions = {
video: {
cursor: "always"
},
audio: true
}; //{ echoCancellation: true, noiseSuppression: true, sampleRate: 44100 }
//displayMediaOptions = {
// video: true,
// audio: true
//};
const gdmOptions = {
video: {
cursor: "always"
},
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 44100
}
};
let voiceStream, aStream, vStream;
let desktopStream;
var timev=-1, timea=-1;
var delaya=0;
var recordedChunks = [];
var mediaRecorder=null;
var blob=null, bloba=null;
var url=null, audioUrl=null, aaurl=null;
var a=null, aa=null;
var amediaRecorder=null, xblob=null, chunks=[], xaudio=null, xaudioURL=null, astream=null, microphone=null, audioCtx=null, acanvas=null;
const mergeAudioStreams = (desktopStream, voiceStream) => { // thanks to https://glitch.com/edit/#!/screen-record-voice?path=script.js%3A45%3A0
const context = new AudioContext();
const destination = context.createMediaStreamDestination();
let hasDesktop = false;
let hasVoice = false;
if (desktopStream && desktopStream.getAudioTracks().length > 0) {
// If you don't want to share Audio from the desktop it should still work with just the voice.
const source1 = context.createMediaStreamSource(desktopStream);
const desktopGain = context.createGain();
desktopGain.gain.value = 0.7;
source1.connect(desktopGain).connect(destination);
hasDesktop = true;
}
if (voiceStream && voiceStream.getAudioTracks().length > 0) {
const source2 = context.createMediaStreamSource(voiceStream);
const voiceGain = context.createGain();
voiceGain.gain.value = 0.7;
source2.connect(voiceGain).connect(destination);
hasVoice = true;
}
return (hasDesktop || hasVoice) ? destination.stream.getAudioTracks() : [];
};
function RequestAsBlob(inbloburl, infunction) {
return inbloburl;
}
async function startCapture() {
recordedChunks = [];
if (typeof(logElem) !== 'undefined') {
console.log(typeof(logElem));
console.log(typeof(stream));
logElem.innerHTML = "";
}
try {
if (typeof(videoElem) !== 'undefined') {
document.getElementById('astart').disabled=false;
timev=eval('' + (new Date()).getTime());
// videoElem.srcObject = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
desktopStream = await navigator.mediaDevices.getDisplayMedia({ video:true, audio:true });
videoElem.srcObject = desktopStream;
document.getElementById('download').disabled=false;
document.getElementById('stop').disabled=false;
if (document.getElementById('caudio').checked) {
voiceStream = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
document.getElementById('download').innerHTML+=' 🔊 ';
}
const tracks = [
...desktopStream.getVideoTracks(),
...mergeAudioStreams(desktopStream, voiceStream)
];
console.log('Tracks to add to stream', tracks);
vStream = new MediaStream(tracks);
console.log('Stream', vStream)
videoElem.srcObject = vStream;
videoElem.muted = true;
dumpOptionsInfo();
timev=eval('' + (new Date()).getTime());
}
} catch(err) {
console.error("Error: " + err);
}
if (typeof(stream) === 'undefined') {
//console.log('precanvas');
const canvas = document.querySelector("canvas");
//console.log('canvas=' + canvas);
// Optional frames per second argument.
const stream = canvas.captureStream(25);
//const recordedChunks = [];
//console.log(stream);
window.stream = stream;
const options = { mimeType: "video/webm;codecs=vp8,opus" }; // vs 9
//mediaRecorder = new MediaRecorder(window.stream, options);
//mediaRecorder = new MediaRecorder(videoElem.srcObject, options);
mediaRecorder = new MediaRecorder(vStream, options);
//const _this = this;
mediaRecorder.ondataavailable = function(e) {
//console.log("data_available " + e.data.size);
if (e.data.size > 0){
recordedChunks.push(e.data);
// _this.setState({recordedChunks:recordedChunks});
//download();
}
};
mediaRecorder.start(1000);
//mediaRecorder.onstop = (event) => {
//};
mediaRecorder.onstop = function(e) {
if (recordedChunks) {
if (1 == 5 && recordedChunks.length > 0 && document.getElementById('audio')) {
//const audio = document.querySelector('audio');
audio.controls = true;
bloba = new Blob(recordedChunks, { 'type' : 'audio/ogg;codecs=opus' });
audioURL = window.URL.createObjectURL(bloba);
audio.src = audioURL;
}
}
if (window.parent) {
if (('' + parent.document.URL.indexOf('screen_capture_api_va.htm')) != -1) {
parent.location.href=document.URL;
} else {
location.href=document.URL;
}
} else {
location.href=document.URL;
}
};
//setTimeout(event => {
// console.log("recording1 vs " + mediaRecorder.state);
// mediaRecorder.stop();
// console.log("recording2 vs " + mediaRecorder.state);
// mediaRecorder.requestData();
// console.log("recording3 vs " + mediaRecorder.state);
// mediaRecorder.start(1000);
// console.log("recording4 vs " + mediaRecorder.state);
//}, 9000);
//this.setState({mediaRecorder:mediaRecorder});
//mediaRecorder.ondataavailable = handleDataAvailable;
//mediaRecorder.start();
// demo: to download after 9sec
//setTimeout(event => {
// console.log("stopping");
// mediaRecorder.stop();
//}, 9000);
//} else if (window.stream) {
}
if (document.getElementById('mybody')) {
document.getElementById('mybody').style.backgroundImage='none';
}
}
function stopCapture(evt) {
//let tracks = videoElem.srcObject.getTracks();
let tracks = desktopStream.getTracks();
if (desktopStream.getAudioTracks().length > 0) { alert('Recording audio'); }
//tracks.forEach(track => mediaRecorder.addTrack(track, stream));
tracks.forEach(track => track.stop());
videoElem.srcObject = null;
document.getElementById('mybody').style.background="linear-gradient(rgba(255,255,255,0.9),rgba(255,255,255,0.9)),url('./screen_capture_api_test.gif') right";
document.getElementById('mybody').style.backgroundSize="contain";
document.getElementById('mybody').style.backgroundRepeat='no-repeat';
if (mediaRecorder) {
mediaRecorder.stop();
}
if (document.getElementById('adownload')) { astopCapture(); }
}
function dumpOptionsInfo() {
//const videoTrack = videoElem.srcObject.getVideoTracks()[0];
const videoTrack = desktopStream.getVideoTracks()[0];
console.info("Track settings:");
console.info(JSON.stringify(videoTrack.getSettings(), null, 2));
console.info("Track constraints:");
console.info(JSON.stringify(videoTrack.getConstraints(), null, 2));
download();
}
function handleDataAvailable(event) {
//console.log("data-available " + event.data.size);
if (event.data.size > 0) {
recordedChunks.push(event.data);
//console.log(recordedChunks);
//download();
} else {
// ...
}
}
function download() {
//console.log("downloading 0");
// let dtracks = videoElem.srcObject.getTracks();
//tracks.forEach(track => mediaRecorder.addTrack(track, stream));
// dtracks.forEach(track => window.stream.addTrack(track));
//console.log("recording1 vs " + mediaRecorder.state);
//mediaRecorder.stop();
//console.log("recording2 vs " + mediaRecorder.state);
if (mediaRecorder) {
if (('' + mediaRecorder.state) != 'inactive') {
mediaRecorder.requestData();
}
//console.log("recording3 vs " + mediaRecorder.state);
blob = new Blob(recordedChunks, {
type: "video/webm" //";codecs=vp8,opus"
});
url = URL.createObjectURL(blob);
if (1 == 11) {
RequestAsBlob(url,
function(vblob)
{
var vurl = URL.createObjectURL(vblob);
document.getElementById('playvideo').src = vurl;
document.getElementById('playvideo').play();
});
} else {
//document.getElementById('playvideov').src=url;
document.getElementById('playvideo').src = url;
document.getElementById('playvideo').play();
}
if (1 == 1) {
//document.getElementById('playvideov').style.display='block';
document.getElementById('playvideo').style.display='block';
document.getElementById('myplayt').style.backgroundColor='yellow';
document.getElementById('myplayt').scrollIntoView(); //location.href='#lastp'; //document.getElementById('lastp').scrollIntoView(); //document.getElementById('playvideo').scrollIntoView(); //location.href='#myhr';
window.scrollBy(0,120);
}
a = document.createElement("a");
document.body.appendChild(a);
a.style = "display: none";
a.href = url;
a.download = "screen_capture_api_va.webm";
a.click();
setTimeout(() => {
document.body.removeChild(a);
window.URL.revokeObjectURL(url);
window.scrollTo(0,document.body.scrollHeight);
}, 100);
//window.URL.revokeObjectURL(url);
//console.log("downloading 1");
//mediaRecorder.start(1000);
//console.log("recording4 vs " + mediaRecorder.state);
}
if (document.getElementById('adownload')) { adownload(); }
}
async function astartCapture() {
audioCtx = new AudioContext();
if (navigator.mediaDevices) {
//console.log('precanvas');
acanvas = document.querySelector("acanvas");
//console.log('canvas=' + canvas);
// Optional frames per second argument.
//const stream = canvas.captureStream(25);
//console.log(stream);
//window.stream = stream;
if (timev >= 0) {
timea=eval('' + (new Date()).getTime());
delaya=eval(timea - timev);
//alert(delaya);
}
navigator.mediaDevices.getUserMedia({ video:false, audio:true }).then((stream) => {
aStream = stream;
audioElem.srcObject = stream;
document.getElementById('astart').disabled=true;
document.getElementById('download').innerHTML+='🎤';
document.getElementById('download').disabled=false;
document.getElementById('stop').disabled=false;
microphone = audioCtx.createMediaStreamSource(stream);
// Instantiate the media recorder.
//amediaRecorder = new MediaRecorder(audioElem.srcObject); //audioElem.srcObject);
amediaRecorder = new MediaRecorder(aStream); //audioElem.srcObject);
// Create a buffer to store the incoming data.
chunks = [];
amediaRecorder.ondataavailable = (event) => {
chunks.push(event.data);
}
amediaRecorder.start(1000);
amediaRecorder.onstop = () => {
// A "blob" combines all the audio chunks into a single entity
xblob = new Blob(chunks, {"type": "audio/ogg;codecs=opus"});
chunks = []; // clear buffer
// One of many ways to use the blob
xaudio = new Audio();
xaudioURL = window.URL.createObjectURL(xblob);
xaudio.src = xaudioURL;
}
// `microphone` can now act like any other AudioNode
}).catch((err) => {
// browser unable to access microphone
// (check to see if microphone is attached)
});
} else {
// browser unable to access media devices
// (update your browser)
}
}
function astopCapture() {
//let atracks = audioElem.srcObject.getTracks();
let atracks = aStream.getTracks();
//tracks.forEach(track => amediaRecorder.addTrack(track, stream));
atracks.forEach(track => track.stop());
audioElem.srcObject = null;
if (amediaRecorder) {
amediaRecorder.stop();
}
}
function adownload() {
//console.log("downloading 0");
// let dtracks = videoElem.srcObject.getTracks();
//tracks.forEach(track => mediaRecorder.addTrack(track, stream));
// dtracks.forEach(track => window.stream.addTrack(track));
//console.log("recording1 vs " + mediaRecorder.state);
//mediaRecorder.stop();
//console.log("recording2 vs " + mediaRecorder.state);
if (amediaRecorder) {
if (('' + amediaRecorder.state) != 'inactive') {
amediaRecorder.requestData();
}
//console.log("recording3 vs " + amediaRecorder.state);
xblob = new Blob(chunks, {
type: "audio/ogg;codecs=opus"
});
aaurl = URL.createObjectURL(xblob);
if (1 == 11) {
RequestAsBlob(aaurl,
function(aablob)
{
var aaaurl = URL.createObjectURL(aablob);
document.getElementById('playvideoa').src = aaaurl;
document.getElementById('playvideoa').play();
});
} else {
//document.getElementById('playvideoa').src=url;
document.getElementById('playvideoa').src = aaurl;
if (delaya == 0) {
document.getElementById('playvideoa').play();
} else {
document.getElementById('divsync').innerHTML='<br><br>Clicking within video synchronizes an audiovisual replay';
document.getElementById('playvideo').title='Clicking within this video (not clicking play button) synchronizes an appropriate audiovisual replay';
playa(); //setTimeout(function(){ document.getElementById('playvideoa').play(); }, delaya);
//document.getElementById('playvideo').addEventListener('click', playa);
}
}
if (8 == 8) {
document.getElementById('playvideoa').style.display='block';
document.getElementById('playvideo').style.display='block';
}
aa = document.createElement("a");
document.body.appendChild(aa);
aa.style = "display: none";
aa.href = aaurl;
aa.download = "screen_capture_api_va.ogg";
aa.click();
setTimeout(() => {
document.body.removeChild(aa);
window.URL.revokeObjectURL(aaurl);
}, 100);
//window.URL.revokeObjectURL(url);
//console.log("downloading 1");
//amediaRecorder.start(1000);
//console.log("recording4 vs " + amediaRecorder.state);
}
}
function playa() {
setTimeout(function(){ document.getElementById('playvideoa').play(); }, delaya);
}
function dostopit() {
astopCapture();
}
</script>
</head>
<body id=mybody data-onload=onl();>
<table id=mytable><tr><td><button id=start>Capture<br>Screen<br>Start ▶</button> <table style='display:inline-block;'><tr><td><input title='Leave checked and capture of button to left (which should be clicked first for any screen capturing) is a single webm video with audio and video tracks synchronized whereas unchecking this checkbox and button to right usage has timer synchronization of a video (with only video tracks) with an audio (only with audio tracks) element playing so as to finish at the same time.' type=checkbox id=caudio checked></input><button id=astart>Audio ▶</button><button style='display:none;' onclick="adownload();" id=adownload>Download 🎤</button></td></tr><tr><td><button onclick="download();" id=download disabled>Download 📹</button></td></tr></table> <button id=stop disabled>Stop ⏹</button><button id=astop style='display:none;'>Stop ⏹</button><br><br>
<video id=video autoplay></video><audio id=audio autoplay></audio>
<br>
<!--strong id=stronge>Log:</strong>
<br>
<pre id="log"></pre--></td><td style="width:20%;"><h2>Using Screen Capture API</h2><h3>RJM Programming - June, 2022</h3><h4>Thanks to <a target=_blank href='//developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API/Using_Screen_Capture' title='https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API/Using_Screen_Capture'>https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API/Using_Screen_Capture</a> and <a target=_blank title='https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API' href='//developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API'>https://developer.mozilla.org/en-US/docs/Web/API/MediaStream_Recording_API </a></h4><br><br><strong id=stronge>Log:</strong>
<br>
<pre id="log"></pre><canvas id=canvas></canvas></td></tr></table>
<script type='text/javascript'>
const videoElem = document.getElementById("video");
const audioElem = document.getElementById("audio");
const logElem = document.getElementById("log");
const startElem = document.getElementById("start");
const stopElem = document.getElementById("stop");
const astartElem = document.getElementById("astart");
const astopElem = document.getElementById("astop");
console.log = msg => logElem.innerHTML += `${msg}<br>`;
console.error = msg => logElem.innerHTML += `<span class="error">${msg}</span><br>`;
console.warn = msg => logElem.innerHTML += `<span class="warn">${msg}<span><br>`;
console.info = msg => logElem.innerHTML += `<span class="info">${msg}</span><br>`;
// Set event listeners for the start and stop buttons ... thanks to https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API/Using_Screen_Capture
startElem.addEventListener("click", function(evt) {
startCapture();
}, false);
stopElem.addEventListener("click", function(evt) {
stopCapture();
}, false);
// Set event listeners for the start and stop buttons ... thanks to https://developer.mozilla.org/en-US/docs/Web/API/Screen_Capture_API/Using_Screen_Capture
astartElem.addEventListener("click", function(evt) {
astartCapture();
}, false);
astopElem.addEventListener("click", function(evt) {
astopCapture();
}, false);
if (document.URL.indexOf('iframe=') == -1) {
document.getElementById('video').style.display='none';
document.getElementById('audio').style.display='none';
document.getElementById('log').style.display='none';
document.getElementById('start').style.display='none';
document.getElementById('stop').style.display='none';
document.getElementById('stronge').style.display='none';
document.getElementById('mytable').style.display='none';
document.body.style.backgroundImage='none';
document.write('<div style="overflow-y:visible;width:100%;height:' + eval(1.2 * eval('' + screen.height)) + 'px;"><iframe class="sample-code-frame" title="Simple screen capture sample" id="frame_simple_screen_capture" min-width="640" data-min-height="680" width="100%" height="' + eval(1.2 * eval('' + screen.height)) + '" style="' + eval(1.2 * eval('' + screen.height)) + 'px;" src="./screen_capture_api_test.htm?iframe=yes' + Math.floor(Math.random() * 19875643) + '" allow="display-capture" loading="lazy"></iframe></div>');
}
</script>
<hr id=myhr>
<table id=myplayt style="width:96%;"><tr><td style="width:70%;">
<video id=playvideo style='display:none;width:95%;' type='video/webm' onloadeddata='playa();' onclick='playa();' controls>
<!--source id=playvideov type='video/webm' style='display:none;' src=''></source-->
<!--source id=playvideoas type='audio/ogg' style='display:none;' src=''></source-->
</video></td><td style="width:30%;">
<audio id=playvideoa style='display:none;width:95%;' type='audio/ogg' controls></audio><br><div id=divsync></div></td></tr></table><br><br><p id=lastp></p>
</body>
</html>