2 * Copyright (C) 2009 Apple Inc.
3 * Copyright (C) 2009 Google Inc.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
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.
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.
29 #include "core/rendering/RenderMediaControls.h"
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"
40 typedef WTF::HashMap<const char*, Image*> MediaControlImageMap;
41 static MediaControlImageMap* gMediaControlImageMap = 0;
43 static Image* platformResource(const char* name)
45 if (!gMediaControlImageMap)
46 gMediaControlImageMap = new MediaControlImageMap();
47 if (Image* image = gMediaControlImageMap->get(name))
49 if (Image* image = Image::loadPlatformResource(name).leakRef()) {
50 gMediaControlImageMap->set(name, image);
57 static bool hasSource(const HTMLMediaElement* mediaElement)
59 return mediaElement->networkState() != HTMLMediaElement::NETWORK_EMPTY
60 && mediaElement->networkState() != HTMLMediaElement::NETWORK_NO_SOURCE;
63 static bool paintMediaButton(GraphicsContext* context, const IntRect& rect, Image* image)
65 context->drawImage(image, rect);
69 static bool paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
71 HTMLMediaElement* mediaElement = toParentMediaElement(object);
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");
81 if (!hasSource(mediaElement) || !mediaElement->hasAudio())
82 return paintMediaButton(paintInfo.context, rect, soundDisabled);
84 if (mediaElement->muted() || mediaElement->volume() <= 0)
85 return paintMediaButton(paintInfo.context, rect, soundLevel0);
87 if (mediaElement->volume() <= 0.33)
88 return paintMediaButton(paintInfo.context, rect, soundLevel1);
90 if (mediaElement->volume() <= 0.66)
91 return paintMediaButton(paintInfo.context, rect, soundLevel2);
93 return paintMediaButton(paintInfo.context, rect, soundLevel3);
96 static bool paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
98 HTMLMediaElement* mediaElement = toParentMediaElement(object);
102 static Image* mediaPlay = platformResource("mediaplayerPlay");
103 static Image* mediaPause = platformResource("mediaplayerPause");
104 static Image* mediaPlayDisabled = platformResource("mediaplayerPlayDisabled");
106 if (!hasSource(mediaElement))
107 return paintMediaButton(paintInfo.context, rect, mediaPlayDisabled);
109 return paintMediaButton(paintInfo.context, rect, mediaControlElementType(object->node()) == MediaPlayButton ? mediaPlay : mediaPause);
112 static bool paintMediaOverlayPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
114 HTMLMediaElement* mediaElement = toParentMediaElement(object);
118 if (!hasSource(mediaElement) || !mediaElement->togglePlayStateWillPlay())
121 static Image* mediaOverlayPlay = platformResource("mediaplayerOverlayPlay");
122 return paintMediaButton(paintInfo.context, rect, mediaOverlayPlay);
125 static Image* getMediaSliderThumb()
127 static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb");
128 return mediaSliderThumb;
131 static void paintRoundedSliderBackground(const IntRect& rect, const RenderStyle* style, GraphicsContext* context)
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);
139 static void paintSliderRangeHighlight(const IntRect& rect, const RenderStyle* style, GraphicsContext* context, int startPosition, int endPosition, Color startColor, Color endColor)
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);
146 // Calculate highlight rectangle and edge dimensions.
147 int startOffset = startPosition;
148 int endOffset = rect.width() - endPosition;
149 int rangeWidth = endPosition - startPosition;
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;
160 // Set rectangle to highlight range.
161 IntRect highlightRect = rect;
162 highlightRect.move(startOffset, 0);
163 highlightRect.setWidth(rangeWidth);
165 // Don't bother drawing an empty area.
166 if (highlightRect.isEmpty())
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);
177 // Fill highlight rectangle with gradient, potentially rounded if on left or right edge.
179 context->setFillGradient(gradient);
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);
188 context->fillRect(highlightRect);
193 const int mediaSliderThumbWidth = 32;
195 static bool paintMediaSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
197 HTMLMediaElement* mediaElement = toParentMediaElement(object);
201 RenderStyle* style = object->style();
202 GraphicsContext* context = paintInfo.context;
204 paintRoundedSliderBackground(rect, style, context);
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))
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)
219 int startPosition = int(start * rect.width() / duration);
220 int currentPosition = int(currentTime * rect.width() / duration);
221 int endPosition = int(end * rect.width() / duration);
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;
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);
234 // Draw grey-ish highlight after current time.
235 startColor = Color(60, 60, 60);
236 endColor = Color(76, 76, 76);
238 if (endPosition > currentPosition)
239 paintSliderRangeHighlight(rect, style, context, currentPosition, endPosition, startColor, endColor);
247 static bool paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
252 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost());
256 if (!hasSource(mediaElement))
259 Image* mediaSliderThumb = getMediaSliderThumb();
260 return paintMediaButton(paintInfo.context, rect, mediaSliderThumb);
263 const int mediaVolumeSliderThumbWidth = 24;
265 static bool paintMediaVolumeSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
267 HTMLMediaElement* mediaElement = toParentMediaElement(object);
271 GraphicsContext* context = paintInfo.context;
272 RenderStyle* style = object->style();
274 paintRoundedSliderBackground(rect, style, context);
276 // Calculate volume position for white background rectangle.
277 float volume = mediaElement->volume();
278 if (std::isnan(volume) || volume < 0)
282 if (!hasSource(mediaElement) || !mediaElement->hasAudio() || mediaElement->muted())
285 // Calculate the position relative to the center of the thumb.
288 float thumbCenter = mediaVolumeSliderThumbWidth / 2;
289 float zoomLevel = style->effectiveZoom();
290 float positionWidth = volume * (rect.width() - (zoomLevel * thumbCenter));
291 fillWidth = positionWidth + (zoomLevel * thumbCenter / 2);
294 Color startColor = Color(195, 195, 195);
295 Color endColor = Color(217, 217, 217);
297 paintSliderRangeHighlight(rect, style, context, 0.0, fillWidth, startColor, endColor);
302 static bool paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
307 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost());
311 if (!hasSource(mediaElement) || !mediaElement->hasAudio())
314 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb");
315 return paintMediaButton(paintInfo.context, rect, mediaVolumeSliderThumb);
318 static bool paintMediaFullscreenButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
320 HTMLMediaElement* mediaElement = toParentMediaElement(object);
324 static Image* mediaFullscreenButton = platformResource("mediaplayerFullscreen");
325 return paintMediaButton(paintInfo.context, rect, mediaFullscreenButton);
328 static bool paintMediaToggleClosedCaptionsButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
330 HTMLMediaElement* mediaElement = toParentMediaElement(object);
334 static Image* mediaClosedCaptionButton = platformResource("mediaplayerClosedCaption");
335 static Image* mediaClosedCaptionButtonDisabled = platformResource("mediaplayerClosedCaptionDisabled");
337 if (mediaElement->closedCaptionsVisible())
338 return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButton);
340 return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButtonDisabled);
342 static bool paintMediaCastButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
344 HTMLMediaElement* mediaElement = toParentMediaElement(object);
348 static Image* mediaCastOnButton = platformResource("mediaplayerCastOn");
349 static Image* mediaCastOffButton = platformResource("mediaplayerCastOff");
351 if (mediaElement->isPlayingRemotely()) {
352 return paintMediaButton(paintInfo.context, rect, mediaCastOnButton);
355 return paintMediaButton(paintInfo.context, rect, mediaCastOffButton);
359 bool RenderMediaControls::paintMediaControlsPart(MediaControlElementType part, RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
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);
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();
405 const int mediaSliderThumbHeight = 24;
406 const int mediaVolumeSliderThumbHeight = 24;
408 void RenderMediaControls::adjustMediaSliderThumbSize(RenderStyle* style)
410 static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb");
411 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb");
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;
426 float zoomLevel = style->effectiveZoom();
428 style->setWidth(Length(static_cast<int>(width * zoomLevel), Fixed));
429 style->setHeight(Length(static_cast<int>(height * zoomLevel), Fixed));
433 static String formatChromiumMediaControlsTime(float time, float duration)
435 if (!std::isfinite(time))
437 if (!std::isfinite(duration))
439 int seconds = static_cast<int>(fabsf(time));
440 int hours = seconds / (60 * 60);
441 int minutes = (seconds / 60) % 60;
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;
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);
454 return String::format("%s%01d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
457 String RenderMediaControls::formatMediaControlsTime(float time)
459 return formatChromiumMediaControlsTime(time, time);
462 String RenderMediaControls::formatMediaControlsCurrentTime(float currentTime, float duration)
464 return formatChromiumMediaControlsTime(currentTime, duration);