ImageCapture.takePhoto throws DOMException: platform error - javascript

Im trying to use the takePhoto method of image capture, using the following code.
addPicture: function (typeEvidence) {
if ('mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices) {
this.typeEvidence = typeEvidence;
var _self = this;
this.initializeCamera();
navigator.mediaDevices.getUserMedia({ video: { facingMode: "environment" } })
.then(mediaStream => {
_self.video.srcObject = mediaStream;
_self.video.onloadedmetadata = () => {
_self.video.play();
};
_self.track = mediaStream.getVideoTracks()[0];
_self.imageCapture = new ImageCapture(_self.track);
})
.catch((err) => {
$.MenssagesGrowl.Show(3, 'Error: ' + err.name + ' - ' + err.message);
});
$("#modalAddPicture").modal("show");
}
else {
$.MenssagesGrowl.Show(3, 'The browser does not support media devices');
}
},
initializeCamera: function () {
var _self = this;
this.dataURL = '';
this.disableTakePicture = true;
this.blob = null;
this.streaming = false;
this.imageCapture= null;
this.video = document.querySelector('video');
this.canvas = document.getElementById('cameraCanvas');
this.video.addEventListener('canplay', (ev) => {
if (!_self.streaming) {
_self.picWidth = _self.video.videoWidth;
_self.picHeight = _self.video.videoHeight;
//_self.picWidth = _self.video.videoWidth / (_self.video.videoHeight / _self.picHeight);
// Firefox currently has a bug where the height can't be read from
// the video, so we will make assumptions if this happens.
if (isNaN(_self.picWidth)) {
_self.picWidth = _self.picHeight / (4 / 3);
}
_self.video.setAttribute('width', _self.picWidth);
_self.video.setAttribute('height', _self.picHeight);
_self.canvas.setAttribute('width', _self.picWidth);
_self.canvas.setAttribute('height', _self.picHeight);
_self.streaming = true;
_self.disableTakePicture = false;
}
}, false);
this.clearPhoto();
},
takePicture: function () {
var _self = this;
this.imageCapture.takePhoto(null)
.then((blob) => {
return Promise.all([createImageBitmap(blob),blob]);
})
.then((result) => {
//result[0] bitmap
//result[1] blob
//guardar foto
var _sequence = _self.getConsecutiveForFileName();
var _filename = _self.PrevioHeader.ControlNum + "_" + _self.PreviousConsignment.ConsignmentNum + "_" + _sequence + ".png";
var _file = new File([result[1]], _filename, {
type: 'image/png'
});
var _formData = new FormData();
_formData.append("image", _file);
_formData.append("id", _self.PreviousConsignment.Guid);
axios({
method: 'post',
url: this.url,
data: _formData,
headers: { "Content-Type": "multipart/form-data" }
}
).then((response) => {
if (response.data.result == true) {
$.MenssagesGrowl.Show(response.data.typeMessage, response.data.message);
var _context = _self.canvas.getContext('2d');
if (_self.picWidth && _self.picHeight) {
_self.canvas.width = _self.picWidth;
_self.canvas.height = _self.picHeight;
_context.drawImage(result[0], 0, 0, _self.picWidth, _self.picHeight);
var _dataURL = _self.canvas.toDataURL('image/png');
_self.PreviousConsignment.Images.push({
dataURL: _dataURL,
fileName: _filename,
id: response.data.id,
sequence: _sequence,
typeEvidence: _self.typeEvidence,
temporal: 1
});
}
else
_self.clearPhoto();
}
});
})
.catch((error) => {
console.log(error);
});
},
The application was working ok with the following line
this.imageCapture.takePhoto()
, but suddenly stopped working today throwing a generic error DOMException: platform error.
I did a little bit of research and realized they had made a change a year ago to use it as
takePhoto(photoSettings)
as the documentation in mdn web docs says, since photoSettings is optional i tried to use
this.imageCapture.takePhoto(null)
and it worked for a while until later this day, when it started throwing again the same error.
Does anyone knows the reason, is the ImageCapture not stable for production and if it is not is there an alternative or workaround?
Im running the app under chrome and using vue as framework.

