Media Center Automation: Difference between revisions

From wiki.jriver.com
Jump to navigation Jump to search
No edit summary
No edit summary
 
(27 intermediate revisions by 6 users not shown)
Line 47: Line 47:
* [[MJPlaylistsAutomation]] - represents a collection of playlists
* [[MJPlaylistsAutomation]] - represents a collection of playlists
* [[MJPlaylistAutomation]] - interface for working with a single playlist
* [[MJPlaylistAutomation]] - interface for working with a single playlist
* [[MJSchemeAutomation]] - interface for working with Media Library View Schemes
* [[MJVersionAutomation]] - interface for getting Media Center version information
* [[MJInternetAutomation]] - interface for helping in downloading files from internet
* [[MJViewItemAutomation]] - interface for creating / walking the Media Center tree structure
* [[MJFieldsAutomation]] - interface for working with database fields
* [[MJFieldAutomation]] - interface for working with an individual database field
* [[MJZonesAutomation]] - interface for working with multi-zone playback system
* [[MJZoneAutomation]] - interface for working with a single playback zone (requires MC 11.0.20 or later)
* [[MJTaskAutomation]] - Interface for working with a task
* [[MJServicesAutomation]] - interface for working with services
* [[MJServiceAutomation]] - interface for working with a single service
* [[MJCDDVDAutomation]] - interface for working with CD/DVDs


==Event Handling==
==MJSchemeAutomation - interface for working with Media Library==
MediaCenter version 11.1 and higher supports an event interface for automation clients.
===Functions===
string GetName()
Description: gets the name of the scheme item (i.e. "Classic Rock", "Artist\Album", etc.)
Return Value: a string containing the name of the scheme item


===Basics===
number GetNumberSchemes()
An event is fired upon certain actions with three string parameters:
Description: gets the number of children schemes
FireMJEvent(string1, string2, string3)
Return Value: number of children schemes


The first parameter is the event type and currently there is just one event type, namely "MJEvent type: MCCommand"
MJSchemeAutomation * GetScheme(number nScheme)
The second parameter identifies the MCC command. See the list below.
Description: gets the specified child scheme
The third parameter is optional and may contain information specific to the command.
Parameters:
* nScheme: the index of the child scheme to retrieve (0 to GetNumberSchemes() - 1)
Return Value: a MJSchemeAutomation interface for the specified child scheme


Here are the commands currently sent to the event handler:
MJFilesAutomation * GetFiles()
* '''MCC: NOTIFY_TRACK_CHANGE'''
Description: gets the collection of files in the scheme
** Fired when a new track starts to play
Return Value: MJFilesAutomation interface of files
** ''string3'' is the zone
* '''MCC: NOTIFY_PLAYERSTATE_CHANGE'''
** Fired when a the player state changes
*** A new track starts to play
*** Playback is stopped, paused, or started
** ''string3 '' is the zone
*'''MCC: NOTIFY_PLAYLIST_ADDED'''
** Fired when a playlist is added by the user
** ''string3'' is the playlist ID
*'''MCC: NOTIFY_PLAYLIST_INFO_CHANGED'''
** Fired when a playlist is renamed
** Fired when a smartlist rule is changed
** Fired when a station is added to My Stations
** ''string3'' is the playlist ID
*'''MCC: NOTIFY_PLAYLIST_FILES_CHANGED'''
** Fired when a playlist's files change
** Fired when a service notifies Media Center that the service's playlists need to be refreshed
** ''string3'' is the playlist ID
*'''MCC: NOTIFY_PLAYLIST_REMOVED'''
** Fired when a playlist is removed
** ''string3'' is the ID of the deleted playlist
*'''MCC: NOTIFY_PLAYLIST_COLLECTION_CHANGED'''
** Fired when a playlist is moved or copied
** ''string3'' is not used
*'''MCC: NOTIFY_PLAYLIST_PROPERTIES_CHANGED'''
** Fired when a playlist property is changed
** ''string3'' is the playlist ID
* '''MCC: NOTIFY_SKIN_CHANGED'''
** MC 12.0.211 or higher
** Fired when the skin is changed
** ''string3'' is not used
* '''MCC: NOTIFY_VOLUME_CHANGED'''
** MC 12.0.217 or higher
** Fired when the volume is changed
** ''string3'' is the zone
* '''MCC: NOTIFY_EQ_CHANGED'''
** MCC 12.0.217 or higher
** Fired when the Equalizer settings are changed, or the Equalizer is turned on or off.
** ''string3'' is not used
** Because of internal implementation considerations, you will receive multiple copies of this event for each trigger.


