Bug 481040 - video controls should indicate when an error occurs. r=enn
authorJustin Dolske <dolske@mozilla.com>
Mon, 23 Mar 2009 16:40:39 -0700
changeset 26477 ad88f5d62c7c18e838f4d7010d9a70c3527da848
parent 26476 6ed2d1cbc338b24a3d2bea97a75595288bc65f85
child 26478 999cd55bd9c046b6205ead0b6c7c17bd5552d731
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-esr52@a95d42642281 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersenn
bugs481040
milestone1.9.2a1pre
Bug 481040 - video controls should indicate when an error occurs. r=enn
toolkit/content/widgets/videocontrols.css
toolkit/content/widgets/videocontrols.xml
toolkit/themes/pinstripe/global/jar.mn
toolkit/themes/pinstripe/global/media/error.png
toolkit/themes/pinstripe/global/media/videocontrols.css
toolkit/themes/winstripe/global/jar.mn
toolkit/themes/winstripe/global/media/error.png
toolkit/themes/winstripe/global/media/videocontrols.css
--- a/toolkit/content/widgets/videocontrols.css
+++ b/toolkit/content/widgets/videocontrols.css
@@ -5,12 +5,12 @@
      visibility: hidden;
      opacity: 0.0;
 }
 
 .scrubber {
     -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#suppressChangeEvent");
 }
 
-.throbberOverlay {
+.statusOverlay {
     visibility: hidden;
     opacity: 0.0;
 }
--- a/toolkit/content/widgets/videocontrols.xml
+++ b/toolkit/content/widgets/videocontrols.xml
@@ -47,18 +47,18 @@
 
     <resources>
         <stylesheet src="chrome://global/content/bindings/videocontrols.css"/>
         <stylesheet src="chrome://global/skin/media/videocontrols.css"/>
     </resources>
 
     <xbl:content xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
         <stack flex="1">
-            <vbox class="throbberOverlay">
-                <box class="throbber" flex="1"/>
+            <vbox class="statusOverlay">
+                <box class="statusIcon" flex="1"/>
             </vbox>
 
             <vbox>
                 <spacer flex="1"/>
                 <hbox class="controlBar">
                     <button class="playButton" oncommand="document.getBindingParent(this).Utils.togglePause();"/>
                     <stack class="scrubberStack" flex="1">
                         <box class="backgroundBar"/>
@@ -136,33 +136,35 @@
                 progressBar    : null,
                 bufferBar      : null,
                 thumbWidth : 0,
 
                 randomID : 0,
                 videoEvents : ["play", "pause", "ended", "volumechange", "loadeddata",
                                "loadstart", "durationchange", "timeupdate", "progress",
                                "playing", "waiting", "canplaythrough", "seeking",
-                               "seeked", "emptied", "loadedmetadata"],
+                               "seeked", "emptied", "loadedmetadata", "error"],
 
+                // controlFader holds the fade state for the control bar.
                 controlFader  : {
                                     name : "controls", // shorthand for debugging
                                     element : null,    // the element to fade in/out
                                     runtime : 0,       // duration of active animation
                                     fadingIn : false,  // are we fading in, or fading out?
                                     isVisible : false, // is it at all visible?
                                     timer : null,      // handle from setInterval()
                                     delayTimer : null, // handle from setTimeout()
                                     START_DELAY : 0,   // ms, delay before fading in
                                     RUNTIME_MAX : 200, // ms
                                     RUNTIME_STEP : 30  // ms
                                 },
 
-                throbberFader : {
-                                    name : "throbber",
+                // statusFader holds the fade state for the status overlay (inc. throbber)
+                statusFader : {
+                                    name : "status",
                                     element : null,
                                     runtime : 0,
                                     fadingIn : false,
                                     isVisible : false,
                                     timer : null,
                                     delayTimer : null,
                                     START_DELAY : 750,
                                     RUNTIME_MAX : 300,
@@ -202,36 +204,50 @@
                             (this.video.videoWidth == 0 || this.videoHeight == 0))
                             this.isAudioOnly = true;
                     }
 
                     // If the first frame hasn't loaded, kick off a throbber fade-in.
                     if (this.video.readyState >= this.video.HAVE_CURRENT_DATA)
                         this.firstFrameShown = true;
                     else
