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/html/HTMLMediaElement.h" 34 #include "core/html/TimeRanges.h" 35 #include "platform/Clock.h" 36 #include "wtf/CurrentTime.h" 37 #include "wtf/StdLibExtras.h" 38 #include "wtf/text/AtomicString.h" 39 40 using namespace WebCore; 41 using namespace std; 42 43 PassRefPtr<MediaController> MediaController::create(ExecutionContext* context) 44 { 45 return adoptRef(new MediaController(context)); 46 } 47 48 MediaController::MediaController(ExecutionContext* context) 49 : m_paused(false) 50 , m_defaultPlaybackRate(1) 51 , m_volume(1) 52 , m_position(MediaPlayer::invalidTime()) 53 , m_muted(false) 54 , m_readyState(HAVE_NOTHING) 55 , m_playbackState(WAITING) 56 , m_asyncEventTimer(this, &MediaController::asyncEventTimerFired) 57 , m_clearPositionTimer(this, &MediaController::clearPositionTimerFired) 58 , m_closedCaptionsVisible(false) 59 , m_clock(Clock::create()) 60 , m_executionContext(context) 61 , m_timeupdateTimer(this, &MediaController::timeupdateTimerFired) 62 , m_previousTimeupdateTime(0) 63 { 64 ScriptWrappable::init(this); 65 } 66 67 MediaController::~MediaController() 68 { 69 } 70 71 void MediaController::addMediaElement(HTMLMediaElement* element) 72 { 73 ASSERT(element); 74 ASSERT(!m_mediaElements.contains(element)); 75 76 m_mediaElements.append(element); 77 bringElementUpToSpeed(element); 78 } 79 80 void MediaController::removeMediaElement(HTMLMediaElement* element) 81 { 82 ASSERT(element); 83 ASSERT(m_mediaElements.contains(element)); 84 m_mediaElements.remove(m_mediaElements.find(element)); 85 } 86 87 bool MediaController::containsMediaElement(HTMLMediaElement* element) const 88 { 89 return m_mediaElements.contains(element); 90 } 91 92 PassRefPtr<TimeRanges> MediaController::buffered() const 93 { 94 if (m_mediaElements.isEmpty()) 95 return TimeRanges::create(); 96 97 // The buffered attribute must return a new static normalized TimeRanges object that represents 98 // the intersection of the ranges of the media resources of the slaved media elements that the 99 // user agent has buffered, at the time the attribute is evaluated. 100 RefPtr<TimeRanges> bufferedRanges = m_mediaElements.first()->buffered(); 101 for (size_t index = 1; index < m_mediaElements.size(); ++index) 102 bufferedRanges->intersectWith(m_mediaElements[index]->buffered().get()); 103 return bufferedRanges; 104 } 105 106 PassRefPtr<TimeRanges> MediaController::seekable() const 107 { 108 if (m_mediaElements.isEmpty()) 109 return TimeRanges::create(); 110 111 // The seekable attribute must return a new static normalized TimeRanges object that represents 112 // the intersection of the ranges of the media resources of the slaved media elements that the 113 // user agent is able to seek to, at the time the attribute is evaluated. 114 RefPtr<TimeRanges> seekableRanges = m_mediaElements.first()->seekable(); 115 for (size_t index = 1; index < m_mediaElements.size(); ++index) 116 seekableRanges->intersectWith(m_mediaElements[index]->seekable().get()); 117 return seekableRanges; 118 } 119 120 PassRefPtr<TimeRanges> MediaController::played() 121 { 122 if (m_mediaElements.isEmpty()) 123 return TimeRanges::create(); 124 125 // The played attribute must return a new static normalized TimeRanges object that represents 126 // the union of the ranges of the media resources of the slaved media elements that the 127 // user agent has so far rendered, at the time the attribute is evaluated. 128 RefPtr<TimeRanges> playedRanges = m_mediaElements.first()->played(); 129 for (size_t index = 1; index < m_mediaElements.size(); ++index) 130 playedRanges->unionWith(m_mediaElements[index]->played().get()); 131 return playedRanges; 132 } 133 134 double MediaController::duration() const 135 { 136 // FIXME: Investigate caching the maximum duration and only updating the cached value 137 // when the slaved media elements' durations change. 138 double maxDuration = 0; 139 for (size_t index = 0; index < m_mediaElements.size(); ++index) { 140 double duration = m_mediaElements[index]->duration(); 141 if (std::isnan(duration)) 142 continue; 143 maxDuration = max(maxDuration, duration); 144 } 145 return maxDuration; 146 } 147 148 double MediaController::currentTime() const 149 { 150 if (m_mediaElements.isEmpty()) 151 return 0; 152 153 if (m_position == MediaPlayer::invalidTime()) { 154 // Some clocks may return times outside the range of [0..duration]. 155 m_position = max(0.0, min(duration(), m_clock->currentTime())); 156 m_clearPositionTimer.startOneShot(0); 157 } 158 159 return m_position; 160 } 161 162 void MediaController::setCurrentTime(double time, ExceptionState& exceptionState) 163 { 164 // When the user agent is to seek the media controller to a particular new playback position, 165 // it must follow these steps: 166 // If the new playback position is less than zero, then set it to zero. 167 time = max(0.0, time); 168 169 // If the new playback position is greater than the media controller duration, then set it 170 // to the media controller duration. 171 time = min(time, duration()); 172 173 // Set the media controller position to the new playback position. 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 (size_t index = 0; index < m_mediaElements.size(); ++index) 178 m_mediaElements[index]->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 (size_t index = 0; index < m_mediaElements.size(); ++index) 202 m_mediaElements[index]->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 (size_t index = 0; index < m_mediaElements.size(); ++index) 250 m_mediaElements[index]->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::failedToSet("volume", "MediaController", "The value provided (" + String::number(level) + ") is not in the range [0.0, 1.0].")); 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 (size_t index = 0; index < m_mediaElements.size(); ++index) 276 m_mediaElements[index]->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 (size_t index = 0; index < m_mediaElements.size(); ++index) 292 m_mediaElements[index]->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 AtomicString eventNameForReadyState(MediaControllerInterface::ReadyState state) 335 { 336 switch (state) { 337 case MediaControllerInterface::HAVE_NOTHING: 338 return EventTypeNames::emptied; 339 case MediaControllerInterface::HAVE_METADATA: 340 return EventTypeNames::loadedmetadata; 341 case MediaControllerInterface::HAVE_CURRENT_DATA: 342 return EventTypeNames::loadeddata; 343 case MediaControllerInterface::HAVE_FUTURE_DATA: 344 return EventTypeNames::canplay; 345 case MediaControllerInterface::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 = 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 newReadyState = m_mediaElements.first()->readyState(); 365 for (size_t index = 1; index < m_mediaElements.size(); ++index) 366 newReadyState = min(newReadyState, m_mediaElements[index]->readyState()); 367 } 368 369 if (newReadyState == oldReadyState) 370 return; 371 372 // If the MediaController's most recently reported readiness state is greater than new readiness 373 // state then queue a task to fire a simple event at the MediaController object, whose name is the 374 // event name corresponding to the value of new readiness state given in the table below. [omitted] 375 if (oldReadyState > newReadyState) { 376 scheduleEvent(eventNameForReadyState(newReadyState)); 377 return; 378 } 379 380 // If the MediaController's most recently reported readiness state is less than the new readiness 381 // state, then run these substeps: 382 // 1. Let next state be the MediaController's most recently reported readiness state. 383 ReadyState nextState = oldReadyState; 384 do { 385 // 2. Loop: Increment next state by one. 386 nextState = static_cast<ReadyState>(nextState + 1); 387 // 3. Queue a task to fire a simple event at the MediaController object, whose name is the 388 // event name corresponding to the value of next state given in the table below. [omitted] 389 scheduleEvent(eventNameForReadyState(nextState)); 390 // If next state is less than new readiness state, then return to the step labeled loop 391 } while (nextState < newReadyState); 392 393 // Let the MediaController's most recently reported readiness state be new readiness state. 394 m_readyState = newReadyState; 395 } 396 397 void MediaController::updatePlaybackState() 398 { 399 PlaybackState oldPlaybackState = m_playbackState; 400 PlaybackState newPlaybackState; 401 402 // Initialize new playback state by setting it to the state given for the first matching 403 // condition from the following list: 404 if (m_mediaElements.isEmpty()) { 405 // If the MediaController has no slaved media elements 406 // Let new playback state be waiting. 407 newPlaybackState = WAITING; 408 } else if (hasEnded()) { 409 // If all of the MediaController's slaved media elements have ended playback and the media 410 // controller playback rate is positive or zero 411 // Let new playback state be ended. 412 newPlaybackState = ENDED; 413 } else if (isBlocked()) { 414 // If the MediaController is a blocked media controller 415 // Let new playback state be waiting. 416 newPlaybackState = WAITING; 417 } else { 418 // Otherwise 419 // Let new playback state be playing. 420 newPlaybackState = PLAYING; 421 } 422 423 // If the MediaController's most recently reported playback state is not equal to new playback state 424 if (newPlaybackState == oldPlaybackState) 425 return; 426 427 // and the new playback state is ended, 428 if (newPlaybackState == ENDED) { 429 // then queue a task that, if the MediaController object is a playing media controller, and 430 // all of the MediaController's slaved media elements have still ended playback, and the 431 // media controller playback rate is still positive or zero, 432 if (!m_paused && hasEnded()) { 433 // changes the MediaController object to a paused media controller 434 m_paused = true; 435 436 // and then fires a simple event named pause at the MediaController object. 437 scheduleEvent(EventTypeNames::pause); 438 } 439 } 440 441 // If the MediaController's most recently reported playback state is not equal to new playback state 442 // then queue a task to fire a simple event at the MediaController object, whose name is playing 443 // if new playback state is playing, ended if new playback state is ended, and waiting otherwise. 444 AtomicString eventName; 445 switch (newPlaybackState) { 446 case WAITING: 447 eventName = EventTypeNames::waiting; 448 m_clock->stop(); 449 m_timeupdateTimer.stop(); 450 break; 451 case ENDED: 452 eventName = EventTypeNames::ended; 453 m_clock->stop(); 454 m_timeupdateTimer.stop(); 455 break; 456 case PLAYING: 457 eventName = EventTypeNames::playing; 458 m_clock->start(); 459 startTimeupdateTimer(); 460 break; 461 default: 462 ASSERT_NOT_REACHED(); 463 } 464 scheduleEvent(eventName); 465 466 // Let the MediaController's most recently reported playback state be new playback state. 467 m_playbackState = newPlaybackState; 468 469 updateMediaElements(); 470 } 471 472 void MediaController::updateMediaElements() 473 { 474 for (size_t index = 0; index < m_mediaElements.size(); ++index) 475 m_mediaElements[index]->updatePlayState(); 476 } 477 478 void MediaController::bringElementUpToSpeed(HTMLMediaElement* element) 479 { 480 ASSERT(element); 481 ASSERT(m_mediaElements.contains(element)); 482 483 // When the user agent is to bring a media element up to speed with its new media controller, 484 // it must seek that media element to the MediaController's media controller position relative 485 // to the media element's timeline. 486 element->seek(currentTime(), IGNORE_EXCEPTION); 487 } 488 489 bool MediaController::isBlocked() const 490 { 491 // A MediaController is a blocked media controller if the MediaController is a paused media 492 // controller, 493 if (m_paused) 494 return true; 495 496 if (m_mediaElements.isEmpty()) 497 return false; 498 499 bool allPaused = true; 500 for (size_t index = 0; index < m_mediaElements.size(); ++index) { 501 HTMLMediaElement* element = m_mediaElements[index]; 502 // or if any of its slaved media elements are blocked media elements, 503 if (element->isBlocked()) 504 return true; 505 506 // or if any of its slaved media elements whose autoplaying flag is true still have their 507 // paused attribute set to true, 508 if (element->isAutoplaying() && element->paused()) 509 return true; 510 511 if (!element->paused()) 512 allPaused = false; 513 } 514 515 // or if all of its slaved media elements have their paused attribute set to true. 516 return allPaused; 517 } 518 519 bool MediaController::hasEnded() const 520 { 521 // If the ... media controller playback rate is positive or zero 522 if (m_clock->playRate() < 0) 523 return false; 524 525 // [and] all of the MediaController's slaved media elements have ended playback ... let new 526 // playback state be ended. 527 if (m_mediaElements.isEmpty()) 528 return false; 529 530 bool allHaveEnded = true; 531 for (size_t index = 0; index < m_mediaElements.size(); ++index) { 532 if (!m_mediaElements[index]->ended()) 533 allHaveEnded = false; 534 } 535 return allHaveEnded; 536 } 537 538 void MediaController::scheduleEvent(const AtomicString& eventName) 539 { 540 m_pendingEvents.append(Event::createCancelable(eventName)); 541 if (!m_asyncEventTimer.isActive()) 542 m_asyncEventTimer.startOneShot(0); 543 } 544 545 void MediaController::asyncEventTimerFired(Timer<MediaController>*) 546 { 547 Vector<RefPtr<Event> > pendingEvents; 548 549 m_pendingEvents.swap(pendingEvents); 550 size_t count = pendingEvents.size(); 551 for (size_t index = 0; index < count; ++index) 552 dispatchEvent(pendingEvents[index].release(), IGNORE_EXCEPTION); 553 } 554 555 void MediaController::clearPositionTimerFired(Timer<MediaController>*) 556 { 557 m_position = MediaPlayer::invalidTime(); 558 } 559 560 bool MediaController::hasAudio() const 561 { 562 for (size_t index = 0; index < m_mediaElements.size(); ++index) { 563 if (m_mediaElements[index]->hasAudio()) 564 return true; 565 } 566 return false; 567 } 568 569 bool MediaController::hasVideo() const 570 { 571 for (size_t index = 0; index < m_mediaElements.size(); ++index) { 572 if (m_mediaElements[index]->hasVideo()) 573 return true; 574 } 575 return false; 576 } 577 578 bool MediaController::hasClosedCaptions() const 579 { 580 for (size_t index = 0; index < m_mediaElements.size(); ++index) { 581 if (m_mediaElements[index]->hasClosedCaptions()) 582 return true; 583 } 584 return false; 585 } 586 587 void MediaController::setClosedCaptionsVisible(bool visible) 588 { 589 m_closedCaptionsVisible = visible; 590 for (size_t index = 0; index < m_mediaElements.size(); ++index) 591 m_mediaElements[index]->setClosedCaptionsVisible(visible); 592 } 593 594 void MediaController::beginScrubbing() 595 { 596 for (size_t index = 0; index < m_mediaElements.size(); ++index) 597 m_mediaElements[index]->beginScrubbing(); 598 if (m_playbackState == PLAYING) 599 m_clock->stop(); 600 } 601 602 void MediaController::endScrubbing() 603 { 604 for (size_t index = 0; index < m_mediaElements.size(); ++index) 605 m_mediaElements[index]->endScrubbing(); 606 if (m_playbackState == PLAYING) 607 m_clock->start(); 608 } 609 610 bool MediaController::canPlay() const 611 { 612 if (m_paused) 613 return true; 614 615 for (size_t index = 0; index < m_mediaElements.size(); ++index) { 616 if (!m_mediaElements[index]->canPlay()) 617 return false; 618 } 619 return true; 620 } 621 622 bool MediaController::hasCurrentSrc() const 623 { 624 for (size_t index = 0; index < m_mediaElements.size(); ++index) { 625 if (!m_mediaElements[index]->hasCurrentSrc()) 626 return false; 627 } 628 return true; 629 } 630 631 const AtomicString& MediaController::interfaceName() const 632 { 633 return EventTargetNames::MediaController; 634 } 635 636 // The spec says to fire periodic timeupdate events (those sent while playing) every 637 // "15 to 250ms", we choose the slowest frequency 638 static const double maxTimeupdateEventFrequency = 0.25; 639 640 void MediaController::startTimeupdateTimer() 641 { 642 if (m_timeupdateTimer.isActive()) 643 return; 644 645 m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency); 646 } 647 648 void MediaController::timeupdateTimerFired(Timer<MediaController>*) 649 { 650 scheduleTimeupdateEvent(); 651 } 652 653 void MediaController::scheduleTimeupdateEvent() 654 { 655 double now = WTF::currentTime(); 656 double timedelta = now - m_previousTimeupdateTime; 657 658 if (timedelta < maxTimeupdateEventFrequency) 659 return; 660 661 scheduleEvent(EventTypeNames::timeupdate); 662 m_previousTimeupdateTime = now; 663 } 664