e691740f1484bb3418097b51349b6fc0d810215b
[WebKit-https.git] / WebCore / html / HTMLMediaElement.cpp
1 /*
2  * Copyright (C) 2007 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 "csshelper.h"
32 #include "CSSPropertyNames.h"
33 #include "CSSValueKeywords.h"
34 #include "EventNames.h"
35 #include "ExceptionCode.h"
36 #include "HTMLDocument.h"
37 #include "HTMLNames.h"
38 #include "HTMLSourceElement.h"
39 #include "HTMLVideoElement.h"
40 #include <limits>
41 #include "MediaError.h"
42 #include "MediaList.h"
43 #include "MediaQueryEvaluator.h"
44 #include "MIMETypeRegistry.h"
45 #include "Movie.h"
46 #include "RenderVideo.h"
47 #include "SystemTime.h"
48 #include "TimeRanges.h"
49 #include "VoidCallback.h"
50
51 using namespace std;
52
53 namespace WebCore {
54
55 using namespace EventNames;
56 using namespace HTMLNames;
57
58 HTMLMediaElement::HTMLMediaElement(const QualifiedName& tagName, Document* doc)
59     : HTMLElement(tagName, doc)
60     , m_loadTimer(this, &HTMLMediaElement::loadTimerFired)
61     , m_asyncEventTimer(this, &HTMLMediaElement::asyncEventTimerFired)
62     , m_progressEventTimer(this, &HTMLMediaElement::progressEventTimerFired)
63     , m_defaultPlaybackRate(1.0f)
64     , m_networkState(EMPTY)
65     , m_readyState(DATA_UNAVAILABLE)
66     , m_begun(false)
67     , m_loadedFirstFrame(false)
68     , m_autoplaying(true)
69     , m_wasPlayingBeforeMovingToPageCache(false)
70     , m_currentLoop(0)
71     , m_volume(0.5f)
72     , m_muted(false)
73     , m_previousProgress(0)
74     , m_previousProgressTime(numeric_limits<double>::max())
75     , m_sentStalledEvent(false)
76     , m_bufferingRate(0)
77     , m_loadNestingLevel(0)
78     , m_terminateLoadBelowNestingLevel(0)
79     , m_movie(0)
80 {
81     document()->registerForCacheCallbacks(this);
82 }
83
84 HTMLMediaElement::~HTMLMediaElement()
85 {
86     document()->unregisterForCacheCallbacks(this);
87     delete m_movie;
88     for (HashMap<float, CallbackVector*>::iterator it = m_cuePoints.begin(); it != m_cuePoints.end(); ++it)
89         delete it->second;
90 }
91
92 bool HTMLMediaElement::checkDTD(const Node* newChild)
93 {
94     return newChild->hasTagName(sourceTag) || HTMLElement::checkDTD(newChild);
95 }
96
97 void HTMLMediaElement::insertedIntoDocument()
98 {
99     HTMLElement::insertedIntoDocument();
100     if (!src().isEmpty())
101         scheduleLoad();
102 }
103
104 void HTMLMediaElement::removedFromDocument()
105 {
106     delete m_movie;
107     m_movie = 0;
108     HTMLElement::removedFromDocument();
109 }
110
111 void HTMLMediaElement::scheduleLoad()
112 {
113     m_loadTimer.startOneShot(0);
114 }
115
116 void HTMLMediaElement::initAndDispatchProgressEvent(const AtomicString& eventName)
117 {
118     bool totalKnown = m_movie && m_movie->totalBytesKnown();
119     unsigned loaded = m_movie ? m_movie->bytesLoaded() : 0;
120     unsigned total = m_movie ? m_movie->totalBytes() : 0;
121     dispatchProgressEvent(eventName, totalKnown, loaded, total);
122 }
123
124 void HTMLMediaElement::dispatchEventAsync(const AtomicString& eventName)
125 {
126     m_asyncEventsToDispatch.add(eventName);
127     if (!m_asyncEventTimer.isActive())                            
128         m_asyncEventTimer.startOneShot(0);
129 }
130
131 void HTMLMediaElement::loadTimerFired(Timer<HTMLMediaElement>*)
132 {
133     ExceptionCode ec;
134     load(ec);
135 }
136
137 void HTMLMediaElement::asyncEventTimerFired(Timer<HTMLMediaElement>*)
138 {
139     HashSet<String>::const_iterator end = m_asyncEventsToDispatch.end();
140     for (HashSet<String>::const_iterator it = m_asyncEventsToDispatch.begin(); it != end; ++it)
141         dispatchHTMLEvent(*it, false, true);
142     m_asyncEventsToDispatch.clear();
143 }
144
145 String serializeTimeOffset(float time)
146 {
147     String timeString = String::number(time);
148     // FIXME serialize time offset values properly (format not specified yet)
149     timeString.append("s");
150     return timeString;
151 }
152
153 float parseTimeOffset(String timeString, bool* ok = 0)
154 {
155     if (timeString.endsWith("s"))
156         timeString = timeString.left(timeString.length() - 1);
157     // FIXME parse time offset values (format not specified yet)
158     float val = (float)timeString.toDouble(ok);
159     return val;
160 }
161
162 float HTMLMediaElement::getTimeOffsetAttribute(const QualifiedName& name, float valueOnError) const
163 {
164     bool ok;
165     String timeString = getAttribute(name);
166     float result = parseTimeOffset(timeString, &ok);
167     if (ok)
168         return result;
169     return valueOnError;
170 }
171
172 void HTMLMediaElement::setTimeOffsetAttribute(const QualifiedName& name, float value)
173 {
174     setAttribute(name, serializeTimeOffset(value));
175 }
176
177 PassRefPtr<MediaError> HTMLMediaElement::error() const 
178 {
179     return m_error;
180 }
181
182 String HTMLMediaElement::src() const
183 {
184     return document()->completeURL(getAttribute(srcAttr));
185 }
186
187 void HTMLMediaElement::HTMLMediaElement::setSrc(const String& url)
188 {
189     setAttribute(srcAttr, url);
190 }
191
192 String HTMLMediaElement::currentSrc() const
193 {
194     return m_currentSrc;
195 }
196
197 HTMLMediaElement::NetworkState HTMLMediaElement::networkState() const
198 {
199     return m_networkState;
200 }
201
202 float HTMLMediaElement::bufferingRate()
203 {
204     if (!m_movie)
205         return 0;
206     return m_bufferingRate;
207     //return m_movie->dataRate();
208 }
209
210 void HTMLMediaElement::load(ExceptionCode& ec)
211 {
212     String mediaSrc;
213     
214     // 3.14.9.4. Loading the media resource
215     // 1
216     // if an event generated during load() ends up re-entering load(), terminate previous instances
217     m_loadNestingLevel++;
218     m_terminateLoadBelowNestingLevel = m_loadNestingLevel;
219     
220     m_progressEventTimer.stop();
221     m_sentStalledEvent = false;
222     m_bufferingRate = 0;
223     
224     m_loadTimer.stop();
225     
226     // 2
227     if (m_begun) {
228         m_begun = false;
229         m_error = new MediaError(MediaError::MEDIA_ERR_ABORTED);
230         initAndDispatchProgressEvent(abortEvent);
231         if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
232             goto end;
233     }
234     
235     // 3
236     m_error = 0;
237     m_loadedFirstFrame = false;
238     m_autoplaying = true;
239     
240     // 4
241     setPlaybackRate(defaultPlaybackRate(), ec);
242     
243     // 5
244     if (networkState() != EMPTY) {
245         m_networkState = EMPTY;
246         m_readyState = DATA_UNAVAILABLE;
247         if (m_movie) {
248             m_movie->pause();
249             m_movie->seek(0);
250         }
251         m_currentLoop = 0;
252         dispatchHTMLEvent(emptiedEvent, false, true);
253         if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
254             goto end;
255     }
256     
257     // 6
258     mediaSrc = pickMedia();
259     if (mediaSrc.isEmpty()) {
260         ec = INVALID_STATE_ERR;
261         goto end;
262     }
263     
264     // 7
265     m_networkState = LOADING;
266     
267     // 8
268     m_currentSrc = mediaSrc;
269     
270     // 9
271     m_begun = true;        
272     dispatchProgressEvent(beginEvent, false, 0, 0); // progress event draft calls this loadstart
273     if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
274         goto end;
275     
276     // 10, 11, 12, 13
277     delete m_movie;
278     m_movie = new Movie(this);
279     m_movie->setVolume(m_volume);
280     m_movie->setMuted(m_muted);
281     for (HashMap<float, CallbackVector*>::iterator it = m_cuePoints.begin(); it != m_cuePoints.end(); ++it)
282         m_movie->addCuePoint(it->first);
283     m_movie->load(m_currentSrc);
284     if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
285         goto end;
286     
287     if (renderer()) {
288         renderer()->updateFromElement();
289         m_movie->setVisible(true);
290     }
291     
292     // 14
293     m_previousProgressTime = WebCore::currentTime();
294     m_previousProgress = 0;
295     if (m_begun)
296         // 350ms is not magic, it is in the spec!
297         m_progressEventTimer.startRepeating(0.350);
298 end:
299     ASSERT(m_loadNestingLevel);
300     m_loadNestingLevel--;
301 }
302
303
304 void HTMLMediaElement::movieNetworkStateChanged(Movie*)
305 {
306     if (!m_begun || m_networkState == EMPTY)
307         return;
308     
309     m_terminateLoadBelowNestingLevel = m_loadNestingLevel;
310
311     Movie::NetworkState state = m_movie->networkState();
312     
313     // 3.14.9.4. Loading the media resource
314     // 14
315     if (state == Movie::LoadFailed) {
316         //delete m_movie;
317         //m_movie = 0;
318         // FIXME better error handling
319         m_error = new MediaError(MediaError::MEDIA_ERR_NETWORK);
320         m_begun = false;
321         m_progressEventTimer.stop();
322         m_bufferingRate = 0;
323         
324         initAndDispatchProgressEvent(errorEvent); 
325         if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
326             return;
327         
328         m_networkState = EMPTY;
329         
330         if (isVideo())
331             static_cast<HTMLVideoElement*>(this)->updatePosterImage();
332
333         dispatchHTMLEvent(emptiedEvent, false, true);
334         return;
335     }
336     
337     if (state >= Movie::Loading && m_networkState < LOADING)
338         m_networkState = LOADING;
339     
340     if (state >= Movie::LoadedMetaData && m_networkState < LOADED_METADATA) {
341         m_movie->seek(effectiveStart());
342         m_movie->setEndTime(currentLoop() == loopCount() - 1 ? effectiveEnd() : effectiveLoopEnd());
343         m_networkState = LOADED_METADATA;
344         
345         dispatchHTMLEvent(durationchangeEvent, false, true);
346         if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
347             return;
348         
349         dispatchHTMLEvent(loadedmetadataEvent, false, true);
350         if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
351             return;
352     }
353     
354     if (state >= Movie::LoadedFirstFrame && m_networkState < LOADED_FIRST_FRAME) {
355         m_networkState = LOADED_FIRST_FRAME;
356         
357         setReadyState(CAN_SHOW_CURRENT_FRAME);
358         
359         if (isVideo())
360             static_cast<HTMLVideoElement*>(this)->updatePosterImage();
361         
362         if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
363             return;
364         
365         m_loadedFirstFrame = true;
366         if (renderer()) {
367             ASSERT(!renderer()->isImage());
368             static_cast<RenderVideo*>(renderer())->videoSizeChanged();
369         }
370         
371         dispatchHTMLEvent(loadedfirstframeEvent, false, true);
372         if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
373             return;
374         
375         dispatchHTMLEvent(canshowcurrentframeEvent, false, true);
376         if (m_loadNestingLevel < m_terminateLoadBelowNestingLevel)
377             return;
378     }
379     
380     // 15
381     if (state == Movie::Loaded && m_networkState < LOADED) {
382         m_begun = false;
383         m_networkState = LOADED;
384         m_progressEventTimer.stop();
385         m_bufferingRate = 0;
386         initAndDispatchProgressEvent(loadEvent); 
387     }
388 }
389
390 void HTMLMediaElement::movieReadyStateChanged(Movie*)
391 {
392     Movie::ReadyState state = m_movie->readyState();
393     setReadyState((ReadyState)state);
394 }
395
396 void HTMLMediaElement::setReadyState(ReadyState state)
397 {
398     // 3.14.9.6. The ready states
399     if (m_readyState == state)
400         return;
401     
402     bool wasActivelyPlaying = activelyPlaying();
403     m_readyState = state;
404     
405     if (networkState() == EMPTY)
406         return;
407     
408     if (state == DATA_UNAVAILABLE) {
409         dispatchHTMLEvent(dataunavailableEvent, false, true);
410         if (wasActivelyPlaying) {
411             dispatchHTMLEvent(timeupdateEvent, false, true);
412             dispatchHTMLEvent(waitingEvent, false, true);
413         }
414     } else if (state == CAN_SHOW_CURRENT_FRAME) {
415         if (m_loadedFirstFrame)
416             dispatchHTMLEvent(canshowcurrentframeEvent, false, true);
417         if (wasActivelyPlaying) {
418             dispatchHTMLEvent(timeupdateEvent, false, true);
419             dispatchHTMLEvent(waitingEvent, false, true);
420         }
421     } else if (state == CAN_PLAY) {
422         dispatchHTMLEvent(canplayEvent, false, true);
423     } else if (state == CAN_PLAY_THROUGH) {
424         dispatchHTMLEvent(canplaythroughEvent, false, true);
425         if (m_autoplaying && paused() && autoplay()) {
426             m_movie->play();
427             dispatchHTMLEvent(playEvent, false, true);
428         }
429     }
430 }
431
432 void HTMLMediaElement::progressEventTimerFired(Timer<HTMLMediaElement>*)
433 {
434     ASSERT(m_movie);
435     unsigned progress = m_movie->bytesLoaded();
436     double time = WebCore::currentTime();
437     double timedelta = time - m_previousProgressTime;
438     if (timedelta)
439         m_bufferingRate = (float)(0.8 * m_bufferingRate + 0.2 * ((float)(progress - m_previousProgress)) / timedelta);
440     
441     if (progress == m_previousProgress) {
442         if (timedelta > 3.0 && !m_sentStalledEvent) {
443             m_bufferingRate = 0;
444             initAndDispatchProgressEvent(stalledEvent);
445             m_sentStalledEvent = true;
446         }
447     } else {
448         initAndDispatchProgressEvent(progressEvent);
449         m_previousProgress = progress;
450         m_previousProgressTime = time;
451         m_sentStalledEvent = false;
452     }
453 }
454
455 void HTMLMediaElement::seek(float time, ExceptionCode& ec)
456 {
457     // 3.14.9.8. Seeking
458     // 1
459     if (networkState() < LOADED_METADATA) {
460         ec = INVALID_STATE_ERR;
461         return;
462     }
463     
464     // 2
465     float minTime;
466     if (currentLoop() == 0)
467         minTime = effectiveStart();
468     else
469         minTime = effectiveLoopStart();
470  
471     // 3
472     float maxTime = currentLoop() == loopCount() - 1 ? effectiveEnd() : effectiveLoopEnd();
473     
474     // 4
475     time = min(time, maxTime);
476     
477     // 5
478     time = max(time, minTime);
479     
480     // 6
481     RefPtr<TimeRanges> seekableRanges = seekable();
482     if (!seekableRanges->contain(time)) {
483         ec = INDEX_SIZE_ERR;
484         return;
485     }
486     
487     // 7
488     if (m_movie) {
489         m_movie->seek(time);
490         m_movie->setEndTime(maxTime);
491     }
492     
493     // 8
494     // The seeking DOM attribute is implicitly set to true
495     
496     // 9
497     dispatchHTMLEvent(timeupdateEvent, false, true);
498     
499     // 10
500     // As soon as the user agent has established whether or not the media data for the new playback position is available, 
501     // and, if it is, decoded enough data to play back that position, the seeking DOM attribute must be set to false.
502 }
503
504 HTMLMediaElement::ReadyState HTMLMediaElement::readyState() const
505 {
506     return m_readyState;
507 }
508
509 bool HTMLMediaElement::seeking() const
510 {
511     if (!m_movie)
512         return false;
513     RefPtr<TimeRanges> seekableRanges = seekable();
514     return m_movie->seeking() && seekableRanges->contain(currentTime());
515 }
516
517 // playback state
518 float HTMLMediaElement::currentTime() const
519 {
520     return m_movie ? m_movie->currentTime() : 0;
521 }
522
523 void HTMLMediaElement::setCurrentTime(float time, ExceptionCode& ec)
524 {
525     seek(time, ec);
526 }
527
528 float HTMLMediaElement::duration() const
529 {
530     return m_movie ? m_movie->duration() : 0;
531 }
532
533 bool HTMLMediaElement::paused() const
534 {
535     return m_movie ? m_movie->paused() : true;
536 }
537
538 float HTMLMediaElement::defaultPlaybackRate() const
539 {
540     return m_defaultPlaybackRate;
541 }
542
543 void HTMLMediaElement::setDefaultPlaybackRate(float rate, ExceptionCode& ec)
544 {
545     if (rate == 0.0f) {
546         ec = NOT_SUPPORTED_ERR;
547         return;
548     }
549     if (m_defaultPlaybackRate != rate) {
550         m_defaultPlaybackRate = rate;
551         dispatchEventAsync(ratechangeEvent);
552     }
553 }
554
555 float HTMLMediaElement::playbackRate() const
556 {
557     return m_movie ? m_movie->rate() : 0;
558 }
559
560 void HTMLMediaElement::setPlaybackRate(float rate, ExceptionCode& ec)
561 {
562     if (rate == 0.0f) {
563         ec = NOT_SUPPORTED_ERR;
564         return;
565     }
566     if (m_movie && m_movie->rate() != rate) {
567         m_movie->setRate(rate);
568         dispatchEventAsync(ratechangeEvent);
569     }
570 }
571
572 bool HTMLMediaElement::ended()
573 {
574     return networkState() >= LOADED_METADATA && currentTime() >= effectiveEnd() && currentLoop() == loopCount() - 1;
575 }
576
577 bool HTMLMediaElement::autoplay() const
578 {
579     return hasAttribute(autoplayAttr);
580 }
581
582 void HTMLMediaElement::setAutoplay(bool b)
583 {
584     setBooleanAttribute(autoplayAttr, b);
585 }
586
587 void HTMLMediaElement::play(ExceptionCode& ec)
588 {
589     // 3.14.9.7. Playing the media resource
590     if (!m_movie || networkState() == EMPTY) {
591         load(ec);
592         if (ec)
593             return;
594     }
595     if (endedPlayback()) {
596         m_currentLoop = 0;
597         seek(effectiveStart(), ec);
598         if (ec)
599             return;
600     }
601     setPlaybackRate(defaultPlaybackRate(), ec);
602     if (ec)
603         return;
604     
605     m_autoplaying = false;
606     
607     if (m_movie->paused()) {
608         dispatchHTMLEvent(playEvent, false, true);
609         m_movie->play();
610     }
611 }
612
613 void HTMLMediaElement::pause(ExceptionCode& ec)
614 {
615     // 3.14.9.7. Playing the media resource
616     if (!m_movie || networkState() == EMPTY) {
617         load(ec);
618     }
619
620     m_autoplaying = false;
621
622     if (!m_movie->paused()) {
623         dispatchHTMLEvent(pauseEvent, false, true);
624         m_movie->pause();
625     }
626 }
627
628 unsigned HTMLMediaElement::loopCount() const
629 {
630     String val = getAttribute(loopcountAttr);
631     int count = val.toInt();
632     return max(count, 1); 
633 }
634
635 void HTMLMediaElement::setLoopCount(unsigned count, ExceptionCode& ec)
636 {
637     if (!count) {
638         ec = INDEX_SIZE_ERR;
639         return;
640     }
641     setAttribute(loopcountAttr, String::number(count));
642     checkIfSeekNeeded();
643 }
644
645 float HTMLMediaElement::start() const 
646
647     return getTimeOffsetAttribute(startAttr, 0); 
648 }
649
650 void HTMLMediaElement::setStart(float time) 
651
652     setTimeOffsetAttribute(startAttr, time); 
653     checkIfSeekNeeded();
654 }
655
656 float HTMLMediaElement::end() const 
657
658     return getTimeOffsetAttribute(endAttr, std::numeric_limits<float>::infinity()); 
659 }
660
661 void HTMLMediaElement::setEnd(float time) 
662
663     setTimeOffsetAttribute(endAttr, time); 
664     checkIfSeekNeeded();
665 }
666
667 float HTMLMediaElement::loopStart() const 
668
669     return getTimeOffsetAttribute(loopstartAttr, 0); 
670 }
671
672 void HTMLMediaElement::setLoopStart(float time) 
673 {
674     setTimeOffsetAttribute(loopstartAttr, time); 
675     checkIfSeekNeeded();
676 }
677
678 float HTMLMediaElement::loopEnd() const 
679
680     return getTimeOffsetAttribute(loopendAttr, std::numeric_limits<float>::infinity()); 
681 }
682
683 void HTMLMediaElement::setLoopEnd(float time) 
684
685     setTimeOffsetAttribute(loopendAttr, time); 
686     checkIfSeekNeeded();
687 }
688
689 unsigned HTMLMediaElement::currentLoop() const
690 {
691     return m_currentLoop;
692 }
693
694 void HTMLMediaElement::setCurrentLoop(unsigned currentLoop)
695 {
696     m_currentLoop = currentLoop;
697 }
698
699 bool HTMLMediaElement::controls() const
700 {
701     return hasAttribute(controlsAttr);
702 }
703
704 void HTMLMediaElement::setControls(bool b)
705 {
706     setBooleanAttribute(controlsAttr, b);
707 }
708
709 float HTMLMediaElement::volume() const
710 {
711     return m_volume;
712 }
713
714 void HTMLMediaElement::setVolume(float vol, ExceptionCode& ec)
715 {
716     if (vol < 0.0f || vol > 1.0f) {
717         ec = INDEX_SIZE_ERR;
718         return;
719     }
720     
721     if (m_volume != vol) {
722         m_volume = vol;
723         dispatchEventAsync(volumechangeEvent);
724     
725         if (m_movie)
726             m_movie->setVolume(vol);
727     }
728 }
729
730 bool HTMLMediaElement::muted() const
731 {
732     return m_muted;
733 }
734
735 void HTMLMediaElement::setMuted(bool muted)
736 {
737     if (m_muted != muted) {
738         m_muted = muted;
739         dispatchEventAsync(volumechangeEvent);
740         if (m_movie)
741             m_movie->setMuted(muted);
742     }
743 }
744
745 String HTMLMediaElement::pickMedia()
746 {
747     // 3.14.9.2. Location of the media resource
748     String mediaSrc = getAttribute(srcAttr);
749     if (mediaSrc.isEmpty()) {
750         for (Node* n = firstChild(); n; n = n->nextSibling()) {
751             if (n->hasTagName(sourceTag)) {
752                 HTMLSourceElement* source = static_cast<HTMLSourceElement*>(n);
753                 if (!source->hasAttribute(srcAttr))
754                     continue; 
755                 if (source->hasAttribute(mediaAttr)) {
756                     MediaQueryEvaluator screenEval("screen", document()->page(), renderer() ? renderer()->style() : 0);
757                     RefPtr<MediaList> media = new MediaList((CSSStyleSheet*)0, source->media(), true);
758                     if (!screenEval.eval(media.get()))
759                         continue;
760                 }
761                 if (source->hasAttribute(typeAttr)) {
762                     String type = source->type();
763                     if (!MIMETypeRegistry::isSupportedMovieMIMEType(type))
764                         continue;
765                 }
766                 mediaSrc = source->src();
767                 break;
768             }
769         }
770     }
771     if (!mediaSrc.isEmpty())
772         mediaSrc = document()->completeURL(mediaSrc);
773     return mediaSrc;
774 }
775
776 void HTMLMediaElement::checkIfSeekNeeded()
777 {
778     // 3.14.9.5. Offsets into the media resource
779     // 1
780     if (loopCount() - 1 < m_currentLoop)
781         m_currentLoop = loopCount() - 1;
782     
783     // 2
784     if (networkState() <= LOADING)
785         return;
786     
787     // 3
788     ExceptionCode ec;
789     float time = currentTime();
790     if (!m_currentLoop && time < effectiveStart())
791         seek(effectiveStart(), ec);
792
793     // 4
794     if (m_currentLoop && time < effectiveLoopStart())
795         seek(effectiveLoopStart(), ec);
796         
797     // 5
798     if (m_currentLoop < loopCount() - 1 && time > effectiveLoopEnd()) {
799         seek(effectiveLoopStart(), ec);
800         m_currentLoop++;
801     }
802     
803     // 6
804     if (m_currentLoop == loopCount() - 1 && time > effectiveEnd())
805         seek(effectiveEnd(), ec);
806 }
807
808 void HTMLMediaElement::movieVolumeChanged(Movie*)
809 {
810     if (!m_movie)
811         return;
812     if (m_movie->volume() != m_volume || m_movie->muted() != m_muted) {
813         m_volume = m_movie->volume();
814         m_muted = m_movie->muted();
815         dispatchEventAsync(volumechangeEvent);
816     }
817 }
818
819 void HTMLMediaElement::movieDidEnd(Movie*)
820 {
821     if (m_currentLoop < loopCount() - 1 && currentTime() >= effectiveLoopEnd()) {
822         m_movie->seek(effectiveLoopStart());
823         m_currentLoop++;
824         m_movie->setEndTime(m_currentLoop == loopCount() - 1 ? effectiveEnd() : effectiveLoopEnd());
825         if (m_movie)
826             m_movie->play();
827         dispatchHTMLEvent(timeupdateEvent, false, true);
828     }
829     
830     if (m_currentLoop == loopCount() - 1 && currentTime() >= effectiveEnd()) {
831         dispatchHTMLEvent(timeupdateEvent, false, true);
832         dispatchHTMLEvent(endedEvent, false, true);
833     }
834 }
835
836 void HTMLMediaElement::movieCuePointReached(Movie*, float cueTime)
837 {
838     CallbackVector* callbackVector = m_cuePoints.get(cueTime);
839     if (!callbackVector)
840         return;
841     for (unsigned n = 0; n < callbackVector->size(); n++) {
842         CallbackEntry ce = (*callbackVector)[n];
843         if (ce.m_pause) {
844             ExceptionCode ec;
845             pause(ec);
846             break;
847         }
848     }    
849     
850     dispatchHTMLEvent(timeupdateEvent, false, true);
851     
852     for (unsigned n = 0; n < callbackVector->size(); n++) {
853         CallbackEntry ce = (*callbackVector)[n];
854         if (ce.m_voidCallback) 
855             ce.m_voidCallback->execute(document()->frame());
856     }      
857 }
858
859 void HTMLMediaElement::addCuePoint(float time, VoidCallback* voidCallback, bool pause)
860 {
861     if (time < 0 || !isfinite(time))
862         return;
863     CallbackVector* callbackVector = m_cuePoints.get(time);
864     if (!callbackVector) {
865         callbackVector = new CallbackVector;
866         m_cuePoints.add(time, callbackVector);
867     }
868     callbackVector->append(CallbackEntry(voidCallback, pause));
869     
870     if (m_movie)
871         m_movie->addCuePoint(time);
872 }
873
874 void HTMLMediaElement::removeCuePoint(float time, VoidCallback* callback)
875 {
876     if (time < 0 || !isfinite(time))
877         return;
878     CallbackVector* callbackVector = m_cuePoints.get(time);
879     if (callbackVector) {
880         for (unsigned n = 0; n < callbackVector->size(); n++) {
881             if (*(*callbackVector)[n].m_voidCallback == *callback) {
882                 callbackVector->remove(n);
883                 break;
884             }
885         }
886         if (!callbackVector->size()) {
887             delete callbackVector;
888             m_cuePoints.remove(time);
889             if (m_movie)
890                 m_movie->removeCuePoint(time);
891         }
892     }
893 }
894
895 PassRefPtr<TimeRanges> HTMLMediaElement::buffered() const
896 {
897     // FIXME real ranges support
898     if (!m_movie || !m_movie->maxTimeBuffered())
899         return new TimeRanges;
900     return new TimeRanges(0, m_movie->maxTimeBuffered());
901 }
902
903 PassRefPtr<TimeRanges> HTMLMediaElement::played() const
904 {
905     // FIXME track played
906     return new TimeRanges;
907 }
908
909 PassRefPtr<TimeRanges> HTMLMediaElement::seekable() const
910 {
911     // FIXME real ranges support
912     if (!m_movie || !m_movie->maxTimeSeekable())
913         return new TimeRanges;
914     return new TimeRanges(0, m_movie->maxTimeSeekable());
915 }
916
917 float HTMLMediaElement::effectiveStart() const
918 {
919     if (!m_movie)
920         return 0;
921     return min(start(), m_movie->duration());
922 }
923
924 float HTMLMediaElement::effectiveEnd() const
925 {
926     if (!m_movie)
927         return 0;
928     return min(max(end(), max(start(), loopStart())), m_movie->duration());
929 }
930
931 float HTMLMediaElement::effectiveLoopStart() const
932 {
933     if (!m_movie)
934         return 0;
935     return min(loopStart(), m_movie->duration());
936 }
937
938 float HTMLMediaElement::effectiveLoopEnd() const
939 {
940     if (!m_movie)
941         return 0;
942     return min(max(start(), max(loopStart(), loopEnd())), m_movie->duration());
943 }
944
945 bool HTMLMediaElement::activelyPlaying() const
946 {
947     return !paused() && readyState() >= CAN_PLAY && !endedPlayback(); // && !stoppedDueToErrors() && !pausedForUserInteraction();
948 }
949
950 bool HTMLMediaElement::endedPlayback() const
951 {
952     return networkState() >= LOADED_METADATA && currentTime() >= effectiveEnd() && currentLoop() == loopCount() - 1;
953 }
954
955 void HTMLMediaElement::willSaveToCache()
956 {
957     // 3.14.9.4. Loading the media resource
958     // 14
959     if (m_begun) {
960         if (m_movie)
961             m_movie->cancelLoad();
962         m_error = new MediaError(MediaError::MEDIA_ERR_ABORTED);
963         m_begun = false;
964         initAndDispatchProgressEvent(abortEvent);
965         if (m_networkState >= LOADING) {
966             m_networkState = EMPTY;
967             dispatchHTMLEvent(emptiedEvent, false, true);
968         }
969     }
970     
971     ExceptionCode ec;
972     m_wasPlayingBeforeMovingToPageCache = !paused();
973     if (m_wasPlayingBeforeMovingToPageCache)
974         pause(ec);
975     if (m_movie)
976         m_movie->setVisible(false);
977 }
978
979 void HTMLMediaElement::didRestoreFromCache()
980 {
981     ExceptionCode ec;
982     if (m_wasPlayingBeforeMovingToPageCache)
983         play(ec);
984     if (renderer())
985         m_movie->setVisible(true);
986 }
987
988 }
989
990 #endif