===Event Handling and Zones===
string GetSchemeName(number nScheme)
The events '''MCC: NOTIFY_TRACK_CHANGE''' and '''MCC: NOTIFY_PLAYERSTATE_CHANGE''' contain the zone where the change occurred. Media Center, however, will only notify changes in the currently active zone. If a track changes in the non-active zone, there will be no event. Only track changes in the active zone will have events.
Description: gets the name of the specified child scheme (faster than GetScheme(...))
Parameters:
* nScheme: the index of the child scheme to retrieve (0 to GetNumberSchemes() - 1)
Return Value: a string containing the name of the child scheme item


===Handling the events from Visual Basic===
==MJVersionAutomation - interface for getting Media Center version information==
To add a handler in VB.NET for events coming from Media Center:
===Properties===
number Major (read only)
Description: Major version of the Media Center (i.e. 9)


1. Add a reference to Media Center's type library ("Media Center 12.tlb"). In VB.NET use the Projects/Add Reference option, select the "COM" tab, then select "MediaCenter" from the list.
number Minor (read only)
Description: Minor Version of the Media Center (i.e. 0)


2. Add the "Imports MediaCenter" statement to declaration section of the code.
number Build (read only)
Description: Build number of the Media Center (i.e. 160)


3. In the main class section for your project (e.g. - Form1) do the following:
string Version (read only)
A) Declare your MC automation variable to handle events, like this:
Description: string version of the Media Center (i.e. "9.0.160")
Dim WithEvents MC As MediaCenter.MCAutomation


B) Create a delagate class object and set it up to handle the three parameters passed by MEDIACENTER's object:
==MJInternetAutomation - interface for helping in downloading files from internet==
Private _RelayEvent As RelayEvent
===Functions===
Delegate Sub RelayEvent(ByVal EventData1 As String, ByVal EventData2 As String, ByVal EventData3 As String)
string DownloadToString(string URL)
Description: Downloads web page from the specified URL to a string. (should not be used with binary data, only web pages)
Parameters:
* URL: The URL from which the web page will be downloaded
Return Value: a string containing the contents of the web page.


4. Create a subroutine to receive the event message and to perform actions based on it. It is triggered by the event handler created below in step 4:
number DownloadToFile(string URL, string FileName)
Private Sub MyRelayEvent(ByVal EventData1 As String, ByVal EventData2 As String, ByVal EventData3 As String)
Description: Downloads data from a resource identified by a URL to a local file.
lbParam1.Text = EventData1
Parameters:
lbParam2.Text = Trim(EventData2)
* URL: The URL from which the data will be downloaded.
lbParam3.Text = EventData3
* FileName: The name of the local file to receive the data.
Application.DoEvents()
Return Value: True if the function completed successfully, otherwise FALSE.
End Sub


4. Declare a handler function for the event. In the handler function, your will need to assign the delegate pointer to the subroutine that will actually act on the event. Then use the BeginInvoke function to transfer the event data when it is received:
string DownloadToTempFile(string URL)
Public Sub MC_FireMJEvent(ByVal s0 As String, ByVal s1 As String, ByVal s2 As String) Handles MC.FireMJEvent
Description: Downloads data from a resource identified by a URL to a local temp file.
_RelayEvent = New RelayEvent(AddressOf MyRelayEvent)
Parameters:
BeginInvoke(_RelayEvent, s0, s1, s2)
* URL: The URL from which the data will be downloaded.
End Sub
Return Value: Returns the full path to the temp file.


