Chapter 23

Netscape's LiveConnect Sample


CONTENTS


Introduction

With the release of Netscape's 3.0 Navigator SDK comes a LiveConnect sample for playing AVI movies. This sample shows how to integrate JavaScript, Java, and Navigator plug-ins with LiveConnect.

LiveConnect provides communication among JavaScript, Java, and Navigator plug-ins by letting each language call the methods of the other language. Therefore, a plug-in can call a Java method, Java can call a plug-in method, JavaScript can call a Java method, and Java can call a JavaScript method. Because there is no connection between plug-ins and JavaScript, you must use Java to connect these two. To see this visually, look at Figure 23.1.

Figure 23.1 : The LiveConnect interface between plug-ins, Java, and JavaScript.

At first glance, Netscape's LiveConnect example might seem overwhelming, but using this chapter to break it up into small components should help you understand how it works. One thing you might notice right away is that the sample does not use an Integrated Development Environment (IDE) to build the components. Most of today's Java tools are command-line based, so Netscape uses a makefile to build the sample. Other new things are the addition of a Java compiler (javac) and Java header/stub generator (javah). These new tools are used with a C++ compiler (cl), linker (link), and resource compiler (rc) from Microsoft.

Where to Find the Files

Netscape's FTP servers are the best place to find the files needed to build this sample. That way, you'll be sure to get the most up-to-date tools and headers. The new SDK can be found on any one of Netscape's FTP servers in the PUB/NAVIGATOR/SDK directory.

For example, to get the Windows SDK, use this address:

ftp://ftp3.netscape.com/pub/navigator/sdk/windows/nspi30.zip

If you are developing for Windows, unzip this file using an unzip program that can handle long filenames. After you unzip the file, the following directories are created:

Get a Java Compiler

You also need to get a command-line based Java compiler. This example works with the javac compiler from Sun Microsystems. You can get this from http://java.sun.com.

The Design of the LiveConnect AVI Sample

Netscape's LiveConnect sample is based on the same plug-in design found in the sample plug-ins in this book. The Navigator created plug-in window is subclassed with an object called CPluginWindow. Video is controlled through MCI with a CAvi object, and plug-in APIs are handled through a file called npshell.cpp.

Here are some of the differences between Netscape's LiveConnect sample and this book's code samples found in Chapters 17 through 22:

Components

Looking at the block diagram in Figure 23.2, you can see how Java attaches to a plug-in with LiveConnect. Notice that this diagram is divided into four sections: Netscape Navigator, Java and JavaScript Interpreters, Plug-in DLL, and Windows System. Arrows show the calling paths to each code module. Figure 23.2 is a more detailed view of the same concept shown in Figure 23.1. You might want to refer to Figure 23.2 while reading through this chapter, because much of this chapter is organized to match this figure.

Figure 23.2 : The components of the LiveConnect AVI sample.

The Java Applet

The Java applet uses two classes and an interface to implement Java control over the plug-in. The classes are AviTest and AviPlayer. The Java interface is AviObserver. Of these, AviTest creates and maintains a user interface, AviObserver is called from the plug-in, and AviPlayer calls into the plug-in.

AviTest

The Java code shown in Listing 23.1 implements the test applet (AviTest.class) for this sample. Notice that AviObserver and AviPlayer classes are imported. Also, Play and Stop buttons are created and handled. For more specific information on this code, Core Java from The SunSoft Press Java Series is suggested reading.


Listing 23.1. The Java code to implement a user interface for Netscape's LiveConnect sample.
import AviObserver;
import AviPlayer;
import java.awt.Panel;
import java.awt.TextField;
import java.awt.Button;
import java.awt.Event;
import java.awt.BorderLayout;
import java.awt.Frame;
import java.applet.Applet;
import netscape.javascript.JSObject;

public class AviTest extends Applet implements AviObserver{
    MediaControl control;

    public void init() {
        setLayout(new BorderLayout());
        add("Center", control = new MediaControl(getDocument()));
        control.enable();
    }

    public static void main(String args[]) {
        Frame f = new Frame("AviTest");
        AviTest aviTest = new AviTest();

        aviTest.init();

        f.add("Center", aviTest);
        f.resize(300, 30);
        f.show();
    }

    public void onStop() {
        control.resetButton();
    }

    public void onPositionChange(int newPosition) {
        control.updateFrame(newPosition);
    }

