Is it possible to feed MediaStream frames at lower framerate? - html5-canvas

I would like to use MediaStream.captureStream() method, but it is either rendered useless due to specification and bugs or I am using it totally wrong.
I know that captureStream gets maximal framerate as the parameter, not constant and it does not even guarantee that, but it is possible to change MediaStream currentTime (currently in Chrome, in Firefox it has no effect but in return there is requestFrame, not available at Chrome), but the idea of manual frame requests or setting the placement of the frame in the MediaStream should override this effect. It doesn't.
In Firefox it smoothly renders the video, frame by frame, but the video result is as long as wall clock time used for processing.
In Chrome there are some dubious black frames or reordered ones (currently I do not care about it until the FPS matches), and the manual setting of currentTime gives nothing, the same result as in FF.
I use modified code from MediaStream Capture Canvas and Audio Simultaneously answer.
const FPS = 30;
var cStream, vid, recorder, chunks = [], go = true,
Q = 61, rec = document.getElementById('rec'),
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
ctx.strokeStyle = 'rgb(255, 0, 0)';
function clickHandler() {
this.textContent = 'stop recording';
//it has no effect no matter if it is empty or set to 30
cStream = canvas.captureStream(FPS);
recorder = new MediaRecorder(cStream);
recorder.ondataavailable = saveChunks;
recorder.onstop = exportStream;
this.onclick = stopRecording;
recorder.start();
draw();
}
function exportStream(e) {
if (chunks.length) {
var blob = new Blob(chunks)
var vidURL = URL.createObjectURL(blob);
var vid2 = document.createElement('video');
vid2.controls = true;
vid2.src = vidURL;
vid2.onend = function() {
URL.revokeObjectURL(vidURL);
}
document.body.insertBefore(vid2, vid);
} else {
document.body.insertBefore(document.createTextNode('no data saved'), canvas);
}
}
function saveChunks(e) {
e.data.size && chunks.push(e.data);
}
function stopRecording() {
go = false;
this.parentNode.removeChild(this);
recorder.stop();
}
var loadVideo = function() {
vid = document.createElement('video');
document.body.insertBefore(vid, canvas);
vid.oncanplay = function() {
rec.onclick = clickHandler;
rec.disabled = false;
canvas.width = vid.videoWidth;
canvas.height = vid.videoHeight;
vid.oncanplay = null;
ctx.drawImage(vid, 0, 0);
}
vid.onseeked = function() {
ctx.drawImage(vid, 0, 0);
/*
Here I want to include additional drawing per each frame,
for sure taking more than 180ms
*/
if(cStream && cStream.requestFrame) cStream.requestFrame();
draw();
}
vid.crossOrigin = 'anonymous';
vid.src = 'https://dl.dropboxusercontent.com/s/bch2j17v6ny4ako/movie720p.mp4';
vid.currentTime = 0;
}
function draw() {
if(go && cStream) {
++Q;
cStream.currentTime = Q / FPS;
vid.currentTime = Q / FPS;
}
};
loadVideo();
<button id="rec" disabled>record</button><br>
<canvas id="canvas" width="500" height="500"></canvas>
Is there a way to make it operational?
The goal is to load video, process every frame (which is time consuming in my case) and return the processed one.
Footnote: I do not want to use ffmpeg.js, external server or other technologies. I can process it by classic ffmpeg without using JavaScript at all, but this is not the point of this question, it is more about MediaStream usability / maturity. The context is Firefox/Chrome here, but it may be node.js or nw.js as well. If this is possible at all or awaiting bug fixes, the next question would be feeding audio to it, but I think it would be good as separate question.

Related

Updating Texture2D frequently causes process to crash (UpdateSubresource)

