The npAVI plug-in, which plays Microsoft AVI movies, is easily the most complex plug-in in the LiveConnect/Plug-Ins SDK. It includes full LiveConnect integration and exercises nearly all of the NPP methods. This plug-in also uses Microsoft Foundation Classes (MFC) to display a custom pop-up menu. npAVI has some of the same capabilities as Netscape's LiveVideo plug-in-one of the major features that distinguishes Navigator 3.0 from Navigator 2.0.
The plug-in named npSimp32, described in Chapter 13, "Analysis of a Simple Plug-In," was intended purelyfor illustrative purposes. In contrast, npAVI could bereleased as a commercial product, and few users would question it.
npAVI displays a video (in AVI format) inside the Navigator window.
(It's intended for use as an embedded plug-in.) For an idea of
how npAVI looks, start by compiling and then running the plug-in.
Because this plug-in uses a Windows-specific AVI engine, only
a Windows version is provided by Netscape.
Tip |
If you're new to plug-ins, read Chapter 13. Simple is much smaller than npAVI, so some of its interaction is easier to follow. When you understand that plug-in, come back here to npAVI. |
Chapter 13 describes a plug-in in which the Java classes already have been hooked into the plug-in. This transformation is an essential step in using LiveConnect, Netscape's client-side integration technology. (LiveConnect is described in greater detail in Chapter 14, "Principles of LiveConnect.")
In the npAVI project, you, as the programmer, are expected to
build your own header and interface files from the Java .class
files. You need the Java compiler, javac, and Netscape's special
version of the Java utility javah.
Tip |
The readme file supplied by Netscape suggests that you install the JDK into a directory named C:\java. Instead, install it in C:\jdk. That way, you can use the makefile's default definition of JDK_ROOT, saving a step (and an opportunity to make a mistake) when building the plug-in with nmake. |
Unlike the Netscape sample plug-in in Chapter 13, npAVI is built from a makefile. Use Microsoft Developer Studio to open Examples\npavi\Source\makefile. Note the message in the file header: You must either set SDK_ROOT and JDK_ROOT in the environment or in your command line. (SDK_ROOT is the directory that contains bin\win32\javah.exe; JDK_ROOT contains bin\javac.exe.) The fastest way to build npAVI is to specify these directories in the command line. For example, you can type the following:
nmake -f makefile JDK_ROOT=\jdk
Tip |
If you leave SDK_ROOT and JDK_ROOT undefined, the makefile has definitions it uses. It defines SDK_ROOT as ..\..\.., which is perfect if you are building in, say, C:\PluginSDK30b5\Examples\npavi\Source\. If you leave JDK_ROOT undefined, the makefile sets it to c:\jdk\. |
Tip |
If you turn on DEBUG, javah builds type-safe functions instead of macros. Opt for safety, even when you're not building a debug version. Leave DEBUG=1 in the command line all the time. |
Building all If you previously used any member of the make family (including nmake), you probably know that, by default, make makes the first target it finds in the file. In the Netscape-supplied makefile, that target is specified in the following line:
all : java .\objs\$(TARGET).dll
Here, $(TARGET) was specified earlier in the makefile by the following:
TARGET = npavi32
Building java The target java includes dirs, $(CLASSES), $(ZIP), $(GEN), and $(STUBS). The target dirs calls the MS-DOS command mkdir for each of the three subdirectories: _gen, _stubs, and objs. These directories are used to hold the results of the builds of the remaining targets.
The target CLASSES resolves to the two targets .\AviPlayer.class and .\AviObserver.class. Toward the bottom of the file is a set of implicit rules. One of these rules, shown on the following lines, says that to make a file of type .class from a file of type .java, run the Java compiler (javac) on the .java file, using the switches given in JAVA_CLASSES:
java.class: $(JAVAC) $(JAVA_CLASSES) $<
These switches resolve to -classpath
..\..\..\classes\moz3_0.zip.
Tip |
You can change JAVA_CLASSES if you need to keep your Netscape classes someplace else. Remember, however, that Netscape has a reputation for bringing out a new release of Navigator every few months. You may find it easier to leave the makefile alone and just install the file of the SDK in their default locations. |
Caution |
The file moz3_0.zip is used by Netscape as a convenient package for their Java classes. Under no circumstances should you use WinZip, PKUNZIP, or similar tools to unZIP moz3_0.zip. |
The target ZIP resolves to three files:
These three files are rebuilt with explicit rules every time moz3_0.zip
changes. They are built by using a special Java Runtime Interface
(JRI) version of the javah
utility. The JRI version is produced by Netscape; it builds .h
and .c files based on the contents of Java source files.
Note |
Note that the -jri switch must be on for the Netscape-enhanced version of javah to work correctly. |
The real work of javah is to produce header files, which go in the _gen directory, and stub C files, which go in the stub directory. The make targets GEN and STUBS resolve to the header and stub files for the two Java classes AviObserver and AviPlayer. When you build your plug-in, use this makefile as a starting point and change GEN and STUBS to name your own Java classes. The "Implicit Rules" section of the makefile contains rules for transforming .class files to .h and .c files, using javah.
Building .\objs\$(TARGET).dll The final target associated with all is .\objs\$(TARGET).dll. (By substituting your project name into TARGET, you can easily customize this makefile to generate your own plug-in.) This target depends, via an explicit rule, on $(TARGET).def, $(OBJS), and $(RES). The .def file, of course, is written by hand, and identifies the following three entry points required of every plug-in:
Except for changing the name of the library, you seldom need to edit your plug-in's .def file.
OBJS lists the object files, which are generated, via an implicit rule, from the C++ source files of your plug-in.
Note that npAVI includes a resource file, npAVI32.res. The final implicit rule in the makefile describes how to run the resource compiler to transform a .rc file into the .res file.
After you've built the plug-in by using nmake, your Source directory contains the AviObserver.class and AviPlayer.class files, and the objs subdirectory contains npavi32.dll.
Copy the two Java class files and the .dll file to the Navigator\plug-ins
directory.
Tip |
You may want to skim Chapter 14 to get the bigger picture of Java and LiveConnect. In Chapter 14, you learn how to use javac and javah to make your own class, header, and stub files. |
You must now restart Navigator. Make sure that you are using a version of Navigator that is at least version 3.0b5 or later, or you won't see all the features of this plug-in working.
When Navigator restarts, it examines the files in the plug-ins directory and registers plug-ins based on their MIME media type. Chapter 9, "Understanding Plug-Ins," describes this process in more detail.
After Navigator is up, choose File, Open File to open Examples\npAVI\Testing\npAVIExample.html. You probably want to bookmark this page while you learn about plug-ins.
As soon as it opens, npAVI downloads its stream and displays the first frame of the movie. Figure 15.1 shows the npAVIExample.html user interface. The plug-in is now waiting for commands that you enter through the buttons via JavaScript.
npAVI displays its first frame immediately and then pauses the movie. You can use the controls to start, stop, and resume the movie or go directly to a specific frame. (The default movie, seahorse.avi, has 199 frames.)
In Navigator, choose View, Document Source to see how the buttons are hooked up. The start and stop buttons call document.avi.play(false) and document.avi.stop(false), respectively. The seek button picks up the value of the frame field through JavaScript:
onclick="document.avi.seek(false, parseInt(form.editSeek.value))"
In npAVI's default configuration, you can't use Microsoft Developer Studio's ClassView to open that function because Netscape has not built npAVI into a project.
You can take advantage of ClassView by choosing File, New, Project Workspace. A list of possible project types appears. Select Makefile from the list and give the project a new name and path.
Microsoft Developer Studio offers to take you to the Project Settings dialog box, where you can specify the path to the makefile. Answer Yes to that offer (see Figure 15.2).
In the Project Settings dialog box (see Figure 15.3), if you want to use this project to build the plug-in, and as the way to examine classes, fill in the Build Command Line text box with your real nmake command line, including DEBUG=1, if you are using it. Make sure that you specify the path of the makefile relative to the project directory. (If you are copying the files into the project directory and have set up the SDK and JDK following the recommended values, use nmake -f makefile DEBUG=1.)
If you just want to examine the npAVI files, start Microsoft Developer Studio and choose Insert, Files into Project to make the npAVI .cpp and .rc files in the Examples\npavi\Source directory a part of the new project. To use npAVI as a starting point for your plug-in, copy the npAVI files to the new project directory, and then add them into the project. In this way, all changes you make become part of the new project, and you won't inadvertently change npAVI.
npAVI is a sophisticated plug-in that uses nearly all the NPP methods. Open npshell.cpp to begin the process of tracing this plug-in's flow of control.
As you saw when you ran Simple, a plug-in has two interfaces: one to Navigator and another to the user. Because the plug-in can't do anything with the user until most of its Navigator functions are called, this section starts by tracing the flow of control between Navigator and npAVI (see Figure 15.4).
Figure 15.4: Navigator makes a series of calls to the plug-in to get things started.
How npAVI Is Called Unlike Simple, which takes
no input data, npAVI reads a stream-in this case, an AVI movie.
Because this stream is a local file, there is no server to translate
the file extension into a MIME type. Rather, Navigator looks at
the file extensions registered by each plug-in and determines
that files with the .avi extension get handled by npAVI.
Caution |
Because the file extension .avi and the MIME media types video/msvideo and video/x-msvideo are so common, you may already have a plug-in configured to handle these types. In Navigator, choose Help, About Plug-ins to display the list of configured plug-ins. Figure 15.5 shows the table that describes npAVI. If npAVI32 isn't listed as handling Video for Windows, temporarily remove the conflicting plug-in from the plugins subdirectory, restart Navigator, and choose Help, About Plug-ins again. |
Figure 15.5: Be sure that about:plugins shows npAVI handling the Video for Windows types.
NPP_Initialize() When Navigator sees the <EMBED> tag, it looks up the plug-in that is registered to handle files with the .avi extension. Navigator finds npAVI32 and loads this library into its address space. Then it exchanges entry points, as Chapter 11, "Step-by-Step Through the NPP Methods," described. The first method it calls for which you are responsible is NPP_Initialize().
If you built npAVI into a project as previously described, you can use Microsoft Developer Studio's ClassView to open this function. (It's in the Globals folder because it isn't part of any class.)
You can see that NPP_Initialize()simply returns NPERROR_NO_ERROR.
NPP_GetJavaClass() NPP_GetJavaClass() follows a classic design: npAVI gets a pointer to the Java runtime environment through NPN_GetJavaEnv(). Then it calls the use function for each of the Java classes: netscape.plugin.Plugin, AviObserver, and AviPlayer. The function returns the reference from use_AviPlayer(). (Chapter 14 explains why this sequence is necessary.) The following is the implementation of NPP_GetJavaClass().
jref NPP_GetJavaClass(void) { JRIEnv* env = NPN_GetJavaEnv(); use_netscape_plugin_Plugin(env); use_AviObserver(env); return use_AviPlayer(env); }
The use functions such as use_netscape_plugin_Plugin() and use_AviObserver() are written by javah. (See Chapter 14 for more information on javah.)
NPP_New() Now Navigator is ready to make the plug-in instance. Open NPP_New() with ClassWizard. Note that the new instance parses out the autostart and loop parameters from argv. Then it builds a CPluginWindow, which is used as the basis for instance data.
// NPP_New // // create a new plugin instance // handle any instance specific code initialization here // NPError NP_LOADDS NPP_New(NPMIMEType pluginType, NPP instance, uint16 mode, int16 argc, char* argn[], char* argv[], NPSavedData* saved) { BOOL bAutoStart, bLoop; // CPluginWindow is the main plugin object. Keep state information // about the specific instance to be created CPluginWindow* pluginData; // trap a NULL ptr if (instance == NULL) return NPERR_INVALID_INSTANCE_ERROR; // extract the pseudo command line arguments which were passed as attributes in the // embed tag of the document // for this example the plugin takes a true/false value for both // autostart and loop to determine the plugin style characteristics bAutoStart = FALSE; bLoop = FALSE; for (int idx =0; idx<argc; idx++) { if (!strcmpi(argn[idx],"autostart")) { if (!strcmpi(argv[idx],"true")) { bAutoStart = TRUE; } } if (!strcmpi(argn[idx],"loop")) { if (!strcmpi(argv[idx],"true")) { bLoop = TRUE; } } } // create a data pointer to pass around with the instance pluginData = new CPluginWindow (bAutoStart, bLoop, mode, instance); // save my data pointer in the instance pdata pointer instance->pdata = pluginData; // this will be passed back to me in all calls so that I // can extract it later return NPERR_NO_ERROR; }
Because you encapsulated npAVI in a project, you can read the definition of CPluginWindow by using ClassView. Double-click the class name at the top of the ClassView pane. (You can view the implementation by expanding that branch of the tree and double-clicking the member name.) Figure 15.6 shows CPluginWindow as viewed through ClassView.
Figure 15.6: Use ClassView to examine the classes of npAVI.
NPP_SetWindow() NPP_SetWindow() quickly gets through to CPluginWindow::SetWindow(). NPP_SetWindow() lets the work be done by this class member and then calls InvalidateRect() and UpdateWindow() so that the plug-in gets a new WM_PAINT message and redraws the window's contents. The following is the implementation of NPP_SetWindow(), from npshell.cpp:
// NPP_SetWindow // // Associates a platform specific window handle with a plug-in instance. // Called multiple times while, e.g., scrolling. Can be called for three // reasons: // // 1. A new window has been created // 2. A window has been moved or resized // 3. A window has been destroyed // // There is also the degenerate case; that it was called spuriously, and // the window handle and or coords may have or have not changed, or // the window handle and or coords may be ZERO. State information // must be maintained by the plug-in to correctly handle the degenerate // case. // NPError NP_LOADDS NPP_SetWindow(NPP instance, NPWindow* window) { // strange... if (!window) return NPERR_GENERIC_ERROR; // strange... if (!instance) return NPERR_INVALID_INSTANCE_ERROR; // get back the plugin instance object CPluginWindow * pluginData = (CPluginWindow *)instance->pdata; if (pluginData) { if (!window->window) { // watch out this case. // window went away //delete pluginData; (?????) return NPERR_NO_ERROR; } if (!pluginData->GetWndProc()) { //\\// First time in //\\// // grab the handle so we can control the messages flow pluginData->SetWindow((HWND)window->window); } // resize or moved window (or newly created) InvalidateRect(*pluginData, NULL, TRUE); UpdateWindow(*pluginData); return NPERR_NO_ERROR; } // return an error if no object defined return NPERR_GENERIC_ERROR; }
As shown above, NPP_SetWindow() does much of its work through CPluginWindow::SetWindow(). CPluginWindow::SetWindow() is quite short: it simply subclasses the Netscape-supplied window with our own CPluginWindow::PluginWndProc:
// SetWindow // // store the window handle and subclass the window proc // Associate "this" with the window handle so we can redirect window // messages to the proper instance // void CPluginWindow::SetWindow(HWND hWnd) { hPluginWnd = hWnd; // subclass pfnDefault = (WNDPROC)::SetWindowLong(hWnd, GWL_WNDPROC, (LONG)CPluginWindow:: PluginWndProc); // register "this" with the window structure ::SetProp(hWnd, CPluginWindow::ThisLookUp, (HANDLE)this); }
Figure 15.7 illustrates how the components of NPP_SetWindow() communicate.
CPluginWindow::PluginWndProc() is defined in plgwnd.cpp:
// PluginWndProc // // static member function of CPluginWindow // this is the window proc used to subclass the plugin window the // navigator created and passed in NPP_SetWindow (npshell.c) // LRESULT CALLBACK CPluginWindow::PluginWndProc(HWND hWnd, UINT Msg, WPARAM WParam, LPARAM lParam) { // pull out the instance object receiving the message CPluginWindow* pluginObj = (CPluginWindow*)GetProp(hWnd, CPluginWindow::ThisLookUp); // message switch switch (Msg) { case WM_LBUTTONDOWN: { POINT p; p.x = LOWORD(lParam); p.y = HIWORD(lParam); pluginObj->OnLButtonDown(WParam, &p); break; } case WM_RBUTTONDOWN: { POINT p; p.x = LOWORD(lParam); p.y = HIWORD(lParam); pluginObj->OnRButtonDown(WParam, &p); break; } case WM_PAINT: { PAINTSTRUCT PaintStruct; ::BeginPaint(hWnd, &PaintStruct); pluginObj->OnPaint(); ::EndPaint(hWnd, &PaintStruct); break; } case WM_PALETTECHANGED: pluginObj->OnPaletteChanged((HWND)WParam); break; // the following two messages are used from the CAvi class // MM_MCINOTIFY informs about a stop event case MM_MCINOTIFY: pluginObj->GetAviStream().OnStop(); break; // WM_TIMER is used to update the position status case WM_TIMER: pluginObj->GetAviStream().OnPositionChange(); break; // menu handling // pass to CPluginWindow instance? (too much work...) // // WARNING // those ids are also used from the native functions (avijava.cpp) // when the flag isAsync is setted to TRUE (see avijava.cpp and AviPlayer.java) case WM_COMMAND: if (!HIWORD(WParam)) { switch LOWORD(WParam) { case ID_VIDEO_PLAY: pluginObj->OnLButtonDown(0, 0); return 0; case ID_VIDEO_STOP: pluginObj->GetAviStream().Stop(); return 0; case ID_VIDEO_REWIND: pluginObj->GetAviStream().Rewind(); return 0; case ID_VIDEO_FORWARD: pluginObj->GetAviStream().Forward(); return 0; case ID_VIDEO_FRAME_BACK: pluginObj->GetAviStream().FrameBack(); return 0; case ID_VIDEO_FRAME_FORWARD: pluginObj->GetAviStream().FrameForward(); return 0; // this is hidden to the menu but it's used from // the java class in asynchronous mode (see AviPlayer.java // and avijava.cpp) case ID_VIDEO_SEEK: pluginObj->GetAviStream().Seek(lParam); return 0; } } default: return CallWindowProc(pluginObj->GetWndProc(), hWnd, Msg, WParam, lParam); }; return 0; }
Notice that CPluginWindow::PluginWndProc() includes handlers for WM_LBUTTONDOWN, WM_RBUTTONDOWN, and WM_COMMAND, and also the now-expected WM_PAINT. When the user clicks the left mouse button, CPluginWindow::OnLButtonDown(), which toggles the run state of the AVI movie, gets called.
When the user right-clicks the mouse, the plug-in calls the Windows global function ::CreatePopupMenu() and reads strings from the string table in the resource file to populate the menu.
Here's the implementation of OnRButtonDown():
// OnRButtonDown // // bring up a menu with avi commands // void CPluginWindow::OnRButtonDown(UINT uFlags, LPPOINT pPoint) { UINT uState; char szMenuString[128]; // Create the popup. HMENU hPopup = ::CreatePopupMenu(); if(hPopup == 0) { return; } if(_pAvi->isPlaying()) uState = MF_GRAYED; else uState = MF_ENABLED; //"Play..." ::LoadString(g_hDllInstance, MENU_PLAY, szMenuString, 128); ::AppendMenu(hPopup, uState, ID_VIDEO_PLAY, szMenuString); //"Pause..." ::LoadString(g_hDllInstance, MENU_PAUSE, szMenuString, 128); ::AppendMenu(hPopup, !uState, ID_VIDEO_STOP, szMenuString); // Separator ::AppendMenu(hPopup, MF_SEPARATOR, 0, 0); uState = MF_ENABLED; //"Rewind (Start of movie)..." ::LoadString(g_hDllInstance, MENU_REWIND, szMenuString, 128); ::AppendMenu(hPopup, uState, ID_VIDEO_REWIND, szMenuString); //"Forward (End of movie)..." ::LoadString(g_hDllInstance, MENU_FORWARD, szMenuString, 128); ::AppendMenu(hPopup, uState, ID_VIDEO_FORWARD, szMenuString); // Separator ::AppendMenu(hPopup, MF_SEPARATOR, 0, 0); //"Frame Back..." ::LoadString(g_hDllInstance, MENU_FRAME_BACK, szMenuString, 128); ::AppendMenu(hPopup, uState, ID_VIDEO_FRAME_BACK, szMenuString); //"Frame Forward..." ::LoadString(g_hDllInstance, MENU_FRAME_FORWARD, szMenuString, 128); ::AppendMenu(hPopup, uState, ID_VIDEO_FRAME_FORWARD, szMenuString); ::ClientToScreen(_hPluginWnd, pPoint); ::TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON, pPoint->x, pPoint->y, 0, hPluginWnd, NULL); }
Tip |
To add new commands to the menu, just add a string for the menu item in the string table, and add the string to the menu in CPluginWindow::OnRButtonDown(). Then add the handler for the new command in the WM_COMMAND section of CPluginWindow::PluginWndProc. |
NPP_NewStream() and NPP_StreamAsFile() Movies (AVI and also other formats) take too much bandwidth to play in real time. npAVI specifies *stype=NP_ASFILE so that the entire movie is downloaded to the cache. The following is the implementation of NPP_NewStream():
// NPP_NewStream // // Notifies the plugin of a new data stream. // The data type of the stream (a MIME name) is provided. // The stream object indicates whether it is seekable. // The plugin specifies how it wants to handle the stream. // // In this case, I set the streamtype to be NPAsFile. This tells the // Navigator the plugin doesn't handle streaming and can only deal with // the object as a complete disk file. It will still call the write // functions but it will also pass the filename of the cached file in // a later NPE_StreamAsFile call when it is done transfering the file. // // If a plugin handles the data in a streaming manner, it should set // streamtype to NPNormal (e.g. *streamtype = NPNormal)...the NPE_ // StreamAsFile function will never be called in this case // NPError NP_LOADDS NPP_NewStream(NPP instance, NPMIMEType type, NPStream *stream, NPBool seekable, uint16 *stype) { if(!instance) return NPERR_INVALID_INSTANCE_ERROR; // save the plugin instance object in the stream instance stream->pdata = instance->pdata; *stype = NP_ASFILE; return NPERR_NO_ERROR; }
Then NPP_StreamAsFile() is called so that the player can play the movie from the hard drive. The following is how that function is implemented:
// NPP_StreamAsFile // // The stream is done transferring and here is a pointer to the file // in the cache. This function is only called if the streamtype was // set to NPAsFile. // void NP_LOADDS NPP_StreamAsFile(NPP instance, NPStream *stream, const char* fname) { if(fname == NULL || fname[0] == NULL) return; // get back the plugin instance object CPluginWindow * pluginData = (CPluginWindow *)instance->pdata; // get the avi object controller CAvi& aviPlayer = pluginData->GetAviStream(); // open the avi driver with the specified name aviPlayer.Open(*pluginData, fname); aviPlayer.Update(); // The AVI window Update() paint doesn't work in Win95. // It works fine in NT and Win3.1, but Win95 is doing something // hostile. So, I have a hack here that steps the frame forward // to paint the window // figure out whether to hack for Win 95 DWORD dwVer = GetVersion(); int iVer = (LOBYTE(LOWORD(dwVer))*100)+HIBYTE(LOWORD(dwVer)); if(iVer > 394) { // Win 95 aviPlayer.FrameForward(); } }
NPP_StreamAsFile() gets the aviPlayer object (_pAvi) built into CPluginWindow, directs it to open on the Netscape-supplied file name, and calls the player's Update method. These Java-based methods are described in following sections of this chapter.
NPP_Print() NPP_Print() is hooked up, drawing a rectangle onto the print window's device context. The following is the implementation of NPP_Print():
// NPP_Print // // Printing the plugin (to be continued...) // void NP_LOADDS NPP_Print(NPP instance, NPPrint* printInfo) { if(printInfo == NULL) // trap invalid parm return; if (instance != NULL) { CPluginWindow* pluginData = (CPluginWindow*) instance->pdata; pluginData->Print(printInfo); } }
The work is done in CPluginWindow::Print():
// Print // void CPluginWindow::Print(NPPrint* printInfo) const { if (printInfo->mode == NP_FULL) { // // *Developers*: If your plugin would like to take over // printing completely when it is in full-screen mode, // set printInfo->pluginPrinted to TRUE and print your // plugin as you see fit. If your plugin wants Netscape // to handle printing in this case, set printInfo->pluginPrinted // to FALSE (the default) and do nothing. If you do want // to handle printing yourself, printOne is true if the // print button (as opposed to the print menu) was clicked. // On the Macintosh, platformPrint is a THPrint; on Windows, // platformPrint is a structure (defined in npapi.h) containing // the printer name, port, etc. // void* platformPrint = printInfo->print.fullPrint.platformPrint; NPBool printOne = printInfo->print.fullPrint.printOne; printInfo->print.fullPrint.pluginPrinted = FALSE; // Do the default } else { // If not fullscreen, we must be embedded // // *Developers*: If your plugin is embedded, or is full-screen // but you returned false in pluginPrinted above, NPP_Print // will be called with mode == NP_EMBED. The NPWindow // in the printInfo gives the location and dimensions of // the embedded plugin on the printed page. On the Macintosh, // platformPrint is the printer port; on Windows, platformPrint // is the handle to the printing device context. // NPWindow* printWindow = &(printInfo->print.embedPrint.window); void* platformPrint = printInfo->print.embedPrint.platformPrint; HPEN hPen, hPenOld; #ifdef WIN32 /* Initialize the pen's "brush" */ LOGBRUSH lb; lb.lbStyle = BS_SOLID; lb.lbColor = RGB(128, 128, 128); lb.lbHatch = 0; hPen = ::ExtCreatePen(PS_COSMETIC | PS_SOLID, 1, &lb, 0, NULL); #else COLORREF cref = RGB(128, 128, 128); hPen = ::CreatePen(PS_SOLID, 32, cref); #endif HDC hDC = (HDC)(DWORD)platformPrint; hPenOld = (HPEN)::SelectObject(hDC, hPen); BOOL result = ::Rectangle(hDC, (int)(printWindow->x), (int)(printWindow->y), (int)(printWindow->x + printWindow->width), (int)(printWindow->y + printWindow->height)); ::SelectObject(hDC, hPenOld); ::DeleteObject(hPen); } }
While this Print() function does not attempt to print the contents of the plug-in window, it at least prints a rectangle as a placeholder on the page with an embedded plug-in. You could use this routine as a starting point for the implementation of NPP_Print() in your plug-in and then replace the call to ::Rectangle() with something more meaningful for your plug-in's content.
The real work of npAVI is done through the Java classes. Much of this work, in turn, is implemented back in the plug-in through native methods. Figure 15.8 shows the interaction between JavaScript, Java, and the plug-in.
Open the file AviPlayer.java. (You have to use File, Open;
.java files are not part of the project.) Note that the AviPlayer
class includes a private member, AviObserver. This member is an
implementation of an interface and can be treated as though it
were an instance of a class. This section looks at the implementation
of both AviPlayer and AviObserver.
Tip |
Make sure that you turn on the Visual C++ browser information to make it easier to trace from one class and method to the next. |
The Java AviPlayer Class Most of the methods in the AviPlayer class are native. Follow the thread of control out of the AviPlayer class and back to AviPlayer.c, in the _stubs directory. Pick one function, such as SetTimeOut(). SetTimeOut() is specified as native in AviPlayer.java, so it appears in the stub file AviPlayer.c. The following is the native declaration, in AviPlayerr.java:
public class AviPlayer extends Plugin { ... // set the timeout for the position checking timer public native void setTimeOut(int timeout); ... }
Note also, that by specifying DEBUG you get the typesafe C function AviPlayer_setTimeOut() rather than the macro. AviPlayer_setTimeOut() relies on use_AviPlayer_setTimeOut() being called as part of use_AviPlayer. (Recall that use_AviPlayer() was called by NPP_GetJavaClass().)
The programmer's implementations for native methods such as SetTimeOut() are in thefile avijava.cpp. There you learn that native_AviPlayer_setTimeOut() calls CAvi::SetFrequency(), which, in turn, calls the Window API ::SetTimer():
extern "C" JRI_PUBLIC_API(void) native_AviPlayer_setTimeOut(JRIEnv* env, struct AviPlayer* self, jint timeout) { NPP instance = (NPP)self->getPeer(env); CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata; pPluginData->GetAviStream().SetFrequency(timeout); }
Note |
The timer in npAVI is used to set the pace for the playback, so each frame pauses on-screen long enough for the user to see it. The timer in an AVI movie should be set to a very small interval, typically 1/30 of a second. On slower machines, the computer cannot refresh the screen at this rate, and the video looks slow and jerky. On faster machines, the timer keeps the video from running too quickly. |
The Java AviObserver Interface Unlike C++, Java does not support multiple inheritance. Java interfaces, however, can be used to specify required behavior in a class. Recall that the Java AviPlayer class includes a private member of type AviObserver. AviObserver is based on an interface. To understand AviObserver, start from a plug-in member such as CAvi::OnStop(). When Windows sends the MM_MCINOTIFY message, the WindowsProc dispatches CAvi::OnStop(). This method is implemented by getting the Java peer object (with NPN_GetJavaPeer()), and then retrieving its observer object. Finally, CAvi::OnStop() calls the observer's onStop() method.
Recall from npAVIExample.html that the HTML file held a form:
<html> <body> <center> <h1>Avi Plugin test using JavaScript</h1> (You'll need to change this demo and supply your own AVI movie). <embed name=avi SRC="seahorse.avi" WIDTH=100 HEIGHT=100> <hr> <form> <input type=button onclick="document.avi.play(false)" value=play> <input type=button onclick="document.avi.stop(false)" value=stop> <input type=button onclick="document.avi.seek(false, parseInt(form.editSeek.value))" value=seek> <input name=editSeek type=text value=0 size=10></form> </center> </body> </html>
This form directs the function calls such as play() to the named plug-in. Refer back to Figure 15.8 to see the flow of control between JavaScript, the Java peer object, and the plug-in.
When the interaction is over and the user is ready to leave npAVI, Navigator calls NPP_Destroy(). Because there is only one instance, NPP_Shutdown() is called immediately after the instance is deleted. The implementation of NPP_Destroy() follows the standard pattern: It deletes pdata and resets that pointer to NULL:
// NPP_Destroy // // Deletes a plug-in instance and releases all of its resources. // NPError NP_LOADDS NPP_Destroy(NPP instance, NPSavedData** save) { CPluginWindow * pluginData = (CPluginWindow *)instance->pdata; delete pluginData; instance->pdata = 0; return NPERR_NO_ERROR; }
Note that NPP_Destroy has hooks for all the usual plug-in features, including NPSavedData. Although the function isn't used here, it can be hooked up easily.
This chapter showed how to build a complete Windows plug-in, including Java headers and stubs, through a makefile. The plug-in is full-featured, including support for LiveConnect.
You'll find npAVI in the LiveConnect/Plug-Ins SDK, part of the
Netscape ONE set of SDKs.
ON THE WEB |
http://home.netscape.com/comprod/development_partners/plugin_api/index.html Start at this page to download a copy of the LiveWire/Plug-Ins SDK for your platform. For a full set of information about this technology, you should also visit http:// home.netscape.com/eng/mozilla/3.0/handbook/plugins/index.html and follow the links from that page. |