#include "pch.hpp"
//===========================================================================//
// File:	 VideoPlayback.cpp												 //
// Project:  GameOS					                                         //
// Contents: Video Playback routines										 //
//---------------------------------------------------------------------------//
// Date      Who    Modification                                             //
// 12/19/97  JTR    Created													 //
//																			 //
//---------------------------------------------------------------------------//
// Copyright (C) 1999 Microsoft Corp.						                 //
// All Rights reserved worldwide                                             //
// This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL               //
//===========================================================================//
#include "Globals.hpp"
#include "LinkedList.hpp"
#include "DirectX.hpp"
#include "VideoPlayback.hpp"
#include "Texture Manager.hpp"
#include "Sound Renderer.hpp"
#include "Sound DS3DMixer.hpp"
#include "MemoryManager.hpp"
#include "ErrorHandler.hpp"

extern SoundRendererData SoundRenderer;
bool	gMediaPause = false;
HGOSHEAP gos_Video::m_videoHeap = 0;
extern DWORD gLanguageDLL;

bool NoVideoPlayback = false;
bool DisableVideoPlayback = false;
bool gIsQuiet = false;

extern HWND g_MovieHandle;
extern LinkedList<HGOSVIDEO> g_activeVideos;
extern IDirectDraw7* DDobject;
extern IDirectDraw7* CurrentDDobject;
extern bool g_DDperformFlip;
extern bool g_DDneedsRestore;
extern DWORD gStopRendering;
extern bool DebuggerActive;
extern DWORD gFreezeLogic;
extern DWORD gGameLogicActive;			   
extern DWORD gStopSystem;
extern HWND hWindow;

extern DWORD FrameAdvance;
extern bool DisableSoundSystem;

extern IDirectDrawSurface7*	BackBufferSurface;

extern HGOSHEAP ParentGameOSHeap;

extern bool InsideBeginScene;
extern DWORD gFreezeLogic;

void gosVideo_Display(HGOSVIDEO vid);

extern bool gGotFocus;

void VideoManagerInstall()
{
	SPEW(( GROUP_DIRECTSHOW, "VideoManagerInstall()" ));
	///////////////////////////////
	gos_Video::m_videoHeap = gos_CreateMemoryHeap("Video", 0, ParentGameOSHeap);
}




void VideoManagerRestore()
{
	if (NoVideoPlayback)
		return;
	SPEW(( GROUP_DIRECTSHOW, "VideoManagerRestore()" ));
	HGOSVIDEO 
		video;
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();

	while((video = itr.ReadAndNext()) != NULL)
	{
		video->Restore();
	}
	SPEW(( GROUP_DIRECTSHOW, "VideoManagerRestore() - Finished" ));
}


void VideoManagerRelease()
{
	if (NoVideoPlayback)
		return;
	SPEW(( GROUP_DIRECTSHOW, "VideoManagerRelease()" ));
	HGOSVIDEO 
		video;
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();

	while((video = itr.ReadAndNext()) != NULL)
	{
		video->Release();
	}
	SPEW(( GROUP_DIRECTSHOW, "VideoManagerRelease() - Finished" ));
}

///////////////////////////////////////////////////////////////////
//
// The update function iterates through the list of movies and
// renders any that are active. If we are in 8bit mode, make
// sure that the palette is correct to show our movie.
//
void VideoManagerUpdate()
{
	if (NoVideoPlayback)
		return;

	if (gFreezeLogic || DisableVideoPlayback || (DebuggerActive && (!gGameLogicActive  || gStopSystem)) )
	{
		if (!gMediaPause)
		{
			VideoManagerPause();
		}
	}
	else
	{
		if (gMediaPause)
			VideoManagerContinue();
	}

	HGOSVIDEO 
		video;
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();

	while((video = itr.ReadAndNext()) != NULL)
		if (video->m_videoStatus == gosVideo_Pause ||
			video->m_videoPlayMode == gosVideo_PlayOnceHide || 
			video->m_videoPlayMode == gosVideo_PlayOnceHold || 
			video->m_videoStatus == gosVideo_Loop)
		{
			if (!gGotFocus || gIsQuiet || !gActive)
			{
				if (SoundRenderer.m_Mixer)
				{
					if (video->m_pBasicAudio)
					{
						AllowFail = true;
						wPut_Volume( video->m_pBasicAudio, -10000 );
						AllowFail = false;
					}
				}
			}
			else
			if (gActive && gGotFocus && !gIsQuiet)
			{
				if (SoundRenderer.m_Mixer)
				{
					if (video->m_pBasicAudio)
					{
						if (video->m_volume == 0.0f)
						{
							AllowFail = true;
							wPut_Volume( video->m_pBasicAudio, -10000 );
							AllowFail = false;
						}
						else
						{
							AllowFail = true;
							wPut_Volume( video->m_pBasicAudio, (LONG) (log(video->m_volume)*1000.0f ) );
							AllowFail = false;
						}
					}
				}
			}
			if (!gMediaPause)
			{
				video->Update();
				Sleep(0); // this forces the scheduler to give some CPU time to the streaming thread in order to avoid any stutters when playing large videos
			}
			if (!video->m_texture)
				gosVideo_Display(video);
		}
	if (!gGotFocus)
		gIsQuiet = true;
	else
		gIsQuiet = false;
}

void VideoManagerPause()
{
	SPEW(( GROUP_DIRECTSHOW, "VideoManagerPause()" ));
	if (NoVideoPlayback)
		return;
	HGOSVIDEO 
		video;
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();

	while((video = itr.ReadAndNext()) != NULL)
		if (video->m_videoStatus == gosVideo_PlayOnceHide || 
			video->m_videoStatus == gosVideo_PlayOnceHold || 
			video->m_videoStatus == gosVideo_Loop)
		{
			video->Pause();
		}
	gMediaPause = true;
}

