1 /*
2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26 #include "config.h"
27
28 #if ENABLE(VIDEO)
29 #include "RenderMedia.h"
30
31 #include "CSSStyleSelector.h"
32 #include "Event.h"
33 #include "EventNames.h"
34 #include "FloatConversion.h"
35 #include "FrameView.h"
36 #include "GraphicsContext.h"
37 #include "HTMLMediaElement.h"
38 #include "HTMLNames.h"
39 #include "MediaControlElements.h"
40 #include "MouseEvent.h"
41 #include "MediaPlayer.h"
42 #include "RenderSlider.h"
43 #include <wtf/CurrentTime.h>
44 #include <wtf/MathExtras.h>
45
46 using namespace std;
47
48 namespace WebCore {
49
50 static const double cTimeUpdateRepeatDelay = 0.2;
51 static const double cOpacityAnimationRepeatDelay = 0.05;
52 // FIXME get this from style
53 static const double cOpacityAnimationDuration = 0.1;
54
RenderMedia(HTMLMediaElement * video)55 RenderMedia::RenderMedia(HTMLMediaElement* video)
56 : RenderReplaced(video)
57 , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
58 , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
59 , m_mouseOver(false)
60 , m_opacityAnimationStartTime(0)
61 , m_opacityAnimationFrom(0)
62 , m_opacityAnimationTo(1.0f)
63 , m_previousVisible(VISIBLE)
64 {
65 }
66
RenderMedia(HTMLMediaElement * video,const IntSize & intrinsicSize)67 RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize)
68 : RenderReplaced(video, intrinsicSize)
69 , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
70 , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
71 , m_mouseOver(false)
72 , m_opacityAnimationStartTime(0)
73 , m_opacityAnimationFrom(0)
74 , m_opacityAnimationTo(1.0f)
75 {
76 }
77
~RenderMedia()78 RenderMedia::~RenderMedia()
79 {
80 }
81
destroy()82 void RenderMedia::destroy()
83 {
84 if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
85
86 // detach the panel before removing the shadow renderer to prevent a crash in m_controlsShadowRoot->detach()
87 // when display: style changes
88 m_panel->detach();
89
90 removeChild(m_controlsShadowRoot->renderer());
91 m_controlsShadowRoot->detach();
92 }
93 RenderReplaced::destroy();
94 }
95
mediaElement() const96 HTMLMediaElement* RenderMedia::mediaElement() const
97 {
98 return static_cast<HTMLMediaElement*>(node());
99 }
100
player() const101 MediaPlayer* RenderMedia::player() const
102 {
103 return mediaElement()->player();
104 }
105
layout()106 void RenderMedia::layout()
107 {
108 IntSize oldSize = contentBoxRect().size();
109
110 RenderReplaced::layout();
111
112 RenderBox* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderBox() : 0;
113 if (!controlsRenderer)
114 return;
115 IntSize newSize = contentBoxRect().size();
116 if (newSize != oldSize || controlsRenderer->needsLayout()) {
117 controlsRenderer->setLocation(borderLeft() + paddingLeft(), borderTop() + paddingTop());
118 controlsRenderer->style()->setHeight(Length(newSize.height(), Fixed));
119 controlsRenderer->style()->setWidth(Length(newSize.width(), Fixed));
120 controlsRenderer->setNeedsLayout(true, false);
121 controlsRenderer->layout();
122 setChildNeedsLayout(false);
123 }
124 }
125
firstChild() const126 RenderObject* RenderMedia::firstChild() const
127 {
128 return m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
129 }
130
lastChild() const131 RenderObject* RenderMedia::lastChild() const
132 {
133 return m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
134 }
135
removeChild(RenderObject * child)136 void RenderMedia::removeChild(RenderObject* child)
137 {
138 ASSERT(m_controlsShadowRoot);
139 ASSERT(child == m_controlsShadowRoot->renderer());
140 child->removeLayers(enclosingLayer());
141 static_cast<RenderMediaControlShadowRoot*>(child)->setParent(0);
142 }
143
createControlsShadowRoot()144 void RenderMedia::createControlsShadowRoot()
145 {
146 ASSERT(!m_controlsShadowRoot);
147 m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement());
148 }
149
createPanel()150 void RenderMedia::createPanel()
151 {
152 ASSERT(!m_panel);
153 RenderStyle* style = getCachedPseudoStyle(RenderStyle::MEDIA_CONTROLS_PANEL);
154 m_panel = new HTMLDivElement(HTMLNames::divTag, document());
155 RenderObject* renderer = m_panel->createRenderer(renderArena(), style);
156 if (renderer) {
157 m_panel->setRenderer(renderer);
158 renderer->setStyle(style);
159 m_panel->setAttached();
160 m_panel->setInDocument(true);
161 m_controlsShadowRoot->addChild(m_panel);
162 m_controlsShadowRoot->renderer()->addChild(renderer);
163 }
164 }
165
createMuteButton()166 void RenderMedia::createMuteButton()
167 {
168 ASSERT(!m_muteButton);
169 m_muteButton = new MediaControlMuteButtonElement(document(), mediaElement());
170 m_muteButton->attachToParent(m_panel.get());
171 }
172
createPlayButton()173 void RenderMedia::createPlayButton()
174 {
175 ASSERT(!m_playButton);
176 m_playButton = new MediaControlPlayButtonElement(document(), mediaElement());
177 m_playButton->attachToParent(m_panel.get());
178 }
179
createSeekBackButton()180 void RenderMedia::createSeekBackButton()
181 {
182 ASSERT(!m_seekBackButton);
183 m_seekBackButton = new MediaControlSeekButtonElement(document(), mediaElement(), false);
184 m_seekBackButton->attachToParent(m_panel.get());
185 }
186
createSeekForwardButton()187 void RenderMedia::createSeekForwardButton()
188 {
189 ASSERT(!m_seekForwardButton);
190 m_seekForwardButton = new MediaControlSeekButtonElement(document(), mediaElement(), true);
191 m_seekForwardButton->attachToParent(m_panel.get());
192 }
193
createTimelineContainer()194 void RenderMedia::createTimelineContainer()
195 {
196 ASSERT(!m_timelineContainer);
197 RenderStyle* style = getCachedPseudoStyle(RenderStyle::MEDIA_CONTROLS_TIMELINE_CONTAINER);
198 m_timelineContainer = new HTMLDivElement(HTMLNames::divTag, document());
199 RenderObject* renderer = m_timelineContainer->createRenderer(renderArena(), style);
200 if (renderer) {
201 m_timelineContainer->setRenderer(renderer);
202 renderer->setStyle(style);
203 m_timelineContainer->setAttached();
204 m_timelineContainer->setInDocument(true);
205 m_panel->addChild(m_timelineContainer);
206 m_panel->renderer()->addChild(renderer);
207 }
208 }
209
createTimeline()210 void RenderMedia::createTimeline()
211 {
212 ASSERT(!m_timeline);
213 m_timeline = new MediaControlTimelineElement(document(), mediaElement());
214 m_timeline->attachToParent(m_timelineContainer.get());
215 }
216
createCurrentTimeDisplay()217 void RenderMedia::createCurrentTimeDisplay()
218 {
219 ASSERT(!m_currentTimeDisplay);
220 m_currentTimeDisplay = new MediaTimeDisplayElement(document(), mediaElement(), true);
221 m_currentTimeDisplay->attachToParent(m_timelineContainer.get());
222 }
223
createTimeRemainingDisplay()224 void RenderMedia::createTimeRemainingDisplay()
225 {
226 ASSERT(!m_timeRemainingDisplay);
227 m_timeRemainingDisplay = new MediaTimeDisplayElement(document(), mediaElement(), false);
228 m_timeRemainingDisplay->attachToParent(m_timelineContainer.get());
229 }
230
createFullscreenButton()231 void RenderMedia::createFullscreenButton()
232 {
233 ASSERT(!m_fullscreenButton);
234 m_fullscreenButton = new MediaControlFullscreenButtonElement(document(), mediaElement());
235 m_fullscreenButton->attachToParent(m_panel.get());
236 }
237
updateFromElement()238 void RenderMedia::updateFromElement()
239 {
240 updateControls();
241 }
242
updateControls()243 void RenderMedia::updateControls()
244 {
245 HTMLMediaElement* media = mediaElement();
246 if (!media->controls() || !media->inActiveDocument()) {
247 if (m_controlsShadowRoot) {
248 m_controlsShadowRoot->detach();
249 m_panel = 0;
250 m_muteButton = 0;
251 m_playButton = 0;
252 m_timelineContainer = 0;
253 m_timeline = 0;
254 m_seekBackButton = 0;
255 m_seekForwardButton = 0;
256 m_currentTimeDisplay = 0;
257 m_timeRemainingDisplay = 0;
258 m_fullscreenButton = 0;
259 m_controlsShadowRoot = 0;
260 }
261 m_opacityAnimationTo = 1.0f;
262 m_opacityAnimationTimer.stop();
263 m_timeUpdateTimer.stop();
264 return;
265 }
266
267 if (!m_controlsShadowRoot) {
268 createControlsShadowRoot();
269 createPanel();
270 createMuteButton();
271 createPlayButton();
272 createTimelineContainer();
273 createTimeline();
274 createSeekBackButton();
275 createSeekForwardButton();
276 createCurrentTimeDisplay();
277 createTimeRemainingDisplay();
278 createFullscreenButton();
279 }
280
281 if (media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA) {
282 if (m_timeUpdateTimer.isActive())
283 m_timeUpdateTimer.stop();
284 } else if (style()->visibility() == VISIBLE && m_timeline && m_timeline->renderer() && m_timeline->renderer()->style()->display() != NONE ) {
285 m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
286 }
287
288 m_previousVisible = style()->visibility();
289
290 if (m_muteButton)
291 m_muteButton->update();
292 if (m_playButton)
293 m_playButton->update();
294 if (m_timeline)
295 m_timeline->update();
296 if (m_seekBackButton)
297 m_seekBackButton->update();
298 if (m_seekForwardButton)
299 m_seekForwardButton->update();
300 if (m_fullscreenButton)
301 m_fullscreenButton->update();
302 updateTimeDisplay();
303 updateControlVisibility();
304 }
305
timeUpdateTimerFired(Timer<RenderMedia> *)306 void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*)
307 {
308 if (m_timeline)
309 m_timeline->update(false);
310 updateTimeDisplay();
311 }
312
formatTime(float time)313 String RenderMedia::formatTime(float time)
314 {
315 if (!isfinite(time))
316 time = 0;
317 int seconds = (int)fabsf(time);
318 int hours = seconds / (60 * 60);
319 int minutes = (seconds / 60) % 60;
320 seconds %= 60;
321 if (hours) {
322 if (hours > 9)
323 return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
324 else
325 return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
326 }
327 else
328 return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
329 }
330
updateTimeDisplay()331 void RenderMedia::updateTimeDisplay()
332 {
333 if (!m_currentTimeDisplay || !m_currentTimeDisplay->renderer() || m_currentTimeDisplay->renderer()->style()->display() == NONE || style()->visibility() != VISIBLE)
334 return;
335 float now = mediaElement()->currentTime();
336 float duration = mediaElement()->duration();
337
338 String timeString = formatTime(now);
339 ExceptionCode ec;
340 m_currentTimeDisplay->setInnerText(timeString, ec);
341
342 timeString = formatTime(now - duration);
343 m_timeRemainingDisplay->setInnerText(timeString, ec);
344 }
345
updateControlVisibility()346 void RenderMedia::updateControlVisibility()
347 {
348 if (!m_panel || !m_panel->renderer())
349 return;
350
351 // Don't fade for audio controls.
352 HTMLMediaElement* media = mediaElement();
353 if (player() && !player()->hasVideo() || !media->isVideo())
354 return;
355
356 // do fading manually, css animations don't work well with shadow trees
357 bool visible = style()->visibility() == VISIBLE && (m_mouseOver || media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA);
358 if (visible == (m_opacityAnimationTo > 0))
359 return;
360
361 if (style()->visibility() != m_previousVisible) {
362 // don't fade gradually if it the element has just changed visibility
363 m_previousVisible = style()->visibility();
364 m_opacityAnimationTo = m_previousVisible == VISIBLE ? 1.0f : 0;
365 changeOpacity(m_panel.get(), m_opacityAnimationTo);
366 return;
367 }
368
369 if (visible) {
370 m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
371 m_opacityAnimationTo = 1.0f;
372 } else {
373 m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
374 m_opacityAnimationTo = 0;
375 }
376 m_opacityAnimationStartTime = currentTime();
377 m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
378 }
379
changeOpacity(HTMLElement * e,float opacity)380 void RenderMedia::changeOpacity(HTMLElement* e, float opacity)
381 {
382 if (!e || !e->renderer() || !e->renderer()->style())
383 return;
384 RefPtr<RenderStyle> s = RenderStyle::clone(e->renderer()->style());
385 s->setOpacity(opacity);
386 // z-index can't be auto if opacity is used
387 s->setZIndex(0);
388 e->renderer()->setStyle(s.release());
389 }
390
opacityAnimationTimerFired(Timer<RenderMedia> *)391 void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*)
392 {
393 double time = currentTime() - m_opacityAnimationStartTime;
394 if (time >= cOpacityAnimationDuration) {
395 time = cOpacityAnimationDuration;
396 m_opacityAnimationTimer.stop();
397 }
398 float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / cOpacityAnimationDuration);
399 changeOpacity(m_panel.get(), opacity);
400 }
401
forwardEvent(Event * event)402 void RenderMedia::forwardEvent(Event* event)
403 {
404 if (event->isMouseEvent() && m_controlsShadowRoot) {
405 MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
406 IntPoint point(mouseEvent->pageX(), mouseEvent->pageY());
407 if (m_muteButton && m_muteButton->hitTest(point))
408 m_muteButton->defaultEventHandler(event);
409
410 if (m_playButton && m_playButton->hitTest(point))
411 m_playButton->defaultEventHandler(event);
412
413 if (m_seekBackButton && m_seekBackButton->hitTest(point))
414 m_seekBackButton->defaultEventHandler(event);
415
416 if (m_seekForwardButton && m_seekForwardButton->hitTest(point))
417 m_seekForwardButton->defaultEventHandler(event);
418
419 if (m_timeline && m_timeline->hitTest(point))
420 m_timeline->defaultEventHandler(event);
421
422 if (m_fullscreenButton && m_fullscreenButton->hitTest(point))
423 m_fullscreenButton->defaultEventHandler(event);
424
425 if (event->type() == eventNames().mouseoverEvent) {
426 m_mouseOver = true;
427 updateControlVisibility();
428 }
429 if (event->type() == eventNames().mouseoutEvent) {
430 // FIXME: moving over scrollbar thumb generates mouseout for the ancestor media element for some reason
431 m_mouseOver = absoluteBoundingBoxRect().contains(point);
432 updateControlVisibility();
433 }
434 }
435 }
436
lowestPosition(bool includeOverflowInterior,bool includeSelf) const437 int RenderMedia::lowestPosition(bool includeOverflowInterior, bool includeSelf) const
438 {
439 int bottom = RenderReplaced::lowestPosition(includeOverflowInterior, includeSelf);
440 if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
441 return bottom;
442
443 return max(bottom, m_controlsShadowRoot->renderBox()->y() + m_controlsShadowRoot->renderer()->lowestPosition(includeOverflowInterior, includeSelf));
444 }
445
rightmostPosition(bool includeOverflowInterior,bool includeSelf) const446 int RenderMedia::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const
447 {
448 int right = RenderReplaced::rightmostPosition(includeOverflowInterior, includeSelf);
449 if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
450 return right;
451
452 return max(right, m_controlsShadowRoot->renderBox()->x() + m_controlsShadowRoot->renderer()->rightmostPosition(includeOverflowInterior, includeSelf));
453 }
454
leftmostPosition(bool includeOverflowInterior,bool includeSelf) const455 int RenderMedia::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const
456 {
457 int left = RenderReplaced::leftmostPosition(includeOverflowInterior, includeSelf);
458 if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
459 return left;
460
461 return min(left, m_controlsShadowRoot->renderBox()->x() + m_controlsShadowRoot->renderer()->leftmostPosition(includeOverflowInterior, includeSelf));
462 }
463
464 } // namespace WebCore
465
466 #endif
467