Wait until video loads to capture snapshot as image - javascript

I'm working on a script that will take an mp4 video and create a snapshot of the first frame. The purpose for doing this is some mobile browsers won't play a mp4 on load. It will show the mp4 with a play button. Therefore, creating a snapshot will be a good fallback for mobile devices.
I have a basic snippet already, and it works 70% of the time. However, when it doesn't work I think I'm having issues with the script trying to take a snapshot from a cached video, or it's trying to capture before the video fully loads. Does anyone has suggestions on how to make this 100%? I've tried deferring lines of code to wait until everything loads, yet sometimes it doesn't work.... What has helped a little is adding a small setTimeOut...
( function( window, $ ) {
const document = window.document;
const ImgSnapshot = (el) => {
//setup variables
const $el = $(el);
const video = $el.find(".wave-animation__container").get(0);
$(video).ready( () => {
setTimeout(() => {
function createCanvas(){
//create a canvas obj
let canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d')
.drawImage(video, 0, 0, canvas.width, canvas.height);
//wait until we have the canvas object captured
return $.Deferred().resolve( canvas ).promise();
}
createCanvas().done( ( canvas ) => {
//create an image element to append
let img = document.createElement("img");
img.src = canvas.toDataURL();
img.classList.add('hide-for-medium', 'snapshot');
$el.append(img);
video.classList.add( 'show-for-medium' );
});
}, 150);
});
};
$(document).ready(function(){
$('.js-wave-animation').each(function(){
new ImgSnapshot(this);
});
});
} )( window, jQuery );

I have a different approach for this problem. Instead of using deferred objects to check if video is loaded how about using media events of video
<video width="400" controls id="myvideo">
<source src="" type="video/mp4">
</video>
<script type="text/javascript">
var jqvideo = $("#myvideo");
var video = jqvideo[0];
video.addEventListener("loadeddata", function() {
console.log("loaded");
let canvas = document.createElement("canvas");
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);
let img = document.createElement("img");
img.src = canvas.toDataURL();
$("body").append(img);
}, true);
jqvideo.find("source").attr("src", "yourvideourl.mp4");
</script>

Related

Create a popup media player using JS?