    public JSObject getDocument() {
        return (JSObject)JSObject.getWindow(this).getMember("document");
    }

}
    
class MediaControl extends Panel {
    TextField s;
    Button play;
    Button stop;

    JSObject document;

    public MediaControl(JSObject doc) {
        document = doc;

        add(play = new Button("Play"));
        add(stop = new Button("Stop"));
        add(s = new TextField("0", 4));
        stop.disable();
    }

    public void updateFrame(int frame) {
        s.setText(String.valueOf(frame));
    }

    public void resetButton() {
        play.enable();
        stop.disable();
    }

    public boolean action(Event ev, Object arg) {
        if (ev.target instanceof Button) {
            AviPlayer avi = (AviPlayer)document.getMember("avi");
            
            String label = (String)arg;
            if (label == "Play") {
                // look AviPlayer.java for exaplanation
                avi.play(true);
                play.disable();
                stop.enable();
                return true;
            }
            else {
                // look AviPlayer.java for exaplanation
                avi.stop(true);
                stop.disable();
                play.enable();
                return true;
            }
        }
        return false;
    }
}

AviObserver

If you refer back to Figure 23.2, you'll see a Java class interface called AviObserver for the plug-in DLL. This Java class is used for events coming from the AVI plug-in, such as media event notifications (media stopped, position advisories, and so on). The following code shows how the AviObserver interface handles calls from the plug-in via stub code in AviObserver.c:

// java interface for events coming from the avi plugin
// any object implementing this interface may register itself into the
// AviPlayer to get notifications (see advise function in AviPlayer)

interface AviObserver {

    public void onStop();
    public void onPositionChange(int newPosition);
}

AviPlayer

Look again at Figure 23.2, and notice the Java class AviPlayer. This Java class calls into native C/C++ methods located in AviPlayer.c and avijava.cpp. The code for the AviPlayer Java class is given in Listing 23.2. This code held a large comment from Netscape developers that detailed a problem with another thread calling MCI video. This code comment is now printed in the following warning box.

The AviPlayer Java class is run by the Navigator Java Interpreter, which is on a different thread than the plug-in APIs called from Navigator. Apparently, Windows MCI does not allow a different thread to make MCI calls for controlling video. This is why Netscape had to post window messages to CPluginWindow instead of calling that object directly (see Figure 23.2).

A Warning from the Netscape Developers
For AVI methods with MCI drivers, an instance of an AVI driver belongs to the thread that created it. When an applet tries to call one of the play, stop, seek, rewind, forward, frameForward, and frameBack functions (see Listing 23.2), MCI fails to execute the command because the applet is in a different thread than the one in which the AVI instance was created (which is the main thread when it loads the plug-in). The isAsync argument is used to see whether the function can be called directly (isAsync = false) or whether a message needs to be posted in order to execute the function.

This problem could happen every time. In some applets, you try to access (directly or indirectly) thread-safe data from within another thread. This usually doesn't happen because your data is in the java class, so it is available from other applets. However, when you have native functions hiding data (the plug-in is a very good example of this), this problem might occur.

It would be nice if the MCI drivers were not so strict, and why do they need to be?


Listing 23.2. Java code to implement the AviPlayer Java class.
import netscape.plugin.Plugin;
import AviObserver;

public class AviPlayer extends Plugin {

    // used to fire avi asynchronous events like OnStop or OnPositionChange.
    // AviObserver is an interface (see AviObserver.java)

    private AviObserver observer;

    public AviObserver getObserver() {
        return observer;
    }

    // the object interested in listening the avi must register here.
    // timeout defines the time that occurs beetwen two OnPositionChange events

    public boolean advise(AviObserver o, int timeout) {
        System.err.println("called advise "+o+" "+timeout);
        if (observer == null)
            observer = o;
        else
            return false;

        setTimeOut(timeout);
        return true;
    }

    //\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//.
    // those are all native.
    // check AviPlayer.c/.h for stubs and prototypes information
    // check avijava.cpp for native implementation

    // set the timeout for the position checking timer

    public native void setTimeOut (int timeout);

    public native boolean play (boolean isAsync);
    public native boolean stop (boolean isAsync);
    public native boolean seek (boolean isAsync, int position);
    public native boolean rewind (boolean isAsync);
    public native boolean forward (boolean isAsync);
    public native boolean frameForward (boolean isAsync);
    public native boolean frameBack (boolean isAsync);
}

