NOTE : This is not duplicate as I didn't find any question related to take screenshot of video and canvas combined and I tried html2canvas
We have a div which internally contains video element and canvas. Video is for streaming and canvas is to draw any thing on that video. Now if I take a screenshot of div, it has to contain the video frame and the drawing. I didn't find any way to get this. I tried html2canvas but it is giving the screenshot of only canvas. Below is code.
HTML:
<div id="remoteScreen">
<video id="remoteVideo" autoplay></video>
<canvas id="remoteCanvas"></canvas>
</div>
<button id="captureButton">Capture</button>
CSS:
video {
max-width: 100%;
width: 320px;
}
canvas {
position: absolute;
background: transparent;
}
JS:
const captureBtn = document.querySelector("#captureButton");
captureBtn.addEventListener('click', captureCanvasVideoShot);
function captureCanvasVideoShot() {
html2canvas(document.querySelector("#remoteScreen")).then(function(canvas) {
document.body.appendChild(canvas);
});
By using html2canvas, I thought I will get combined screenshot of video and canvas as canvas is on top of video and both are part of remoteScreen div. But I got screenshot of canvas only. Can any one please let me know is there any way to get the screenshot of video + canvas combined or can I pass any additional configuration parameters to html2canvas to get the screenshot of video + canvas combined?
html2canvas cannot reproduce a video screenshot. It generates an image by reading a page's HTML elements and rendering a picture of them according to the CSS styling information. It doesn't actually take a screenshot.
Related
I am currently working on a small Web App Game written angular. I wanted to add a feature for users to draw their on profile picture using a canvas HTML element.
I never worked with canvas before so i searched the internet for examples and i found this one which really helped me out getting started.
So i created a component and used exactly this code which worked immediately. My next goal was to make the canvas size responsive, so it looks good on every device.
A quick google search gave me multiple ways to achive this:
Adding width: 100%; height: 100% to the canvas style
Setting the size in ngAfterViewInit() like this:
canvasEl.style.width = "100%";
canvasEl.style.height = "100%";
canvasEl.width = canvasEl.offsetWidth;
canvasEl.height = canvasEl.offsetHeight;
Both solution kind of worked. The canvas was now as big as possible which was great.
However the drawing did not work properly anymore.
When drawing the canvas wasn't changing at all or the lines were not created at the position of the cursor (instead of increased canvas size it felt like the canvas was stretched and the actual size in pixel did not change).
As CBroe mentioned i have to set the width and height of the properties of the canvas itself.
So now i am trying to retrieve the height and width of the parent container in ngAfterViewInit() but the values are always zero:
#ViewChild('parent') parent: ElementRef;
ngAfterViewInit() {
...
console.log (this.parent.nativeElement.offsetHeight);
...
}
html code:
<div #parent fxFlex>
<canvas id="responsive-canvas" #myCanvas></canvas>
</div>
After a lot of try and error i found a solution to my problem which looks like that:
replaced the height and width set in the ngAfterViewInit() of the canvas with this code
canvasEl.style.width = "100%";
canvasEl.style.height = "100%";
canvasEl.width = canvasEl.offsetWidth;
canvasEl.height = canvasEl.offsetHeight;
and this is what my html code looks like:
<div #parent fxFlex style="height: 200px;">
<canvas
id="responsive-canvas"
[height]="parent.offsetHeight"
[width]="parent.offsetWidth"
#myCanvas
>
</canvas>
</div>
It is important, that the height of the parent is set. Otherwise it does not work.
Hi all I am using Signalwire's video calling functionality to make a video calling app. I am facing one issue here, as most of the times, we use video calling through phones or small screen sizes the height of the video is very small there.
Is there any way to increase the height of that div on which the video stream is getting injected?
Here What it looks like-
In mobile my video screen is quite small I want to increase the height.
I tried something like-
$scope.roomObject = new SignalWire.Video.RoomSession({
token: token,
rootElement: document.getElementById('root'), // an html element to display the video
audio: true,
video: {
width: { min: 720},
height: { min: 1280}
}
}
});
This does change the inner video into portrait mode but the issue remains, I can't increase the height.
Note- increasing the height of div not working I can increase the width though.
Thanks
It sounds like you're trying to extend the video canvas vertically to fill the entire screen. While you can change the aspect ratio of your video stream itself (which is how you're swapping your stream to portrait), you can't change the aspect ratio of the whole canvas.
I need to draw the stream from camera on top of a canvas using drawImage, however when I add the video element in react component it shows the element's stream in the page too. If I use display:'none' drawImage can't draw the user's webcam, it draws blank screen. Do I have to show html5 video element in the page or is there a way to play it without showing?
I am getting video stream from camera with getUserMedia:
async function getCameraStream() {
try {
const constraint = { video: true }
const stream = await navigator.mediaDevices.getUserMedia(constraint)
if (videoRef.current) {
videoRef.current.srcObject = stream
return
}
} catch (error) {
console.error('Error opening video camera.', error)
}
setLoading(false)
setCameraError(true)
}
And the video ref is:
return( <video
style={{display: 'none' }}
ref={videoRef}
className={classes.sourcePlayback}
src={sourceUrl}
hidden={isLoading}
autoPlay
playsInline
controls={false}
muted
loop
onLoadedData={handleVideoLoad}
/>)
I am using this videoRef in canvas like:
ctx.drawImage(sourcePlayback.htmlElement, 0, 0)
Here if the htmlElement is given with display:'none' it doesn't draw. I want to draw the htmlElement but not show it in the page.Is there a way to achieve this? I want html5VideoElement to play invisibly basically.display:none does not work.
EDIT:
Add css as visibility:'hidden' works for this one.
sourcePlayback: {
visibility: 'hidden',
display: 'flex',
width: 0,
height: 0,
},
I was able to replicate your issue in chrome with https://record.a.video.
When I used display: none on the video attribute, the video overlay on the canvas was black instead of my camera.
Workaround:
When I set the CSS on the video to width:1px, the video shows up on the canvas. I suppose it's technically on the screen, but it'll probably work for your purposes.
I have a video (let's call it composite video) composed by multiple other videos concatenated using some pattern. For example, see the screenshot of the videos below, composed by two and four other videos, respectively:
However, I need to display it differently: One main, larger, video and N-1 video thumbnails, where N is the total number of videos. Here are this other display corresponding to the videos above:
To display the main I'm using a combination of HTML and CSS to position the video I want in the larger div. It runs smoothly, no matter the number of videos in the composite videos.
To display the thumbnails, I'm using <canvas> to draw the parts I want:
video.addEventListener('play', function() {
(function loop() {
drawThumbnails();
setTimeout(loop, 1000 / 30); // drawing at 30fps
})();
}, false);
function drawThumbnails() {
for (var i = thumbs.length - 1; i >= 0; i--) {
drawThumbnail(thumbs[i]);
};
}
function drawThumbnail(thumb) {
var thumbNumber = Number(thumb.id.match(/\d+/g));
var canvasContext = thumb.getContext('2d');
var thumbCoordinates = getVideoCoordinates(thumbNumber);
var srcX = thumbCoordinates.column * videoWidth;
var srcY = thumbCoordinates.row * videoHeight;
canvasContext.drawImage(
video, srcX, srcY, videoWidth, videoHeight, // Source
0, 0, thumb.width, thumb.height); // Destination
}
It was working well for 3 (sometimes 4) videos. However, as the number of videos in the composite video increases, the videos in the thumbnails start to freeze and run not in a smooth way. This is probably happening because there's too much image processing being done at the same time.
I think the proper way to do it is, somehow, using <video> and methods specific for videos, not for images. I've also tried to use the same src in the multiple <video> tags (one for each thumbnail) and add eventListeners to play/pause the videos in the thumbnails once the main video is played/paused. That's not very efficient, particularly because videos can get out of sync sometimes, when seeking/buffering.
Is there a way of using only one video in multiple <video> tags and use only one of them (in my case, the one that contains the main video) to control all the others? In case there's no way of doing that, is there an alternative approach for my problem?
Thanks a lot,
P.S. Having multiple, separated, videos is not an option in my situation. It would take a very long time to process the input video and divide it in multiple videos.
You can certainly reference the same video across multiple video elements. Cloning the original and appending them as thumbnail videos might alleviate some of the tedium.
Iterating over the thumbnails and .play()ing them should be fine so long as you set their currentTime with that of the main video prior to playing, to minimize drift. There may be some need to wait for canplay to fire on the main video and/or the thumbnails depending on the exact experience you're looking to deliver.
If each thumbnail is given a parent container you could possibly position the video element serving as your thumbnail such that only the portion of the video you care to see is visible, clipping the rest.
FWIW, CSS masking might be of interest to you as a performance optimization if it helps the compositing performance.
You will need to manually coordinate playing/pausing all of the video elements, but that should be easy enough to do with a facade object that handle the play pause of all the "linked" video elements.
I know I'm posting late, and you may have already found an answer. However, if anyone else comes across this question, here is my answer:
You can use multiple video elements with the same source. The way to do it is with css.
.wrapper {
height: /*height of one video*/;
width: /*width of one video*/;
overflow: hidden;
}
video {
position: relative;
top: /*height offset*/;
left: /*width offset*/;
}
And HTML
<div class="wrapper">
<video src="myvideo.mp4"></video>
</div>
So, if I was doing the top right video, and each one was 250px by 250px, I would set my wrapper height and width to 250px and my video top to 0px and my video left to 250px
What's the format of the main video? Is it an on demand mp4/webm file?
If you still want to go with your approach of grabbing frames and paint them but is facing performance issues, consider using web workers for the heavy jobs. Here you can find some examples of video/canvas manipulation with web workers.
I have a video as 100% width & height, over that are interactive elements, when you click on the chapter it goes to white and then loads in a video very quickly.. when the video ends & you click on the video, it will go to the next video and again it goes to white and loads the video.
My issue is that it goes to a white screen for ~500ms, because I change the video source of the video-frame, the background-color of the body is white so I believe that's where the white comes from, changing the background color to blue or black changes the issue witht he white in their respective color, I was wondering if there is a solution for this?
I've suggested the following:
Loading screen for the ~500ms it goes to white.. this however doesn't look good.
First frame of the next video as background of the body, where I load the video over, so that it appears to be on the video but it's actually loading in the video.
The code as to how I change the video frame to the next video:
$("#klikimg").on('click', function(){
switch(klik) {
case 100:
$("#wereldbol").attr("src", "aardeFrag/klik1.mp4");
klik = 90;
break;
});
With Chris S. his suggestion of trying image frames again I did the following:
html:
<video src="Wereldbol.mp4" onclick="this.play();" id='wereldbol' preload="auto" > </video>
Loaded the video in after the image, so that it wouldn't fall back on white or black for example.
CSS:
#tFrame{
position: absolute;
bottom: 0px;
right: 0px;
min-width: 100%;
min-height: 100%;
z-index: -1;
overflow: hidden;
background-size:cover;
}
this is the css code I used, thank you, Chris S
note - this only works for one video source changing, as whenever it loads in a new frame it still goes to black if you don't have the image already present on the page
edit: For multiple video's: Load in every image at the beginning of your body tag, give them all the same class, and a width of 1px, height of 1 px and opacity of 0, then when you change your video source, change the width & height of the image you need to 100% and opacity to 1, on the next click, just before you change the image again change the image width & height to 1px and opacity to 0, this way it won't go to white or black -- Credit to: Chris S. for this solution, thank you Sir!
Try adding a hidden div on top of the video element, show this div before you set the src of the video and hide it again on video's loadeddata event:
case 100:
$("#loadingDiv").show();
$("#wereldbol").attr("src", "aardeFrag/klik1.mp4");
klik = 90;
break;
and on document ready:
$("#wereldbol").on("loadeddata", function() { $("#loadingDiv").hide(); } );
You can find the supported media events here: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Media_events
I've similar problem and I've solved just creating differents video dom elements and append it in to the DOM. When a new video to be played appears just remove from the dom the older and add the new dom video element to the DOM. Here is an example of my code:`
function playVideo(aviso){
let newVideo = document.createElement('video');
newVideo.style.cursor = 'none !important';
newVideo.style.width = pantalla.width;
newVideo.style.height = pantalla.height;
newVideo.muted = true;
newVideo.src = aviso.url;
newVideo.style.objectFit = 'contain';
const sourceElem = document.createElement('source');
sourceElem.src = '';
const pElem = document.createElement('P');
pElem.innerHTML = 'No soporta video';
newVideo.appendChild(sourceElem);
newVideo.appendChild(pElem);
newVideo.oncanplay = function() {
// video.style.visibility = 'visible';
removeCurrents();
document.body.appendChild(newVideo);
newVideo.play();
currentVideo = newVideo;
};
`