4b5c0f0974c34dd72db49b3ba8c386cc633339fc
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / core / html / shadow / MediaControls.cpp
1 /*
2  * Copyright (C) 2011, 2012 Apple Inc. All rights reserved.
3  * Copyright (C) 2011, 2012 Google Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "core/html/shadow/MediaControls.h"
29
30 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
31 #include "core/events/MouseEvent.h"
32 #include "core/frame/Settings.h"
33 #include "core/html/HTMLMediaElement.h"
34 #include "core/html/MediaController.h"
35 #include "core/rendering/RenderTheme.h"
36
37 namespace blink {
38
39 // If you change this value, then also update the corresponding value in
40 // LayoutTests/media/media-controls.js.
41 static const double timeWithoutMouseMovementBeforeHidingMediaControls = 3;
42
43 static bool fullscreenIsSupported(const Document& document)
44 {
45     return !document.settings() || document.settings()->fullscreenSupported();
46 }
47
48 MediaControls::MediaControls(HTMLMediaElement& mediaElement)
49     : HTMLDivElement(mediaElement.document())
50     , m_mediaElement(&mediaElement)
51     , m_panel(nullptr)
52     , m_textDisplayContainer(nullptr)
53     , m_overlayPlayButton(nullptr)
54     , m_overlayEnclosure(nullptr)
55     , m_playButton(nullptr)
56     , m_currentTimeDisplay(nullptr)
57     , m_timeline(nullptr)
58     , m_muteButton(nullptr)
59     , m_volumeSlider(nullptr)
60     , m_toggleClosedCaptionsButton(nullptr)
61     , m_fullScreenButton(nullptr)
62     , m_durationDisplay(nullptr)
63     , m_enclosure(nullptr)
64     , m_hideMediaControlsTimer(this, &MediaControls::hideMediaControlsTimerFired)
65     , m_isMouseOverControls(false)
66     , m_isPausedForScrubbing(false)
67 {
68 }
69
70 PassRefPtrWillBeRawPtr<MediaControls> MediaControls::create(HTMLMediaElement& mediaElement)
71 {
72     RefPtrWillBeRawPtr<MediaControls> controls = adoptRefWillBeNoop(new MediaControls(mediaElement));
73
74     if (controls->initializeControls())
75         return controls.release();
76
77     return nullptr;
78 }
79
80 bool MediaControls::initializeControls()
81 {
82     TrackExceptionState exceptionState;
83
84     if (document().settings() && document().settings()->mediaControlsOverlayPlayButtonEnabled()) {
85         RefPtrWillBeRawPtr<MediaControlOverlayEnclosureElement> overlayEnclosure = MediaControlOverlayEnclosureElement::create(*this);
86         RefPtrWillBeRawPtr<MediaControlOverlayPlayButtonElement> overlayPlayButton = MediaControlOverlayPlayButtonElement::create(*this);
87         m_overlayPlayButton = overlayPlayButton.get();
88         overlayEnclosure->appendChild(overlayPlayButton.release(), exceptionState);
89         if (exceptionState.hadException())
90             return false;
91
92         m_overlayEnclosure = overlayEnclosure.get();
93         appendChild(overlayEnclosure.release(), exceptionState);
94         if (exceptionState.hadException())
95             return false;
96     }
97
98     // Create an enclosing element for the panel so we can visually offset the controls correctly.
99     RefPtrWillBeRawPtr<MediaControlPanelEnclosureElement> enclosure = MediaControlPanelEnclosureElement::create(*this);
100
101     RefPtrWillBeRawPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(*this);
102
103     RefPtrWillBeRawPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(*this);
104     m_playButton = playButton.get();
105     panel->appendChild(playButton.release(), exceptionState);
106     if (exceptionState.hadException())
107         return false;
108
109     RefPtrWillBeRawPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(*this);
110     m_timeline = timeline.get();
111     panel->appendChild(timeline.release(), exceptionState);
112     if (exceptionState.hadException())
113         return false;
114
115     RefPtrWillBeRawPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(*this);
116     m_currentTimeDisplay = currentTimeDisplay.get();
117     m_currentTimeDisplay->hide();
118     panel->appendChild(currentTimeDisplay.release(), exceptionState);
119     if (exceptionState.hadException())
120         return false;
121
122     RefPtrWillBeRawPtr<MediaControlTimeRemainingDisplayElement> durationDisplay = MediaControlTimeRemainingDisplayElement::create(*this);
123     m_durationDisplay = durationDisplay.get();
124     panel->appendChild(durationDisplay.release(), exceptionState);
125     if (exceptionState.hadException())
126         return false;
127
128     RefPtrWillBeRawPtr<MediaControlMuteButtonElement> muteButton = MediaControlMuteButtonElement::create(*this);
129     m_muteButton = muteButton.get();
130     panel->appendChild(muteButton.release(), exceptionState);
131     if (exceptionState.hadException())
132         return false;
133
134     RefPtrWillBeRawPtr<MediaControlVolumeSliderElement> slider = MediaControlVolumeSliderElement::create(*this);
135     m_volumeSlider = slider.get();
136     panel->appendChild(slider.release(), exceptionState);
137     if (exceptionState.hadException())
138         return false;
139
140     RefPtrWillBeRawPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(*this);
141     m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
142     panel->appendChild(toggleClosedCaptionsButton.release(), exceptionState);
143     if (exceptionState.hadException())
144         return false;
145
146     RefPtrWillBeRawPtr<MediaControlFullscreenButtonElement> fullscreenButton = MediaControlFullscreenButtonElement::create(*this);
147     m_fullScreenButton = fullscreenButton.get();
148     panel->appendChild(fullscreenButton.release(), exceptionState);
149     if (exceptionState.hadException())
150         return false;
151
152     m_panel = panel.get();
153     enclosure->appendChild(panel.release(), exceptionState);
154     if (exceptionState.hadException())
155         return false;
156
157     m_enclosure = enclosure.get();
158     appendChild(enclosure.release(), exceptionState);
159     if (exceptionState.hadException())
160         return false;
161
162     return true;
163 }
164
165 void MediaControls::reset()
166 {
167     double duration = mediaElement().duration();
168     m_durationDisplay->setInnerText(RenderTheme::theme().formatMediaControlsTime(duration), ASSERT_NO_EXCEPTION);
169     m_durationDisplay->setCurrentValue(duration);
170
171     updatePlayState();
172
173     updateCurrentTimeDisplay();
174
175     m_timeline->setDuration(duration);
176     m_timeline->setPosition(mediaElement().currentTime());
177
178     if (!mediaElement().hasAudio())
179         m_volumeSlider->hide();
180     else
181         m_volumeSlider->show();
182     updateVolume();
183
184     refreshClosedCaptionsButtonVisibility();
185
186     if (mediaElement().hasVideo() && fullscreenIsSupported(document()))
187         m_fullScreenButton->show();
188     else
189         m_fullScreenButton->hide();
190
191     makeOpaque();
192 }
193
194 void MediaControls::show()
195 {
196     makeOpaque();
197     m_panel->setIsDisplayed(true);
198     m_panel->show();
199     if (m_overlayPlayButton)
200         m_overlayPlayButton->updateDisplayType();
201 }
202
203 void MediaControls::mediaElementFocused()
204 {
205     show();
206     stopHideMediaControlsTimer();
207 }
208
209 void MediaControls::hide()
210 {
211     m_panel->setIsDisplayed(false);
212     m_panel->hide();
213     if (m_overlayPlayButton)
214         m_overlayPlayButton->hide();
215 }
216
217 void MediaControls::makeOpaque()
218 {
219     m_panel->makeOpaque();
220 }
221
222 void MediaControls::makeTransparent()
223 {
224     m_panel->makeTransparent();
225 }
226
227 bool MediaControls::shouldHideMediaControls(unsigned behaviorFlags) const
228 {
229     // Never hide for a media element without visual representation.
230     if (!mediaElement().hasVideo())
231         return false;
232     // Don't hide if the controls are hovered or the mouse is over the video area.
233     const bool ignoreVideoHover = behaviorFlags & IgnoreVideoHover;
234     if (m_panel->hovered() || (!ignoreVideoHover && m_isMouseOverControls))
235         return false;
236     // Don't hide if focus is on the HTMLMediaElement or within the
237     // controls/shadow tree. (Perform the checks separately to avoid going
238     // through all the potential ancestor hosts for the focused element.)
239     const bool ignoreFocus = behaviorFlags & IgnoreFocus;
240     if (!ignoreFocus && (mediaElement().focused() || contains(document().focusedElement())))
241         return false;
242     return true;
243 }
244
245 void MediaControls::playbackStarted()
246 {
247     m_currentTimeDisplay->show();
248     m_durationDisplay->hide();
249
250     updatePlayState();
251     m_timeline->setPosition(mediaElement().currentTime());
252     updateCurrentTimeDisplay();
253
254     startHideMediaControlsTimer();
255 }
256
257 void MediaControls::playbackProgressed()
258 {
259     m_timeline->setPosition(mediaElement().currentTime());
260     updateCurrentTimeDisplay();
261
262     if (shouldHideMediaControls())
263         makeTransparent();
264 }
265
266 void MediaControls::playbackStopped()
267 {
268     updatePlayState();
269     m_timeline->setPosition(mediaElement().currentTime());
270     updateCurrentTimeDisplay();
271     makeOpaque();
272
273     stopHideMediaControlsTimer();
274 }
275
276 void MediaControls::updatePlayState()
277 {
278     if (m_isPausedForScrubbing)
279         return;
280
281     if (m_overlayPlayButton)
282         m_overlayPlayButton->updateDisplayType();
283     m_playButton->updateDisplayType();
284 }
285
286 void MediaControls::beginScrubbing()
287 {
288     if (!mediaElement().togglePlayStateWillPlay()) {
289         m_isPausedForScrubbing = true;
290         mediaElement().togglePlayState();
291     }
292 }
293
294 void MediaControls::endScrubbing()
295 {
296     if (m_isPausedForScrubbing) {
297         m_isPausedForScrubbing = false;
298         if (mediaElement().togglePlayStateWillPlay())
299             mediaElement().togglePlayState();
300     }
301 }
302
303 void MediaControls::updateCurrentTimeDisplay()
304 {
305     double now = mediaElement().currentTime();
306     double duration = mediaElement().duration();
307
308     // After seek, hide duration display and show current time.
309     if (now > 0) {
310         m_currentTimeDisplay->show();
311         m_durationDisplay->hide();
312     }
313
314     // Allow the theme to format the time.
315     m_currentTimeDisplay->setInnerText(RenderTheme::theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
316     m_currentTimeDisplay->setCurrentValue(now);
317 }
318
319 void MediaControls::updateVolume()
320 {
321     m_muteButton->updateDisplayType();
322     if (m_muteButton->renderer())
323         m_muteButton->renderer()->paintInvalidationForWholeRenderer();
324
325     if (mediaElement().muted())
326         m_volumeSlider->setVolume(0);
327     else
328         m_volumeSlider->setVolume(mediaElement().volume());
329 }
330
331 void MediaControls::changedClosedCaptionsVisibility()
332 {
333     m_toggleClosedCaptionsButton->updateDisplayType();
334 }
335
336 void MediaControls::refreshClosedCaptionsButtonVisibility()
337 {
338     if (mediaElement().hasClosedCaptions())
339         m_toggleClosedCaptionsButton->show();
340     else
341         m_toggleClosedCaptionsButton->hide();
342 }
343
344 void MediaControls::closedCaptionTracksChanged()
345 {
346     refreshClosedCaptionsButtonVisibility();
347 }
348
349 void MediaControls::enteredFullscreen()
350 {
351     m_fullScreenButton->setIsFullscreen(true);
352     stopHideMediaControlsTimer();
353     startHideMediaControlsTimer();
354 }
355
356 void MediaControls::exitedFullscreen()
357 {
358     m_fullScreenButton->setIsFullscreen(false);
359     stopHideMediaControlsTimer();
360     startHideMediaControlsTimer();
361 }
362
363 void MediaControls::defaultEventHandler(Event* event)
364 {
365     HTMLDivElement::defaultEventHandler(event);
366
367     if (event->type() == EventTypeNames::mouseover) {
368         if (!containsRelatedTarget(event)) {
369             m_isMouseOverControls = true;
370             if (!mediaElement().togglePlayStateWillPlay()) {
371                 makeOpaque();
372                 if (shouldHideMediaControls())
373                     startHideMediaControlsTimer();
374             }
375         }
376         return;
377     }
378
379     if (event->type() == EventTypeNames::mouseout) {
380         if (!containsRelatedTarget(event)) {
381             m_isMouseOverControls = false;
382             stopHideMediaControlsTimer();
383         }
384         return;
385     }
386
387     if (event->type() == EventTypeNames::mousemove) {
388         // When we get a mouse move, show the media controls, and start a timer
389         // that will hide the media controls after a 3 seconds without a mouse move.
390         makeOpaque();
391         if (shouldHideMediaControls(IgnoreVideoHover))
392             startHideMediaControlsTimer();
393         return;
394     }
395 }
396
397 void MediaControls::hideMediaControlsTimerFired(Timer<MediaControls>*)
398 {
399     if (mediaElement().togglePlayStateWillPlay())
400         return;
401
402     if (!shouldHideMediaControls(IgnoreFocus | IgnoreVideoHover))
403         return;
404
405     makeTransparent();
406 }
407
408 void MediaControls::startHideMediaControlsTimer()
409 {
410     m_hideMediaControlsTimer.startOneShot(timeWithoutMouseMovementBeforeHidingMediaControls, FROM_HERE);
411 }
412
413 void MediaControls::stopHideMediaControlsTimer()
414 {
415     m_hideMediaControlsTimer.stop();
416 }
417
418 const AtomicString& MediaControls::shadowPseudoId() const
419 {
420     DEFINE_STATIC_LOCAL(AtomicString, id, ("-webkit-media-controls"));
421     return id;
422 }
423
424 bool MediaControls::containsRelatedTarget(Event* event)
425 {
426     if (!event->isMouseEvent())
427         return false;
428     EventTarget* relatedTarget = toMouseEvent(event)->relatedTarget();
429     if (!relatedTarget)
430         return false;
431     return contains(relatedTarget->toNode());
432 }
433
434 void MediaControls::createTextTrackDisplay()
435 {
436     if (m_textDisplayContainer)
437         return;
438
439     RefPtrWillBeRawPtr<MediaControlTextTrackContainerElement> textDisplayContainer = MediaControlTextTrackContainerElement::create(*this);
440     m_textDisplayContainer = textDisplayContainer.get();
441
442     // Insert it before (behind) all other control elements.
443     if (m_overlayEnclosure && m_overlayPlayButton)
444         m_overlayEnclosure->insertBefore(textDisplayContainer.release(), m_overlayPlayButton);
445     else
446         insertBefore(textDisplayContainer.release(), m_enclosure);
447 }
448
449 void MediaControls::showTextTrackDisplay()
450 {
451     if (!m_textDisplayContainer)
452         createTextTrackDisplay();
453     m_textDisplayContainer->show();
454 }
455
456 void MediaControls::hideTextTrackDisplay()
457 {
458     if (!m_textDisplayContainer)
459         createTextTrackDisplay();
460     m_textDisplayContainer->hide();
461 }
462
463 void MediaControls::updateTextTrackDisplay()
464 {
465     if (!m_textDisplayContainer)
466         createTextTrackDisplay();
467
468     m_textDisplayContainer->updateDisplay();
469 }
470
471 void MediaControls::trace(Visitor* visitor)
472 {
473     visitor->trace(m_mediaElement);
474     visitor->trace(m_panel);
475     visitor->trace(m_textDisplayContainer);
476     visitor->trace(m_overlayPlayButton);
477     visitor->trace(m_overlayEnclosure);
478     visitor->trace(m_playButton);
479     visitor->trace(m_currentTimeDisplay);
480     visitor->trace(m_timeline);
481     visitor->trace(m_muteButton);
482     visitor->trace(m_volumeSlider);
483     visitor->trace(m_toggleClosedCaptionsButton);
484     visitor->trace(m_fullScreenButton);
485     visitor->trace(m_durationDisplay);
486     visitor->trace(m_enclosure);
487     HTMLDivElement::trace(visitor);
488 }
489
490 }