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 #include "config.h" 26 27 #include "QTMovieGWorld.h" 28 29 #include "QTMovieTask.h" 30 #include <GXMath.h> 31 #include <Movies.h> 32 #include <QTML.h> 33 #include <QuickTimeComponents.h> 34 #include <wtf/Assertions.h> 35 #include <wtf/HashSet.h> 36 #include <wtf/Noncopyable.h> 37 #include <wtf/Vector.h> 38 39 using namespace std; 40 41 static const long minimumQuickTimeVersion = 0x07300000; // 7.3 42 43 static LPCWSTR fullscreenQTMovieGWorldPointerProp = L"fullscreenQTMovieGWorldPointer"; 44 45 // Resizing GWorlds is slow, give them a minimum size so size of small 46 // videos can be animated smoothly 47 static const int cGWorldMinWidth = 640; 48 static const int cGWorldMinHeight = 360; 49 50 static const float cNonContinuousTimeChange = 0.2f; 51 52 union UppParam { 53 long longValue; 54 void* ptr; 55 }; 56 57 static MovieDrawingCompleteUPP gMovieDrawingCompleteUPP = 0; 58 static HashSet<QTMovieGWorldPrivate*>* gTaskList; 59 static Vector<CFStringRef>* gSupportedTypes = 0; 60 static SInt32 quickTimeVersion = 0; 61 62 class QTMovieGWorldPrivate : public QTMovieClient { 63 public: 64 QTMovieGWorldPrivate(QTMovieGWorld* movieWin); 65 virtual ~QTMovieGWorldPrivate(); 66 67 void registerDrawingCallback(); 68 void unregisterDrawingCallback(); 69 void drawingComplete(); 70 void updateGWorld(); 71 void createGWorld(); 72 void deleteGWorld(); 73 void clearGWorld(); 74 void updateMovieSize(); 75 76 void setSize(int, int); 77 78 virtual void movieEnded(QTMovie*); 79 virtual void movieLoadStateChanged(QTMovie*); 80 virtual void movieTimeChanged(QTMovie*); 81 82 QTMovieGWorld* m_movieWin; 83 RefPtr<QTMovie> m_qtMovie; 84 Movie m_movie; 85 QTMovieGWorldClient* m_client; 86 long m_loadState; 87 int m_width; 88 int m_height; 89 bool m_visible; 90 GWorldPtr m_gWorld; 91 int m_gWorldWidth; 92 int m_gWorldHeight; 93 GWorldPtr m_savedGWorld; 94 float m_widthScaleFactor; 95 float m_heightScaleFactor; 96 #if !ASSERT_DISABLED 97 bool m_scaleCached; 98 #endif 99 WindowPtr m_fullscreenWindow; 100 GWorldPtr m_fullscreenOrigGWorld; 101 Rect m_fullscreenRect; 102 QTMovieGWorldFullscreenClient* m_fullscreenClient; 103 char* m_fullscreenRestoreState; 104 bool m_disabled; 105 }; 106 107 QTMovieGWorldPrivate::QTMovieGWorldPrivate(QTMovieGWorld* movieWin) 108 : m_movieWin(movieWin) 109 , m_movie(0) 110 , m_client(0) 111 , m_loadState(0) 112 , m_width(0) 113 , m_height(0) 114 , m_visible(false) 115 , m_gWorld(0) 116 , m_gWorldWidth(0) 117 , m_gWorldHeight(0) 118 , m_savedGWorld(0) 119 , m_widthScaleFactor(1) 120 , m_heightScaleFactor(1) 121 #if !ASSERT_DISABLED 122 , m_scaleCached(false) 123 #endif 124 , m_fullscreenWindow(0) 125 , m_fullscreenOrigGWorld(0) 126 , m_fullscreenClient(0) 127 , m_fullscreenRestoreState(0) 128 , m_disabled(false) 129 , m_qtMovie(0) 130 { 131 Rect rect = { 0, 0, 0, 0 }; 132 m_fullscreenRect = rect; 133 } 134 135 QTMovieGWorldPrivate::~QTMovieGWorldPrivate() 136 { 137 ASSERT(!m_fullscreenWindow); 138 139 if (m_gWorld) 140 deleteGWorld(); 141 } 142 143 pascal OSErr movieDrawingCompleteProc(Movie movie, long data) 144 { 145 UppParam param; 146 param.longValue = data; 147 QTMovieGWorldPrivate* mp = static_cast<QTMovieGWorldPrivate*>(param.ptr); 148 if (mp) 149 mp->drawingComplete(); 150 return 0; 151 } 152 153 void QTMovieGWorldPrivate::registerDrawingCallback() 154 { 155 if (!gMovieDrawingCompleteUPP) 156 gMovieDrawingCompleteUPP = NewMovieDrawingCompleteUPP(movieDrawingCompleteProc); 157 158 UppParam param; 159 param.ptr = this; 160 SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, gMovieDrawingCompleteUPP, param.longValue); 161 } 162 163 void QTMovieGWorldPrivate::unregisterDrawingCallback() 164 { 165 SetMovieDrawingCompleteProc(m_movie, movieDrawingCallWhenChanged, 0, 0); 166 } 167 168 void QTMovieGWorldPrivate::drawingComplete() 169 { 170 if (!m_gWorld || m_movieWin->m_private->m_disabled || m_loadState < QTMovieLoadStateLoaded) 171 return; 172 m_client->movieNewImageAvailable(m_movieWin); 173 } 174 175 void QTMovieGWorldPrivate::updateGWorld() 176 { 177 bool shouldBeVisible = m_visible; 178 if (!m_height || !m_width) 179 shouldBeVisible = false; 180 181 if (shouldBeVisible && !m_gWorld) 182 createGWorld(); 183 else if (!shouldBeVisible && m_gWorld) 184 deleteGWorld(); 185 else if (m_gWorld && (m_width > m_gWorldWidth || m_height > m_gWorldHeight)) { 186 // need a bigger, better gWorld 187 deleteGWorld(); 188 createGWorld(); 189 } 190 } 191 192 void QTMovieGWorldPrivate::createGWorld() 193 { 194 ASSERT(!m_gWorld); 195 if (!m_movie || m_loadState < QTMovieLoadStateLoaded) 196 return; 197 198 m_gWorldWidth = max(cGWorldMinWidth, m_width); 199 m_gWorldHeight = max(cGWorldMinHeight, m_height); 200 Rect bounds; 201 bounds.top = 0; 202 bounds.left = 0; 203 bounds.right = m_gWorldWidth; 204 bounds.bottom = m_gWorldHeight; 205 OSErr err = QTNewGWorld(&m_gWorld, k32BGRAPixelFormat, &bounds, 0, 0, 0); 206 if (err) 207 return; 208 GetMovieGWorld(m_movie, &m_savedGWorld, 0); 209 SetMovieGWorld(m_movie, m_gWorld, 0); 210 bounds.right = m_width; 211 bounds.bottom = m_height; 212 SetMovieBox(m_movie, &bounds); 213 } 214 215 void QTMovieGWorldPrivate::clearGWorld() 216 { 217 if (!m_movie || !m_gWorld) 218 return; 219 220 GrafPtr savePort; 221 GetPort(&savePort); 222 MacSetPort((GrafPtr)m_gWorld); 223 224 Rect bounds; 225 bounds.top = 0; 226 bounds.left = 0; 227 bounds.right = m_gWorldWidth; 228 bounds.bottom = m_gWorldHeight; 229 EraseRect(&bounds); 230 231 MacSetPort(savePort); 232 } 233 234 void QTMovieGWorldPrivate::setSize(int width, int height) 235 { 236 if (m_width == width && m_height == height) 237 return; 238 m_width = width; 239 m_height = height; 240 241 // Do not change movie box before reaching load state loaded as we grab 242 // the initial size when task() sees that state for the first time, and 243 // we need the initial size to be able to scale movie properly. 244 if (!m_movie || m_loadState < QTMovieLoadStateLoaded) 245 return; 246 247 #if !ASSERT_DISABLED 248 ASSERT(m_scaleCached); 249 #endif 250 251 updateMovieSize(); 252 } 253 254 void QTMovieGWorldPrivate::updateMovieSize() 255 { 256 if (!m_movie || m_loadState < QTMovieLoadStateLoaded) 257 return; 258 259 Rect bounds; 260 bounds.top = 0; 261 bounds.left = 0; 262 bounds.right = m_width; 263 bounds.bottom = m_height; 264 SetMovieBox(m_movie, &bounds); 265 updateGWorld(); 266 } 267 268 269 void QTMovieGWorldPrivate::deleteGWorld() 270 { 271 ASSERT(m_gWorld); 272 if (m_movie) 273 SetMovieGWorld(m_movie, m_savedGWorld, 0); 274 m_savedGWorld = 0; 275 DisposeGWorld(m_gWorld); 276 m_gWorld = 0; 277 m_gWorldWidth = 0; 278 m_gWorldHeight = 0; 279 } 280 281 void QTMovieGWorldPrivate::movieEnded(QTMovie*) 282 { 283 // Do nothing 284 } 285 286 void QTMovieGWorldPrivate::movieLoadStateChanged(QTMovie* movie) 287 { 288 long loadState = GetMovieLoadState(movie->getMovieHandle()); 289 if (loadState != m_loadState) { 290 291 // we only need to erase the movie gworld when the load state changes to loaded while it 292 // is visible as the gworld is destroyed/created when visibility changes 293 bool movieNewlyPlayable = loadState >= QTMovieLoadStateLoaded && m_loadState < QTMovieLoadStateLoaded; 294 m_loadState = loadState; 295 296 if (movieNewlyPlayable) { 297 updateMovieSize(); 298 if (m_visible) 299 clearGWorld(); 300 } 301 } 302 } 303 304 void QTMovieGWorldPrivate::movieTimeChanged(QTMovie*) 305 { 306 // Do nothing 307 } 308 309 QTMovieGWorld::QTMovieGWorld(QTMovieGWorldClient* client) 310 : m_private(new QTMovieGWorldPrivate(this)) 311 { 312 m_private->m_client = client; 313 } 314 315 QTMovieGWorld::~QTMovieGWorld() 316 { 317 delete m_private; 318 } 319 320 void QTMovieGWorld::setSize(int width, int height) 321 { 322 m_private->setSize(width, height); 323 QTMovieTask::sharedTask()->updateTaskTimer(); 324 } 325 326 void QTMovieGWorld::setVisible(bool b) 327 { 328 m_private->m_visible = b; 329 m_private->updateGWorld(); 330 } 331 332 void QTMovieGWorld::getCurrentFrameInfo(void*& buffer, unsigned& bitsPerPixel, unsigned& rowBytes, unsigned& width, unsigned& height) 333 { 334 if (!m_private->m_gWorld) { 335 buffer = 0; 336 bitsPerPixel = 0; 337 rowBytes = 0; 338 width = 0; 339 height = 0; 340 return; 341 } 342 PixMapHandle offscreenPixMap = GetGWorldPixMap(m_private->m_gWorld); 343 buffer = (*offscreenPixMap)->baseAddr; 344 bitsPerPixel = (*offscreenPixMap)->pixelSize; 345 rowBytes = (*offscreenPixMap)->rowBytes & 0x3FFF; 346 width = m_private->m_width; 347 height = m_private->m_height; 348 } 349 350 void QTMovieGWorld::paint(HDC hdc, int x, int y) 351 { 352 if (!m_private->m_gWorld) 353 return; 354 355 HDC hdcSrc = static_cast<HDC>(GetPortHDC(reinterpret_cast<GrafPtr>(m_private->m_gWorld))); 356 if (!hdcSrc) 357 return; 358 359 // FIXME: If we could determine the movie has no alpha, we could use BitBlt for those cases, which might be faster. 360 BLENDFUNCTION blendFunction; 361 blendFunction.BlendOp = AC_SRC_OVER; 362 blendFunction.BlendFlags = 0; 363 blendFunction.SourceConstantAlpha = 255; 364 blendFunction.AlphaFormat = AC_SRC_ALPHA; 365 AlphaBlend(hdc, x, y, m_private->m_width, m_private->m_height, hdcSrc, 366 0, 0, m_private->m_width, m_private->m_height, blendFunction); 367 } 368 369 void QTMovieGWorld::setDisabled(bool b) 370 { 371 m_private->m_disabled = b; 372 } 373 374 bool QTMovieGWorld::isDisabled() const 375 { 376 return m_private->m_disabled; 377 } 378 379 LRESULT QTMovieGWorld::fullscreenWndProc(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) 380 { 381 QTMovieGWorld* movie = static_cast<QTMovieGWorld*>(GetPropW(wnd, fullscreenQTMovieGWorldPointerProp)); 382 383 if (message == WM_DESTROY) 384 RemovePropW(wnd, fullscreenQTMovieGWorldPointerProp); 385 386 if (!movie) 387 return DefWindowProc(wnd, message, wParam, lParam); 388 389 return movie->m_private->m_fullscreenClient->fullscreenClientWndProc(wnd, message, wParam, lParam); 390 } 391 392 HWND QTMovieGWorld::enterFullscreen(QTMovieGWorldFullscreenClient* client) 393 { 394 m_private->m_fullscreenClient = client; 395 396 BeginFullScreen(&m_private->m_fullscreenRestoreState, 0, 0, 0, &m_private->m_fullscreenWindow, 0, fullScreenAllowEvents); 397 QTMLSetWindowWndProc(m_private->m_fullscreenWindow, fullscreenWndProc); 398 CreatePortAssociation(GetPortNativeWindow(m_private->m_fullscreenWindow), 0, 0); 399 400 GetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect); 401 GetMovieGWorld(m_private->m_movie, &m_private->m_fullscreenOrigGWorld, 0); 402 SetMovieGWorld(m_private->m_movie, reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow), GetGWorldDevice(reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow))); 403 404 // Set the size of the box to preserve aspect ratio 405 Rect rect = m_private->m_fullscreenWindow->portRect; 406 407 float movieRatio = static_cast<float>(m_private->m_width) / m_private->m_height; 408 int windowWidth = rect.right - rect.left; 409 int windowHeight = rect.bottom - rect.top; 410 float windowRatio = static_cast<float>(windowWidth) / windowHeight; 411 int actualWidth = (windowRatio > movieRatio) ? (windowHeight * movieRatio) : windowWidth; 412 int actualHeight = (windowRatio < movieRatio) ? (windowWidth / movieRatio) : windowHeight; 413 int offsetX = (windowWidth - actualWidth) / 2; 414 int offsetY = (windowHeight - actualHeight) / 2; 415 416 rect.left = offsetX; 417 rect.right = offsetX + actualWidth; 418 rect.top = offsetY; 419 rect.bottom = offsetY + actualHeight; 420 421 SetMovieBox(m_private->m_movie, &rect); 422 ShowHideTaskBar(true); 423 424 // Set the 'this' pointer on the HWND 425 HWND wnd = static_cast<HWND>(GetPortNativeWindow(m_private->m_fullscreenWindow)); 426 SetPropW(wnd, fullscreenQTMovieGWorldPointerProp, static_cast<HANDLE>(this)); 427 428 return wnd; 429 } 430 431 void QTMovieGWorld::exitFullscreen() 432 { 433 if (!m_private->m_fullscreenWindow) 434 return; 435 436 HWND wnd = static_cast<HWND>(GetPortNativeWindow(m_private->m_fullscreenWindow)); 437 DestroyPortAssociation(reinterpret_cast<CGrafPtr>(m_private->m_fullscreenWindow)); 438 SetMovieGWorld(m_private->m_movie, m_private->m_fullscreenOrigGWorld, 0); 439 EndFullScreen(m_private->m_fullscreenRestoreState, 0L); 440 SetMovieBox(m_private->m_movie, &m_private->m_fullscreenRect); 441 m_private->m_fullscreenWindow = 0; 442 } 443 444 void QTMovieGWorld::setMovie(PassRefPtr<QTMovie> movie) 445 { 446 if (m_private->m_qtMovie) { 447 m_private->unregisterDrawingCallback(); 448 m_private->m_qtMovie->removeClient(m_private); 449 m_private->m_qtMovie = 0; 450 m_private->m_movie = 0; 451 } 452 453 m_private->m_qtMovie = movie; 454 455 if (m_private->m_qtMovie) { 456 m_private->m_qtMovie->addClient(m_private); 457 m_private->m_movie = m_private->m_qtMovie->getMovieHandle(); 458 m_private->registerDrawingCallback(); 459 } 460 } 461 462 QTMovie* QTMovieGWorld::movie() const 463 { 464 return m_private->m_qtMovie.get(); 465 } 466