зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
1689490893
Коммит
d5f8eeb841
|
@ -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;
|
||||
|
|
|
@ -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 \
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
}
|
|
@ -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>
|
Загрузка…
Ссылка в новой задаче