Bug 71648

Summary: SoundCloud <audio> test failures
Product: WebKit Reporter: Eric Carlson <eric.carlson>
Component: MediaAssignee: Eric Carlson <eric.carlson>
Status: ASSIGNED ---    
Severity: Normal CC: brad, jer.noble, tomas, webkit-bug-importer, yves.vangoethem
Priority: P2 Keywords: InRadar
Version: 528+ (Nightly build)   
Hardware: Mac   
OS: OS X 10.7   
URL: http://areweplayingyet.org/
Bug Depends on: 72145    
Bug Blocks:    

Description Eric Carlson 2011-11-06 21:11:00 PST
SoundCloud has published an HTML 5 <audio> interop test.  Mac WebKit fails 8 tests:

1) Seeking to unbuffered position with seamless playback, http://areweplayingyet.org/support-seeking-unbuffered-position
2) Supports OGG format, http://areweplayingyet.org/support-ogg-format
3) Consistent timeupdate interval (15ms - 250ms) - http://areweplayingyet.org/support-consistent-timeupdate-interval
4) roperty "seeking" - http://areweplayingyet.org/prop-seeking
5) Property "seek able" - http://areweplayingyet.org/prop-seekable
6) Property "currentTime" - http://areweplayingyet.org/prop-currentTime
7) Property "buffered" - http://areweplayingyet.org/prop-buffered
8) Event "progress" - http://areweplayingyet.org/event-progress

None of the tests say why they fail, so we will have to investigate each to see what the problems are.
Comment 1 Eric Carlson 2011-11-06 21:14:49 PST
#3 fails on my machine because the interval between 'timeupdate' events is sometimes 251ms. This isn't surprising because the timer we use to post these events fires every 250ms, so we don't have any room for error. We should decrease the timer interval.
Comment 2 Eric Carlson 2011-11-06 21:31:32 PST
#7 fails because the test is incorrect. It fails if audio.buffered.length is 0 when 'havemetadata' fires, but that event fires when [1]: 

    The user agent has just determined the duration and dimensions 
    of the media resource and the text tracks are ready.

which does *not* necessarily mean that any media data has loaded

[1] http://dev.w3.org/html5/spec/the-iframe-element.html#event-media-loadedmetadata
Comment 3 Eric Carlson 2011-11-06 21:34:16 PST
#2 checks for OGG support, which we don't have with a default QuickTime install.
Comment 4 Yves Van Goethem 2011-11-07 04:27:02 PST
Hey guys, looks like you found our little test suite :)
It's not officially live yet, we will redesign it, polish the tests and make sure everything is solid and well documented, the announcement will be done on Wednesday.
In the meantime we appreciate your feedback, we are looking into fixing tests you mentioned and linking to webkit bugs.
If you like to help us you can create pull requests or open issues on our github repo:
https://github.com/soundcloud/areweplayingyet
https://github.com/soundcloud/areweplayingyet/issues

Thanks!
Comment 5 Eric Carlson 2011-11-10 07:12:55 PST
rdar://10425842
Comment 6 Eric Carlson 2011-11-10 08:14:26 PST
"Seeking to unbuffered position with seamless playback"[1] fails because the test is incorrect. 

The script sets preload = 'metadata, sets 'src', and seeks when the 'loadedmetadata' event fires:

    audio.addEventListener('loadedmetadata', function() {
      audio.currentTime = seekedTime = AWPY.sound.long.duration * 0.8;
    }, false);

    audio.preload = 'metadata';
    audio.src = AWPY.sound.long.stream_url();

This means that if the UA implements preload='metadata' correctly, *only* metadata will have been loaded when 'currentTime' is set. 

When the AVFoundation media engine is used, preload = 'metadata' is implemented by only allocating the AVAsset and literally only loading a file's metadata. This means that when the 'loadedmetadata' event fires, 'seekable()' returns a Range object with length 0. Step 7 of the seeking algorithm [2] says:

    If the (possibly now changed) new playback position is not 
    in one of the ranges given in the seekable attribute, then 
    let it be the position in one of the ranges given in the 
    seekable attribute that is the nearest to the new playback 
    position.

In other words, the seek time is clamped to the the seekable range. In this case, the causes the seek time to be clamped to 0 and no seek happens.

