The eko platform UI is the global, non-project specific interface that controls things like playback (pause/play/skipping), project metadata, social features (sharing) and more.
Each part of the UI is referred to as a platform “addon”. Addons are loosely coupled modules that target a specific interface use case, and their combination is what defines the overall platform user inerface

In addition to the default set of platform addons, a user may also customize an existing platform addon, or create their own custom addon from scratch,
ekoshell API
Available under player.ekoshell (e.g. player.ekoshell.getAllAddons())
addAddon(addonId, metaDescriptor)- adds a new addon. See What makes up an addongetAddon() => {object}- returns the meta descriptor of a user addongetAllAddons() => {object}- returns all user addonssetParentContainer(addonId, parentContainer)- Changes the current addon’s parentContainer. An addon’s container can be dynamically changed by callingplayer.ekoshell.setParentContainer(addonId, parentContainerId).
Specifyingnullas theparentContainerIdwould remove it from the container, and prevent it from being rendered. See parentContainer
What makes up an addon
An ekoshell addon consists of several parts. The meta descriptor is an object which describes these parts, and gets passed to the player.ekoshell.addAddon method. This object is required in order to actually add an addon to ekoshell. However, all properties within this object (aka parts of the addon) are optional.
Example:
{
init: (addonParams) => { ... },
component,
reducer: produce((draft, action) => { ... });
api: (addonParams) => { ... },
epic: (addonParams) => { ... },
events: { ... },
parentContainer: "root",
order: 5,
props: { ...},
// properties below for platform addons only
mapStateToProps, (globalState) => { ...},
mapEkoContextToProps: ({pluginOptions, ctx}) => ({ ... }),
uiScope: 'platformAbove'
}
All the other parts are optional, here’s a breakdown and detailed description of each:
init
A method which gets called as soon as the addon is loaded successfully. Possible use cases include:
- Calling any other addon’s relevant API (e.g. adding an
AutoHideaction to the ControlsOverlay addon) Initializing sub-addons (other addons that are separate but depend on this addon)
The function accepts an object containing the addon parameters
component
A functional React component representing this addon’s UI. This component will get rendered according what’s specified in parentContainer and order in the meta descriptor.
It receives whatever was specified as the props value in the meta descriptor and addition, these values:
- The result of
mapEkoContextToProps addonIdparentContainer- this addon’sparentContaineriduseAddonSelector- see addon parametersdispatchAddon- see addon parametersuseContainer- A react hook which receives the name of a container and returns a list of the containers children components to be rendered. See usage example belowplayer- A reference to the eko player
Example:
// this is the component for an addons that displays a list of buttons.
// Each button is assumed to be its own addon with a parentContainer set to 'buttonRow' (a container that's defined in this component)
export default function CustomButtonRow(props){
let rowCollapsed = props.useAddonSelector(addonState => addonState.rowCollapsed);
let buttonRow = props.useContainer("buttonRow");
return (
<div className="customButtonRow">
<button onClick={ () => props.dispatchAddon({type: "TOGGLE_ROW"}) }>Toggle Row</button>
{!rowCollapsed && <div>{buttonRow}</div>}
</div>
)
}
reducer
A redux reducer which receives the state and actions scoped to this particular addon.
Example:
import produce from "immer"
import Actions from "./SeekIndicationActions"
export default produce((draft, action) => {
if (typeof draft === "undefined"){
return {
seekDirection: null
};
}
switch (action.type) {
case Actions.SHOW:
draft.seekDirection = action.seekDirection;
break;
case Actions.HIDE:
draft.seekDirection = null;
break;
}
});
api
An addon’s API describes all the imperative logic or tasks that the addon can perform. It’s a set of functions exposed to be used by its component, epic, or any other parts of the interactive video, such as the project or other addons. The meta descriptor expects a function which takes the addon parameters and returns and object with the addon functions to be exposed.
This object will be added under the player.ekoshell.addons.myAddon namespace (e.g. player.ekoshell.addons.myAddon.doSomething()) , unless its a platform addon in which case its API will be available under player.ekoshell.platformAddonId. This is so that if a user replaces a platform addon, they can still use the original addon API even in the case they supply their own API with the custom addon.
Example:
export default function createAddonApi(addonParams) {
let {player, dispatchAddon} = addonParams;
function doSomething(){
player.seek("MY_NODE_ID"); // using the player
dispatchAddon({type: "DO_SOMETHING"}}
}
return {
doSomething
}
}
epic
The epic allows an addon to initiate “side effects”, operations that live outside the scope of actions/reducers. Behind the hood it uses redux observable to read the redux action list as stream and react to it. The meta descriptor expects a function which takes the addon parameters and returns a redux observable epic. Note - this is highly advanced usage of addons.
Simple Example:
// hide the language menu when the Controls Overlay addon is hidden
export default function createEpic({addonId}) {
let hideWhenControlsOverlayIsHidden$ = action$ => action$.pipe(
ofType(namespaceAction("HIDE", "ControlsOverlay")),
mapTo(transformAction({type: Actions.HIDE_LANGUAGE_MENU}, addonId)),
);
return hideWhenControlsOverlayIsHidden$;
};
Complex Example:
const SEEK_INDICATION_TIMER = 2000;
// Show the seek indication for a certain amount of time before hiding
export default function createEpic({player, addonId}) {
return action$ => action$.pipe(
filter(action => action.type === namespaceAction(Actions.SHOW, addonId)),
switchMap(() =>
{
return timer(SEEK_INDICATION_TIMER)
.pipe(
mapTo(transformAction({type: Actions.HIDE}, addonId))
)
}
)
);
};
events
The events part registers a series of events that should be tracked by eko’s analytics service. The event names should be passed in as an object. For example, the ControlsOverlay addon tracks when the play/pause button is clicked:
export default {
CONTROLS_PAUSE: 'ekoshell.button.pause',
CONTROLS_PLAY: 'ekoshell.button.play'
};
In the ControlsOverlay api, there is a function that toggles the play/pause state like so:
function togglePlayPause(e) {
if (!player.paused) {
player.pause();
player.trigger(events.CONTROLS_PAUSE);
} else {
player.play();
player.trigger(events.CONTROLS_PLAY);
}
if (e) {
e.stopPropagation();
}
}
Once the event is triggered, it will automatically be tracked by our player analytics.
parentContainer
{string} - an ID of a container (default: “root”)
Each addon may belong to a container, which is a data structure with a unique ID that defines a list of addons. An addon may also include one or more containers, which represent logical sub-addons, or “slots” for other addons to be fit into.
For example, the ControlsOverlay addon defines a ControlsOverlay_buttonRow container for a list of buttons (the buttons themselves also being addons).
This allows a developer to customize, or completely replace the ControlsOverlay addon, and as long as they still retain the proper ControlsOverlay_buttonRow ID, the same buttons would appear in the same way in the new customized addon. This demonstrates the concept of loose coupling between addons which we described earlier.


