Bug 469255 - Seeking while seek in progress not handled correctly by Wave decoder; r+sr=roc

This commit is contained in:
Matthew Gregan 2008-12-14 15:54:45 +01:00
Родитель f2e176127e
Коммит 95f82c5d78
16 изменённых файлов: 594 добавлений и 12 удалений

Просмотреть файл

@ -252,6 +252,13 @@ private:
// underlying channel type.
nsAutoPtr<nsMediaStream> mStream;
// The media time of the last requested seek. This has not been validated
// against the current media, so may be out of bounds. Set when
// Seek(float) is called, and passed to the state machine when the
// SeekStarted event fires to tell it to update its time offset. The
// state machine will validate the offset against the current media.
float mTimeOffset;
// Copy of the current time and duration when the state machine was
// disposed. Used to respond to time and duration queries with sensible
// values after playback has ended.

Просмотреть файл

@ -151,6 +151,11 @@ public:
// Main state machine loop. Runs forever, until shutdown state is reached.
NS_IMETHOD Run();
// Called by the decoder when the SeekStarted event runs. This ensures
// the current time offset of the state machine is updated to the new seek
// position at the appropriate time.
void UpdateTimeOffset(float aTime);
private:
// Change the current state and wake the playback thread if it is waiting
// on mMonitor. Used by public member functions called from both threads,
@ -281,7 +286,7 @@ private:
float mInitialVolume;
// Time position (in seconds) to offset current time from audio stream.
// Set by calling Seek(float) when seeking, and when the stream is closed
// Set when the seek started event runs and when the stream is closed
// during shutdown.
float mTimeOffset;
@ -290,6 +295,9 @@ private:
// Available() reports).
PRPackedBool mExpectMoreData;
// Time position (in seconds) to seek to. Set by Seek(float).
float mSeekTime;
// True once metadata has been parsed and validated. Users of mSampleRate,
// mChannels, mSampleSize, mSampleFormat, mWaveLength, mWavePCMOffset must
// check this flag before assuming the values are valid.
@ -316,6 +324,7 @@ nsWaveStateMachine::nsWaveStateMachine(nsWaveDecoder* aDecoder, nsMediaStream* a
mInitialVolume(aInitialVolume),
mTimeOffset(0.0),
mExpectMoreData(PR_TRUE),
mSeekTime(0.0),
mMetadataValid(PR_FALSE)
{
mMonitor = nsAutoMonitor::NewMonitor("nsWaveStateMachine");
@ -379,9 +388,9 @@ nsWaveStateMachine::Seek(float aTime)
{
nsAutoMonitor monitor(mMonitor);
mNextState = mState;
mTimeOffset = NS_MIN(aTime, BytesToTime(mWaveLength));
if (mTimeOffset < 0.0) {
mTimeOffset = 0.0;
mSeekTime = NS_MIN(aTime, BytesToTime(mWaveLength));
if (mSeekTime < 0.0) {
mSeekTime = 0.0;
}
ChangeState(STATE_SEEKING);
}
@ -391,7 +400,15 @@ nsWaveStateMachine::GetDuration()
{
nsAutoMonitor monitor(mMonitor);
if (mMetadataValid) {
return BytesToTime(mWaveLength);
PRUint32 length = mWaveLength;
PRInt64 contentLength = mDecoder->GetTotalBytes();
// If the decoder has a valid content length, and it's shorter than the
// expected length of the PCM data, calculate the playback duration from
// the content length rather than the expected PCM data length.
if (contentLength >= 0 && contentLength - mWavePCMOffset < length) {
length = contentLength - mWavePCMOffset;
}
return BytesToTime(length);
}
return std::numeric_limits<float>::quiet_NaN();
}
@ -562,6 +579,9 @@ nsWaveStateMachine::Run()
{
CloseAudioStream();
mSeekTime = NS_MIN(mSeekTime, GetDuration());
float seekTime = mSeekTime;
monitor.Exit();
nsCOMPtr<nsIRunnable> startEvent =
NS_NEW_RUNNABLE_METHOD(nsWaveDecoder, mDecoder, SeekingStarted);
@ -572,8 +592,12 @@ nsWaveStateMachine::Run()
break;
}
PRInt64 position = RoundDownToSample(TimeToBytes(mTimeOffset)) + mWavePCMOffset;
NS_ABORT_IF_FALSE(position >= 0 && position <= mWaveLength + mWavePCMOffset, "Invalid seek position");
// Calculate relative offset within PCM data.
PRInt64 position = RoundDownToSample(TimeToBytes(seekTime));
NS_ABORT_IF_FALSE(position >= 0 && position <= mWaveLength, "Invalid seek position");
// Convert to absolute offset within stream.
position += mWavePCMOffset;
monitor.Exit();
nsresult rv = mStream->Seek(nsISeekableStream::NS_SEEK_SET, position);
@ -592,7 +616,7 @@ nsWaveStateMachine::Run()
NS_DispatchToMainThread(stopEvent, NS_DISPATCH_SYNC);
monitor.Enter();
if (mState != STATE_SHUTDOWN) {
if (mState == STATE_SEEKING && mSeekTime == seekTime) {
ChangeState(mNextState);
}
}
@ -649,6 +673,16 @@ nsWaveStateMachine::Run()
return NS_OK;
}
void
nsWaveStateMachine::UpdateTimeOffset(float aTime)
{
nsAutoMonitor monitor(mMonitor);
mTimeOffset = NS_MIN(aTime, GetDuration());
if (mTimeOffset < 0.0) {
mTimeOffset = 0.0;
}
}
void
nsWaveStateMachine::ChangeState(State aState)
{
@ -915,6 +949,7 @@ nsWaveDecoder::nsWaveDecoder()
: mBytesDownloaded(0),
mInitialVolume(1.0),
mStream(nsnull),
mTimeOffset(0.0),
mEndedCurrentTime(0.0),
mEndedDuration(std::numeric_limits<float>::quiet_NaN()),
mNotifyOnShutdown(PR_FALSE),
@ -955,8 +990,10 @@ nsWaveDecoder::GetCurrentTime()
nsresult
nsWaveDecoder::Seek(float aTime)
{
mTimeOffset = aTime;
if (mPlaybackStateMachine) {
mPlaybackStateMachine->Seek(aTime);
mPlaybackStateMachine->Seek(mTimeOffset);
return NS_OK;
}
return NS_ERROR_FAILURE;
@ -1255,6 +1292,10 @@ nsWaveDecoder::SeekingStarted()
return;
}
if (mPlaybackStateMachine) {
mPlaybackStateMachine->UpdateTimeOffset(mTimeOffset);
}
if (mElement) {
mElement->SeekStarted();
}

Просмотреть файл

@ -71,12 +71,23 @@ _TEST_FILES = test_autoplay.html \
test_volume.html \
test_wav_8bit.html \
test_wav_ended1.html \
test_wav_seek3.html \
test_wav_seek4.html \
test_wav_seek5.html \
test_wav_seek6.html \
test_wav_seek7.html \
test_wav_seek8.html \
test_wav_seek_past_end.html \
test_wav_seek_then_play.html \
test_wav_trailing.html \
test_wav_trunc.html \
test_wav_trunc_seek.html \
320x240.ogg \
bug461281.ogg \
seek.ogg \
r11025_s16_c1.wav \
r11025_s16_c1_trailing.wav \
r11025_s16_c1_trunc.wav \
r11025_u8_c1.wav \
# test_bug448534.html \
$(NULL)

Двоичные данные
content/media/video/test/r11025_s16_c1_trailing.wav Normal file

Двоичный файл не отображается.

Двоичные данные
content/media/video/test/r11025_s16_c1_trunc.wav Normal file

Двоичный файл не отображается.

Просмотреть файл

@ -22,9 +22,10 @@ var seekFlagEnd = false;
var readonly = true;
var completed = false;
ok(!document.getElementById('v').seeking, "seeking should default to false");
var v = document.getElementById('v');
ok(!v.seeking, "seeking should default to false");
try {
v1.seeking = 1;
v.seeking = 1;
readonly = false;
}
catch(e) {

Просмотреть файл

@ -45,7 +45,7 @@ function startTest() {
completed = true;
ok(thrown1, "Setting currentTime to invalid value of NaN");
ok(thrown2, "Setting currentTime to invalid value of -1");
ok(thrown2, "Setting currentTime to invalid value of a function");
ok(thrown3, "Setting currentTime to invalid value of a function");
SimpleTest.finish();
return false;
}

Просмотреть файл

@ -0,0 +1,68 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: seek test 3</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 id='v'
onloadedmetadata='return startTest();'
onended='return playbackEnded();'
onseeking='return seekStarted();'
onseeked='return seekEnded();'>
<source type='audio/x-wav' src='r11025_s16_c1.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test seeking works if current time is set before audio is
// playing.
var startPassed = false;
var endPassed = false;
var completed = false;
function startTest() {
if (completed)
return false;
var v = document.getElementById('v');
v.currentTime=0.5;
v.play();
return false;
}
function seekStarted() {
if (completed)
return false;
startPassed = true;
return false;
}
function seekEnded() {
if (completed)
return false;
var t = document.getElementById('v').currentTime;
ok(t >= 0.4 && t <= 0.6, "Video currentTime should be around 0.5: " + t);
endPassed = true;
return false;
}
function playbackEnded() {
if (completed)
return false
completed = true;
ok(startPassed, "send seeking event");
ok(endPassed, "send seeked event");
SimpleTest.finish();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,54 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: seek test 4</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 id='v'
onloadedmetadata='return startTest();'
onseeking='return seekStarted();'
onseeked='return seekEnded();'>
<source type='audio/x-wav' src='r11025_s16_c1.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test seeking works if current time is set but audio is not played.
var startPassed = false;
var completed = false;
function startTest() {
if (completed)
return false;
var v = document.getElementById('v');
v.currentTime=0.5;
return false;
}
function seekStarted() {
if (completed)
return false;
startPassed = true;
return false;
}
function seekEnded() {
if (completed)
return false;
var t = document.getElementById('v').currentTime;
ok(t >= 0.4 && t<= 0.6, "Video currentTime should be around 0.5: " + t);
completed = true;
SimpleTest.finish();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,71 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: seek test 5</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 id='v'
onloadedmetadata='return startTest();'
onseeking='return seekStarted();'
onseeked='return seekEnded();'>
<source type='audio/x-wav' src='r11025_s16_c1.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test for a seek, followed by another seek before the first is complete.
var startPassed = false;
var seek1Passed = false;
var seek2Passed = false;
var seekCount = 0;
var completed = false;
function startTest() {
if (completed)
return false;
var v = document.getElementById('v');
v.currentTime=0.8;
return false;
}
function seekStarted() {
if (completed)
return false;
startPassed = true;
var v = document.getElementById('v');
v.currentTime=0.5;
return false;
}
function seekEnded() {
if (completed)
return false;
var v = document.getElementById('v');
seekCount++;
if(seekCount == 1) {
if (v.currentTime >= 0.7 && v.currentTime <= 0.9)
seek1Passed = true;
}
if(seekCount == 2) {
if (v.currentTime >= 0.4 && v.currentTime <= 0.6)
seek2Passed = true;
ok(seek1Passed, "First seek");
ok(seek2Passed, "Second seek");
completed = true;
SimpleTest.finish();
}
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,63 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: seek test 6</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 id='v'
onloadedmetadata='return startTest();'
onended='return playbackEnded();'
onseeking='return seekStarted();'
onseeked='return seekEnded();'>
<source type='audio/x-wav' src='r11025_s16_c1.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test for a seek, followed by a play before the seek completes, ensure we play at the end of the seek.
var startPassed = false;
var completed = false;
var seekCount = 0;
function startTest() {
if (completed)
return false;
var v = document.getElementById('v');
v.currentTime=0.5;
return false;
}
function seekStarted() {
if (completed)
return false;
startPassed = true;
var v = document.getElementById('v');
v.play();
return false;
}
function seekEnded() {
if (completed)
return false;
var v = document.getElementById('v');
ok(v.currentTime>=0.4 && v.currentTime<=0.6, "First seek");
return false;
}
function playbackEnded() {
if (completed)
return false;
ok(true, "Playback ended");
completed = true;
SimpleTest.finish();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,60 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: seek test 7</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 id='v'
onloadedmetadata='return startTest();'
onseeked='return seekEnded();'>
<source type='audio/x-wav' src='r11025_s16_c1.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript;version=1.7">
// Test for bug identified by Chris Pearce in comment 40 on
// bug 449159.
var v = document.getElementById('v');
var seekCount = 0;
var completed = false;
var interval;
function startTest() {
if (completed)
return false;
var seek = function () {
var i = 0;
for (;;) {
i += 0.1;
if (i > 1)
i = 0;
yield i;
}
}();
interval = setInterval(function() { v.currentTime=seek.next(); }, 10);
return false;
}
function seekEnded() {
if (completed)
return false;
seekCount++;
ok(true, "Seek " + seekCount);
if (seekCount == 3) {
clearInterval(interval);
completed = true;
SimpleTest.finish();
}
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,58 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: seek test 8</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 id='v'
onloadedmetadata='return startTest();'>
<source type='audio/x-wav' src='r11025_s16_c1.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript">
// If a NaN is passed to currentTime, make sure this is caught
// otherwise an infinite loop in the Wave backend occurs.
var v = document.getElementById('v');
var completed = false;
var thrown1 = false;
var thrown2 = false;
var thrown3 = false;
function startTest() {
if (completed)
return false;
try {
v.currentTime = NaN;
} catch(e) {
thrown1 = true;
}
try {
v.currentTime = -1;
} catch(e) {
thrown2 = true;
}
try {
v.currentTime = Math.random;
} catch(e) {
thrown3 = true;
}
completed = true;
ok(thrown1, "Setting currentTime to invalid value of NaN");
ok(thrown2, "Setting currentTime to invalid value of -1");
ok(thrown3, "Setting currentTime to invalid value of a function");
SimpleTest.finish();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,46 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: playback ends when file has trailing non-PCM data</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 id='v'
onloadedmetadata='return startTest();'
onended='return playbackEnded();'>
<source type='audio/x-wav' src='r11025_s16_c1_trailing.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test if the ended event works correctly.
var v = document.getElementById('v');
var endPassed = false;
var completed = false;
function startTest() {
if (completed)
return false;
v.play();
return false;
}
function playbackEnded() {
if (completed)
return false
completed = true;
ok(v.currentTime >= 0.9 && v.currentTime <= 1.1,
"Checking currentTime at end: " + v.currentTime);
ok(v.ended, "Checking playback has ended");
SimpleTest.finish();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,48 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: playback ends when file is truncated</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 id='v'
onloadedmetadata='return startTest();'
onended='return playbackEnded();'>
<source type='audio/x-wav' src='r11025_s16_c1_trunc.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test if the ended event works correctly.
var v = document.getElementById('v');
var endPassed = false;
var completed = false;
function startTest() {
if (completed)
return false;
ok(v.duration > 0.3 && v.duration < 0.5, "Duration should be around 0.4: " + v.duration);
v.play();
return false;
}
function playbackEnded() {
if (completed)
return false
completed = true;
ok(v.currentTime >= 0.3 && v.currentTime <= 0.5,
"Checking currentTime at end: " + v.currentTime);
ok(v.ended, "Checking playback has ended");
SimpleTest.finish();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>

Просмотреть файл

@ -0,0 +1,54 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Wave Media test: seeking in truncated file</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 id='v'
onloadedmetadata='return startTest();'
onseeked='return endSeek();'
onended='return endTest();'>
<source type='audio/x-wav' src='r11025_s16_c1_trunc.wav'>
</audio>
<pre id="test">
<script class="testbody" type="text/javascript">
// Test if the ended event works correctly.
var v = document.getElementById('v');
var endPassed = false;
var completed = false;
function startTest() {
if (completed)
return false;
ok(v.duration > 0.3 && v.duration < 0.5, "Duration should be around 0.4: " + v.duration);
v.currentTime = 1.0;
return false;
}
function endTest() {
ok(v.ended, "Checking ended set after seeking to EOF and playing");
SimpleTest.finish();
return false;
}
function endSeek() {
if (completed)
return false
completed = true;
ok(v.currentTime >= 0.3 && v.currentTime <= 0.5,
"Checking currentTime at end: " + v.currentTime);
v.play();
return false;
}
SimpleTest.waitForExplicitFinish();
</script>
</pre>
</body>
</html>