You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2875 lines
78 KiB
2875 lines
78 KiB
/******************************************************************************
|
|
|
|
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);
|
|
}
|
|
|