Edit 1/31/2023:
Chrome is now actively working on fixing this issue:
https://bugs.chromium.org/p/chromium/issues/detail?id=1287227
It appears updating Chrome doesn't fix the issue, because Chrome is feature flag testing MediaFoundationD3D11VideoCapture. When MediaFoundationD3D11VideoCapture is disabled, the issue doesn't occur.
Original Answer 1/12/2023
My customers started getting this error on 1/12/2023. I think Chrome released a version with a bug in the webcam APIs.
I had them update their version of chrome and it immediately started working again. The version that fixed it was v109.0.5414.75
To update chrome:
Version that worked for me:

Related

Javascript working in Chrome, working in firefox locally, but not after deployment

This is part of a Spark Java app, but the error is happening in this JS part. The relevant code is as follows:
const addErrBox = async () => {
const controls = document.querySelector(".pure-controls");
const errBox = document.createElement("p");
errBox.setAttribute("id", "errBox");
errBox.setAttribute("style", "color:red");
errBox.innerHTML = "Short URL not valid or already in use!";
controls.appendChild(errBox);
}
const submitForm = () => {
const form = document.forms.namedItem("new-url-form");
const longUrl = form.elements["longUrl"];
const shortUrl = form.elements["shortUrl"];
const url = `/api/new`;
fetch(url, {
method: "POST",
body: `${longUrl.value};${shortUrl.value}`
})
.then((res) => {
if (!res.ok) {
if (document.getElementById("errBox") == null) {
addErrBox();
}
}
else {
document.getElementById("errBox")?.remove();
longUrl.value = "";
shortUrl.value = "";
refreshData();
}
});
};
(async () => {
await refreshData();
const form = document.forms.namedItem("new-url-form");
form.onsubmit = e => {
e.preventDefault();
submitForm();
}
})();
Basically, "/api/new" checks for validity of input, adds the data to database if valid and prints error otherwise. Now, when the input is valid, it seems to work. The "/api/new" code is in Java, which seems to work properly as well since I do get a 400 error. All of it works properly when built inside a docker locally, but when accessed over internet using Nginx reverse proxy, it stops working inside firefox. Chrome still works. I'm not sure what's happening.
The code for "/api/new" is this:
public static String addUrl(Request req, Response res) {
var body = req.body();
if (body.endsWith(";")) {
body = body + "$";
}
var split = body.split(";");
String longUrl = split[0];
if (split[1].equals("$")) {
split[1] = Utils.randomString();
}
String shortUrl = split[1];
shortUrl = shortUrl.toLowerCase();
var shortUrlPresent = urlRepository
.findForShortUrl(shortUrl);
if (shortUrlPresent.isEmpty() && Utils.validate(shortUrl)) {
return urlRepository.addUrl(longUrl, shortUrl);
} else {
res.status(HttpStatus.BAD_REQUEST_400);
return "shortUrl not valid or already in use";
}
}
Update: it suddenly started working, without any change on the server side. I think it was some kind of issue with caching, either in firefox, cloudflare or Nginx's part.

Having trouble console.log anything from this API, we're not allowed to use JQuery or Bootstrap, so I have to use fetch and bulma

