@@ -17,59 +17,31 @@ export function visibleBounds(content: ContentBounds, width: number, height: num
1717// Long enough that one late frame doesn't flash it, short enough that a real stutter gets explained.
1818const SHOW_AFTER_MS = 500 ;
1919const HIDE_AFTER_MS = 1500 ;
20- const EXPANDED_FOR_MS = 4000 ;
21- // Narrower than this, the full message would cover too much of the picture.
22- const NARROW_VIDEO_PX = 400 ;
2320const INSET_PX = 12 ;
2421// A longer gap between ticks means ticking paused, as in an export or a hidden tab, and says nothing about stutter.
2522const MAX_TICK_GAP_MS = 100 ;
26- const LABEL = "Buffering" ;
27- const MESSAGE = "You can keep watching. It'll be smoother once it's loaded." ;
2823
2924/**
30- * Explains stuttering html5 playback from the video's bottom-left corner, then collapses to a word . It holds only its
31- * own display state, and the canvas feeds it on every tick.
25+ * Labels stuttering html5 playback "Buffering" in the video's bottom-left corner. It holds only its own display
26+ * state, and the canvas feeds it on every tick.
3227 * @internal
3328 */
3429export class BufferingNotice {
35- public readonly element : HTMLButtonElement ;
30+ public readonly element : HTMLDivElement ;
3631 private readonly now : ( ) => number ;
3732 private bufferingSince : number | null = null ;
3833 private smoothSince : number | null = null ;
3934 private lastTickAt : number | null = null ;
40- private shownAt = 0 ;
41- private shownBefore = false ;
42- private opensExpanded = false ;
43- private hovered = false ;
44- private pinned = false ;
4535
4636 constructor ( now : ( ) => number = ( ) => performance . now ( ) ) {
4737 this . now = now ;
48- this . element = document . createElement ( "button" ) ;
49- this . element . type = "button" ;
38+ this . element = document . createElement ( "div" ) ;
5039 this . element . className = "ss-buffering-notice" ;
5140 this . element . hidden = true ;
52- this . element . setAttribute ( "aria-label" , `${ LABEL } . ${ MESSAGE } ` ) ;
53- this . element . setAttribute ( "aria-expanded" , "false" ) ;
54- this . element . innerHTML =
55- '<span class="ss-buffering-notice-spinner" aria-hidden="true"></span><span class="ss-buffering-notice-label"></span><span class="ss-buffering-notice-message"></span>' ;
56- this . element . querySelector ( ".ss-buffering-notice-label" ) ! . textContent = LABEL ;
57- this . element . querySelector ( ".ss-buffering-notice-message" ) ! . textContent = MESSAGE ;
58- // The canvas root pans on pointerdown, and open popups close on outside clicks.
59- this . element . addEventListener ( "pointerdown" , event => event . stopPropagation ( ) ) ;
60- this . element . addEventListener ( "click" , event => {
61- event . stopPropagation ( ) ;
62- this . pinned = ! this . pinned ;
63- this . render ( ) ;
64- } ) ;
65- this . element . addEventListener ( "pointerenter" , ( ) => {
66- this . hovered = true ;
67- this . render ( ) ;
68- } ) ;
69- this . element . addEventListener ( "pointerleave" , ( ) => {
70- this . hovered = false ;
71- this . render ( ) ;
72- } ) ;
41+ // The toolbar's status line already tells assistive tech that playback is preparing.
42+ this . element . setAttribute ( "aria-hidden" , "true" ) ;
43+ this . element . innerHTML = '<span class="ss-buffering-notice-spinner"></span>' ;
44+ this . element . append ( "Buffering" ) ;
7345 }
7446
7547 public mount ( root : HTMLElement ) : void {
@@ -85,45 +57,24 @@ export class BufferingNotice {
8557 if ( idle || ! bounds ) {
8658 this . bufferingSince = null ;
8759 this . smoothSince = null ;
88- this . hide ( ) ;
60+ this . element . hidden = true ;
8961 return ;
9062 }
9163 if ( buffering ) {
9264 this . smoothSince = null ;
9365 this . bufferingSince ??= previous ;
94- if ( this . element . hidden && now - this . bufferingSince >= SHOW_AFTER_MS ) this . show ( now , bounds ) ;
66+ if ( now - this . bufferingSince >= SHOW_AFTER_MS ) this . element . hidden = false ;
9567 } else {
9668 this . bufferingSince = null ;
9769 this . smoothSince ??= previous ;
98- if ( ! this . element . hidden && now - this . smoothSince >= HIDE_AFTER_MS ) this . hide ( ) ;
70+ if ( now - this . smoothSince >= HIDE_AFTER_MS ) this . element . hidden = true ;
9971 }
10072 if ( this . element . hidden ) return ;
10173 this . element . style . left = `${ bounds . left + INSET_PX } px` ;
10274 this . element . style . top = `${ bounds . bottom - INSET_PX } px` ;
103- this . render ( ) ;
10475 }
10576
10677 public dispose ( ) : void {
10778 this . element . remove ( ) ;
10879 }
109-
110- private show ( now : number , bounds : ContentBounds ) : void {
111- this . shownAt = now ;
112- // Once read, a later stutter only needs the word.
113- this . opensExpanded = ! this . shownBefore && bounds . right - bounds . left >= NARROW_VIDEO_PX ;
114- this . shownBefore = true ;
115- this . pinned = false ;
116- this . element . hidden = false ;
117- }
118-
119- private hide ( ) : void {
120- this . pinned = false ;
121- this . element . hidden = true ;
122- }
123-
124- private render ( ) : void {
125- const expanded = this . pinned || this . hovered || ( this . opensExpanded && this . now ( ) - this . shownAt < EXPANDED_FOR_MS ) ;
126- this . element . classList . toggle ( "ss-buffering-notice--expanded" , expanded ) ;
127- this . element . setAttribute ( "aria-expanded" , String ( expanded ) ) ;
128- }
12980}
0 commit comments