diff --git a/toolkit/content/widgets/videocontrols.css b/toolkit/content/widgets/videocontrols.css index 8b6980b0dc52..c7bbde78d83e 100644 --- a/toolkit/content/widgets/videocontrols.css +++ b/toolkit/content/widgets/videocontrols.css @@ -10,7 +10,7 @@ -moz-binding: url("chrome://global/content/bindings/videocontrols.xml#suppressChangeEvent"); } -.throbberOverlay { +.statusOverlay { visibility: hidden; opacity: 0.0; } diff --git a/toolkit/content/widgets/videocontrols.xml b/toolkit/content/widgets/videocontrols.xml index 1e9f5a777bc3..ffd22a17d68a 100644 --- a/toolkit/content/widgets/videocontrols.xml +++ b/toolkit/content/widgets/videocontrols.xml @@ -52,8 +52,8 @@ - - + + @@ -141,8 +141,9 @@ 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 @@ -156,8 +157,9 @@ 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, @@ -207,7 +209,7 @@ 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 @@ -217,6 +219,15 @@ 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() { @@ -227,6 +238,11 @@ 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; }, @@ -263,6 +279,7 @@ break; case "loadstart": this.maxCurrentTimeSeen = 0; + this.statusIcon.setAttribute("type", "throbber"); this.isAudioOnly = (this.video instanceof HTMLAudioElement); break; case "durationchange": @@ -297,17 +314,25 @@ 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!"); @@ -322,10 +347,10 @@ 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 ---"); }, @@ -403,12 +428,12 @@ 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) { @@ -543,7 +568,8 @@ 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"); diff --git a/toolkit/themes/pinstripe/global/jar.mn b/toolkit/themes/pinstripe/global/jar.mn index 873775066a18..99a0ba752483 100644 --- a/toolkit/themes/pinstripe/global/jar.mn +++ b/toolkit/themes/pinstripe/global/jar.mn @@ -179,6 +179,7 @@ classic.jar: + 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) diff --git a/toolkit/themes/pinstripe/global/media/error.png b/toolkit/themes/pinstripe/global/media/error.png new file mode 100644 index 000000000000..58e37283a73e Binary files /dev/null and b/toolkit/themes/pinstripe/global/media/error.png differ diff --git a/toolkit/themes/pinstripe/global/media/videocontrols.css b/toolkit/themes/pinstripe/global/media/videocontrols.css index 278f101e309f..9bc2f9d14d2a 100644 --- a/toolkit/themes/pinstripe/global/media/videocontrols.css +++ b/toolkit/themes/pinstripe/global/media/videocontrols.css @@ -80,13 +80,20 @@ 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; - height: 36px; +.statusIcon { + margin-bottom: 28px; /* same height as .controlBar, to keep icon centered above it */ width: 36px; - margin-bottom: 28px; /* same height as .controlBar, to keep throbber centered above it */ + height: 36px; +} + +.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; } diff --git a/toolkit/themes/winstripe/global/jar.mn b/toolkit/themes/winstripe/global/jar.mn index d0638830fdff..09f2713ab46b 100644 --- a/toolkit/themes/winstripe/global/jar.mn +++ b/toolkit/themes/winstripe/global/jar.mn @@ -153,6 +153,7 @@ classic.jar: 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) @@ -326,6 +327,7 @@ classic.jar: 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) diff --git a/toolkit/themes/winstripe/global/media/error.png b/toolkit/themes/winstripe/global/media/error.png new file mode 100644 index 000000000000..58e37283a73e Binary files /dev/null and b/toolkit/themes/winstripe/global/media/error.png differ diff --git a/toolkit/themes/winstripe/global/media/videocontrols.css b/toolkit/themes/winstripe/global/media/videocontrols.css index 203ef924a2f4..1b0f59fd2f3e 100644 --- a/toolkit/themes/winstripe/global/media/videocontrols.css +++ b/toolkit/themes/winstripe/global/media/videocontrols.css @@ -87,13 +87,20 @@ 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; - height: 36px; +.statusIcon { + margin-bottom: 28px; /* same height as .controlBar, to keep icon centered above it */ width: 36px; - margin-bottom: 28px; /* same height as .controlBar, to keep throbber centered above it */ + height: 36px; +} + +.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; }