When scrubbing, make the timeline progress match exactly to the cursor
position. Also, set the timestamp to match that progress. Both are not
allowed to change until the scrub completes.
This makes the UI stable while scrubbing after the end of the media
data in subsequent commits that jump the time to the duration at EOS.
Whenever the controls' timeline is clicked, it pauses the media element
before seeking, then play()s on mouseup. However, the play() returns a
promise that resolves when the media actually becomes playable at the
new position. If that takes long enough that a second click pauses the
element again, then that play() promise gets rejected, and an unhandled
rejection is logged.
To prevent that, mark all play promises as handled to silence them.
The media element's timeupdate event only fires every 250ms. To make
the timeline update more smoothly, poll currentTime to update the
timeline every frame.
Instead of manually writing code to instantiate DOM elements in
MediaControls.cpp, use a Python script to generate a separate C++
struct to create and store the DOM elements.
The generator reads an HTML file and the HTML/SVG tags/attributes
headers to create C++ source that instantiates the DOM elements.
To enable embedding of stylesheets in shadow DOM, the generator
replaces `<link rel="stylesheet">` elements with plain `<style>`
elements containing the source from the linked stylesheet.
Elements that should be stored in the resulting struct should be marked
have the `data-name` attribute, which will be converted to snake_case
and used as the public field's name.
Optional elements can be marked with a 'data-option' attribute. Each
unique option value will be converted to PascalCase and added to a
bitwise enum named `Options` nested within the struct. Optional
elements and all their children will not be instantiated unless their
option is set in the constructor argument.
The MediaControls class stores the generated MediaControlsDOM struct
and sets up event handlers to implement user interactions.
Instead of using a custom paintable to draw the controls for video and
audio elements, we build them out of plain old HTML elements within a
shadow root.
This required a few hacks in the previous commits in order to allow a
replaced element to host children within a shadow root, but it's
fairly self-contained.
A big benefit is that we can drive all the UI updates off of plain old
DOM events (except the play button overlay on videos, which uses the
video element representation), so we can test our media and input event
handling more thoroughly. :^)
The control bar visibility is now more similar to how other browsers
handle it. It will show upon hovering over the element, but if the
cursor is kept still for more than a second, it will hide again. While
dragging, the controls remain visible, and will then hide after the
mouse button is released.
The icons have been redesigned from scratch, and the mute icon now
visualizes the volume level along with indicating the mute state.