I am using SharpDX to basically render browser (chromium) output buffer on directX process.
Process is relatively simple, I intercept CEF buffer (by overriding OnPaint method) and write that to a texture2D.
Code is relatively simple:
Texture creation:
public void BuildTextureWrap() {
var oldTexture = texture;
texture = new D3D11.Texture2D(DxHandler.Device, new D3D11.Texture2DDescription() {
Width = overlay.Size.Width,
Height = overlay.Size.Height,
MipLevels = 1,
ArraySize = 1,
Format = DXGI.Format.B8G8R8A8_UNorm,
SampleDescription = new DXGI.SampleDescription(1, 0),
Usage = D3D11.ResourceUsage.Default,
BindFlags = D3D11.BindFlags.ShaderResource,
CpuAccessFlags = D3D11.CpuAccessFlags.None,
OptionFlags = D3D11.ResourceOptionFlags.None,
});
var view = new D3D11.ShaderResourceView(
DxHandler.Device,
texture,
new D3D11.ShaderResourceViewDescription {
Format = texture.Description.Format,
Dimension = D3D.ShaderResourceViewDimension.Texture2D,
Texture2D = { MipLevels = texture.Description.MipLevels },
}
);
textureWrap = new D3DTextureWrap(view, texture.Description.Width, texture.Description.Height);
if (oldTexture != null) {
obsoleteTextures.Add(oldTexture);
}
}
That piece of code is executed at start and when resize is happening.
Now when CEF OnDraw I basically copy their buffer to texture:
var destinationRegion = new D3D11.ResourceRegion {
Top = Math.Min(r.dirtyRect.y, texDesc.Height),
Bottom = Math.Min(r.dirtyRect.y + r.dirtyRect.height, texDesc.Height),
Left = Math.Min(r.dirtyRect.x, texDesc.Width),
Right = Math.Min(r.dirtyRect.x + r.dirtyRect.width, texDesc.Width),
Front = 0,
Back = 1,
};
// Draw to the target
var context = targetTexture.Device.ImmediateContext;
context.UpdateSubresource(targetTexture, 0, destinationRegion, sourceRegionPtr, rowPitch, depthPitch);
There are some more code out there but basically this is only relevant piece. Whole thing works until OnDraw happens frequently.
Apparently if I force CEF to Paint frequently, whole host process dies.
This is happening at UpdateSubresource.
So my question is, is there another, safer way to do this? (Update texture frequently)
Solution to this problem was relatively simple yet not so obvious at the beginning.
I simply moved the code responsible for updating texture inside render loop and just keep internal buffer pointer cached.

How to add countdown timer to Phaser game

