Chapter 20

A Plug-In with MCIWnd


CONTENTS


Introduction

Many media types can be easily supported in your plug-in through the use of MCIWnd. MCIWnd is a window class for controlling multimedia devices. This window class is composed of macros, messages, and functions that provide a simple way for an application to add multimedia capabilities.

When you use MCIWnd, you don't really need to subclass the plug-in window. The MCIWndCreate function takes the plug-in window handle as a parameter and creates another window for media playback. Controls for playback are provided by MCIWnd, avoiding the need for subclassing.

The NPMCIWND plug-in sample supports AVI video, WAV audio, and MIDI playback with the default controls provided by MCIWnd. Because the plug-in window is not subclassed, all playback controls are handled by MCIWnd.

Where to Find the Files

All the necessary files for building NPMCIWND.DLL are located on the CD-ROM. These files are in the \CODE\MCIWND, \CODE\INC, and \CODE\COMMON directories. You will need the files shown in the following lists.

For the directory \CODE\MCIWND, you need these files:

For the directory \CODE\INC, you need these files:

For the directory \CODE\COMMON, you need these files:

The Design of the MCIWnd Plug-In

The MCIWnd plug-in's design is quite simple. Aside from common code, only two small source files are required for this plug-in: npmciwnd.cpp and npshell.cpp. The file npwindow.cpp, which is used in many other sample plug-ins in this book, is not required because this plug-in does not subclass Navigator's plug-in window.

This plug-in is file-based. That is, it sets stype to NP_ASFILE during the NPP_NewStream method. NPP_WriteReady and NPP_Write return the standard values for a file-based plug-in, and all processing is done during NPP_StreamAsFile when the file is completely in cache.

As in the CPU Plug-in Sample, the MCIWnd plug-in's class CMCIWnd has a method called GotFileName. This is called during NPP_StreamAsFile to handle processing the file in Navigator's cache. During this method, a new MCIWnd window is created with a call to MCIWndCreate. The sample plug-in currently supports AVI, WAV, and MIDI. A different window and control set are created depending on the type of the file. This sample can support any MCI device by just adding the appropriate MIME type to its resource.

Figure 20.1 shows the MCIWnd plug-in's components. Notice that only one class is used: CMCIWnd. Also note that the MCI drivers communicate directly with the plug-in window.

Figure 20.1 : The components of the MCIWnd plug-in.

Multiple MIME Types

The MCIWnd sample plug-in supports three MIME types. These types are denoted by "MIMEType" in the plug-in's resource file npmciwnd.rc2. Vertical bars are used to separate each type:

VALUE "MIMEType", "video/x-msvideo|audio/x-wav|audio/x-midi\0"

Each MIME type has an appropriate file extension. In this case, they are AVI, WAV, and MID:

VALUE "FileExtents", "avi|wav|mid\0"

Tip
Remember that if the extension is not registered on the HTTP server to an appropriate MIME type, Navigator does not get the Content-type: designator in the HTTP header. This means your plug-in will load with a local file, but not with an HTTP server file.

Vertical bars also separate the names for the Files of type: section of the file open dialog. With this plug-in are Video, Audio, and MIDI:

VALUE "FileOpenName", "Video (*.avi)|Audio (*.wav)|MIDI (*.mid)\0"

Plug-In Entry Points

As with any plug-in, this sample must handle all of the NPP_ entry points. By handling, you can just return the proper value (if any). The MCIWnd sample uses npshell.cpp to support the standard plug-in API entry points.

Of these, NPP_Initialize, NPP_Shutdown, and NPP_Print simply return without executing any code. NPP_WriteReady and NPP_Write return the proper values to allow for a file-based plug-in.

NPP_New

The NPP_New method creates a CMCIWnd object and attaches a pointer referencing this object to Navigator's instance structure. In addition, the mode is saved. Plug-in instance data is stored in the CMCIWnd object. The parameters argc, argn, and argv, which hold the EMBED tag line attributes, are not parsed in this example.

//
// NPP_New - Create a new plug-in instance.
//
NPError NP_LOADDS NPP_New (NPMIMEType pluginType,
    NPP pInstance,
    uint16 mode,
    int16 argc,
    char* argn[],
    char* argv[],
    NPSavedData* saved)
{  
    if (pInstance == NULL)
        return NPERR_INVALID_INSTANCE_ERROR;

    // Create a new CMCIWnd object

    CMCIWnd* pMCIWnd = new CMCIWnd (pInstance);

    // Attach it to the instance structure

    pInstance->pdata = pMCIWnd;

    // Save our plug-in's mode

    pMCIWnd->mode = mode;

    return NPERR_NO_ERROR;
}