I think the test would be more reasonable if 'preload' was not set to 'metadata' and if the seek was requested when 'canplay' fires.

    audio.addEventListener('canplay', function() {
      audio.currentTime = seekedTime = AWPY.sound.long.duration * 0.8;
    }, false);

    audio.src = AWPY.sound.long.stream_url();



[1] http://areweplayingyet.org/support-seeking-unbuffered-position
[2] http://dev.w3.org/html5/spec/the-iframe-element.html#seeking
Comment 7 Eric Carlson 2011-11-10 15:00:26 PST
Test #3, "Consistent timeupdate interval (15ms - 250ms)", http://areweplayingyet.org/event-progress, is also incorrect. 

This test "fails" if two 'timeupate' event fire less than 15ms or more than 250ms apart:

    audio.addEventListener('timeupdate', function() {
      if (lastTime) {
        var now = new Date();
        if ((now - lastTime) < 15 || (now - lastTime) > 250) {
          finish(false);
        } else if (++count === 30) {
          finish(true);
        }
      }
      lastTime = new Date();
 
This is wrong because the spec says that the interval between events should be 150ms to 550ms [1]:

    While the load is not suspended (see below), every 350ms 
    (±200ms) or for every byte received, whichever is least frequent, 
    queue a task to fire a simple event named progress at the element.


[1] http://dev.w3.org/html5/spec/the-iframe-element.html#concept-media-load-resource
Comment 8 Tomás Senart 2011-11-10 15:56:44 PST
(In reply to comment #6)
> "Seeking to unbuffered position with seamless playback"[1] fails because the test is incorrect. 
> 
> The script sets preload = 'metadata, sets 'src', and seeks when the 'loadedmetadata' event fires:
> 
>     audio.addEventListener('loadedmetadata', function() {
>       audio.currentTime = seekedTime = AWPY.sound.long.duration * 0.8;
>     }, false);
> 
>     audio.preload = 'metadata';
>     audio.src = AWPY.sound.long.stream_url();
> 
> This means that if the UA implements preload='metadata' correctly, *only* metadata will have been loaded when 'currentTime' is set. 
> 
> When the AVFoundation media engine is used, preload = 'metadata' is implemented by only allocating the AVAsset and literally only loading a file's metadata. This means that when the 'loadedmetadata' event fires, 'seekable()' returns a Range object with length 0. Step 7 of the seeking algorithm [2] says:
> 
>     If the (possibly now changed) new playback position is not 
>     in one of the ranges given in the seekable attribute, then 
>     let it be the position in one of the ranges given in the 
>     seekable attribute that is the nearest to the new playback 
>     position.
> 
> In other words, the seek time is clamped to the the seekable range. In this case, the causes the seek time to be clamped to 0 and no seek happens.
> 
> I think the test would be more reasonable if 'preload' was not set to 'metadata' and if the seek was requested when 'canplay' fires.
> 
>     audio.addEventListener('canplay', function() {
>       audio.currentTime = seekedTime = AWPY.sound.long.duration * 0.8;
>     }, false);
> 
>     audio.src = AWPY.sound.long.stream_url();
> 
> 
> 
> [1] http://areweplayingyet.org/support-seeking-unbuffered-position
> [2] http://dev.w3.org/html5/spec/the-iframe-element.html#seeking

Hello! I tried to alter the test to what you suggest and it still fails. Why shouldn't the audio object be capable of seeking after loadedmetadata was triggered?
Comment 9 Tomás Senart 2011-11-10 15:57:52 PST
(In reply to comment #7)
> Test #3, "Consistent timeupdate interval (15ms - 250ms)", http://areweplayingyet.org/event-progress, is also incorrect. 
> 
> This test "fails" if two 'timeupate' event fire less than 15ms or more than 250ms apart:
> 
>     audio.addEventListener('timeupdate', function() {
>       if (lastTime) {
>         var now = new Date();
>         if ((now - lastTime) < 15 || (now - lastTime) > 250) {
>           finish(false);
>         } else if (++count === 30) {
>           finish(true);
>         }
>       }
>       lastTime = new Date();
> 
> This is wrong because the spec says that the interval between events should be 150ms to 550ms [1]:
> 
>     While the load is not suspended (see below), every 350ms 
>     (±200ms) or for every byte received, whichever is least frequent, 
>     queue a task to fire a simple event named progress at the element.
> 
> 
> [1] http://dev.w3.org/html5/spec/the-iframe-element.html#concept-media-load-resource

I think you confused http://areweplayingyet.org/support-consistent-timeupdate-interval with http://areweplayingyet.org/event-progress.
Comment 10 Tomás Senart 2011-11-10 17:11:46 PST
(In reply to comment #8)
> (In reply to comment #6)
> > "Seeking to unbuffered position with seamless playback"[1] fails because the test is incorrect. 
> > 
> > The script sets preload = 'metadata, sets 'src', and seeks when the 'loadedmetadata' event fires:
> > 
> >     audio.addEventListener('loadedmetadata', function() {
> >       audio.currentTime = seekedTime = AWPY.sound.long.duration * 0.8;
> >     }, false);
> > 
> >     audio.preload = 'metadata';
> >     audio.src = AWPY.sound.long.stream_url();
> > 
> > This means that if the UA implements preload='metadata' correctly, *only* metadata will have been loaded when 'currentTime' is set. 
> > 
> > When the AVFoundation media engine is used, preload = 'metadata' is implemented by only allocating the AVAsset and literally only loading a file's metadata. This means that when the 'loadedmetadata' event fires, 'seekable()' returns a Range object with length 0. Step 7 of the seeking algorithm [2] says:
> > 
> >     If the (possibly now changed) new playback position is not 
> >     in one of the ranges given in the seekable attribute, then 
> >     let it be the position in one of the ranges given in the 
> >     seekable attribute that is the nearest to the new playback 
> >     position.
> > 
> > In other words, the seek time is clamped to the the seekable range. In this case, the causes the seek time to be clamped to 0 and no seek happens.
> > 
> > I think the test would be more reasonable if 'preload' was not set to 'metadata' and if the seek was requested when 'canplay' fires.
> > 
> >     audio.addEventListener('canplay', function() {
> >       audio.currentTime = seekedTime = AWPY.sound.long.duration * 0.8;
> >     }, false);
> > 
> >     audio.src = AWPY.sound.long.stream_url();
> > 
> > 
> > 
> > [1] http://areweplayingyet.org/support-seeking-unbuffered-position
> > [2] http://dev.w3.org/html5/spec/the-iframe-element.html#seeking
> 
> Hello! I tried to alter the test to what you suggest and it still fails. Why shouldn't the audio object be capable of seeking after loadedmetadata was triggered?

So the test code was updated to reflect this. Thanks for the input. Webkit passes now.
I had to add audio.removeEventListener('canplay', arguments.callee, false); inside the canplay event handler because canplay is triggered again after the seek.
Comment 11 Eric Carlson 2011-11-11 09:39:11 PST
(In reply to comment #9)
> 
> I think you confused http://areweplayingyet.org/support-consistent-timeupdate-interval with http://areweplayingyet.org/event-progress.

Right you are - silly me!
Comment 12 Tomás Senart 2011-11-11 10:18:30 PST
(In reply to comment #11)
> (In reply to comment #9)
> > 
> > I think you confused http://areweplayingyet.org/support-consistent-timeupdate-interval with http://areweplayingyet.org/event-progress.
> 
> Right you are - silly me!

No problem! Referring to http://areweplayingyet.org/support-seeking-unbuffered-position:
Mozilla people are saying this on their bug tracker:
"Requiring the canplay event before seeking corresponds to HAVE_FUTURE_DATA.  It should be possible to seek as soon as you reach HAVE_METADATA (and receive the loadedmetadata event)."

It makes sense to me. What do you think?
Comment 13 Tomás Senart 2011-11-14 15:39:21 PST
Please continue discussion here: https://github.com/soundcloud/areweplayingyet/issues/28
Comment 14 Yves Van Goethem 2012-10-11 11:26:31 PDT
Hey guys,
We made minors update to AWPY:
 - Introduced check for Opus support http://areweplayingyet.org/support-format-opus :)
 - Split the test 'support-seeking-unbuffered-position' into 3 tests to take into account different codecs and removed the former one. (you can read more about the motivations: https://github.com/soundcloud/areweplayingyet/pull/41)
  * http://areweplayingyet.org/support-seeking-unbuffered-position-ogg
  * http://areweplayingyet.org/support-seeking-unbuffered-position-mp3
  * http://areweplayingyet.org/support-seeking-unbuffered-position-aac

We will also introduce an equivalent test for Opus.
Or if you want to contribute you can propose a patch :)