void Cancel()
Description: Cancels previously called download function. Can be used only when DownloadMode set to DOWNLOADMODE_NO_UI or DOWNLOADMODE_NO_UI_BLOCKING.


5. Events fired from MediaCenter will be received by "MC_FireMJEvent" and it will use the delegate pointer to trigger "MyRelayEvent", which can act on the event.
===Properties===
MJInetDownloadModes DownloadMode (read / write)
Description: Gets or sets download mode.
* DOWNLOAD_MODE_DEFAULT - displays UI while downloading files from the Internet
* DOWNLOADMODE_NO_UI - does not display UI but processes the message loop so the Cancel function can be called from the same thread.
* DOWNLOADMODE_BLOCKING - will lock the app until the download is finished.
* DOWNLOAD_MODE_NO_AUTHENTICATION - disables authentication.
Example:
MJIA.DownloadMode = DOWNLOADMODE_NO_UI DOWNLOADMODE_BLOCKING;


Note: The event handler is automatically created in a separate thread from the main routine/class. One thread cannot make changes to another thread, so it is necessary to use the delegate pointer to make the main thread aware that the event occured and to receive the information. Then the receiving subroutine, which is in the main thread, can make updates without creating an exception error.
==MJViewItemAutomation - interface for creating / walking the Media Center tree structure==
===Functions===
string GetName()
Description: gets the name of this item
Return Value: the name of the item


===Handling the events from Visual C++ with MFC===
string GetFullName()
In your C++ MFC application, you should have a class which is derived from CCmdTarget (or from CWnd or CDialog which derive from CCmdTarget). This class will sink the events from MC's automation object. In the sample code below, this class is called "CMyClass", substitute your class's name for CMyClass.
Description: gets the full name of this item (the whole path -- backslash delimited -- i.e. Media Library\Audio\Artist/Album\Bob Dylan)
In the header file of the class, add the following import using the correct tlb path for your system. Also add the afxctl.h include file if you get warnings of undefined AfxConnectionAdvise and AfxConnectionUnadvise.
Return Value: the full name of the item


#import "C:\\Program Files\\J River\\Media Center 12\\Media Center 12.tlb" no_namespace, named_guids
MJFilesAutomation * GetFiles()
#include "afxctl.h"
Description: gets the files associated with the current item
Return Value: MJFilesAutomation interface


Then within the header file's class declaration for CMyClass, add this code:
void DoCommand(number nParam)
<pre>
Description: performs the command associated with the current item (play / show, playing now command, etc.)
DECLARE_DISPATCH_MAP()
Parameters:
DECLARE_INTERFACE_MAP()
* nParam: for future use -- must be zero
afx_msg void MJEvent(LPCTSTR strType, LPCTSTR strParam1, LPCTSTR strParam2);
IMJAutomationPtr m_pMJ;
DWORD m_SinkID;
</pre>


Now in the cpp file for the class, add these lines which connect your handler function to the correct entry in the dispatch map:
number GetNumberChildren()
<pre>
Description: gets the number of child items
BEGIN_DISPATCH_MAP(CMyClass, CCmdTarget)
Return Value: the number of children
DISP_FUNCTION_ID(CMyClass,"MJEvent",1,MJEvent,VT_EMPTY,VTS_BSTR VTS_BSTR VTS_BSTR)
END_DISPATCH_MAP()


BEGIN_INTERFACE_MAP(CMyClass, CCmdTarget)
string GetChildName(number nIndex)
INTERFACE_PART(CMyClass, DIID_IMJAutomationEvents, Dispatch)
Description: gets the name of a child item
END_INTERFACE_MAP()
Parameters:
</pre>
* nIndex: the index of the child item (0 to GetNumberChildren() - 1)
Return Value: the name of the child item


