| OLD | NEW |
| (Empty) |
| 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/RenderMediaControlsChromium.h" | |
| 30 | |
| 31 #include "bindings/v8/ExceptionStatePlaceholder.h" | |
| 32 #include "core/html/HTMLMediaElement.h" | |
| 33 #include "core/html/TimeRanges.h" | |
| 34 #include "core/platform/graphics/Gradient.h" | |
| 35 #include "core/platform/graphics/GraphicsContext.h" | |
| 36 #include "core/rendering/PaintInfo.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, Imag
e* image) | |
| 64 { | |
| 65 context->drawImage(image, rect); | |
| 66 return true; | |
| 67 } | |
| 68 | |
| 69 static bool paintMediaMuteButton(RenderObject* object, const PaintInfo& paintInf
o, 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& paintInf
o, 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() ? m
ediaPlay : mediaPause); | |
| 110 } | |
| 111 | |
| 112 static bool paintMediaOverlayPlayButton(RenderObject* object, const PaintInfo& p
aintInfo, 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, sliderBackgroundC
olor); | |
| 138 context->restore(); | |
| 139 } | |
| 140 | |
| 141 static void paintSliderRangeHighlight(const IntRect& rect, const RenderStyle* st
yle, GraphicsContext* context, int startPosition, int endPosition, Color startCo
lor, 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 re
tain 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, star
tColor); | |
| 187 else if (startOffset < borderRadius) | |
| 188 context->fillRoundedRect(highlightRect, radii, IntSize(0, 0), radii, Int
Size(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, c
onst 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 ran
ges and it'd be | |
| 211 // distracting/'busy' to show all of them, show only the buffered range cont
aining 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 paint
ing 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, curre
ntPosition, 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, end
Position, startColor, endColor); | |
| 244 | |
| 245 return true; | |
| 246 } | |
| 247 | |
| 248 return true; | |
| 249 } | |
| 250 | |
| 251 static bool paintMediaSliderThumb(RenderObject* object, const PaintInfo& paintIn
fo, const IntRect& rect) | |
| 252 { | |
| 253 ASSERT(object->node()); | |
| 254 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadow
Host()); | |
| 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& paintI
nfo, 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->m
uted()) | |
| 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& p
aintInfo, const IntRect& rect) | |
| 305 { | |
| 306 ASSERT(object->node()); | |
| 307 HTMLMediaElement* mediaElement = toParentMediaElement(object->node()->shadow
Host()); | |
| 308 if (!mediaElement) | |
| 309 return false; | |
| 310 | |
| 311 if (!hasSource(mediaElement) || !mediaElement->hasAudio()) | |
| 312 return true; | |
| 313 | |
| 314 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSl
iderThumb"); | |
| 315 return paintMediaButton(paintInfo.context, rect, mediaVolumeSliderThumb); | |
| 316 } | |
| 317 | |
| 318 static bool paintMediaFullscreenButton(RenderObject* object, const PaintInfo& pa
intInfo, const IntRect& rect) | |
| 319 { | |
| 320 HTMLMediaElement* mediaElement = toParentMediaElement(object); | |
| 321 if (!mediaElement) | |
| 322 return false; | |
| 323 | |
| 324 static Image* mediaFullscreenButton = platformResource("mediaplayerFullscree
n"); | |
| 325 return paintMediaButton(paintInfo.context, rect, mediaFullscreenButton); | |
| 326 } | |
| 327 | |
| 328 static bool paintMediaToggleClosedCaptionsButton(RenderObject* object, const Pai
ntInfo& paintInfo, const IntRect& rect) | |
| 329 { | |
| 330 HTMLMediaElement* mediaElement = toParentMediaElement(object); | |
| 331 if (!mediaElement) | |
| 332 return false; | |
| 333 | |
| 334 static Image* mediaClosedCaptionButton = platformResource("mediaplayerClosed
Caption"); | |
| 335 static Image* mediaClosedCaptionButtonDisabled = platformResource("mediaplay
erClosedCaptionDisabled"); | |
| 336 | |
| 337 if (mediaElement->webkitClosedCaptionsVisible()) | |
| 338 return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButto
n); | |
| 339 | |
| 340 return paintMediaButton(paintInfo.context, rect, mediaClosedCaptionButtonDis
abled); | |
| 341 } | |
| 342 | |
| 343 | |
| 344 bool RenderMediaControlsChromium::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 RenderMediaControlsChromium::adjustMediaSliderThumbSize(RenderStyle* style) | |
| 394 { | |
| 395 static Image* mediaSliderThumb = platformResource("mediaplayerSliderThumb"); | |
| 396 static Image* mediaVolumeSliderThumb = platformResource("mediaplayerVolumeSl
iderThumb"); | |
| 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, sec
onds); | |
| 438 | |
| 439 return String::format("%s%01d:%02d", (time < 0 ? "-" : ""), minutes, seconds
); | |
| 440 } | |
| 441 | |
| 442 String RenderMediaControlsChromium::formatMediaControlsTime(float time) | |
| 443 { | |
| 444 return formatChromiumMediaControlsTime(time, time); | |
| 445 } | |
| 446 | |
| 447 String RenderMediaControlsChromium::formatMediaControlsCurrentTime(float current
Time, float duration) | |
| 448 { | |
| 449 return formatChromiumMediaControlsTime(currentTime, duration); | |
| 450 } | |
| 451 | |
| 452 } // namespace WebCore | |
| OLD | NEW |