Home | History | Annotate | Download | only in rendering
      1 /*
      2  * Copyright (C) 2009 Apple Inc.
      3  * Copyright (C) 2009 Google Inc.
      4  * All rights reserved.
      5  *
      6  * Redistribution and use in source and binary forms, with or without
      7  * modification, are permitted provided that the following conditions
      8  * are met:
      9  * 1. Redistributions of source code must retain the above copyright
     10  *    notice, this list of conditions and the following disclaimer.
     11  * 2. Redistributions in binary form must reproduce the above copyright
     12  *    notice, this list of conditions and the following disclaimer in the
     13  *    documentation and/or other materials provided with the distribution.
     14  *
     15  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
     16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
     19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     26  */
     27 
     28 #include "config.h"
     29 #include "core/rendering/RenderMediaControls.h"
     30 
     31 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
     32 #include "core/html/HTMLMediaElement.h"
     33 #include "core/html/TimeRanges.h"
     34 #include "core/rendering/PaintInfo.h"
     35 #include "platform/graphics/Gradient.h"
     36 #include "platform/graphics/GraphicsContext.h"
     37 
     38 namespace blink {
     39 
     40 typedef WTF::HashMap<const char*, Image*> MediaControlImageMap;
     41 static MediaControlImageMap* gMediaControlImageMap = 0;
     42 
     43 static Image* platformResource(const char* name)
     44 {
     45     if (!gMediaControlImageMap)
     46         gMediaControlImageMap = new MediaControlImageMap();
     47     if (Image* image = gMediaControlImageMap->get(name))
     48         return image;
     49     if (Image* image = Image::loadPlatformResource(name).leakRef()) {
     50         gMediaControlImageMap->set(name, image);
     51         return image;
     52     }
     53     ASSERT_NOT_REACHED();
     54     return 0;
     55 }
     56 
     57 static bool hasSource(const HTMLMediaElement* mediaElement)
     58 {
     59     return mediaElement->networkState() != HTMLMediaElement::NETWORK_EMPTY
     60         && mediaElement->networkState() != HTMLMediaElement::NETWORK_NO_SOURCE;
     61 }
     62 
     63 static bool paintMediaButton(GraphicsContext* context, const IntRect& rect, Image* image)
     64 {
     65     context->drawImage(image, rect);
     66     return true;
     67 }
     68 
     69 static bool paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
     70 {
     71     HTMLMediaElement* mediaElement = toParentMediaElement(object);
     72     if (!mediaElement)
     73         return false;
     74 
     75     static Image* soundLevel3 = platformResource("mediaplayerSoundLevel3");
     76     static Image* soundLevel2 = platformResource("mediaplayerSoundLevel2");
     77     static Image* soundLevel1 = platformResource("mediaplayerSoundLevel1");
     78     static Image* soundLevel0 = platformResource("mediaplayerSoundLevel0");
     79     static Image* soundDisabled = platformResource("mediaplayerSoundDisabled");
     80 
     81     if (!hasSource(mediaElement) || !mediaElement->hasAudio())
     82         return paintMediaButton(paintInfo.context, rect, soundDisabled);
     83 
     84     if (mediaElement->muted() || mediaElement->volume() <= 0)
     85         return paintMediaButton(paintInfo.context, rect, soundLevel0);
     86 
     87     if (mediaElement->volume() <= 0.33)
     88         return paintMediaButton(paintInfo.context, rect, soundLevel1);
     89 
     90     if (mediaElement->volume() <= 0.66)
     91         return paintMediaButton(paintInfo.context, rect, soundLevel2);
     92 
     93     return paintMediaButton(paintInfo.context, rect, soundLevel3);
     94 }
     95 
     96 static bool paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
     97 {
     98     HTMLMediaElement* mediaElement = toParentMediaElement(object);
     99     if (!mediaElement)
    100         return false;
    101 
    102     static Image* mediaPlay = platformResource("mediaplayerPlay");
    103     static Image* mediaPause = platformResource("mediaplayerPause");
    104     static Image* mediaPlayDisabled = platformResource("mediaplayerPlayDisabled");
    105 
    106     if (!hasSource(mediaElement))
    107         return paintMediaButton(paintInfo.context, rect, mediaPlayDisabled);
    108 
    109     return paintMediaButton(paintInfo.context, rect, mediaControlElementType(object->node()) == MediaPlayButton ? mediaPlay : mediaPause);
    110 }
    111 
    112 static bool paintMediaOverlayPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    113 {
    114     HTMLMediaElement* mediaElement = toParentMediaElement(object);
    115     if (!mediaElement)
    116         return false;
    117 
    118     if (!hasSource(mediaElement) || !mediaElement->togglePlayStateWillPlay())
    119         return false;
    120 
    121     static Image* mediaOverlayPlay = platformResource("mediaplayerOverlayPlay");
    122     return paintMediaButton(paintInfo.context, rect, mediaOverlayPlay);
    123 }
    124 
    125 static Image* getMediaSliderThumb()
    126 {
    127     static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb");
    128     return mediaSliderThumb;
    129 }
    130 
    131 static void paintRoundedSliderBackground(const IntRect& rect, const RenderStyle* style, GraphicsContext* context)
    132 {
    133     int borderRadius = rect.height() / 2;
    134     IntSize radii(borderRadius, borderRadius);
    135     Color sliderBackgroundColor = Color(11, 11, 11);
    136     context->fillRoundedRect(rect, radii, radii, radii, radii, sliderBackgroundColor);
    137 }
    138 
    139 static void paintSliderRangeHighlight(const IntRect& rect, const RenderStyle* style, GraphicsContext* context, int startPosition, int endPosition, Color startColor, Color endColor)
    140 {
    141     // Calculate border radius; need to avoid being smaller than half the slider height
    142     // because of https://bugs.webkit.org/show_bug.cgi?id=30143.
    143     int borderRadius = rect.height() / 2;
    144     IntSize radii(borderRadius, borderRadius);
    145 
    146     // Calculate highlight rectangle and edge dimensions.
    147     int startOffset = startPosition;
    148     int endOffset = rect.width() - endPosition;
    149     int rangeWidth = endPosition - startPosition;
    150 
    151     if (rangeWidth <= 0)
    152         return;
    153 
    154     // Make sure the range width is bigger than border radius at the edges to retain rounded corners.
    155     if (startOffset < borderRadius && rangeWidth < borderRadius)
    156         rangeWidth = borderRadius;
    157     if (endOffset < borderRadius && rangeWidth < borderRadius)
    158         rangeWidth = borderRadius;
    159 
    160     // Set rectangle to highlight range.
    161     IntRect highlightRect = rect;
    162     highlightRect.move(startOffset, 0);
    163     highlightRect.setWidth(rangeWidth);
    164 
    165     // Don't bother drawing an empty area.
    166     if (highlightRect.isEmpty())
    167         return;
    168 
    169     // Calculate white-grey gradient.
    170     IntPoint sliderTopLeft = highlightRect.location();
    171     IntPoint sliderBottomLeft = sliderTopLeft;
    172     sliderBottomLeft.move(0, highlightRect.height());
    173     RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderBottomLeft);
    174     gradient->addColorStop(0.0, startColor);
    175     gradient->addColorStop(1.0, endColor);
    176 
    177     // Fill highlight rectangle with gradient, potentially rounded if on left or right edge.
    178     context->save();
    179     context->setFillGradient(gradient);
    180 
    181     if (startOffset < borderRadius && endOffset < borderRadius)
    182         context->fillRoundedRect(highlightRect, radii, radii, radii, radii, startColor);
    183     else if (startOffset < borderRadius)
    184         context->fillRoundedRect(highlightRect, radii, IntSize(0, 0), radii, IntSize(0, 0), startColor);
    185     else if (endOffset < borderRadius)
    186         context->fillRoundedRect(highlightRect, IntSize(0, 0), radii, IntSize(0, 0), radii, startColor);
    187     else
    188         context->fillRect(highlightRect);
    189 
    190     context->restore();
    191 }
    192 
    193 const int mediaSliderThumbWidth = 32;
    194 
    195 static bool paintMediaSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    196 {
    197     HTMLMediaElement* mediaElement = toParentMediaElement(object);
    198     if (!mediaElement)
    199         return false;
    200 
    201     RenderStyle* style = object->style();
    202     GraphicsContext* context = paintInfo.context;
    203 
    204     paintRoundedSliderBackground(rect, style, context);
    205 
    206     // Draw the buffered range. Since the element may have multiple buffered ranges and it'd be
    207     // distracting/'busy' to show all of them, show only the buffered range containing the current play head.
    208     RefPtrWillBeRawPtr<TimeRanges> bufferedTimeRanges = mediaElement->buffered();
    209     float duration = mediaElement->duration();
    210     float currentTime = mediaElement->currentTime();
    211     if (std::isnan(duration) || std::isinf(duration) || !duration || std::isnan(currentTime))
    212         return true;
    213 
    214     for (unsigned i = 0; i < bufferedTimeRanges->length(); ++i) {
    215         float start = bufferedTimeRanges->start(i, ASSERT_NO_EXCEPTION);
    216         float end = bufferedTimeRanges->end(i, ASSERT_NO_EXCEPTION);
    217         if (std::isnan(start) || std::isnan(end) || start > currentTime || end < currentTime)
    218             continue;
    219         int startPosition = int(start * rect.width() / duration);
    220         int currentPosition = int(currentTime * rect.width() / duration);
    221         int endPosition = int(end * rect.width() / duration);
    222 
    223         // Add half the thumb width proportionally adjusted to the current painting position.
    224         int thumbCenter = mediaSliderThumbWidth / 2;
    225         int addWidth = thumbCenter * (1.0 - 2.0 * currentPosition / rect.width());
    226         currentPosition += addWidth;
    227 
    228         // Draw white-ish highlight before current time.
    229         Color startColor = Color(195, 195, 195);
    230         Color endColor = Color(217, 217, 217);
    231         if (currentPosition > startPosition)
    232             paintSliderRangeHighlight(rect, style, context, startPosition, currentPosition, startColor, endColor);
    233 
    234         // Draw grey-ish highlight after current time.
    235         startColor = Color(60, 60, 60);
    236         endColor = Color(76, 76, 76);
    237 
    238         if (endPosition > currentPosition)
    239             paintSliderRangeHighlight(rect, style, context, currentPosition, endPosition, startColor, endColor);
    240 
    241         return true;
    242     }
    243 
    244     return true;
    245 }
    246 
    247 static bool paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    248 {
    249     if (!object->node())
    250         return false;
    251 
    252     HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost());
    253     if (!mediaElement)
    254         return false;
    255 
    256     if (!hasSource(mediaElement))
    257         return true;
    258 
    259     Image* mediaSliderThumb = getMediaSliderThumb();
    260     return paintMediaButton(paintInfo.context, rect, mediaSliderThumb);
    261 }
    262 
    263 const int mediaVolumeSliderThumbWidth = 24;
    264 
    265 static bool paintMediaVolumeSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    266 {
    267     HTMLMediaElement* mediaElement = toParentMediaElement(object);
    268     if (!mediaElement)
    269         return false;
    270 
    271     GraphicsContext* context = paintInfo.context;
    272     RenderStyle* style = object->style();
    273 
    274     paintRoundedSliderBackground(rect, style, context);
    275 
    276     // Calculate volume position for white background rectangle.
    277     float volume = mediaElement->volume();
    278     if (std::isnan(volume) || volume < 0)
    279         return true;
    280     if (volume > 1)
    281         volume = 1;
    282     if (!hasSource(mediaElement) || !mediaElement->hasAudio() || mediaElement->muted())
    283         volume = 0;
    284 
    285     // Calculate the position relative to the center of the thumb.
    286     float fillWidth = 0;
    287     if (volume > 0) {
    288         float thumbCenter = mediaVolumeSliderThumbWidth / 2;
    289         float zoomLevel = style->effectiveZoom();
    290         float positionWidth = volume * (rect.width() - (zoomLevel * thumbCenter));
    291         fillWidth = positionWidth + (zoomLevel * thumbCenter / 2);
    292     }
    293 
    294     Color startColor = Color(195, 195, 195);
    295     Color endColor = Color(217, 217, 217);
    296 
    297     paintSliderRangeHighlight(rect, style, context, 0.0, fillWidth, startColor, endColor);
    298 
    299     return true;
    300 }
    301 
    302 static bool paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    303 {
    304     if (!object->node())
    305         return false;
    306 
    307     HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost());
    308     if (!mediaElement)
    309         return false;
    310 
    311     if (!hasSource(mediaElement) || !mediaElement->hasAudio())
    312         return true;
    313 
    314     static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb");
    315     return paintMediaButton(paintInfo.context, rect, mediaVolumeSliderThumb);
    316 }
    317 
    318 static bool paintMediaFullscreenButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    319 {
    320     HTMLMediaElement* mediaElement = toParentMediaElement(object);
    321     if (!mediaElement)
    322         return false;
    323 
    324     static Image* mediaFullscreenButton = platformResource("mediaplayerFullscreen");
    325     return paintMediaButton(paintInfo.context, rect, mediaFullscreenButton);
    326 }
    327 
    328 static bool paintMediaToggleClosedCaptionsButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    329 {
    330     HTMLMediaElement* mediaElement = toParentMediaElement(object);
    331     if (!mediaElement)
    332         return false;
    333 
    334     static Image* mediaClosedCaptionButton = platformResource("mediaplayerClosedCaption");
    335     static Image* mediaClosedCaptionButtonDisabled = platformResource("mediaplayerClosedCaptionDisabled");
    336 
    337     if (mediaElement->closedCaptionsVisible())
    338         return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButton);
    339 
    340     return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButtonDisabled);
    341 }
    342 static bool paintMediaCastButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    343 {
    344     HTMLMediaElement* mediaElement = toParentMediaElement(object);
    345     if (!mediaElement)
    346         return false;
    347 
    348     static Image* mediaCastOnButton = platformResource("mediaplayerCastOn");
    349     static Image* mediaCastOffButton = platformResource("mediaplayerCastOff");
    350 
    351     if (mediaElement->isPlayingRemotely()) {
    352         return paintMediaButton(paintInfo.context, rect, mediaCastOnButton);
    353     }
    354 
    355     return paintMediaButton(paintInfo.context, rect, mediaCastOffButton);
    356 
    357 }
    358 
    359 bool RenderMediaControls::paintMediaControlsPart(MediaControlElementType part, RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
    360 {
    361     switch (part) {
    362     case MediaMuteButton:
    363     case MediaUnMuteButton:
    364         return paintMediaMuteButton(object, paintInfo, rect);
    365     case MediaPauseButton:
    366     case MediaPlayButton:
    367         return paintMediaPlayButton(object, paintInfo, rect);
    368     case MediaShowClosedCaptionsButton:
    369         return paintMediaToggleClosedCaptionsButton(object, paintInfo, rect);
    370     case MediaSlider:
    371         return paintMediaSlider(object, paintInfo, rect);
    372     case MediaSliderThumb:
    373         return paintMediaSliderThumb(object, paintInfo, rect);
    374     case MediaVolumeSlider:
    375         return paintMediaVolumeSlider(object, paintInfo, rect);
    376     case MediaVolumeSliderThumb:
    377         return paintMediaVolumeSliderThumb(object, paintInfo, rect);
    378     case MediaEnterFullscreenButton:
    379     case MediaExitFullscreenButton:
    380         return paintMediaFullscreenButton(object, paintInfo, rect);
    381     case MediaOverlayPlayButton:
    382         return paintMediaOverlayPlayButton(object, paintInfo, rect);
    383     case MediaCastOffButton:
    384     case MediaCastOnButton:
    385     case MediaOverlayCastOffButton:
    386     case MediaOverlayCastOnButton:
    387         return paintMediaCastButton(object, paintInfo, rect);
    388     case MediaVolumeSliderContainer:
    389     case MediaTimelineContainer:
    390     case MediaCurrentTimeDisplay:
    391     case MediaTimeRemainingDisplay:
    392     case MediaControlsPanel:
    393     case MediaStatusDisplay:
    394     case MediaHideClosedCaptionsButton:
    395     case MediaTextTrackDisplayContainer:
    396     case MediaTextTrackDisplay:
    397     case MediaFullScreenVolumeSlider:
    398     case MediaFullScreenVolumeSliderThumb:
    399         ASSERT_NOT_REACHED();
    400         break;
    401     }
    402     return false;
    403 }
    404 
    405 const int mediaSliderThumbHeight = 24;
    406 const int mediaVolumeSliderThumbHeight = 24;
    407 
    408 void RenderMediaControls::adjustMediaSliderThumbSize(RenderStyle* style)
    409 {
    410     static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb");
    411     static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb");
    412     int width = 0;
    413     int height = 0;
    414 
    415     Image* thumbImage = 0;
    416     if (style->appearance() == MediaSliderThumbPart) {
    417         thumbImage = mediaSliderThumb;
    418         width = mediaSliderThumbWidth;
    419         height = mediaSliderThumbHeight;
    420     } else if (style->appearance() == MediaVolumeSliderThumbPart) {
    421         thumbImage = mediaVolumeSliderThumb;
    422         width = mediaVolumeSliderThumbWidth;
    423         height = mediaVolumeSliderThumbHeight;
    424     }
    425 
    426     float zoomLevel = style->effectiveZoom();
    427     if (thumbImage) {
    428         style->setWidth(Length(static_cast<int>(width * zoomLevel), Fixed));
    429         style->setHeight(Length(static_cast<int>(height * zoomLevel), Fixed));
    430     }
    431 }
    432 
    433 static String formatChromiumMediaControlsTime(float time, float duration)
    434 {
    435     if (!std::isfinite(time))
    436         time = 0;
    437     if (!std::isfinite(duration))
    438         duration = 0;
    439     int seconds = static_cast<int>(fabsf(time));
    440     int hours = seconds / (60 * 60);
    441     int minutes = (seconds / 60) % 60;
    442     seconds %= 60;
    443 
    444     // duration defines the format of how the time is rendered
    445     int durationSecs = static_cast<int>(fabsf(duration));
    446     int durationHours = durationSecs / (60 * 60);
    447     int durationMins = (durationSecs / 60) % 60;
    448 
    449     if (durationHours || hours)
    450         return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
    451     if (durationMins > 9)
    452         return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
    453 
    454     return String::format("%s%01d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
    455 }
    456 
    457 String RenderMediaControls::formatMediaControlsTime(float time)
    458 {
    459     return formatChromiumMediaControlsTime(time, time);
    460 }
    461 
    462 String RenderMediaControls::formatMediaControlsCurrentTime(float currentTime, float duration)
    463 {
    464     return formatChromiumMediaControlsTime(currentTime, duration);
    465 }
    466 
    467 } // namespace blink
    468