/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): Shane Stephens (CSIRO), Michael Martin, Marcin Lubonski * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #if defined(XP_UX) #include #include #elif defined(XP_WIN) #include #include #include #ifdef NDEBUG #pragma warning ( disable : 4311 ) // pointer truncation #pragma warning ( disable : 4312 ) // conversion to type of greater size #pragma warning ( disable : 4344 ) // coversion to type of smaller size, possible loss of data #endif #endif #include "plugin.h" #include "nsIServiceManager.h" #include "nsISupportsUtils.h" // this is where some useful macros defined #include "nsIPrefService.h" #include "nsIPrefBranch.h" #include extern "C" { #include "plugin_gui.h" #include "plugin_oggplay.h" #include "plugin_tools.h" #include "plugin_cmml.h" } #define PLUGIN_VERSION "1.0" #if defined (XP_WIN) // polling period for plugin timer #define POLLING_PERIOD 10 static VOID CALLBACK PluginCallbackProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime); #endif #if defined(XP_UX) || defined(XP_MACOSX) #define MIME_TYPES_HANDLED_1 "application/liboggplay" #define MIME_TYPES_HANDLED_2 "application/ogg" #define MIME_TYPES_HANDLED_3 "video/theora" #define PLUGIN_NAME "Annodex/Ogg Plugin for Mozilla" #define MIME_TYPES_DESCRIPTION MIME_TYPES_HANDLED_1"::"PLUGIN_NAME";"MIME_TYPES_HANDLED_2"::"PLUGIN_NAME";"MIME_TYPES_HANDLED_3"::"PLUGIN_NAME #define PLUGIN_DESCRIPTION PLUGIN_NAME PLUGIN_VERSION char* NPP_GetMIMEDescription(void) { static char s[] = MIME_TYPES_DESCRIPTION; return s; } // get values per plugin NPError NS_PluginGetValue(NPPVariable aVariable, void *aValue) { static char pluginName[] = PLUGIN_NAME; static char pluginDescription[] = PLUGIN_NAME; NPError err = NPERR_NO_ERROR; switch (aVariable) { case NPPVpluginNameString: *((char **)aValue) = pluginName; break; case NPPVpluginDescriptionString: *((char **)aValue) = pluginDescription; break; default: err = NPERR_INVALID_PARAM; break; } return err; } #endif // XP_UX, XP_MACOSX ////////////////////////////////////// // // general initialization and shutdown // NPError NS_PluginInitialize() { #if defined(XP_UX) NPError err = NPERR_NO_ERROR; PRBool supportsXEmbed = PR_FALSE; NPNToolkitType toolkit = (NPNToolkitType)0; err = NPN_GetValue(NULL, NPNVSupportsXEmbedBool, (void *)&supportsXEmbed); if (err != NPERR_NO_ERROR || supportsXEmbed != PR_TRUE) return NPERR_INCOMPATIBLE_VERSION_ERROR; err = NPN_GetValue(NULL, NPNVToolkit, (void *)&toolkit); if (err != NPERR_NO_ERROR || toolkit != NPNVGtk2) return NPERR_INCOMPATIBLE_VERSION_ERROR; #endif return NPERR_NO_ERROR; } void NS_PluginShutdown() { } ///////////////////////////////////////////////////////////// // // construction and destruction of our plugin instance object // nsPluginInstanceBase * NS_NewPluginInstance(nsPluginCreateData * aCreateDataStruct) { int i; char * source = NULL; if (!aCreateDataStruct) return NULL; for (i = 0; i < aCreateDataStruct->argc; i++) { printf("%s=%s; ", aCreateDataStruct->argn[i], aCreateDataStruct->argv[i]); if (strcmp(aCreateDataStruct->argn[i], "src") == 0) { source = aCreateDataStruct->argv[i]; } } printf("\n"); nsPluginInstance * plugin = new nsPluginInstance(aCreateDataStruct->instance, source); // Set to FALSE for windowless. But that doesn't seem to be supported on Lunix NPN_SetValue(aCreateDataStruct->instance, NPPVpluginWindowBool, (void *)TRUE); /* NPObject * value; NPVariant v1, v2; NPObject * children; int length; NPIdentifier childNodesID; NPIdentifier lengthID; NPIdentifier tagNameID; NPString name; childNodesID = NPN_GetStringIdentifier("childNodes"); lengthID = NPN_GetStringIdentifier("length"); tagNameID = NPN_GetStringIdentifier("tagName"); NPN_GetValue(aCreateDataStruct->instance, NPNVPluginElementNPObject, &value); NPN_GetProperty(aCreateDataStruct->instance, value, childNodesID, &v1); children = NPVARIANT_TO_OBJECT(v1); NPN_GetProperty(aCreateDataStruct->instance, children, lengthID, &v2); length = NPVARIANT_TO_INT32(v2); NPN_ReleaseVariantValue(&v1); NPN_ReleaseVariantValue(&v2); NPN_GetProperty(aCreateDataStruct->instance, value, tagNameID, &v1); name = NPVARIANT_TO_STRING(v1); printf("tagName is %s\n", name.utf8characters); NPN_ReleaseVariantValue(&v1); NPN_ReleaseObject(value); printf("length is %d\n", length); */ return plugin; } void NS_DestroyPluginInstance(nsPluginInstanceBase * aPlugin) { if(aPlugin) delete (nsPluginInstance *)aPlugin; } //////////////////////////////////////// // // nsPluginInstance class implementation // nsPluginInstance::nsPluginInstance(NPP aInstance, const char *source) : nsPluginInstanceBase(), mInstance(aInstance), mPluginInitialised(FALSE), mWindowInitialised(FALSE), mWindow(NULL), mScriptablePeer(NULL), mOggHandle(NULL), mGuiHandle(NULL), mServiceManager(NULL), mProxyHost(NULL), mProxyPort(80), mPlaylistPos(0), mPlaylistFreeze(FALSE), mPlaylistFrozen(FALSE), mReachedEndOfMovie(FALSE), mAsyncCmmlString(NULL), mCmmlCallback(NULL), mAsyncCmmlCallback(NULL), mEndPlayCallback(NULL), mPlaylistCallback(NULL), mMouseClickCallback(NULL) #if defined(XP_MACOSX) , mOutputCleared(FALSE) #elif defined (XP_WIN) , mPollingStarted(false), mPollingPeriod(POLLING_PERIOD) #endif { nsISupports * sm = NULL; nsIPrefService * prefs = NULL; nsIPrefBranch * branch = NULL; // Jump through some hoops to get the proxy info NPN_GetValue(NULL, NPNVserviceManager, &sm); if (sm) { sm->QueryInterface(NS_GET_IID(nsIServiceManager), (void **)&mServiceManager); NS_RELEASE(sm); } if (mServiceManager) { mServiceManager->GetServiceByContractID( "@mozilla.org/preferences-service;1", NS_GET_IID(nsIPrefService), (void **)&prefs); } if (prefs != NULL) { prefs->ReadUserPrefs(nsnull); prefs->GetBranch("network.proxy.", &branch); } if (branch != NULL) { char *proxy; int proxy_type; branch->GetCharPref("http", &proxy); branch->GetIntPref("type", &proxy_type); branch->GetIntPref("http_port", &mProxyPort); if (proxy_type == 0 || strlen(proxy) == 0) { mProxyHost = NULL; } else { mProxyHost = strdup(proxy); } } // Initialise the remaining members, and create our playlist of 1 SEM_CREATE(mCrossThreadSem, 1); appendMovie(source); } nsPluginInstance::~nsPluginInstance() { #if defined(XP_WIN) if (mPollingStarted == true) { KillTimer((HWND)mWindow->window, IDT_CALLBACK_TIMER); } #endif // mScriptablePeer may be also held by the browser // so releasing it here does not guarantee that it is over // we should take precaution in case it will be called later // and zero its mPlugin member if (mScriptablePeer != NULL) { mScriptablePeer->SetInstance(NULL); NS_IF_RELEASE(mScriptablePeer); } if (mCmmlCallback != NULL) { NS_RELEASE(mCmmlCallback); } if (mEndPlayCallback != NULL) { NS_RELEASE(mEndPlayCallback); } if (mPlaylistCallback != NULL) { NS_RELEASE(mPlaylistCallback); } if (mAsyncCmmlCallback != NULL) { NS_RELEASE(mAsyncCmmlCallback); } for (unsigned int i = 0; i < mPlaylist.size(); i++) { free(mPlaylist[i]); } clearCmmlStrings(); SEM_CLOSE(mCrossThreadSem); } NPBool nsPluginInstance::init(NPWindow* aWindow) { if(aWindow == NULL) return FALSE; mPluginInitialised = TRUE; return TRUE; } void nsPluginInstance::shut() { if (mWindowInitialised) { shut_gui(mGuiHandle); mGuiHandle = NULL; mWindowInitialised = FALSE; } mPluginInitialised = FALSE; } NPBool nsPluginInstance::isInitialized() { return (NPBool)mPluginInitialised; } NPError nsPluginInstance::NewStream(NPMIMEType type, NPStream* stream, NPBool seekable, uint16* stype) { //printf("URL %s for this Mime-Type %s\n", (char*)type, stream->url); return NPERR_NO_ERROR; } NPError nsPluginInstance::SetWindow(NPWindow *aWindow) { // -- MAC OS X SPECIFIC -- // When the browser window is resized, we usually receive one or more calls // to SetWindow, often with a zero-sized clip, followed by an update event // in HandleEvent with the correct clip. Therefore, we want to ignore the // SetWindow calls and only update the clip size in HandleEvent. // -- MAC OS X SPECIFIC - if (mWindowInitialised == FALSE) { if (curMovie() == NULL) { return NPERR_NO_ERROR; } // Despite the fact the we initialise the oggplay instance here, the gui // code is responsible for destroying it, because only the gui code knows // when to shut down the old handle after a new media source has been // provided. mOggHandle = initialise_oggplay(this, curMovie(), mProxyHost, mProxyPort); mGuiHandle = initialise_gui(this, aWindow, mOggHandle); mWindow = aWindow; mWindowInitialised = TRUE; #if defined(XP_WIN) HWND hWnd; // set callback data timer on main browser window // and pass pointer to the plugin object instance // to the callback handler procedure NPN_GetValue(mInstance, NPNVnetscapeWindow, &hWnd); SetWindowLongPtr((HWND)hWnd, GWL_USERDATA, (LONG)this); SetTimer((HWND)hWnd, IDT_CALLBACK_TIMER, mPollingPeriod,(TIMERPROC)PluginCallbackProc); mPollingStarted = true; #endif } return NPERR_NO_ERROR; } // Event handling for the GUI uint16 nsPluginInstance::HandleEvent(void *aEvent) { #if defined(XP_MACOSX) NPEvent * event = (NPEvent *)aEvent; // The browser sends a constant stream of events to this function, most of // which are the null event. While this is not exactly efficient, it does // turn out to be useful for a couple of things. // // First, to process any events triggered from the display thread.. processCrossThreadCalls(TRUE); // ..and second, to handle hiding our output when the user changes tabs or // scrolls the media off the page. // // As per the comment in SetWindow, we want to update our clip region when // we receive an update event. This is complicated by the fact that our output // needs to be hidden when the user changes tabs, and the browser does this by // passing us a zero-sized clip in a SetWindow call -- which we're ignoring. // We can't distinguish between a spurious zero-clip-SetWindow and a real one, // and we don't receive an update event for a tab change. So we need to detect // the first null event with a zero-clip and hide our output accordingly. switch (event->what) { case nullEvent: if (mWindow->clipRect.right <= mWindow->clipRect.left || mWindow->clipRect.bottom <= mWindow->clipRect.top) { if (!mOutputCleared) { update_gui_with_new_display_size(mGuiHandle, mWindow); mOutputCleared = TRUE; } } else { mOutputCleared = FALSE; } break; case updateEvt: update_gui_with_new_display_size(mGuiHandle, mWindow); break; } #endif return 1; } #if defined(XP_WIN) VOID CALLBACK PluginCallbackProc(HWND hWnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) { nsPluginInstance * plugin = NULL; plugin = (nsPluginInstance *)GetWindowLongPtr(hWnd, GWL_USERDATA); plugin->processCrossThreadCalls(TRUE); } #endif // ============================== // ! Scriptability related code ! // ============================== // // here the plugin is asked by Mozilla to tell if it is scriptable // we should return a valid interface id and a pointer to // nsScriptablePeer interface which we should have implemented // and which should be defined in the corressponding *.xpt file // in the bin/components folder NPError nsPluginInstance::GetValue(NPPVariable aVariable, void *aValue) { NPError rv = NPERR_NO_ERROR; switch (aVariable) { case NPPVpluginScriptableInstance: { // addref happens in getter, so we don't addref here nsILibOggPlugin * scriptablePeer = getScriptablePeer(); if (scriptablePeer) { *(nsISupports **)aValue = scriptablePeer; } else rv = NPERR_OUT_OF_MEMORY_ERROR; } break; case NPPVpluginScriptableIID: { static nsIID scriptableIID = NS_ILIBOGGPLUGIN_IID; nsIID* ptr = (nsIID *)NPN_MemAlloc(sizeof(nsIID)); if (ptr) { *ptr = scriptableIID; *(nsIID **)aValue = ptr; } else rv = NPERR_OUT_OF_MEMORY_ERROR; } break; #if defined(XP_UX) case NPPVpluginNeedsXEmbed: *((PRBool *)aValue) = PR_TRUE; break; #endif default: rv = NPERR_INVALID_PARAM; break; } return rv; } // ============================== // ! Scriptability related code ! // ============================== // // this method will return the scriptable object (and create it if necessary) nsScriptablePeer* nsPluginInstance::getScriptablePeer() { if (!mScriptablePeer) { mScriptablePeer = new nsScriptablePeer(this); if(!mScriptablePeer) return NULL; NS_ADDREF(mScriptablePeer); } // add reference for the caller requesting the object NS_ADDREF(mScriptablePeer); return mScriptablePeer; } //////////////////////////////////////// // // Private helper functions // void nsPluginInstance::clearCmmlStrings() { for (unsigned int i = 0; i < mCmmlStrings.size(); i++) { free(mCmmlStrings[i]); } mCmmlStrings.clear(); } char * nsPluginInstance::curMovie() const { return mPlaylist[mPlaylistPos]; } bool nsPluginInstance::playlistIndexOk(long index) const { return (index >= 0 && index < (long)mPlaylist.size()); } int nsPluginInstance::playlistLastIndex() const { return (int)mPlaylist.size() - 1; } //////////////////////////////////////// // // Javascript API - standard playback // void nsPluginInstance::getVersionString( char **versionString, int apiVersionMajor, int apiVersionMinor ) { *versionString = (char *)NPN_MemAlloc(100); if (*versionString != NULL) { sprintf(*versionString, "liboggplay (" PLUGIN_VERSION ") Annodex Media Plugin (API %d.%d)", apiVersionMajor, apiVersionMinor ); } } void nsPluginInstance::play() { gui_play(mGuiHandle); } void nsPluginInstance::pause() { gui_pause(mGuiHandle); } void nsPluginInstance::restart() { if (curMovie() != NULL) { mPlaylistFrozen = FALSE; mOggHandle = initialise_oggplay(this, curMovie(), mProxyHost, mProxyPort); update_gui_with_new_oggplay(mGuiHandle, mOggHandle); gui_play(mGuiHandle); } } short nsPluginInstance::getCurrentState() { return gui_get_current_state(mGuiHandle); } void nsPluginInstance::setCurrentMovie(const char *url) { setMovieAt(mPlaylistPos, url); } char * nsPluginInstance::getCurrentMovie() { char *url = NULL; if (curMovie() != NULL) { url = (char *)NPN_MemAlloc(strlen(curMovie()) + 1); if (url != NULL) { strcpy(url, curMovie()); } } return url; } bool nsPluginInstance::setPlayPosition(long milliseconds) { return set_oggplay_play_position(mOggHandle, milliseconds); } long nsPluginInstance::getPlayPosition() { return get_oggplay_play_position(mOggHandle); } void nsPluginInstance::setVolume(float volume) { gui_set_volume(mGuiHandle, volume); } float nsPluginInstance::getVolume() { return gui_get_volume(mGuiHandle); } long nsPluginInstance::getWindowWidth() { return gui_get_window_width(mGuiHandle); } long nsPluginInstance::getWindowHeight() { return gui_get_window_height(mGuiHandle); } long nsPluginInstance::getBufferedTime() { return get_oggplay_available(mOggHandle); } long nsPluginInstance::getMovieLength() { return get_oggplay_duration(mOggHandle); } bool nsPluginInstance::retrieveAnnotations(nsILibOggCallbackString * callback) { return retrieveAnnotationsAt(mPlaylistPos, callback); } //////////////////////////////////////// // // Javascript API - playlist functions // void nsPluginInstance::freezePlaylistProgression() { mPlaylistFreeze = TRUE; } void nsPluginInstance::unfreezePlaylistProgression() { mPlaylistFreeze = FALSE; if (mPlaylistFrozen) { mPlaylistPos++; restart(); } } long nsPluginInstance::getPlaylistLength() { return mPlaylist.size(); } long nsPluginInstance::getCurrentPlaylistPosition() { return mPlaylistPos; } bool nsPluginInstance::playMovieAt(long position) { if (!playlistIndexOk(position)) { return FALSE; } mPlaylistPos = position; restart(); return TRUE; } bool nsPluginInstance::playlistNext() { if (mPlaylistPos == playlistLastIndex()) { return FALSE; } mPlaylistPos++; restart(); return TRUE; } bool nsPluginInstance::playlistPrev() { if (mPlaylistPos == 0) { return FALSE; } mPlaylistPos--; restart(); return TRUE; } char * nsPluginInstance::getMovieAt(long position) { if (!playlistIndexOk(position)) { return NULL; } char *movie = (char *)NPN_MemAlloc(strlen(mPlaylist[position]) + 1); if (movie != NULL) { strcpy(movie, mPlaylist[position]); } return movie; } bool nsPluginInstance::setMovieAt(long position, const char *url) { if (!playlistIndexOk(position)) { return FALSE; } free(mPlaylist[position]); mPlaylist[position] = strdup(url); if (position == mPlaylistPos) { // We can just discard our reference to the old oggplay handle; the gui // code will destroy it before swapping over to the new one. mPlaylistFrozen = FALSE; mOggHandle = initialise_oggplay(this, curMovie(), mProxyHost, mProxyPort); update_gui_with_new_oggplay(mGuiHandle, mOggHandle); } return TRUE; } void nsPluginInstance::appendMovie(const char *url) { mPlaylist.push_back(strdup(url)); } bool nsPluginInstance::insertMovieBefore(long position, const char *url) { if (!playlistIndexOk(position)) { return FALSE; } mPlaylist.insert(mPlaylist.begin() + position, strdup(url)); if (position <= mPlaylistPos) { mPlaylistPos++; } return TRUE; } bool nsPluginInstance::removeMovieAt(long position) { if (!playlistIndexOk(position) || mPlaylist.size() <= 1) { return FALSE; } // If we're removing the current movie, we should start playing the next // one in the playlist. If we're already at the last movie in the playlist, // ideally we'd skip to the end of the previous one and stop playing, but // that's too hard, so we'll just start playing the previous movie. bool startNextMovie = (position == mPlaylistPos); free(mPlaylist[position]); mPlaylist.erase(mPlaylist.begin() + position); // Remember that mPlaylist.size() has been decremented now! if ((position < mPlaylistPos && mPlaylistPos > 0) || (mPlaylistPos == (int)mPlaylist.size())) { mPlaylistPos--; } if (startNextMovie) { restart(); } return TRUE; } bool nsPluginInstance::retrieveAnnotationsAt(long position, nsILibOggCallbackString * callback) { if (!playlistIndexOk(position)) { return FALSE; } if (mAsyncCmmlCallback != NULL) return FALSE; mAsyncCmmlCallback = callback; NS_ADDREF(callback); start_cmml_thread(this, mPlaylist[position], mProxyHost, mProxyPort); return TRUE; } long nsPluginInstance::getMovieLengthAt(long position) { if (!playlistIndexOk(position)) { return -1; } //!todo return -1; } /////////////////////////////////////////// // // Javascript API - callback registration // void nsPluginInstance::registerCMMLCallback(nsILibOggCallbackString *cbObj) { SEM_WAIT(mCrossThreadSem); if (mCmmlCallback != NULL) { NS_RELEASE(mCmmlCallback); } mCmmlCallback = cbObj; if (mCmmlCallback != NULL) { NS_ADDREF(mCmmlCallback); } else { clearCmmlStrings(); } SEM_SIGNAL(mCrossThreadSem); } void nsPluginInstance::registerEndPlayCallback(nsILibOggCallbackNoArg *cbObj) { SEM_WAIT(mCrossThreadSem); if (mEndPlayCallback != NULL) { NS_RELEASE(mEndPlayCallback); } mEndPlayCallback = cbObj; if (mEndPlayCallback != NULL) { NS_ADDREF(mEndPlayCallback); } SEM_SIGNAL(mCrossThreadSem); } void nsPluginInstance::registerPlaylistCallback(nsILibOggCallbackNoArg *cbObj) { SEM_WAIT(mCrossThreadSem); if (mPlaylistCallback != NULL) { NS_RELEASE(mPlaylistCallback); } mPlaylistCallback = cbObj; if (mPlaylistCallback != NULL) { NS_ADDREF(mPlaylistCallback); } SEM_SIGNAL(mCrossThreadSem); } void nsPluginInstance::registerOnMouseClickCallback(nsILibOggCallbackNoArg *cbObj) { SEM_WAIT(mCrossThreadSem); if (this->mMouseClickCallback != NULL) { NS_RELEASE(mPlaylistCallback); } mPlaylistCallback = cbObj; if (mMouseClickCallback != NULL) { NS_ADDREF(mMouseClickCallback); } SEM_SIGNAL(mCrossThreadSem); } //////////////////////////////////////// // // Cross-thread event handling // // Mac and Windows don't like cross-thread calls between the GUI thread and // the main plugin/browser thread, so the callbacks are triggered by the // display thread but must be invoked by the main thread. We do this with a // deferred invocation model: // - The callback notification functions are executed by the display thread, // and simply store their data in the plugin class. // - processCrossThreadCalls is executed at regular intervals by the main // thread, and invokes the Javascript callback objects when needed. // // On the Mac, the "regular intervals" bit is achieved by piggybacking on the // constant stream of browser-originated calls to HandleEvent. On Windows we // use SetTimer to provide an artificial heartbeat. // // Linux doesn't have a problem with cross-thread calls, so we can execute // processCrossThreadCalls directly from the callback notification functions. // // This function gets called both by the display thread (for in-line CMML // annotations) and by a seperate CMML fetching thread (for asynchronous // CMML annotations). // -- Callback notification functions -- // These are executed in the display thread. void nsPluginInstance::onCMMLData(char **cmml_data, int cmml_size, int async) { SEM_WAIT(mCrossThreadSem); if (async) { assert (cmml_size == 1); mAsyncCmmlString = cmml_data[0]; } else { if (mCmmlCallback != NULL) { for (int i = 0; i < cmml_size; i++) { // We need to make copies of the strings because the oggplay frame in // which they're stored can be freed before processCrossThreadCalls // passes them to the browser. mCmmlStrings.push_back(strdup(cmml_data[i])); } } } #if defined(XP_UX) processCrossThreadCalls(FALSE); #endif SEM_SIGNAL(mCrossThreadSem); } void nsPluginInstance::onEndOfMovie() { SEM_WAIT(mCrossThreadSem); mReachedEndOfMovie = TRUE; #if defined(XP_UX) processCrossThreadCalls(FALSE); #endif SEM_SIGNAL(mCrossThreadSem); } void nsPluginInstance::onMovieDownload() { printf("Movie Downloaded\n"); } void nsPluginInstance::onMouseClick() { SEM_WAIT(mCrossThreadSem); mMouseButtonDown = TRUE; printf("Mouse was clicked in plugin area\n"); SEM_SIGNAL(mCrossThreadSem); } // C hooks for the callback notification functions extern "C" { void onCMMLData(nsPluginInstance *i, char **cmml_data, int cmml_size, int async) { i->onCMMLData(cmml_data, cmml_size, async); } void onEndOfMovie(nsPluginInstance *i) { i->onEndOfMovie(); } void onMovieDownload(nsPluginInstance *i) { i->onMovieDownload(); } void onMouseClick(nsPluginInstance *i) { i->onMouseClick(); } } // extern "C" // -- Deferred callback processing -- // This method must protected by mCrossThreadSem; if it's being called // from somewhere already thus protected, set useSemaphore to FALSE. void nsPluginInstance::processCrossThreadCalls(bool useSemaphore) { if (useSemaphore) { SEM_WAIT(mCrossThreadSem); } // Send any cmml strings collected by onCMMLData. if (mCmmlStrings.size() > 0) { if (mCmmlCallback != NULL) { for (unsigned int i = 0; i < mCmmlStrings.size(); i++) { mCmmlCallback->Call(mCmmlStrings[i]); } } clearCmmlStrings(); } if (mAsyncCmmlString != NULL && mAsyncCmmlCallback != NULL) { mAsyncCmmlCallback->Call(mAsyncCmmlString); free(mAsyncCmmlString); NS_RELEASE(mAsyncCmmlCallback); mAsyncCmmlString = NULL; mAsyncCmmlCallback = NULL; } else if (mAsyncCmmlString != NULL) { free(mAsyncCmmlString); mAsyncCmmlString = NULL; } // End-of-movie handling. if (mReachedEndOfMovie) { mReachedEndOfMovie = FALSE; // If we've reached the end of playlist, just throw the end-play callback // and we're done. Otherwise, throw the playlist callback and advance to // the next movie. if (mPlaylistPos == playlistLastIndex()) { if (mEndPlayCallback != NULL) { mEndPlayCallback->Call(); } } else { if (mPlaylistCallback != NULL) { mPlaylistCallback->Call(); } // Don't advance if the user has frozen playlist progression. This is // handled by two separate flags, so the user can change to a different // movie in the playlist and have that start playing, but still freeze // again when it reaches the end. mPlaylistFrozen = mPlaylistFreeze; if (!mPlaylistFrozen) { mPlaylistPos++; restart(); } } } if (mMouseButtonDown) { mMouseButtonDown = FALSE; if (this->mMouseClickCallback != NULL) { mMouseClickCallback->Call(); } } if (useSemaphore) { SEM_SIGNAL(mCrossThreadSem); } }