void VideoManagerContinue()
{
	SPEW(( GROUP_DIRECTSHOW, "VideoManagerContinue()" ));
	if (NoVideoPlayback)
		return;
	HGOSVIDEO 
		video;
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();

	while((video = itr.ReadAndNext()) != NULL)
			video->Continue();
	gMediaPause = false;
}

///////////////////////////////////////////////////////////////////
//
// Video Manager shutdown iterates through the list of allocated
// videos and deletes them. There really shouldn't be any by the
// time this gets called-- we should probably inform the runtime
// log if any still exist
//
void 
	VideoManagerUninstall()
{
	if ( !NoVideoPlayback )
	{
		SPEW(( GROUP_DIRECTSHOW, "VideoManagerUninstall()" ));
		LinkedListIterator<HGOSVIDEO> itr 
			= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
		itr.Head();

		HGOSVIDEO 
			video;
		while((video = itr.ReadAndNext()) != NULL)
		{
			delete video;
		}
	}

	gos_DestroyMemoryHeap(gos_Video::m_videoHeap);
	gos_Video::m_videoHeap = 0;
}

///////////////////////////////////////////////////////////////////
//
// A wrapper for creating a Video resource. It requires a pointer
// to a video handle, a path, and a destination surface.
//
void __stdcall gosVideo_CreateResource(HGOSVIDEO* video, char * path)
{
	SPEW((GROUP_DIRECTSHOW, "Creating video \"%s\" for standard playback", path));
	*video = (HGOSVIDEO) new(gos_Video::m_videoHeap) gos_Video(path, 0);
}

void __stdcall gosVideo_CreateResourceAsTexture(HGOSVIDEO * video, DWORD * txt, char * path)
{
	SPEW((GROUP_DIRECTSHOW, "Creating video \"%s\" as texture", path));
	*video = (HGOSVIDEO) new(gos_Video::m_videoHeap) gos_Video(path, 1);
	if (txt) 
	{
		*txt = ((HGOSVIDEO) *video)->m_texture;
	}

}
///////////////////////////////////////////////////////////////////
//
// Destroy a video (the destructor removes the video from the list)
//
void __stdcall gosVideo_DestroyResource(HGOSVIDEO* video)
{
#ifdef LAB_ONLY
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();


	HGOSVIDEO 
		v;
	while((v = itr.ReadAndNext()) != NULL)
	{
		if (*video == v)
		{
#endif
			SPEW((GROUP_DIRECTSHOW, "Deleting video \"%s\"", ((HGOSVIDEO)*video)->m_lpPath));
			delete *video;
			*video = NULL;
			return;
#ifdef LAB_ONLY
		}
	}
	STOP(( "gosVideo_DestroyResource was passed a bad HGOSVIDEO"));
#endif
}

void __stdcall gosVideo_GetResourceInfo( HGOSVIDEO video, gosVideo_ResourceInfo * gvri)
{
	memset(gvri,0,sizeof(gosVideo_ResourceInfo));

	if (NoVideoPlayback)
	{
		// fill with dummy information
		gvri->fDurationSec = 1.0f;	
		gvri->fSoFarSec = 1.0f;
		gvri->lpData = 0;
		gvri->dwSurfaceWidth = 0;
		gvri->dwSurfaceHeight = 0;
		gvri->dwPitch = video->m_pMMStreamSurfaceDesc.lPitch;
		gvri->dwHeight = 0;
		gvri->dwWidth = 0;

		// plug in the data
		gvri->fScaleOfX = 100;
		gvri->fScaleOfY = 100;
		gvri->dwOriginX = video->m_videoLocX;
		gvri->dwOriginY = video->m_videoLocY;
		gvri->ePlayMode = gosVideo_Stop;
		gvri->ePlayStatus = gosVideo_Stop;
		return;
	}


	if (!(video->m_pMMStream))
	{
		gvri->fDurationSec = 1.0f;	
		gvri->fSoFarSec = 1.0f;
		return;
	}

	gvri->lpData = (unsigned char *) video->m_pMMStreamSurfaceDesc.lpSurface;
	gvri->dwSurfaceWidth = video->m_pMMStreamSurfaceDesc.dwWidth;
	gvri->dwSurfaceHeight = video->m_pMMStreamSurfaceDesc.dwHeight;
	gvri->dwPitch = video->m_pMMStreamSurfaceDesc.lPitch;
	gvri->dwHeight = video->m_videoSrcRect.bottom;
	gvri->dwWidth = video->m_videoSrcRect.right;

	// plug in the data
	gvri->fScaleOfX = video->m_scaleX;
	gvri->fScaleOfY = video->m_scaleY;
	gvri->dwOriginX = video->m_videoLocX;
	gvri->dwOriginY = video->m_videoLocY;
	gvri->ePlayMode = video->m_videoPlayMode;
	gvri->ePlayStatus = video->m_videoStatus;

	gvri->lpstrPath = NULL;
	STREAM_TIME st;
	gvri->fDurationSec = ((float) video->m_duration) / 10000000.0f;	
	wGetTime(video->m_pMMStream, &st);
	gvri->fSoFarSec = ((float) st) / 10000000.0f;

	if( 1==LogMode )	// Log file playing back?
	{
		DWORD temp=ReadBitStream(32,Log_Media);
		gvri->fSoFarSec=*(float*)&temp;
		gvri->ePlayMode=(gosVideo_PlayMode)ReadBitStream(4,Log_Media);
		gvri->ePlayStatus=(gosVideo_PlayMode)ReadBitStream(4,Log_Media);
	}
	else				// Log file recording
	{
		WriteBitStream(*(DWORD*)&gvri->fSoFarSec,32,Log_Media);
		WriteBitStream(*(DWORD*)&gvri->ePlayMode,4,Log_Media);
		WriteBitStream(*(DWORD*)&gvri->ePlayStatus,4,Log_Media);
	}
}

