Skip to main content
Skip table of contents

Plugins

MediaPortal plugins allows customers to develop their own code which will be dynamically loaded into pre-defined locations within MediaPortal’s code. This allows plugins to natively interact with the Javascript objects and functions in the same context giving more flexibility than alternatives such as inline frames.

This feature is achieved through JavaScript Module Federation, where MediaPortal acts as the “host”, and each plugin is a “remote”. There is a list of “shared modules” which the plugin can import from the host.

MediaPortal has a predefined list of “entry-points” where a plugin’s code will be loaded. Each plugin must include configuration to list which code it “exposes” to the MediaPortal. Each plugin can choose to use one or more entry-points, and MediaPortal can use multiple plugins at the same time. ConfigPortal is used to store information about where the plugins are located and which entry-points should be loaded.

Configuration

Order by number: MediaPortal will load plugins in ascending order, allowing one plugin to potentially override another.

Name: Any errors or warnings will use this name in the logs.

Plugin Remote Entry: The URL to the “remoteEntry.js” file. All other assets (js, css, fonts) should be relative to this path.

Entry-Points: Enable which entry-points the plugin will be loaded into MediaPortal. Only enable the entry-point if the plugin has implemented it.

Active: Whether the plugin is loaded.

 

Plugin Development

 

This tutorial assumes that the reader is familiar with Javascript development, has Node (v20 LTS or later) installed, and is running MediaPortal (v24.1 or later).

Folder Structure

CODE
my-mediaportal-plugin/
├─ .vscode/
│  ├─ extensions.json
│  ├─ settings.json
│  ├─ tasks.json
├─ dist/
│  ├─ my-mediaportal-plugin
│  │  ├─ ...
│  │  ├─ remoteEntry.js
├─ node_modules/
├─ src/
│  ├─ ItemTabs.jsx
│  ├─ NavMenuItems.jsx
│  ├─ Routes.jsx
├─ index.html
├─ package.json
├─ vite.config.js

Initial Setup

Create the initial project:

CODE
mkdir my-mediaportal-plugin
cd my-mediaportal-plugin
npm init -y

Configure the package.json:

CODE
npm pkg delete main author description keywords license
npm pkg set type="module"
npm pkg set private=true --json
npm pkg set scripts.build="vite build --assetsDir $npm_package_name"
npm pkg set scripts.watch="vite build --assetsDir $npm_package_name --watch"
npm pkg set scripts.serve="vite preview --port 5001 --strictPort"
npm pkg set scripts.start="concurrently npm:watch npm:preview"

Install dependencies:

CODE
npm install --save-dev vite @vitejs/plugin-react
npm install --save-dev @originjs/vite-plugin-federation
npm install --save-dev concurrently
npm install --save-prod react react-dom

Vite Configuration

Create a new file vite.config.js at the root of the project for use with react and the plugin-federation:

CODE
// vite.config.js
import federation from '@originjs/vite-plugin-federation';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
// https://vitejs.dev/config/
export default defineConfig({
  plugins: [
    react(),
    federation({
      name: 'remote_app',
      filename: 'remoteEntry.js',
      exposes: {
        './NavMenuItems': './src/NavMenuItems',
        './Routes': './src/Routes',
        './ItemTabs': './src/ItemTabs',
      },
      shared: {
        react: { generate: true, import: false },
        'react-dom': { generate: true, import: false },      
      },
    }),
  ],
  build: {
    modulePreload: false,
    target: 'esnext',
    minify: false,
    cssCodeSplit: false,
  },
});

Create an placeholder file index.html at the root of the project:

CODE
<!-- index.html -->
<!-- This is not used -->
<html />

 

Entry-Points

Each of the entry-points must be defined in the vite.config.js so that they are exposed by the plugin. Edit the exposes key to set the appropriate values, with the key-name being the host entry-point and the value being the relative path to the file within the plugin project directory without the suffix (see here for more detail).

In this example, create a new folder src at the root of the project and add the files for each entry-point.

Routes

