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/core/v8/ExceptionMessages.h" 30 #include "bindings/core/v8/ExceptionState.h" 31 #include "bindings/core/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 blink { 44 45 PassRefPtrWillBeRawPtr<MediaController> MediaController::create(ExecutionContext* context) 46 { 47 return adoptRefWillBeNoop(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 } 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.add(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 PassRefPtrWillBeRawPtr<TimeRanges> MediaController::buffered() const 88 { 89 if (m_mediaElements.isEmpty()) 90 return TimeRanges::create(); 91 92 // The buffered attribute must return a new static normalized TimeRanges object that represents 93 // the intersection of the ranges of the media resources of the slaved media elements that the 94 // user agent has buffered, at the time the attribute is evaluated. 95 MediaElementSequence::const_iterator it = m_mediaElements.begin(); 96 RefPtrWillBeRawPtr<TimeRanges> bufferedRanges = (*it)->buffered(); 97 for (++it; it != m_mediaElements.end(); ++it) 98 bufferedRanges->intersectWith((*it)->buffered().get()); 99 return bufferedRanges; 100 } 101 102 PassRefPtrWillBeRawPtr<TimeRanges> MediaController::seekable() const 103 { 104 if (m_mediaElements.isEmpty()) 105 return TimeRanges::create(); 106 107 // The seekable attribute must return a new static normalized TimeRanges object that represents 108 // the intersection of the ranges of the media resources of the slaved media elements that the 109 // user agent is able to seek to, at the time the attribute is evaluated. 110 MediaElementSequence::const_iterator it = m_mediaElements.begin(); 111 RefPtrWillBeRawPtr<TimeRanges> seekableRanges = (*it)->seekable(); 112 for (++it; it != m_mediaElements.end(); ++it) 113 seekableRanges->intersectWith((*it)->seekable().get()); 114 return seekableRanges; 115 } 116 117 PassRefPtrWillBeRawPtr<TimeRanges> MediaController::played() 118 { 119 if (m_mediaElements.isEmpty()) 120 return TimeRanges::create(); 121 122 // The played attribute must return a new static normalized TimeRanges object that represents 123 // the union of the ranges of the media resources of the slaved media elements that the 124 // user agent has so far rendered, at the time the attribute is evaluated. 125 MediaElementSequence::const_iterator it = m_mediaElements.begin(); 126 RefPtrWillBeRawPtr<TimeRanges> playedRanges = (*it)->played(); 127 for (++it; it != m_mediaElements.end(); ++it) 128 playedRanges->unionWith((*it)->played().get()); 129 return playedRanges; 130 } 131 132 double MediaController::duration() const 133 { 134 // FIXME: Investigate caching the maximum duration and only updating the cached value 135 // when the slaved media elements' durations change. 136 double maxDuration = 0; 137 for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) { 138 double duration = (*it)->duration(); 139 if (std::isnan(duration)) 140 continue; 141 maxDuration = std::max(maxDuration, duration); 142 } 143 return maxDuration; 144 } 145 146 double MediaController::currentTime() const 147 { 148 if (m_mediaElements.isEmpty()) 149 return 0; 150 151 if (m_position == MediaPlayer::invalidTime()) { 152 // Some clocks may return times outside the range of [0..duration]. 153 m_position = std::max(0.0, std::min(duration(), m_clock->currentTime())); 154 m_clearPositionTimer.startOneShot(0, FROM_HERE); 155 } 156 157 return m_position; 158 } 159 160 void MediaController::setCurrentTime(double time) 161 { 162 // When the user agent is to seek the media controller to a particular new playback position, 163 // it must follow these steps: 164 // If the new playback position is less than zero, then set it to zero. 165 time = std::max(0.0, time); 166 167 // If the new playback position is greater than the media controller duration, then set it 168 // to the media controller duration. 169 time = std::min(time, duration()); 170 171 // Set the media controller position to the new playback position. 172 m_position = time; 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 (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) 177 (*it)->seek(time); 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(EventTypeNames::play); 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 (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) 201 (*it)->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(EventTypeNames::pause); 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(EventTypeNames::ratechange); 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 (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) 249 (*it)->updatePlaybackRate(); 250 251 // then queue a task to fire a simple event named ratechange at the MediaController. 252 scheduleEvent(EventTypeNames::ratechange); 253 } 254 255 void MediaController::setVolume(double level, ExceptionState& exceptionState) 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 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::indexOutsideRange("volume", level, 0.0, ExceptionMessages::InclusiveBound, 1.0, ExceptionMessages::InclusiveBound)); 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(EventTypeNames::volumechange); 273 274 for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) 275 (*it)->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(EventTypeNames::volumechange); 289 290 for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) 291 (*it)->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 const AtomicString& eventNameForReadyState(HTMLMediaElement::ReadyState state) 334 { 335 switch (state) { 336 case HTMLMediaElement::HAVE_NOTHING: 337 return EventTypeNames::emptied; 338 case HTMLMediaElement::HAVE_METADATA: 339 return EventTypeNames::loadedmetadata; 340 case HTMLMediaElement::HAVE_CURRENT_DATA: 341 return EventTypeNames::loadeddata; 342 case HTMLMediaElement::HAVE_FUTURE_DATA: 343 return EventTypeNames::canplay; 344 case HTMLMediaElement::HAVE_ENOUGH_DATA: 345 return EventTypeNames::canplaythrough; 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 = HTMLMediaElement::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 MediaElementSequence::const_iterator it = m_mediaElements.begin(); 364 newReadyState = (*it)->readyState(); 365 for (++it; it != m_mediaElements.end(); ++it) 366 newReadyState = std::min(newReadyState, (*it)->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 (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) 475 (*it)->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()); 487 488 // Update volume to take controller volume and mute into account. 489 element->updateVolume(); 490 } 491 492 bool MediaController::isRestrained() const 493 { 494 ASSERT(!m_mediaElements.isEmpty()); 495 496 // A MediaController is a restrained media controller if the MediaController is a playing media 497 // controller, 498 if (m_paused) 499 return false; 500 501 bool anyAutoplayingAndPaused = false; 502 bool allPaused = true; 503 for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) { 504 HTMLMediaElement* element = *it; 505 506 if (element->isAutoplaying() && element->paused()) 507 anyAutoplayingAndPaused = true; 508 509 if (!element->paused()) 510 allPaused = false; 511 } 512 513 // but either at least one of its slaved media elements whose autoplaying flag is true still has 514 // its paused attribute set to true, or, all of its slaved media elements have their paused 515 // attribute set to true. 516 return anyAutoplayingAndPaused || allPaused; 517 } 518 519 bool MediaController::isBlocked() const 520 { 521 ASSERT(!m_mediaElements.isEmpty()); 522 523 // A MediaController is a blocked media controller if the MediaController is a paused media 524 // controller, 525 if (m_paused) 526 return true; 527 528 bool allPaused = true; 529 for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) { 530 HTMLMediaElement* element = *it; 531 532 // or if any of its slaved media elements are blocked media elements, 533 if (element->isBlocked()) 534 return true; 535 536 // or if any of its slaved media elements whose autoplaying flag is true still have their 537 // paused attribute set to true, 538 if (element->isAutoplaying() && element->paused()) 539 return true; 540 541 if (!element->paused()) 542 allPaused = false; 543 } 544 545 // or if all of its slaved media elements have their paused attribute set to true. 546 return allPaused; 547 } 548 549 bool MediaController::hasEnded() const 550 { 551 // If the ... media controller playback rate is positive or zero 552 if (m_clock->playRate() < 0) 553 return false; 554 555 // [and] all of the MediaController's slaved media elements have ended playback ... let new 556 // playback state be ended. 557 if (m_mediaElements.isEmpty()) 558 return false; 559 560 bool allHaveEnded = true; 561 for (MediaElementSequence::const_iterator it = m_mediaElements.begin(); it != m_mediaElements.end(); ++it) { 562 if (!(*it)->ended()) 563 allHaveEnded = false; 564 } 565 return allHaveEnded; 566 } 567 568 void MediaController::scheduleEvent(const AtomicString& eventName) 569 { 570 m_pendingEventsQueue->enqueueEvent(Event::createCancelable(eventName)); 571 } 572 573 void MediaController::clearPositionTimerFired(Timer<MediaController>*) 574 { 575 m_position = MediaPlayer::invalidTime(); 576 } 577 578 const AtomicString& MediaController::interfaceName() const 579 { 580 return EventTargetNames::MediaController; 581 } 582 583 // The spec says to fire periodic timeupdate events (those sent while playing) every 584 // "15 to 250ms", we choose the slowest frequency 585 static const double maxTimeupdateEventFrequency = 0.25; 586 587 void MediaController::startTimeupdateTimer() 588 { 589 if (m_timeupdateTimer.isActive()) 590 return; 591 592 m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE); 593 } 594 595 void MediaController::timeupdateTimerFired(Timer<MediaController>*) 596 { 597 scheduleTimeupdateEvent(); 598 } 599 600 void MediaController::scheduleTimeupdateEvent() 601 { 602 double now = WTF::currentTime(); 603 double timedelta = now - m_previousTimeupdateTime; 604 605 if (timedelta < maxTimeupdateEventFrequency) 606 return; 607 608 scheduleEvent(EventTypeNames::timeupdate); 609 m_previousTimeupdateTime = now; 610 } 611 612 void MediaController::trace(Visitor* visitor) 613 { 614 #if ENABLE(OILPAN) 615 visitor->trace(m_mediaElements); 616 visitor->trace(m_pendingEventsQueue); 617 visitor->trace(m_executionContext); 618 #endif 619 EventTargetWithInlineData::trace(visitor); 620 } 621 622 } 623