so I've been having trouble trying to get anything to console.log from this API, the most I can get is a null or undefined return. I've tried just the base statement from the API company, and the code snippet runs for them but for some reason won't even return a console.log for me. not sure if it's a problem on their end or mine because it seems to run fine on their website. also as I said in the title, I have to use fetch
EDIT: I'm in coding school right now so I'm pretty new to all of this
EDIT: the dashboard shows that the API is being called, so why am I not getting any responses?
EDIT: I figured it out, the API wasn't working
// global variables
var countryInput = document.querySelector("#ipt-country");
var submitButton = document.querySelector("#submit-btn");
var currencyDisplay = document.querySelector("#currency-display");
// country currency array codes
var countryCurrencyArray =[ list off all countries with iso code for currency goes here, not going to list it bc its 270 lines long
];
// begins exchange rate function
function getExchangeRate() {
// grab data attribute
var countrySearch = countryInput.value.toLowerCase().trim();
var currencyUSA = "USD";
var countryCurrency = "AUD";
// var countryCurrency = countryInput.value.countryCurrencyArray[i];
// if else errors
if (countrySearch.length < 1) {
return(null);
} else {
var countryConversion = countryCurrencyArray.find(
(element) => element.name === countrySearch)
}
if (countryConversion === void 0) {
return(null);
} else {
var countryCurrency = countryConversion.code;
}
// fetch API to change currency rate from American Dollar (USD) to selected currency
fetch(
'https://currency-converter5.p.rapidapi.com/currency/convert?' +
'&from=' + currencyUSA +
'&to=' + countryCurrency +
'&amount=1',
{
"method": "GET",
"headers": {
"x-rapidapi-host": "currency-converter5.p.rapidapi.com",
"x-rapidapi-key": "API-KEY"
},
}).then(response => {
console.log(response);
})
.then(function(response) {
response.json().then(function(data){
for (i=0; i < data.response.countryCurrency.length; i++) {
var exchangeMoney = document.createElement("div");
exchangeMoney.classList.add(
"is-flex-mobile",
"column",
"has-text-centered",
"is-justify-content-space-evenly"
);
var exchangeOutput = document.createElement("div");
exchangeRateOutput.classList.add("level-item");
exchangeRateOutput.innerText = data.response.countryCurrency[i].code;
exchangeRateMoney.appendChild(exchangeOutput);
console.log(exchangeOutput);
}
});
}).catch(err => {
console.error('Request Failed', err);
});
}
getExchangeRate();
console.log(getExchangeRate());
submitButton.addEventListener("click", getExchangeRate);

I'm capturing screen by using media recorder and making video from blob but that video is not showing it's duration [duplicate]

