I have tested many css properties to make my webcam streaming full screen but i am not getting proper results.
Sometimes the height gets overflow.
I tried object-fit also, but did not get proper result.
Sharing my code here .https://jsfiddle.net/jn6payn6/#&togetherjs=Sc8JuHsSfU
CSS-code
video{
width: 100%;
min-height: 100%;
margin:0 auto;
transform: rotateY(180deg);
}
img{
position: relative;
top: 50%;
transform: rotateY(180deg);
}
HTML code:
<video autoplay></video>
Javascript code:
(function() {
'use strict';
var video = document.querySelector('video')
, canvas;
/**
* generates a still frame image from the stream in the <video>
* appends the image to the <body>
*/
function takeSnapshot() {
var img = document.querySelector('img') || document.createElement('img');
var context;
var width = video.offsetWidth
, height = video.offsetHeight;
canvas = canvas || document.createElement('canvas');
canvas.width = width;
canvas.height = height;
context = canvas.getContext('2d');
context.drawImage(video, 0, 0, width, height);
img.src = canvas.toDataURL('image/png');
document.body.appendChild(img);
}
// use MediaDevices API
// docs: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia
if (navigator.mediaDevices) {
// access the web cam
navigator.mediaDevices.getUserMedia({video: true})
// permission granted:
.then(function(stream) {
video.srcObject = stream;
video.addEventListener('click', takeSnapshot);
})
// permission denied:
.catch(function(error) {
document.body.textContent = 'Could not access the camera. Error: ' + error.name;
});
}
})();
Need help!
Related
i am trying to design a video player like youtube player and when the mouse over the time line of the video,i want it to show a minimize image of the video on the time of the time line like youtube player
and to do this i have put two video tag in html file
one is the main video and the other is appear when mouse over the time line of the video with a currentTime of the time of time line where the mouse is above time line
but i noticed that this way is stress on the internet because the second video each time i set to it
the currentTime is buffering
is there a solution to stop buffering or any alternative way to show this minimize image
Create video timeline thumbnails (stills)
The best you could do would be: on video upload, (i.e: using FFMPEG) to take still images/thumbnails of the video data. Than when you draw your seek-preview, simply take the desired thumbnail image index.
Otherwise, you could:
Create a video element
Create a canvas element
Create an array to store the stills
If a still does not exist in array:
set the cloned video time
grab the video data to canvas
store the image data into the stills array
If a still exists, just use that image
Here's a dirty code just to give you an idea:
const totStills = 10;
const addSeekThumbnail = (EL) => {
const VID = EL.querySelector("video");
const DSK = document.createElement("div");
const VSK = document.createElement("video");
const CTX = document.createElement("canvas").getContext("2d");
const stills = new Array(totStills);
VSK.setAttribute("preload", "metadata");
VSK.setAttribute("crossorigin", "*");
DSK.classList.add("videoSeek");
VSK.src = VID.src;
EL.append(DSK);
VID.addEventListener("mousemove", seekShot);
EL.addEventListener("mousemove", toggleSeek);
EL.addEventListener("mouseleave", toggleSeek);
function takeStill() {
const i = VSK.dataset.fr * totStills;
if (stills[i]) return;
CTX.drawImage(VSK, 0, 0, VSK.videoWidth, VSK.videoHeight);
stills[i] = CTX.canvas.toDataURL("image/png");
loadStill()
}
function loadStill() {
const i = VSK.dataset.fr * totStills;
if (stills[i]) {
DSK.style.backgroundImage = `url(${stills[i]})`;
} else {
DSK.style.backgroundImage = `none`;
VSK.currentTime = Math.round(VSK.dataset.fr * VID.duration);
VSK.addEventListener("seeked", takeStill, {
once: true
});
}
}
function toggleSeek(ev) {
if (ev.type === "mouseleave") {
return DSK.classList.remove("show");
}
const bcr = EL.getBoundingClientRect();
const my = ev.clientY - bcr.top;
DSK.classList.toggle("show", bcr.height - my < 30);
}
function seekShot(ev) {
const bcr = EL.getBoundingClientRect();
const mx = ev.clientX - bcr.left;
const x = mx / bcr.width;
const fr = Math.round(x * totStills) / totStills;
if (VSK.dataset.fr === fr) return;
VSK.dataset.fr = fr;
DSK.style.left = `${fr * 80}%`; // 80 because it's 20% wide
loadStill();
}
};
const ELS_frames = document.querySelectorAll(".videoFrame");
ELS_frames.forEach(addSeekThumbnail);
video {
max-width: 100%;
}
.videoFrame {
display: inline-block;
position: relative;
margin: 0;
}
.videoSeek {
position: absolute;
z-index: 2;
bottom: 70px;
left: 0;
width: 20%;
height: 20%;
pointer-events: none;
box-shadow: 0 0 0 2px #fff;
background-color: rgba(255, 255, 255, 0.6);
transition: opacity 0.3s, left 0.2s;
opacity: 0;
}
.videoSeek.show {
opacity: 1;
}
<div class="videoFrame">
<video id="video" crossorigin="*" src="https://raw.githubusercontent.com/Ahmetaksungur/aksvideoplayer/main/videos/video-360.mp4" controls></video>
</div>
I found this code from a YouTube tutorial produced by Adam Khoury from his Analyser Bars Animation HTML5 Audio API JavaScript Tutorial. Where a music analyser graphic reacts to a sound file
It is from 2013. I have tried to get it to work by updating the code as far as I understand it. I have removed webkit and adjusted the syntax changing some letters to lowercase. It plays fine in the Edge browser but not in Chrome. Obviously, I have missed something, any help would be greatly appreciated.
HTML code:
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="js.js"></script>
<title>Untitled Document</title>
</head>
<body>
<div id="mp3_player">
<div id="audio_box"></div>
<canvas id="analyser_render"></canvas>
</div>
</body>
</html>
CSS code:
body{
background-color: #494545;
}
#mp3_player{
width: 420px;
height: 75px;
background-color:#FAFAFA;
margin-left: auto;
margin-right: auto;
margin-top: 300px;
text-align: center;
}
audio{
width: 400px;
padding-top: 10px;
}
#analyser_render{
width: 400px;
height: 20px;
background-color: aliceblue;
}
Javascript Code:
var audio = new Audio();
audio.src = 'Piano_01.mp3';
audio.controls = true;
audio.loop = true;
audio.autoplay = false;
// Establish all variables that your Analyser will use
var canvas, ctx, source, context, analyser, fbc_array, bars, bar_x, bar_width, bar_height;
// Initialize the MP3 player after the page loads all of its HTML into the window
window.addEventListener("load", initMp3Player, false);
function initMp3Player(){
document.getElementById('audio_box').appendChild(audio);
context = new AudioContext(); // AudioContext object instance
analyser = context.createAnalyser(); // AnalyserNode method
canvas = document.getElementById('analyser_render');
ctx = canvas.getContext('2d');
// Re-route audio playback into the processing graph of the AudioContext
source = context.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(context.destination);
frameLooper();
}
// frameLooper() animates any style of graphics you wish to the audio frequency
// Looping at the default frame rate that the browser provides(approx. 60 FPS)
function frameLooper(){
window.requestAnimationFrame(frameLooper);
fbc_array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(fbc_array);
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
ctx.fillStyle = '#006600'; // Color of the bars
bars = 100;
for (var i = 0; i < bars; i++) {
bar_x = i * 3;
bar_width = 2;
bar_height = -(fbc_array[i] / 2);
// fillRect( x, y, width, height ) // Explanation of the parameters below
ctx.fillRect(bar_x, canvas.height, bar_width, bar_height);
}
}
In Chrome you're running into two issues here:
in the callback handler for the onload event you're creating a new AudioContext() which is not allowed because playback needs to be triggered by a user interaction like a mouse click
chrome prevents access to audio files from the local file system. If you look into the console you most likely spot an error like
Access to audio at ... from origin 'null' has been blocked by CORS
policy
To workaround:
upload your files to a webserver or run from a local webserver.
instead of catching the onload event to set up your AudioContext, listen for the Audio objects playing event and do the initializaion in it's callback handler. This will make sure the AudioContext is created as soon as the audio started playing because the user clicked on the play button.
Here's an example (just click on 'Run code snippet'):
var audio = new Audio();
audio.crossOrigin = "anonymous";
audio.src = 'https://api.codetabs.com/v1/proxy?quest=http://www.hyperion-records.co.uk/audiotest/3%20Schubert%20String%20Quartet%20No%2014%20in%20D%20minor%20Death%20and%20the%20Maiden,%20D810%20-%20Movement%203%20Scherzo%20Allegro%20molto.MP3';
audio.controls = true;
audio.loop = true;
audio.autoplay = false;
audio.addEventListener("playing", start);
// Establish all variables that your Analyser will use
var canvas, ctx, source, context, analyser, fbc_array, bars, bar_x, bar_width, bar_height;
// Initialize the MP3 player after the page loads all of its HTML into the window
window.addEventListener("load", initMp3Player, false);
function initMp3Player() {
document.getElementById('audio_box').appendChild(audio);
}
// frameLooper() animates any style of graphics you wish to the audio frequency
// Looping at the default frame rate that the browser provides(approx. 60 FPS)
function frameLooper() {
window.requestAnimationFrame(frameLooper);
fbc_array = new Uint8Array(analyser.frequencyBinCount);
analyser.getByteFrequencyData(fbc_array);
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the canvas
ctx.fillStyle = '#006600'; // Color of the bars
bars = 100;
for (var i = 0; i < bars; i++) {
bar_x = i * 3;
bar_width = 2;
bar_height = -(fbc_array[i] / 2);
// fillRect( x, y, width, height ) // Explanation of the parameters below
ctx.fillRect(bar_x, canvas.height, bar_width, bar_height);
}
}
function start() {
context = new AudioContext(); // AudioContext object instance
analyser = context.createAnalyser(); // AnalyserNode method
canvas = document.getElementById('analyser_render');
ctx = canvas.getContext('2d');
// Re-route audio playback into the processing graph of the AudioContext
source = context.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(context.destination);
frameLooper();
audio.removeEventListener("playing", start);
}
body {
background-color: #494545;
}
#mp3_player {
width: 420px;
height: 75px;
background-color: #FAFAFA;
margin-left: auto;
margin-right: auto;
margin-top: 300px;
text-align: center;
}
audio {
width: 400px;
padding-top: 10px;
}
#analyser_render {
width: 400px;
height: 20px;
background-color: aliceblue;
}
<body>
<div id="mp3_player">
<div id="audio_box"></div>
<canvas id="analyser_render"></canvas>
</div>
</body>
I am building an application where I need to be able to capture a frame-grab from a web camera and upload it to the application. To do this, I am using WebRTC to capture the image from the video stream, write it to a canvas, and then grab the data and upload it. This works fine for the most part. However, I needed to give the user a way to adjust the brightness. To accomplish this, I added a slider to the page with the video stream on it, that applies "style.webkitFilter = 'brightness(X%)'" when adjusted. Again, this works fine, but did not adjust the brightness of the captured image, just the displayed video stream. The javascript function I use to actually capture the image is:
function takepicture() {
var context = canvas.getContext('2d');
var b = document.getElementById('txtBrightness').value;
if (width && height) {
canvas.width = width;
canvas.height = height;
context.filter = 'brightness(' + b + '%)';
context.drawImage(video, 0, 0, width, height);
var data = canvas.toDataURL();
//photo.setAttribute('src', data);
//alert(data);
document.getElementById('Image').value = data;
//$("#Image").val(data);
$("#form1").submit();
} else {
clearphoto();
}
}
This works in Chrome, Firefox, and the Chromium based version of Edge, however, it does NOT work in the EdgeHTML versions of Edge. In those versions of Edge, the video stream is correctly displayed with the adjusted brightness, but the captured image does not have the brightness adjusted. Is there a workaround to make context filters work with the EdgeHTML versions of the Edge browser?
From the CanvasRenderingContext2D.filter, it seems that CanvasRenderingContext2D.filter not support Microsoft Edge browser (EdgeHtml version).
As a workaround, you could try to use Konva plugin to apply filter to an image.
Sample code as below:
<head>
<meta charset="utf-8" />
<title></title>
<script src="https://unpkg.com/konva#4.1.3/konva.min.js"></script>
<meta charset="utf-8" />
<title>Konva Brighten Image Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #f0f0f0;
}
#slider {
position: absolute;
top: 20px;
left: 20px;
}
</style>
</head>
<body>
<div id="container"></div>
<input id="slider" type="range" min="-1" max="1" step="0.05" value="0" />
<script>
function loadImages(sources, callback) {
var images = {};
var loadedImages = 0;
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];
}
}
function buildStage(images) {
var stage = new Konva.Stage({
container: 'container',
width: window.innerWidth,
height: window.innerHeight
});
var layer = new Konva.Layer();
var lion = new Konva.Image({
image: images.lion,
x: 80,
y: 30,
draggable: true
});
lion.cache();
lion.filters([Konva.Filters.Brighten]);
layer.add(lion);
stage.add(layer);
var slider = document.getElementById('slider');
slider.oninput = function () {
lion.brightness(slider.value);
layer.batchDraw();
};
}
var sources = {
lion: '/images/lion.png'
};
loadImages(sources, buildStage);
</script>
</body>
The output like this:
I would like the background of a video to match the background of its container. However, setting the container background manually has different results in different browsers (apparently due to localized hardware acceleration settings).
The solution is to read the color of the pixels in the video and update the background accordingly-- originally found here ---> https://stackoverflow.com/a/44523649/4072697
However, this method (including the one presented in the comment for it) produce the same result (different colors in different browsers)
This jsfiddle will not run the video due to cross origin errors, but includes the code:
HTML:
<div class="container">
<video class="videos" id="test" controls="none" autoplay="" loop name="media"><source src="https://r4---sn-ab5szne7.googlevideo.com/videoplayback?expire=1574210358&ei=1jbUXaubDo_t8gTuhJzwDw&ip=24.103.48.189&id=o-AMlyNgFeuPfxNCz0P6eVhp14foi9XPmWLqDxWO5zEwYA&itag=22&source=youtube&requiressl=yes&mm=31,26&mn=sn-ab5szne7,sn-vgqsknll&ms=au,onr&mv=m&mvi=3&pl=18&initcwndbps=1726250&mime=video/mp4&ratebypass=yes&dur=5.363&lmt=1471775895725173&mt=1574188655&fvip=4&fexp=23842630&c=WEB&sparams=expire,ei,ip,id,itag,source,requiressl,mime,ratebypass,dur,lmt&sig=ALgxI2wwRQIgJuAGaoYOYn6-XQ9ylB8uNwW2rKt09Q2zkixlP-5r4-ACIQCqMpK0BRTwnTzErcNFADAKc7k0YQPjBxJJAQD2vSRR7w==&lsparams=mm,mn,ms,mv,mvi,pl,initcwndbps&lsig=AHylml4wRgIhAOYG1-AAZcBkmaJJI502ztNGkv_9dUhOPNd8kZpJCYLRAiEA9BeuIqWpXG5JMtTFTgDCtmEsoaz0A7zOl6Lku3bj6bQ=" type="video/mp4"></video>
</div>
JS:
//allow crossOrigin
var testVid = document.getElementById('test')
testVid.crossOrigin = "Anonymous";
function drawingLoop() {
requestId = window.requestAnimationFrame(drawingLoop)
ctx.drawImage(vid, 0, 0, vidWidth, vidHeight, 0, 0, canvas.width, canvas.height);
}
function setVideoBgColor(vid, backgroundElement) {
var canvas = document.createElement("canvas");
//size of canvas space to copy pixel onto
canvas.width = 10;
canvas.height = 10;
var ctx = canvas.getContext("2d");
ctx.drawImage(vid, 0, 0, 10, 10);
var p = ctx.getImageData(0, 0, 10, 10).data;
var colorBoost = 0;
var colorR = p[0]+colorBoost;
var colorG = p[1]+colorBoost;
var colorB = p[1]+colorBoost;
//colorLayer.data[pixelPos];
backgroundElement.style.backgroundColor = "rgb(" + colorR+ "," + colorG + "," + colorB + ")";
}
document.getElementById('test').addEventListener("play",function(){
setVideoBgColor(document.getElementById('test'),this.parentElement);
})
CSS:
.container{
background-color: yellow;
width: 100vw;
height: 100vh;
}
#test{
margin-top:20%;
margin-left:15%;
width:70%;
height:auto;
}
This is my first time using HTML canvas. I have been given a working camera that returns images as a base64 string. I am trying to pass this string to a new function that handles the image resizing(the actual dimensions aren't important right now as I am just testing the resizing).
Right now I am getting a blank image from my generateThumbnail function. Below I have attached an image of the output in the application, as well as an image of the console output.
For the first attached image, you will see 2 photos. The photo on the left is the original which is working correctly (it is all black because my webcam is covered). The photo on the right is the blank output when I attempt to resize.
generateThumbnail(imageData) {
let canvas = document.createElement("canvas");
let ctx = canvas.getContext("2d");
let img = new Image();
img.onload = () => {
ctx.drawImage(img, 0, 0, 300, 300);
}
img.src = imageData;
let dataUrl = canvas.toDataURL("image/png");
console.log(imageData);
console.log(dataUrl);
return dataUrl;
}
image of taken photos
image of console output from thumbnail base64
Although your code is not working on the embedded fiddle, I think your problem is because the image has not loaded yet as you're returning dataUrl as soon as you execute your method.
dataUrl
should be returning inside img.onload method , maybe something like this will work?
** EDIT **
Make sure you set width and height of your canvas as well.
const generateThumbnail = (imageData) => {
let canvas = document.createElement("canvas");
canvas.width = 300;
canvas.height = 300;
document.querySelector(".canvas").appendChild(canvas);
let ctx = canvas.getContext("2d");
let img = new Image();
img.onload = () => {
ctx.drawImage(img, 0 , 0, 300, 300);
console.log(imageData);
console.log(dataUrl);
return dataUrl;
}
img.src = imageData;
let dataUrl = canvas.toDataURL("image/png");
document.querySelector(".image").appendChild(img);
}
window.onload = () => {
generateThumbnail(window.image_base64);
}
.canvas, .image {
position: absolute;
top: 0;
left: 0;
width: 300px;
height: 300px;
color: white;
}
.image { border: solid 1px white; }
.canvas {
left: 310px;
top: 0;
width: 300px;
height: 300px;
}
body {
background: purple;
}
<div class="image"></div>
<div class="canvas"></div>
<script>
window.image_base64 = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAFU2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS40LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIgogICAgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIgogICAgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIgogICAgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iCiAgICB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE4IChNYWNpbnRvc2gpIgogICB4bXA6Q3JlYXRlRGF0ZT0iMjAxOC0xMC0yNFQxNjo0NTowNiswMTowMCIKICAgeG1wOk1vZGlmeURhdGU9IjIwMTgtMTAtMjRUMTY6NTE6MTUrMDE6MDAiCiAgIHhtcDpNZXRhZGF0YURhdGU9IjIwMTgtMTAtMjRUMTY6NTE6MTUrMDE6MDAiCiAgIGRjOmZvcm1hdD0iaW1hZ2UvcG5nIgogICBwaG90b3Nob3A6Q29sb3JNb2RlPSIyIgogICBwaG90b3Nob3A6SUNDUHJvZmlsZT0ic1JHQiBJRUM2MTk2Ni0yLjEiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6N2Q3MTU5YWYtYzM3Ni00YWVhLWFkNTYtYzNlYWFiZTUw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";
</script>