void gosVideo_Display(HGOSVIDEO video)
{
	gosASSERT( !InsideBeginScene );
	if (NoVideoPlayback)
		return;
	if ( !(video->m_pMMStream) || gStopRendering )
		return;

	// has the client told the sucker where to blit?
	if (video->m_videoLocX == -1 && video->m_videoLocY == -1)
		return;
	// we need to blit this to the backbuffer
	RECT dstRect;
	memcpy(&dstRect, &video->m_videoRect, sizeof(RECT));
	RECT srcRect;
	memcpy(&srcRect, &video->m_videoSrcRect, sizeof(RECT));

	if (dstRect.left < 0)
	{
		srcRect.left -= (long) (1.0f /video->m_scaleX)*dstRect.left;
		dstRect.left = 0;
	}

	if (dstRect.right > Environment.screenWidth)
	{
		srcRect.right -= (long) (1.0f/video->m_scaleX)*(dstRect.right - Environment.screenWidth);
		dstRect.right = Environment.screenWidth;
	}

	if (dstRect.top < 0)
	{
		srcRect.top -= (long) (1.0f/video->m_scaleY)*dstRect.top;
		dstRect.top = 0;
	}

	if (dstRect.bottom > Environment.screenHeight)
	{
		srcRect.bottom -= (long) (1.0f/video->m_scaleY)*(dstRect.bottom - Environment.screenHeight);
		dstRect.bottom = Environment.screenHeight;
	}

	if ((dstRect.right <= dstRect.left || dstRect.right < 0) ||
		(dstRect.top >= dstRect.bottom || dstRect.bottom < 0))
	{
		return;
	}

	AllowFail = true;
	SafeFPU();					// Make sure exceptions cannot be generated (Voodoo3 had trouble here when
								// switching between windowed/fullscreen.
	HRESULT hr = wBlt( BackBufferSurface, &dstRect, video->m_pMMStreamSurface7, &srcRect, DDBLT_WAIT, NULL);
	GameOSFPU();
	AllowFail = false;
	if (hr == E_NOTIMPL)
	{
		// alternate blit code...
	}
	else
	if (hr == DDERR_DEVICEDOESNTOWNSURFACE)
	{
		// multi-monitor situation?
	}
}

///////////////////////////////////////////////////////////////////
//
// Return the play status of the specified video. Find out if its
// PLAYING, STOPPED, PAUSED.
//
gosVideo_PlayMode			
	gosVideo_GetVideoPlayMode(HGOSVIDEO video)
{
	gosASSERT(video);
	if (NoVideoPlayback)
		return gosVideo_Stop;

	if( 1==LogMode )	// Log file playing back?
	{
		DWORD retVal;
		retVal = ReadBitStream(4,Log_Media);
		return (gosVideo_PlayMode) retVal;
	}
	else
	{
		if ( !(video->m_pMMStream) )
		{
			WriteBitStream(gosVideo_Stop,4,Log_Media);
			return gosVideo_Stop;
		}
		else
		{

			WriteBitStream(video->m_videoStatus,4,Log_Media);
			return video->m_videoStatus;
		}
	}
}


///////////////////////////////////////////////////////////////////
//
// Create the basic mm stream for a a/v stream.
//
void
	gos_Video::OpenMMStream(const char * pszFileName, IDirectDraw *pDD, IMultiMediaStream **ppMMStream)
{
    *ppMMStream = NULL;
    IAMMultiMediaStream *
		pAMStream = NULL;

	if (gos_DoesFileExist(pszFileName) == FALSE)
	{
		STOP(( gos_GetResourceString(gLanguageDLL, GOS_FILE_NOT_FOUND), pszFileName )); 
	}
//
// Create and init the stream object
//
    wCoCreateInstance(CLSID_AMMultiMediaStream, NULL, CLSCTX_INPROC_SERVER, IID_IAMMultiMediaStream, (void **)&pAMStream);
	if (pAMStream == NULL)
	{
		STOP(( gos_GetResourceString(gLanguageDLL, GOS_VIDEO_NODIRECTSHOW) )); 
	}
    wInitialize(pAMStream, STREAMTYPE_READ, 0, NULL);
//
// Add video and audio streams (unless audio disabled)
//
    wAddMediaStream(pAMStream, pDD, &MSPID_PrimaryVideo, 0, NULL);
	if ( SoundRenderer.m_Mixer && !DisableSoundSystem )
	    wAddMediaStream(pAMStream, NULL, &MSPID_PrimaryAudio, AMMSF_ADDDEFAULTRENDERER, NULL);
//
// Open the file
//
    WCHAR       wPath[MAX_PATH];
    MultiByteToWideChar(CP_ACP, 0, pszFileName, -1, wPath, sizeof(wPath)/sizeof(wPath[0]));

	AllowFail = true;
	HRESULT result=wOpenFile(pAMStream,wPath, 0);
	AllowFail = false;

    if( FAILED(result) )
	{
		wRelease(pAMStream);
		*ppMMStream = 0;
		m_pBasicAudio = 0;
		PAUSE(( "You do not have the correct video codec installed to play '%s'", pszFileName));
		return;
	};
//
//
//    
	*ppMMStream = pAMStream;
    wAddRef( pAMStream );

	IGraphBuilder * pGB;
	wGetFilterGraph( pAMStream, &pGB );
	IMediaControl * pMC = NULL;
    wQueryInterface(pGB,IID_IMediaControl, (void **)&pMC);

	// if the next function fails, we know that the media file has no audio track in it and
	// ppBasicAudio remains NULL.

	m_pBasicAudio = 0;
	if (SoundRenderer.m_Mixer)
	{
		AllowFail = true;
		wQueryInterface(pMC,IID_IBasicAudio, (void **)&m_pBasicAudio);
		AllowFail = false;
	}

	wRelease(pMC);
	wRelease(pGB);
    wRelease(pAMStream);
}

