2 * Copyright (C) 2007 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
29 #include "RenderMedia.h"
31 #include "CSSStyleSelector.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"
44 #include "RenderSlider.h"
45 #include "SystemTime.h"
51 using namespace EventNames;
52 using namespace HTMLNames;
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;
59 class RenderMediaControlShadowRoot : public RenderBlock {
61 RenderMediaControlShadowRoot(Element* e) : RenderBlock(e) { }
62 void setParent(RenderObject* p) { RenderObject::setParent(p); }
65 class MediaControlShadowRootElement : public HTMLDivElement {
67 MediaControlShadowRootElement(Document* doc, HTMLMediaElement* mediaElement);
69 virtual bool isShadowNode() const { return true; }
70 virtual Node* shadowParentNode() { return m_mediaElement; }
73 HTMLMediaElement* m_mediaElement;
76 MediaControlShadowRootElement::MediaControlShadowRootElement(Document* doc, HTMLMediaElement* mediaElement)
78 , m_mediaElement(mediaElement)
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);
91 // ----------------------------
93 class MediaControlInputElement : public HTMLInputElement {
95 MediaControlInputElement(Document*, RenderStyle::PseudoId, String type, HTMLMediaElement*);
96 void attachToParent(PassRefPtr<Element>);
98 HTMLMediaElement* m_mediaElement;
101 MediaControlInputElement::MediaControlInputElement(Document* doc, RenderStyle::PseudoId pseudo, String type, HTMLMediaElement* mediaElement)
102 : HTMLInputElement(doc)
103 , m_mediaElement(mediaElement)
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();
115 void MediaControlInputElement::attachToParent(PassRefPtr<Element> parent)
117 parent->addChild(this);
118 parent->renderer()->addChild(renderer());
121 // ----------------------------
123 class MediaControlPlayButtonElement : public MediaControlInputElement {
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*);
132 bool MediaControlPlayButtonElement::inPausedState() const
134 return m_mediaElement->paused() || m_mediaElement->ended() || m_mediaElement->networkState() < HTMLMediaElement::LOADED_METADATA;
137 void MediaControlPlayButtonElement::defaultEventHandler(Event* event)
139 if (event->type() == clickEvent) {
142 m_mediaElement->play(ec);
144 m_mediaElement->pause(ec);
145 event->defaultHandled();
147 HTMLInputElement::defaultEventHandler(event);
150 void MediaControlPlayButtonElement::update()
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();
159 // ----------------------------
161 class MediaControlTimelineElement : public MediaControlInputElement {
163 MediaControlTimelineElement(Document* doc, HTMLMediaElement* element)
164 : MediaControlInputElement(doc, RenderStyle::MEDIA_CONTROLS_TIMELINE, "range", element) {
165 setAttribute(precisionAttr, "float");
167 virtual void defaultEventHandler(Event*);
168 void update(bool updateDuration = true);
171 void MediaControlTimelineElement::defaultEventHandler(Event* event)
173 float oldTime = (float)value().toDouble();
174 HTMLInputElement::defaultEventHandler(event);
175 float time = (float)value().toDouble();
176 if (oldTime != time) {
178 m_mediaElement->setCurrentTime(time, ec);
182 void MediaControlTimelineElement::update(bool updateDuration)
184 if (updateDuration) {
185 float dur = m_mediaElement->duration();
186 setAttribute(maxAttr, String::number(isfinite(dur) ? dur : 0));
188 setValue(String::number(m_mediaElement->currentTime()));
191 // ----------------------------
193 RenderMedia::RenderMedia(HTMLMediaElement* video)
194 : RenderReplaced(video)
195 , m_controlsShadowRoot(0)
200 , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
201 , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
203 , m_opacityAnimationStartTime(0)
204 , m_opacityAnimationFrom(0)
205 , m_opacityAnimationTo(1.0f)
209 RenderMedia::RenderMedia(HTMLMediaElement* video, const IntSize& intrinsicSize)
210 : RenderReplaced(video, intrinsicSize)
211 , m_controlsShadowRoot(0)
216 , m_timeUpdateTimer(this, &RenderMedia::timeUpdateTimerFired)
217 , m_opacityAnimationTimer(this, &RenderMedia::opacityAnimationTimerFired)
219 , m_opacityAnimationStartTime(0)
220 , m_opacityAnimationFrom(0)
221 , m_opacityAnimationTo(1.0f)
225 RenderMedia::~RenderMedia()
227 if (m_controlsShadowRoot && m_controlsShadowRoot->renderer()) {
228 static_cast<RenderMediaControlShadowRoot*>(m_controlsShadowRoot->renderer())->setParent(0);
229 m_controlsShadowRoot->detach();
233 HTMLMediaElement* RenderMedia::mediaElement() const
235 return static_cast<HTMLMediaElement*>(node());
238 Movie* RenderMedia::movie() const
240 return mediaElement()->movie();
243 void RenderMedia::layout()
245 IntSize oldSize = contentBox().size();
247 RenderReplaced::layout();
249 RenderObject* controlsRenderer = m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
250 if (!controlsRenderer)
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);
262 RenderObject* RenderMedia::firstChild() const
264 return m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
267 RenderObject* RenderMedia::lastChild() const
269 return m_controlsShadowRoot ? m_controlsShadowRoot->renderer() : 0;
272 void RenderMedia::removeChild(RenderObject* child)
274 ASSERT(m_controlsShadowRoot);
275 ASSERT(child == m_controlsShadowRoot->renderer());
276 child->removeLayers(enclosingLayer());
279 void RenderMedia::createControlsShadowRoot()
281 ASSERT(!m_controlsShadowRoot);
282 m_controlsShadowRoot = new MediaControlShadowRootElement(document(), mediaElement());
285 void RenderMedia::createPanel()
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);
299 void RenderMedia::createPlayButton()
301 ASSERT(!m_playButton);
302 m_playButton = new MediaControlPlayButtonElement(document(), mediaElement());
303 m_playButton->attachToParent(m_panel);
306 void RenderMedia::createTimeline()
309 m_timeline = new MediaControlTimelineElement(document(), mediaElement());
310 m_timeline->attachToParent(m_panel);
313 void RenderMedia::createTimeDisplay()
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);
327 void RenderMedia::updateFromElement()
332 void RenderMedia::updateControls()
334 HTMLMediaElement* media = mediaElement();
335 if (!media->controls()) {
336 if (m_controlsShadowRoot) {
337 m_controlsShadowRoot->detach();
342 m_controlsShadowRoot = 0;
347 if (!m_controlsShadowRoot) {
348 createControlsShadowRoot();
355 if (media->paused() || media->ended() || media->networkState() < HTMLMediaElement::LOADED_METADATA)
356 m_timeUpdateTimer.stop();
358 m_timeUpdateTimer.startRepeating(cTimeUpdateRepeatDelay);
361 m_playButton->update();
363 m_timeline->update();
365 updateControlVisibility();
368 void RenderMedia::timeUpdateTimerFired(Timer<RenderMedia>*)
371 m_timeline->update(false);
375 String RenderMedia::formatTime(float time)
379 int seconds = (int)time;
380 int hours = seconds / (60 * 60);
381 int minutes = (seconds / 60) % 60;
383 return String::format("%02d:%02d:%02d", hours, minutes, seconds);
386 void RenderMedia::updateTimeDisplay()
390 String timeString = formatTime(mediaElement()->currentTime());
392 m_timeDisplay->setInnerText(timeString, ec);
395 void RenderMedia::updateControlVisibility()
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))
405 m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
406 m_opacityAnimationTo = 1.0f;
408 m_opacityAnimationFrom = m_panel->renderer()->style()->opacity();
409 m_opacityAnimationTo = 0;
411 m_opacityAnimationStartTime = currentTime();
412 m_opacityAnimationTimer.startRepeating(cOpacityAnimationRepeatDelay);
415 void RenderMedia::changeOpacity(HTMLElement* e, float opacity)
417 if (!e || !e->renderer() || !e->renderer()->style())
419 RenderStyle* s = new (renderArena()) RenderStyle(*e->renderer()->style());
420 s->setOpacity(opacity);
421 // z-index can't be auto if opacity is used
423 e->renderer()->setStyle(s);
426 void RenderMedia::opacityAnimationTimerFired(Timer<RenderMedia>*)
428 double time = currentTime() - m_opacityAnimationStartTime;
429 if (time >= cOpacityAnimationDuration) {
430 time = cOpacityAnimationDuration;
431 m_opacityAnimationTimer.stop();
433 float opacity = narrowPrecisionToFloat(m_opacityAnimationFrom + (m_opacityAnimationTo - m_opacityAnimationFrom) * time / cOpacityAnimationDuration);
434 changeOpacity(m_panel.get(), opacity);
437 void RenderMedia::forwardEvent(Event* event)
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);
447 if (event->type() == mouseoverEvent) {
449 updateControlVisibility();
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();
459 } // namespace WebCore