Stub Code and Headers with javah

When you build this sample, stub code and headers are generated to connect the Java interface, AviObserver, and Java class, AviPlayer, to the appropriate C++ code implemented with avijava.cpp and cavi.cpp. You can see how the stub code fits in the mix by looking back at Figure 23.2.

Native Plug-In Methods for Java

A native Java method is one that is implemented in another more machine-dependent language such as C++. Native code is implemented in the file avijava.cpp. These methods are called from a Java interpreter thread and therefore must access the plug-in's instance through a call to getPeer. This instance handle is set by the system.

After the instance structure is retrieved, a pointer to the CPluginWindow object is dereferenced. If the call is asynchronous (which it must be for MCI video because of the previously mentioned thread problem), a message is posted to the plug-in window with a call to PostMessage. CPluginWindow handles these posted messages because it has previously subclassed the plug-in window.

AviPlayer_setTimeOut

The native Java method AviPlayer_setTimeOut sets the frequency for the video update position. This method is not implemented by the previous Java code.

extern "C" JRI_PUBLIC_API(void)
native_AviPlayer_setTimeOut (JRIEnv* env,
    struct AviPlayer* self,
    JRIMethodThunk* method,
                 jint timeout)
{
    NPP instance = (NPP)self->getPeer(env);
    CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata;
    pPluginData->GetAviStream().SetFrequency(timeout);
}

AviPlayer_play

AviPlayer_play plays the video. A call to this C++ method originates from clicking on the Java-created play button.

native_AviPlayer_play (JRIEnv* env,
    struct AviPlayer* self,
    JRIMethodThunk* method,
    jbool isAsync)
{
    NPP instance = (NPP)self->getPeer(env);
    CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata;
    if (isAsync) {
        ::PostMessage(*pPluginData, WM_COMMAND, MAKEWPARAM(ID_VIDEO_PLAY, 0), 0);
        return TRUE;
    }
    else
        return pPluginData->GetAviStream().Play();
}

AviPlayer_stop

A call to AviPlayer_stop stops the video. This call results from clicking on the Java-created stop button.

extern "C" JRI_PUBLIC_API(jbool)
native_AviPlayer_stop (JRIEnv* env,
    struct AviPlayer* self,
    JRIMethodThunk* method,
    jbool isAsync)
{
    NPP instance = (NPP)self->getPeer(env);
    CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata;
    if (isAsync) {
        ::PostMessage(*pPluginData, WM_COMMAND, MAKEWPARAM(ID_VIDEO_STOP, 0), 0);
        return TRUE;
    }
    else
        return pPluginData->GetAviStream().Stop();
}

AviPlayer_seek

AviPlayer_seek allows seeking within the video file. As in all of these methods, notice the call to PostMessage to prevent the MCI driver multithreading bug discussed previously.

extern "C" JRI_PUBLIC_API(jbool)
native_AviPlayer_seek (JRIEnv* env,
    struct AviPlayer* self,
    JRIMethodThunk* method,
    jbool isAsync,
    jint position)
{
    NPP instance = (NPP)self->getPeer(env);
    CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata;
    if (isAsync) {
        ::PostMessage(*pPluginData, WM_COMMAND, MAKEWPARAM(ID_VIDEO_SEEK, 0), 0);
        return TRUE;
    }
    else
        // CAvi::Seek(..)
        return pPluginData->GetAviStream().Seek(position);
}

AviPlayer_rewind

The AviPlayer_rewind method rewinds the AVI video to the first frame. Notice that each of these methods in this section have a parameter for isAsync. This parameter determines whether the method can call the MCI video driver on the current thread, or whether it must post a window message to effectively switch threads.

extern "C" JRI_PUBLIC_API(jbool)
native_AviPlayer_rewind (JRIEnv* env,
    struct AviPlayer* self,
    JRIMethodThunk* method,
    jbool isAsync)
{
    NPP instance = (NPP)self->getPeer(env);
    CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata;
    if (isAsync) {
        ::PostMessage(*pPluginData, WM_COMMAND, MAKEWPARAM(ID_VIDEO_REWIND, 0), 0);
        return TRUE;
    }
    else
        return pPluginData->GetAviStream().Rewind();
}