NPP_Destroy

The plug-in instance is destroyed by a call to NPP_Destroy. During this method, the CMCIWnd object is simply deleted. The destructor of CMCIWnd makes a call to MCIWndDestroy, which closes the MCI device. The instance data pointer pInstance->pdata is set to NULL as a precaution.

//
// NPP_Destroy - Destroy our plug-in instance.
//
NPError NP_LOADDS NPP_Destroy (NPP pInstance, NPSavedData** save)
{
    CMCIWnd* pMCIWnd = (CMCIWnd *)pInstance->pdata;

    if (pMCIWnd)
    {
        delete pMCIWnd;
        pInstance->pdata = NULL;
    }
    return NPERR_NO_ERROR;
}

NPP_SetWindow

Because there is no subclassing in this plug-in, NPP_SetWindow is quite different from other plug-ins. After the standard checks for the NULL parameter and spurious entries, the Navigator-provided plug-in window handle is saved in the CMCIWnd object. If the handle was already saved, the method CMCIWnd::UpdateWindow is called to redraw the MCIWnd and controls.

//
// NPP_SetWindow - Just get the window handle.
//
NPError NP_LOADDS NPP_SetWindow (NPP pInstance, NPWindow* window)
{
    if (!window)
        return NPERR_GENERIC_ERROR;

    if (!pInstance)
        return NPERR_INVALID_INSTANCE_ERROR;

    CMCIWnd* pMCIWnd = (CMCIWnd *)pInstance->pdata;

    if (!pMCIWnd)
        return NPERR_GENERIC_ERROR;

    // Spurious entry - just return

    if (!window->window && !pMCIWnd->hNavigatorWnd)
        return NPERR_NO_ERROR;

    // Get Navigator's window handle

    if (window->window)
    {
        if (!pMCIWnd->hNavigatorWnd)
            pMCIWnd->hNavigatorWnd = (HWND)window->window;
        else
        {
            pMCIWnd->UpdateWindow (pMCIWnd->hNavigatorWnd);
        }
    }
    return NPERR_NO_ERROR;
}

NPP_NewStream

When NPP_NewStream is called, this plug-in sets *stype to NP_ASFILE. This sets up for a file-based plug-in and a future call to NPP_StreamAsFile. The CMCIWnd::Open method is called to notify the object of a new stream.

//
// NPP_NewStream - A new stream was created.
//
NPError NP_LOADDS NPP_NewStream(NPP pInstance,
    NPMIMEType type,
    NPStream* pStream,
    NPBool seekable,
    uint16* stype)
{
    if(!pInstance)
        return NPERR_INVALID_INSTANCE_ERROR;

    CMCIWnd* pMCIWnd = (CMCIWnd *)pInstance->pdata;

    *stype = NP_ASFILE;

    if (pMCIWnd)
        pMCIWnd->Open (pStream);

    return NPERR_NO_ERROR;
}

NPP_WriteReady and NPP_Write

Like most file-based plug-ins, NPP_WriteReady and NPP_Write are not really used. A large value is returned during NPP_WriteReady to tell Navigator that the plug-in can handle any buffer size. Calls to NPP_Write just return the length.

//
// NPP_WriteReady - Returns amount of data we can handle for the next NPP_Write
//
int32 NP_LOADDS    NPP_WriteReady (NPP pInstance, NPStream *stream)
{
    return 0x0FFFFFFF;
}

//
// NPP_Write
//
int32 NP_LOADDS NPP_Write (NPP pInstance, NPStream *stream,
    int32 offset, int32 len, void *buffer)
{  
    return len;
}

Note
To improve performance, use NPN_Version and check whether the minor version is greater than or equal to 8. If so, Navigator 3.0 is running (Atlas) and you can use NP_ASFILEONLY instead of NP_ASFILE in the preceding NPP_NewStream example. By using this new type, you avoid streaming the file if it is already on a local drive or network. This dramatically improves performance for very large multimedia data files often used with MCIWnd. NPP_WriteReady and NPP_Write are not called with NP_ASFILEONLY. See Chapter 10, "Stream Creation and Destruction," for more information on NP_ASFILEONLY.

NPP_StreamAsFile

When the file is ready for the plug-in, NPP_StreamAsFile is called with the filename. This file might be in either Navigator cache or another directory, depending on the origination. The method CMCIWnd::GotFileName is called to start processing the file.