///////////////////////////////////////////////////////////////////
//
// Create an a/v stream and make sure it is synched
//
gos_Video::gos_Video( char * path, bool texture = 0)
{
	gosASSERT(path);
	LPDIRECTDRAW lpDDtmp;

	m_texture = NULL;
	m_pSample = NULL;
	m_pMMStream = NULL;
	m_pPrimaryVidStream = NULL;
	m_pMMStreamSurface = NULL;
	m_pMMStreamSurface7 = NULL;
	m_pDDStream = NULL;
	m_videoStatus = gosVideo_Stop;
	m_videoPlayMode = gosVideo_Stop;
	m_pBasicAudio = NULL;
	m_lastKnownTime = m_nextFrameTime = 0L;
	m_volume = 1.0f;
	m_panning = 0.0f;

	m_lpPath = (char *) gos_Malloc(strlen(path)+1,gos_Video::m_videoHeap);
	strcpy(m_lpPath, path);
	if (NoVideoPlayback)
	{
		g_activeVideos.Add(this);
		return;
	}

//
// Get a DirectDraw 1 interface (used by DirectShow)
//
	wQueryInterface(CurrentDDobject, IID_IDirectDraw, (void **) &lpDDtmp);
//
// Create the stream
//
	OpenMMStream(path, lpDDtmp, &m_pMMStream);
	wRelease(lpDDtmp);

	if(!m_pMMStream)
	{
		g_activeVideos.Add(this);
		return;
	}

    wGetMediaStream(m_pMMStream,MSPID_PrimaryVideo, &m_pPrimaryVidStream);
    wQueryInterface(m_pPrimaryVidStream,IID_IDirectDrawMediaStream, (void **)&m_pDDStream);
//
// get orig format here
//
	DWORD flags = DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_CAPS;
	DDSURFACEDESC originalMovie;
	memset(&originalMovie,0,sizeof(originalMovie));
	originalMovie.dwSize = sizeof(originalMovie);
	AllowFail = true;
	HRESULT result=wGetFormat( m_pDDStream, &originalMovie, NULL, &flags );
	AllowFail = false;
	if( FAILED(result) )
		STOP(( gos_GetResourceString(gLanguageDLL, GOS_NO_VIDEO), m_lpPath));

	m_videoSrcRect.top = 0;
	m_videoSrcRect.bottom = originalMovie.dwHeight;
	m_videoSrcRect.right = originalMovie.dwWidth;
	m_videoSrcRect.left = 0;
	
	if (!texture)
	{
//
// Playing a video full screen
//
		DDSURFACEDESC2 ofBackBuffer;
		memset(&ofBackBuffer,0,sizeof(ofBackBuffer));
		ofBackBuffer.dwSize = sizeof(ofBackBuffer);

		DDSURFACEDESC ofBackBufferOld;
		memset(&ofBackBufferOld,0,sizeof(ofBackBufferOld));
		ofBackBufferOld.dwSize = sizeof(ofBackBufferOld);
		
		LPDIRECTDRAWSURFACE7 tmp;
		LPDIRECTDRAWSURFACE  tmpOld;

		wQueryInterface(BackBufferSurface,IID_IDirectDrawSurface7,(void**) &tmp);
		wGetSurfaceDesc( tmp, &ofBackBuffer );
		wRelease( tmp );

		wQueryInterface(BackBufferSurface,IID_IDirectDrawSurface,(void**) &tmpOld);
		wGetSurfaceDesc( tmpOld, &ofBackBufferOld );
		wRelease( tmpOld );

		ofBackBuffer.dwWidth = originalMovie.dwWidth;
		ofBackBuffer.dwHeight = originalMovie.dwHeight;
		ofBackBuffer.dwFlags = DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_CAPS;
		ofBackBuffer.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
		
		ofBackBufferOld.dwWidth = originalMovie.dwWidth;
		ofBackBufferOld.dwHeight = originalMovie.dwHeight;
		ofBackBufferOld.dwFlags = DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_CAPS;
		ofBackBufferOld.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
		
		wSetFormat( m_pDDStream, &ofBackBufferOld );

		wCreateSurface( CurrentDDobject, &ofBackBuffer, &m_pMMStreamSurface7, NULL );

		wQueryInterface( m_pMMStreamSurface7, IID_IDirectDrawSurface, (void**)&m_pMMStreamSurface );
		wCreateSample( m_pDDStream, m_pMMStreamSurface, 0, DDSFF_PROGRESSIVERENDER, &m_pSample );
	
		m_pMMStreamSurfaceDesc.dwSize=sizeof(m_pMMStreamSurfaceDesc);
		wGetSurfaceDesc( m_pMMStreamSurface7, &m_pMMStreamSurfaceDesc );		// Cache surface information

		gos_ChangeHeapSize( gos_Video::m_videoHeap, m_pMMStreamSurfaceDesc.lPitch*m_pMMStreamSurfaceDesc.dwHeight, 1 );
	}
	else
	{
//
// Playing a video to a texture
//
		m_texture = gos_NewEmptyTexture( gos_Texture_Solid, "Movie texture", RECT_TEX( originalMovie.dwWidth, originalMovie.dwHeight ), gosHint_DisableMipmap );
		wQueryInterface(TEXHANDLE_TO_PTR(m_texture)->GetOriginalSurf(false)   ,IID_IDirectDrawSurface,(void**) &m_pMMStreamSurface);

		m_pMMStreamSurfaceDesc.dwSize=sizeof(m_pMMStreamSurfaceDesc);
		wLock( TEXHANDLE_TO_PTR(m_texture)->GetOriginalSurf(false), NULL, &m_pMMStreamSurfaceDesc, DDLOCK_NOSYSLOCK | DDLOCK_WAIT, NULL );		// Cache surface information
		wUnlock( TEXHANDLE_TO_PTR(m_texture)->GetOriginalSurf(false), NULL );

		wGetSurfaceDesc( m_pMMStreamSurface, &originalMovie );
		originalMovie.dwHeight = m_videoSrcRect.bottom;
		originalMovie.dwWidth = m_videoSrcRect.right;
		RECT rectToUse = {0,0, originalMovie.dwWidth, originalMovie.dwHeight};
		wSetFormat(m_pDDStream,&originalMovie);
	    wCreateSample(m_pDDStream,m_pMMStreamSurface, &rectToUse, DDSFF_PROGRESSIVERENDER, &m_pSample);
	}

	m_scaleX = 1.0f;
	m_scaleY = 1.0f;
	m_videoLocX = -1;
	m_videoLocY = -1;

	// mark the videoRect so that we only blit once a setcoords has been done
	m_videoRect.left = 0;
	m_videoRect.top = 0;
	m_videoRect.right = (U32)(m_videoSrcRect.right);
	m_videoRect.bottom = (U32)(m_videoSrcRect.bottom);
	
	wGetDuration(m_pMMStream, &m_duration);
    wSetState(m_pMMStream,STREAMSTATE_STOP);

	g_activeVideos.Add(this);
}