AviPlayer_forward

AviPlayer_forward moves the video to the last video frame. This is like rewind, but it goes to the first frame rather than the last.

extern "C" JRI_PUBLIC_API(jbool)
native_AviPlayer_forward (JRIEnv* env,
    struct AviPlayer* self,
    JRIMethodThunk* method,
    jbool isAsync)
{
    NPP instance = (NPP)self->getPeer(env);
    CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata;
    if (isAsync) {
        ::PostMessage(*pPluginData, WM_COMMAND, MAKEWPARAM(ID_VIDEO_FORWARD, 0), 0);
        return TRUE;
    }
    else
        return pPluginData->GetAviStream().Forward();
}

AviPlayer_frameForward

AviPlayer_frameForward enables you to step the video forward a single frame.

extern "C" JRI_PUBLIC_API(jbool)
native_AviPlayer_frameForward(JRIEnv* env,
    struct AviPlayer* self,
    JRIMethodThunk* method,
    jbool isAsync)
{
    NPP instance = (NPP)self->getPeer(env);
    CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata;
    if (isAsync) {
        ::PostMessage(*pPluginData, WM_COMMAND,
ÂMAKEWPARAM(ID_VIDEO_FRAME_BACK, 0), 0);
        return TRUE;
    }
    else
        return pPluginData->GetAviStream().FrameForward();
}

AviPlayer_frameBack

Just like you can step a frame forward with AviPlayer_frameForward, you can step backward with AviPlayer_frameBack.

extern "C" JRI_PUBLIC_API(jbool)
native_AviPlayer_frameBack (JRIEnv* env,
    struct AviPlayer* self,
    JRIMethodThunk* method,
                jbool isAsync)
{
    NPP instance = (NPP)self->getPeer(env);
    CPluginWindow* pPluginData = (CPluginWindow*)instance->pdata;
    if (isAsync) {
        ::PostMessage(*pPluginData, WM_COMMAND,
ÂMAKEWPARAM(ID_VIDEO_FRAME_FORWARD, 0), 0);
        return TRUE;
    }
    else
        return pPluginData->GetAviStream().FrameBack();
}

CPluginWindow

CPluginWindow contains a window procedure that handles messages from the native Java methods in avijava.cpp and handles standard window messages from the plug-in window. This C++ class also creates a CAvi object, subclasses itself to the plug-in window, and implements a menu.

The Window Procedure

The window procedure handles mouse messages, palette changes, and commands from the Java native method. While you are looking at Listing 23.3, notice that both window messages originating from Java (WM_COMMAND) and direct mouse commands (WM_LBUTTONDOWN and WM_RBUTTONDOWN) are handled. This is a good example of how a plug-in can have both a Java and a C++ implemented user interface at the same time!


Listing 23.3. The window procedure CPluginWindow::PluginWndProc.
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->GetAviStream().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;
}

Constructor

The constructor initializes values, saves the mode, and creates a new CAvi object for MCI video control.

CPluginWindow::CPluginWindow (BOOL bAutoStart,
    BOOL bLoop,
    uint16 mode,
    NPP instance)
{
    // initialized in SetWindow
    _hPluginWnd = 0;
    _pfnDefault = 0;

    _mode = mode;
    // make an avi object
    // passing the NPP instance down is necessary because of the
    // java callback in CAvi (see CAvi::OnStop() and CAvi::OnPositionChange()
    _pAvi = new CAvi(bAutoStart, bLoop, instance);
}

Destructor

The destructor deletes the CAvi object and removes the window subclass and property.

CPluginWindow::~CPluginWindow()
{
    // delete the avi object
    delete _pAvi;
    // restore the old window proc and delete the property
    if (_pfnDefault) {
        ::SetWindowLong(_hPluginWnd, GWL_WNDPROC, (LONG)_pfnDefault);
        ::RemoveProp(_hPluginWnd, CPluginWindow::_ThisLookUp);
    }
}

CPluginWindow::SetWindow

SetWindow is called from NPP_SetWindow in npshell.cpp. This method stores the window handle and subclasses the window procedure. It also saves a pointer to CPluginWindow in the window property with SetProp.

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);
}

CPluginWindow::OnLButtonDown

A call to CPluginWindow::OnLButtonDown originates from handling a WM_LBUTTONDOWN in the window procedure. This method can either stop the video or play it depending on the video state and position.

