Skip to content

Ease Live Bridge SDK for Roku

This is a guide for developers to get up and running with the Ease Live Bridge SDK for Roku.

Ease Live's main UI engine is HTML5 based. Unfortunately, Roku does not support WebViews or web apps in any way, and all apps on the Roku platform is written in BrightScript. Ease Live solves this on the Roku by rendering the UI using a combination of native SceneGraph components and cloud rendered Ease Live graphics.

Any Roku model that supports OpenGL ES 2.0. This includes all currently manufactured and supported devices by Roku.

Adding the SDK to your app

The Bridge object contains APIs available for the Ease Live Bridge SDK. In order to initialize the SDK, we need to add the component as a component library to a Scene or a SceneGraph component and load it over the network:

brightscript
m.easeLiveSDK = createObject("roSGNode", "ComponentLibrary")
m.easeLiveSDK.id = "EaseLiveSDK"
m.easeLiveSDK.uri = "https://sdk.easelive.tv/roku/1/easelivesdk.zip"
m.easeLiveSDK.observeField("loadStatus", "onLoadStatusChanged")
m.top.appendChild(m.easeLiveSDK)

Once the loadStatus field of the Ease Live component library changes to ready, we can use it to reference the SDK:

brightscript
function onLoadStatusChanged()
  if m.easeLiveSDK.loadStatus = "ready"
    ' ...
  end if
end function
brightscript
m.easeLive = createObject("roSGNode", "EaseLiveSDK:EaseLive")
m.easeLive.id = "easeLive"

Next up, add the SDK to your SceneGraph layout:

brightscript
m.top.appendChild(m.easeLive)

Loading a project

To load an Ease Live overlay, call the load function with the following configuration:

brightscript
m.easeLive.callFunc("load", {
  accountId: "<ID>"
  projectId: "<ID>"
  programId: "<ID>"
})

Required fields

  • accountId: The shorthand ID for the Ease Live account
  • projectId: The ID for the project
  • programId: The ID for the program

Optional fields

  • env: In your project setting you can configure which Studio URL to load in different environments, such as in prod or staging. If you're not sure, just leave it out and it will select prod automatically. Values are prod, staging or dev.
  • version: Specify a project version to load. If you leave it out, the SDK will automatically find the latest project version.
  • streamId: Custom ID which can be used in timecode settings to configure offsets for custom contexts. To change this during runtime, call the setStream function.
  • userId: Custom User ID that will be used in tracking analytics. If not specified, Roku's RIDA identifer will be used.
  • customAdTargeting: A key-value object. Any values set here will be included in the targeting parameters for ads. To set or change any values after startup, call the function setCustomAdTargeting.

Handling focus

The Roku app should implement handling of moving focus to and from the overlay, and send key events to the overlay while it has focus.

Give focus to Ease Live like this:

brightscript
m.easeLive.setFocus(true)

When the user decides to leave the Ease Live overlay, for instance to go back to the video player controls or go out of the video stream entirely, you can listen for this by observing focusChanged:

brightscript
m.easeLive.observeField("focusChanged", "onFocusChanged")
brightscript
function onFocusChanged()
  if m.easeLive.focusChanged = "unfocused"
    ' return focus to your own components
  end if
end function

Bridge message with focus metadata

When the SDK releases focus, it sends a view.focus event over the bridge. This event has metadata that includes a value with the focus state, and the reason for the change as trigger.

Triggers:

  • view.hide – app hidden by program or project configuration
  • view.leave – scene does not handle "back"
  • focus.none – no focusable targets left
  • user.back – user pressed back on the remote

Payload example:

json
{
  "event": "view.focus",
  "metadata": {
    "value": false,
    "trigger": "user.back"
  }
}

Synchronizing the graphics

Player time

A key feature of Ease Live is the ability to synchronize the overlay graphics with the video. To do this, pick up on timing information in your video player and pass it to Ease Live as a Unix timestamp (milliseconds since 1st of January 1970) in UTC (time zone 0).

brightscript
m.easeLive.callFunc("setPlayerTime", 1674641327000)

The data type for the timecode, and any variables used to pre-calculate this value, should be of type LongInteger. This is to prevent unexpected overflow.

Player state

Whenever the player is paused, buffering, seeking or just playing, let Ease Live know about the player state:

