1/*
2 * Copyright (C) 2007, 2008, 2009, 2010, 2011 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 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 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
29#if ENABLE(VIDEO)
30#include "MediaControlsApple.h"
31
32#include "CSSValueKeywords.h"
33#include "ExceptionCodePlaceholder.h"
34#include "HTMLNames.h"
35#include "WheelEvent.h"
36
37namespace WebCore {
38
39MediaControlsApple::MediaControlsApple(Document& document)
40    : MediaControls(document)
41    , m_rewindButton(0)
42    , m_returnToRealTimeButton(0)
43    , m_statusDisplay(0)
44    , m_timeRemainingDisplay(0)
45    , m_timelineContainer(0)
46    , m_seekBackButton(0)
47    , m_seekForwardButton(0)
48    , m_closedCaptionsTrackList(0)
49    , m_closedCaptionsContainer(0)
50    , m_volumeSliderMuteButton(0)
51    , m_volumeSliderContainer(0)
52    , m_fullScreenMinVolumeButton(0)
53    , m_fullScreenVolumeSlider(0)
54    , m_fullScreenMaxVolumeButton(0)
55{
56}
57
58PassRefPtr<MediaControls> MediaControls::create(Document& document)
59{
60    return MediaControlsApple::createControls(document);
61}
62
63PassRefPtr<MediaControlsApple> MediaControlsApple::createControls(Document& document)
64{
65    if (!document.page())
66        return 0;
67
68    RefPtr<MediaControlsApple> controls = adoptRef(new MediaControlsApple(document));
69
70    RefPtr<MediaControlPanelElement> panel = MediaControlPanelElement::create(document);
71
72    ExceptionCode ec;
73
74    RefPtr<MediaControlRewindButtonElement> rewindButton = MediaControlRewindButtonElement::create(document);
75    controls->m_rewindButton = rewindButton.get();
76    panel->appendChild(rewindButton.release(), ec);
77    if (ec)
78        return 0;
79
80    RefPtr<MediaControlPlayButtonElement> playButton = MediaControlPlayButtonElement::create(document);
81    controls->m_playButton = playButton.get();
82    panel->appendChild(playButton.release(), ec);
83    if (ec)
84        return 0;
85
86    RefPtr<MediaControlReturnToRealtimeButtonElement> returnToRealtimeButton = MediaControlReturnToRealtimeButtonElement::create(document);
87    controls->m_returnToRealTimeButton = returnToRealtimeButton.get();
88    panel->appendChild(returnToRealtimeButton.release(), ec);
89    if (ec)
90        return 0;
91
92    if (document.page()->theme().usesMediaControlStatusDisplay()) {
93        RefPtr<MediaControlStatusDisplayElement> statusDisplay = MediaControlStatusDisplayElement::create(document);
94        controls->m_statusDisplay = statusDisplay.get();
95        panel->appendChild(statusDisplay.release(), ec);
96        if (ec)
97            return 0;
98    }
99
100    RefPtr<MediaControlTimelineContainerElement> timelineContainer = MediaControlTimelineContainerElement::create(document);
101
102    RefPtr<MediaControlCurrentTimeDisplayElement> currentTimeDisplay = MediaControlCurrentTimeDisplayElement::create(document);
103    controls->m_currentTimeDisplay = currentTimeDisplay.get();
104    timelineContainer->appendChild(currentTimeDisplay.release(), ec);
105    if (ec)
106        return 0;
107
108    RefPtr<MediaControlTimelineElement> timeline = MediaControlTimelineElement::create(document, controls.get());
109    controls->m_timeline = timeline.get();
110    timelineContainer->appendChild(timeline.release(), ec);
111    if (ec)
112        return 0;
113
114    RefPtr<MediaControlTimeRemainingDisplayElement> timeRemainingDisplay = MediaControlTimeRemainingDisplayElement::create(document);
115    controls->m_timeRemainingDisplay = timeRemainingDisplay.get();
116    timelineContainer->appendChild(timeRemainingDisplay.release(), ec);
117    if (ec)
118        return 0;
119
120    controls->m_timelineContainer = timelineContainer.get();
121    panel->appendChild(timelineContainer.release(), ec);
122    if (ec)
123        return 0;
124
125    // FIXME: Only create when needed <http://webkit.org/b/57163>
126    RefPtr<MediaControlSeekBackButtonElement> seekBackButton = MediaControlSeekBackButtonElement::create(document);
127    controls->m_seekBackButton = seekBackButton.get();
128    panel->appendChild(seekBackButton.release(), ec);
129    if (ec)
130        return 0;
131
132    // FIXME: Only create when needed <http://webkit.org/b/57163>
133    RefPtr<MediaControlSeekForwardButtonElement> seekForwardButton = MediaControlSeekForwardButtonElement::create(document);
134    controls->m_seekForwardButton = seekForwardButton.get();
135    panel->appendChild(seekForwardButton.release(), ec);
136    if (ec)
137        return 0;
138
139    if (document.page()->theme().supportsClosedCaptioning()) {
140        RefPtr<MediaControlClosedCaptionsContainerElement> closedCaptionsContainer = MediaControlClosedCaptionsContainerElement::create(document);
141
142        RefPtr<MediaControlClosedCaptionsTrackListElement> closedCaptionsTrackList = MediaControlClosedCaptionsTrackListElement::create(document, controls.get());
143        controls->m_closedCaptionsTrackList = closedCaptionsTrackList.get();
144        closedCaptionsContainer->appendChild(closedCaptionsTrackList.release(), ec);
145        if (ec)
146            return 0;
147
148        RefPtr<MediaControlToggleClosedCaptionsButtonElement> toggleClosedCaptionsButton = MediaControlToggleClosedCaptionsButtonElement::create(document, controls.get());
149        controls->m_toggleClosedCaptionsButton = toggleClosedCaptionsButton.get();
150        panel->appendChild(toggleClosedCaptionsButton.release(), ec);
151        if (ec)
152            return 0;
153
154        controls->m_closedCaptionsContainer = closedCaptionsContainer.get();
155        controls->appendChild(closedCaptionsContainer.release(), ec);
156        if (ec)
157            return 0;
158    }
159
160    // FIXME: Only create when needed <http://webkit.org/b/57163>
161    RefPtr<MediaControlFullscreenButtonElement> fullScreenButton = MediaControlFullscreenButtonElement::create(document);
162    controls->m_fullScreenButton = fullScreenButton.get();
163    panel->appendChild(fullScreenButton.release(), ec);
164
165    // The mute button and the slider element should be in the same div.
166    RefPtr<HTMLDivElement> panelVolumeControlContainer = HTMLDivElement::create(document);
167
168    if (document.page()->theme().usesMediaControlVolumeSlider()) {
169        RefPtr<MediaControlVolumeSliderContainerElement> volumeSliderContainer = MediaControlVolumeSliderContainerElement::create(document);
170
171        RefPtr<MediaControlPanelVolumeSliderElement> slider = MediaControlPanelVolumeSliderElement::create(document);
172        controls->m_volumeSlider = slider.get();
173        volumeSliderContainer->appendChild(slider.release(), ec);
174        if (ec)
175            return 0;
176
177        // This is a duplicate mute button, which is visible in some ports at the bottom of the volume bar.
178        // It's important only when the volume bar is displayed below the controls.
179        RefPtr<MediaControlVolumeSliderMuteButtonElement> volumeSliderMuteButton = MediaControlVolumeSliderMuteButtonElement::create(document);
180        controls->m_volumeSliderMuteButton = volumeSliderMuteButton.get();
181        volumeSliderContainer->appendChild(volumeSliderMuteButton.release(), ec);
182
183        if (ec)
184            return 0;
185
186        controls->m_volumeSliderContainer = volumeSliderContainer.get();
187        panelVolumeControlContainer->appendChild(volumeSliderContainer.release(), ec);
188        if (ec)
189            return 0;
190    }
191
192    RefPtr<MediaControlPanelMuteButtonElement> panelMuteButton = MediaControlPanelMuteButtonElement::create(document, controls.get());
193    controls->m_panelMuteButton = panelMuteButton.get();
194    panelVolumeControlContainer->appendChild(panelMuteButton.release(), ec);
195    if (ec)
196        return 0;
197
198    panel->appendChild(panelVolumeControlContainer, ec);
199    if (ec)
200        return 0;
201
202    // FIXME: Only create when needed <http://webkit.org/b/57163>
203    RefPtr<MediaControlFullscreenVolumeMinButtonElement> fullScreenMinVolumeButton = MediaControlFullscreenVolumeMinButtonElement::create(document);
204    controls->m_fullScreenMinVolumeButton = fullScreenMinVolumeButton.get();
205    panel->appendChild(fullScreenMinVolumeButton.release(), ec);
206    if (ec)
207        return 0;
208
209    RefPtr<MediaControlFullscreenVolumeSliderElement> fullScreenVolumeSlider = MediaControlFullscreenVolumeSliderElement::create(document);
210    controls->m_fullScreenVolumeSlider = fullScreenVolumeSlider.get();
211    panel->appendChild(fullScreenVolumeSlider.release(), ec);
212    if (ec)
213        return 0;
214
215    RefPtr<MediaControlFullscreenVolumeMaxButtonElement> fullScreenMaxVolumeButton = MediaControlFullscreenVolumeMaxButtonElement::create(document);
216    controls->m_fullScreenMaxVolumeButton = fullScreenMaxVolumeButton.get();
217    panel->appendChild(fullScreenMaxVolumeButton.release(), ec);
218    if (ec)
219        return 0;
220
221    controls->m_panel = panel.get();
222    controls->appendChild(panel.release(), ec);
223    if (ec)
224        return 0;
225
226    return controls.release();
227}
228
229void MediaControlsApple::setMediaController(MediaControllerInterface* controller)
230{
231    if (m_mediaController == controller)
232        return;
233
234    MediaControls::setMediaController(controller);
235
236    if (m_rewindButton)
237        m_rewindButton->setMediaController(controller);
238    if (m_returnToRealTimeButton)
239        m_returnToRealTimeButton->setMediaController(controller);
240    if (m_statusDisplay)
241        m_statusDisplay->setMediaController(controller);
242    if (m_timeRemainingDisplay)
243        m_timeRemainingDisplay->setMediaController(controller);
244    if (m_timelineContainer)
245        m_timelineContainer->setMediaController(controller);
246    if (m_seekBackButton)
247        m_seekBackButton->setMediaController(controller);
248    if (m_seekForwardButton)
249        m_seekForwardButton->setMediaController(controller);
250    if (m_volumeSliderMuteButton)
251        m_volumeSliderMuteButton->setMediaController(controller);
252    if (m_volumeSliderContainer)
253        m_volumeSliderContainer->setMediaController(controller);
254    if (m_fullScreenMinVolumeButton)
255        m_fullScreenMinVolumeButton->setMediaController(controller);
256    if (m_fullScreenVolumeSlider)
257        m_fullScreenVolumeSlider->setMediaController(controller);
258    if (m_fullScreenMaxVolumeButton)
259        m_fullScreenMaxVolumeButton->setMediaController(controller);
260    if (m_closedCaptionsTrackList)
261        m_closedCaptionsTrackList->setMediaController(controller);
262    if (m_closedCaptionsContainer)
263        m_closedCaptionsContainer->setMediaController(controller);
264}
265
266void MediaControlsApple::defaultEventHandler(Event* event)
267{
268    if (event->type() == eventNames().clickEvent) {
269        if (m_closedCaptionsContainer && m_closedCaptionsContainer->isShowing()) {
270            hideClosedCaptionTrackList();
271            event->setDefaultHandled();
272        }
273    }
274
275    MediaControls::defaultEventHandler(event);
276}
277
278void MediaControlsApple::hide()
279{
280    MediaControls::hide();
281    m_volumeSliderContainer->hide();
282    if (m_closedCaptionsContainer)
283        hideClosedCaptionTrackList();
284}
285
286void MediaControlsApple::makeTransparent()
287{
288    MediaControls::makeTransparent();
289    m_volumeSliderContainer->hide();
290    if (m_closedCaptionsContainer)
291        hideClosedCaptionTrackList();
292}
293
294void MediaControlsApple::changedClosedCaptionsVisibility()
295{
296    MediaControls::changedClosedCaptionsVisibility();
297    if (m_closedCaptionsContainer && m_closedCaptionsContainer->isShowing())
298        hideClosedCaptionTrackList();
299
300}
301
302void MediaControlsApple::reset()
303{
304    Page* page = document().page();
305    if (!page)
306        return;
307
308    updateStatusDisplay();
309
310    if (m_mediaController->supportsFullscreen())
311        m_fullScreenButton->show();
312    else
313        m_fullScreenButton->hide();
314
315    double duration = m_mediaController->duration();
316    if (std::isfinite(duration) || page->theme().hasOwnDisabledStateHandlingFor(MediaSliderPart)) {
317        m_timeline->setDuration(duration);
318        m_timelineContainer->show();
319        m_timeline->setPosition(m_mediaController->currentTime());
320        updateCurrentTimeDisplay();
321    } else
322        m_timelineContainer->hide();
323
324    if (m_mediaController->hasAudio() || page->theme().hasOwnDisabledStateHandlingFor(MediaMuteButtonPart))
325        m_panelMuteButton->show();
326    else
327        m_panelMuteButton->hide();
328
329    if (m_volumeSlider)
330        setSliderVolume();
331
332    if (m_toggleClosedCaptionsButton) {
333        if (m_mediaController->hasClosedCaptions())
334            m_toggleClosedCaptionsButton->show();
335        else
336            m_toggleClosedCaptionsButton->hide();
337    }
338
339    if (m_playButton)
340        m_playButton->updateDisplayType();
341
342#if ENABLE(FULLSCREEN_API)
343    if (m_fullScreenVolumeSlider)
344        setFullscreenSliderVolume();
345
346    if (m_isFullscreen) {
347        if (m_mediaController->isLiveStream()) {
348            m_seekBackButton->hide();
349            m_seekForwardButton->hide();
350            m_rewindButton->show();
351            m_returnToRealTimeButton->show();
352        } else {
353            m_seekBackButton->show();
354            m_seekForwardButton->show();
355            m_rewindButton->hide();
356            m_returnToRealTimeButton->hide();
357        }
358    } else
359#endif
360    if (!m_mediaController->isLiveStream()) {
361        m_returnToRealTimeButton->hide();
362        m_rewindButton->show();
363    } else {
364        m_returnToRealTimeButton->show();
365        m_rewindButton->hide();
366    }
367
368    makeOpaque();
369}
370
371void MediaControlsApple::updateCurrentTimeDisplay()
372{
373    double now = m_mediaController->currentTime();
374    double duration = m_mediaController->duration();
375
376    Page* page = document().page();
377    if (!page)
378        return;
379
380    // Allow the theme to format the time.
381    m_currentTimeDisplay->setInnerText(page->theme().formatMediaControlsCurrentTime(now, duration), IGNORE_EXCEPTION);
382    m_currentTimeDisplay->setCurrentValue(now);
383    m_timeRemainingDisplay->setInnerText(page->theme().formatMediaControlsRemainingTime(now, duration), IGNORE_EXCEPTION);
384    m_timeRemainingDisplay->setCurrentValue(now - duration);
385}
386
387void MediaControlsApple::reportedError()
388{
389    Page* page = document().page();
390    if (!page)
391        return;
392
393    if (!page->theme().hasOwnDisabledStateHandlingFor(MediaSliderPart))
394        m_timelineContainer->hide();
395
396    if (!page->theme().hasOwnDisabledStateHandlingFor(MediaMuteButtonPart))
397        m_panelMuteButton->hide();
398
399    m_fullScreenButton->hide();
400
401    if (m_volumeSliderContainer)
402        m_volumeSliderContainer->hide();
403    if (m_toggleClosedCaptionsButton && !page->theme().hasOwnDisabledStateHandlingFor(MediaToggleClosedCaptionsButtonPart))
404        m_toggleClosedCaptionsButton->hide();
405    if (m_closedCaptionsContainer)
406        hideClosedCaptionTrackList();
407}
408
409void MediaControlsApple::updateStatusDisplay()
410{
411    if (m_statusDisplay)
412        m_statusDisplay->update();
413}
414
415void MediaControlsApple::loadedMetadata()
416{
417    if (m_statusDisplay && !m_mediaController->isLiveStream())
418        m_statusDisplay->hide();
419
420    MediaControls::loadedMetadata();
421}
422
423void MediaControlsApple::changedMute()
424{
425    MediaControls::changedMute();
426
427    if (m_volumeSliderMuteButton)
428        m_volumeSliderMuteButton->changedMute();
429}
430
431void MediaControlsApple::changedVolume()
432{
433    MediaControls::changedVolume();
434
435    if (m_fullScreenVolumeSlider)
436        setFullscreenSliderVolume();
437}
438
439void MediaControlsApple::enteredFullscreen()
440{
441    MediaControls::enteredFullscreen();
442    m_panel->setCanBeDragged(true);
443
444    if (m_mediaController->isLiveStream()) {
445        m_seekBackButton->hide();
446        m_seekForwardButton->hide();
447        m_rewindButton->show();
448        m_returnToRealTimeButton->show();
449    } else {
450        m_seekBackButton->show();
451        m_seekForwardButton->show();
452        m_rewindButton->hide();
453        m_returnToRealTimeButton->hide();
454    }
455}
456
457void MediaControlsApple::exitedFullscreen()
458{
459    m_rewindButton->show();
460    m_seekBackButton->show();
461    m_seekForwardButton->show();
462    m_returnToRealTimeButton->show();
463
464    m_panel->setCanBeDragged(false);
465
466    // We will keep using the panel, but we want it to go back to the standard position.
467    // This will matter right away because we use the panel even when not fullscreen.
468    // And if we reenter fullscreen we also want the panel in the standard position.
469    m_panel->resetPosition();
470
471    MediaControls::exitedFullscreen();
472}
473
474void MediaControlsApple::showVolumeSlider()
475{
476    if (!m_mediaController->hasAudio())
477        return;
478
479    if (m_volumeSliderContainer)
480        m_volumeSliderContainer->show();
481}
482
483void MediaControlsApple::toggleClosedCaptionTrackList()
484{
485    if (!m_mediaController->hasClosedCaptions())
486        return;
487
488    if (m_closedCaptionsContainer) {
489        if (m_closedCaptionsContainer->isShowing())
490            hideClosedCaptionTrackList();
491        else {
492            if (m_closedCaptionsTrackList)
493                m_closedCaptionsTrackList->updateDisplay();
494            showClosedCaptionTrackList();
495        }
496    }
497}
498
499void MediaControlsApple::showClosedCaptionTrackList()
500{
501    if (!m_closedCaptionsContainer || m_closedCaptionsContainer->isShowing())
502        return;
503
504    m_closedCaptionsContainer->show();
505
506    // Ensure the controls panel does not receive any events while the captions
507    // track list is visible as all events now need to be captured by the
508    // track list.
509    m_panel->setInlineStyleProperty(CSSPropertyPointerEvents, CSSValueNone);
510
511    RefPtr<EventListener> listener = eventListener();
512    m_closedCaptionsContainer->addEventListener(eventNames().wheelEvent, listener, true);
513
514    // Track click events in the capture phase at two levels, first at the document level
515    // such that a click outside of the <video> may dismiss the track list, second at the
516    // media controls level such that a click anywhere outside of the track list hides the
517    // track list. These two levels are necessary since it would not be possible to get a
518    // reference to the track list when handling the event outside of the shadow tree.
519    document().addEventListener(eventNames().clickEvent, listener, true);
520    addEventListener(eventNames().clickEvent, listener, true);
521}
522
523void MediaControlsApple::hideClosedCaptionTrackList()
524{
525    if (!m_closedCaptionsContainer || !m_closedCaptionsContainer->isShowing())
526        return;
527
528    m_closedCaptionsContainer->hide();
529
530    // Buttons in the controls panel may now be interactive.
531    m_panel->removeInlineStyleProperty(CSSPropertyPointerEvents);
532
533    EventListener* listener = eventListener().get();
534    m_closedCaptionsContainer->removeEventListener(eventNames().wheelEvent, listener, true);
535    document().removeEventListener(eventNames().clickEvent, listener, true);
536    removeEventListener(eventNames().clickEvent, listener, true);
537}
538
539void MediaControlsApple::setFullscreenSliderVolume()
540{
541    m_fullScreenVolumeSlider->setVolume(m_mediaController->muted() ? 0.0 : m_mediaController->volume());
542}
543
544bool MediaControlsApple::shouldClosedCaptionsContainerPreventPageScrolling(int wheelDeltaY)
545{
546    int scrollTop = m_closedCaptionsContainer->scrollTop();
547    // Scrolling down.
548    if (wheelDeltaY < 0 && (scrollTop + m_closedCaptionsContainer->offsetHeight()) >= m_closedCaptionsContainer->scrollHeight())
549        return true;
550    // Scrolling up.
551    if (wheelDeltaY > 0 && scrollTop <= 0)
552        return true;
553    return false;
554}
555
556void MediaControlsApple::handleClickEvent(Event* event)
557{
558    Node* currentTarget = event->currentTarget()->toNode();
559    Node* target = event->target()->toNode();
560
561    if ((currentTarget == &document() && !shadowHost()->contains(target)) || (currentTarget == this && !m_closedCaptionsContainer->contains(target))) {
562        hideClosedCaptionTrackList();
563        event->stopImmediatePropagation();
564        event->setDefaultHandled();
565    }
566}
567
568void MediaControlsApple::closedCaptionTracksChanged()
569{
570    if (m_toggleClosedCaptionsButton) {
571        if (m_mediaController->hasClosedCaptions())
572            m_toggleClosedCaptionsButton->show();
573        else
574            m_toggleClosedCaptionsButton->hide();
575    }
576}
577
578PassRefPtr<MediaControlsAppleEventListener> MediaControlsApple::eventListener()
579{
580    if (!m_eventListener)
581        m_eventListener = MediaControlsAppleEventListener::create(this);
582    return m_eventListener;
583}
584
585// --------
586
587void MediaControlsAppleEventListener::handleEvent(ScriptExecutionContext*, Event* event)
588{
589    if (event->type() == eventNames().clickEvent)
590        m_mediaControls->handleClickEvent(event);
591    else if ((event->type() == eventNames().wheelEvent || event->type() == eventNames().mousewheelEvent) && event->eventInterface() == WheelEventInterfaceType) {
592        WheelEvent* wheelEvent = toWheelEvent(event);
593        if (m_mediaControls->shouldClosedCaptionsContainerPreventPageScrolling(wheelEvent->wheelDeltaY()))
594            event->preventDefault();
595    }
596}
597
598bool MediaControlsAppleEventListener::operator==(const EventListener& listener)
599{
600    if (const MediaControlsAppleEventListener* mediaControlsAppleEventListener = MediaControlsAppleEventListener::cast(&listener))
601        return m_mediaControls == mediaControlsAppleEventListener->m_mediaControls;
602    return false;
603}
604
605}
606
607#endif
608