Bug 522114. While we're suspending the download, a Web server might close our connection, thinking we're dead. This might look just like a normal connection close. So whenever the connection closes after we resumed, try to reopen it, if we're seekable. r=kinetik

This commit is contained in:
Robert O'Callahan 2009-10-15 15:20:49 +13:00
parent b91922846c
commit f92c87bc09
4 changed files with 201 additions and 2 deletions

View File

@ -286,8 +286,18 @@ nsMediaChannelStream::OnStopRequest(nsIRequest* aRequest, nsresult aStatus)
mChannelStatistics.Stop(TimeStamp::Now());
}
if (NS_FAILED(aStatus) && aStatus != NS_ERROR_PARSED_DATA_CACHED &&
mReopenOnError) {
// Note that aStatus might have succeeded --- this might be a normal close
// --- even in situations where the server cut us off because we were
// suspended. So we need to "reopen on error" in that case too. The only
// cases where we don't need to reopen are when *we* closed the stream.
// But don't reopen if we need to seek and we don't think we can... that would
// cause us to just re-read the stream, which would be really bad.
if (mReopenOnError &&
aStatus != NS_ERROR_PARSED_DATA_CACHED && aStatus != NS_BINDING_ABORTED &&
(mOffset == 0 || mCacheStream.IsSeekable())) {
// If the stream did close normally, then if the server is seekable we'll
// just seek to the end of the resource and get an HTTP 416 error because
// there's nothing there, so this isn't bad.
nsresult rv = CacheClientSeek(mOffset, PR_FALSE);
if (NS_SUCCEEDED(rv))
return rv;

View File

@ -69,6 +69,7 @@ include $(topsrcdir)/config/rules.mk
_TEST_FILES = \
can_play_type_ogg.js \
can_play_type_wave.js \
cancellable_request.sjs \
manifest.js \
seek1.js \
seek2.js \
@ -97,6 +98,7 @@ _TEST_FILES = \
test_playback.html \
test_playback_errors.html \
test_readyState.html \
test_resume.html \
test_seek2.html \
test_volume.html \
use_large_cache.js \

View File

@ -0,0 +1,140 @@
function push32BE(array, input) {
array.push(String.fromCharCode((input >> 24) & 0xff));
array.push(String.fromCharCode((input >> 16) & 0xff));
array.push(String.fromCharCode((input >> 8) & 0xff));
array.push(String.fromCharCode((input) & 0xff));
}
function push32LE(array, input) {
array.push(String.fromCharCode((input) & 0xff));
array.push(String.fromCharCode((input >> 8) & 0xff));
array.push(String.fromCharCode((input >> 16) & 0xff));
array.push(String.fromCharCode((input >> 24) & 0xff));
}
function push16LE(array, input) {
array.push(String.fromCharCode((input) & 0xff));
array.push(String.fromCharCode((input >> 8) & 0xff));
}
function buildWave(samples, sample_rate) {
const RIFF_MAGIC = 0x52494646;
const WAVE_MAGIC = 0x57415645;
const FRMT_MAGIC = 0x666d7420;
const DATA_MAGIC = 0x64617461;
const RIFF_SIZE = 44;
var header = [];
push32BE(header, RIFF_MAGIC);
push32LE(header, RIFF_SIZE + samples.length * 2);
push32BE(header, WAVE_MAGIC);
push32BE(header, FRMT_MAGIC);
push32LE(header, 16);
push16LE(header, 1);
push16LE(header, 1);
push32LE(header, sample_rate);
push32LE(header, sample_rate);
push16LE(header, 2);
push16LE(header, 16);
push32BE(header, DATA_MAGIC);
push32LE(header, samples.length * 2);
for (var i = 0; i < samples.length; ++i) {
push16LE(header, samples[i], 2);
}
return header;
}
const Ci = Components.interfaces;
const CC = Components.Constructor;
const Timer = CC("@mozilla.org/timer;1", "nsITimer", "initWithCallback");
const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
"nsIBinaryOutputStream",
"setOutputStream");
function poll(f) {
if (f()) {
return;
}
new Timer(function() { poll(f); }, 100, Ci.nsITimer.TYPE_ONE_SHOT);
}
function handleRequest(request, response)
{
var cancel = request.queryString.match(/^cancelkey=(.*)$/);
if (cancel) {
setState(cancel[1], "cancelled");
response.setStatusLine(request.httpVersion, 200, "OK");
response.write("Cancel approved!");
return;
}
var samples = [];
for (var i = 0; i < 100000; ++i) {
samples.push(0);
}
var bytes = buildWave(samples, 44100).join("");
var key = request.queryString.match(/^key=(.*)$/);
response.setHeader("Content-Type", "audio/x-wav");
response.setHeader("Content-Length", ""+bytes.length, false);
var out = new BinaryOutputStream(response.bodyOutputStream);
var start = 0, end = bytes.length - 1;
if (request.hasHeader("Range"))
{
var rangeMatch = request.getHeader("Range").match(/^bytes=(\d+)?-(\d+)?$/);
if (rangeMatch[1] !== undefined)
start = parseInt(rangeMatch[1], 10);
if (rangeMatch[2] !== undefined)
end = parseInt(rangeMatch[2], 10);
// No start given, so the end is really the count of bytes from the
// end of the file.
if (start === undefined)
{
start = Math.max(0, bytes.length - end);
end = bytes.length - 1;
}
// start and end are inclusive
if (end === undefined || end >= bytes.length)
end = bytes.length - 1;
if (end < start)
{
response.setStatusLine(request.httpVersion, 200, "OK");
start = 0;
end = bytes.length - 1;
}
else
{
response.setStatusLine(request.httpVersion, 206, "Partial Content");
var contentRange = "bytes " + start + "-" + end + "/" + bytes.length;
response.setHeader("Content-Range", contentRange);
}
}
if (start > 0) {
// Send all requested data
out.write(bytes.slice(start, end + 1), end + 1 - start);
return;
}
// Write the first 120K of the Wave file. We know the cache size is set to
// 100K so this will fill the cache and and cause a "suspend" event on
// the loading element.
out.write(bytes, 120000);
response.processAsync();
// Now wait for the message to cancel this response
poll(function() {
if (getState(key[1]) != "cancelled") {
return false;
}
response.finish();
return true;
});
}

View File

@ -0,0 +1,47 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Media test: Test resume of server-dropped connections</title>
<script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<audio autobuffer id="a"></audio>
<iframe id="f"></iframe>
<pre id="test">
<script class="testbody" type="text/javascript">
var key = Math.round(Math.random()*1000000000);
var a = document.getElementById("a");
var f = document.getElementById("f");
function didEnd() {
ok(a.currentTime > 2.26, "Reached correct end time (got " + a.currentTime + ", expected > 2.26");
SimpleTest.finish();
}
function didSendCancel() {
a.addEventListener("ended", didEnd, false);
a.play();
}
function didSuspend() {
a.removeEventListener("suspend", didSuspend, false);
// Cache must have filled up, or something. Tell the Web server to drop
// our connection.
f.addEventListener("load", didSendCancel, false);
f.src = "cancellable_request.sjs?cancelkey=" + key;
}
if (!a.canPlayType("audio/wave")) {
todo(false, "Test requires support for audio/wave");
} else {
a.addEventListener("suspend", didSuspend, false);
a.src = "cancellable_request.sjs?key=" + key;
SimpleTest.waitForExplicitFinish();
}
</script>
</pre>
</body>
</html>