void
CPluginWindow::OnLButtonDown(UINT uFlags, LPPOINT pPoint)
{
    if (_pAvi->isPlaying()) {
        // if plying, stop
        _pAvi->Stop();
    }
    else {
        // if stopped, play
        DWORD dwPos, dwLen;
        dwPos = _pAvi->GetPosition();
        dwLen = _pAvi->GetLength();

        if (dwPos >= dwLen)
            _pAvi->Rewind();
        _pAvi->Play();
    }
}

CPluginWindow::OnRButtonDown

If the user clicks the right mouse button over the video window, the CPluginWindow::OnRButtonDown method brings up a popup menu for video control. When this menu is up, the user can control the video through the menu in the same way as with Java-created buttons.

void
CPluginWindow::OnRButtonDown(UINT uFlags, LPPOINT pPoint)
{
    UINT uState;
    
    //  Create the popup.
    HMENU hPopup = ::CreatePopupMenu();
    if(hPopup == 0)  {
        return;
    }

    if(_pAvi->isPlaying())
        uState = MF_GRAYED;
    else
        uState = MF_ENABLED;

    ::AppendMenu(hPopup, uState, ID_VIDEO_PLAY, "Play...");  
    ::AppendMenu(hPopup, !uState, ID_VIDEO_STOP, "Pause...");

    //  Separator
    ::AppendMenu(hPopup, MF_SEPARATOR, 0, 0);

    uState = MF_ENABLED;    
    ::AppendMenu(hPopup, uState, ID_VIDEO_REWIND,  "Rewind (Start of movie)...");
    ::AppendMenu(hPopup, uState, ID_VIDEO_FORWARD, "Forward (End of movie)...");

    //  Separator
    ::AppendMenu(hPopup, MF_SEPARATOR, 0, 0);
    
    ::AppendMenu(hPopup, uState, ID_VIDEO_FRAME_BACK,  "Frame Back...");
    ::AppendMenu(hPopup, uState, ID_VIDEO_FRAME_FORWARD, "Frame Forward...");

    ::ClientToScreen(_hPluginWnd, pPoint);
    ::TrackPopupMenu(hPopup,
                        TPM_LEFTALIGN | TPM_RIGHTBUTTON,
                        pPoint->x,
                        pPoint->y,
                        0,
                        _hPluginWnd,
                        NULL);
}

CPluginWindow::OnPaint

If the video needs repainting, a WM_PAINT message handled in the window procedure results in a call to CPluginWindow::OnPaint. This method simply calls the video object's update method.

void
CPluginWindow::OnPaint()
{
    _pAvi->Update();
}

CPluginWindow::OnPaletteChange

Palette changes are indicated by a WM_PALETTECHANGED window message that results in a call to the CPluginWindow::OnPaletteChanged method. The video control object is called to realize the current palette.

void
CPluginWindow::OnPaletteChanged(HWND hFocusWnd)
{
    //  Don't do this if we caused it to happen.
    if(hFocusWnd != _hPluginWnd)   {
        _pAvi->Realize();
    }
}

CAvi

The CAvi class is called by CPluginWindow to control AVI video files through MCI. This class calls back Java to notify of video stop and position change. MCI commands are sent to the MCI AVI driver with the Windows mciSendCommand API. Remember that MCI needs to be called on the same thread for video.

Constructor

The constructor saves the plug-in instance and initializes members. Notice the flags for autostart (bAutoStart) and looping (bLoop).

CAvi::CAvi (BOOL autoStart, BOOL bLoop, NPP instance)
{
    _pluginInstance = instance;
    _mDeviceID = 0;
    _hMovieWnd = 0;

    _bLoop = bLoop;
    _bAutoStart = autoStart;
    _bPlaying = FALSE;

    _uTimeOut = 0;
    _uTimerID = ++s_InstanceCount;
}

Destructor

The destructor closes the video device by calling CAvi::Close, and the MCI video driver is closed with an MCI_CLOSE message.

CAvi::~CAvi ()
{
    Close();
}

CAvi::Open

CAvi::Open opens the MCI video device and saves the device ID. A window handle (hWnd) is passed to the method so that the video driver can use it for displaying video. Also, the filename (Filename) that contains the name of the AVI file is passed in as the second parameter. The MCI driver is opened with MCI_OPEN and the window handle is associated with MCI_WINDOW.