This relationship between containers including addons, and addons including containers de-facto defines a logical tree structure:
order
{number} - The logical order in which this addon would appear relative to the other addons in the same container. The lower the number, the closer to start it would be in DOM. If order is not specifying, it would be placed after all other addons which do have the order specified.
mapStateToProps (platform addon only)
A function which maps the player’s global redux state to props for the addon component. Similar in signature and purpose to Redux’s function of the same name.
It used in platform addons to supply the same required props even when the component is replaced by a user addon.
mapEkoContextToProps (platform addon only)
A function which maps the player’s received pluginOptions and ctx object to props for the addon component.
It used in platform addons to supply the same required props even when the component is replaced by a user addon.
Example:
({pluginOptions, ctx}) => ({
showPauseButton: pluginOptions.showPauseButton
})
props
The props that the addon component receives will be extended with this object.
uiScope (platform addon only)
Specifies which UI scope this addon belongs to. default is “platformAbove”.
addon parameters
This set of parameters is supplied to various functions and components
player {object}- A reference to the eko playeraddonId {string}- This addon’s iduseAddonSelector {function}- A react hook, similar in function and signature to useSelector but scoped to the addon’s state. It provides an easy way to use data from the addon state.
Example:let seekDirection = props.useAddonSelector(state => state.seekDirection)
dispatchAddon {function}- Similar to redux’s dispatch function, this dispatches an action that will be caught by the addon’s reducer. It does so by amending the action object with metadata and transforming the type attribute to contain the addon id. SeegetActionTypeName;
Example:dispatchAddon({type: "SHOW"});getActionTypeName {function}- A function which receives an action type (string) and returns a namespaced action for this addon. It’s mostly useful in epics, where one needs to know the actual type name of an action after it has been transformed.
Example:// running in seekIndication addon let transformedActionTypeName = getActionTypeName('SHOW"); console.log(transformedActionTypeName); // prints EKOSHELL_SEEKINDICATION_show
pluginOptions {object} (platform addon only)- The pluginOptions object