brightscript
m.easeLive.callFunc("setPlayerState", "playing")

Supported values are:

  • playing
  • seeking
  • buffering
  • paused
  • stopped

Set Stream

You can set a streamId to apply a matching timecode offset configured in project and program settings. A streamId declares the stream a client is watching, and any other information used to differentiate between settings. See Managing timecodes for more information.

To set streamId, either pass it along with the other parameters to the load function (can only be called once per instance):

brightscript
m.easeLive.callFunc("load", {
  ...
  streamId: "stream-1-on-roku"
})

or during runtime (i.e. after load has been called) by using the setStream function:

brightscript
m.easeLive.callFunc("setStream", "stream-2-on-roku")

Debugging

If the stream is configured with an offset, you should see it change in the debug overlay.

Analytics

Set User ID

If you want to track your own User ID for analytics purposes. If not specified, Roku's RIDA identifer will be used.

To set userId, either pass it along with the other parameters to the load function (can only be called once per instance):

brightscript
m.easeLive.callFunc("load", {
  ...
  userId: "my-custom-user-id"
})

or during runtime (i.e. after load has been called) by using the setUser function:

brightscript
m.easeLive.callFunc("setUser", "my-custom-user-id")

Set custom ad targeting parameters

You can add custom ad targeting parameters either through the parameter customAdTargeting in the call to load:

brightscript
m.easeLive.callFunc("load", {
  ...
  customAdTargeting: {
    "account.propName": "some-id"
  }
})

Conflict warning

It is highly recommended to prefix the keys, so as not to conflict with params configured in Studio or ones that are automatically added by the SDK.

To set targeting parameters in runtime (i.e. after load has been called), call this function:

brightscript
m.easeLive.callFunc("setCustomAdTargeting", {
  "account.propName": "new-id"
  "account.newProp": 123
})

Existing parameters will be overwritten. If you wish to delete a parameter, set it to invalid.

Bridge messages

Going beyond player synchronization, sometimes it's necessary to communicate back and forth between the OTT app and the Ease Live overlays. Example: A native app button opening up a panel within Ease Live.

The actual event name needs to be decided together with the ones working in Studio setting up the project UI. The name can be anything as long as it's a string, but we recommend prefixing it with app. or <yourProduct>..

Send messages

To send a message to the overlays, use the following syntax.

brightscript
m.easeLive.callFunc("sendMessage", {
  event: "app.myMessage"
})

Receive messages

To listen for messages being sent from the overlays to your app, observe message:

brightscript
m.easelive.observeField("message", "onMessage")

sub onMessage(event)
  payload = event.getData()

  ' if payload.event = "..."
  '   handleSomeEvent(payload.metadata)
  ' end if
end sub

Video scaling

In Studio, you can use the Bridge -> Send video scale action with the intent to resize and move the video player in your Roku app. Ease Live can't modify the player directly, which is why we send a bridge message player.videoscale with the values necessary for you to implement this in your app.

Event metadata

  • scaleX: Horizontal scale factor, between 0 and 1
  • scaleY: Vertical scale factor, between 0 and 1
  • pivotX: Horizontal anchor for the scaling animation, between 0 and 1
  • pivotY: Vertical anchor for the scaling animation, between 0 and 1
  • duration: Duration of the scale animation in milliseconds, example 400 (ms)

Code sample

You can use the following code as a reference on how to animate your video player to the values provided in the metadata.

Note

In this example, m.video is the target node. An id is required for the animation to know which node to apply the transformation to.

brightscript
m.easelive.observeField("message", "onMessage")

...

' Handle bridge events
sub onMessage(event)
  payload = event.getData()

  if payload.event = "player.videoscale"
    scaleVideo(payload.metadata)
  end if
end sub

