2008-04-17 Marco Barisione <marco.barisione@collabora.co.uk>
[WebKit-https.git] / WebCore / platform / graphics / win / QTMovieWin.cpp
1 /*
2  * Copyright (C) 2007 Apple Computer, 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 #include "config.h"
26
27 #include <windows.h>
28
29 #include "QTMovieWin.h"
30
31 // Put Movies.h first so build failures here point clearly to QuickTime
32 #include <Movies.h>
33 #include <GXMath.h>
34 #include <QTML.h>
35
36 #include "QTMovieWinTimer.h"
37
38 #include <wtf/Assertions.h>
39 #include <wtf/HashSet.h>
40 #include <wtf/Noncopyable.h>
41 #include <wtf/Vector.h>
42
43 using namespace std;
44
45 static const long minimumQuickTimeVersion = 0x07300000; // 7.3
46
47 // Resizing GWorlds is slow, give them a minimum size so size of small 
48 // videos can be animated smoothly
49 static const int cGWorldMinWidth = 640;
50 static const int cGWorldMinHeight = 360;
51
52 static const float cNonContinuousTimeChange = 0.2f;
53
54 union UppParam {
55     long longValue;
56     void* ptr;
57 };
58
59 static MovieDrawingCompleteUPP gMovieDrawingCompleteUPP = 0;
60 static HashSet<QTMovieWinPrivate*>* gTaskList;
61 static Vector<CFStringRef>* gSupportedTypes = 0;
62
63 static void updateTaskTimer(int maxInterval = 1000)
64 {
65     if (!gTaskList->size()) {
66         stopSharedTimer();
67         return;    
68     }
69     
70     long intervalInMS;
71     QTGetTimeUntilNextTask(&intervalInMS, 1000);
72     if (intervalInMS > maxInterval)
73         intervalInMS = maxInterval;
74     setSharedTimerFireDelay(static_cast<float>(intervalInMS) / 1000);
75 }
76
77 class QTMovieWinPrivate : Noncopyable {
78 public:
79     QTMovieWinPrivate();
80     ~QTMovieWinPrivate();
81     void task();
82     void startTask();
83     void endTask();
84
85     void registerDrawingCallback();
86     void drawingComplete();
87     void updateGWorld();
88     void createGWorld();
89     void deleteGWorld();
90
91     void setSize(int, int);
92
93     QTMovieWin* m_movieWin;
94     Movie m_movie;
95     bool m_tasking;
96     QTMovieWinClient* m_client;
97     long m_loadState;
98     bool m_ended;
99     bool m_seeking;
100     float m_lastMediaTime;
101     double m_lastLoadStateCheckTime;
102     int m_width;
103     int m_height;
104     bool m_visible;
105     GWorldPtr m_gWorld;
106     int m_gWorldWidth;
107     int m_gWorldHeight;
108     GWorldPtr m_savedGWorld;
109     long m_loadError;
110 };
111
112 QTMovieWinPrivate::QTMovieWinPrivate()
113     : m_movieWin(0)
114     , m_movie(0)
115     , m_tasking(false)
116     , m_client(0)
117     , m_loadState(0)
118     , m_ended(false)
119     , m_seeking(false)
120     , m_lastMediaTime(0)
121     , m_lastLoadStateCheckTime(0)
122     , m_width(0)
123     , m_height(0)
124     , m_visible(false)
125     , m_gWorld(0)
126     , m_gWorldWidth(0)
127     , m_gWorldHeight(0)
128     , m_savedGWorld(0)
129     , m_loadError(0)
130 {
131 }
132
133 QTMovieWinPrivate::~QTMovieWinPrivate()
134 {
135     endTask();
136     if (m_gWorld)
137         deleteGWorld();
138     if (m_movie)
139         DisposeMovie(m_movie);
140 }
141
142 static void taskTimerFired()
143 {
144     // The hash content might change during task()
145     Vector<QTMovieWinPrivate*> tasks;
146     copyToVector(*gTaskList, tasks);
147     size_t count = tasks.size();
148     for (unsigned n = 0; n < count; ++n)
149         tasks[n]->task();
150
151     updateTaskTimer();
152 }
153
154 void QTMovieWinPrivate::startTask() 
155 {
156     if (m_tasking)
157         return;
158     if (!gTaskList)
159         gTaskList = new HashSet<QTMovieWinPrivate*>;
160     gTaskList->add(this);
161     m_tasking = true;
162     updateTaskTimer();
163 }
164
165 void QTMovieWinPrivate::endTask() 
166 {
167     if (!m_tasking)
168         return;
169     gTaskList->remove(this);
170     m_tasking = false;
171     updateTaskTimer();
172 }
173
174 void QTMovieWinPrivate::task() 
175 {
176     ASSERT(m_tasking);
177
178     if (!m_loadError)
179         MoviesTask(m_movie, 0);
180
181     // GetMovieLoadState documentation says that you should not call it more often than every quarter of a second.
182     if (systemTime() >= m_lastLoadStateCheckTime + 0.25 || m_loadError) { 
183         // If load fails QT's load state is kMovieLoadStateComplete.
184         // This is different from QTKit API and seems strange.
185         long loadState = m_loadError ? kMovieLoadStateError : GetMovieLoadState(m_movie);
186         if (loadState != m_loadState) {
187             m_loadState = loadState;
188             m_client->movieLoadStateChanged(m_movieWin);
189         }
190         m_lastLoadStateCheckTime = systemTime();
191     }
192
193     bool ended = !!IsMovieDone(m_movie);
194     if (ended != m_ended) {
195         m_ended = ended;
196         if (m_client && ended)
197             m_client->movieEnded(m_movieWin);
198     }
199
200     float time = m_movieWin->currentTime();
201     if (time < m_lastMediaTime || time >= m_lastMediaTime + cNonContinuousTimeChange || m_seeking) {
202         m_seeking = false;
203         if (m_client)
204             m_client->movieTimeChanged(m_movieWin);
205     }
206     m_lastMediaTime = time;
207
208     if (m_loadError)
209         endTask();
210 }
211
212 void QTMovieWinPrivate::registerDrawingCallback()
213 {
214     UppParam param;
215     param.ptr = this;
216     SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, gMovieDrawingCompleteUPP, param.longValue);
217 }
218
219 void QTMovieWinPrivate::drawingComplete()
220 {
221     if (!m_gWorld)
222         return;
223     m_client->movieNewImageAvailable(m_movieWin);
224 }
225
226 void QTMovieWinPrivate::updateGWorld()
227 {
228     bool shouldBeVisible = m_visible;
229     if (!m_height || !m_width)
230         shouldBeVisible = false;
231
232     if (shouldBeVisible && !m_gWorld)
233         createGWorld();
234     else if (!shouldBeVisible && m_gWorld)
235         deleteGWorld();
236     else if (m_gWorld && (m_width > m_gWorldWidth || m_height > m_gWorldHeight)) {
237         // need a bigger, better gWorld
238         deleteGWorld();
239         createGWorld();
240     }
241 }
242
243 void QTMovieWinPrivate::createGWorld()
244 {
245     ASSERT(!m_gWorld);
246     if (!m_movie)
247         return;
248
249     m_gWorldWidth = max(cGWorldMinWidth, m_width);
250     m_gWorldHeight = max(cGWorldMinHeight, m_height);
251     Rect bounds; 
252     bounds.top = 0;
253     bounds.left = 0; 
254     bounds.right = m_gWorldWidth;
255     bounds.bottom = m_gWorldHeight;
256     OSErr err = QTNewGWorld(&m_gWorld, k32BGRAPixelFormat, &bounds, NULL, NULL, NULL); 
257     if (err) 
258         return;
259     GetMovieGWorld(m_movie, &m_savedGWorld, 0);
260     SetMovieGWorld(m_movie, m_gWorld, 0);
261     bounds.right = m_width;
262     bounds.bottom = m_height;
263     SetMovieBox(m_movie, &bounds);
264 }
265
266 void QTMovieWinPrivate::setSize(int width, int height)
267 {
268     if (m_width == width && m_height == height)
269         return;
270     m_width = width;
271     m_height = height;
272     if (!m_movie)
273         return;
274     Rect bounds; 
275     bounds.top = 0;
276     bounds.left = 0; 
277     bounds.right = width;
278     bounds.bottom = height;
279     SetMovieBox(m_movie, &bounds);
280     updateGWorld();
281 }
282
283 void QTMovieWinPrivate::deleteGWorld()
284 {
285     ASSERT(m_gWorld);
286     if (m_movie)
287         SetMovieGWorld(m_movie, m_savedGWorld, 0);
288     m_savedGWorld = 0;
289     DisposeGWorld(m_gWorld); 
290     m_gWorld = 0;
291     m_gWorldWidth = 0;
292     m_gWorldHeight = 0;
293 }
294
295
296 QTMovieWin::QTMovieWin(QTMovieWinClient* client)
297     : m_private(new QTMovieWinPrivate())
298 {
299     m_private->m_movieWin = this;
300     m_private->m_client = client;
301     initializeQuickTime();
302 }
303
304 QTMovieWin::~QTMovieWin()
305 {
306     delete m_private;
307 }
308
309 void QTMovieWin::play()
310 {
311     StartMovie(m_private->m_movie);
312     m_private->startTask();
313 }
314
315 void QTMovieWin::pause()
316 {
317     StopMovie(m_private->m_movie);
318     updateTaskTimer();
319 }
320
321 float QTMovieWin::rate() const
322 {
323     return FixedToFloat(GetMovieRate(m_private->m_movie));
324 }
325
326 void QTMovieWin::setRate(float rate)
327 {
328     SetMovieRate(m_private->m_movie, FloatToFixed(rate));
329     updateTaskTimer();
330 }
331
332 float QTMovieWin::duration() const
333 {
334     if (!m_private->m_movie)
335         return 0;
336     TimeValue val = GetMovieDuration(m_private->m_movie);
337     TimeScale scale = GetMovieTimeScale(m_private->m_movie);
338     return static_cast<float>(val) / scale;
339 }
340
341 float QTMovieWin::currentTime() const
342 {
343     if (!m_private->m_movie)
344         return 0;
345     TimeValue val = GetMovieTime(m_private->m_movie, 0);
346     TimeScale scale = GetMovieTimeScale(m_private->m_movie);
347     return static_cast<float>(val) / scale;
348 }
349
350 void QTMovieWin::setCurrentTime(float time) const
351 {
352     if (!m_private->m_movie)
353         return;
354     m_private->m_seeking = true;
355     TimeScale scale = GetMovieTimeScale(m_private->m_movie);
356     SetMovieTimeValue(m_private->m_movie, TimeValue(time * scale));
357     updateTaskTimer();
358 }
359
360 void QTMovieWin::setVolume(float volume)
361 {
362     SetMovieVolume(m_private->m_movie, static_cast<short>(volume * 256));
363 }
364
365 unsigned QTMovieWin::dataSize() const
366 {
367     // FIXME: How to get this?
368     return 1000;
369 }
370
371 float QTMovieWin::maxTimeLoaded() const
372 {
373     if (!m_private->m_movie)
374         return 0;
375     TimeValue val;
376     GetMaxLoadedTimeInMovie(m_private->m_movie, &val);
377     TimeScale scale = GetMovieTimeScale(m_private->m_movie);
378     return static_cast<float>(val) / scale;
379 }
380
381 long QTMovieWin::loadState() const
382 {
383     return m_private->m_loadState;
384 }
385
386 void QTMovieWin::getNaturalSize(int& width, int& height)
387 {
388     Rect rect;
389     GetMovieNaturalBoundsRect(m_private->m_movie, &rect);
390     width = rect.right;
391     height = rect.bottom;
392 }
393
394 void QTMovieWin::setSize(int width, int height)
395 {
396     m_private->setSize(width, height);
397     updateTaskTimer(0);
398 }
399
400 void QTMovieWin::setVisible(bool b)
401 {
402     m_private->m_visible = b;
403     m_private->updateGWorld();
404 }
405
406 void QTMovieWin::paint(HDC hdc, int x, int y)
407 {
408     if (!m_private->m_gWorld)
409         return;
410
411     HDC hdcSrc = static_cast<HDC>(GetPortHDC(reinterpret_cast<GrafPtr>(m_private->m_gWorld))); 
412     if (!hdcSrc)
413         return;
414
415     // FIXME: If we could determine the movie has no alpha, we could use BitBlt for those cases, which might be faster.
416     BLENDFUNCTION blendFunction; 
417     blendFunction.BlendOp = AC_SRC_OVER;
418     blendFunction.BlendFlags = 0;
419     blendFunction.SourceConstantAlpha = 255;
420     blendFunction.AlphaFormat = AC_SRC_ALPHA;
421     AlphaBlend(hdc, x, y, m_private->m_width, m_private->m_height, hdcSrc, 
422          0, 0, m_private->m_width, m_private->m_height, blendFunction);
423 }
424
425 void QTMovieWin::load(const UChar* url, int len)
426 {
427     if (m_private->m_movie) {
428         m_private->endTask();
429         if (m_private->m_gWorld)
430             m_private->deleteGWorld();
431         DisposeMovie(m_private->m_movie);
432         m_private->m_movie = 0;
433     }  
434
435     // Define a property array for NewMovieFromProperties. 8 should be enough for our needs. 
436     QTNewMoviePropertyElement movieProps[8]; 
437     ItemCount moviePropCount = 0; 
438
439     bool boolTrue = true;
440
441     // Create a URL data reference of type CFURL 
442     CFStringRef urlStringRef = CFStringCreateWithCharacters(kCFAllocatorDefault, reinterpret_cast<const UniChar*>(url), len);
443     
444     // Disable streaming support for now. 
445     if (CFStringHasPrefix(urlStringRef, CFSTR("rtsp:"))) {
446         m_private->m_loadError = noMovieFound;
447         goto end;
448     }
449
450     CFURLRef urlRef = CFURLCreateWithString(kCFAllocatorDefault, urlStringRef, 0); 
451
452     // Add the movie data location to the property array 
453     movieProps[moviePropCount].propClass = kQTPropertyClass_DataLocation; 
454     movieProps[moviePropCount].propID = kQTDataLocationPropertyID_CFURL; 
455     movieProps[moviePropCount].propValueSize = sizeof(urlRef); 
456     movieProps[moviePropCount].propValueAddress = &urlRef; 
457     movieProps[moviePropCount].propStatus = 0; 
458     moviePropCount++; 
459
460     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 
461     movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_DontAskUnresolvedDataRefs; 
462     movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 
463     movieProps[moviePropCount].propValueAddress = &boolTrue; 
464     movieProps[moviePropCount].propStatus = 0; 
465     moviePropCount++; 
466
467     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 
468     movieProps[moviePropCount].propID = kQTMovieInstantiationPropertyID_AsyncOK; 
469     movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 
470     movieProps[moviePropCount].propValueAddress = &boolTrue; 
471     movieProps[moviePropCount].propStatus = 0; 
472     moviePropCount++; 
473
474     movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty; 
475     movieProps[moviePropCount].propID = kQTNewMoviePropertyID_Active; 
476     movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 
477     movieProps[moviePropCount].propValueAddress = &boolTrue; 
478     movieProps[moviePropCount].propStatus = 0; 
479     moviePropCount++; 
480
481     movieProps[moviePropCount].propClass = kQTPropertyClass_NewMovieProperty; 
482     movieProps[moviePropCount].propID = kQTNewMoviePropertyID_DontInteractWithUser; 
483     movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 
484     movieProps[moviePropCount].propValueAddress = &boolTrue; 
485     movieProps[moviePropCount].propStatus = 0; 
486     moviePropCount++; 
487
488     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation;
489     movieProps[moviePropCount].propID = '!url';
490     movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 
491     movieProps[moviePropCount].propValueAddress = &boolTrue; 
492     movieProps[moviePropCount].propStatus = 0; 
493     moviePropCount++; 
494
495     movieProps[moviePropCount].propClass = kQTPropertyClass_MovieInstantiation; 
496     movieProps[moviePropCount].propID = 'site';
497     movieProps[moviePropCount].propValueSize = sizeof(boolTrue); 
498     movieProps[moviePropCount].propValueAddress = &boolTrue; 
499     movieProps[moviePropCount].propStatus = 0; 
500     moviePropCount++; 
501
502     m_private->m_loadError = NewMovieFromProperties(moviePropCount, movieProps, 0, NULL, &m_private->m_movie);
503
504     CFRelease(urlRef);
505 end:
506     m_private->startTask();
507     // get the load fail callback quickly 
508     if (m_private->m_loadError)
509         updateTaskTimer(0);
510     else
511         m_private->registerDrawingCallback();
512
513     CFRelease(urlStringRef);
514 }
515
516 void QTMovieWin::disableUnsupportedTracks(unsigned& enabledTrackCount)
517 {
518     if (!m_private->m_movie) {
519         enabledTrackCount = 0;
520         return;
521     }
522
523     static HashSet<OSType>* allowedTrackTypes = 0;
524     if (!allowedTrackTypes) {
525         allowedTrackTypes = new HashSet<OSType>;
526         allowedTrackTypes->add(VideoMediaType);
527         allowedTrackTypes->add(SoundMediaType);
528         allowedTrackTypes->add(TextMediaType);
529         allowedTrackTypes->add(BaseMediaType);
530         allowedTrackTypes->add('clcp'); // Closed caption
531         allowedTrackTypes->add('sbtl'); // Subtitle
532     }
533
534     long trackCount = GetMovieTrackCount(m_private->m_movie);
535     enabledTrackCount = trackCount;
536     
537     // Track indexes are 1-based. yuck. These things must descend from old-
538     // school mac resources or something.
539     for (long trackIndex = 1; trackIndex <= trackCount; trackIndex++) {
540         // Grab the track at the current index. If there isn't one there, then
541         // we can move onto the next one.
542         Track currentTrack = GetMovieIndTrack(m_private->m_movie, trackIndex);
543         if (!currentTrack)
544             continue;
545         
546         // Check to see if the track is disabled already, we should move along.
547         // We don't need to re-disable it.
548         if (!GetTrackEnabled(currentTrack))
549             continue;
550
551         // Grab the track's media. We're going to check to see if we need to
552         // disable the tracks. They could be unsupported.
553         Media trackMedia = GetTrackMedia(currentTrack);
554         if (!trackMedia)
555             continue;
556         
557         // Grab the media type for this track. Make sure that we don't
558         // get an error in doing so. If we do, then something really funky is
559         // wrong.
560         OSType mediaType;
561         GetMediaHandlerDescription(trackMedia, &mediaType, nil, nil);
562         OSErr mediaErr = GetMoviesError();    
563         if (mediaErr != noErr)
564             continue;
565         
566         if (!allowedTrackTypes->contains(mediaType)) {
567             SetTrackEnabled(currentTrack, false);
568             --enabledTrackCount;
569         }
570         
571         // Grab the track reference count for chapters. This will tell us if it
572         // has chapter tracks in it. If there aren't any references, then we
573         // can move on the next track.
574         long referenceCount = GetTrackReferenceCount(currentTrack, kTrackReferenceChapterList);
575         if (referenceCount <= 0)
576             continue;
577         
578         long referenceIndex = 0;        
579         while (1) {
580             // If we get nothing here, we've overstepped our bounds and can stop
581             // looking. Chapter indices here are 1-based as well - hence, the
582             // pre-increment.
583             referenceIndex++;
584             Track chapterTrack = GetTrackReference(currentTrack, kTrackReferenceChapterList, referenceIndex);
585             if (!chapterTrack)
586                 break;
587             
588             // Try to grab the media for the track.
589             Media chapterMedia = GetTrackMedia(chapterTrack);
590             if (!chapterMedia)
591                 continue;
592         
593             // Grab the media type for this track. Make sure that we don't
594             // get an error in doing so. If we do, then something really
595             // funky is wrong.
596             OSType mediaType;
597             GetMediaHandlerDescription(chapterMedia, &mediaType, nil, nil);
598             OSErr mediaErr = GetMoviesError();
599             if (mediaErr != noErr)
600                 continue;
601             
602             // Check to see if the track is a video track. We don't care about
603             // other non-video tracks.
604             if (mediaType != VideoMediaType)
605                 continue;
606             
607             // Check to see if the track is already disabled. If it is, we
608             // should move along.
609             if (!GetTrackEnabled(chapterTrack))
610                 continue;
611             
612             // Disabled the evil, evil track.
613             SetTrackEnabled(chapterTrack, false);
614             --enabledTrackCount;
615         }
616     }
617 }
618
619 pascal OSErr movieDrawingCompleteProc(Movie movie, long data)
620 {
621     UppParam param;
622     param.longValue = data;
623     QTMovieWinPrivate* mp = static_cast<QTMovieWinPrivate*>(param.ptr);
624     if (mp)
625         mp->drawingComplete();
626     return 0;
627 }
628
629 static void initializeSupportedTypes() 
630 {
631     if (gSupportedTypes)
632         return;
633     // FIXME: This list might not be complete. 
634     // There must be some way to get it out from QuickTime.
635     gSupportedTypes = new Vector<CFStringRef>;
636     gSupportedTypes->append(CFSTR("video/3gpp"));
637     gSupportedTypes->append(CFSTR("video/3gpp2"));
638     gSupportedTypes->append(CFSTR("video/mp4"));
639     gSupportedTypes->append(CFSTR("video/mpeg"));
640     gSupportedTypes->append(CFSTR("video/quicktime"));
641     gSupportedTypes->append(CFSTR("audio/ac3"));
642     gSupportedTypes->append(CFSTR("audio/aiff"));
643     gSupportedTypes->append(CFSTR("audio/basic"));
644     gSupportedTypes->append(CFSTR("audio/mpeg"));
645 }
646
647 unsigned QTMovieWin::countSupportedTypes()
648 {
649     initializeSupportedTypes();
650     return static_cast<unsigned>(gSupportedTypes->size());
651 }
652
653 void QTMovieWin::getSupportedType(unsigned index, const UChar*& str, unsigned& len)
654 {
655     initializeSupportedTypes();
656     ASSERT(index < gSupportedTypes->size());
657
658     // Allocate sufficient buffer to hold any MIME type
659     static UniChar* staticBuffer = 0;
660     if (!staticBuffer)
661         staticBuffer = new UniChar[32];
662
663     CFStringRef cfstr = gSupportedTypes->at(index);
664     len = CFStringGetLength(cfstr);
665     CFRange range = { 0, len };
666     CFStringGetCharacters(cfstr, range, staticBuffer);
667     str = reinterpret_cast<const UChar*>(staticBuffer);
668     
669 }
670
671 bool QTMovieWin::initializeQuickTime() 
672 {
673     static bool initialized = false;
674     static bool initializationSucceeded = false;
675     if (!initialized) {
676         initialized = true;
677         // Initialize and check QuickTime version
678         OSErr result = InitializeQTML(0);
679         SInt32 version = 0;
680         if (result == noErr)
681             result = Gestalt(gestaltQuickTime, &version);
682         if (result != noErr) {
683             LOG_ERROR("No QuickTime available. Disabling <video> and <audio> support.");
684             return false;
685         }
686         if (version < minimumQuickTimeVersion) {
687             LOG_ERROR("QuickTime version %x detected, at least %x required. Disabling <video> and <audio> support.", version, minimumQuickTimeVersion);
688             return false;
689         }
690         EnterMovies();
691         setSharedTimerFiredFunction(taskTimerFired);
692         gMovieDrawingCompleteUPP = NewMovieDrawingCompleteUPP(movieDrawingCompleteProc);
693         initializationSucceeded = true;
694     }
695     return initializationSucceeded;
696 }
697
698 BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
699 {
700     switch (fdwReason) {
701         case DLL_PROCESS_ATTACH:
702             setSharedTimerInstanceHandle(hinstDLL);
703             return TRUE;
704         case DLL_PROCESS_DETACH:
705         case DLL_THREAD_ATTACH:
706         case DLL_THREAD_DETACH:
707             return FALSE;
708     }
709     ASSERT_NOT_REACHED();
710     return FALSE;
711 }