///////////////////////////////////////////////////////////////////
//
// Kill the data relating to a particular videostream and remove
// it from the list of videostreams.
//
gos_Video::~gos_Video()
{
	g_activeVideos.Del(this);

	if( !NoVideoPlayback )
	{
		if (m_videoStatus != gosVideo_Stop) 
			Stop();

		if( m_pMMStreamSurface7 )
		{
			wRelease( m_pMMStreamSurface7 );
			m_pMMStreamSurface7=0;
		}
		if( m_pMMStreamSurface )
		{
			if( !m_texture )
				gos_ChangeHeapSize(gos_Video::m_videoHeap, -m_pMMStreamSurfaceDesc.lPitch * m_pMMStreamSurfaceDesc.dwHeight, 1 );
			wRelease( m_pMMStreamSurface );
			m_pMMStreamSurface=0;
		}
		if (m_pSample) 
		{
			wRelease(m_pSample);
			m_pSample=0;
		}
		if (m_pMMStream) 
		{
			wRelease(m_pMMStream);
			m_pMMStream=0;
		}
		if (m_pBasicAudio) 
		{
			wRelease(m_pBasicAudio);
			m_pBasicAudio=0;
		}
		if (m_pPrimaryVidStream) 
		{
			wRelease(m_pPrimaryVidStream);				
			m_pPrimaryVidStream=0;
		}
		if (m_pDDStream) 
		{
			wRelease(m_pDDStream);
			m_pDDStream=0;
		}
		if (m_texture)
		{
			gos_DestroyTexture(m_texture);
			m_texture=0;
		}
	}
	if (m_lpPath)
	{
		gos_Free(m_lpPath);				
		m_lpPath = 0;
	}
}

///////////////////////////////////////////////////////////////////
//
// If a video is playing, update the stream. If the video is done
// playing and the looping flag is set, wrap the stream around to
// the beginning. If not set the status flag to STOPPED.
//
bool
	gos_Video::Update()
{
	// if paused, keep display the current frame/sound
	if (!m_pMMStream || NoVideoPlayback)
		return FALSE;
	if (m_videoStatus == gosVideo_Pause || 
		(m_videoStatus == gosVideo_Stop && m_videoPlayMode == gosVideo_PlayOnceHold ) )
	{
		return TRUE; 
	}

	//
	// check to see whether we're ready to update the frame.
	// if we don't do this check, continually calling Update will
	// drag our app's framerate down to that of the movie with the
	// lowest framerate. Not good.
	//
	STREAM_TIME startMe;
	wGetSampleTimes( m_pSample, &startMe, &m_nextFrameTime, &m_lastKnownTime );
//
// These variables are badly named. If the 'current' time is not within the start/end time of the current frame, update to the next frame.
//
	if( m_lastKnownTime<m_nextFrameTime && m_lastKnownTime>startMe )
		return false;

	SafeFPU();					// Make sure exceptions cannot be generated
	HRESULT hr=wUpdate( m_pSample, 0 , NULL, NULL, 0 );
	GameOSFPU();				// Restore current GameOS FPU control word setup
	if( hr!=S_OK )
	{
		if (m_videoPlayMode == gosVideo_PlayOnceHide)
		{
			Stop();
			return FALSE;
		}
		if (m_videoPlayMode == gosVideo_PlayOnceHold)
		{
			m_videoStatus = gosVideo_Stop;
			return TRUE;
		}
		else
		{
			// go to the start of the movie
			wSeek( m_pMMStream, 0 );
		    wSetState( m_pMMStream, STREAMSTATE_RUN );
			wGetSampleTimes( m_pSample, &startMe, &m_nextFrameTime, &m_lastKnownTime );

			SafeFPU();					// Make sure exceptions cannot be generated
			HRESULT hr=wUpdate( m_pSample, 0, NULL, NULL, 0 );
			GameOSFPU();				// Restore current GameOS FPU control word setup

			if( hr!=S_OK )
			{
				// couldn't loop
				return FALSE;
			}
			if (m_texture)
			{
				TEXTUREPTR ti;
				gos_LockTexture(m_texture, 0, false, &ti);
				gos_UnLockTexture(m_texture);
			}
			return TRUE;
		}
	}
	else
	{
		if (m_texture)
		{
			TEXTUREPTR ti;
			gos_LockTexture(m_texture, 0, false, &ti);
			gos_UnLockTexture(m_texture);
		}
		return TRUE;
	}
}