-                        this.startFadeIn(this.throbberFader);
+                        this.startFadeIn(this.statusFader);
 
                     // We can't determine the exact buffering status, but do know if it's
                     // fully loaded. (If it's still loading, it will fire a progress event
                     // and we'll figure out the exact state then.)
                     this.bufferBar.setAttribute("max", 100);
                     if (this.video.networkState == this.video.NETWORK_LOADED)
                         this.bufferBar.setAttribute("value", 100);
                     else
                         this.bufferBar.setAttribute("value", 0);
+
+                    // Set the current status icon. If the video is in an error state,
+                    // show the status overlay now.
+                    if (this.video.error) {
+                        this.statusIcon.setAttribute("type", "error");
+                        this.startFadeIn(this.statusFader, true);
+                    } else {
+                        this.statusIcon.setAttribute("type", "throbber");
+                    }
                 },
 
                 get dynamicControls() {
                     // Don't fade controls for <audio> elements.
                     var enabled = !this.isAudioOnly;
 
                     // Allow tests to explicitly suppress the fading of controls.
                     if (this.video.hasAttribute("mozNoDynamicControls"))
                         enabled = false;
 
+                    // If the video hits an error, suppress controls if it
+                    // hasn't managed to do anything else yet.
+                    if (!this.firstFrameShown && this.video.error)
+                        enabled = false;
+
                     return enabled;
                 },
                 
                 handleEvent : function (aEvent) {
                     this.log("Got media event ----> " + aEvent.type);
 
                     // If the binding is detached (or has been replaced by a
                     // newer instance of the binding), nuke our event-listeners.
@@ -258,16 +274,17 @@
                                 this.startFadeIn(this.controlFader);
                             }
                             break;
                         case "loadeddata":
                             this.firstFrameShown = true;
                             break;
                         case "loadstart":
                             this.maxCurrentTimeSeen = 0;
+                            this.statusIcon.setAttribute("type", "throbber");
                             this.isAudioOnly = (this.video instanceof HTMLAudioElement);
                             break;
                         case "durationchange":
                             var duration = Math.round(this.video.duration * 1000); // in ms
                             this.durationChange(duration);
                             break;
                         case "progress":
                             var loaded = aEvent.loaded;
@@ -292,45 +309,53 @@
 
                             this.showPosition(currentTime, duration);
                             break;
                         case "emptied":
                             this.bufferBar.value = 0;
                             break;
                         case "seeking":
                         case "waiting":
-                            this.startFadeIn(this.throbberFader);
+                            this.statusIcon.setAttribute("type", "throbber");
+                            this.startFadeIn(this.statusFader);
                             break;
                         case "seeked":
                             // Normally we'd expect canplaythough to fire, but if we already
                             // have the data cached it shouldn't fire again.
                             if (this.video.readyState == this.video.HAVE_ENOUGH_DATA)
-                                this.startFadeOut(this.throbberFader);
+                                this.startFadeOut(this.statusFader);
                             break;
                         case "playing":
                         case "canplaythrough":
-                            this.startFadeOut(this.throbberFader);
+                            this.startFadeOut(this.statusFader);
+                            break;
+                        case "error":
+                            this.statusIcon.setAttribute("type", "error");
+                            this.startFadeIn(this.statusFader, true);
+                            // If video hasn't shown anything yet, disable the controls.
+                            if (!this.firstFrameShown)
+                                this.startFadeOut(this.controlFader);
                             break;
                         default:
                             this.log("!!! event " + aEvent.type + " not handled!");
                     }
                 },
 
                 terminateEventListeners : function () {
                     for each (var event in this.videoEvents)
                         this.video.removeEventListener(event, this, false);
 
                     if (this.controlFader.timer)
                         clearInterval(this.controlFader.timer);
                     if (this.controlFader.delayTimer)
                         clearInterval(this.controlFader.delayTimer);
-                    if (this.throbberFader.timer)
-                        clearInterval(this.throbberFader.timer);
-                    if (this.throbberFader.delayTimer)
-                        clearTimeout(this.throbberFader.delayTimer);
+                    if (this.statusFader.timer)
+                        clearInterval(this.statusFader.timer);
+                    if (this.statusFader.delayTimer)
+                        clearTimeout(this.statusFader.delayTimer);
                     this.log("--- videocontrols terminated ---");
                 },
 
                 durationChange : function (duration) {
                     if (isNaN(duration))
                         duration = this.maxCurrentTimeSeen;
                     this.log("Duration is " + duration + "ms");
 
@@ -398,22 +423,22 @@
                     // controls, let them fade-out so the controls don't get stuck on.
                     if (!this.firstFrameShown && !isMouseOver &&
                         !(this.video.autoplay && this.video.mozAutoplayEnabled))
                         return;
 
                     this.startFade(this.controlFader, isMouseOver);
                 },
 
