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