///////////////////////////////////////////////////////////////////
//
// Stop the video at its current location. The video can be 
// restarted by calling "play" again.
//
void
	gos_Video::Stop()
{
	if ( !m_pMMStream  || NoVideoPlayback)
		return;
    m_videoPlayMode = gosVideo_Stop;
    m_videoStatus = gosVideo_Stop;
    wSetState(m_pMMStream, STREAMSTATE_STOP);
}

///////////////////////////////////////////////////////////////////
//
// Pause the video at its current location.
//
void
	gos_Video::Pause()
{
	SPEW((GROUP_DIRECTSHOW, "Pausing a movie"));
	if (m_videoStatus == gosVideo_Pause || m_videoStatus == gosVideo_Stop || NoVideoPlayback)
		return;
	gosASSERT(m_videoStatus == gosVideo_Loop || m_videoStatus == gosVideo_PlayOnceHide || m_videoStatus == gosVideo_PlayOnceHold );
    m_videoStatus = gosVideo_Pause;
	if (m_pBasicAudio)
		wPut_Volume(m_pBasicAudio,-10000);
}

///////////////////////////////////////////////////////////////////
//
// Set the video to running mode beginning at where we left off
// when we paused.
//
void
	gos_Video::Continue()
{
	SPEW((GROUP_DIRECTSHOW, "Continuing a movie"));
	if ( !m_pMMStream || NoVideoPlayback )
		return;
	if (m_videoPlayMode == gosVideo_Stop)
		return;
	if ( m_videoStatus != gosVideo_Pause && !DisableVideoPlayback)
		return;
	if (m_pSample)
	{
		SafeFPU();					// Make sure exceptions cannot be generated
		wUpdate(m_pSample, 0, NULL, NULL, 0);
		GameOSFPU();				// Restore current GameOS FPU control word setup
    }
	m_videoStatus = m_videoPlayMode;
	gosVideo_Command(this, gosVideo_Volume, m_volume);
}

///////////////////////////////////////////////////////////////////
//
// Change the location of the upper left corner of the video with
// respect to the destination surface.
//
void
	gos_Video::SetLocation(DWORD x, DWORD y)
{
	if ( !m_pMMStream )
		return;
	m_videoLocX = x;
	m_videoLocY = y;
	m_videoRect.left = m_videoLocX;
	m_videoRect.top = m_videoLocY;
	m_videoRect.right = (U32)(m_videoSrcRect.right*m_scaleX)+ m_videoLocX;
	m_videoRect.bottom = (U32)(m_videoSrcRect.bottom*m_scaleY) + m_videoLocY;
}

void __stdcall gosVideo_Command( HGOSVIDEO video, enum gosVideo_Command vc, float x, float y)
{
	if ( !(video->m_pMMStream) || NoVideoPlayback)
		return;
#ifdef LAB_ONLY
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();
	HGOSVIDEO 
		v;
	bool found = false;
	while((v = itr.ReadAndNext()) != NULL)
	{
		if (v == video)
		{
			found = true;
			break;
		}
	}
	if (!found)
		STOP(( "gosVideo_DestroyResource was passed a bad HGOSVIDEO"));
#endif
	switch(	vc )
	{
	case gosVideo_SeekTime: 		// Seek to a frame or timestamp in <hVideo>
		{
			STREAM_TIME stime;
			stime = (STREAM_TIME) x*10000000;
#ifdef LAB_ONLY
			STREAM_TIME dur;
			wGetTime(video->m_pMMStream, &dur);
			if (stime > dur)
				STOP(( "You've attempted to Seek past the end of a video" ));
#endif
			wSeek(video->m_pMMStream,stime);
			SafeFPU();					// Make sure exceptions cannot be generated
			wUpdate( video->m_pSample,0, NULL, NULL, 0 );
			GameOSFPU();				// Restore current GameOS FPU control word setup
			break;
		};
	case gosVideo_SetCoords:		// Set the destination coordinates of <hVideo> via <dwOriginX/Y>
		{
			video->SetLocation( (unsigned long) x, (unsigned long) y);
			break;
		};
	case gosVideo_SetScale:			// Change the scale of <hVideo> on the fly via <fScaleOfX/Y>
		{
			gosASSERT(x > 0 && y > 0);
			video->m_scaleX = x;
			video->m_scaleY = y;
			video->m_videoRect.right = (U32)(video->m_videoSrcRect.right*x+video->m_videoRect.left);
			video->m_videoRect.bottom = (U32)(video->m_videoSrcRect.bottom*y+video->m_videoRect.top);
			break;
		};
	case gosVideo_Volume:
		{
			if (x < 0) x = 0.0f;
			if (x > 1.0) x = 1.0f;
			video->m_volume = x;
			if (SoundRenderer.m_Mixer)
			{
				if (video->m_pBasicAudio)
				{
					if (x == 0.0f)
					{
						AllowFail = true;
						wPut_Volume( video->m_pBasicAudio, -10000 );
						AllowFail = false;
					}
					else
					{
						AllowFail = true;
						wPut_Volume( video->m_pBasicAudio, (LONG) (log(x)*1000.0f ) );
						AllowFail = false;
					}
				}
			}
			break;
		}
	case gosVideo_Panning:
		{
			if (x < -1.0) x = -1.0f;
			if (x > 1.0) x = 1.0f;
			video->m_panning = x;
			if (SoundRenderer.m_Mixer)
			{
				if (video->m_pBasicAudio  )
				{
					if (x > 0.0f)
					{
						if (x >= .99f)
						{
							AllowFail = 1;
							wPut_Balance( video->m_pBasicAudio, 10000 );
							AllowFail = 0;
						}
						else
						{
							AllowFail = 1;
							wPut_Balance( video->m_pBasicAudio, (LONG)((log(1.0f-x)*-1000.0f)) );
							AllowFail = 0;
						}
					}
					else
					if (x < 0.0f)
					{
						if (x <= -.99f)
						{
							AllowFail = 1;
							wPut_Balance( video->m_pBasicAudio, -10000 );
							AllowFail = 0;
						}
						else
						{
							AllowFail = 1;
							wPut_Balance( video->m_pBasicAudio, (LONG)((log(1.0f+x)*1000.0f)) );
							AllowFail = 0;
						}
					}
					else
					{
						AllowFail = 1;
						wPut_Balance( video->m_pBasicAudio, 0 );
					 	AllowFail = 0;
					}
				}
			}
			break;
		}
	}
}