BOOL CAvi::Open (HWND hWnd, LPCSTR Filename)
{
    DWORD RetCode;  
    MCI_ANIM_OPEN_PARMS OpenParms;
    MCI_ANIM_WINDOW_PARMS WindowParms;

    // Close any device that is already open.
    if (_mDeviceID){
        Close ();
    }

    // Open a device for playback.

    OpenParms.dwCallback = NULL;
    OpenParms.wDeviceID = 0;
    OpenParms.lpstrDeviceType = NULL;
    OpenParms.lpstrElementName = Filename;
    OpenParms.lpstrAlias = 0;
    OpenParms.dwStyle = WS_CHILD | WS_VISIBLE;
    OpenParms.hWndParent = hWnd;

    if (RetCode = mciSendCommand (0, MCI_OPEN,
       (DWORD) MCI_OPEN_ELEMENT |
        MCI_ANIM_OPEN_PARENT |
        MCI_ANIM_OPEN_WS,
       (DWORD)(LPVOID)&OpenParms)) {
            char szBuf[256];
            mciGetErrorString(RetCode, szBuf, 256);
            MessageBox(NULL, szBuf, "AVI Plugin: Error Opening Device!", MB_OK);
            return FALSE;
        }
    // The device was opened, cache the device ID.

    _mDeviceID = OpenParms.wDeviceID;

    // set and cache the AVI window handle

    WindowParms.dwCallback = NULL;
    WindowParms.hWnd = hWnd;
    WindowParms.nCmdShow = SW_SHOW;
    WindowParms.lpstrText = (LPSTR) NULL;

    if (RetCode = mciSendCommand (_mDeviceID,
                                    MCI_WINDOW,
                                    MCI_ANIM_WINDOW_HWND,
                                    (DWORD)(LPVOID)&WindowParms)) {
        return FALSE;
    }
    _hMovieWnd = WindowParms.hWnd;

    // start playing if auto start defined

    if (_bAutoStart)
        Play();

    return TRUE;
}

CAvi::Close

CAvi::Close closes the MCI video device. The close is performed by sending an MCI_CLOSE message via the mciSendCommand MCI API. After the close, playing status is set to false and the video device ID is set to 0.

void CAvi::Close (void)
{
    // Closing a device ID will stop the video playback.

    if (_mDeviceID)
        mciSendCommand (_mDeviceID, MCI_CLOSE, 0L, NULL);

    _bPlaying = FALSE;
    _mDeviceID = 0;
}

CAvi::Play

CAvi::Play starts the video playback asynchronously. It also sets a timer for position tracking. Video playback is started with the MCI_PLAY command. The video window (hMovieWnd) is notified when the play is complete.

BOOL CAvi::Play ()
{
    DWORD RetCode, dwFlags = MCI_NOTIFY;    // notify a window proc when
                                          &nbs p; // the play status change (like stop)
    MCI_ANIM_PLAY_PARMS PlayParms;
    
    // if no device open return
    if (!_mDeviceID)
        return FALSE;

    // Start playback using the MCI_PLAY command.
    PlayParms.dwCallback = (DWORD)_hMovieWnd;
    PlayParms.dwFrom = PlayParms.dwTo = 0;

#ifdef WIN32
    if (_bLoop)
        dwFlags = MCI_DGV_PLAY_REPEAT;
#endif
    if (RetCode = mciSendCommand (_mDeviceID,
                                    MCI_PLAY,
                                    dwFlags,
                                    (DWORD)(LPVOID)&PlayParms)) {
        char szBuf[256];
        mciGetErrorString(RetCode,szBuf,256);
        MessageBox(NULL, szBuf, "MCI Play Error", MB_OK);

        return FALSE;
    }

    // start the timer so we can track down the position
    if (_uTimeOut)
        SetTimer(_hMovieWnd, _uTimerID, _uTimeOut, NULL);
    
    _bPlaying = TRUE;
    
    return TRUE;
}

CAvi::Stop

CAvi::Stop sends an MCI_STOP to stop video playback.

BOOL CAvi::Stop (void)
{
    // Stop playback

    if (_mDeviceID && mciSendCommand (_mDeviceID, MCI_STOP, 0L, NULL)) {
        return FALSE;
    }

    return TRUE;
}

CAvi::Seek

