Game client codebase including: - CharacterActionControl: Character and creature management - GlobalScript: Network, items, skills, quests, utilities - RYLClient: Main client application with GUI and event handlers - Engine: 3D rendering engine (RYLGL) - MemoryManager: Custom memory allocation - Library: Third-party dependencies (DirectX, boost, etc.) - Tools: Development utilities 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1181 lines
30 KiB
C++
1181 lines
30 KiB
C++
//------------------------------------------------------------------------------
|
|
// File: PlayWnd.cpp
|
|
//
|
|
// Desc: DirectShow sample code - a simple audio/video media file player
|
|
// application. Pause, stop, mute, and fullscreen mode toggle can
|
|
// be performed via keyboard commands.
|
|
//
|
|
// Copyright (c) 1996-2001 Microsoft Corporation. All rights reserved.
|
|
//------------------------------------------------------------------------------
|
|
|
|
|
|
#include <dshow.h>
|
|
#include <commctrl.h>
|
|
#include <commdlg.h>
|
|
#include <stdio.h>
|
|
#include <tchar.h>
|
|
#include <atlbase.h>
|
|
|
|
#include "playwnd.h"
|
|
|
|
// An application can advertise the existence of its filter graph
|
|
// by registering the graph with a global Running Object Table (ROT).
|
|
// The GraphEdit application can detect and remotely view the running
|
|
// filter graph, allowing you to 'spy' on the graph with GraphEdit.
|
|
//
|
|
// To enable registration in this sample, define REGISTER_FILTERGRAPH.
|
|
//
|
|
#define REGISTER_FILTERGRAPH
|
|
|
|
//
|
|
// Global data
|
|
//
|
|
HWND ghApp=0;
|
|
HMENU ghMenu=0;
|
|
HINSTANCE ghInst=0;
|
|
TCHAR g_szFileName[MAX_PATH]={0};
|
|
BOOL g_bAudioOnly=FALSE, g_bFullscreen=FALSE;
|
|
LONG g_lVolume=VOLUME_FULL;
|
|
DWORD g_dwGraphRegister=0;
|
|
PLAYSTATE g_psCurrent=Stopped;
|
|
double g_PlaybackRate=1.0;
|
|
|
|
// DirectShow interfaces
|
|
IGraphBuilder *pGB = NULL;
|
|
IMediaControl *pMC = NULL;
|
|
IMediaEventEx *pME = NULL;
|
|
IVideoWindow *pVW = NULL;
|
|
IBasicAudio *pBA = NULL;
|
|
IBasicVideo *pBV = NULL;
|
|
IMediaSeeking *pMS = NULL;
|
|
IMediaPosition *pMP = NULL;
|
|
IVideoFrameStep *pFS = NULL;
|
|
|
|
|
|
|
|
HRESULT PlayMovieInWindow(LPTSTR szFile)
|
|
{
|
|
USES_CONVERSION;
|
|
WCHAR wFile[MAX_PATH];
|
|
HRESULT hr;
|
|
|
|
// Clear open dialog remnants before calling RenderFile()
|
|
UpdateWindow(ghApp);
|
|
|
|
// Convert filename to wide character string
|
|
wcscpy(wFile, T2W(szFile));
|
|
|
|
// Get the interface for DirectShow's GraphBuilder
|
|
JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER,
|
|
IID_IGraphBuilder, (void **)&pGB));
|
|
|
|
if(SUCCEEDED(hr))
|
|
{
|
|
// Have the graph builder construct its the appropriate graph automatically
|
|
JIF(pGB->RenderFile(wFile, NULL));
|
|
|
|
// QueryInterface for DirectShow interfaces
|
|
JIF(pGB->QueryInterface(IID_IMediaControl, (void **)&pMC));
|
|
JIF(pGB->QueryInterface(IID_IMediaEventEx, (void **)&pME));
|
|
JIF(pGB->QueryInterface(IID_IMediaSeeking, (void **)&pMS));
|
|
JIF(pGB->QueryInterface(IID_IMediaPosition, (void **)&pMP));
|
|
|
|
// Query for video interfaces, which may not be relevant for audio files
|
|
JIF(pGB->QueryInterface(IID_IVideoWindow, (void **)&pVW));
|
|
JIF(pGB->QueryInterface(IID_IBasicVideo, (void **)&pBV));
|
|
|
|
// Query for audio interfaces, which may not be relevant for video-only files
|
|
JIF(pGB->QueryInterface(IID_IBasicAudio, (void **)&pBA));
|
|
|
|
// Is this an audio-only file (no video component)?
|
|
CheckVisibility();
|
|
|
|
// Have the graph signal event via window callbacks for performance
|
|
JIF(pME->SetNotifyWindow((OAHWND)ghApp, WM_GRAPHNOTIFY, 0));
|
|
|
|
if (!g_bAudioOnly)
|
|
{
|
|
JIF(pVW->put_Owner((OAHWND)ghApp));
|
|
JIF(pVW->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS | WS_CLIPCHILDREN));
|
|
|
|
JIF(InitVideoWindow(1, 1));
|
|
GetFrameStepInterface();
|
|
}
|
|
else
|
|
{
|
|
JIF(InitPlayerWindow());
|
|
}
|
|
|
|
// Let's get ready to rumble!
|
|
CheckSizeMenu(ID_FILE_SIZE_NORMAL);
|
|
ShowWindow(ghApp, SW_SHOWNORMAL);
|
|
UpdateWindow(ghApp);
|
|
SetForegroundWindow(ghApp);
|
|
SetFocus(ghApp);
|
|
g_bFullscreen = FALSE;
|
|
g_PlaybackRate = 1.0;
|
|
UpdateMainTitle();
|
|
|
|
#ifdef REGISTER_FILTERGRAPH
|
|
hr = AddGraphToRot(pGB, &g_dwGraphRegister);
|
|
if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Failed to register filter graph with ROT! hr=0x%x"), hr);
|
|
g_dwGraphRegister = 0;
|
|
}
|
|
#endif
|
|
|
|
// Run the graph to play the media file
|
|
JIF(pMC->Run());
|
|
g_psCurrent=Running;
|
|
|
|
SetFocus(ghApp);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT InitVideoWindow(int nMultiplier, int nDivider)
|
|
{
|
|
LONG lHeight, lWidth;
|
|
HRESULT hr = S_OK;
|
|
RECT rect;
|
|
|
|
if (!pBV)
|
|
return S_OK;
|
|
|
|
// Read the default video size
|
|
hr = pBV->GetVideoSize(&lWidth, &lHeight);
|
|
if (hr == E_NOINTERFACE)
|
|
return S_OK;
|
|
|
|
EnablePlaybackMenu(TRUE);
|
|
|
|
// Account for requests of normal, half, or double size
|
|
lWidth = lWidth * nMultiplier / nDivider;
|
|
lHeight = lHeight * nMultiplier / nDivider;
|
|
|
|
SetWindowPos(ghApp, NULL, 0, 0, lWidth, lHeight,
|
|
SWP_NOMOVE | SWP_NOOWNERZORDER);
|
|
|
|
int nTitleHeight = GetSystemMetrics(SM_CYCAPTION);
|
|
int nBorderWidth = GetSystemMetrics(SM_CXBORDER);
|
|
int nBorderHeight = GetSystemMetrics(SM_CYBORDER);
|
|
|
|
// Account for size of title bar and borders for exact match
|
|
// of window client area to default video size
|
|
SetWindowPos(ghApp, NULL, 0, 0, lWidth + 2*nBorderWidth,
|
|
lHeight + nTitleHeight + 2*nBorderHeight,
|
|
SWP_NOMOVE | SWP_NOOWNERZORDER);
|
|
|
|
GetClientRect(ghApp, &rect);
|
|
JIF(pVW->SetWindowPosition(rect.left, rect.top, rect.right, rect.bottom));
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT InitPlayerWindow(void)
|
|
{
|
|
// Reset to a default size for audio and after closing a clip
|
|
SetWindowPos(ghApp, NULL, 0, 0,
|
|
DEFAULT_AUDIO_WIDTH,
|
|
DEFAULT_AUDIO_HEIGHT,
|
|
SWP_NOMOVE | SWP_NOOWNERZORDER);
|
|
|
|
// Check the 'full size' menu item
|
|
CheckSizeMenu(ID_FILE_SIZE_NORMAL);
|
|
EnablePlaybackMenu(FALSE);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
void MoveVideoWindow(void)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Track the movement of the container window and resize as needed
|
|
if(pVW)
|
|
{
|
|
RECT client;
|
|
|
|
GetClientRect(ghApp, &client);
|
|
hr = pVW->SetWindowPosition(client.left, client.top,
|
|
client.right, client.bottom);
|
|
}
|
|
}
|
|
|
|
|
|
void CheckVisibility(void)
|
|
{
|
|
long lVisible;
|
|
HRESULT hr;
|
|
|
|
if ((!pVW) || (!pBV))
|
|
{
|
|
// Audio-only files have no video interfaces. This might also
|
|
// be a file whose video component uses an unknown video codec.
|
|
g_bAudioOnly = TRUE;
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// Clear the global flag
|
|
g_bAudioOnly = FALSE;
|
|
}
|
|
|
|
hr = pVW->get_Visible(&lVisible);
|
|
if (FAILED(hr))
|
|
{
|
|
// If this is an audio-only clip, get_Visible() won't work.
|
|
//
|
|
// Also, if this video is encoded with an unsupported codec,
|
|
// we won't see any video, although the audio will work if it is
|
|
// of a supported format.
|
|
//
|
|
if (hr == E_NOINTERFACE)
|
|
{
|
|
g_bAudioOnly = TRUE;
|
|
}
|
|
else
|
|
{
|
|
Msg(TEXT("Failed(%08lx) in pVW->get_Visible()!\r\n"), hr);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void PauseClip(void)
|
|
{
|
|
if (!pMC)
|
|
return;
|
|
|
|
// Toggle play/pause behavior
|
|
if((g_psCurrent == Paused) || (g_psCurrent == Stopped))
|
|
{
|
|
if (SUCCEEDED(pMC->Run()))
|
|
g_psCurrent = Running;
|
|
}
|
|
else
|
|
{
|
|
if (SUCCEEDED(pMC->Pause()))
|
|
g_psCurrent = Paused;
|
|
}
|
|
|
|
UpdateMainTitle();
|
|
}
|
|
|
|
|
|
void StopClip(void)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if ((!pMC) || (!pMS))
|
|
return;
|
|
|
|
// Stop and reset postion to beginning
|
|
if((g_psCurrent == Paused) || (g_psCurrent == Running))
|
|
{
|
|
LONGLONG pos = 0;
|
|
hr = pMC->Stop();
|
|
g_psCurrent = Stopped;
|
|
|
|
// Seek to the beginning
|
|
hr = pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,
|
|
NULL, AM_SEEKING_NoPositioning);
|
|
|
|
// Display the first frame to indicate the reset condition
|
|
hr = pMC->Pause();
|
|
}
|
|
|
|
UpdateMainTitle();
|
|
}
|
|
|
|
|
|
void OpenClip()
|
|
{
|
|
HRESULT hr;
|
|
|
|
// If no filename specified by command line, show file open dialog
|
|
if(g_szFileName[0] == L'\0')
|
|
{
|
|
TCHAR szFilename[MAX_PATH];
|
|
|
|
UpdateMainTitle();
|
|
|
|
// If no filename was specified on the command line, then our video
|
|
// window has not been created or made visible. Make our main window
|
|
// visible and bring to the front to allow file selection.
|
|
InitPlayerWindow();
|
|
ShowWindow(ghApp, SW_SHOWNORMAL);
|
|
SetForegroundWindow(ghApp);
|
|
|
|
if (! GetClipFileName(szFilename))
|
|
{
|
|
DWORD dwDlgErr = CommDlgExtendedError();
|
|
|
|
// Don't show output if user cancelled the selection (no dlg error)
|
|
if (dwDlgErr)
|
|
{
|
|
Msg(TEXT("GetClipFileName Failed! Error=0x%x\r\n"), GetLastError());
|
|
}
|
|
return;
|
|
}
|
|
|
|
// This sample does not support playback of ASX playlists.
|
|
// Since this could be confusing to a user, display a warning
|
|
// message if an ASX file was opened.
|
|
if (_tcsstr((_tcslwr(szFilename)), TEXT(".asx")))
|
|
{
|
|
Msg(TEXT("ASX Playlists are not supported by this application.\n\n")
|
|
TEXT("Please select a valid media file.\0"));
|
|
return;
|
|
}
|
|
|
|
lstrcpy(g_szFileName, szFilename);
|
|
}
|
|
|
|
// Reset status variables
|
|
g_psCurrent = Stopped;
|
|
g_lVolume = VOLUME_FULL;
|
|
|
|
// Start playing the media file
|
|
hr = PlayMovieInWindow(g_szFileName);
|
|
|
|
// If we couldn't play the clip, clean up
|
|
if (FAILED(hr))
|
|
CloseClip();
|
|
}
|
|
|
|
|
|
BOOL GetClipFileName(LPTSTR szName)
|
|
{
|
|
static OPENFILENAME ofn={0};
|
|
static BOOL bSetInitialDir = FALSE;
|
|
|
|
// Reset filename
|
|
*szName = 0;
|
|
|
|
// Fill in standard structure fields
|
|
ofn.lStructSize = sizeof(OPENFILENAME);
|
|
ofn.hwndOwner = ghApp;
|
|
ofn.lpstrFilter = NULL;
|
|
ofn.lpstrFilter = FILE_FILTER_TEXT;
|
|
ofn.lpstrCustomFilter = NULL;
|
|
ofn.nFilterIndex = 1;
|
|
ofn.lpstrFile = szName;
|
|
ofn.nMaxFile = MAX_PATH;
|
|
ofn.lpstrTitle = TEXT("Open Media File...\0");
|
|
ofn.lpstrFileTitle = NULL;
|
|
ofn.lpstrDefExt = TEXT("*\0");
|
|
ofn.Flags = OFN_FILEMUSTEXIST | OFN_READONLY | OFN_PATHMUSTEXIST;
|
|
|
|
// Remember the path of the first selected file
|
|
if (bSetInitialDir == FALSE)
|
|
{
|
|
ofn.lpstrInitialDir = DEFAULT_MEDIA_PATH;
|
|
bSetInitialDir = TRUE;
|
|
}
|
|
else
|
|
ofn.lpstrInitialDir = NULL;
|
|
|
|
// Create the standard file open dialog and return its result
|
|
return GetOpenFileName((LPOPENFILENAME)&ofn);
|
|
}
|
|
|
|
|
|
void CloseClip()
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Stop media playback
|
|
if(pMC)
|
|
hr = pMC->Stop();
|
|
|
|
// Clear global flags
|
|
g_psCurrent = Stopped;
|
|
g_bAudioOnly = TRUE;
|
|
g_bFullscreen = FALSE;
|
|
|
|
// Free DirectShow interfaces
|
|
CloseInterfaces();
|
|
|
|
// Clear file name to allow selection of new file with open dialog
|
|
g_szFileName[0] = L'\0';
|
|
|
|
// No current media state
|
|
g_psCurrent = Init;
|
|
|
|
// Reset the player window
|
|
RECT rect;
|
|
GetClientRect(ghApp, &rect);
|
|
InvalidateRect(ghApp, &rect, TRUE);
|
|
|
|
UpdateMainTitle();
|
|
InitPlayerWindow();
|
|
}
|
|
|
|
|
|
void CloseInterfaces(void)
|
|
{
|
|
HRESULT hr;
|
|
|
|
// Relinquish ownership (IMPORTANT!) after hiding video window
|
|
if(pVW)
|
|
{
|
|
hr = pVW->put_Visible(OAFALSE);
|
|
hr = pVW->put_Owner(NULL);
|
|
}
|
|
|
|
// Disable event callbacks
|
|
if (pME)
|
|
hr = pME->SetNotifyWindow((OAHWND)NULL, 0, 0);
|
|
|
|
#ifdef REGISTER_FILTERGRAPH
|
|
if (g_dwGraphRegister)
|
|
{
|
|
RemoveGraphFromRot(g_dwGraphRegister);
|
|
g_dwGraphRegister = 0;
|
|
}
|
|
#endif
|
|
|
|
// Release and zero DirectShow interfaces
|
|
SAFE_RELEASE(pME);
|
|
SAFE_RELEASE(pMS);
|
|
SAFE_RELEASE(pMP);
|
|
SAFE_RELEASE(pMC);
|
|
SAFE_RELEASE(pBA);
|
|
SAFE_RELEASE(pBV);
|
|
SAFE_RELEASE(pVW);
|
|
SAFE_RELEASE(pFS);
|
|
SAFE_RELEASE(pGB);
|
|
}
|
|
|
|
|
|
#ifdef REGISTER_FILTERGRAPH
|
|
|
|
HRESULT AddGraphToRot(IUnknown *pUnkGraph, DWORD *pdwRegister)
|
|
{
|
|
IMoniker * pMoniker;
|
|
IRunningObjectTable *pROT;
|
|
if (FAILED(GetRunningObjectTable(0, &pROT)))
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
WCHAR wsz[128];
|
|
wsprintfW(wsz, L"FilterGraph %08x pid %08x", (DWORD_PTR)pUnkGraph,
|
|
GetCurrentProcessId());
|
|
|
|
HRESULT hr = CreateItemMoniker(L"!", wsz, &pMoniker);
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
hr = pROT->Register(0, pUnkGraph, pMoniker, pdwRegister);
|
|
pMoniker->Release();
|
|
}
|
|
|
|
pROT->Release();
|
|
return hr;
|
|
}
|
|
|
|
void RemoveGraphFromRot(DWORD pdwRegister)
|
|
{
|
|
IRunningObjectTable *pROT;
|
|
|
|
if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
|
|
{
|
|
pROT->Revoke(pdwRegister);
|
|
pROT->Release();
|
|
}
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
void Msg(TCHAR *szFormat, ...)
|
|
{
|
|
TCHAR szBuffer[512]; // Large buffer for very long filenames (like HTTP)
|
|
|
|
// Format the input string
|
|
va_list pArgs;
|
|
va_start(pArgs, szFormat);
|
|
_vstprintf(szBuffer, szFormat, pArgs);
|
|
va_end(pArgs);
|
|
|
|
// Display a message box with the formatted string
|
|
MessageBox(NULL, szBuffer, TEXT("PlayWnd Sample"), MB_OK);
|
|
}
|
|
|
|
|
|
HRESULT ToggleMute(void)
|
|
{
|
|
HRESULT hr=S_OK;
|
|
|
|
if ((!pGB) || (!pBA))
|
|
return S_OK;
|
|
|
|
// Read current volume
|
|
hr = pBA->get_Volume(&g_lVolume);
|
|
if (hr == E_NOTIMPL)
|
|
{
|
|
// Fail quietly if this is a video-only media file
|
|
return S_OK;
|
|
}
|
|
else if (FAILED(hr))
|
|
{
|
|
Msg(TEXT("Failed to read audio volume! hr=0x%x\r\n"), hr);
|
|
return hr;
|
|
}
|
|
|
|
// Switch volume levels
|
|
if (g_lVolume == VOLUME_FULL)
|
|
g_lVolume = VOLUME_SILENCE;
|
|
else
|
|
g_lVolume = VOLUME_FULL;
|
|
|
|
// Set new volume
|
|
JIF(pBA->put_Volume(g_lVolume));
|
|
|
|
UpdateMainTitle();
|
|
return hr;
|
|
}
|
|
|
|
|
|
void UpdateMainTitle(void)
|
|
{
|
|
TCHAR szTitle[MAX_PATH]={0}, szFile[MAX_PATH]={0};
|
|
|
|
// If no file is loaded, just show the application title
|
|
if (g_szFileName[0] == L'\0')
|
|
{
|
|
wsprintf(szTitle, TEXT("%s"), APPLICATIONNAME);
|
|
}
|
|
|
|
// Otherwise, show useful information
|
|
else
|
|
{
|
|
// Get file name without full path
|
|
GetFilename(g_szFileName, szFile);
|
|
|
|
char szPlaybackRate[16];
|
|
if (g_PlaybackRate == 1.0)
|
|
szPlaybackRate[0] = '\0';
|
|
else
|
|
sprintf(szPlaybackRate, "(Rate:%2.2f)", g_PlaybackRate);
|
|
|
|
TCHAR szRate[20];
|
|
|
|
#ifdef UNICODE
|
|
MultiByteToWideChar(CP_ACP, 0, szPlaybackRate, -1, szRate, 20);
|
|
|
|
#else
|
|
lstrcpy(szRate, szPlaybackRate);
|
|
#endif
|
|
|
|
// Update the window title to show filename and play state
|
|
wsprintf(szTitle, TEXT("%s [%s] %s%s%s\0\0"),
|
|
szFile,
|
|
g_bAudioOnly ? TEXT("Audio\0") : TEXT("Video\0"),
|
|
(g_lVolume == VOLUME_SILENCE) ? TEXT("(Muted)\0") : TEXT("\0"),
|
|
(g_psCurrent == Paused) ? TEXT("(Paused)\0") : TEXT("\0"),
|
|
szRate);
|
|
}
|
|
|
|
SetWindowText(ghApp, szTitle);
|
|
}
|
|
|
|
|
|
void GetFilename(TCHAR *pszFull, TCHAR *pszFile)
|
|
{
|
|
int nLength;
|
|
TCHAR szPath[MAX_PATH]={0};
|
|
BOOL bSetFilename=FALSE;
|
|
|
|
// Strip path and return just the file's name
|
|
_tcscpy(szPath, pszFull);
|
|
nLength = (int) _tcslen(szPath);
|
|
|
|
for (int i=nLength-1; i>=0; i--)
|
|
{
|
|
if ((szPath[i] == '\\') || (szPath[i] == '/'))
|
|
{
|
|
szPath[i] = '\0';
|
|
lstrcpy(pszFile, &szPath[i+1]);
|
|
bSetFilename = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If there was no path given (just a file name), then
|
|
// just copy the full path to the target path.
|
|
if (!bSetFilename)
|
|
_tcscpy(pszFile, pszFull);
|
|
}
|
|
|
|
|
|
HRESULT ToggleFullScreen(void)
|
|
{
|
|
HRESULT hr=S_OK;
|
|
LONG lMode;
|
|
static HWND hDrain=0;
|
|
|
|
// Don't bother with full-screen for audio-only files
|
|
if ((g_bAudioOnly) || (!pVW))
|
|
return S_OK;
|
|
|
|
// Read current state
|
|
JIF(pVW->get_FullScreenMode(&lMode));
|
|
|
|
if (lMode == OAFALSE)
|
|
{
|
|
// Save current message drain
|
|
LIF(pVW->get_MessageDrain((OAHWND *) &hDrain));
|
|
|
|
// Set message drain to application main window
|
|
LIF(pVW->put_MessageDrain((OAHWND) ghApp));
|
|
|
|
// Switch to full-screen mode
|
|
lMode = OATRUE;
|
|
JIF(pVW->put_FullScreenMode(lMode));
|
|
g_bFullscreen = TRUE;
|
|
}
|
|
else
|
|
{
|
|
// Switch back to windowed mode
|
|
lMode = OAFALSE;
|
|
JIF(pVW->put_FullScreenMode(lMode));
|
|
|
|
// Undo change of message drain
|
|
LIF(pVW->put_MessageDrain((OAHWND) hDrain));
|
|
|
|
// Reset video window
|
|
LIF(pVW->SetWindowForeground(-1));
|
|
|
|
// Reclaim keyboard focus for player application
|
|
UpdateWindow(ghApp);
|
|
SetForegroundWindow(ghApp);
|
|
SetFocus(ghApp);
|
|
g_bFullscreen = FALSE;
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
//
|
|
// Some video renderers support stepping media frame by frame with the
|
|
// IVideoFrameStep interface. See the interface documentation for more
|
|
// details on frame stepping.
|
|
//
|
|
BOOL GetFrameStepInterface(void)
|
|
{
|
|
HRESULT hr;
|
|
IVideoFrameStep *pFSTest = NULL;
|
|
|
|
// Get the frame step interface, if supported
|
|
hr = pGB->QueryInterface(__uuidof(IVideoFrameStep), (PVOID *)&pFSTest);
|
|
if (FAILED(hr))
|
|
return FALSE;
|
|
|
|
// Check if this decoder can step
|
|
hr = pFSTest->CanStep(0L, NULL);
|
|
|
|
if (hr == S_OK)
|
|
{
|
|
pFS = pFSTest; // Save interface to global variable for later use
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
pFSTest->Release();
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
|
|
HRESULT StepOneFrame(void)
|
|
{
|
|
HRESULT hr=S_OK;
|
|
|
|
// If the Frame Stepping interface exists, use it to step one frame
|
|
if (pFS)
|
|
{
|
|
// The graph must be paused for frame stepping to work
|
|
if (g_psCurrent != State_Paused)
|
|
PauseClip();
|
|
|
|
// Step the requested number of frames, if supported
|
|
hr = pFS->Step(1, NULL);
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
HRESULT StepFrames(int nFramesToStep)
|
|
{
|
|
HRESULT hr=S_OK;
|
|
|
|
// If the Frame Stepping interface exists, use it to step frames
|
|
if (pFS)
|
|
{
|
|
// The renderer may not support frame stepping for more than one
|
|
// frame at a time, so check for support. S_OK indicates that the
|
|
// renderer can step nFramesToStep successfully.
|
|
if ((hr = pFS->CanStep(nFramesToStep, NULL)) == S_OK)
|
|
{
|
|
// The graph must be paused for frame stepping to work
|
|
if (g_psCurrent != State_Paused)
|
|
PauseClip();
|
|
|
|
// Step the requested number of frames, if supported
|
|
hr = pFS->Step(nFramesToStep, NULL);
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT ModifyRate(double dRateAdjust)
|
|
{
|
|
HRESULT hr=S_OK;
|
|
double dRate;
|
|
|
|
// If the IMediaPosition interface exists, use it to set rate
|
|
if ((pMP) && (dRateAdjust != 0))
|
|
{
|
|
if ((hr = pMP->get_Rate(&dRate)) == S_OK)
|
|
{
|
|
// Add current rate to adjustment value
|
|
double dNewRate = dRate + dRateAdjust;
|
|
hr = pMP->put_Rate(dNewRate);
|
|
|
|
// Save global rate
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
g_PlaybackRate = dNewRate;
|
|
UpdateMainTitle();
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT SetRate(double dRate)
|
|
{
|
|
HRESULT hr=S_OK;
|
|
|
|
// If the IMediaPosition interface exists, use it to set rate
|
|
if (pMP)
|
|
{
|
|
hr = pMP->put_Rate(dRate);
|
|
|
|
// Save global rate
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
g_PlaybackRate = dRate;
|
|
UpdateMainTitle();
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
HRESULT HandleGraphEvent(void)
|
|
{
|
|
LONG evCode, evParam1, evParam2;
|
|
HRESULT hr=S_OK;
|
|
|
|
// Make sure that we don't access the media event interface
|
|
// after it has already been released.
|
|
if (!pME)
|
|
return S_OK;
|
|
|
|
// Process all queued events
|
|
while(SUCCEEDED(pME->GetEvent(&evCode, (LONG_PTR *) &evParam1,
|
|
(LONG_PTR *) &evParam2, 0)))
|
|
{
|
|
// Free memory associated with callback, since we're not using it
|
|
hr = pME->FreeEventParams(evCode, evParam1, evParam2);
|
|
|
|
// If this is the end of the clip, reset to beginning
|
|
if(EC_COMPLETE == evCode)
|
|
{
|
|
LONGLONG pos=0;
|
|
|
|
// Reset to first frame of movie
|
|
hr = pMS->SetPositions(&pos, AM_SEEKING_AbsolutePositioning ,
|
|
NULL, AM_SEEKING_NoPositioning);
|
|
if (FAILED(hr))
|
|
{
|
|
// Some custom filters (like the Windows CE MIDI filter)
|
|
// may not implement seeking interfaces (IMediaSeeking)
|
|
// to allow seeking to the start. In that case, just stop
|
|
// and restart for the same effect. This should not be
|
|
// necessary in most cases.
|
|
if (FAILED(hr = pMC->Stop()))
|
|
{
|
|
Msg(TEXT("Failed(0x%08lx) to stop media clip!\r\n"), hr);
|
|
break;
|
|
}
|
|
|
|
if (FAILED(hr = pMC->Run()))
|
|
{
|
|
Msg(TEXT("Failed(0x%08lx) to reset media clip!\r\n"), hr);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return hr;
|
|
}
|
|
|
|
|
|
void CheckSizeMenu(WPARAM wParam)
|
|
{
|
|
WPARAM nItems[4] = {ID_FILE_SIZE_HALF, ID_FILE_SIZE_DOUBLE,
|
|
ID_FILE_SIZE_NORMAL, ID_FILE_SIZE_THREEQUARTER};
|
|
|
|
// Set/clear checkboxes that indicate the size of the video clip
|
|
for (int i=0; i<4; i++)
|
|
{
|
|
// Check the selected item
|
|
CheckMenuItem(ghMenu, (UINT) nItems[i],
|
|
(UINT) (wParam == nItems[i]) ? MF_CHECKED : MF_UNCHECKED);
|
|
}
|
|
}
|
|
|
|
|
|
void EnablePlaybackMenu(BOOL bEnable)
|
|
{
|
|
WPARAM nItems[14] = {ID_FILE_PAUSE, ID_FILE_STOP,
|
|
ID_FILE_MUTE, ID_SINGLE_STEP,
|
|
ID_FILE_SIZE_HALF, ID_FILE_SIZE_DOUBLE,
|
|
ID_FILE_SIZE_NORMAL, ID_FILE_SIZE_THREEQUARTER,
|
|
ID_FILE_FULLSCREEN, ID_RATE_INCREASE,
|
|
ID_RATE_DECREASE, ID_RATE_NORMAL,
|
|
ID_RATE_HALF, ID_RATE_DOUBLE};
|
|
|
|
// Set/clear checkboxes that indicate the size of the video clip
|
|
for (int i=0; i<14; i++)
|
|
{
|
|
// Check the selected item
|
|
EnableMenuItem(ghMenu, (UINT) nItems[i],
|
|
(UINT) (bEnable) ? MF_ENABLED : MF_GRAYED);
|
|
}
|
|
}
|
|
|
|
|
|
LRESULT CALLBACK AboutDlgProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch (message)
|
|
{
|
|
case WM_INITDIALOG:
|
|
return TRUE;
|
|
|
|
case WM_COMMAND:
|
|
if (wParam == IDOK)
|
|
{
|
|
EndDialog(hWnd, TRUE);
|
|
return TRUE;
|
|
}
|
|
break;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
LRESULT CALLBACK WndMainProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
switch(message)
|
|
{
|
|
// Resize the video when the window changes
|
|
case WM_MOVE:
|
|
case WM_SIZE:
|
|
if ((hWnd == ghApp) && (!g_bAudioOnly))
|
|
MoveVideoWindow();
|
|
break;
|
|
|
|
// Enforce a minimum size
|
|
case WM_GETMINMAXINFO:
|
|
{
|
|
LPMINMAXINFO lpmm = (LPMINMAXINFO) lParam;
|
|
lpmm->ptMinTrackSize.x = MINIMUM_VIDEO_WIDTH;
|
|
lpmm->ptMinTrackSize.y = MINIMUM_VIDEO_HEIGHT;
|
|
}
|
|
break;
|
|
|
|
case WM_KEYDOWN:
|
|
|
|
switch(toupper((int) wParam))
|
|
{
|
|
// Frame stepping
|
|
case VK_SPACE:
|
|
case '1':
|
|
StepOneFrame();
|
|
break;
|
|
|
|
// Frame stepping (multiple frames)
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
StepFrames((int) wParam - '0');
|
|
break;
|
|
|
|
case VK_LEFT: // Reduce playback speed by 25%
|
|
ModifyRate(-0.25);
|
|
break;
|
|
|
|
case VK_RIGHT: // Increase playback speed by 25%
|
|
ModifyRate(0.25);
|
|
break;
|
|
|
|
case VK_DOWN: // Set playback speed to normal
|
|
SetRate(1.0);
|
|
break;
|
|
|
|
case 'P':
|
|
PauseClip();
|
|
break;
|
|
|
|
case 'S':
|
|
StopClip();
|
|
break;
|
|
|
|
case 'M':
|
|
ToggleMute();
|
|
break;
|
|
|
|
case 'F':
|
|
case VK_RETURN:
|
|
ToggleFullScreen();
|
|
break;
|
|
|
|
case 'H':
|
|
InitVideoWindow(1,2);
|
|
CheckSizeMenu(wParam);
|
|
break;
|
|
case 'N':
|
|
InitVideoWindow(1,1);
|
|
CheckSizeMenu(wParam);
|
|
break;
|
|
case 'D':
|
|
InitVideoWindow(2,1);
|
|
CheckSizeMenu(wParam);
|
|
break;
|
|
case 'T':
|
|
InitVideoWindow(3,4);
|
|
CheckSizeMenu(wParam);
|
|
break;
|
|
|
|
case VK_ESCAPE:
|
|
if (g_bFullscreen)
|
|
ToggleFullScreen();
|
|
else
|
|
CloseClip();
|
|
break;
|
|
|
|
case VK_F12:
|
|
case 'Q':
|
|
case 'X':
|
|
CloseClip();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WM_COMMAND:
|
|
|
|
switch(wParam)
|
|
{ // Menus
|
|
|
|
case ID_FILE_OPENCLIP:
|
|
// If we have ANY file open, close it and shut down DShow
|
|
if (g_psCurrent != Init)
|
|
CloseClip();
|
|
|
|
// Open the new clip
|
|
OpenClip();
|
|
break;
|
|
|
|
case ID_FILE_EXIT:
|
|
CloseClip();
|
|
PostQuitMessage(0);
|
|
break;
|
|
|
|
case ID_FILE_PAUSE:
|
|
PauseClip();
|
|
break;
|
|
|
|
case ID_FILE_STOP:
|
|
StopClip();
|
|
break;
|
|
|
|
case ID_FILE_CLOSE:
|
|
CloseClip();
|
|
break;
|
|
|
|
case ID_FILE_MUTE:
|
|
ToggleMute();
|
|
break;
|
|
|
|
case ID_FILE_FULLSCREEN:
|
|
ToggleFullScreen();
|
|
break;
|
|
|
|
case ID_HELP_ABOUT:
|
|
DialogBox(ghInst, MAKEINTRESOURCE(IDD_ABOUTBOX),
|
|
ghApp, (DLGPROC) AboutDlgProc);
|
|
break;
|
|
|
|
case ID_FILE_SIZE_HALF:
|
|
InitVideoWindow(1,2);
|
|
CheckSizeMenu(wParam);
|
|
break;
|
|
case ID_FILE_SIZE_NORMAL:
|
|
InitVideoWindow(1,1);
|
|
CheckSizeMenu(wParam);
|
|
break;
|
|
case ID_FILE_SIZE_DOUBLE:
|
|
InitVideoWindow(2,1);
|
|
CheckSizeMenu(wParam);
|
|
break;
|
|
case ID_FILE_SIZE_THREEQUARTER:
|
|
InitVideoWindow(3,4);
|
|
CheckSizeMenu(wParam);
|
|
break;
|
|
|
|
case ID_SINGLE_STEP:
|
|
StepOneFrame();
|
|
break;
|
|
|
|
case ID_RATE_DECREASE: // Reduce playback speed by 25%
|
|
ModifyRate(-0.25);
|
|
break;
|
|
case ID_RATE_INCREASE: // Increase playback speed by 25%
|
|
ModifyRate(0.25);
|
|
break;
|
|
case ID_RATE_NORMAL: // Set playback speed to normal
|
|
SetRate(1.0);
|
|
break;
|
|
case ID_RATE_HALF: // Set playback speed to 1/2 normal
|
|
SetRate(0.5);
|
|
break;
|
|
case ID_RATE_DOUBLE: // Set playback speed to 2x normal
|
|
SetRate(2.0);
|
|
break;
|
|
|
|
} // Menus
|
|
break;
|
|
|
|
|
|
case WM_GRAPHNOTIFY:
|
|
HandleGraphEvent();
|
|
break;
|
|
|
|
case WM_CLOSE:
|
|
SendMessage(ghApp, WM_COMMAND, ID_FILE_EXIT, 0);
|
|
break;
|
|
|
|
case WM_DESTROY:
|
|
PostQuitMessage(0);
|
|
break;
|
|
|
|
default:
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
|
|
} // Window msgs handling
|
|
|
|
// Pass this message to the video window for notification of system changes
|
|
if (pVW)
|
|
pVW->NotifyOwnerMessage((LONG_PTR) hWnd, message, wParam, lParam);
|
|
|
|
return DefWindowProc(hWnd, message, wParam, lParam);
|
|
}
|
|
|
|
|
|
int PASCAL WinMain(HINSTANCE hInstC, HINSTANCE hInstP, LPSTR lpCmdLine, int nCmdShow)
|
|
{
|
|
MSG msg={0};
|
|
WNDCLASS wc;
|
|
USES_CONVERSION;
|
|
|
|
// Initialize COM
|
|
if(FAILED(CoInitialize(NULL)))
|
|
{
|
|
Msg(TEXT("CoInitialize Failed!\r\n"));
|
|
exit(1);
|
|
}
|
|
|
|
// Was a filename specified on the command line?
|
|
if(lpCmdLine[0] != '\0')
|
|
#ifdef UNICODE
|
|
MultiByteToWideChar(CP_ACP, 0, lpCmdLine, -1, g_szFileName, MAX_PATH);
|
|
#else
|
|
lstrcpy(g_szFileName, lpCmdLine);
|
|
#endif
|
|
|
|
// Set initial media state
|
|
g_psCurrent = Init;
|
|
|
|
// Register the window class
|
|
ZeroMemory(&wc, sizeof wc);
|
|
wc.lpfnWndProc = WndMainProc;
|
|
ghInst = wc.hInstance = hInstC;
|
|
wc.lpszClassName = CLASSNAME;
|
|
wc.lpszMenuName = MAKEINTRESOURCE(IDR_MENU);
|
|
wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
|
|
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
|
wc.hIcon = LoadIcon(hInstC, MAKEINTRESOURCE(IDI_PLAYWND));
|
|
if(!RegisterClass(&wc))
|
|
{
|
|
Msg(TEXT("RegisterClass Failed! Error=0x%x\r\n"), GetLastError());
|
|
CoUninitialize();
|
|
exit(1);
|
|
}
|
|
|
|
// Create the main window. The WS_CLIPCHILDREN style is required.
|
|
ghApp = CreateWindow(CLASSNAME, APPLICATIONNAME,
|
|
WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_CLIPCHILDREN,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
CW_USEDEFAULT, CW_USEDEFAULT,
|
|
0, 0, ghInst, 0);
|
|
|
|
if(ghApp)
|
|
{
|
|
// Save menu handle for later use
|
|
ghMenu = GetMenu(ghApp);
|
|
|
|
// Open the specified media file or prompt for a title
|
|
PostMessage(ghApp, WM_COMMAND, ID_FILE_OPENCLIP, 0);
|
|
|
|
// Main message loop
|
|
while(GetMessage(&msg,NULL,0,0))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
Msg(TEXT("Failed to create the main window! Error=0x%x\r\n"), GetLastError());
|
|
}
|
|
|
|
// Finished with COM
|
|
CoUninitialize();
|
|
|
|
return (int) msg.wParam;
|
|
}
|
|
|
|
|