WebCore:
authorantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 17 Nov 2007 02:10:16 +0000 (02:10 +0000)
committerantti@apple.com <antti@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 17 Nov 2007 02:10:16 +0000 (02:10 +0000)
        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

37 files changed:
LayoutTests/ChangeLog
LayoutTests/http/tests/media/video-seekable-stall-expected.txt [new file with mode: 0644]
LayoutTests/http/tests/media/video-seekable-stall.html [new file with mode: 0644]
LayoutTests/media/audio-constructor-expected.txt
LayoutTests/media/audio-constructor-src-expected.txt
LayoutTests/media/audio-constructor-src.html
LayoutTests/media/audio-constructor.html
LayoutTests/media/video-dom-loopcount-expected.txt
LayoutTests/media/video-dom-loopcount.html
LayoutTests/media/video-dom-loopend-expected.txt
LayoutTests/media/video-dom-loopend.html
LayoutTests/media/video-dom-loopstart-expected.txt
LayoutTests/media/video-dom-loopstart.html
LayoutTests/media/video-dom-start-expected.txt
LayoutTests/media/video-dom-start.html
LayoutTests/media/video-loopcount-expected.txt
LayoutTests/media/video-loopcount.html
LayoutTests/media/video-loopend-expected.txt
LayoutTests/media/video-loopend.html
LayoutTests/media/video-loopstart-expected.txt
LayoutTests/media/video-loopstart.html
LayoutTests/media/video-seek-past-end-paused-expected.txt [new file with mode: 0644]
LayoutTests/media/video-seek-past-end-paused.html [new file with mode: 0644]
LayoutTests/media/video-seek-past-end-playing-expected.txt [new file with mode: 0644]
LayoutTests/media/video-seek-past-end-playing.html [new file with mode: 0644]
LayoutTests/media/video-seeking-expected.txt [new file with mode: 0644]
LayoutTests/media/video-seeking.html [new file with mode: 0644]
LayoutTests/media/video-test.js
WebCore/ChangeLog
WebCore/html/HTMLAttributeNames.in
WebCore/html/HTMLMediaElement.cpp
WebCore/html/HTMLMediaElement.h
WebCore/html/HTMLMediaElement.idl
WebCore/platform/graphics/Movie.cpp
WebCore/platform/graphics/Movie.h
WebCore/platform/graphics/mac/MoviePrivateQTKit.h
WebCore/platform/graphics/mac/MoviePrivateQTKit.mm

index 592eb7f..2e3486a 100644 (file)
@@ -1,3 +1,44 @@
+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.
diff --git a/LayoutTests/http/tests/media/video-seekable-stall-expected.txt b/LayoutTests/http/tests/media/video-seekable-stall-expected.txt
new file mode 100644 (file)
index 0000000..c130f0e
--- /dev/null
@@ -0,0 +1,11 @@
+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
+
diff --git a/LayoutTests/http/tests/media/video-seekable-stall.html b/LayoutTests/http/tests/media/video-seekable-stall.html
new file mode 100644 (file)
index 0000000..8ca0362
--- /dev/null
@@ -0,0 +1,16 @@
+<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>
index 8243223..58f24ac 100644 (file)
@@ -1,5 +1,8 @@
+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
 
index 8243223..366aa6c 100644 (file)
@@ -1,5 +1,7 @@
+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
 
index 8cb1256..a86b772 100644 (file)
@@ -1,11 +1,10 @@
 <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>
index 0ea531e..f4013a6 100644 (file)
@@ -1,12 +1,12 @@
 <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>
index 0473d1a..efa7ed9 100644 (file)
@@ -1,6 +1,9 @@
-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
 
index 41f9d14..397aab2 100644 (file)
@@ -1,9 +1,11 @@
-<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>
index 4a100ec..5de01c5 100644 (file)
@@ -1,5 +1,11 @@
+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
 
index 893c326..d64a01a 100644 (file)
@@ -1,9 +1,17 @@
-<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>
index ff54475..f78354a 100644 (file)
@@ -1,6 +1,13 @@
+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
 
index 5cd54e1..44265cc 100644 (file)
@@ -1,10 +1,21 @@
-<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>
index d850920..c6ab6b5 100644 (file)
@@ -1,5 +1,8 @@
+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
 
index e39129c..ffa390b 100644 (file)
@@ -1,8 +1,11 @@
-<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>
index 83c346a..2b0e657 100644 (file)
@@ -1,5 +1,7 @@
-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
 
index aedc950..c184851 100644 (file)
@@ -1,8 +1,9 @@
-<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>
index 008d202..48437f1 100644 (file)
@@ -1,4 +1,10 @@
+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
 
index 00cfeb1..845372f 100644 (file)
@@ -1,9 +1,15 @@
-<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>
index 35cc641..fd85493 100644 (file)
@@ -1,5 +1,12 @@
+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
 
