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 43 namespace WebCore { 44 45 PassRefPtrWillBeRawPtr<MediaController> MediaController::create(ExecutionContext* context) 46 { 47 return adoptRefWillBeRefCountedGarbageCollected(new MediaController(context)); 48 } 49 50 MediaController::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 68 MediaController::~MediaController() 69 { 70 } 71 72 void 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 81 void MediaController::removeMediaElement(HTMLMediaElement* element) 82 { 83 ASSERT(element); 84 ASSERT(m_mediaElements.contains(element)); 85 m_mediaElements.remove(m_mediaElements.find(element)); 86 } 87 88 PassRefPtr<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 103 PassRefPtr<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 118 PassRefPtr<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 133 double 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 147 double 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 161 void 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 183 void 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 197 void 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 208 void 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 222 void 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 235 double MediaController::playbackRate() const 236 { 237 return m_clock->playRate(); 238 } 239 240 void 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 256 void 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 279 void 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 295 static const AtomicString& playbackStateWaiting() 296 { 297 DEFINE_STATIC_LOCAL(AtomicString, waiting, ("waiting", AtomicString::ConstructFromLiteral)); 298 return waiting; 299 } 300 301 static const AtomicString& playbackStatePlaying() 302 { 303 DEFINE_STATIC_LOCAL(AtomicString, playing, ("playing", AtomicString::ConstructFromLiteral)); 304 return playing; 305 } 306 307 static const AtomicString& playbackStateEnded() 308 { 309 DEFINE_STATIC_LOCAL(AtomicString, ended, ("ended", AtomicString::ConstructFromLiteral)); 310 return ended; 311 } 312 313 const 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 328 void MediaController::reportControllerState() 329 { 330 updateReadyState(); 331 updatePlaybackState(); 332 } 333 334 static 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 353 void 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 398 void 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 473 void MediaController::updateMediaElements() 474 { 475 for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) 476 (*it)->updatePlayState(); 477 } 478 479 void 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 490 bool 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 521 bool 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 551 bool 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 570 void MediaController::scheduleEvent(const AtomicString& eventName) 571 { 572 m_pendingEventsQueue->enqueueEvent(Event::createCancelable(eventName)); 573 } 574 575 void MediaController::clearPositionTimerFired(Timer<MediaController>*) 576 { 577 m_position = MediaPlayer::invalidTime(); 578 } 579 580 const 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 587 static const double maxTimeupdateEventFrequency = 0.25; 588 589 void MediaController::startTimeupdateTimer() 590 { 591 if (m_timeupdateTimer.isActive()) 592 return; 593 594 m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE); 595 } 596 597 void MediaController::timeupdateTimerFired(Timer<MediaController>*) 598 { 599 scheduleTimeupdateEvent(); 600 } 601 602 void 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 614 void 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