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