I realize that the title may be a little confusing, so let me give some context.
I'm writing an extension to do some audio manipulation with Google Meet, and, after studying its behavior, I found a weird issue that I can't seem to wrap my head around.
Google Meet seems to use three <audio> elements to play audio, with each one having their own MediaStreams. Through some testing, it seems that:
Muting the <audio> element stops Google Meet's audio visualizations as to who is talking.
Swapping the .srcObject properties of two audio elements and then calling .play() on them does not affect Google Meet's audio visualizations.
These seem to point to Google Meet connecting the source MediaStream into its audio processing graph to create the visualizations rather than capturing the <audio> element, since I can swap MediaStreams without affecting the visualizations.
However, one more thing that I noticed seem to make no sense considering the past information:
Adding a new MediaStreamAudioSourceNode from the .srcObject of the <audio> element and connecting it to an AnalyserNode showed that, even when I mute the <audio> element, I can still analyse the audio being played through the MediaStream.
Here's some example code and outputs done through the browser console:
ac = new AudioContext();
an = ac.createAnalyser()
sn = ac.createMediaStreamSource(document.querySelectorAll("audio")[0].srcObject)
sn.connect(an)
function analyse(aNode) {
const ret = new Float32Array(aNode.frequencyBinCount);
aNode.getFloatTimeDomainData(ret);
return ret;
}
analyse(an)
// > Float32Array(1024) [ 0.342987060546875, 0.36688232421875, 0.37115478515625, 0.362457275390625, 0.35150146484375, 0.3402099609375, 0.321075439453125, 0.308746337890625, 0.29779052734375, 0.272552490234375, … ]
document.querySelectorAll("audio")[0].muted = true
analyse(an)
// > Float32Array(1024) [ -0.203582763671875, -0.258026123046875, -0.31134033203125, -0.34375, -0.372802734375, -0.396484375, -0.3919677734375, -0.36328125, -0.31689453125, -0.247650146484375, … ]
// Here, I mute the microphone on *my end* through Google Meet.
analyse(an)
// > Float32Array(1024) [ -0.000030517578125, 0, 0, -0.000030517578125, -0.000091552734375, -0.000091552734375, -0.000091552734375, -0.00006103515625, 0, 0.000030517578125, … ]
// The values here are much closer to zero.
As you can see, when the audio element is muted, the AnalyserNode can still pick up on the audio, but Meet's visualizations break. That is what I don't understand. How can that be?
How can a connected AnalyserNode not break when the <audio> element is muted, but something else is, without using .captureStream()?
Another weird thing is that it only happens on Chrome. On Firefox, Meet's visualizations work fine even if the audio element is muted. I think this might be related to a known Chrome issue where MediaStreams require a playing <audio> element to output anything to the audio graph (https://stackoverflow.com/a/55644983), but I can't see how that would affect a muted <audio> element.
It's a bit confusing but the behavior of AudioElement.captureStream() is actually different from using a MediaElementAudioSourceNode.
new MediaStreamAudioSourceNode(audioContext, audioElement.captureStream());
// is not equal to
new MediaElementAudioSourceNode(audioContext, audioElement);
The stream obtained by calling AudioElement.captureStream() is not affected by any volume changes on the audio element. Calling AudioElement.captureStream() will also not change the volume of the audio element itself.
However using a MediaElementAudioSourceNode will re-route the audio of an audio element into an AudioContext. The audio will be affected by any volume changes that are made to the audio element. Which means muting the audio element will result in muting the audio that gets piped into the AudioContext.
On top of that using a MediaElementAudioSourceNode will make the audio element itself silent.
I assume Google Meet uses a MediaElementAudioSourceNode for each audio element to process the audio.
I am recording an HTML5 Canvas Stream using MediaRecorder. Stream to MediaRecorder is a mixed stream - it is a mixture of (1) canvas.captureStream(30) (2) Audio Stream (since the canvas animation has an audio on the page)
When the recording is longer than, say 1 minute, the chrome tab crashes after the line:
var blob = new Blob(recordedChunks, { 'type' : 'video/mp4' });
When it is less, say 10 seconds, the crash does not occur.
The resulting video is big in dimensions. Not sure if that is the issue. My canvas animation is mostly images and mp4 being played in a sequence (think of it as a slide show)
How can I fix this crash? Even when there is not a crash it takes a long time for new Blob to complete before I get the final video.
Background
I am trying to create an interactive video player using the HTML5 video element.
I came across Media Source Extensions API, and got it working.
var mediaSource = new MediaSource();
video.src = window.URL.createObjectURL(mediaSource);
sourceBuffer = mediaSource.addSourceBuffer(mime);
Next I make a REST call to fetch the video segment and attach it to the source buffer.
sourceBuffer.appendBuffer(arrayBuffer);
Problem
I am trying to fire events when the newly loaded video segment reaches a certain time.
Also, I want to be able to seamlessly loop a video segment, which exits and continues to another video segment on interaction.
How can I achieve these features?
I need to show video from the users camera and virtual objects created with WebGL on a web page in a single html element, probably, either <canvas> or <video>.
I can successfully get user video, it is a stream, from navigator's media devices and show it in <video> element.
I can successfully create virtual visual objects with WebGL and show them in a <canvas> element, by using other's example code here (from MDN).
I need to mix them on a single html element. How can I achieve that.
My further research shows me that there is a captureStream() method of HTMLMediaElement interface. Both <video> and canvas have this method. I can capture the stream from such elements and use it for something else, like attaching into another html element (but not into a canvas element probably) or a WebRTC Peer Connection as source, recording it. But this overwrites the previous stream.
Then I have found that a stream, called MediaStream, has tracks inside them, like video tracks, audio tracks even text tracks. And more can be added by addTrack method of the MediaStream, and they can be gotten by getTracks method. I have added the video track from my <canvas> element's stream to the <video> elements stream, however, I can only view the original video track from the user media in the <video> element.
What am I missing to achieve that?
<html>
<body>
getting video stream from camera into screen
<video autoplay></video>
getting virtual objects into screen
<canvas id="glcanvas" width="640" height="480"></canvas>
</body>
// webgl codes that draws a rotating colored cube on html canvas webgl context
<script src="gl-matrix.js"></script>
<script src="webgl-demo.js"></script>
<script>
// getting video stream from camera into screen
const video = document.querySelector("video");
navigator.mediaDevices.getUserMedia({video: true})
.then(stream => {
let canv = document.querySelector("#glcanvas");
let canvstrm = canv.captureStream();
// get track from the canvas stream and add to the user media stream
let canvstrmtrack = canvstrm.getTracks()[0]
stream.addTrack(canvstrmtrack);
video.srcObject = stream;
})
</script>
</html>
Complete gist
A video element can only play a single video track at a time.
Support for this is found in the MediaCapture spec:
Since the order in the MediaStream's track set is undefined, no requirements are put on how the AudioTrackList and VideoTrackList is ordered
And in HTML:
Either zero or one video track is selected; selecting a new track while a previous one is selected will unselect the previous one.
It sounds like you expected the graphics to overlay the video, with e.g. black as transparent.
There are no good video composition tools in the web platform at the moment. About the only option is to repeatedly draw frames from the video element into a canvas, and use canvas.captureStream, but it is resource intensive and slow.
If you are merely doing this for playback (not recording or transmitting the result), then you might be able to achieve this effect much more cheaply by positioning a canvas with transparency on top of the video element using CSS. This approach also avoids cross-origin restrictions.
I'm working on a web project where user chooses a design of a mobile mockup and save some chat conversations.
As an output the application should give a high quality video (or 1080p at least) of the chat saved before so that it looks like the real chat conversation is captured.
As of now I'm drawing mockup and conversation on HTML5 Canvas and recording it with canvas.captureStream() method.
It is able to record upto 1280px wide canvas but when I tried Increasing it to achieve 1080p video. Canvas animations slows down and browser stop working sometimes.
I'm done with googling how to optimize canvas and all the stuff that can help me.
Looks like canvas is no more able to work for me, So is there any way to record DOM and render it as video.
I was using captureStream method of canvas
const stream = canvas.captureStream();
And mediaRecorder to capture it.
let options = {mimeType: 'video/webm'};
let mediaRecorder = new MediaRecorder(stream, options);
I expect to get a way of recording video of DOM in high quality. So that I can run chat with javascript and it records the same in order to achieve the output.