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, mediaElement->canPlay() ? 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->canPlay()) 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->save(); 137 context->fillRoundedRect(rect, radii, radii, radii, radii, sliderBackgroundColor); 138 context->restore(); 139 } 140 141 static void paintSliderRangeHighlight(const IntRect& rect, const RenderStyle* style, GraphicsContext* context, int startPosition, int endPosition, Color startColor, Color endColor) 142 { 143 // Calculate border radius; need to avoid being smaller than half the slider height 144 // because of https://bugs.webkit.org/show_bug.cgi?id=30143. 145 int borderRadius = rect.height() / 2; 146 IntSize radii(borderRadius, borderRadius); 147 148 // Calculate highlight rectangle and edge dimensions. 149 int startOffset = startPosition; 150 int endOffset = rect.width() - endPosition; 151 int rangeWidth = endPosition - startPosition; 152 153 if (rangeWidth <= 0) 154 return; 155 156 // Make sure the range width is bigger than border radius at the edges to retain rounded corners. 157 if (startOffset < borderRadius && rangeWidth < borderRadius) 158 rangeWidth = borderRadius; 159 if (endOffset < borderRadius && rangeWidth < borderRadius) { 160 startPosition -= borderRadius - rangeWidth; 161 rangeWidth = borderRadius; 162 } 163 164 // Set rectangle to highlight range. 165 IntRect highlightRect = rect; 166 highlightRect.move(startOffset, 0); 167 highlightRect.setWidth(rangeWidth); 168 169 // Don't bother drawing an empty area. 170 if (highlightRect.isEmpty()) 171 return; 172 173 // Calculate white-grey gradient. 174 IntPoint sliderTopLeft = highlightRect.location(); 175 IntPoint sliderBottomLeft = sliderTopLeft; 176 sliderBottomLeft.move(0, highlightRect.height()); 177 RefPtr<Gradient> gradient = Gradient::create(sliderTopLeft, sliderBottomLeft); 178 gradient->addColorStop(0.0, startColor); 179 gradient->addColorStop(1.0, endColor); 180 181 // Fill highlight rectangle with gradient, potentially rounded if on left or right edge. 182 context->save(); 183 context->setFillGradient(gradient); 184 185 if (startOffset < borderRadius && endOffset < borderRadius) 186 context->fillRoundedRect(highlightRect, radii, radii, radii, radii, startColor); 187 else if (startOffset < borderRadius) 188 context->fillRoundedRect(highlightRect, radii, IntSize(0, 0), radii, IntSize(0, 0), startColor); 189 else if (endOffset < borderRadius) 190 context->fillRoundedRect(highlightRect, IntSize(0, 0), radii, IntSize(0, 0), radii, startColor); 191 else 192 context->fillRect(highlightRect); 193 194 context->restore(); 195 } 196 197 const int mediaSliderThumbWidth = 32; 198 199 static bool paintMediaSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 200 { 201 HTMLMediaElement* mediaElement = toParentMediaElement(object); 202 if (!mediaElement) 203 return false; 204 205 RenderStyle* style = object->style(); 206 GraphicsContext* context = paintInfo.context; 207 208 paintRoundedSliderBackground(rect, style, context); 209 210 // Draw the buffered range. Since the element may have multiple buffered ranges and it'd be 211 // distracting/'busy' to show all of them, show only the buffered range containing the current play head. 212 RefPtr<TimeRanges> bufferedTimeRanges = mediaElement->buffered(); 213 float duration = mediaElement->duration(); 214 float currentTime = mediaElement->currentTime(); 215 if (std::isnan(duration) || std::isinf(duration) || !duration || std::isnan(currentTime)) 216 return true; 217 218 for (unsigned i = 0; i < bufferedTimeRanges->length(); ++i) { 219 float start = bufferedTimeRanges->start(i, ASSERT_NO_EXCEPTION); 220 float end = bufferedTimeRanges->end(i, ASSERT_NO_EXCEPTION); 221 if (std::isnan(start) || std::isnan(end) || start > currentTime || end < currentTime) 222 continue; 223 int startPosition = int(start * rect.width() / duration); 224 int currentPosition = int(currentTime * rect.width() / duration); 225 int endPosition = int(end * rect.width() / duration); 226 227 // Add half the thumb width proportionally adjusted to the current painting position. 228 int thumbCenter = mediaSliderThumbWidth / 2; 229 int addWidth = thumbCenter * (1.0 - 2.0 * currentPosition / rect.width()); 230 currentPosition += addWidth; 231 232 // Draw white-ish highlight before current time. 233 Color startColor = Color(195, 195, 195); 234 Color endColor = Color(217, 217, 217); 235 if (currentPosition > startPosition) 236 paintSliderRangeHighlight(rect, style, context, startPosition, currentPosition, startColor, endColor); 237 238 // Draw grey-ish highlight after current time. 239 startColor = Color(60, 60, 60); 240 endColor = Color(76, 76, 76); 241 242 if (endPosition > currentPosition) 243 paintSliderRangeHighlight(rect, style, context, currentPosition, endPosition, startColor, endColor); 244 245 return true; 246 } 247 248 return true; 249 } 250 251 static bool paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 252 { 253 ASSERT(object->node()); 254 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadowHost()); 255 if (!mediaElement) 256 return false; 257 258 if (!hasSource(mediaElement)) 259 return true; 260 261 Image* mediaSliderThumb = getMediaSliderThumb(); 262 return paintMediaButton(paintInfo.context, rect, mediaSliderThumb); 263 } 264 265 const int mediaVolumeSliderThumbWidth = 24; 266 267 static bool paintMediaVolumeSlider(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 268 { 269 HTMLMediaElement* mediaElement = toParentMediaElement(object); 270 if (!mediaElement) 271 return false; 272 273 GraphicsContext* context = paintInfo.context; 274 RenderStyle* style = object->style(); 275 276 paintRoundedSliderBackground(rect, style, context); 277 278 // Calculate volume position for white background rectangle. 279 float volume = mediaElement->volume(); 280 if (std::isnan(volume) || volume < 0) 281 return true; 282 if (volume > 1) 283 volume = 1; 284 if (!hasSource(mediaElement) || !mediaElement->hasAudio() || mediaElement->muted()) 285 volume = 0; 286 287 // Calculate the position relative to the center of the thumb. 288 float fillWidth = 0; 289 if (volume > 0) { 290 float thumbCenter = mediaVolumeSliderThumbWidth / 2; 291 float zoomLevel = style->effectiveZoom(); 292 float positionWidth = volume * (rect.width() - (zoomLevel * thumbCenter)); 293 fillWidth = positionWidth + (zoomLevel * thumbCenter / 2); 294 } 295 296 Color startColor = Color(195, 195, 195); 297 Color endColor = Color(217, 217, 217); 298 299 paintSliderRangeHighlight(rect, style, context, 0.0, fillWidth, startColor, endColor); 300 301 return true; 302 } 303 304 static bool paintMediaVolumeSliderThumb(RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 305 { 306 ASSERT(object->node()); 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 343 344 bool RenderMediaControls::paintMediaControlsPart(MediaControlElementType part, RenderObject* object, const PaintInfo& paintInfo, const IntRect& rect) 345 { 346 switch (part) { 347 case MediaMuteButton: 348 case MediaUnMuteButton: 349 return paintMediaMuteButton(object, paintInfo, rect); 350 case MediaPauseButton: 351 case MediaPlayButton: 352 return paintMediaPlayButton(object, paintInfo, rect); 353 case MediaShowClosedCaptionsButton: 354 return paintMediaToggleClosedCaptionsButton(object, paintInfo, rect); 355 case MediaSlider: 356 return paintMediaSlider(object, paintInfo, rect); 357 case MediaSliderThumb: 358 return paintMediaSliderThumb(object, paintInfo, rect); 359 case MediaVolumeSlider: 360 return paintMediaVolumeSlider(object, paintInfo, rect); 361 case MediaVolumeSliderThumb: 362 return paintMediaVolumeSliderThumb(object, paintInfo, rect); 363 case MediaEnterFullscreenButton: 364 case MediaExitFullscreenButton: 365 return paintMediaFullscreenButton(object, paintInfo, rect); 366 case MediaOverlayPlayButton: 367 return paintMediaOverlayPlayButton(object, paintInfo, rect); 368 case MediaVolumeSliderMuteButton: 369 case MediaSeekBackButton: 370 case MediaSeekForwardButton: 371 case MediaVolumeSliderContainer: 372 case MediaTimelineContainer: 373 case MediaCurrentTimeDisplay: 374 case MediaTimeRemainingDisplay: 375 case MediaControlsPanel: 376 case MediaRewindButton: 377 case MediaReturnToRealtimeButton: 378 case MediaStatusDisplay: 379 case MediaHideClosedCaptionsButton: 380 case MediaTextTrackDisplayContainer: 381 case MediaTextTrackDisplay: 382 case MediaFullScreenVolumeSlider: 383 case MediaFullScreenVolumeSliderThumb: 384 ASSERT_NOT_REACHED(); 385 break; 386 } 387 return false; 388 } 389 390 const int mediaSliderThumbHeight = 24; 391 const int mediaVolumeSliderThumbHeight = 24; 392 393 void RenderMediaControls::adjustMediaSliderThumbSize(RenderStyle* style) 394 { 395 static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb"); 396 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSliderThumb"); 397 int width = 0; 398 int height = 0; 399 400 Image* thumbImage = 0; 401 if (style->appearance() == MediaSliderThumbPart) { 402 thumbImage = mediaSliderThumb; 403 width = mediaSliderThumbWidth; 404 height = mediaSliderThumbHeight; 405 } else if (style->appearance() == MediaVolumeSliderThumbPart) { 406 thumbImage = mediaVolumeSliderThumb; 407 width = mediaVolumeSliderThumbWidth; 408 height = mediaVolumeSliderThumbHeight; 409 } 410 411 float zoomLevel = style->effectiveZoom(); 412 if (thumbImage) { 413 style->setWidth(Length(static_cast<int>(width * zoomLevel), Fixed)); 414 style->setHeight(Length(static_cast<int>(height * zoomLevel), Fixed)); 415 } 416 } 417 418 static String formatChromiumMediaControlsTime(float time, float duration) 419 { 420 if (!std::isfinite(time)) 421 time = 0; 422 if (!std::isfinite(duration)) 423 duration = 0; 424 int seconds = static_cast<int>(fabsf(time)); 425 int hours = seconds / (60 * 60); 426 int minutes = (seconds / 60) % 60; 427 seconds %= 60; 428 429 // duration defines the format of how the time is rendered 430 int durationSecs = static_cast<int>(fabsf(duration)); 431 int durationHours = durationSecs / (60 * 60); 432 int durationMins = (durationSecs / 60) % 60; 433 434 if (durationHours || hours) 435 return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds); 436 if (durationMins > 9) 437 return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds); 438 439 return String::format("%s%01d:%02d", (time < 0 ? "-" : ""), minutes, seconds); 440 } 441 442 String RenderMediaControls::formatMediaControlsTime(float time) 443 { 444 return formatChromiumMediaControlsTime(time, time); 445 } 446 447 String RenderMediaControls::formatMediaControlsCurrentTime(float currentTime, float duration) 448 { 449 return formatChromiumMediaControlsTime(currentTime, duration); 450 } 451 452 } // namespace WebCore 453