' Initialize video scale
sub initVideoScale()
  if m.videoScale <> invalid then return

  m.videoScale = {
    origin: m.video.boundingRect()
  }

  ' Create animation and interpolator nodes
  m.videoScale.animation = createObject("roSGNode", "Animation")

  ' Scale interpolator
  m.videoScale.scaleInterp = createObject("roSGNode", "Vector2DFieldInterpolator")
  m.videoScale.scaleInterp.setFields({
    key: [0.0, 1.0]
    fieldToInterp: m.video.id + ".scale"
  })
  m.videoScale.animation.appendChild(m.videoScale.scaleInterp)

  ' Pivot point interpolator
  m.videoScale.pivotInterp = createObject("roSGNode", "Vector2DFieldInterpolator")
  m.videoScale.pivotInterp.setFields({
    key: [0.0, 1.0]
    fieldToInterp: m.video.id + ".scaleRotateCenter"
  })
  m.videoScale.animation.appendChild(m.videoScale.pivotInterp)

  m.top.appendChild(m.videoScale.animation)
end sub

' Animate video scale
sub scaleVideo(options)
  initVideoScale()

  ' Scale from and to
  fromScaleX = m.video.scale[0]
  fromScaleY = m.video.scale[1]
  toScaleX = options.scaleX
  toScaleY = options.scaleY

  m.videoScale.scaleInterp.keyValue = [
    [fromScaleX, fromScaleY]
    [toScaleX, toScaleY]
  ]

  ' Pivot from and to
  fromPivotX = m.video.scaleRotateCenter[0]
  fromPivotY = m.video.scaleRotateCenter[1]
  toPivotX = m.videoScale.origin.width * options.pivotX
  toPivotY = m.videoScale.origin.height * options.pivotY

  m.videoScale.pivotInterp.keyValue = [
    [fromPivotX, fromPivotY]
    [toPivotX, toPivotY]
  ]

  ' Update duration and start animating
  m.videoScale.animation.duration = options.duration / 1000
  m.videoScale.animation.control = "start"
end sub

To test your implementation, you can use the example project for video scaling.

Debugging

Enable debug mode to display information about the project, program and player on the screen.

brightscript
m.easeLive.debug = true

When debug is off (default), you can also open up the debug panel by by pressing the following combination on the remote:
Longpress Left for 5 secondsUpRightLeft
Using the shortcut again will close the overlay.

Overriding the shortcut

It is recommended to keep the default shortcut, as users are unlikely to trigger it by accident, as well as having a minimal effect on the interface.
Keeping that in mind, you can override it by adding a line to your manifest file like this:

ease_live_debug_overlay_default_shortcut=...

Replace ... with your shortcut in this pattern: key.long+key+key
For example, the default shortcut looks like this: left.long+up+right+left

Alternatively, there's a developer shortcut option that only works while in developer mode:

ease_live_debug_overlay_dev_shortcut=...

Find the supported key strings in the official documentation.

Debugging nodes

To enable debugging of nodes, set this in your manifest:

ease_live_debug_nodes=true

This will render node names, IDs and rectangles around the nodes.

Unload the overlay

To safely remove the overlay from SceneGraph, call the unload function:

brightscript
m.easeLive.callFunc("unload")

If you want to be notified when the SDK is done removing itself, observe the statusChanged field for unloaded:

brightscript
m.easeLive.observeField("statusChanged", "onStatusChanged")
brightscript
function onStatusChanged()
  if m.easeLive.statusChanged = "unloaded"
    ' ...
  end if
end function

SDK status

By observing statusChanged you will know which state the SDK is in. Available states are:

  • loading: The SDK is currently loading in the project, graphics and data
  • unloaded: The SDK has finished tearing down the UI
  • ready: The SDK and overlay is all setup and is ready to be used
  • failed: Something went wrong and you should remove the SDK from the scene

Project and program status

The project and program that is loaded into the app, can be visible or hidden based on settings. These can be changed during runtime. Example: Something goes wrong during a game and you want to hide the graphics for everyone already watching.

brightscript
m.easeLive.observeField("appStatusChanged", "onAppStatusChanged")
brightscript
function onAppStatusChanged()
  if m.easeLive.appStatusChanged = "disabled"
    ' ... unload the overlays
  end if
end function
  • enabled: The overlay is running normally and is displayed on screen
  • hidden: The overlay is loaded in, but the graphics are hidden. They can be turned on/off in project and program settings. Whenever the graphics are hidden, focus is given back to the app
  • disabled: The project or program is disabled, and you should likely unload the entire SDK

Error handling

If something critical happens and the SDK isn't able to handle it, the error is logged and passed to the error field. Observe it to make sure you can catch the error and remove the overlay:

