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/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 WebCore { 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 RefPtr<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 ASSERT(object->node()); 250 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost()); 251 if (!mediaElement) 252 return false; 253 254 if (!hasSource(mediaElement)) 255 return true; 256 257 Image* mediaSliderThumb = getMediaSliderThumb(); 258 return paintMediaButton(paintInfo.context, rect, mediaSliderThumb); 259 } 260 261 const int mediaVolumeSliderThumbWidth = 24; 262 263 static bool paintMediaVolumeSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 264 { 265 HTMLMediaElement* mediaElement = toParentMediaElement(object); 266 if (!mediaElement) 267 return false; 268 269 GraphicsContext* context = paintInfo.context; 270 RenderStyle* style = object->style(); 271 272 paintRoundedSliderBackground(rect, style, context); 273 274 // Calculate volume position for white background rectangle. 275 float volume = mediaElement->volume(); 276 if (std::isnan(volume) || volume < 0) 277 return true; 278 if (volume > 1) 279 volume = 1; 280 if (!hasSource(mediaElement) || !mediaElement->hasAudio() || mediaElement->muted()) 281 volume = 0; 282 283 // Calculate the position relative to the center of the thumb. 284 float fillWidth = 0; 285 if (volume > 0) { 286 float thumbCenter = mediaVolumeSliderThumbWidth / 2; 287 float zoomLevel = style->effectiveZoom(); 288 float positionWidth = volume * (rect.width() - (zoomLevel * thumbCenter)); 289 fillWidth = positionWidth + (zoomLevel * thumbCenter / 2); 290 } 291 292 Color startColor = Color(195, 195, 195); 293 Color endColor = Color(217, 217, 217); 294 295 paintSliderRangeHighlight(rect, style, context, 0.0, fillWidth, startColor, endColor); 296 297 return true; 298 } 299 300 static bool paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 301 { 302 ASSERT(object->node()); 303 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost()); 304 if (!mediaElement) 305 return false; 306 307 if (!hasSource(mediaElement) || !mediaElement->hasAudio()) 308 return true; 309 310 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb"); 311 return paintMediaButton(paintInfo.context, rect, mediaVolumeSliderThumb); 312 } 313 314 static bool paintMediaFullscreenButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 315 { 316 HTMLMediaElement* mediaElement = toParentMediaElement(object); 317 if (!mediaElement) 318 return false; 319 320 static Image* mediaFullscreenButton = platformResource("mediaplayerFullscreen"); 321 return paintMediaButton(paintInfo.context, rect, mediaFullscreenButton); 322 } 323 324 static bool paintMediaToggleClosedCaptionsButton(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 325 { 326 HTMLMediaElement* mediaElement = toParentMediaElement(object); 327 if (!mediaElement) 328 return false; 329 330 static Image* mediaClosedCaptionButton = platformResource("mediaplayerClosedCaption"); 331 static Image* mediaClosedCaptionButtonDisabled = platformResource("mediaplayerClosedCaptionDisabled"); 332 333 if (mediaElement->closedCaptionsVisible()) 334 return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButton); 335 336 return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButtonDisabled); 337 } 338 339 340 bool RenderMediaControls::paintMediaControlsPart(MediaControlElementType part, RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 341 { 342 switch (part) { 343 case MediaMuteButton: 344 case MediaUnMuteButton: 345 return paintMediaMuteButton(object, paintInfo, rect); 346 case MediaPauseButton: 347 case MediaPlayButton: 348 return paintMediaPlayButton(object, paintInfo, rect); 349 case MediaShowClosedCaptionsButton: 350 return paintMediaToggleClosedCaptionsButton(object, paintInfo, rect); 351 case MediaSlider: 352 return paintMediaSlider(object, paintInfo, rect); 353 case MediaSliderThumb: 354 return paintMediaSliderThumb(object, paintInfo, rect); 355 case MediaVolumeSlider: 356 return paintMediaVolumeSlider(object, paintInfo, rect); 357 case MediaVolumeSliderThumb: 358 return paintMediaVolumeSliderThumb(object, paintInfo, rect); 359 case MediaEnterFullscreenButton: 360 case MediaExitFullscreenButton: 361 return paintMediaFullscreenButton(object, paintInfo, rect); 362 case MediaOverlayPlayButton: 363 return paintMediaOverlayPlayButton(object, paintInfo, rect); 364 case MediaVolumeSliderContainer: 365 case MediaTimelineContainer: 366 case MediaCurrentTimeDisplay: 367 case MediaTimeRemainingDisplay: 368 case MediaControlsPanel: 369 case MediaStatusDisplay: 370 case MediaHideClosedCaptionsButton: 371 case MediaTextTrackDisplayContainer: 372 case MediaTextTrackDisplay: 373 case MediaFullScreenVolumeSlider: 374 case MediaFullScreenVolumeSliderThumb: 375 ASSERT_NOT_REACHED(); 376 break; 377 } 378 return false; 379 } 380 381 const int mediaSliderThumbHeight = 24; 382 const int mediaVolumeSliderThumbHeight = 24; 383 384 void RenderMediaControls::adjustMediaSliderThumbSize(RenderStyle* style) 385 { 386 static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb"); 387 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb"); 388 int width = 0; 389 int height = 0; 390 391 Image* thumbImage = 0; 392 if (style->appearance() == MediaSliderThumbPart) { 393 thumbImage = mediaSliderThumb; 394 width = mediaSliderThumbWidth; 395 height = mediaSliderThumbHeight; 396 } else if (style->appearance() == MediaVolumeSliderThumbPart) { 397 thumbImage = mediaVolumeSliderThumb; 398 width = mediaVolumeSliderThumbWidth; 399 height = mediaVolumeSliderThumbHeight; 400 } 401 402 float zoomLevel = style->effectiveZoom(); 403 if (thumbImage) { 404 style->setWidth(Length(static_cast<int>(width * zoomLevel), Fixed)); 405 style->setHeight(Length(static_cast<int>(height * zoomLevel), Fixed)); 406 } 407 } 408 409 static String formatChromiumMediaControlsTime(float time, float duration) 410 { 411 if (!std::isfinite(time)) 412 time = 0; 413 if (!std::isfinite(duration)) 414 duration = 0; 415 int seconds = static_cast<int>(fabsf(time)); 416 int hours = seconds / (60 * 60); 417 int minutes = (seconds / 60) % 60; 418 seconds %= 60; 419 420 // duration defines the format of how the time is rendered 421 int durationSecs = static_cast<int>(fabsf(duration)); 422 int durationHours = durationSecs / (60 * 60); 423 int durationMins = (durationSecs / 60) % 60; 424 425 if (durationHours || hours) 426 return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds); 427 if (durationMins > 9) 428 return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds); 429 430 return String::format("%s%01d:%02d", (time < 0 ? "-" : ""), minutes, seconds); 431 } 432 433 String RenderMediaControls::formatMediaControlsTime(float time) 434 { 435 return formatChromiumMediaControlsTime(time, time); 436 } 437 438 String RenderMediaControls::formatMediaControlsCurrentTime(float currentTime, float duration) 439 { 440 return formatChromiumMediaControlsTime(currentTime, duration); 441 } 442 443 } // namespace WebCore 444