Writing a Plugin
While we have published a majority of the plugins we have developed, there will always be new use cases that may require new functionality. Writing a plugin in the easiest way to extend Player functionality for these cases. Plugins work slightly differently on each platform so in this guide we will cover how to write a plugin for each platform.
Core plugins are the easiest way to extend Player functionality regardless of what platform you are using Player on. To make writing core plugins easy @player-ui/player
exposes an interface PlayerPlugin
that denotes everything needed. The two mandatory features are a name
property which is lets Player know how to refer to the plugin and an implemented apply
function that takes a player
object. Optionally a symbol
property can be used to provide a unique identifier that can be used to retrieve the plugin from Player.
The first step for creating a plugin is making our plugin class, making sure it implements the PlayerPlugin
interface from @player-ui/player
. By convention, a name attribute with the dash-cased name of your plugin should be defined.
The apply
function is where the actual logic of the plugin lives. By tapping the hooks exposed via player.hooks
you gain access to the internal pipeline of components that comprise Player and can inject your functionality into their exposed hooks. For example if you want to do something any time Player’s state changes you could do the following:
Some components expose hooks themselves which may require multiple levels of taps which is not uncommon. For example if you wanted to modify the ViewInstance
before it was resolved you would do the following:
It is not uncommon for core plugins to have constructors for cases where the plugin needs to take some configuration. In cases where plugin configs are more complicated than basic feature flags, it is recommended to make an interface to represent the config object. As an added benefit it also makes it easier to down stream consumers to use your plugin.
For a more comprehensive guide on plugins, check out this Plugin Implementation example.
Note: For the React Player you can import and load the plugin the same way you would a React Player Plugin but for the iOS and Android Players you will need to wrap the javascript bundle in a iOS/Android plugin to ensure it is available on your platform.
React Player Plugins are very similar to core plugins in both their composition and use. The @player-ui/react
package exposes an interface ReactPlayerPlugin
that, much like the PlayerPlugin
interface provides the necessary attributes that are required for a React Player plugin. Again a dash-cased name
attribute should be used by convention, and a function applyReact
is required that takes a ReactPlayer
instance. Similarly to core plugins in the applyReact
function you have access to the React Player object and access to the three exposed hooks:
- The
webComponent
hook allows you to modify a React component that is stored in the React Player for use when it renders content. This happens during the initialization phase and ise useful if you want to wrap components in various content providers. - The
playerComponent
hook allows you to modify a component or execute functionality when the React Player is rendering a component after the view has been reconciled in Player. This is useful if you want to inject additional props to components or collect data on which component was rendered. - The
onBeforeViewReset
hook is fired when the view is resetting to undefined and you want to execute some asynchronous tasks.
Below is an example of a basic ReactPlayerPlugin
that would expose a function to every component that gets loaded in the React Player:
Lastly React plugins can also act as a core plugin in cases where core functionality needs to be extended for the React plugin to work. Since both the PlayerPlugin
and ReactPlayerPlugin
are typescript interfaces a plugin can implement both and be considered a valid plugin.
iOS Player Plugins are very similar to core and react plugins in both their composition and use.
NativePlugin
The PlayerUI/Core
subspec exposes an interface NativePlugin
that, much like the core PlayerPlugin
interfaces, provides the necessary attributes that are required for an iOS Player plugin. A pluginName
attributed is required, and a function apply
is required that takes an instance of a Player implementation. Similarly to core plugins, in the apply
function you have access to the Player object and access to the hooks. apply
uses generics to future proof so plugins can be used for multiple Player implementations should they be created.
The player
passed to apply
exposes hooks from the core player, as well as hooks specific to that player implementation. For the current state of this project, the SwiftUIPlayer
is the primary iOS Player, and exposes two hooks for the SwiftUI layer specifically:
- The
view
hook allows you to modify the root view that will be displayed in the SwiftUIPlayer body. This is useful for applying changes to the environment for the SwiftUI view tree, or apply ViewModifiers and such. - The
transition
hook allows you to specify aPlayerViewTransition
object to be applied when the flow transitions from one view to another, to animate the transition.
Basic Example
Below is an example of a basic NativePlugin
that sets a value in the EnvironmentValues when the plugin is included:
Asset Registration
Likely the most common usecase for plugins is to register assets:
JSBasePlugin
Building native features on top of shared functionality is one of the primary benefits of using player. As such we expose convenience utilities to enable loading JavaScript Player Plugins as the base for your NativePlugin
.
Basic Setup
This example will load the SharedJSPlugin
in the JavaScript layer when included as a plugin to SwiftUIPlayer
.
Arguments for constructing the JavaScript plugins
To simplify the ease of use, the JSContext
for JSBasePlugin
implementations is provided when the underlying resources for the core player
are being setup. This means that we need to provide the arguments for the JavaScript constructor late. To do this, override the getArguments
function:
Note: As JavaScriptCore
cannot resolve dependencies at runtime, using a module bundler such as tsup is required.
Note: JSBasePlugin
implementations do not necessarily need to be a PlayerPlugin
, for example, the BeaconPlugin can take plugins in it’s constructor, that are not PlayerPlugin
.