Wiki Navigation
- Loading...
MediaPortal is written in C#, and although you can write a plugin with any .NET language this tutorial will cover how to write a plugin in C# using one of the following programming environments:
- Visual Studio 2010 (commercial)
- Visual C# 2010 Express (free, closed source)
- SharpDevelop 4.0 (open source)
Creating the MediaPortal Plugin Skeleton
MediaPortal supports several types of plugins. You can use each of these types to extend MediaPortal in a specific way. The plugin types are:
Plugin types
- Process plugins are plugins without any user interface, and do work in the background. Examples of process plugins:
- Caller id plugin
- LCD/VFD plugin
- Plugins remote controls
- Tag Reader Plugins are used for reading media tags from media files, for example from MP3 and WMA files.
- External Player Plugins are used for playing media using external applications, for example WinAmp, Foobar and iTunes.
- GUI (or Window) Plugins are the most interesting. As the name suggest they contain a user interface and allow the user to interact with them. Examples:
- Music Plugin
- TV Plugin
- Pictures Plugin
In this tutorial we'll focus on how to create your own GUI plugin. Documentation of how to create another type of plugin may follow later.
For further details, see Reference > Plugins
Creating a new project
We start with creating a new project. Choose File -> New Project (File -> New -> Solution in SharpDevelop) from the menu.
In Visual Studio and SharpDevelop, select the (Visual) C# tree-item. (Express Edition users have no choice of language.) Now select the Class Library template.
Fill in a Name, when using Visual Studio specify a location, and press OK (or Create in SharpDevelop).
Visual Studio and Visual C# users may now get a Security Warning. Choose Load project normally (Visual C#) or just click OK (Visual Studio).
A new project for our plugin has now been created. If you are using Visual C#, save your project now using File -> Save All. (If you don't, your plugin will be compiled in a temporary location.)
Users of Visual Studio 2010 / Visual Studio Express 2010 or later: In order for the DLL to be compatible with MediaPortal you must change the project's target framework from 4 to 3.5. To do this goto Project->Project Name Setttings, select the Application tab and choose*.Net Framework 3.5' from the target framework drop-list.
Adding references to the MediaPortal interfaces
Next we'll need to tell it that this is going to be a plugin for MediaPortal. To do this we'll add a reference to the MediaPortal core.dll and dialogs.dll which defines all the interfaces needed. We will also add a reference to the Windows Forms dll.
Choose Project -> Add Reference.
On the .NET tabsheet (GAC in SharpDevelop), select System.Windows.Forms. SharpDevelop users now press the Select button to add the reference. Visual Studio and Visual C# users press OK, and have to re-open the Add Reference dialog to add the next reference.
Now go to the Browse tabsheet (.NET Assembly Browser in SharpDevelop), and select the core.dll file which you can find in your MediaPortal folder.
Note: If you use Visual Studio 2010, you can't select System.Windows.Forms from the .NET tab. Instead you have to browse (see below) to the directory of the .NET installation usually "%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client" and add the System.Windows.Forms.dll.
Now go to the Browse tabsheet (.NET Assembly Browser in SharpDevelop), and select the dialogs.dll file which you can find in your MediaPortal\plugins\windows folder.
Next in the source code add
- using System.Windows.Forms;
- using MediaPortal.GUI.Library;
- using MediaPortal.Dialogs;
The code should now look more or less like this:
using System; using System.Windows.Forms; using MediaPortal.GUI.Library; using MediaPortal.Dialogs; namespace OurPlugin { public class Class1 { } }
Also be sure to add the references/code for the new Version Compatability introduced in MP 1.2.0.
Implementing GUIWindow and ISetupForm
To make our plugin a GUI plugin for MediaPortal, we need to derive it from GUIWindow and implement the ISetupForm interface. The GUIWindow class in MediaPortal is the basic class for a screen. It knows how to load/render a skin file and handle user actions. By implementing the ISetupForm interface MediaPortal will recognize this class as a new plugin. Using this interface we can tell MP about the plugin name, type etc. Both will be discussed in more detail later.
Each window / screen has it's own unique id. This example uses 5678, use another code for your plugins. The same id should be in the skin file.
Change the code so it looks like this:
using System; using System.Windows.Forms; using MediaPortal.GUI.Library; using MediaPortal.Dialogs; namespace OurPlugin { public class Class1 : GUIWindow, ISetupForm { public Class1() { } #region ISetupForm Members // Returns the name of the plugin which is shown in the plugin menu public string PluginName() { return "MyFirstPlugin"; } // Returns the description of the plugin is shown in the plugin menu public string Description() { return "My First Plugin"; } // Returns the author of the plugin which is shown in the plugin menu public string Author() { return "YourNameHere"; } // show the setup dialog public void ShowPlugin() { MessageBox.Show("Nothing to configure, this is just an example"); } // Indicates whether plugin can be enabled/disabled public bool CanEnable() { return true; } // Get Windows-ID public int GetWindowId() { // WindowID of windowplugin belonging to this setup // enter your own unique code return 5678; } // Indicates if plugin is enabled by default; public bool DefaultEnabled() { return true; } // indicates if a plugin has it's own setup screen public bool HasSetup() { return true; } /// <summary> /// If the plugin should have it's own button on the main menu of MediaPortal then it /// should return true to this method, otherwise if it should not be on home /// it should return false /// </summary> /// <param name="strButtonText">text the button should have</param> /// <param name="strButtonImage">image for the button, or empty for default</param> /// <param name="strButtonImageFocus">image for the button, or empty for default</param> /// <param name="strPictureImage">subpicture for the button or empty for none</param> /// <returns>true : plugin needs it's own button on home /// false : plugin does not need it's own button on home</returns> public bool GetHome(out string strButtonText, out string strButtonImage, out string strButtonImageFocus, out string strPictureImage) { strButtonText = String.Empty; strButtonImage = String.Empty; strButtonImageFocus = String.Empty; strPictureImage = String.Empty; return false; } // With GetID it will be an window-plugin / otherwise a process-plugin // Enter the id number here again public override int GetID { get { return 5678; } set { } } #endregion } }
Compiling the plugin
Time to see some action. Compile the plugin by choose Build -> Build solution from the menu. Again you should see that compilation was successful with 0 errors.
Now we're going to test the plugin in MediaPortal!
Testing the plugin
MediaPortal will look for plugins in it's
plugin
subfolder. To get it to notice our new plugin, we have to copy it first. Copy the plugin from either
bin\release\OurPlugin.dll
or
bin\debug\OurPlugin.dll
underneath your project folder (this depends on your project settings), to
plugins\windows\OurPlugin.dll
underneath your MediaPortal application folder.
Now, start MediaPortal Setup and go to the plugins section. Guess what? Our plugin shows up!
Notice it's showing the plugin name, author, description as we told it and that we can enable/disable our plugin. Now let's try the setup. Select our plugin and hit the setup button and we see:
Well the basics are working. We got our plugin registered in MP, we can assign it a name, author, description and if needed we can add any setup screens for the plugin.
In the next chapter, we're going to add some GUI to the plugin so it actually becomes usefull.
Adding GUI to our plugin
To add a nice GUI to our plugin we'll need to do things:
- Create a skin file.
- Add some code to load the skin file.
Creating a skin file
All GUI in MediaPortal is defined in skin files. A skin file is a simple XML file which describes wha the GUI should look like. Although the details are very different, the idea is the same as for making a webpage in HTML. For more details see SkinArchitecture.
For creating a skin file we can use a text-editor like Notepad, or our programming environment (Visual Studio/SharpDevelop). A skin editor is in progress and you can use a preliminary version, but for now we'll do it by hand. Create a new file and enter the following code:
<window> <id>5678</id> <defaultcontrol>2</defaultcontrol> <allowoverlay>yes</allowoverlay> <controls> <control> <description>BackGround</description> <type>image</type> <id>4</id> <posX>0</posX> <posY>0</posY> <width>720</width> <height>576</height> <texture>background.png</texture> </control> <control> <description>an Image</description> <type>image</type> <id>5</id> <posX>75</posX> <posY>370</posY> <texture>hover_my videos.png</texture> </control> <control> <description>text label</description> <type>label</type> <id>1</id> <posX>250</posX> <posY>70</posY> <label>Some text</label> <font>font16</font> <align>right</align> <textcolor>ffffffff</textcolor> </control> <control> <description>Try Me</description> <type>button</type> <id>2</id> <posX>60</posX> <posY>97</posY> <label>Try Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>3</ondown> </control> <control> <description>Or Me</description> <type>button</type> <id>3</id> <posX>60</posX> <posY>131</posY> <label>Or Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>2</ondown> </control> </controls> </window>
Save the file in skin\Blue3\OurPlugin.xml underneath your MediaPortal application folder. Before discussing the xml, here's a picture of what this skin file produces:
The Skin file
Each skin file starts with the following section:
<window> <id>5678</id> <defaultcontrol>2</defaultcontrol> <allowoverlay>yes</allowoverlay> <controls>
Here you see again the id value we used earlier.
The <defaultcontrol> specifies which control will have the focus when the user enters the screen. As you can see in the screenshot our skin file has several controls:
- a blue background
- a label with the text ‘Some text'
- 2 buttons
- 1 picture showing a person with popcorn
Each control is specified in the xml file and each control has an <id>. In this case the default control is the control which has id 2, which is the 1st button
The <allowoverlay> specifies if MediaPortal is allowed to draw video/TV/music preview screens in the bottom right hand corner. By setting it to false you make sure there will be no preview windows drawn. When it's set to true, MP will show the preview window when it's playing a media file.
All the GUI controls are put between the <controls> and </controls> tags.
Image control
<control> <description>BackGround</description> <type>image</type> <id>4</id> <posX>0</posX> <posY>0</posY> <width>720</width> <height>576</height> <texture>background.png</texture> </control>
This section specifies the background control. The <description> tag is optional and only put here for clarity. Each control has a <type> tag which specifies the type of control. You can check references.xml for all types of controls. Since this is a background image we set it to type image.
Next is the <id> which we already mentioned before. I set it to 4, but you can use any (positive) number you want. The id will couple the skin file to the code, so if we later on want to check that a user pressed a button, the id will be needed. Therefore it's always a wise decision to make the control id's unique within a skin file
<posX> and <posY>: These give the x,y position of the upper left point of the image.
<width> and <height>: Give the width/height of the image.
<texture> specifies which image file to use. You can use .png, .jpg and .gif.
The image file should be in the skin's media folder. So if you for example are using the Blue3 skin then this image file should be stored in skin\Blue3\media underneath your MediaPortal application folder.
The following control specifies the image of our popcorn man:
<control> <description>an Image</description> <type>image</type> <id>5</id> <posX>75</posX> <posY>370</posY> <texture>hover_my videos.png</texture> </control>
It's basically the same as the background control. Notice we didn't fill in the <width> and <height> tags. If you don't supply this then MediaPortal simply will use the dimensions of the texture file itself.
Text Label Control
<control> <description>text label</description> <type>label</type> <id>1</id> <posX>250</posX> <posY>70</posY> <label>Some text</label> <font>font16</font> <align>right</align> <textcolor>ffffffff</textcolor> </control>
The text label control can be used to draw text on-screen. The type is label and you'll see the <posX> and <posY> tags again for specifying the position.
The <label> tag should contain the text to be presented. This can be normal text as shown above or a string id from the MediaPortal\Language(language)\strings.xml file.
<align> specifies how the text is aligned, can be left, centered or right.
<textcolor> specifies the color in AARRGGBB notation (hex).
Button Control
<control> <description>Try Me</description> <type>button</type> <id>2</id> <posX>60</posX> <posY>97</posY> <label>Try Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>3</ondown> </control> <control> <description>Or Me</description> <type>button</type> <id>3</id> <posX>60</posX> <posY>131</posY> <label>Or Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>2</ondown> </control>
These 2 controls represent the 2 buttons. The type here is button and you'll see the <posX>, <posY>, <label> tags again which behave the same as explained above.
Next are the <onleft>, <onright>, <onup> and <ondown> tags. These tags specify what happens when the user hits one of the cursor keys. For example if the user is on the top button and you press down, you would like to have the focus moved to the lower button. You can do this by editing the <ondown> tag of the 1st button. In there you place the <id> of the control which should get the focus. The same holds for the bottom button. If you press up, the focus should go to the upper button. Therefore we set the <onup> tag of the bottom button to the <id> of the top button.
For more detailed information on creating skin files, read Skin Architecture.
Modifying the code to use the skin file
Now that we have a skin file, we must load it from our plugin. This can be done by editing the source code and adding the following method:
public override bool Init() { return Load(GUIGraphicsContext.Skin+@"\ourplugin.xml"); }
The Init() function will be called by MediaPortal when it wants to load your plugin. In there we simply ask it to load our xml file.
We also would like to have our plugin show up in the main menu of MediaPortal. For this we change the GetHome() method so it becomes:
public bool GetHome(out string strButtonText, out string strButtonImage, out string strButtonImageFocus, out string strPictureImage) { strButtonText = PluginName(); strButtonImage = String.Empty; strButtonImageFocus = String.Empty; strPictureImage = String.Empty; return true; }
Now recompile the plugin and copy it again to plugins\windows. Then go back to MediaPortal configuration and change the plugin display to Home menu but changing the option to 'Listed in Home'.
Start MediaPortal and voilà, our plugin shows in the Home menu:
Select the plugin and hit Enter. Your plugin appears!
Notice you can switch between the two buttons and that you can get back to the home screen by pressing Escape. Pretty neat huh? The buttons themselves don't do anything yet when pressed, but that's our next task.
Note: The graphic that appears when the menu entry for your plugin is highlighted, is hover_pluginname.png. This is independent from what is defined in the skin xml file.
Catching user actions
So we managed to get our plugin to show a screen, but it's still pretty useless. When you press a button nothing happens. This chapter will describe how you catch user actions like keypresses and some basic things of the GUIWindow class.
The GUIWindow class has several virtual methods which might be useful.
virtual void OnPageLoad(); This method gets called just before your plugin is shown. You can use it to setup the screen like setting checkboxes, filling lists, etc, etc.
virtual void OnPageDestroy(int newWindowId); This method gets called when the user switches to another screen (it happens). You can use it to clean up, store any settings, etc. The newWindowId will contain the id of the screen the user is switching to.
virtual void OnShowContextMenu(); This method gets called if the user presses the show context menu key/button. Normally this is F9 on the keyboard. You can use it to show context menu's you see all over MediaPortal.
virtual void Process(); When the user is interacting with your screen, this method will be called on a regular basis. You can use it to do some processing like refreshing (parts) of the screen.
virtual void OnClicked( int controlId, GUIControl control, Action.ActionType actionType) This method gets called when the user presses a button or selects something from a list. controlId will contain the id of the control which is pressed. control will contain the GUIControl object. actionType will contain which action was done (like select, queue, delete, etc.).
Now this sounds like what we want next for our plugin, let's add some code when the buttons get clicked! First let's add 2 new member variables to the class which represent these buttons. This can be done with the following lines:
- [SkinControlAttribute(2)] protected GUIButtonControl buttonOne = null;
[SkinControlAttribute(3)] protected GUIButtonControl buttonTwo = null;
These 2 lines declare the two buttons. A button in the skin file is represented by the GUIButtonControl class. The attributes are needed to tell MediaPortal which button of the skin file the variable should represent. Remember the id tags of the two buttons. The upper one had <id>2</id> and the lower one had <id>3</id>. The ids are given as parameter to the attribute so the mapping is done automatically.
Note you can use this for any control, for example a label control could be mapped by:
- [SkinControlAttribute(1)] protected GUILabelControl lblText = null;
Next, we override the OnClicked() method:
protected override void OnClicked(int controlId, GUIControl control, Action.ActionType actionType) { if (control == buttonOne) OnButtonOne(); if (control == buttonTwo) OnButtonTwo(); base.OnClicked (controlId, control, actionType); } private void OnButtonOne() { } private void OnButtonTwo() { }
Now when you press button 1, the OnButtonOne() gets called. If you press button 2, the OnButtonTwo() gets called. To make sure this works, we are going to present a simple dialog.
Next change the OnButtonOne() and OnButtonTwo() into:
private void OnButtonOne() { GUIDialogOK dlg = (GUIDialogOK)GUIWindowManager.GetWindow( (int)GUIWindow.Window.WINDOW_DIALOG_OK); dlg.SetHeading("Button has been pressed"); dlg.SetLine(1, "You pressed button 1"); dlg.SetLine(2, String.Empty); dlg.SetLine(3, String.Empty); dlg.DoModal(GUIWindowManager.ActiveWindow); } private void OnButtonTwo() { GUIDialogOK dlg = (GUIDialogOK)GUIWindowManager.GetWindow( (int)GUIWindow.Window.WINDOW_DIALOG_OK); dlg.SetHeading("Button has been pressed"); dlg.SetLine(1, "You pressed button 2"); dlg.SetLine(2, String.Empty); dlg.SetLine(3, String.Empty); dlg.DoModal(GUIWindowManager.ActiveWindow); }
Now recompile your plugin (don't forget to copy it) and try again. Now when you press the first button you should see:
And when pressing the 2nd button:
For information about other dialogs see List of Dialogs.
Debug your plugin
Copy your plugin's debug binary and .pdb to the proper plugins directory.
Visual Studio
You have two options:
- Start MP, then in VS with you plugin's solution open, go to Debug|Attach to Process... and attach to MP's process. Then you can set break points in your code and start debugging. This is also applicable to TVServer plugins, in which case you have to attach to the tvservice process.
- If you need to debug startup issues, you need to go to Project Properties, in the Debug tab, under Start Action select Start External Program and select MP's executable (from your MP installation dir). Then set break points you want and run your project. VS will automatically run MP which will load your plugin, and you can start debugging.
Visual Studio Express
VS Express comes with the DLL debugging options unavailable in the GUI but there is a simple work around. In your project directory there should be file called NameOfProject.csproj.user but if there isn't just create a new one.
Open the file and modify it the settings below, with the path modified to correctly point at yourexecutabl:
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <StartAction>Program</StartAction> <StartProgram>C:\Program Files (x86)\Team MediaPortal\MediaPortal\MediaPortal.exe</StartProgram> </PropertyGroup> </Project>
If you are modiying an existing file you obviously need to leave out the XML header. You are now good to go, debugging your project will automatically start MediaPortal and attach your DLL to it.
Version Compatibility Check
1.2.0 beta: As of 1.2.0 Beta version of MediaPortal, a new version compatibility system is included that allows the application to determine if a certain plugin is compatible with this version of the application. Implementing this check is mandatory if you want the plugin to be compatible with MediaPortal 1.2.0 Beta or newer. Read more.
Appendix A: complete source code of the plugin
using System; using System.Windows.Forms; using MediaPortal.GUI.Library; using MediaPortal.Dialogs; namespace OurPlugin { public class Class1 : GUIWindow, ISetupForm { [SkinControlAttribute(2)] protected GUIButtonControl buttonOne=null; [SkinControlAttribute(3)] protected GUIButtonControl buttonTwo=null; public Class1() { } #region ISetupForm Members // Returns the name of the plugin which is shown in the plugin menu public string PluginName() { return "MyFirstPlugin"; } // Returns the description of the plugin is shown in the plugin menu public string Description() { return "My First plugin tutorial"; } // Returns the author of the plugin which is shown in the plugin menu public string Author() { return "Frodo"; } // show the setup dialog public void ShowPlugin() { MessageBox.Show("Nothing to configure, this is just an example"); } // Indicates whether plugin can be enabled/disabled public bool CanEnable() { return true; } // Get Windows-ID public int GetWindowId() { // WindowID of windowplugin belonging to this setup // enter your own unique code return 5678; } // Indicates if plugin is enabled by default; public bool DefaultEnabled() { return true; } // indicates if a plugin has it's own setup screen public bool HasSetup() { return true; } /// <summary> /// If the plugin should have it's own button on the main menu of Mediaportal then it /// should return true to this method, otherwise if it should not be on home /// it should return false /// </summary> /// <param name="strButtonText">text the button should have</param> /// <param name="strButtonImage">image for the button, or empty for default</param> /// <param name="strButtonImageFocus">image for the button, or empty for default</param> /// <param name="strPictureImage">subpicture for the button or empty for none</param> /// <returns>true : plugin needs it's own button on home /// false : plugin does not need it's own button on home</returns> public bool GetHome(out string strButtonText, out string strButtonImage, out string strButtonImageFocus, out string strPictureImage) { strButtonText=PluginName(); strButtonImage=String.Empty; strButtonImageFocus=String.Empty; strPictureImage=String.Empty; return true; } // With GetID it will be an window-plugin / otherwise a process-plugin // Enter the id number here again public override int GetID { get { return 5678; } set { } } #endregion public override bool Init() { return Load(GUIGraphicsContext.Skin+@"\ourplugin.xml"); } protected override void OnClicked(int controlId, GUIControl control, MediaPortal.GUI.Library.Action.ActionType actionType) { if (control==buttonOne) OnButtonOne(); if (control==buttonTwo) OnButtonTwo(); base.OnClicked (controlId, control, actionType); } private void OnButtonOne() { GUIDialogOK dlg = (GUIDialogOK)GUIWindowManager.GetWindow( (int)GUIWindow.Window.WINDOW_DIALOG_OK); dlg.SetHeading("Button has been pressed"); dlg.SetLine(1, "You pressed button 1"); dlg.SetLine(2, String.Empty); dlg.SetLine(3, String.Empty); dlg.DoModal(GUIWindowManager.ActiveWindow); } private void OnButtonTwo() { GUIDialogOK dlg = (GUIDialogOK)GUIWindowManager.GetWindow( (int)GUIWindow.Window.WINDOW_DIALOG_OK); dlg.SetHeading("Button has been pressed"); dlg.SetLine(1, "You pressed button 2"); dlg.SetLine(2, String.Empty); dlg.SetLine(3, String.Empty); dlg.DoModal(GUIWindowManager.ActiveWindow); } } }
Appendix B: skin file for plugin
<window> <id>5678</id> <defaultcontrol>2</defaultcontrol> <allowoverlay>yes</allowoverlay> <controls> <control> <description>BackGround</description> <type>image</type> <id>4</id> <posX>0</posX> <posY>0</posY> <width>720</width> <height>576</height> <texture>background.png</texture> </control> <control> <description>an Image</description> <type>image</type> <id>5</id> <posX>75</posX> <posY>370</posY> <texture>hover_my videos.png</texture> </control> <control> <description>text label</description> <type>label</type> <id>1</id> <posX>250</posX> <posY>70</posY> <label>Some text</label> <font>font16</font> <align>right</align> <textcolor>ffffffff</textcolor> </control> <control> <description>Try Me</description> <type>button</type> <id>2</id> <posX>60</posX> <posY>97</posY> <label>Try Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>3</ondown> </control> <control> <description>Or Me</description> <type>button</type> <id>3</id> <posX>60</posX> <posY>131</posY> <label>Or Me</label> <onleft>2</onleft> <onright>2</onright> <onup>2</onup> <ondown>2</ondown> </control> </controls> </window>
Advanced Plugin Development
Reference
- List of Dialogs
- Skin Architecture > Control Types
Tips and Tricks
Plug-in Development Tips & Tricks2011-12-27T21:56:41Z
3 Comments
Team-MediaPortal
says:
I agree with the skin properties in case of translatable controls. There is indeed not too much information about interaction, control-ids and how these relate. It is new to me what you wrote about ID 0 and 1. Thanks a lot! It will help me for sure!
Posted Dez, 29 2011 14:03
Team-MediaPortal
says:
But this is definately a great help for starting with plugin-development for MP.
Posted Dez, 27 2011 21:56
Team-MediaPortal
says:
@Heimkinofan1 - Control ID 1 is a 'global' ID which can be used as many times as you like in a skin xml (same as ID 0). The only time you need a specific control ID in skin xml is if it is set in the plugin. Thus it is usually much easier/better to use skin properties rather than hard coded IDs. Usually menu buttons have fixed IDs to match the functions defined in the plugin, though often the labels are translatable skin properties. I have been trying to get a plugin developer to work on this Wiki section for ages - especially regarding control IDs!
Posted Dez, 29 2011 10:47