-                startFadeIn : function (fader) {
-                    this.startFade(fader, true);
+                startFadeIn : function (fader, immediate) {
+                    this.startFade(fader, true, immediate);
                 },
 
-                startFadeOut : function (fader) {
-                    this.startFade(fader, false);
+                startFadeOut : function (fader, immediate) {
+                    this.startFade(fader, false, immediate);
                 },
 
                 startFade : function (fader, fadeIn, immediate) {
                     // If the fader specifies a start delay, don't immediately fade in...
                     // Unless there's already a fade underway, in which case we want to be
                     // able to immediately reverse it (eg, a seeking event right after seeked).
                     if (fadeIn && fader.START_DELAY && !immediate && !fader.timer) {
                         function delayedFadeStart(self, fader) {
@@ -538,17 +563,18 @@
             <body>
             <![CDATA[
             var video = this.parentNode;
             this.Utils.video = video;
             this.Utils.videocontrols = this;
             this.Utils.isAudioOnly = (video instanceof HTMLAudioElement);
 
             this.Utils.controlFader.element  = document.getAnonymousElementByAttribute(this, "class", "controlBar");
-            this.Utils.throbberFader.element = document.getAnonymousElementByAttribute(this, "class", "throbberOverlay");
+            this.Utils.statusFader.element = document.getAnonymousElementByAttribute(this, "class", "statusOverlay");
+            this.Utils.statusIcon = document.getAnonymousElementByAttribute(this, "class", "statusIcon");
 
             this.Utils.playButton = document.getAnonymousElementByAttribute(this, "class", "playButton");
             this.Utils.muteButton = document.getAnonymousElementByAttribute(this, "class", "muteButton");
             this.Utils.progressBar    = document.getAnonymousElementByAttribute(this, "class", "progressBar");
             this.Utils.bufferBar      = document.getAnonymousElementByAttribute(this, "class", "bufferBar");
             this.Utils.scrubber       = document.getAnonymousElementByAttribute(this, "class", "scrubber");
 
             // Get the width of the scrubber thumb.
--- a/toolkit/themes/pinstripe/global/jar.mn
+++ b/toolkit/themes/pinstripe/global/jar.mn
@@ -174,16 +174,17 @@ classic.jar:
 +  skin/classic/global/notification/info-bar-background.png           (notification/info-bar-background.png)
 +  skin/classic/global/notification/warning-bar-background.png        (notification/warning-bar-background.png)
 +  skin/classic/global/media/videocontrols.css                        (media/videocontrols.css)
 +  skin/classic/global/media/pauseButton.png                          (media/pauseButton.png)
 +  skin/classic/global/media/playButton.png                           (media/playButton.png)
 +  skin/classic/global/media/muteButton.png                           (media/muteButton.png)
 +  skin/classic/global/media/unmuteButton.png                         (media/unmuteButton.png)
 +  skin/classic/global/media/scrubberThumb.png                        (media/scrubberThumb.png)
++  skin/classic/global/media/error.png                                (media/error.png)
 +  skin/classic/global/media/throbber.png                             (media/throbber.png)
 +  skin/classic/global/menu/menu-arrow-dis.gif                        (menu/menu-arrow-dis.gif)
 +  skin/classic/global/menu/menu-arrow-hov.gif                        (menu/menu-arrow-hov.gif)
 +  skin/classic/global/menu/menu-arrow.gif                            (menu/menu-arrow.gif)
 +  skin/classic/global/menu/menu-arrow-dis-rtl.gif                    (menu/menu-arrow-dis-rtl.gif)
 +  skin/classic/global/menu/menu-arrow-hov-rtl.gif                    (menu/menu-arrow-hov-rtl.gif)
 +  skin/classic/global/menu/menu-arrow-rtl.gif                        (menu/menu-arrow-rtl.gif)
 +  skin/classic/global/menu/menu-check-dis.gif                        (menu/menu-check-dis.gif)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..58e37283a73eff086420d0969544fa8bc5dd7924
GIT binary patch
literal 433
zc$@*T0Z#sjP)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ0004aNkl<ZSi`m0
zQLcj^5Cu@0v;<A+0!(b${V%}o&SEndKon-?`f)#AV&H`G^n&@$v!UF4I-jRcPk);{
z%d)Ui8iU5}X|67wD?D)b8GSxcCLebJ$kEvGTq7Aw(B_zscaBnm9F19;Yc<>qE!pQJ
z2}%Jv!-RC}gG1$^1SJHuVAc#RE0mxFpjOPbLT7uZx~D4PV75K9=Hc@Kqd1rs8B9EY
zm7;_jIL6rgJ)}YjH}DQq0*O$<4ZOn~fut*CwU|HX<lmKp;^dDxnz!~sL!-8pk%Ew@
z<Y&@=5UAar(SkIn;s!>50*VWAoTA4CIRVIC1>(H)s^2~>NQz-MPgK(re~`H#6sEa(
zqM9BXbqE5Ms}51}5Ev``AQw?;OrfBuP>|XQ>Qe~_iBX}#F=zr~B&aDIgL*{M12xay
z(3Oa~>4BQ(v*jq=)bv2jvwD<nZhC;`nH;4{7gl39-hwZz9tU&X^Y>7=(i`$?wu`Rx
bigWz{uoo^%o)rPO00000NkvXXu0mjfb>P7_
--- a/toolkit/themes/pinstripe/global/media/videocontrols.css
+++ b/toolkit/themes/pinstripe/global/media/videocontrols.css
@@ -75,18 +75,25 @@
     /* Override the default thumb appearance with a custom image. */
     -moz-appearance: none;
     background: url(chrome://global/skin/media/scrubberThumb.png) no-repeat center;
     border: none;
     min-width: 11px;
     min-height: 20px;
 }
 
-.throbberOverlay {
+.statusOverlay {
     background-color: rgba(0,0,0,0.55);
 }
 
-.throbber {
-    background: url(chrome://global/skin/media/throbber.png) no-repeat center;
+.statusIcon {
+    margin-bottom: 28px; /* same height as .controlBar, to keep icon centered above it */
+    width: 36px;
     height: 36px;
-    width: 36px;
-    margin-bottom: 28px; /* same height as .controlBar, to keep throbber centered above it */
+}
+
+.statusIcon[type="throbber"] {
+    background: url(chrome://global/skin/media/throbber.png) no-repeat center;
 }
+
+.statusIcon[type="error"] {
+    background: url(chrome://global/skin/media/error.png) no-repeat center;
+}
--- a/toolkit/themes/winstripe/global/jar.mn
+++ b/toolkit/themes/winstripe/global/jar.mn
@@ -148,16 +148,17 @@ classic.jar:
         skin/classic/global/icons/wrap.png                       (icons/wrap.png)
         skin/classic/global/media/videocontrols.css              (media/videocontrols.css)
         skin/classic/global/media/pauseButton.png                (media/pauseButton.png)
         skin/classic/global/media/playButton.png                 (media/playButton.png)
         skin/classic/global/media/muteButton.png                 (media/muteButton.png)
         skin/classic/global/media/unmuteButton.png               (media/unmuteButton.png)
         skin/classic/global/media/scrubberThumb.png              (media/scrubberThumb.png)
         skin/classic/global/media/throbber.png                   (media/throbber.png)
+        skin/classic/global/media/error.png                      (media/error.png)
         skin/classic/global/radio/radio-check.gif                (radio/radio-check.gif)
         skin/classic/global/radio/radio-check-dis.gif            (radio/radio-check-dis.gif)
         skin/classic/global/scrollbar/slider.gif                 (scrollbar/slider.gif)
         skin/classic/global/splitter/grip-hrz-after.gif          (splitter/grip-hrz-after.gif)
         skin/classic/global/splitter/grip-hrz-before.gif         (splitter/grip-hrz-before.gif)
         skin/classic/global/splitter/grip-vrt-after.gif          (splitter/grip-vrt-after.gif)
         skin/classic/global/splitter/grip-vrt-before.gif         (splitter/grip-vrt-before.gif)
         skin/classic/global/toolbar/chevron.gif                  (toolbar/chevron.gif)
@@ -321,16 +322,17 @@ classic.jar:
         skin/classic/aero/global/icons/wrap.png                          (icons/wrap-aero.png)
         skin/classic/aero/global/media/videocontrols.css                 (media/videocontrols.css)
         skin/classic/aero/global/media/pauseButton.png                   (media/pauseButton.png)
         skin/classic/aero/global/media/playButton.png                    (media/playButton.png)
         skin/classic/aero/global/media/muteButton.png                    (media/muteButton.png)
         skin/classic/aero/global/media/unmuteButton.png                  (media/unmuteButton.png)
         skin/classic/aero/global/media/scrubberThumb.png                 (media/scrubberThumb.png)
         skin/classic/aero/global/media/throbber.png                      (media/throbber.png)
+        skin/classic/aero/global/media/error.png                         (media/error.png)
         skin/classic/aero/global/radio/radio-check.gif                   (radio/radio-check.gif)
         skin/classic/aero/global/radio/radio-check-dis.gif               (radio/radio-check-dis.gif)
         skin/classic/aero/global/scrollbar/slider.gif                    (scrollbar/slider.gif)
         skin/classic/aero/global/splitter/grip-hrz-after.gif             (splitter/grip-hrz-after.gif)
         skin/classic/aero/global/splitter/grip-hrz-before.gif            (splitter/grip-hrz-before.gif)
         skin/classic/aero/global/splitter/grip-vrt-after.gif             (splitter/grip-vrt-after.gif)
         skin/classic/aero/global/splitter/grip-vrt-before.gif            (splitter/grip-vrt-before.gif)
         skin/classic/aero/global/toolbar/chevron.gif                     (toolbar/chevron.gif)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..58e37283a73eff086420d0969544fa8bc5dd7924
GIT binary patch
literal 433
zc$@*T0Z#sjP)<h;3K|Lk000e1NJLTq001Na001Ni1^@s6;Q*MJ0004aNkl<ZSi`m0
zQLcj^5Cu@0v;<A+0!(b${V%}o&SEndKon-?`f)#AV&H`G^n&@$v!UF4I-jRcPk);{
z%d)Ui8iU5}X|67wD?D)b8GSxcCLebJ$kEvGTq7Aw(B_zscaBnm9F19;Yc<>qE!pQJ
z2}%Jv!-RC}gG1$^1SJHuVAc#RE0mxFpjOPbLT7uZx~D4PV75K9=Hc@Kqd1rs8B9EY
zm7;_jIL6rgJ)}YjH}DQq0*O$<4ZOn~fut*CwU|HX<lmKp;^dDxnz!~sL!-8pk%Ew@
z<Y&@=5UAar(SkIn;s!>50*VWAoTA4CIRVIC1>(H)s^2~>NQz-MPgK(re~`H#6sEa(
zqM9BXbqE5Ms}51}5Ev``AQw?;OrfBuP>|XQ>Qe~_iBX}#F=zr~B&aDIgL*{M12xay
z(3Oa~>4BQ(v*jq=)bv2jvwD<nZhC;`nH;4{7gl39-hwZz9tU&X^Y>7=(i`$?wu`Rx
bigWz{uoo^%o)rPO00000NkvXXu0mjfb>P7_
--- a/toolkit/themes/winstripe/global/media/videocontrols.css
+++ b/toolkit/themes/winstripe/global/media/videocontrols.css
@@ -82,18 +82,25 @@
     /* Override the default thumb appearance with a custom image. */
     -moz-appearance: none;
     background: url(chrome://global/skin/media/scrubberThumb.png) no-repeat center;
     border: none;
     min-width: 11px;
     min-height: 20px;
 }
 
-.throbberOverlay {
+.statusOverlay {
     background-color: rgba(0,0,0,0.55);
 }
 
-.throbber {
-    background: url(chrome://global/skin/media/throbber.png) no-repeat center;
+.statusIcon {
+    margin-bottom: 28px; /* same height as .controlBar, to keep icon centered above it */
+    width: 36px;
     height: 36px;
-    width: 36px;
-    margin-bottom: 28px; /* same height as .controlBar, to keep throbber centered above it */
+}
+
+.statusIcon[type="throbber"] {
+    background: url(chrome://global/skin/media/throbber.png) no-repeat center;
 }
+
+.statusIcon[type="error"] {
+    background: url(chrome://global/skin/media/error.png) no-repeat center;
+}