Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / rendering / RenderMediaControls.cpp
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