Skip to main content

Recommendations / Related videos

Breaking change after version 5.13.x
Upgrade required

The previous implementation is deprecated and will be maintained only until version 5.13.x. After that, it will no longer be supported. Upgrade now to ensure continued compatibility.

What you learn​

You're instantiating DIVA Player in your web front-end and relying on DIVA Back Office as the video streaming source.

The goal of this article is to build the simplest front-end to stream a video from the DIVA Back Office with, at the end, the activation of Recommendations in combination with end of play.

Before starting​

  • Ensure the DIVA Back Office instance you rely on is up and running.
  • Ask your video engineers team the <video id> and the related <settings URL>.
  • Ensure the VideoMetadata contains the media lists data.
  • Get read-and-write access to the VideoMetadata file.
note

The Diva Player requires the videoId parameter during initialization, as it is the expected field for asset identification and loading. However, in this implementation, we refer to it generically as itemId.

Autoplay setup:

Instantiation​

Write the Basic instantiation code. There's no additional code to write, unless you need to overwrite the autoplay behavior that the VideoMetadata contains.

Working sample code (.tsx)​

Write the App() function to read the VideoMetadataMap as in the following example, and set the medialists array:

import { AssetState, BoAdapterWebComponentProps, DivaWebBoAdapter, VideoMetadata } from "@deltatre-vxp/diva-sdk/diva-web-bo-adapter";
import "@deltatre-vxp/diva-sdk/diva-web-sdk/index.min.css";
import { useState } from "react";

const itemId = "<itemId>";

const libs = {
mux: "https://cdnjs.cloudflare.com/ajax/libs/mux.js/6.2.0/mux.min.js",
shaka: "https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.11.2/shaka-player.compiled.js",
hlsJs: "https://cdn.jsdelivr.net/npm/hls.js@1.5.7",
googleIMA: false,
googleDAI: false,
googleCast: false,
threeJs: false
};

const config = {
videoId: itemId,
libs: libs,
settings: {
general: {
timeToDisableAutoplay: 5400000
}
}
};

function App() {
const [isVideoLive, setIsVideoLive] = useState(false);
const props: BoAdapterWebComponentProps = {
settingsUrl: "<settings URL>",
languageCode: "en-US",
languageDictionary: "en-US",
onDivaBoAdapterError: console.error,
config: config,
videoMetadataMap: async (vd: VideoMetadata) => {
vd.mediaLists = [
{
listId:"123466",
rowTitle:"diva_eop_related_title",
defaulImageFormat:"tile",
defaultItemLayout: "vertical",
defaultItemType: "related",
items: [
{
itemId: "12345678",
title: "Star Wars EP I - The Phantom Menace",
caption: "Mesa Jar Jar Binks",
imageUrl: "http://sw.image/ep1.jpg",
ccBadge: false,
adBadge: false,
},
{
itemId: "12345679"
title: "Star Wars EP II - Attack Of The Clones",
caption: "I don't like sand. It's coarse and rough and irritating and it gets everywhere",
imageUrl: "http://sw.image/ep2.jpg",
ccBadge: false,
adBadge: false,
},
{
itemId: "12345680"
title: "Star Wars EP III - Revenge Of The Sith",
caption: "I have brought peace, freedom, justice and security to my new Empire!",
imageUrl: "http://sw.image/ep3.jpg",
ccBadge: false,
adBadge: false,
}
]
},
];
vd.behavior = {
endOfPlay: {
countdownToAutoplay: 10000
}
}
return vd;
}
}

  return (
<>
     <DivaWebBoAdapter {...props} />
</>
);
}

Override the Recommendations/Related component​

To override the Recommendations/Related component, you can use the ChainPlayComponent property in the renderCustomization object. This property allows you to pass a custom component to the Recommendations/Related screen.

const config = {
// ...other configuration
renderCustomization: {
ChainPlayComponent: (props) => React.ReactElement // CustomChainPlayComponent
},
};

Props​

There are several props provided by Diva and passed to your component that may be useful for you.

type Props = {
/**
* The metadata of the video that is currently playing/ending.
*/
videoMetadata: VideoMetadata | undefined;
/**
* Function to switch the video to the next one.
* @param videoId The ID of the video to switch to.
*/
switchVideo: (videoId: string) => void;
/**
* Information whether the component should be focused. Default: false
* Passed only on webtv platforms.
*/
focused?: boolean;
};