I am in the process of replacing RecordRTC with the built in MediaRecorder for recording audio in Chrome. The recorded audio is then played in the program with audio api. I am having trouble getting the audio.duration property to work. It says
If the video (audio) is streamed and has no predefined length, "Inf" (Infinity) is returned.
With RecordRTC, I had to use ffmpeg_asm.js to convert the audio from wav to ogg. My guess is somewhere in the process RecordRTC sets the predefined audio length. Is there any way to set the predefined length using MediaRecorder?
This is a chrome bug.
FF does expose the duration of the recorded media, and if you do set the currentTimeof the recorded media to more than its actual duration, then the property is available in chrome...
var recorder,
chunks = [],
ctx = new AudioContext(),
aud = document.getElementById('aud');
function exportAudio() {
var blob = new Blob(chunks);
aud.src = URL.createObjectURL(new Blob(chunks));
aud.onloadedmetadata = function() {
// it should already be available here
log.textContent = ' duration: ' + aud.duration;
// handle chrome's bug
if (aud.duration === Infinity) {
// set it to bigger than the actual duration
aud.currentTime = 1e101;
aud.ontimeupdate = function() {
this.ontimeupdate = () => {
return;
}
log.textContent += ' after workaround: ' + aud.duration;
aud.currentTime = 0;
}
}
}
}
function getData() {
var request = new XMLHttpRequest();
request.open('GET', 'https://upload.wikimedia.org/wikipedia/commons/4/4b/011229beowulf_grendel.ogg', true);
request.responseType = 'arraybuffer';
request.onload = decodeAudio;
request.send();
}
function decodeAudio(evt) {
var audioData = this.response;
ctx.decodeAudioData(audioData, startRecording);
}
function startRecording(buffer) {
var source = ctx.createBufferSource();
source.buffer = buffer;
var dest = ctx.createMediaStreamDestination();
source.connect(dest);
recorder = new MediaRecorder(dest.stream);
recorder.ondataavailable = saveChunks;
recorder.onstop = exportAudio;
source.start(0);
recorder.start();
log.innerHTML = 'recording...'
// record only 5 seconds
setTimeout(function() {
recorder.stop();
}, 5000);
}
function saveChunks(evt) {
if (evt.data.size > 0) {
chunks.push(evt.data);
}
}
// we need user-activation
document.getElementById('button').onclick = function(evt){
getData();
this.remove();
}
<button id="button">start</button>
<audio id="aud" controls></audio><span id="log"></span>
So the advice here would be to star the bug report so that chromium's team takes some time to fix it, even if this workaround can do the trick...
Thanks to #Kaiido for identifying bug and offering the working fix.
I prepared an npm package called get-blob-duration that you can install to get a nice Promise-wrapped function to do the dirty work.
Usage is as follows:
// Returns Promise<Number>
getBlobDuration(blob).then(function(duration) {
console.log(duration + ' seconds');
});
Or ECMAScript 6:
// yada yada async
const duration = await getBlobDuration(blob)
console.log(duration + ' seconds')
A bug in Chrome, detected in 2016, but still open today (March 2019), is the root cause behind this behavior. Under certain scenarios audioElement.duration will return Infinity.
Chrome Bug information here and here
The following code provides a workaround to avoid the bug.
Usage : Create your audioElement, and call this function a single time, providing a reference of your audioElement. When the returned promise resolves, the audioElement.duration property should contain the right value. ( It also fixes the same problem with videoElements )
/**
* calculateMediaDuration()
* Force media element duration calculation.
* Returns a promise, that resolves when duration is calculated
**/
function calculateMediaDuration(media){
return new Promise( (resolve,reject)=>{
media.onloadedmetadata = function(){
// set the mediaElement.currentTime to a high value beyond its real duration
media.currentTime = Number.MAX_SAFE_INTEGER;
// listen to time position change
media.ontimeupdate = function(){
media.ontimeupdate = function(){};
// setting player currentTime back to 0 can be buggy too, set it first to .1 sec
media.currentTime = 0.1;
media.currentTime = 0;
// media.duration should now have its correct value, return it...
resolve(media.duration);
}
}
});
}
// USAGE EXAMPLE :
calculateMediaDuration( yourAudioElement ).then( ()=>{
console.log( yourAudioElement.duration )
});
Thanks #colxi for the actual solution, I've added some validation steps (As the solution was working fine but had problems with long audio files).
It took me like 4 hours to get it to work with long audio files turns out validation was the fix
function fixInfinity(media) {
return new Promise((resolve, reject) => {
//Wait for media to load metadata
media.onloadedmetadata = () => {
//Changes the current time to update ontimeupdate
media.currentTime = Number.MAX_SAFE_INTEGER;
//Check if its infinite NaN or undefined
if (ifNull(media)) {
media.ontimeupdate = () => {
//If it is not null resolve the promise and send the duration
if (!ifNull(media)) {
//If it is not null resolve the promise and send the duration
resolve(media.duration);
}
//Check if its infinite NaN or undefined //The second ontime update is a fallback if the first one fails
media.ontimeupdate = () => {
if (!ifNull(media)) {
resolve(media.duration);
}
};
};
} else {
//If media duration was never infinity return it
resolve(media.duration);
}
};
});
}
//Check if null
function ifNull(media) {
if (media.duration === Infinity || media.duration === NaN || media.duration === undefined) {
return true;
} else {
return false;
}
}
//USAGE EXAMPLE
//Get audio player on html
const AudioPlayer = document.getElementById('audio');
const getInfinity = async () => {
//Await for promise
await fixInfinity(AudioPlayer).then(val => {
//Reset audio current time
AudioPlayer.currentTime = 0;
//Log duration
console.log(val)
})
}
I wrapped the webm-duration-fix package to solve the webm length problem, which can be used in nodejs and web browsers to support video files over 2GB with not too much memory usage.
Usage is as follows:
import fixWebmDuration from 'webm-duration-fix';
const mimeType = 'video/webm\;codecs=vp9';
const blobSlice: BlobPart[] = [];
mediaRecorder = new MediaRecorder(stream, {
mimeType
});
mediaRecorder.ondataavailable = (event: BlobEvent) => {
blobSlice.push(event.data);
}
mediaRecorder.onstop = async () => {
// fix blob, support fix webm file larger than 2GB
const fixBlob = await fixWebmDuration(new Blob([...blobSlice], { type: mimeType }));
// to write locally, it is recommended to use fs.createWriteStream to reduce memory usage
const fileWriteStream = fs.createWriteStream(inputPath);
const blobReadstream = fixBlob.stream();
const blobReader = blobReadstream.getReader();
while (true) {
let { done, value } = await blobReader.read();
if (done) {
console.log('write done.');
fileWriteStream.close();
break;
}
fileWriteStream.write(value);
value = null;
}
blobSlice = [];
};
//If you want to modify the video file completely, you can use this package "webmFixDuration", Other methods are applied at the display level only on the video tag With this method, the complete video file is modified
webmFixDuration github example
mediaRecorder.onstop = async () => {
const duration = Date.now() - startTime;
const buggyBlob = new Blob(mediaParts, { type: 'video/webm' });
const fixedBlob = await webmFixDuration(buggyBlob, duration);
displayResult(fixedBlob);
};