void __stdcall gosVideo_SetPlayMode(HGOSVIDEO video, enum gosVideo_PlayMode gvpm)
{
	if ( !(video->m_pMMStream) || NoVideoPlayback)
		return;
#ifdef LAB_ONLY
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();
	HGOSVIDEO 
		v;
	bool found = false;
	while((v = itr.ReadAndNext()) != NULL)
	{
		if (v == video)
		{
			found = true;
			break;
		}
	}
	if (!found)
		STOP(( "gosVideo_DestroyResource was passed a bad HGOSVIDEO"));
#endif
	switch(gvpm)
	{
	case gosVideo_PlayOnceHide:		// The video is currently playing (will hide when done)
	case gosVideo_PlayOnceHold:		// The video is currently playing (will hold when done)
	case gosVideo_Loop:				// The video is currently in continuous play mode
		{
			video->m_videoPlayMode = gvpm;
			video->m_videoStatus = gvpm;
			video->m_videoRect.left = video->m_videoLocX;
			video->m_videoRect.top = video->m_videoLocY;
			video->m_videoRect.right = (U32)(video->m_videoSrcRect.right*video->m_scaleX)+ video->m_videoLocX;
			video->m_videoRect.bottom = (U32)(video->m_videoSrcRect.bottom*video->m_scaleY) + video->m_videoLocY;

			// go to the start of the movie
			wSeek(video->m_pMMStream,0);
		    wSetState(video->m_pMMStream, STREAMSTATE_RUN);
			break;
		}
	case gosVideo_Stop:				// The video is stopped
		{
			video->Stop();
			break;
		}
	case gosVideo_Pause:				// The video has been paused
		{
			video->Pause();
			break;
		}
	case gosVideo_Continue:			// SET ONLY: continue a paused video
		{
			video->Continue();
			break;
		}
	}
}

void gos_Video::FF(double time)
{
	//
	// To FF the movie, grab the current time off the stream and then
	// add a seek based on the time we've been past.
	//

	STREAM_TIME     current;
	STREAM_TIME		work;

	wGetTime(m_pMMStream, &current);
	current += (_int64) (time *10000000.0f);
	current %= m_duration;
	wSeek(m_pMMStream, current); 

	//
	// if we don't force this update, gos_Video::Update won't
	// for awhile. do it here.
	//
	SafeFPU();					// Make sure exceptions cannot be generated
	if (!FrameAdvance)
	{
		while (current < m_nextFrameTime)
		{
			wUpdate(m_pSample, 0, 0, NULL, 0);
			wGetSampleTimes( m_pSample, &work, &m_nextFrameTime, &current );
		}
	}
	if (m_texture)
	{
		TEXTUREPTR ti;
		gos_LockTexture(m_texture, 0, false, &ti);
		gos_UnLockTexture(m_texture);
	}
	GameOSFPU();				// Restore current GameOS FPU control word setup
}

void VideoManagerFF(double sec)
{
	if (NoVideoPlayback)
		return;
	if (gFreezeLogic || (DebuggerActive && (!gGameLogicActive  || gStopSystem)) )
	{
		if (!gMediaPause)
		{
			VideoManagerPause();
		}
	}
	else
	{
		if (gMediaPause)
			VideoManagerContinue();
	}

	HGOSVIDEO 
		video;
	LinkedListIterator<HGOSVIDEO> itr 
		= LinkedListIterator<HGOSVIDEO> (&g_activeVideos);
	itr.Head();

	while((video = itr.ReadAndNext()) != NULL)
		if (video->m_videoStatus == gosVideo_Pause ||
			video->m_videoStatus == gosVideo_PlayOnceHide || 
			video->m_videoStatus == gosVideo_PlayOnceHold || 
			video->m_videoStatus == gosVideo_Loop)
		{
			 video->FF(sec);
		}
}




//
// Release all surafces (about to change device)
//
void gos_Video::Release()
{
	if( m_pMMStreamSurface7 )
	{
		wRelease( m_pMMStreamSurface7 );
		m_pMMStreamSurface7=0;
	}
	if( m_pMMStreamSurface )
	{
		if( !m_texture )
			gos_ChangeHeapSize(gos_Video::m_videoHeap, -m_pMMStreamSurfaceDesc.lPitch * m_pMMStreamSurfaceDesc.dwHeight, 1 );
		wRelease( m_pMMStreamSurface );
		m_pMMStreamSurface=0;
	}
	if (m_pSample) 
	{
		wRelease(m_pSample);
		m_pSample=0;
	}
	if (m_pMMStream) 
	{
		wRelease(m_pMMStream);
		m_pMMStream=0;
	}
	if (m_pBasicAudio) 
	{
		wRelease(m_pBasicAudio);
		m_pBasicAudio=0;
	}
	if (m_pPrimaryVidStream) 
	{
		wRelease(m_pPrimaryVidStream);				
		m_pPrimaryVidStream=0;
	}
	if (m_pDDStream) 
	{
		wRelease(m_pDDStream);
		m_pDDStream=0;
	}





}







