/******************************************************************************

   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);
}

