WebCore:
[WebKit-https.git] / WebCore / rendering / RenderMedia.cpp
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 cOpacityAnimationDuration = 0.1;
49
50 RenderMedia::RenderMedia(HTMLMediaElement* video)
51     : RenderReplaced(video)
52     , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
53     , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
54     , m_mouseOver(false)
55     , m_opacityAnimationStartTime(0)
56     , m_opacityAnimationFrom(0)
57     , m_opacityAnimationTo(1.0f)
58     , m_previousVisible(VISIBLE)
59 {
60 }
61
62 RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize)
63     : RenderReplaced(video, intrinsicSize)
64     , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
65     , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
66     , m_mouseOver(false)
67     , m_opacityAnimationStartTime(0)
68     , m_opacityAnimationFrom(0)
69     , m_opacityAnimationTo(1.0f)
70 {
71 }
72
73 RenderMedia::~RenderMedia()
74 {
75 }
76
77 void RenderMedia::destroy()
78 {
79     if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
80
81         // detach the panel before removing the shadow renderer to prevent a crash in m_controlsShadowRoot->detach() 
82         //  when display: style changes
83         m_panel->detach();
84
85         removeChild(m_controlsShadowRoot->renderer());
86         m_controlsShadowRoot->detach();
87         m_controlsShadowRoot = 0;
88     }
89     RenderReplaced::destroy();
90 }
91
92 HTMLMediaElement* RenderMedia::mediaElement() const
93
94     return static_cast<HTMLMediaElement*>(node()); 
95 }
96
97 MediaPlayer* RenderMedia::player() const
98 {
99     return mediaElement()->player();
100 }
101
102 void RenderMedia::styleDidChange(StyleDifference diff, const RenderStyle* oldStyle)
103 {
104     RenderReplaced::styleDidChange(diff, oldStyle);
105
106     if (m_controlsShadowRoot) {
107         if (m_panel->renderer())
108             m_panel->renderer()->setStyle(getCachedPseudoStyle(MEDIA_CONTROLS_PANEL));
109
110         if (m_timelineContainer->renderer())
111             m_timelineContainer->renderer()->setStyle(getCachedPseudoStyle(MEDIA_CONTROLS_TIMELINE_CONTAINER));
112         
113         m_muteButton->updateStyle();
114         m_playButton->updateStyle();
115         m_seekBackButton->updateStyle();
116         m_seekForwardButton->updateStyle();
117         m_timeline->updateStyle();
118         m_fullscreenButton->updateStyle();
119         m_currentTimeDisplay->updateStyle();
120         m_timeRemainingDisplay->updateStyle();
121     }
122 }
123
124 void RenderMedia::layout()
125 {
126     IntSize oldSize = contentBoxRect().size();
127
128     RenderReplaced::layout();
129
130     RenderBox* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderBox() : 0;
131     if (!controlsRenderer)
132         return;
133     IntSize newSize = contentBoxRect().size();
134     if (newSize != oldSize || controlsRenderer->needsLayout()) {
135         controlsRenderer->setLocation(borderLeft() + paddingLeft(), borderTop() + paddingTop());
136         controlsRenderer->style()->setHeight(Length(newSize.height(), Fixed));
137         controlsRenderer->style()->setWidth(Length(newSize.width(), Fixed));
138         controlsRenderer->setNeedsLayout(true, false);
139         controlsRenderer->layout();
140         setChildNeedsLayout(false);
141     }
142 }
143
144 void RenderMedia::createControlsShadowRoot()
145 {
146     ASSERT(!m_controlsShadowRoot);
147     m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement());
148     addChild(m_controlsShadowRoot->renderer());
149 }
150
151 void RenderMedia::createPanel()
152 {
153     ASSERT(!m_panel);
154     RenderStyle* style = getCachedPseudoStyle(MEDIA_CONTROLS_PANEL);
155     m_panel = new HTMLDivElement(HTMLNames::divTag, document());
156     RenderObject* renderer = m_panel->createRenderer(renderArena(), style);
157     if (renderer) {
158         m_panel->setRenderer(renderer);
159         renderer->setStyle(style);
160         m_panel->setAttached();
161         m_panel->setInDocument(true);
162         m_controlsShadowRoot->addChild(m_panel);
163         m_controlsShadowRoot->renderer()->addChild(renderer);
164     }
165 }
166
167 void RenderMedia::createMuteButton()
168 {
169     ASSERT(!m_muteButton);
170     m_muteButton = new MediaControlMuteButtonElement(document(), mediaElement());
171     m_muteButton->attachToParent(m_panel.get());
172 }
173
174 void RenderMedia::createPlayButton()
175 {
176     ASSERT(!m_playButton);
177     m_playButton = new MediaControlPlayButtonElement(document(), mediaElement());
178     m_playButton->attachToParent(m_panel.get());
179 }
180
181 void RenderMedia::createSeekBackButton()
182 {
183     ASSERT(!m_seekBackButton);
184     m_seekBackButton = new MediaControlSeekButtonElement(document(), mediaElement(), false);
185     m_seekBackButton->attachToParent(m_panel.get());
186 }
187
188 void RenderMedia::createSeekForwardButton()
189 {
190     ASSERT(!m_seekForwardButton);
191     m_seekForwardButton = new MediaControlSeekButtonElement(document(), mediaElement(), true);
192     m_seekForwardButton->attachToParent(m_panel.get());
193 }
194
195 void RenderMedia::createTimelineContainer()
196 {
197     ASSERT(!m_timelineContainer);
198     RenderStyle* style = getCachedPseudoStyle(MEDIA_CONTROLS_TIMELINE_CONTAINER);
199     m_timelineContainer = new HTMLDivElement(HTMLNames::divTag, document());
200     RenderObject* renderer = m_timelineContainer->createRenderer(renderArena(), style);
201     if (renderer) {
202         m_timelineContainer->setRenderer(renderer);
203         renderer->setStyle(style);
204         m_timelineContainer->setAttached();
205         m_timelineContainer->setInDocument(true);
206         m_panel->addChild(m_timelineContainer);
207         m_panel->renderer()->addChild(renderer);
208     }
209 }
210
211 void RenderMedia::createTimeline()
212 {
213     ASSERT(!m_timeline);
214     m_timeline = new MediaControlTimelineElement(document(), mediaElement());
215     m_timeline->setAttribute(precisionAttr, "float");
216     m_timeline->attachToParent(m_timelineContainer.get());
217 }
218   
219 void RenderMedia::createCurrentTimeDisplay()
220 {
221     ASSERT(!m_currentTimeDisplay);
222     m_currentTimeDisplay = new MediaTimeDisplayElement(document(), mediaElement(), true);
223     m_currentTimeDisplay->attachToParent(m_timelineContainer.get());
224 }
225
226 void RenderMedia::createTimeRemainingDisplay()
227 {
228     ASSERT(!m_timeRemainingDisplay);
229     m_timeRemainingDisplay = new MediaTimeDisplayElement(document(), mediaElement(), false);
230     m_timeRemainingDisplay->attachToParent(m_timelineContainer.get());
231 }
232
233 void RenderMedia::createFullscreenButton()
234 {
235     ASSERT(!m_fullscreenButton);
236     m_fullscreenButton = new MediaControlFullscreenButtonElement(document(), mediaElement());
237     m_fullscreenButton->attachToParent(m_panel.get());
238 }
239     
240 void RenderMedia::updateFromElement()
241 {
242     updateControls();
243 }
244             
245 void RenderMedia::updateControls()
246 {
247     HTMLMediaElement* media = mediaElement();
248     if (!media->controls() || !media->inActiveDocument()) {
249         if (m_controlsShadowRoot) {
250             m_controlsShadowRoot->detach();
251             m_panel = 0;
252             m_muteButton = 0;
253             m_playButton = 0;
254             m_timelineContainer = 0;
255             m_timeline = 0;
256             m_seekBackButton = 0;
257             m_seekForwardButton = 0;
258             m_currentTimeDisplay = 0;
259             m_timeRemainingDisplay = 0;
260             m_fullscreenButton = 0;
261             m_controlsShadowRoot = 0;
262         }
263         m_opacityAnimationTo = 1.0f;
264         m_opacityAnimationTimer.stop();
265         m_timeUpdateTimer.stop();
266         return;
267     }
268     
269     if (!m_controlsShadowRoot) {
270         createControlsShadowRoot();
271         createPanel();
272         createMuteButton();
273         createPlayButton();
274         createTimelineContainer();
275         createTimeline();
276         createSeekBackButton();
277         createSeekForwardButton();
278         createCurrentTimeDisplay();
279         createTimeRemainingDisplay();
280         createFullscreenButton();
281     }
282
283     if (media->canPlay()) {
284         if (m_timeUpdateTimer.isActive())
285             m_timeUpdateTimer.stop();
286     } else if (style()->visibility() == VISIBLE && m_timeline && m_timeline->renderer() && m_timeline->renderer()->style()->display() != NONE ) {
287         m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
288     }
289
290     m_previousVisible = style()->visibility();
291     
292     if (m_muteButton)
293         m_muteButton->update();
294     if (m_playButton)
295         m_playButton->update();
296     if (m_timeline)
297         m_timeline->update();
298     if (m_seekBackButton)
299         m_seekBackButton->update();
300     if (m_seekForwardButton)
301         m_seekForwardButton->update();
302     if (m_fullscreenButton)
303         m_fullscreenButton->update();
304     updateTimeDisplay();
305     updateControlVisibility();
306 }
307
308 void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*)
309 {
310     if (m_timeline)
311         m_timeline->update(false);
312     updateTimeDisplay();
313 }
314     
315 String RenderMedia::formatTime(float time)
316 {
317     if (!isfinite(time))
318         time = 0;
319     int seconds = (int)fabsf(time); 
320     int hours = seconds / (60 * 60);
321     int minutes = (seconds / 60) % 60;
322     seconds %= 60;
323     if (hours) {
324         if (hours > 9)
325             return String::format("%s%02d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
326         else
327             return String::format("%s%01d:%02d:%02d", (time < 0 ? "-" : ""), hours, minutes, seconds);
328     }
329     else
330         return String::format("%s%02d:%02d", (time < 0 ? "-" : ""), minutes, seconds);
331 }
332
333 void RenderMedia::updateTimeDisplay()
334 {
335     if (!m_currentTimeDisplay || !m_currentTimeDisplay->renderer() || m_currentTimeDisplay->renderer()->style()->display() == NONE || style()->visibility() != VISIBLE)
336         return;
337     float now = mediaElement()->currentTime();
338     float duration = mediaElement()->duration();
339
340     String timeString = formatTime(now);
341     ExceptionCode ec;
342     m_currentTimeDisplay->setInnerText(timeString, ec);
343     
344     timeString = formatTime(now - duration);
345     m_timeRemainingDisplay->setInnerText(timeString, ec);
346 }
347
348 void RenderMedia::updateControlVisibility() 
349 {
350     if (!m_panel || !m_panel->renderer())
351         return;
352
353     // Don't fade for audio controls.
354     HTMLMediaElement* media = mediaElement();
355     if (!media->hasVideo())
356         return;
357
358     // do fading manually, css animations don't work well with shadow trees
359     bool visible = style()->visibility() == VISIBLE && (m_mouseOver || media->canPlay());
360     if (visible == (m_opacityAnimationTo > 0))
361         return;
362
363     if (style()->visibility() != m_previousVisible) {
364         // don't fade gradually if it the element has just changed visibility
365         m_previousVisible = style()->visibility();
366         m_opacityAnimationTo = m_previousVisible == VISIBLE ? 1.0f : 0;
367         changeOpacity(m_panel.get(), m_opacityAnimationTo);
368         return;
369     }
370
371     if (visible) {
372         m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
373         m_opacityAnimationTo = 1.0f;
374     } else {
375         m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
376         m_opacityAnimationTo = 0;
377     }
378     m_opacityAnimationStartTime = currentTime();
379     m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
380 }
381     
382 void RenderMedia::changeOpacity(HTMLElement* e, float opacity) 
383 {
384     if (!e || !e->renderer() || !e->renderer()->style())
385         return;
386     RefPtr<RenderStyle> s = RenderStyle::clone(e->renderer()->style());
387     s->setOpacity(opacity);
388     // z-index can't be auto if opacity is used
389     s->setZIndex(0);
390     e->renderer()->setStyle(s.release());
391 }
392     
393 void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*)
394 {
395     double time = currentTime() - m_opacityAnimationStartTime;
396     if (time >= cOpacityAnimationDuration) {
397         time = cOpacityAnimationDuration;
398         m_opacityAnimationTimer.stop();
399     }
400     float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / cOpacityAnimationDuration);
401     changeOpacity(m_panel.get(), opacity);
402 }
403
404 void RenderMedia::forwardEvent(Event* event)
405 {
406     if (event->isMouseEvent() && m_controlsShadowRoot) {
407         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
408         IntPoint point(mouseEvent->absoluteLocation());
409         if (m_muteButton && m_muteButton->hitTest(point))
410             m_muteButton->defaultEventHandler(event);
411
412         if (m_playButton && m_playButton->hitTest(point))
413             m_playButton->defaultEventHandler(event);
414
415         if (m_seekBackButton && m_seekBackButton->hitTest(point))
416             m_seekBackButton->defaultEventHandler(event);
417
418         if (m_seekForwardButton && m_seekForwardButton->hitTest(point))
419             m_seekForwardButton->defaultEventHandler(event);
420
421         if (m_timeline && m_timeline->hitTest(point))
422             m_timeline->defaultEventHandler(event);
423
424         if (m_fullscreenButton && m_fullscreenButton->hitTest(point))
425             m_fullscreenButton->defaultEventHandler(event);
426         
427         if (event->type() == eventNames().mouseoverEvent) {
428             m_mouseOver = true;
429             updateControlVisibility();
430         }
431         if (event->type() == eventNames().mouseoutEvent) {
432             // 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
433             Node* mouseOverNode = mouseEvent->relatedTarget() ? mouseEvent->relatedTarget()->toNode() : 0;
434             RenderObject* mouseOverRenderer = mouseOverNode ? mouseOverNode->renderer() : 0;
435             m_mouseOver = mouseOverRenderer && mouseOverRenderer->isDescendantOf(this);
436             updateControlVisibility();
437         }
438     }
439 }
440
441 int RenderMedia::lowestPosition(bool includeOverflowInterior, bool includeSelf) const
442 {
443     int bottom = RenderReplaced::lowestPosition(includeOverflowInterior, includeSelf);
444     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
445         return bottom;
446     
447     return max(bottom,  m_controlsShadowRoot->renderBox()->y() + m_controlsShadowRoot->renderBox()->lowestPosition(includeOverflowInterior, includeSelf));
448 }
449
450 int RenderMedia::rightmostPosition(bool includeOverflowInterior, bool includeSelf) const
451 {
452     int right = RenderReplaced::rightmostPosition(includeOverflowInterior, includeSelf);
453     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
454         return right;
455     
456     return max(right, m_controlsShadowRoot->renderBox()->x() + m_controlsShadowRoot->renderBox()->rightmostPosition(includeOverflowInterior, includeSelf));
457 }
458
459 int RenderMedia::leftmostPosition(bool includeOverflowInterior, bool includeSelf) const
460 {
461     int left = RenderReplaced::leftmostPosition(includeOverflowInterior, includeSelf);
462     if (!m_controlsShadowRoot || !m_controlsShadowRoot->renderer())
463         return left;
464     
465     return min(left, m_controlsShadowRoot->renderBox()->x() +  m_controlsShadowRoot->renderBox()->leftmostPosition(includeOverflowInterior, includeSelf));
466 }
467
468 } // namespace WebCore
469
470 #endif