Now add the following code to the constructor or wherever you do initialization:
MJViewItemAutomation * GetChild(number nIndex)
<pre>
Description: gets the MJViewItemAutomation interface (see below) for the given child index
CoInitialize(NULL);
Parameters:
EnableAutomation();
* nIndex: the index of the child to retrieve (0 to GetNumberChildren() - 1)
HRESULT hr = m_pMJ.GetActiveObject (L"MediaJukebox Application");
Return Value: MJViewItemAutomation interface
if (hr != S_OK)
m_pMJ.CreateInstance(L"MediaJukebox Application");


// Get a pointer to the sink IDispatch interface
boolean GetChildHasChildren(number nIndex)
LPUNKNOWN pUnknownSink = GetIDispatch(FALSE);
Description: gets the name of a child item
Parameters:
* nIndex: the index of the child item (0 to GetNumberChildren() - 1)
Return Value: whether the specified child has children of its own


// Connect the event source (MJ automation) to the sink
MJViewItemAutomation * GetParent()
AfxConnectionAdvise(m_pMJ, DIID_IMJAutomationEvents, pUnknownSink, FALSE, &m_SinkID);
Description: gets the parent item
</pre>
Return Value: MJViewItemAutomation interface


Add the function body which receives the events:
string GetThumbnailFilenames(number nThumbnailSize, number nMaxCount)
Description: not implemented
Parameters:
* nThumbnailSize: N/A
* nMaxCount: N/A
Return Value: N/A


<pre>
boolean GetChildIsFolder(number nIndex)
// MJAutomationSink message handler
Description: determine if the specified child contains sub-view items
void CMyClass::MJEvent(LPCTSTR strType, LPCTSTR strParam1, LPCTSTR strParam2)
Parameters:
{
* nIndex: the number of the child
// handle events here...
Return Value: true only if the child contains sub-view items
}
</pre>


And finally, add this code to the destructor or wherever you do final cleanup. The release for m_pMJ has to be called prior to CoUninitialize.
boolean GetIsSearch()
<pre>
Description: not implemented
LPUNKNOWN pUnknownSink = GetIDispatch(FALSE);
Return Value: FALSE
AfxConnectionUnadvise(m_pMJ, DIID_IMJAutomationEvents, pUnknownSink, FALSE, m_SinkID);
m_pMJ.Release();
CoUninitialize();
</pre>


===Handling the Events from Visual C++ with ATL===
==MJFieldsAutomation - interface for working with database fields==
If you have a conventional Interface plugin that was generated using the ATL DLL and ATL composite controls, you can also sink MediaCenter's events. Simply implement the IMJAutomationEvents (perhaps using class view->Implement Interface) using the IDispEventImpl template. The wizard will default to IDispatch, but you will need to use IDispEventImpl and the SINK_MAP functions to handle events correctly.
===Functions===
number GetNumberFields()
Description: gets the total number of database fields
Return Value: the number of fields


Ensure that the type library is imported as for the MFC version above. Set your SINK_MAP as follows:
IMJFieldAutomation * GetField(number nField)
Description: gets the field at the given index
Parameters:
* nField: the zero based index of the field
Return Value: MJFieldAutomation * to the field (see documentation below)


<pre>
IMJFieldAutomation * CreateFieldSimple(string strFieldName, string strDisplayName, number bAllowEdit, long bSaveInTag)
BEGIN_SINK_MAP(CEQdbCtrl)
Description: Creates a new field
SINK_ENTRY_EX(1, DIID_IMJAutomationEvents, 1, MJEvent)
Parameters:
END_SINK_MAP()
* strFieldName: the internal name of the new field
</pre>
* strDisplayName: the name displayed to the user
* bAllowEdit: can field be changed by user
* bSaveInTag: save the field in the tag in the content file
Return Value: the FieldAutomation interface for the new field