void gos_Video::Restore()
{
//
// Get a DirectDraw 1 interface (used by DirectShow)
//
	LPDIRECTDRAW lpDDtmp = NULL;
	wQueryInterface(CurrentDDobject, IID_IDirectDraw, (void **) &lpDDtmp);
	gosASSERT(lpDDtmp != NULL);
//
// Create the stream
//
	OpenMMStream(m_lpPath, lpDDtmp, &m_pMMStream);
	wRelease(lpDDtmp);

	if(!m_pMMStream)
		return;

    wGetMediaStream(m_pMMStream,MSPID_PrimaryVideo, &m_pPrimaryVidStream);
    wQueryInterface(m_pPrimaryVidStream,IID_IDirectDrawMediaStream, (void **)&m_pDDStream);
//
// get orig format here
//
	DWORD flags = DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_CAPS;
	DDSURFACEDESC originalMovie;
	memset(&originalMovie,0,sizeof(originalMovie));
	originalMovie.dwSize = sizeof(originalMovie);
	AllowFail = true;
	HRESULT result=wGetFormat( m_pDDStream, &originalMovie, NULL, &flags );
	AllowFail = false;
	if( FAILED(result) )
		STOP(( gos_GetResourceString(gLanguageDLL, GOS_NO_VIDEO), m_lpPath));

	m_videoSrcRect.top = 0;
	m_videoSrcRect.bottom = originalMovie.dwHeight;
	m_videoSrcRect.right = originalMovie.dwWidth;
	m_videoSrcRect.left = 0;
	
	if (!m_texture)
	{
//
// Playing a video full screen
//
		DDSURFACEDESC2 ofBackBuffer;
		memset(&ofBackBuffer,0,sizeof(ofBackBuffer));
		ofBackBuffer.dwSize = sizeof(ofBackBuffer);

		DDSURFACEDESC ofBackBufferOld;
		memset(&ofBackBufferOld,0,sizeof(ofBackBufferOld));
		ofBackBufferOld.dwSize = sizeof(ofBackBufferOld);
		
		LPDIRECTDRAWSURFACE7 tmp;
		LPDIRECTDRAWSURFACE  tmpOld;

		wQueryInterface(BackBufferSurface,IID_IDirectDrawSurface7,(void**) &tmp);
		wGetSurfaceDesc( tmp, &ofBackBuffer );
		wRelease( tmp );

		wQueryInterface(BackBufferSurface,IID_IDirectDrawSurface,(void**) &tmpOld);
		wGetSurfaceDesc( tmpOld, &ofBackBufferOld );
		wRelease( tmpOld );

		ofBackBuffer.dwWidth = originalMovie.dwWidth;
		ofBackBuffer.dwHeight = originalMovie.dwHeight;
		ofBackBuffer.dwFlags = DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_CAPS;
		ofBackBuffer.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
		
		ofBackBufferOld.dwWidth = originalMovie.dwWidth;
		ofBackBufferOld.dwHeight = originalMovie.dwHeight;
		ofBackBufferOld.dwFlags = DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | DDSD_CAPS;
		ofBackBufferOld.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | DDSCAPS_SYSTEMMEMORY;
		
		wSetFormat( m_pDDStream, &ofBackBufferOld );

		wCreateSurface( CurrentDDobject, &ofBackBuffer, &m_pMMStreamSurface7, NULL );

		wQueryInterface( m_pMMStreamSurface7, IID_IDirectDrawSurface, (void**)&m_pMMStreamSurface );
		wCreateSample( m_pDDStream, m_pMMStreamSurface, 0, DDSFF_PROGRESSIVERENDER, &m_pSample );
	
		m_pMMStreamSurfaceDesc.dwSize=sizeof(m_pMMStreamSurfaceDesc);
		wGetSurfaceDesc( m_pMMStreamSurface7, &m_pMMStreamSurfaceDesc );		// Cache surface information

		gos_ChangeHeapSize( gos_Video::m_videoHeap, m_pMMStreamSurfaceDesc.lPitch*m_pMMStreamSurfaceDesc.dwHeight, 1 );
	}
	else
	{
//
// Playing a video to a texture
//
		wQueryInterface(TEXHANDLE_TO_PTR(m_texture)->GetOriginalSurf(false)   ,IID_IDirectDrawSurface,(void**) &m_pMMStreamSurface);

		m_pMMStreamSurfaceDesc.dwSize=sizeof(m_pMMStreamSurfaceDesc);
		wLock( TEXHANDLE_TO_PTR(m_texture)->GetOriginalSurf(false), NULL, &m_pMMStreamSurfaceDesc, DDLOCK_NOSYSLOCK | DDLOCK_WAIT, NULL );		// Cache surface information
		wUnlock( TEXHANDLE_TO_PTR(m_texture)->GetOriginalSurf(false), NULL );

		wGetSurfaceDesc( m_pMMStreamSurface, &originalMovie );
		originalMovie.dwHeight = m_videoSrcRect.bottom;
		originalMovie.dwWidth = m_videoSrcRect.right;
		RECT rectToUse = {0,0, originalMovie.dwWidth, originalMovie.dwHeight};
		wSetFormat(m_pDDStream,&originalMovie);
	    wCreateSample(m_pDDStream,m_pMMStreamSurface, &rectToUse, DDSFF_PROGRESSIVERENDER, &m_pSample);
	}
	if (m_videoPlayMode == gosVideo_Stop || m_videoPlayMode == gosVideo_Pause)
	{
		wSeek( m_pMMStream, m_nextFrameTime);
	}
	else
	{
		wSeek( m_pMMStream, m_nextFrameTime);
	    wSetState( m_pMMStream, STREAMSTATE_RUN );
		wSeek( m_pMMStream, m_nextFrameTime);
		wUpdate(m_pSample, 0,0,0,0);
		wUpdate(m_pSample, 0,0,0,0);
	}
}