• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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