/* ** i_movie.cpp ** **--------------------------------------------------------------------------- ** Copyright 1998-2006 Randy Heit ** All rights reserved. ** ** Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions ** are met: ** ** 1. Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** 2. Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in the ** documentation and/or other materials provided with the distribution. ** 3. The name of the author may not be used to endorse or promote products ** derived from this software without specific prior written permission. ** ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. **--------------------------------------------------------------------------- ** */ /* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! * If you do not have dshow.h, either download the latest DirectX SDK * or #define I_DO_NOT_LIKE_BIG_DOWNLOADS * !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */ #ifndef I_DO_NOT_LIKE_BIG_DOWNLOADS #define I_DO_NOT_LIKE_BIG_DOWNLOADS #endif #define WIN32_LEAN_AND_MEAN #include #define USE_WINDOWS_DWORD #include "c_cvars.h" CUSTOM_CVAR (Float, snd_movievolume, 1.f, CVAR_ARCHIVE|CVAR_GLOBALCONFIG) { if (self < 0.f) self = 0.f; else if (self > 1.f) self = 1.f; } #ifdef I_DO_NOT_LIKE_BIG_DOWNLOADS #include "i_movie.h" int I_PlayMovie (const char *movie) { return MOVIE_Failed; } #else #include #include "i_movie.h" #include "i_sound.h" #include "v_video.h" #include "c_console.h" #include "win32iface.h" #include "sbar.h" EXTERN_CVAR (String, language) #define WM_GRAPHNOTIFY (WM_APP+321) #ifdef _DEBUG #define INGAME_PRIORITY_CLASS NORMAL_PRIORITY_CLASS #else //#define INGAME_PRIORITY_CLASS HIGH_PRIORITY_CLASS #define INGAME_PRIORITY_CLASS NORMAL_PRIORITY_CLASS #endif extern HWND Window; extern IVideo *Video; extern LRESULT CALLBACK WndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam); extern void I_CheckNativeMouse (bool preferNative); static bool MovieNotDone; static bool MovieInterrupted; static bool MovieDestroyed; static bool FullVideo; static bool NoVideo; static IVideoWindow *vidwin; static IMediaEventEx *event; static IGraphBuilder *graph; static IMediaControl *control; static IBasicAudio *audio; static IBasicVideo *video; static void CheckIfVideo (); static void SetMovieSize (); static void SetTheVolume (); static void SizeWindowForVideo (); LRESULT CALLBACK MovieWndProc (HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_GRAPHNOTIFY: { long code; LONG_PTR parm1, parm2; while (event->GetEvent (&code, &parm1, &parm2, 0) == S_OK) { if (code == EC_COMPLETE) { MovieNotDone = false; } event->FreeEventParams (code, parm1, parm2); } } break; case WM_SIZE: if (vidwin == NULL) { InvalidateRect (Window, NULL, FALSE); } else if ((wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) && !FullVideo) { SetMovieSize (); return 0; } break; case WM_PAINT: if (vidwin == NULL) { if (screen != NULL) { static_cast (screen)->PaintToWindow (); } } else if (!FullVideo) { PAINTSTRUCT ps; HBRUSH br; HDC dc; long left, top, width, height; if (S_OK == vidwin->GetWindowPosition (&left, &top, &width, &height)) { dc = BeginPaint (Window, &ps); if (dc != NULL) { RECT rect = { left, top, left+width, top+height }; ScreenToClient (hWnd, (LPPOINT)&rect.left); ScreenToClient (hWnd, (LPPOINT)&rect.right); br = (HBRUSH)GetStockObject (BLACK_BRUSH); switch (ExcludeClipRect (dc, rect.left, rect.top, rect.right, rect.bottom)) { case SIMPLEREGION: case COMPLEXREGION: FillRect (dc, &ps.rcPaint, br); break; default: break; } EndPaint (Window, &ps); //return 0; } } } break; case WM_DESTROY: case WM_KEYDOWN: case WM_SYSKEYDOWN: /* case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: case WM_MBUTTONDOWN: */ if (MovieNotDone) { if (wParam == VK_ESCAPE || message == WM_CANCELMODE || message == WM_DESTROY) { control->Stop (); MovieNotDone = false; MovieInterrupted = true; } } if (message == WM_DESTROY) { MovieDestroyed = true; } return 0; case WM_GETMINMAXINFO: if (screen && !FullVideo) { LPMINMAXINFO mmi = (LPMINMAXINFO)lParam; RECT rect = { 0, 0, screen->GetWidth(), screen->GetHeight() }; AdjustWindowRectEx(&rect, WS_VISIBLE|WS_OVERLAPPEDWINDOW, FALSE, WS_EX_APPWINDOW); mmi->ptMinTrackSize.x = rect.right - rect.left; mmi->ptMinTrackSize.y = rect.bottom - rect.top; return 0; } break; case WM_SETTINGCHANGE: // In case regional settings were changed, reget preferred languages language.Callback (); return 0; } return DefWindowProc (hWnd, message, wParam, lParam); } int I_PlayMovie (const char *name) { HRESULT hr; int returnval = MOVIE_Failed; size_t namelen = strlen (name) + 1; wchar_t *uniname = new wchar_t[namelen]; bool returnSound = false; bool runningFull = false; bool hotkey = false; size_t i; MSG msg; MovieNotDone = true; if (MultiByteToWideChar (CP_ACP, MB_PRECOMPOSED, name, -1, uniname, (int)namelen) == 0) { // Could not convert name to Unicode goto bomb1; } // Convert slashes to backslashes because IGraphBuilder cannot deal with them for (i = 0; i < namelen; ++i) { if (uniname[i] == L'/') uniname[i] = L'\\'; } if (FAILED(hr = CoCreateInstance (CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, IID_IGraphBuilder, (void **)&graph))) { goto bomb1; } control = NULL; event = NULL; vidwin = NULL; audio = NULL; video = NULL; if (FAILED(hr = graph->RenderFile (uniname, NULL))) { goto bomb2; } graph->QueryInterface (IID_IMediaControl, (void **)&control); graph->QueryInterface (IID_IMediaEventEx, (void **)&event); graph->QueryInterface (IID_IVideoWindow, (void **)&vidwin); graph->QueryInterface (IID_IBasicAudio, (void **)&audio); graph->QueryInterface (IID_IBasicVideo, (void **)&video); if (control == NULL || event == NULL) { goto bomb3; } GSnd->MovieDisableSound (); returnSound = true; CheckIfVideo (); SetTheVolume (); FullVideo = false; if (vidwin != NULL) { FullVideo = runningFull = screen->IsFullscreen (); // If the message drain cannot be set, then we simply won't be able // to catch mouse button presses in the video window. HRESULT drainhr = vidwin->put_MessageDrain ((OAHWND)Window); if (FullVideo) { // Try to avoid nasty palette flashes by clearing the screen to black. // Does not always work. :-( static_cast (Video)->BlankForGDI (); static_cast (Video)->GoFullscreen (false); static_cast (screen)->ReleaseResources (); if (FAILED (drainhr) || FAILED(hr = vidwin->put_FullScreenMode (OATRUE))) { SizeWindowForVideo (); FullVideo = false; } } if (!FullVideo) { vidwin->put_Owner ((OAHWND)Window); vidwin->put_WindowStyle (WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN); SetMovieSize (); } else { RegisterHotKey (Window, 0, MOD_ALT, VK_TAB); hotkey = true; } } if (FAILED (hr = event->SetNotifyWindow ((OAHWND)Window, WM_GRAPHNOTIFY, 0))) { goto bomb3; } SetPriorityClass (GetCurrentProcess(), NORMAL_PRIORITY_CLASS); I_CheckNativeMouse (true); SetWindowLongPtr (Window, GWLP_WNDPROC, (LONG_PTR)MovieWndProc); if (FAILED (hr = control->Run ())) { goto bomb4; } MovieInterrupted = false; MovieDestroyed = false; while (MovieNotDone && GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } returnval = MovieInterrupted ? MOVIE_Played_Aborted : NoVideo ? MOVIE_Played_NoVideo : MOVIE_Played; bomb4: SetWindowLongPtr (Window, GWLP_WNDPROC, (LONG_PTR)WndProc); SetPriorityClass (GetCurrentProcess(), INGAME_PRIORITY_CLASS); bomb3: if (hotkey) { UnregisterHotKey (Window, 0); } if (vidwin != NULL) { if (!FullVideo) { vidwin->put_Visible (OAFALSE); vidwin->put_Owner (NULL); } vidwin->Release (); } if (video != NULL) { video->Release (); } if (audio != NULL) { audio->Release (); } if (event != NULL) { event->Release (); } if (control != NULL) { control->Release (); } SetActiveWindow (Window); bomb2: graph->Release (); if (returnSound) { GSnd->MovieResumeSound (); C_FlushDisplay (); } if (runningFull) { static_cast (Video)->GoFullscreen (true); static_cast (screen)->CreateResources (); } bomb1: delete[] uniname; if (MovieDestroyed) { exit (0); } return returnval; } static void CheckIfVideo () { long visible; if (vidwin == NULL || video == NULL) { NoVideo = true; return; } else if (vidwin->get_Visible (&visible) == E_NOINTERFACE) { NoVideo = true; } else { NoVideo = false; } if (NoVideo) { if (vidwin != NULL) { vidwin->Release (); vidwin = NULL; } DHUDMessage *message = new DHUDMessage ( "This movie either does not contain a video stream " "or no suitable decompressor could be found.", 1.5f, 0.375f, 0, 0, CR_RED, 1.f); StatusBar->AttachMessage (message); screen->Lock (false); StatusBar->Draw (HUD_None); StatusBar->DrawTopStuff (HUD_None); screen->Update (); StatusBar->DetachMessage (message); delete message; } } static void SetTheVolume () { if (audio != NULL) { // Convert snd_movievolume from a linear range to 1/100th // decibels and pass that to the IBasicAudio interface. long volume; if (snd_movievolume == 0.f) { volume = -10000; } else { volume = (long)(log10 (snd_movievolume) * 10000.0); } audio->put_Volume (volume); } } static void SizeWindowForVideo () { LONG width, height; if (video == NULL || FAILED (video->GetVideoSize (&width, &height))) return; SetWindowPos (Window, NULL, 0, 0, width + 2*GetSystemMetrics(SM_CXBORDER), height + GetSystemMetrics(SM_CYCAPTION) + 2*GetSystemMetrics(SM_CYBORDER), SWP_NOMOVE|SWP_NOOWNERZORDER); } static void SetMovieSize () { LONG width, height, left, top; RECT grc; HRESULT hr; GetClientRect (Window, &grc); hr = video->GetVideoSize (&width, &height); if (FAILED (hr)) { vidwin->SetWindowPosition (0, 0, grc.right, grc.bottom); return; } double aspect = (double)height / (double)width; // Calculate size of video window that fits in our window if (grc.right > grc.bottom) { height = grc.bottom; width = (LONG)((double)height / aspect); } if (grc.right <= grc.bottom || width > grc.right) { width = grc.right; height = (LONG)((double)width * aspect); } // Then center it left = (grc.right - width) / 2; top = (grc.bottom - height) / 2; // And use it vidwin->SetWindowPosition (left, top, width, height); InvalidateRect (Window, NULL, FALSE); } #endif // I_DO_NOT_LIKE_BIG_DOWNLOADS