
/*	Copyright (C) G. Jespersen

	LAV Filters - Copyright (C) 2010-2013 Hendrik Leppkes http://www.1f0.de
	See LAV Licence.txt.*/

/*	Copyright (C) G. Jespersen
	See Licence.txt.*/

// 10/10/2020 Modified PlayVideoLav

#include "stdafx.h"
#include "DsLavSplit.h"
#include "Player.h"
#include "math.h"
#include "LAVSplitterSettings.h" // LAV Settings interfaces
#include "LAVVideoSettings.h"
#include "LAVAudioSettings.h"

#include "DShowUtil.cpp" // project-settings-link-input-ignore libraries "atlthunk.lib"

// test DS Debug - need Strmbase Strmbasd lib
#include "DshowUtil.h"
#include "dsound.h"		// new for vs2010

#include <dshow.h>

// 8/22 find
// DVD functions
// Play DVD

/*
// From LAV_CSLID.txt
LAV Video Decoder:		{EE30215D-164F-4A92-A4EB-9D4C13390F9F} LAVVideo_1.ax
						  { 0xEE30215D, 0x164F, 0x4A92, 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F }
LAV Audio Decoder:		{E8E73B6B-4CB3-44A4-BE99-4F7BCB96E491} LAVAudio_1.ax		
						  { 0xE8E73B6B, 0x4CB3, 0x44A4, 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91 }
LAV Splitter:			{171252A0-8820-4AFE-9DF8-5C92B2D66B04} LAVSplitter_1.ax
						  { 0x171252A0, 0x8820, 0x4AFE, 0x9D, 0xF8, 0x5C, 0x92, 0xB2, 0xD6, 0x6B, 0x04 }
LAV Splitter Source:	{B98D13E7-55DB-4385-A33D-09FD1BA26338} LAVSplitter_1.ax
						  { 0xB98D13E7, 0x55DB, 0x4385, 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38 }

Note - from:
 LAV Filters\LAV_0741\Source\LAVFilters-0.74.1_Source\LAVFilters-0.74.1\demuxer\LAVSplitter\LavSplitter.h
  class __declspec(uuid("171252A0-8820-4AFE-9DF8-5C92B2D66B04")) CLAVSplitter
  class __declspec(uuid("B98D13E7-55DB-4385-A33D-09FD1BA26338")) CLAVSplitterSource : public CLAVSplitter
*/

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

CPlayer::CPlayer()
{
	// Initial Video playback.
	m_pGraph			= NULL;
	m_pControl		= NULL;
	m_pEvent			= NULL;
	m_pSeeking		= NULL;
	m_pVidWin		= NULL;
	m_pBaseVid		= NULL;
	m_pBaseAudio		= NULL;
	m_redrawVid		= FALSE;
	m_muted			= FALSE;
	m_curVolPercent = 50;
	m_fsCurrent		= State_Stopped; // State_Stopped = 0, State_Paused, State_Running
	m_rtTotalTime	= 0;
	m_hWndVideo		= NULL;			// playback window
	m_hWndEvents		= NULL;			// callback, MsgBox
	m_nPlays			= 0;				// debug

	// DVD
	m_pDvdNav		= NULL;
	m_pIDvdMedia		= NULL;
	m_pIDvdControl2	= NULL;			// IDvdControl2 interface
	m_pIDvdInfo2		= NULL;			// IDvdInfo2 interface
	m_dvdPlaying		= FALSE;
	m_dvdCurChapter = 0;
	m_dvdCurTitle	= 0;
	ZeroMemory(&m_dvdCurDomain, sizeof(DVD_DOMAIN)); 

	// DVD seeking
	m_dvdCurTimeSec		= 0;
	m_dvdTotalTimeSec	= 0;
	m_dvdPrevChapter		= 0;
	m_dvdPrevTitle		= 0;
	m_dvdNumTitles		= 0;
	m_dvdNumChapters		= 0;			// this title
	m_nMenuButtons		= 0;			// set in HandleVideoEvent
	m_nMenuButtonSel		= 0;			// set in HandleVideoEvent
	ZeroMemory(&m_dvdPrevDomain, sizeof(DVD_DOMAIN)); 

	// CD Audio
	m_cdPlaying		= FALSE;			// used only for IsRunningCd().

	// LAV Filters - for smart pointers.
	m_pLavSrcFilter = NULL;
	m_pLavVidFilter = NULL;
	m_pLavAudFilter = NULL;

	// LAV Filters - for app managed pointers.
	m_pLavSrc		= NULL;
	m_pLavVid		= NULL;
	m_pLavAud		= NULL;

	// AsyncCd Filter - for app managed pointer.
	m_pAsyncCdSrc	= NULL;

	// Get current directory.
	TCHAR szPath[1024];
	::GetCurrentDirectory(sizeof (szPath) / sizeof(TCHAR), szPath);
	m_curDirectory = ((CDsLavApp*)AfxGetApp())->m_curDirectory;

	// Set LAV DLL paths per OS Version.
	SetLavPerOsVersion();

	// Set AsyncCd Path.
	CString AsyncCd = _T("AsyncCd");
	// Release
	m_pathAsyncCdSrc.Format(_T("%s\\%s\\AsyncCd.ax"), m_curDirectory, AsyncCd); 
	// Debug
	//m_pathAsyncCdSrc.Format(_T("%s\\%s\\%s\\AsyncCd.ax"), m_curDirectory, AsyncCd, _T("Debug")); 
}

CPlayer::~CPlayer()
{
	TRACE(_T("In CPlayer destructor.\n"));

	// Clean up. No - see CDsLavDoc::OnCloseDocument()
	//CleanPlayback();

	m_hWndVideo = NULL;
	m_hWndEvents = NULL;
}


// 6/16/2021
// Get version and set appropriate Lav Filters directory.
void CPlayer::SetLavPerOsVersion()
{
	//
	// Choose between LAV 0.70.2 for XP, or the latest LAV release.
	//
	// Note the latest LAV header files:
	//   LAVSplitterSettings.h, LAVAudioSettings.h, and LAVVideoSettings.h, 
	// work with the older 0.70.2 DLLs - the newer LAVVideoSettings.h has one 
	// additional method: STDMETHOD(SetEnableCCOutputPin)
	//

	// Set LAV DLL Paths for XP / 03 Server as default.
	// Uses LAV 0.70.2 max.
	CString lav = _T("LAV_XP");
	m_pathLavSrc.Format(_T("%s\\%s\\LAVSplitter_1.ax"), m_curDirectory, lav); 
	m_pathLavVid.Format(_T("%s\\%s\\LAVVideo_1.ax"), m_curDirectory, lav); 
	m_pathLavAud.Format(_T("%s\\%s\\LAVAudio_1.ax"), m_curDirectory, lav); 

	// Check for later OS.
	OSVERSIONINFOEX osvi;
	BOOL bOsVersionInfoEx;
	DWORD dwVersion = 0, dwMajorVersion = 0, dwMinorVersion = 0, dwBuild = 0;

	// Call GetVersionEx using the OSVERSIONINFOEX structure.
	ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX));
	osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX);

	if(!(bOsVersionInfoEx = GetVersionEx((OSVERSIONINFO *) &osvi)) )
	{
		// If OSVERSIONINFOEX doesn't work, try OSVERSIONINFO.
		osvi.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
		if(!GetVersionEx( (OSVERSIONINFO *) &osvi) )
		{
			return;
		}
	}
	// Gets the Major version
	dwMajorVersion = osvi.dwMajorVersion;

	// Gets the Minor version
	dwMinorVersion = osvi.dwMinorVersion; 

	// Get the build number. 
	dwBuild = osvi.dwBuildNumber & 0xFFFF;

	// If Vista or above, use latest LAV - 0.74.1 - 6/2021
	if(dwMajorVersion >= 6)
	{
		CString lav = _T("LAV");
		m_pathLavSrc.Format(_T("%s\\%s\\LAVSplitter_1.ax"), m_curDirectory, lav); 
		m_pathLavVid.Format(_T("%s\\%s\\LAVVideo_1.ax"), m_curDirectory, lav); 
		m_pathLavAud.Format(_T("%s\\%s\\LAVAudio_1.ax"), m_curDirectory, lav); 
	}
	/* On 03 Server X64
	Major = 5
	Minor = 2
	Build = 3790 	
	Major vers list:
	Windows 10 				10.0*
	Windows Server 2019 		10.0*
	Windows Server 2016		10.0*
	Windows 8.1 				6.3*
	Windows Server 2012 R2 	6.3*
	Windows 8 				6.2
	Windows Server 2012 		6.2
	Windows 7 				6.1
	Windows Server 2008 R2 	6.1
	Windows Server 2008 		6.0
	Windows Vista 			6.0
	Windows Server 2003 R2 	5.2
	Windows Server 2003 		5.2
	Windows XP 64-Bit 		5.2
	Windows XP 				5.1
	Windows 2000 			5.0	*/
}