and then implement the MJEvent handler function
==MJFieldAutomation - interface for working with an individual database field==
===Functions===
string GetName(boolean bFormatted)
Description: gets the name of the field
Parameters:
* bFormatted: 0 = returns the unique name of the field as used in DB lookups, 1 = returns the display name of the field (not necessarily unique)
Return Value: the field name


<pre>
==MJZonesAutomation - interface for working with multi-zone playback system==
STDMETHOD_(void, MJEvent)(LPCTSTR strType, LPCTSTR strParam1, LPCTSTR strParam2);
===Functions===
</pre>
number GetNumberZones()
Description: gets the total number of playback zones
Return Value: the number of playback zones


Also, you need to advise the sink after attaching to the passed IDispatch* in your Init function.
number GetActiveZone()
Description: gets the index of the active zone
Return Value: index of active zone


<pre>
void SetActiveZone(number nIndex)
HRESULT hr;
Description: sets the index of the active zone.
hr = DispEventAdvise(m_pMJ, &DIID_IMJAutomationEvents);
* nIndex: the zero based index of the new zone
if(hr == S_OK) {
// Successfully advised.
} else {
// Did not advise correctly.
}
</pre>


Here's the (somewhat) tricky part. Using the CComComposite class means that AtlAdviseSinkMap is called during the window constructor, and it will cause an ATLASSERT failure during debug builds. This produces the "Abort" "Retry" "Ignore" message box stating there's been an assertion failure in atlcom.h. Click ignore. Once you move to release builds, it will compile to nothing and will not cause you any trouble.
string GetZoneName(number nIndex)
Description: gets the name of a given zone
Parameters:
* nIndex: the zero based index of the new zone
Return Value: the name of the zone


[[Category:Developer]]
MJZoneAutomation * GetZone(number nIndex) (requires MC 11.0.20 or later)
Description: gets a MJZoneAutomation interface for this zone (see below)
Return Value: MJZoneAutomationinterface

void SynchronizeZones(number nSource, number nDestination)
Description: attempts to have the two zones play the same content at the same time
Parameters:
* nSource: the master zone
* nDestination: the slave zone

==MJZoneAutomation - interface for working with a single playback zone (requires MC 11.0.20 or later)==
===Functions===

string GetName()

Description: gets the name of the zone
Return Value: the name of the zone

MJPlaybackAutomation * GetPlayback()

Description: gets a MJPlayback interface for this zone (see above)
Return Value: MJPlayback interface

MJCurPlaylistAutomation * GetCurPlaylist()

Description: gets a MJCurPlaylistAutomation interface for this zone (see above)
Return Value: MJCurPlaylistAutomation interface

IMJMixerAutomation * GetMixer()

Description: gets a MJMixerAutomation interface for this zone
Return Value: the interface

IMJFileAutomation * GetPlayingFile()

Description: get a MJFileAutomation interface for the file currently playing in this zone
Return Value: the interface

==MJTaskAutomation - Interface for working with a task==
===Functions===

void Pump()

Description: pumps the task's message loop

==MJServicesAutomation - interface for working with services==
===Functions===

VARIANT_BOOL GetLicense(string bstrFilename)

Description: Get a license for a content file
Parameters:

bstrFilename: the name of the content file

Return Value: true if the license was retrieved, false otherwise

IMJServiceAutomation *GetService(string bstrServiceName)

Description: Get a service by name
Parameters:

bstrServiceName: the name of the service

Return Value: a MJServiceAutomation interface

==MJServiceAutomation - interface for working with a single service==
===Functions===

BOOL Process(string bstrCurrentURL, string bstrNewURL, string bstrPostData)

Description: calls a service's Process function
Parameters:

bstrCurrentURL: the current URL for the service
bstrNewURL: the URL to visit
bstrPostData: empty or the name of a file containing POST data, the file will be deleted

