|
1 /* |
|
2 * Copyright (C) 2007, 2008, 2009, 2010 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 |
|
28 #if ENABLE(VIDEO) |
|
29 #include "HTMLMediaElement.h" |
|
30 |
|
31 #include "Attribute.h" |
|
32 #include "CSSHelper.h" |
|
33 #include "CSSPropertyNames.h" |
|
34 #include "CSSValueKeywords.h" |
|
35 #include "Chrome.h" |
|
36 #include "ChromeClient.h" |
|
37 #include "ClientRect.h" |
|
38 #include "ClientRectList.h" |
|
39 #include "ContentType.h" |
|
40 #include "DocLoader.h" |
|
41 #include "Event.h" |
|
42 #include "EventNames.h" |
|
43 #include "ExceptionCode.h" |
|
44 #include "Frame.h" |
|
45 #include "FrameLoader.h" |
|
46 #include "FrameLoaderClient.h" |
|
47 #include "FrameView.h" |
|
48 #include "HTMLDocument.h" |
|
49 #include "HTMLNames.h" |
|
50 #include "HTMLSourceElement.h" |
|
51 #include "HTMLVideoElement.h" |
|
52 #include "MIMETypeRegistry.h" |
|
53 #include "MediaDocument.h" |
|
54 #include "MediaError.h" |
|
55 #include "MediaList.h" |
|
56 #include "MediaPlayer.h" |
|
57 #include "MediaQueryEvaluator.h" |
|
58 #include "Page.h" |
|
59 #include "RenderVideo.h" |
|
60 #include "RenderView.h" |
|
61 #include "ScriptEventListener.h" |
|
62 #include "Settings.h" |
|
63 #include "TimeRanges.h" |
|
64 #include <limits> |
|
65 #include <wtf/CurrentTime.h> |
|
66 #include <wtf/MathExtras.h> |
|
67 |
|
68 #if USE(ACCELERATED_COMPOSITING) |
|
69 #include "RenderView.h" |
|
70 #include "RenderLayerCompositor.h" |
|
71 #endif |
|
72 |
|
73 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
74 #include "RenderEmbeddedObject.h" |
|
75 #include "Widget.h" |
|
76 #endif |
|
77 |
|
78 using namespace std; |
|
79 |
|
80 namespace WebCore { |
|
81 |
|
82 using namespace HTMLNames; |
|
83 |
|
84 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* doc) |
|
85 : HTMLElement(tagName, doc) |
|
86 , m_loadTimer(this, &HTMLMediaElement::loadTimerFired) |
|
87 , m_asyncEventTimer(this, &HTMLMediaElement::asyncEventTimerFired) |
|
88 , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired) |
|
89 , m_playbackProgressTimer(this, &HTMLMediaElement::playbackProgressTimerFired) |
|
90 , m_playedTimeRanges() |
|
91 , m_playbackRate(1.0f) |
|
92 , m_defaultPlaybackRate(1.0f) |
|
93 , m_webkitPreservesPitch(true) |
|
94 , m_networkState(NETWORK_EMPTY) |
|
95 , m_readyState(HAVE_NOTHING) |
|
96 , m_readyStateMaximum(HAVE_NOTHING) |
|
97 , m_volume(1.0f) |
|
98 , m_lastSeekTime(0) |
|
99 , m_previousProgress(0) |
|
100 , m_previousProgressTime(numeric_limits<double>::max()) |
|
101 , m_lastTimeUpdateEventWallTime(0) |
|
102 , m_lastTimeUpdateEventMovieTime(numeric_limits<float>::max()) |
|
103 , m_loadState(WaitingForSource) |
|
104 , m_currentSourceNode(0) |
|
105 , m_player(0) |
|
106 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
107 , m_proxyWidget(0) |
|
108 #endif |
|
109 , m_restrictions(NoRestrictions) |
|
110 , m_preload(MediaPlayer::Auto) |
|
111 , m_playing(false) |
|
112 , m_processingMediaPlayerCallback(0) |
|
113 , m_isWaitingUntilMediaCanStart(false) |
|
114 , m_processingLoad(false) |
|
115 , m_delayingTheLoadEvent(false) |
|
116 , m_haveFiredLoadedData(false) |
|
117 , m_inActiveDocument(true) |
|
118 , m_autoplaying(true) |
|
119 , m_muted(false) |
|
120 , m_paused(true) |
|
121 , m_seeking(false) |
|
122 , m_sentStalledEvent(false) |
|
123 , m_sentEndEvent(false) |
|
124 , m_pausedInternal(false) |
|
125 , m_sendProgressEvents(true) |
|
126 , m_isFullscreen(false) |
|
127 , m_closedCaptionsVisible(false) |
|
128 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
129 , m_needWidgetUpdate(false) |
|
130 #endif |
|
131 , m_dispatchingCanPlayEvent(false) |
|
132 , m_loadInitiatedByUserGesture(false) |
|
133 , m_completelyLoaded(false) |
|
134 { |
|
135 document()->registerForDocumentActivationCallbacks(this); |
|
136 document()->registerForMediaVolumeCallbacks(this); |
|
137 } |
|
138 |
|
139 HTMLMediaElement::~HTMLMediaElement() |
|
140 { |
|
141 if (m_isWaitingUntilMediaCanStart) |
|
142 document()->removeMediaCanStartListener(this); |
|
143 document()->unregisterForDocumentActivationCallbacks(this); |
|
144 document()->unregisterForMediaVolumeCallbacks(this); |
|
145 } |
|
146 |
|
147 void HTMLMediaElement::willMoveToNewOwnerDocument() |
|
148 { |
|
149 if (m_isWaitingUntilMediaCanStart) |
|
150 document()->removeMediaCanStartListener(this); |
|
151 document()->unregisterForDocumentActivationCallbacks(this); |
|
152 document()->unregisterForMediaVolumeCallbacks(this); |
|
153 HTMLElement::willMoveToNewOwnerDocument(); |
|
154 } |
|
155 |
|
156 void HTMLMediaElement::didMoveToNewOwnerDocument() |
|
157 { |
|
158 if (m_isWaitingUntilMediaCanStart) |
|
159 document()->addMediaCanStartListener(this); |
|
160 document()->registerForDocumentActivationCallbacks(this); |
|
161 document()->registerForMediaVolumeCallbacks(this); |
|
162 HTMLElement::didMoveToNewOwnerDocument(); |
|
163 } |
|
164 |
|
165 |
|
166 bool HTMLMediaElement::checkDTD(const Node* newChild) |
|
167 { |
|
168 return newChild->hasTagName(sourceTag) || HTMLElement::checkDTD(newChild); |
|
169 } |
|
170 |
|
171 void HTMLMediaElement::attributeChanged(Attribute* attr, bool preserveDecls) |
|
172 { |
|
173 HTMLElement::attributeChanged(attr, preserveDecls); |
|
174 |
|
175 const QualifiedName& attrName = attr->name(); |
|
176 if (attrName == srcAttr) { |
|
177 // Trigger a reload, as long as the 'src' attribute is present. |
|
178 if (!getAttribute(srcAttr).isEmpty()) |
|
179 scheduleLoad(); |
|
180 } |
|
181 else if (attrName == controlsAttr) { |
|
182 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
183 if (!isVideo() && attached() && (controls() != (renderer() != 0))) { |
|
184 detach(); |
|
185 attach(); |
|
186 } |
|
187 if (renderer()) |
|
188 renderer()->updateFromElement(); |
|
189 #else |
|
190 if (m_player) |
|
191 m_player->setControls(controls()); |
|
192 #endif |
|
193 } |
|
194 } |
|
195 |
|
196 void HTMLMediaElement::parseMappedAttribute(Attribute* attr) |
|
197 { |
|
198 const QualifiedName& attrName = attr->name(); |
|
199 |
|
200 if (attrName == preloadAttr) { |
|
201 String value = attr->value(); |
|
202 |
|
203 if (equalIgnoringCase(value, "none")) |
|
204 m_preload = MediaPlayer::None; |
|
205 else if (equalIgnoringCase(value, "metadata")) |
|
206 m_preload = MediaPlayer::MetaData; |
|
207 else { |
|
208 // The spec does not define an "invalid value default" but "auto" is suggested as the |
|
209 // "missing value default", so use it for everything except "none" and "metadata" |
|
210 m_preload = MediaPlayer::Auto; |
|
211 } |
|
212 |
|
213 // The attribute must be ignored if the autoplay attribute is present |
|
214 if (!autoplay() && m_player) |
|
215 m_player->setPreload(m_preload); |
|
216 |
|
217 } else if (attrName == onabortAttr) |
|
218 setAttributeEventListener(eventNames().abortEvent, createAttributeEventListener(this, attr)); |
|
219 else if (attrName == onbeforeloadAttr) |
|
220 setAttributeEventListener(eventNames().beforeloadEvent, createAttributeEventListener(this, attr)); |
|
221 else if (attrName == oncanplayAttr) |
|
222 setAttributeEventListener(eventNames().canplayEvent, createAttributeEventListener(this, attr)); |
|
223 else if (attrName == oncanplaythroughAttr) |
|
224 setAttributeEventListener(eventNames().canplaythroughEvent, createAttributeEventListener(this, attr)); |
|
225 else if (attrName == ondurationchangeAttr) |
|
226 setAttributeEventListener(eventNames().durationchangeEvent, createAttributeEventListener(this, attr)); |
|
227 else if (attrName == onemptiedAttr) |
|
228 setAttributeEventListener(eventNames().emptiedEvent, createAttributeEventListener(this, attr)); |
|
229 else if (attrName == onendedAttr) |
|
230 setAttributeEventListener(eventNames().endedEvent, createAttributeEventListener(this, attr)); |
|
231 else if (attrName == onerrorAttr) |
|
232 setAttributeEventListener(eventNames().errorEvent, createAttributeEventListener(this, attr)); |
|
233 else if (attrName == onloadeddataAttr) |
|
234 setAttributeEventListener(eventNames().loadeddataEvent, createAttributeEventListener(this, attr)); |
|
235 else if (attrName == onloadedmetadataAttr) |
|
236 setAttributeEventListener(eventNames().loadedmetadataEvent, createAttributeEventListener(this, attr)); |
|
237 else if (attrName == onloadstartAttr) |
|
238 setAttributeEventListener(eventNames().loadstartEvent, createAttributeEventListener(this, attr)); |
|
239 else if (attrName == onpauseAttr) |
|
240 setAttributeEventListener(eventNames().pauseEvent, createAttributeEventListener(this, attr)); |
|
241 else if (attrName == onplayAttr) |
|
242 setAttributeEventListener(eventNames().playEvent, createAttributeEventListener(this, attr)); |
|
243 else if (attrName == onplayingAttr) |
|
244 setAttributeEventListener(eventNames().playingEvent, createAttributeEventListener(this, attr)); |
|
245 else if (attrName == onprogressAttr) |
|
246 setAttributeEventListener(eventNames().progressEvent, createAttributeEventListener(this, attr)); |
|
247 else if (attrName == onratechangeAttr) |
|
248 setAttributeEventListener(eventNames().ratechangeEvent, createAttributeEventListener(this, attr)); |
|
249 else if (attrName == onseekedAttr) |
|
250 setAttributeEventListener(eventNames().seekedEvent, createAttributeEventListener(this, attr)); |
|
251 else if (attrName == onseekingAttr) |
|
252 setAttributeEventListener(eventNames().seekingEvent, createAttributeEventListener(this, attr)); |
|
253 else if (attrName == onstalledAttr) |
|
254 setAttributeEventListener(eventNames().stalledEvent, createAttributeEventListener(this, attr)); |
|
255 else if (attrName == onsuspendAttr) |
|
256 setAttributeEventListener(eventNames().suspendEvent, createAttributeEventListener(this, attr)); |
|
257 else if (attrName == ontimeupdateAttr) |
|
258 setAttributeEventListener(eventNames().timeupdateEvent, createAttributeEventListener(this, attr)); |
|
259 else if (attrName == onvolumechangeAttr) |
|
260 setAttributeEventListener(eventNames().volumechangeEvent, createAttributeEventListener(this, attr)); |
|
261 else if (attrName == onwaitingAttr) |
|
262 setAttributeEventListener(eventNames().waitingEvent, createAttributeEventListener(this, attr)); |
|
263 else if (attrName == onwebkitbeginfullscreenAttr) |
|
264 setAttributeEventListener(eventNames().webkitbeginfullscreenEvent, createAttributeEventListener(this, attr)); |
|
265 else if (attrName == onwebkitendfullscreenAttr) |
|
266 setAttributeEventListener(eventNames().webkitendfullscreenEvent, createAttributeEventListener(this, attr)); |
|
267 else |
|
268 HTMLElement::parseMappedAttribute(attr); |
|
269 } |
|
270 |
|
271 bool HTMLMediaElement::rendererIsNeeded(RenderStyle* style) |
|
272 { |
|
273 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
274 UNUSED_PARAM(style); |
|
275 Frame* frame = document()->frame(); |
|
276 if (!frame) |
|
277 return false; |
|
278 |
|
279 return true; |
|
280 #else |
|
281 return controls() ? HTMLElement::rendererIsNeeded(style) : false; |
|
282 #endif |
|
283 } |
|
284 |
|
285 RenderObject* HTMLMediaElement::createRenderer(RenderArena* arena, RenderStyle*) |
|
286 { |
|
287 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
288 // Setup the renderer if we already have a proxy widget. |
|
289 RenderEmbeddedObject* mediaRenderer = new (arena) RenderEmbeddedObject(this); |
|
290 if (m_proxyWidget) { |
|
291 mediaRenderer->setWidget(m_proxyWidget); |
|
292 |
|
293 Frame* frame = document()->frame(); |
|
294 FrameLoader* loader = frame ? frame->loader() : 0; |
|
295 if (loader) |
|
296 loader->showMediaPlayerProxyPlugin(m_proxyWidget.get()); |
|
297 } |
|
298 return mediaRenderer; |
|
299 #else |
|
300 return new (arena) RenderMedia(this); |
|
301 #endif |
|
302 } |
|
303 |
|
304 void HTMLMediaElement::insertedIntoDocument() |
|
305 { |
|
306 HTMLElement::insertedIntoDocument(); |
|
307 if (!src().isEmpty() && m_networkState == NETWORK_EMPTY) |
|
308 scheduleLoad(); |
|
309 } |
|
310 |
|
311 void HTMLMediaElement::removedFromDocument() |
|
312 { |
|
313 if (m_networkState > NETWORK_EMPTY) |
|
314 pause(processingUserGesture()); |
|
315 if (m_isFullscreen) |
|
316 exitFullscreen(); |
|
317 HTMLElement::removedFromDocument(); |
|
318 } |
|
319 |
|
320 void HTMLMediaElement::attach() |
|
321 { |
|
322 ASSERT(!attached()); |
|
323 |
|
324 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
325 m_needWidgetUpdate = true; |
|
326 #endif |
|
327 |
|
328 HTMLElement::attach(); |
|
329 |
|
330 if (renderer()) |
|
331 renderer()->updateFromElement(); |
|
332 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
333 else if (m_proxyWidget) { |
|
334 Frame* frame = document()->frame(); |
|
335 FrameLoader* loader = frame ? frame->loader() : 0; |
|
336 if (loader) |
|
337 loader->hideMediaPlayerProxyPlugin(m_proxyWidget.get()); |
|
338 } |
|
339 #endif |
|
340 } |
|
341 |
|
342 void HTMLMediaElement::recalcStyle(StyleChange change) |
|
343 { |
|
344 HTMLElement::recalcStyle(change); |
|
345 |
|
346 if (renderer()) |
|
347 renderer()->updateFromElement(); |
|
348 } |
|
349 |
|
350 void HTMLMediaElement::scheduleLoad() |
|
351 { |
|
352 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
353 createMediaPlayerProxy(); |
|
354 #endif |
|
355 |
|
356 if (m_loadTimer.isActive()) |
|
357 return; |
|
358 prepareForLoad(); |
|
359 m_loadTimer.startOneShot(0); |
|
360 } |
|
361 |
|
362 void HTMLMediaElement::scheduleNextSourceChild() |
|
363 { |
|
364 // Schedule the timer to try the next <source> element WITHOUT resetting state ala prepareForLoad. |
|
365 m_loadTimer.startOneShot(0); |
|
366 } |
|
367 |
|
368 void HTMLMediaElement::scheduleEvent(const AtomicString& eventName) |
|
369 { |
|
370 m_pendingEvents.append(Event::create(eventName, false, true)); |
|
371 if (!m_asyncEventTimer.isActive()) |
|
372 m_asyncEventTimer.startOneShot(0); |
|
373 } |
|
374 |
|
375 void HTMLMediaElement::asyncEventTimerFired(Timer<HTMLMediaElement>*) |
|
376 { |
|
377 Vector<RefPtr<Event> > pendingEvents; |
|
378 ExceptionCode ec = 0; |
|
379 |
|
380 m_pendingEvents.swap(pendingEvents); |
|
381 unsigned count = pendingEvents.size(); |
|
382 for (unsigned ndx = 0; ndx < count; ++ndx) { |
|
383 if (pendingEvents[ndx]->type() == eventNames().canplayEvent) { |
|
384 m_dispatchingCanPlayEvent = true; |
|
385 dispatchEvent(pendingEvents[ndx].release(), ec); |
|
386 m_dispatchingCanPlayEvent = false; |
|
387 } else |
|
388 dispatchEvent(pendingEvents[ndx].release(), ec); |
|
389 } |
|
390 } |
|
391 |
|
392 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*) |
|
393 { |
|
394 if (m_loadState == LoadingFromSourceElement) |
|
395 loadNextSourceChild(); |
|
396 else |
|
397 loadInternal(); |
|
398 } |
|
399 |
|
400 static String serializeTimeOffset(float time) |
|
401 { |
|
402 String timeString = String::number(time); |
|
403 // FIXME serialize time offset values properly (format not specified yet) |
|
404 timeString.append("s"); |
|
405 return timeString; |
|
406 } |
|
407 |
|
408 static float parseTimeOffset(const String& timeString, bool* ok = 0) |
|
409 { |
|
410 const UChar* characters = timeString.characters(); |
|
411 unsigned length = timeString.length(); |
|
412 |
|
413 if (length && characters[length - 1] == 's') |
|
414 length--; |
|
415 |
|
416 // FIXME parse time offset values (format not specified yet) |
|
417 float val = charactersToFloat(characters, length, ok); |
|
418 return val; |
|
419 } |
|
420 |
|
421 float HTMLMediaElement::getTimeOffsetAttribute(const QualifiedName& name, float valueOnError) const |
|
422 { |
|
423 bool ok; |
|
424 String timeString = getAttribute(name); |
|
425 float result = parseTimeOffset(timeString, &ok); |
|
426 if (ok) |
|
427 return result; |
|
428 return valueOnError; |
|
429 } |
|
430 |
|
431 void HTMLMediaElement::setTimeOffsetAttribute(const QualifiedName& name, float value) |
|
432 { |
|
433 setAttribute(name, serializeTimeOffset(value)); |
|
434 } |
|
435 |
|
436 PassRefPtr<MediaError> HTMLMediaElement::error() const |
|
437 { |
|
438 return m_error; |
|
439 } |
|
440 |
|
441 KURL HTMLMediaElement::src() const |
|
442 { |
|
443 return getNonEmptyURLAttribute(srcAttr); |
|
444 } |
|
445 |
|
446 void HTMLMediaElement::setSrc(const String& url) |
|
447 { |
|
448 setAttribute(srcAttr, url); |
|
449 } |
|
450 |
|
451 String HTMLMediaElement::currentSrc() const |
|
452 { |
|
453 return m_currentSrc; |
|
454 } |
|
455 |
|
456 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const |
|
457 { |
|
458 return m_networkState; |
|
459 } |
|
460 |
|
461 String HTMLMediaElement::canPlayType(const String& mimeType) const |
|
462 { |
|
463 MediaPlayer::SupportsType support = MediaPlayer::supportsType(ContentType(mimeType)); |
|
464 String canPlay; |
|
465 |
|
466 // 4.8.10.3 |
|
467 switch (support) |
|
468 { |
|
469 case MediaPlayer::IsNotSupported: |
|
470 canPlay = ""; |
|
471 break; |
|
472 case MediaPlayer::MayBeSupported: |
|
473 canPlay = "maybe"; |
|
474 break; |
|
475 case MediaPlayer::IsSupported: |
|
476 canPlay = "probably"; |
|
477 break; |
|
478 } |
|
479 |
|
480 return canPlay; |
|
481 } |
|
482 |
|
483 void HTMLMediaElement::load(bool isUserGesture, ExceptionCode& ec) |
|
484 { |
|
485 if (m_restrictions & RequireUserGestureForLoadRestriction && !isUserGesture) |
|
486 ec = INVALID_STATE_ERR; |
|
487 else { |
|
488 m_loadInitiatedByUserGesture = isUserGesture; |
|
489 prepareForLoad(); |
|
490 loadInternal(); |
|
491 } |
|
492 } |
|
493 |
|
494 void HTMLMediaElement::prepareForLoad() |
|
495 { |
|
496 // Perform the cleanup required for the resource load algorithm to run. |
|
497 stopPeriodicTimers(); |
|
498 m_loadTimer.stop(); |
|
499 m_sentStalledEvent = false; |
|
500 m_haveFiredLoadedData = false; |
|
501 m_completelyLoaded = false; |
|
502 |
|
503 // 1 - Abort any already-running instance of the resource selection algorithm for this element. |
|
504 m_currentSourceNode = 0; |
|
505 |
|
506 // 2 - If there are any tasks from the media element's media element event task source in |
|
507 // one of the task queues, then remove those tasks. |
|
508 cancelPendingEventsAndCallbacks(); |
|
509 |
|
510 // 3 - If the media element's networkState is set to NETWORK_LOADING or NETWORK_IDLE, queue |
|
511 // a task to fire a simple event named abort at the media element. |
|
512 if (m_networkState == NETWORK_LOADING || m_networkState == NETWORK_IDLE) |
|
513 scheduleEvent(eventNames().abortEvent); |
|
514 |
|
515 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
516 m_player = MediaPlayer::create(this); |
|
517 #else |
|
518 createMediaPlayerProxy(); |
|
519 #endif |
|
520 |
|
521 // 4 - If the media element's networkState is not set to NETWORK_EMPTY, then run these substeps |
|
522 if (m_networkState != NETWORK_EMPTY) { |
|
523 m_networkState = NETWORK_EMPTY; |
|
524 m_readyState = HAVE_NOTHING; |
|
525 m_readyStateMaximum = HAVE_NOTHING; |
|
526 m_paused = true; |
|
527 m_seeking = false; |
|
528 scheduleEvent(eventNames().emptiedEvent); |
|
529 } |
|
530 |
|
531 // 5 - Set the playbackRate attribute to the value of the defaultPlaybackRate attribute. |
|
532 setPlaybackRate(defaultPlaybackRate()); |
|
533 |
|
534 // 6 - Set the error attribute to null and the autoplaying flag to true. |
|
535 m_error = 0; |
|
536 m_autoplaying = true; |
|
537 |
|
538 m_playedTimeRanges = TimeRanges::create(); |
|
539 m_lastSeekTime = 0; |
|
540 m_closedCaptionsVisible = false; |
|
541 |
|
542 } |
|
543 |
|
544 void HTMLMediaElement::loadInternal() |
|
545 { |
|
546 // If we can't start a load right away, start it later. |
|
547 Page* page = document()->page(); |
|
548 if (page && !page->canStartMedia()) { |
|
549 if (m_isWaitingUntilMediaCanStart) |
|
550 return; |
|
551 document()->addMediaCanStartListener(this); |
|
552 m_isWaitingUntilMediaCanStart = true; |
|
553 return; |
|
554 } |
|
555 |
|
556 // Steps 1 - 6 were done in prepareForLoad |
|
557 |
|
558 // 7 - Invoke the media element's resource selection algorithm. |
|
559 selectMediaResource(); |
|
560 m_processingLoad = false; |
|
561 } |
|
562 |
|
563 void HTMLMediaElement::selectMediaResource() |
|
564 { |
|
565 enum Mode { attribute, children }; |
|
566 Mode mode = attribute; |
|
567 |
|
568 // 1 - Set the networkState to NETWORK_NO_SOURCE |
|
569 m_networkState = NETWORK_NO_SOURCE; |
|
570 |
|
571 // 2 - Asynchronously await a stable state. |
|
572 |
|
573 // 3 - ... the media element has neither a src attribute ... |
|
574 if (!hasAttribute(srcAttr)) { |
|
575 // ... nor a source element child: ... |
|
576 Node* node; |
|
577 for (node = firstChild(); node; node = node->nextSibling()) { |
|
578 if (node->hasTagName(sourceTag)) |
|
579 break; |
|
580 } |
|
581 |
|
582 if (!node) { |
|
583 m_loadState = WaitingForSource; |
|
584 |
|
585 // ... set the networkState to NETWORK_EMPTY, and abort these steps |
|
586 m_networkState = NETWORK_EMPTY; |
|
587 ASSERT(!m_delayingTheLoadEvent); |
|
588 return; |
|
589 } |
|
590 |
|
591 mode = children; |
|
592 } |
|
593 |
|
594 // 4 |
|
595 m_delayingTheLoadEvent = true; |
|
596 m_networkState = NETWORK_LOADING; |
|
597 |
|
598 // 5 |
|
599 scheduleEvent(eventNames().loadstartEvent); |
|
600 |
|
601 // 6 - If mode is attribute, then run these substeps |
|
602 if (mode == attribute) { |
|
603 // If the src attribute's value is the empty string ... jump down to the failed step below |
|
604 KURL mediaURL = getNonEmptyURLAttribute(srcAttr); |
|
605 if (mediaURL.isEmpty()) { |
|
606 noneSupported(); |
|
607 return; |
|
608 } |
|
609 |
|
610 if (isSafeToLoadURL(mediaURL, Complain) && dispatchBeforeLoadEvent(mediaURL.string())) { |
|
611 ContentType contentType(""); |
|
612 m_loadState = LoadingFromSrcAttr; |
|
613 loadResource(mediaURL, contentType); |
|
614 } else |
|
615 noneSupported(); |
|
616 |
|
617 return; |
|
618 } |
|
619 |
|
620 // Otherwise, the source elements will be used |
|
621 m_currentSourceNode = 0; |
|
622 loadNextSourceChild(); |
|
623 } |
|
624 |
|
625 void HTMLMediaElement::loadNextSourceChild() |
|
626 { |
|
627 ContentType contentType(""); |
|
628 KURL mediaURL = selectNextSourceChild(&contentType, Complain); |
|
629 if (!mediaURL.isValid()) { |
|
630 waitForSourceChange(); |
|
631 return; |
|
632 } |
|
633 |
|
634 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
635 // Recreate the media player for the new url |
|
636 m_player = MediaPlayer::create(this); |
|
637 #endif |
|
638 |
|
639 m_loadState = LoadingFromSourceElement; |
|
640 loadResource(mediaURL, contentType); |
|
641 } |
|
642 |
|
643 void HTMLMediaElement::loadResource(const KURL& initialURL, ContentType& contentType) |
|
644 { |
|
645 ASSERT(isSafeToLoadURL(initialURL, Complain)); |
|
646 |
|
647 Frame* frame = document()->frame(); |
|
648 if (!frame) |
|
649 return; |
|
650 FrameLoader* loader = frame->loader(); |
|
651 if (!loader) |
|
652 return; |
|
653 |
|
654 KURL url(initialURL); |
|
655 if (!loader->willLoadMediaElementURL(url)) |
|
656 return; |
|
657 |
|
658 // The resource fetch algorithm |
|
659 m_networkState = NETWORK_LOADING; |
|
660 |
|
661 m_currentSrc = url; |
|
662 |
|
663 if (m_sendProgressEvents) |
|
664 startProgressEventTimer(); |
|
665 |
|
666 if (!autoplay()) |
|
667 m_player->setPreload(m_preload); |
|
668 m_player->setPreservesPitch(m_webkitPreservesPitch); |
|
669 updateVolume(); |
|
670 |
|
671 m_player->load(m_currentSrc, contentType); |
|
672 |
|
673 if (isVideo() && m_player->canLoadPoster()) { |
|
674 KURL posterUrl = poster(); |
|
675 if (!posterUrl.isEmpty()) |
|
676 m_player->setPoster(posterUrl); |
|
677 } |
|
678 |
|
679 if (renderer()) |
|
680 renderer()->updateFromElement(); |
|
681 } |
|
682 |
|
683 bool HTMLMediaElement::isSafeToLoadURL(const KURL& url, InvalidSourceAction actionIfInvalid) |
|
684 { |
|
685 if (!url.isValid()) |
|
686 return false; |
|
687 |
|
688 Frame* frame = document()->frame(); |
|
689 FrameLoader* loader = frame ? frame->loader() : 0; |
|
690 |
|
691 // don't allow remote to local urls, and check with the frame loader client. |
|
692 if (!loader || !SecurityOrigin::canLoad(url, String(), document())) { |
|
693 if (actionIfInvalid == Complain) |
|
694 FrameLoader::reportLocalLoadFailed(frame, url.string()); |
|
695 return false; |
|
696 } |
|
697 |
|
698 return true; |
|
699 } |
|
700 |
|
701 void HTMLMediaElement::startProgressEventTimer() |
|
702 { |
|
703 if (m_progressEventTimer.isActive()) |
|
704 return; |
|
705 |
|
706 m_previousProgressTime = WTF::currentTime(); |
|
707 m_previousProgress = 0; |
|
708 // 350ms is not magic, it is in the spec! |
|
709 m_progressEventTimer.startRepeating(0.350); |
|
710 } |
|
711 |
|
712 void HTMLMediaElement::waitForSourceChange() |
|
713 { |
|
714 stopPeriodicTimers(); |
|
715 m_loadState = WaitingForSource; |
|
716 |
|
717 // 6.17 - Waiting: Set the element's networkState attribute to the NETWORK_NO_SOURCE value |
|
718 m_networkState = NETWORK_NO_SOURCE; |
|
719 |
|
720 // 6.18 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
|
721 m_delayingTheLoadEvent = false; |
|
722 } |
|
723 |
|
724 void HTMLMediaElement::noneSupported() |
|
725 { |
|
726 stopPeriodicTimers(); |
|
727 m_loadState = WaitingForSource; |
|
728 m_currentSourceNode = 0; |
|
729 |
|
730 // 5 - Reaching this step indicates that either the URL failed to resolve, or the media |
|
731 // resource failed to load. Set the error attribute to a new MediaError object whose |
|
732 // code attribute is set to MEDIA_ERR_SRC_NOT_SUPPORTED. |
|
733 m_error = MediaError::create(MediaError::MEDIA_ERR_SRC_NOT_SUPPORTED); |
|
734 |
|
735 // 6 - Set the element's networkState attribute to the NETWORK_NO_SOURCE value. |
|
736 m_networkState = NETWORK_NO_SOURCE; |
|
737 |
|
738 // 7 - Queue a task to fire a progress event called error at the media element, in |
|
739 // the context of the fetching process that was used to try to obtain the media |
|
740 // resource in the resource fetch algorithm. |
|
741 scheduleEvent(eventNames().errorEvent); |
|
742 |
|
743 // 8 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
|
744 m_delayingTheLoadEvent = false; |
|
745 |
|
746 // 9 -Abort these steps. Until the load() method is invoked, the element won't attempt to load another resource. |
|
747 |
|
748 updatePosterImage(); |
|
749 |
|
750 if (renderer()) |
|
751 renderer()->updateFromElement(); |
|
752 } |
|
753 |
|
754 void HTMLMediaElement::mediaEngineError(PassRefPtr<MediaError> err) |
|
755 { |
|
756 // 1 - The user agent should cancel the fetching process. |
|
757 stopPeriodicTimers(); |
|
758 m_loadState = WaitingForSource; |
|
759 |
|
760 // 2 - Set the error attribute to a new MediaError object whose code attribute is |
|
761 // set to MEDIA_ERR_NETWORK/MEDIA_ERR_DECODE. |
|
762 m_error = err; |
|
763 |
|
764 // 3 - Queue a task to fire a progress event called error at the media element, in |
|
765 // the context of the fetching process started by this instance of this algorithm. |
|
766 scheduleEvent(eventNames().errorEvent); |
|
767 |
|
768 // 4 - Set the element's networkState attribute to the NETWORK_EMPTY value and queue a |
|
769 // task to fire a simple event called emptied at the element. |
|
770 m_networkState = NETWORK_EMPTY; |
|
771 scheduleEvent(eventNames().emptiedEvent); |
|
772 |
|
773 // 5 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
|
774 m_delayingTheLoadEvent = false; |
|
775 |
|
776 // 6 - Abort the overall resource selection algorithm. |
|
777 m_currentSourceNode = 0; |
|
778 } |
|
779 |
|
780 void HTMLMediaElement::cancelPendingEventsAndCallbacks() |
|
781 { |
|
782 m_pendingEvents.clear(); |
|
783 |
|
784 for (Node* node = firstChild(); node; node = node->nextSibling()) { |
|
785 if (node->hasTagName(sourceTag)) |
|
786 static_cast<HTMLSourceElement*>(node)->cancelPendingErrorEvent(); |
|
787 } |
|
788 } |
|
789 |
|
790 Document* HTMLMediaElement::mediaPlayerOwningDocument() |
|
791 { |
|
792 Document* d = document(); |
|
793 |
|
794 if (!d) |
|
795 d = ownerDocument(); |
|
796 |
|
797 return d; |
|
798 } |
|
799 |
|
800 void HTMLMediaElement::mediaPlayerNetworkStateChanged(MediaPlayer*) |
|
801 { |
|
802 beginProcessingMediaPlayerCallback(); |
|
803 setNetworkState(m_player->networkState()); |
|
804 endProcessingMediaPlayerCallback(); |
|
805 } |
|
806 |
|
807 void HTMLMediaElement::setNetworkState(MediaPlayer::NetworkState state) |
|
808 { |
|
809 if (state == MediaPlayer::Empty) { |
|
810 // just update the cached state and leave, we can't do anything |
|
811 m_networkState = NETWORK_EMPTY; |
|
812 return; |
|
813 } |
|
814 |
|
815 if (state == MediaPlayer::FormatError || state == MediaPlayer::NetworkError || state == MediaPlayer::DecodeError) { |
|
816 stopPeriodicTimers(); |
|
817 |
|
818 // If we failed while trying to load a <source> element, the movie was never parsed, and there are more |
|
819 // <source> children, schedule the next one |
|
820 if (m_readyState < HAVE_METADATA && m_loadState == LoadingFromSourceElement) { |
|
821 m_currentSourceNode->scheduleErrorEvent(); |
|
822 if (havePotentialSourceChild()) |
|
823 scheduleNextSourceChild(); |
|
824 else |
|
825 waitForSourceChange(); |
|
826 |
|
827 return; |
|
828 } |
|
829 |
|
830 if (state == MediaPlayer::NetworkError) |
|
831 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_NETWORK)); |
|
832 else if (state == MediaPlayer::DecodeError) |
|
833 mediaEngineError(MediaError::create(MediaError::MEDIA_ERR_DECODE)); |
|
834 else if (state == MediaPlayer::FormatError && m_loadState == LoadingFromSrcAttr) |
|
835 noneSupported(); |
|
836 |
|
837 updatePosterImage(); |
|
838 return; |
|
839 } |
|
840 |
|
841 if (state == MediaPlayer::Idle) { |
|
842 if (m_networkState > NETWORK_IDLE) { |
|
843 m_progressEventTimer.stop(); |
|
844 scheduleEvent(eventNames().suspendEvent); |
|
845 } |
|
846 m_networkState = NETWORK_IDLE; |
|
847 } |
|
848 |
|
849 if (state == MediaPlayer::Loading) { |
|
850 if (m_networkState < NETWORK_LOADING || m_networkState == NETWORK_NO_SOURCE) |
|
851 startProgressEventTimer(); |
|
852 m_networkState = NETWORK_LOADING; |
|
853 } |
|
854 |
|
855 if (state == MediaPlayer::Loaded) { |
|
856 if (m_networkState != NETWORK_IDLE) { |
|
857 m_progressEventTimer.stop(); |
|
858 |
|
859 // Schedule one last progress event so we guarantee that at least one is fired |
|
860 // for files that load very quickly. |
|
861 scheduleEvent(eventNames().progressEvent); |
|
862 } |
|
863 m_networkState = NETWORK_IDLE; |
|
864 m_completelyLoaded = true; |
|
865 } |
|
866 } |
|
867 |
|
868 void HTMLMediaElement::mediaPlayerReadyStateChanged(MediaPlayer*) |
|
869 { |
|
870 beginProcessingMediaPlayerCallback(); |
|
871 |
|
872 setReadyState(m_player->readyState()); |
|
873 |
|
874 endProcessingMediaPlayerCallback(); |
|
875 } |
|
876 |
|
877 void HTMLMediaElement::setReadyState(MediaPlayer::ReadyState state) |
|
878 { |
|
879 // Set "wasPotentiallyPlaying" BEFORE updating m_readyState, potentiallyPlaying() uses it |
|
880 bool wasPotentiallyPlaying = potentiallyPlaying(); |
|
881 |
|
882 ReadyState oldState = m_readyState; |
|
883 m_readyState = static_cast<ReadyState>(state); |
|
884 |
|
885 if (m_readyState == oldState) |
|
886 return; |
|
887 |
|
888 if (oldState > m_readyStateMaximum) |
|
889 m_readyStateMaximum = oldState; |
|
890 |
|
891 if (m_networkState == NETWORK_EMPTY) |
|
892 return; |
|
893 |
|
894 if (m_seeking) { |
|
895 // 4.8.10.10, step 8 |
|
896 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) |
|
897 scheduleEvent(eventNames().waitingEvent); |
|
898 |
|
899 // 4.8.10.10, step 9 |
|
900 if (m_readyState < HAVE_CURRENT_DATA) { |
|
901 if (oldState >= HAVE_CURRENT_DATA) |
|
902 scheduleEvent(eventNames().seekingEvent); |
|
903 } else { |
|
904 // 4.8.10.10 step 12 & 13. |
|
905 finishSeek(); |
|
906 } |
|
907 |
|
908 } else { |
|
909 if (wasPotentiallyPlaying && m_readyState < HAVE_FUTURE_DATA) { |
|
910 // 4.8.10.9 |
|
911 scheduleTimeupdateEvent(false); |
|
912 scheduleEvent(eventNames().waitingEvent); |
|
913 } |
|
914 } |
|
915 |
|
916 if (m_readyState >= HAVE_METADATA && oldState < HAVE_METADATA) { |
|
917 scheduleEvent(eventNames().durationchangeEvent); |
|
918 scheduleEvent(eventNames().loadedmetadataEvent); |
|
919 |
|
920 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
921 if (renderer() && renderer()->isVideo()) { |
|
922 toRenderVideo(renderer())->videoSizeChanged(); |
|
923 } |
|
924 #endif |
|
925 m_delayingTheLoadEvent = false; |
|
926 m_player->seek(0); |
|
927 } |
|
928 |
|
929 bool shouldUpdatePosterImage = false; |
|
930 |
|
931 // 4.8.10.7 says loadeddata is sent only when the new state *is* HAVE_CURRENT_DATA: "If the |
|
932 // previous ready state was HAVE_METADATA and the new ready state is HAVE_CURRENT_DATA", |
|
933 // but the event table at the end of the spec says it is sent when: "readyState newly |
|
934 // increased to HAVE_CURRENT_DATA or greater for the first time" |
|
935 // We go with the later because it seems useful to count on getting this event |
|
936 if (m_readyState >= HAVE_CURRENT_DATA && oldState < HAVE_CURRENT_DATA && !m_haveFiredLoadedData) { |
|
937 m_haveFiredLoadedData = true; |
|
938 shouldUpdatePosterImage = true; |
|
939 scheduleEvent(eventNames().loadeddataEvent); |
|
940 } |
|
941 |
|
942 bool isPotentiallyPlaying = potentiallyPlaying(); |
|
943 if (m_readyState == HAVE_FUTURE_DATA && oldState <= HAVE_CURRENT_DATA) { |
|
944 scheduleEvent(eventNames().canplayEvent); |
|
945 if (isPotentiallyPlaying) |
|
946 scheduleEvent(eventNames().playingEvent); |
|
947 shouldUpdatePosterImage = true; |
|
948 } |
|
949 |
|
950 if (m_readyState == HAVE_ENOUGH_DATA && oldState < HAVE_ENOUGH_DATA) { |
|
951 if (oldState <= HAVE_CURRENT_DATA) |
|
952 scheduleEvent(eventNames().canplayEvent); |
|
953 |
|
954 scheduleEvent(eventNames().canplaythroughEvent); |
|
955 |
|
956 if (isPotentiallyPlaying && oldState <= HAVE_CURRENT_DATA) |
|
957 scheduleEvent(eventNames().playingEvent); |
|
958 |
|
959 if (m_autoplaying && m_paused && autoplay()) { |
|
960 m_paused = false; |
|
961 scheduleEvent(eventNames().playEvent); |
|
962 scheduleEvent(eventNames().playingEvent); |
|
963 } |
|
964 |
|
965 shouldUpdatePosterImage = true; |
|
966 } |
|
967 |
|
968 if (shouldUpdatePosterImage) |
|
969 updatePosterImage(); |
|
970 |
|
971 updatePlayState(); |
|
972 } |
|
973 |
|
974 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*) |
|
975 { |
|
976 ASSERT(m_player); |
|
977 if (m_networkState != NETWORK_LOADING) |
|
978 return; |
|
979 |
|
980 unsigned progress = m_player->bytesLoaded(); |
|
981 double time = WTF::currentTime(); |
|
982 double timedelta = time - m_previousProgressTime; |
|
983 |
|
984 if (progress == m_previousProgress) { |
|
985 if (timedelta > 3.0 && !m_sentStalledEvent) { |
|
986 scheduleEvent(eventNames().stalledEvent); |
|
987 m_sentStalledEvent = true; |
|
988 } |
|
989 } else { |
|
990 scheduleEvent(eventNames().progressEvent); |
|
991 m_previousProgress = progress; |
|
992 m_previousProgressTime = time; |
|
993 m_sentStalledEvent = false; |
|
994 if (renderer()) |
|
995 renderer()->updateFromElement(); |
|
996 } |
|
997 } |
|
998 |
|
999 void HTMLMediaElement::rewind(float timeDelta) |
|
1000 { |
|
1001 ExceptionCode e; |
|
1002 setCurrentTime(max(currentTime() - timeDelta, minTimeSeekable()), e); |
|
1003 } |
|
1004 |
|
1005 void HTMLMediaElement::returnToRealtime() |
|
1006 { |
|
1007 ExceptionCode e; |
|
1008 setCurrentTime(maxTimeSeekable(), e); |
|
1009 } |
|
1010 |
|
1011 void HTMLMediaElement::addPlayedRange(float start, float end) |
|
1012 { |
|
1013 if (!m_playedTimeRanges) |
|
1014 m_playedTimeRanges = TimeRanges::create(); |
|
1015 m_playedTimeRanges->add(start, end); |
|
1016 } |
|
1017 |
|
1018 bool HTMLMediaElement::supportsSave() const |
|
1019 { |
|
1020 return m_player ? m_player->supportsSave() : false; |
|
1021 } |
|
1022 |
|
1023 void HTMLMediaElement::seek(float time, ExceptionCode& ec) |
|
1024 { |
|
1025 // 4.8.9.9 Seeking |
|
1026 |
|
1027 // 1 - If the media element's readyState is HAVE_NOTHING, then raise an INVALID_STATE_ERR exception. |
|
1028 if (m_readyState == HAVE_NOTHING || !m_player) { |
|
1029 ec = INVALID_STATE_ERR; |
|
1030 return; |
|
1031 } |
|
1032 |
|
1033 // Get the current time before setting m_seeking, m_lastSeekTime is returned once it is set. |
|
1034 float now = currentTime(); |
|
1035 |
|
1036 // 3 - Set the seeking IDL attribute to true. |
|
1037 // The flag will be cleared when the engine tells is the time has actually changed |
|
1038 m_seeking = true; |
|
1039 |
|
1040 // 4 - Queue a task to fire a simple event named timeupdate at the element. |
|
1041 scheduleTimeupdateEvent(false); |
|
1042 |
|
1043 // 6 - If the new playback position is later than the end of the media resource, then let it be the end |
|
1044 // of the media resource instead. |
|
1045 time = min(time, duration()); |
|
1046 |
|
1047 // 7 - If the new playback position is less than the earliest possible position, let it be that position instead. |
|
1048 float earliestTime = m_player->startTime(); |
|
1049 time = max(time, earliestTime); |
|
1050 |
|
1051 // 8 - If the (possibly now changed) new playback position is not in one of the ranges given in the |
|
1052 // seekable attribute, then let it be the position in one of the ranges given in the seekable attribute |
|
1053 // that is the nearest to the new playback position. ... If there are no ranges given in the seekable |
|
1054 // attribute then set the seeking IDL attribute to false and abort these steps. |
|
1055 RefPtr<TimeRanges> seekableRanges = seekable(); |
|
1056 if (!seekableRanges->length() || time == now) { |
|
1057 m_seeking = false; |
|
1058 return; |
|
1059 } |
|
1060 time = seekableRanges->nearest(time); |
|
1061 |
|
1062 if (m_playing) { |
|
1063 if (m_lastSeekTime < now) |
|
1064 addPlayedRange(m_lastSeekTime, now); |
|
1065 } |
|
1066 m_lastSeekTime = time; |
|
1067 m_sentEndEvent = false; |
|
1068 |
|
1069 // 9 - Set the current playback position to the given new playback position |
|
1070 m_player->seek(time); |
|
1071 |
|
1072 // 10-15 are handled, if necessary, when the engine signals a readystate change. |
|
1073 |
|
1074 } |
|
1075 |
|
1076 void HTMLMediaElement::finishSeek() |
|
1077 { |
|
1078 // 4.8.10.10 Seeking step 12 |
|
1079 m_seeking = false; |
|
1080 |
|
1081 // 4.8.10.10 Seeking step 13 |
|
1082 scheduleEvent(eventNames().seekedEvent); |
|
1083 } |
|
1084 |
|
1085 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const |
|
1086 { |
|
1087 return m_readyState; |
|
1088 } |
|
1089 |
|
1090 MediaPlayer::MovieLoadType HTMLMediaElement::movieLoadType() const |
|
1091 { |
|
1092 return m_player ? m_player->movieLoadType() : MediaPlayer::Unknown; |
|
1093 } |
|
1094 |
|
1095 bool HTMLMediaElement::hasAudio() const |
|
1096 { |
|
1097 return m_player ? m_player->hasAudio() : false; |
|
1098 } |
|
1099 |
|
1100 bool HTMLMediaElement::seeking() const |
|
1101 { |
|
1102 return m_seeking; |
|
1103 } |
|
1104 |
|
1105 // playback state |
|
1106 float HTMLMediaElement::currentTime() const |
|
1107 { |
|
1108 if (!m_player) |
|
1109 return 0; |
|
1110 if (m_seeking) |
|
1111 return m_lastSeekTime; |
|
1112 return m_player->currentTime(); |
|
1113 } |
|
1114 |
|
1115 void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec) |
|
1116 { |
|
1117 seek(time, ec); |
|
1118 } |
|
1119 |
|
1120 float HTMLMediaElement::startTime() const |
|
1121 { |
|
1122 if (!m_player) |
|
1123 return 0; |
|
1124 return m_player->startTime(); |
|
1125 } |
|
1126 |
|
1127 float HTMLMediaElement::duration() const |
|
1128 { |
|
1129 if (m_player && m_readyState >= HAVE_METADATA) |
|
1130 return m_player->duration(); |
|
1131 |
|
1132 return numeric_limits<float>::quiet_NaN(); |
|
1133 } |
|
1134 |
|
1135 bool HTMLMediaElement::paused() const |
|
1136 { |
|
1137 return m_paused; |
|
1138 } |
|
1139 |
|
1140 float HTMLMediaElement::defaultPlaybackRate() const |
|
1141 { |
|
1142 return m_defaultPlaybackRate; |
|
1143 } |
|
1144 |
|
1145 void HTMLMediaElement::setDefaultPlaybackRate(float rate) |
|
1146 { |
|
1147 if (m_defaultPlaybackRate != rate) { |
|
1148 m_defaultPlaybackRate = rate; |
|
1149 scheduleEvent(eventNames().ratechangeEvent); |
|
1150 } |
|
1151 } |
|
1152 |
|
1153 float HTMLMediaElement::playbackRate() const |
|
1154 { |
|
1155 return m_player ? m_player->rate() : 0; |
|
1156 } |
|
1157 |
|
1158 void HTMLMediaElement::setPlaybackRate(float rate) |
|
1159 { |
|
1160 if (m_playbackRate != rate) { |
|
1161 m_playbackRate = rate; |
|
1162 scheduleEvent(eventNames().ratechangeEvent); |
|
1163 } |
|
1164 if (m_player && potentiallyPlaying() && m_player->rate() != rate) |
|
1165 m_player->setRate(rate); |
|
1166 } |
|
1167 |
|
1168 bool HTMLMediaElement::webkitPreservesPitch() const |
|
1169 { |
|
1170 return m_webkitPreservesPitch; |
|
1171 } |
|
1172 |
|
1173 void HTMLMediaElement::setWebkitPreservesPitch(bool preservesPitch) |
|
1174 { |
|
1175 m_webkitPreservesPitch = preservesPitch; |
|
1176 |
|
1177 if (!m_player) |
|
1178 return; |
|
1179 |
|
1180 m_player->setPreservesPitch(preservesPitch); |
|
1181 } |
|
1182 |
|
1183 bool HTMLMediaElement::ended() const |
|
1184 { |
|
1185 // 4.8.10.8 Playing the media resource |
|
1186 // The ended attribute must return true if the media element has ended |
|
1187 // playback and the direction of playback is forwards, and false otherwise. |
|
1188 return endedPlayback() && m_playbackRate > 0; |
|
1189 } |
|
1190 |
|
1191 bool HTMLMediaElement::autoplay() const |
|
1192 { |
|
1193 return hasAttribute(autoplayAttr); |
|
1194 } |
|
1195 |
|
1196 void HTMLMediaElement::setAutoplay(bool b) |
|
1197 { |
|
1198 setBooleanAttribute(autoplayAttr, b); |
|
1199 } |
|
1200 |
|
1201 String HTMLMediaElement::preload() const |
|
1202 { |
|
1203 switch (m_preload) { |
|
1204 case MediaPlayer::None: |
|
1205 return "none"; |
|
1206 break; |
|
1207 case MediaPlayer::MetaData: |
|
1208 return "metadata"; |
|
1209 break; |
|
1210 case MediaPlayer::Auto: |
|
1211 return "auto"; |
|
1212 break; |
|
1213 } |
|
1214 |
|
1215 ASSERT_NOT_REACHED(); |
|
1216 return String(); |
|
1217 } |
|
1218 |
|
1219 void HTMLMediaElement::setPreload(const String& preload) |
|
1220 { |
|
1221 setAttribute(preloadAttr, preload); |
|
1222 } |
|
1223 |
|
1224 void HTMLMediaElement::play(bool isUserGesture) |
|
1225 { |
|
1226 if (m_restrictions & RequireUserGestureForRateChangeRestriction && !isUserGesture) |
|
1227 return; |
|
1228 |
|
1229 Document* doc = document(); |
|
1230 Settings* settings = doc->settings(); |
|
1231 if (settings && settings->needsSiteSpecificQuirks() && m_dispatchingCanPlayEvent && !m_loadInitiatedByUserGesture) { |
|
1232 // It should be impossible to be processing the canplay event while handling a user gesture |
|
1233 // since it is dispatched asynchronously. |
|
1234 ASSERT(!isUserGesture); |
|
1235 String host = doc->baseURL().host(); |
|
1236 if (host.endsWith(".npr.org", false) || equalIgnoringCase(host, "npr.org")) |
|
1237 return; |
|
1238 } |
|
1239 |
|
1240 playInternal(); |
|
1241 } |
|
1242 |
|
1243 void HTMLMediaElement::playInternal() |
|
1244 { |
|
1245 // 4.8.10.9. Playing the media resource |
|
1246 if (!m_player || m_networkState == NETWORK_EMPTY) |
|
1247 scheduleLoad(); |
|
1248 |
|
1249 if (endedPlayback()) { |
|
1250 ExceptionCode unused; |
|
1251 seek(0, unused); |
|
1252 } |
|
1253 |
|
1254 setPlaybackRate(defaultPlaybackRate()); |
|
1255 |
|
1256 if (m_paused) { |
|
1257 m_paused = false; |
|
1258 scheduleEvent(eventNames().playEvent); |
|
1259 |
|
1260 if (m_readyState <= HAVE_CURRENT_DATA) |
|
1261 scheduleEvent(eventNames().waitingEvent); |
|
1262 else if (m_readyState >= HAVE_FUTURE_DATA) |
|
1263 scheduleEvent(eventNames().playingEvent); |
|
1264 } |
|
1265 m_autoplaying = false; |
|
1266 |
|
1267 updatePlayState(); |
|
1268 } |
|
1269 |
|
1270 void HTMLMediaElement::pause(bool isUserGesture) |
|
1271 { |
|
1272 if (m_restrictions & RequireUserGestureForRateChangeRestriction && !isUserGesture) |
|
1273 return; |
|
1274 |
|
1275 pauseInternal(); |
|
1276 } |
|
1277 |
|
1278 |
|
1279 void HTMLMediaElement::pauseInternal() |
|
1280 { |
|
1281 // 4.8.10.9. Playing the media resource |
|
1282 if (!m_player || m_networkState == NETWORK_EMPTY) |
|
1283 scheduleLoad(); |
|
1284 |
|
1285 m_autoplaying = false; |
|
1286 |
|
1287 if (!m_paused) { |
|
1288 m_paused = true; |
|
1289 scheduleTimeupdateEvent(false); |
|
1290 scheduleEvent(eventNames().pauseEvent); |
|
1291 } |
|
1292 |
|
1293 updatePlayState(); |
|
1294 } |
|
1295 |
|
1296 bool HTMLMediaElement::loop() const |
|
1297 { |
|
1298 return hasAttribute(loopAttr); |
|
1299 } |
|
1300 |
|
1301 void HTMLMediaElement::setLoop(bool b) |
|
1302 { |
|
1303 setBooleanAttribute(loopAttr, b); |
|
1304 } |
|
1305 |
|
1306 bool HTMLMediaElement::controls() const |
|
1307 { |
|
1308 Frame* frame = document()->frame(); |
|
1309 |
|
1310 // always show controls when scripting is disabled |
|
1311 if (frame && !frame->script()->canExecuteScripts(NotAboutToExecuteScript)) |
|
1312 return true; |
|
1313 |
|
1314 return hasAttribute(controlsAttr); |
|
1315 } |
|
1316 |
|
1317 void HTMLMediaElement::setControls(bool b) |
|
1318 { |
|
1319 setBooleanAttribute(controlsAttr, b); |
|
1320 } |
|
1321 |
|
1322 float HTMLMediaElement::volume() const |
|
1323 { |
|
1324 return m_volume; |
|
1325 } |
|
1326 |
|
1327 void HTMLMediaElement::setVolume(float vol, ExceptionCode& ec) |
|
1328 { |
|
1329 if (vol < 0.0f || vol > 1.0f) { |
|
1330 ec = INDEX_SIZE_ERR; |
|
1331 return; |
|
1332 } |
|
1333 |
|
1334 if (m_volume != vol) { |
|
1335 m_volume = vol; |
|
1336 updateVolume(); |
|
1337 scheduleEvent(eventNames().volumechangeEvent); |
|
1338 } |
|
1339 } |
|
1340 |
|
1341 bool HTMLMediaElement::muted() const |
|
1342 { |
|
1343 return m_muted; |
|
1344 } |
|
1345 |
|
1346 void HTMLMediaElement::setMuted(bool muted) |
|
1347 { |
|
1348 if (m_muted != muted) { |
|
1349 m_muted = muted; |
|
1350 // Avoid recursion when the player reports volume changes. |
|
1351 if (!processingMediaPlayerCallback()) { |
|
1352 if (m_player) { |
|
1353 m_player->setMuted(m_muted); |
|
1354 if (renderer()) |
|
1355 renderer()->updateFromElement(); |
|
1356 } else |
|
1357 updateVolume(); |
|
1358 } |
|
1359 scheduleEvent(eventNames().volumechangeEvent); |
|
1360 } |
|
1361 } |
|
1362 |
|
1363 void HTMLMediaElement::togglePlayState() |
|
1364 { |
|
1365 // We can safely call the internal play/pause methods, which don't check restrictions, because |
|
1366 // this method is only called from the built-in media controller |
|
1367 if (canPlay()) |
|
1368 playInternal(); |
|
1369 else |
|
1370 pauseInternal(); |
|
1371 } |
|
1372 |
|
1373 void HTMLMediaElement::beginScrubbing() |
|
1374 { |
|
1375 if (!paused()) { |
|
1376 if (ended()) { |
|
1377 // Because a media element stays in non-paused state when it reaches end, playback resumes |
|
1378 // when the slider is dragged from the end to another position unless we pause first. Do |
|
1379 // a "hard pause" so an event is generated, since we want to stay paused after scrubbing finishes. |
|
1380 pause(processingUserGesture()); |
|
1381 } else { |
|
1382 // Not at the end but we still want to pause playback so the media engine doesn't try to |
|
1383 // continue playing during scrubbing. Pause without generating an event as we will |
|
1384 // unpause after scrubbing finishes. |
|
1385 setPausedInternal(true); |
|
1386 } |
|
1387 } |
|
1388 } |
|
1389 |
|
1390 void HTMLMediaElement::endScrubbing() |
|
1391 { |
|
1392 if (m_pausedInternal) |
|
1393 setPausedInternal(false); |
|
1394 } |
|
1395 |
|
1396 // The spec says to fire periodic timeupdate events (those sent while playing) every |
|
1397 // "15 to 250ms", we choose the slowest frequency |
|
1398 static const double maxTimeupdateEventFrequency = 0.25; |
|
1399 |
|
1400 void HTMLMediaElement::startPlaybackProgressTimer() |
|
1401 { |
|
1402 if (m_playbackProgressTimer.isActive()) |
|
1403 return; |
|
1404 |
|
1405 m_previousProgressTime = WTF::currentTime(); |
|
1406 m_previousProgress = 0; |
|
1407 m_playbackProgressTimer.startRepeating(maxTimeupdateEventFrequency); |
|
1408 } |
|
1409 |
|
1410 void HTMLMediaElement::playbackProgressTimerFired(Timer<HTMLMediaElement>*) |
|
1411 { |
|
1412 ASSERT(m_player); |
|
1413 if (!m_playbackRate) |
|
1414 return; |
|
1415 |
|
1416 scheduleTimeupdateEvent(true); |
|
1417 |
|
1418 // FIXME: deal with cue ranges here |
|
1419 } |
|
1420 |
|
1421 void HTMLMediaElement::scheduleTimeupdateEvent(bool periodicEvent) |
|
1422 { |
|
1423 double now = WTF::currentTime(); |
|
1424 double timedelta = now - m_lastTimeUpdateEventWallTime; |
|
1425 |
|
1426 // throttle the periodic events |
|
1427 if (periodicEvent && timedelta < maxTimeupdateEventFrequency) |
|
1428 return; |
|
1429 |
|
1430 // Some media engines make multiple "time changed" callbacks at the same time, but we only want one |
|
1431 // event at a given time so filter here |
|
1432 float movieTime = m_player ? m_player->currentTime() : 0; |
|
1433 if (movieTime != m_lastTimeUpdateEventMovieTime) { |
|
1434 scheduleEvent(eventNames().timeupdateEvent); |
|
1435 m_lastTimeUpdateEventWallTime = now; |
|
1436 m_lastTimeUpdateEventMovieTime = movieTime; |
|
1437 } |
|
1438 } |
|
1439 |
|
1440 bool HTMLMediaElement::canPlay() const |
|
1441 { |
|
1442 return paused() || ended() || m_readyState < HAVE_METADATA; |
|
1443 } |
|
1444 |
|
1445 float HTMLMediaElement::percentLoaded() const |
|
1446 { |
|
1447 if (!m_player) |
|
1448 return 0; |
|
1449 float duration = m_player->duration(); |
|
1450 |
|
1451 if (!duration || isinf(duration)) |
|
1452 return 0; |
|
1453 |
|
1454 float buffered = 0; |
|
1455 RefPtr<TimeRanges> timeRanges = m_player->buffered(); |
|
1456 for (unsigned i = 0; i < timeRanges->length(); ++i) { |
|
1457 ExceptionCode ignoredException; |
|
1458 float start = timeRanges->start(i, ignoredException); |
|
1459 float end = timeRanges->end(i, ignoredException); |
|
1460 buffered += end - start; |
|
1461 } |
|
1462 return buffered / duration; |
|
1463 } |
|
1464 |
|
1465 bool HTMLMediaElement::havePotentialSourceChild() |
|
1466 { |
|
1467 // Stash the current <source> node so we can restore it after checking |
|
1468 // to see there is another potential |
|
1469 HTMLSourceElement* currentSourceNode = m_currentSourceNode; |
|
1470 KURL nextURL = selectNextSourceChild(0, DoNothing); |
|
1471 m_currentSourceNode = currentSourceNode; |
|
1472 |
|
1473 return nextURL.isValid(); |
|
1474 } |
|
1475 |
|
1476 KURL HTMLMediaElement::selectNextSourceChild(ContentType *contentType, InvalidSourceAction actionIfInvalid) |
|
1477 { |
|
1478 KURL mediaURL; |
|
1479 Node* node; |
|
1480 bool lookingForPreviousNode = m_currentSourceNode; |
|
1481 bool canUse = false; |
|
1482 |
|
1483 for (node = firstChild(); !canUse && node; node = node->nextSibling()) { |
|
1484 if (!node->hasTagName(sourceTag)) |
|
1485 continue; |
|
1486 |
|
1487 if (lookingForPreviousNode) { |
|
1488 if (m_currentSourceNode == static_cast<HTMLSourceElement*>(node)) |
|
1489 lookingForPreviousNode = false; |
|
1490 continue; |
|
1491 } |
|
1492 |
|
1493 HTMLSourceElement* source = static_cast<HTMLSourceElement*>(node); |
|
1494 |
|
1495 // If candidate does not have a src attribute, or if its src attribute's value is the empty string ... jump down to the failed step below |
|
1496 mediaURL = source->getNonEmptyURLAttribute(srcAttr); |
|
1497 if (mediaURL.isEmpty()) |
|
1498 goto check_again; |
|
1499 |
|
1500 if (source->hasAttribute(mediaAttr)) { |
|
1501 MediaQueryEvaluator screenEval("screen", document()->frame(), renderer() ? renderer()->style() : 0); |
|
1502 RefPtr<MediaList> media = MediaList::createAllowingDescriptionSyntax(source->media()); |
|
1503 if (!screenEval.eval(media.get())) |
|
1504 goto check_again; |
|
1505 } |
|
1506 |
|
1507 if (source->hasAttribute(typeAttr)) { |
|
1508 if (!MediaPlayer::supportsType(ContentType(source->type()))) |
|
1509 goto check_again; |
|
1510 } |
|
1511 |
|
1512 // Is it safe to load this url? |
|
1513 if (!isSafeToLoadURL(mediaURL, actionIfInvalid) || !dispatchBeforeLoadEvent(mediaURL.string())) |
|
1514 goto check_again; |
|
1515 |
|
1516 // Making it this far means the <source> looks reasonable |
|
1517 canUse = true; |
|
1518 if (contentType) |
|
1519 *contentType = ContentType(source->type()); |
|
1520 |
|
1521 check_again: |
|
1522 if (!canUse && actionIfInvalid == Complain) |
|
1523 source->scheduleErrorEvent(); |
|
1524 m_currentSourceNode = static_cast<HTMLSourceElement*>(node); |
|
1525 } |
|
1526 |
|
1527 if (!canUse) |
|
1528 m_currentSourceNode = 0; |
|
1529 return canUse ? mediaURL : KURL(); |
|
1530 } |
|
1531 |
|
1532 void HTMLMediaElement::mediaPlayerTimeChanged(MediaPlayer*) |
|
1533 { |
|
1534 beginProcessingMediaPlayerCallback(); |
|
1535 |
|
1536 // Always call scheduleTimeupdateEvent when the media engine reports a time discontinuity, |
|
1537 // it will only queue a 'timeupdate' event if we haven't already posted one at the current |
|
1538 // movie time. |
|
1539 scheduleTimeupdateEvent(false); |
|
1540 |
|
1541 // 4.8.10.10 step 12 & 13. Needed if no ReadyState change is associated with the seek. |
|
1542 if (m_readyState >= HAVE_CURRENT_DATA && m_seeking) |
|
1543 finishSeek(); |
|
1544 |
|
1545 float now = currentTime(); |
|
1546 float dur = duration(); |
|
1547 if (!isnan(dur) && dur && now >= dur) { |
|
1548 if (loop()) { |
|
1549 ExceptionCode ignoredException; |
|
1550 m_sentEndEvent = false; |
|
1551 seek(0, ignoredException); |
|
1552 } else { |
|
1553 if (!m_sentEndEvent) { |
|
1554 m_sentEndEvent = true; |
|
1555 scheduleEvent(eventNames().endedEvent); |
|
1556 } |
|
1557 } |
|
1558 } |
|
1559 else |
|
1560 m_sentEndEvent = false; |
|
1561 |
|
1562 updatePlayState(); |
|
1563 endProcessingMediaPlayerCallback(); |
|
1564 } |
|
1565 |
|
1566 void HTMLMediaElement::mediaPlayerVolumeChanged(MediaPlayer*) |
|
1567 { |
|
1568 beginProcessingMediaPlayerCallback(); |
|
1569 if (m_player) |
|
1570 m_volume = m_player->volume(); |
|
1571 updateVolume(); |
|
1572 endProcessingMediaPlayerCallback(); |
|
1573 } |
|
1574 |
|
1575 void HTMLMediaElement::mediaPlayerMuteChanged(MediaPlayer*) |
|
1576 { |
|
1577 beginProcessingMediaPlayerCallback(); |
|
1578 if (m_player) |
|
1579 setMuted(m_player->muted()); |
|
1580 endProcessingMediaPlayerCallback(); |
|
1581 } |
|
1582 |
|
1583 void HTMLMediaElement::mediaPlayerDurationChanged(MediaPlayer*) |
|
1584 { |
|
1585 beginProcessingMediaPlayerCallback(); |
|
1586 scheduleEvent(eventNames().durationchangeEvent); |
|
1587 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
1588 if (renderer()) { |
|
1589 renderer()->updateFromElement(); |
|
1590 if (renderer()->isVideo()) |
|
1591 toRenderVideo(renderer())->videoSizeChanged(); |
|
1592 } |
|
1593 #endif |
|
1594 endProcessingMediaPlayerCallback(); |
|
1595 } |
|
1596 |
|
1597 void HTMLMediaElement::mediaPlayerRateChanged(MediaPlayer*) |
|
1598 { |
|
1599 beginProcessingMediaPlayerCallback(); |
|
1600 // Stash the rate in case the one we tried to set isn't what the engine is |
|
1601 // using (eg. it can't handle the rate we set) |
|
1602 m_playbackRate = m_player->rate(); |
|
1603 endProcessingMediaPlayerCallback(); |
|
1604 } |
|
1605 |
|
1606 void HTMLMediaElement::mediaPlayerSawUnsupportedTracks(MediaPlayer*) |
|
1607 { |
|
1608 // The MediaPlayer came across content it cannot completely handle. |
|
1609 // This is normally acceptable except when we are in a standalone |
|
1610 // MediaDocument. If so, tell the document what has happened. |
|
1611 if (ownerDocument()->isMediaDocument()) { |
|
1612 MediaDocument* mediaDocument = static_cast<MediaDocument*>(ownerDocument()); |
|
1613 mediaDocument->mediaElementSawUnsupportedTracks(); |
|
1614 } |
|
1615 } |
|
1616 |
|
1617 // MediaPlayerPresentation methods |
|
1618 void HTMLMediaElement::mediaPlayerRepaint(MediaPlayer*) |
|
1619 { |
|
1620 beginProcessingMediaPlayerCallback(); |
|
1621 if (renderer()) |
|
1622 renderer()->repaint(); |
|
1623 |
|
1624 updatePosterImage(); |
|
1625 endProcessingMediaPlayerCallback(); |
|
1626 } |
|
1627 |
|
1628 void HTMLMediaElement::mediaPlayerSizeChanged(MediaPlayer*) |
|
1629 { |
|
1630 beginProcessingMediaPlayerCallback(); |
|
1631 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
1632 if (renderer() && renderer()->isVideo()) |
|
1633 toRenderVideo(renderer())->videoSizeChanged(); |
|
1634 #endif |
|
1635 endProcessingMediaPlayerCallback(); |
|
1636 } |
|
1637 |
|
1638 #if USE(ACCELERATED_COMPOSITING) |
|
1639 bool HTMLMediaElement::mediaPlayerRenderingCanBeAccelerated(MediaPlayer*) |
|
1640 { |
|
1641 if (renderer() && renderer()->isVideo()) { |
|
1642 ASSERT(renderer()->view()); |
|
1643 return renderer()->view()->compositor()->canAccelerateVideoRendering(toRenderVideo(renderer())); |
|
1644 } |
|
1645 return false; |
|
1646 } |
|
1647 |
|
1648 void HTMLMediaElement::mediaPlayerRenderingModeChanged(MediaPlayer*) |
|
1649 { |
|
1650 // Kick off a fake recalcStyle that will update the compositing tree. |
|
1651 setNeedsStyleRecalc(SyntheticStyleChange); |
|
1652 } |
|
1653 #endif |
|
1654 |
|
1655 PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const |
|
1656 { |
|
1657 if (!m_player) |
|
1658 return TimeRanges::create(); |
|
1659 return m_player->buffered(); |
|
1660 } |
|
1661 |
|
1662 PassRefPtr<TimeRanges> HTMLMediaElement::played() |
|
1663 { |
|
1664 if (m_playing) { |
|
1665 float time = currentTime(); |
|
1666 if (time > m_lastSeekTime) |
|
1667 addPlayedRange(m_lastSeekTime, time); |
|
1668 } |
|
1669 |
|
1670 if (!m_playedTimeRanges) |
|
1671 m_playedTimeRanges = TimeRanges::create(); |
|
1672 |
|
1673 return m_playedTimeRanges->copy(); |
|
1674 } |
|
1675 |
|
1676 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const |
|
1677 { |
|
1678 // FIXME real ranges support |
|
1679 if (!maxTimeSeekable()) |
|
1680 return TimeRanges::create(); |
|
1681 return TimeRanges::create(minTimeSeekable(), maxTimeSeekable()); |
|
1682 } |
|
1683 |
|
1684 bool HTMLMediaElement::potentiallyPlaying() const |
|
1685 { |
|
1686 // "pausedToBuffer" means the media engine's rate is 0, but only because it had to stop playing |
|
1687 // when it ran out of buffered data. A movie is this state is "potentially playing", modulo the |
|
1688 // checks in couldPlayIfEnoughData(). |
|
1689 bool pausedToBuffer = m_readyStateMaximum >= HAVE_FUTURE_DATA && m_readyState < HAVE_FUTURE_DATA; |
|
1690 return (pausedToBuffer || m_readyState >= HAVE_FUTURE_DATA) && couldPlayIfEnoughData(); |
|
1691 } |
|
1692 |
|
1693 bool HTMLMediaElement::couldPlayIfEnoughData() const |
|
1694 { |
|
1695 return !paused() && !endedPlayback() && !stoppedDueToErrors() && !pausedForUserInteraction(); |
|
1696 } |
|
1697 |
|
1698 bool HTMLMediaElement::endedPlayback() const |
|
1699 { |
|
1700 float dur = duration(); |
|
1701 if (!m_player || isnan(dur)) |
|
1702 return false; |
|
1703 |
|
1704 // 4.8.10.8 Playing the media resource |
|
1705 |
|
1706 // A media element is said to have ended playback when the element's |
|
1707 // readyState attribute is HAVE_METADATA or greater, |
|
1708 if (m_readyState < HAVE_METADATA) |
|
1709 return false; |
|
1710 |
|
1711 // and the current playback position is the end of the media resource and the direction |
|
1712 // of playback is forwards and the media element does not have a loop attribute specified, |
|
1713 float now = currentTime(); |
|
1714 if (m_playbackRate > 0) |
|
1715 return now >= dur && !loop(); |
|
1716 |
|
1717 // or the current playback position is the earliest possible position and the direction |
|
1718 // of playback is backwards |
|
1719 if (m_playbackRate < 0) |
|
1720 return now <= 0; |
|
1721 |
|
1722 return false; |
|
1723 } |
|
1724 |
|
1725 bool HTMLMediaElement::stoppedDueToErrors() const |
|
1726 { |
|
1727 if (m_readyState >= HAVE_METADATA && m_error) { |
|
1728 RefPtr<TimeRanges> seekableRanges = seekable(); |
|
1729 if (!seekableRanges->contain(currentTime())) |
|
1730 return true; |
|
1731 } |
|
1732 |
|
1733 return false; |
|
1734 } |
|
1735 |
|
1736 bool HTMLMediaElement::pausedForUserInteraction() const |
|
1737 { |
|
1738 // return !paused() && m_readyState >= HAVE_FUTURE_DATA && [UA requires a decitions from the user] |
|
1739 return false; |
|
1740 } |
|
1741 |
|
1742 float HTMLMediaElement::minTimeSeekable() const |
|
1743 { |
|
1744 return 0; |
|
1745 } |
|
1746 |
|
1747 float HTMLMediaElement::maxTimeSeekable() const |
|
1748 { |
|
1749 return m_player ? m_player->maxTimeSeekable() : 0; |
|
1750 } |
|
1751 |
|
1752 void HTMLMediaElement::updateVolume() |
|
1753 { |
|
1754 if (!m_player) |
|
1755 return; |
|
1756 |
|
1757 // Avoid recursion when the player reports volume changes. |
|
1758 if (!processingMediaPlayerCallback()) { |
|
1759 Page* page = document()->page(); |
|
1760 float volumeMultiplier = page ? page->mediaVolume() : 1; |
|
1761 |
|
1762 m_player->setMuted(m_muted); |
|
1763 m_player->setVolume(m_volume * volumeMultiplier); |
|
1764 } |
|
1765 |
|
1766 if (renderer()) |
|
1767 renderer()->updateFromElement(); |
|
1768 } |
|
1769 |
|
1770 void HTMLMediaElement::updatePlayState() |
|
1771 { |
|
1772 if (!m_player) |
|
1773 return; |
|
1774 |
|
1775 if (m_pausedInternal) { |
|
1776 if (!m_player->paused()) |
|
1777 m_player->pause(); |
|
1778 m_playbackProgressTimer.stop(); |
|
1779 return; |
|
1780 } |
|
1781 |
|
1782 bool shouldBePlaying = potentiallyPlaying(); |
|
1783 bool playerPaused = m_player->paused(); |
|
1784 if (shouldBePlaying && playerPaused) { |
|
1785 // Set rate before calling play in case the rate was set before the media engine wasn't setup. |
|
1786 // The media engine should just stash the rate since it isn't already playing. |
|
1787 m_player->setRate(m_playbackRate); |
|
1788 m_player->play(); |
|
1789 startPlaybackProgressTimer(); |
|
1790 m_playing = true; |
|
1791 } else if (!shouldBePlaying && !playerPaused) { |
|
1792 m_player->pause(); |
|
1793 m_playbackProgressTimer.stop(); |
|
1794 m_playing = false; |
|
1795 float time = currentTime(); |
|
1796 if (time > m_lastSeekTime) |
|
1797 addPlayedRange(m_lastSeekTime, time); |
|
1798 } else if (couldPlayIfEnoughData() && playerPaused) |
|
1799 m_player->prepareToPlay(); |
|
1800 |
|
1801 if (renderer()) |
|
1802 renderer()->updateFromElement(); |
|
1803 } |
|
1804 |
|
1805 void HTMLMediaElement::setPausedInternal(bool b) |
|
1806 { |
|
1807 m_pausedInternal = b; |
|
1808 updatePlayState(); |
|
1809 } |
|
1810 |
|
1811 void HTMLMediaElement::stopPeriodicTimers() |
|
1812 { |
|
1813 m_progressEventTimer.stop(); |
|
1814 m_playbackProgressTimer.stop(); |
|
1815 } |
|
1816 |
|
1817 void HTMLMediaElement::userCancelledLoad() |
|
1818 { |
|
1819 if (m_networkState == NETWORK_EMPTY || m_completelyLoaded) |
|
1820 return; |
|
1821 |
|
1822 // If the media data fetching process is aborted by the user: |
|
1823 |
|
1824 // 1 - The user agent should cancel the fetching process. |
|
1825 #if !ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
1826 m_player.clear(); |
|
1827 #endif |
|
1828 stopPeriodicTimers(); |
|
1829 |
|
1830 // 2 - Set the error attribute to a new MediaError object whose code attribute is set to MEDIA_ERR_ABORTED. |
|
1831 m_error = MediaError::create(MediaError::MEDIA_ERR_ABORTED); |
|
1832 |
|
1833 // 3 - Queue a task to fire a progress event called abort at the media element, in the context |
|
1834 // of the fetching process started by this instance of this algorithm. |
|
1835 scheduleEvent(eventNames().abortEvent); |
|
1836 |
|
1837 // 5 - If the media element's readyState attribute has a value equal to HAVE_NOTHING, set the |
|
1838 // element's networkState attribute to the NETWORK_EMPTY value and queue a task to fire a |
|
1839 // simple event called emptied at the element. Otherwise, set set the element's networkState |
|
1840 // attribute to the NETWORK_IDLE value. |
|
1841 if (m_readyState == HAVE_NOTHING) { |
|
1842 m_networkState = NETWORK_EMPTY; |
|
1843 scheduleEvent(eventNames().emptiedEvent); |
|
1844 } |
|
1845 else |
|
1846 m_networkState = NETWORK_IDLE; |
|
1847 |
|
1848 // 6 - Set the element's delaying-the-load-event flag to false. This stops delaying the load event. |
|
1849 m_delayingTheLoadEvent = false; |
|
1850 |
|
1851 // 7 - Abort the overall resource selection algorithm. |
|
1852 m_currentSourceNode = 0; |
|
1853 |
|
1854 // Reset m_readyState since m_player is gone. |
|
1855 m_readyState = HAVE_NOTHING; |
|
1856 } |
|
1857 |
|
1858 void HTMLMediaElement::documentWillBecomeInactive() |
|
1859 { |
|
1860 if (m_isFullscreen) |
|
1861 exitFullscreen(); |
|
1862 |
|
1863 m_inActiveDocument = false; |
|
1864 userCancelledLoad(); |
|
1865 |
|
1866 // Stop the playback without generating events |
|
1867 setPausedInternal(true); |
|
1868 |
|
1869 if (renderer()) |
|
1870 renderer()->updateFromElement(); |
|
1871 |
|
1872 stopPeriodicTimers(); |
|
1873 cancelPendingEventsAndCallbacks(); |
|
1874 } |
|
1875 |
|
1876 void HTMLMediaElement::documentDidBecomeActive() |
|
1877 { |
|
1878 m_inActiveDocument = true; |
|
1879 setPausedInternal(false); |
|
1880 |
|
1881 if (m_error && m_error->code() == MediaError::MEDIA_ERR_ABORTED) { |
|
1882 // Restart the load if it was aborted in the middle by moving the document to the page cache. |
|
1883 // m_error is only left at MEDIA_ERR_ABORTED when the document becomes inactive (it is set to |
|
1884 // MEDIA_ERR_ABORTED while the abortEvent is being sent, but cleared immediately afterwards). |
|
1885 // This behavior is not specified but it seems like a sensible thing to do. |
|
1886 ExceptionCode ec; |
|
1887 load(processingUserGesture(), ec); |
|
1888 } |
|
1889 |
|
1890 if (renderer()) |
|
1891 renderer()->updateFromElement(); |
|
1892 } |
|
1893 |
|
1894 void HTMLMediaElement::mediaVolumeDidChange() |
|
1895 { |
|
1896 updateVolume(); |
|
1897 } |
|
1898 |
|
1899 IntRect HTMLMediaElement::screenRect() |
|
1900 { |
|
1901 if (!renderer()) |
|
1902 return IntRect(); |
|
1903 return renderer()->view()->frameView()->contentsToScreen(renderer()->absoluteBoundingBoxRect()); |
|
1904 } |
|
1905 |
|
1906 void HTMLMediaElement::defaultEventHandler(Event* event) |
|
1907 { |
|
1908 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
1909 RenderObject* r = renderer(); |
|
1910 if (!r || !r->isWidget()) |
|
1911 return; |
|
1912 |
|
1913 Widget* widget = toRenderWidget(r)->widget(); |
|
1914 if (widget) |
|
1915 widget->handleEvent(event); |
|
1916 #else |
|
1917 if (renderer() && renderer()->isMedia()) |
|
1918 toRenderMedia(renderer())->forwardEvent(event); |
|
1919 if (event->defaultHandled()) |
|
1920 return; |
|
1921 HTMLElement::defaultEventHandler(event); |
|
1922 #endif |
|
1923 } |
|
1924 |
|
1925 bool HTMLMediaElement::processingUserGesture() const |
|
1926 { |
|
1927 Frame* frame = document()->frame(); |
|
1928 FrameLoader* loader = frame ? frame->loader() : 0; |
|
1929 |
|
1930 // return 'true' for safety if we don't know the answer |
|
1931 return loader ? loader->isProcessingUserGesture() : true; |
|
1932 } |
|
1933 |
|
1934 #if ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
1935 |
|
1936 void HTMLMediaElement::ensureMediaPlayer() |
|
1937 { |
|
1938 if (!m_player) |
|
1939 m_player = MediaPlayer::create(this); |
|
1940 } |
|
1941 |
|
1942 void HTMLMediaElement::deliverNotification(MediaPlayerProxyNotificationType notification) |
|
1943 { |
|
1944 if (notification == MediaPlayerNotificationPlayPauseButtonPressed) { |
|
1945 togglePlayState(); |
|
1946 return; |
|
1947 } |
|
1948 |
|
1949 if (m_player) |
|
1950 m_player->deliverNotification(notification); |
|
1951 } |
|
1952 |
|
1953 void HTMLMediaElement::setMediaPlayerProxy(WebMediaPlayerProxy* proxy) |
|
1954 { |
|
1955 ensureMediaPlayer(); |
|
1956 m_player->setMediaPlayerProxy(proxy); |
|
1957 } |
|
1958 |
|
1959 void HTMLMediaElement::getPluginProxyParams(KURL& url, Vector<String>& names, Vector<String>& values) |
|
1960 { |
|
1961 Frame* frame = document()->frame(); |
|
1962 FrameLoader* loader = frame ? frame->loader() : 0; |
|
1963 |
|
1964 if (isVideo()) { |
|
1965 String poster = poster(); |
|
1966 if (!poster.isEmpty() && loader) { |
|
1967 KURL posterURL = loader->completeURL(poster); |
|
1968 if (posterURL.isValid() && loader->willLoadMediaElementURL(posterURL)) { |
|
1969 names.append("_media_element_poster_"); |
|
1970 values.append(posterURL.string()); |
|
1971 } |
|
1972 } |
|
1973 } |
|
1974 |
|
1975 if (controls()) { |
|
1976 names.append("_media_element_controls_"); |
|
1977 values.append("true"); |
|
1978 } |
|
1979 |
|
1980 url = src(); |
|
1981 if (!isSafeToLoadURL(url, Complain)) |
|
1982 url = selectNextSourceChild(0, DoNothing); |
|
1983 |
|
1984 m_currentSrc = url.string(); |
|
1985 if (url.isValid() && loader && loader->willLoadMediaElementURL(url)) { |
|
1986 names.append("_media_element_src_"); |
|
1987 values.append(m_currentSrc); |
|
1988 } |
|
1989 } |
|
1990 |
|
1991 void HTMLMediaElement::finishParsingChildren() |
|
1992 { |
|
1993 HTMLElement::finishParsingChildren(); |
|
1994 document()->updateStyleIfNeeded(); |
|
1995 createMediaPlayerProxy(); |
|
1996 } |
|
1997 |
|
1998 void HTMLMediaElement::createMediaPlayerProxy() |
|
1999 { |
|
2000 ensureMediaPlayer(); |
|
2001 |
|
2002 if (m_proxyWidget || (inDocument() && !m_needWidgetUpdate)) |
|
2003 return; |
|
2004 |
|
2005 Frame* frame = document()->frame(); |
|
2006 FrameLoader* loader = frame ? frame->loader() : 0; |
|
2007 if (!loader) |
|
2008 return; |
|
2009 |
|
2010 KURL url; |
|
2011 Vector<String> paramNames; |
|
2012 Vector<String> paramValues; |
|
2013 |
|
2014 getPluginProxyParams(url, paramNames, paramValues); |
|
2015 |
|
2016 // Hang onto the proxy widget so it won't be destroyed if the plug-in is set to |
|
2017 // display:none |
|
2018 m_proxyWidget = loader->subframeLoader()->loadMediaPlayerProxyPlugin(this, url, paramNames, paramValues); |
|
2019 if (m_proxyWidget) |
|
2020 m_needWidgetUpdate = false; |
|
2021 } |
|
2022 #endif // ENABLE(PLUGIN_PROXY_FOR_VIDEO) |
|
2023 |
|
2024 void HTMLMediaElement::enterFullscreen() |
|
2025 { |
|
2026 ASSERT(!m_isFullscreen); |
|
2027 m_isFullscreen = true; |
|
2028 if (document() && document()->page()) { |
|
2029 document()->page()->chrome()->client()->enterFullscreenForNode(this); |
|
2030 scheduleEvent(eventNames().webkitbeginfullscreenEvent); |
|
2031 } |
|
2032 } |
|
2033 |
|
2034 void HTMLMediaElement::exitFullscreen() |
|
2035 { |
|
2036 ASSERT(m_isFullscreen); |
|
2037 m_isFullscreen = false; |
|
2038 if (document() && document()->page()) { |
|
2039 document()->page()->chrome()->client()->exitFullscreenForNode(this); |
|
2040 scheduleEvent(eventNames().webkitendfullscreenEvent); |
|
2041 } |
|
2042 } |
|
2043 |
|
2044 PlatformMedia HTMLMediaElement::platformMedia() const |
|
2045 { |
|
2046 return m_player ? m_player->platformMedia() : NoPlatformMedia; |
|
2047 } |
|
2048 |
|
2049 #if USE(ACCELERATED_COMPOSITING) |
|
2050 PlatformLayer* HTMLMediaElement::platformLayer() const |
|
2051 { |
|
2052 return m_player ? m_player->platformLayer() : 0; |
|
2053 } |
|
2054 #endif |
|
2055 |
|
2056 bool HTMLMediaElement::hasClosedCaptions() const |
|
2057 { |
|
2058 return m_player && m_player->hasClosedCaptions(); |
|
2059 } |
|
2060 |
|
2061 bool HTMLMediaElement::closedCaptionsVisible() const |
|
2062 { |
|
2063 return m_closedCaptionsVisible; |
|
2064 } |
|
2065 |
|
2066 void HTMLMediaElement::setClosedCaptionsVisible(bool closedCaptionVisible) |
|
2067 { |
|
2068 if (!m_player ||!hasClosedCaptions()) |
|
2069 return; |
|
2070 |
|
2071 m_closedCaptionsVisible = closedCaptionVisible; |
|
2072 m_player->setClosedCaptionsVisible(closedCaptionVisible); |
|
2073 if (renderer()) |
|
2074 renderer()->updateFromElement(); |
|
2075 } |
|
2076 |
|
2077 void HTMLMediaElement::setWebkitClosedCaptionsVisible(bool visible) |
|
2078 { |
|
2079 setClosedCaptionsVisible(visible); |
|
2080 } |
|
2081 |
|
2082 bool HTMLMediaElement::webkitClosedCaptionsVisible() const |
|
2083 { |
|
2084 return closedCaptionsVisible(); |
|
2085 } |
|
2086 |
|
2087 |
|
2088 bool HTMLMediaElement::webkitHasClosedCaptions() const |
|
2089 { |
|
2090 return hasClosedCaptions(); |
|
2091 } |
|
2092 |
|
2093 void HTMLMediaElement::mediaCanStart() |
|
2094 { |
|
2095 ASSERT(m_isWaitingUntilMediaCanStart); |
|
2096 m_isWaitingUntilMediaCanStart = false; |
|
2097 loadInternal(); |
|
2098 } |
|
2099 |
|
2100 bool HTMLMediaElement::isURLAttribute(Attribute* attribute) const |
|
2101 { |
|
2102 return attribute->name() == srcAttr; |
|
2103 } |
|
2104 |
|
2105 } |
|
2106 |
|
2107 #endif |