windows-nt/Source/XPSP1/NT/multimedia/media/avi/mciavi32/avitask.c
2020-09-26 16:20:57 +08:00

2876 lines
75 KiB
C

/******************************************************************************
Copyright (C) Microsoft Corporation 1985-1995. All rights reserved.
Title: avitask.c - Background task that actually manipulates AVI files.
*****************************************************************************/
#include "graphic.h"
STATICFN BOOL OnTask_ProcessRequest(NPMCIGRAPHIC npMCI);
STATICFN void OnTask_WinProcRequests(NPMCIGRAPHIC npMCI, BOOL bPlaying);
STATICFN void OnTask_StopTemporarily(NPMCIGRAPHIC npMCI);
STATICFN void OnTask_RestartAgain(NPMCIGRAPHIC npMCI, BOOL bSetEvent);
STATICFN DWORD InternalPlayStart(
NPMCIGRAPHIC npMCI,
DWORD dwFlags,
long lReqTo,
long lReqFrom,
DWORD_PTR dwCallback
);
BOOL TryStreamUpdate(
NPMCIGRAPHIC npMCI,
DWORD dwFlags,
HDC hdc,
LPRECT lprc
);
/*
* design overview under construction
*
* this file contains the core code for the worker thread that manages
* playback on request from the user's thread. The worker thread also
* creates a wndproc thread that owns the default playback window.
*/
// set the error flag and signal completion of request
void
TaskReturns(NPMCIGRAPHIC npMCI, DWORD dwErr)
{
npMCI->dwReturn = dwErr;
// clear the hEventSend manual-reset event now that we
// have processed it
ResetEvent(npMCI->hEventSend);
#ifdef DEBUG
// make the message invalid
npMCI->message = 0;
#endif
// Wake up the thread that made the request
DPF2(("...[%x] ok", npMCI->hRequestor));
SetEvent(npMCI->hEventResponse);
}
/*
* KillWinProcThread:
*
* If the winproc thread exists, send a message to the window to cause
* the thread to destroy the window and terminate.
*/
STATICFN void KillWinProcThread(NPMCIGRAPHIC npMCI)
{
// kill the winproc thread and wait for it to die
if (npMCI->hThreadWinproc) {
INT_PTR bOK = TRUE;
if (npMCI->hwndDefault) {
// must destroy on creating thread
bOK = SendMessage(npMCI->hwndDefault, AVIM_DESTROY, 0, 0);
if (!bOK) {
DPF1(("failed to destroy window: %d", GetLastError()));
} else {
Assert(!IsWindow(npMCI->hwndDefault));
}
}
// wait for winproc thread to destroy itself when the window goes
if (bOK) {
WaitForSingleObject(npMCI->hThreadWinproc, INFINITE);
}
CloseHandle(npMCI->hThreadWinproc);
npMCI->hThreadWinproc = 0;
}
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api void | mciaviTask | This function is the background task which plays
* AVI files. It is called as a result of the call to mmTaskCreate()
* in DeviceOpen(). When this function returns, the task is destroyed.
*
* @parm DWORD | dwInst | instance data passed to mmCreateTask - contains
* a pointer to an instance data block.
*
***************************************************************************/
void FAR PASCAL _LOADDS mciaviTask(DWORD_PTR dwInst)
{
NPMCIGRAPHIC npMCI;
DWORD dwThreadId;
BOOL bExit = FALSE;
npMCI = (NPMCIGRAPHIC) dwInst;
// Set this task's error mode to the same as the parent's.
SetErrorMode(npMCI->uErrorMode);
DPF1(("Bkgd Task hTask=%04X\n", GetCurrentTask()));
/* We must set hTask up before changing the TaskState as the UI */
/* thread can abort as soon as wTaskState is altered */
/* NB: this comment is no longer true. Since the rewrite of */
/* mciavi the UI thread will create the task thread and wait */
/* until it is explicitly signalled. */
npMCI->hTask = GetCurrentTask();
npMCI->wTaskState = TASKIDLE;
npMCI->dwTaskError = 0;
// create a critical section to protect window-update code between
// the worker and the winproc thread
InitializeCriticalSection(&npMCI->HDCCritSec);
SetNTFlags(npMCI, NTF_DELETEHDCCRITSEC);
// create a critical section to protect window-request code between
// the worker and the winproc thread
InitializeCriticalSection(&npMCI->WinCritSec);
SetNTFlags(npMCI, NTF_DELETEWINCRITSEC);
// create an event to wait on for the winproc thread to tell us that
// init is ok.
npMCI->hEventWinProcOK = CreateEvent(NULL, FALSE, FALSE, NULL);
// also a second event that the winproc signals when it has
// requests for us
npMCI->heWinProcRequest = CreateEvent(NULL, TRUE, FALSE, NULL);
if (!npMCI->hEventWinProcOK || !npMCI->heWinProcRequest) {
npMCI->dwTaskError = MCIERR_OUT_OF_MEMORY;
mciaviTaskCleanup(npMCI);
// Abort this thread. Our waiter will wake up when our thread
// handle is signalled.
return;
}
// create a second background task to create the default window and
// own the winproc.
#if 0
if (mmTaskCreate((LPTASKCALLBACK) aviWinProcTask,
&npMCI->hThreadWinproc,
(DWORD)(UINT)npMCI) == 0)
#else
if (npMCI->hThreadWinproc = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)aviWinProcTask,
(LPVOID)npMCI, 0, &dwThreadId ))
#endif
{
// wait to be sure that window can be created
// hThreadWinproc will be signalled if the thread exits.
if (WaitForMultipleObjects(2, &npMCI->hThreadWinproc,
FALSE, INFINITE) == WAIT_OBJECT_0) {
// thread aborted
npMCI->dwTaskError = MCIERR_CREATEWINDOW;
// dont wait for this thread in cleanup phase
CloseHandle(npMCI->hThreadWinproc);
npMCI->hThreadWinproc = 0;
mciaviTaskCleanup(npMCI);
// Abort this thread. Our waiter will wake up when our thread
// handle is signalled.
return;
}
} else {
// could not create winproc thread
npMCI->dwTaskError = MCIERR_CREATEWINDOW;
mciaviTaskCleanup(npMCI);
// Abort this thread. Our waiter will wake up when our thread
// handle is signalled.
return;
}
/* Open the file */
// open has now been done on app thread -complete init here.
if (!OpenFileInit(npMCI)) {
DPF1(("Failed to complete open of AVI file\n"));
mciaviTaskCleanup(npMCI);
// Abort this thread. Our waiter will wake up when our thread
// handle is signalled.
return;
}
// signal that the open is complete
TaskReturns(npMCI, 0);
// now loop waiting for requests and processing them
// ProcessRequest returns TRUE when it is time to exit
// hEventSend is manual-reset so we can poll it during play.
// it is reset in TaskReturns just before we signal the response
// event.
// hEventAllDone is set here for bDelayedComplete requests
// (eg play+wait) when the entire request is satisfied and
// the worker thread is back to idle. hEventResponse is set in
// ProcessMessage when the request itself is complete - eg for play, once
// play has started the event will be set.
// we can't handle more than one thread potentially blocking on
// hEventAllDone at one time, so while one thread has made a request
// that could block on hEventAllDone, no other such request is permitted
// from other threads. In other words, while one (user) thread has
// requested a play+wait, other threads can request stop, but not
// play + wait.
while (!bExit) {
DWORD dwObject;
npMCI->wTaskState = TASKIDLE;
#ifdef DEBUG
// A complex assertion. If we have stopped temporarily, then we
// do not want to go back to sleep.
if ((npMCI->dwFlags & MCIAVI_UPDATING)
&& (WAIT_TIMEOUT
== WaitForMultipleObjects(IDLEWAITFOR, &npMCI->hEventSend, FALSE, 0))
) {
Assert(!"About to go to sleep when we should be restarting!");
}
#endif
// the OLE guys are kind enough to create windows on this thread.
// so we need to handle sent messages here to avoid deadlocks.
// same is true of the similar loop in BePaused()
do {
#ifdef DEBUG
if (npMCI->hWaiter) {
DPF(("About to call MsgWaitForMultipleObjects while hWaiter=%x\n", npMCI->hWaiter));
}
#endif
dwObject = MsgWaitForMultipleObjects(IDLEWAITFOR, &npMCI->hEventSend,
FALSE, INFINITE, QS_SENDMESSAGE);
DPF2(("Task woken up, dwObject=%x, hWaiter=%x\n", dwObject, npMCI->hWaiter));
if (dwObject == WAIT_OBJECT_0 + IDLEWAITFOR) {
MSG msg;
// just a single peekmessage with NOREMOVE will
// process the inter-thread send and not affect the queue
PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE);
}
} while (dwObject == WAIT_OBJECT_0 + IDLEWAITFOR);
switch (dwObject) {
case WAIT_OBJECT_0:
// check that the message has actually been set
Assert(npMCI->message != 0);
if (npMCI->bDelayedComplete) {
if (npMCI->hWaiter && (npMCI->hWaiter != npMCI->hRequestor)) {
TaskReturns(npMCI, MCIERR_DEVICE_NOT_READY);
continue;
} else {
DPF2(("Setting hWaiter to %x\n", npMCI->hRequestor));
npMCI->hWaiter = npMCI->hRequestor;
}
}
DPF2(("get %d [%x]...", npMCI->message, npMCI->hRequestor));
// we must reset this event here, or OnTask_PeekRequest will
// think it is a new request and will process and
// potentially complete it!!
ResetEvent(npMCI->hEventSend);
bExit = OnTask_ProcessRequest(npMCI);
break;
case WAIT_OBJECT_0+1:
default:
//
// winproc request to do something to the task - while idle
//
#ifdef DEBUG
if (dwObject != WAIT_OBJECT_0+1) {
DPF2(("dwObject == %d\n", dwObject));
}
#endif
Assert(dwObject == WAIT_OBJECT_0+1);
OnTask_WinProcRequests(npMCI, FALSE);
// this request may have resulted in a temporary stop - so we
// need to restart
if (npMCI->dwFlags & MCIAVI_UPDATING) {
OnTask_RestartAgain(npMCI, FALSE);
}
}
// if we have stopped temporarily to restart with new params,
// then don't signal completion. However if we did restart
// and now everything is quiescent, signal any waiter that happens
// to be around. This code is common to both the winproc request
// and user request legs, as it is possible to stop and restart
// from both winproc and user requests.
if (npMCI->hWaiter && (!(npMCI->dwFlags & MCIAVI_UPDATING))) {
SetEvent(npMCI->hEventAllDone);
} else {
if (npMCI->hWaiter) {
DPF2(("Would have Set hEventAllDone, but am updating\n"));
}
}
// QUERY: if we had processed all the requests, and therefore the
// two events on which we were waiting had been reset, AND
// MCIAVI_UPDATING is set (due to a temporary stop) then perhaps
// we ought to call OnTask_RestartAgain here. This would mean that
// all the ugly RestartAgain calls within OnTask_ProcessRequest
// could be removed.
}
// be sure to wake him up before cleanup just in case
if (npMCI->hWaiter) {
DPF2(("Setting hEventAllDone before closing\n"));
SetEvent(npMCI->hEventAllDone);
}
// close the window and destroy the winproc thread before any other
// cleanup, so that paints or realizes don't happen during
// a partially closed state
KillWinProcThread(npMCI);
mciaviCloseFile(npMCI);
mciaviTaskCleanup(npMCI);
return;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api void | mciaviTaskCleanup | called when the background task
* is being destroyed. This is where critical cleanup goes.
*
***************************************************************************/
void FAR PASCAL mciaviTaskCleanup(NPMCIGRAPHIC npMCI)
{
npMCI->hTask = 0;
/* Closing the driver causes any currently saved notifications */
/* to abort. */
GraphicDelayedNotify(npMCI, MCI_NOTIFY_ABORTED);
GdiFlush();
// if still alive, kill the winproc thread and wait for it to die
KillWinProcThread(npMCI);
// free winproc <-> worker thread communication resources
if (npMCI->hEventWinProcOK) {
CloseHandle(npMCI->hEventWinProcOK);
}
if (npMCI->heWinProcRequest) {
CloseHandle(npMCI->heWinProcRequest);
}
if (TestNTFlags(npMCI, NTF_DELETEWINCRITSEC)) {
DeleteCriticalSection(&npMCI->WinCritSec);
}
if (TestNTFlags(npMCI, NTF_DELETEHDCCRITSEC)) {
DeleteCriticalSection(&npMCI->HDCCritSec);
}
//
// call a MSVideo shutdown routine.
//
}
// task message functions
// utility functions called on worker thread
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api void | ShowStage | This utility function brings the default stage
* window to the foreground on play, seek, step and pause commands. It
* does nothing if the stage window is not the default window
*
* @parm NPMCIGRAPHIC | npMCI | near ptr to the instance data
*
***************************************************************************/
void NEAR PASCAL ShowStage(NPMCIGRAPHIC npMCI)
{
if (!(npMCI->dwFlags & MCIAVI_NEEDTOSHOW))
return;
// don't show stage if we are working in response to a winproc
// update request, as this could cause deadlock and is any case
// pointless - if the window is now hidden, we can't possibly need
// to paint it!
if (npMCI->bDoingWinUpdate) {
return;
}
if ((npMCI->dwFlags & MCIAVI_SHOWVIDEO) &&
npMCI->hwndPlayback == npMCI->hwndDefault &&
////////////!(GetWindowLong(npMCI->hwnd, GWL_STYLE) & WS_CHILD) &&
(!IsWindowVisible (npMCI->hwndPlayback) ||
npMCI->hwndPlayback != GetActiveWindow ())) {
// if this is our window, then we need to show it
// ourselves
if (npMCI->hwndDefault == npMCI->hwndPlayback) {
WinCritCheckOut(npMCI);
PostMessage(npMCI->hwndPlayback, AVIM_SHOWSTAGE, 0, 0);
} else {
SetWindowPos(npMCI->hwndPlayback, HWND_TOP, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE |
SWP_SHOWWINDOW |
(IsWindowVisible(npMCI->hwndPlayback) ? SWP_NOACTIVATE : 0));
}
}
//
// if the movie has palette changes we need to make it the active
// window otherwise the palette animation will not work right
//
if ((npMCI->dwFlags & MCIAVI_ANIMATEPALETTE) &&
!(npMCI->dwFlags & MCIAVI_SEEKING) &&
!(npMCI->dwFlags & MCIAVI_FULLSCREEN) &&
!(npMCI->dwFlags & MCIAVI_UPDATING) &&
npMCI->hwndPlayback == npMCI->hwndDefault &&
!(GetWindowLong(npMCI->hwndPlayback, GWL_STYLE) & WS_CHILD)) {
// if this is our window, then we need to show it
// ourselves
if (npMCI->hwndDefault == npMCI->hwndPlayback) {
// force activation even if visible
WinCritCheckOut(npMCI);
PostMessage(npMCI->hwndPlayback, AVIM_SHOWSTAGE, TRUE, 0);
} else {
SetActiveWindow(npMCI->hwndPlayback);
}
}
npMCI->dwFlags &= ~(MCIAVI_NEEDTOSHOW);
}
//
// called to save state if we stop temporarily to change something.
// we will restart with OnTask_RestartAgain. Called on worker thread from
// somewhere in aviTaskCheckRequests
STATICFN void OnTask_StopTemporarily(NPMCIGRAPHIC npMCI)
{
// save old state and flags
npMCI->oldState = npMCI->wTaskState;
npMCI->oldTo = npMCI->lTo;
npMCI->oldFrom = npMCI->lFrom;
npMCI->oldFlags = npMCI->dwFlags;
npMCI->oldCallback = (DWORD_PTR) npMCI->hCallback;
npMCI->dwFlags |= (MCIAVI_UPDATING | MCIAVI_STOP);
DPF(("StopTemp: OldState=%d, oldTo=%d, oldFrom=%d, oldFlags=%8x\n",
npMCI->oldState, npMCI->oldTo, npMCI->oldFrom, npMCI->oldFlags));
}
// called from worker thread on completion of a (idle-time) request
// to restart a suspended play function
//
// responsible for signalling hEventResponse once the restart is complete
// (or failed).
STATICFN void OnTask_RestartAgain(NPMCIGRAPHIC npMCI, BOOL bSetEvent)
{
DWORD dwErr;
DWORD dwFlags = 0;
long lFrom;
UINT wNotify;
// we're restarting after a temporary stop- so clear the flag.
// also turn off REPEATING - we might reset this in a moment
npMCI->dwFlags &= ~(MCIAVI_UPDATING | MCIAVI_REPEATING);
// Turn on the repeating flag if it was originally set
npMCI->dwFlags |= (npMCI->oldFlags & MCIAVI_REPEATING);
if (npMCI->oldFlags & MCIAVI_REVERSE) {
dwFlags |= MCI_DGV_PLAY_REVERSE;
}
switch (npMCI->oldState) {
case TASKPAUSED:
// get to the old From frame and pause when you get there
npMCI->dwFlags |= MCIAVI_PAUSE; // Make sure we end up paused
// NOTE: InternalPlayStart no longer clears the PAUSE flag
lFrom = npMCI->oldFrom;
break;
case TASKCUEING:
// npMCI->dwFlags should still say whether to pause at
// end of cueing or play
lFrom = npMCI->oldFrom;
dwFlags |= MCI_TO; // Stop when we get to the right frame
break;
case TASKPLAYING:
lFrom = npMCI->lCurrentFrame;
dwFlags |= MCI_TO;
break;
default:
DPF(("asked to restart to idle (%d) state", npMCI->oldState));
if (bSetEvent) {
TaskReturns(npMCI, 0);
}
return;
}
DPF2(("RestartAgain calling InternalPlayStart, flags=%8x\n",dwFlags));
dwErr = InternalPlayStart(npMCI, dwFlags,
npMCI->oldTo, lFrom, npMCI->oldCallback);
if (bSetEvent && dwErr) {
TaskReturns(npMCI, dwErr);
}
if (!dwErr) {
wNotify = mciaviPlayFile(npMCI, bSetEvent);
// if we stopped to pick up new params without actually completing the
// the play (OnTask_StopTemporarily) then MCIAVI_UPDATING will be set
if (! (npMCI->dwFlags & MCIAVI_UPDATING)) {
// perform any notification
if (wNotify != MCI_NOTIFY_FAILURE) {
GraphicDelayedNotify(npMCI, wNotify);
}
}
}
}
/***************************************************************************
*
* IsScreenDC() - returns true if the passed DC is a DC to the screen.
* NOTE this checks for a DCOrg != 0, bitmaps always have
* a origin of (0,0) This will give the wrong info a
* fullscreen DC.
*
***************************************************************************/
#ifndef _WIN32
#define IsScreenDC(hdc) (GetDCOrg(hdc) != 0L)
#else
INLINE BOOL IsScreenDC(HDC hdc)
{
return (WindowFromDC(hdc) != NULL);
}
#endif
// called from several task side functions to initiate play
// when stopped. All you need to do is call mciaviPlayFile
// once this function returns
STATICFN DWORD
InternalPlayStart(
NPMCIGRAPHIC npMCI,
DWORD dwFlags,
long lReqTo,
long lReqFrom,
DWORD_PTR dwCallback
)
{
long lTo, lFrom;
DWORD dwRet;
if (dwFlags & (MCI_MCIAVI_PLAY_FULLSCREEN | MCI_MCIAVI_PLAY_FULLBY2)) {
// do nothing here - handled in fullproc
} else {
if (!IsWindow(npMCI->hwndPlayback)) {
return MCIERR_NO_WINDOW;
}
npMCI->dwFlags |= MCIAVI_NEEDTOSHOW;
}
/* Range checks : 0 < 'from' <= 'to' <= last frame */
if (dwFlags & MCI_TO) {
lTo = lReqTo;
if (lTo < 0L || lTo > npMCI->lFrames) {
return MCIERR_OUTOFRANGE;
}
} else {
if (dwFlags & MCI_DGV_PLAY_REVERSE)
lTo = 0;
else
lTo = npMCI->lFrames;
dwFlags |= MCI_TO;
}
// if no from setting, then get current position
if (dwFlags & MCI_FROM) {
lFrom = lReqFrom;
if (lFrom < 0L || lFrom > npMCI->lFrames) {
return MCIERR_OUTOFRANGE;
}
} else if (dwRet = InternalGetPosition(npMCI, &lFrom)) {
return dwRet;
}
/* check 'to' and 'from' relationship. */
if (lTo < lFrom)
dwFlags |= MCI_DGV_PLAY_REVERSE;
if ((lFrom < lTo) && (dwFlags & MCI_DGV_PLAY_REVERSE)) {
return MCIERR_OUTOFRANGE;
}
/* If the test flag is set, return without doing anything. */
/* Question: do we have to check for more possible errors? */
if (dwFlags & MCI_TEST) {
return 0;
}
npMCI->lFrom = lFrom;
if (dwFlags & MCI_DGV_PLAY_REPEAT) {
/* If from position isn't given, repeat from either the beginning or
** end of file as appropriate.
*/
npMCI->lRepeatFrom =
(dwFlags & MCI_FROM) ? lFrom :
((dwFlags & MCI_DGV_PLAY_REVERSE) ? npMCI->lFrames : 0);
}
/* if not already playing, start the task up. */
/* First, figure out what mode to use. */
if (dwFlags & (MCI_MCIAVI_PLAY_FULLSCREEN | MCI_MCIAVI_PLAY_FULLBY2)) {
if (npMCI->rcDest.right - npMCI->rcDest.left >
npMCI->rcMovie.right - npMCI->rcMovie.left)
dwFlags |= MCI_MCIAVI_PLAY_FULLBY2;
if ((dwFlags & MCI_MCIAVI_PLAY_FULLBY2) &&
(npMCI->rcMovie.right <= 160) &&
(npMCI->rcMovie.bottom <= 120)) {
npMCI->dwFlags |= MCIAVI_ZOOMBY2;
} else {
npMCI->dwFlags &= ~(MCIAVI_ZOOMBY2);
}
if ((dwFlags & MCI_WAIT) && !(npMCI->dwFlags & MCIAVI_REPEATING))
npMCI->dwFlags |= MCIAVI_NOBREAK;
else
npMCI->dwFlags &= ~(MCIAVI_NOBREAK);
npMCI->dwFlags |= MCIAVI_FULLSCREEN;
} else {
npMCI->dwFlags &= ~(MCIAVI_FULLSCREEN);
}
// Make sure flags are cleared if they should be
//npMCI->dwFlags &= ~(MCIAVI_PAUSE | MCIAVI_CUEING | MCIAVI_REVERSE);
// PAUSE is NOT turned off, otherwise we cannot RestartAgain to a
// paused state.
npMCI->dwFlags &= ~(MCIAVI_CUEING | MCIAVI_REVERSE);
if (dwFlags & MCI_DGV_PLAY_REPEAT) {
npMCI->dwFlags |= MCIAVI_REPEATING;
}
/* Don't set up notify until here, so that the seek won't make it happen*/
// idle so no current notify
if (dwFlags & MCI_NOTIFY) {
GraphicSaveCallback(npMCI, (HANDLE) (UINT_PTR)dwCallback);
}
if (lTo > npMCI->lFrames)
lTo = npMCI->lFrames;
if (lTo < 0)
lTo = 0;
if (dwFlags & MCI_TO)
npMCI->lTo = lTo;
DPF2(("InternalPlayStart Flags=%8x, ReqTo=%d ReqFrom=%d To=%d\n",
dwFlags, lReqTo, lReqFrom, lTo));
if (dwFlags & MCI_DGV_PLAY_REVERSE)
npMCI->dwFlags |= MCIAVI_REVERSE;
if (npMCI->dwFlags & MCIAVI_NEEDTOSHOW) {
ShowStage(npMCI);
//
// leave this set so the play code knows this is a "real" play
// coming from the user, not an internal play/stop
//
// if the window needs shown we want to do it here if we can
// not in the background task.
//
npMCI->dwFlags |= MCIAVI_NEEDTOSHOW;
}
return 0;
}
// called at task idle time to initiate a play request -
// the worker thread is NOT busy playing, seeking, cueing or paused
// at this point.
//
// responsible for calling TaskReturns() appropriately.
void
OnTask_Play(NPMCIGRAPHIC npMCI)
{
DWORD dwRet;
DWORD dwMCIFlags = npMCI->dwParamFlags;
LPMCI_DGV_PLAY_PARMS lpPlay = (LPMCI_DGV_PLAY_PARMS)npMCI->lParam;
long lTo, lFrom;
UINT wNotify;
if (lpPlay != NULL) {
lTo = lpPlay->dwTo;
lFrom = lpPlay->dwFrom;
} else {
dwMCIFlags &= ~(MCI_TO | MCI_FROM);
}
npMCI->dwFlags &= ~MCIAVI_REPEATING;
// need to convert to frames before calling InternalPlayStart
if (dwMCIFlags & MCI_TO) {
lTo = ConvertToFrames(npMCI, lTo);
}
if (dwMCIFlags & MCI_FROM) {
lFrom = ConvertToFrames(npMCI, lFrom);
}
dwRet = InternalPlayStart(npMCI, dwMCIFlags, lTo, lFrom,
npMCI->dwReqCallback);
if (dwRet || (dwMCIFlags & MCI_TEST)) {
TaskReturns(npMCI, dwRet);
return;
}
// actually play the file
wNotify = mciaviPlayFile(npMCI, TRUE);
// if we stopped to pick up new params without actually completing the
// the play (OnTask_StopTemporarily) then MCIAVI_UPDATING will be set
if (! (npMCI->dwFlags & MCIAVI_UPDATING)) {
// perform any notification
if (wNotify != MCI_NOTIFY_FAILURE) {
GraphicDelayedNotify(npMCI, wNotify);
}
}
return;
}
//
// called to process a play request when play is actually happening.
// if parameters can be adjusted without stopping the current play,
// returns FALSE. Also if the request is rejected (and hEventResponse
// signalled) because of some error, returns FALSE indicating no need to
// stop. Otherwise returns TRUE, so that OnTask_Play() will
// be called after stopping the current play.
BOOL
OnTask_PlayDuringPlay(NPMCIGRAPHIC npMCI)
{
DWORD dwFlags = npMCI->dwParamFlags;
LPMCI_DGV_PLAY_PARMS lpPlay = (LPMCI_DGV_PLAY_PARMS)npMCI->lParam;
long lTo, lFrom;
DWORD dwRet;
// since this is a real play request coming from the user we need
// to show the stage window
if (dwFlags & (MCI_MCIAVI_PLAY_FULLSCREEN | MCI_MCIAVI_PLAY_FULLBY2)) {
// do nothing here - handled in fullproc
} else {
npMCI->dwFlags |= MCIAVI_NEEDTOSHOW;
}
// can be called with null lpPlay (in the resume case)
// in this case, to and from will be left unchanged
// if you pass lpPlay, then to and from will be set to defaults even
// if you don't set MCI_TO and MCI_FROM
if (lpPlay == NULL) {
dwFlags &= ~(MCI_TO | MCI_FROM);
}
/* Range checks : 0 < 'from' <= 'to' <= last frame */
if (dwFlags & MCI_TO) {
lTo = ConvertToFrames(npMCI, lpPlay->dwTo);
if (lTo < 0L || lTo > npMCI->lFrames) {
TaskReturns(npMCI, MCIERR_OUTOFRANGE);
return FALSE;
}
} else {
// don't touch to and from for resume
if (lpPlay) {
if (dwFlags & MCI_DGV_PLAY_REVERSE)
lTo = 0;
else
lTo = npMCI->lFrames;
dwFlags |= MCI_TO;
} else {
lTo = npMCI->lTo;
}
}
// if no from setting, then get current position
if (dwFlags & MCI_FROM) {
lFrom = ConvertToFrames(npMCI, lpPlay->dwFrom);
if (lFrom < 0L || lFrom > npMCI->lFrames) {
TaskReturns(npMCI, MCIERR_OUTOFRANGE);
return FALSE;
}
} else if (dwRet = InternalGetPosition(npMCI, &lFrom)) {
TaskReturns(npMCI, dwRet);
return FALSE;
}
/* check 'to' and 'from' relationship. */
if (lTo < lFrom)
dwFlags |= MCI_DGV_PLAY_REVERSE;
if ((lFrom < lTo) && (dwFlags & MCI_DGV_PLAY_REVERSE)) {
TaskReturns(npMCI, MCIERR_OUTOFRANGE);
return FALSE;
}
/* If the test flag is set, return without doing anything. */
/* Question: do we have to check for more possible errors? */
if (dwFlags & MCI_TEST) {
TaskReturns(npMCI, 0L);
return FALSE;
}
/* We want any previous playing to be aborted if and only if a 'from'
** parameter is specified. If only a new 'to' parameter is specified,
** we can just change the 'to' value, and play will stop at the
** proper time.
**
** Also abort the play if we have lost the audio. An explicit play
** command has been issued and we should try and get the audio again.
*/
// if it's new from position or we are seeking to the wrong stop,
// or we are reversing the direction of play,
// or we had lost the audio
// then we need to stop.
if ( (dwFlags & MCI_FROM)
|| (dwFlags & (MCI_MCIAVI_PLAY_FULLSCREEN | MCI_MCIAVI_PLAY_FULLBY2))
|| ((npMCI->dwFlags & MCIAVI_SEEKING) && (npMCI->lTo != lFrom))
|| (npMCI->wTaskState == TASKCUEING)
|| (npMCI->dwFlags & MCIAVI_LOSTAUDIO)
|| (((npMCI->dwFlags & MCIAVI_REVERSE) != 0) != ((dwFlags & MCI_DGV_PLAY_REVERSE) != 0))
) {
// we can't continue the play - we have to stop, and then pick up
// this request again in OnTask_Play().
// this will abort the current notify
return TRUE;
}
// ok to continue the current play with revised params.
// set the from position correctly
npMCI->lFrom = lFrom;
/* If we're changing the "to" position, abort any pending notify. */
if (lTo != npMCI->lTo) {
GraphicDelayedNotify (npMCI, MCI_NOTIFY_ABORTED);
}
/* Don't set up notify until here, so that the seek won't make it happen*/
if (dwFlags & MCI_NOTIFY) {
GraphicSaveCallback(npMCI, (HANDLE) (UINT_PTR)npMCI->dwReqCallback);
}
// Make sure flags are cleared if they should be
npMCI->dwFlags &= ~(MCIAVI_PAUSE | MCIAVI_CUEING | MCIAVI_REVERSE | MCIAVI_LOSEAUDIO);
/* Set up the 'repeat' flags */
npMCI->dwFlags &= ~(MCIAVI_REPEATING);
if (dwFlags & MCI_DGV_PLAY_REPEAT) {
/* If from position isn't given, repeat from either the beginning or
** end of file as appropriate.
**
** if no lpPlay is given, then don't change repeatfrom
*/
if (lpPlay) {
npMCI->lRepeatFrom =
(dwFlags & MCI_FROM) ? lFrom :
((dwFlags & MCI_DGV_PLAY_REVERSE) ? npMCI->lFrames : 0);
}
npMCI->dwFlags |= MCIAVI_REPEATING;
}
/* if not already playing, start the task up. */
if (lTo > npMCI->lFrames)
lTo = npMCI->lFrames;
if (lTo < 0)
lTo = 0;
if (dwFlags & MCI_TO)
npMCI->lTo = lTo;
if (dwFlags & MCI_DGV_PLAY_REVERSE)
npMCI->dwFlags |= MCIAVI_REVERSE;
if (npMCI->dwFlags & MCIAVI_NEEDTOSHOW) {
ShowStage(npMCI);
//
// leave this set so the play code knows this is a "real" play
// coming from the user, not an internal play/stop
//
// if the window needs shown we want to do it here if we can
// not in the background task.
//
npMCI->dwFlags |= MCIAVI_NEEDTOSHOW;
}
//everything adjusted - tell user ok and return to playing
TaskReturns(npMCI, 0);
return FALSE;
}
void OnTask_Realize(NPMCIGRAPHIC npMCI)
{
DWORD dw;
EnterHDCCrit(npMCI);
dw = InternalRealize(npMCI);
LeaveHDCCrit(npMCI);
TaskReturns(npMCI, dw);
}
DWORD InternalRealize(NPMCIGRAPHIC npMCI)
{
BOOL fGetDC;
BOOL fPalChanged;
#ifndef _WIN32
BOOL fAlreadyDoneThat;
#endif
HDCCritCheckIn(npMCI);
if (npMCI->dwFlags & MCIAVI_WANTMOVE)
CheckWindowMove(npMCI, TRUE);
#ifndef _WIN32
if (fAlreadyDoneThat = (BOOL)(npMCI->dwFlags & MCIAVI_UPDATING)) {
DPF(("Re-entering InternalRealize - but we don't care, npMCI=%8x\n",npMCI));
}
#endif
if (!IsTask(npMCI->hTask))
return(0L);
if (fGetDC = (npMCI->hdc == NULL)) {
npMCI->hdc = GetDC(npMCI->hwndPlayback);
Assert(npMCI->hdc != NULL);
}
#ifndef _WIN32
// this only prevents playback window alignment - which is not done
// for NT anyway
npMCI->dwFlags |= MCIAVI_UPDATING;
#endif
fPalChanged = PrepareDC(npMCI) > 0;
#ifndef _WIN32
if (!fAlreadyDoneThat)
npMCI->dwFlags &= ~MCIAVI_UPDATING;
#endif
if (fGetDC) {
UnprepareDC(npMCI);
ReleaseDC(npMCI->hwndPlayback, npMCI->hdc);
npMCI->hdc = NULL;
HDCCritCheckIn(npMCI);
}
if (fPalChanged)
InvalidateRect(npMCI->hwndPlayback, &npMCI->rcDest, TRUE);
CheckIfActive(npMCI);
return 0L;
}
void OnTask_Update(NPMCIGRAPHIC npMCI)
{
RECT rc;
LPMCI_DGV_UPDATE_PARMS lpParms = (LPMCI_DGV_UPDATE_PARMS) npMCI->lParam;
DWORD dwFlags = npMCI->dwFlags;
DWORD dwErr;
rc.left = lpParms->ptOffset.x;
rc.top = lpParms->ptOffset.y;
rc.right = lpParms->ptOffset.x + lpParms->ptExtent.x;
rc.bottom = lpParms->ptOffset.y + lpParms->ptExtent.y;
dwErr = Internal_Update (npMCI, dwFlags, lpParms->hDC, (dwFlags & MCI_DGV_RECT) ? &rc : NULL);
//now, where were we ?
if (!dwErr && (npMCI->dwFlags & MCIAVI_UPDATING)) {
OnTask_RestartAgain(npMCI, TRUE);
} else {
TaskReturns(npMCI, dwErr);
}
}
BOOL OnTask_UpdateDuringPlay(NPMCIGRAPHIC npMCI)
{
RECT userrc, rc;
LPMCI_DGV_UPDATE_PARMS lpParms = (LPMCI_DGV_UPDATE_PARMS) npMCI->lParam;
DWORD dwFlags = npMCI->dwFlags;
HDC hdc = lpParms->hDC;
userrc.left = lpParms->ptOffset.x;
userrc.top = lpParms->ptOffset.y;
userrc.right = lpParms->ptOffset.x + lpParms->ptExtent.x;
userrc.bottom = lpParms->ptOffset.y + lpParms->ptExtent.y;
//
// mark the proper streams dirty, this will set the proper update flags
//
if (hdc)
GetClipBox(hdc, &rc);
else
rc = npMCI->rcDest;
if (dwFlags & MCI_DGV_RECT)
IntersectRect(&rc, &rc, &userrc);
StreamInvalidate(npMCI, &rc);
//
// if they are drawing to the screen *assume* they wanted to set
// the MCI_DGV_UPDATE_PAINT flag
//
if (IsScreenDC(hdc))
dwFlags |= MCI_DGV_UPDATE_PAINT;
// we are playing now (we have a dc). just realize
// the palette and set the update flag
// unless we are painting to a memory dc.
//
// if we are paused, fall through so we can handle the case where
// a update fails
//
// !!!mabey we should rework this code to do this even if playing?
//
if (npMCI->hdc &&
(dwFlags & MCI_DGV_UPDATE_PAINT) &&
(npMCI->wTaskState != TASKPAUSED) &&
//!!! what is this?
((npMCI->wTaskState != TASKCUEING) ||
(npMCI->lCurrentFrame <= 1) ||
(npMCI->lCurrentFrame > npMCI->lRealStart - 30)) ) {
Assert(npMCI->wTaskState == TASKPLAYING ||
npMCI->wTaskState == TASKCUEING);
EnterHDCCrit(npMCI);
UnprepareDC(npMCI);
PrepareDC(npMCI); // re-prepare
LeaveHDCCrit(npMCI);
// all ok - no need for stop.
TaskReturns(npMCI, 0);
return FALSE;
}
// try to use DoStreamUpdate - if this fails, we need to stop
if (TryStreamUpdate(npMCI, dwFlags, hdc,
(dwFlags & MCI_DGV_RECT) ? &userrc : NULL)) {
// we are playing and so have an hdc. However, we have just
// done a update to another hdc. switching back to the original
// hdc without this will fail
PrepareDC(npMCI);
TaskReturns(npMCI, 0);
return FALSE;
}
// otherwise we need to stop to do this
// indicate that we should restart after doing this, and
// save enough state to do this
OnTask_StopTemporarily(npMCI);
return TRUE;
}
// attempt repaint using DoStreamUpdate - if this fails (eg wrong frame)
// then you need to use mciaviPlayFile to do it (to/from same frame)
BOOL
TryStreamUpdate(
NPMCIGRAPHIC npMCI,
DWORD dwFlags,
HDC hdc,
LPRECT lprc
)
{
HDC hdcSave;
BOOL f;
//
// are we updating to a memory bitmap?
//
if (!(dwFlags & MCI_DGV_UPDATE_PAINT))
npMCI->dwFlags |= MCIAVI_UPDATETOMEMORY;
//
// if we are using a draw device (or are in stupid mode) make sure we seek
// to the frame we want and dont use the current decompress buffer that
// may not be correct.
//
if ((npMCI->dwFlags & MCIAVI_UPDATETOMEMORY) ||
(npMCI->dwFlags & MCIAVI_STUPIDMODE)) {
DPF(("DeviceUpdate: decompress buffer may be bad, ignoring it....\n"));
npMCI->lFrameDrawn = (- (LONG) npMCI->wEarlyRecords) - 1;
}
//
// honor the passed rect
//
if (lprc) {
Assert(hdc);
SaveDC(hdc);
IntersectClipRect(hdc, lprc->left, lprc->top,
lprc->right, lprc->bottom);
}
//
// Always do an Update, if the update succeeds and we are at the right
// frame keep it.
//
// if it fails or the frame is wrong need to re-draw using play.
//
// we need to do this because even though lFrameDrawn is a valid
// frame the draw handler may fail a update anyway (for example
// when decompressing to screen) so lFrameDrawn can be bogus and
// we do not know it until we try it.
//
if (npMCI->lFrameDrawn <= npMCI->lCurrentFrame &&
npMCI->lFrameDrawn >= 0) {
DPF2(("Update: redrawing frame %ld, current = %ld.\n", npMCI->lFrameDrawn, npMCI->lCurrentFrame));
/* Save the DC, in case we're playing, but need to update
** to a memory bitmap.
*/
// worker thread must hold critsec round all drawing
EnterHDCCrit(npMCI);
hdcSave = npMCI->hdc;
npMCI->hdc = hdc;
/* Realize the palette here, because it will cause strange
** things to happen if we do it in the task.
*/
if (npMCI->dwFlags & MCIAVI_NEEDDRAWBEGIN) {
DrawBegin(npMCI, NULL);
if (npMCI->lFrameDrawn < npMCI->lVideoStart) {
npMCI->hdc = hdcSave;
HDCCritCheckIn(npMCI);
npMCI->lFrameDrawn = (- (LONG) npMCI->wEarlyRecords) - 1;
LeaveHDCCrit(npMCI);
return FALSE; // need to use play
}
}
PrepareDC(npMCI); // make sure the palette is in there
f = DoStreamUpdate(npMCI, FALSE);
UnprepareDC(npMCI); // be sure to put things back....
Assert(hdc == npMCI->hdc);
HDCCritCheckIn(npMCI);
npMCI->hdc = hdcSave;
LeaveHDCCrit(npMCI);
if (!f) {
DPF(("DeviceUpdate failed! invalidating lFrameDrawn\n"));
npMCI->lFrameDrawn = (- (LONG) npMCI->wEarlyRecords) - 1;
Assert(!lprc);
}
else if (npMCI->lFrameDrawn >= npMCI->lCurrentFrame-1) {
if (lprc) {
RestoreDC(hdc, -1);
}
npMCI->dwFlags &= ~(MCIAVI_UPDATING|MCIAVI_UPDATETOMEMORY);
if (npMCI->dwFlags & MCIAVI_NEEDUPDATE) {
DPF(("**** we did a DeviceUpdate but still dirty?\n"));
}
return TRUE;
}
//return FALSE; Drop through
}
return FALSE;
}
// called in stopped case to paint from OnTask_Update, and
// also on winproc thread (when stopped). Not called during play.
DWORD Internal_Update(NPMCIGRAPHIC npMCI, DWORD dwFlags, HDC hdc, LPRECT lprc)
{
DWORD dwErr = 0L;
HWND hCallback;
HCURSOR hcurPrev;
RECT rc;
LONG lFrameDrawn;
if (npMCI->dwFlags & MCIAVI_WANTMOVE)
CheckWindowMove(npMCI, TRUE);
//
// see if we are the active movie now.
//
CheckIfActive(npMCI);
//
// mark the proper streams dirty, this will set the proper update flags
//
if (hdc)
GetClipBox(hdc, &rc);
else
rc = npMCI->rcDest;
if (lprc)
IntersectRect(&rc, &rc, lprc);
StreamInvalidate(npMCI, &rc);
//
// if they are drawing to the screen *assume* they wanted to set
// the MCI_DGV_UPDATE_PAINT flag
//
if (IsScreenDC(hdc))
dwFlags |= MCI_DGV_UPDATE_PAINT;
lFrameDrawn = npMCI->lFrameDrawn; // save this for compare
// try to use DoStreamUpdate
if (TryStreamUpdate(npMCI, dwFlags, hdc, lprc)) {
return 0;
}
// no - need to use Play
// note we are already stopped at this point.
//
// the problem this tries to fix is the following:
// sometimes we are at N+1 but frame N is on the
// screen, if we now play to N+1 a mismatch will occur
//
if (lFrameDrawn >= 0 && lFrameDrawn == npMCI->lCurrentFrame-1)
npMCI->lFrom = npMCI->lTo = lFrameDrawn;
else
npMCI->lFrom = npMCI->lTo = npMCI->lCurrentFrame;
/* Realize the palette here, because it will cause strange
** things to happen if we do it in the task.
*/
EnterHDCCrit(npMCI);
npMCI->hdc = hdc;
PrepareDC(npMCI); // make sure the palette is in there
LeaveHDCCrit(npMCI);
hcurPrev = SetCursor(LoadCursor(NULL, IDC_WAIT));
/* Hide any notification, so it won't get sent... */
hCallback = npMCI->hCallback;
npMCI->hCallback = NULL;
mciaviPlayFile(npMCI, FALSE);
npMCI->hCallback = hCallback;
// We may have just yielded.. so only set the cursor back if we
// are still the wait cursor.
if (hcurPrev) {
hcurPrev = SetCursor(hcurPrev);
if (hcurPrev != LoadCursor(NULL, IDC_WAIT))
SetCursor(hcurPrev);
}
//HDCCritCheckIn(npMCI) ??? This is an atomic operation - and
// why are we setting it to NULL here ??
npMCI->hdc = NULL;
if (lprc) {
RestoreDC(hdc, -1);
}
npMCI->dwFlags &= ~(MCIAVI_UPDATETOMEMORY);
if (npMCI->dwFlags & MCIAVI_NEEDUPDATE) {
DPF(("**** we did a DeviceUpdate but still dirty?\n"));
}
return dwErr;
}
void
OnTask_PauseDuringPlay(NPMCIGRAPHIC npMCI)
{
DWORD dwFlags = npMCI->dwParamFlags;
DPF3(("Pause during play\n"));
// no pause during cueing
if (npMCI->wTaskState == TASKCUEING) {
// leave event sent - wait till later
return;
}
// save the notify
if (dwFlags & MCI_NOTIFY) {
GraphicSaveCallback(npMCI, (HANDLE) (UINT_PTR)npMCI->dwReqCallback);
}
// what about delayed completion pause ?
// especially "pause" followed by "pause wait"
if (dwFlags & MCI_WAIT) {
// indicate hEventAllDone should be set on Pause, not
// on idle (ie at final stop)
npMCI->dwFlags |= MCIAVI_WAITING;
}
if (npMCI->wTaskState == TASKPAUSED) {
// all done already
if (dwFlags & MCI_NOTIFY) {
GraphicDelayedNotify(npMCI, MCI_NOTIFY_SUCCESSFUL);
}
} else if (npMCI->wTaskState == TASKPLAYING) {
// remember to pause
npMCI->dwFlags |= MCIAVI_PAUSE;
if (dwFlags & MCI_NOTIFY) {
// remember to send a notify when we pause
npMCI->dwFlags |= MCIAVI_CUEING;
}
}
TaskReturns(npMCI, 0);
}
void
OnTask_Cue(NPMCIGRAPHIC npMCI, DWORD dwFlags, long lTo)
{
UINT wNotify;
DPF3(("OnTask_Cue: dwFlags=%8x, To=%d\n", dwFlags, lTo));
GraphicDelayedNotify(npMCI, MCI_NOTIFY_ABORTED);
if (dwFlags & MCI_NOTIFY) {
GraphicSaveCallback(npMCI, (HANDLE) (UINT_PTR)npMCI->dwReqCallback);
}
/* Clear the 'repeat' flags */
npMCI->dwFlags &= ~(MCIAVI_REPEATING);
if (dwFlags & MCI_TO) {
npMCI->lFrom = lTo;
} else if (npMCI->wTaskState == TASKIDLE) {
npMCI->lFrom = npMCI->lCurrentFrame;
}
/* If we're ever resumed, we want to go to the end of the file. */
npMCI->lTo = npMCI->lFrames;
npMCI->dwFlags |= MCIAVI_PAUSE | MCIAVI_CUEING;
if (dwFlags & MCI_WAIT) {
npMCI->dwFlags |= MCIAVI_WAITING;
}
wNotify = mciaviPlayFile(npMCI, TRUE);
// if we stopped to pick up new params without actually completing the
// the play (OnTask_StopTemporarily) then MCIAVI_UPDATING will be set
if (! (npMCI->dwFlags & MCIAVI_UPDATING)) {
// perform any notification
if (wNotify != MCI_NOTIFY_FAILURE) {
GraphicDelayedNotify(npMCI, wNotify);
}
}
}
BOOL
OnTask_CueDuringPlay(NPMCIGRAPHIC npMCI)
{
DWORD dw = 0L;
DWORD dwFlags = npMCI->dwParamFlags;
long lTo = (LONG) npMCI->lParam;
DPF3(("OnTask_CueDuringPlay\n"));
if (npMCI->dwFlags & MCIAVI_SEEKING) {
/* We're currently seeking, so we have to start again to get audio
** to work.
*/
return TRUE;
}
if (dwFlags & MCI_TO) {
return TRUE;
}
/* Clear the 'repeat' flags */
npMCI->dwFlags &= ~(MCIAVI_REPEATING);
GraphicDelayedNotify(npMCI, MCI_NOTIFY_ABORTED);
if (dwFlags & MCI_NOTIFY) {
GraphicSaveCallback(npMCI, (HANDLE) (UINT_PTR)npMCI->dwReqCallback);
}
/* If we're ever resumed, we want to go to the end of the file. */
npMCI->lTo = npMCI->lFrames;
if (npMCI->wTaskState == TASKPAUSED) {
/* We're already paused at the right place, so
** that means we did it.
*/
if (dwFlags & MCI_NOTIFY)
GraphicDelayedNotify(npMCI, MCI_NOTIFY_SUCCESSFUL);
// complete completed
TaskReturns(npMCI, 0);
// delayed completion is also done!
if (dwFlags & MCI_WAIT) {
SetEvent(npMCI->hEventAllDone);
}
// don't drop through to the second TaskReturns() below!
return FALSE;
} else if ((npMCI->wTaskState == TASKCUEING) ||
(npMCI->wTaskState == TASKPLAYING)) {
// ask for pause on completion of cueing/playing,
// and for notify and hEventAllDone when pause reached
npMCI->dwFlags |= MCIAVI_PAUSE | MCIAVI_CUEING;
if (dwFlags & MCI_WAIT) {
npMCI->dwFlags |= MCIAVI_WAITING;
}
} else {
TaskReturns (npMCI, MCIERR_NONAPPLICABLE_FUNCTION);
return FALSE;
}
TaskReturns(npMCI, 0);
return FALSE;
}
void OnTask_Seek(NPMCIGRAPHIC npMCI)
{
UINT wNotify;
DWORD dwFlags = npMCI->dwParamFlags;
long lTo = (long) npMCI->lParam;
DPF3(("DeviceSeek - to frame %d (CurrentFrame==%d) Current State is %d\n", lTo, npMCI->lCurrentFrame, npMCI->wTaskState));
/* The window will be shown by the play code. */
// task state is now TASKIDLE and blocked
if (dwFlags & MCI_NOTIFY) {
GraphicSaveCallback(npMCI, (HANDLE) (UINT_PTR)npMCI->dwReqCallback);
}
/* Clear the 'repeat' flags */
npMCI->dwFlags &= ~(MCIAVI_REPEATING);
if (npMCI->lCurrentFrame != lTo) {
/* Essentially, we are telling the task: play just frame <lTo>.
** When it gets there, it will update the screen for us.
*/
npMCI->lFrom = npMCI->lTo = lTo;
wNotify = mciaviPlayFile(npMCI, TRUE);
// if we stopped to pick up new params without actually completing the
// the play (OnTask_StopTemporarily) then MCIAVI_UPDATING will be set
if (! (npMCI->dwFlags & MCIAVI_UPDATING)) {
// perform any notification
if (wNotify != MCI_NOTIFY_FAILURE) {
GraphicDelayedNotify(npMCI, wNotify);
}
}
} else {
// task complete
TaskReturns(npMCI, 0);
/* Be sure the window gets shown and the notify gets sent,
** even though we don't have to do anything.
*/
if (npMCI->dwFlags & MCIAVI_NEEDTOSHOW)
ShowStage(npMCI);
if (dwFlags & MCI_NOTIFY)
GraphicDelayedNotify(npMCI, MCI_NOTIFY_SUCCESSFUL);
}
}
OnTask_SeekDuringPlay(NPMCIGRAPHIC npMCI)
{
long lTo = (long) npMCI->lParam;
DWORD dwFlags = npMCI->dwParamFlags;
DPF3(("DeviceSeek - to frame %d (CurrentFrame==%d) Current State is %d\n", lTo, npMCI->lCurrentFrame, npMCI->wTaskState));
/* The window will be shown by the play code. */
/* If we can just shorten a previous seek, do it. */
if ((npMCI->wTaskState == TASKCUEING) &&
(npMCI->dwFlags & MCIAVI_SEEKING) &&
(npMCI->lCurrentFrame <= lTo) &&
(npMCI->lTo >= lTo)) {
npMCI->lTo = lTo;
/* Clear the 'repeat' flags */
npMCI->dwFlags &= ~(MCIAVI_REPEATING);
GraphicDelayedNotify (npMCI, MCI_NOTIFY_ABORTED);
if (dwFlags & MCI_NOTIFY) {
GraphicSaveCallback(npMCI, (HANDLE) (UINT_PTR)npMCI->dwReqCallback);
}
TaskReturns(npMCI, 0);
return FALSE;
}
// we have to stop to do this seek
return TRUE;
}
void OnTask_SetWindow(NPMCIGRAPHIC npMCI)
{
npMCI->hwndPlayback = (HWND) npMCI->lParam;
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
InvalidateRect(npMCI->hwndPlayback, &npMCI->rcDest, FALSE);
/* Should we update the window here? */
/* Start playing again in the new window (if we had to stop) */
//now, where were we ?
if (npMCI->dwFlags & MCIAVI_UPDATING) {
OnTask_RestartAgain(npMCI, TRUE);
} else {
TaskReturns(npMCI, 0);
}
}
void OnTask_SetSpeed(NPMCIGRAPHIC npMCI)
{
npMCI->dwSpeedFactor = (DWORD)npMCI->lParam;
// if we stopped to do this, then restart whatever we were doing
if (npMCI->dwFlags & MCIAVI_UPDATING) {
OnTask_RestartAgain(npMCI, TRUE);
} else {
TaskReturns(npMCI, 0);
}
}
BOOL
OnTask_SetSpeedDuringPlay(NPMCIGRAPHIC npMCI)
{
/* If new speed is the same as the old speed, done. */
if ((DWORD)npMCI->lParam == npMCI->dwSpeedFactor) {
TaskReturns(npMCI, 0);
return FALSE;
}
// otherwise we have to stop and restart
OnTask_StopTemporarily(npMCI);
return TRUE;
}
void OnTask_WaveSteal(NPMCIGRAPHIC npMCI) {
DPF2(("OnTask_WaveSteal, '%ls' hTask=%04X\n", (LPSTR)npMCI->szFilename, npMCI->hTask));
// if we stopped to do this, then restart whatever we were doing
if (npMCI->dwFlags & MCIAVI_UPDATING) {
// We stopped to do this...
EnterWinCrit(npMCI);
// Turn the lose audio flag on so that when we restart we do not
// try and pick up the wave device. The flag will be reset in
// SetUpAudio.
npMCI->dwFlags |= MCIAVI_LOSEAUDIO;
// Hint that we would like sound again
npMCI->dwFlags |= MCIAVI_LOSTAUDIO;
LeaveWinCrit(npMCI);
OnTask_RestartAgain(npMCI, TRUE);
Assert(!(npMCI->dwFlags & MCIAVI_LOSEAUDIO));
// The flag has been reset by SetUpAudio
// By using MCIAVI_LOSEAUDIO we do not have to change the state of
// the MCIAVI_PLAYAUDIO flag. This is goodness as that flag controls
// the mute state - and that is independent of the availability of a
// wave device, activation and/or deactivation.
} else {
TaskReturns(npMCI, 0);
}
}
void OnTask_WaveReturn(NPMCIGRAPHIC npMCI) {
// Turn off the flag that caused us to get called.
// Note: if the audio device is still unavailable, this flag will get
// turned on again when we fail to open the device.
npMCI->dwFlags &= ~MCIAVI_LOSTAUDIO;
DPF2(("OnTask_WaveReturn... pick up the audio\n"));
// if we stopped to do this, then restart whatever we were doing
if (npMCI->dwFlags & MCIAVI_UPDATING) {
OnTask_RestartAgain(npMCI, TRUE);
} else {
TaskReturns(npMCI, 0);
}
}
BOOL OnTask_WaveStealDuringPlay(NPMCIGRAPHIC npMCI) {
DPF2(("OnTask_WaveStealDuringPlay, '%ls' hTask=%04X\n", (LPSTR)npMCI->szFilename, npMCI->hTask));
/* If we do not have the audio, just return. */
if (npMCI->hWave == 0) {
TaskReturns(npMCI, 0);
return FALSE;
}
/* Stop before changing sound status */
OnTask_StopTemporarily(npMCI);
return(TRUE);
}
/*
* A wave device may have become available. Stop and try to pick it up.
*/
BOOL OnTask_WaveReturnDuringPlay(NPMCIGRAPHIC npMCI) {
/* If there's no audio, just return. */
if (npMCI->nAudioStreams == 0) {
npMCI->dwFlags &= ~MCIAVI_LOSTAUDIO;
TaskReturns(npMCI, 0);
return FALSE;
}
/* Stop before changing sound status */
OnTask_StopTemporarily(npMCI);
return(TRUE);
}
BOOL
OnTask_MuteDuringPlay(NPMCIGRAPHIC npMCI)
{
// If npMCI->lParam is TRUE, this means that we are to mute the
// device - hence turn off the PLAYAUDIO flag.
DWORD fPlayAudio = (DWORD)((BOOL) npMCI->lParam ? 0 : MCIAVI_PLAYAUDIO);
/* If there's no audio, just return. Should this be an error? */
if (npMCI->nAudioStreams == 0) {
TaskReturns(npMCI, 0);
return FALSE;
}
/* If the mute state isn't changing, don't do anything. */
if ( (npMCI->dwFlags & MCIAVI_PLAYAUDIO) == fPlayAudio) {
TaskReturns(npMCI, 0);
return FALSE;
}
DPF2(("DeviceMute, fPlayAudio = %x, npMCI=%8x\n", fPlayAudio, npMCI));
/* Stop before changing mute */
OnTask_StopTemporarily(npMCI);
return TRUE;
}
void
OnTask_Mute(NPMCIGRAPHIC npMCI)
{
// If npMCI->lParam is TRUE, this means that we are to mute the
// device - hence turn off the PLAYAUDIO flag.
// We do not bother to check a change in state. That is only
// relevant if we are already playing when we only want to stop
// for a change in state.
BOOL fMute = (BOOL)npMCI->lParam;
/* If there's no audio, just return. Should this be an error? */
if (npMCI->nAudioStreams != 0) {
EnterWinCrit(npMCI);
if (fMute)
npMCI->dwFlags &= ~MCIAVI_PLAYAUDIO;
else
npMCI->dwFlags |= MCIAVI_PLAYAUDIO;
LeaveWinCrit(npMCI);
}
// if we stopped to do this, then restart whatever we were doing
if (npMCI->dwFlags & MCIAVI_UPDATING) {
OnTask_RestartAgain(npMCI, TRUE);
} else {
TaskReturns(npMCI, 0);
}
}
// all access to the hWave *must* be restricted to the thread that created
// the wave device. So even getting the volume must be done on the
// worker thread only
//
// this function gets the current volume setting and stores it in
// npMCI->dwVolume
DWORD
InternalGetVolume(NPMCIGRAPHIC npMCI)
{
DWORD dw = 0;
DWORD dwVolume = 0;
if (npMCI->hWave) {
// Get the current audio volume....
dw = waveOutMessage(npMCI->hWave, WODM_GETVOLUME,
(DWORD_PTR) (DWORD FAR *)&dwVolume, 0);
} else if (!(npMCI->dwFlags & MCIAVI_VOLUMESET)) {
// We have no device open, and the user hasn't chosen a
// volume yet.
//
// Try to find out what the current "default" volume is.
//
// I really doubt zero is the current volume, try to work
// with broken cards like the windows sound system.
//
dw = waveOutGetVolume((HWAVEOUT)(UINT)WAVE_MAPPER, &dwVolume);
if ((dw != 0) || (dwVolume != 0)) {
dw = waveOutGetVolume((HWAVEOUT)0, &dwVolume);
}
// don't accept default volume of 0
if ((dwVolume == 0) && (dw == 0)) {
dw = MCIERR_NONAPPLICABLE_FUNCTION;
}
}
if (dw == 0) {
npMCI->dwVolume = MAKELONG((UINT)muldiv32(LOWORD(dwVolume), 500L, 32768L),
(UINT)muldiv32(HIWORD(dwVolume), 500L, 32768L));
}
return dw;
}
DWORD
InternalSetVolume(NPMCIGRAPHIC npMCI, DWORD dwVolume)
{
DWORD dw = 0;
npMCI->dwVolume = dwVolume;
EnterWinCrit(npMCI);
npMCI->dwFlags |= MCIAVI_VOLUMESET;
LeaveWinCrit(npMCI);
/* clear flag to emulate volume */;
npMCI->fEmulatingVolume = FALSE;
/* If there's no audio, just return. Should this be an error? */
if (npMCI->nAudioStreams != 0) {
if (npMCI->hWave) {
WORD wLeft;
WORD wRight;
if (LOWORD(dwVolume) >= 1000)
wLeft = 0xFFFF;
else
wLeft = (WORD) muldiv32(LOWORD(dwVolume), 32768L, 500L);
if (HIWORD(dwVolume) >= 1000)
wRight = 0xFFFF;
else
wRight = (WORD) muldiv32(HIWORD(dwVolume), 32768L, 500L);
// !!! Support left and right volume?
dw = waveOutMessage(npMCI->hWave, WODM_SETVOLUME,
MAKELONG(wLeft, wRight), 0);
if (dw != MMSYSERR_NOERROR && LOWORD(dwVolume) != 500) {
npMCI->fEmulatingVolume = TRUE;
BuildVolumeTable(npMCI);
}
dw = 0;
}
}
return dw;
}
INLINE void
OnTask_SetVolume(NPMCIGRAPHIC npMCI)
{
DWORD dwVolume = (DWORD) npMCI->lParam;
TaskReturns(npMCI, InternalSetVolume(npMCI, dwVolume));
}
void OnTask_SetAudioStream(NPMCIGRAPHIC npMCI)
{
UINT wAudioStream = npMCI->dwParamFlags;
int stream;
/* If there's no audio, we're done. Should this be an error? */
if (npMCI->nAudioStreams != 0) {
for (stream = 0; stream < npMCI->streams; stream++) {
if (SH(stream).fccType == streamtypeAUDIO) {
--wAudioStream;
if (wAudioStream == 0)
break;
}
}
Assert(stream < npMCI->streams);
npMCI->psiAudio = SI(stream);
npMCI->nAudioStream = stream;
}
// if we stopped to do this, then restart whatever we were doing
if (npMCI->dwFlags & MCIAVI_UPDATING) {
OnTask_RestartAgain(npMCI, TRUE);
} else {
TaskReturns(npMCI, 0);
}
}
void
OnTask_SetVideoStream(NPMCIGRAPHIC npMCI)
{
UINT uStream = npMCI->dwParamFlags;
BOOL fOn = (BOOL) npMCI->lParam;
DWORD dw = 0L;
int stream;
STREAMINFO *psi;
//
// find the Nth non-audio, non-error stream
//
for (stream = 0; stream < npMCI->streams; stream++) {
psi = SI(stream);
if (psi->sh.fccType == streamtypeAUDIO)
continue;
if (psi->dwFlags & STREAM_ERROR)
continue;
if (--uStream == 0)
break;
}
if (stream == npMCI->streams) {
dw = MCIERR_OUTOFRANGE;
} else {
if (fOn)
psi->dwFlags |= STREAM_ENABLED;
else
psi->dwFlags &= ~STREAM_ENABLED;
if (fOn && psi->sh.fccType == streamtypeVIDEO) {
//!!! should we change the master timebase?
DOUT("Setting main video stream\n");
#if 0
//
// the master video stream is too special cased to change!
//
npMCI->psiVideo = psi;
npMCI->nVideoStream = stream;
#endif
}
if (!fOn && npMCI->nVideoStream == stream) {
DOUT("Turning off main video stream\n");
npMCI->dwFlags &= ~MCIAVI_SHOWVIDEO;
}
//
// now we turn MCIAVI_SHOWVIDEO off if no video/other streams
// are enabled.
//
npMCI->dwFlags &= ~MCIAVI_SHOWVIDEO; // assume off.
for (stream = 0; stream < npMCI->streams; stream++) {
psi = SI(stream);
if (psi->sh.fccType == streamtypeAUDIO)
continue;
if (psi->dwFlags & STREAM_ERROR)
continue;
if (!(psi->dwFlags & STREAM_ENABLED))
continue;
// at least one stream is enabled show "video"
npMCI->dwFlags |= MCIAVI_SHOWVIDEO;
}
if (!(npMCI->dwFlags & MCIAVI_SHOWVIDEO))
DOUT("All streams off\n");
}
// if we stopped to do this, then restart whatever we were doing
if ( (dw == 0) && (npMCI->dwFlags & MCIAVI_UPDATING)) {
OnTask_RestartAgain(npMCI, TRUE);
} else {
TaskReturns(npMCI, dw);
}
}
/***************************************************************************
*
***************************************************************************/
static void MapRect(RECT *prc, RECT*prcIn, RECT *prcFrom, RECT *prcTo)
{
if (IsRectEmpty(prcFrom)) {
SetRectEmpty(prc);
}
else {
prc->left = prcTo->left + MulDiv(prcIn->left - prcFrom->left, prcTo->right - prcTo->left, prcFrom->right - prcFrom->left);
prc->top = prcTo->top + MulDiv(prcIn->top - prcFrom->top, prcTo->bottom - prcTo->top, prcFrom->bottom - prcFrom->top);
prc->right = prcTo->left + MulDiv(prcIn->right - prcFrom->left, prcTo->right - prcTo->left, prcFrom->right - prcFrom->left);
prc->bottom= prcTo->top + MulDiv(prcIn->bottom- prcFrom->top, prcTo->bottom - prcTo->top, prcFrom->bottom - prcFrom->top);
}
}
/***************************************************************************
*
***************************************************************************/
static void MapStreamRects(NPMCIGRAPHIC npMCI)
{
int i;
//
// now set the source and dest rects for each stream.
//
for (i=0; i<npMCI->streams; i++)
{
//
// make sure the stream rect is in bounds
//
IntersectRect(&SI(i)->rcSource, &SH(i).rcFrame, &npMCI->rcSource);
//
// now map the stream source rect onto the destination
//
MapRect(&SI(i)->rcDest, &SI(i)->rcSource, &npMCI->rcSource, &npMCI->rcDest);
//
// make the stream source RECT (rcSource) relative to the
// stream rect (rcFrame)
//
OffsetRect(&SI(i)->rcSource,-SH(i).rcFrame.left,-SH(i).rcFrame.top);
}
}
//
// try to set the dest or source rect without stopping play.
// called both at stop time and at play time
//
// returns TRUE if stop needed, or else FALSE if all handled.
// lpdwErr is set to a non-zero error if any error occured (in which case
// FALSE will be returned.
//
BOOL
TryPutRect(NPMCIGRAPHIC npMCI, DWORD dwFlags, LPRECT lprc, LPDWORD lpdwErr)
{
RECT rc;
PRECT prcPut;
DWORD dw = 0;
// assume no error
*lpdwErr = 0;
if (dwFlags & MCI_DGV_PUT_DESTINATION) {
DPF2(("DevicePut: destination [%d, %d, %d, %d]\n", *lprc));
prcPut = &npMCI->rcDest;
} else {
DPF2(("DevicePut: source [%d, %d, %d, %d]\n", *lprc));
prcPut = &npMCI->rcSource;
//
// make sure source rectangle is in range.
//
// !!!should we return a error, or just fix the rectangle???
//
// ?? Why do we use an intermediate structure?
rc = npMCI->rcMovie;
IntersectRect(lprc, &rc, lprc); // fix up the passed rect.
}
//
// check for a bogus rect. either a NULL or inverted rect is considered
// invalid.
//
// !!!NOTE we should handle a inverted rect (mirrored stretch)
//
if (lprc->left >= lprc->right ||
lprc->top >= lprc->bottom) {
// this is fine if there are no video streams
if (npMCI->nVideoStreams <= 0) {
// no video so all ok
return FALSE;
}
DPF2(("DevicePut: invalid rectangle [%d, %d, %d, %d]\n", *lprc));
*lpdwErr = MCIERR_OUTOFRANGE;
return FALSE;
}
/* make sure the rect changed */
if (EqualRect(prcPut,lprc)) {
return FALSE;
}
InvalidateRect(npMCI->hwndPlayback, &npMCI->rcDest, TRUE);
rc = *prcPut; /* save it */
*prcPut = *lprc; /* change it */
InvalidateRect(npMCI->hwndPlayback, &npMCI->rcDest, FALSE);
/* have both the dest and source been set? */
if (IsRectEmpty(&npMCI->rcDest) || IsRectEmpty(&npMCI->rcSource)) {
return FALSE;
}
MapStreamRects(npMCI);
StreamInvalidate(npMCI, NULL); // invalidate the world
if (npMCI->wTaskState == TASKIDLE) {
DPF2(("TryPutRect: Idle, force DrawBegin on update\n"));
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
}
else {
BOOL fRestart = FALSE;
//
// we dont need to start/stop just begin again.
//
DPF2(("TryPutRect: Calling DrawBegin()\n"));
if (!DrawBegin(npMCI, &fRestart)) {
*lpdwErr = npMCI->dwTaskError;
return FALSE;
}
if (!DoStreamUpdate(npMCI, FALSE)) {
DPF(("TryPutRect: Failed update, forcing restart....\n"));
npMCI->lFrameDrawn = (- (LONG) npMCI->wEarlyRecords) - 1;
fRestart = TRUE;
}
if (fRestart) {
// restart needed
return TRUE;
}
}
// all ok
return FALSE;
}
void
OnTask_Put(NPMCIGRAPHIC npMCI)
{
DWORD dwFlags = npMCI->dwParamFlags;
LPRECT lprc = (LPRECT) npMCI->lParam;
DWORD dw = 0;
//If the user is doing an MCI_PUT to set the rectangle we should
//stop any previous requests to set the rectangle.
npMCI->dwWinProcRequests &= ~WINPROC_RESETDEST;
if (TryPutRect(npMCI, dwFlags, lprc, &dw)) {
// what to do now? It says we need to stop, but we
// are stopped.
TaskReturns(npMCI, MCIERR_DEVICE_NOT_READY);
return;
}
// if we stopped to do this, then restart whatever we were doing
if ((dw == 0) && (npMCI->dwFlags & MCIAVI_UPDATING)) {
// !!! We used to call InitDecompress here...
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
OnTask_RestartAgain(npMCI, TRUE);
} else {
TaskReturns(npMCI, dw);
}
}
BOOL
OnTask_PutDuringPlay(NPMCIGRAPHIC npMCI)
{
DWORD dwFlags = npMCI->dwParamFlags;
LPRECT lprc = (LPRECT) npMCI->lParam;
DWORD dw = 0;
if (TryPutRect(npMCI, dwFlags, lprc, &dw)) {
// need to stop to handle this one.
// !!! Set a flag here to prevent any more drawing
npMCI->fNoDrawing = TRUE;
OnTask_StopTemporarily(npMCI);
return TRUE;
}
// handled ok or error - no stop needed
TaskReturns(npMCI, dw);
return FALSE;
}
void OnTask_Palette(NPMCIGRAPHIC npMCI)
{
HPALETTE hpal = (HPALETTE)npMCI->lParam;
// Remember this for later.
npMCI->hpal = hpal;
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
InvalidateRect(npMCI->hwndPlayback, NULL, TRUE);
// !!! Do we need to stop and restart here?
// Answer: probably not, because if they care about the palette not being
// messed up, they probably never allowed us to be shown at all.
TaskReturns(npMCI, 0);
return;
}
void OnTask_PaletteColor(NPMCIGRAPHIC npMCI)
{
DWORD index = (DWORD)npMCI->lParam;
DWORD color = (DWORD)npMCI->dwParamFlags;
// !!! Do we need to stop and restart here?
// Answer: probably not, because if they care about the palette not being
// messed up, they probably never allowed us to be shown at all.
// Note: chicago code does stop... but they stop for most things.
// (it would be cleaner to stop and restart...)
// Pound the new color into the old format.
((DWORD FAR *) ((BYTE FAR *) npMCI->pbiFormat +
npMCI->pbiFormat->biSize))[index] = color;
((DWORD FAR *) npMCI->argb)[index] = color;
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
InvalidateRect(npMCI->hwndPlayback, NULL, TRUE);
TaskReturns(npMCI, 0);
return;
}
/*
* OnTask_ProcessRequest
*
* Process a request on the worker thread. Set hEventResponse if completed
* in error, or once the request has been completed (in the case of async
* requests such as play, set the event once play has begun ok
*
* return TRUE if it is time for the thread to exit, or else false.
*
*/
BOOL
OnTask_ProcessRequest(NPMCIGRAPHIC npMCI)
{
switch(npMCI->message) {
case AVI_CLOSE:
// release the requesting thread so he can go and wait on the
// worker thread exit
TaskReturns(npMCI, 0);
// now go and exit
return TRUE;
case AVI_RESUME:
// same as play, except that repeat and reverse flags need
// to be set on worker thread
npMCI->dwParamFlags |=
((npMCI->dwFlags & MCIAVI_REVERSE)? MCI_DGV_PLAY_REVERSE : 0);
// fall through
case AVI_PLAY:
OnTask_Play(npMCI);
break;
case AVI_STOP:
npMCI->dwFlags &= ~MCIAVI_REPEATING; // Give the wave device away
TaskReturns(npMCI, 0);
break;
case AVI_REALIZE:
OnTask_Realize(npMCI);
break;
case AVI_UPDATE:
OnTask_Update(npMCI);
break;
case AVI_PAUSE:
// not playing, so same as cue to current frame
OnTask_Cue(npMCI, npMCI->dwParamFlags | MCI_TO, npMCI->lCurrentFrame);
break;
case AVI_CUE:
OnTask_Cue(npMCI, npMCI->dwParamFlags, (LONG) npMCI->lParam);
break;
case AVI_SEEK:
OnTask_Seek(npMCI);
break;
case AVI_WINDOW:
OnTask_SetWindow(npMCI);
break;
case AVI_MUTE:
OnTask_Mute(npMCI);
break;
case AVI_SETSPEED:
OnTask_SetSpeed(npMCI);
break;
case AVI_SETVOLUME:
OnTask_SetVolume(npMCI);
break;
case AVI_GETVOLUME:
TaskReturns(npMCI, InternalGetVolume(npMCI));
break;
case AVI_AUDIOSTREAM:
OnTask_SetAudioStream(npMCI);
break;
case AVI_VIDEOSTREAM:
OnTask_SetVideoStream(npMCI);
break;
case AVI_PUT:
OnTask_Put(npMCI);
break;
case AVI_PALETTE:
OnTask_Palette(npMCI);
break;
case AVI_PALETTECOLOR:
OnTask_PaletteColor(npMCI);
break;
case AVI_WAVESTEAL:
OnTask_WaveSteal(npMCI);
break;
case AVI_WAVERETURN:
OnTask_WaveReturn(npMCI);
break;
default:
TaskReturns(npMCI, MCIERR_UNSUPPORTED_FUNCTION);
break;
}
return FALSE;
}
// OnTask_PeekRequest
//
// called from aviTaskCheckRequests() to process a message at play time.
// if the message requires a stop, then this function returns TRUE and
// leaves the message unprocessed.
//
// otherwise the message is fully processed. This must include resetting
// hEventSend
//
INLINE STATICFN BOOL
OnTask_PeekRequest(NPMCIGRAPHIC npMCI)
{
switch(npMCI->message) {
// always need to stop
case AVI_CLOSE:
case AVI_STOP:
npMCI->dwFlags &= ~MCIAVI_REPEATING; // Give the wave device away
return TRUE;
// may need to stop
case AVI_RESUME:
// same as play, except that repeat and reverse flags need
// to be set on worker thread
npMCI->dwParamFlags |=
((npMCI->dwFlags & MCIAVI_REPEATING)? MCI_DGV_PLAY_REPEAT : 0) |
((npMCI->dwFlags & MCIAVI_REVERSE)? MCI_DGV_PLAY_REVERSE : 0);
// fall through
case AVI_PLAY:
return OnTask_PlayDuringPlay(npMCI);
case AVI_UPDATE:
return OnTask_UpdateDuringPlay(npMCI);
case AVI_SEEK:
return OnTask_SeekDuringPlay(npMCI);
case AVI_CUE:
return OnTask_CueDuringPlay(npMCI);
case AVI_MUTE:
return OnTask_MuteDuringPlay(npMCI);
case AVI_WAVESTEAL:
return OnTask_WaveStealDuringPlay(npMCI);
case AVI_WAVERETURN:
return OnTask_WaveReturnDuringPlay(npMCI);
case AVI_SETSPEED:
return OnTask_SetSpeedDuringPlay(npMCI);
case AVI_PUT:
return OnTask_PutDuringPlay(npMCI);
// need temporary stop
case AVI_WINDOW:
case AVI_AUDIOSTREAM:
case AVI_VIDEOSTREAM:
OnTask_StopTemporarily(npMCI);
return TRUE;
// never need to stop
case AVI_REALIZE:
OnTask_Realize(npMCI);
break;
case AVI_PAUSE:
OnTask_PauseDuringPlay(npMCI);
break;
case AVI_SETVOLUME:
OnTask_SetVolume(npMCI);
break;
case AVI_GETVOLUME:
TaskReturns(npMCI, InternalGetVolume(npMCI));
break;
case AVI_PALETTE:
OnTask_Palette(npMCI);
break;
case AVI_PALETTECOLOR:
OnTask_PaletteColor(npMCI);
break;
default:
TaskReturns(npMCI, MCIERR_UNSUPPORTED_FUNCTION);
break;
}
return FALSE;
}
/*
* This routine is called from the IDLE loop and at key points while
* playing. If it is possible to service the request, the state of the
* device is updated and the request flag is cleared.
*
* If the request cannot be handled now (e.g. while playing) the flag
* is not set and we will be called again (e.g. when idle).
*
* If we need to stop to service the request (i.e. to regain the sound
* device) we return TRUE. In all other cases we return FALSE. The
* return value is only checked if we are actually playing.
*/
STATICFN void OnTask_WinProcRequests(NPMCIGRAPHIC npMCI, BOOL bPlaying)
{
DWORD requests;
EnterWinCrit(npMCI);
// grab the request bits now, so we don't need to hold the
// critsec while servicing them.
// any that are not cleared will be or-ed back in at the end
requests = npMCI->dwWinProcRequests;
npMCI->dwWinProcRequests = 0;
LeaveWinCrit(npMCI);
if (requests & WINPROC_STOP) {
requests &= ~WINPROC_STOP;
npMCI->dwFlags |= MCIAVI_STOP;
}
if (requests & WINPROC_MUTE) {
if (bPlaying) {
OnTask_StopTemporarily(npMCI);
} else {
// toggle audio flag
npMCI->dwFlags ^= MCIAVI_PLAYAUDIO;
requests &= ~WINPROC_MUTE;
}
}
if (requests & WINPROC_SOUND) {
// We might be able to pick up the sound. This is only of interest
// if we are currently playing, do not have a sound device, and want
// the audio.
if (bPlaying && (NULL == npMCI->hWave) && (MCIAVI_PLAYAUDIO & npMCI->dwFlags)) {
OnTask_StopTemporarily(npMCI);
} else {
// We have finished this request. Make sure we try and
// get sound when we restart
requests &= ~WINPROC_SOUND;
npMCI->dwFlags &= ~MCIAVI_LOSEAUDIO;
}
}
#ifdef REMOTESTEAL
if (requests & WINPROC_SILENT) {
extern HWND hwndWantAudio;
DPF2(("WINPROC_SILENT request made, bPlaying=%x\n", bPlaying));
// If we are playing, and we have a wave device, stop.
// When we are recalled, we will start again without the wave device.
if (bPlaying && npMCI->hWave) {
OnTask_StopTemporarily(npMCI);
// Stopping will cause the wave device to be released, which
// means a message will be posted to whoever wanted the wave
// device
} else {
// If we are playing, we do not have a wave device, and we
// do not want to stop.
// Otherwise we want to lose our wave device.
// Either way, we will finish with WINPROC_SILENT on this pass
requests &= ~WINPROC_SILENT;
hwndWantAudio = 0; // In case we did not have to stop
if (!bPlaying) {
// Remember we lost audio, and start again without audio
npMCI->dwFlags |= MCIAVI_LOSTAUDIO;
npMCI->dwFlags |= MCIAVI_LOSEAUDIO;
}
}
}
#endif
if (requests & WINPROC_RESETDEST) {
RECT rc;
DWORD dw;
if (npMCI->hwndPlayback &&
npMCI->hwndPlayback == npMCI->hwndDefault &&
(npMCI->dwOptionFlags & MCIAVIO_STRETCHTOWINDOW)) {
GetClientRect(npMCI->hwndPlayback, &rc);
} else if (npMCI->streams > 0) {
rc = npMCI->rcMovie;
if (npMCI->dwOptionFlags & MCIAVIO_ZOOMBY2) {
rc.right *= 2;
rc.bottom *= 2;
}
}
if (TryPutRect(npMCI, MCI_DGV_PUT_DESTINATION, &rc, &dw) && bPlaying) {
OnTask_StopTemporarily(npMCI);
} else {
requests &= ~WINPROC_RESETDEST;
}
}
if (requests & WINPROC_ACTIVE) {
// We are being made active. The only extra work we must do
// is grab the wave device - if we have ever lost it.
// If we are playing, and we do not have the audio, and we want
// the audio...
if (bPlaying
&& (npMCI->hWave == 0)
&& (npMCI->dwFlags & MCIAVI_PLAYAUDIO)) {
// Let's try and make the sound active by stealing the wave device
// Must stop before trying to reset the sound
if (StealWaveDevice(npMCI)) {
OnTask_StopTemporarily(npMCI);
// Force ourselves to be called again. Doing it this way
// means that we will be recalled. We cannot rely on
// WINPROC_ACTIVE staying around. A deactivation could
// cause the flag to be cleared
requests |= WINPROC_SOUND;
}
} else {
// We had not lost the wave device...
// OR we are playing silently, and so there is no point
// in trying to steal it.
// We are finished.
}
// Clear WINPROC_ACTIVE - all processing done.
// Note: we might have set WINPROC_SOUND, which will cause this
// routine to be recalled. Once recalled, then playing can restart
requests &= ~ WINPROC_ACTIVE;
} else { // We never have both INACTIVE and ACTIVE at the same time
if (requests & WINPROC_INACTIVE) {
//!!!need to support this
requests &= ~WINPROC_INACTIVE;
}
}
EnterWinCrit(npMCI); // Do we really need this one here??
if (requests & WINPROC_UPDATE) {
if (bPlaying) {
npMCI->dwFlags |= MCIAVI_NEEDUPDATE;
} else {
HDC hdc;
// don't do this if the window is now hidden
// or showstage will be called with the critsec and deadlock
if (IsWindowVisible(npMCI->hwndPlayback)) {
EnterHDCCrit(npMCI);
npMCI->bDoingWinUpdate = TRUE;
hdc = GetDC(npMCI->hwndPlayback);
Assert(hdc);
Internal_Update(npMCI, MCI_DGV_UPDATE_PAINT, hdc, NULL);
ReleaseDC(npMCI->hwndPlayback, hdc);
npMCI->bDoingWinUpdate = FALSE;
LeaveHDCCrit(npMCI);
}
}
requests &= ~WINPROC_UPDATE;
}
if (requests & WINPROC_REALIZE) {
EnterHDCCrit(npMCI);
InternalRealize(npMCI);
LeaveHDCCrit(npMCI);
requests &= ~ WINPROC_REALIZE;
}
// or back the bits we didn't clear
npMCI->dwWinProcRequests |= requests;
// if we processed all the bits (and no new bits were set)
if (! npMCI->dwWinProcRequests) {
ResetEvent(npMCI->heWinProcRequest);
}
LeaveWinCrit(npMCI);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api void| aviTaskCheckRequests | called on the worker thread at least once per
*
* frame. We use this to check for requests from the user thread.
*
*
***************************************************************************/
void NEAR PASCAL aviTaskCheckRequests(NPMCIGRAPHIC npMCI)
{
HANDLE hWaiter;
if (WaitForSingleObject(npMCI->hEventSend, 0) == WAIT_OBJECT_0) {
// there is a request
Assert(npMCI->message != 0);
// check the waiter
// if this is an async request with wait, we need to set hWaiter
// so that hEventAllDone is set correctly. If we stop
// and process this message in the idle loop, then we don't want to
// set hWaiter here, or EventAllDone could be signalled when we
// stop - before we've even started this request.
// so we need to check the validity (no waiting if another thread is
// waiting) and pick up the waiter and bDelayed while the critsec
// is still held, but only set hWaiter if the request was processed
// here.
// no - that leaves a timing window when hWaiter is not set and the
// critsec is not held. Set hWaiter, but be prepared to unset it
// if we postpone processing during the idle loop (in which case,
// the waiter will hold the critsec until we have stopped).
hWaiter = npMCI->hWaiter;
if (npMCI->bDelayedComplete) {
if (npMCI->hWaiter && (npMCI->hWaiter != npMCI->hRequestor)) {
TaskReturns(npMCI, MCIERR_DEVICE_NOT_READY);
return;
} else {
DPF2(("Replacing hWaiter in aviTaskCheckRequests... was %x, now %x\n", npMCI->hWaiter, npMCI->hRequestor));
npMCI->hWaiter = npMCI->hRequestor;
}
}
DPF2(("peek %d [%x] ...", npMCI->message, npMCI->hRequestor));
if (OnTask_PeekRequest(npMCI)) {
// we need to stop
// must be set on WORKER THREAD ONLY
npMCI->dwFlags |= MCIAVI_STOP;
DPF2(("Need to stop - replacing hWaiter (was %x, now %x)\n", npMCI->hWaiter, hWaiter));
// replace hWaiter so idle loop does not set hEventAllDone for
// a request he has not yet started.
npMCI->hWaiter = hWaiter;
}
// else the request has already been dealt with
}
// did the winproc have any requests
if (WaitForSingleObject(npMCI->heWinProcRequest, 0) == WAIT_OBJECT_0) {
//
// We have a request from the window thread. Go process it
//
OnTask_WinProcRequests(npMCI, TRUE);
}
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | CheckIfActive | check to see if we are the active movie
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
void CheckIfActive(NPMCIGRAPHIC npMCI)
{
BOOL fActive;
HWND hwndA;
if (!IsTask(npMCI->hTask)) return;
//
// are we the foreground window?
//
// ??? should the value of <npMCI->fForceBackground> matter?
//
// IMPORTANT: This does NOT work under NT. The best that can
// be done is to check GetForegroundWindow
#ifndef _WIN32
hwndA = GetActiveWindow();
fActive = (hwndA == npMCI->hwndPlayback) ||
(GetFocus() == npMCI->hwndPlayback) ||
(IsWindow(hwndA) && IsChild(hwndA, npMCI->hwndPlayback) && !npMCI->fForceBackground);
#else
hwndA = GetForegroundWindow();
fActive = (hwndA == npMCI->hwndPlayback) ||
(IsWindow(hwndA) && IsChild(hwndA, npMCI->hwndPlayback) && !npMCI->fForceBackground);
#endif
DeviceSetActive(npMCI, fActive);
}