I am trying to add a simple countdown timer to a Phaser game. When the timer ends I want the game to restart. After adding the relevant code there are no errors in the console but I can't get the timer to appear on the screen. I am new to Phaser and am still learning Javascript. Where I am going wrong please? I have posted only the relevant code below, and the code used for already existing text in the game that is working fine (text to count coins collected).
PlayState = {};
PlayState.init = function () {
//....
};
PlayState.preload = function () {
this.game.load.image('font:numbers', 'images/numbers.png'); //for the
//HUD coin count - not the timer
};
PlayState.create = function () {
//TIMER CODE:
this.timeInSeconds = 120;
this.timeText = this.game.add.text(this.game.world.centerX,
this.game.world.centerY, "0:00",{font: '15px Arial', fill: '#FFFFFF', align:
'center'});
this.timeText.anchor.set(0.5, 0.5);
this.timer = this.game.time.events.loop(Phaser.Timer.SECOND,
this.updateTimer, this);
};
PlayState.update = function () {
this.coinFont.text = `x${this.coinPickupCount}`; //for HUD coin count not
//the timer
};
//TIMER CODE:
PlayState.updateTimer = function() {
this.timeInSeconds--;
var minutes = Math.floor(this.timeInSeconds / 60);
var seconds = this.timeInSeconds - (minutes * 60);
var timeString = this.addZeros(minutes) + ":" + this.addZeros(seconds);
this.timeText.text = timeString;
if (this.timeInSeconds == 0) {
this.game.state.restart();
}
};
//add leading zeros to any number less than 10
//for example turn 1 to 01
PlayState.addZeros = function(num) {
if (num < 10) {
num = "0" + num;
}
return num;
};
//BELOW IS CODE FOR THE COIN COUNT NOT THE TIMER
PlayState._createHud = function () {
this.keyIcon = this.game.make.image(0, 30, 'icon:key');
this.keyIcon.anchor.set(0, 0.5);
const NUMBERS_STR = '0123456789X ';
this.coinFont = this.game.add.retroFont('font:numbers', 20,
26,NUMBERS_STR, 6);
let coinIcon = this.game.make.image(this.keyIcon.width + 7,
0, 'icon:coin');
let coinScoreImg = this.game.make.image(coinIcon.x +
coinIcon.width, coinIcon.height / 2, this.coinFont);
coinScoreImg.anchor.set(0, 0.5);
this.hud = this.game.add.group();
this.hud.add(coinIcon);
this.hud.position.set(10, 10);
this.hud.add(coinScoreImg);
this.hud.add(this.keyIcon);
this.hud.fixedToCamera = true;
};
window.onload = function () {
let game = new Phaser.Game(1280, 800, Phaser.CANVAS, 'game');
game.state.add('play', PlayState);
game.state.start('play');
};
I have finally solved the issue. The text was not showing because it was being rendered AFTER the background image in create. So it was there but being hidden by the background image. I simply moved the timer code to the end of create and it now works.
PlayState.create = function () {
this.game.world.setBounds(0, 0, 2560, 800);
background1 = this.game.add.sprite(0, 0, 'background');
background2 = this.game.add.sprite(1280, 0, 'background2');
this.game.scale.scaleMode = Phaser.ScaleManager.SHOW_ALL;
this.game.scale.setMinMax(480,320,1280,800);
this.game.scale.windowConstraints.bottom = 'visual';
this.game.add.image(0, 0, 'background');
this._loadLevel(this.game.cache.getJSON('level:1'));
this._createHud();
//TIMER CODE SHOULD GO HERE AND NOT AT THE BEGINNING OF CREATE
this.timeInSeconds = 120;
this.timeText = this.game.add.text(220, 30, "0:00",{font: '30px Arial', fill:
'#FFFFFF', align: 'center'});
this.timeText.anchor.set(0.5, 0.5);
this.timer = this.game.time.events.loop(Phaser.Timer.SECOND, this.updateTimer,
this);
};
You set the initial text to "0:00" and even that doesn't show on screen? First thing I would do is look at the coordinates where the text is located, maybe it's not visible off screen. Instead of this.game.world.centerX, this.game.world.centerY try something like 100,100, does it show up then? Also try to set very long initial text, so something like "blah test ABC 123" instead of "0:00" maybe makes some difference.
Secondly, maybe the Arial font is not available for some reason. If you leave out the {font: '15px..'center'} part it will use a default font, does that change anything?
Thirdly, you say you didn't post all your code here, but maybe you accidentally overwrite the variable this.timeText somewhere in you code? So check that you don't set that variable to be something else.
Finally, I would add a console.log to the updateTimer function, just to see if it is being called. so:
PlayState.updateTimer = function() {
console.log('updateTimer was called: ' + this.timeInSeconds);
this.timeInSeconds--;
// etc.

postman - how to check picture size

I am trying to write some tests in Postman (I am running the Postman Jetpacks packaged app, if it does matter) and I am facing some inconsistencies.
The scope of my test is to verify the size (width and height) of a set of images against some predefined values.
The scenario is like this: I make a call to a method that returns some urls, then I set the URLs as environment variables and then I verifying the size of each picture. Below is the code I am using in Tests tab in Postman.
tests["Status code is 200"] = responseCode.code === 200;
var data = JSON.parse(responseBody);
//test that response contains the expected attributes
tests["splash_image_url present"] = data.hasOwnProperty("splash_image_url");
tests["home_image_url present"] = data.hasOwnProperty("home_image_url");
tests["login_image_url present"] = data.hasOwnProperty("login_image_url");
tests["register_image_url present"] = data.hasOwnProperty("register_image_url");
tests["splash_logo_url present"] = data.hasOwnProperty("splash_logo_url");
tests["bar_logo_url present"] = data.hasOwnProperty("bar_logo_url");
//set each image URL as environment variable
postman.setEnvironmentVariable("splash_image_url", data.splash_image_url);
postman.setEnvironmentVariable("home_image_url", data.home_image_url);
postman.setEnvironmentVariable("login_image_url", data.login_image_url);
postman.setEnvironmentVariable("register_image_url", data.register_image_url);
postman.setEnvironmentVariable("splash_logo_url", data.splash_logo_url);
postman.setEnvironmentVariable("bar_logo_url", data.bar_logo_url);
//extract images from each URL
var splash_image_url = document.createElement("img");
splash_image_url.src = environment.splash_image_url;
var home_image_url = document.createElement("img");
home_image_url.src = environment.home_image_url;
var login_image_url = document.createElement("img");
login_image_url.src = environment.login_image_url;
var register_image_url = document.createElement("img");
register_image_url.src = environment.register_image_url;
var splash_logo_url = document.createElement("img");
splash_logo_url.src = environment.splash_logo_url;
var bar_logo_url = document.createElement("img");
bar_logo_url.src = environment.bar_logo_url;
//test the size for each picture
tests["splash_image_url width"] = splash_image_url.width === 640;
tests["splash_image_url height"] = splash_image_url.height === 960;
tests["home_image_url width"] = home_image_url.width === 640;
tests["home_image_url height"] = home_image_url.height === 960;
tests["login_image_url width"] = login_image_url.width === 640;
tests["login_image_url height"] = login_image_url.height === 960;
tests["register_image_url width"] = register_image_url.width === 640;
tests["register_image_url height"] = register_image_url.height === 960;
tests["splash_logo_url width"] = splash_logo_url.width === 310;
tests["splash_logo_url height"] = splash_logo_url.height === 80;
tests["bar_logo_url width"] = bar_logo_url.width === 155;
tests["bar_logo_url height"] = bar_logo_url.height === 40;
The problem is that sometimes when running the request all or some of the picture size verification fail. If I continue to manually run the same request again and again it will eventually show all the tests passed. This inconsistency makes the test unreliable.
I am missing something or doing something wrong? Is there a batter way to verify the picture size?
Thanks
Very nice question, was a fun challenge. Thanks !
1. Your problem
The tests actually work after you manually run them a couple of times, because the images are cached at that point.
The main issue here, is that you are not waiting for images to actually load, before checking the properties associated with them.
2. Postman Test Results
I tried a proof of concept on this by waiting for the images to actually load and found out... that my tests were not actually being displayed either as passing or failing.
This was mainly due to the way tests are run. Postman uses eval in the context of the request (more or less).
in Evaluator.js # 111
if (command === "runcode") {
try {
var result = eval(code);
event.source.postMessage({'type': 'test_result', 'result': result, 'scriptType': scriptType}, event.origin);
}
catch(e) {
console.log(e);
event.source.postMessage({'type': 'test_error', 'errorMessage': e.message, 'scriptType': scriptType}, event.origin);
}
}
Unfortunately for us, any sort of the callback logic will not get retroactively pushed back into the results chain.
2.1 Postman Secondary Test Results
It seems that posting a new set of results to event.source will not trigger a new set of results, but get completely discarded.
I have managed to find a workaround for this. Simply end the script with:
function postmanJetpacksSupportsOnlyOneResultPerTest()
{
event.source.postMessage({'type': 'test_result', 'result': tests, 'scriptType': 'test'}, event.origin);
}
throw 'ignore this. Enforcing failure on this test case, real values will come by manually calling *postmanJetpacksSupportsOnlyOneResultPerTest* when you are done with the test case.';
Then just call postmanJetpacksSupportsOnlyOneResultPerTest when all of your callbacks are done.
3. Postman Developers
I really hope you can somehow include the concept of promises. Example:
In the test runner:
var defered = new jQuery.Deferred();
tests['before timeout'] = true;
setTimeout(function() {
tests['on timeout'] = true;
defered.resolve(tests);
}, 500);
tests['after timeout'] = true;
return defered.promise();
In Evaluator.js # 113:
var result = eval(code);
if (result.promise instanceof Function) {
result.then(function(result) {
event.source.postMessage({'type': 'test_result', 'result': result, 'scriptType': scriptType}, event.origin);
});
} else {
event.source.postMessage({'type': 'test_result', 'result': result, 'scriptType': scriptType}, event.origin);
}
4. An example
// handlers
var waiting = 0;
function errorHandler() {
waiting--;
tests[this.image.name + ' load'] = false;
}
function successHandler() {
waiting--;
tests[this.image.name + ' load'] = true;
tests[this.image.name + ' width'] = this.image.width == this.width;
tests[this.image.name + ' height'] = this.image.height == this.height;
}
// test case kind of
function createImageTest(name, url, width, height)
{
// create image tag
var image = document.createElement('img');
// set the name
image.name = name;
// set error handlers
image.onerror = errorHandler.bind({
image: image
});
image.onload = successHandler.bind({
image: image,
width: width,
height: height
});
// finally attach the src
image.src = url;
// waiting on either a fail or a load
waiting++;
}
// the actual test cases
createImageTest('stackexchange logo', 'http://cdn.sstatic.net/stackexchange/img/se-logo.png', 223, 52);
createImageTest('stackoverflow logo', 'http://cdn.sstatic.net/stackoverflow/img/sprites.png', 240, 500);
// wait for all callbacks finished
(function checkFinished(){
// still images to process
if (waiting) {
// check back in another 0.1 seconds
return setTimeout(checkFinished, 100);
}
// ready to send result
postmanJetpacksSupportsOnlyOneResultPerTest();
})();
// the hack from #2.1
function postmanJetpacksSupportsOnlyOneResultPerTest()
{
event.source.postMessage({'type': 'test_result', 'result': tests, 'scriptType': 'test'}, event.origin);
}
throw 'ignore this. Enforcing failure on this test case, real values will come from init...';

Clicking or hovering over multiple images in canvas?

I am working on a project for my programming class. I'd like to essentially have a canvas with a background element (say a room.jpg) and then maybe three interactive objects in the room (lamp.jpg, couch.jpg, desk.jpg). I'd like for it to be that if you hover over the lamp a small box or text pops out, giving you some information. Or maybe have it so if you click an image, the same concept happens. You know, something interactive with the objects in the canvas. Again, I'm new to canvas but we have to use it in our assignment. My current code is:
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
var numImages = 0;
// get num of sources
for(var src in sources) {
numImages++;
}
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
var sources = {
room: 'room.jpg',
title: 'title.jpg'
};
loadImages(sources, function(images) {
context.drawImage(images.room, 0,0);
context.drawImage(images.title, 0,0);
});
}
But from what I understand, it makes the two jpegs a "permanent" canvas element (unable to be messed with). I had been trying to get it so that when I clicked I'd go from the title.jpg to the room.jpg but I've since given up. Essentially, all I want now is just to have the room.jpg appear when the page is first loaded, and have three other png objects on top (as objects in the room). Are these able to be interacted with, or do I have to put the images into the canvas in another way? Thanks for all your help and your patience!
// --- Image Loader ----
var images = {};
var loadedImages = 0;
var pictures = {
room: 'room.jpg',
title: 'title.jpg'
lamp1: 'lampoff.jpg'
lamp2: 'lampon.jpg'
};
function loadImages(sources, callback) {
var numImages = 0;
for(var src in sources)numImages++;
for(var src in sources) {
images[src] = new Image();
images[src].onload = function() {
if(++loadedImages >= numImages) {
callback(images);
}
};
images[src].src = sources[src];
}
}
// --- Mouse Down Functionality ----
$('#canvas').addEventListener('mouseDown', function(e){
if(e.clientX){
var rect = this.getBoundingClientRect();
if(rect) clickCanvas(e.clientX - rect.left, e.clientY - rect.top)
else clickCanvas(e.clientX - this.offsetLeft, e.clientY - this.offsetTop);
}else if(e.offsetX) clickCanvas(e.offsetX, e.offsetY);
else if(e.layerX) clickCanvas(e.layerX, e.layerY);
else console.warn("Couldn't Determine Mouse Coordinates");
})
var lampOn;
function drawCanvas(showLamp){
lampOn = showLamp;
canvas.width = canvas.width //clears canvas
context.drawImage(images.room, 0,0);
context.drawImage(images.title, 0,0);
if(lampOn){
context.drawImage(images.lamp2, 100,100);
}else{
context.drawImage(images.lamp1, 100,100);
}
}
function clickCanvas(x,y){
console.log('clicked canvas at:',x,y)
if(clickedLamp){
drawCanvas(!lampOn)
}
}
loadImages(pictures, function(images) {
drawCanvas(false)
});
Make sure to replace "clickedLamp" and "#canvas"! The idea here is that you redraw the same canvas, using the same function. Everytime you modify any of it, you rerender ALL of it. See if you can get this example working, it will help clarify alot. If you don't understand something comment

JavaScript for loop and print like animation (Canvas)

I have question about my code.
It is really working weird.... I have no clue why this happens but let me explain. (MY English is bad but I will try my best to explain and give you guys good picture what is going on)
var spaceshipIncoming = document.createElement('audio');
var canvasMS12Main = document.getElementById('canvasMS12Main');
var imgMainMs12 = canvasMS12Main.getContext('2d');
var mainMs12 = new Image();
var number2 = 5;
var formatImage = ".png";
var stopeIncreaseMS12 = false;
function introMenu(){ //509,734,306,345
imgMain.drawImage(mainImg, 6, 8, gameWidth, gameHeight, 0, 0, gameWidth, gameHeight);
audioElement.setAttribute('src', 'soma.mp3');
audioElement.play();
}
function ms12AniAndSound(){
setTimeout("introMS12()",5000);
spaceshipIncoming.setAttribute('src', 'spaceshipIncoming.mp3');
spaceshipIncoming.play();
}
function introMS12(){//78
if(number2 < 78){
number2 = number2 + 1;
}else if (number2 == 78){
stopeIncreaseMS12 = true;
}
var num = new Number(number2);
mainMs12.src = 'ms12IntroAnimation/introMS12-' + num + formatImage;
clearMainMS12();
imgMainMs12.drawImage(mainMs12, 0, 0, gameWidth, gameHeight, 0, 0, gameWidth, gameHeight);
delayMS12();
}
function delayMS12(){
if(stopeIncreaseMS12 == false){
setTimeout("introMS12()",25);
}else if(stopeIncreaseMS12 == true){
clearMainMS12();
}
}
function clearMainMS12(){
imgMainMs12.clearRect(0, 0, 800, 500);
}
I have folder that has animation images, and the each PNG files are named like: introMS12-#. and each # starts from 6 to 78 (so basically it looks like introMS12-6.png, introMS12-7.png, introMS12-8.png.....introMS12-78.png)
When I first load the webpage, it doesn't show animation, but plays only mp3 sound on canvas. However, if I refresh it or press F5, it plays animation..... sometimes it doesn't play animation even if I refresh it. However, if I close the Chrome web browser and restart it and try refresh button, then it starts playing animation..... (it works randomly....)
I have been trying to make this work over 10 hours... but I do not know what is wrong with my code...... pleas help me ! thanks.

Resources