Reviewed by Adele.
Seeking related fixes, updates match to the latest specification
- rename loopCount of HTMLMediaElement to playCount
- add explicit seeking attribute to HTMLMediaElement to get semantics right
- implement the specification behavior that currentTime must immediately return seeked position in HTMLMediaElement
instead of MoviePrivateQTKit
- fix broken behavior when seeking past end of the media, add tests
- replace Movie didEnd callback with broader timeChanged callback (which gets called in didEnd case too)
- use setDelayCallbacks: in various MoviePrivateQTKit methods to avoid bug prone synchronous callbacks from QT, make
HTMLMediaElement not depend on synchronous callbacks
- do some cleanups and simplifications in MoviePrivateQTKit, get rid of m_rateBeforeSeek and m_blockStateUpdate variables
Tests: http/tests/media/video-seekable-stall.html
media/video-seeking.html
media/video-seek-past-end-paused.html
media/video-seek-past-end-playing.html
* html/HTMLAttributeNames.in:
* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::HTMLMediaElement):
(WebCore::HTMLMediaElement::load):
(WebCore::HTMLMediaElement::movieNetworkStateChanged):
(WebCore::HTMLMediaElement::setReadyState):
(WebCore::HTMLMediaElement::seek):
(WebCore::HTMLMediaElement::seeking):
(WebCore::HTMLMediaElement::currentTime):
(WebCore::HTMLMediaElement::ended):
(WebCore::HTMLMediaElement::play):
(WebCore::HTMLMediaElement::pause):
(WebCore::HTMLMediaElement::playCount):
(WebCore::HTMLMediaElement::setPlayCount):
(WebCore::HTMLMediaElement::checkIfSeekNeeded):
(WebCore::HTMLMediaElement::movieTimeChanged):
(WebCore::HTMLMediaElement::endedPlayback):
(WebCore::HTMLMediaElement::updateMovie):
* html/HTMLMediaElement.h:
* html/HTMLMediaElement.idl:
* platform/graphics/Movie.cpp:
(WebCore::Movie::timeChanged):
* platform/graphics/Movie.h:
(WebCore::MovieClient::movieTimeChanged):
* platform/graphics/mac/MoviePrivateQTKit.h:
* platform/graphics/mac/MoviePrivateQTKit.mm:
(WebCore::MoviePrivate::MoviePrivate):
(WebCore::MoviePrivate::load):
(WebCore::MoviePrivate::play):
(WebCore::MoviePrivate::pause):
(WebCore::MoviePrivate::currentTime):
(WebCore::MoviePrivate::seek):
(WebCore::MoviePrivate::doSeek):
(WebCore::MoviePrivate::cancelSeek):
(WebCore::MoviePrivate::seekTimerFired):
(WebCore::MoviePrivate::startCuePointTimerIfNeeded):
(WebCore::MoviePrivate::paused):
(WebCore::MoviePrivate::updateStates):
(WebCore::MoviePrivate::timeChanged):
(WebCore::MoviePrivate::didEnd):
LayoutTests:
Reviewed by Adele.
Seeking related test updates
- update to match specificiation
- rename loopCount to playCount everywhere
- timeupdate events during seek
- fix that some tests (like audio ones) depended on timing sensitive ordering of play and load events
- add text about what is being tested
Add new tests for seeking past end, seeking past loaded position and 'seeking' DOM attribute.
* http/tests/media/video-seekable-stall-expected.txt: Added.
* http/tests/media/video-seekable-stall.html: Added.
* media/audio-constructor-expected.txt:
* media/audio-constructor-src-expected.txt:
* media/audio-constructor-src.html:
* media/audio-constructor.html:
* media/video-dom-loopcount-expected.txt:
* media/video-dom-loopcount.html:
* media/video-dom-loopend-expected.txt:
* media/video-dom-loopend.html:
* media/video-dom-loopstart-expected.txt:
* media/video-dom-loopstart.html:
* media/video-dom-start-expected.txt:
* media/video-dom-start.html:
* media/video-loopcount-expected.txt:
* media/video-loopcount.html:
* media/video-loopend-expected.txt:
* media/video-loopend.html:
* media/video-loopstart-expected.txt:
* media/video-loopstart.html:
* media/video-seek-past-end-paused-expected.txt: Added.
* media/video-seek-past-end-paused.html: Added.
* media/video-seek-past-end-playing-expected.txt: Added.
* media/video-seek-past-end-playing.html: Added.
* media/video-seeking-expected.txt: Added.
* media/video-seeking.html: Added.
* media/video-test.js:
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@27866
268f45cc-cd09-0410-ab3c-
d52691b4dbfc
+2007-11-16 Antti Koivisto <antti@apple.com>
+
+ Reviewed by Adele.
+
+ Seeking related test updates
+ - update to match specificiation
+ - rename loopCount to playCount everywhere
+ - timeupdate events during seek
+ - fix that some tests (like audio ones) depended on timing sensitive ordering of play and load events
+ - add text about what is being tested
+
+ Add new tests for seeking past end, seeking past loaded position and 'seeking' DOM attribute.
+
+ * http/tests/media/video-seekable-stall-expected.txt: Added.
+ * http/tests/media/video-seekable-stall.html: Added.
+ * media/audio-constructor-expected.txt:
+ * media/audio-constructor-src-expected.txt:
+ * media/audio-constructor-src.html:
+ * media/audio-constructor.html:
+ * media/video-dom-loopcount-expected.txt:
+ * media/video-dom-loopcount.html:
+ * media/video-dom-loopend-expected.txt:
+ * media/video-dom-loopend.html:
+ * media/video-dom-loopstart-expected.txt:
+ * media/video-dom-loopstart.html:
+ * media/video-dom-start-expected.txt:
+ * media/video-dom-start.html:
+ * media/video-loopcount-expected.txt:
+ * media/video-loopcount.html:
+ * media/video-loopend-expected.txt:
+ * media/video-loopend.html:
+ * media/video-loopstart-expected.txt:
+ * media/video-loopstart.html:
+ * media/video-seek-past-end-paused-expected.txt: Added.
+ * media/video-seek-past-end-paused.html: Added.
+ * media/video-seek-past-end-playing-expected.txt: Added.
+ * media/video-seek-past-end-playing.html: Added.
+ * media/video-seeking-expected.txt: Added.
+ * media/video-seeking.html: Added.
+ * media/video-test.js:
+
2007-11-16 Anders Carlsson <andersca@apple.com>
Reviewed by Adam.
--- /dev/null
+Test seekable range when video load is incomplete.
+
+EVENT(canplay)
+TEST(video.seekable) OK
+TEST(video.seekable.length == 1) OK
+TEST(video.seekable.start(0) == 0) OK
+TEST(video.seekable.end(0) > 0) OK
+TEST(video.seekable.end(0) < video.duration - 1) OK
+TEST(video.currentTime = video.duration - 1) THROWS(DOMException.INDEX_SIZE_ERR) OK
+END OF TEST
+
--- /dev/null
+<video></video>
+<p>Test seekable range when video load is incomplete.</p>
+<script src=../../../media/video-test.js></script>
+<script>
+waitForEvent('canplay', function () {
+ test("video.seekable");
+ test("video.seekable.length == 1");
+ test("video.seekable.start(0) == 0");
+ test("video.seekable.end(0) > 0");
+ test("video.seekable.end(0) < video.duration - 1");
+ testException("video.currentTime = video.duration - 1", "DOMException.INDEX_SIZE_ERR");
+ endTest();
+} );
+video.src = "http://127.0.0.1:8000/media/video-load-and-stall.cgi?name=../../../media/content/test.mp4&stallAt=80000";
+video.load();
+</script>
+Test that Audio() object loads the resource after src attribute is set and load() is called.
+
TEST(audio instanceof HTMLAudioElement) OK
-EVENT(load) TEST(relativeURL(audio.currentSrc)=='content/test.wav') OK
-EVENT(play)
+RUN(audio.load())
+EVENT(begin) TEST(relativeURL(audio.currentSrc)=='content/test.wav') OK
+EVENT(load)
END OF TEST
+Test that Audio("url") constructor loads the specified resource.
+
TEST(audio instanceof HTMLAudioElement) OK
-EVENT(load) TEST(relativeURL(audio.currentSrc)=='content/test.wav') OK
-EVENT(play)
+EVENT(begin) TEST(relativeURL(audio.currentSrc)=='content/test.wav') OK
+EVENT(load)
END OF TEST
<body>
+<p>Test that Audio("url") constructor loads the specified resource.</p>
<script src=video-test.js></script>
<script>
var audio = new Audio("content/test.wav");
media = audio;
-
test("audio instanceof HTMLAudioElement");
-waitForEventAndTest("load", "relativeURL(audio.currentSrc)=='content/test.wav'");
-waitForEventAndEnd("play");
-audio.play();
+waitForEventAndTest("begin", "relativeURL(audio.currentSrc)=='content/test.wav'");
+waitForEventAndEnd("load");
</script>
<body>
+<p>Test that Audio() object loads the resource after src attribute is set and load() is called.</p>
<script src=video-test.js></script>
<script>
var audio = new Audio();
media = audio;
-
test("audio instanceof HTMLAudioElement");
-waitForEventAndTest("load", "relativeURL(audio.currentSrc)=='content/test.wav'");
-waitForEventAndEnd("play");
+waitForEventAndTest("begin", "relativeURL(audio.currentSrc)=='content/test.wav'");
+waitForEventAndEnd("load");
audio.src = "content/test.wav";
-audio.play();
+run("audio.load()");
</script>
-TEST(video.loopCount == 2) OK
+Test that playcount DOM attribute causes playback to end after right number of repeats.
+
+TEST(video.playCount == 3) OK
TEST(video.currentLoop == 0) OK
-TEST(video.getAttribute('loopCount') == 2) OK
-EVENT(ended) TEST(video.currentLoop == 1) OK
+TEST(video.getAttribute('playCount') == 3) OK
+RUN(video.play())
+EVENT(ended) TEST(video.currentLoop == 2) OK
END OF TEST
-<video src=content/test.mp4 loopend=0.2s end=0.2s autoplay></video>
+<video src=content/test.mp4 loopend=0.2s end=0.2s></video>
+<p>Test that playcount DOM attribute causes playback to end after right number of repeats.</p>
<script src=video-test.js></script>
<script>
-video.loopCount = 2;
-test("video.loopCount == 2");
+video.playCount = 3;
+test("video.playCount == 3");
test("video.currentLoop == 0");
-test("video.getAttribute('loopCount') == 2");
-waitForEventTestAndEnd('ended', "video.currentLoop == 1");
+test("video.getAttribute('playCount') == 3");
+waitForEventTestAndEnd('ended', "video.currentLoop == 2");
+run("video.play()");
</script>
+Test that video loops when time reaches the value specified by the loopEnd DOM attribute.
+
TEST(video.loopEnd==0.5) OK
TEST(video.getAttribute('loopend') == '0.5s') OK
-EVENT(timeupdate) TEST(video.currentTime.toFixed(1) == 0 && video.currentLoop==1) OK
+RUN(video.play())
+EVENT(timeupdate)
+EVENT(timeupdate)
+TEST(video.currentLoop == 1) OK
+TEST(video.currentTime.toFixed(1) == 0) OK
END OF TEST
-<video src=content/test.mp4 loopcount=2 autoplay></video>
+<video src=content/test.mp4 playcount=2></video>
+<p>Test that video loops when time reaches the value specified by the loopEnd DOM attribute.</p>
<script src=video-test.js></script>
<script>
video.loopEnd=0.5;
test("video.loopEnd==0.5");
test("video.getAttribute('loopend') == '0.5s'");
setTimeout(function () { test("video.currentTime<=0.5"); }, 2000);
-waitForEventTestAndEnd('timeupdate', "video.currentTime.toFixed(1) == 0 && video.currentLoop==1");
+waitForEvent('timeupdate', function () {
+ if (video.currentLoop == 1) {
+ test("video.currentLoop == 1");
+ test("video.currentTime.toFixed(1) == 0");
+ endTest();
+ }
+});
+run("video.play()");
</script>
+Test that playing video rewinds to time specified by the loopStart DOM attribute when it loops.
+
TEST(video.loopStart==1.0) OK
TEST(video.getAttribute('loopstart') == '1s') OK
-EVENT(play)
-EVENT(timeupdate) TEST(video.currentTime.toFixed(1) && video.currentLoop==1) OK
+RUN(video.play())
+EVENT(load)
+EVENT(timeupdate)
+EVENT(timeupdate)
+EVENT(timeupdate)
+TEST(video.currentLoop == 1) OK
+TEST(video.currentTime.toFixed(1) == 1.0) OK
END OF TEST
-<video src=content/test.mp4 loopcount=2></video>
+<video playcount=2></video>
+<p>Test that playing video rewinds to time specified by the loopStart DOM attribute when it loops.</p>
<script src=video-test.js></script>
<script>
video.loopStart = 1.0;
test("video.loopStart==1.0");
test("video.getAttribute('loopstart') == '1s'");
-waitForEvent('play', function () { video.currentTime = 500; });
-waitForEventTestAndEnd('timeupdate', "video.currentTime.toFixed(1) && video.currentLoop==1");
-video.play();
+waitForEvent('load', function () {
+ waitForEvent('timeupdate', function () {
+ failTestIn(1000);
+ if (video.currentLoop == 1) {
+ test("video.currentLoop == 1");
+ test("video.currentTime.toFixed(1) == 1.0");
+ endTest();
+ }
+ });
+ video.currentTime = video.duration - 0.2;
+});
+video.src = "content/test.mp4";
+run("video.play()");
</script>
+Test that when start DOM attribute is set, playback starts from that time.
+
TEST(video.start == 1.0) OK
TEST(video.getAttribute('start') == '1s') OK
+RUN(video.load())
EVENT(canplaythrough) TEST(video.currentTime == 1.0) OK
END OF TEST
-<video src=content/test.mp4></video>
+<video></video>
+<p>Test that when start DOM attribute is set, playback starts from that time.</p>
<script src=video-test.js></script>
<script>
video.start = 1.0;
test("video.start == 1.0");
test("video.getAttribute('start') == '1s'");
waitForEventTestAndEnd('canplaythrough', "video.currentTime == 1.0");
+video.src = "content/test.mp4";
+run("video.load()");
</script>
-TEST(video.loopCount == 2) OK
+Test that playcount attribute causes playback to end after right number of repeats.
+
+TEST(video.playCount == 3) OK
TEST(video.currentLoop == 0) OK
-EVENT(ended) TEST(video.currentLoop == 1) OK
+EVENT(ended) TEST(video.currentLoop == 2) OK
END OF TEST
-<video src=content/test.mp4 loopcount=2 loopend=0.2s end=0.2s></video>
+<video src=content/test.mp4 playcount=3 loopend=0.2s end=0.2s></video>
+<p>Test that playcount attribute causes playback to end after right number of repeats.</p>
<script src=video-test.js></script>
<script>
-test("video.loopCount == 2");
+test("video.playCount == 3");
test("video.currentLoop == 0");
-waitForEventTestAndEnd('ended', "video.currentLoop == 1");
+waitForEventTestAndEnd('ended', "video.currentLoop == 2");
video.play();
</script>
+Test that video loops when time reaches the value specified by the loopEnd attribute.
+
TEST(video.loopEnd==0.5) OK
-EVENT(timeupdate) TEST(video.currentTime.toFixed(1) == 0 && video.currentLoop==1) OK
+RUN(video.play())
+EVENT(timeupdate)
+EVENT(timeupdate)
+TEST(video.currentLoop == 1) OK
+TEST(video.currentTime.toFixed(1) == 0) OK
END OF TEST
-<video src=content/test.mp4 loopcount=2></video>
+<video src=content/test.mp4 playcount=2 loopend=0.5s></video>
+<p>Test that video loops when time reaches the value specified by the loopEnd attribute.</p>
<script src=video-test.js></script>
<script>
-video.loopEnd = 0.5;
test("video.loopEnd==0.5");
setTimeout(function () { test("video.currentTime<=0.5"); }, 2000);
-waitForEventTestAndEnd('timeupdate', "video.currentTime.toFixed(1) == 0 && video.currentLoop==1");
-video.play();
+waitForEvent('timeupdate', function () {
+ if (video.currentLoop == 1) {
+ test("video.currentLoop == 1");
+ test("video.currentTime.toFixed(1) == 0");
+ endTest();
+ }
+});
+run("video.play()");
</script>
+Test that playing video rewinds to time specified by the loopstart attribute when it loops.
+
TEST(video.loopStart==1.0) OK
-EVENT(play)
-EVENT(timeupdate) TEST(video.currentTime.toFixed(1) == 1.0 && video.currentLoop==1) OK
+RUN(video.play())
+EVENT(load)
+EVENT(timeupdate)
+EVENT(timeupdate)
+EVENT(timeupdate)
+TEST(video.currentLoop == 1) OK
+TEST(video.currentTime.toFixed(1) == 1.0) OK
END OF TEST
-<video src=content/test.mp4 loopcount=2 loopstart=1s></video>
+<video playcount=2 loopstart=1s></video>
+<p>Test that playing video rewinds to time specified by the loopstart attribute when it loops.</p>
<script src=video-test.js></script>
<script>
test("video.loopStart==1.0");
-waitForEvent('play', function () { video.currentTime = 500; });
-waitForEventTestAndEnd('timeupdate', "video.currentTime.toFixed(1) == 1.0 && video.currentLoop==1");
-video.play();
+waitForEvent('load', function () {
+ waitForEvent('timeupdate', function () {
+ failTestIn(1000);
+ if (video.currentLoop == 1) {
+ test("video.currentLoop == 1");
+ test("video.currentTime.toFixed(1) == 1.0");
+ endTest();
+ }
+ });
+ video.currentTime = video.duration - 0.2;
+});
+video.src = "content/test.mp4";
+run("video.play()");
</script>
--- /dev/null
+Test that seeking paused video past it's duration time sets currentTime to duration and leaves video paused.
+
+RUN(video.load())
+EVENT(load)
+RUN(video.currentTime = 500)
+EVENT(timeupdate) TEST(video.currentTime == video.duration) OK
+EVENT(timeupdate) TEST(video.currentTime == video.duration) OK
+EVENT(timeupdate) TEST(video.currentTime == video.duration) OK
+EVENT(timeupdate) TEST(video.currentTime == video.duration) OK
+END OF TEST
+
--- /dev/null
+<video></video>
+<p>Test that seeking paused video past it's duration time sets currentTime to duration and leaves video paused.</p>
+<script src=video-test.js></script>
+<script>
+waitForEvent('load', function () {
+ waitForEventAndTest('timeupdate', "video.currentTime == video.duration");
+ run("video.currentTime = 500");
+ endTestLater();
+});
+video.src = "content/test.mp4";
+run("video.load()");
+</script>
--- /dev/null
+Test that seeking video with remaining loops past it's end rewinds to the beginning and continues playback.
+
+RUN(video.play())
+EVENT(load)
+EVENT(timeupdate)
+EVENT(timeupdate)
+EVENT(timeupdate)
+TEST(video.currentLoop == 1) OK
+END OF TEST
+
--- /dev/null
+<video playcount=2></video>
+<p>Test that seeking video with remaining loops past it's end rewinds to the beginning and continues playback.</p>
+<script src=video-test.js></script>
+<script>
+waitForEvent('load', function () {
+ waitForEvent('timeupdate', function () {
+ failTestIn(1000);
+ if (video.currentLoop == 1) {
+ test("video.currentLoop == 1");
+ endTest();
+ }
+ });
+ video.currentTime = 500;
+});
+video.src = "content/test.mp4";
+run("video.play()");
+</script>
--- /dev/null
+Test that seeking attribute is true in timeupdate event after seek and goes back to false when seeking completes
+
+EVENT(load)
+TEST(!video.seeking) OK
+RUN(video.currentTime = 2)
+EVENT(timeupdate)
+TEST(video.seeking) OK
+TEST(video.currentTime == 2) OK
+TEST(!video.seeking) OK
+END OF TEST
+
--- /dev/null
+<video src=content/test.mp4></video>
+<p>Test that seeking attribute is true in timeupdate event after seek and goes back to false when seeking completes </p>
+<script src=video-test.js></script>
+<script>
+waitForEvent('load',
+ function ()
+ {
+ test("!video.seeking");
+ waitForEvent('timeupdate', function () {
+ test("video.seeking");
+ test("video.currentTime == 2");
+ setTimeout(function () {
+ testAndEnd("!video.seeking");
+ }, 200);
+ });
+ run("video.currentTime = 2");
+ }
+);
+</script>
setTimeout(endTest, 250);
}
+function failTestIn(ms)
+{
+ setTimeout(function () {
+ consoleWrite("FAIL: did not end fast enough");
+ endTest();
+ }, ms);
+}
+
function consoleWrite(text)
{
if (testEnded)
+2007-11-16 Antti Koivisto <antti@apple.com>
+
+ Reviewed by Adele.
+
+ Seeking related fixes, updates match to the latest specification
+ - rename loopCount of HTMLMediaElement to playCount
+ - add explicit seeking attribute to HTMLMediaElement to get semantics right
+ - implement the specification behavior that currentTime must immediately return seeked position in HTMLMediaElement
+ instead of MoviePrivateQTKit
+ - fix broken behavior when seeking past end of the media, add tests
+ - replace Movie didEnd callback with broader timeChanged callback (which gets called in didEnd case too)
+ - use setDelayCallbacks: in various MoviePrivateQTKit methods to avoid bug prone synchronous callbacks from QT, make
+ HTMLMediaElement not depend on synchronous callbacks
+ - do some cleanups and simplifications in MoviePrivateQTKit, get rid of m_rateBeforeSeek and m_blockStateUpdate variables
+
+ Tests: http/tests/media/video-seekable-stall.html
+ media/video-seeking.html
+ media/video-seek-past-end-paused.html
+ media/video-seek-past-end-playing.html
+
+ * html/HTMLAttributeNames.in:
+ * html/HTMLMediaElement.cpp:
+ (WebCore::HTMLMediaElement::HTMLMediaElement):
+ (WebCore::HTMLMediaElement::load):
+ (WebCore::HTMLMediaElement::movieNetworkStateChanged):
+ (WebCore::HTMLMediaElement::setReadyState):
+ (WebCore::HTMLMediaElement::seek):
+ (WebCore::HTMLMediaElement::seeking):
+ (WebCore::HTMLMediaElement::currentTime):
+ (WebCore::HTMLMediaElement::ended):
+ (WebCore::HTMLMediaElement::play):
+ (WebCore::HTMLMediaElement::pause):
+ (WebCore::HTMLMediaElement::playCount):
+ (WebCore::HTMLMediaElement::setPlayCount):
+ (WebCore::HTMLMediaElement::checkIfSeekNeeded):
+ (WebCore::HTMLMediaElement::movieTimeChanged):
+ (WebCore::HTMLMediaElement::endedPlayback):
+ (WebCore::HTMLMediaElement::updateMovie):
+ * html/HTMLMediaElement.h:
+ * html/HTMLMediaElement.idl:
+ * platform/graphics/Movie.cpp:
+ (WebCore::Movie::timeChanged):
+ * platform/graphics/Movie.h:
+ (WebCore::MovieClient::movieTimeChanged):
+ * platform/graphics/mac/MoviePrivateQTKit.h:
+ * platform/graphics/mac/MoviePrivateQTKit.mm:
+ (WebCore::MoviePrivate::MoviePrivate):
+ (WebCore::MoviePrivate::load):
+ (WebCore::MoviePrivate::play):
+ (WebCore::MoviePrivate::pause):
+ (WebCore::MoviePrivate::currentTime):
+ (WebCore::MoviePrivate::seek):
+ (WebCore::MoviePrivate::doSeek):
+ (WebCore::MoviePrivate::cancelSeek):
+ (WebCore::MoviePrivate::seekTimerFired):
+ (WebCore::MoviePrivate::startCuePointTimerIfNeeded):
+ (WebCore::MoviePrivate::paused):
+ (WebCore::MoviePrivate::updateStates):
+ (WebCore::MoviePrivate::timeChanged):
+ (WebCore::MoviePrivate::didEnd):
+
2007-11-16 Anders Carlsson <andersca@apple.com>
Reviewed by Adam.
link
longdesc
loop
-loopcount
+playcount
loopend
loopstart
lowsrc
, m_volume(0.5f)
, m_muted(false)
, m_paused(true)
+ , m_seeking(false)
+ , m_currentTimeDuringSeek(0)
, m_previousProgress(0)
, m_previousProgressTime(numeric_limits<double>::max())
, m_sentStalledEvent(false)
m_networkState = EMPTY;
m_readyState = DATA_UNAVAILABLE;
m_paused = true;
+ m_seeking = false;
if (m_movie) {
m_movie->pause();
m_movie->seek(0);
if (state >= Movie::LoadedMetaData && m_networkState < LOADED_METADATA) {
m_movie->seek(effectiveStart());
- m_movie->setEndTime(currentLoop() == loopCount() - 1 ? effectiveEnd() : effectiveLoopEnd());
m_networkState = LOADED_METADATA;
dispatchHTMLEvent(durationchangeEvent, false, true);
bool wasActivelyPlaying = activelyPlaying();
m_readyState = state;
+ if (state >= CAN_PLAY)
+ m_seeking = false;
+
if (networkState() == EMPTY)
return;
dispatchHTMLEvent(playEvent, false, true);
}
}
- updatePlayState();
+ updateMovie();
}
void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
minTime = effectiveLoopStart();
// 3
- float maxTime = currentLoop() == loopCount() - 1 ? effectiveEnd() : effectiveLoopEnd();
+ float maxTime = currentLoop() == playCount() - 1 ? effectiveEnd() : effectiveLoopEnd();
// 4
time = min(time, maxTime);
}
// 7
- if (m_movie) {
- m_movie->seek(time);
- m_movie->setEndTime(maxTime);
- }
-
+ m_currentTimeDuringSeek = time;
+
// 8
- // The seeking DOM attribute is implicitly set to true
+ m_seeking = true;
// 9
dispatchHTMLEvent(timeupdateEvent, false, true);
// 10
// As soon as the user agent has established whether or not the media data for the new playback position is available,
// and, if it is, decoded enough data to play back that position, the seeking DOM attribute must be set to false.
+ if (m_movie) {
+ m_movie->setEndTime(maxTime);
+ m_movie->seek(time);
+ }
}
HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
bool HTMLMediaElement::seeking() const
{
- if (!m_movie)
- return false;
- RefPtr<TimeRanges> seekableRanges = seekable();
- return m_movie->seeking() && seekableRanges->contain(currentTime());
+ return m_seeking;
}
// playback state
float HTMLMediaElement::currentTime() const
{
- return m_movie ? m_movie->currentTime() : 0;
+ if (!m_movie)
+ return 0;
+ if (m_seeking)
+ return m_currentTimeDuringSeek;
+ return m_movie->currentTime();
}
void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec)
bool HTMLMediaElement::ended()
{
- return networkState() >= LOADED_METADATA && currentTime() >= effectiveEnd() && currentLoop() == loopCount() - 1;
+ return endedPlayback();
}
bool HTMLMediaElement::autoplay() const
if (m_paused) {
m_paused = false;
- updatePlayState();
dispatchEventAsync(playEvent);
}
m_autoplaying = false;
+
+ updateMovie();
}
void HTMLMediaElement::pause(ExceptionCode& ec)
if (!m_paused) {
m_paused = true;
- updatePlayState();
dispatchEventAsync(timeupdateEvent);
dispatchEventAsync(pauseEvent);
}
m_autoplaying = false;
+
+ updateMovie();
}
-unsigned HTMLMediaElement::loopCount() const
+unsigned HTMLMediaElement::playCount() const
{
- String val = getAttribute(loopcountAttr);
+ String val = getAttribute(playcountAttr);
int count = val.toInt();
return max(count, 1);
}
-void HTMLMediaElement::setLoopCount(unsigned count, ExceptionCode& ec)
+void HTMLMediaElement::setPlayCount(unsigned count, ExceptionCode& ec)
{
if (!count) {
ec = INDEX_SIZE_ERR;
return;
}
- setAttribute(loopcountAttr, String::number(count));
+ setAttribute(playcountAttr, String::number(count));
checkIfSeekNeeded();
}
{
// 3.14.9.5. Offsets into the media resource
// 1
- if (loopCount() - 1 < m_currentLoop)
- m_currentLoop = loopCount() - 1;
+ if (playCount() <= m_currentLoop)
+ m_currentLoop = playCount() - 1;
// 2
if (networkState() <= LOADING)
seek(effectiveLoopStart(), ec);
// 5
- if (m_currentLoop < loopCount() - 1 && time > effectiveLoopEnd()) {
+ if (m_currentLoop < playCount() - 1 && time > effectiveLoopEnd()) {
seek(effectiveLoopStart(), ec);
m_currentLoop++;
}
// 6
- if (m_currentLoop == loopCount() - 1 && time > effectiveEnd())
+ if (m_currentLoop == playCount() - 1 && time > effectiveEnd())
seek(effectiveEnd(), ec);
- updatePlayState();
+ updateMovie();
}
void HTMLMediaElement::movieVolumeChanged(Movie*)
}
}
-void HTMLMediaElement::movieDidEnd(Movie*)
+void HTMLMediaElement::movieTimeChanged(Movie*)
{
- if (m_currentLoop < loopCount() - 1 && currentTime() >= effectiveLoopEnd()) {
- m_movie->seek(effectiveLoopStart());
+ if (readyState() >= CAN_PLAY)
+ m_seeking = false;
+
+ if (m_currentLoop < playCount() - 1 && currentTime() >= effectiveLoopEnd()) {
+ ExceptionCode ec;
+ seek(effectiveLoopStart(), ec);
m_currentLoop++;
- m_movie->setEndTime(m_currentLoop == loopCount() - 1 ? effectiveEnd() : effectiveLoopEnd());
- updatePlayState();
dispatchHTMLEvent(timeupdateEvent, false, true);
}
- if (m_currentLoop == loopCount() - 1 && currentTime() >= effectiveEnd()) {
+ if (m_currentLoop == playCount() - 1 && currentTime() >= effectiveEnd()) {
dispatchHTMLEvent(timeupdateEvent, false, true);
dispatchHTMLEvent(endedEvent, false, true);
}
+
+ updateMovie();
}
void HTMLMediaElement::movieCuePointReached(Movie*, float cueTime)
bool HTMLMediaElement::endedPlayback() const
{
- return networkState() >= LOADED_METADATA && currentTime() >= effectiveEnd() && currentLoop() == loopCount() - 1;
+ return networkState() >= LOADED_METADATA && currentTime() >= effectiveEnd() && currentLoop() == playCount() - 1;
}
-void HTMLMediaElement::updatePlayState()
+void HTMLMediaElement::updateMovie()
{
if (!m_movie)
return;
- bool shouldBePlaying = activelyPlaying();
+
+ m_movie->setEndTime(currentLoop() == playCount() - 1 ? effectiveEnd() : effectiveLoopEnd());
+
+ bool shouldBePlaying = activelyPlaying() && currentTime() < effectiveEnd();
if (shouldBePlaying && m_movie->paused())
m_movie->play();
else if (!shouldBePlaying && !m_movie->paused())
void setLoopStart(float time);
float loopEnd() const;
void setLoopEnd(float time);
- unsigned loopCount() const;
- void setLoopCount(unsigned, ExceptionCode&);
+ unsigned playCount() const;
+ void setPlayCount(unsigned, ExceptionCode&);
unsigned currentLoop() const;
void setCurrentLoop(unsigned);
private: // MovieObserver
virtual void movieNetworkStateChanged(Movie*);
virtual void movieReadyStateChanged(Movie*);
+ virtual void movieTimeChanged(Movie*);
virtual void movieVolumeChanged(Movie*);
- virtual void movieDidEnd(Movie*);
virtual void movieCuePointReached(Movie*, float cueTime);
private:
void checkIfSeekNeeded();
String pickMedia();
- void updatePlayState();
+ void updateMovie();
float effectiveStart() const;
float effectiveEnd() const;
float effectiveLoopStart() const;
bool m_paused;
bool m_seeking;
+ float m_currentTimeDuringSeek;
+
unsigned m_previousProgress;
double m_previousProgressTime;
bool m_sentStalledEvent;
attribute float end;
attribute float loopStart;
attribute float loopEnd;
- attribute unsigned long loopCount
+ attribute unsigned long playCount
setter raises (DOMException);
attribute unsigned long currentLoop;
m_movieClient->movieVolumeChanged(this);
}
-void Movie::didEnd()
+void Movie::timeChanged()
{
if (m_movieClient)
- m_movieClient->movieDidEnd(this);
+ m_movieClient->movieTimeChanged(this);
}
void Movie::cuePointReached(float cueTime)
virtual void movieNetworkStateChanged(Movie*) { }
virtual void movieReadyStateChanged(Movie*) { }
virtual void movieVolumeChanged(Movie*) { }
- virtual void movieDidEnd(Movie*) { }
+ virtual void movieTimeChanged(Movie*) { }
virtual void movieCuePointReached(Movie*, float cueTime) { }
};
void networkStateChanged();
void readyStateChanged();
void volumeChanged();
- void didEnd();
+ void timeChanged();
void cuePointReached(float cueTime);
private:
static void getSupportedTypes(HashSet<String>& types);
private:
-
void updateStates();
+ void doSeek();
void cancelSeek();
void seekTimerFired(Timer<MoviePrivate>*);
void cuePointTimerFired(Timer<MoviePrivate>*);
Timer<MoviePrivate> m_seekTimer;
Timer<MoviePrivate> m_cuePointTimer;
float m_previousTimeCueTimerFired;
- float m_rateBeforeSeek;
Movie::NetworkState m_networkState;
Movie::ReadyState m_readyState;
bool m_startedPlaying;
- bool m_blockStateUpdate;
bool m_isStreaming;
};
, m_seekTimer(this, &MoviePrivate::seekTimerFired)
, m_cuePointTimer(this, &MoviePrivate::cuePointTimerFired)
, m_previousTimeCueTimerFired(0)
- , m_rateBeforeSeek(0)
, m_networkState(Movie::Empty)
, m_readyState(Movie::DataUnavailable)
, m_startedPlaying(false)
- , m_blockStateUpdate(false)
, m_isStreaming(false)
{
}
}
cancelSeek();
m_cuePointTimer.stop();
+
+ [m_objcObserver.get() setDelayCallbacks:YES];
+
createQTMovie(url);
if (m_movie->visible())
createQTMovieView();
-
- updateStates();
+
+ [m_objcObserver.get() loadStateChanged:nil];
+ [m_objcObserver.get() setDelayCallbacks:NO];
}
void MoviePrivate::play()
{
- cancelSeek();
if (!m_qtMovie)
return;
m_startedPlaying = true;
+ [m_objcObserver.get() setDelayCallbacks:YES];
[m_qtMovie.get() setRate: m_movie->rate()];
+ [m_objcObserver.get() setDelayCallbacks:NO];
startCuePointTimerIfNeeded();
}
void MoviePrivate::pause()
{
- cancelSeek();
if (!m_qtMovie)
return;
m_startedPlaying = false;
+ [m_objcObserver.get() setDelayCallbacks:YES];
[m_qtMovie.get() stop];
+ [m_objcObserver.get() setDelayCallbacks:NO];
m_cuePointTimer.stop();
}
{
if (!m_qtMovie)
return 0;
- if (seeking())
- return m_seekTo;
QTTime time = [m_qtMovie.get() currentTime];
float current = (float)time.timeValue / time.timeScale;
current = std::min(current, m_endTime);
if (time > duration())
time = duration();
- if (maxTimeLoaded() < time) {
- m_seekTo = time;
- m_seekTimer.startRepeating(0.5f);
- m_rateBeforeSeek = [m_qtMovie.get() rate];
- [m_qtMovie.get() setRate:0.0f];
- updateStates();
- } else {
- QTTime qttime = createQTTime(time);
- // setCurrentTime generates several event callbacks, update afterwards
- m_blockStateUpdate = true;
- [m_qtMovie.get() setCurrentTime: qttime];
- m_blockStateUpdate = false;
+ m_seekTo = time;
+ if (maxTimeLoaded() >= m_seekTo)
+ doSeek();
+ else
+ m_seekTimer.start(0, 0.5f);
+}
+
+void MoviePrivate::doSeek()
+{
+ QTTime qttime = createQTTime(m_seekTo);
+ // setCurrentTime generates several event callbacks, update afterwards
+ [m_objcObserver.get() setDelayCallbacks:YES];
+ float oldRate = [m_qtMovie.get() rate];
+ [m_qtMovie.get() setRate:0];
+ [m_qtMovie.get() setCurrentTime: qttime];
+ float timeAfterSeek = currentTime();
+ // restore playback only if not at end, othewise QTMovie will loop
+ if (timeAfterSeek < duration() && timeAfterSeek < m_endTime)
+ [m_qtMovie.get() setRate:oldRate];
+ cancelSeek();
+ [m_objcObserver.get() setDelayCallbacks:NO];
+}
+
+void MoviePrivate::cancelSeek()
+{
+ m_seekTo = -1;
+ m_seekTimer.stop();
+}
+
+void MoviePrivate::seekTimerFired(Timer<MoviePrivate>*)
+{
+ if (!m_qtMovie || !seeking() || currentTime() == m_seekTo) {
+ cancelSeek();
updateStates();
+ m_movie->timeChanged();
+ return;
+ }
+
+ if (maxTimeLoaded() >= m_seekTo)
+ doSeek();
+ else {
+ Movie::NetworkState state = networkState();
+ if (state == Movie::Empty || state == Movie::Loaded) {
+ cancelSeek();
+ updateStates();
+ m_movie->timeChanged();
+ }
}
}
void MoviePrivate::startCuePointTimerIfNeeded()
{
-
if ((m_endTime < duration() || !m_movie->m_cuePoints.isEmpty())
&& m_startedPlaying && !m_cuePointTimer.isActive()) {
m_previousTimeCueTimerFired = currentTime();
}
}
-void MoviePrivate::cancelSeek()
-{
- if (m_seekTo > -1) {
- m_seekTo = -1;
- if (m_qtMovie)
- [m_qtMovie.get() setRate:m_rateBeforeSeek];
- }
- m_rateBeforeSeek = 0.0f;
- m_seekTimer.stop();
-}
-
-void MoviePrivate::seekTimerFired(Timer<MoviePrivate>*)
-{
- if (!m_qtMovie) {
- cancelSeek();
- return;
- }
- if (!seeking()) {
- updateStates();
- return;
- }
-
- if (maxTimeLoaded() > m_seekTo) {
- QTTime qttime = createQTTime(m_seekTo);
- // setCurrentTime generates several event callbacks, update afterwards
- m_blockStateUpdate = true;
- [m_qtMovie.get() setCurrentTime: qttime];
- m_blockStateUpdate = false;
- cancelSeek();
- updateStates();
- }
-
- Movie::NetworkState state = networkState();
- if (state == Movie::Empty || state == Movie::Loaded) {
- cancelSeek();
- updateStates();
- }
-}
-
void MoviePrivate::cuePointTimerFired(Timer<MoviePrivate>*)
{
float time = currentTime();
{
if (!m_qtMovie)
return true;
- return [m_qtMovie.get() rate] == 0.0f && (!seeking() || m_rateBeforeSeek == 0.0f);
+ return [m_qtMovie.get() rate] == 0.0f;
}
bool MoviePrivate::seeking() const
void MoviePrivate::updateStates()
{
- if (m_blockStateUpdate)
- return;
-
Movie::NetworkState oldNetworkState = m_networkState;
Movie::ReadyState oldReadyState = m_readyState;
// 100000 is kMovieLoadStateComplete
if (m_networkState < Movie::Loaded)
m_networkState = Movie::Loaded;
- m_readyState = Movie::CanPlayThrough;
+ m_readyState = Movie::CanPlayThrough;
} else if (loadState >= 20000) {
// 20000 is kMovieLoadStatePlaythroughOK
if (m_networkState < Movie::LoadedFirstFrame && !seeking())
m_networkState = Movie::LoadFailed;
m_readyState = Movie::DataUnavailable;
}
-
+
if (seeking())
m_readyState = Movie::DataUnavailable;
{
m_previousTimeCueTimerFired = -1;
updateStates();
+ m_movie->timeChanged();
}
void MoviePrivate::volumeChanged()
{
m_cuePointTimer.stop();
m_startedPlaying = false;
- m_movie->didEnd();
+ updateStates();
+ m_movie->timeChanged();
}
void MoviePrivate::setRect(const IntRect& r)