OLD | NEW |
| (Empty) |
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 MediaController* MediaController::create(ExecutionContext* context) | |
46 { | |
47 return 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(std::numeric_limits<double>::quiet_NaN()) | |
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 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 obj
ect 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 TimeRanges* bufferedRanges = (*it)->buffered(); | |
97 for (++it; it != m_mediaElements.end(); ++it) | |
98 bufferedRanges->intersectWith((*it)->buffered()); | |
99 return bufferedRanges; | |
100 } | |
101 | |
102 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 obj
ect 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 TimeRanges* seekableRanges = (*it)->seekable(); | |
112 for (++it; it != m_mediaElements.end(); ++it) | |
113 seekableRanges->intersectWith((*it)->seekable()); | |
114 return seekableRanges; | |
115 } | |
116 | |
117 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 objec
t that represents | |
123 // the union of the ranges of the media resources of the slaved media elemen
ts 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 TimeRanges* playedRanges = (*it)->played(); | |
127 for (++it; it != m_mediaElements.end(); ++it) | |
128 playedRanges->unionWith((*it)->played()); | |
129 return playedRanges; | |
130 } | |
131 | |
132 double MediaController::duration() const | |
133 { | |
134 // FIXME: Investigate caching the maximum duration and only updating the cac
hed value | |
135 // when the slaved media elements' durations change. | |
136 double maxDuration = 0; | |
137 for (const HTMLMediaElement* element : m_mediaElements) { | |
138 double duration = element->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 (std::isnan(m_position)) { | |
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 p
layback 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 duratio
n, 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 t
he media element timeline. | |
176 for (HTMLMediaElement* element : m_mediaElements) | |
177 element->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 contr
oller, | |
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 me
thod of each | |
199 // slaved media element in turn, | |
200 for (HTMLMediaElement* element : m_mediaElements) | |
201 element->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 m
edia controller, | |
210 if (m_paused) | |
211 return; | |
212 | |
213 // then the user agent must change the MediaController into a paused media c
ontroller, | |
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 MediaControll
er'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 MediaCon
troller. | |
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 me
dia controller | |
245 // playback rate to the new value, | |
246 m_clock->setPlayRate(rate); | |
247 | |
248 for (HTMLMediaElement* element : m_mediaElements) | |
249 element->updatePlaybackRate(); | |
250 | |
251 // then queue a task to fire a simple event named ratechange at the MediaCon
troller. | |
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 sett
ing, an | |
261 // IndexSizeError exception must be raised instead. | |
262 if (level < 0 || level > 1) { | |
263 exceptionState.throwDOMException(IndexSizeError, ExceptionMessages::inde
xOutsideRange("volume", level, 0.0, ExceptionMessages::InclusiveBound, 1.0, Exce
ptionMessages::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 MediaCo
ntroller. | |
272 scheduleEvent(EventTypeNames::volumechange); | |
273 | |
274 for (HTMLMediaElement* element : m_mediaElements) | |
275 element->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 con
troller 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 MediaCo
ntroller. | |
288 scheduleEvent(EventTypeNames::volumechange); | |
289 | |
290 for (HTMLMediaElement* element : m_mediaElements) | |
291 element->updateVolume(); | |
292 } | |
293 | |
294 static const AtomicString& playbackStateWaiting() | |
295 { | |
296 DEFINE_STATIC_LOCAL(AtomicString, waiting, ("waiting", AtomicString::Constru
ctFromLiteral)); | |
297 return waiting; | |
298 } | |
299 | |
300 static const AtomicString& playbackStatePlaying() | |
301 { | |
302 DEFINE_STATIC_LOCAL(AtomicString, playing, ("playing", AtomicString::Constru
ctFromLiteral)); | |
303 return playing; | |
304 } | |
305 | |
306 static const AtomicString& playbackStateEnded() | |
307 { | |
308 DEFINE_STATIC_LOCAL(AtomicString, ended, ("ended", AtomicString::ConstructFr
omLiteral)); | |
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 s
tate) | |
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 readines
s state be 0. | |
359 newReadyState = HTMLMediaElement::HAVE_NOTHING; | |
360 } else { | |
361 // Otherwise, let it have the lowest value of the readyState IDL attribu
tes 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 greate
r than new readiness | |
373 // state then queue a task to fire a simple event at the MediaController obj
ect, 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 t
han the new readiness | |
381 // state, then run these substeps: | |
382 // 1. Let next state be the MediaController's most recently reported readine
ss 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 tabl
e below. [omitted] | |
389 scheduleEvent(eventNameForReadyState(nextState)); | |
390 // If next state is less than new readiness state, then return to the st
ep labeled loop | |
391 } while (nextState < newReadyState); | |
392 | |
393 // Let the MediaController's most recently reported readiness state be new r
eadiness 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 fi
rst 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 play
back 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 equ
al 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 me
dia controller, and | |
430 // all of the MediaController's slaved media elements have still ended p
layback, 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 equ
al to new playback state | |
442 // then queue a task to fire a simple event at the MediaController object, w
hose name is playing | |
443 // if new playback state is playing, ended if new playback state is ended, a
nd 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 pl
ayback state. | |
467 m_playbackState = newPlaybackState; | |
468 | |
469 updateMediaElements(); | |
470 } | |
471 | |
472 void MediaController::updateMediaElements() | |
473 { | |
474 for (HTMLMediaElement* element : m_mediaElements) | |
475 element->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 (const HTMLMediaElement* element : m_mediaElements) { | |
504 if (element->isAutoplaying() && element->paused()) | |
505 anyAutoplayingAndPaused = true; | |
506 | |
507 if (!element->paused()) | |
508 allPaused = false; | |
509 } | |
510 | |
511 // but either at least one of its slaved media elements whose autoplaying fl
ag is true still has | |
512 // its paused attribute set to true, or, all of its slaved media elements ha
ve their paused | |
513 // attribute set to true. | |
514 return anyAutoplayingAndPaused || allPaused; | |
515 } | |
516 | |
517 bool MediaController::isBlocked() const | |
518 { | |
519 ASSERT(!m_mediaElements.isEmpty()); | |
520 | |
521 // A MediaController is a blocked media controller if the MediaController is
a paused media | |
522 // controller, | |
523 if (m_paused) | |
524 return true; | |
525 | |
526 bool allPaused = true; | |
527 for (const HTMLMediaElement* element : m_mediaElements) { | |
528 // or if any of its slaved media elements are blocked media elements, | |
529 if (element->isBlocked()) | |
530 return true; | |
531 | |
532 // or if any of its slaved media elements whose autoplaying flag is true
still have their | |
533 // paused attribute set to true, | |
534 if (element->isAutoplaying() && element->paused()) | |
535 return true; | |
536 | |
537 if (!element->paused()) | |
538 allPaused = false; | |
539 } | |
540 | |
541 // or if all of its slaved media elements have their paused attribute set to
true. | |
542 return allPaused; | |
543 } | |
544 | |
545 bool MediaController::hasEnded() const | |
546 { | |
547 // If the ... media controller playback rate is positive or zero | |
548 if (m_clock->playRate() < 0) | |
549 return false; | |
550 | |
551 // [and] all of the MediaController's slaved media elements have ended playb
ack ... let new | |
552 // playback state be ended. | |
553 if (m_mediaElements.isEmpty()) | |
554 return false; | |
555 | |
556 bool allHaveEnded = true; | |
557 for (const HTMLMediaElement* element : m_mediaElements) { | |
558 if (!element->ended()) | |
559 allHaveEnded = false; | |
560 } | |
561 return allHaveEnded; | |
562 } | |
563 | |
564 void MediaController::scheduleEvent(const AtomicString& eventName) | |
565 { | |
566 m_pendingEventsQueue->enqueueEvent(Event::createCancelable(eventName)); | |
567 } | |
568 | |
569 void MediaController::clearPositionTimerFired(Timer<MediaController>*) | |
570 { | |
571 m_position = std::numeric_limits<double>::quiet_NaN(); | |
572 } | |
573 | |
574 const AtomicString& MediaController::interfaceName() const | |
575 { | |
576 return EventTargetNames::MediaController; | |
577 } | |
578 | |
579 // The spec says to fire periodic timeupdate events (those sent while playing) e
very | |
580 // "15 to 250ms", we choose the slowest frequency | |
581 static const double maxTimeupdateEventFrequency = 0.25; | |
582 | |
583 void MediaController::startTimeupdateTimer() | |
584 { | |
585 if (m_timeupdateTimer.isActive()) | |
586 return; | |
587 | |
588 m_timeupdateTimer.startRepeating(maxTimeupdateEventFrequency, FROM_HERE); | |
589 } | |
590 | |
591 void MediaController::timeupdateTimerFired(Timer<MediaController>*) | |
592 { | |
593 scheduleTimeupdateEvent(); | |
594 } | |
595 | |
596 void MediaController::scheduleTimeupdateEvent() | |
597 { | |
598 double now = WTF::currentTime(); | |
599 double timedelta = now - m_previousTimeupdateTime; | |
600 | |
601 if (timedelta < maxTimeupdateEventFrequency) | |
602 return; | |
603 | |
604 scheduleEvent(EventTypeNames::timeupdate); | |
605 m_previousTimeupdateTime = now; | |
606 } | |
607 | |
608 DEFINE_TRACE(MediaController) | |
609 { | |
610 #if ENABLE(OILPAN) | |
611 visitor->trace(m_mediaElements); | |
612 visitor->trace(m_pendingEventsQueue); | |
613 visitor->trace(m_executionContext); | |
614 #endif | |
615 RefCountedGarbageCollectedEventTargetWithInlineData<MediaController>::trace(
visitor); | |
616 } | |
617 | |
618 } | |
OLD | NEW |