//
// NPP_StreamAsFile
//
void NP_LOADDS NPP_StreamAsFile (NPP pInstance,
    NPStream* stream,
    const char* fname)
{
    CMCIWnd* pMCIWnd = (CMCIWnd *)pInstance->pdata;
    
     if (pMCIWnd)  
        pMCIWnd->GotFileName (fname);
}

NPP_DestroyStream

Although this plug-in doesn't need to know when the stream is destroyed, a call is made into a stub method, CMCIWnd::EndOfStream, for any future implementation.

//
// NPP_DestroyStream
//
NPError NP_LOADDS NPP_DestroyStream (NPP pInstance,
    NPStream *stream,
    NPError reason)
{
     CMCIWnd* pMCIWnd = (CMCIWnd *)pInstance->pdata;
    
     if (pMCIWnd)  
        pMCIWnd->EndOfStream ();
          
    return NPERR_NO_ERROR;
}

The CMCIWnd Class

The MCIWnd plug-in has a very simple implementation of its main class, CMCIWnd. This class provides for creating, closing, and updating the MCIWnd window. Here are the methods of this class:

CMCIWnd::CMCIWnd
CMCIWnd::~CMCIWnd
CMCIWnd::Open
CMCIWnd::GotFileName
CMCIWnd::UpdateWindow
CMCIWnd::EndOfStream

Construction and Destruction

The CMCIWnd constructor saves the plug-in instance (which is not used) and zeroes out the members hMCIWnd and hNavigatorWnd. The destructor closes the MCIWnd window with a call to MCIWndDestroy. MCIWndDestroy corresponds to a WM_CLOSE message with wParam and lParam set to zero.

/
// Constructor
//
CMCIWnd::CMCIWnd (NPP pInstance)
{
    this->pInstance = pInstance;

    hMCIWnd = hNavigatorWnd = NULL;

}

//
// Destructor
//
CMCIWnd::~CMCIWnd()
{
    if (hMCIWnd)
        MCIWndDestroy (hMCIWnd);
}

Open

CMCIWnd::Open saves the stream instance pointer, NPStream*. Other than that, it does nothing. Remember that by saving a pointer to the stream, you can look at things such as the URL at any time. Be careful not to reference the stream after it has been destroyed!

//
// Open
//
void CMCIWnd::Open (NPStream* pStream)
{
    this->pStream = pStream;
}

GotFileName

When a file is ready, the member GotFileName is called. During this call, a new MCIWnd window is created with MCIWndCreate. After creation, there is a small workaround to force a video window to redraw. If MCIWndGetDevice shows a device type of "AVIVideo", the video is stepped forward one frame.

//
// GotFileName
//
void CMCIWnd::GotFileName (const char* fname)
{
    // Open the MCIWnd file

    if (hMCIWnd = MCIWndCreate (hNavigatorWnd, 0, NULL, fname))
    {
        char DeviceName[256];

        if (!MCIWndGetDevice (hMCIWnd, DeviceName, sizeof(DeviceName)))
            if (!strcmp (DeviceName, "AVIVideo"))
                MCIWndStep (hMCIWnd, 1);  // Step because of a bug in MCIWnd
    }
}

UpdateWindow

A call to the UpdateWindow method originates from the NPP_SetWindow API. NPP_SetWindow is called when the window needs to be redrawn. During the Updatewindow method, a call to SetWindowPos is made, which forces the Navigator-created plug-in window to the bottom of the z-order. This allows the MCIWnd created window to remain on top.

Additionally, the plug-in window's rectangle is invalidated and updated.

//
// UpdateWindow
//
void CMCIWnd::UpdateWindow (HWND hWnd)
{
    RECT rect;

    SetWindowPos (hWnd, HWND_BOTTOM, 0,0,0,0, SWP_NOMOVE | SWP_NOSIZE);

    GetWindowRect (hWnd, &rect);
    InvalidateRect (hWnd, &rect, TRUE);

    ::UpdateWindow (hWnd);
}

EndOfStream

CMciWnd::EndOfStream does nothing and is provided for any future code.

//
// EndOfStream
//
void CMCIWnd::EndOfStream ()
{
}

A Bit about MCIWnd

MCIWnd makes using Windows multimedia devices a simple task. Full motion video can be displayed, complete with controls, by a quick call to MCIWndCreate. Other than that, you only need to call MCIWndDestroy to close the device.

Although it is simple to use, MCIWnd is quite flexible. The following lists cover all of the functions, messages, and macros that can be used with MCIWnd.

