NPWAVE is a Windows 95 plug-in for Netscape Navigator 2.x and above. This plug-in allows audio to play as it is streamed, rather than waiting for the complete file to download. Audio buffers are passed on to the Windows audio driver as they are received. Audio can be started and paused with buttons on the plug-in window.
The plug-in is made up of three classes: CWave, CPluginWindow, and CBuffer. CWave manages the plug-in and its use of Windows audio resources. CPluginWindow is a small class derived from CWnd that is used to subclass the plug-in window. CBuffer is a generic circular buffer management class. It is explained further in the next chapter.
Audio buffers are passed to the plug-in with the help of a worker thread. This thread interacts with CWave and CBuffer to provide the audio driver with data. The audio driver is opened with a callback routine specified. The callback informs the plug-in when a buffer is exhausted so that the buffer can be recycled.
The plug-in was built with Microsoft Visual C++ 4.0. Other compilers should work as well, such as Borland or Visual C++ 2.x.
All the necessary files for building NPWAVE.DLL are located on the CD-ROM. These files are in the \CODE\WAVE, \CODE\INC, and \CODE\COMMON directories. You need the following files:
\code\wave:
npshell.cpp - Plug-in entry point methods.
npwave.cpp, npwave.h - The audio class CWave
npbuffer.cpp, npbuffer.h - The buffer class CBuffer
npwindow.cpp, npwindow.h - The window class CPluginWindow
npwave.def - DLL module definition file
npwave.rc - Resources
npwave.mak - Makefile for Microsoft Visual C++
\code\inc
npapi.h - Plug-in API prototypes and associated structures
npupp.h - Plug-in API prototypes
stdafx.h - Standard include files
\code\common
npwin.cpp - DLL entry point to plug-in API mapping
stdafx.cpp - Includes standard include files
The main objective of NPWAVE is to play audio while streaming data from the HTTP server. Audio buffers are sent to the audio driver as they come in from the server. The user does not have to wait for the file to download before hearing audio.
This example is intended as a learning tool and, as such, does not provide for audio compression, which is needed for many applications. Audio compression can easily be added using Microsoft's Audio Compression Manager (ACM).
The current version of NPWAVE supports standard .WAV file formats. Without compression, using 11,025 samples per second audio data, a bandwidth of 88,200 bps is required to use NPWAVE in real time. Of course, you can use the plug-in on your local hard drive or a LAN to easily make this bandwidth requirement.
As you can see in Figure 17.1, the audio plug-in consists of several main components that fit together to create a working audio streamer:
Figure 17.1 : The components of the audio plug in.
As you might remember from Chapter 8, "The Plug-In Application Programming Interface (API)," Navigator uses a file called npwin.cpp to provide access to APIs both within your plug-in and within the Navigator. Methods that reside in the plug-in are prefaced with NPP_, and methods in the Navigator are prefaced with NPN_. The audio plug-in uses only NPP_ type methods. No calls are made to the Navigator from the plug-in.
A file called npshell.cpp contains these methods. Some methods, such as NPP_Print and NPP_StreamAsFile, are not used but stubs are still provided for the Navigator to call. If you don't provide these stubs, Navigator traps.
Creating a New Instance
The first two methods called in the audio plug-in are NPP_Initialize and NPP_New. NPP_Initialize does nothing and is provided as a stub method. NPP_New enables you to create a plug-in instance and save it for later reference.
Rather than having a separate instance structure, NPWAVE uses the CWave class to hold its instance data. It's simple to allocate a CWave object with the new operator and attach it to Netscape's instance structure:
// Create a new CWave object
CWave* pWave = new CWave (pInstance);
// Attach it to the instance structure
pInstance->pdata = pWave;
After this, if the plug-in was started in embedded mode, the HTML line is checked for an autostart parameter. If autostart is set to true, the audio begins playing as soon as the plug-in is loaded. Otherwise, the user must click on Play to start the audio. You can add as many HTML arguments as you like. For example, a loop=true named pair could make the plug-in loop the audio clip.
After a new instance is created, NPP_NewStream is called to inform the plug-in of a new audio stream. During this method, after retrieving the plug-in's instance data, the Open method of CWave is called to inform the CWave class of a new stream.
NPP_SetWindow is called after the new stream is created. During this API, the plug-in creates a CWnd child called CPluginWindow and attaches a pointer referencing this object to the instance object, CWave:
// Get instance data
CWave* pWave = (CWave *)pInstance->pdata;
.
.
.
// Create our plug-in's window class
pWave->pWindow = new CPluginWindow (pWave);
Notice that a pointer to CWave is passed to CPluginWindow during creation. This pointer allows easy access to CWave from the window class.
When you create a child of CWnd, it is important to remember that just a window object-not actually a window-is created. Because of this, the plug-in can attach this newly created class to the Navigator's window using window subclassing. Navigator has already created the plug-in window before the call to NPP_SetWindow. You can override Navigator's window procedure by subclassing it with CPluginWindow:
// Subclass the plug-in window
pWave->pWindow->SubclassWindow ((HWND)window->window);
When the stream starts, data is written to the plug-in with calls to both NPP_WriteReady and NPP_Write. As you might remember, NPP_WriteReady asks the plug-in how much data it is willing to receive, and NPP_Write sends the actual data. The audio plug-in hands off these APIs to CWave's methods, IsBufferAvailable and FillBuffer, respectively.
Stream completion, usually indicating total consumption of a file by the plug-in, is indicated by a call to NPP_DestroyStream. This method calls the CWave method EndOfStream.
Calls to NPP_Destroy and NPP_Shutdown tell the plug-in to clean up all system resources. The user has caused the plug-in to unload, perhaps moving on to more exciting things. Such is the life of a Web surfer.
This plug-in uses NPP_Destroy to unsubclass the window and delete it. The Close method of CWave is also called, after which CWave is deleted:
if (pWave->pWindow)
{
// Unsubclass the window, clean it up and delete it.
pWave->pWindow->UnsubclassWindow ();
pWave->pWindow->CleanupWindow();
delete pWave->pWindow;
}
// Close the audio driver and delete the audio object.
pWave->Close ();
delete pWave;
The real guts of NPWAVE are in the CWave class. CWave is located in the file npwave.cpp. This class handles audio buffer management, the audio driver, and a thread. Most CWave methods are called from npshell.cpp. Methods called from npshell.cpp are as follows:
CWave::Cwave
CWave::~Cwave
CWave::Open
CWave::IsBufferAvailable
CWave::FillBuffer
CWave::EndOfStream
CWave::Close
CWave is created with the new operator in the file npshell.cpp. CWave's constructor initializes variables and gets a pointer to the plug-in's instance, NPP. Don't look for where this instance is used because it is not used in this case. Because many Navigator methods require the NPP instance structure, it's handy to have around just in case.
Next, after construction, a call is made to CWave::Open. This method is called from the NPP_NewStream API. It initializes a critical section, enters it, and creates a circular buffer with the CBuffer class:
// Protect data with critical sections
InitializeCriticalSection (&CritSect);
// Create a new CBuffer and have it make a circular buffer.
EnterCriticalSection (&CritSect);
pCBuffer = new CBuffer;
pCBuffer->AllocateCircularBuffer (MAX_BUFFER_SIZE, MAX_BUFFERS);
LeaveCriticalSection (&CritSect);
A call to CWave::Open requires
a stream pointer of NPStream*.
Like the instance pointer, this is never used but is nice to have
within the class.
| Critical Sections in Windows 95 |
| Windows 95 introduces multithreading to the Windows programming world. The NPWAVE plug-in uses an additional thread to feed the audio driver buffers of audio data. This worker thread and the main thread from Netscape both need to access
the CWave object. MFC objects are not thread safe at object level, but only at class level.
CWave is a class that can be used to create a CWave object. When multiple threads access a single CWave object, you must provide synchronization to this object and protect its data members. Throughout the NPWAVE plug-in, this is provided with the use of critical sections. Critical sections are an efficient mechanism of mutual-exclusion between threads. To use critical sections, you must first create a critical section object with the Windows API InitializeCriticalSection. After it is created, a thread uses EnterCriticalSection to request ownership and LeaveCriticalSection to release ownership. If the thread is not granted ownership, it waits indefinitely until it is granted ownership. Call DeleteCriticalSection when you are done with it to release system resources. You can also use the MFC CCriticalSection object, which works in a very similar manner. Consult the MFC Class Library Reference for further details. |
NPP_WriteReady and NPP_Write call the CWave methods, IsBufferAvailable and FillBuffer, respectively. IsBufferAvailble, as the name implies, checks for an available empty buffer by calling the CBuffer object. CBuffer is explained in detail in the next chapter. Critical sections are used to protect CBuffer:
int32 CWave::IsBufferAvailable (void)
{
ulBufSize = 0;
EnterCriticalSection (&CritSect);
if (pCBuffer->AnyEmptyBuffers ())
ulBufSize = MAX_BUFFER_SIZE - STRUCT_SIZE;
else
ulBufSize = 0;
LeaveCriticalSection (&CritSect);
return ulBufSize;
}
The available buffer size is returned. Each buffer has a header whose size is subtracted from the total buffer size.
Next called is FillBuffer with the actual audio data. This method's job is to get the data from the Navigator and start the play for autostart-type plug-ins. First, it gets a buffer and sets the length. If this is the first buffer, the .WAV information is extracted:
// Get an empty buffer
BUFFER* pBuff = (BUFFER*)pCBuffer->GetNextEmptyBuffer();
if (pBuff)
{
// Got a buffer, now copy the audio data.
pBuff->pvData = &pBuff->ulData;
memcpy (pBuff->pvData, pvNetscapeData, len);
// Always set the length, it could be any value under
// your buffer size.
pBuff->ulDataSize = len;
if (++ulBufferFullCount == 1) // First buffer, parse wav header
{
pBuff->ulFlags |= BUFFER_FIRST;
if (!ParseWaveHeader (pBuff))
{
// Bad format, bail out...
bRc = FALSE;
goto ExitFillBuffer;
}
}
Next, an autostart condition is handled. If there are enough buffers to start, the thread is started with a call to PlayTheAudio:
if (bAutoStart)
{
// Start it if we have enough buffers.
if (ulBufferFullCount >= ulLowWaterMark &&
usState != AUDIO_PLAYING && usState != AUDIO_PAUSED)
{
BOOL rc = PlayTheAudio ();
}
}
Finally, a data underrun is handled:
if (bDataUnderrun)
{
// The audio data can't keep up with the device. Get enough
// buffers to clear our low water mark and restart the thread.
if (ulBufferFullCount > ulLowWaterMark)
{
ulBufferFullCount = 1; // Start over
pBuff->ulFlags |= BUFFER_FIRST;
}
if (ulBufferFullCount == ulLowWaterMark)
{
bDataUnderrun = FALSE;
BOOL fSuccess = SetEvent (hEvent);
}
}
An underrun occurs when the NPWAVE plug-in is starved for data. The bDataUnderrun flag is set by the driver callback routine when it can't get any more buffers. If an underrun occurs and the buffer count is over the minimum amount of buffers for starting (ulLowWaterMark), the thread is restarted with a call to SetEvent.
EndOfStream is called from NPP_DestroyStream. This method is in charge of marking the last audio buffer and starting any autostart streams.
Mark the last buffer:
EnterCriticalSection (&CritSect);
// Mark the last full buffer. Once the audio driver proc sees the
// last buffer it will signal the thread to close the audio driver
// and exit gracefully.
BUFFER* pBuff = (BUFFER*)pCBuffer->GetLastFullBuffer();
if (pBuff)
pBuff->ulFlags |= BUFFER_LAST;
LeaveCriticalSection (&CritSect);
Remember that a call to NPP_DestroyStream does not mean to stop playing; it just means that the data is done. In light of this, the last audio buffer is flagged. When the thread sees this flag, it closes the audio driver and exits.
Again, autostart is handled:
// In some cases we will not clear our low water marker for an auto start
// by the end of stream. Play what we have (if anything).
if (bAutoStart)
{
if (usState == AUDIO_WAITING_FOR_START && ulBufferFullCount > 0)
{
BOOL rc = PlayTheAudio ();
}
}
Shutting down a multithreaded plug-in is an exercise in caution. Exit processing bugs have caused many a project to run late. Always make sure you think of all possibilities for deadlocks on exit. A thread context will switch on order of milliseconds. Beware of thoughts such as "That would never happen," or "That window of opportunity is too small to deadlock." It might not happen to you, but someone could be in for a real treat.
The Close method is called from NPP_Destroy, which instructs the plug-in to free its instance and all associated resources:
BOOL CWave::Close (void)
{
// Don't try to grab our critical section in this routine.
// waveOutReset may cause buffers to be returned with WOM_DONE
// messages which get a critical section in the driver proc.
hEventCloseSync = CreateEvent (NULL, FALSE, FALSE, NULL);
if (pThread)
{
bKillThread = TRUE;
BOOL fSuccess = SetEvent (hEvent);
DWORD dwRc = WaitForSingleObject (hEventCloseSync, INFINITE);
}
if (hWave)
{
// We are going to wait for the WOM_CLOSE message in the driver callback
// proc. Otherwise we may try to access an invalid CWave object while
// processing WOM_CLOSE. Although this may not be necessary, it is a good
// safety measure.
waveOutReset (hWave); // Stop any current play (returns all buffers)
waveOutClose (hWave); // Close the audio device.
DWORD dwRc = WaitForSingleObject (hEventCloseSync, INFINITE);
}
CloseHandle (hEventCloseSync);
// It's safe now to free the audio buffers. The audio driver
// has returned all buffers.
pCBuffer->FreeCircularBuffer();
delete pCBuffer;
return TRUE;
}
During this method, a synchronization event is created for two purposes. First, it allows the worker thread to exit gracefully while the main thread waits. If the main thread did not wait, the whole object could be deleted before the worker thread was switched back in, causing a trap. Second, the event allows all audio buffers to be returned while the main thread waits. Windows 95 does the audio driver synchronization for you, but you might as well be safe.
Finally, at the end of the method, the plug-in can safely free all audio buffers without fear of trapping.
The worker thread routine, although not part of the CWave class, is located in the same file and has direct access to the object. The thread calls the following methods:
CBuffer::GetNextFullBuffer
CWave::SendDataToDriver
An infinite while loop keeps the thread alive throughout its mission. The worker thread first waits for another thread to wake it:
// Wait for someone to start us...
DWORD dwRc = WaitForSingleObject (pWav->hEvent, INFINITE);
The thread starting the worker is either the main Navigator calling thread or the audio driver callback thread. After waking, the kill flag is checked, and if it is set, the thread exits:
if (pWav->bKillThread)
{
// Main thread wants us dead
BOOL fSuccess = SetEvent (pWav->hEventCloseSync);
AfxEndThread (0);
}
Notice the call to SetEvent to signal the main thread that the kill is acknowledged.
The next order of business is to check whether the audio driver has played and returned the last audio buffer. If so, this thread is done and exits:
EnterCriticalSection (&pWav->CritSect);
if (pWav->bPlayedLastBuffer)
{
// The last buffer was played. We are done.
// Close the audio device and exit this thread.
waveOutClose (pWav->hWave);
pWav->hWave = 0;
pWav->pThread = 0;
LeaveCriticalSection (&pWav->CritSect);
AfxEndThread (0);
}
The audio driver is closed with waveOutClose, and the thread exits with AfxEndThread.
If the thread still lives after all of these potential deaths, it's time to send the audio driver some data. First, call CBuffer::GetNextFullBuffer to get a buffer, and if that is successful, the buffer is sent to the driver with CWave::SendDataToDriver. The first buffer in the audio stream is denoted by the BUFFER_FIRST flag. When the thread sees that it is the first buffer, it sends a few more buffers to keep the audio driver happy for awhile. Those extra buffers might not be available. In that case, CWave::SendDataToDriver sees the NULL buffer pointer and simply returns the following:
else
{
// Get another buffer and send it to the audio driver.
BUFFER* pBuf = (BUFFER*)pWav->pCBuffer->GetNextFullBuffer ();
if (pBuf)
{
pWav->SendDataToDriver (pBuf);
if (pBuf->ulFlags & BUFFER_FIRST)
{
pBuf->ulFlags &= ~BUFFER_FIRST;
// Send the audio driver a bunch of
// buffers to chew on for awhile.
for (int i=0; i<STARTING_BUFFERS; i++)
{
pBuf = (BUFFER*)pWav->pCBuffer->GetNextFullBuffer ();
pWav->SendDataToDriver (pBuf);
}
}
pWav->bDataUnderrun = FALSE;
}
}
On completion of these tasks, the worker thread loops back and waits again.
You've already learned about CWave::PlayTheAudio. This method is called when the plug-in is starting the audio playback. It opens the default audio device and creates the worker thread:
BOOL CWave::PlayTheAudio (void)
{
// Open the audio device
MMRESULT mmRc = waveOutOpen (&hWave,
WAVE_MAPPER,
&WaveFormat,
(DWORD)DriverCallback,
(DWORD)this,
CALLBACK_FUNCTION);
if (mmRc)
return FALSE;
// Create the thread
usState = AUDIO_PLAYING;
CreateWriteThread ();
return TRUE;
}
Look at the call to waveOutOpen. This Windows API opens a waveform device. In this case, it is the default audio device, WAVE_MAPPER. A pointer to a WAVEFORMATEX structure previously filled is supplied along with the address of a callback routine (DriverCallback) and a reference to the CWave object (this).
The audio driver callback routine is an important piece of the NPWAVE plug-in equation. This routine processes three basic driver messages:
WOM_CLOSE
WOM_OPEN
WOM_DONE
WOM_CLOSE is sent after a call to waveOutClose. This message indicates that all audio buffers have been returned and the driver is closing. During this message, the plug-in driver callback signals the main thread that it is safe to continue with exiting:
case WOM_CLOSE: // Driver was closed with waveOutClose
{
BOOL fSuccess = SetEvent (pWav->hEventCloseSync);
break;
}
WOM_OPEN is sent after a call to waveOutOpen. The plug-in callback does nothing for this message.
WOM_DONE is sent when the driver has finished with a data block. During this message, the callback signals the worker thread to continue processing buffers. If the last buffer is detected, the flag bPlayedLastBuffer is set to TRUE. When the thread sees this flag set, it exits:
case WOM_DONE: // Driver is finished with last data block
{
EnterCriticalSection (&pWav->CritSect);
WAVEHDR* pWaveHdr = (WAVEHDR*)dw1;
BUFFER* pBuff = (BUFFER*)pWaveHdr->dwUser;
// Clean up preparation performed by the waveOutPrepareHeader function
waveOutUnprepareHeader (pWav->hWave, pWaveHdr, sizeof(WAVEHDR));
if (pBuff->ulFlags & BUFFER_LAST)
{
pWav->bPlayedLastBuffer = TRUE;
BOOL fSuccess = SetEvent (pWav->hEvent); // Release the thread
}
else
{
pWav->pCBuffer->ReturnUsedBuffer (pBuff);
if (pWav->pCBuffer->AnyFullBuffers ())
BOOL fSuccess = SetEvent (pWav->hEvent); // Release the thread
else
pWav->bDataUnderrun = TRUE;
}
LeaveCriticalSection (&pWav->CritSect);
break;
}
During WOM_DONE processing, the CBuffer::ReturnUsedBuffer and CBuffer::AnyFullBuffers methods are called. ReturnUsedBuffer gives the used buffer back to the buffer pool. AnyFullBuffers checks to ensure that more buffers are available. If not, an underrun condition is flagged.
The audio driver callback allows the audio plug-in to efficiently manage its memory by returning consumed buffers. This technique also lets the plug-in send multiple buffers on startup to prevent potential audio breakup during high system load.
As shown in Figure 17.2, the NPWAVE plug-in has an extremely simple user interface. This interface consists of a Play button and a Pause button. These buttons are children of the plug-in's window. The window was created by the Navigator, and it is sized depending on the plug-in's type. To access this window, the plug-in creates a CPluginWindow object and subclasses it to the Navigator's window.
Figure 17.2 : The audio plug-in's user interface.
Clicking the Play button starts the audio playing. Pause, as the name implies, pauses the audio playback. A pause is resumed with another click to the Play button.
CPluginWindow is a small class with a message map for window message processing. The class lives in a file called npwindow.cpp. CPluginWindow's message map looks for the plug-in defined messages ID_AUDIO_PLAY and ID_AUDIO_PAUSE. These messages are generated when the Play or Pause buttons are clicked. ID_AUDIO_PLAY resolves to the OnPlay method, and ID_AUDIO_PAUSE resolves to the OnPause method. Here is the message map:
BEGIN_MESSAGE_MAP( CPluginWindow, CWnd )
//{{AFX_MSG_MAP( CMainWindow )
ON_COMMAND(ID_AUDIO_PLAY,OnPlay)
ON_COMMAND(ID_AUDIO_PAUSE,OnPause)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
When the Play button is clicked, the OnPlay method simply calls the CWave::Play method:
//
// OnPlay - The "Play" button was clicked
//
void CPluginWindow::OnPlay()
{
if (pWave)
if (!pWave->Play ())
AfxMessageBox ("Can't play now !");
}
Notice that a pointer to CWave is used to call the Play method. This pointer was acquired during creation. CWave's Play method looks like this:
BOOL CWave::Play (void)
{
if (usState == AUDIO_PAUSED)
{
MMRESULT mmRc = waveOutRestart (hWave);
usState = AUDIO_PLAYING;
}
else if (!bAutoStart)
{
if (usState == AUDIO_WAITING_FOR_START && ulBufferFullCount > 0)
{
BOOL rc = PlayTheAudio ();
if (!rc)
return FALSE;
}
else
return FALSE;
}
return TRUE;
}
In a paused condition, the audio is restarted using waveOutRestart. Otherwise, PlayTheAudio is called. PlayTheAudio opens the audio device with a call to waveOutOpen and creates the worker thread. The thread, in turn, passes buffers to the audio driver.
The OnPause method is called when the Pause button is clicked. It looks like this:
//
// OnPause - The "Pause" button was clicked
//
void CPluginWindow::OnPause()
{
if (pWave)
if (!pWave->Pause ())
AfxMessageBox ("Can't pause now !");
}
OnPause in turn calls CWave::Pause, which looks like this:
BOOL CWave::Pause (void)
{
if (usState == AUDIO_PLAYING)
{
MMRESULT mmRc = waveOutPause (hWave);
usState = AUDIO_PAUSED;
}
else
return FALSE;
return TRUE;
}
The Pause method calls the waveOutPause API, which temporarily suspends audio playback.
The buttons are created in the CPluginWindow::InitWindow method by creating Cbutton objects with the appropriate parameters:
void CPluginWindow::InitWindow()
{
CRect rect (10,10,90,50);
cbPlay = new CButton();
cbPlay->Create("Play",
BS_PUSHBUTTON | WS_VISIBLE,
rect,
this,
ID_AUDIO_PLAY);
rect.SetRect (100,10,180,50);
cbPause = new CButton();
cbPause->Create("Pause",
BS_PUSHBUTTON | WS_VISIBLE,
rect,
this,
ID_AUDIO_PAUSE);
}
Writers of commercial-grade plug-ins probably want to have some really cool bitmap buttons using CBitmapButton, instead of these plain examples. The buttons are simply deleted during a call to the CPluginWindow::CleanupWindow method:
void CPluginWindow::CleanupWindow ()
{
delete cbPlay;
delete cbPause;
}
| Note |
You might have noticed in the file npshell.cpp an object called theApp. It was allocated globally at the beginning of npshell.cpp like this: CWinApp theApp; Interestingly enough, the object is never accessed. Why? Well, for one thing, those buttons created in CPluginWindow would not work properly. To use MFC routines in a DLL, the class library must be initialized. When you use Visual C's MFC AppWizard to create a DLL, a CWinApp is provided for you with a source file for overriding members of CWinApp, such as the constructor. Because the NPWAVE DLL was not created using AppWizard, it simply declares a CWinApp object, fulfilling the required MFC initialization. |
Any Netscape plug-in should support multiple instances. In some cases, as with the audio plug-in, a single resource is shared among these instances. Because the audio plug-in shares a single audio device, only one plug-in instance can use this device at any given time. To play another audio instance, you must either allow the stream to complete, or click the Stop button on the Navigator.
When you click the Stop button on the Navigator, the audio does not immediately stop because a number of audio buffers are queued in the plug-in. The size and amount of these buffers can be configured in the npwave.h file. Feel free to change these values and see how it affects playback performance:
#define MAX_BUFFER_SIZE (1024 * 16) + STRUCT_SIZE
#define MAX_BUFFERS 20
#define LOW_WATER 10
#define STARTING_BUFFERS 4
With the preceding values, there are 20 buffers with a size of 16KB each. Total memory required for a stream is 320KB. If you are playing 8-bit PCM at 11025 samples per second, each 16KB buffer holds about 1.5 seconds of audio. Notice the STARTING_BUFFERS define is set to 4. This value instructs the plug-in to send four full buffers to the audio driver on start. Four buffers is about six seconds of audio data. The LOW_WATER define tells the plug-in to wait for this many full buffers before starting or restarting audio playback.
Your plug-in can have a number of instance configurations depending on the way it is run. Here are some possibilities:
A multiple instance full page scenario is usually created by the user creating a new Navigator instance with File|New Web Browser. With the audio plug-in, it looks like the one in Figure 17.3.
Figure 17.3 : Multiple instances of full page audio plug-ins.
A multiple-instance embedded plug-in can have more than one instance within the same Web page. You might even run into multiple pages of multiple embedded plug-ins! Figure 17.4 shows a single page with multiple embedded audio plug-ins.
Figure 17.4 : Multiple embedded instances of the audio plug-in.
The importance of separate instance data and very careful protection of shared resources is shown in these examples.
Another little function that the audio plug-in supports in embedded mode is an autostart flag. This flag, if set to true, makes the audio playback start as soon as the page is loaded. You can pass any HTML parameters in your HTML embed statement. Each named pair is passed into your plug-in. For example, the autostart=true statement is passed to the audio plug-in like this:
<embed SRC=mission.wav WIDTH=190 HEIGHT=60 autostart=true>
The sample audio plug-in, NPWAVE.DLL, is a Window's 95 multithreaded DLL. Multithreaded applications require careful protection for shared resources through the use of synchronization objects. This chapter gave an example of safe interaction between threads and MFC objects.
Real-time data streaming and data processing really shows off the power of a Netscape Navigator plug-in module. Although this audio example might not perform in a real-time fashion using a typical 28.8 baud modem, a savvy programmer can add compression to stream data at whatever bandwidth is available.
The fundamental design of the NPWAVE plug-in allows for generic streaming and buffering of any data type. Buffering is implemented with a circular buffer via the CBuffer class. This generic class is the topic of the next chapter.