46b4b09b180d0e99a9be6075db8622179a882e87
[WebKit-https.git] / WebCore / rendering / RenderMedia.cpp
1 /*
2  * Copyright (C) 2007 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 "Document.h"
33 #include "Event.h"
34 #include "EventNames.h"
35 #include "FloatConversion.h"
36 #include "FrameView.h"
37 #include "GraphicsContext.h"
38 #include "HTMLDivElement.h"
39 #include "HTMLInputElement.h"
40 #include "HTMLMediaElement.h"
41 #include "HTMLNames.h"
42 #include "MouseEvent.h"
43 #include "Movie.h"
44 #include "RenderSlider.h"
45 #include "SystemTime.h"
46
47 using namespace std;
48
49 namespace WebCore {
50
51 using namespace EventNames;
52 using namespace HTMLNames;
53     
54 static const double cTimeUpdateRepeatDelay = 0.2;
55 static const double cOpacityAnimationRepeatDelay = 0.05;
56 // FIXME get this from style
57 static const double cOpacityAnimationDuration = 0.5;
58
59 class RenderMediaControlShadowRoot : public RenderBlock {
60 public:
61     RenderMediaControlShadowRoot(Element* e) : RenderBlock(e) { }
62     void setParent(RenderObject* p) { RenderObject::setParent(p); }
63 };
64
65 class MediaControlShadowRootElement : public HTMLDivElement {
66 public:
67     MediaControlShadowRootElement(Document* doc, HTMLMediaElement* mediaElement);
68     
69     virtual bool isShadowNode() const { return true; }
70     virtual Node* shadowParentNode() { return m_mediaElement; }
71     
72 private:
73     HTMLMediaElement* m_mediaElement;    
74 };
75     
76 MediaControlShadowRootElement::MediaControlShadowRootElement(Document* doc, HTMLMediaElement* mediaElement) 
77     : HTMLDivElement(doc)
78     , m_mediaElement(mediaElement) 
79 {
80     RenderStyle* rootStyle = new (mediaElement->renderer()->renderArena()) RenderStyle();
81     rootStyle->setDisplay(BLOCK);
82     rootStyle->setPosition(AbsolutePosition);
83     RenderMediaControlShadowRoot* renderer = new (mediaElement->renderer()->renderArena()) RenderMediaControlShadowRoot(this);
84     renderer->setParent(mediaElement->renderer());
85     renderer->setStyle(rootStyle);
86     setRenderer(renderer);
87     setAttached();
88     setInDocument(true);
89 }
90     
91 // ----------------------------
92
93 class MediaControlInputElement : public HTMLInputElement {
94 public:
95     MediaControlInputElement(Document*, RenderStyle::PseudoId, String type, HTMLMediaElement*);
96     void attachToParent(PassRefPtr<Element>);
97 protected:
98     HTMLMediaElement* m_mediaElement;   
99 };
100     
101 MediaControlInputElement::MediaControlInputElement(Document* doc, RenderStyle::PseudoId pseudo, String type, HTMLMediaElement* mediaElement) 
102 : HTMLInputElement(doc)
103 , m_mediaElement(mediaElement)
104 {
105     setInputType(type);
106     RenderStyle* style = m_mediaElement->renderer()->getPseudoStyle(pseudo);
107     RenderObject* renderer = createRenderer(m_mediaElement->renderer()->renderArena(), style);
108     setRenderer(renderer);
109     renderer->setStyle(style);
110     renderer->updateFromElement();
111     setAttached();
112     setInDocument(true);
113 }
114
115 void MediaControlInputElement::attachToParent(PassRefPtr<Element> parent)
116 {
117     parent->addChild(this);
118     parent->renderer()->addChild(renderer());
119 }
120     
121 // ----------------------------
122
123 class MediaControlPlayButtonElement : public MediaControlInputElement {
124 public:
125     MediaControlPlayButtonElement(Document* doc, HTMLMediaElement* element)
126         : MediaControlInputElement(doc, RenderStyle::MEDIA_CONTROLS_PLAY_BUTTON, "button", element) { }
127     bool inPausedState() const;
128     virtual void defaultEventHandler(Event*);
129     void update();
130 };
131
132 bool MediaControlPlayButtonElement::inPausedState() const
133 {
134     return m_mediaElement->paused() || m_mediaElement->ended() || m_mediaElement->networkState() < HTMLMediaElement::LOADED_METADATA;
135 }
136
137 void MediaControlPlayButtonElement::defaultEventHandler(Event* event)
138 {
139     if (event->type() == clickEvent) {
140         ExceptionCode ec;
141         if (inPausedState())
142             m_mediaElement->play(ec);
143         else 
144             m_mediaElement->pause(ec);
145         event->defaultHandled();
146     }
147     HTMLInputElement::defaultEventHandler(event);
148 }
149
150 void MediaControlPlayButtonElement::update()
151 {
152     // FIXME: these are here just for temporary look
153     static const UChar blackRightPointingTriangle = 0x25b6;
154     const UChar twoBlackVerticalRectangles[] = { 0x25AE, 0x25AE };
155     setValue(inPausedState() ? String(&blackRightPointingTriangle, 1) : String(&twoBlackVerticalRectangles[0], 2));
156     renderer()->updateFromElement();
157 }
158
159 // ----------------------------
160
161 class MediaControlTimelineElement : public MediaControlInputElement {
162 public:
163     MediaControlTimelineElement(Document* doc, HTMLMediaElement* element)
164         : MediaControlInputElement(doc, RenderStyle::MEDIA_CONTROLS_TIMELINE, "range", element) { 
165             setAttribute(precisionAttr, "float");
166         }
167     virtual void defaultEventHandler(Event*);
168     void update(bool updateDuration = true);
169 };
170
171 void MediaControlTimelineElement::defaultEventHandler(Event* event)
172 {
173     float oldTime = (float)value().toDouble();
174     HTMLInputElement::defaultEventHandler(event);
175     float time = (float)value().toDouble();
176     if (oldTime != time) {
177         ExceptionCode ec;
178         m_mediaElement->setCurrentTime(time, ec);
179     }
180 }
181
182 void MediaControlTimelineElement::update(bool updateDuration) 
183 {
184     if (updateDuration) {
185         float dur = m_mediaElement->duration();
186         setAttribute(maxAttr, String::number(isfinite(dur) ? dur : 0));
187     }
188     setValue(String::number(m_mediaElement->currentTime()));
189 }
190
191 // ----------------------------
192
193 RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize)
194     : RenderReplaced(video, intrinsicSize)
195     , m_controlsShadowRoot(0)
196     , m_panel(0)
197     , m_playButton(0)
198     , m_timeline(0)
199     , m_timeDisplay(0)
200     , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
201     , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
202     , m_mouseOver(false)
203     , m_opacityAnimationStartTime(0)
204     , m_opacityAnimationFrom(0)
205     , m_opacityAnimationTo(1.0f)
206 {
207 }
208
209 RenderMedia::~RenderMedia()
210 {
211     if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
212         static_cast<RenderMediaControlShadowRoot*>(m_controlsShadowRoot->renderer())->setParent(0);
213         m_controlsShadowRoot->detach();
214     }
215 }
216  
217 HTMLMediaElement* RenderMedia::mediaElement() const
218
219     return static_cast<HTMLMediaElement*>(node()); 
220 }
221
222 Movie* RenderMedia::movie() const
223 {
224     return mediaElement()->movie();
225 }
226
227 void RenderMedia::layout()
228 {
229     IntSize oldSize = contentBox().size();
230
231     RenderReplaced::layout();
232
233     RenderObject* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
234     if (!controlsRenderer)
235         return;
236     IntSize newSize = contentBox().size();
237     if (newSize != oldSize || controlsRenderer->needsLayout()) {
238         controlsRenderer->style()->setHeight(Length(newSize.height(), Fixed));
239         controlsRenderer->style()->setWidth(Length(newSize.width(), Fixed));
240         controlsRenderer->setNeedsLayout(true, false);
241         controlsRenderer->layout();
242         setChildNeedsLayout(false);
243     }
244 }
245
246 RenderObject* RenderMedia::firstChild() const 
247
248     return m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0; 
249 }
250
251 RenderObject* RenderMedia::lastChild() const 
252
253     return m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
254 }
255     
256 void RenderMedia::removeChild(RenderObject* child)
257 {
258     ASSERT(m_controlsShadowRoot);
259     ASSERT(child == m_controlsShadowRoot->renderer());
260     child->removeLayers(enclosingLayer());
261 }
262     
263 void RenderMedia::createControlsShadowRoot()
264 {
265     ASSERT(!m_controlsShadowRoot);
266     m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement());
267 }
268
269 void RenderMedia::createPanel()
270 {
271     ASSERT(!m_panel);
272     RenderStyle* style = getPseudoStyle(RenderStyle::MEDIA_CONTROLS_PANEL);
273     m_panel = new HTMLDivElement(document());
274     RenderObject* renderer = m_panel->createRenderer(renderArena(), style);
275     m_panel->setRenderer(renderer);
276     renderer->setStyle(style);
277     m_panel->setAttached();
278     m_panel->setInDocument(true);
279     m_controlsShadowRoot->addChild(m_panel);
280     m_controlsShadowRoot->renderer()->addChild(renderer);
281 }
282
283 void RenderMedia::createPlayButton()
284 {
285     ASSERT(!m_playButton);
286     m_playButton = new MediaControlPlayButtonElement(document(), mediaElement());
287     m_playButton->attachToParent(m_panel);
288 }
289
290 void RenderMedia::createTimeline()
291 {
292     ASSERT(!m_timeline);
293     m_timeline = new MediaControlTimelineElement(document(), mediaElement());
294     m_timeline->attachToParent(m_panel);
295 }
296   
297 void RenderMedia::createTimeDisplay()
298 {
299     ASSERT(!m_timeDisplay);
300     RenderStyle* style = getPseudoStyle(RenderStyle::MEDIA_CONTROLS_TIME_DISPLAY);
301     m_timeDisplay = new HTMLDivElement(document());
302     RenderObject* renderer = m_timeDisplay->createRenderer(renderArena(), style);
303     m_timeDisplay->setRenderer(renderer);
304     renderer->setStyle(style);
305     m_timeDisplay->setAttached();
306     m_timeDisplay->setInDocument(true);
307     m_panel->addChild(m_timeDisplay);
308     m_panel->renderer()->addChild(renderer);
309 }
310     
311 void RenderMedia::updateFromElement()
312 {
313     updateControls();
314 }
315             
316 void RenderMedia::updateControls()
317 {
318     HTMLMediaElement* media = mediaElement();
319     if (!media->controls()) {
320         if (m_controlsShadowRoot) {
321             m_controlsShadowRoot->detach();
322             m_panel = 0;
323             m_playButton = 0;
324             m_timeline = 0;
325             m_timeDisplay = 0;
326             m_controlsShadowRoot = 0;
327         }
328         return;
329     }
330     
331     if (!m_controlsShadowRoot) {
332         createControlsShadowRoot();
333         createPanel();
334         createPlayButton();
335         createTimeline();
336         createTimeDisplay();
337     }
338     
339     if (media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA)
340         m_timeUpdateTimer.stop();
341     else
342         m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
343     
344     if (m_playButton)
345         m_playButton->update();
346     if (m_timeline)
347         m_timeline->update();
348     updateTimeDisplay();
349     updateControlVisibility();
350 }
351
352 void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*)
353 {
354     if (m_timeline)
355         m_timeline->update(false);
356     updateTimeDisplay();
357 }
358     
359 String RenderMedia::formatTime(float time)
360 {
361     if (!isfinite(time))
362         time = 0;
363     int seconds = (int)time; 
364     int hours = seconds / (60 * 60);
365     int minutes = (seconds / 60) % 60;
366     seconds %= 60;
367     return String::format("%02d:%02d:%02d", hours, minutes, seconds);
368 }
369
370 void RenderMedia::updateTimeDisplay()
371 {
372     if (!m_timeDisplay)
373         return;
374     String timeString = formatTime(mediaElement()->currentTime());
375     ExceptionCode ec;
376     m_timeDisplay->setInnerText(timeString, ec);
377 }
378     
379 void RenderMedia::updateControlVisibility() 
380 {
381     if (!m_panel)
382         return;
383     // do fading manually, css animations don't work well with shadow trees
384     HTMLMediaElement* media = mediaElement();
385     bool visible = m_mouseOver || media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA;
386     if (visible == (m_opacityAnimationTo > 0))
387         return;
388     if (visible) {
389         m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
390         m_opacityAnimationTo = 1.0f;
391     } else {
392         m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
393         m_opacityAnimationTo = 0;
394     }
395     m_opacityAnimationStartTime = currentTime();
396     m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
397 }
398     
399 void RenderMedia::changeOpacity(HTMLElement* e, float opacity) 
400 {
401     if (!e || !e->renderer() || !e->renderer()->style())
402         return;
403     RenderStyle* s = new (renderArena()) RenderStyle(*e->renderer()->style());
404     s->setOpacity(opacity);
405     // z-index can't be auto if opacity is used
406     s->setZIndex(0);
407     e->renderer()->setStyle(s);
408 }
409     
410 void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*)
411 {
412     double time = currentTime() - m_opacityAnimationStartTime;
413     if (time >= cOpacityAnimationDuration) {
414         time = cOpacityAnimationDuration;
415         m_opacityAnimationTimer.stop();
416     }
417     float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / cOpacityAnimationDuration);
418     changeOpacity(m_panel.get(), opacity);
419 }
420     
421 void RenderMedia::forwardEvent(Event* event)
422 {
423     if (event->isMouseEvent() && m_controlsShadowRoot) {
424         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
425         IntPoint point(mouseEvent->pageX(), mouseEvent->pageY());
426         if (m_playButton && m_playButton->renderer()->absoluteBoundingBoxRect().contains(point))
427             m_playButton->defaultEventHandler(event);
428         if (m_timeline && m_timeline->renderer()->absoluteBoundingBoxRect().contains(point))
429             m_timeline->defaultEventHandler(event);
430         
431         if (event->type() == mouseoverEvent) {
432             m_mouseOver = true;
433             updateControlVisibility();
434         }
435         if (event->type() == mouseoutEvent) {
436             // FIXME: moving over scrollbar thumb generates mouseout for the ancestor media element for some reason
437             m_mouseOver = absoluteBoundingBoxRect().contains(point);
438             updateControlVisibility();
439         }
440     }
441 }
442
443 } // namespace WebCore
444
445 #endif