I have a <video> and <audio> element which load a file (mp4, mp3, doesn't matter) from my server via Range requests.
It seems however that the element only request the end Range from my server, and from there on out tries to stream directly from bytes 0 to the end, causing the player to be stuck in a "download loop", which makes the browser suspend all other actions until the download is complete.
Does anyone know a solution to this issue? Do I for example have to make my stream request HAVE an actual end to its content length or accept-ranges?
Here's the full request list from Chrome and at the bottom you can see that a request for the url view?watch=v__AAAAAA673pxX just stays pending, basically until either a new request is placed by the element.
In a nutshell: html5 elements get stuck in a download loop when using http-range requests and cause all other requests to stay "pending".
UPDATE
The issue was resolved server-side.
Whereas the original stream function would literally output every byte, I've modified the code to output ONLY the size of the actual buffer. This forces the elements to make a new request for the remaining data.
An important note here is to return the content-length, accept-ranges and content-ranges that match the file's size, start and ending position in each HTTP RANGE request.
For future references:
function stream(){
$i = $this->start;
set_time_limit(0);
while(!feof($this->stream) && $i <= $this->end) {
$bytesToRead = $this->buffer;
if(($i+$bytesToRead) > $this->end) {
$bytesToRead = $this->end - $i + 1;
}
$data = fread($this->stream, $bytesToRead);
echo $data;
flush();
$i += $bytesToRead;
}
}
new stream function:
function stream()
{
//added a time limit for safe-guarding
set_time_limit(3);
echo fread($this->stream, $this->offset);
flush();
}
Suppose you have a video of 1M bytes
When you browser request for video the first time it will send headers like this
Host:localhost
Range:bytes=0-
Range header bytes=0- means browser is asking server to return till whatever it can return ie. no end position is specified
To this server would usually reply with whole file except last byte to preserve the range context
Accept-Ranges:bytes
Content-Length:99999
Content-Range:bytes 0-99999/1000000
Now suppose your video is downloaded till 30% and you seek to 70% then browser will request that part header would be like this
Host:localhost
Range:bytes=700000-
It seems however that the element only request the end Range from my server,
You can see you inferred wrongly it's the starting position of video part
Now server might reply like
Accept-Ranges:bytes
Content-Length:300000
Content-Range:bytes 700000-99999/1000000
Note Content-Range it explicitly tells what portion of file .So my guess it that your server is not sending this information and browser is getting bugged.
Also sometimes mime-types can also cause problems try to use the exact mimetype of your file like Content-Type: video/mp4.If you use Content-Type: application/octet-stream then might cause compression which would disabled range headers
Related
I am attempting to simulate a scheduled live audio stream without the use of any third party tools/software. In order to do so, I would need every visitor on the website to be on the same point on the audio file. My initial plan was to have a PHP script that keeps track of the time, and write to a .json file :
ini_set('max_execution_time', 0);
include 'mp3file.class.php';
$file = "./audioDuration.json";
$mp3file = new MP3File("Nightride.mp3");
$duration = $mp3file->getDurationEstimate();
$tracker = 0;
while($tracker < $duration){
$tracker++;
file_put_contents($file, $tracker);
sleep(1);
}
And the Javascript :
$.getJSON( "audioDuration.json",
function( returnedData ) {
document.getElementById('audioElement').currentTime = returnedData;
}
However, being completely new to PHP, I did not realize that any user can run this script on their own browser, and it would cause the audioDuration.json to contain the wrong data. I've done some research, and it appears that there are ways to have a PHP script only run if the server requests it. I am not sure if this is the most practical way to accomplish this.
I feel you should use a server side resource to be sure any client get the same "time" to set-up your audio file.
Why don't you use something like server date('H:i:s); function. If you get a 1hour long file you just need to dont take care about hours, and use only minutes and seconds to get which time should be used to start the audio file.
And you don't even need to use javascript to call server to get the value. If you use php to generate your HTML you can directly print value in the HTML's javascript when loading the page, something like :
echo '<script type="text/javascript">
document.getElementById('audioElement').currentTime = ' . $timer . ';
</script>';
I want to download and play m3u8 file which is on server machine. I am using following code to read and send m3u8 file to web server.
Browser is displaying contents of file instead of downloading it.
So please let me know that, how to download it.
if ((exportHandle = fopen(v3FileName, "a+")) != NULL) {
long end = 0, start = 0, pos = 0;
char* m3u8FileDataBuff = NULL;
fseek(exportHandle, 0, SEEK_END);
end = ftell(exportHandle);
fseek(exportHandle, 0, SEEK_SET);
start = ftell(exportHandle);
pos = end - start;
m3u8FileDataBuff = (char *) malloc(pos);
end = 0;
start = 0;
fread(m3u8FileDataBuff, 1, pos, exportHandle);
pClienCommunication->writeBuffer(m3u8FileDataBuff, pos);
free(m3u8FileDataBuff);
fclose(exportHandle);
}
Client's web browser is displaying the content, because the MIME type of the response is either nil, or something like "text/plain". Set up the http response header properly to indicate mime type of m3u8 file (application/x-mpegURL or vnd.apple.mpegURL).
The piece of code you provided does not seem to set anything around response header, just content.
Check available API of pClienCommunication->, or place where that originates, what are your options to adjust response header.
Or maybe it's possible to work-around this also by some rule set up in the web server serving the response, to set the MIME type for certain URLs, or based on the response content (but applying such rules on web server level is usually more costly then adjusting the response while being created in the C++ part).
And why is this tagged C++, when the code itself is C-like with all the problems of it. In modern C++ you never do things like "fclose(..)", because that is done in the destructor of the file wrapper class, so you don't risk the fclose will be skipped in case of some exception raised in fread, etc.
So in modern C++ these things should look somewhat like this:
{
SomeFileClass exportFile(v3FileName, "a+");
if (exportFile.isOK()) {
SomeFileContentBuffer data = exportFile.read();
pClienCommunication->writeBuffer(data.asCharPtr(), data.size());
}
}
So you can't forget to release any file handle, or buffer memory (as the destructors of particular helper classes will handle that).
When you do a XMLHttpRequest, the data is often compressed. Looking at the Content-Length header
xhr.getResponseHeader("Content-Length");
gives you the number of octets in the response body, to which you could add an approximation of the header by sizing the response headers.
But: How do you find the number of (compressed) bytes actually transferred? (in Firefox, if this is only possible in a browser-specific way.)
In the screenshot below, you see a difference for several files:
The following should all be equal to this
the number of bytes read from the socket
the file size in the squid log
the number of application-layer octets sent over the network in response to the request
UPDATE: the performance api seems to provide this: Call as
performance.getEntries()[0]
and see the encodedBodySize (see also at MDN).
The screenshot above shows the Network Monitor. Its code seems to use the network-monitor.js file which
implements the nsIStreamListener and nsIRequestObserver interfaces. This is used within the NetworkMonitor feature to get the response body of the request.
The relevant code seems to be
onProgress: function(request, context, progress, progressMax) {
this.transferredSize = progress;
// Need to forward as well to keep things like Download Manager's progress
// bar working properly.
this._forwardNotification(Ci.nsIProgressEventSink, "onProgress", arguments);
},
with
_onComplete: function NRL__onComplete(aData)
{
let response = {
mimeType: "",
text: aData || "",
};
response.size = response.text.length;
response.transferredSize = this.transferredSize;
The progress event is part of neither interface. It may be from xhr, but where it came from is as of yet unclear.
As long as the response is text (as opposed to binary blobs), you have some good starting points here, on SO :
Measure string length in bytes
I have an input field where I paste a download url.
After that, the I use an AJAX request to get the fileinfos such as headerinfo, content-length, mime type & in case I use curl accept-ranges.
I then start a consecutive loop of xhr2 requests with ranges to my php file.
http://www.example.com/chunks.php?url=http://url.com/someFile.ext&range=0-1024
http://www.example.com/chunks.php?url=http://url.com/someFile.ext&range=1024-2048
....
I can also change it to
http://www.example.com/chunks.php?url=http://url.com/someFile.ext&range=0-1024
http://www.example.com/chunks.php?url=http://url.com/someFile.ext&range=1025-2049
....
depending where my script starts to read the file.
My first approach was using cUrl & setting the ranges
<?php
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$_GET['url']);
curl_setopt($ch,CURLOPT_RANGE,$_GET['range']);
curl_setopt($ch,CURLOPT_BINARYTRANSFER,1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
$result=curl_exec($ch);
curl_close($ch);
echo $result;
?>
works great but if the range chunks are bigger than 1mb there is no animation on the client side onprogress event using ajax.
i prolly could use a custom CURLOPT_READFUNCTION... but i don't know how that works... so i changed approach and used the simple fopen
<?php
$r=explode('-',$_GET['range']);//get (from to) ranges
$cc=($r[1]-$r[0]); //Calculate Client Chunk length
$sc=128; //Set the Server chunk length
$b=""; //Buffer
$bytes=0; //bytes read
$h=fopen($_GET['url'],"rb"); // open the url
fseek($h,$r[0]); // jump to the from pointer retrieved from links
while($bytes<$cc){ //while bytes read is smaller than my client chunk
$sc=(($bytes+$sc)>$cc?($cc-$bytes):$sc); //prolley an error here
//if the server chunk + bytes read is bigger than the client chunk
//then the server chunk is clinet chunk - bytes read
$b=fread($h,$sc); // read the buffer
$bytes+=strlen($b); //add the buffer length to bytes read
echo $b;// echo the buffer
ob_flush(); // flush
flush(); // flush
}
fclose($h); //close
?>
now this works ... I get the right animation on the client and also the final size is correct the pointers should be ok (0-1024,1024-2048) as I use fseek && fread.
but the file is corrupt.
Now after some tests ... this is very slow.
A better approach would be cUrl with CURLOPT_READFUNCTION or fsoket open...
so I guess:
<?php
function $READ(){
//here i need small chuncks of the response flushed.
}
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$_GET['url']);
curl_setopt($ch,CURLOPT_RANGE,$_GET['range']);
curl_setopt($ch,CURLOPT_BINARYTRANSFER,1);
curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch,CURLOPT_READFUNCTION,$READ);
$result=curl_exec($ch);
curl_close($ch);
echo $result;
?>
If you have a better solution I'm open to everything that uses javascript and php.
The point of this is to create a download manager with resume that stores the file into the window.webkitRequestFileSystem without filling the memory of the browser.
Let's say the client has chunks of 8mb and the server chunks are 256kb ..
then every 8mb of the chunk is appended to a file previously created with window.webkitRequestFileSystem
and every 256kb I have an update of the average download speed and this way I can create a nice animation.
The php on the server uses only 256kb ram and the client browser can empty the garbage collection every 8mb (theoretically).
EDIT2
For this code I found a solution:
the code allows you to get ranges for example:0-100
and get the output of this 100bytes chunked !!
this allows you to have a AJAX script that has a continuos flawless PROGRESSBAR
<?php
function w($ch,$chunk){
echo $chunk;
ob_flush();
flush();
return strlen($chunk);
};
$ch=curl_init();
curl_setopt($ch,CURLOPT_URL,$_GET['url']);
curl_setopt($ch,CURLOPT_RANGE,$_GET['range']);
curl_setopt($ch,CURLOPT_BINARYTRANSFER,1);
curl_setopt($ch,CURLOPT_WRITEFUNCTION,w);
curl_exec($ch);
curl_close($ch);
?>
But I hope you guys have a better solution at all!! thanks
I could get it to work with PHP curl's CURLOPT_WRITEFUNCTION callback setting. The following example callback function curl_write_flush intended for that curl option writes every chunk received and flushes the output to the browser.
<?php
/**
* CURLOPT_WRITEFUNCTION which flushes the output buffer and the SAPI buffer.
*
* #param resource $curl_handle
* #param string $chunk
*/
function curl_write_flush($curl_handle, $chunk)
{
echo $chunk;
ob_flush(); // flush output buffer (Output Control configuration specific)
flush(); // flush output body (SAPI specific)
return strlen($chunk); // tell Curl there was output (if any).
};
$curl_handle = curl_init($_GET['url']);
curl_setopt($curl_handle, CURLOPT_RANGE, $_GET['range']);
curl_setopt($curl_handle, CURLOPT_BINARYTRANSFER, 1);
curl_setopt($curl_handle, CURLOPT_WRITEFUNCTION, 'curl_write_flush');
curl_exec($curl_handle);
curl_close($curl_handle);
I tried with small files and big files and it works great but you can't set custom chunk size.
Download stream is the same speed as I can get with my ISP.
If you have anything better i'm open for any answer.
I have an unusual requirement. Essentially I need a way so that, when the user clicks on a link or button, they will receive a PDF. The tricky part here is that the server won't process the request at all unless a custom header is sent with it (otherwise it deems the person logged out and sends them to the login screen).
At the moment the way the header works cannot be changed so please don't dwell on it; it will get changed in the future and is an internal application that I have no control over.
The options I have explored:
Using an iframe or simply opening a new window with some sort of path that will return the PDF. This can't work because I cannot specify the required header for the PDF and would be redirected before reaching the PDF itself.
Using a form and submitting the request can't work because I can't
add any custom headers to forms (only XHR and plugins can, AFAIK).
Using XHR can't work because, while it can add the header and
retrieve the file, there is no way to save it on the client side.
It would appear my only options at this point are essentially:
Use some sort of plugin such as Flash or Silverlight to request the file.
Force the change of the requirement much earlier than expected so that a header is no longer required.
Is there anything I am missing here? I'm hoping someone can either verify my findings or point me to something I missed because, as far as I can tell, there isn't really anything I can do here.
EDIT: This seems apt and confirms what I was thinking: XMLHttpRequest to open PDF in browser
Tested to work in chrome:
function toBinaryString(data) {
var ret = [];
var len = data.length;
var byte;
for (var i = 0; i < len; i++) {
byte=( data.charCodeAt(i) & 0xFF )>>> 0;
ret.push( String.fromCharCode(byte) );
}
return ret.join('');
}
var xhr = new XMLHttpRequest;
xhr.open( "GET", "/test.pdf" ); //I had test.pdf this on my local server
xhr.addEventListener( "load", function(){
var data = toBinaryString(this.responseText);
data = "data:application/pdf;base64,"+btoa(data);
document.location = data;
}, false);
xhr.setRequestHeader("magic", "header" );
xhr.overrideMimeType( "application/octet-stream; charset=x-user-defined;" );
xhr.send(null);
You can change application/pdf to application/octet-stream to have download prompt. But it's pretty easy to download from the chrome's reader as well.
In firefox nothing happens I guess it's because I don't have a plugin to deal with application/pdf installed. Changing to application/octet-stream will prompt a dl.
With IE I suppose you need some kind of VBScript/ActiveX hackery
If the file is huge, using data uri might crash the browser, in that case you can use BlobBuilder and Object URLs.
Instead of linking to the .PDF file, instead do something like
Download my eBook
which outputs a custom header, opens the PDF (binary safe) and prints the data to the user's browser, then they can choose to save the PDF despite their browser settings. The pdf_server.php should look like this:
header("Content-Type: application/octet-stream");
$file = $_GET["file"] .".pdf";
header("Content-Disposition: attachment; filename=" . urlencode($file));
header("Content-Type: application/force-download");
header("Content-Type: application/octet-stream");
header("Content-Type: application/download");
header("Content-Description: File Transfer");
header("Content-Length: " . filesize($file));
flush(); // this doesn't really matter.
$fp = fopen($file, "r");
while (!feof($fp))
{
echo fread($fp, 65536);
flush(); // this is essential for large downloads
}
fclose($fp);
EDIT: The only way to add headers to a request from inside a browser (client-side) is use the XmlHttpRequest setRequestHeader method.
xhr.setRequestHeader('custom-header', 'value');