index 7134f10..c789b80 100644 (file)
@@ -1,8 +1,19 @@
-<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>
diff --git a/LayoutTests/media/video-seek-past-end-paused-expected.txt b/LayoutTests/media/video-seek-past-end-paused-expected.txt
new file mode 100644 (file)
index 0000000..653c45a
--- /dev/null
@@ -0,0 +1,11 @@
+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
+
diff --git a/LayoutTests/media/video-seek-past-end-paused.html b/LayoutTests/media/video-seek-past-end-paused.html
new file mode 100644 (file)
index 0000000..5825ae5
--- /dev/null
@@ -0,0 +1,12 @@
+<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>
diff --git a/LayoutTests/media/video-seek-past-end-playing-expected.txt b/LayoutTests/media/video-seek-past-end-playing-expected.txt
new file mode 100644 (file)
index 0000000..f027746
--- /dev/null
@@ -0,0 +1,10 @@
+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
+
diff --git a/LayoutTests/media/video-seek-past-end-playing.html b/LayoutTests/media/video-seek-past-end-playing.html
new file mode 100644 (file)
index 0000000..a3cde96
--- /dev/null
@@ -0,0 +1,17 @@
+<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>
diff --git a/LayoutTests/media/video-seeking-expected.txt b/LayoutTests/media/video-seeking-expected.txt
new file mode 100644 (file)
index 0000000..a716281
--- /dev/null
@@ -0,0 +1,11 @@
+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
+
diff --git a/LayoutTests/media/video-seeking.html b/LayoutTests/media/video-seeking.html
new file mode 100644 (file)
index 0000000..e3d5d90
--- /dev/null
@@ -0,0 +1,19 @@
+<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>
index e6b0560..de6ec6c 100644 (file)
@@ -118,6 +118,14 @@ function endTestLater()
     setTimeout(endTest, 250);
 }
 
+function failTestIn(ms)
+{
+    setTimeout(function () {
+        consoleWrite("FAIL: did not end fast enough");
+        endTest();
+    }, ms);
+}
+
 function consoleWrite(text)
 {
     if (testEnded)
index c92504b..987df34 100644 (file)
@@ -1,3 +1,64 @@
+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.
index cf94dac..a949640 100644 (file)
@@ -73,7 +73,7 @@ leftmargin
 link
 longdesc
 loop
-loopcount
+playcount
 loopend
 loopstart
 lowsrc
index 9760d94..3dac44b 100644 (file)
@@ -72,6 +72,8 @@ HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* doc)
     , 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)
@@ -261,6 +263,7 @@ void HTMLMediaElement::load(ExceptionCode& ec)
         m_networkState = EMPTY;
         m_readyState = DATA_UNAVAILABLE;
         m_paused = true;
+        m_seeking = false;
         if (m_movie) {
             m_movie->pause();
             m_movie->seek(0);
@@ -354,7 +357,6 @@ void HTMLMediaElement::movieNetworkStateChanged(Movie*)
     
     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);
@@ -417,6 +419,9 @@ void HTMLMediaElement::setReadyState(ReadyState state)
     bool wasActivelyPlaying = activelyPlaying();
     m_readyState = state;
     
+    if (state >= CAN_PLAY)
+        m_seeking = false;
+    
     if (networkState() == EMPTY)
         return;
     
@@ -442,7 +447,7 @@ void HTMLMediaElement::setReadyState(ReadyState state)
             dispatchHTMLEvent(playEvent, false, true);
         }
     }
-    updatePlayState();
+    updateMovie();
 }
 
 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
@@ -485,7 +490,7 @@ void HTMLMediaElement::seek(float time, ExceptionCode& ec)
         minTime = effectiveLoopStart();
  
     // 3
-    float maxTime = currentLoop() == loopCount() - 1 ? effectiveEnd() : effectiveLoopEnd();
+    float maxTime = currentLoop() == playCount() - 1 ? effectiveEnd() : effectiveLoopEnd();
     
     // 4
     time = min(time, maxTime);
@@ -501,13 +506,10 @@ void HTMLMediaElement::seek(float time, ExceptionCode& ec)
     }
     
     // 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);
@@ -515,6 +517,10 @@ void HTMLMediaElement::seek(float time, ExceptionCode& ec)
     // 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
@@ -524,16 +530,17 @@ 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)
@@ -587,7 +594,7 @@ void HTMLMediaElement::setPlaybackRate(float rate, ExceptionCode& ec)
 
 bool HTMLMediaElement::ended()
 {
-    return networkState() >= LOADED_METADATA && currentTime() >= effectiveEnd() && currentLoop() == loopCount() - 1;
+    return endedPlayback();
 }
 
 bool HTMLMediaElement::autoplay() const
@@ -618,11 +625,12 @@ void HTMLMediaElement::play(ExceptionCode& ec)
     
     if (m_paused) {
         m_paused = false;
-        updatePlayState();
         dispatchEventAsync(playEvent);
     }
 
     m_autoplaying = false;