Return Value: TRUE on success, FALSE otherwise

BOOL Execute(string bstrCommand, VARIANT vFileDisp, string *bstrResult)

Description: calls a service's Execute function
Parameters:

bstrCommand: the command
vFileDisp: MJFilesAutomation interface
bstrResult: a pointer to the result

Return Value: TRUE on success, FALSE otherwise

LPDISPATCH GetWebBrowserHTMLDocument()

Description: get the currently viewed service web page
Return Value: an IDispatch pointer to an IHTMLDocument2 interface

==MJCDDVDAutomation - interface for working with CD/DVDs==
===Functions===
IMJFilesAutomation *GetFiles(string strPath)

Description: Get the files on the device named
Parameters:

strPath: the name of the device

Return Value: MJFilesAutomation interface

Latest revision as of 15:30, 7 April 2014

Media Center exports much of its power and functionality through COM automation interfaces. This makes it easy to create your own plug-ins for Media Center that integrate tightly with its interface. Since automation interfaces are based on COM, these plug-ins can be written in almost any language. (C++, VB, Delphi, etc.). Also, the interfaces can be accessed in Metamorphis skins and Display plug-ins (see documentation for the plug-ins). Prior to build 9.1.238, the only way to access the automation interfaces was to write a plug-in for MC because the interfaces were not accessible by objects which were outside Media Center's process. Now, any application, including VBS and Java script, can start MC or just run it to query information about MC's database.

The functions in this document are for Media Center 11.1. Most will work with earlier versions, but not all.

Initialization

Media Center can be initialized by in-proc or out-of-proc objects.

Out-of-proc initialization (C++):

#import "Media Jukebox.tlb" no_namespace, named_guids

void GetMJAutomation()
{
    IMJAutomationPtr pMJ;

    HRESULT hr = pMJ.GetActiveObject (L"MediaJukebox Application");
    if (hr != S_OK)
        pMJ.CreateInstance(L"MediaJukebox Application");
}

Out-of-proc initialization (VB):

Private Sub Form_Load()
' First try to get an already running object
On Error Resume Next
Set myobj = GetObject(, "MediaJukebox Application")

If Err.Number = 429 Then
    'Then, create a new object
    Set myobj = CreateObject("MediaJukebox Application")

End Sub

NOTE: If Media Center was created as out-of-proc object, the main window of the program will be invisible. To show the window use ShowProgram function from MJAutomation interface.

For information on how to get access to MJAutomation interface from in-proc plug-ins see plug-in SDK.

Automation Objects

Event Handling

MediaCenter version 11.1 and higher supports an event interface for automation clients.

Basics

An event is fired upon certain actions with three string parameters:

FireMJEvent(string1, string2, string3)

The first parameter is the event type and currently there is just one event type, namely "MJEvent type: MCCommand" The second parameter identifies the MCC command. See the list below. The third parameter is optional and may contain information specific to the command.

Here are the commands currently sent to the event handler:

  • MCC: NOTIFY_TRACK_CHANGE
    • Fired when a new track starts to play
    • string3 is the zone
  • MCC: NOTIFY_PLAYERSTATE_CHANGE
    • Fired when a the player state changes
      • A new track starts to play
      • Playback is stopped, paused, or started
    • string3 is the zone
  • MCC: NOTIFY_PLAYLIST_ADDED
    • Fired when a playlist is added by the user
    • string3 is the playlist ID
  • MCC: NOTIFY_PLAYLIST_INFO_CHANGED
    • Fired when a playlist is renamed
    • Fired when a smartlist rule is changed
    • Fired when a station is added to My Stations
    • string3 is the playlist ID
  • MCC: NOTIFY_PLAYLIST_FILES_CHANGED
    • Fired when a playlist's files change
    • Fired when a service notifies Media Center that the service's playlists need to be refreshed
    • string3 is the playlist ID
  • MCC: NOTIFY_PLAYLIST_REMOVED
    • Fired when a playlist is removed
    • string3 is the ID of the deleted playlist
  • MCC: NOTIFY_PLAYLIST_COLLECTION_CHANGED
    • Fired when a playlist is moved or copied
    • string3 is not used
  • MCC: NOTIFY_PLAYLIST_PROPERTIES_CHANGED
    • Fired when a playlist property is changed
    • string3 is the playlist ID
  • MCC: NOTIFY_SKIN_CHANGED
    • MC 12.0.211 or higher
    • Fired when the skin is changed
    • string3 is not used
  • MCC: NOTIFY_VOLUME_CHANGED
    • MC 12.0.217 or higher
    • Fired when the volume is changed
    • string3 is the zone
  • MCC: NOTIFY_EQ_CHANGED
    • MCC 12.0.217 or higher
    • Fired when the Equalizer settings are changed, or the Equalizer is turned on or off.
    • string3 is not used
    • Because of internal implementation considerations, you will receive multiple copies of this event for each trigger.