WebRTC-Problem: Cannot create answer in stable (no Chrome but AJAX signalizing involved)

can somebody help me out a little? I am a little stuck.
I am trying to write a signaling process with ajax and a database involved (this is just for learning the basics of WebRTC for now).
I am receiving the SDP fine from the JSON-object as it seems, but then I always get an error "Cannot create answer in stable" when I try to create an answer in get_remote_offer() for pc_partner.
I am pretty sure it is something obvious, but I am pretty new to WebRTC and just can't see what.
I am using Firefox here and just trying to connect two instances of it (one in private mode, one in "normal" mode, but I am trying to make it work for remote users.
This is my code:
var opt;
var video_el_partner;
var video_el_local;
var pc_partner;
var pc_local;
var interval_gro;
var remote_offer_available = false;
var service_url = "https://xyz.de/webrtc";
var pwd = "xxx";
var signaling_url = "https://xyz.de/webrtc/sdp_transfer.php";
function init_stream(video_partner_id, video_local_id, allow_video, allow_audio){
if (location.protocol === 'https:') { // only possible for https!
pc_local = new RTCPeerConnection();
pc_partner = new RTCPeerConnection();
if(document.getElementById(video_partner_id) != null){
video_el_partner = document.getElementById(video_partner_id);
video_el_local = document.getElementById(video_local_id);
if(allow_video == null){
allow_video = true;
}
if(allow_audio == null){
allow_audio = true;
}
opt = { audio: allow_audio, video: allow_video };
if(typeof navigator != 'undefined' && typeof navigator.mediaDevices != 'undefined' && navigator.mediaDevices.getUserMedia != null){
navigator.mediaDevices.getUserMedia(opt).then (
function (this_stream){
// local video directly into video element:
video_el_local.srcObject = this_stream;
// remote one is more insteresting:
pc_local.addStream(this_stream);
pc_local.createOffer().then(
function (this_sdp) {
// sdp (session dependend protocol object) is now available... this would need to go to a server somehow now.
// they use socket.io for that... maybe I can use my own thing to do that?
pc_local.setLocalDescription(this_sdp);
var this_sdp_json = JSON.stringify(this_sdp)
var params_ins = "mode=insert_offer&sdp_con=" + this_sdp_json + "&pass=" + pwd + "&service_url=" + service_url;
ajax_request_simple (
signaling_url,
params_ins,
function (res_ins) {
// insert done. Lets read for another candidate.
console.log('Set Interval!');
interval_gro = window.setInterval('get_remote_offer();', 5000);
}
);
}
);
}
).catch(
function (error) {
console.log('Problem: ');
console.log(error);
}
);
} else {
console.log("navgiator or navigator.mediaDevices is not defined.");
}
}
} else {
console.log('init_stream(): We can only do anything like that on https-connections! Http is not supported by the browser!');
}
}
window.onload = function () {
document.getElementById('button_start_stream').onclick = function () {
init_stream('video_partner', 'video_local', true, false);
}
}
function is_json_str(str) {
try {
JSON.parse(str);
} catch (e) {
return false;
}
return true;
}
function get_remote_offer() {
var params_read = "mode=get_offer&pass=" + pwd + "&service_url=" + service_url;
ajax_request_simple (
signaling_url,
params_read,
function (res_read) {
// done.
if(is_json_str(res_read)){
// seems like we get one now.
// lets use that to connect and stream the video to the remote view.
var partner_offer = res_read;
partner_offer = JSON.parse(partner_offer);
// clear interval if found.
window.clearInterval(interval_gro);
console.log('Cleared Interval. Found!');
pc_local.setRemoteDescription(
new RTCSessionDescription(partner_offer), function(){
// video_el_partner.srcObject = event.stream;
pc_local.onicecandidate = function (e) {
if ( e.candidate != null ) {
pc_partner.addIceCandidate( new RTCIceCandidate(e.candidate) );
}
};
pc_partner.onicecandidate = function (e) {
if ( e.candidate != null ) {
pc_local.addIceCandidate( new RTCIceCandidate(e.candidate) );
}
};
pc_partner.createAnswer(
function (offer) {
pc_local.setRemoteDescription(offer);
pc_partner.setLocalDescription(offer);
}
);
// pc_local.ontrack = function (evt) {
// video_el_local.srcObject = evt.stream;
// };
pc_partner.ontrack = function (evt) {
video_el_partner.srcObject = evt.stream;
};
},
function(e) {
console.log("Problem while doing client-answer: ", e);
}
);
} else {
console.log("Can not parse: ");
console.log(res_read);
}
}
);
}
Sorry for the mix of promises and callbacks... I tried a couple of things out just in case... when it is working I will rewrite the callback parts.
Thank you very much in advance for any hint you can give me :).
Best regards and thanks for reading till now ;).
Fuchur

