Detect 404 on video blob:https source - javascript

I am using blob:https as source for my video-tags, like this:
function mk_bloburl(source_id, url) {
var xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'blob'; //important
xhr.onload = function(e) {
if (this.status == 200) {
var blob = this.response;
var source = document.getElementById(source_id);
var video = source;
if (video.tagName=="SOURCE") {
video = source.parentNode;
}
video.oncanplaythrough = function() {
URL.revokeObjectURL(source.src);
};
source.src = URL.createObjectURL(blob);
video.load();
}
};
xhr.send();
}
mk_bloburl('review-video-source', my_video_url );
Having HTML like this:
<video id="review-video" controls="" width="100%">
<source id="review-video-source" />
</video>
Now if I reload the page and start playing, it works. If I play and let it play through and then replay with no wait-time it works.
If I however reload the page and then wait for a while (like 1-2 minutes) and then press play, it fails.
Message I get in the Chrome browser looks like this:
GET blob:https://example.com/debeecfe-49b0-4c98-87d6-8ead325b2d75 404 (Not Found)
So, it's like the blob is auto-removed from the browser memory after a while. I want to catch the event when it is erased or when I get the 404 by starting the playback, so I can refresh the blob.
I have tried:
var source = document.querySelector("#review-video-source");
source.addEventListener("error", function(event) {
console.debug("An error accoured");
});
But this does not seem to catch the error.
What can I do?

