Optimize the performance for `TextTrackCueList::AddCue()`
Categories
(Core :: Audio/Video: Playback, defect, P3)
Tracking
()
Tracking | Status | |
---|---|---|
firefox142 | --- | fixed |
People
(Reporter: emterroso, Assigned: alwu)
References
(Blocks 1 open bug)
Details
Attachments
(4 files)
User Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:139.0) Gecko/20100101 Firefox/139.0
Steps to reproduce:
This is more noticeable on text tracks with many cues, like the one in this manifest, containing tens of thousands of them:
https://ed14.cdn.svt.se/d0/world/20250623/9ba84fdc-325a-426b-896f-6d0a14a29226/dash-full.mpd
While playing it, run this in the console:
(()=>{
const track = $('video')[0].textTracks[0]
const newMode = track.mode === 'showing' ? 'hidden' : 'showing'
const before = performance.now()
track.mode = newMode
const elapsedTime = performance.now() - before
const cues = track.cues?.length ?? 0;
console.log(Setting text track mode to ${newMode} took ${elapsedTime}ms (${cues} cues)
);
})()
Actual results:
The main thread got blocked for over a minute and the snippet above printed:
Setting text track mode to hidden took 78990ms (38780 cues)
Expected results:
The browser should have remained responsive, without any noticeable lag. Doing exactly the same in Chrome, I got this:
Setting text track mode to hidden took 8.599999904632568ms (38780 cues)
Comment 1•3 months ago
|
||
The Bugbug bot thinks this bug should belong to the 'Core::Audio/Video: Playback' component, and is moving the bug to that component. Please correct in case you think the bot is wrong.
Comment 2•3 months ago
|
||
Note the public link to the dash reference player is here.
And the code to run in a code block:
(()=>{
const track = $('video')[0].textTracks[0]
const newMode = track.mode === 'showing' ? 'hidden' : 'showing'
const before = performance.now()
track.mode = newMode
const elapsedTime = performance.now() - before
const cues = track.cues?.length ?? 0;
console.log(`Setting text track mode to ${newMode} took ${elapsedTime}ms (${cues} cues)`);
})()
On my Macbook Pro (M2 Pro) it takes ~37 seconds (Setting text track mode to hidden took 36877ms (38780 cues)
), which is certainly enough to run out of buffered media and stall playback.
Comment 3•3 months ago
|
||
Here's a profile of just a 1-second slice with only WebVTT:5
logging during loading of the video. I didn't get to run the provided script in the console, but you get an idea of the performance issues here.
Comment 5•3 months ago
|
||
Here's another profile, without logging (the overhead becomes too much). This one covers the full page load and the script.
Yes, we do seem pretty slow overall, intensely so for setting track mode. Without having looked at the code I wonder why we are iterating over all cues doing something, rather than just affect the cues that cover the current time, and perhaps the next cue after that. TL;DR setting the mode takes 39s, of which ~36s is made up of various media::TimeUnit
operations.
Re loading, which is outside the scope of this bug, the deeper issue seems to be that dash.js doesn't let the event loop run until it has added all cues for the 7h video? IMHO it could just stream them, making sure to have added all cues from currentTime and a few seconds ahead. Or follow the duration of buffered media data. I mean -- 7h worth of cues added and then the user changes language! What a waste.
Comment 6•3 months ago
•
|
||
Looking a bit into the track mode setter, the problem is:
For each cue (~39k of them) in the (unsorted) cue list:Notify the media element it was added or removed, depending on mode.Add or Remove it from the list of active cues, depending on mode.What is the list of active cues? A time-sorted (media::TimeUnit
) list! And we add elements one by one! Geeee
And of course, if dash.js kept, say, 100 cues in the track at any given time instead of 39k, this wouldn't be a user-observable problem.
edit: nope, the list of active cues use double
for sorting, so the TimeUnit
operations come in elsewhere.
Assignee | ||
Comment 7•3 months ago
|
||
I can take some time to look into this tomorrow—keep my NI.
When I clicked the player link in comment 2, I saw an error after hitting “Load”:
https://ed14.cdn.svt.se/d0/world/20250623/9ba84fdc-325a-426b-896f-6d0a14a29226/dash-full.mpd is not available.
Did I do something wrong?
Also, how does the performance compare on Chrome? Thanks.
Comment 8•3 months ago
•
|
||
Nothing wrong, it should load automatically on document load.
Reporter, is this content region blocked? If so, do you have something accessible from north america?
Per comment 0 we're a factor ~10x ~10000x slower than Chrome on setting the mode. That's still multiple seconds worth of jank in Chrome on a fast machine.
edit: oh, it was millis!
Comment 9•3 months ago
|
||
There is not much use of TimeUnit
in webvtt, but there's this in TextTrack::GetCurrentCuesAndOtherCues
, which does TimeUnit
operations on every cue in the list, and is only called from TextTrackManager::TimeMarchesOn
.
TextTrackManager::TimeMarchesOn
happens (by spec?) to be called synchronously from both SetMode
and AddCue
(through e.g. NotifyCueAdded
, i.e. on every cue). Yeah that's quadratic.
Comment 10•3 months ago
|
||
Bug 1951071 may be related (and has a minimal artificial testcase)
Reporter | ||
Comment 11•3 months ago
|
||
The content should not be blocked by region, but I tried with a VPN from the US and also got that error, for some reason. When I used our player (also using Dash.js) instead of the reference player, it worked from the US: https://videoplayer-static.app.borealis.svt.se/demo/master/?videoId=%22ed61zDr%22
About the performance on Chrome, on my machine it was a factor of ~10000x. On Chrome it takes ~8ms, while on Firefox, ~78000ms.
Comment 12•3 months ago
|
||
In a quick experiment I put all the TimeMarchesOn()
, MaybeRunTimeMarchesOn()
and UpdateCueDisplay()
calls behind a watch manager and got
Setting text track mode to hidden took 255ms (38780 cues)
, and
Setting text track mode to showing took 237ms (38780 cues)
, for two rounds of the console script, respectively.
Note this was a local, optimized, debug build, so it may be faster yet in non-debug/release. Some irrelevant webrtc dirs were compiled unoptimized, which I believe is why the profiler compiles about optimizations.
See the profile here.
I haven't tried running the test suite.
Comment 13•3 months ago
|
||
Ok, the WPT WebVTT suite is happy locally.
Comment 14•3 months ago
|
||
Updated•3 months ago
|
Comment 15•3 months ago
|
||
Comment 16•3 months ago
|
||
Comment 17•3 months ago
|
||
Backed out for causing WPT failures @activeCues.html
Assignee | ||
Updated•3 months ago
|
Assignee | ||
Comment 19•3 months ago
|
||
Since D255294 is not compliant with the spec, the "time marches on" algorithm must run immediately after any of the relevant conditions change. To address performance concerns, I optimized the algorithm itself, achieving approximately 400x
improvement in Bug 1975580.
However, the change to use WatchManager
for cue display in D255294 should still work. We can try that in this bug as well.
Assignee | ||
Updated•3 months ago
|
Assignee | ||
Comment 20•3 months ago
|
||
After fixing bug 1975580, this should be a trivial issue.
Assignee | ||
Comment 21•3 months ago
•
|
||
The performance bottleneck was in TextTrackCueList::AddCue(). After optimizing that function, I'm able to achieve comparable performance—from ~200ms
to ~5ms
—in my local debug build.
Assignee | ||
Comment 22•3 months ago
|
||
Assignee | ||
Comment 23•3 months ago
|
||
In a test scenario with approximately 40000 cues, the speed improvement is
dropping from around 200ms to about 5ms.
Assignee | ||
Comment 24•3 months ago
|
||
Comment 25•3 months ago
|
||
Comment 26•3 months ago
|
||
bugherder |
https://hg.mozilla.org/mozilla-central/rev/331337e5fdb8
https://hg.mozilla.org/mozilla-central/rev/6d41b0e9c444
https://hg.mozilla.org/mozilla-central/rev/31ddd392b913
Updated•2 months ago
|
Description
•