How do I connect to a Clover mini device using ONLY node?

I'm fairly new to Node, and I'm trying to connect to a Clover Mini device through a websocket using the API provided by Clover.
I've tried modifying the example code below to work using only node, but when I open it in node nothing happens. (No errors, just nothing happens at all)
It works in Chrome just fine, so what's missing?
https://github.com/clover/remote-pay-cloud
var $ = require('jQuery');
var clover = require("remote-pay-cloud");
var log = clover.Logger.create();
var connector = new clover.CloverConnectorFactory().createICloverConnector({
"oauthToken": "1e7a9007-141a-293d-f41d-f603f0842139",
"merchantId": "BBFF8NBCXEMDV",
"clientId": "3RPTN642FHXTX",
"remoteApplicationId": "com.yourname.yourapplication:1.0.0-beta1",
"deviceSerialId": "C031UQ52340015",
"domain": "https://sandbox.dev.clover.com/"
});
var ExampleCloverConnectorListener = function(cloverConnector) {
clover.remotepay.ICloverConnectorListener.call(this);
this.cloverConnector = cloverConnector;
};
ExampleCloverConnectorListener.prototype = Object.create(clover.remotepay.ICloverConnectorListener.prototype);
ExampleCloverConnectorListener.prototype.constructor = ExampleCloverConnectorListener;
ExampleCloverConnectorListener.prototype.onReady = function (merchantInfo) {
var saleRequest = new clover.remotepay.SaleRequest();
saleRequest.setExternalId(clover.CloverID.getNewId());
saleRequest.setAmount(10000);
this.cloverConnector.sale(saleRequest);
};
ExampleCloverConnectorListener.prototype.onVerifySignatureRequest = function (request) {
log.info(request);
this.cloverConnector.acceptSignature(request);
};
ExampleCloverConnectorListener.prototype.onConfirmPaymentRequest = function (request) {
this.cloverConnector.acceptPayment(request.payment);
};
ExampleCloverConnectorListener.prototype.onSaleResponse = function (response) {
log.info(response);
connector.dispose();
if(!response.getIsSale()) {
console.error("Response is not an sale!");
console.error(response);
}
};
var connectorListener = new ExampleCloverConnectorListener(connector);
connector.addCloverConnectorListener(connectorListener);
connector.initializeConnection();
After getting into contact with the developers at clover, their documentation had some errors. For other users sake here is the link to that issue on their gitub as well as some example code.
link to github issue
const endpoint = "ws://yourip:yourport/remote_pay";
var webSocketFactory = function () {
let webSocketOverrides = {
createWebSocket: function () {
// To support self-signed certificates you must pass rejectUnauthorized = false.
// https://github.com/websockets/ws/blob/master/examples/ssl.js
let sslOptions = {
rejectUnauthorized: false
};
// Use the ws library by default.
return new WebSocket(endpoint, sslOptions);
}
}
return Object.assign(new clover.CloverWebSocketInterface(endpoint), webSocketOverrides);
};
var ExampleWebsocketPairedCloverDeviceConfiguration = function () {
clover.WebSocketPairedCloverDeviceConfiguration.call(this,
endpoint, // endpoint
"com.cloverconnector.javascript.simple.sample:1.4", // Application Id
"Javascript Simple Sample", // posName
"Register_1", // serialNumber
null, // authToken().get(
webSocketFactory,
new clover.ImageUtil());
};

Categories

Resources