// 1/26/2021
// Override for DsLavSplit. See notes at original.
// 
// This override calls CMainFrames SetVideoWindow() to switch
// views as needed per available streams.
//
// Called from:
//	CMainFrame::StartVideoPlayback
BOOL CPlayer::PlayVideoLav(const CString & path, CMainFrame * pMainFrame) 
{
	TRACE(_T("In CPlayer::PlayVideoLav override.\n"));

	// Get ptr for user settings.
	CDsLavDoc * pDoc = ((CDsLavApp*)AfxGetApp())->m_pDocument; 

	// Set WaitCursor - resets when out of scope.
	CWaitCursor wait;

	IPin * p1 = 0;
	IPin * p2 = 0;
	int ref = 0;
	HRESULT hr;
	CRect videoRect(0, 0, 0, 0);

	// Clean any currently playing - seems ok.
	if(m_fsCurrent != State_Stopped)
	{
		TRACE(_T("In CPlayer::PlayVideoLav stopping playback before restarting.\n"));
		if(!CleanPlayback())
			return FALSE;
	}
	//
	// Note AfxOleInit() must have been called in CDsLavApp::InitInstance()
	//

	////////////////////////////////////
	// Create the filter graph manager.
	hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, 
						  IID_IGraphBuilder, (void **)&m_pGraph);
	if(FAILED(hr))
		return CleanPlayback("ERROR - Could not create the Filter Graph Manager.");

	///////////////////////////////////////////////////
	// Set a dbg Log - file created only if a failure.
	#ifdef _DEBUG
	CString logPath = m_curDirectory;
	logPath += _T("\\DxLog.txt");
	HANDLE hFile = CreateFile(logPath, GENERIC_WRITE, 0, NULL, 
							  CREATE_ALWAYS,
							  FILE_ATTRIBUTE_NORMAL, NULL);
	hr = m_pGraph->SetLogFile((unsigned long)hFile);
	if(FAILED(hr))
		return CleanPlayback("ERROR - SetLogFile failed.");
	#endif

	//////////////////////////////////////////
	// Create the Lav Source Splitter filter.
	CLSID clsidLavSrc = { 0xB98D13E7, 0x55DB, 0x4385, 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38 };
	hr = CreateObjectFromPath(m_pathLavSrc, clsidLavSrc, &m_pLavSrc);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - CreateObjectFromPath2 failed - clsidLavSrc"));

	// Set default Lav Source Splitter settings.
	// Note: sample code for LAV settings interfaces, not necessary.
	ILAVFSettings * pSourceSettings;
	hr = m_pLavSrc->QueryInterface(IID_ILAVFSettings, (void **)&pSourceSettings);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - ILAVFSettings not supported"));
	hr = pSourceSettings->SetRuntimeConfig(TRUE); // Set to default. Changes are not saved.
	DWORD sz = pSourceSettings->GetMaxQueueMemSize();
	pSourceSettings->Release();

	// Load the media.
	IFileSourceFilter * pFileSource;
	hr = m_pLavSrc->QueryInterface(IID_IFileSourceFilter,(void **)&pFileSource);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - QueryInterface failed - IID_IFileSourceFilter"));
	_bstr_t s = path;
	hr = pFileSource->Load(s, NULL);
	if(FAILED(hr))
	{
		CString str;
		str.Format(_T("File Source Error - could not open %s."), path);
		return CleanPlayback(str);
	}
	// Add the Lav Source Splitter filter.
	hr = m_pGraph->AddFilter(m_pLavSrc, L"LAV Splitter Source");
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - AddFilter failed - m_pLavSrc"));
	pFileSource->Release();

	//////////////////////////////////////
	// Check for Video and Audio streams.
	BOOL haveAudio = FALSE, haveVideo = FALSE;
	hr = GetPin(m_pLavSrc, PINDIR_OUTPUT, MEDIATYPE_Video, &p1);
	if(SUCCEEDED(hr))
	{
		haveVideo = TRUE;
		ref = p1->Release(); // 3
	}
	hr = GetPin(m_pLavSrc, PINDIR_OUTPUT, MEDIATYPE_Audio, &p2);
	if(SUCCEEDED(hr))
	{
		haveAudio = TRUE;
		ref = p2->Release(); // 3
	}
	if(!haveAudio && !haveVideo) 
		return CleanPlayback("ERROR - Media type not supported.");

	/////////////////////////////////////////////////
	// Call CMainFrame to set:
	//  the video window based on available streams-
	//  the video window HWND and client rect-
	//  the HWND to recieve events-
	if(pMainFrame->SetVideoWindow(haveVideo, haveAudio, m_hWndVideo, m_hWndEvents, videoRect))
	{
		TRACE(_T("CPlayer::PlayVideoLav pMainFrame->SetVideoWindow returned true\n"));
	}
	else
	{
		TRACE(_T("CPlayer::PlayVideoLav pMainFrame->SetVideoWindow returned false\n"));
		return CleanPlayback("ERROR - SetVideoWindow function failed.");
	}

	//////////////////////
	// If a Video stream.
	if(haveVideo)
	{
		// Create the Lav Video Decoder filter.
		CLSID clsidLavVid = { 0xEE30215D, 0x164F, 0x4A92, 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F };
		hr = CreateObjectFromPath(m_pathLavVid, clsidLavVid, &m_pLavVid);
		if(FAILED(hr))
			return CleanPlayback(_T("ERROR - CreateObjectFromPath failed - clsidLavVid"));

		// Set default Lav Video Decoder settings.
		ILAVVideoSettings * pVideoSettings;
		hr = m_pLavVid->QueryInterface(IID_ILAVVideoSettings, (void **)&pVideoSettings);
		if(FAILED(hr))
			return CleanPlayback(_T("ERROR - ILAVVideoSettings not supported"));
		hr = pVideoSettings->SetRuntimeConfig(TRUE); // Set to default. Changes are not saved.
		//check.
		//BOOL bDvd = pVideoSettings->GetDVDVideoSupport();
		//TRACE(_T("CPlayer PlayVideoLav - DVD support = %d\n"), bDvd); // returns 1
		// test off - no change
		//pVideoSettings->SetDVDVideoSupport(FALSE);
		// Done settings.
		pVideoSettings->Release();

		// Add the LAV decoder.
		hr = m_pGraph->AddFilter(m_pLavVid, L"LAV Video Decoder");
		if(FAILED(hr))
			return CleanPlayback("ERROR - AddFilter failed - LAV Video Decoder.");

		// Connect Lav Splitter Source Video to Lav Video Decoder
		hr = GetPin(m_pLavSrc, PINDIR_OUTPUT, MEDIATYPE_Video, &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - GetPin - Out Lav Source Splitter Video.");
		hr = GetPin(m_pLavVid, PINDIR_INPUT, MEDIATYPE_Video, &p2);
		if(FAILED(hr))
		{
			p1->Release();
			return CleanPlayback("ERROR - GetPin - In Lav Video Decoder.");
		}
		hr = p1->Connect(p2, NULL);
		ref = p1->Release();
		ref = p2->Release();
		if(FAILED(hr))
			return CleanPlayback("ERROR - Connect failed - Lav Source to Video.");

		// Find and render the LAV Video decoder output pin. 
		hr = GetPin(m_pLavVid, PINDIR_OUTPUT, MEDIATYPE_Video, &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - GetPin - Out Video Decoder.");
		hr = m_pGraph->Render(p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - Render video failed.");
		ref = p1->Release();

		// Set Video interfaces.
		hr = m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&m_pVidWin);
		if(FAILED(hr))
			return CleanPlayback("ERROR - IVideoWindow not supported.");

		hr = m_pGraph->QueryInterface(IID_IBasicVideo, (void **)&m_pBaseVid);
		if(FAILED(hr))
			return CleanPlayback("ERROR - IBasicVideo not supported.");

		// Setup the video window to use our window handle
		hr = m_pVidWin->put_Owner((OAHWND)m_hWndVideo);
		if(FAILED(hr))
			return CleanPlayback("ERROR - put_Owner failed.");

		// Drain forewards windows msgs (WM_KEYUP etc) from the
		// video renderer to hwnd param.
		hr = m_pVidWin->put_MessageDrain((OAHWND)m_hWndVideo);
		if(FAILED(hr))
			return CleanPlayback("ERROR - put_MessageDrain failed.");

		// Set the style of the video window
		hr = m_pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);// | WS_CLIPCHILDREN);
		if(FAILED(hr))
			return CleanPlayback("ERROR - put_WindowStyle failed.");

		// Set video rect size to native vid scale.
		// Function cleans up if an error.
		if(!SetVideoRect(videoRect))
			return FALSE;	
	}

	///////////////////////
	// If an Audio stream.
	if(haveAudio)
	{
		// Create the Lav Audio Decoder filter.
		CLSID clsidLavAud = { 0xE8E73B6B, 0x4CB3, 0x44A4, 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91 };
		hr = CreateObjectFromPath(m_pathLavAud, clsidLavAud, &m_pLavAud);
		if(FAILED(hr))
			return CleanPlayback(_T("ERROR - CreateObjectFromPath failed - clsidLavAud"));

		// Get Audio Decoder settings interface.
		ILAVAudioSettings * pAudioSettings;
		hr = m_pLavAud->QueryInterface(IID_ILAVAudioSettings, (void **)&pAudioSettings);
		if(FAILED(hr))
			return CleanPlayback(_T("ERROR - ILAVAudioSettings not supported"));

		// Set LAV Audio settings per user.
		hr = pDoc->m_lavSetup.SetDsLavAudio(pAudioSettings);
		if(FAILED(hr))
			return CleanPlayback("ERROR - m_lavSetup.SetDsLavAudio failed - LAV Audio Decoder.");

		/* test replace with user settings
		hr = pAudioSettings->SetRuntimeConfig(TRUE); // Set to default. Changes are not saved.

		// 1/26/2021
		// Fixes WMA audio pin connect failures on 03_Serv X64. 
		// SetRuntimeConfig(TRUE) above turns these codecs off.
		// See LaAudioSettings.h for others, and Lav_audio_settings_code.txt.
		pAudioSettings->SetFormatConfiguration(Codec_WMA2, TRUE);
		pAudioSettings->SetFormatConfiguration(Codec_WMAPRO, TRUE);
		pAudioSettings->SetFormatConfiguration(Codec_WMALL, TRUE);
		// Fixes one mp4 file - no sound.
		pAudioSettings->SetMixingEnabled(TRUE);*/

		// Done with settings.
		pAudioSettings->Release();

		// Add the Lav Audio Decoder filter.
		hr = m_pGraph->AddFilter(m_pLavAud, L"LAV Audio Decoder");
		if(FAILED(hr))
			return CleanPlayback("ERROR - AddFilter failed - LAV Audio Decoder.");

		// Connect Lav Splitter Source Audio to Lav Audio Decoder
		hr = GetPin(m_pLavSrc, PINDIR_OUTPUT, MEDIATYPE_Audio, &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - GetPin - Out Lav Source Splitter Audio.");
		hr = GetPin(m_pLavAud, PINDIR_INPUT, MEDIATYPE_Audio, &p2);
		if(FAILED(hr))
		{
			p1->Release();
			return CleanPlayback("ERROR - GetPin - In Lav Audio Decoder.");
		}
		hr = p1->Connect(p2, NULL);
		ref = p1->Release();
		ref = p2->Release();
		if(FAILED(hr))
			return CleanPlayback("ERROR - Connect failed - Lav Source to Audio.");

		// Find and render the LAV Audio decoder output pin.
		hr = GetPin(m_pLavAud, PINDIR_OUTPUT, MEDIATYPE_Audio, &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - GetPin - Out Audio Decoder.");
		hr = m_pGraph->Render(p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - Render audio failed.");
		ref = p1->Release();
		
		// Set Audio interfaces.
		hr = m_pGraph->QueryInterface(IID_IBasicAudio, (void **)&m_pBaseAudio);
		if(FAILED(hr))
			return CleanPlayback("ERROR - IBasicAudio not supported.");

		// Set volume to normal - 50%.
		LONG norm = DxVolFromPercent(50);
		hr = m_pBaseAudio->put_Volume(norm);
		if(FAILED(hr))
			return CleanPlayback("ERROR - put_Volume failed.");
	}
	//////////////////////////
	// Get common interfaces.
	hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pControl);
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaControl not supported.");

	hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&m_pEvent); // IID_IMediaEvent
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaEventEx not supported.");

	hr = m_pGraph->QueryInterface(IID_IMediaSeeking, (void **)&m_pSeeking);
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaSeeking not supported.");

	// Have the graph signal event via window callbacks to the m_hWndEvents.
	// See GetVideoEvent().
	hr = m_pEvent->SetNotifyWindow((OAHWND)m_hWndEvents, WM_USER_VIDEOEVENT, 0);
	if(FAILED(hr))
		return CleanPlayback("ERROR - SetNotifyWindow failed.");

	//////////////////////////////////////////////////
	// Run the graph.
	// Note CMainFrame::OnVideoEvent() will be called 
	// when plaback is finished.
	TRACE(_T("Calling m_pControl Run\n"));

	hr = m_pControl->Run();

	TRACE(_T("m_pControl Run returned %d\n"), hr);
	if(FAILED(hr))
		return CleanPlayback("ERROR - Run failed.");

	// Check state - have not seen it fail.
	FILTER_STATE fs;
	TRACE(_T("PlayVideoLav calling GetState.\n"));
	hr = m_pControl->GetState(1000, (OAFilterState*)&fs);
	if(FAILED(hr)) 
	{
		TRACE(_T("ERROR PlayVideoLav GetState failed.\n"));
		return CleanPlayback("ERROR - GetState failed.");// handle error
	}
	if(fs != State_Running)
		return CleanPlayback("ERROR - not State_Running.");// handle error
	TRACE(_T("PlayVideoLav State_Running OK.\n"));

	// Set state
	m_fsCurrent = State_Running;

	// Prepare seeking.
	PrepareSeeking();

	// OK
	return TRUE;
}

// Play DVD.
// MSDN doc:
//  https://docs.microsoft.com/en-us/windows/win32/directshow/dvd-applications
//  https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/dvd-decoder-support-in-windows
//
// Called from:
//	CMainFrame::StartVideoPlayback
BOOL CPlayer::PlayDvdLav(CString path, CMainFrame * pMainFrame) 
{
	TRACE(_T("In CPlayer::PlayDvdLav.\n"));

	// Get ptr for user settings.
	CDsLavDoc * pDoc = ((CDsLavApp*)AfxGetApp())->m_pDocument; 

	// Set WaitCursor - resets when out of scope.
	CWaitCursor wait;

	// DvdNavigator has 3 pins - string ID's
	// used with IBaseFilter::FindPin below;
	CString strPinIdDvdNavVideo = _T("Video");
	CString strPinIdDvdNavAudio = _T("AC3");
	CString strPinIdDvdNavSub = _T("SubPicture");

	IPin * p1 = 0;
	IPin * p2 = 0;
	int ref = 0;
	HRESULT hr;
	CRect videoRect(0, 0, 0, 0);

	// Clean any currently playing - seems ok.
	if(m_fsCurrent != State_Stopped)
	{
		TRACE(_T("In CPlayer::PlayDvdLav stopping playback before restarting.\n"));
		if(!CleanPlayback())
			return FALSE;
	}

	////////////////////////////////////
	// Create the filter graph manager.
	hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, 
						  IID_IGraphBuilder, (void **)&m_pGraph);
	if(FAILED(hr))
		return CleanPlayback("ERROR - Could not create the Filter Graph Manager.");

	///////////////////////////////////////////////////
	// Set a dbg Log - file created only if a failure.
	#ifdef _DEBUG
	CString logPath = m_curDirectory;
	logPath += _T("\\DxLog.txt");
	HANDLE hFile = CreateFile(logPath, GENERIC_WRITE, 0, NULL, 
							  CREATE_ALWAYS,
							  FILE_ATTRIBUTE_NORMAL, NULL);
	hr = m_pGraph->SetLogFile((unsigned long)hFile);
	if(FAILED(hr))
		return CleanPlayback("ERROR - SetLogFile failed.");
	#endif

	///////////////////////////////////
	// Create the DVD Navigator filter
	hr = CoCreateInstance(CLSID_DVDNavigator, NULL, CLSCTX_INPROC_SERVER, 
						  IID_IBaseFilter, (void **)&m_pDvdNav);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - CreateObjectFromPath2 failed - clsidDvdNav"));

	// Add the DVD Navigator filter.
	hr = m_pGraph->AddFilter(m_pDvdNav, _T("DVD Navigator"));
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - AddFilter failed - m_pDvdNav"));

	// Get IDvdControl2 interface. 
	hr = m_pDvdNav->QueryInterface(IID_IDvdControl2, (void **)&m_pIDvdControl2);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - QueryInterface failed - IID_IDvdControl2"));

	// Get IID_IDvdInfo2 interface. error
	hr = m_pDvdNav->QueryInterface(IID_IDvdInfo2, (void **)&m_pIDvdInfo2);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - QueryInterface failed - IID_IDvdInfo2"));

	// Set directory.
	hr = m_pIDvdControl2->SetDVDDirectory(path);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - SetDVDDirectory failed - IID_IDvdControl2"));

	//////////////////////////////////////
	// Check for Video and Audio streams. fails
	BOOL haveAudio = FALSE, haveVideo = FALSE;

	// DvdNav Video.
	hr = m_pDvdNav->FindPin(strPinIdDvdNavVideo, &p1);
	if(SUCCEEDED(hr))
	{
		haveVideo = TRUE;
		ref = p1->Release();
	}
	// DvdNav Audio
	hr = m_pDvdNav->FindPin(strPinIdDvdNavAudio, &p2);
	if(SUCCEEDED(hr))
	{
		haveAudio = TRUE;
		ref = p2->Release();
	}
	// We want both.
	if(!haveAudio || !haveVideo) 
		return CleanPlayback("ERROR - Could not get both Audio and Video DVD streams.");

	/////////////////////////////////////////////////
	// Call CMainFrame to set:
	//  the video window based on available streams-
	//  the video window HWND and client rect-
	//  the HWND to recieve events-
	if(pMainFrame->SetVideoWindow(haveVideo, haveAudio, m_hWndVideo, m_hWndEvents, videoRect))
	{
		TRACE(_T("CPlayer::PlayDvdLav pMainFrame->SetVideoWindow returned true\n"));
	}
	else
	{
		TRACE(_T("CPlayer::PlayDvdLav pMainFrame->SetVideoWindow returned false\n"));
		return CleanPlayback("ERROR - SetVideoWindow function failed.");
	}
	//////////////////////
	// If a Video stream.
	if(haveVideo)
	{
		// Create the Lav Video Decoder filter.
		CLSID clsidLavVid = { 0xEE30215D, 0x164F, 0x4A92, 0xA4, 0xEB, 0x9D, 0x4C, 0x13, 0x39, 0x0F, 0x9F };
		hr = CreateObjectFromPath(m_pathLavVid, clsidLavVid, &m_pLavVid);
		if(FAILED(hr))
			return CleanPlayback(_T("ERROR - CreateObjectFromPath failed - clsidLavVid"));

		// Set default Lav Video Decoder settings.
		ILAVVideoSettings * pVideoSettings;
		hr = m_pLavVid->QueryInterface(IID_ILAVVideoSettings, (void **)&pVideoSettings);
		if(FAILED(hr))
			return CleanPlayback(_T("ERROR - ILAVVideoSettings not supported"));
		hr = pVideoSettings->SetRuntimeConfig(TRUE); // Set to default. Changes are not saved.

		// LAV DVD video support:
		// Setting this false will cause failure when connecting
		// to the DvdNavigator below.
		pVideoSettings->SetDVDVideoSupport(TRUE); // default-TRUE
		pVideoSettings->Release();

		// Add the LAV decoder.
		hr = m_pGraph->AddFilter(m_pLavVid, _T("LAV Video Decoder"));
		if(FAILED(hr))
			return CleanPlayback("ERROR - AddFilter failed - LAV Video Decoder.");

		// Connect DvdNavigator to Lav Video Decoder
		//
		hr = m_pDvdNav->FindPin(strPinIdDvdNavVideo, &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - GetPin - Out DvdNavigator.");

		hr = GetPin(m_pLavVid, PINDIR_INPUT, MEDIATYPE_DVD_ENCRYPTED_PACK, &p2);
		if(FAILED(hr))
		{
			p1->Release();
			return CleanPlayback("ERROR - GetPin - In Lav Video Decoder.");
		}
		hr = p1->Connect(p2, NULL);
		ref = p1->Release();
		ref = p2->Release();
		if(FAILED(hr))
			return CleanPlayback("ERROR - Connect failed - DvdNavigator to Video.");

		// Connect DvdNavigator sub-picture to Lav Video Decoder sub-title.
		//  See notes at DVDMouseSelect().
		//
		// Get DvdNav sub-picture pin.
		hr = m_pDvdNav->FindPin(_T("SubPicture"), &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - FindPin - Out - DvdNavigator sub-picture.");

		// Get LAV sub-title pin - Pin # 1 - Subtitle Input
		hr = GetPin2(m_pLavVid, PINDIR_INPUT, 1, &p2);
		if(FAILED(hr))
		{
			p1->Release();
			return CleanPlayback("ERROR - GetPin2 - In - Lav Video Decoder sub-title.");
		}
		// Connect DvdNav sub-picture to Lav sub-title.
		hr = p1->Connect(p2, NULL);
		ref = p1->Release();
		ref = p2->Release();
		if(FAILED(hr))
			return CleanPlayback("ERROR - Connect failed - DvdNavigator subpicture to LAv subtitle.");

		// Find and render the LAV Video decoder output pin. 
		hr = GetPin(m_pLavVid, PINDIR_OUTPUT, MEDIATYPE_Video, &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - GetPin - Out Video Decoder.");
		hr = m_pGraph->Render(p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - Render video failed.");
		ref = p1->Release();

		// Set Video interfaces.
		hr = m_pGraph->QueryInterface(IID_IVideoWindow, (void **)&m_pVidWin);
		if(FAILED(hr))
			return CleanPlayback("ERROR - IVideoWindow not supported.");

		hr = m_pGraph->QueryInterface(IID_IBasicVideo, (void **)&m_pBaseVid);
		if(FAILED(hr))
			return CleanPlayback("ERROR - IBasicVideo not supported.");

		// Setup the video window to use the window handle gotten 
		// with pMainFrame->SetVideoWindow() call above.
		hr = m_pVidWin->put_Owner((OAHWND)m_hWndVideo);
		if(FAILED(hr))
			return CleanPlayback("ERROR - put_Owner failed.");

		// Drain forewards windows msgs (WM_KEYUP etc) from the
		// video renderer to hwnd param.
		hr = m_pVidWin->put_MessageDrain((OAHWND)m_hWndVideo);
		if(FAILED(hr))
			return CleanPlayback("ERROR - put_MessageDrain failed.");

		// Set the style of the video window
		hr = m_pVidWin->put_WindowStyle(WS_CHILD | WS_CLIPSIBLINGS);// | WS_CLIPCHILDREN);
		if(FAILED(hr))
			return CleanPlayback("ERROR - put_WindowStyle failed.");

		// Set video rect size to native vid scale.
		// Function cleans up if an error.
		if(!SetVideoRect(videoRect))
			return FALSE;	
	}

	///////////////////////
	// If an Audio stream.
	if(haveAudio)
	{
		// Create the Lav Audio Decoder filter.
		CLSID clsidLavAud = { 0xE8E73B6B, 0x4CB3, 0x44A4, 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91 };
		hr = CreateObjectFromPath(m_pathLavAud, clsidLavAud, &m_pLavAud);
		if(FAILED(hr))
			return CleanPlayback(_T("ERROR - CreateObjectFromPath failed - clsidLavAud"));

		// Get Audio Decoder settings interface.
		ILAVAudioSettings * pAudioSettings;
		hr = m_pLavAud->QueryInterface(IID_ILAVAudioSettings, (void **)&pAudioSettings);
		if(FAILED(hr))
			return CleanPlayback(_T("ERROR - ILAVAudioSettings not supported"));

		// Set LAV Audio settings per user.
		hr = pDoc->m_lavSetup.SetDsLavAudio(pAudioSettings);
		if(FAILED(hr))
			return CleanPlayback("ERROR - m_lavSetup.SetDsLavAudio failed - LAV Audio Decoder.");

		// Done with settings.
		pAudioSettings->Release();

		// Add the Lav Audio Decoder filter.
		hr = m_pGraph->AddFilter(m_pLavAud, _T("LAV Audio Decoder"));
		if(FAILED(hr))
			return CleanPlayback("ERROR - AddFilter failed - LAV Audio Decoder.");

		// Connect DvdNavigator Source Audio to Lav Audio Decoder.
		//
		hr = m_pDvdNav->FindPin(strPinIdDvdNavAudio, &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - GetPin - Out DvdNavigator Audio.");

		hr = GetPin(m_pLavAud, PINDIR_INPUT, MEDIATYPE_DVD_ENCRYPTED_PACK, &p2);
		if(FAILED(hr))
		{
			p1->Release();
			return CleanPlayback("ERROR - GetPin - In Lav Audio Decoder.");
		}
		hr = p1->Connect(p2, NULL);
		ref = p1->Release();
		ref = p2->Release();
		if(FAILED(hr))
			return CleanPlayback("ERROR - Connect failed - DvdNavigator to Audio.");

		// Find and render the LAV Audio decoder output pin.
		hr = GetPin(m_pLavAud, PINDIR_OUTPUT, MEDIATYPE_Audio, &p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - GetPin - Out Audio Decoder.");
		hr = m_pGraph->Render(p1);
		if(FAILED(hr))
			return CleanPlayback("ERROR - Render audio failed.");
		ref = p1->Release();
		
		// Set Audio interfaces.
		hr = m_pGraph->QueryInterface(IID_IBasicAudio, (void **)&m_pBaseAudio);
		if(FAILED(hr))
			return CleanPlayback("ERROR - IBasicAudio not supported.");

		// Set volume to normal - 50%.
		LONG norm = DxVolFromPercent(50);
		hr = m_pBaseAudio->put_Volume(norm);
		if(FAILED(hr))
			return CleanPlayback("ERROR - put_Volume failed.");
	}

	//////////////////////////
	// Get common interfaces.
	hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pControl);
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaControl not supported.");

	hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&m_pEvent); // IID_IMediaEvent
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaEventEx not supported.");

	hr = m_pGraph->QueryInterface(IID_IMediaSeeking, (void **)&m_pSeeking);
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaSeeking not supported.");

	// Have the graph signal event via window callbacks to the m_hWndEvents.
	// See GetVideoEvent().
	hr = m_pEvent->SetNotifyWindow((OAHWND)m_hWndEvents, WM_USER_VIDEOEVENT, 0);
	if(FAILED(hr))
		return CleanPlayback("ERROR - SetNotifyWindow failed.");

	//////////////////////////////////////////////////
	// Run the graph.
	// Note CMainFrame::OnVideoEvent() will be called 
	// when plaback is finished.
	TRACE(_T("Calling m_pControl Run\n"));

	hr = m_pControl->Run();

	TRACE(_T("m_pControl Run returned %d\n"), hr);
	if(FAILED(hr))
		return CleanPlayback("ERROR - Run failed.");

	// Check state - have not seen it fail.
	FILTER_STATE fs;
	TRACE(_T("PlayDvdLav calling GetState.\n"));
	hr = m_pControl->GetState(1000, (OAFilterState*)&fs);
	if(FAILED(hr)) 
	{
		TRACE(_T("ERROR PlayVideoLav GetState failed.\n"));
		return CleanPlayback("ERROR - GetState failed.");
	}
	if(fs != State_Running)
		return CleanPlayback("ERROR - not State_Running.");
	TRACE(_T("PlayDvdLav State_Running OK.\n"));

	// Set state
	m_fsCurrent = State_Running;

	// Set flag
	m_dvdPlaying = TRUE;

	// Prepare seeking.
	PrepareSeeking(); 

	// OK
	return TRUE;
}

// Play CD music disc: .cda cdda
// Called from:
//	CMainFrame::StartVideoPlayback
// Path is a .cda file - i.e E:\Track01.cda.
BOOL CPlayer::PlayCdLav(CString path, CMainFrame * pMainFrame) 
{
	//
	// Note: Using AsyncDC release path. 
	//  See m_pathAsyncCdSrc init in constructor 
	//	 for Debug path alternative.
	//
	TRACE(_T("In CPlayer::PlayCdLav.\n"));

	// Get ptr for user settings.
	CDsLavDoc * pDoc = ((CDsLavApp*)AfxGetApp())->m_pDocument; 

	// Set WaitCursor - resets when out of scope.
	CWaitCursor wait;

	// AsyncCd.ax has one output pin "Output", "AsyncCD Filter"
	CString strPinIdAsyncCD = _T("Output");

	IPin * p1 = 0;
	IPin * p2 = 0;
	int ref = 0;
	HRESULT hr;
	CRect videoRect(0, 0, 0, 0);

	// Clean any currently playing - seems ok.
	if(m_fsCurrent != State_Stopped)
	{
		TRACE(_T("In CPlayer::PlayVideoLav stopping playback before restarting.\n"));
		if(!CleanPlayback())
			return FALSE;
	}
	//
	// Note AfxOleInit() must have been called in CDsLavApp::InitInstance()
	//

	////////////////////////////////////
	// Create the filter graph manager.
	hr = CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER, 
						  IID_IGraphBuilder, (void **)&m_pGraph);
	if(FAILED(hr))
		return CleanPlayback("ERROR - Could not create the Filter Graph Manager.");

	///////////////////////////////////////////////////
	// Set a dbg Log - file created only if a failure.
	#ifdef _DEBUG
	CString logPath = m_curDirectory;
	logPath += _T("\\DxLog.txt");
	HANDLE hFile = CreateFile(logPath, GENERIC_WRITE, 0, NULL, 
							  CREATE_ALWAYS,
							  FILE_ATTRIBUTE_NORMAL, NULL);
	hr = m_pGraph->SetLogFile((unsigned long)hFile);
	if(FAILED(hr))
		return CleanPlayback("ERROR - SetLogFile failed.");
	#endif

	///////////////////////////////////////////////
	// Create the AsyncCd source filter:
	//   CLSID:   { 0440ADA7-00DF-492d-87AA-51A150876202 }
	//   Major:   MEDIATYPE_Stream {E436EB83-524F-11CE-9F53-0020AF0BA770}  -uuids.h
	//   Subtype: MEDIASUBTYPE_WAVE {E436EB8B-524F-11CE-9F53-0020AF0BA770} -uuids.h
	//   ObjName: AsyncCD Filter
	CLSID clsidAsyncCd = { 0x440ada7, 0xdf, 0x492d, 0x87, 0xaa, 0x51, 0xa1, 0x50, 0x87, 0x62, 0x2 };			

	// Using release path.
	hr = CreateObjectFromPath(m_pathAsyncCdSrc, clsidAsyncCd, &m_pAsyncCdSrc);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - CreateObjectFromPath failed - clsidAsyncCd"));

	// Load the .cda media.
	IFileSourceFilter * pFileSource;
	hr = m_pAsyncCdSrc->QueryInterface(IID_IFileSourceFilter,(void **)&pFileSource);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - QueryInterface failed - IID_IFileSourceFilter"));
	_bstr_t s = path;

	hr = pFileSource->Load(s, NULL); // {"E:\Track01.cda"} failed
	if(FAILED(hr))
	{
		CString str;
		str.Format(_T("File Source Error - could not open %s."), path);
		return CleanPlayback(str);
	}
	
	// Add the AsyncCD Filter.
	hr = m_pGraph->AddFilter(m_pAsyncCdSrc, _T("AsyncCD Filter"));
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - AddFilter failed - m_pAsyncCdSrc"));

	///////////////////////////////////
	// Create the Lav Splitter filter.
	//
	// This CSLID is for the "LAV Splitter Source"
	//CLSID clsidLavSrc = { 0xB98D13E7, 0x55DB, 0x4385, 0xA3, 0x3D, 0x09, 0xFD, 0x1B, 0xA2, 0x63, 0x38 };

	// This CSLID is for the "LAV Splitter".
	//   It appears "LAV Splitter Source" has no input pin.
	CLSID clsidLavSrc = { 0x171252A0, 0x8820, 0x4AFE, 0x9D, 0xF8, 0x5C, 0x92, 0xB2, 0xD6, 0x6B, 0x04 };
	hr = CreateObjectFromPath(m_pathLavSrc, clsidLavSrc, &m_pLavSrc);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - CreateObjectFromPath2 failed - clsidLavSrc"));

	// Set default Lav Source Splitter settings.
	// Note: sample code for LAV settings interfaces, not necessary.
	ILAVFSettings * pSourceSettings;
	hr = m_pLavSrc->QueryInterface(IID_ILAVFSettings, (void **)&pSourceSettings);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - ILAVFSettings not supported"));
	hr = pSourceSettings->SetRuntimeConfig(TRUE); // Set to default. Changes are not saved.
	DWORD sz = pSourceSettings->GetMaxQueueMemSize();
	pSourceSettings->Release();

	// Add the Lav Source Splitter filter.
	hr = m_pGraph->AddFilter(m_pLavSrc, L"LAV Splitter Source");
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - AddFilter failed - m_pLavSrc"));
	pFileSource->Release();

	// Connect the AsyncCD filter to the Lav Source Splitter.
	hr = m_pAsyncCdSrc->FindPin(strPinIdAsyncCD, &p1);
	if(FAILED(hr))
	{
		return CleanPlayback("ERROR - GetPin - Out AsyncCD Filter.");
	}
	hr = GetPin(m_pLavSrc, PINDIR_INPUT, MEDIATYPE_Stream, &p2);
	if(FAILED(hr))
	{
		p1->Release();
		return CleanPlayback("ERROR - GetPin - In Lav Video Decoder.");
	}
	hr = p1->Connect(p2, NULL);
	ref = p1->Release();
	ref = p2->Release();
	if(FAILED(hr))
		return CleanPlayback("ERROR - Connect failed - Lav Source to Video.");

	///////////////////////////
	// Check for  Audio stream.
	BOOL haveAudio = FALSE, haveVideo = FALSE;
	hr = GetPin(m_pLavSrc, PINDIR_OUTPUT, MEDIATYPE_Audio, &p1);
	if(SUCCEEDED(hr))
	{
		haveAudio = TRUE;
		ref = p1->Release(); 
	}
	if(!haveAudio) 
		return CleanPlayback("ERROR - Media type not supported.");

	/////////////////////////////////////////////////
	// Call CMainFrame to set:
	//  the video window based on available streams-
	//  the video window HWND and client rect-
	//  the HWND to recieve events-
	if(pMainFrame->SetVideoWindow(haveVideo, haveAudio, m_hWndVideo, m_hWndEvents, videoRect))
	{
		TRACE(_T("CPlayer::PlayVideoLav pMainFrame->SetVideoWindow returned true\n"));
	}
	else
	{
		TRACE(_T("CPlayer::PlayVideoLav pMainFrame->SetVideoWindow returned false\n"));
		return CleanPlayback("ERROR - SetVideoWindow function failed.");
	}

	/////////////////////////
	// Set the Audio stream.
	//
	// Create the Lav Audio Decoder filter.
	CLSID clsidLavAud = { 0xE8E73B6B, 0x4CB3, 0x44A4, 0xBE, 0x99, 0x4F, 0x7B, 0xCB, 0x96, 0xE4, 0x91 };
	hr = CreateObjectFromPath(m_pathLavAud, clsidLavAud, &m_pLavAud);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - CreateObjectFromPath failed - clsidLavAud"));

	// Get Audio Decoder settings interface.
	ILAVAudioSettings * pAudioSettings;
	hr = m_pLavAud->QueryInterface(IID_ILAVAudioSettings, (void **)&pAudioSettings);
	if(FAILED(hr))
		return CleanPlayback(_T("ERROR - ILAVAudioSettings not supported"));

	// Set LAV Audio settings per user.
	hr = pDoc->m_lavSetup.SetDsLavAudio(pAudioSettings);
	if(FAILED(hr))
		return CleanPlayback("ERROR - m_lavSetup.SetDsLavAudio failed - LAV Audio Decoder.");

	// Done with settings.
	pAudioSettings->Release();

	// Add the Lav Audio Decoder filter.
	hr = m_pGraph->AddFilter(m_pLavAud, L"LAV Audio Decoder");
	if(FAILED(hr))
		return CleanPlayback("ERROR - AddFilter failed - LAV Audio Decoder.");

	// Connect Lav Splitter Source Audio to Lav Audio Decoder
	hr = GetPin(m_pLavSrc, PINDIR_OUTPUT, MEDIATYPE_Audio, &p1);
	if(FAILED(hr))
		return CleanPlayback("ERROR - GetPin - Out Lav Source Splitter Audio.");
	hr = GetPin(m_pLavAud, PINDIR_INPUT, MEDIATYPE_Audio, &p2);
	if(FAILED(hr))
	{
		p1->Release();
		return CleanPlayback("ERROR - GetPin - In Lav Audio Decoder.");
	}
	hr = p1->Connect(p2, NULL);
	ref = p1->Release();
	ref = p2->Release();
	if(FAILED(hr))
		return CleanPlayback("ERROR - Connect failed - Lav Source to Audio.");

	// Find and render the LAV Audio decoder output pin.
	hr = GetPin(m_pLavAud, PINDIR_OUTPUT, MEDIATYPE_Audio, &p1);
	if(FAILED(hr))
		return CleanPlayback("ERROR - GetPin - Out Audio Decoder.");
	hr = m_pGraph->Render(p1);
	if(FAILED(hr))
		return CleanPlayback("ERROR - Render audio failed.");
	ref = p1->Release();
	
	// Set Audio interfaces.
	hr = m_pGraph->QueryInterface(IID_IBasicAudio, (void **)&m_pBaseAudio);
	if(FAILED(hr))
		return CleanPlayback("ERROR - IBasicAudio not supported.");

	// Set volume to normal - 50%.
	LONG norm = DxVolFromPercent(50);
	hr = m_pBaseAudio->put_Volume(norm);
	if(FAILED(hr))
		return CleanPlayback("ERROR - put_Volume failed.");

	//////////////////////////////
	// Set the common interfaces.
	hr = m_pGraph->QueryInterface(IID_IMediaControl, (void **)&m_pControl);
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaControl not supported.");

	hr = m_pGraph->QueryInterface(IID_IMediaEventEx, (void **)&m_pEvent); // IID_IMediaEvent
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaEventEx not supported.");

	hr = m_pGraph->QueryInterface(IID_IMediaSeeking, (void **)&m_pSeeking);
	if(FAILED(hr))
		return CleanPlayback("ERROR - IMediaSeeking not supported.");

	// Have the graph signal event via window callbacks to the m_hWndEvents.
	// See GetVideoEvent().
	hr = m_pEvent->SetNotifyWindow((OAHWND)m_hWndEvents, WM_USER_VIDEOEVENT, 0);
	if(FAILED(hr))
		return CleanPlayback("ERROR - SetNotifyWindow failed.");

	//////////////////////////////////////////////////
	// Run the graph.
	// Note CMainFrame::OnVideoEvent() will be called 
	// when plaback is finished.
	TRACE(_T("Calling m_pControl Run\n"));

	hr = m_pControl->Run();

	TRACE(_T("m_pControl Run returned %d\n"), hr);
	if(FAILED(hr))
		return CleanPlayback("ERROR - Run failed.");

	// Check state - have not seen it fail.
	FILTER_STATE fs;
	TRACE(_T("PlayVideoLav calling GetState.\n"));
	hr = m_pControl->GetState(1000, (OAFilterState*)&fs);
	if(FAILED(hr)) 
	{
		TRACE(_T("ERROR PlayVideoLav GetState failed.\n"));
		return CleanPlayback("ERROR - GetState failed.");// handle error
	}
	if(fs != State_Running)
		return CleanPlayback("ERROR - not State_Running.");// handle error
	TRACE(_T("PlayVideoLav State_Running OK.\n"));

	// Set state
	m_fsCurrent = State_Running;

	// CD flag - used only for IsRunningCd().
	m_cdPlaying = TRUE;

	// Prepare seeking.
	PrepareSeeking();

	// OK
	return TRUE;
}

// From GDCL - uses smart pointers.
//  http://www.gdcl.co.uk/2011/June/UnregisteredFilters.htm
HRESULT CPlayer::CreateObjectFromPath(CString path, REFCLSID clsid, IUnknown** ppUnk)
{
	// Load the target DLL directly.
	HMODULE lib = LoadLibrary(path);
	if(!lib)
	{
		return HRESULT_FROM_WIN32(GetLastError());
	}

	// The entry point is an exported function.
	FN_DLLGETCLASSOBJECT fn = (FN_DLLGETCLASSOBJECT)GetProcAddress(lib, "DllGetClassObject");
	if(fn == NULL)
	{
		return HRESULT_FROM_WIN32(GetLastError());
	}

	// Create a class factory.
	IUnknownPtr pUnk;
	HRESULT hr = fn(clsid,  IID_IUnknown,  (void**)(IUnknown**)&pUnk);
	if (SUCCEEDED(hr))
	{
		IClassFactoryPtr pCF = pUnk;
		if (pCF == NULL)
		{
			hr = E_NOINTERFACE;
		}
		else
		{
			// Ask the class factory to create the object.
			hr = pCF->CreateInstance(NULL, IID_IUnknown, (void**)ppUnk);
		}
	}
	return hr;
}

// Get IBaseFilter ptr from its path and clsid.
// Release() must be called on ptr after use.
// Hacked from: 
//  http://www.gdcl.co.uk/2011/June/UnregisteredFilters.htm  and
//	https://raw.github.com/william0wang/dshownative/master/dump.cpp
HRESULT CPlayer::CreateObjectFromPath(CString path, REFCLSID clsid, 
										  IBaseFilter ** pBaseFilter)
{
	DllGetBaseFilterFunc pFunc;
	HRESULT hr;

	// Load the target DLL directly.
	HMODULE lib = AfxLoadLibrary(path); // or LoadLibrary
	if(!lib)
	{
		return HRESULT_FROM_WIN32(GetLastError());
	}

	// The entry point is an exported function.
	pFunc = (DllGetBaseFilterFunc)GetProcAddress(lib,"DllGetClassObject");
	if(pFunc == NULL)
	{
		return HRESULT_FROM_WIN32(GetLastError());
	}

	// Create a class factory.
	IClassFactory * pCf = NULL;
	hr = pFunc(clsid, IID_IClassFactory, (void**)&pCf);
	if(FAILED(hr))
		return hr;

	// Ask the class factory to create an instance.
	IUnknown * pUnk = NULL;
	hr = pCf->CreateInstance(NULL, IID_IUnknown, (void **)&pUnk);
	if(FAILED(hr))
		return hr;
	pCf->Release();

	// Get the IBaseFilter ptr.
	hr = pUnk->QueryInterface(IID_IBaseFilter, (void **)pBaseFilter);
	if(FAILED(hr))
		return hr;
	pUnk->Release();

	// Ensure.
	if(!pBaseFilter)
		return S_FALSE;

	return S_OK;
}

// From msdn - Working with Pin Categories
HRESULT CPlayer::GetPin(IBaseFilter * pF,		// Pointer to the filter to search.
						PIN_DIRECTION PinDir,	// Direction of the pin.
						const GUID& Category,	// Pin category.
						IPin **ppPin)			// Receives a pointer to the pin.
{
	if(!pF || !ppPin) 
		return E_POINTER;

	*ppPin = 0;
	HRESULT hr;
	IEnumPins *pEnum = 0;
	if (SUCCEEDED(pF->EnumPins(&pEnum)))
	{
		IPin *pPin = 0;
		while (hr = pEnum->Next(1, &pPin, 0), hr == S_OK)
		{
			PIN_DIRECTION ThisPinDir;
			hr = pPin->QueryDirection(&ThisPinDir);
			if (FAILED(hr))
			{
				pPin->Release();
				pEnum->Release();
				return E_UNEXPECTED; // Something strange happened.
			}
			if( (ThisPinDir == PinDir) && PinMatchesCategory(pPin, Category, PinDir) )
			{
				*ppPin = pPin;  // Caller must release the interface.
				pEnum->Release();
				return S_OK;
			}
			pPin->Release();
		}
		pEnum->Release();
    }
    // No pin matches.
    return E_FAIL;
}

// From msdn - Working with Pin Categories - but with AM_MEDIA_TYPE.
BOOL CPlayer::PinMatchesCategory(IPin * pPin, const GUID & Category, PIN_DIRECTION PinDir)
{
    if(pPin == NULL) 
		return E_POINTER;

	// Check for a major media type match.
	BOOL bFound = FALSE;
	int nMedTypes = 0;
	IEnumMediaTypes * ppEnum;
	AM_MEDIA_TYPE	* amType = NULL;
	int ref = 0;
	
	HRESULT hr = pPin->EnumMediaTypes(&ppEnum);
	if(SUCCEEDED(hr))
	{
		ULONG fetched;
		while(ppEnum->Next(1, &amType, &fetched) == S_OK)// fails with lav input
		{
			nMedTypes ++;
			if(amType->majortype == Category)
			{
				bFound = TRUE;
				ref = ppEnum->Release();
				ppEnum = NULL;

				// Delete a heap allocated AM_MEDIA_TYPE structure
				UtilDeleteMediaType(amType);
				amType = NULL;
				break;
			}
			// Delete a heap allocated AM_MEDIA_TYPE structure
			UtilDeleteMediaType(amType);
			amType = NULL;
		}
		// Release IEnumMediaTypes
		if(ppEnum)
			ref = ppEnum->Release();
	}

	// If no media types available and looking for an input pin, 
	// go for it as some inputs will not report a media type.
	if(!nMedTypes && !bFound && PinDir == PINDIR_INPUT)
		bFound = TRUE;

	return bFound;
}

// GetPin() from dshowutil renamed.
HRESULT CPlayer::GetPin2(IBaseFilter * pFilter, PIN_DIRECTION dirrequired, 
						 int iNum, IPin **ppPin)
{
    CComPtr< IEnumPins > pEnum;
    *ppPin = NULL;

    if (!pFilter)
       return E_POINTER;

    HRESULT hr = pFilter->EnumPins(&pEnum);
    if(FAILED(hr)) 
        return hr;

    ULONG ulFound;
    IPin *pPin;
    hr = E_FAIL;

    while(S_OK == pEnum->Next(1, &pPin, &ulFound))
    {
        PIN_DIRECTION pindir = (PIN_DIRECTION)3;

        pPin->QueryDirection(&pindir);
        if(pindir == dirrequired)
        {
            if(iNum == 0)
            {
                *ppPin = pPin;  // Return the pin's interface
                hr = S_OK;      // Found requested pin, so clear error
                break;
            }
            iNum--;
        } 

        pPin->Release();
    } 

    return hr;
}

// Clean up video playback error and show message.
// Always return FALSE.
BOOL CPlayer::CleanPlayback(const CString & errMsg)
{
	CleanPlayback();
	TRACE(_T("CPlayer::CleanPlayback - Error msg %s\n"), errMsg);
	MessageBox(m_hWndEvents, errMsg, _T("DsLav"), MB_OK);

	// Error 
	return FALSE;
}

// Clean up video playback error and show msg param, dx msg, and hresult.
// Always return FALSE.
BOOL CPlayer::CleanPlayback(const CString & errMsg, int hr)
{
	CleanPlayback();

	CString am, msg;
	TCHAR temp[1024]; 
	AMGetErrorText(hr, temp, sizeof(TCHAR) * 1024);
	am = temp;
	msg.Format(_T("%s\n%s\nCode %d"), errMsg, am, hr);
	MessageBox(m_hWndEvents, msg, _T("DsLav"), MB_OK);

	// Error 
	return FALSE;
}

// Clean up video playback.
// Return TRUE unless cleanup fails.
BOOL CPlayer::CleanPlayback()
{
	TRACE(_T("In CPlayer::CleanPlayback.\n"));

	HRESULT hr;
	BOOL ret = TRUE;
	int ref = 0;

	// Stop the graph (first).
	if(m_pControl)
	{
		hr = m_pControl->Stop();
		if(FAILED(hr))
		{
			if(m_hWndEvents)
			{
				MessageBox(m_hWndEvents, 
						   _T("ERROR CleanPlayback - Stop failed."), 
						   _T("DsLav"), MB_OK);
			}
			ret = FALSE;
		}
	}

	// Use the WaitForCompletion method to wait for the stop operation 
	// to complete. See msdn Using Event Notification.
	long evCode;
	if(m_pEvent)
	{
		hr = m_pEvent->WaitForCompletion(INFINITE, &evCode);
		if(FAILED(hr))
		{
			// Fails if the graph is not in or transitioning into a running state.
		}
	}

	// Clean video window.
	if(m_pVidWin)
	{
		hr = m_pVidWin->put_Visible(OAFALSE);
		if(FAILED(hr))
		{
			if(m_hWndEvents)
			{
				MessageBox(m_hWndEvents, 
						   _T("ERROR CleanPlayback - put_Visable failed."), 
						   _T("DsLav"), MB_OK);
			}
			ret = FALSE;
		}

		hr = m_pVidWin->put_Owner(NULL);
		if(FAILED(hr))
		{
			if(m_hWndEvents)
			{
				MessageBox(m_hWndEvents, 
						   _T("ERROR CleanPlayback - put_Owner failed."), 
						   _T("DsLav"), MB_OK);
			}
			ret = FALSE;
		}
	}

	// Debug - show the filters actually used.
	#ifdef _DEBUG
	EnumFilters();
	#endif*

	// Release interfaces.
	if(m_pSeeking)
		ref = m_pSeeking->Release();
	
	if(m_pEvent)
	{		
		m_pEvent->SetNotifyWindow(NULL, 0, 0);// test new fix crash
		ref = m_pEvent->Release();
	}
	if(m_pControl)
		ref = m_pControl->Release();
	if(m_pBaseAudio)
		ref = m_pBaseAudio->Release();	
	if(m_pBaseVid)
		ref = m_pBaseVid->Release();
	if(m_pVidWin)
		ref = m_pVidWin->Release();
	// DVD
	if(m_pDvdNav)
		ref = m_pDvdNav->Release();
	if(m_pIDvdMedia)
		ref = m_pIDvdMedia->Release();
	if(m_pIDvdControl2)
		ref = m_pIDvdControl2->Release();
	if(m_pIDvdInfo2)
		ref = m_pIDvdInfo2->Release();

	// AsyncCd 
	if(m_pAsyncCdSrc)
	{
		m_pAsyncCdSrc->SetSyncSource(NULL);
		m_pGraph->RemoveFilter(m_pAsyncCdSrc);
		ref = m_pAsyncCdSrc->Release();
	}

	// LAV Filters - app managed pointers.
	if(m_pLavAud)
	{
		m_pLavAud->SetSyncSource(NULL);
		m_pGraph->RemoveFilter(m_pLavAud);
		ref = m_pLavAud->Release();
	}
	if(m_pLavVid)
	{
		m_pLavVid->SetSyncSource(NULL);
		m_pGraph->RemoveFilter(m_pLavVid);
		ref = m_pLavVid->Release();
	}
	if(m_pLavSrc)
	{
		m_pLavSrc->SetSyncSource(NULL);
		m_pGraph->RemoveFilter(m_pLavSrc);
		ref = m_pLavSrc->Release();
	}

	// Release the graph.
	if(m_pGraph)
	{
		ref = m_pGraph->Release();
		if(ref != 0)
		{
			TRACE(_T("ERROR CPlayer CleanPlayback final ref count not zero.\n"));
		}
	}

	// Set ptrs etc.
	m_pGraph			= NULL;
	m_pControl		= NULL;
	m_pEvent			= NULL;
	m_pVidWin		= NULL;
	m_pBaseVid		= NULL;
	m_pBaseAudio		= NULL;
	m_pSeeking		= NULL;

	// Flags, state.------
	m_redrawVid		= FALSE;
	m_muted			= FALSE;
	m_fsCurrent		= State_Stopped;

	// DVD
	m_pDvdNav		= NULL;
	m_pIDvdControl2	= NULL;
	m_pIDvdInfo2		= NULL;
	m_dvdPlaying		= FALSE;
	m_redrawVid		= FALSE;
	m_muted			= FALSE;
	m_fsCurrent		= State_Stopped;

	// LAV Filters - app managed pointers.
	m_pLavSrc		= NULL;
	m_pLavVid		= NULL;
	m_pLavAud		= NULL;

	// AsyncCd
	m_pAsyncCdSrc	= NULL;
	m_cdPlaying		= FALSE;

	// Misc.
	m_rtTotalTime = 0;

	return ret;
}

///////////////////////////
// Control Video Playback.

// Called from PlayVideo(), OnSizeVideo().
BOOL CPlayer::SetVideoRect(CRect rect)
{
	HRESULT hr;

	// Ensure a valid Video stream. Not used if only Audio.
	if(!m_pGraph		 ||
	   !m_pVidWin		 || 
	   !m_pBaseVid		 || 
	   rect.IsRectEmpty() )
	{
		TRACE(_T("CPlayer SetVideoRect no video stream.\n"));
		return FALSE;
		//return CleanPlayback("ERROR - SetVideoRect null interface.");
	}

	double y = rect.Height();
	double x = rect.Width();

	// Get video size.
	LONG width, height;
	hr = m_pBaseVid->GetVideoSize(&width, &height);
	if(FAILED(hr))
		return CleanPlayback("ERROR - GetVideoSize failed.");

	// Get video size ratio.
	double ratio = (double)width / (double)height;

	// Maximize the video height.
	if( y * ratio <= x )
	{
		x -= y * ratio;
		x /= 2;

		// Set width.
		rect.DeflateRect((int)x, 0);
	}

	// Maximize the video width.
	else if( x / ratio <= y )
	{
		y -= x / ratio;
		y /= 2;

		// Set height.
		rect.DeflateRect(0, (int)y);
	}
	else
		return CleanPlayback("ERROR - SetVideoRect error.");

	// Set the video size.
	int horz = rect.Width();
	int vert = rect.Height();
	hr = m_pVidWin->SetWindowPosition(rect.left, rect.top, horz, vert);
	if(FAILED(hr))
		return CleanPlayback("ERROR - SetWindowPosition failed.");

	// Ok
	return TRUE;
}	

// Toggle pause / depause.
BOOL CPlayer::TogglePlaybackPause()
{
    HRESULT hr = S_OK;

	// Not stopped.
    if(!m_pControl || m_fsCurrent == State_Stopped)
        return FALSE;

    // If running, pause
    if(m_fsCurrent == State_Running)
	{
		hr = m_pControl->Pause();
		if (FAILED(hr))
			return FALSE;
		m_fsCurrent = State_Paused;
	}
	// If paused, run.
    else if(m_fsCurrent == State_Paused)
	{
		// Resume playback
		hr = m_pControl->Run();
		if(FAILED(hr))
			return FALSE;
		m_fsCurrent = State_Running;
	}
	return TRUE;
}

// Pause the video. For deactivate.
void CPlayer::PausePlayback()
{
	// If not paused.
	if(m_fsCurrent == State_Running)
		TogglePlaybackPause();
}

// Set visability.
void CPlayer::PutVisible(BOOL hide)
{
	if(!m_pVidWin)
		return;

	// Hide
	if(hide)
		m_pVidWin->put_Visible(OAFALSE);
	// Show.
	else
		m_pVidWin->put_Visible(OATRUE);
}

// Mute demute volume.
void CPlayer::ToggleVolumeMute()
{
	if(m_pBaseAudio == NULL)
		return;

    HRESULT hr = S_OK;

	long lVolume;

    // Read current volume
    hr = m_pBaseAudio->get_Volume(&lVolume);
    if(hr == E_NOTIMPL)
        return;

    else if(FAILED(hr))
		return;

	// Demute.
	if(lVolume == DSBVOLUME_MIN) // -10000 muted
	{
		// Set to last volume
		LONG cur = DxVolFromPercent(m_curVolPercent);
		hr = m_pBaseAudio->put_Volume(cur);
		if(FAILED(hr))
			return;
		m_muted = FALSE;

	}
	// Mute
	else
	{
		// Set to mute volume
		hr = m_pBaseAudio->put_Volume(DSBVOLUME_MIN);
		if(FAILED(hr))
			return;
		m_muted = TRUE;
	}
}

// Step the volume up/down.
// Called from OnMouseWheel().
BOOL CPlayer::StepVolume(BOOL stepUp, LONG & newVol)
{
    HRESULT hr = S_OK;

	if(m_muted || m_pBaseAudio == NULL)
		return FALSE;

	// Adjust percent - step by 4%.
	int percent = m_curVolPercent;
	percent += stepUp ? 4 : -4;
	if(percent > 100)
		percent = 100;
	if(percent < 0)
		percent = 0;

	// Get dx value.
	LONG vol = DxVolFromPercent(percent);

	// Set.
	hr = m_pBaseAudio->put_Volume(vol);
	if(FAILED(hr))
		return FALSE;

	m_curVolPercent = percent;
	newVol = percent;
	return TRUE;
}

BOOL CPlayer::StopPlayback()
{
	TRACE(_T("In CPlayer StopPlayback calling CleanPlayback\n"));
	return CleanPlayback();
}

// Video playback is starting - prepare seeking.
void CPlayer::PrepareSeeking()
{
	if(m_fsCurrent != State_Running)
		return;

	// DVD
	if(m_dvdPlaying)
		return;

	// Set up playback time.
	m_rtTotalTime = 0;

	if(m_pSeeking) 
	{
		DWORD caps = AM_SEEKING_CanSeekAbsolute | 
					 AM_SEEKING_CanGetDuration; 
		if(m_pSeeking->CheckCapabilities(&caps) == S_OK) // fails for DVD
		{
			// Get total vid time.
			if(m_pSeeking->GetDuration(&m_rtTotalTime) != S_OK)
			{
				TRACE(_T("ERROR CPlayer PrepareSeeking GetDuration failed\n"));
				m_rtTotalTime = 0;
			}
		}
		else
		{
			TRACE(_T("ERROR CPlayer PrepareSeeking CheckCapabilities failed\n"));
			m_rtTotalTime = 0;
		}
	}
}

// 8/22
////////////////////////////////////////////////////////////////////////
// DVD functions
//
// See:
//  https://docs.microsoft.com/en-us/windows/win32/directshow/dvd-basics 
//	https://en.wikibooks.org/wiki/Category:Book:Inside_DVD-Video
//  https://docs.microsoft.com/en-us/windows/win32/directshow/dvd-applications
//  https://docs.microsoft.com/en-us/windows-hardware/drivers/stream/dvd-decoder-support-in-windows
//	https://www.drdobbs.com/architecture-and-design/inside-dvd/184410609
//  https://forum.doom9.org/showthread.php?t=133828 - getting chapter time

// DVD playback is starting - prepare seeking.
// Called from HandleVideoEvent().
void CPlayer::PrepareSeekingDvd()
{
	// Functions involved with seeking:
	//	CPlayer::PlaybackGetCurTime
	//	CPlayer::PlaybackTimeSliderMoved
	//	CPlayer::PlaybackTimeSliderMovedKeyboard	8/22
	//
	// Note: The ControlsBar slider gets set to 1000 divs,
	// with some 400 pix to choose from. A dvd with 4 hrs
	// of play would have 14500 seconds / 400 pix = 30 sec
	// seek intervals - good enough for the DsLav crowd.
	//	See: CControlsBar::OnVidStart()
	//
	TRACE(_T("In CPlayer::PrepareSeekingDvd.\n")); // 8/22

	// Zero.
	m_dvdTotalTimeSec = 0;

	// Ensure running.
	//
	if(m_fsCurrent != State_Running)
		return;
	if(!m_dvdPlaying || !m_pIDvdControl2)
		return;

	//////////////////////////////////////////////
	// Get the total playback time in seconds for 
	// the current title. 
	DVD_HMSF_TIMECODE timeCode;
	HRESULT hr;
	hr = m_pIDvdInfo2->GetTotalTitleTime(&timeCode, NULL);
	if(FAILED(hr))
	{
		TRACE(_T("In CPlayer::PrepareSeekingDvd GetTotalTitleTime failed.\n"));
		return;
	}
	// Set as seconds.
	m_dvdTotalTimeSec = GetTimeSeconds2(timeCode);
	//  Cast to DVD_TIMECODE.
	DVD_TIMECODE tc = *(DVD_TIMECODE*) &timeCode;

	//dbg
	TRACE(_T("In CPlayer::PrepareSeekingDvd GetTotalTitleTime - Total seconds = %u\n"), 
				m_dvdTotalTimeSec);
	TRACE(_T("  Title = %u\n"),   m_dvdCurTitle);
	TRACE(_T("  Chapter = %u\n"), m_dvdCurChapter);
	// Results for disc with:  Title 1 - 3:59:26 = 14,366 sec, 17 chapters,  
	//	GetTotalTitleTime - Time = 31	 Title = 5 Chapter = 1
	//	GetTotalTitleTime - Time = 13166 Title = 1 Chapter = 1

	////////////////////////
	// Get number of Titles.
	ULONG numVolumes = 0;
	ULONG thisVolNumber = 0;
	DVD_DISC_SIDE diskSide; // side A or B
	m_dvdNumTitles = 0;
	hr = m_pIDvdInfo2->GetDVDVolumeInfo(&numVolumes, &thisVolNumber, 
										&diskSide, (ULONG*)&m_dvdNumTitles);

	if(FAILED(hr))
	{
		TRACE(_T("In CPlayer::PrepareSeekingDvd GetCurrentVolumeInfo failed.\n"));
		return;
	}

	///////////////////////////////
	// Get chapters in this title.
	m_dvdNumChapters = DvdGetNumChaptersInTitle(m_dvdCurTitle);

	// Inform DvdNav dlg changes have occured
	//  if DvdNav available. 8/22
	HWND hWndDvdNav = ((CDsLavApp*)AfxGetApp())->m_hwndDvdNavDlg;
	if(hWndDvdNav)
	{
		TRACE(_T("In CPlayer::PrepareSeekingDvd Posting to DvdNavDlg.\n"));
		::PostMessage(hWndDvdNav, WM_USER_DVD_NAV_UPDATE, 0, 0);
	}
	// 8/22 - update ControlsBar ??
}
/*pre 8/22--------
// DVD playback is starting - prepare seeking.
// Called from HandleVideoEvent().
void CPlayer::PrepareSeekingDvd()
{
	// Note: The ControlsBar slider gets set to 1000 divs,
	// with some 400 pix to choose from. A dvd with 4 hrs
	// of play would have 14500 seconds / 400 pix = 30 sec
	// seek intervals - good enough for the DsLav crowd.
	//	See: CControlsBar::OnVidStart()

	// Zero.
	m_dvdTotalTimeSec = 0;

	// Ensure running.
	//
	if(m_fsCurrent != State_Running)
		return;
	if(!m_dvdPlaying || !m_pIDvdControl2)
		return;

	//////////////////////////////////////////////
	// Get the total playback time in seconds for 
	// the current title. 
	DVD_HMSF_TIMECODE timeCode;
	HRESULT hr;
	hr = m_pIDvdInfo2->GetTotalTitleTime(&timeCode, NULL);
	if(FAILED(hr))
	{
		TRACE(_T("In CPlayer::PrepareSeekingDvd GetTotalTitleTime failed.\n"));
		return;
	}
	// Set as seconds.
	m_dvdTotalTimeSec = GetTimeSeconds2(timeCode);

	//dbg
	TRACE(_T("In CPlayer::PrepareSeekingDvd GetTotalTitleTime - Total seconds = %u\n"), 
				m_dvdTotalTimeSec);
	TRACE(_T("  Title = %u\n"),   m_dvdCurTitle);
	TRACE(_T("  Chapter = %u\n"), m_dvdCurChapter);
	// Results for disc with:  Title 1 - 3:59:26 = 14,366 sec, 17 chapters,  
	//	GetTotalTitleTime - Time = 31	 Title = 5 Chapter = 1
	//	GetTotalTitleTime - Time = 13166 Title = 1 Chapter = 1

	/////////////////////////
	// Get number of Titles.
	ULONG numVolumes = 0;
	ULONG thisVolNumber = 0;
	DVD_DISC_SIDE diskSide; // side A or B
	m_dvdNumTitles = 0;
	hr = m_pIDvdInfo2->GetDVDVolumeInfo(&numVolumes, &thisVolNumber, 
										&diskSide, (ULONG*)&m_dvdNumTitles);
	if(FAILED(hr))
	{
		TRACE(_T("In CPlayer::PrepareSeekingDvd GetCurrentVolumeInfo failed.\n"));
		return;
	}

	///////////////////////////////
	// Get chapters in this title.
	m_dvdNumChapters = DvdGetNumChaptersInTitle(m_dvdCurTitle);

	// Inform DvdNav dlg changes have occured.
	TRACE(_T("In CPlayer::PrepareSeekingDvd Posting to DvdNavDlg.\n"));
	HWND hWndDvdNav = ((CDsLavApp*)AfxGetApp())->m_hwndDvdNavDlg;
	if(hWndDvdNav)
		::PostMessage(hWndDvdNav, WM_USER_DVD_NAV_UPDATE, 0, 0);
}*/

// Play dvd title - chapter.
void CPlayer::DvdTitleChapterPlay(UINT title,  UINT chapter)
{
	if(!m_pIDvdControl2)
		return;
	// Call ChapterPlay().
	HRESULT hr;
	hr = m_pIDvdControl2->PlayChapterInTitle(title, chapter, NULL, NULL); 

	if(SUCCEEDED(hr))
	{
		// 8/22-2
		// Update members.
		m_dvdCurChapter = chapter;
		m_dvdCurTitle = title;

		TRACE(_T("CPlayer::DvdTitleChapterPlay success for title %u, chapter %u.\n"),
					title, chapter);
	}
	else
		TRACE(_T("CPlayer::DvdTitleChapterPlay failed for title %u, chapter %u.\n"),
					title, chapter);	
}

UINT CPlayer::DvdGetNumChaptersInTitle(UINT title)
{
	UINT numChapters = 0;
	HRESULT hr;

	if(!m_pIDvdInfo2)
		return 0;
	// Get chapters in this title.
	hr = m_pIDvdInfo2->GetNumberOfChapters((ULONG)title, (ULONG*)&numChapters);

	if(FAILED(hr))
	{
		TRACE(_T("CPlayer::DvdGetNumChaptersInTitle GetNumberOfChapters failed.\n"));
	}
	return numChapters;
}

// Show a title or root menu. Note most discs do not allow the viewer 
// to show a menu while in First Play domain.
void CPlayer::DvdMenuCall(DVD_MENU_ID id)
{
	HRESULT hr;
	// DVD_MENU_Title = 2, DVD_MENU_Root, DVD_MENU_Subpicture,
	// DVD_MENU_Audio, DVD_MENU_Angle, DVD_MENU_Chapter.

	if(!m_pIDvdControl2)
		return;
	hr = m_pIDvdControl2->ShowMenu(id, 0, NULL);
//	hr = m_pIDvdControl2->ShowMenu(id, DVD_CMD_FLAG_Block | DVD_CMD_FLAG_Flush, NULL);no change

	if(SUCCEEDED(hr))
	{	
		TRACE(_T("CPlayer::DvdMenuCall MenuCall succeded.\n"));
		return;
	}
	else
		TRACE(_T("CPlayer::DvdMenuCall MenuCall failed.\n"));
	
}

// Called from CDsLavVideoView::OnMouseMove.
//  Selecting a button highlights its borders.
void CPlayer::DvdMouseSelect(POINT point, POINT pointScreen)
{
	/*-----------------------------------------------------------
	DvdNavigator provides menu selection functionality through
	its sub-picture pin.

	If the pin is not connected, IDvdControl::MouseSelect() 
	(or IDvdControl2::SelectAtPosition()) returns E_FAIL.

	See PlayDvdLav() for pin connection details.
	--------------------------------------------------------------*/

	// Ensure
	if(!m_pIDvdControl2)
		return;

	// Restrict to domains. MouseSelect is valid in any domain.
	if(m_dvdCurDomain != DVD_DOMAIN_VideoManagerMenu	&&
	   m_dvdCurDomain != DVD_DOMAIN_VideoTitleSetMenu	)	//&&
	   //m_dvdCurDomain != DVD_DOMAIN_Title				)	// some menus are in title
	{
		CString strDomain;	// 8/22
		if(m_dvdCurDomain == DVD_DOMAIN_FirstPlay)
			strDomain = _T("DVD_DOMAIN_FirstPlay");
		else if(m_dvdCurDomain == DVD_DOMAIN_VideoManagerMenu)
			strDomain = _T("DVD_DOMAIN_VideoManagerMenu");
		else if(m_dvdCurDomain == DVD_DOMAIN_VideoTitleSetMenu)
			strDomain = _T("DVD_DOMAIN_VideoTitleSetMenu");
		else if(m_dvdCurDomain == DVD_DOMAIN_Title)
			strDomain = _T("DVD_DOMAIN_Title");
		else if(m_dvdCurDomain == DVD_DOMAIN_Stop)
			strDomain = _T("DVD_DOMAIN_Stop");
		TRACE(_T("CPlayer::DvdMouseSelect wrong domain=%s.\n"), strDomain);
		return;
	}
	// Must have a button selected - members are set in
	// HandleVideoEvent() case EC_DVD_BUTTON_CHANGE.
	if(!m_nMenuButtons || !m_nMenuButtonSel || m_nMenuButtonSel > m_nMenuButtons) 
	{
		TRACE(_T("CPlayer::DvdMouseSelect invalid button numbers.\n"));
		return;
	}

	// Select.	
	HRESULT hr;	
	hr = m_pIDvdControl2->SelectAtPosition(point);
	if(SUCCEEDED(hr))
	{
		TRACE(_T("CPlayer::DvdMouseSelect Select succeded.\n"));
		TRACE(_T(" ptX = %d ptY = %d.\n"), point.x, point.y);
		TRACE(_T(" nButtons = %d nButtonSel = %d.\n"), m_nMenuButtons, m_nMenuButtonSel);
		return;
	}
	else
	{
	//	TRACE(_T("CPlayer::DvdMouseSelect Select failed hr = %#x\n"), hr);
	//	TRACE(_T(" nButtons = %d nButtonSel = %d.\n"), m_nMenuButtons, m_nMenuButtonSel);
		CString msg;		// 8/22
		TCHAR temp[1024]; 
		AMGetErrorText(hr, temp, sizeof(TCHAR) * 1024);
		msg = temp;
		TRACE(_T("CPlayer::DvdMouseSelect MouseSelect failed hr = %#x\n"), hr);
		TRACE(_T(" nButtons = %d nButtonSel = %d msg=%s.\n"), m_nMenuButtons, m_nMenuButtonSel, msg);

	}
	// Note IDvdControl::GetCurrentButton() reports correct numbers
	// only after a call to MouseActivate - not usefull.
}

// Called from CDsLavVideoView::OnLButtonDown.
//  Selects and activates a DVD button in response to a mouse click.
void CPlayer::DvdMouseActivate(POINT point)
{
	// Ensure.
	if(!m_pIDvdControl2)
		return;

	// Activate.	
	HRESULT hr;	
	//
	hr = m_pIDvdControl2->ActivateAtPosition(point);

	if(SUCCEEDED(hr))
	{
		TRACE(_T("CPlayer::DvdMouseActivate Activate succeded.\n"));
		TRACE(_T(" ptX = %d ptY = %d.\n"), point.x, point.y);
		TRACE(_T(" nButtons = %d nButtonSel = %d.\n"), m_nMenuButtons, m_nMenuButtonSel);
		return;
	}
	else
	{
		TRACE(_T("CPlayer::DvdMouseActivate Activate failed hr = %#x\n"), hr);
		TRACE(_T(" ptX = %d ptY = %d.\n"), point.x, point.y);
		TRACE(_T(" nButtons = %d nButtonSel = %d.\n"), m_nMenuButtons, m_nMenuButtonSel);
	}
}

// Get percent volume (0 to 100) from dx volume (0 to -10000).
int CPlayer::DxVolToPercent(LONG dxVol)
{
	// Calcs hacked From: 
	//	https://qt.gitorious.org/qt/qt/source/f2d89dee45e2f08e8e7613c18660c5c3e133c0ff:src/
	//	plugins/mediaservices/directshow/mediaplayer/directshowplayercontrol.cpp

	// Muted
	if(dxVol <= DSBVOLUME_MIN)	// -10000
		return 0;
	// Max
	if(dxVol >= DSBVOLUME_MAX)	// 0
		return 100;

	double d = 100.0 * pow( 10.0, (double)dxVol / 5000.0 );

	int percent = (int)d;
	if(percent > 100)
		percent = 100;
	if(percent < 0)
		percent = 0;

	return percent;
}

// Get dx volume (0 to -10000) from percent (0 to 100).
LONG CPlayer::DxVolFromPercent(int percent)
{
	if(percent <= 0)
		return DSBVOLUME_MIN; // -10000
	else if(percent >= 100)
		return DSBVOLUME_MAX; // 0

	double d1 = (double)percent / 100.0;
	double d2 = fabs( log10(d1) ) * -5000;

	// Verify
	LONG vol = (LONG)d2;
	if(vol < DSBVOLUME_MIN)
		return DSBVOLUME_MIN;
	if(vol > DSBVOLUME_MAX)
		return DSBVOLUME_MAX;

	return vol;
}

// DVD Nav Dlg access.
CString CPlayer::DvdGetTotalTimeStr()
{
	CString str;
	DVD_TIMECODE tc = GetTimeFromSeconds(m_dvdTotalTimeSec);
	UINT hr = tc.Hours10 * 10 + tc.Hours1;
	UINT min = tc.Minutes10 * 10 + tc.Minutes1;
	UINT sec = tc.Seconds10 * 10 + tc.Seconds1;
	str.Format(_T("Hr: %u Min: %u Sec: %u"), hr, min, sec);
	return str;
}
CString CPlayer::DvdGetCurDomainStr()
{
	CString str;
	switch(m_dvdCurDomain)
	{
	case DVD_DOMAIN_FirstPlay:
		str = _T("First Play");
		break;
	case DVD_DOMAIN_VideoManagerMenu:
		str = _T("Video Manager Menu");
		break;
	case DVD_DOMAIN_VideoTitleSetMenu:
		str = _T("Video Title Set Menu");
		break;
	case DVD_DOMAIN_Title:
		str = _T("Title");
		break;
	case DVD_DOMAIN_Stop:
		str = _T("Stop");
		break;
	default:
		str = _T("None");
		break;
	}
	return str;
}

// Handle video events.
// Called from: CMainFrame::OnVideoEvent() - WM_USER_VIDEOEVENT.
BOOL CPlayer::HandleVideoEvent(CMainFrame * pFrm)
{
	if(!m_pEvent)
	{
		TRACE(_T("CPlayer::HandleVideoEvent m_pEvent NULL\n"));
		return FALSE;
	}

	LONG code, param1, param2, timeOut = 0;
	BOOL ret = TRUE;
	CString msg;
	CStringArray msgAr;
	HRESULT hr;

	// Check all events.
	while(SUCCEEDED(m_pEvent->GetEvent(&code, &param1, &param2, timeOut)) ) 
	{
		// Free first.
		m_pEvent->FreeEventParams(code, param1, param2);
		LONG codebase = code - EC_DVDBASE;
		if(code != EC_DVD_CURRENT_TIME) // every second
		{
			TRACE(_T("In CPlayer::HandleVideoEvent code: %d, 0x%x, EC_DVDBASE + 0x%x\n"), 
						code, code, codebase);
		}

		switch(code)
		{
			//////////////////////
			// File & DVD events:
			case EC_OPENING_FILE:
				TRACE(_T("CPlayer::HandleVideoEvent EC_OPENING_FILE\n"));
				break;
			case EC_ACTIVATE:
				TRACE(_T("CPlayer::HandleVideoEvent EC_ACTIVATE\n"));
				break;
			// 8/22 additions
			case EC_CLOCK_CHANGED:			// 13 0x0D
				TRACE(_T("CPlayer::HandleVideoEvent EC_CLOCK_CHANGED\n"));
				break;
			case EC_VIDEO_SIZE_CHANGED:		// 10 0x0A
				TRACE(_T("CPlayer::HandleVideoEvent EC_VIDEO_SIZE_CHANGED\n"));
				break;
			case EC_VMR_RENDERDEVICE_SET:	// 83 0x53
				TRACE(_T("CPlayer::HandleVideoEvent EC_VMR_RENDERDEVICE_SET\n"));
				break;

			// Playback has finished automatically.
			case EC_COMPLETE:
			case EC_USERABORT:
			{
				TRACE(_T("CPlayer::HandleVideoEvent calling StopVideoPlayback\n"));

				// Stop the playback.
				pFrm->StopVideoPlayback(TRUE); 
				// Player will be shut down - no more events.
				return TRUE;
			}
			case EC_ERRORABORT:
				TRACE(_T("ERROR CPlayer::HandleVideoEvent EC_ERRORABORT\n"));
				break;

			case EC_PAUSED:							// 0x0E 7/22
				TRACE(_T("CPlayer::HandleVideoEvent EC_PAUSED\n"));
				break;

			/////////////////////////////////
			// DVD events: - from DvdSample:
			case EC_DVD_AUDIO_STREAM_CHANGE:		// EC_DVDBASE + 0x04
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_AUDIO_STREAM_CHANGE\n"));
				break;
			case EC_DVD_SUBPICTURE_STREAM_CHANGE:	// EC_DVDBASE + 0x05
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_SUBPICTURE_STREAM_CHANGE\n"));
				break;
			// DVD can provide 9 camera angles from which to view the same scene. 8/22 comment 
			case EC_DVD_ANGLE_CHANGE:				// EC_DVDBASE + 0x06
				break;
			case EC_DVD_VALID_UOPS_CHANGE:			// EC_DVDBASE + 0x08
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_VALID_UOPS_CHANGE\n")); // 8/22
				break;
			case EC_DVD_BUTTON_AUTO_ACTIVATED:		// DVD_DOMAIN_MENU
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_BUTTON_AUTO_ACTIVATED\n"));
				break;

			// A Menu button has the mouse over it.
			// Note CDsLavVideoView::OnMouseMove() recieves player 
			// mouse move messages and calls DvdMouseSelect().
			case EC_DVD_BUTTON_CHANGE:				// EC_DVDBASE + 0x07
			{
				// lParam1 is the number of available buttons.
				// lParam2 is the current selected button number.
				m_nMenuButtons = (DWORD)param1;;
				m_nMenuButtonSel = (DWORD)param2;
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_BUTTON_CHANGE\n"));
				TRACE(_T("Num buttons = %d Selected button = %d\n"), 
							m_nMenuButtons, m_nMenuButtonSel);
				break;
			}

			// Save the Current playback time for title - IDvdInfo
			// DVD_TIMECODE 
			//  Only signaled for One_Sequential_PGC_Titles. 8/22
			case EC_DVD_CURRENT_TIME:				
			{
				DVD_TIMECODE tc = *(DVD_TIMECODE*) &param1;
				m_dvdCurTimeSec = GetTimeSeconds(tc);
	
				/*dbg
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_CURRENT_TIME\n"));
				TRACE(_T("CPlayer::HandleVideoEvent TIME h10-%d h1-%d m10-%d m1-%d s10-%d s1-%d \n"),
						tc.Hours10, tc.Hours1, tc.Minutes10, tc.Minutes1,
						tc.Seconds10, tc.Seconds1);*/
				break;
			}
			// Save the Current playback time for title - IDvdInfo2
			// DVD_HMSF_TIMECODE
			case EC_DVD_CURRENT_HMSF_TIME: 
			{
				DVD_HMSF_TIMECODE tc = *(DVD_HMSF_TIMECODE*)&param1;
				m_dvdCurTimeSec = GetTimeSeconds2(tc);
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_CURRENT_HMSF_TIME\n"));
				TRACE(_T("CPlayer::HandleVideoEvent TIME %d hr. %d mn. %d sec. %d frames\n"),
						tc.bHours, tc.bMinutes, tc.bSeconds, tc.bFrames);
				break;
			}

			// Save the current Chapter and set up/update seeking.
			// Note any title/domain event change should be set already,
			// but an angle change event comes later.
			//  Only signaled for One_Sequential_PGC_Titles. 8/22
			case EC_DVD_CHAPTER_START:
			{
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_CHAPTER_START chapter = %u\n"),
							(ULONG)(param1));
				m_dvdCurChapter = (ULONG)(param1);

				// Prepare seeking.
				PrepareSeekingDvd();
				break;
			}
			// Save current Title.
			// 8/22 test update seeking.
			case EC_DVD_TITLE_CHANGE:			// EC_DVDBASE + 0x02
			{
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_TITLE_CHANGE title = %u\n"),
							(ULONG)(param1));
				m_dvdCurTitle = (ULONG)(param1);
				break;
			}
			// Indicates that the DVD disc does not have a FP_PGC (First Play Program Chain)
			// and the DVD Navigator will not automatically load any PGC and start playback.
			case EC_DVD_NO_FP_PGC:
			{
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_NO_FP_PGC calling TitlePlay\n"));
				if(m_pIDvdControl2)
				{
					hr = m_pIDvdControl2->PlayTitle(1, 0, NULL);
					if(FAILED(hr))
					{
						TRACE(_T("CPlayer::HandleVideoEvent TitlePlay failed\n"));
					}
				}
				break;
			}

			// Save current Domain.
			case EC_DVD_DOMAIN_CHANGE:				// EC_DVDBASE + 0x01
			{
				TRACE(_T("CPlayer::HandleVideoEvent DVD_DOMAIN_CHANGE\n"));

				// Set member.
				m_dvdCurDomain = (DVD_DOMAIN)param1;

				// Info dbg.
				switch(param1)
				{
					case DVD_DOMAIN_FirstPlay:  // = 1
						TRACE(_T("CPlayer::HandleVideoEvent DVD_DOMAIN_FirstPlay\n"));
						break;
					case DVD_DOMAIN_Stop:       // = 5
						TRACE(_T("CPlayer::HandleVideoEvent DVD_DOMAIN_Stop\n"));
						break;
					case DVD_DOMAIN_VideoManagerMenu:  // = 2
						TRACE(_T("CPlayer::HandleVideoEvent DVD_DOMAIN_VideoManagerMenu\n"));
						break;
					case DVD_DOMAIN_VideoTitleSetMenu: // = 3
						TRACE(_T("CPlayer::HandleVideoEvent DVD_DOMAIN_VideoTitleSetMenu\n"));
						/* Inform the app to update the menu option to show "Resume" now */
						break;
					case DVD_DOMAIN_Title:      // = 4
						TRACE(_T("CPlayer::HandleVideoEvent DVD_DOMAIN_Title\n"));
						/* Inform the app to update the menu option to show "Menu" again */
						break;
				}
				// End of domain change switch.
				break;
			}
			case EC_DVD_PARENTAL_LEVEL_CHANGE:
			{
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_PARENTAL_LEVEL_CHANGE\n"));
				/*
				TCHAR szMsg[1000];
				hr = StringCchPrintf( szMsg, NUMELMS(szMsg),TEXT("Accept Parental Level Change Request to level %ld?"),
				static_cast<ULONG>(param1) );
				if (IDYES == MessageBox(m_hWnd, szMsg,  TEXT("Accept Change?"), MB_YESNO))
				{
					m_pIDvdC2->AcceptParentalLevelChange(TRUE);
				}
				else
				{
					m_pIDvdC2->AcceptParentalLevelChange(FALSE);
				}*/
				break;
			}

			case EC_DVD_ERROR:
			{
			//	DbgLog((LOG_TRACE, 3, TEXT("DVD Event: Error event received (code %ld)"),
			//	static_cast<ULONG>(param1))) ;
				switch (param1)
				{
					case DVD_ERROR_Unexpected:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_ERROR_Unexpected\n"));
						msg = _T("An unexpected error (possibly incorrectly authored content)")
							  _T("\nwas encountered.")
							  _T("\nCan't playback this DVD-Video disc.\n");
						msgAr.Add(msg);
						break ;

					case DVD_ERROR_CopyProtectFail:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_ERROR_CopyProtectFail\n"));
						msg = _T("Key exchange for DVD copy protection failed.")
							  _T("\nCan't playback this DVD-Video disc.\n");
						msgAr.Add(msg);
						break ;

					case DVD_ERROR_InvalidDVD1_0Disc:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_ERROR_InvalidDVD1_0Disc\n"));
						msg = _T("This DVD-Video disc is incorrectly authored.")
							  _T("\nCan't playback this disc.\n");
						msgAr.Add(msg);
						break ;

					case DVD_ERROR_InvalidDiscRegion:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_ERROR_InvalidDiscRegion\n"));
						msg = _T("This DVD-Video disc cannot be played, because it is not")
							  _T("\nauthored to play in the current system region.")
							  _T("\nThe region mismatch may be fixed by changing the")
							  _T("\nsystem region (with DVDRgn.exe).\n");
						msgAr.Add(msg);
						break ;

					case DVD_ERROR_LowParentalLevel:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_ERROR_LowParentalLevel\n"));
						msg = _T("Player parental level is set lower than the lowest parental")
							  _T("\nlevel available in this DVD-Video content.")
							  _T("\nCannot playback this DVD-Video disc.\n");
						msgAr.Add(msg);
						break ;

					case DVD_ERROR_MacrovisionFail:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_ERROR_MacrovisionFail\n"));
						msg = _T("This DVD-Video content is protected by Macrovision.")
							  _T("\nThe system does not satisfy Macrovision requirement.")
							  _T("\nCan't continue playing this disc.\n");
						msgAr.Add(msg);
						break ;

					case DVD_ERROR_IncompatibleSystemAndDecoderRegions:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_ERROR_IncompatibleSystemAndDecoderRegions\n"));
						msg = _T("No DVD-Video disc can be played on this system, because ")
							  _T("\nthe system region does not match the decoder region.")
							  _T("\nPlease contact the manufacturer of this system.\n");
						msgAr.Add(msg);
					break ;

					case DVD_ERROR_IncompatibleDiscAndDecoderRegions:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_ERROR_IncompatibleDiscAndDecoderRegions\n"));
						msg = _T("This DVD-Video disc cannot be played on this system, because it is")
							  _T("\nnot authored to be played in the installed decoder's region.\n");
						msgAr.Add(msg);
						break ;
				}  
				// end of switch (param1).
				break ;
			}

			// Next is warning
			case EC_DVD_WARNING:
			{
				switch (param1)
				{
					case DVD_WARNING_InvalidDVD1_0Disc:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_WARNING_InvalidDVD1_0Disc\n"));
						msg = _T("DVD Warning: Current disc is not v1.0 spec compliant\n");
						msgAr.Add(msg);
						break;

					case DVD_WARNING_FormatNotSupported:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_WARNING_FormatNotSupported\n"));
						msg = _T("DVD Warning: The decoder does not support the new format\n.");
						msgAr.Add(msg);
						break;

					case DVD_WARNING_IllegalNavCommand:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_WARNING_IllegalNavCommand\n"));
					msg = _T("DVD Warning: An illegal navigation command was encountered.\n");
					msgAr.Add(msg);
					break;

					case DVD_WARNING_Open:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_WARNING_Open\n"));
					msg = _T("DVD Warning: Could not open a file on the DVD disc.\n");
					msgAr.Add(msg);
					break;

					case DVD_WARNING_Seek:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_WARNING_Seek\n"));
					msg = _T("DVD Warning: Could not seek in a file on the DVD disc.\n");
					msgAr.Add(msg);
					break;

					case DVD_WARNING_Read:
						TRACE(_T("CPlayer::HandleVideoEvent DVD_WARNING_Read\n"));
					msg = _T("DVD Warning: Could not read a file on the DVD disc.\n");
					msgAr.Add(msg);
					break;

					default:
						TRACE(_T("CPlayer::HandleVideoEvent default EC_DVD_WARNING\n"));
					msg.Format(_T("DVD Warning: An unknown (%ld) warning received.\n"),
								static_cast<ULONG>(param1) );
					msgAr.Add(msg);
					break ;
				}
				break ;
			}

			case EC_DVD_STILL_ON:
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_STILL_ON\n"));
				if (TRUE == param1) // if there is a still without buttons, we can call StillOff
				{
					//m_bStillOn = true;
				}
				break;

			case EC_DVD_STILL_OFF:
				TRACE(_T("CPlayer::HandleVideoEvent EC_DVD_STILL_OFF\n"));
				break;
		}
	}

	// dbg msgs
	#ifdef _DEBUG
	int sz = msgAr.GetSize();
	if(sz)
	{
		TRACE(_T("CPlayer::HandleVideoEvent %d messages\n"), sz);
		for(int i = 0; i < sz; i++)
		{
			TRACE(_T("%s"), msgAr[i] );
		}
	}
	#endif

	return ret;
}

// Get time in seconds.
UINT CPlayer::GetTimeSeconds(DVD_TIMECODE tc)
{
	// Set as seconds.
	UINT seconds  =	tc.Hours10	 * 60 * 60 * 10  +
					tc.Hours1	 * 60 * 60 +
					tc.Minutes10 * 60 * 10 +
					tc.Minutes1	 * 60 +
					tc.Seconds10 * 10 +
					tc.Seconds1;
	return seconds;
}
// Get time from seconds.
DVD_TIMECODE CPlayer::GetTimeFromSeconds(UINT seconds)
{
	// 14366 seconds
	DVD_TIMECODE tc;
	ZeroMemory(&tc, sizeof(DVD_TIMECODE));

	UINT sec = seconds;
	if(sec >= 36000)				// 60 * 60 * 10
	{
		tc.Hours10 = sec / 36000;
		sec = sec % 36000;
	}
	if(sec >= 3600)					// 60 * 60
	{
		tc.Hours1 = sec / 3600;
		sec = sec % 3600;
	}
	if(sec >= 600)					// 60 * 10
	{
		tc.Minutes10 = sec / 600;
		sec = sec % 600;
	}
	if(sec >= 60)					// 60
	{
		tc.Minutes1 = sec / 60;
		sec = sec % 60;
	}
	if(sec >= 10)					// 10
	{
		tc.Seconds10 = sec / 10;
		sec = sec % 10;
	}
	if(sec)							// 1
	{
		tc.Seconds1 = sec;
	}
	return tc;
}
// Get time in seconds.
// For DVD_HMSF_TimeCodeEvents.
// Note IDvdControl2::SetOption() with the DVD_OPTION_FLAG
// is used to enable DVD_HMSF_TIMECODE. Default is false.
UINT CPlayer::GetTimeSeconds2(DVD_HMSF_TIMECODE tc)
{
	// Set as seconds.
	UINT seconds  =	tc.bHours	 * 60 * 60 +
					tc.bMinutes	 * 60 +
					tc.bSeconds;
	return seconds;
}
// Get time from seconds.
// For DVD_HMSF_TimeCodeEvents.
DVD_HMSF_TIMECODE CPlayer::GetTimeFromSeconds2(UINT seconds)
{
	// 14366 seconds
	DVD_HMSF_TIMECODE tc;
	ZeroMemory(&tc, sizeof(DVD_TIMECODE));

	UINT sec = seconds;
	if(sec >= 3600)					// 60 * 60
	{
		tc.bHours = sec / 3600;
		sec = sec % 3600;
	}
	if(sec >= 60)					// 60
	{
		tc.bMinutes = sec / 60;
		sec = sec % 60;
	}
	if(sec)							// 1
	{
		tc.bSeconds = sec;
	}
	return tc;
}

// ControlsBar Time slider moved.
// Moving param indicates user is dragging the time slider.
// NewPos param is the current Slider position.
// Seconds param is used by caller to update the slider.
// Called from CMainFrame::OnPlaybackTime.
//  moving:  in  user dragging slider
//  newPos:  in  user pos of slider, 0 - 1000
//  seconds: out new seconds
BOOL CPlayer::PlaybackTimeSliderMoved(BOOL moving, const UINT & newPos, UINT & seconds)	
{
	/////////////////
	// DVD playback.
	if(m_dvdPlaying && m_pIDvdControl2)
	{
		HRESULT hr;

		// Calc the new seconds per newPos param.
		UINT64 pos = newPos;								// 0 - 1000
		UINT64 newSeconds = (m_dvdTotalTimeSec * pos) / 1000; 

		// Time too little to show on slider.
		if(newSeconds < 0)
			return FALSE;

		// If slider is being moved, pause the graph and update the
		// ui slider time. Note calling TimePlay() here stores each
		// call in a queue of some sort then plays each back over
		// many seconds/minutes - no good.
		if(moving)
		{
			// If running, pause
			if(m_fsCurrent == State_Running)
			{
				hr = m_pControl->Pause();
				if(FAILED(hr))
				{
					TRACE(_T("EROOR - CPlayer::PlaybackTimeSliderMoved DVD Pause failed\n"));
					return FALSE;
				}
				m_fsCurrent = State_Paused;
			}
			// Set param to show user new time position.
			seconds = (UINT)newSeconds;
		}
		// Else slider has been released - call TimePlay() and de-pause.
		else if(m_fsCurrent == State_Paused)
		{
			ULONG uiTitle = m_dvdCurTitle;

			// Change time - fails if over limit, and fails if in
			//  fbi/copyright area (which is DVD_DOMAIN_Title).
			//
			// Get a DVD_HMSF_TIMECODE time from current seconds. 
			DVD_HMSF_TIMECODE tc = GetTimeFromSeconds2((UINT)newSeconds);
			//hr = m_pIDvdControl2->PlayAtTime(&tc, 0, NULL);
			hr = m_pIDvdControl2->PlayAtTimeInTitle(uiTitle, &tc, 0, NULL);

			if(FAILED(hr))
			{
				TRACE(_T("CPlayer::PlaybackTimeSliderMoved DVD TimePlay failed\n"));

				// De-pause playback - runs at original pos.
				hr = m_pControl->Run();
				if(FAILED(hr))
				{
					TRACE(_T("CPlayer::PlaybackTimeSliderMoved DVD Run failed\n"));
					return FALSE;
				}
				// Running
				m_fsCurrent = State_Running;
				return FALSE;
			}

			// De-pause playback.
			hr = m_pControl->Run();
			if(FAILED(hr))
			{
				TRACE(_T("CPlayer::PlaybackTimeSliderMoved DVD Run failed\n"));
				return FALSE;
			}
			// Running
			m_fsCurrent = State_Running;

			// Set param.
			seconds = (UINT)newSeconds;
		}
		return TRUE;
	}
	//////////////////////////////////////
	// Else normal file/stream playback.
	//
	// Ensure.
	if(!m_pControl || !m_pSeeking  ||
	   m_fsCurrent == State_Stopped)
		return FALSE;

	HRESULT hr;

	// 8/22
	// Calc the new ref time per newPos param.
	//  Each unit of reference time is 100 nanoseconds, or 10,000,000 
	//  ticks per second (1,000,000,000 / 100 = 10,000,000).
	REFERENCE_TIME pos = (REFERENCE_TIME)newPos; // 0 - 1000
	REFERENCE_TIME rtNew = (m_rtTotalTime * pos) / 1000;

	// 7/22 update - fix fail
	// Seek to one second minimum (could be less).
	if(rtNew < 10000000)
	{
		TRACE(_T("WARNING CPlayer::PlaybackTimeSliderMoved rtNew < 10,000,000, setting to 10,000,000\n"));
		rtNew = 10000000;
	}

	// Calc the current seconds - will be used
	// by caller to set control.
	seconds = (long)(rtNew / 10000000);

	// If slider is being moved, pause the graph.
	if(moving)
	{
		// If running, pause
		if(m_fsCurrent == State_Running)
		{
			hr = m_pControl->Pause();
			if(FAILED(hr))
			{
				TRACE(_T("EROOR - CPlayer::OnPlaybackTime Pause failed\n"));
				return FALSE;
			}
			m_fsCurrent = State_Paused;
		}
		// Graph is paused - Update the position continuously.
		hr = m_pSeeking->SetPositions(&rtNew, 
									  AM_SEEKING_AbsolutePositioning,
									  NULL,
									  AM_SEEKING_NoPositioning);
		if(FAILED(hr))
		{
			TRACE(_T("EROOR - CPlayer::OnPlaybackTime SetPosition 1 failed\n"));
			return FALSE;
		}
	}

	// Else slider has been released - de-pause if paused.
	else if(m_fsCurrent == State_Paused)
	{
		// Set the final position.
		hr = m_pSeeking->SetPositions(&rtNew, 
									  AM_SEEKING_AbsolutePositioning,
									  NULL,
									  AM_SEEKING_NoPositioning);
		if(FAILED(hr))
		{
			TRACE(_T("EROOR - CPlayer::OnPlaybackTime SetPosition 2 failed\n"));
			return FALSE;
		}

		// De-pause playback.
		hr = m_pControl->Run();
		if(FAILED(hr))
		{
			TRACE(_T("EROOR - CPlayer::OnPlaybackTime Run failed\n"));
			return FALSE;
		}
		m_fsCurrent = State_Running;
	}
	return TRUE;
}
// 7/22 PlaybackTimeSliderMovedKeyboard
// 8/22 DVD
// ControlsBar Time slider moved.
// NewPos param is the current Slider position.
// Seconds param is used by caller to update the slider.
// Called from CMainFrame::OnPlaybackTime.
//  moving:  in  user dragging slider
//  newPos:  in  user pos of slider, 0 - 1000
//  seconds: out new seconds
BOOL CPlayer::PlaybackTimeSliderMovedKeyboard(const UINT & newPos, UINT & seconds)	
{
	// Ensure.
	if(!m_pControl || !m_pSeeking   ||
	   m_fsCurrent != State_Running )
	{
		if(m_fsCurrent == State_Paused)
			TRACE(_T("ERROR CPlayer::PlaybackTimeSliderMovedKeyboard - State_Paused.\n"));
		else if(m_fsCurrent == State_Stopped)
			TRACE(_T("ERROR CPlayer::PlaybackTimeSliderMovedKeyboard - State_Stopped.\n"));
		return FALSE;
	}

	/////////////////
	// DVD playback. todo finish
	if(m_dvdPlaying && m_pIDvdControl2)
	{
		// 8/22 notes - rework playback times - big job. 
		// todo same in PlaybackTimeSliderMoved
		//
		// For DVDs, we search only in the currently playing Title
		// which may have multiple Chapters.
		//
		// Note the DVD will auto play through Tiles and Chapters
		// per how it was programmed.
		//
		// User can switch Titles and Chapters via DVD Navigator.

		TRACE(_T("In CPlayer::PlaybackTimeSliderMovedKeyboard - DVD\n"));
		HRESULT hr;

		// Calc the new seconds per newPos param.
		UINT64 pos = newPos;								// 0 - 1000
		UINT64 newSeconds = (m_dvdTotalTimeSec * pos) / 1000; 

		// Ensure time in range.
		if(newSeconds < 0 || newSeconds > m_dvdTotalTimeSec)
		{
			TRACE(_T("WARNING CPlayer::PlaybackTimeSliderMovedKeyboard - DVD newSeconds range, setting to min-max\n"));
			// Set min or max.
			newSeconds = newSeconds < 0 ? 0 : m_dvdTotalTimeSec;
		}

		// Get Title.
		ULONG uiTitle = m_dvdCurTitle;

		// Get chapter & domain. 8/22-2
		UINT uiChapter = m_dvdCurChapter;
		DVD_DOMAIN domain = m_dvdCurDomain;
		CString strDomain = DvdGetCurDomainStr();

		// Get a DVD_TIMECODE time from current seconds. 
//		DVD_TIMECODE tc = GetTimeFromSeconds((UINT)newSeconds);

		// Get as ULONG for param.
//		DVD_TIMECODE * pTc = &tc;
//		ULONG ulTc = *(ULONG*)pTc;

		// Get a DVD_HMSF_TIMECODE time from new seconds. 
		DVD_HMSF_TIMECODE tc = GetTimeFromSeconds2((UINT)newSeconds);
		
		// Call PlayAtTimeInTitle - fails if over limit, and fails if in
		//  fbi/copyright area (which is DVD_DOMAIN_Title).
		hr = m_pIDvdControl2->PlayAtTimeInTitle(uiTitle, &tc, 0, NULL);
		if(FAILED(hr))
		{
			CString msg;
			TCHAR temp[1024]; 
			AMGetErrorText(hr, temp, sizeof(TCHAR) * 1024);
			msg = temp;

			TRACE(_T("ERROR CPlayer::PlaybackTimeSliderMovedKeyboard DVD TimePlay failed\n"));
			TRACE(_T(" newSeconds=%u, error msg=%s\n"), newSeconds, msg);
			return FALSE;
		}
		// Set param.
		seconds = (UINT)newSeconds;

		return TRUE;
	}
	//////////////////////////////////////
	// Else normal file/stream playback.
	//
	TRACE(_T("In CPlayer::PlaybackTimeSliderMovedKeyboard - normal playback\n"));

	HRESULT hr;

	// Calc the new ref time per newPos param.
	//  Each unit of reference time is 100 nanoseconds, or 10,000,000 
	//  ticks per second (1,000,000,000 / 100 = 10,000,000).
	REFERENCE_TIME pos = (REFERENCE_TIME)newPos; // 0 - 1000
	REFERENCE_TIME rtNew = (m_rtTotalTime * pos) / 1000;

	// Seek to one second minimum (could be less).
	if(rtNew < 10000000)
	{
		TRACE(_T("In CPlayer::PlaybackTimeSliderMovedKeyboard - rtNew < 10000000, set to 10000000\n"));
		rtNew = 10000000;
	}

	// Calc the current seconds - will be used
	// by caller to set control.
	seconds = (long)(rtNew / 10000000);

	// Set the new/final position.
	hr = m_pSeeking->SetPositions(&rtNew, 
								  AM_SEEKING_AbsolutePositioning,
								  NULL,
								  AM_SEEKING_NoPositioning);
	if(FAILED(hr))
	{
		TRACE(_T("ERROR - CPlayer::PlaybackTimeSliderMovedKeyboard SetPositions failed\n"));
		return FALSE;
	}

	/*dbg Check state
	FILTER_STATE fs;
	hr = m_pControl->GetState(500, (OAFilterState*)&fs);
	if(FAILED(hr)) 
	{
		TRACE(_T("ERROR CPlayer::PlaybackTimeSliderMovedKeyboard GetState failed.\n"));
	}
	if(fs == State_Running)
		TRACE(_T("CPlayer::PlaybackTimeSliderMovedKeyboard - State_running.\n"));
	else if(fs == State_Paused)
		TRACE(_T("CPlayer::PlaybackTimeSliderMovedKeyboard - State_Paused.\n"));
	else if(fs == State_Stopped)
		TRACE(_T("CPlayer::PlaybackTimeSliderMovedKeyboard - State_Stopped.\n"));*/

	// Done
	return TRUE;
}
/* pre 8/22----------------
// 7/22 PlaybackTimeSliderMovedKeyboard
// ControlsBar Time slider moved.
// NewPos param is the current Slider position.
// Seconds param is used by caller to update the slider.
// Called from CMainFrame::OnPlaybackTime.
//  moving:  in  user dragging slider
//  newPos:  in  user pos of slider, 0 - 1000
//  seconds: out new seconds
BOOL CPlayer::PlaybackTimeSliderMovedKeyboard(const UINT & newPos, UINT & seconds)	
{
	/////////////////
	// DVD playback. todo finish
	if(m_dvdPlaying && m_pIDvdControl2)
	{
		return FALSE;// todo
		HRESULT hr;
		BOOL moving;	 //TODO finish DVD

		// Calc the new seconds per newPos param.
		UINT64 pos = newPos;								// 0 - 1000
		UINT64 newSeconds = (m_dvdTotalTimeSec * pos) / 1000; 

		// Time too little to show on slider.
		if(newSeconds < 0)
			return FALSE;

		// If slider is being moved, pause the graph and update the
		// ui slider time. Note calling TimePlay() here stores each
		// call in a queue of some sort then plays each back over
		// many seconds/minutes - no good.
		if(moving)
		{
			// If running, pause
			if(m_fsCurrent == State_Running)
			{
				hr = m_pControl->Pause();
				if(FAILED(hr))
				{
					TRACE(_T("EROOR - CPlayer::PlaybackTimeSliderMoved DVD Pause failed\n"));
					return FALSE;
				}
				m_fsCurrent = State_Paused;
			}
			// Set param to show user new time position.
			seconds = (UINT)newSeconds;
		}
		// Else slider has been released - call TimePlay() and de-pause.
		else if(m_fsCurrent == State_Paused)
		{
			ULONG uiTitle = m_dvdCurTitle;

			// Change time - fails if over limit, and fails if in
			//  fbi/copyright area (which is DVD_DOMAIN_Title).
			//
			// Get a DVD_HMSF_TIMECODE time from current seconds. 
			DVD_HMSF_TIMECODE tc = GetTimeFromSeconds2((UINT)newSeconds);
			//hr = m_pIDvdControl2->PlayAtTime(&tc, 0, NULL);
			hr = m_pIDvdControl2->PlayAtTimeInTitle(uiTitle, &tc, 0, NULL);

			if(FAILED(hr))
			{
				TRACE(_T("CPlayer::PlaybackTimeSliderMoved DVD TimePlay failed\n"));

				// De-pause playback - runs at original pos.
				hr = m_pControl->Run();
				if(FAILED(hr))
				{
					TRACE(_T("CPlayer::PlaybackTimeSliderMoved DVD Run failed\n"));
					return FALSE;
				}
				// Running
				m_fsCurrent = State_Running;
				return FALSE;
			}

			// De-pause playback.
			hr = m_pControl->Run();
			if(FAILED(hr))
			{
				TRACE(_T("CPlayer::PlaybackTimeSliderMoved DVD Run failed\n"));
				return FALSE;
			}
			// Running
			m_fsCurrent = State_Running;

			// Set param.
			seconds = (UINT)newSeconds;
		}
		return TRUE;
	}
	//////////////////////////////////////
	// Else normal file/stream playback.
	//
	// Ensure.
	if(!m_pControl || !m_pSeeking   ||
	   m_fsCurrent != State_Running )
	{
		if(m_fsCurrent == State_Paused)
			TRACE(_T("Entering CPlayer::OnPlaybackTimeKeyboard - State_Paused.\n"));
		else if(m_fsCurrent == State_Stopped)
			TRACE(_T("Entering CPlayer::OnPlaybackTimeKeyboard - State_Stopped.\n"));
		return FALSE;
	}

	TRACE(_T("In CPlayer::PlaybackTimeSliderMovedKeyboard - normal playback\n"));

	HRESULT hr;

	// Calc the new ref time per newPos param.
	REFERENCE_TIME pos = (REFERENCE_TIME)newPos; // 0 - 1000
	REFERENCE_TIME rtNew = (m_rtTotalTime * pos) / 1000;

	if(rtNew < 10000000)
		return FALSE;

	// Calc the current seconds - will be used
	// by caller to set control.
	seconds = (long)(rtNew / 10000000);

	// na m_pControl->Pause();

	// Set the new/final position.
	hr = m_pSeeking->SetPositions(&rtNew, 
								  AM_SEEKING_AbsolutePositioning,
								  NULL,
								  AM_SEEKING_NoPositioning);
	if(FAILED(hr))
	{
		TRACE(_T("EROOR - CPlayer::OnPlaybackTimeKeyboard SetPositions failed\n"));
		return FALSE;
	}

	// Check state
	FILTER_STATE fs;
	hr = m_pControl->GetState(500, (OAFilterState*)&fs);
	if(FAILED(hr)) 
	{
		TRACE(_T("ERROR CPlayer::OnPlaybackTimeKeyboard GetState failed.\n"));
	}
	if(fs == State_Running)
		TRACE(_T("CPlayer::OnPlaybackTimeKeyboard - State_running.\n"));
	else if(fs == State_Paused)
		TRACE(_T("CPlayer::OnPlaybackTimeKeyboard - State_Paused.\n"));
	else if(fs == State_Stopped)
		TRACE(_T("CPlayer::OnPlaybackTimeKeyboard - State_Stopped.\n"));

	//m_pControl->Run();

	// Done
	return TRUE;
}*/

// ControlsBar vol slider moved.
// NewPos param is percent, 0 - 100.
// Called from CDsLavView::OnPlaybackVol().
BOOL CPlayer::PlaybackVolSliderMoved(int newPos)
{
    HRESULT hr = S_OK;

	if(m_muted || m_pBaseAudio == NULL)
		return FALSE;

	// Adjust percent.
	int percent = (int)newPos;
	if(percent > 100)
		percent = 100;
	if(percent < 0)
		percent = 0;

	// Get dx value.
	LONG vol = DxVolFromPercent(percent);

	// Set.
	hr = m_pBaseAudio->put_Volume(vol);
	if(FAILED(hr))
	{
		TRACE(_T("ERROR CPlayer PlaybackVolSliderMoved put_Volume failed\n"));
		return FALSE;
	}
	m_curVolPercent = percent;

	return TRUE;
}

// Get the current Video Time:
//	position (0 - 10000)
//	seconds  (0 - 1190 hrs).
// Called from MainFrm::OnTimer().
BOOL CPlayer::PlaybackGetCurTime(UINT & position, UINT & seconds) 
{
	// DVD
	if(m_dvdPlaying)
	{
		if(!m_dvdTotalTimeSec || !m_dvdCurTimeSec)
			return FALSE;

		UINT64 pos = m_dvdCurTimeSec * 1000 / m_dvdTotalTimeSec; // 1000 divs
		UINT64 sec = m_dvdCurTimeSec;							 // cur play seconds
		if(pos >= 0 && pos <= 1000 && sec >= 0)
		{
			position = (UINT) pos;
			seconds  = (UINT) sec;
			return TRUE;
		}
		else
		{
			TRACE(_T("ERROR CPlayer PlaybackGetCurTime DVD bad calcs\n"));
		}


		return FALSE;
	}

	// If playback not stopped / paused.
	if(m_pSeeking && m_fsCurrent == State_Running)
	{
		REFERENCE_TIME pos, sec, rtNow = 0;
		HRESULT hr = m_pSeeking->GetCurrentPosition(&rtNow);
		if(SUCCEEDED(hr))
		{
			// 10/04/2020
			// BUG - divide by zero - happens when streaming a radio station
			//
			//test
			if(m_rtTotalTime <= 0)
			{
			//	TRACE(_T("ERROR CPlayer PlaybackGetCurTime m_rtTotalTime <= 0\n"));
				return FALSE;
			}
			// end test

			pos = rtNow * 1000 / m_rtTotalTime;	// 1000 divs
			sec = rtNow / 10000000;				// cur play seconds
			if(pos >= 0 && pos <= 1000 && sec >= 0)
			{
				position = (UINT) pos;
				seconds  = (UINT) sec;
				return TRUE;
			}
			else
			{
				TRACE(_T("ERROR CPlayer PlaybackGetCurTime bad calcs\n"));
			}
		}
	}
	return FALSE;
}

/////////////////////////////////////////////////////////
// Helpers

// From DShowUtil
void CPlayer::UtilDeleteMediaType(AM_MEDIA_TYPE *pmt)
{
    // Allow NULL pointers for coding simplicity
    if (pmt == NULL) 
	{
        return;
    }

    // Free media type's format data
    if (pmt->cbFormat != 0) 
    {
        CoTaskMemFree((PVOID)pmt->pbFormat);

        // Strictly unnecessary but tidier
        pmt->cbFormat = 0;
        pmt->pbFormat = NULL;
    }

    // Release interface
    if (pmt->pUnk != NULL) 
    {
        pmt->pUnk->Release();
        pmt->pUnk = NULL;
    }

    // Free media type
    CoTaskMemFree((PVOID)pmt);
}

// Debug - show filters used in the graph.
// From msdn: Enumerating Filters
HRESULT CPlayer::EnumFilters()
{
	TRACE(_T("\n"));
	TRACE(_T("Entering CPlayer EnumFilters\n"));

	IEnumFilters *pEnum = NULL;
	IBaseFilter *pFilter;
	ULONG cFetched;

	// 10/10/2020 DsLavSplit
	if(!m_pGraph)
	{
		TRACE(_T("CPlayer::EnumFilters m_pGraph is NULL\n"));
		return 0;
	}

	HRESULT hr = m_pGraph->EnumFilters(&pEnum);
	if(FAILED(hr)) 
		return hr;

	while(pEnum->Next(1, &pFilter, &cFetched) == S_OK)
	{
		FILTER_INFO FilterInfo;
		hr = pFilter->QueryFilterInfo(&FilterInfo);
		if(FAILED(hr))
		{
			TRACE(_T("Could not get the filter info\n"));
			continue;  // Maybe the next one will work.
		}
		// Print to output.
		TRACE(_T("Using filter: %s\n"), FilterInfo.achName);

		// The FILTER_INFO structure holds a pointer to the Filter Graph
		// Manager, with a reference count that must be released.
		if(FilterInfo.pGraph != NULL)
		{
			FilterInfo.pGraph->Release();
		}
		pFilter->Release();
	}
	TRACE(_T("\n"));

	pEnum->Release();
	return S_OK;
}