Window Management
MCIWndChangeStyles
MCIWndCreate
MCIWndGetStyles
MCIWndRegisterClass
File and Device Management
MCIWndClose
MCIWndDestroy
MCIWndEject
MCIWndNew
MCIWndOpen
MCIWndOpenDialog
MCIWndSave
MCIWndSaveDialog
Playback Options
MCIWndGetRepeat
MCIWndPlay
MCIWndPlayFrom
MCIWndPlayFromTo
MCIWndPlayReverse
MCIWndPlayTo
MCIWndSetRepeat
Recording
MCIWndRecord
Positioning
MCIWndEnd
MCIWndGetEnd
MCIWndGetLength
MCIWndGetPosition
MCIWndGetPositionString
MCIWndGetStart
MCIWndHome
MCIWndSeek
MCIWndStep
Pause and Resume Playback
MCIWndGetRepeat
MCIWndPlay
MCIWndPlayFrom
MCIWndPlayFromTo
MCIWndPlayReverse
MCIWndPlayTo
MCIWndSetRepeat
Performance Tuning
MCIWndGetSpeed
MCIWndGetVolume
MCIWndGetZoom
MCIWndSetSpeed
MCIWndSetVolume
MCIWndSetZoom
Image and Palette Adjustments
MCIWndGetDest
MCIWndGetPalette
MCIWndGetSource
MCIWndPutDest
MCIWndPutSource
MCIWndRealize
MCIWndSetPalette
Last Error Retrieval
MCIWndGetError
Event and Error Notification Messages
MCIWNDM_NOTIFYERROR
MCIWNDM_NOTIFYMEDIA
MCIWNDM_NOTIFYMODE
MCIWNDM_NOTIFYPOS
MCIWNDM_NOTIFYSIZE
Time Formats
MCIWndGetTimeFormat
MCIWndSetTimeFormat
MCIWndUseFrames
MCIWndUseTime
MCIWndValidateMedia
Status Updates
MCIWndGetActiveTimer
MCIWndGetInactiveTimer
MCIWndSetActiveTimer
MCIWndSetInactiveTimer
MCIWndSetTimers
Device Capabilities
MCIWndCanConfig
MCIWndCanEject
MCIWndCanPlay
MCIWndCanRecord
MCIWndCanSave
MCIWndCanWindow
MCI Device Settings
MCIWndGetAlias
MCIWndGetDevice
MCIWndGetDeviceID
MCIWndGetFileName
MCIWndGetMode
MCI Command-String Interface
MCIWndReturnString
MCIWndSendString

It looks like Microsoft has everything but the kitchen sink! Feel free to explore MCIWnd and its use in plug-ins. Most MCIWnd calls are very easy to make. For example, this call steps video forward one frame:

MCIWndStep (hMCIWnd, 1)

You can also create an MCIWnd window without any default controls and make your own controls using the plug-in subclassed window technique discussed in Chapter 17, "A Streaming Audio Sample."

Running the Plug-In

The MCIWnd plug-in is fun to run and enhance. Sliders are provided for seeking, which allows users to randomly scan through media. The video window has controls for scaling, volume, and speed. You can even copy an image to the clipboard or send a string to the MCI device.

A good example of this plug-in's capabilities is shown by embedding all three media types-AVI, WAV, and MIDI-into a single Web page. The following HTML code does this:

<html>
<body>

<Body Background="blue_pap.gif">

<h1 align=center>MCIWnd Based Plug-in</h1>

<h2 align=center>Video</h2>

<center><p><embed SRC=sample.avi WIDTH=322 HEIGHT=268></p></center>

<h2 align=center>Wave Audio</h2>

<center><p><embed SRC=outofb.wav WIDTH=300 HEIGHT=28></p></center>

<h2 align=center>MIDI</h2>

<center><p><embed SRC=canyon.mid WIDTH=300 HEIGHT=28></p></center>

</body>
</html>

The result of this code should look something like what you see in Figure 20.2.

Figure 20.2 : The MCIWnd plug-in sample running video, audio, and MIDI.

Conclusion

Use MCIWnd to make a full-featured MCI device plug-in capable of supporting multiple data types. The MCIWnd window class is flexible, yet easy to use. You don't have to subclass the Navigator-created plug-in window if you use MCIWnd.

Because the MCIWnd sample plug-in has no use for MFC, it is not linked in. This results in a very small plug-in with huge functionality! Compiled as a retail build (without debugging information), this plug-in is less than 25KB. Not a bad deal for a user, considering it can be downloaded in less than 15 seconds.

What's Next?

Plug-ins can be hidden using the HIDDEN parameter in a plug-in's HTML embed line. The next chapter explores a background MIDI player implemented as a hidden plug-in.