Event Handling and Zones

The events MCC: NOTIFY_TRACK_CHANGE and MCC: NOTIFY_PLAYERSTATE_CHANGE contain the zone where the change occurred. Media Center, however, will only notify changes in the currently active zone. If a track changes in the non-active zone, there will be no event. Only track changes in the active zone will have events.

Handling the events from Visual Basic

To add a handler in VB.NET for events coming from Media Center:

1. Add a reference to Media Center's type library ("Media Center 12.tlb"). In VB.NET use the Projects/Add Reference option, select the "COM" tab, then select "MediaCenter" from the list.

2. Add the "Imports MediaCenter" statement to declaration section of the code.

3. In the main class section for your project (e.g. - Form1) do the following:

  A) Declare your MC automation variable to handle events, like this:
        Dim WithEvents MC As MediaCenter.MCAutomation
  B) Create a delagate class object and set it up to handle the three parameters passed by MEDIACENTER's object:
        Private _RelayEvent As RelayEvent
        Delegate Sub RelayEvent(ByVal EventData1 As String, ByVal EventData2 As String, ByVal EventData3 As String)

4. Create a subroutine to receive the event message and to perform actions based on it. It is triggered by the event handler created below in step 4:

  Private Sub MyRelayEvent(ByVal EventData1 As String, ByVal EventData2 As String, ByVal EventData3 As String)
     lbParam1.Text = EventData1
     lbParam2.Text = Trim(EventData2)
     lbParam3.Text = EventData3
     Application.DoEvents()
  End Sub

4. Declare a handler function for the event. In the handler function, your will need to assign the delegate pointer to the subroutine that will actually act on the event. Then use the BeginInvoke function to transfer the event data when it is received:

  Public Sub MC_FireMJEvent(ByVal s0 As String, ByVal s1 As String, ByVal s2 As String) Handles MC.FireMJEvent
     _RelayEvent = New RelayEvent(AddressOf MyRelayEvent)
     BeginInvoke(_RelayEvent, s0, s1, s2)
  End Sub


5. Events fired from MediaCenter will be received by "MC_FireMJEvent" and it will use the delegate pointer to trigger "MyRelayEvent", which can act on the event.

Note: The event handler is automatically created in a separate thread from the main routine/class. One thread cannot make changes to another thread, so it is necessary to use the delegate pointer to make the main thread aware that the event occured and to receive the information. Then the receiving subroutine, which is in the main thread, can make updates without creating an exception error.

Handling the events from Visual C++ with MFC

In your C++ MFC application, you should have a class which is derived from CCmdTarget (or from CWnd or CDialog which derive from CCmdTarget). This class will sink the events from MC's automation object. In the sample code below, this class is called "CMyClass", substitute your class's name for CMyClass. In the header file of the class, add the following import using the correct tlb path for your system. Also add the afxctl.h include file if you get warnings of undefined AfxConnectionAdvise and AfxConnectionUnadvise.

 #import "C:\\Program Files\\J River\\Media Center 12\\Media Center 12.tlb" no_namespace, named_guids
 #include "afxctl.h"