Your main problem is caused by the fact you revoked the blobURI in the canplaythrough event.
The canplaythrough event is just a notice sent by the browser to let us know it thinks it will be able to load and play the whole media without interruption ; it doesn't mean that it has loaded everything yet.
In the case of BlobURI, the connection speed is so fast (it comes from memory) that the browser could think it is able to fetch the StarWars saga in 4k in a blink.
So you get this canplaythrough event really early, but the browser didn't actually uncompressed all the data yet. Still, you revoke the BlobURI, and when the browser tries again to fetch data so it can uncompress and read it, there is nothing anymore at the end of the BlobURI's pointer.
So for your problem, you've got two solutions :
In case you need to play the media only once :
call URL.revokeObjectURL(blobURI) in the ended event. This will fire, the first time the currentTime of the video will reach the end.
If you need to play the video multiple times :
revoke the blobURI in the beforeunload event of the page. This way, your BlobURI pointer is always active for as long as the page is alive, but will not block the whole Blob in memory for longer than the page life (which would happen if you don't revoke the BlobURI at all).
And about how to Detect 404 on video blob:https source, I don't really know a good way, except listening for an unexpected jump to the end, but this should not be needed for blobURIs anyway.

One way to get around it is using the fetch API like so :
function mk_bloburl(source_id, url){
fetch(url)//usual get
.then(response=>{
if(response.status == 404)
//Should set here what is an error (ex: x>299)
Promise.reject("Error 404!");
return response.blob();
})//get blob
.then(blobObj=>{//use blob
var blob = blobObj;
var source = focument.getElementById(source_id);
var video = source;
if(video.tagName === "SOURCE")
video = source.parentNode;
video.oncanplaythrough = ()=>{
URL.revokeObjectUrl(source.src);
};
source.src = URL.revokeObjectURL(blob);
video.load();
})
.catch(error=>{
//There has been an error,
//do something about it
//here
});
}

Related

How to detect when mjpeg stream stops with javascript

I have a html/javascript client that is listening to a mjpeg video stream:
myImg = document.getElementById('my-image');
myImg.src = 'http://myserver.com/camera.mjpeg';
Works fine but if the video stream dies for whatever reason the video feed "freezes" on the last received image and I have no opportunity to display an error to the user. I've see this post that offers a solution (creating a long running ajax request alongside the stream) that only works some of the time. I was hoping there would be a more supported method like through a disconnect event or something.
Even an event for when data is received would be better than nothing. At least that way I could tell if it's been a while since a frame came through. Using addEventListener('load') only works on the very first frame.
Any ideas?
Update:
Based on comments I have tried the following approaches, none of which has worked:
myImg.addEventListener('error', event => { ... });
myImg.addEventListener('stalled', event => { ... });
myImg.addEventListener('suspend', event => { ... });
This is common with a normal implementation of a mjpeg, for example
<video src="http://myserver.com/camera.mjpeg" controls>
Your browser does not support the <code>video</code> element.
</video>
the mjpeg is a series of images and eventually it will not get the next one for whatever reason, breaking the connection. (this is sometimes because the source is cached, causing the browser to use the last image every time). I don't consider this an error, more something to program around with mjpeg streams.
A simple solution you can do, set a refresh rate and set the src continuously refreshing the connection every ~500ms (or less depending on your network connection/resources).
setInterval(function() {
var myImg = document.getElementById('myImg');
myImg.src = 'http://myserver.com/camera.mjpeg?rand=' + Math.random();
}, 5000);
The random number is added to prevent browser side caching in the event the server sends those headers.
Or you can create a ReadableStream, and keep reading a blob of bytes directly into the source of the image. There is a robust example in this repo, from this other question.
In Safari document.readyState will change from interactive to complete.
For example put this before the image loads:
<script>
console.log('Initial ready state', document.readyState);
document.onreadystatechange = function() {
console.log('Ready state changed to:', document.readyState);
}
</script>
And the output will be:
Initial ready state – "loading"
Ready state changed to: – "interactive"
// When the connection disconnects:
Ready state changed to: – "complete"
In google chrome the readyState doesn't stay on interactive, but it looks like chrome is better at reconnecting, so might not be an issue for you.
Edit: One way to make use of this is to drop the image in an iframe, you'll continually get load events in safari (this does not work in chrome).
iframe = document.createElement('iframe')
iframe.onload = console.log
iframe.src = "http://10.0.0.119:8080/stream"
document.body.append(iframe)
Edit2: Another technique -- use image.decode to detect when the connection is down and reload the image:
<img id="stream" src="http://10.0.0.119:8080/stream"></img>
<script>
let image = document.getElementById('stream');
async function check() {
while (true) {
try {
await image.decode();
} catch {
let src = image.src;
image.src = "";
image.src = src;
}
await new Promise((resolve) => setTimeout(resolve, 5000));
}
}
check();
</script>
Would something like this work?
function hasLoaded(myImg) {
return myImg.complete && myImg.naturalHeight !== 0;
}
Following Beau Bouchard answers.
The setInterval timer, works fine but it tends to max out active client listening. ( if ur mjpeg stream are coming directly from an IP Camera). Could possibly create a restreaming server the mjpeg server to allow more clients to be able to be listening to it) Short pooling though does tend to be very resource heavy.
Tried the Restream Api as well. When loading the image back into the img tag, you do get a jittery effect, most likely because the chunks are coming in randomly and not smooth out via time?
In the end, i use the onload img tag event. This triggers whenever an img is loaded. Then a time interval to check if the img tag has stop loading to determine if the mjpeg stream has stop.
Met the same requirement, test on Image onload event and works!!
If FFMPEG feed FFSERVER stop, although the MJPEG in a still image,
After couple times error count, mjpeg FAIL! detected.
<!DOCTYPE HTML>
<HTML>
<HEAD>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<TITLE> mjpeg detect </TITLE>
<script type="text/javascript" language="JavaScript" src="/js/jquery.js"></script>
<script type="text/javascript" language="JavaScript">
//------------------------------------------------------------------------------
// localhost/tool/mjpeg.htm
// document.ready
$(function(){
setTimeout("mjpegRefresh()", 10000);
});
var mTmjpegRefresh, mBmjpegStatus=0, mNmjpegError=0;
var mjpegRefresh = function()
{
clearTimeout(mTmjpegRefresh);
mBmjpegStatus=0;
$('#myMJPEG').attr('src', "http://192.168.1.17:8090/live.mjpeg?rand=" + Math.random());
console.log("mjpeg refresh: ", Math.round( (new Date()).getTime()/1000)) ;
mTmjpegRefresh = setTimeout("mjpegRefresh()", 10000);
mTmjpegStatusCheck = setTimeout("mjpegStatusCheck()", 5000);
}
var mjpegOnload = function()
{
console.log("mjpeg Onload");
mBmjpegStatus=1;
}
var mTmjpegStatusCheck;
var mjpegStatusCheck = function()
{
clearTimeout(mTmjpegStatusCheck);
if(mBmjpegStatus>0)
{
mNmjpegError=0;
}
else
{
mNmjpegError++;
}
if(mNmjpegError>5)
{
console.log("mjpeg FAIL!");
}
mTmjpegStatusCheck = setTimeout("mjpegStatusCheck()", 5000);
}
//------------------------------------------------------------------------------
</script>
</HEAD>
<BODY>
<img src="http://192.168.1.17:8090/live.mjpeg" width="720" height="404" id="myMJPEG" onload="mjpegOnload()">
</BODY>
</HTML>