CAvi::Seek sends an MCI_SEEK to seek within the AVI file. The seek position is indicated by the dwSeekPosition parameter, which is passed into this method.

BOOL CAvi::Seek (ULONG dwSeekPosition)
{
    MCI_SEEK_PARMS seekParams;
    seekParams.dwTo = dwSeekPosition;

    if (_mDeviceID && mciSendCommand(_mDeviceID,
                                        MCI_SEEK,
                                        MCI_TO,
                                        (DWORD)(LPVOID)& amp;seekParams)) {
        return FALSE;
    }

    return TRUE;
}

CAvi::Rewind

CAvi::Rewind rewinds the video to the beginning and displays the first frame. Notice that a rewind is accomplished with an MCI_SEEK message using the MCI_SEEK_TO_START flag.

BOOL CAvi::Rewind (void)
{
    // Use the MCI_SEEK command to return to the beginning of the file.
    if (_mDeviceID && mciSendCommand (_mDeviceID,
                            MCI_SEEK,
                            MCI_SEEK_TO_START,
                            NULL)) {
        return FALSE;
    }

    return TRUE;
}

CAvi::Forward

CAvi::Forward forwards the video to the last frame and displays it. When the video is at the end of the MCI_SEEK message with an MCI_SEEK_TO_END flag, private methods FrameBack and FrameForward are called to avoid a display bug in the video driver. Whatever it takes!

BOOL CAvi::Forward (void)
{
    // Use the MCI_SEEK command to go to the end of the file.
    if (_mDeviceID && mciSendCommand (_mDeviceID,
                            MCI_SEEK,
                            MCI_SEEK_TO_END,
                            NULL)) {
        return FALSE;
    }

    FrameBack();
    FrameForward();

    return TRUE;
}

CAvi::FrameForward

CAvi::FrameForward moves the video forward one frame. This is done with the MCI_STEP message using the MCI_ANIM_STEP_FRAMES flag. StepParms.dwFrames is set to 1 to allow for single-frame stepping.

BOOL CAvi::FrameForward (void)
{
    MCI_ANIM_STEP_PARMS StepParms;

    StepParms.dwFrames = 1L;
    if (_mDeviceID && mciSendCommand (_mDeviceID,
                        MCI_STEP,
                        MCI_ANIM_STEP_FRAMES,
                        (DWORD)(LPVOID)&StepParms)) {
        return FALSE;
    }

    return TRUE;
}

CAvi::FrameBack

CAvi::FrameBack moves the video back one frame. This method is very similar to CAvi::FrameForward, but it uses the MCI_ANIM_STEP_REVERSE flag to step backward one frame.

BOOL CAvi::FrameBack (void)
{
    MCI_ANIM_STEP_PARMS StepParms;

    // Use MCI_STEP to move back one frame.

    StepParms.dwFrames = 1L;

    if (_mDeviceID && mciSendCommand (_mDeviceID,
                        MCI_STEP,
                        MCI_ANIM_STEP_REVERSE,
                        (DWORD)(LPVOID)&StepParms)) {
        return FALSE;
        }

    return TRUE;
}

Sizing, Positioning, and Updating

Sizing, positioning, and updating the video are handled by CAvi with the following methods:

CAvi::GetLength
CAvi::GetPosition
CAvi::GetWidth
CAvi::GetHeight
CAvi::Center
CAvi::Update
CAvi::Realize

These methods are beyond the scope of this chapter. You should check CAVI.CPP to see how they work.

CAvi::SetFrequency

The CAvi::SetFrequency method changes the timer value for position updates. The method first stops the old timer with a call to KillTimer and then sets a new timer with SetTimer using the timer frequency passed into the method as uTimer. The actual WM_TIMER timer messages are sent to the main window procedure by the Windows subsystem.

void
CAvi::SetFrequency(UINT uTimer)
{
    if (_bPlaying && _uTimeOut)
        KillTimer(_hMovieWnd, _uTimerID);
        
    _uTimeOut = uTimer;
        
    if (_uTimeOut && _bPlaying)
         SetTimer(_hMovieWnd, _uTimerID, _uTimeOut, 0);
}

CAvi::OnStop

