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