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