CAvi::OnStop is called when the video is stopped. The Java stub code located in AviObserver.c is called, which bubbles up to the Java Interface AviObserver method onStop. Notice the calls to new Navigator 3.0 plug-in APIs: NPN_GetJavaPeer and NPN_GetJavaEnv. These calls are documented in Chapter 14, "LiveConnect."

void
CAvi::OnStop()
{
    AviPlayer* javaAviInst;
    JRIEnv* env;

    // load the java instance representing the plugin instance

    javaAviInst = (AviPlayer*)NPN_GetJavaPeer(_pluginInstance);
    env = NPN_GetJavaEnv();

    // find the listener if any (observer on AviPlayer java class;
    // AviPlayer.java)

    AviObserver* observer = javaAviInst->getObserver(env);

    // if time out is non zero we set a timer on play.

    if (_uTimeOut) {
        KillTimer(_hMovieWnd, _uTimerID);
        if (observer)
            observer->onPositionChange(env, GetPosition());
    }

    if (observer)
        observer->onStop(env);

    _bPlaying = FALSE;
}

CAvi::OnPositionChange

CAvi::OnPosition sends position change events to the Java stub code located in AviObserver.c. These events bubble up to the Java interface AviObserver method OnPositionChange.

void
CAvi::OnPositionChange()
{
    AviPlayer* javaAviInst;
    JRIEnv* env;

    javaAviInst = (AviPlayer*)NPN_GetJavaPeer(_pluginInstance);
    env = NPN_GetJavaEnv();

    AviObserver* observer = javaAviInst->getObserver(env);

    if (observer)
        observer->onPositionChange(env, GetPosition());
}

Plug-In Entry Points

Netscape's LiveConnect sample supports all the standard 2.x plug-in APIs. You can look at any other sample code in this book to see how these APIs work. For the purposes of this chapter, a new 3.x plug-in API called NPP_GetJavaClass is used.

NPP_GetJavaClass

NPP_GetJavaClass is used to associate a Java class with a plug-in (discussed in Chapter 14). Within this plug-in implemented API, the Java environment is retrieved with a call to NPN_GetJavaEnv. The information returned from this call is needed for all Java Runtime Interface (JRI) calls. After getting the Java environment, a call is made to initialize the native C implemented Java class with init_AviPlayer. This initialization method was created by Netscape's javah tool. If your plug-in is not using Java, a NULL is returned from the NPP_GetJavaClass API.

jref
NPP_GetJavaClass(void)
{

    JRIEnv* env = NPN_GetJavaEnv();

    return init_AviPlayer(env);

}

Running the Sample with JavaScript

To get the whole thing started, Netscape uses an HTML file with some embedded JavaScript. This script brings in both the Java applet and the plug-in with an EMBED tag:

<html>
<head>
<script language="JavaScript">

<!--

function setup() {
    document.avi.advise(document.controller, 500);
}

//-->

</script>
</head>

<body onLoad="setup()">

<center>
<h1>Avi Plugin test using a Java Applet</h1>

<hr>

<applet name=controller code=AviTest.class width=400 height=30 mayscript>
</applet>

<hr>

<embed    name=avi SRC="sample.avi" WIDTH=100 HEIGHT=100>

<hr>

<a href="AviTest.java">The source.</a>
</center>

Figure 23.3 shows what the sample looks like when it is running. Notice the Java-created buttons and the plug-in video window with C++ created menu controls.

Figure 23.3 : Netscape's LiveConnect sample in action.

Conclusion

You should be familiar with Java in order to use LiveConnect with your plug-in. This chapter has intentionally left out JavaScript because it has no direct interface with plug-ins and only confuses the matter. JavaScript does have plug-in specific methods that you might want to look into if you are using JavaScript.

You should find it very helpful to study Figure 23.2 while reading through this chapter. Each block in the figure corresponds to a section in the chapter.

You might have noticed that there were no code listings for plug-in 2.x APIs from npshell.cpp. This chapter is really about LiveConnect. Look at any other sample in this book for explanation of 2.x APIs.

At the time of this writing, LiveConnect is still in beta. You might notice some discrepancies between this book and the plug-in SDK with which you are working. Concentrate on the LiveConnect concept rather than each line of code. You might even find some bugs if you look carefully. Be sure to get the latest plug-in SDK!

What's Next?

Do you ever wonder what the heck Netscape is doing on the WinSock layer? If so, check out the next chapter detailing Socket Spy, a technique for "spying" on a WinSock application's network data flow.