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 "RenderMediaControlsChromium.h"
32 #include "GraphicsContext.h"
33 #include "HTMLMediaElement.h"
34 #include "HTMLNames.h"
35 #include "PaintInfo.h"
36 #include "TimeRanges.h"
42 typedef WTF::HashMap<const char*, Image*> MediaControlImageMap;
43 static MediaControlImageMap* gMediaControlImageMap = 0;
45 static Image* platformResource(const char* name)
47 if (!gMediaControlImageMap)
48 gMediaControlImageMap = new MediaControlImageMap();
49 if (Image* image = gMediaControlImageMap->get(name))
51 if (Image* image = Image::loadPlatformResource(name).leakRef()) {
52 gMediaControlImageMap->set(name, image);
59 static bool hasSource(const HTMLMediaElement* mediaElement)
61 return mediaElement->networkState() != HTMLMediaElement::NETWORK_EMPTY
62 && mediaElement->networkState() != HTMLMediaElement::NETWORK_NO_SOURCE;
65 static bool paintMediaButton(GraphicsContext* context, const IntRect& rect, Image* image)
67 context->drawImage(image, ColorSpaceDeviceRGB, rect);
71 static bool paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
73 HTMLMediaElement* mediaElement = toParentMediaElement(object);
77 static Image* soundLevel3 = platformResource("mediaplayerSoundLevel3");
78 static Image* soundLevel2 = platformResource("mediaplayerSoundLevel2");
79 static Image* soundLevel1 = platformResource("mediaplayerSoundLevel1");
80 static Image* soundLevel0 = platformResource("mediaplayerSoundLevel0");
81 static Image* soundDisabled = platformResource("mediaplayerSoundDisabled");
83 if (!hasSource(mediaElement) || !mediaElement->hasAudio())
84 return paintMediaButton(paintInfo.context, rect, soundDisabled);
86 if (mediaElement->muted() || mediaElement->volume() <= 0)
87 return paintMediaButton(paintInfo.context, rect, soundLevel0);
89 if (mediaElement->volume() <= 0.33)
90 return paintMediaButton(paintInfo.context, rect, soundLevel1);
92 if (mediaElement->volume() <= 0.66)
93 return paintMediaButton(paintInfo.context, rect, soundLevel2);
95 return paintMediaButton(paintInfo.context, rect, soundLevel3);
98 static bool paintMediaPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
100 HTMLMediaElement* mediaElement = toParentMediaElement(object);
104 static Image* mediaPlay = platformResource("mediaplayerPlay");
105 static Image* mediaPause = platformResource("mediaplayerPause");
106 static Image* mediaPlayDisabled = platformResource("mediaplayerPlayDisabled");
108 if (!hasSource(mediaElement))
109 return paintMediaButton(paintInfo.context, rect, mediaPlayDisabled);
111 return paintMediaButton(paintInfo.context, rect, mediaElement->canPlay() ? mediaPlay : mediaPause);
114 static bool paintMediaOverlayPlayButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
116 HTMLMediaElement* mediaElement = toParentMediaElement(object);
120 if (!hasSource(mediaElement) || !mediaElement->canPlay())
123 static Image* mediaOverlayPlay = platformResource("mediaplayerOverlayPlay");
124 return paintMediaButton(paintInfo.context, rect, mediaOverlayPlay);
127 static Image* getMediaSliderThumb()
129 static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb");
130 return mediaSliderThumb;
133 static void paintRoundedSliderBackground(const IntRect& rect, const RenderStyle* style, GraphicsContext* context)
135 int borderRadius = rect.height() / 2;
136 IntSize radii(borderRadius, borderRadius);
137 Color sliderBackgroundColor = Color(11, 11, 11);
139 context->fillRoundedRect(rect, radii, radii, radii, radii, sliderBackgroundColor, ColorSpaceDeviceRGB);
143 static void paintSliderRangeHighlight(const IntRect& rect, const RenderStyle* style, GraphicsContext* context, int startPosition, int endPosition, Color startColor, Color endColor)
145 // Calculate border radius; need to avoid being smaller than half the slider height
146 // because of https://bugs.webkit.org/show_bug.cgi?id=30143.
147 int borderRadius = rect.height() / 2;
148 IntSize radii(borderRadius, borderRadius);
150 // Calculate highlight rectangle and edge dimensions.
151 int startOffset = startPosition;
152 int endOffset = rect.width() - endPosition;
153 int rangeWidth = endPosition - startPosition;
158 // Make sure the range width is bigger than border radius at the edges to retain rounded corners.
159 if (startOffset < borderRadius && rangeWidth < borderRadius)
160 rangeWidth = borderRadius;
161 if (endOffset < borderRadius && rangeWidth < borderRadius) {
162 startPosition -= borderRadius - rangeWidth;
163 rangeWidth = borderRadius;
166 // Set rectangle to highlight range.
167 IntRect highlightRect = rect;
168 highlightRect.move(startOffset, 0);
169 highlightRect.setWidth(rangeWidth);
171 // Don't bother drawing an empty area.
172 if (highlightRect.isEmpty())
175 // Calculate white-grey gradient.
176 IntPoint sliderTopLeft = highlightRect.location();
177 IntPoint sliderBottomLeft = sliderTopLeft;
178 sliderBottomLeft.move(0, highlightRect.height());
179 RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderBottomLeft);
180 gradient->addColorStop(0.0, startColor);
181 gradient->addColorStop(1.0, endColor);
183 // Fill highlight rectangle with gradient, potentially rounded if on left or right edge.
185 context->setFillGradient(gradient);
187 if (startOffset < borderRadius && endOffset < borderRadius)
188 context->fillRoundedRect(highlightRect, radii, radii, radii, radii, startColor, ColorSpaceDeviceRGB);
189 else if (startOffset < borderRadius)
190 context->fillRoundedRect(highlightRect, radii, IntSize(0, 0), radii, IntSize(0, 0), startColor, ColorSpaceDeviceRGB);
191 else if (endOffset < borderRadius)
192 context->fillRoundedRect(highlightRect, IntSize(0, 0), radii, IntSize(0, 0), radii, startColor, ColorSpaceDeviceRGB);
194 context->fillRect(highlightRect);
199 const int mediaSliderThumbWidth = 32;
201 static bool paintMediaSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
203 HTMLMediaElement* mediaElement = toParentMediaElement(object);
207 RenderStyle* style = object->style();
208 GraphicsContext* context = paintInfo.context;
210 paintRoundedSliderBackground(rect, style, context);
212 // Draw the buffered range. Since the element may have multiple buffered ranges and it'd be
213 // distracting/'busy' to show all of them, show only the buffered range containing the current play head.
214 RefPtr<TimeRanges> bufferedTimeRanges = mediaElement->buffered();
215 float duration = mediaElement->duration();
216 float currentTime = mediaElement->currentTime();
217 if (isnan(duration) || isinf(duration) || !duration || isnan(currentTime))
220 for (unsigned i = 0; i < bufferedTimeRanges->length(); ++i) {
221 float start = bufferedTimeRanges->start(i, ASSERT_NO_EXCEPTION);
222 float end = bufferedTimeRanges->end(i, ASSERT_NO_EXCEPTION);
223 if (isnan(start) || isnan(end) || start > currentTime || end < currentTime)
225 int startPosition = int(start * rect.width() / duration);
226 int currentPosition = int(currentTime * rect.width() / duration);
227 int endPosition = int(end * rect.width() / duration);
229 // Add half the thumb width proportionally adjusted to the current painting position.
230 int thumbCenter = mediaSliderThumbWidth / 2;
231 int addWidth = thumbCenter * (1.0 - 2.0 * currentPosition / rect.width());
232 currentPosition += addWidth;
234 // Draw white-ish highlight before current time.
235 Color startColor = Color(195, 195, 195);
236 Color endColor = Color(217, 217, 217);
237 if (currentPosition > startPosition)
238 paintSliderRangeHighlight(rect, style, context, startPosition, currentPosition, startColor, endColor);
240 // Draw grey-ish highlight after current time.
241 startColor = Color(60, 60, 60);
242 endColor = Color(76, 76, 76);
244 if (endPosition > currentPosition)
245 paintSliderRangeHighlight(rect, style, context, currentPosition, endPosition, startColor, endColor);
253 static bool paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
255 ASSERT(object->node());
256 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost());
260 if (!hasSource(mediaElement))
263 Image* mediaSliderThumb = getMediaSliderThumb();
264 return paintMediaButton(paintInfo.context, rect, mediaSliderThumb);
267 const int mediaVolumeSliderThumbWidth = 24;
269 static bool paintMediaVolumeSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
271 HTMLMediaElement* mediaElement = toParentMediaElement(object);
275 GraphicsContext* context = paintInfo.context;
276 RenderStyle* style = object->style();
278 paintRoundedSliderBackground(rect, style, context);
280 // Calculate volume position for white background rectangle.
281 float volume = mediaElement->volume();
282 if (isnan(volume) || volume < 0)
286 if (!hasSource(mediaElement) || !mediaElement->hasAudio() || mediaElement->muted())
289 // Calculate the position relative to the center of the thumb.
292 float thumbCenter = mediaVolumeSliderThumbWidth / 2;
293 float zoomLevel = style->effectiveZoom();
294 float positionWidth = volume * (rect.width() - (zoomLevel * thumbCenter));
295 fillWidth = positionWidth + (zoomLevel * thumbCenter / 2);
298 Color startColor = Color(195, 195, 195);
299 Color endColor = Color(217, 217, 217);
301 paintSliderRangeHighlight(rect, style, context, 0.0, fillWidth, startColor, endColor);
306 static bool paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
308 ASSERT(object->node());
309 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost());
313 if (!hasSource(mediaElement) || !mediaElement->hasAudio())
316 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb");
317 return paintMediaButton(paintInfo.context, rect, mediaVolumeSliderThumb);
320 static bool paintMediaFullscreenButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
322 HTMLMediaElement* mediaElement = toParentMediaElement(object);
326 static Image* mediaFullscreenButton = platformResource("mediaplayerFullscreen");
327 return paintMediaButton(paintInfo.context, rect, mediaFullscreenButton);
330 bool RenderMediaControlsChromium::paintMediaControlsPart(MediaControlElementType part, RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect)
333 case MediaMuteButton:
334 case MediaUnMuteButton:
335 return paintMediaMuteButton(object, paintInfo, rect);
336 case MediaPauseButton:
337 case MediaPlayButton:
338 return paintMediaPlayButton(object, paintInfo, rect);
340 return paintMediaSlider(object, paintInfo, rect);
341 case MediaSliderThumb:
342 return paintMediaSliderThumb(object, paintInfo, rect);
343 case MediaVolumeSlider:
344 return paintMediaVolumeSlider(object, paintInfo, rect);
345 case MediaVolumeSliderThumb:
346 return paintMediaVolumeSliderThumb(object, paintInfo, rect);
347 case MediaEnterFullscreenButton:
348 case MediaExitFullscreenButton:
349 return paintMediaFullscreenButton(object, paintInfo, rect);
350 case MediaOverlayPlayButton:
351 return paintMediaOverlayPlayButton(object, paintInfo, rect);
352 case MediaVolumeSliderMuteButton:
353 case MediaSeekBackButton:
354 case MediaSeekForwardButton:
355 case MediaVolumeSliderContainer:
356 case MediaTimelineContainer:
357 case MediaCurrentTimeDisplay:
358 case MediaTimeRemainingDisplay:
359 case MediaControlsPanel:
360 case MediaRewindButton:
361 case MediaReturnToRealtimeButton:
362 case MediaStatusDisplay:
363 case MediaShowClosedCaptionsButton:
364 case MediaHideClosedCaptionsButton:
365 case MediaTextTrackDisplayContainer:
366 case MediaTextTrackDisplay:
367 case MediaFullScreenVolumeSlider:
368 case MediaFullScreenVolumeSliderThumb:
369 ASSERT_NOT_REACHED();
375 const int mediaSliderThumbHeight = 24;
376 const int mediaVolumeSliderThumbHeight = 24;
378 void RenderMediaControlsChromium::adjustMediaSliderThumbSize(RenderStyle* style)
380 static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb");
381 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb");
385 Image* thumbImage = 0;
386 if (style->appearance() == MediaSliderThumbPart) {
387 thumbImage = mediaSliderThumb;
388 width = mediaSliderThumbWidth;
389 height = mediaSliderThumbHeight;
390 } else if (style->appearance() == MediaVolumeSliderThumbPart) {
391 thumbImage = mediaVolumeSliderThumb;
392 width = mediaVolumeSliderThumbWidth;
393 height = mediaVolumeSliderThumbHeight;
396 float zoomLevel = style->effectiveZoom();
398 style->setWidth(Length(static_cast<int>(width * zoomLevel), Fixed));
399 style->setHeight(Length(static_cast<int>(height * zoomLevel), Fixed));
403 static String formatChromiumMediaControlsTime(float time, float duration)
407 if (!isfinite(duration))
409 int seconds = static_cast<int>(fabsf(time));
410 int hours = seconds / (60 * 60);
411 int minutes = (seconds / 60) % 60;
414 // duration defines the format of how the time is rendered
415 int durationSecs = static_cast<int>(fabsf(duration));
416 int durationHours = durationSecs / (60 * 60);
417 int durationMins = (durationSecs / 60) % 60;
419 if (durationHours || hours)
420 return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
421 if (durationMins > 9)
422 return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
424 return String::format("%s%01d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
427 String RenderMediaControlsChromium::formatMediaControlsTime(float time)
429 return formatChromiumMediaControlsTime(time, time);
432 String RenderMediaControlsChromium::formatMediaControlsCurrentTime(float currentTime, float duration)
434 return formatChromiumMediaControlsTime(currentTime, duration);
437 String RenderMediaControlsChromium::formatMediaControlsRemainingTime(float currentTime, float duration)
439 return formatChromiumMediaControlsTime(currentTime - duration, duration);
442 #endif // #if ENABLE(VIDEO)
444 } // namespace WebCore