brightscript
m.easeLive.observeField("error", "onEaseLiveError")
brightscript
sub onEaseLiveError(event)
  ' error = event.getData()
  ' error.type
  ' error.message
  ' error.description
end sub

TIP

If an error is thrown, m.easeLive.statusChanged will also be set to failed.

Initial load timeout error

If it takes more than 20 seconds to load the overlays, it means the Bridge hasn't received any data from the API. statusChanged will be set to failed and the following error event will be triggered:

{
  message: "Loading is taking too long"
}

Widgets

Custom SceneGraph components can be embedded into the Ease Live overlay in Roku, if you have it packaged as a Component Library.

Studio configuration

In the properties inspector for your embed layer, look for:

Embed for Roku

  • Component library URI
  • Library name
  • Component name

Embed data
Key-value object passed to the widget.

Incoming widget event

To handle lifecycle and focus events, your component needs to have the incomingWidgetEvent field.

Interface

xml
<field id="incomingWidgetEvent" type="assocarray" alwaysNotify="true" />

Payload format

{
  event: <event-name>
  metadata: <dynamic>
}

Event types

eventmetadata
installSee own section
show
hide
focustrue false
resizeVector2D
playerState"stopped" "playing" "paused" "buffering" "seeking"
playerTimeLongInteger Unix timestamp in milliseconds
destroy

Code example

brightscript
sub init()
  m.top.observeField("incomingWidgetEvent", "onWidgetEvent")
end sub

sub onWidgetEvent(ev)
  payload = ev.getData()
  event = payload.event
  metadata = payload.metadata

  if event = "install"
    m.widgetMetadata = metadata
    onWidgetInstall()
  else if event = "show"
  else if event = "hide"
  else if event = "focus"
    onWidgetFocus(metadata)
  else if event = "resize"
    ' updateSize(metadata)
  else if event = "playerState"
  else if event = "playerTime"
  else if event = "destroy"
    onWidgetDestroy()
  end if
end sub

' Widget is added to the node tree
sub onWidgetInstall()
  ' updateSize(m.widgetMetadata.size)
  ' ...
end sub

' Widget layer focus state changed
sub onWidgetFocus(focused)
  if focused
    ' Take focus from EaseLive and start handling key events
    m.top.setFocus(true)
    ' To release focus again, do `m.top.setFocus(false)`
  end if
end sub

' Widget instance will be removed
sub onWidgetDestroy()
  ' Stop ongoing tasks and timers
end sub

Warning

If you take focus away from EaseLive, make sure to release it again in a predictable manner (including but not limited to handling "Back" key), to avoid situations where the user gets stuck.

Install event metadata

{

  ' Ease Live SDK load params
  ...custom-load-params
  accountId: String
  projectId: String
  programId: String
  streamId: String
  userId: String

  scheduledStartTime: ISO 8601
  scheduledStartEnd: ISO 8601

  player: {
    state: String ' stopped|playing|paused|buffering|seeking
    time: LongInteger ' Unix timestamp in ms
  }

  size: Vector2D ' [width, height]

  widgetData: {
    ' Data configured for the layer in Studio
  }
}

Outgoing widget event

To send messages to Ease Live, add a field outgoingWidgetEvent to the interface of your root component.

Interface

xml
<field id="outgoingWidgetEvent" type="assocarray" alwaysNotify="true" />

Event format

{
  event: <name as string>
  metadata: <payload as dynamic>
}

Key event

Send this to allow Ease Live to continue navigating in the direction that resulted in focus being released from your widget.

brightscript
function onKeyEvent(key, press) as boolean
  ' Send key event
  m.top.outgoingWidgetEvent = {
    event: "key"
    metadata: {
      key: key
      press: press
    }
  }

  ' Example key handler
  handled = false
  if press
    if key = "down"
      if atLastItem()
        m.top.setFocus(false)
      else
        nextItem()
      end if
      handled = true
    else if key = "up"
      if atFirstItem()
        m.top.setFocus(false)
      else
        prevItem()
      end if
      handled = true
    else if key = "back"
      m.top.setFocus(false)
    end if
  else
  end if

  return handled
end function

Widget size

The size of the viewport the widget is embedded in can be accessed in the install event metadata as metadata.size, and resize event metadata directly as metadata, as a Vector2D.