windows-nt/Source/XPSP1/NT/multimedia/media/avi/mciavi.16/device.c

2239 lines
61 KiB
C
Raw Normal View History

2020-09-26 03:20:57 -05:00
/******************************************************************************
Copyright (C) Microsoft Corporation 1985-1991. All rights reserved.
Title: device.c - Multimedia Systems Media Control Interface
driver for AVI.
*****************************************************************************/
#include "graphic.h"
#include "avitask.h"
#define ALIGNULONG(i) ((i+3)&(~3)) /* ULONG aligned ! */
#define WIDTHBYTES(i) ((unsigned)((i+31)&(~31))/8) /* ULONG aligned ! */
#define DIBWIDTHBYTES(bi) (DWORD)WIDTHBYTES((int)(bi).biWidth * (int)(bi).biBitCount)
#ifdef WIN32
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api void | TaskWaitComplete | wait for a task to complete
*
***************************************************************************/
void TaskWaitComplete(NPMCIGRAPHIC npMCI)
{
LONG lCount;
/*
** Release the critical section so that the task can complete!
*/
lCount = npMCI->lCritRefCount;
npMCI->lCritRefCount = 0;
LeaveCriticalSection(&npMCI->CritSec);
/*
** Use the handle given to us when we created the task to wait
** for the thread to complete
*/
WaitForSingleObject(npMCI->hThreadTermination, INFINITE);
CloseHandle(npMCI->hThreadTermination);
/*
** Restore our critical section state
*/
EnterCriticalSection(&npMCI->CritSec);
npMCI->lCritRefCount = lCount;
}
#endif
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api void | TaskWait | wait for a task state
* background task.
*
***************************************************************************/
DWORD mciaviTaskWait(NPMCIGRAPHIC npMCI, int state, BOOL fMciWait)
{
#ifdef WIN32
long lCount;
MSG msg;
#endif
DWORD dwStartWaitTime = timeGetTime();
// #define TIMEOUT_VALUE 10000L
#ifndef WIN32
Assert(npMCI->hTask != GetCurrentTask());
#endif
/*
** either wait for a state (state > 0) or wait for not state (state < 0)
**
** !!! if we want to timeout this is the place to do it.
**
** !!! Should we put up a wait cursor here?
*/
while (state < 0
? (int)npMCI->wTaskState == -state
: (int)npMCI->wTaskState != state)
{
if (!IsTask(npMCI->hTask)) {
npMCI->dwTaskError = MCIERR_DEVICE_NOT_READY;
return MCIERR_DEVICE_NOT_READY;
}
#ifdef WIN32
/*
* Critical sections:
*
* We hold a critical section around the whole of the
* WinProc (among other things). The owning thread can re-enter
* a critical section, but needs to Leave the same number of times.
*
* When yielding here, we need to release the critical section. To avoid
* the problem of multiple entries, EnterCrit is a macro that
* increments an entry count (protected by the critical section), and
* only goes one level into the critsec. Correspondingly,
* LeaveCrit decrements the count and only actually leaves if
* the count reaches 0.
*
* Here, however, we need to actually Enter and Leave regardless
* of the count, so that we release the critical section
* to other threads during the yield. Thus we don't use the macro,
* and we also save/restore the critsec count so someone else
* getting the critsec while we are yielding will behave correctly.
*/
lCount = npMCI->lCritRefCount;
npMCI->lCritRefCount = 0;
LeaveCriticalSection(&npMCI->CritSec);
/*
* Sleep for > 0 because this thread may be at a higher priority than
* then thread actually playing the AVI because of the use of
* SetThreadPriorityBackground and Sleep(0) only relinquishes
* the remainder of the time slice if another thread of the SAME
* PRIORITY is waiting to run.
*/
Sleep(10);
#else
// DirectedYield(npMCI->hTask);
Yield();
#endif
#ifdef WM_AVISWP
if (TRUE)
#else
if (fMciWait)
#endif
{
#ifdef WIN32
/*
* if it's safe to yield, it's safe to poll
* messages fully. This way, we will be absolutely
* sure of getting the async size messages etc
*/
//aviTaskYield();
// it clearly is not safe at this point, since this
// yield can cause mci to close the driver, leaving us
// with nowhere to return to.
// handling messages for our own window is safe and should have the
// desired effect.
#ifdef WM_AVISWP
if (npMCI->hwnd) {
if (PeekMessage(&msg, npMCI->hwnd, WM_AVISWP, WM_AVISWP, PM_REMOVE))
DispatchMessage(&msg);
}
#endif
#endif
if (fMciWait && mciDriverYield(npMCI->wDevID)) {
#ifdef WIN32
EnterCriticalSection(&npMCI->CritSec);
npMCI->lCritRefCount = lCount;
#endif
break;
}
} else {
#ifdef TIMEOUT_VALUE
if (timeGetTime() > dwStartWaitTime + TIMEOUT_VALUE) {
Assert(0);
npMCI->dwTaskError = MCIERR_DEVICE_NOT_READY;
#ifdef WIN32
EnterCriticalSection(&npMCI->CritSec);
npMCI->lCritRefCount = lCount;
#endif
return MCIERR_DEVICE_NOT_READY;
}
#endif
}
#ifdef WIN32
EnterCriticalSection(&npMCI->CritSec);
npMCI->lCritRefCount = lCount;
#endif
}
return 0L;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | mciaviTaskMessage | this function sends a message to the
* background task.
*
***************************************************************************/
DWORD mciaviTaskMessage(NPMCIGRAPHIC npMCI, int msg)
{
if (!IsTask(npMCI->hTask)) {
npMCI->dwTaskError = MCIERR_DEVICE_NOT_READY;
return npMCI->dwTaskError;
}
if (GetCurrentTask() == npMCI->hTask) {
mciaviMessage(npMCI, msg);
return npMCI->dwTaskError;
}
if (npMCI->wTaskState == TASKPAUSED) {
DPF(("Ack! message while PAUSED!\n"));
return 1; // !!! Real error?
}
#ifdef DEBUG
if (npMCI->wTaskState != TASKIDLE) {
DPF0(("Unknown task state (mciaviTaskMessage) %d\n", npMCI->wTaskState));
}
Assert(npMCI->wTaskState == TASKIDLE);
#endif
if (mciaviTaskWait(npMCI, TASKIDLE, FALSE) != 0) {
DPF(("Error waiting for TASKIDLE in mciaviTaskMessage\n"));
return npMCI->dwTaskError;
}
npMCI->dwTaskError = 0L;
npMCI->wTaskState = msg;
mmTaskSignal(npMCI->hTask);
/*
** wait for the message to kick in
*/
mciaviTaskWait(npMCI, -msg, FALSE);
return npMCI->dwTaskError;
}
DWORD NEAR PASCAL StopTemporarily(NPMCIGRAPHIC npMCI, TEMPORARYSTATE FAR * ps)
{
DWORD dw;
HWND hCallback;
DPF2(("StopTemporarily: stopping from state %u.\n", npMCI->wTaskState));
Assert(ps);
ps->wOldTaskState = npMCI->wTaskState;
ps->dwFlags = npMCI->dwFlags;
ps->lTo = npMCI->lTo - (LONG)npMCI->dwBufferedVideo;
ps->lFrom = npMCI->lFrom;
//
// setting MCIAVI_UPDATING will make sure we dont yield or do
// other wierd things unless we need to. it is a bad name for the
// flag I know I am sorry.
//
// it means we are stoping temporarily and will be restarted
// the code will not do things like give our wave device
// away or become the active window.
//
npMCI->dwFlags |= MCIAVI_UPDATING;
/* Hide the delayed notification, if any, so it doesn't happen now. */
hCallback = npMCI->hCallback;
npMCI->hCallback = NULL;
dw = DeviceStop(npMCI, MCI_WAIT);
/* Restore the notification */
npMCI->hCallback = hCallback;
if (dw != 0 ) {
if (ps->dwFlags & MCIAVI_UPDATING)
npMCI->dwFlags |= MCIAVI_UPDATING;
else
npMCI->dwFlags &= ~MCIAVI_UPDATING;
}
DPF2(("StopTemporarily: stopped.\n"));
return dw;
}
DWORD NEAR PASCAL RestartAgain(NPMCIGRAPHIC npMCI, TEMPORARYSTATE FAR * ps)
{
DWORD dw = 0;
DWORD dwFlags = 0;
DPF2(("Restart Again: restarting.\n"));
Assert(ps);
if (ps->dwFlags & MCIAVI_REVERSE)
dwFlags = MCI_DGV_PLAY_REVERSE;
// !!! Make sure that this will actually cause a repeat in all cases....
if (ps->dwFlags & MCIAVI_REPEATING)
npMCI->dwFlags |= MCIAVI_REPEATING;
else
npMCI->dwFlags &= ~MCIAVI_REPEATING;
if (ps->wOldTaskState == TASKPLAYING) {
/* The only flags that matter at this point are the
** VGA flags and the wait flag. If we managed to
** get a new command, neither is in effect, so it's
** okay to pass zero for these flags.
*/
npMCI->lFrom = npMCI->lCurrentFrame;
dw = DevicePlay(npMCI, ps->lTo, dwFlags | MCI_TO);
} else if (ps->wOldTaskState == TASKCUEING) {
/* Continue whatever we were doing */
npMCI->lFrom = ps->lFrom;
dw = DevicePlay(npMCI, ps->lTo, dwFlags | MCI_TO);
} else if (ps->wOldTaskState == TASKPAUSED) {
dw = DeviceCue(npMCI, 0, MCI_WAIT);
npMCI->lTo = ps->lTo;
} else if (ps->wOldTaskState == TASKIDLE) {
} else {
DPF(("Trying to restart: task state %u...\n", ps->wOldTaskState));
Assert(0);
}
//
// restore this flag so we can yield again.
//
if (ps->dwFlags & MCIAVI_UPDATING)
npMCI->dwFlags |= MCIAVI_UPDATING;
else
npMCI->dwFlags &= ~MCIAVI_UPDATING;
DPF2(("Restart Again: restarted.\n"));
return dw;
}
/***************************************************************************
*
* @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)) {
DPF0(("ShowStage returning NEEDTOSHOW is OFF\n"));
return;
}
if ((npMCI->dwFlags & MCIAVI_SHOWVIDEO) &&
npMCI->hwnd == npMCI->hwndDefault &&
////////////!(GetWindowLong(npMCI->hwnd, GWL_STYLE) & WS_CHILD) &&
(!IsWindowVisible (npMCI->hwnd) ||
npMCI->hwnd != GetActiveWindow ())) {
#ifdef WM_AVISWP
// Get the UI thread to do the window positioning
// This routine can be called on the background task while the main
// routine is waiting in mciaviTaskWait
SendMessage(npMCI->hwnd, WM_AVISWP, 0,
SWP_NOMOVE | SWP_NOSIZE |
SWP_SHOWWINDOW |
(IsWindowVisible(npMCI->hwnd) ? SWP_NOACTIVATE : 0));
#else
SetWindowPos(npMCI->hwnd, HWND_TOP, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE |
SWP_SHOWWINDOW |
(IsWindowVisible(npMCI->hwnd) ? SWP_NOACTIVATE : 0));
#endif
}
//
// 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->hwnd == npMCI->hwndDefault &&
!(GetWindowLong(npMCI->hwnd, GWL_STYLE) & WS_CHILD)) {
SetActiveWindow(npMCI->hwnd);
}
npMCI->dwFlags &= ~(MCIAVI_NEEDTOSHOW);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceOpen | Open an AVI file.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm LPSTR | lpName | file name.
*
* @parm DWORD | dwFlags | Open flags.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceOpen(NPMCIGRAPHIC npMCI, DWORD dwFlags)
{
DWORD dwRet = 0L;
npMCI->wTaskState = TASKBEINGCREATED;
npMCI->uErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS |
SEM_NOOPENFILEERRORBOX);
#ifndef WIN32
// give our PSP to the task
npMCI->pspParent = GetCurrentPDB();
#endif
switch (mmTaskCreate(mciaviTask, &npMCI->hThreadTermination, (DWORD)(UINT)npMCI)) {
case 0:
// Yield to the newly created task until it has
// had a chance to initialize or fail to initialize
while (npMCI->wTaskState <= TASKINIT) {
#ifndef WIN32
Yield();
#else
/* we have to peekmsg here since the threads are
* synchronised. but we don't need to actually
* pick up any messages - so limit ourselves to the
* avi window ones
*/
Sleep(1);
if (npMCI->hwnd) {
MSG msg;
if (PeekMessage(&msg, npMCI->hwnd, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
}
#endif
if (npMCI->wTaskState != TASKBEINGCREATED && !IsTask(npMCI->hTask))
break;
}
/*
* we need to do this peek message again. We may have never
* entered the body of the loop above, or if this thread
* gets very little cpu during the above loop, we might fail to
* execute the PeekMessage above AFTER the SetWindowPos happens
* in mciaviOpen. In that case, the swp resizing will not happen
* until the next getmessage or peekmessage - in that case,
* it could come after the ShowWindow (bad) or after
* another size request (much worse).
*
* First check the thread opened the device successfully
*/
if (!IsTask(npMCI->hTask)) {
// Task thread failed its initialisation. Wait for the
// task to terminate before returning to the user.
DPF2(("Waiting for task thread to terminate\n"));
#ifdef WIN32
// On Win32 we must explicitly wait. On Win16, because this
// "thread" does not get control back until the task thread
// releases control the wait is irrelevant and is not used.
TaskWaitComplete(npMCI);
#endif
dwRet = npMCI->dwTaskError;
} else {
if (npMCI->hwnd) {
MSG msg;
if (PeekMessage(&msg, npMCI->hwnd, 0, 0, PM_REMOVE)) {
DispatchMessage(&msg);
}
}
}
break;
case TASKERR_NOTASKSUPPORT:
case TASKERR_OUTOFMEMORY:
default:
npMCI->hTask = 0;
dwRet = MCIERR_OUT_OF_MEMORY;
break;
}
SetErrorMode(npMCI->uErrorMode);
return dwRet;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceStop | Stop an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | Flags.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceStop(NPMCIGRAPHIC npMCI, DWORD dwFlags)
{
DWORD dw = 0L;
/* Stop the record or playback if the task is currently playing */
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
if (npMCI->wTaskState == TASKPLAYING || npMCI->wTaskState == TASKPAUSED
|| npMCI->wTaskState == TASKCUEING
|| npMCI->wTaskState == TASKSTARTING) {
/* Set STOP flag - the task watches for this flag to be set. The
** STOP flag is cleared by the task when playback has stopped.
*/
// Assert(!(npMCI->dwFlags & MCIAVI_STOP));
npMCI->dwFlags |= MCIAVI_STOP;
/* Send an extra signal to the task in case it is still
** blocked. This will be true if we are paused or if play
** has just completed.
*/
mmTaskSignal(npMCI->hTask);
/* Yield until playback is finished and we've really stopped. */
mciaviTaskWait(npMCI, TASKIDLE, FALSE);
} else {
#ifdef DEBUG
if (npMCI->wTaskState != TASKIDLE) {
DPF0(("Unknown task state (DeviceStop) %d\n", npMCI->wTaskState));
}
Assert(npMCI->wTaskState == TASKIDLE); // ??? Why ???
#endif
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DevicePause | Pause an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | Flags.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DevicePause(NPMCIGRAPHIC npMCI, DWORD dwFlags)
{
DWORD dw = 0L;
// If we're currently seeking, allow that to finish before
// pausing. This could potentially lock up the machine for
// a while, but the alternatives are ugly.
mciaviTaskWait(npMCI, -TASKCUEING, FALSE);
// Pause the record or playback if the task is currently playing
// or recording (BUSY)
if (npMCI->wTaskState == TASKPAUSED) {
/* We're already paused at the right place, so
** that means we did it. Reset the flag, though, just in
** case we were about to restart.
*/
npMCI->dwFlags |= MCIAVI_PAUSE;
if (dwFlags & MCI_NOTIFY)
GraphicDelayedNotify(npMCI, MCI_NOTIFY_SUCCESSFUL);
} else if (npMCI->wTaskState == TASKPLAYING) {
npMCI->dwFlags |= MCIAVI_PAUSE | MCIAVI_WAITING;
/* If the notify flag is set, set a flag which will tell us to
** send a notification when we actually pause.
*/
if (dwFlags & MCI_NOTIFY)
npMCI->dwFlags |= MCIAVI_CUEING;
if (dwFlags & MCI_WAIT) {
/* We have to wait to actually pause. */
mciaviTaskWait(npMCI, -TASKPLAYING, TRUE);
}
npMCI->dwFlags &= ~(MCIAVI_WAITING);
} else if (npMCI->wTaskState == TASKIDLE) {
/* We're stopped. Put us in paused mode by cueing. */
npMCI->lTo = npMCI->lCurrentFrame;
DeviceCue(npMCI, 0, dwFlags);
} else {
dw = MCIERR_NONAPPLICABLE_FUNCTION;
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceClose | Close an AVI file.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceClose (NPMCIGRAPHIC npMCI)
{
DWORD dw = 0L;
if (npMCI && IsTask(npMCI->hTask)) {
/* Be sure to stop playing, one way or another... */
DeviceStop(npMCI, MCI_WAIT);
// task state is now TASKIDLE and blocked
#ifdef DEBUG
if (npMCI->wTaskState != TASKIDLE) {
DPF0(("Unknown task state (DeviceClose) %d\n", npMCI->wTaskState));
}
Assert(npMCI->wTaskState == TASKIDLE);
#endif
// Set task state to TASKCLOSE - this informs the task that it is
// time to die.
mciaviTaskMessage(npMCI, TASKCLOSE);
mciaviTaskWait(npMCI, TASKCLOSED, FALSE);
#ifdef WIN32
/*
** Wait for the thread to complete so the DLL doesn't get unloaded
** while it's still executing code in that thread
*/
TaskWaitComplete(npMCI);
#endif // WIN32
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DevicePlay | Play an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DevicePlay (NPMCIGRAPHIC npMCI, LONG lPlayTo, DWORD dwFlags)
{
HWND hCallback;
DWORD dw = 0L;
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
/* if not already playing, start the task up. */
if (dwFlags & MCI_NOTIFY) {
/* Hide the delayed notification, if any, so it doesn't happen now. */
hCallback = npMCI->hCallback;
npMCI->hCallback = NULL;
}
/* 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);
}
// !!! This check is dumb: Some DISPDIB's may support > 320x240....
if ((npMCI->rcMovie.right > 320) || (npMCI->rcMovie.bottom > 240)) {
dw = MCIERR_AVI_TOOBIGFORVGA;
goto Exit;
}
/* If playing, we have to stop so that we'll get put into DispDib
mode correctly. */
dw = DeviceStop(npMCI, MCI_WAIT);
if (dw)
goto Exit;
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);
}
if ((npMCI->dwFlags & MCIAVI_SEEKING) && (npMCI->lTo != npMCI->lFrom)) {
/* We're currently seeking, so we have to restart to get audio
** to work.
*/
DeviceStop(npMCI, MCI_WAIT);
}
/* If we're currently seeking, stop so play can begin immediately. */
if (npMCI->wTaskState == TASKCUEING) {
DeviceStop(npMCI, MCI_WAIT);
}
if (npMCI->wTaskState == TASKPLAYING || npMCI->wTaskState == TASKPAUSED) {
if (((npMCI->dwFlags & MCIAVI_REVERSE) != 0) !=
((dwFlags & MCI_DGV_PLAY_REVERSE) != 0))
DeviceStop(npMCI, MCI_WAIT);
}
// Make sure flags are cleared if they should be
npMCI->dwFlags &= ~(MCIAVI_PAUSE | MCIAVI_CUEING | MCIAVI_REVERSE);
if (dwFlags & MCI_DGV_PLAY_REPEAT) {
npMCI->dwFlags |= MCIAVI_REPEATING;
}
if (dwFlags & MCI_NOTIFY) {
/* Restore the notification */
npMCI->hCallback = hCallback;
}
if (lPlayTo > npMCI->lFrames)
lPlayTo = npMCI->lFrames;
if (lPlayTo < 0)
lPlayTo = 0;
if (dwFlags & MCI_TO)
npMCI->lTo = lPlayTo;
if (dwFlags & MCI_DGV_PLAY_REVERSE)
npMCI->dwFlags |= MCIAVI_REVERSE;
npMCI->dwFlags |= MCIAVI_WAITING;
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 a interal 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;
}
if (npMCI->wTaskState == TASKPAUSED) {
/* Wake the task up from pausing */
mmTaskSignal(npMCI->hTask);
} else if (npMCI->wTaskState == TASKCUEING ||
npMCI->wTaskState == TASKPLAYING) {
} else {
/* Tell the task what to do when it wakes up */
mciaviTaskMessage(npMCI, TASKSTARTING);
dw = npMCI->dwTaskError;
}
if (dwFlags & MCI_WAIT) {
// yield to playback task until playback completes but don't
// yield to application - apps must use driveryield to get
// out of waits.
mciaviTaskWait(npMCI, TASKIDLE, TRUE);
dw = npMCI->dwTaskError;
}
if (dwFlags & (MCI_MCIAVI_PLAY_FULLSCREEN | MCI_MCIAVI_PLAY_FULLBY2)) {
MSG msg;
/* Remove stray mouse and keyboard events after DispDib. */
while (PeekMessage(&msg, NULL, WM_KEYFIRST, WM_KEYLAST,
PM_NOYIELD | PM_REMOVE) ||
PeekMessage(&msg, NULL, WM_MOUSEFIRST, WM_MOUSELAST,
PM_NOYIELD | PM_REMOVE))
;
}
npMCI->dwFlags &= ~(MCIAVI_WAITING);
Exit:
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceResume | Play an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceResume(NPMCIGRAPHIC npMCI, DWORD dwFlags)
{
DWORD dw = 0L;
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
dw = DevicePlay(npMCI, 0, dwFlags |
((npMCI->dwFlags & MCIAVI_REVERSE) ? MCI_DGV_PLAY_REVERSE : 0));
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceCue | Cue an AVI movie for playing.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm LONG | lTo | Frame to seek to, if MCI_TO set in <p dwFlags>.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceCue(NPMCIGRAPHIC npMCI, LONG lTo, DWORD dwFlags)
{
DWORD dw = 0L;
HWND hCallback;
/* if not already playing, start animation and set timer */
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
if (dwFlags & MCI_NOTIFY) {
/* Hide the delayed notification, if any, so it doesn't happen now. */
hCallback = npMCI->hCallback;
npMCI->hCallback = NULL;
}
if (npMCI->dwFlags & MCIAVI_SEEKING) {
/* We're currently seeking, so we have to start again to get audio
** to work.
*/
DeviceStop(npMCI, MCI_WAIT);
}
if (dwFlags & MCI_TO) {
DeviceStop(npMCI, MCI_WAIT);
npMCI->lFrom = lTo;
} else if (npMCI->wTaskState == TASKIDLE) {
npMCI->lFrom = npMCI->lCurrentFrame;
}
if (dwFlags & MCI_NOTIFY) {
/* Restore the notification */
npMCI->hCallback = hCallback;
}
/* 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);
} else if (npMCI->wTaskState == TASKIDLE) {
// !!! Is this the only condition we can do this in?
npMCI->dwFlags |= MCIAVI_PAUSE | MCIAVI_CUEING | MCIAVI_WAITING;
mciaviTaskMessage(npMCI, TASKSTARTING);
if (dwFlags & MCI_WAIT) {
mciaviTaskWait(npMCI, -TASKCUEING, TRUE);
}
npMCI->dwFlags &= ~(MCIAVI_WAITING);
dw = npMCI->dwTaskError;
} else if (npMCI->wTaskState == TASKCUEING) {
npMCI->dwFlags |= MCIAVI_PAUSE | MCIAVI_CUEING | MCIAVI_WAITING;
if (dwFlags & MCI_WAIT) {
mciaviTaskWait(npMCI, -TASKCUEING, TRUE);
}
npMCI->dwFlags &= ~(MCIAVI_WAITING);
dw = npMCI->dwTaskError;
} else if (npMCI->wTaskState == TASKPLAYING) {
npMCI->dwFlags |= MCIAVI_PAUSE | MCIAVI_CUEING | MCIAVI_WAITING;
if (dwFlags & MCI_WAIT) {
mciaviTaskWait(npMCI, -TASKPLAYING, TRUE);
}
npMCI->dwFlags &= ~(MCIAVI_WAITING);
dw = npMCI->dwTaskError;
} else {
dw = MCIERR_NONAPPLICABLE_FUNCTION;
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSeek | Seek to a position in an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm LONG | lTo | Frame to seek to.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceSeek(NPMCIGRAPHIC npMCI, LONG lTo, DWORD dwFlags)
{
DWORD dw = 0;
HWND hCallback;
DPF3(("DeviceSeek\n"));
/* 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)) {
if (lTo != npMCI->lTo) {
DPF3(("Seeking to %ld instead.\n", lTo));
}
npMCI->lTo = lTo;
return 0L;
}
if (dwFlags & MCI_NOTIFY) {
/* Hide the delayed notification, if any, so it doesn't happen now. */
hCallback = npMCI->hCallback;
npMCI->hCallback = NULL;
}
/* If playing, stop, so we can seek. */
dw = DeviceStop(npMCI, MCI_WAIT);
if (dwFlags & MCI_NOTIFY) {
/* Restore the notification */
npMCI->hCallback = hCallback;
}
// task state is now TASKIDLE and blocked
if (npMCI->lCurrentFrame != lTo) {
npMCI->dwFlags |= MCIAVI_WAITING;
/* 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;
mciaviTaskMessage(npMCI, TASKSTARTING);
if (dwFlags & MCI_WAIT) {
mciaviTaskWait(npMCI, -TASKCUEING, TRUE);
}
npMCI->dwFlags &= ~(MCIAVI_WAITING);
} else {
/* 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);
}
return dw;
}
/***************************************************************************
*
* @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
*
***************************************************************************/
static void CheckIfActive(NPMCIGRAPHIC npMCI)
{
BOOL fActive;
HWND hwndA;
//
// 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
hwndA = GetActiveWindow();
fActive = (hwndA == npMCI->hwnd) ||
(GetFocus() == npMCI->hwnd) ||
(IsChild(hwndA, npMCI->hwnd) && !npMCI->fForceBackground);
DeviceSetActive(npMCI, fActive);
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceRealize | Updates the frame into the given DC
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @parm BOOL | fForceBackground | Realize as background palette?
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceRealize(NPMCIGRAPHIC npMCI)
{
BOOL fGetDC;
BOOL fPalChanged;
BOOL fAlreadyDoneThat;
if (npMCI->dwFlags & MCIAVI_WANTMOVE)
CheckWindowMove(npMCI, TRUE);
if (fAlreadyDoneThat = (BOOL)(npMCI->dwFlags & MCIAVI_UPDATING)) {
DPF(("Re-entering DeviceRealize - but we don't care"));
}
if (fGetDC = (npMCI->hdc == NULL)) {
npMCI->hdc = GetDC(npMCI->hwnd);
}
npMCI->dwFlags |= MCIAVI_UPDATING;
fPalChanged = PrepareDC(npMCI) > 0;
if (!fAlreadyDoneThat)
npMCI->dwFlags &= ~MCIAVI_UPDATING;
if (fGetDC) {
UnprepareDC(npMCI);
ReleaseDC(npMCI->hwnd, npMCI->hdc);
npMCI->hdc = NULL;
}
if (fPalChanged)
InvalidateRect(npMCI->hwnd, &npMCI->rcDest, TRUE);
CheckIfActive(npMCI);
return 0L;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceActivate | is the movie active?
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceSetActive(NPMCIGRAPHIC npMCI, BOOL fActive)
{
if (fActive)
#ifdef WIN32
// We must explicitly request a unicode string. %s will not
// work as dprintf uses wvsprintfA
DPF(("**** '%hs' is active.\n", (LPTSTR)npMCI->szFilename));
#else
DPF(("**** '%s' is active.\n", (LPTSTR)npMCI->szFilename));
#endif
//
// if we are now the foreground "window" try to get the wave
// device back (iff it was stolen from us)
//
if (fActive && (npMCI->dwFlags & MCIAVI_LOSTAUDIO)) {
if (StealWaveDevice(npMCI)) {
Assert(npMCI->dwFlags & MCIAVI_PLAYAUDIO);
Assert(npMCI->hWave == NULL);
npMCI->dwFlags &= ~MCIAVI_PLAYAUDIO;
DeviceMute(npMCI, FALSE);
}
}
return 0;
}
/***************************************************************************
*
* 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)
{
POINT pt;
GetDCOrgEx(hdc, &pt);
return pt.x != 0 && pt.y != 0;
}
#endif
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceUpdate | Updates the frame into the given DC
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @parm HDC | hDC | DC to draw frame into.
*
* @parm LPRECT | lprc | Update rect.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceUpdate(NPMCIGRAPHIC npMCI, DWORD dwFlags, HDC hdc, LPRECT lprc)
{
DWORD dwErr = 0L;
BOOL f;
HDC hdcSave;
TEMPORARYSTATE ts;
HWND hCallback;
HCURSOR hcurPrev;
RECT rc;
LONG lFrameDrawn;
if (!IsTask(npMCI->hTask)) {
DPF0(("Returning DEVICE_NOT_READY from DeviceUpdate\n"));
return MCIERR_DEVICE_NOT_READY;
}
if (npMCI->dwFlags & MCIAVI_UPDATING) {
DPF(("DeviceUpdate has been reentered.\n"));
Assert(0);
return MCIERR_DEVICE_NOT_READY;
}
if (npMCI->dwFlags & MCIAVI_WANTMOVE)
CheckWindowMove(npMCI, TRUE);
//
// see if we are the active movie now.
//
CheckIfActive(npMCI);
/* Setting this flag insures that the background task doesn't
** yield while we're trying to update.
*/
npMCI->dwFlags |= MCIAVI_UPDATING;
//
// 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;
//
// if we are playing/seeking... (ie have a DC)
// then realize the palette now. and set the update flag if we just need to
// paint
//
// 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);
UnprepareDC(npMCI);
PrepareDC(npMCI); // re-prepare
////////
//////// a update can fail when paused so we may have to stop/restart the task
////////
////////if (npMCI->wTaskState == TASKPAUSED)
//////// mmTaskSignal(npMCI->hTask);
npMCI->dwFlags &= ~MCIAVI_UPDATING;
return 0L;
}
//////////////////////////////////////////////////////////////////////
//
// when we get here one of the follow applies
//
// 1. we aren't playing/seeking/...
//
// 2. we need to draw into a memory bitmap (not the screen)
//
//////////////////////////////////////////////////////////////////////
//
// 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 easy 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) {
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 re-draw
//
// 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.
//
lFrameDrawn = npMCI->lFrameDrawn; // save this for compare
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.
*/
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;
goto SlowUpdate;
}
}
PrepareDC(npMCI); // make sure the palette is in there
// worker thread must hold critsec round all drawing
EnterCrit(npMCI);
f = DoStreamUpdate(npMCI, FALSE);
LeaveCrit(npMCI);
UnprepareDC(npMCI); // be sure to put things back....
npMCI->hdc = hdcSave;
if (!f) {
SlowUpdate:
DPF(("DeviceUpdate failed! invalidating lFrameDrawn\n"));
npMCI->lFrameDrawn = (- (LONG) npMCI->wEarlyRecords) - 1;
}
else if (npMCI->lFrameDrawn >= npMCI->lCurrentFrame-1) {
goto Exit;
}
}
DPF(("Update: drawn = %ld, current = %ld.\n", npMCI->lFrameDrawn, npMCI->lCurrentFrame));
//
// stop everything.
//
StopTemporarily(npMCI, &ts);
Assert(npMCI->hdc == NULL);
Assert(npMCI->wTaskState == TASKIDLE);
//
// 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.
*/
npMCI->hdc = hdc;
PrepareDC(npMCI); // make sure the palette is in there
hcurPrev = SetCursor(LoadCursor(NULL, IDC_WAIT));
/* Hide any notification, so it won't get sent... */
hCallback = npMCI->hCallback;
npMCI->hCallback = NULL;
/* Wake the task up, and wait until it quiets down. */
mciaviTaskMessage(npMCI, TASKSTARTING);
mciaviTaskWait(npMCI, TASKIDLE, FALSE);
dwErr = npMCI->dwTaskError;
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);
}
npMCI->hdc = NULL;
if (dwErr == 0)
dwErr = RestartAgain(npMCI,&ts);
Exit:
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 dwErr;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceStatus | Returns the current status
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @rdesc Returns value for MCI's return value
*
***************************************************************************/
UINT PASCAL DeviceMode(NPMCIGRAPHIC npMCI)
{
if (!IsTask(npMCI->hTask)) {
return MCI_MODE_NOT_READY;
}
switch (npMCI->wTaskState) {
case TASKIDLE:
return MCI_MODE_STOP;
case TASKCUEING:
return MCI_MODE_SEEK;
case TASKPLAYING:
return MCI_MODE_PLAY;
case TASKPAUSED:
return MCI_MODE_PAUSE;
case TASKBEINGCREATED:
case TASKINIT:
case TASKCLOSE:
case TASKSTARTING:
case TASKREADINDEX:
default:
DPF(("Unexpected state %d in DeviceMode()\n", npMCI->wTaskState));
return MCI_MODE_NOT_READY;
}
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DevicePosition | Returns the current frame
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data block.
*
* @parm LPLONG | lpl | returns current frame
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DevicePosition(NPMCIGRAPHIC npMCI, LPLONG lpl)
{
LONG NEAR PASCAL WhatFrameIsItTimeFor(NPMCIGRAPHIC npMCI);
LONG l;
l = npMCI->lCurrentFrame - npMCI->dwBufferedVideo;
#if 0
if (npMCI->wTaskState == TASKPLAYING &&
npMCI->wPlaybackAlg != MCIAVI_ALG_INTERLEAVED)
l = WhatFrameIsItTimeFor(npMCI);
#endif
if ((npMCI->wTaskState == TASKCUEING) &&
!(npMCI->dwFlags & MCIAVI_SEEKING) &&
l < npMCI->lRealStart)
l = npMCI->lRealStart;
if (l < 0)
l = 0;
*lpl = l;
return 0L;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetWindow | Set window for display
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm HWND | hwnd | Window to display into.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm Should this only take effect at time of next play?
*
***************************************************************************/
DWORD PASCAL DeviceSetWindow(NPMCIGRAPHIC npMCI, HWND hwnd)
{
DWORD dw = 0L;
TEMPORARYSTATE ts;
/* Stop play before changing windows. */
dw = StopTemporarily(npMCI, &ts);
if (!dw) {
npMCI->hwnd = hwnd;
if (ts.wOldTaskState == TASKIDLE) {
#if 0
DrawBegin(npMCI);
DrawEnd(npMCI);
#else
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
InvalidateRect(hwnd, &npMCI->rcDest, FALSE);
#endif
}
/* Should we update the window here? */
/* Start playing again in the new window */
dw = RestartAgain(npMCI, &ts);
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSpeed | Adjust the playback speed of an AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwNewSpeed | New speed, where 1000 is 'normal' speed.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm If we are currently playing, we stop the device, set our flag,
* and start playing again where we left off. If we were paused,
* we end up stopped. Is this bad?
*
***************************************************************************/
DWORD PASCAL DeviceSetSpeed(NPMCIGRAPHIC npMCI, DWORD dwNewSpeed)
{
DWORD dw = 0L;
TEMPORARYSTATE ts;
/* If new speed is the same as the old speed, return. */
if (dwNewSpeed == npMCI->dwSpeedFactor)
return 0L;
// !!! What if we were cueing or paused?
npMCI->dwSpeedFactor = dwNewSpeed;
if (npMCI->wTaskState == TASKIDLE)
return 0L;
/* We're playing, so we have to adjust the playback rate in
** midstream. If we don't have sound going, this is pretty
** easy. If we do have sound, we either need to speed it up
** or slow it down or stop and start over.
*/
// This code doesn't work, since there are internal variables that
// need to be updated. Therefore, just stop and restart, even if there
// isn't any sound.
#if 0
/* Figure out how fast we're playing.... */
npMCI->dwPlayMicroSecPerFrame = muldiv32(npMCI->dwMicroSecPerFrame, 1000L,
npMCI->dwSpeedFactor);
/* If there's no sound, we're done. */
if ((npMCI->nAudioStreams == 0) ||
!(npMCI->dwFlags & MCIAVI_PLAYAUDIO))
return 0L;
if (npMCI->hWave) {
/* We could potentially try to do a waveOutSetPlaybackRate() here. */
}
#endif
dw = StopTemporarily(npMCI, &ts);
if (!dw) {
dw = RestartAgain(npMCI, &ts);
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceMute | Turn AVI sound on/off.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm BOOL | fMute | TRUE If sound should be turned off, FALSE
* if sound should stay on.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm If we are currently playing, we stop the device, set our flag,
* and start playing again where we left off. If we were paused,
* we end up stopped. Is this bad?
*
***************************************************************************/
DWORD PASCAL DeviceMute(NPMCIGRAPHIC npMCI, BOOL fMute)
{
DWORD dw = 0L;
TEMPORARYSTATE ts;
/* If there's no audio, just return. Should this be an error? */
if (npMCI->nAudioStreams == 0)
return 0L;
/* If the mute state isn't changing, don't do anything. */
if (npMCI->dwFlags & MCIAVI_PLAYAUDIO) {
if (!fMute)
return 0L;
} else {
if (fMute)
return 0L;
}
/* Stop before changing mute */
dw = StopTemporarily(npMCI, &ts);
if (!dw) {
if (fMute)
npMCI->dwFlags &= ~MCIAVI_PLAYAUDIO;
else
npMCI->dwFlags |= MCIAVI_PLAYAUDIO;
dw = RestartAgain(npMCI, &ts);
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetVolume | Set AVI volume.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwVolume | ranges from 0 to 1000.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm If we are currently playing, we try to change the volume of the
* wave out device.
*
***************************************************************************/
DWORD PASCAL DeviceSetVolume(NPMCIGRAPHIC npMCI, DWORD dwVolume)
{
DWORD dw = 0L;
npMCI->dwVolume = dwVolume;
npMCI->dwFlags |= MCIAVI_VOLUMESET;
/* clear flag to emulate volume */;
npMCI->fEmulatingVolume = FALSE;
/* If there's no audio, just return. Should this be an error? */
if (npMCI->nAudioStreams == 0)
return 0L;
dw = DeviceMute(npMCI, dwVolume == 0);
if (npMCI->hWave && dw == 0L) {
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;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceGetVolume | Check the wave output device's current
* volume.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm The volume is left in npMCI->dwVolume
*
* Issue: On devices with global volume control, like an SBPro, how should
* things work?
*
***************************************************************************/
DWORD PASCAL DeviceGetVolume(NPMCIGRAPHIC npMCI)
{
DWORD dw;
DWORD dwVolume;
if (npMCI->hWave) {
// Get the current audio volume....
dw = waveOutMessage(npMCI->hWave, WODM_GETVOLUME,
(DWORD) (DWORD FAR *)&dwVolume, 0);
if (dw == 0) {
returnvolume:
npMCI->dwVolume = MAKELONG((UINT)muldiv32(LOWORD(dwVolume), 500L, 32768L),
(UINT)muldiv32(HIWORD(dwVolume), 500L, 32768L));
}
} 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 realy doubt zero is the current volume, try to work
// with broken cards like the windows sound system.
//
dw = waveOutGetVolume((UINT)WAVE_MAPPER, &dwVolume);
if (dw == 0 && dwVolume != 0)
goto returnvolume;
dw = waveOutGetVolume(0, &dwVolume);
if (dw == 0 && dwVolume != 0)
goto returnvolume;
return MCIERR_NONAPPLICABLE_FUNCTION;
}
return 0;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetAudioStream | Choose which audio stream to use.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm WORD | wStream | ranges from 1 to the number of streams.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceSetAudioStream(NPMCIGRAPHIC npMCI, UINT wAudioStream)
{
DWORD dw = 0L;
TEMPORARYSTATE ts;
int stream;
/* If there's no audio, just return. Should this be an error? */
if (npMCI->nAudioStreams == 0)
return 0;
for (stream = 0; stream < npMCI->streams; stream++) {
if (SH(stream).fccType == streamtypeAUDIO) {
--wAudioStream;
if (wAudioStream == 0)
break;
}
}
if (stream == npMCI->nAudioStream)
return 0;
Assert(stream < npMCI->streams);
/* Stop before changing mute */
dw = StopTemporarily(npMCI, &ts);
if (!dw) {
npMCI->psiAudio = SI(stream);
npMCI->nAudioStream = stream;
dw = RestartAgain(npMCI, &ts);
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetVideoStream | Choose which video stream is the
* "default". Also can enable/disable a stream. this works for both
* video and "other" streams.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm WORD | wStream | ranges from 1 to the number of streams.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceSetVideoStream(NPMCIGRAPHIC npMCI, UINT uStream, BOOL fOn)
{
DWORD dw = 0L;
TEMPORARYSTATE ts;
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)
return MCIERR_OUTOFRANGE;
/* Stop before changing */
dw = StopTemporarily(npMCI, &ts);
if (!dw) {
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");
dw = RestartAgain(npMCI, &ts);
}
return dw;
}
/***************************************************************************
*
***************************************************************************/
static void MapRect(RECT *prc, RECT*prcIn, RECT *prcFrom, RECT *prcTo)
{
if (IsRectEmpty(prcFrom)) {
SetRectEmpty(prc);
}
else {
DPF0(("MapRect: In [%d %d %d %d]\n", *prcIn));
DPF0(("MapRect: From [%d %d %d %d]\n", *prcFrom));
DPF0(("MapRect: To [%d %d %d %d]\n", *prcTo));
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);
DPF0(("MapRect: OUT [%d %d %d %d]\n", *prc));
}
}
/***************************************************************************
*
***************************************************************************/
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
//
DPF0(("SH(%d) rcFrame [%d %d %d %d]\n", i, SH(i).rcFrame));
DPF0(("SI(%d) rcSource [%d %d %d %d]\n", i, SI(i)->rcSource));
DPF0(("SI(%d) rcDest [%d %d %d %d]\n", i, SI(i)->rcDest));
DPF0(("np(%d) rcSource [%d %d %d %d]\n", i, npMCI->rcSource));
DPF0(("np(%d) rcDest [%d %d %d %d]\n", i, npMCI->rcDest ));
DPF0(("\n"));
IntersectRect(&SI(i)->rcSource, &SH(i).rcFrame, &npMCI->rcSource);
DPF0(("SI(%d) rcSource [%d %d %d %d]\n", i, SI(i)->rcSource));
DPF0(("\n"));
//
// now map the stream source rect onto the destination
//
MapRect(&SI(i)->rcDest, &SI(i)->rcSource, &npMCI->rcSource, &npMCI->rcDest);
DPF0(("SI(%d) rcSource [%d %d %d %d]\n", i, SI(i)->rcSource));
DPF0(("SI(%d) rcDest [%d %d %d %d]\n", i, SI(i)->rcDest));
DPF0(("np(%d) rcSource [%d %d %d %d]\n", i, npMCI->rcSource));
DPF0(("np(%d) rcDest [%d %d %d %d]\n", i, npMCI->rcDest ));
DPF0(("\n"));
//
// make the stream source RECT (rcSource) relative to the
// stream rect (rcFrame)
//
OffsetRect(&SI(i)->rcSource,-SH(i).rcFrame.left,-SH(i).rcFrame.top);
DPF0(("SI(%d) rcSource [%d %d %d %d]\n", i, SI(i)->rcSource));
DPF0(("SI(%d) rcDest [%d %d %d %d]\n", i, SI(i)->rcDest));
DPF0(("np(%d) rcSource [%d %d %d %d]\n", i, npMCI->rcSource));
DPF0(("np(%d) rcDest [%d %d %d %d]\n", i, npMCI->rcDest ));
DPF0(("\n"));
}
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DevicePut | Change source or destination rectangle
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm LPRECT | lprc | Pointer to new rectangle to use.
*
* @parm DWORD | dwFlags | Flags: will be either MCI_DGV_PUT_DESTINATION
* or MCI_DGV_PUT_SOURCE.
*
* @rdesc 0 means OK, otherwise mci error
*
* @comm
* If we end up using a custom stretch buffer, it would go here.
*
***************************************************************************/
DWORD FAR PASCAL DevicePut(NPMCIGRAPHIC npMCI, LPRECT lprc, DWORD dwFlags)
{
RECT rc;
PRECT prcPut;
DWORD dw = 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 rectange???
//
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) {
DPF2(("DevicePut: invalid rectangle [%d, %d, %d, %d]\n", *lprc));
return MCIERR_OUTOFRANGE;
}
/* make sure the rect changed */
if (EqualRect(prcPut,lprc))
return 0L;
InvalidateRect(npMCI->hwnd, &npMCI->rcDest, TRUE);
rc = *prcPut; /* save it */
*prcPut = *lprc; /* change it */
InvalidateRect(npMCI->hwnd, &npMCI->rcDest, FALSE);
/* has both the dest and source been set? */
if (IsRectEmpty(&npMCI->rcDest) || IsRectEmpty(&npMCI->rcSource))
return 0L;
MapStreamRects(npMCI);
StreamInvalidate(npMCI, NULL); // invalidate the world
if (npMCI->wTaskState <= TASKIDLE) {
DPF2(("DevicePut: Idle, force DrawBegin on update\n"));
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
}
else {
BOOL fRestart;
//
// we dont need to start/stop just begin again.
//
DPF2(("DevicePut: Calling DrawBegin()\n"));
if (!DrawBegin(npMCI, &fRestart)) {
return npMCI->dwTaskError;
}
if (!DoStreamUpdate(npMCI, FALSE)) {
DPF(("Put: Failed update, forcing restart....\n"));
npMCI->lFrameDrawn = (- (LONG) npMCI->wEarlyRecords) - 1;
fRestart = TRUE;
}
if (fRestart) {
TEMPORARYSTATE ts;
DPF2(("DevicePut: Stopping temporarily()\n"));
// !!! Set a flag here to prevent any more drawing
npMCI->fNoDrawing = TRUE;
if (StopTemporarily(npMCI, &ts) != 0)
return npMCI->dwTaskError;
// !!! We used to call InitDecompress here...
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
RestartAgain(npMCI, &ts);
dw = npMCI->dwTaskError;
}
}
return dw;
}
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceSetPalette | Changes the override palette.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm HPALETTE | hpal | New palette to use.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD FAR PASCAL DeviceSetPalette(NPMCIGRAPHIC npMCI, HPALETTE hpal)
{
//
// You might think it is a good idea to allow the app to change the
// the palette while playing; think again. This will break
// MagicSchoolBus, and cause us to get into a infinite palette fight.
//
#if 0
DWORD dw = 0L;
TEMPORARYSTATE ts;
dw = StopTemporarily(npMCI, &ts);
// Remember this for later.
npMCI->hpal = hpal;
if (!dw) {
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
dw = RestartAgain(npMCI, &ts);
}
return dw;
#else
if (npMCI->hpal != hpal) {
// Remember this for later.
npMCI->hpal = hpal;
// This won't happen until we restart the movie, so effectively, this
// request for a palette change will be ignored for now.
npMCI->dwFlags |= MCIAVI_NEEDDRAWBEGIN;
InvalidateRect(npMCI->hwnd, NULL, TRUE);
}
return 0;
#endif
}
#ifndef LOADACTUALLYWORKS
/***************************************************************************
*
* @doc INTERNAL MCIAVI
*
* @api DWORD | DeviceLoad | Load a new AVI movie.
*
* @parm NPMCIGRAPHIC | npMCI | Pointer to instance data.
*
* @parm DWORD | dwFlags | MCI flags from command.
*
* @rdesc 0 means OK, otherwise mci error
*
***************************************************************************/
DWORD PASCAL DeviceLoad(NPMCIGRAPHIC npMCI)
{
DWORD dw = 0L;
if (!IsTask(npMCI->hTask))
return MCIERR_DEVICE_NOT_READY;
dw = DeviceStop(npMCI, MCI_WAIT);
// Kill the current file and open a new file...
mciaviTaskMessage(npMCI, TASKRELOAD);
dw = npMCI->dwTaskError;
return dw;
}
#endif