Focus Handling on WebTV platforms​

The component should not focus itself on render. Rather, the focus should be based on the focused prop.

  • When this prop becomes true, the component should focus itself visually and make sure that it will be able to receive keydown events.
  • When this prop becomes false, the component should only remove visual focus representation and make sure not to react to any events. Any change in activeElement will be handled by Diva.

Events handling​

If the component handles keydown events, it should stop the propagation of the event if the event was handled by the component. Events that do not result in any action by the component should be propagated to Diva. This allows handling the focus back to Diva in case of direction keys not being processed by the component.

Dynamic fallback to default component​

It is possible to fallback back to the default component provided by Diva dynamically from your custom component. To do this, simply throw CustomComponentRenderDefault error during the render phase.

import { CustomComponentRenderDefault } from '@deltatre-vxp/diva-webtv-sdk';

const MyCustomComponent = ({ focused }: Props) => {
const [dataError, setDataError] = useState(false);

useEffect(() => {
(async () => {
try {
// Fetch data
} catch (error) {
setDataError(true);
}
})();
}, [])

if (dataError) {
throw new CustomComponentRenderDefault();
}

return (
// Your component
)
};

Empty render​

Sometimes you might wish not to render anything. In such case, it is not possible to just return null. The null render result is not detectable easily by the parent component. Because of focus handling, Diva would still try to focus this empty component. To avoid this, you can throw CustomComponentRenderNothing error. This will be handled by Diva, nothing will be rendered and the focus will not be sent to the component.

import { CustomComponentRenderNothing } from '@deltatre-vxp/diva-webtv-sdk';

const MyCustomComponent = ({ focused }: Props) => {
const data = useDataProvider();

if (data.length === 0) {
throw new CustomComponentRenderNothing();
}

return (
// Your component
)
};

Write the App() function to read the VideoMetadataMap as in the following example, and set the related property:

  • undefined default value if not set and recommendations not shown
  • This is the only mandatory value to set to show recommendations:
  items: [
{
itemId: "<videoId>",
}
]
  • These are the complete fields that can be set:
  rowTitle: "<rowTitle>", // if empty, the default will be: diva_eop_recommendation_title
defaulImageFormat: "<imageFormat>", // "tile"
items: [
{
itemId: "<videoId>",
title: "<title>",
caption: "<caption>",
imageUrl: "<imageUrl>",
ccBadge: "<ccBadge>", //true or false, TV only
adBadge: "<adBadge>", //true or false, TV only
}
]

Customize the click behaviour​

The default behaviour once we click on the item is switch (play the clicked video).  If the integrators need to override the click behaviour they can set the callback on Diva config by adding:

    config.onMediaListItemClick = (itemId: string): boolean => {
// custom logic
return false; // false means the default behaviour (switch) will be executed. On WebTV, the default behaviour is never executed.
}

Autoplay​

Write the App() function to read the VideoMetadataMap as in the following example, and set the endOfPlay properties:

"behaviour": {
"endOfPlay": {
"countdownToAutoplay":10000 //milliseconds, default 10000, 0 or negative means disabled
},
},

In the settings file, the general section must contain "timeToDisableAutoplay": 5400000, like in the following example:

{
"general": {
"timeToDisableAutoplay": 5400000, //milliseconds, default 0, 0 or negative means disabled
},
}

The autoplay will start if countdownToAutoplay has set. In this case the countdown will start.

  • countdownToAutoplay will enable or disable the autoplay at the end of the milliseconds set ()
  • timeToDisableAutoplay will disable the autoplay at the end of the milliseconds set with no interactions

Working sample code (.tsx)​

import { AssetState, BoAdapterWebComponentProps, DivaWebBoAdapter, VideoMetadata } from "@deltatre-vxp/diva-sdk/diva-web-bo-adapter";
import "@deltatre-vxp/diva-sdk/diva-web-sdk/index.min.css";
import { useState } from "react";

const videoId = "<videoId>";

const libs = {
mux: "https://cdnjs.cloudflare.com/ajax/libs/mux.js/6.2.0/mux.min.js",
shaka: "https://cdnjs.cloudflare.com/ajax/libs/shaka-player/4.11.2/shaka-player.compiled.js",
hlsJs: "https://cdn.jsdelivr.net/npm/hls.js@1.5.7",
googleIMA: false,
googleDAI: false,
googleCast: false,
threeJs: false
};

