• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007, 2008, 2009 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 "EventNames.h"
32 #include "FloatConversion.h"
33 #include "HTMLNames.h"
34 #include "MediaControlElements.h"
35 #include "MouseEvent.h"
36 #include <wtf/CurrentTime.h>
37 #include <wtf/MathExtras.h>
38 
39 using namespace std;
40 
41 namespace WebCore {
42 
43 using namespace HTMLNames;
44 
45 static const double cTimeUpdateRepeatDelay = 0.2;
46 static const double cOpacityAnimationRepeatDelay = 0.05;
47 // FIXME get this from style
48 static const double cOpacityAnimationDurationFadeIn = 0.1;
49 static const double cOpacityAnimationDurationFadeOut = 0.3;
50 
RenderMedia(HTMLMediaElement * video)51 RenderMedia::RenderMedia(HTMLMediaElement* video)
52     : RenderReplaced(video)
53     , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
54     , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
55     , m_mouseOver(false)
56     , m_opacityAnimationStartTime(0)
57     , m_opacityAnimationDuration(cOpacityAnimationDurationFadeIn)
58     , m_opacityAnimationFrom(0)
59     , m_opacityAnimationTo(1.0f)
60 {
61 }
62 
RenderMedia(HTMLMediaElement * video,const IntSize & intrinsicSize)63 RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize)
64     : RenderReplaced(video, intrinsicSize)
65     , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
66     , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
67     , m_mouseOver(false)
68     , m_opacityAnimationStartTime(0)
69     , m_opacityAnimationDuration(cOpacityAnimationDurationFadeIn)
70     , m_opacityAnimationFrom(0)
71     , m_opacityAnimationTo(1.0f)
72 {
73 }
74 
~RenderMedia()75 RenderMedia::~RenderMedia()
76 {
77 }
78 
destroy()79 void RenderMedia::destroy()
80 {
81     if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
82 
83         // detach the panel before removing the shadow renderer to prevent a crash in m_controlsShadowRoot->detach()
84         //  when display: style changes
85         m_panel->detach();
86 
87         removeChild(m_controlsShadowRoot->renderer());
88         m_controlsShadowRoot->detach();
89         m_controlsShadowRoot = 0;
90     }
91     RenderReplaced::destroy();
92 }
93 
mediaElement() const94 HTMLMediaElement* RenderMedia::mediaElement() const
95 {
96     return static_cast<HTMLMediaElement*>(node());
97 }
98 
player() const99 MediaPlayer* RenderMedia::player() const
100 {
101     return mediaElement()->player();
102 }
103 
styleDidChange(StyleDifference diff,const RenderStyle * oldStyle)104 void RenderMedia::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
105 {
106     RenderReplaced::styleDidChange(diff, oldStyle);
107 
108     if (m_controlsShadowRoot) {
109         if (m_panel)
110             m_panel->updateStyle();
111         if (m_muteButton)
112             m_muteButton->updateStyle();
113         if (m_playButton)
114             m_playButton->updateStyle();
115         if (m_seekBackButton)
116             m_seekBackButton->updateStyle();
117         if (m_seekForwardButton)
118             m_seekForwardButton->updateStyle();
119         if (m_rewindButton)
120             m_rewindButton->updateStyle();
121         if (m_returnToRealtimeButton)
122             m_returnToRealtimeButton->updateStyle();
123         if (m_statusDisplay)
124             m_statusDisplay->updateStyle();
125         if (m_timelineContainer)
126             m_timelineContainer->updateStyle();
127         if (m_timeline)
128             m_timeline->updateStyle();
129         if (m_fullscreenButton)
130             m_fullscreenButton->updateStyle();
131         if (m_currentTimeDisplay)
132             m_currentTimeDisplay->updateStyle();
133         if (m_timeRemainingDisplay)
134             m_timeRemainingDisplay->updateStyle();
135     }
136 }
137 
layout()138 void RenderMedia::layout()
139 {
140     IntSize oldSize = contentBoxRect().size();
141 
142     RenderReplaced::layout();
143 
144     RenderBox* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderBox() : 0;
145     if (!controlsRenderer)
146         return;
147     IntSize newSize = contentBoxRect().size();
148     if (newSize != oldSize || controlsRenderer->needsLayout()) {
149 
150         if (m_currentTimeDisplay && m_timeRemainingDisplay) {
151             bool shouldShowTimeDisplays = shouldShowTimeDisplayControls();
152             m_currentTimeDisplay->setVisible(shouldShowTimeDisplays);
153             m_timeRemainingDisplay->setVisible(shouldShowTimeDisplays);
154         }
155 
156         controlsRenderer->setLocation(borderLeft() + paddingLeft(), borderTop() + paddingTop());
157         controlsRenderer->style()->setHeight(Length(newSize.height(), Fixed));
158         controlsRenderer->style()->setWidth(Length(newSize.width(), Fixed));
159         controlsRenderer->setNeedsLayout(true, false);
160         controlsRenderer->layout();
161         setChildNeedsLayout(false);
162     }
163 }
164 
createControlsShadowRoot()165 void RenderMedia::createControlsShadowRoot()
166 {
167     ASSERT(!m_controlsShadowRoot);
168     m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement());
169     addChild(m_controlsShadowRoot->renderer());
170 }
171 
createPanel()172 void RenderMedia::createPanel()
173 {
174     ASSERT(!m_panel);
175     m_panel = new MediaControlElement(document(), MEDIA_CONTROLS_PANEL, mediaElement());
176     m_panel->attachToParent(m_controlsShadowRoot.get());
177 }
178 
createMuteButton()179 void RenderMedia::createMuteButton()
180 {
181     ASSERT(!m_muteButton);
182     m_muteButton = new MediaControlMuteButtonElement(document(), mediaElement());
183     m_muteButton->attachToParent(m_panel.get());
184 }
185 
createPlayButton()186 void RenderMedia::createPlayButton()
187 {
188     ASSERT(!m_playButton);
189     m_playButton = new MediaControlPlayButtonElement(document(), mediaElement());
190     m_playButton->attachToParent(m_panel.get());
191 }
192 
createSeekBackButton()193 void RenderMedia::createSeekBackButton()
194 {
195     ASSERT(!m_seekBackButton);
196     m_seekBackButton = new MediaControlSeekButtonElement(document(), mediaElement(), false);
197     m_seekBackButton->attachToParent(m_panel.get());
198 }
199 
createSeekForwardButton()200 void RenderMedia::createSeekForwardButton()
201 {
202     ASSERT(!m_seekForwardButton);
203     m_seekForwardButton = new MediaControlSeekButtonElement(document(), mediaElement(), true);
204     m_seekForwardButton->attachToParent(m_panel.get());
205 }
206 
createRewindButton()207 void RenderMedia::createRewindButton()
208 {
209     ASSERT(!m_rewindButton);
210     m_rewindButton = new MediaControlRewindButtonElement(document(), mediaElement());
211     m_rewindButton->attachToParent(m_panel.get());
212 }
213 
createReturnToRealtimeButton()214 void RenderMedia::createReturnToRealtimeButton()
215 {
216     ASSERT(!m_returnToRealtimeButton);
217     m_returnToRealtimeButton = new MediaControlReturnToRealtimeButtonElement(document(), mediaElement());
218     m_returnToRealtimeButton->attachToParent(m_panel.get());
219 }
220 
createStatusDisplay()221 void RenderMedia::createStatusDisplay()
222 {
223     ASSERT(!m_statusDisplay);
224     m_statusDisplay = new MediaControlStatusDisplayElement(document(), mediaElement());
225     m_statusDisplay->attachToParent(m_panel.get());
226 }
227 
createTimelineContainer()228 void RenderMedia::createTimelineContainer()
229 {
230     ASSERT(!m_timelineContainer);
231     m_timelineContainer = new MediaControlTimelineContainerElement(document(), mediaElement());
232     m_timelineContainer->attachToParent(m_panel.get());
233 }
234 
createTimeline()235 void RenderMedia::createTimeline()
236 {
237     ASSERT(!m_timeline);
238     m_timeline = new MediaControlTimelineElement(document(), mediaElement());
239     m_timeline->setAttribute(precisionAttr, "float");
240     m_timeline->attachToParent(m_timelineContainer.get());
241 }
242 
createCurrentTimeDisplay()243 void RenderMedia::createCurrentTimeDisplay()
244 {
245     ASSERT(!m_currentTimeDisplay);
246     m_currentTimeDisplay = new MediaControlTimeDisplayElement(document(), MEDIA_CONTROLS_CURRENT_TIME_DISPLAY, mediaElement());
247     m_currentTimeDisplay->attachToParent(m_timelineContainer.get());
248 }
249 
createTimeRemainingDisplay()250 void RenderMedia::createTimeRemainingDisplay()
251 {
252     ASSERT(!m_timeRemainingDisplay);
253     m_timeRemainingDisplay = new MediaControlTimeDisplayElement(document(), MEDIA_CONTROLS_TIME_REMAINING_DISPLAY, mediaElement());
254     m_timeRemainingDisplay->attachToParent(m_timelineContainer.get());
255 }
256 
createFullscreenButton()257 void RenderMedia::createFullscreenButton()
258 {
259     ASSERT(!m_fullscreenButton);
260     m_fullscreenButton = new MediaControlFullscreenButtonElement(document(), mediaElement());
261     m_fullscreenButton->attachToParent(m_panel.get());
262 }
263 
updateFromElement()264 void RenderMedia::updateFromElement()
265 {
266     updateControls();
267 }
268 
updateControls()269 void RenderMedia::updateControls()
270 {
271     HTMLMediaElement* media = mediaElement();
272     if (!media->controls() || !media->inActiveDocument()) {
273         if (m_controlsShadowRoot) {
274             m_controlsShadowRoot->detach();
275             m_panel = 0;
276             m_muteButton = 0;
277             m_playButton = 0;
278             m_statusDisplay = 0;
279             m_timelineContainer = 0;
280             m_timeline = 0;
281             m_seekBackButton = 0;
282             m_seekForwardButton = 0;
283             m_rewindButton = 0;
284             m_returnToRealtimeButton = 0;
285             m_currentTimeDisplay = 0;
286             m_timeRemainingDisplay = 0;
287             m_fullscreenButton = 0;
288             m_controlsShadowRoot = 0;
289         }
290         m_opacityAnimationTo = 1.0f;
291         m_opacityAnimationTimer.stop();
292         m_timeUpdateTimer.stop();
293         return;
294     }
295 
296     if (!m_controlsShadowRoot) {
297         createControlsShadowRoot();
298         createPanel();
299         if (m_panel) {
300             createRewindButton();
301             createMuteButton();
302             createPlayButton();
303             createReturnToRealtimeButton();
304             createStatusDisplay();
305             createTimelineContainer();
306             createSeekBackButton();
307             createSeekForwardButton();
308             createFullscreenButton();
309             if (m_timelineContainer) {
310                 createCurrentTimeDisplay();
311                 createTimeline();
312                 createTimeRemainingDisplay();
313             }
314             m_panel->attach();
315         }
316     }
317 
318     if (media->canPlay()) {
319         if (m_timeUpdateTimer.isActive())
320             m_timeUpdateTimer.stop();
321     } else if (style()->visibility() == VISIBLE && m_timeline && m_timeline->renderer() && m_timeline->renderer()->style()->display() != NONE) {
322         m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
323     }
324 
325 
326     if (m_panel) {
327         // update() might alter the opacity of the element, especially if we are in the middle
328         // of an animation. This is the only element concerned as we animate only this element.
329         float opacityBeforeChangingStyle = m_panel->renderer() ? m_panel->renderer()->style()->opacity() : 0;
330         m_panel->update();
331         changeOpacity(m_panel.get(), opacityBeforeChangingStyle);
332     }
333     if (m_muteButton)
334         m_muteButton->update();
335     if (m_playButton)
336         m_playButton->update();
337     if (m_timelineContainer)
338         m_timelineContainer->update();
339     if (m_timeline)
340         m_timeline->update();
341     if (m_currentTimeDisplay)
342         m_currentTimeDisplay->update();
343     if (m_timeRemainingDisplay)
344         m_timeRemainingDisplay->update();
345     if (m_seekBackButton)
346         m_seekBackButton->update();
347     if (m_seekForwardButton)
348         m_seekForwardButton->update();
349     if (m_rewindButton)
350         m_rewindButton->update();
351     if (m_returnToRealtimeButton)
352         m_returnToRealtimeButton->update();
353     if (m_statusDisplay)
354         m_statusDisplay->update();
355     if (m_fullscreenButton)
356         m_fullscreenButton->update();
357 
358     updateTimeDisplay();
359     updateControlVisibility();
360 }
361 
timeUpdateTimerFired(Timer<RenderMedia> *)362 void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*)
363 {
364     if (m_timeline)
365         m_timeline->update(false);
366     updateTimeDisplay();
367 }
368 
formatTime(float time)369 String RenderMedia::formatTime(float time)
370 {
371     if (!isfinite(time))
372         time = 0;
373     int seconds = (int)fabsf(time);
374     int hours = seconds / (60 * 60);
375     int minutes = (seconds / 60) % 60;
376     seconds %= 60;
377     if (hours) {
378         if (hours > 9)
379             return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
380         else
381             return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
382     }
383     else
384         return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
385 }
386 
updateTimeDisplay()387 void RenderMedia::updateTimeDisplay()
388 {
389     if (!m_currentTimeDisplay || !m_currentTimeDisplay->renderer() || m_currentTimeDisplay->renderer()->style()->display() == NONE || style()->visibility() != VISIBLE)
390         return;
391     float now = mediaElement()->currentTime();
392     float duration = mediaElement()->duration();
393 
394     String timeString = formatTime(now);
395     ExceptionCode ec;
396     m_currentTimeDisplay->setInnerText(timeString, ec);
397 
398     timeString = formatTime(now - duration);
399     m_timeRemainingDisplay->setInnerText(timeString, ec);
400 }
401 
updateControlVisibility()402 void RenderMedia::updateControlVisibility()
403 {
404     if (!m_panel || !m_panel->renderer())
405         return;
406 
407     // Don't fade for audio controls.
408     HTMLMediaElement* media = mediaElement();
409     if (!media->hasVideo())
410         return;
411 
412     // Don't fade if the media element is not visible
413     if (style()->visibility() != VISIBLE)
414         return;
415 
416     bool shouldHideController = !m_mouseOver && !media->canPlay();
417 
418     // Do fading manually, css animations don't work with shadow trees
419 
420     float animateFrom = m_panel->renderer()->style()->opacity();
421     float animateTo = shouldHideController ? 0.0f : 1.0f;
422 
423     if (animateFrom == animateTo)
424         return;
425 
426     if (m_opacityAnimationTimer.isActive()) {
427         if (m_opacityAnimationTo == animateTo)
428             return;
429         m_opacityAnimationTimer.stop();
430     }
431 
432     if (animateFrom < animateTo)
433         m_opacityAnimationDuration = cOpacityAnimationDurationFadeIn;
434     else
435         m_opacityAnimationDuration = cOpacityAnimationDurationFadeOut;
436 
437     m_opacityAnimationFrom = animateFrom;
438     m_opacityAnimationTo = animateTo;
439 
440     m_opacityAnimationStartTime = currentTime();
441     m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
442 }
443 
changeOpacity(HTMLElement * e,float opacity)444 void RenderMedia::changeOpacity(HTMLElement* e, float opacity)
445 {
446     if (!e || !e->renderer() || !e->renderer()->style())
447         return;
448     RefPtr<RenderStyle> s = RenderStyle::clone(e->renderer()->style());
449     s->setOpacity(opacity);
450     // z-index can't be auto if opacity is used
451     s->setZIndex(0);
452     e->renderer()->setStyle(s.release());
453 }
454 
opacityAnimationTimerFired(Timer<RenderMedia> *)455 void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*)
456 {
457     double time = currentTime() - m_opacityAnimationStartTime;
458     if (time >= m_opacityAnimationDuration) {
459         time = m_opacityAnimationDuration;
460         m_opacityAnimationTimer.stop();
461     }
462     float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / m_opacityAnimationDuration);
463     changeOpacity(m_panel.get(), opacity);
464 }
465 
forwardEvent(Event * event)466 void RenderMedia::forwardEvent(Event* event)
467 {
468     if (event->isMouseEvent() && m_controlsShadowRoot) {
469         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
470         IntPoint point(mouseEvent->absoluteLocation());
471         if (m_muteButton && m_muteButton->hitTest(point))
472             m_muteButton->defaultEventHandler(event);
473 
474         if (m_playButton && m_playButton->hitTest(point))
475             m_playButton->defaultEventHandler(event);
476 
477         if (m_seekBackButton && m_seekBackButton->hitTest(point))
478             m_seekBackButton->defaultEventHandler(event);
479 
480         if (m_seekForwardButton && m_seekForwardButton->hitTest(point))
481             m_seekForwardButton->defaultEventHandler(event);
482 
483         if (m_rewindButton && m_rewindButton->hitTest(point))
484             m_rewindButton->defaultEventHandler(event);
485 
486         if (m_returnToRealtimeButton && m_returnToRealtimeButton->hitTest(point))
487             m_returnToRealtimeButton->defaultEventHandler(event);
488 
489         if (m_timeline && m_timeline->hitTest(point))
490             m_timeline->defaultEventHandler(event);
491 
492         if (m_fullscreenButton && m_fullscreenButton->hitTest(point))
493             m_fullscreenButton->defaultEventHandler(event);
494 
495         if (event->type() == eventNames().mouseoverEvent) {
496             m_mouseOver = true;
497             updateControlVisibility();
498         }
499         if (event->type() == eventNames().mouseoutEvent) {
500             // When the scrollbar thumb captures mouse events, we should treat the mouse as still being over our renderer if the new target is a descendant
501             Node* mouseOverNode = mouseEvent->relatedTarget() ? mouseEvent->relatedTarget()->toNode() : 0;
502             RenderObject* mouseOverRenderer = mouseOverNode ? mouseOverNode->renderer() : 0;
503             m_mouseOver = mouseOverRenderer && mouseOverRenderer->isDescendantOf(this);
504             updateControlVisibility();
505         }
506     }
507 }
508 
lowestPosition(bool includeOverflowInterior,bool includeSelf) const509 int RenderMedia::lowestPosition(bool includeOverflowInterior, bool includeSelf) const
510 {
511     int bottom = RenderReplaced::lowestPosition(includeOverflowInterior, includeSelf);
512     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
513         return bottom;
514 
515     return max(bottom,  m_controlsShadowRoot->renderBox()->y() + m_controlsShadowRoot->renderBox()->lowestPosition(includeOverflowInterior, includeSelf));
516 }
517 
rightmostPosition(bool includeOverflowInterior,bool includeSelf) const518 int RenderMedia::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const
519 {
520     int right = RenderReplaced::rightmostPosition(includeOverflowInterior, includeSelf);
521     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
522         return right;
523 
524     return max(right, m_controlsShadowRoot->renderBox()->x() + m_controlsShadowRoot->renderBox()->rightmostPosition(includeOverflowInterior, includeSelf));
525 }
526 
leftmostPosition(bool includeOverflowInterior,bool includeSelf) const527 int RenderMedia::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const
528 {
529     int left = RenderReplaced::leftmostPosition(includeOverflowInterior, includeSelf);
530     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
531         return left;
532 
533     return min(left, m_controlsShadowRoot->renderBox()->x() +  m_controlsShadowRoot->renderBox()->leftmostPosition(includeOverflowInterior, includeSelf));
534 }
535 
536 
537 // We want the timeline slider to be at least 100 pixels wide.
538 static const int minWidthToDisplayTimeDisplays = 16 + 16 + 45 + 100 + 45 + 16 + 1;
539 
shouldShowTimeDisplayControls() const540 bool RenderMedia::shouldShowTimeDisplayControls() const
541 {
542     if (!m_currentTimeDisplay && !m_timeRemainingDisplay)
543         return false;
544 
545     int width = mediaElement()->renderBox()->width();
546     return width >= minWidthToDisplayTimeDisplays * style()->effectiveZoom();
547 }
548 
549 } // namespace WebCore
550 
551 #endif
552