I have been trying to make an audio player that has basic controls allowing audio to be played instead of a video. It needs to be viewed in PIP mode, as well. Here is my code so far:
var audio = document.createElement('audio'); // The Audio
var canvas = document.createElement('canvas'); // New Canvas To Contain Video
canvas.width = canvas.height = 512;
var video = document.createElement('video'); // Create Video
video.srcObject = canvas.captureStream(); // Make Video Reflect The Canvas
video.muted = true;
function showPictureInPictureWindow() {
const image = new Image();
image.crossOrigin = true;
image.src = [...navigator.mediaSession.metadata.artwork].pop().src;
image.decode();
canvas.getContext('2d').drawImage(image, 0, 0, 512, 512);
video.onloadedmetadata = function() {
video.play();
video.requestPictureInPicture();
};
}
window.VID = video;
window.AU = audio;
function playAudio() {
audio.src = "mysong.mp3";
// Update Media Session metadata
navigator.mediaSession.metadata = new MediaMetadata({
title: "Audio Title",
artist: "MEEEEEEE",
album: "LOOOL",
artwork: [{src: "graphics/128x128.png", sizes: "128x128", type: "image/png"}]
});
// Play audio
audio.play();
// Show track album in a Picture-in-Picture window
showPictureInPictureWindow();
}
window.VID.requestPictureInPicture();
playAudio();
Any help that you can give me would be very much appreciated. (I already have the pause and play functions but I didn't include them for message size)
PS: (I don't know why, but earlier I was getting an error that said "JavaScript exception: Failed to execute 'requestPictureInPicture' on 'HTMLVideoElement': Metadata for the video element are not loaded yet" and now I have no errors at all... and no music either...)
PPS: (I fixed some of the bugs... and now I have the original error again.)
I have the same problem as you, but I have solved it
My solution:
My guess is that the video data did not load successfully when the RequestPicturePicture function was executed
So you can execute the RequestPicturePicture function in the loadedData function

How to access document.body from a script inside of document.write

Help! I am working on an application which downloads a report from an API which gives me HTML as part of the JSON return. Because it returns JSON I cannot simply open this in a new window.
In this script I am injecting this code:
let tmp = result.data;
let spot = tmp.indexOf('</body>');
let insert = `<div style="position:fixed; top:200px;left:20px; width:200px;font-size:15pt">
<div class="camera">
<video id="video" style='display:hidden'>Video stream not available.</video>
<button onClick='takepicture()'>Take photo</button>
</div>
<canvas id="canvas" style='display:none;'></canvas>
<!--<button onClick='genPDF()'>PDF</button>-->
Press CTRL+S to save page as HTML
<img id="testDownloadImage" src='' width=600 height=400 />
</div>
<script type='text/javascript'>
alert(document.body.innerHtml)
// function clearphoto() {
// var context = canvas.getContext('2d');
// context.fillStyle = "#AAA";
// context.fillRect(0, 0, canvas.width, canvas.height);
// var data = canvas.toDataURL('image/png');
// // photo.setAttribute('src', data);
// }
// Capture a photo by fetching the current contents of the video
// and drawing it into a canvas, then converting that to a PNG
// format data URL. By drawing it on an offscreen canvas and then
// drawing that to the screen, we can change its size and/or apply
// other changes before drawing it.
async function takepicture() {
let video = document.getElementById('video');
let canvas = document.getElementById('canvas');
let displayMediaOptions={video: true,displaySurface:'browser', logicalSurface:true, cursor:'never', audio: false};
try{
video.srcObject = await navigator.mediaDevices.getDisplayMedia(displayMediaOptions);
// video.play();
var context = canvas.getContext('2d');
width=window.innerWidth;
height=window.scrollHeight;
if (width && height) {
canvas.width = width;
canvas.height = height;
context.drawImage(video, 0, 0, width, height);
var data = canvas.toDataURL('image/jpg'); //.replace(/^data:image\/(png|jpg);base64,/, "");
// photo.setAttribute('src', data);
document.getElementById('testDownloadImage').src=data;
// location.href=data;
} else {
// clearphoto();
}
}
catch(err) {
console.log("An error occurred: " + err);
}
finally{
let tracks = video.srcObject.getTracks();
tracks.forEach(track => track.stop());
video.srcObject = null;
}
}
</script>
`
let newStr = [tmp.slice(0, spot), insert, tmp.slice(spot)].join('');
document.write(newStr);
to allow me to add a print screen option so that the report can be imaged. I have tried other utilities to save the HTML as a PDF or even saving the page but because the report uses a lot of XSLT and scripting to display the results these methods fail, leaving me with the print screen as the only option.
WebRTC handles this aptly, with one minor difficult: Because I am using document.write I am unable to find the document.body even though the code I am passed is a properly formed page ( etc).
How can I use the inserted script to display the page and capture it as an image?
Thanks
This is not a genuine answer sadly but I resolved the problem by moving the entire write to an iFrame with the script on the outside. I then got the scrollHeight of the iFrame document and resized the frame to match that, effectively making it appear as if the document occupied the entire page. This way I can place my script outside the iFrame and still use it to capture the entire page, which is what I was shooting for.
Thank you for any comments. As of now this is no longer needed.

How to wait for the multiple images to load in the canvas and convert to base64?

I'm trying to get a base64 version of a canvas in HTML5.
Currently, the base64 image that I get from the canvas is blank.
I have found similar questions for example this one:
HTML Canvas image to Base64 problem
However, the issue that I have is that because I am adding 2 images in the canvas, I cannot use the example provided in those answers as most of them are using single image.
I understand that I have to wait until the canvas image is loaded properly before trying to get the base64 image. But I don't know how in my case.
This is my code:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.canvas.width = 1000;
context.canvas.height = 1000;
var imageObj1 = new Image();
imageObj1.src = "http://www.offthegridnews.com/wp-content/uploads/2017/01/selfie-psuDOTedu.jpg";
imageObj1.onload = function() {
context.drawImage(imageObj1, 0, 180, canvas.width, canvas.height);
};
var imageObj2 = new Image();
imageObj2.src = "http://www.publicdomainpictures.net/pictures/150000/velka/banner-header-tapete-145002399028x.jpg"
imageObj2.onload = function() {
context.drawImage(imageObj2, 0, 0, canvas.width, 180);
};
// get png data url
//var pngUrl = canvas.toDataURL();
var pngUrl = canvas.toDataURL('image/png');
// get jpeg data url
var jpegUrl = canvas.toDataURL('image/jpeg');
$('#base').val(pngUrl);
<div class="contents" style="overflow-x:hidden; overflow-y:scroll;">
<div style="width:100%; height:90%;">
<canvas id="myCanvas" class="snap" style="width:100%; height:100%;" onclick="takephoto()"></canvas>
</div>
</div>
<p>
This is the base64 image
</p>
<textarea id="base">
</textarea>
and this is a working FIDDLE:
https://jsfiddle.net/3p3e6Ldu/1/
Can someone please advice on this issue?
Thanks in advance.
EDIT:
As suggested in the comments bellow, i tried to use a counter and when the counter reaches a specific number, I convert the canvas to base64.
Like so:https://jsfiddle.net/3p3e6Ldu/4/
In both your examples (the one from the question and the one from the comments), the order of commands does not really respect the async nature of the task at hand.
In your later example, you should put the if( count == 2 ) block inside the onload callbacks to make it work.
However, even then you will run into the next problem: You are loading the images from different domains. You can still draw them (either into the canvas or using an <img> tag), but you are not able to access their contents directly. Not even with the detour of using the <canvas> element.
I changed to code so it would work, if the images are hosted on the same domain. I also used a function to load the image and promises to handle the callbacks. The direct way of using callbacks and a counting variable, seem error-prone to me. If you check out the respective fiddle, you will notice the SecurityError shown. This is the result of the aforementioned problem with the Same-Origin-Policy I mentioned.
A previous question of mine in a similar direction was about how to detect, if I can still read the contents of a <canvas> after adding some images.
const canvas = document.getElementById('myCanvas');
const context = canvas.getContext('2d');
context.canvas.width = 1000;
context.canvas.height = 1000;
// function to retrieve an image
function loadImage(url) {
return new Promise((fulfill, reject) => {
let imageObj = new Image();
imageObj.onload = () => fulfill(imageObj);
imageObj.src = url;
});
}
// get images
Promise.all([
loadImage("http://www.offthegridnews.com/wp-content/uploads/2017/01/selfie-psuDOTedu.jpg"),
loadImage("http://www.publicdomainpictures.net/pictures/150000/velka/banner-header-tapete-145002399028x.jpg"),
])
.then((images) => {
// draw images to canvas
context.drawImage(images[0], 0, 180, canvas.width, canvas.height);
context.drawImage(images[1], 0, 0, canvas.width, 180);
// export to png/jpg
const pngUrl = canvas.toDataURL('image/png');
const jpegUrl = canvas.toDataURL('image/jpeg');
// show in textarea
$('#base').val(pngUrl);
})
.catch( (e) => alert(e) );

Recording canvas image to video file using recordrtc

I am trying to create some animation using canvas and saving it as a video file using RecordRTC, this code works perfect, but I am having difficulties in getting the div with image to record.
<div style="display:none;" id="background_goa">
<img src="Http://domain/wp-content/themes/custom/images/top10-bg-goa.png'; ?>" />
</div>
function video_create(){
var ctx = document.getElementById('canvas').getContext('2d');
ctx.save();
ctx.clearRect(0, 0, 150, 150);
var img = document.getElementById("background_goa");
ctx.drawImage(img,100,100);
// ctx.restore();
window.requestAnimationFrame(top_video_create);
}
window.requestAnimationFrame(top_video_create);
var recorder = RecordRTC(canvas, {
type: 'canvas'
});
recorder.startRecording();
setTimeout(function(){
recorder.stopRecording(function() {
var blob = recorder.getBlob();
document.body.innerHTML = '<video controls src="' + URL.createObjectURL(blob) + '" autoplay loop></video>';
});
}, 5000);
The mistake was using ctx.restore(); basically what it does is restore the entire canvas after the draw, its supposed to be used before drawing the new element to canvas, so that the screen is blank before the next frame loads.

Safari 9 paints only the first frame of a video on canvas (bug)

I'm trying to paint the current frame of a <video> on a canvas but Safari 9.0.3 on 10.11 only paints the first frameā€¦ sometimes! It appears to work only after the video is cached because a hard refresh causes it to not work again.
.drawImage(video, 0, 0, width, height) is how I'm painting it.
This simple snippet works correctly in all browsers, including Safari 9 Yosemite, but not in Safari 9 El Capitan
var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
canvas.addEventListener('click', function () {
canvas
.getContext('2d')
.drawImage(video, 0, 0, canvas.width, canvas.height);
});
<p>Play the video and then click on the canvas to paint</p>
<canvas width='240' height='135' style="border: solid;"></canvas>
<video width='240' height='135' controls loop src="http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4"></video>
Is there any workaround? Any other way to paint videos on canvas?
Using blob cache seems to work even on Safari
Sample (in CoffeeScript):
prepareLocalCache: ->
#vidtemp = document.createElement('video')
#vidtemp.preload = 'auto'
#xhr = new window.XMLHttpRequest
#xhr.open 'GET', 'video link here.mp4', true
#xhr.responseType = 'blob'
#xhr.onload = (e) =>
if e.currentTarget.status == 200
myBlob = e.currentTarget.response
#vidcache = window.URL.createObjectURL(myBlob)
#vidtemp.src = #vidcache
#xhr.send()
...
...
context = canvas.getContext '2d'
context.drawImage #vidtemp, 0, 0, canvas.width, canvas.height
#vidtemp is a dynamically created video tag.

Categories

Resources