const config = {
videoId: videoId,
libs: libs,
settings: {
general: {
timeToDisableAutoplay: 5400000
}
}
};

function App() {
const [isVideoLive, setIsVideoLive] = useState(false);
const props: BoAdapterWebComponentProps = {
settingsUrl: "<settings URL>",
languageCode: "en-US",
languageDictionary: "en-US",
onDivaBoAdapterError: console.error,
config: config,
videoMetadataMap: async (vd: VideoMetadata) => {
vd.mediaLists = [
{
listId:"123466",
rowTitle:"diva_eop_related_title",
defaulImageFormat:"tile",
defaultItemLayout: "vertical",
defaultItemType: "related",
items: [
{
itemId: "12345678",
title: "Star Wars EP I - The Phantom Menace",
caption: "Mesa Jar Jar Binks",
imageUrl: "http://sw.image/ep1.jpg",
ccBadge: false,
adBadge: false,
},
{
itemId: "12345679"
title: "Star Wars EP II - Attack Of The Clones",
caption: "I don't like sand. It's coarse and rough and irritating and it gets everywhere",
imageUrl: "http://sw.image/ep2.jpg",
ccBadge: false,
adBadge: false,
},
{
itemId: "12345680"
title: "Star Wars EP III - Revenge Of The Sith",
caption: "I have brought peace, freedom, justice and security to my new Empire!",
imageUrl: "http://sw.image/ep3.jpg",
ccBadge: false,
adBadge: false,
}
]
},
];
vd.behavior = {
endOfPlay: {
countdownToAutoplay: 10000
}
}
return vd;
}
}

  return (
<>
     <DivaWebBoAdapter {...props} />
</>
);
}

Override the Recommendations/Related component​

To override the Recommendations/Related component, you can use the ChainPlayComponent property in the renderCustomization object. This property allows you to pass a custom component to the Recommendations/Related screen.

const config = {
// ...other configuration
renderCustomization: {
ChainPlayComponent: (props) => React.ReactElement // CustomChainPlayComponent
},
};

Props​

There are several props provided by Diva and passed to your component that may be useful for you.

type Props = {
/**
* The metadata of the video that is currently playing/ending.
*/
videoMetadata: VideoMetadata | undefined;
/**
* Function to switch the video to the next one.
* @param videoId The ID of the video to switch to.
*/
switchVideo: (videoId: string) => void;
/**
* Information whether the component should be focused. Default: false
* Passed only on webtv platforms.
*/
focused?: boolean;
};

Focus Handling on WebTV platforms​

The component should not focus itself on render. Rather, the focus should be based on the focused prop.

  • When this prop becomes true, the component should focus itself visually and make sure that it will be able to receive keydown events.
  • When this prop becomes false, the component should only remove visual focus representation and make sure not to react to any events. Any change in activeElement will be handled by Diva.

Events handling​

If the component handles keydown events, it should stop the propagation of the event if the event was handled by the component. Events that do not result in any action by the component should be propagated to Diva. This allows handling the focus back to Diva in case of direction keys not being processed by the component.

Dynamic fallback to default component​

It is possible to fallback back to the default component provided by Diva dynamically from your custom component. To do this, simply throw CustomComponentRenderDefault error during the render phase.

import { CustomComponentRenderDefault } from '@deltatre-vxp/diva-webtv-sdk';

const MyCustomComponent = ({ focused }: Props) => {
const [dataError, setDataError] = useState(false);

useEffect(() => {
(async () => {
try {
// Fetch data
} catch (error) {
setDataError(true);
}
})();
}, [])

if (dataError) {
throw new CustomComponentRenderDefault();
}

return (
// Your component
)
};

Empty render​

Sometimes you might wish not to render anything. In such case, it is not possible to just return null. The null render result is not detectable easily by the parent component. Because of focus handling, Diva would still try to focus this empty component. To avoid this, you can throw CustomComponentRenderNothing error. This will be handled by Diva, nothing will be rendered and the focus will not be sent to the component.

import { CustomComponentRenderNothing } from '@deltatre-vxp/diva-webtv-sdk';