An array of objects containing path, children and exact keys which will be rendered as a react-router Route component (see docs for "path“, “children“ and “exact“).

CODE
// Routes.jsx
const Routes = [
  {
    path: '/helloworld',
    exact: true,
    children: <div>Hello World</div>,
  },
];
export default Routes;

This can be used to override the default MediaPortal routes with custom components as plugins are loaded at the start of the Switch component.

NavMenuItems

An array of objects containing path, title and icon keys which will be rendered into the left-side navigation component.

CODE
// NavMenuItems.jsx
const NavMenuItems = [
  {
    icon: <h2>👋</h2>,
    title: 'Hello World',
    path: '/helloworld',
  },
];
export default NavMenuItems;

Use the NavMenuItems entry-point to link to any of the plugin’s routes.

 

ItemTabs

An array of objects containing name, label and component keys which will be rendered on the item tab heading and loaded into the active tab when selected.

CODE
// ItemTabs.jsx
function HelloTabComponent({ itemId }) {
  return (
    <div>
      <h1>Hello</h1>
      <span>Here is a new tab for ${itemId}</span>
    </div>
  );
}
const ItemTabs = [
  {
    name: 'hello',
    label: 'Hello Tab',
    component: HelloTabComponent,
  },
];
export default ItemTabs;

 

The component will receive props for itemId and playerRef .

 

Scripts

Run the following commands during development and when you want to distribute your plugin.

watch

npm run watch - Rebuilds when modules have changed on disk. Use this during development.

preview

npm run preview - Starts a development web-server listing on port 5001 . Use this during development.

build

npm run build - Creates a production build in the dist folder. Use this for distribution.

 

Develop and Preview Plugin

Start both the watch script and preview script at the concurrently using the start script

CODE
npm run start

Create a new Plugin definition in ConfigPortal for local development. Set the Plugin Remote Entry to http://localhost:5001/my-mediaportal-plugin/remoteEntry.js, enable the entry-points, and set the plugin to be Active.

Refresh MediaPortal to load the new configuration and the plugin will be displayed. Any changes made to the plugin during development will require the page to be refreshed as hot-reload is currently not supported.

Publish Plugin

Building the plugin will output the files in the dist folder.

CODE
npm run build

Upload the folder containing the remoteEntry.js file to a web-server. This could be an Amazon S3 Bucket with Websites enabled (see here).

Alternatively, a container image with the plugin files and the nginx web-server can be used. An example Dockerfile will copy the dist directory

CODE
FROM nginx
COPY dist /usr/share/nginx/html

Build and start the container listening on port 8081

CODE
docker build -t my-mediaportal-plugin-nginx .
docker run --rm -d -p 8081:80 my-mediaportal-plugin-nginx

 

Shared Modules

The following shared modules are available from MediaPortal:

CODE
react: 18.2.0
react-dom: 18.2.0
react-router-dom: 5.3.4
@vidispine/vdt-api: 23.4.0-pre.4
@vidispine/vdt-js: 23.4.0-pre.4
@vidispine/vdt-react: 23.4.0-pre.4
@vidispine/vdt-materialui: 23.4.0-pre.4
@material-ui/core: 4.12.4
@material-ui/icons: 4.11.3
@material-ui/lab: 4.0.0-alpha.61
@material-ui/pickers: 3.3.10
@material-ui/styles: 4.11.5
axios: 0.21.4

Any additional shared modules should be added to vite.config.js .

 

Shared Module Example

Install the dependencies

CODE
npm install @vidispine/vdt-materialui@23.4.0-pre.4

Create a new file TranscodeTab.jsx

CODE
// TranscodeTab.jsx
import { TranscodeDialog } from '@vidispine/vdt-materialui';
const tabs = [
    {
      value: 'TRANSCODER',
      label: 'Vidispine',
    },
];
function TranscodeTab({ itemId }) {
  return (
    <TranscodeDialog
      itemId={itemId}
      tabs={tabs}
    />
  );
}
export default TranscodeTab;

Import the new component into the ItemTabs.jsx entry-point.

CODE
// ItemTabs.jsx
import TranscodeTab from './TranscodeTab'
const ItemTabs = [
  {
    name: 'transcode',
    label: 'Transcode',
    component: TranscodeTab,
  },
];
export default ItemTabs;

Code Linting

Install development dependencies for eslint, prettier and the @vidispine configuration:

CODE
npm install --save-dev eslint@^8.0.0
npm install --save-dev @vidispine/eslint-config-react
npm install --save-dev prettier@^3.0.0
npm install --save-dev @vidispine/prettier-config

Edit package.json to enable prettier and eslint:

CODE
npm pkg set prettier="@vidispine/prettier-config"
npm pkg set eslintConfig='{"extends": ["@vidispine/eslint-config-react"]}' --json

Visual Studio Code

If you are using VSCode, create a folder named .vscode at the root of the project.

Create extensions.json within the .vscode folder to specify recommended extensions:

CODE
{
  "recommendations": ["esbenp.prettier-vscode", "dbaeumer.vscode-eslint"]
}

Create settings.json within the .vscode folder to enable the extensions on save:

CODE
{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.formatOnPaste": true,
  "editor.rulers": [
    100
  ],
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  },
}

Create tasks.json within the .vscode folder to specify the tasks which can be run:

CODE
{
  "version": "2.0.0",
  "problemMatcher": [],
  "tasks": [
    {
      "label": "build",
      "command": "npm",
      "args": [
        "run",
        "build"
      ],
    },
    {
      "label": "preview",
      "command": "npm",
      "args": [
        "run",
        "preview"
      ]
    },
    {
      "label": "watch",
      "command": "npm",
      "args": [
        "run",
        "watch"
      ]
    },
    {
      "label": "start",
      "dependsOn": [
        "watch",
        "preview"
      ]
    }
  ]
}

Use the command palette (MacOS: Command+Shift+P, Windows: Ctrl+Shift+P, Linux: Ctrl+Shift+P ) to run the start task to watch and serve at the same time.

 

JavaScript errors detected

Please note, these errors can depend on your browser setup.

If this problem persists, please contact our support.