Sports Stats Integration Guide (Web)
Introduction
Maestro is a robust and full-featured platform for interactive video. Many customers use it as a full end-to-end solution, while others choose to integrate Maestro into their existing applications, sites, and workflows. This guide is designed for the latter; put simply, this implementation is one in which Maestro is a bolt-on to existing platforms.
Concepts & Definitions
This guide will teach you everything you need to get workflows set up for the core of interactive live sports streams.
Site: A maestro instance in the form of a site. Allows you to create engaging live and on-demand video experiences while leveraging the support of monetization tools, brandable pages, insightful analytics, community databases, and more.
Channel: Where you can broadcast live or pre-recorded video content, manage audience access, and integrate various engagement and monetization features. A single site can contain many channels.
Panels: Panels are interactive elements that can be integrated into your broadcasts. Maestro offers a variety of options, including:
- Live chat and watch parties for real-time audience engagement.
- Social feeds to display live updates from social media.
- Live shopping features to enable in-broadcast purchasing.
- Quests and leaderboards to gamify the viewing experience.
- Tipping and sponsor placements for monetization opportunities.
- Custom panels with iFrame support for tailored content.
Overlays: An element that allows users to broadcast contextual graphics and information at key moments of a stream. Engage with polls, raffles, live merch drops, and more. Use overlays to remind viewers to subscribe, sell tickets to an upcoming event, or showcase a tweet.
Web SDK: Allows you to integrate and interact with Maestro components directly on your website or app. With this SDK, you can access livestream content, display overlays at the correct times, interact with panels, and much more. Please refer to the documentation
Getting Started Guide
This guide will walk you through creating a complete live interactive sports experience. It is recommended that you launch your Maestro site (maestro.tv/sitename) in a browser while you conduct these steps.
The Approach
Here is a high-level diagram of what we’re going to create:
The Iframe will be loaded in as a secure IFrame via Maestro’s Web SDK into your target DIV.
Gathering Inputs
Before we get started, let’s gather the following values which we will use to make API calls:
- Site ID:
- You can obtain the Site ID from the Developer section in the admin sidebar. Navigate to Site Settings > Developer, then copy the value from the "Client ID" field. Despite the different names, they represent the same identifier.
- API Key:
- The API Key is accessible from the admin sidebar. Go to Site Settings > Developer, then select "Generate API Key." For more information, refer to the documentation
- Channel ID:
- To obtain the Channel ID, navigate to the desired channel and open the Developer Tools console. In the console, type INIT.OBJECT._id. This command will display the Channel ID.
Authentication
To interact with the API's you must first obtain a JWT.
{
email: "YOUR EMAIL",
password: "YOUR PASSWORD",
siteId: "YOUR SITE ID"
}
The account you use to sign in must have write access on the Overlays permission. For all of the requests you make, you must put the JWT received from login in the authentication header as a bearer token eg. Bearer {JWT}
Part 1: Overlays
Overlays interactive graphics that are used to drive calls to action during a stream. Maestro features a large library of built-in options, such as polls, raffles, and lower thirds. You can also create our own overlay templates using HTML and CSS, including support for dynamic data insertion.
In this example, we’ll be creating an overlay that is triggered programmatically and that is dynamically populated with data.
Creating an Overlay Template
Let’s create a new overlay template. Variables are defined using { } notation which we will use in the next step. Notice how you also have full control over the animation. We like using Lottie which is a performant and expressive animation format for the web. Motion graphics artists will be pleased to learn that they can export their AfterEffects videos directly into Lottie format.
You’ll want to store the returned overlay ID of this overlay for later.
POST https://api.maestro.io/overlay/v1/
curl --location 'https://api.maestro.io/overlay/v1' \
--header 'Authorization: Bearer ••••••' \
--header 'x-maestro-client-id: {{SITE_ID}}' \
--header 'Content-Type: application/json' \
--data '{
"contents": {
html: "<p>${var1}</p>"
},
"cta": {
"data": "hi",
"type": "login"
},
"description": "img overlay",
"duration": 10,
"name": "overlay 1",
"siteId": "{{SITE_ID}}",
"type": "template"
}'
Response Body:
{
"_id": "{{OVERLAY_ID}}"
"contents": {
"html": "<p>${var1}</p>"
},
"cta": {
"data": "hi",
"type": "login"
},
"description": “img overlay”,
"duration": 10, // seconds
"name": “overlay 1”,
"type": 'template' // see enum
}
Triggering an overlay
Used to trigger an overlay or an overlay template. With our fully populated overlay ready to go, it’s time to send this overlay out on the channel. At scale, we would recommend using a data feed of some kind like a stats feed or odds feed. Key events can be mapped to triggers
After hitting this API, you should see the overlay appear on your channel. Make sure you are on the channel via a browser that matches the channelId you are sending to.
POST https://api.maestro.io/overlay/v1/broadcast/{{OVERLAY_ID}}
curl --location 'https://api.maestro.io/overlay/v1/broadcast/{{OVERLAY_ID}}' \
--header 'Authorization: Bearer ••••••' \
--header 'x-maestro-client-id: {{SITE_ID}}' \
--header 'Content-Type: application/json' \
--data '{
"variables": {"var1": "howdy"}
}'
Request Body:
{
"contents": {
"html": "<p>${var1}</p>",
"templateId": "{{OVERLAY_ID}}",
"variables": {
"var1": "howdy"
}
},
"cta": {
"data": "hi",
"type": "login"
},
"description": "img overlay",
"duration": 10,
"name": "img-overlay",
"siteId": "{{SITE_ID}}",
}
Embedding Panels and Overlays via SDK
Now that you have your panels and overlays ready to go, let’s get them displayed over your video player. Maestro’s Web SDK renders overlay and panel components which we will put over our video player. When overlays are published via API, they will appear within that div. For the best experience, we recommend adding transparency to your overlays so that they look like they are floating over the video.
First, we need to initialize the Maestro SDK and specifically the theater embed mode.
import MaestroIFrameSDK, { EventType } from '@maestro_io/web-sdk';
import React, { createRef, useEffect, useRef } from 'react';
function App() {
const parentDiv = createRef<HTMLDivElement>();
const maestroRef = useRef<MaestroIFrameSDK>();
useEffect(() => {
maestroRef.current = new MaestroIFrameSDK({
element: parentDiv.current,
slug: 'your-site?embed=theater’,
});
// render the maestro platform; see documentation for view options arg
maestroRef.current!.render({ theater: true });
});
return (
<div className="main-view">
// insert your video-player or components here
// <MyComponent />
// MaestroSDK instance
<div ref={parentDiv} />
</div>
);
}
export default App;
Now that this is set up, we can repeat the previous step and see the overlay appear in the transparent div sitting over the video player. A powerful paradigm in Maestro that connects overlays and panels together is the Open Panel CTA. When configured on an overlay, a click event will open the corresponding panel. On mobile, the user will be prompted to flip their phone if watching in full screen.
For Overlays to work properly, special attention must be paid to how fullscreen works within your application. We recommend implementing your own version of fullscreen where divs can appear over the video as opposed to native full-screen functionality.
Part 2: Panels
Panels are the heart of interactivity. Maestro comes with a library of panels, including chat, Shopify, quests, multiple choice, and more. In this demo, we will set up a few panels and add them to the page alongside or below the video player.
Toggling on Panels
This can be done programmatically, but we’ll use the admin UI in this example.
If you haven’t done so already, load up your Maestro site, navigate to the desired channel via the toolbar, and then click Edit Channel at the right side of the toolbar.
Then click the gear to edit the sidebar area where panels live. From here, you’ll notice that you can very easily toggle functionality on and off. Turn a few on (at least 3)
Now that we have a couple of panels set up, let’s add those to our page via the Web SDK.
Putting It All Together
Now that we have both overlays and panels set up on our page, let’s test the interaction of these two components. First, we’ll create an Overlay Template with an Open Panel CTA and broadcast it:
POST https://api.maestro.io/overlay/v1
Body
{
"contents": {
"html": "<!DOCTYPE html>\n<html lang=\"en\">\n\t<head>\n\t\t<meta charset=\"UTF-8\" />\n\t\t<style>\n\t\t\t@import url(\"https://use.typekit.net/wgc7tbu.css\");\n\n\t\t\tbody,\n\t\t\thtml {\n\t\t\t\theight: 100%;\n\t\t\t\twidth: 100%;\n\t\t\t\toverflow: hidden;\n\t\t\t\tmargin: 0;\n\t\t\t\tpadding: 0;\n\t\t\t}\n\n\t\t\t#container {\n\t\t\t\tposition: absolute;\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\talign-content: center;\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t}\n\n\t\t\t#overlay {\n\t\t\t\tposition: fixed;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: flex-end;\n\t\t\t\talign-content: flex-end;\n\t\t\t\twidth: 557px;\n\t\t\t\theight: 190px;\n\t\t\t\tfont-family: \"interface\";\n\t\t\t\tright: 0;\n\t\t\t}\n\n\t\t\t.exit {\n\t\t\t\tanimation: slideOut 1s cubic-bezier(0.9, 0, 0.2, 0.8);\n\t\t\t\tanimation-iteration-count: 1;\n\t\t\t\tanimation-fill-mode: both;\n\t\t\t}\n\n\t\t\t@keyframes slideOut {\n\t\t\t\t0% {\n\t\t\t\t\ttransform: translateX(0);\n\t\t\t\t}\n\t\t\t\t100% {\n\t\t\t\t\ttransform: translateX(557px);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#row1 {\n\t\t\t\tposition: absolute;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: row;\n\t\t\t\twidth: 557px;\n\t\t\t\theight: 95px;\n\t\t\t\tline-height: 60px;\n\t\t\t\tbackground: #aa00cc;\n\t\t\t\tclip-path: polygon(0 0, 100% 0%, 100% 100%, 6.28% 100%);\n\t\t\t\tanimation: row1 1.8s cubic-bezier(0.7, 0, 0.2, 1);\n\t\t\t\tanimation-iteration-count: 1;\n\t\t\t\tanimation-fill-mode: both;\n\t\t\t\tanimation-delay: 0.5s;\n\t\t\t\ttransform: translateY(-35px);\n\t\t\t}\n\n\t\t\t@keyframes row1 {\n\t\t\t\t0% {\n\t\t\t\t\ttransform: translateX(557px) translateY(-35px);\n\t\t\t\t}\n\t\t\t\t50% {\n\t\t\t\t\ttransform: translateX(0) translateY(-35px);\n\t\t\t\t}\n\n\t\t\t\t100% {\n\t\t\t\t\ttransform: translateX(0) translateY(-35px);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#row2 {\n\t\t\t\tdisplay: flex;\n\t\t\t\tposition: absolute;\n\t\t\t\tflex-direction: row;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\talign-content: center;\n\t\t\t\twidth: 557px;\n\t\t\t\theight: 70px;\n\t\t\t\tbackground: #efefef;\n\t\t\t\tanimation: row2 1.8s cubic-bezier(0.7, 0, 0.2, 1) 0.5s both,\n\t\t\t\t\trow2-clip 2s cubic-bezier(0.7, 0, 0.2, 1) 1.3s both;\n\t\t\t\tz-index: 2;\n\t\t\t}\n\n\t\t\t@keyframes row2 {\n\t\t\t\t0% {\n\t\t\t\t\twidth: 557px;\n\t\t\t\t\ttransform: translateX(557px) translateY(16px);\n\t\t\t\t}\n\t\t\t\t50% {\n\t\t\t\t\twidth: 557px;\n\t\t\t\t\ttransform: translateX(0) translateY(16px);\n\t\t\t\t}\n\n\t\t\t\t100% {\n\t\t\t\t\twidth: 557px;\n\t\t\t\t\ttransform: translateX(0) translateY(10px);\n\t\t\t\t}\n\t\t\t}\n\t\t\t@keyframes row2-clip {\n\t\t\t\t0% {\n\t\t\t\t\tclip-path: polygon(2.17% 45%, 100% 45%, 100% 55%, 2.7% 55%);\n\t\t\t\t}\n\t\t\t\t50% {\n\t\t\t\t\tclip-path: polygon(0 0, 100% 0%, 100% 100%, 4.5% 100%);\n\t\t\t\t}\n\n\t\t\t\t100% {\n\t\t\t\t\tclip-path: polygon(0 0, 100% 0%, 100% 100%, 4.5% 100%);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#row3 {\n\t\t\t\tposition: absolute;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\talign-content: center;\n\t\t\t\twidth: 522px;\n\t\t\t\theight: 95px;\n\t\t\t\tbackground: #d8d8d8;\n\t\t\t\tclip-path: polygon(0 0, 100% 0%, 100% 100%, 6.1% 100%);\n\t\t\t\ttransform: translateX(557px) translateY(60px);\n\t\t\t\tanimation: row3 1.66s cubic-bezier(1, 0, 0.2, 1);\n\t\t\t\tanimation-iteration-count: 1;\n\t\t\t\tanimation-fill-mode: forwards;\n\t\t\t\tanimation-delay: 0.7s;\n\t\t\t}\n\n\t\t\t#row3::before {\n\t\t\t\tcontent: \"\";\n\t\t\t\tbackground: url(\"https://c16bf6dae818f25ef804-3497e1c83042e554e7f05925f38cc356.ssl.cf1.rackcdn.com/5dc5b1de73eaf700a476289b/5e7baf4912bb6600a4639b11.png\")\n\t\t\t\t\tno-repeat;\n\t\t\t\tposition: absolute;\n\t\t\t\twidth: 220px;\n\t\t\t\theight: 60px;\n\t\t\t\tright: -18px;\n\t\t\t\tbottom: 0;\n\t\t\t\ttransform: scale(0.8);\n\t\t\t}\n\n\t\t\t@keyframes row3 {\n\t\t\t\t0% {\n\t\t\t\t\ttransform: translateX(557px) translateY(60px);\n\t\t\t\t}\n\t\t\t\t50% {\n\t\t\t\t\ttransform: translateX(0) translateY(60px);\n\t\t\t\t}\n\n\t\t\t\t100% {\n\t\t\t\t\ttransform: translateX(0) translateY(60px);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#bet-title {\n\t\t\t\twidth: 180px;\n\t\t\t\theight: 60px;\n\t\t\t\tbackground: #aa00cc;\n\t\t\t\ttext-align: center;\n\t\t\t\tcolor: #fff;\n\t\t\t\tfont-size: 20px;\n\t\t\t\tfont-weight: 700;\n\t\t\t\tpadding-left: 20px;\n\t\t\t\tanimation: title 2s cubic-bezier(0.6, 0, 0.1, 1) 1.2s both;\n\t\t\t}\n\t\t\t@keyframes title {\n\t\t\t\t0% {\n\t\t\t\t\ttransform: translateX(200px);\n\t\t\t\t}\n\t\t\t\t50% {\n\t\t\t\t\ttransform: translateX(0);\n\t\t\t\t}\n\n\t\t\t\t100% {\n\t\t\t\t\ttransform: translateX(0);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#bet-subtitle {\n\t\t\t\twidth: 377px;\n\t\t\t\theight: 95px;\n\t\t\t\tpadding-left: 40px;\n\t\t\t\tcolor: #efefef;\n\t\t\t\tbackground: #2c2c2c;\n\t\t\t\tfont-size: 20px;\n\t\t\t\tclip-path: polygon(0 0, 100% 0%, 100% 100%, 9.2% 100%);\n\t\t\t\tanimation: subtitle 2s cubic-bezier(0.6, 0, 0.1, 1) 1.2s both;\n\t\t\t}\n\t\t\t@keyframes subtitle {\n\t\t\t\t0% {\n\t\t\t\t\ttransform: translateX(100px);\n\t\t\t\t\tclip-path: polygon(100% 0, 100% 0%, 100% 100%, 104% 100%);\n\t\t\t\t}\n\t\t\t\t50% {\n\t\t\t\t\ttransform: translateX(0);\n\t\t\t\t\tclip-path: polygon(0 0, 100% 0%, 100% 100%, 9.2% 100%);\n\t\t\t\t}\n\n\t\t\t\t100% {\n\t\t\t\t\ttransform: translateX(0);\n\t\t\t\t\tclip-path: polygon(0 0, 100% 0%, 100% 100%, 9.2% 100%);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#team1,\n\t\t\t#team2 {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\tjustify-content: space-evenly;\n\t\t\t\talign-items: center;\n\t\t\t\talign-content: center;\n\t\t\t\tfloat: left;\n\t\t\t\twidth: 220px;\n\t\t\t\tcolor: #2c2c2c;\n\t\t\t\theight: 60px;\n\t\t\t\topacity: 0;\n\t\t\t\tanimation: contentopacity 1s linear;\n\t\t\t\tanimation-iteration-count: 1;\n\t\t\t\tanimation-fill-mode: forwards;\n\t\t\t\tanimation-delay: 1.8s;\n\t\t\t}\n\n\t\t\t@keyframes contentopacity {\n\t\t\t\t0% {\n\t\t\t\t}\n\t\t\t\t50% {\n\t\t\t\t}\n\n\t\t\t\t100% {\n\t\t\t\t\topacity: 1;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t#team1 {\n\t\t\t\tpadding-right: 20px;\n\t\t\t\tpadding-left: 20px;\n\t\t\t\tborder-right: 1px solid #c0c0c0;\n\t\t\t}\n\n\t\t\t#team2 {\n\t\t\t\tpadding-left: 20px;\n\t\t\t\tpadding-right: 20px;\n\t\t\t\tborder-left: 1px solid #c0c0c0;\n\t\t\t}\n\n\t\t\t#bet-callout {\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tborder-radius: 5px;\n\t\t\t\tpadding: 10px 40px;\n\t\t\t\tcolor: #fff;\n\t\t\t\tfont-size: 18px;\n\t\t\t\tfont-weight: 700;\n\t\t\t\tanimation: buttonanimation 2s cubic-bezier(0.6, 0, 0.1, 1) 2s infinite,\n\t\t\t\t\tstripe 6s cubic-bezier(0.6, 0, 0.1, 1) 6s infinite;\n\t\t\t\ttransform: scale(0.9);\n\t\t\t\tbackground-image: repeating-linear-gradient(\n\t\t\t\t\t75deg,\n\t\t\t\t\t#ba80ea 20%,\n\t\t\t\t\t#ba80ea 35%,\n\t\t\t\t\t#aa00cc 35%,\n\t\t\t\t\t#aa00cc 100%\n\t\t\t\t);\n\t\t\t\tbackground-position-x: -85px;\n\t\t\t\tbackground-size: 150%;\n\t\t\t\tbottom: 10px;\n\t\t\t\tposition: absolute;\n\t\t\t}\n\n\t\t\t@keyframes buttonanimation {\n\t\t\t\t0% {\n\t\t\t\t\ttransform: scale(0.9);\n\t\t\t\t}\n\t\t\t\t30% {\n\t\t\t\t\ttransform: scale(1);\n\t\t\t\t}\n\n\t\t\t\t100% {\n\t\t\t\t\ttransform: scale(0.9);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t@keyframes stripe {\n\t\t\t\t0% {\n\t\t\t\t\tbackground-position-x: -85px;\n\t\t\t\t}\n\t\t\t\t10% {\n\t\t\t\t\tbackground-position-x: 115px;\n\t\t\t\t}\n\t\t\t\t100% {\n\t\t\t\t\tbackground-position-x: 115px;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t.team-name {\n\t\t\t\tfont-size: 20px;\n\t\t\t\twidth: 175px;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\n\t\t\t.team-odds {\n\t\t\t\twidth: 175px;\n\t\t\t\tborder-top: 2px solid #c0c0c0;\n\t\t\t\tfont-size: 18px;\n\t\t\t\tfont-weight: 700;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\n\t\t\t#moreteams {\n\t\t\t\tcolor: #aa00cc;\n\t\t\t\tborder-left: 2px solid #c0c0c0;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\tjustify-content: space-evenly;\n\t\t\t\talign-items: center;\n\t\t\t\talign-content: center;\n\t\t\t\tfloat: left;\n\t\t\t\twidth: 140px;\n\t\t\t\theight: 60px;\n\t\t\t\topacity: 0;\n\t\t\t\tanimation: contentopacity 1s linear;\n\t\t\t\tanimation-iteration-count: 1;\n\t\t\t\tanimation-fill-mode: forwards;\n\t\t\t\tanimation-delay: 1.8s;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\t\t</style>\n\t</head>\n\t<body>\n\t\t<div id=\"container\">\n\t\t\t<div id=\"overlay\">\n\t\t\t\t<div id=\"row1\">\n\t\t\t\t\t<div id=\"bet-title\">Who will win this match?</div>\n\t\t\t\t\t<div id=\"bet-subtitle\">{Start betting now!}</div>\n\t\t\t\t</div>\n\t\t\t\t<div id=\"row2\">\n\t\t\t\t\t<div id=\"team1\">\n\t\t\t\t\t\t<div class=\"team-name\">{nameTeam1}</div>\n\t\t\t\t\t\t<div class=\"team-odds\">{oddTeam1}</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div id=\"team2\">\n\t\t\t\t\t\t<div class=\"team-name\">{nameTeam2}</div>\n\t\t\t\t\t\t<div class=\"team-odds\">{oddTeam2}</div>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div id=\"moreteams\">\n\t\t\t\t\t\t+2 more\n\t\t\t\t\t</div>\n\t\t\t\t</div>\n\t\t\t\t<div id=\"row3\">\n\t\t\t\t\t<div id=\"bet-callout\">Bet Now!</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</body>\n</html>\n"
},
"description": "Start betting!",
"cta": {
"type": "show_panel",
"data": "YOUR_PANEL_ID"
},
"duration": 55,
"name": "bet",
"type": "template"
}
Now call this overlay in one of your channels programmatically:
POST http://api.maestro.io/overlay/v1/broadcast/OVERLAY_ID
Body:
{
"variables": {
"nameTeam1": "CF Montreal",
"nameTeam2": "Inter Miami",
"oddTeam1": "+250 (underdog)",
"oddTeam2": "-100",
},
"duration": 55,
"targetId": "CHANNEL_ID_HERE"
}
You should see the overlay appear and then on click, the panel should appear in the panel area. Great work! Now that we have the basics working, let’s discuss timecode and how to scale this with automation.
Part 3: Timecode
Timecode is critical for sports use cases to ensure data sync and prevent spoilers. To enable this ideal experience, your player must send this data to Maestro’s components via the postMessage API. Maestro’s components use that data to trigger overlays and update panel states.
Here is an example code snippet to display overlays according to video time update:
const overlaySchedule = {
// Set video times (seconds) to show overlays
30: overlayObject
};
const timeUpdateNow = () => {
const videoPlayer = document.querySelector('video');
videoPlayer.addEventListener('timeupdate', function (event) {
const time = Math.round(event.target.currentTime);
if (overlaySchedule[time]) {
// emit post event to show overlay
const message = {
source: 'maestro:iframe:sdk:v1',
action: 'show:overlay',
data: overlaySchedule[time],
};
window.postMessage(message, '*');
}
});
};
Maestro’s Overlay component uses this to trigger overlays at the exact right time for each viewer and the Panel component (when relevant) uses this data to update itself (e.g. stats, odds, etc.)
You should see the overlay when that future time arrives and you should be able to seek forward and backward in the video to trigger the overlay at the same relative time every time.
Part 4: Automation
When using Maestro at scale, you’ll want to automate as much as possible. Here are some tips and suggestions, though the specifics will vary depending on your desired implementation and use case.
Data Feed-Driven
Here’s an example of how this works with stats in particular:
There are a wide variety of mechanisms for Maestro to receive data from a stats provider and we are happy to chat with you about the specifics of your project and partners. In the case of Sportradar, data is pushed via API whenever there is an update.
In order to support timecode and a variety of use cases such as seeking/DVR, we effectively need a way to represent the game state at any given point in time. Maestro’s Snapshot DB is designed specifically for this use case, using strategies to optimize performance. If a provider has already built something like, we prefer to use it as the source of truth.
Channel, Panel, and Overlay Automation
Typically, a single channel is mapped to a given event. To programmatically create a channel:
POST https://api.maestro.io/pages/v3
Body:
{
"active_renderers": {
"livestream": true,
"sidebar": true
},
"data": {
"artwork": {
"video": {
"background": "https://static.maestro.io/media/5f32f379c04207002caf9c7f/5f32f653d7165500a4e50689.jpg",
"kind": "images"
}
},
"channel_select_active_image_url": "https://static.maestro.io/media/5f32f379c04207002caf9c7f/5f33601854d19e00a50baa13.png",
"content": {
"offline": {
"kind": "video",
"mode": "tv",
"video": {
"_id": "63dc2504cca55b002f7a6873"
}
}
},
"countdown": {
"title": "Countdown"
},
"default_mode": "none",
"gate": {
"active": false,
"gate": {
"background": {
"color": "#000000",
"desktop_image": "/static/assets/access_control_default_bg.b66267a.jpg",
"mobile_image": "/static/assets/access_control_default_bg.b66267a.jpg",
"opacity": 1,
"type": "image",
"use_gradient_mask": false
},
"logo_link": "",
"logo_title": "Your Brand",
"logo_url": "/static/assets/default-logo.0df12ab.png",
"navigation": {
"state": "on"
},
"subtitle": "Subtitle of the gate goes here. Tell your audience what to expect!\nThe better you can tease your audience. the higher the likelihood that they will get through the gate.",
"subtitle_raw_data": "<p><span style=\"color: rgb(255,255,255);font-size: 14px;\">Subtitle of the gate goes here. Tell your audience what to expect!</span></p>\n<p><span style=\"color: rgb(255,255,255);font-size: 14px;\">The better you can tease your audience. the higher the likelihood that they will get through the gate.</span></p>\n",
"title": "Title of the Gate",
"title_raw_data": "<p><span style=\"color: rgb(255,255,255);font-size: 30px;\">Title of the Gate</span></p>\n",
"subtitle_raw_data_v2": "{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"indent\":0,\"direction\":null,\"format\":\"left\",\"children\":[{\"type\":\"extended-text\",\"text\":\"Subtitle of the gate goes here. Tell your audience what to expect!\",\"detail\":0,\"format\":0,\"mode\":\"normal\",\"version\":1,\"style\":\"color:rgb(255, 255, 255);font-size:14px\"}],\"version\":1},{\"type\":\"paragraph\",\"indent\":0,\"direction\":null,\"format\":\"left\",\"children\":[{\"type\":\"extended-text\",\"text\":\"The better you can tease your audience. the higher the likelihood that they will get through the gate.\",\"detail\":0,\"format\":0,\"mode\":\"normal\",\"version\":1,\"style\":\"color:rgb(255, 255, 255);font-size:14px\"}],\"version\":1}],\"version\":1}}",
"title_raw_data_v2": "{\"root\":{\"type\":\"root\",\"children\":[{\"type\":\"paragraph\",\"indent\":0,\"direction\":null,\"format\":\"left\",\"children\":[{\"type\":\"extended-text\",\"text\":\"Title of the Gate\",\"detail\":0,\"format\":0,\"mode\":\"normal\",\"version\":1,\"style\":\"color:rgb(255, 255, 255);font-size:30px\"}],\"version\":1}],\"version\":1}}"
},
"kind": "password"
},
"name": "100% magic",
"schedule": [
{
"all_day": "yes",
"duration": 2090,
"end_time": "Nov 03 2017 01:00:00 GMT+0000 (GMT)",
"kind": "video",
"repeat": "none",
"start_date": "Nov 03 2017",
"start_time": "Nov 03 2017 00:00:00 GMT+0000 (GMT)",
"video": {
"_id": "5cd3501f74a7d0005b901b71",
"data": {
"name": "Test Video"
},
"slug": "test-video-man"
}
}
],
"sidebar": [
{
"_id": "5cd3501f74a7d0005b901b6f",
"seo": {
"title": "Chat"
},
"slug": "chat"
},
{
"_id": "5cd3501f74a7d0005b901b70",
"seo": {
"title": "Social"
},
"slug": "social"
}
],
"theme": {
"classic_theme_options": {
"accent_primary": "#6E5FFB",
"accent_secondary": "#4D42AD"
},
"id": "62ac0febeffc050e3ec6b42d",
"type": "custom"
},
"welcome_screen": {
"active": false,
"duration": 5,
"image": "",
"mobile_image": ""
}
},
"renderers": {
"livestream": {
"livestream_published": false,
"video_id": "64658c8640c1e5002f498372",
"stream_id": "61bbc377bb9f34002eaf08a7",
"source": null,
"studio_session_id": "64658b370677f19c21c56f9d"
},
"sidebar": {
"items": [
{
"array_id": "APlI1G-Br",
"id": "5fdac6b3ebe525f2631b357a"
},
{
"array_id": "FCqwzhtYH",
"id": "60679b671985358eae10d2c5"
},
{
"array_id": "10d68JgIQ",
"id": "60679fcc3375e4ac2afd127f"
}
],
"card_id": "5fa08a62aebba6154d3e5df1"
}
},
"seo": {
"description": "added seo",
"image": "https://static.maestro.io/media/5f32f379c04207002caf9c7f/5f33601854d19e00a50baa13.png",
"keywords": "live, stream",
"title": "100% magic"
},
"site_id": "5f32f379c04207002caf9c7f",
"slug": "100",
"base_language": "en-US",
"type": "channel"
}
Channels can also be duplicated which is another method of automation:
curl 'https://dev.api.maestro.io/page/v2/clone/{BASE_PAGE_ID_TO_CLONE}?localizedTitle={{NEW_PAGE_TITLE}}&localizedSlug={{NEW_PAGE_SLUG}} \
-X 'POST' \
-H 'accept: application/json, text/plain, */*' \
-H 'authorization: Bearer {{YOUR_TOKEN}}' \
-H 'content-length: 0' \
-H 'x-maestro-client-id: {{YOUR_SITE_ID}}
Once your channels are set up, you can programmatically trigger overlays. The logic used will be based on your particular goals and use cases. Think of the high level structure as:
Next Steps
Great work getting through the guide. You should now have the foundation set to take full advantage of Maestro’s interactive components within your application. We’re happy to help, so if you get stuck or have any questions, please reach out.