WebTV SDK 5.8
This guide explains how to integrate DIVA into a webTV application.
A VXP GitHub account is required to gain access to:
- Basic WebTV integration tagged by SDK versions.
- backend integration example
Requirements​
Our SDK is designed to be used on:
- SmartTV
- Tizen 2015 and up
- WebOS 2.0 and up
- Sky Boxes
- Amidala
- XiOne / Beethoven
- XBox
- XBox One
- XBox Series S
- PlayStation
- PS5
The SDK self-contains every dependency except:
- ReactJS, the host application most provide ReactJS 16+
- The video player, which loads dynamically based on target device and content type
Provisioning​
The Settings, with which DIVA will be configured.
The Dictionary, which will provide DIVA with the translations and localisations for the application.
The VideoMetaData, which provides DIVA with information about the playback for a specific item.
For more information about these items, please consult the Documentation.
This data can be provided as the application developer requires, but will need to return SettingClean, DictionaryClean and VideoMetaDataClean objects respectively in order to work with the DIVA configuration and initialise the player objects.
For the purpose of this example we will be using raw json, but the provision of the data can be provided from any available source.
We can as an example instantiate a mock 'video' item, from a potential video catalog. Crucial to DIVA 5 is the provision of an ID with which to retrieve settings information and a videoID to retrieve the VideoMetaData.
const videos = {
data: [
{
id: "item-1",
image: "./images/fifa-world-cup.jpg",
title: "France vs Croatia (minimal)",
videoId: "c6455bff-945f-42b2-af99-81dcd5aeba29",
settingId: "full",
},
],
};
Settings​
Setting for the application can be provided in various different formats and are used to provide configuration with which to run a particular video, for example:
Setting Example
{
"general":{
"audioSelectionMethod":"lang",
"closedCaptionSelectionMethod":"lang",
"expectedLiveDuration":3600000,
"increaseLiveDuration":600000,
"isMiddleTimelineEventsLineEnabled":true,
"isTimelineEventsVisibleWithCommentaryOpen":false,
"isVideoThumbnailPreviewEnabled":true,
"jumpLargeGaps":true,
"liveBackOff":30000,
"minimalLayoutWidth":600,
"pipMode":true,
"relevantCommentaryStartsVisible":false,
"resolveManifestUrl":true,
"smallGapLimit":0,
"trackVideoDataManifest":false,
"videoAnalyticsEventFrequency":60000,
"culture":"en-GB"
},
"alerts":{
"alertsPath":"https://<url>/alerts_demo.json",
"displayTime":10000
},
"customPlayByPlay":[
{
"key":"FirstExtraTimeEnd_Big",
"value":"https://<url>/icons/{p.density}/phase_end_big.png"
},
{
"key":"FirstExtraTimeEnd_Mini",
"value":"https://<url>/icons/{p.density}/phase_end_small.png"
},
{
"key":"FirstExtraTimeStart_Big",
"value":"https://<url>/icons/{p.density}/phase_start_big.png"
},
{
"key":"FirstExtraTimeStart_Mini",
"value":"https://<url>/icons/{p.density}/phase_start_small.png"
},
{
"key":"FirstHalfEnd_Big",
"value":"https://<url>/icons/{p.density}/phase_end_big.png"
},
{
"key":"FirstHalfEnd_Mini",
"value":"https://<url>/icons/{p.density}/phase_end_small.png"
},
{
"key":"FirstHalfStart_Big",
"value":"https://<url>/icons/{p.density}/phase_start_big.png"
},
{
"key":"FirstHalfStart_Mini",
"value":"https://<url>/icons/{p.density}/phase_start_small.png"
},
{
"key":"Goal_Big",
"value":"https://<url>/icons/{p.density}/goa_big.png"
},
{
"key":"Goal_Mini",
"value":"https://<url>/icons/{p.density}/goa_small.png"
},
{
"key":"OwnGoal_Big",
"value":"https://<url>/icons/{p.density}/ogo_big.png"
},
{
"key":"OwnGoal_Mini",
"value":"https://<url>/icons/{p.density}/ogo_small.png"
},
{
"key":"PenaltyEnd_Big",
"value":"https://<url>/icons/{p.density}/phase_end_big.png"
},
{
"key":"PenaltyEnd_Mini",
"value":"https://<url>/icons/{p.density}/phase_end_small.png"
},
{
"value":"https://<url>/icons/{p.density}/png_big.png",
"key":"PenaltyGoal_Big"
},
{
"key":"PenaltyGoal_Mini",
"value":"https://<url>/icons/{p.density}/png_small.png"
},
{
"key":"PenaltyStart_Big",
"value":"https://<url>/icons/{p.density}/phase_start_big.png"
},
{
"key":"PenaltyStart_Mini",
"value":"https://<url>/icons/{p.density}/phase_start_small.png"
},
{
"key":"PenaltyWrong_Big",
"value":"https://<url>/icons/{p.density}/ppw_big.png"
},
{
"key":"PenaltyWrong_Mini",
"value":"https://<url>/icons/{p.density}/ppw_small.png"
},
{
"key":"PhasePenaltyGoal_Big",
"value":"https://<url>/icons/{p.density}/ppg_big.png"
},
{
"value":"https://<url>/icons/{p.density}/ppg_small.png",
"key":"PhasePenaltyGoal_Mini"
},
{
"key":"PhasePenaltyWrong_Big",
"value":"https://<url>/icons/{p.density}/ppw_big.png"
},
{
"value":"https://<url>/icons/{p.density}/ppw_small.png",
"key":"PhasePenaltyWrong_Mini"
},
{
"key":"RedCard_Big",
"value":"https://<url>/icons/{p.density}/rc_big.png"
},
{
"key":"RedCard_Mini",
"value":"https://<url>/icons/{p.density}/rc_small.png"
},
{
"key":"SecondExtraTimeEnd_Big",
"value":"https://<url>/icons/{p.density}/phase_end_big.png"
},
{
"key":"SecondExtraTimeEnd_Mini",
"value":"https://<url>/icons/{p.density}/phase_end_small.png"
},
{
"key":"SecondExtraTimeStart_Big",
"value":"https://<url>/icons/{p.density}/phase_start_big.png"
},
{
"key":"SecondExtraTimeStart_Mini",
"value":"https://<url>/icons/{p.density}/phase_start_small.png"
},
{
"key":"SecondHalfEnd_Big",
"value":"https://<url>/icons/{p.density}/phase_end_big.png"
},
{
"key":"SecondHalfEnd_Mini",
"value":"https://<url>/icons/{p.density}/phase_end_small.png"
},
{
"key":"SecondHalfStart_Big",
"value":"https://<url>/icons/{p.density}/phase_start_big.png"
},
{
"key":"SecondHalfStart_Mini",
"value":"https://<url>/icons/{p.density}/phase_start_small.png"
},
{
"key":"Substitution_Big",
"value":"https://<url>/icons/{p.density}/sb_big.png"
},
{
"key":"Substitution_Mini",
"value":"https://<url>/icons/{p.density}/sb_small.png"
},
{
"key":"YellowCard_Big",
"value":"https://<url>/icons/{p.density}/yc_big.png"
},
{
"key":"YellowCard_Mini",
"value":"https://<url>/icons/{p.density}/yc_small.png"
}
],
"ecommerce":{
"feedUrl":"https://<url>/ecommerce/index.html?eventId={v.eventId}&culture={d.culture}",
"wordTag":"shop",
"ecommerceId":"e-commerce",
"iconUrl":"https://<url>/img/shop_icon_white.png",
"toleranceWindow":5000,
"showNotificationsOnce":false
},
"highlights":{
"startMode":"short",
"shortFilter":[
"GOAL",
"OwnGoal",
"PenaltyGoal"
],
"mediumFilter":[
"Goal",
"OwnGoal",
"PenaltyGoal",
"YellowCard",
"RedCard"
],
"longFilter":[
"*"
],
"liveFilter":[
"Goal",
"OwnGoal",
"PenaltyGoal",
"YellowCard",
"RedCard",
"Substitution"
]
},
"liveLike":{
"clientId":"WV6W1rkAJAAXAS9l0LpqHzjDyEcPbuGJjX7Kc2hk",
"chatEnabled":true,
"widgetThemeUrl":"https://<url>/LiveLike/customWidgetThemeUpdated.json"
},
"pushEngine":{
"configUrl":"https://<url>/DIVAProduct/www/Data/DivaDemoIBC/PushEngine/pushengineConfig_HBS.json",
"eCommerceCollectionName":"eCommerceDemo",
"eCommerceCollectionEnabled":false,
"editorialCollectionName":" "
},
"syncDataPanels":{
"dataFolderUrl":"https://<url>/DIVAProduct/www/Data/Diva5.0Test/output/OverlayLiteData/{V.EventId}.{d.Culture}/{OverlayID}.xml",
"renderingFolderUrl":"https://<url>/DIVAProduct/www/Data/Diva5.0Test/output/RenderingLiteData{n:ResourceURI}",
"trustedOrigins":"https://divadoc.deltatre.net,https://<url>"
},
"videoCast":{
"castBackground":"https://<url>/img/diva_chromecast.jpg",
"chromecastAppID":"<appID>"
}
}
For more information about setting and the available options click here.
Dictionary​
Dictionary must be provided based on the locale and will resemble something like the following:
Dictionary Example
{
"messages":{
"diva_go_live":"Go Live",
"diva_video_error":"This video is not working or not available in your region.",
"diva_error_button_ok":"OK",
"diva_menu_full_stats_button":"All Stats",
"diva_playbutton":"Play",
"diva_pausebutton":"Pause",
"diva_seekforward_button":"+{n}",
"diva_seekback_button":"-{n}",
"diva_alternate_timeline":"Alternate Timeline",
"diva_alternate_timeline_show_all":"See all events",
"diva_spoil":"SHOW EVENTS",
"diva_fullscreen":"Full Screen",
"diva_exitfullscreen":"Exit Full Screen",
"diva_alert":"Alerts",
"diva_multicam":"MULTICAM",
"diva_360clips":"360 CLIPS",
"diva_360multicam":"360 MULTICAM",
"diva_no_multicam":"Videos will appear as soon as they are published, please retry in a short while",
"diva_back_button":"Back",
"diva_chapters_open":"Chapters",
"diva_chapters_close":"Close Chapters",
"diva_theater_mode":"Theater Mode",
"diva_exit_theater_mode":"Exit Theater Mode",
"diva_mute":"Mute",
"diva_unmute":"Unmute",
"diva_show_multiview":"Show in sideBySide",
"diva_collapse_this_video":"Collapse video",
"diva_close_this_video":"Close video",
"diva_chromecast":"Chromecast",
"diva_close_stats_button":"",
"diva_audio_is_muted":"Audio is muted",
"diva_vr_start_video_loading":"VR loading ...",
"diva_button_close":"Close",
"diva_gck_connect_to_device":"Cast to",
"diva_gck_stop_casting":"Stop casting",
"diva_gck_cancel":"Cancel",
"diva_videometadata_error":"The video metadata are corrupted. Please try another video",
"diva_overlay_load_failure":"Could not load data panel",
"diva_drm_error":"Content protection error",
"diva_ssai_request_error":"This video is not working right now. Please disable any AdBlocker and then try again. In case this error persist please contact the Customer Care at help@diva.com",
"diva_no_highlights_error":"No key moments available at the moment. Please try later",
"diva_settings":"Settings Panel",
"diva_settings_button":"Settings",
"diva_error_title":"Error",
"diva_audio":"Audio Tracks",
"diva_cc_panel_title":"Subtitles",
"diva_closed_caption":"Closed Caption",
"diva_cc_disabled":"Disabled",
"diva_cc_d3608":"Enable CC",
"diva_cc_english":"English",
"diva_cc_french":"FRENCH",
"diva_cc_spanish":"Spanish",
"diva_cc_#1 Fre":"French",
"diva_cc_#2 Eng":"English",
"default_audio_selected":"Default Audio",
"diva_settings_hdr_enable":"Enable HDR",
"diva_adResumeTime":"Your video will resume in {remTime} s",
"diva_adResumeAt":"Your video will resume after the following advertisements {cVideo}/{totVideo}",
"diva_ad_loading_text":"Ad loading",
"diva_adblock":"You need to disable AdBlock to view the content",
"diva_adClickToEnableAudio":"Enable ad audio",
"diva_select_chapter":"Chapter List",
"diva_alert_replay":"Replay",
"diva_alert_back":"Back to",
"diva_highlightsmode_loading":"Highlights cards processing...",
"diva_highlightsmode_howto":"How do you want to watch this match?",
"diva_highlightsmode_short":"Short",
"diva_highlightsmode_medium":"Medium",
"diva_highlightsmode_long":"Long",
"diva_highlightsmode_live":"Live",
"diva_highlightsmode_full":"Full Match",
"diva_highlightsmode_resume":"Resume Highlights",
"diva_highlightsmode_next":"Next",
"diva_highlightsmode_highlights":"Highlights",
"diva_highlightsmode_youarein":"You are in highlights mode",
"diva_highlightsmode_notification_title":"Don't miss {n} key moments",
"diva_highlightsmode_notification_subtitle":"Would you like to watch them?",
"diva_highlightsmode_notification_watch":"Watch",
"diva_highlightsmode_highlights_title":"Highlights duration:",
"diva_hours":"h",
"diva_minutes":"m",
"diva_seconds":"s",
"diva_playbyplay":"Commentary",
"diva_noplaybyplay":"First message will be displayed shortly after the start of the session",
"diva_recommendation_watch_again":"Watch again",
"diva_recommendation_watch_next_video":"Watch the next video",
"diva_recommendation_next_videos":"Next videos",
"diva_recommendation_video_autoload":"Next video will start in {sec} sec",
"shop":"Shop",
"diva_ecommerce_rotate_device":"Rotate your screen to view the shop",
"diva_settings_close":"",
"diva_cc_enabled":"Enabled",
"diva_settings_pip_enable":"Enable Picture-In-Picture",
"diva_enterpip":"Enter pip mode",
"diva_exitpip":"Exit pip mode",
"diva_novideoavailable":"",
"diva_airplay_error":"Error trying to cast to AirPlay",
"diva_airplay_forbiden_error":"AirPlay cast disabled in Diva settings",
"diva_live":"Live Now",
"diva_settings_title":"Settings",
"diva_settings_hdr_label":"HDR",
"diva_settings_hdr_description":"Disable HDR if you are experiencing problems seeing the colours in the video",
"diva_at_button_tooltip":"Audio",
"diva_at_settings_title":"Audio tracks",
"diva_cc_button_tooltip":"Subtitles",
"diva_cc_settings_title":"Subtitles",
"diva_cc_eng":"english (lang)",
"diva_cc_en":"english (lang)",
"diva_airplay_forbidden_error":"AirPlay cast disabled in Diva settings",
"diva_timeline_events_title":"Key moments",
"diva_videolist_watching":"WATCHING",
"diva_videolist_live":"LIVE",
"diva_cc_enhancements_button":"Caption style",
"diva_cc_enhancements_button_subtitle":"Change text size and style",
"diva_cc_enlarge_label":"Enlarge text",
"diva_cc_caption_style_title":"Caption style",
"diva_cc_enlarge_subtitle":"Enlarge text",
"diva_cc_enlarge_description":"Activate for a significant increase of the subtitles text, for better readability",
"diva_cc_style_subtitle":"Style",
"diva_cc_style_description":"Activate to enable a darker background behind the subtitles text for increased visibility",
"diva_cc_style_label":"Style",
"diva_settings_hdr_ON_value":"On",
"diva_settings_hdr_OFF_value":"Off",
"diva_eop_starts_in_seconds":"Starts in {seconds} sec",
"diva_eop_reccomandation_title":"Recommendations",
"diva_eop_rewind":"Replay",
"diva_ccat_panel_title":"Audio and Subtitles",
"diva_accessibility_high_contrast_title":"Increase contrast",
"diva_accessibility_high_contrast_description":"Turn on increase contrast mode if you are having trouble seeing the menu",
"diva_accessibility_high_contrast_label":"Increase contrast",
"diva_videolist_default_title":"Default Title",
"diva_videolist_title_highlight":"Highlights",
"diva_fullmatch_highlights_button_label":"Full Match",
"diva_next_highlights_button_label":"Next",
"diva_videolist_highlight":"HIGHLIGHTS",
"diva_highlights_title_medium":"Highlights medium",
"diva_highlights_title_long":"Highlights long",
"diva_highlights_title_short":"Highlights short",
"diva_cc_caption_style_header":"Caption style",
"diva_cc_settings_header":"Subtitles",
"diva_cc_settings_subheader":"Style",
"diva_cc_enlarge_subheader":"Enlarge text",
"diva_highlights_title_live":"Live highlights",
"diva_highlights_notification":"Match Highlights",
"diva_highlights_badge_live":"Live Highlights",
"diva_highlights_badge_short":"Short Highlights",
"diva_highlights_badge_medium":"Medium Highlights",
"diva_highlights_badge_long":"Long Highlights",
"diva_highlights_alert_seek_unavailable":"Return to the full match to use rewind and fast forward",
"diva_pinned":"Pinned",
"diva_menu_button":"MENU",
"diva_data_panel_close_button":"Close Data Panel",
"diva_menu_close_button":"Close Menu",
"diva_data_panel_button_tooltip":"Stats",
"diva_data_panel_no_data_available_title":"No data available yet",
"diva_data_panel_no_data_available_description":"Please check back here shortly after the game has started!",
"diva_eop_replay":"Replay",
"diva_swap_video":"Swap video"
}
}
For more information about dictionary and the available options click here.
VideoMetaData​
In order to support decoupled OVP provision, DIVA 5 requires that video meta data is retrieved by the integrating application and passed to DIVA 5 as part of the initialisation parameters, an example of which is as follows:
VideoMetadata Example
{
"title":"Video title",
"image":"https://<thumbnail_url>.jpg",
"eventId":"108606",
"programDateTime":"2018-07-15T13:40:15.681Z",
"trimIn":4680858,
"trimOut":11641166,
"ad":"https://<vast_url>/skippable2.xml",
"sources":[
{
"uri":"https://<url>/<video_id>.ism/manifest(format=m3u8-aapl,filter=hls)",
"format":"HLS"
}
],
"audioTracks":[
{
"label":"English",
"id":"English1",
"selector":"English"
}
],
"defaultAudioTrackId":"English1",
"videoLists":[
{
"feedUrl":"https://<url>/rss/108606.xml",
"menu":"Other cameras",
"message":"Watch the live match from different angles",
"behaviour":"multistreamSwitch",
"id":"multistream",
"pollingInterval":30
},
{
"menu":"Other matches",
"message":"Watch other live mathes",
"highlightColor":"0xE65100",
"highlightColorLight":"0xff0000",
"id":"videolist",
"feedUrl":"https://<url>/rss/videolistSwitchDemo.xml"
}
],
"customAttributes":{
"chatId":"444ecf59-dbf2-43db-ae09-99926714022a",
"chatType":"influencer"
},
"recommendation":{
"feedUrl":"https://<url>/rss/videolist_recommendation_short.xml",
"autoLoadTime":0
},
"behaviour":{
"spoilerMode":"highlights"
},
"videoId":"<video_id>"
}
VideoMetadata Provider
// Type definition
export type VideoMetadataProvider = (
videoId: string,
currentVideoMetadata: VideoMetadataClean | undefined,
playbackState?:
| {
chromecast?: boolean | undefined;
hdrMode?: boolean | undefined;
}
| undefined
) => Promise<VideoMetadata>;
// Implementation example
export const requestVideometadata: VideoMetadataProvider = (videoId, currentVideoMetadata, playbackState) => {
videoId = decodeURIComponent(videoId);
const platform = playbackState?.chromecast === true ? 'chromecast' : 'html5';
const hdrType = playbackState?.hdrMode === true ? 'hdr10' : 'none';
const url = `https://<videoMetadataServiceUrl>/${videoId}?platform=${platform}&hdrType=${hdrType}`;
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open('GET', url, true);
xhr.responseType = 'json';
xhr.onload = function () {
if (this.status >= 200 && this.status < 300) {
resolve(xhr.response);
} else {
reject(new Error(`${this.status} ${xhr.statusText}`));
}
};
xhr.onerror = function () {
reject(xhr.statusText);
};
xhr.send();
});
};
Provision must be done with the same signature as above so that DIVA internally is able to call the VideoMetaData provider and if necessary recall based on interval polling if the application requires data to be refreshed, i.e.
if (!isNaN(divaParams.videoPolling) && divaParams.videoPolling > 0) {
setInterval(function () {
divaAPI.requestVideoMetadataUpdate();
}, divaParams.videoPolling * 1000);
}
With the ability to return VMD, we then have all the elements required to instantiate a DIVA session.
For more information about Video Meta Data please click here.
Entitlement​
Entitlement and Entitlement Provider documentation.
SDK Setup​
- Add
@deltatre-vxp:registry=https://npm.pkg.github.com
to your.npmrc
file - Install the SDK
npm install @deltatre-vxp/diva-sdk
DIVA Configuration​
Once the data has been provisioned, we combine the variables into a set of parameters to instantiate DIVA.
Property | Type | Default | Description |
---|---|---|---|
Mandatory | |||
Property: init | type: | Default: NA | init config for DIVA |
Property: dictionary | type: | Default: NA | DIVA dictionary |
Property: videoMetadataProvider | type: | Default: NA | Function used by DIVA to retrieve videoMetadata |
Property: libs | type:object | Default: NA | Object containing the external libraries' url lazy loaded by DIVA only when needed. Libs reference |
Optional | |||
Middlewares | |||
Property: entitlementProvider | type: | Default: NA | Function used by DIVA to perform entitlement and heartbeat calls |
Callbacks | |||
Property: setAPI | type:(apiRef: DivaAPI)=> void | Default: undefined | provide as soon as possible an imperative api to interact with the player |
Property: onVideoError | type:(error: VideoError, videoMetadata: VideoMetadata) => void | Default: undefined | notify about video playing errors |
Property: onEvent | type:(event: { type: 'BACK_PRESS_OUTSIDE' }) => boolean | Promise<boolean> | Default: undefined | actually used only for cases when the user is leaving the player with BACK_PRESS_OUTSIDE |
Property: onAnalytics | type:(event: GenericEvent) => void | Default: undefined | notify analytics events |
Property: onMediaAnalytics | type:(event: MediaEvent) => void | Default: undefined | notify media analytics events |
Property: onTtsMessage | type:(data: TtsData) => void | Default: undefined | notify text to be converted to speech fo accessibility |
Property: onPlayerPosition | type:(position: { relativePosition: number; absolutePosition: Date }) => void | Default: undefined | notify as frequent as possible the position reached by the player in the playing video |
Property: onPlayerMaxPosition | type:(position: { relativePosition: number; absolutePosition: Date }) => void | Default: undefined | Called when player max position (both relative to the video logical start and unix timecode) of the main video changes |
Property: onPlaybackRate | type:(rate: number) => void | Default: undefined | Called when playback speed of the main video changes |
Property: onPlayerState | type:(state: PlayerState) => void | Default: undefined | Called when player state of the main video changes |
Property: onPreferredAudioTrack | type:(value: string) => void | Default: undefined | notify about changes in the audiotrack selected by the user |
Property: onPreferredCCTrack | type:(value: string) => void | Default: undefined | notify about changes about cc selected by the user |
Property: onVideoEnd | type:()=>void | Default: undefined | notify when the playing video reaches the end |
Property: onPlaybackSession | type:(value: {id: string; playbackSessionId: string }) => void | Default: undefined | Called on DIVA playback session changed. It returns also a unique identifier of the diva item. |
Property: onVideoMetadataChanges | type:(videoMetadata: VideoMetadataClean)=>void | Default: undefined | called when videometadata of the main video changes |
Properties | |||
Property: keyboardActive | type:boolean | Default: true | flag to enable/disable keyboard navigation support |
Property: navigationActive | type:boolean (reactive on React) | Default: true | flag to enable or disable navigation inside the component |
Property: keyboardActive | type:boolean (reactive on React) | Default: true | flag to enable/disable keyboard navigation support |
Property: remoteActive | type:boolean (reactive on React) | Default: true | flag to enable/disable navigation through remote |
Property: gamepadActive | type:boolean (reactive on React) | Default: true | flag to enable/disable gamepad navigation support |
Property: noTransitions | type:boolean | Default: false | flag to disable css transitions |
Property: noControlsGradientBackground | type:boolean | Default: false | flag to disable css gradient backgrounds |
Property: unfreezeShakaAtMediaQualityChanges | type:boolean | Default: false | flag to toggle play pause in case of bitrate switches. Hack for sky xione and harmonic stream. It needs at least shaka 3.3.19 |
DivaWebTVInitType​
Accessibility Options​
Typescript interfaces for Accessibility Options​
AccessibilityData and AccessibilityData
export interface AccessibilityData {
/**
* closed captions with special style to improve legibility
*/
darkerBackgroundCc?: boolean;
/**
* closed captions bigger font size to improve legibility
*/
enlargedCcs?: boolean;
/**
* background of transparent system opaque to improve legibility
*/
opaqueBackground?: boolean;
}
export interface AccessibilityOptions extends AccessibilityData {
/**
* if true disable the possibility to change enlargedCcs and darkerBackgroundCc
*/
ccEnhancementsDisabled?: boolean;
/**
* if true disable the possibility to change opaqueBackground
*/
hideTransparencyDisabled?: boolean;
/**
* notify when there are changes on accessibility configuration
*/
onAccessibilityUpdate?: (data: AccessibilityData) => void;
}
Configuration example
{
init: {
videoId: "VIDEO_ID"
autoplay: true,
bitratePreferences: {
max: -1,
min: -1,
starting: -1,
useLast: false
},
deepLinkType: "relative",
deepLinkValue: "1200000",
hdrMode: false,
setting: {
general: {
culture: "en-GB",
isCommentaryFilteredByChapter: false
}
}
},
dictionary: {
messages: {
default_audio_selected: "Default Audio",
diva_360clips: "360 CLIPS",
diva_360multicam: "360 MULTICAM",
...
},
libs: {
googleCast: "https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1",
googleDAI: "https://imasdk.googleapis.com/js/sdkloader/ima3_dai.js",
googleIMA: "https://imasdk.googleapis.com/js/sdkloader/ima3.js",
hlsJs: "https://cdn.jsdelivr.net/npm/hls.js@1.5.7",
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",
threeJs: "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.148.0/three.min.js",
},
onAnalyticEvent: console.log,
onMediaAnalyticEvent: console.log,
onVideoError: console.log,
setAPI: (api) => {
// Here you can save a reference to DIVA APIs
window.divaAPI = api;
},
videoMetadataProvider: (videoId, currentVideoMetadata, playbackState) => {
// returning a Promise of a VideoMetadata
return new Promise((resolve, reject) => {
resolve({
title: "<video_title>",
videoId: '<video_id>',
sources: [
{
format: "HLS",
uri: "https://<url>"
}
],
...
});
});
}
}
Initialization​
- React App
- No React
- No package manager
import React, { useEffect, useState } from "react";
import { DivaWebTV } from "@deltatre-vxp/diva-sdk/diva-webtv-sdk/index{targetEnv}.js";
import "@deltatre-vxp/diva-sdk/diva-webtv-sdk/index{targetEnv}.css";
const SETTINGS_URL = "https://example.com/settings";
const DICTIONARY_URL = "https://example.com/dictionary";
const VIDEO_METADATA_URL = "https://example.com/metadata";
const VIDEO_ID = "42";
const videoMetadataProvider = async (videoId) => {
return await fetch(`${VIDEO_METADATA_URL}/${videoId}`);
};
const getSettings = async () => {
const data = await fetch(SETTINGS_URL);
return await data.json();
};
const getDictionary = async () => {
const data = await fetch(DICTIONARY_URL);
return await data.json();
};
export function App() {
const [setting, setSettings] = useState(null);
const [dictionary, setDictionary] = useState(null);
useEffect(() => {
Promise.all([getSettings, getDictionary]).then(([setting, dictionary]) => {
setSettings(setting);
setDictionary(dictionary);
});
}, []);
if (!(setting && dictionary)) {
return null;
}
return (
<div className="app">
<DivaWebTV
init={{
setting,
videoId: VIDEO_ID,
autoplay: true,
}}
dictionary={dictionary}
videoMetadataProvider={videoMetadataProvider}
entitlementProvider={entitlementProvider}
/>
</div>
);
}
import { createDivaWebTV } from "@deltatre-vxp/diva-sdk/diva-webtv-sdk/index{targetEnv}.js";
import "@deltatre-vxp/diva-sdk/diva-webtv-sdk/index{targetEnv}.css";
const SETTINGS_URL = "https://example.com/settings";
const DICTIONARY_URL = "https://example.com/dictionary";
const VIDEO_METADATA_URL = "https://example.com/metadata";
const VIDEO_ID = "42";
const videoMetadataProvider = async (videoId) => {
return await fetch(`${VIDEO_METADATA_URL}/${videoId}`);
};
const getSettings = async () => {
const data = await fetch(SETTINGS_URL);
return await data.json();
};
const getDictionary = async () => {
const data = await fetch(DICTIONARY_URL);
return await data.json();
};
const el = document.getElementById("video-player-container");
(async () => {
const [setting, dictionary] = await Promise.all([getSettings, getDictionary]);
const diva = createDivaWebTV(el, {
init: {
setting,
videoId: VIDEO_ID,
autoplay: true,
},
dictionary: dictionary,
videoMetadataProvider: videoMetadataProvider,
entitlementProvider: entitlementProvider,
});
// DIVA object's functions available
// diva.setNavigationActive(value: boolean);
// diva.setKeyboardActive(value: boolean);
// diva.setRemoteActive(value: boolean);
// diva.setGamepadActive(value: boolean);
// diva.unmount();
})();
Example of integration of DIVA in a simple JavaScript project without package manager.
<html>
<head>
<!-- ... -->
<script src="path/to/diva/index.reactBundled{targetEnv}.js"></script>
<link rel="path/to/diva/index.reactBundled{targetEnv}.css" />
</head>
<body>
<!-- ... -->
<div id="diva-container"></div>
<!-- ... -->
<script>
const SETTINGS_URL = "https://example.com/settings";
const DICTIONARY_URL = "https://example.com/dictionary";
const VIDEO_METADATA_URL = "https://example.com/metadata";
const VIDEO_ID = "42";
const videoMetadataProvider = async (videoId) => {
return await fetch(`${VIDEO_METADATA_URL}/${videoId}`);
};
const getSettings = async () => {
const data = await fetch(SETTINGS_URL);
return await data.json();
};
const getDictionary = async () => {
const data = await fetch(DICTIONARY_URL);
return await data.json();
};
const el = document.getElementById("diva-container");
(async () => {
const [setting, dictionary] = await Promise.all([getSettings, getDictionary]);
const diva = divaWebtv.createDivaWebTV(el, {
init: {
setting,
videoId: VIDEO_ID,
autoplay: true,
},
dictionary: dictionary,
videoMetadataProvider: videoMetadataProvider,
entitlementProvider: entitlementProvider,
});
// DIVA object's functions available
// diva.setNavigationActive(value: boolean);
// diva.setKeyboardActive(value: boolean);
// diva.setRemoteActive(value: boolean);
// diva.setGamepadActive(value: boolean);
// diva.play();
// diva.pause();
// diva.skipBack();
// diva.skipFoward();
// diva.openSubtitlePanel();
// diva.openAudioPanel();
// diva.openSettingsPanel();
// diva.openVideoList();
// diva.sendCustomKeypress(command, interactive);
// diva.getCurrentPosition();
// diva.getCurrentPositionAbsolute();
// diva.getCurrentDuration();
// diva.closeDiva();
// diva.unmount();
})();
</script>
</body>
</html>
{targetEnv}
:.min
(production),
API​
The property setAPI
inside DIVA Configuration is intended to be a function that returns an object containing all the APIs for the current DIVA instance.
DIVA API reference list:
interface DivaAPI {
/**
* It will force DIVA to request a new VideoMetadata through videoMetadataProvider
*/
requestVideoMetadataUpdate: (videoId?: string) => void;
// Getters
/**
* It returns the player position (both relative to the video logical start and unix timeCode as Date) of the video.
*/
getPlayerPosition: (videoId?: string) => { relative: number; absolute: Date };
/**
* It returns the player duration (both relative to the video logical duration and absolute end point as a Date) of the video.
*/
getPlayerDuration: (videoId?: string) => { relative: number; absolute: Date };
// Actions
/**
* It pauses video playback..
*/
pause: () => void;
/**
* It starts the video playback.
* @returns the videoElement play promise.
*/
play: () => Promise<void>;
/**
* It seeks the video playback.
* @param value number, milliseconds value relative to the video start time.
* @returns A promise usefull to check when the action is done.
*/
seek: (value: number) => Promise<void>;
/**
* It seeks back the video playback by the value get from videoMetadata.behaviour.seekInterval. Default 10 seconds.
*/
skipBack: () => Promise<void>;
/**
* It seeks forward the video playback by the value get from videoMetadata.behaviour.seekInterval. Default 10 seconds.
* @returns A promise usefull to check when the action is done.
*/
skipForward: () => Promise<void>;
/**
* It seeks the video playback by percentage.
* @param value number, percentage from 0 to 100.
* @returns A promise usefull to check when the action is done.
*/
seekPercentage: (value: number) => Promise<void>;
/**
* It seeks the video playback by Date object.
* @param value Date
* @returns A promise usefull to check when the action is done.
*/
seekAbsolute: (value: Date) => Promise<void>;
/**
* It sets the minimum distance between icons on timeline.
* @param value number, in pixels. Default 40. Take in considerations 1080p resolution..
*/
setTimelineIconsMinDistance: (value: number) => void;
// Advanced UI actions
/**
* It opens audio panel.
*/
openAudioPanel: () => void;
/**
* It opens subtitles panel.
*/
openSubtitlePanel: () => void;
/**
* It opens settings panel.
*/
openSettingsPanel: () => void;
/**
* It opens videolist view.
*/
openVideoList: () => void;
/**
* It sends a custom key press.
*/
sendCustomKeypress: (key: Key, e?: KeyboardEvent) => void;
/**
* Fires an event of the specified type with an optional payload.
* Used for Ad beaconing.
* @param type - The type of the event.
* @param payload - The payload associated with the event.
*/
fireEvent: (type: AnalyticsMediaEventType, payload?: { ad: Ad }) => void;
/**
* Exposed path resolver
*/
resolve(
value: string,
options?: {
videoId?: string; // if not provided it will fallback to main item
data?: Record<string, string>;
dontApplyDefault?: boolean;
}
): string;
/**
* Single command entry point for: mute, pause, play, set playback rate, seek, seek absolute
*/
sendPlayerCommand: (command: DivaCommand, interactive: boolean) => void | Promise<void>;
}
// DIVA Commands interfaces
export enum DivaCommandName {
MUTE = 'mute',
PAUSE = 'pause',
PLAY = 'play',
PLAYBACK_RATE = 'playback-rate',
SEEK = 'seek',
SEEK_ABSOLUTE = 'seek-absolute',
}
interface DivaCommandBase {
videoId?: string;
command: DivaCommandName;
}
export interface DivaCommandMute extends DivaCommandBase {
command: DivaCommandName.MUTE;
value: boolean;
}
export interface DivaCommandPause extends DivaCommandBase {
command: DivaCommandName.PAUSE;
}
export interface DivaCommandPlay extends DivaCommandBase {
command: DivaCommandName.PLAY;
}
export interface DivaCommandPlaybackRate extends DivaCommandBase {
command: DivaCommandName.PLAYBACK_RATE;
value: number;
}
export interface DivaCommandSeek extends DivaCommandBase {
command: DivaCommandName.SEEK;
value: number; // Milliseconds relative to trim in
}
export interface DivaCommandSeekAbsolute extends DivaCommandBase {
command: DivaCommandName.SEEK_ABSOLUTE;
value: Date;
}
export type DivaCommand =
| DivaCommandMute
| DivaCommandPause
| DivaCommandPlay
| DivaCommandPlaybackRate
| DivaCommandSeek
| DivaCommandSeekAbsolute;
Logging​
It is possible to attach to DIVA logs or even to use DIVA logger by:
- With package manager
- No package manager
import { onLog, getLogger } from "@deltatre-vxp/diva-sdk/diva-webtv-sdk";
import type {BoAdapterWebTvProps} from "@deltatre-vxp/diva-sdk/diva-webtv-sdk";
// Attach to DIVA logs
const unsubscribe = onLog((log: LogEntry) => {
console.log(log.message, log);
});
// Make use of DIVA logger
const { logDebug, logError, logInfo, logWarning } = getLogger('GENERIC:APP');
logDebug('log message', {
whatever: 'details'
});
// Unsubscribe from DIVA logs
unsubscribe();
<html>
<head>
<!-- ... -->
<script src="path/to/diva/index.reactBundled{targetEnv}.js"></script>
</head>
<body>
<!-- ... -->
<script>
const { onLog, getLogger } = divaWebTv;
// Attach to DIVA logs
const unsubscribe = onLog((log) => {
console.log(log.message, log);
});
// Make use of DIVA logger
const { logDebug, logError, logInfo, logWarning } = getLogger('GENERIC:APP');
logDebug('log message', {
whatever: 'details'
});
// Unsubscribe from DIVA logs
unsubscribe();
</script>
</body>
</html>
{targetEnv}
:.min
(production),
Platform detection​
It is possible to detect the actual platform running in a consistent way with DIVA WebTV using methods exposed:
function | type | description |
---|---|---|
isSkyQ | ()=>boolean | true if SkyQ box is detected |
isTizen | ()=>boolean | true if Tizen TV is detected |
isWebOS | ()=>boolean | true if WebOS TV is detected |
getSmartTvPlatform | ()=>WebTvPlatformTypes | return the platform detected |
type WebTvPlatformTypes = 'Tizen' | 'WebOs' | 'SkyQ' | 'Generic';
- With package manager
- No package manager
import { isSkyQ } from "@deltatre-vxp/diva-sdk/diva-webtv-sdk";
import type { DivaWebTVProps } from "@deltatre-vxp/diva-sdk/diva-webtv-sdk";
const skyFixes: Partial<DivaWebTVProps> = {
noControlsGradientBackground: true,
noTransitions: true,
unfreezeShakaAtMediaQualityChanges: true,
};
// props will contain sky fixes only if diva notice that the app is running on supported Sky boxes
const getPropsWithSkyFixes = (props:DivaWebTVProps):DivaWebTVProps => ({
...props,
...isSkyQ() ? skyFixes : {}
})
<html>
<head>
<!-- ... -->
<script src="path/to/diva/index.reactBundled{targetEnv}.js"></script>
</head>
<body>
<!-- ... -->
<script>
const { isSkyQ } = divaWebTv;
const skyFixes = {
noControlsGradientBackground: true,
noTransitions: true,
unfreezeShakaAtMediaQualityChanges: true,
};
// props will contain sky fixes only if diva notice that the app is running on supported Sky boxes
const getPropsWithSkyFixes = (props) => ({
...props,
...isSkyQ() ? skyFixes : {}
})
</script>
</body>
</html>
{targetEnv}
:.min
(production),