const MyCustomComponent = ({ focused }: Props) => {
const data = useDataProvider();

if (data.length === 0) {
throw new CustomComponentRenderNothing();
}

return (
// Your component
)
};

Error handling​

Your custom component is wrapped by ErrorBoundary. If an error is thrown during the render phase, the component will be unmounted and the default component will be rendered instead. The error will be logged. In case of CustomComponentRenderDefault or CustomComponentRenderNothing error, the errors are not logged as errors.

In development mode, the error message will always be displayed and it may trigger error overlays for dev servers. However, in production mode these errors should be handled gracefully without interrupting the user experience.

Dictionary​

Ensure the dictionary file contains the relevant keys.

Analytics events​

Find here the available analytics events for the Recommendations / Related.

Behaviour​

  • During the end screen, Recommendations elements will be shown at the bottom of the screen, including countdown to autoplay on the first item.
  • For entertainment content, only one row of recommendations should be shown on the screen.
  • If both Up Next and Recommendations are available, Up Next will take priority.

Countdown Activation​

  • The countdown starts only when the Squeeze Back feature is triggered or at the end of playback (if Squeeze Back is not enabled).
  • The countdown timer will display the remaining seconds as per the design specifications. If no countdown value is set, the timer will not appear.
  • When the countdown finishes, the first video item will automatically start unless the user interacts with the Endboard.

Countdown Cancellation​

  • If the user interacts with the Endboard, the countdown timer is canceled, and the automatic transition to the first video is halted. The specific interactions that cancel the countdown vary by platform:
    • WebTV: Any change in focus, or pressing any directional key, even if it does not result in a focus shift.
    • Responsive Web: Any mouse movement will cancel the countdown.

Interaction with Recommendations​

  • In the recommendation row, when a user clicks on an item, the system should provide two possible actions:
    • Start video directly: This is the default behavior.
    • Enable a callback: This allows the integrator to override the default behavior if necessary. The callback will be configurable and should trigger when the item is selected, instead of directly starting the video.

Autoplay Limitations​

  • To prevent continuous autoplay during inactive sessions, a ChainPlay timeout will be applied:
    • The system will pause autoplay after a set number of episodes (set by the platform or session) if there is no user interaction.
    • A session-based timer will track user inactivity. If the user does not interact with the device for a specified period, autoplay will be paused.
    • Any user interaction with the device (e.g., pressing buttons, moving the cursor, or tapping on the screen) will reset the inactivity timer, allowing autoplay to continue.

Migration procedure​

Key Changes​

  • In the videometadata, we need to introduce an array of medialists. The former related is transformed into a mediaList object.
  • What was previously an item within the related node, is now represented as a mediaListItem object.

Migration Mapping​

From "related" to Media List

Old Field
New Field
rowTitlerowTitle
imageFormatdefaulImageFormat
-
New Field
-defaultItemType = "related"
-defaultItemLayout = "vertical"
listIdlistId (must be generated)

From "items" to Media List Items

Old Field
New Field
itemIditemId
titletitle
captioncaption
imageUrlimageUrl
ccBadgeccBadge
adBadgeadBadge
-
New Field
-imageFormat = "tile"
-itemType = "related"
-itemLayout = "vertical"

Example: Transformation Process​

Input - Original Video Metadata JSON (Old Format)

  "related": {
"rowTitle": "diva_eop_related_title",
"defaulImageFormat": "tile",
"items": [{
"itemId": "12345678",
"title": "Long, long time",
"caption": "S1, E3",
"imageUrl": "url",
"ccBadge": false,
"adBadge": false
}]
}

Output - Converted Media List Format (New Format)

  {
"listId": "xxx",
"rowTitle": "diva_eop_related_title",
"defaulImageFormat": "tile",
"defaultItemType": "related",
"defaultItemLayout": "vertical",
"items": [
{
"itemId": "12345678",
"title": "Long, long time",
"caption": "S1, E3",
"imageUrl": "url",
"imageFormat": "tile",
"itemType": "related",
"itemLayout": "vertical",
"ccBadge": false,
"adBadge": false
}
]
}

Handling Item Click Events​

The onRelatedClick callback must be updated to onMediaListItemClick as part of this migration.

Migration Mapping

Old Callback
New Callback
onRelatedClickonMediaListItemClick

For further reference on the item click behavior, visit: 🔗 Customize the click behaviour