Sunday, July 7, 2013

Styling HTML Media Inner Workings


The Problem

While on Stack Overflow I stumbled across a question(right here in case you are curious) about styling the HTML5 Audio Player's time text and track control. Intrigued, my first reaction was that it could not be done because the players are browser-dependent. As I looked deeper into the issue, my answers and comments failed to satisfy the person who asked the question. I became haunted by the question and devoted some time to finding the answer. Since Google Chrome is the most stable browser that I could find that supports the most HTML5 features, I focused almost exclusively on it, but occasionally used a less-stable Chinese webkit browser called Maxthon.

I found that playing with the JavaScript console in these browsers revealed some unseen HTML in their elements panel.

var audio = document.createElement('audio'); audio.controls=true;document.querySelector('body').appendChild(audio);



For those who want an explanation of the picture, it shows the following html:


<div><div><div><input type="button"><input type="range" precision="float" max="0"><div style="display: none;">0:00</div><div>0:00</div><input type="button"><input type="range" precision="float" max="1" style="display: none;"><input type="button" style="display: none;"><input type="button" style="display: none;"></div></div></div>

All of this is in a #document-fragment, which is in the empty audio control.
How is it that when I view any embedded audio or video element, as this picture shows, this hidden DOM tree does not show up? The answer is in the shadows.

The Shadow DOM

One of the features of HTML that is almost exclusive to webkit is the Shadow DOM. According to an article on html5rocks, the purpose of the Shadow DOM is to separate content from style and hide style from any script viewing the DOM. This is shown off with an example of a name tag. Unfortunately, caniuse.com reports that only Chrome 25+ and Opera15+ support this feature.

What does this have to do with styling an <audio> element's track control? It just so happens that the Shadow DOM uses document fragments, and that the media controls in Chrome are in document fragments. Coincidence? I intend to prove otherwise.

There is a way to always show the Shadow DOM in the Chrome debug panel. Go to the settings (the gear to the bottom-right), go to the general tab on your left, and check "Show Shadow DOM".

Now go to any page that uses the Shadow DOM, right-click on an element that you know uses the Shadow DOM, and click "inspect elment".
As you can see, the highlighted element has a #document-fragment. If you try it out on the nametag in the html5rocks article mentioned above, you will see that unless you enable showing the Shadow DOM in the elements panel, the #document-fragment does not show up.

But how does this prove that Chrome uses the Shadow DOM? you may be asking. Try dragging your favorite mp3 into Google Chrome while the elements panel can show the shadow DOM and inspect the audio player. You will notice that the #document-fragment mentioned above is now shown, but you did not add it with JavaScript. Since you can not see said #document-fragment without either generating an audio element using JavaScript or showing the Shadow DOM in the elements tab, it follows that Google Chrome uses the Shadow DOM with its media elements.


Styling <audio> and <video> in Chrome

It is simply wonderful that you can see how the &lt;audio&gt; and &lt;video&gt; tags work with the Shadow DOM, but Chrome does not allow you to edit or create Shadow DOMs for its media elements. How then might we style the track control for these elements? Look more closely at the elements tab. To your right is a panel that shows the styles of selected elements. It also shows pseudo-elements relating to different parts of the controller, most of which which are named obvious names - my complements to the good programmers behind Google Chrome:




entire control:                                            *::-webkit-media-controls

text track (for closed captions)                  video::-webkit-media-text-track-container

container for timeline etc                            audio::-webkit-media-controls-enclosure,video::-webkit-media-controls-enclosure
     

Element inside webkit-media-control:       audio:-webkit-full-page-media::-webkit-media-controls-panel, video:-webkit-full-page-media::-webkit-media-controls-panel

Play/pause button:                                    audio::-webkit-media-controls-play-button,video::-webkit-media-controls-play-button

Timeline control:                                       audio::-webkit-media-controls-timeline,video::-webkit-media-controls-timeline


Current time and time remaining display     audio::-webkit-media-controls-current-time-display, video::-webkit-media-controls-current-time-display, audio::-webkit-media-controls-time-remaining-display,video::-webkit-media-controls-time-remaining-display
Mute button:                                             audio::-webkit-media-controls-mute-button,video::-webkit-media-controls-mute-button

Audio controller:                                        audio::-webkit-media-controls-volume-slider,video::-webkit-media-controls-volume-slider

Closed Captions button:                             audio::-webkit-media-controls-toggle-closed-captions-button, video::-webkit-media-controls-toggle-closed-captions-button

Full Screen Button:                                   audio::-webkit-media-controls-fullscreen-button, video::-webkit-media-controls-fullscreen-button


There are many more pseudo elements that I can not list.

We can set up a different style for these pseudo-elements and therefore change the default audio controller without custom javascript. Below I have an example of how this is done. If you are a Legend of Zelda fan, you might recognize what the embedded song is. Special thanks Zelda Universe for making the song available.







I must make a few notes:

  • This does not work with me using scoped styles, which means this must be done with global styles in the head or, in this case, in the body.
  • The way I found these pseudo-elements was through the Shadow DOM, which means that we can not know how a different browser handles <audio> and <video> without the Shadow DOM being implemented and exposed.
  • The -webkit prefix shows that these pseudo-elements are webkit-specific.
  • Looking into the Shadow DOM reveals some appearances that are non-standard. These appearances can not be used outside of media controllers. For example, this slider should look like the timeline on a media controller, but breaks when the appearance is applied and therefore looks like a text box.
  • I have yet to figure out how to customize the two-color(grey and white) scheme in audio::webkit-media-control-timeline::webkit-media-slider-container

(update): user1844626 on Stack Overflow attempted to apply this to Opera with the -o prefix, but found that nothing happened. After a few non-Chrome tests, I have concluded that:
  • other webkit browsers are likely to support this, even if they do not support Shadow DOM or let debugger tools into the Shadow DOM. 
  • non-webkit browsers do not support this, and will likely call the different parts of the media controller something different.
  • A simple test involving getting on my family's macintosh shows that browsers that this method is only mac-friendly when tested with Chrome.or Maxthon.
To user1844626 on Stack Overflow, I hope this blog post answers your question thoroughly. If not, you can always go through the trouble of making your own generic media player. It may take a bit of work, but it will be faster than waiting for Firefox, Safari, and Internet Exploder to implement the Shadow DOM. For the latter it may even take decades.

1 comment:

  1. This is a brilliant blog! I'm very happy with the comments!.. smm panel

    ReplyDelete