1/*
2 * Copyright (C) 2011 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#include "core/html/MediaController.h"
28
29#include "bindings/v8/ExceptionMessages.h"
30#include "bindings/v8/ExceptionState.h"
31#include "bindings/v8/ExceptionStatePlaceholder.h"
32#include "core/dom/ExceptionCode.h"
33#include "core/dom/ExecutionContext.h"
34#include "core/events/Event.h"
35#include "core/events/GenericEventQueue.h"
36#include "core/html/HTMLMediaElement.h"
37#include "core/html/TimeRanges.h"
38#include "platform/Clock.h"
39#include "wtf/CurrentTime.h"
40#include "wtf/StdLibExtras.h"
41#include "wtf/text/AtomicString.h"
42
43namespace WebCore {
44
45PassRefPtrWillBeRawPtr<MediaController> MediaController::create(ExecutionContext* context)
46{
47    return adoptRefWillBeRefCountedGarbageCollected(new MediaController(context));
48}
49
50MediaController::MediaController(ExecutionContext* context)
51    : m_paused(false)
52    , m_defaultPlaybackRate(1)
53    , m_volume(1)
54    , m_position(MediaPlayer::invalidTime())
55    , m_muted(false)
56    , m_readyState(HTMLMediaElement::HAVE_NOTHING)
57    , m_playbackState(WAITING)
58    , m_pendingEventsQueue(GenericEventQueue::create(this))
59    , m_clearPositionTimer(this, &MediaController::clearPositionTimerFired)
60    , m_clock(Clock::create())
61    , m_executionContext(context)
62    , m_timeupdateTimer(this, &MediaController::timeupdateTimerFired)
63    , m_previousTimeupdateTime(0)
64{
65    ScriptWrappable::init(this);
66}
67
68MediaController::~MediaController()
69{
70}
71
72void MediaController::addMediaElement(HTMLMediaElement* element)
73{
74    ASSERT(element);
75    ASSERT(!m_mediaElements.contains(element));
76
77    m_mediaElements.add(element);
78    bringElementUpToSpeed(element);
79}
80
81void MediaController::removeMediaElement(HTMLMediaElement* element)
82{
83    ASSERT(element);
84    ASSERT(m_mediaElements.contains(element));
85    m_mediaElements.remove(m_mediaElements.find(element));
86}
87
88PassRefPtr<TimeRanges> MediaController::buffered() const
89{
90    if (m_mediaElements.isEmpty())
91        return TimeRanges::create();
92
93    // The buffered attribute must return a new static normalized TimeRanges object that represents
94    // the intersection of the ranges of the media resources of the slaved media elements that the
95    // user agent has buffered, at the time the attribute is evaluated.
96    MediaElementSequence::const_iterator it = m_mediaElements.begin();
97    RefPtr<TimeRanges> bufferedRanges = (*it)->buffered();
98    for (++it; it != m_mediaElements.end(); ++it)
99        bufferedRanges->intersectWith((*it)->buffered().get());
100    return bufferedRanges;
101}
102
103PassRefPtr<TimeRanges> MediaController::seekable() const
104{
105    if (m_mediaElements.isEmpty())
106        return TimeRanges::create();
107
108    // The seekable attribute must return a new static normalized TimeRanges object that represents
109    // the intersection of the ranges of the media resources of the slaved media elements that the
110    // user agent is able to seek to, at the time the attribute is evaluated.
111    MediaElementSequence::const_iterator it = m_mediaElements.begin();
112    RefPtr<TimeRanges> seekableRanges = (*it)->seekable();
113    for (++it; it != m_mediaElements.end(); ++it)
114        seekableRanges->intersectWith((*it)->seekable().get());
115    return seekableRanges;
116}
117
118PassRefPtr<TimeRanges> MediaController::played()
119{
120    if (m_mediaElements.isEmpty())
121        return TimeRanges::create();
122
123    // The played attribute must return a new static normalized TimeRanges object that represents
124    // the union of the ranges of the media resources of the slaved media elements that the
125    // user agent has so far rendered, at the time the attribute is evaluated.
126    MediaElementSequence::const_iterator it = m_mediaElements.begin();
127    RefPtr<TimeRanges> playedRanges = (*it)->played();
128    for (++it; it != m_mediaElements.end(); ++it)
129        playedRanges->unionWith((*it)->played().get());
130    return playedRanges;
131}
132
133double MediaController::duration() const
134{
135    // FIXME: Investigate caching the maximum duration and only updating the cached value
136    // when the slaved media elements' durations change.
137    double maxDuration = 0;
138    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) {
139        double duration = (*it)->duration();
140        if (std::isnan(duration))
141            continue;
142        maxDuration = std::max(maxDuration, duration);
143    }
144    return maxDuration;
145}
146
147double MediaController::currentTime() const
148{
149    if (m_mediaElements.isEmpty())
150        return 0;
151
152    if (m_position == MediaPlayer::invalidTime()) {
153        // Some clocks may return times outside the range of [0..duration].
154        m_position = std::max(0.0, std::min(duration(), m_clock->currentTime()));
155        m_clearPositionTimer.startOneShot(0, FROM_HERE);
156    }
157
158    return m_position;
159}
160
161void MediaController::setCurrentTime(double time, ExceptionState& exceptionState)
162{
163    // When the user agent is to seek the media controller to a particular new playback position,
164    // it must follow these steps:
165    // If the new playback position is less than zero, then set it to zero.
166    time = std::max(0.0, time);
167
168    // If the new playback position is greater than the media controller duration, then set it
169    // to the media controller duration.
170    time = std::min(time, duration());
171
172    // Set the media controller position to the new playback position.
173    m_position = time;
174    m_clock->setCurrentTime(time);
175
176    // Seek each slaved media element to the new playback position relative to the media element timeline.
177    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it)
178        (*it)->seek(time, exceptionState);
179
180    scheduleTimeupdateEvent();
181}
182
183void MediaController::unpause()
184{
185    // When the unpause() method is invoked, if the MediaController is a paused media controller,
186    if (!m_paused)
187        return;
188
189    // the user agent must change the MediaController into a playing media controller,
190    m_paused = false;
191    // queue a task to fire a simple event named play at the MediaController,
192    scheduleEvent(EventTypeNames::play);
193    // and then report the controller state of the MediaController.
194    reportControllerState();
195}
196
197void MediaController::play()
198{
199    // When the play() method is invoked, the user agent must invoke the play method of each
200    // slaved media element in turn,
201    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it)
202        (*it)->play();
203
204    // and then invoke the unpause method of the MediaController.
205    unpause();
206}
207
208void MediaController::pause()
209{
210    // When the pause() method is invoked, if the MediaController is a playing media controller,
211    if (m_paused)
212        return;
213
214    // then the user agent must change the MediaController into a paused media controller,
215    m_paused = true;
216    // queue a task to fire a simple event named pause at the MediaController,
217    scheduleEvent(EventTypeNames::pause);
218    // and then report the controller state of the MediaController.
219    reportControllerState();
220}
221
222void MediaController::setDefaultPlaybackRate(double rate)
223{
224    if (m_defaultPlaybackRate == rate)
225        return;
226
227    // The defaultPlaybackRate attribute, on setting, must set the MediaController's media controller
228    // default playback rate to the new value,
229    m_defaultPlaybackRate = rate;
230
231    // then queue a task to fire a simple event named ratechange at the MediaController.
232    scheduleEvent(EventTypeNames::ratechange);
233}
234
235double MediaController::playbackRate() const
236{
237    return m_clock->playRate();
238}
239
240void MediaController::setPlaybackRate(double rate)
241{
242    if (m_clock->playRate() == rate)
243        return;
244
245    // The playbackRate attribute, on setting, must set the MediaController's media controller
246    // playback rate to the new value,
247    m_clock->setPlayRate(rate);
248
249    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it)
250        (*it)->updatePlaybackRate();
251
252    // then queue a task to fire a simple event named ratechange at the MediaController.
253    scheduleEvent(EventTypeNames::ratechange);
254}
255
256void MediaController::setVolume(double level, ExceptionState& exceptionState)
257{
258    if (m_volume == level)
259        return;
260
261    // If the new value is outside the range 0.0 to 1.0 inclusive, then, on setting, an
262    // IndexSizeError exception must be raised instead.
263    if (level < 0 || level > 1) {
264        exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("volume", level, 0.0, ExceptionMessages::InclusiveBound, 1.0, ExceptionMessages::InclusiveBound));
265        return;
266    }
267
268    // The volume attribute, on setting, if the new value is in the range 0.0 to 1.0 inclusive,
269    // must set the MediaController's media controller volume multiplier to the new value
270    m_volume = level;
271
272    // and queue a task to fire a simple event named volumechange at the MediaController.
273    scheduleEvent(EventTypeNames::volumechange);
274
275    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it)
276        (*it)->updateVolume();
277}
278
279void MediaController::setMuted(bool flag)
280{
281    if (m_muted == flag)
282        return;
283
284    // The muted attribute, on setting, must set the MediaController's media controller mute override
285    // to the new value
286    m_muted = flag;
287
288    // and queue a task to fire a simple event named volumechange at the MediaController.
289    scheduleEvent(EventTypeNames::volumechange);
290
291    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it)
292        (*it)->updateVolume();
293}
294
295static const AtomicString& playbackStateWaiting()
296{
297    DEFINE_STATIC_LOCAL(AtomicString, waiting, ("waiting", AtomicString::ConstructFromLiteral));
298    return waiting;
299}
300
301static const AtomicString& playbackStatePlaying()
302{
303    DEFINE_STATIC_LOCAL(AtomicString, playing, ("playing", AtomicString::ConstructFromLiteral));
304    return playing;
305}
306
307static const AtomicString& playbackStateEnded()
308{
309    DEFINE_STATIC_LOCAL(AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral));
310    return ended;
311}
312
313const AtomicString& MediaController::playbackState() const
314{
315    switch (m_playbackState) {
316    case WAITING:
317        return playbackStateWaiting();
318    case PLAYING:
319        return playbackStatePlaying();
320    case ENDED:
321        return playbackStateEnded();
322    default:
323        ASSERT_NOT_REACHED();
324        return nullAtom;
325    }
326}
327
328void MediaController::reportControllerState()
329{
330    updateReadyState();
331    updatePlaybackState();
332}
333
334static const AtomicString& eventNameForReadyState(HTMLMediaElement::ReadyState state)
335{
336    switch (state) {
337    case HTMLMediaElement::HAVE_NOTHING:
338        return EventTypeNames::emptied;
339    case HTMLMediaElement::HAVE_METADATA:
340        return EventTypeNames::loadedmetadata;
341    case HTMLMediaElement::HAVE_CURRENT_DATA:
342        return EventTypeNames::loadeddata;
343    case HTMLMediaElement::HAVE_FUTURE_DATA:
344        return EventTypeNames::canplay;
345    case HTMLMediaElement::HAVE_ENOUGH_DATA:
346        return EventTypeNames::canplaythrough;
347    default:
348        ASSERT_NOT_REACHED();
349        return nullAtom;
350    }
351}
352
353void MediaController::updateReadyState()
354{
355    ReadyState oldReadyState = m_readyState;
356    ReadyState newReadyState;
357
358    if (m_mediaElements.isEmpty()) {
359        // If the MediaController has no slaved media elements, let new readiness state be 0.
360        newReadyState = HTMLMediaElement::HAVE_NOTHING;
361    } else {
362        // Otherwise, let it have the lowest value of the readyState IDL attributes of all of its
363        // slaved media elements.
364        MediaElementSequence::const_iterator it = m_mediaElements.begin();
365        newReadyState = (*it)->readyState();
366        for (++it; it != m_mediaElements.end(); ++it)
367            newReadyState = std::min(newReadyState, (*it)->readyState());
368    }
369
370    if (newReadyState == oldReadyState)
371        return;
372
373    // If the MediaController's most recently reported readiness state is greater than new readiness
374    // state then queue a task to fire a simple event at the MediaController object, whose name is the
375    // event name corresponding to the value of new readiness state given in the table below. [omitted]
376    if (oldReadyState > newReadyState) {
377        scheduleEvent(eventNameForReadyState(newReadyState));
378        return;
379    }
380
381    // If the MediaController's most recently reported readiness state is less than the new readiness
382    // state, then run these substeps:
383    // 1. Let next state be the MediaController's most recently reported readiness state.
384    ReadyState nextState = oldReadyState;
385    do {
386        // 2. Loop: Increment next state by one.
387        nextState = static_cast<ReadyState>(nextState + 1);
388        // 3. Queue a task to fire a simple event at the MediaController object, whose name is the
389        // event name corresponding to the value of next state given in the table below. [omitted]
390        scheduleEvent(eventNameForReadyState(nextState));
391        // If next state is less than new readiness state, then return to the step labeled loop
392    } while (nextState < newReadyState);
393
394    // Let the MediaController's most recently reported readiness state be new readiness state.
395    m_readyState = newReadyState;
396}
397
398void MediaController::updatePlaybackState()
399{
400    PlaybackState oldPlaybackState = m_playbackState;
401    PlaybackState newPlaybackState;
402
403    // Initialize new playback state by setting it to the state given for the first matching
404    // condition from the following list:
405    if (m_mediaElements.isEmpty()) {
406        // If the MediaController has no slaved media elements
407        // Let new playback state be waiting.
408        newPlaybackState = WAITING;
409    } else if (hasEnded()) {
410        // If all of the MediaController's slaved media elements have ended playback and the media
411        // controller playback rate is positive or zero
412        // Let new playback state be ended.
413        newPlaybackState = ENDED;
414    } else if (isBlocked()) {
415        // If the MediaController is a blocked media controller
416        // Let new playback state be waiting.
417        newPlaybackState = WAITING;
418    } else {
419        // Otherwise
420        // Let new playback state be playing.
421        newPlaybackState = PLAYING;
422    }
423
424    // If the MediaController's most recently reported playback state is not equal to new playback state
425    if (newPlaybackState == oldPlaybackState)
426        return;
427
428    // and the new playback state is ended,
429    if (newPlaybackState == ENDED) {
430        // then queue a task that, if the MediaController object is a playing media controller, and
431        // all of the MediaController's slaved media elements have still ended playback, and the
432        // media controller playback rate is still positive or zero,
433        if (!m_paused && hasEnded()) {
434            // changes the MediaController object to a paused media controller
435            m_paused = true;
436
437            // and then fires a simple event named pause at the MediaController object.
438            scheduleEvent(EventTypeNames::pause);
439        }
440    }
441
442    // If the MediaController's most recently reported playback state is not equal to new playback state
443    // then queue a task to fire a simple event at the MediaController object, whose name is playing
444    // if new playback state is playing, ended if new playback state is ended, and waiting otherwise.
445    AtomicString eventName;
446    switch (newPlaybackState) {
447    case WAITING:
448        eventName = EventTypeNames::waiting;
449        m_clock->stop();
450        m_timeupdateTimer.stop();
451        break;
452    case ENDED:
453        eventName = EventTypeNames::ended;
454        m_clock->stop();
455        m_timeupdateTimer.stop();
456        break;
457    case PLAYING:
458        eventName = EventTypeNames::playing;
459        m_clock->start();
460        startTimeupdateTimer();
461        break;
462    default:
463        ASSERT_NOT_REACHED();
464    }
465    scheduleEvent(eventName);
466
467    // Let the MediaController's most recently reported playback state be new playback state.
468    m_playbackState = newPlaybackState;
469
470    updateMediaElements();
471}
472
473void MediaController::updateMediaElements()
474{
475    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it)
476        (*it)->updatePlayState();
477}
478
479void MediaController::bringElementUpToSpeed(HTMLMediaElement* element)
480{
481    ASSERT(element);
482    ASSERT(m_mediaElements.contains(element));
483
484    // When the user agent is to bring a media element up to speed with its new media controller,
485    // it must seek that media element to the MediaController's media controller position relative
486    // to the media element's timeline.
487    element->seek(currentTime(), IGNORE_EXCEPTION);
488}
489
490bool MediaController::isRestrained() const
491{
492    ASSERT(!m_mediaElements.isEmpty());
493
494    // A MediaController is a restrained media controller if the MediaController is a playing media
495    // controller,
496    if (m_paused)
497        return false;
498
499    bool anyAutoplayingAndPaused = false;
500    bool allPaused = true;
501    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) {
502        HTMLMediaElement* element = *it;
503
504        // and none of its slaved media elements are blocked media elements,
505        if (element->isBlocked())
506            return false;
507
508        if (element->isAutoplaying() && element->paused())
509            anyAutoplayingAndPaused = true;
510
511        if (!element->paused())
512            allPaused = false;
513    }
514
515    // but either at least one of its slaved media elements whose autoplaying flag is true still has
516    // its paused attribute set to true, or, all of its slaved media elements have their paused
517    // attribute set to true.
518    return anyAutoplayingAndPaused || allPaused;
519}
520
521bool MediaController::isBlocked() const
522{
523    ASSERT(!m_mediaElements.isEmpty());
524
525    // A MediaController is a blocked media controller if the MediaController is a paused media
526    // controller,
527    if (m_paused)
528        return true;
529
530    bool allPaused = true;
531    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) {
532        HTMLMediaElement* element = *it;
533
534        // or if any of its slaved media elements are blocked media elements,
535        if (element->isBlocked())
536            return true;
537
538        // or if any of its slaved media elements whose autoplaying flag is true still have their
539        // paused attribute set to true,
540        if (element->isAutoplaying() && element->paused())
541            return true;
542
543        if (!element->paused())
544            allPaused = false;
545    }
546
547    // or if all of its slaved media elements have their paused attribute set to true.
548    return allPaused;
549}
550
551bool MediaController::hasEnded() const
552{
553    // If the ... media controller playback rate is positive or zero
554    if (m_clock->playRate() < 0)
555        return false;
556
557    // [and] all of the MediaController's slaved media elements have ended playback ... let new
558    // playback state be ended.
559    if (m_mediaElements.isEmpty())
560        return false;
561
562    bool allHaveEnded = true;
563    for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) {
564        if (!(*it)->ended())
565            allHaveEnded = false;
566    }
567    return allHaveEnded;
568}
569
570void MediaController::scheduleEvent(const AtomicString& eventName)
571{
572    m_pendingEventsQueue->enqueueEvent(Event::createCancelable(eventName));
573}
574
575void MediaController::clearPositionTimerFired(Timer<MediaController>*)
576{
577    m_position = MediaPlayer::invalidTime();
578}
579
580const AtomicString& MediaController::interfaceName() const
581{
582    return EventTargetNames::MediaController;
583}
584
585// The spec says to fire periodic timeupdate events (those sent while playing) every
586// "15 to 250ms", we choose the slowest frequency
587static const double maxTimeupdateEventFrequency = 0.25;
588
589void MediaController::startTimeupdateTimer()
590{
591    if (m_timeupdateTimer.isActive())
592        return;
593
594    m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE);
595}
596
597void MediaController::timeupdateTimerFired(Timer<MediaController>*)
598{
599    scheduleTimeupdateEvent();
600}
601
602void MediaController::scheduleTimeupdateEvent()
603{
604    double now = WTF::currentTime();
605    double timedelta = now - m_previousTimeupdateTime;
606
607    if (timedelta < maxTimeupdateEventFrequency)
608        return;
609
610    scheduleEvent(EventTypeNames::timeupdate);
611    m_previousTimeupdateTime = now;
612}
613
614void MediaController::trace(Visitor* visitor)
615{
616    visitor->trace(m_mediaElements);
617    visitor->trace(m_pendingEventsQueue);
618    visitor->trace(m_executionContext);
619    EventTargetWithInlineData::trace(visitor);
620}
621
622}
623