+    
+    updateMovie();
 }
 
 void HTMLMediaElement::pause(ExceptionCode& ec)
@@ -637,28 +645,29 @@ 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();
 }
 
@@ -797,8 +806,8 @@ void HTMLMediaElement::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)
@@ -815,16 +824,16 @@ void HTMLMediaElement::checkIfSeekNeeded()
         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*)
@@ -838,20 +847,24 @@ 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)
@@ -956,14 +969,17 @@ bool HTMLMediaElement::activelyPlaying() const
 
 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())
index 656041c..4da50d6 100644 (file)
@@ -108,8 +108,8 @@ public:
     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);
  
@@ -140,8 +140,8 @@ protected:
 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:
@@ -152,7 +152,7 @@ private:
     void checkIfSeekNeeded();
     
     String pickMedia();
-    void updatePlayState();
+    void updateMovie();
     float effectiveStart() const;
     float effectiveEnd() const;
     float effectiveLoopStart() const;
@@ -186,6 +186,8 @@ protected:
     bool m_paused;
     bool m_seeking;
     
+    float m_currentTimeDuringSeek;
+    
     unsigned m_previousProgress;
     double m_previousProgressTime;
     bool m_sentStalledEvent;
index 015177f..82760d9 100644 (file)
@@ -75,7 +75,7 @@ interface [GenerateConstructor, Conditional=VIDEO] HTMLMediaElement : HTMLElemen
     attribute float end;
     attribute float loopStart;
     attribute float loopEnd;
-    attribute unsigned long loopCount
+    attribute unsigned long playCount
         setter raises (DOMException);
     attribute unsigned long currentLoop;
 
index 5647c5f..3352f43 100644 (file)
@@ -263,10 +263,10 @@ void Movie::volumeChanged()
         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)
index 970683a..d322195 100644 (file)
@@ -49,7 +49,7 @@ public:
     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) { }
 };
 
@@ -119,7 +119,7 @@ public:
     void networkStateChanged();
     void readyStateChanged();
     void volumeChanged();
-    void didEnd();
+    void timeChanged();
     void cuePointReached(float cueTime);
 
 private:
index fd1fb01..a5621cf 100644 (file)
@@ -113,8 +113,8 @@ public:
     static void getSupportedTypes(HashSet<String>& types);
     
 private:
-    
     void updateStates();
+    void doSeek();
     void cancelSeek();
     void seekTimerFired(Timer<MoviePrivate>*);
     void cuePointTimerFired(Timer<MoviePrivate>*);
@@ -131,11 +131,9 @@ private:
     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;
 };
 
index 735ba4e..5f56167 100644 (file)
@@ -70,11 +70,9 @@ MoviePrivate::MoviePrivate(Movie* movie)
     , 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)
 {
 }
@@ -169,30 +167,36 @@ void MoviePrivate::load(String url)
     }
     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();
 }
 
@@ -210,8 +214,6 @@ float MoviePrivate::currentTime() const
 {
     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);
@@ -228,19 +230,53 @@ void MoviePrivate::seek(float time)
     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();
+        }
     }
 }
 
@@ -266,7 +302,6 @@ void MoviePrivate::clearCuePoints()
 
 void MoviePrivate::startCuePointTimerIfNeeded()
 {
-    
     if ((m_endTime < duration() || !m_movie->m_cuePoints.isEmpty())
         && m_startedPlaying && !m_cuePointTimer.isActive()) {
         m_previousTimeCueTimerFired = currentTime();
@@ -274,45 +309,6 @@ void MoviePrivate::startCuePointTimerIfNeeded()
     }
 }
 
-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();
@@ -336,7 +332,7 @@ bool MoviePrivate::paused() const
 {
     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
@@ -461,9 +457,6 @@ void MoviePrivate::cancelLoad()
 
 void MoviePrivate::updateStates()
 {
-    if (m_blockStateUpdate)
-        return;
-    
     Movie::NetworkState oldNetworkState = m_networkState;
     Movie::ReadyState oldReadyState = m_readyState;
     
@@ -473,7 +466,7 @@ void MoviePrivate::updateStates()
         // 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())
@@ -497,7 +490,7 @@ void MoviePrivate::updateStates()
         m_networkState = Movie::LoadFailed;
         m_readyState = Movie::DataUnavailable; 
     }
-    
+
     if (seeking())
         m_readyState = Movie::DataUnavailable;
     
@@ -525,6 +518,7 @@ void MoviePrivate::timeChanged()
 {
     m_previousTimeCueTimerFired = -1;
     updateStates();
+    m_movie->timeChanged();
 }
 
 void MoviePrivate::volumeChanged()
@@ -536,7 +530,8 @@ void MoviePrivate::didEnd()
 {
     m_cuePointTimer.stop();
     m_startedPlaying = false;
-    m_movie->didEnd();
+    updateStates();
+    m_movie->timeChanged();
 }
 
 void MoviePrivate::setRect(const IntRect& r)