WebCore:
[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)
194     : RenderReplaced(video)
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(HTMLMediaElement* video, const IntSize& intrinsicSize)
210     : RenderReplaced(video, intrinsicSize)
211     , m_controlsShadowRoot(0)
212     , m_panel(0)
213     , m_playButton(0)
214     , m_timeline(0)
215     , m_timeDisplay(0)
216     , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
217     , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
218     , m_mouseOver(false)
219     , m_opacityAnimationStartTime(0)
220     , m_opacityAnimationFrom(0)
221     , m_opacityAnimationTo(1.0f)
222 {
223 }
224
225 RenderMedia::~RenderMedia()
226 {
227     if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
228         static_cast<RenderMediaControlShadowRoot*>(m_controlsShadowRoot->renderer())->setParent(0);
229         m_controlsShadowRoot->detach();
230     }
231 }
232  
233 HTMLMediaElement* RenderMedia::mediaElement() const
234
235     return static_cast<HTMLMediaElement*>(node()); 
236 }
237
238 Movie* RenderMedia::movie() const
239 {
240     return mediaElement()->movie();
241 }
242
243 void RenderMedia::layout()
244 {
245     IntSize oldSize = contentBox().size();
246
247     RenderReplaced::layout();
248
249     RenderObject* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
250     if (!controlsRenderer)
251         return;
252     IntSize newSize = contentBox().size();
253     if (newSize != oldSize || controlsRenderer->needsLayout()) {
254         controlsRenderer->style()->setHeight(Length(newSize.height(), Fixed));
255         controlsRenderer->style()->setWidth(Length(newSize.width(), Fixed));
256         controlsRenderer->setNeedsLayout(true, false);
257         controlsRenderer->layout();
258         setChildNeedsLayout(false);
259     }
260 }
261
262 RenderObject* RenderMedia::firstChild() const 
263
264     return m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0; 
265 }
266
267 RenderObject* RenderMedia::lastChild() const 
268
269     return m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
270 }
271     
272 void RenderMedia::removeChild(RenderObject* child)
273 {
274     ASSERT(m_controlsShadowRoot);
275     ASSERT(child == m_controlsShadowRoot->renderer());
276     child->removeLayers(enclosingLayer());
277 }
278     
279 void RenderMedia::createControlsShadowRoot()
280 {
281     ASSERT(!m_controlsShadowRoot);
282     m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement());
283 }
284
285 void RenderMedia::createPanel()
286 {
287     ASSERT(!m_panel);
288     RenderStyle* style = getPseudoStyle(RenderStyle::MEDIA_CONTROLS_PANEL);
289     m_panel = new HTMLDivElement(document());
290     RenderObject* renderer = m_panel->createRenderer(renderArena(), style);
291     m_panel->setRenderer(renderer);
292     renderer->setStyle(style);
293     m_panel->setAttached();
294     m_panel->setInDocument(true);
295     m_controlsShadowRoot->addChild(m_panel);
296     m_controlsShadowRoot->renderer()->addChild(renderer);
297 }
298
299 void RenderMedia::createPlayButton()
300 {
301     ASSERT(!m_playButton);
302     m_playButton = new MediaControlPlayButtonElement(document(), mediaElement());
303     m_playButton->attachToParent(m_panel);
304 }
305
306 void RenderMedia::createTimeline()
307 {
308     ASSERT(!m_timeline);
309     m_timeline = new MediaControlTimelineElement(document(), mediaElement());
310     m_timeline->attachToParent(m_panel);
311 }
312   
313 void RenderMedia::createTimeDisplay()
314 {
315     ASSERT(!m_timeDisplay);
316     RenderStyle* style = getPseudoStyle(RenderStyle::MEDIA_CONTROLS_TIME_DISPLAY);
317     m_timeDisplay = new HTMLDivElement(document());
318     RenderObject* renderer = m_timeDisplay->createRenderer(renderArena(), style);
319     m_timeDisplay->setRenderer(renderer);
320     renderer->setStyle(style);
321     m_timeDisplay->setAttached();
322     m_timeDisplay->setInDocument(true);
323     m_panel->addChild(m_timeDisplay);
324     m_panel->renderer()->addChild(renderer);
325 }
326     
327 void RenderMedia::updateFromElement()
328 {
329     updateControls();
330 }
331             
332 void RenderMedia::updateControls()
333 {
334     HTMLMediaElement* media = mediaElement();
335     if (!media->controls()) {
336         if (m_controlsShadowRoot) {
337             m_controlsShadowRoot->detach();
338             m_panel = 0;
339             m_playButton = 0;
340             m_timeline = 0;
341             m_timeDisplay = 0;
342             m_controlsShadowRoot = 0;
343         }
344         return;
345     }
346     
347     if (!m_controlsShadowRoot) {
348         createControlsShadowRoot();
349         createPanel();
350         createPlayButton();
351         createTimeline();
352         createTimeDisplay();
353     }
354     
355     if (media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA)
356         m_timeUpdateTimer.stop();
357     else
358         m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
359     
360     if (m_playButton)
361         m_playButton->update();
362     if (m_timeline)
363         m_timeline->update();
364     updateTimeDisplay();
365     updateControlVisibility();
366 }
367
368 void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*)
369 {
370     if (m_timeline)
371         m_timeline->update(false);
372     updateTimeDisplay();
373 }
374     
375 String RenderMedia::formatTime(float time)
376 {
377     if (!isfinite(time))
378         time = 0;
379     int seconds = (int)time; 
380     int hours = seconds / (60 * 60);
381     int minutes = (seconds / 60) % 60;
382     seconds %= 60;
383     return String::format("%02d:%02d:%02d", hours, minutes, seconds);
384 }
385
386 void RenderMedia::updateTimeDisplay()
387 {
388     if (!m_timeDisplay)
389         return;
390     String timeString = formatTime(mediaElement()->currentTime());
391     ExceptionCode ec;
392     m_timeDisplay->setInnerText(timeString, ec);
393 }
394     
395 void RenderMedia::updateControlVisibility() 
396 {
397     if (!m_panel)
398         return;
399     // do fading manually, css animations don't work well with shadow trees
400     HTMLMediaElement* media = mediaElement();
401     bool visible = m_mouseOver || media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA;
402     if (visible == (m_opacityAnimationTo > 0))
403         return;
404     if (visible) {
405         m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
406         m_opacityAnimationTo = 1.0f;
407     } else {
408         m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
409         m_opacityAnimationTo = 0;
410     }
411     m_opacityAnimationStartTime = currentTime();
412     m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
413 }
414     
415 void RenderMedia::changeOpacity(HTMLElement* e, float opacity) 
416 {
417     if (!e || !e->renderer() || !e->renderer()->style())
418         return;
419     RenderStyle* s = new (renderArena()) RenderStyle(*e->renderer()->style());
420     s->setOpacity(opacity);
421     // z-index can't be auto if opacity is used
422     s->setZIndex(0);
423     e->renderer()->setStyle(s);
424 }
425     
426 void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*)
427 {
428     double time = currentTime() - m_opacityAnimationStartTime;
429     if (time >= cOpacityAnimationDuration) {
430         time = cOpacityAnimationDuration;
431         m_opacityAnimationTimer.stop();
432     }
433     float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / cOpacityAnimationDuration);
434     changeOpacity(m_panel.get(), opacity);
435 }
436     
437 void RenderMedia::forwardEvent(Event* event)
438 {
439     if (event->isMouseEvent() && m_controlsShadowRoot) {
440         MouseEvent* mouseEvent = static_cast<MouseEvent*>(event);
441         IntPoint point(mouseEvent->pageX(), mouseEvent->pageY());
442         if (m_playButton && m_playButton->renderer()->absoluteBoundingBoxRect().contains(point))
443             m_playButton->defaultEventHandler(event);
444         if (m_timeline && m_timeline->renderer()->absoluteBoundingBoxRect().contains(point))
445             m_timeline->defaultEventHandler(event);
446         
447         if (event->type() == mouseoverEvent) {
448             m_mouseOver = true;
449             updateControlVisibility();
450         }
451         if (event->type() == mouseoutEvent) {
452             // FIXME: moving over scrollbar thumb generates mouseout for the ancestor media element for some reason
453             m_mouseOver = absoluteBoundingBoxRect().contains(point);
454             updateControlVisibility();
455         }
456     }
457 }
458
459 } // namespace WebCore
460
461 #endif