Playing audio with javascript

I am trying to play a beep sound a minute after user has come on the page of my website. I found the solution here https://stackoverflow.com/a/18628124/912359
Here's my code:
$(document).ready(function(){
setTimeout(function () {
try{
if(!$(".facebook-chat").hasClass("active")){
$(".facebook-chat").addClass("active");
var audio = new Audio("/sound/chat.mp3");
audio.play();
}
}catch(e){
}
}, 60000);
}):
This throws an exception:
Uncaught (in promise) DOMException
Strangely, once I load the sound file separately in my browser and come back to the page, it works perfectly. Any ideas how I can fix it.
[Edit]
The issue is that user has to interact with the browser before the sound can be played. So I put the same code under click event of the body and it works. But the same doesn't work on scroll event either. I guess chrome doesn't consider scroll a user interaction. Can anyone add what other interactions can be used to trigger this?
Also, how is it working if I load the audio file in a separate window and come back to my page.
You can try loading the audio when the document is ready and then play it later only if the resource is loaded (for this check you can register a callback on onloadeddata). Otherwise, if resource is not loaded, you can try loading it again.
$(document).ready(function()
{
let aud = new Audio('https://dl.dropboxusercontent.com/s/1cdwpm3gca9mlo0/kick.mp3');
let canPlay = false;
aud.onloadeddata = () => (console.log("audio loaded"), canPlay = true);
setInterval(function()
{
if (canPlay)
aud.play();
else
aud = new Audio('https://dl.dropboxusercontent.com/s/1cdwpm3gca9mlo0/kick.mp3');
}, 3000);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Best solution I could come up with when I tried the same was:
const playPromise = audio.play();
if (playPromise !== null){
playPromise.catch(function() { audio.play(); })
}
But sometimes (One out of ten) the second audio.play() where also uncaught and the audio did not play either. I suggest you made a loop that stops only when the Promise is finally caught.

How can I catch a 404 error in Javascript?

I have an HTML audio element and I am dynamically setting the "src" property of the element to an audio file stored on our local area network.
This is how it works:
function setSource(source) {
audio.src = source;
}
var audio = new Audio();
var source = "http://localhost/folder/file.mp3";
setSource(source);
Sometimes, the source audio file that I am pointing to has a broken link and this causes a 404 error to be generated and logged to the browser console.
I want to be able to catch the 404 errors so as to prevent them being logged to the console.
This is how I attempted it:
function setSource(source) {
try {
audio.src = src;
}//end try
catch (e) {
//do nothing
}//end catch
}//end setSource
var audio = new Audio();
var source = "http://localhost/folder/file.mp3";
setSource(source);
Unfortunately, my try/catch statement does absolutely nothing and the error is still logged to the console. Am I doing something wrong?
Due to the nature of my app, there will be lots of 404 errors, which is normal and expected, but it looks really unstable and "ugly" to the users (if they happen to open the console).
FYI: I am using Google Chrome.
In this case, the logging of HTTP errors in the browser console is a feature exclusive to the browser and not of Javascript, or any other website code.
This cannot be prevented.
audio.onload = function() {
console.log('success');
};
audio.onerror = function() {
console.log('fail');
};
audio.src = 'http://localhost/folder/file.mp3';
Yes. With recent updates this is possible. You can enable it here: DevTools->Settings->General->Console->Hide network messages.
How can I catch a 404 error in Javascript?

HTML audio can't set currentTime

I am using Chrome. In my dev tools console, I tried the following:
Everything works as expected except last line. Why can't I set currentTime on it?
Also in general, I am finding this whole HTML5 Audio thing to not be very reliable. Is there a robust javascript wrapper that fallsback to flash ?
You need to do something like this (if you use jQuery)
$('#elem_audio').bind('canplay', function() {
this.currentTime = 10;
});
or in Javascript
var aud = document.getElementById("elem_audio");
aud.oncanplay = function() {
aud.currentTime = 10;
};
The reason behind for this setup is you need to make sure the audio is ready to play.
Check your HTTP server configuration, in my testing environment ( Chrome 69 on Mac OS) setting currentTime property of audio element works only when the audio source is served by a HTTP server support persistent connection.
If the HTTP server you used support persistent connection, you will found (in Chrome DevTool) the Connection field of response headers of your audio source be keep-alive. By contrast if the audio source is served by a persistent connection incompatible server, there will be no Connection field in response headers.
The status code of your audio source HTTP request will be a reference too, 206 Partial Content for persistent connection supported server, 200 OK for an inferior one.
I had the same problem, and the reason was missing headers on the mp3 file, like:
Content-Length, Content-Range, Content-Type
Why cant I set currentTime on it?
Could not reproduce currentTime being set to 0 after setting to 10. Is 18.mp3 duration less than 10?
var request = new XMLHttpRequest();
request.open("GET", "/assets/audio/18.mp3", true);
request.responseType = "blob";
request.onload = function() {
if (this.status == 200) {
var audio = new Audio(URL.createObjectURL(this.response));
audio.load();
audio.currentTime = 10;
audio.play();
}
}
request.send();
jsfiddle http://jsfiddle.net/Lg5L4qso/3/
I ran into this problem after I'd started to use the PHP Server extension to VS Code to serve my HTML/PHP local testing. The problem was resolved for me by going back to my old Abysswebserver setup.
So, it's not simply that "you can't manipulate .currentTime on locally served files" but rather that "you need to pick a server that gives you the right headers". The Status entry for my file from AbyssWebserver, for example, read "Status Code: 206 Partial Content" while the one from PHP Server read "Status Code: 200 OK".
There are other differences though so there may be more to this than just the Status entry. See https://github.com/brapifra/vscode-phpserver/issues/85, for a full header comparison.
If you want to return to the beginning of the audio after it has been played, use load().
// x.currentTime = 0;
x.load();
( https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/load )
this worked for me:
var callaudio = audio; // the audio variable
function fetchAudioFile() {
var requestObj = new Request(callaudio.src, {
method: 'GET',
headers: {
'Accept-Ranges': '1000000000'
},
referrerPolicy: 'no-referrer'
});
fetch(requestObj).then(function (response) {
returnresponse;
}).then(async function (outcome) {
const blob = await outcome.blob();
const url = window.URL.createObjectURL(blob);
callaudio.src = url;
});
};
fetchAudioFile();
The only solution for setting currentTime I got to work reliably was using the onprogress event.
audio.onprogress = function() {
if (audio.currentTime == 0) {
audio.currentTime = 10;
}
}
The solution which worked for me was not setting "src" on directly, but use with type attribute, maybe type attribute is helping browser some way.
My guess is that '10' is longer that you mp3's length.
But that logs the length of mp3 instead of '0'.

Problems preloading audio in Javascript

I'm trying to make a cross-device/browser image and audio preloading scheme for a GameAPI I'm working on. An audio file will preload, and issue a callback once it completes.
The problem is, audio will not start to load on slow page loads, but will usually work on the second try, probably because it cached it and knows it exists.
I've narrowed it down to the audio.load() function. Getting rid of it solves the problem, but interestingly, my motorola droid needs that function.
What are some experiences you've had with HTML5 audio preloading?
Here's my code. Yes, I know loading images in a separate function could cause a race condition :)
var resourcesLoading = 0;
function loadImage(imgSrc) {
//alert("Starting to load an image");
resourcesLoading++;
var image = new Image();
image.src = imgSrc;
image.onload = function() {
//CODE GOES HERE
//alert("A image has been loaded");
resourcesLoading--;
onResourceLoad();
}
}
function loadSound(soundSrc) {
//alert("Starting to load a sound");
resourcesLoading++;
var loaded = false;
//var soundFile = document.createElement("audio");
var soundFile = document.createElement("audio");
console.log(soundFile);
soundFile.autoplay = false;
soundFile.preload = false;
var src = document.createElement("source");
src.src = soundSrc + ".mp3";
soundFile.appendChild(src);
function onLoad() {
loaded = true;
soundFile.removeEventListener("canplaythrough", onLoad, true);
soundFile.removeEventListener("error", onError, true);
//CODE GOES HERE
//alert("A sound has been loaded");
resourcesLoading--;
onResourceLoad();
}
//Attempt to reload the resource 5 times
var retrys = 4;
function onError(e) {
retrys--;
if(retrys > 0) {
soundFile.load();
} else {
loaded = true;
soundFile.removeEventListener("canplaythrough", onLoad, true);
soundFile.removeEventListener("error", onError, true);
alert("A sound has failed to loaded");
resourcesLoading--;
onResourceLoad();
}
}
soundFile.addEventListener("canplaythrough", onLoad, true);
soundFile.addEventListener("error", onError, true);
}
function onResourceLoad() {
if(resourcesLoading == 0)
onLoaded();
}
It's hard to diagnose the problem because it shows no errors and only fails occasionally.
I got it working. The solution was fairly simple actually:
Basically, it works like this:
channel.load();
channel.volume = 0.00000001;
channel.play();
If it isn't obvious, the load function tells browsers and devices that support it to start loading, and then the sound immediately tries to play with the volume virtually at zero. So, if the load function isn't enough, the fact that the sound 'needs' to be played is enough to trigger a load on all the devices I tested.
The load function may actually be redundant now, but based off the inconsistiency with audio implementation, it probably doesn't hurt to have it.
Edit: After testing this on Opera, Safari, Firefox, and Chrome, it looks like setting the volume to 0 will still preload the resource.
canplaythrough fires when enough data has buffered that it probably could play non-stop to the end if you started playing on that event. The HTML Audio element is designed for streaming, so the file may not have completely finished downloading by the time this event fires.
Contrast this to images which only fire their event once they are completely downloaded.
If you navigate away from the page and the audio has not finished completely downloading, the browser probably doesn't cache it at all. However, if it has finished completely downloading, it probably gets cached, which explains the behavior you've seen.
I'd recommend the HTML5 AppCache to make sure the images and audio are certainly cached.
The AppCache, as suggested above, might be your only solution to keep the audio cached from one browser-session to another (that's not what you asked for, right?). but keep in mind the limited amount of space, some browsers offer. Safari for instance allows the user to change this value in the settings but the default is 5MB - hardly enough to save a bunch of songs, especially if other websites that are frequented by your users use AppCache as well. Also IE <10 does not support AppCache.
Alright so I ran into the same problem recently, and my trick was to use a simple ajax request to load the file entirely once (which end into the cache), and then by loading the sound again directly from the cache and use the event binding canplaythrough.
Using Buzz.js as my HTML5 audio library, my code is basically something like that:
var self = this;
$.get(this.file_name+".mp3", function(data) {
self.sound = new buzz.sound(self.file_name, {formats: [ "mp3" ], preload: true});
self.sound.bind("error", function(e) {
console.log("Music Error: " + this.getErrorMessage());
});
self.sound.decreaseVolume(20);
self.sound.bind("canplaythrough",function(){ self.onSoundLoaded(self); });
});

Categories

Resources