Then within the header file's class declaration for CMyClass, add this code:

  DECLARE_DISPATCH_MAP()
  DECLARE_INTERFACE_MAP()
  afx_msg void MJEvent(LPCTSTR strType, LPCTSTR strParam1, LPCTSTR strParam2);
  IMJAutomationPtr m_pMJ;
  DWORD m_SinkID;

Now in the cpp file for the class, add these lines which connect your handler function to the correct entry in the dispatch map:

  BEGIN_DISPATCH_MAP(CMyClass, CCmdTarget)
    DISP_FUNCTION_ID(CMyClass,"MJEvent",1,MJEvent,VT_EMPTY,VTS_BSTR VTS_BSTR VTS_BSTR)
  END_DISPATCH_MAP()

  BEGIN_INTERFACE_MAP(CMyClass, CCmdTarget)
    INTERFACE_PART(CMyClass, DIID_IMJAutomationEvents, Dispatch)
  END_INTERFACE_MAP()

Now add the following code to the constructor or wherever you do initialization:

  CoInitialize(NULL);
  EnableAutomation();
  HRESULT hr = m_pMJ.GetActiveObject (L"MediaJukebox Application");
  if (hr != S_OK)
    m_pMJ.CreateInstance(L"MediaJukebox Application");

  // Get a pointer to the sink IDispatch interface
  LPUNKNOWN pUnknownSink = GetIDispatch(FALSE);

  // Connect the event source (MJ automation) to the sink
  AfxConnectionAdvise(m_pMJ, DIID_IMJAutomationEvents, pUnknownSink, FALSE, &m_SinkID);

Add the function body which receives the events:

  // MJAutomationSink message handler
  void CMyClass::MJEvent(LPCTSTR strType, LPCTSTR strParam1, LPCTSTR strParam2)
  {
    // handle events here...
  }

And finally, add this code to the destructor or wherever you do final cleanup. The release for m_pMJ has to be called prior to CoUninitialize.

  LPUNKNOWN pUnknownSink = GetIDispatch(FALSE);
  AfxConnectionUnadvise(m_pMJ, DIID_IMJAutomationEvents, pUnknownSink, FALSE, m_SinkID);
  m_pMJ.Release();
  CoUninitialize();

Handling the Events from Visual C++ with ATL

If you have a conventional Interface plugin that was generated using the ATL DLL and ATL composite controls, you can also sink MediaCenter's events. Simply implement the IMJAutomationEvents (perhaps using class view->Implement Interface) using the IDispEventImpl template. The wizard will default to IDispatch, but you will need to use IDispEventImpl and the SINK_MAP functions to handle events correctly.

Ensure that the type library is imported as for the MFC version above. Set your SINK_MAP as follows:

BEGIN_SINK_MAP(CEQdbCtrl)
  SINK_ENTRY_EX(1, DIID_IMJAutomationEvents, 1, MJEvent)
END_SINK_MAP()

and then implement the MJEvent handler function

STDMETHOD_(void, MJEvent)(LPCTSTR strType, LPCTSTR strParam1, LPCTSTR strParam2);

Also, you need to advise the sink after attaching to the passed IDispatch* in your Init function.

HRESULT hr;
hr = DispEventAdvise(m_pMJ, &DIID_IMJAutomationEvents);
if(hr == S_OK) {
  // Successfully advised.
} else {
  // Did not advise correctly.
}

Here's the (somewhat) tricky part. Using the CComComposite class means that AtlAdviseSinkMap is called during the window constructor, and it will cause an ATLASSERT failure during debug builds. This produces the "Abort" "Retry" "Ignore" message box stating there's been an assertion failure in atlcom.h. Click ignore. Once you move to release builds, it will compile to nothing and will not cause you any trouble.