HOW TO
INTEGRATE IT IN WEB
Insert a script tag defining the desired fixture, the bookmaker source and end user's device ID (MAID). The additional required parameters will depend on where the user is streaming.
When streaming to an end user located within the United States, provide at least one of the following: end user's consent string (GPP, TCF or USP) OR privacy consent opt-out status. Example script tag:
<script
src="https://genius-live-player-production.betstream.betgenius.com/widgetLoader?customerId=YOUR_CUSTOMER_ID&fixtureId=YOUR_FIXTURE_ID&deviceId=END_USER_DEVICE_ID&gpp=END_USER_GPP&optOut=END_USER_OPT_OUT">
</script>
When streaming to an end user located outside of the United States, provide the end user's hashed device ID, aka a digest. Example script tag:
<script
src="https://genius-live-player-production.betstream.betgenius.com/widgetLoader?customerId=YOUR_CUSTOMER_ID&fixtureId=YOUR_FIXTURE_ID&deviceId=END_USER_DEVICE_ID&digest=END_USER_DIGEST">
</script>
For more information on each required parameter, please review 'Available QUERY PARAMS'
Insert an element with defined id to attach the video player to it,
should be the exact same one used as the script
containerId
parameter
<div id="YOUR_CONTAINER_ID" /> <!-- Defaults to geniusLive if a containerId is not set -->
Add an event listener to your website that listens for the player_ready
event. When it triggers, call our API to retrieve the stream-url
and token, then pass the response back to our player. Everything else is handled automatically
How does it work?
You embed a script tag in your site with the query string parameters you want to pass to the player. The player will detect the available stream types for the specific fixture you are trying to display and select the most appropriate stream for your viewer's device. Our script then raises an event to the containing window, with the necessary information to make a call to our API from your backend.
Our API should never be called from the browser directly.
For each fixture viewer session, your backend makes a call to our API to retrieve a url, a token and any DRM information required to play the fixture. You hand the response straight back to our player and it will play the fixture. Sensible HTTP error codes will be returned from our API around the status of the fixture and whether you have booked it.
And if there is no stream and I load the player?
If there is no fixture the player won't load unless you explicitly set controls to true.
Embed script tag in your application
We provide a UAT environment for you to test, with a continuous non-DRM and DRM'd fixture.
Change the string uat
to production
in the URL to promote to the production environment.
https://genius-live-player-uat.betstream.betgenius.com/widgetLoader?customerId=YOUR_CUSTOMER_ID&fixtureId=YOUR_OR_OUR_FIXTURE_ID&deviceId=END_USER_DEVICE_ID&gpp=END_USER_GPP&optOut=END_USER_OPT_OUT&containerId=geniusLivePlayer&bufferLength=2&width=480px&height=270px
A walk through the code
You bind a listener to the window which listens to a "geniussportsmessagebus" event and when the event type is player_ready
.
The event type that is dispatched onto the window object is a
CustomEvent (MDN docs) (opens in a new tab). The event emitted is:
interface GeniusSportsEvent {
detail: {
type: string;
body : {
streamId: string;
streamType: string;
deliveryType: 'HLS' | 'DASH';
deliveryId: string;
geniusSportsFixtureId: number;
device: string;
region: string;
}
};
}
The call to the backend
This usage example is an adaptation of our own implementation in our support tool. Our support tool (substituting for your website) calls to our support tool backend when the player fires the player_ready event indicating that there is a fixture to stream. Our support tool back end is a reverse proxy, which transparently authenticates and calls our Video-API (opens in a new tab). The method that needs to be called on the Genius Live API is a post to
/fixtures/${geniusSportsFixtureId}/live-streams/${streamId}/deliveries/${deliveryType}/${delivery}
Where the fixtureId, streamId, deliveryType and deliveryId are all contained in the GeniusSportsEvent. This event can occur multiple times for a single fixture, in the case where multi-CDN is available and the primary CDN fails it can be expected that our player will switch over by emitting the player_ready event again asking for a different delivery id.
window.addEventListener('geniussportsmessagebus', async (event: Event) => {
let typedEvent = event as unknown as GeniusSportsEvent
if (typedEvent.detail.type === 'player_ready') {
const playerReadyEvent = typedEvent as PlayerReadyEvent
const deliveryType = playerReadyEvent.detail.body.deliveryType
const streamId = playerReadyEvent.detail.body.streamId
const deliveryId = playerReadyEvent.detail.body.deliveryId
const geniusSportsFixtureId = playerReadyEvent.detail.body.geniusSportsFixtureId
const dataToPost = {
endUserSessionId: document.cookie, //user session id
region: playerReadyEvent.detail.body.region, //region
device: playerReadyEvent.detail.body.device, //device
}
postData(
`${domain}/v3/fixtures/${geniusSportsFixtureId}/live-streams/${streamId}/deliveries/${deliveryType}/${deliveryId}`,
dataToPost
)
.then((data) => {
console.log('Token data arrived', JSON.stringify(data))
// @ts-ignore
GeniusLivePlayer.player.start(data)
})
.catch((error) => {
console.error(error)
})
}
if (typedEvent.detail.type === 'player_not_ready') {
const playerNotReadyEvent = typedEvent as PlayerReadyEvent
const errorsArray = playerNotReadyEvent.detail.body.error
// Add your custom handling here
}
})
async function postData(url = '', data = { }) {
// Default options are marked with *
const response = await fetch(url, {
method: 'POST', // *GET, POST, PUT, DELETE, etc.
mode: 'cors', // no-cors, *cors, same-origin
cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
credentials: 'include', // include, *same-origin, omit
headers: {
'Content-Type': 'application/json',
},
redirect: 'follow', // manual, *follow, error
referrerPolicy: 'no-referrer',
body: JSON.stringify(data),
})
return response.json()
}
Close video player
ⓘ Information!
Close the video player to prevent multiple video players running in the background and continue downloading video segments.
If you have to close the video player, you need to call the close() method in Genius Live Player to close it, and you must also remove the event listener created when you start the video player. You can create or define a function to pass as parameter to addEventListener and removeEventListener, see following example:
...
// create function
function handleEvent(myEvent: Event) {
let typedEvent = event as unknown as GeniusSportsEvent
if (typedEvent.detail.type === 'player_ready') {
const playerReadyEvent = typedEvent as PlayerReadyEvent
const deliveryType = playerReadyEvent.detail.body.deliveryType
const streamId = playerReadyEvent.detail.body.streamId
const deliveryId = playerReadyEvent.detail.body.deliveryId
const geniusSportsFixtureId = playerReadyEvent.detail.body.geniusSportsFixtureId
const dataToPost = {
endUserSessionId: document.cookie, //user session id
region: playerReadyEvent.detail.body.region, //user region
device: playerReadyEvent.detail.body.device, //user device
}
postData(
`${domain}/v3/fixtures/${geniusSportsFixtureId}/live-streams/${streamId}/deliveries/${deliveryType}/${deliveryId}`,
dataToPost
).then((data) => {
console.log('Token data arrived', JSON.stringify(data))
// @ts-ignore
GeniusLivePlayer.player.start(data)
}).catch((error) => {
console.error(error)
})
}
}
...
// Change addEventListener using function
window.addEventListener('geniussportsmessagebus', handleEvent)
...
...
// remove event listener
window.removeEventListener('geniussportsmessagebus', handleEvent)
// close video player
// @ts-ignore
GeniusLivePlayer.player.close()
...
Video player integration example
Please refer to this example source code in our github repository (opens in a new tab)
Available QUERY PARAMS
Required
-
customerId: Genius Sports customer Id
-
fixtureId: Event fixture Id
-
deviceId: The end user's Mobile Advertising ID (MAID). This is the GAID for Android or the IDFA for iOS.
-
End User Consent: Required when streaming to an end user located within the United States. Provide one of the following consent strings OR opt-out status for the end user's consent.
-
gpp = End user's GPP consent string
-
tcf = End user's TCF consent string
-
usp = End user's USP consent string
OR
- optOut = A boolean to indicate the end user's opt-out status. The value is a 0 if a user has opted into data sharing or a 1 if a user has opted out.
-
-
digest: Required when streaming to an end user located outside the United States. Provide a digest for each user, which is an HMAC-SHA256 hash of the end user's device ID (MAID) and a shared secret. Please reach out to the Genius Onboarding team for your shared secret. View the 'Digest Generation Guide' section for an example of how to create the digest for each user.
Optional
-
containerId: value with the div element Id that will contain the Genius Live Player component. If not provided, this will default to 'geniusLive'.
-
controlsEnabled: set this parameter to 'true' to activate video control, in case controlsEnabled is set to 'false', the control bar will always be hidden. Default value: 'true'.
-
allowFullScreen: set this parameter to 'true' to allow full screen, default value: 'true'.
-
audioEnabled: set this parameter to 'true' to enable audio, you could disable audio manually later, if you set this parameter to 'false' you could enable audio manually later. Default value: 'true'.
-
bufferLength: set the buffer length of the player (e.g 4). Default value: 2.
-
width: width of the player in pixels (e.g 500px), default value: '600px'.
-
height: height of the player in pixels (e.g 200px). Default value: '300px'.
-
autoplayEnabled: set autoplayEnabled to 'true' to automatically play the content as soon as possible without manual action. Default value: 'false'.
-
culture: Set the player's culture (e.g., 'en-US') to display the BetVision overlay in the desired language. If not specified, the default is the user's browser language (using navigator.language (opens in a new tab)).
-
userId: The end user's unique persistent user identifier.
ⓘ Information!
Browsers are restricting their autoplay capabilities, for that reason you need to pay attention when you want to send autoplayEnabled in 'true' and audioEnabled in 'true', this combination could cause some browsers to be unable to play the video content and it should be necessary to play it by manual action. To automatically play content without problems in some browsers you could send audioEnabled in 'false'.
See the following example with all of the available optional parameters set:
<script
src="https://genius-live-player-production.betstream.betgenius.com/widgetLoader?customerId=YOUR_CUSTOMER_ID&fixtureId=YOUR_FIXTURE_ID&deviceId=END_USER_DEVICE_ID&userId=END_USER_USER_ID
&containerId=geniusLive&controlsEnabled=true&allowFullScreen=true&audioEnabled=false&bufferLength=2&width=600px&height=300px&autoplayEnabled=true">
</script>
Combinations of player options
You can select from different player options based on what you need from the streaming: Controlls Enabled, Audio Enabled, Auto Play Enabled, Allow Full Screen. From the options we can identify all the possible combinations:
Scenario | controlsEnabled | audioEnabled | autoplayEnabled | allowFullScreen |
---|---|---|---|---|
1 | true | true | true | true |
2 | true | true | true | false |
3 | true | true | false | true |
4 | true | true | false | false |
5 | true | false | true | true |
6 | true | false | true | false |
7 | true | false | false | true |
8 | true | false | false | false |
9 | false | true | true | true |
10 | false | true | true | false |
11 | false | true | false | true |
12 | false | true | false | false |
13 | false | false | true | true |
14 | false | false | true | false |
15 | false | false | false | true |
16 | false | false | false | false |
From all the possible combinations it is recommended always check that the controlsEnabled parameter has the true value to give the possibility to make changes to the streaming:
- Audio: Enable / Disable audio at any time we need to
- Video: Start / Stop the video at any time we need to
- Full screen: Maximize / Minimize the video streaming at any time we need to
If you set the controlsEnabled parameter to false you cannot make any change on the player options and only can see the streaming if the autoplayEnabled parameter was set in true, we can identify the following scenarios:
- In scenarios 9 and 10, the streaming starts automatically with audio and you cannot make changes, also in scenario 9 you cannot maximize the streaming due to player Controls being disabled
- In scenarios 13 and 14, the streaming starts automatically without audio and you cannot make changes, also in scenario 13 you cannot maximize the streaming due to the player Controls being disabled
- In scenarios 11, 12, 15,16 you will not be able to reproduce the streaming or modify it in any way, you will only get an empty black box
The scenarios that allow playing the streaming and making changes for the different player options are 1 to 8 The scenarios that play the streaming and don't allow making changes for the different player options are 9-10 and 13-14 The scenarios that cannot play the streaming and don't allow making changes for the different player options are 11-12 and 15-16
ⓘ Information!
Each customer must create their own backend implementation for the full video player flow to work. The following image shows how the video player works and the "Customer backend" represents the customer implementation. if you don't have this implementation, the video player will not be able to work.
ⓘ Information!
IfautoplayEnabled
was set to true andaudioEnabled
was set to true, our player will ensure that the video starts playing automatically. This will be done by muting the HTML element and then trying to play the video. You will see warnings in the console letting you know that this is happening. You can find more about why this is necessary here for Chrome (opens in a new tab) and here for Safari (opens in a new tab).
Integrating in Safari iOS
Fullscreen support
While the Fullscreen API (MDN docs) (opens in a new tab) is not currently supported in Safari iOS, we've come up with an alternative solution to achieve similar behavior using CSS. By using Screenfull (opens in a new tab) library, we can detect browser support and dynamically add a [data-fullscreen-enabled] attribute to the HTML elements.
Here's the implementation:
/* This code is already in the codebase so you don't need to implement it */
.video-container.full-screen[data-fullscreen-enabled=false] {
position: fixed;
width: 100%;
height: 100%;
inset: 0;
z-index: 10;
}
Caveat
When using the z-index property to control the stacking order of elements, it's important to be aware of its behavior within the context of parent and child elements. Specifically, the z-index of parent containers can impact the z-index of their children. To ensure the proper functioning of the z-index for the video player, we need to take the following considerations into account:
- Avoid conflicting z-index in parent containers: To ensure that the z-index property works as intended for the video player, it is essential to check that none of its parent containers have a specific z-index value. This is because z-index values in parents take precedence over their children. If any parent containers have a defined z-index, it might interfere with the video player's stacking order.
- Set higher z-index for the video player: To guarantee that the video player appears above other elements with z-index, we need to explicitly set a higher z-index for the video player itself. By doing so, we can ensure that it remains visually on top of other elements with z-index defined in the HTML.
Implementation
To ensure the video player has the highest z-index value and appears above other elements, you can use the following CSS selector:
/* Example CSS selector to set higher z-index for the video player */
.video-container.full-screen[data-fullscreen-enabled=false] {
z-index: 1000; /* Adjust the value as needed */
}
Fullscreen support in iframe implementation (iOS safari)
When embedding the Genius Live Player using an iframe, fullscreen behavior must be managed by the parent application.
To enable fullscreen support, follow these steps:
- Forward Fullscreen Events from the Player Listen for the geniussportsmessagebus event in the iframe and forward relevant fullscreen toggle messages to the parent window using postMessage.
window.addEventListener('geniussportsmessagebus', (event) => {
const eventMapped = {
type: event.detail.type,
body: event.detail.body,
};
window?.parent?.postMessage(eventMapped, WebsiteDomain); // Replace WebsiteDomain with your actual domain
});
- Apply Fullscreen Styling Based on Messages In the parent application, listen for the toggleFullscreen message and apply or remove a fullscreen class on the iframe element.
.full-screen {
position: fixed;
width: 100%;
height: 100%;
inset: 0;
z-index: 10; /* Adjust as needed based on your app's layout */
}
window.addEventListener('message', (event) => {
if (event.origin === 'iframe_domain') { // Replace with your iframe's origin
if (event.data.type === 'toggleFullscreen') {
const iframeElement = document.getElementById('video-iframe'); // Update with your iframe's actual ID
iframeElement?.classList.toggle('full-screen');
}
}
});
Notes
-
Make sure to validate the origin (event.origin) to avoid security issues.
-
Adjust the z-index in .full-screen as needed depending on your UI stack.
-
Replace placeholder values like WebsiteDomain and iframe_domain with your actual domains.
Digest Generation Guide
Generate a digest based on the end user's device ID (MAID) and a shared secret, which will be provided by the Genius Onboarding team. You send us:
- maid: the device ID (string)
- digest: HMAC-SHA256(secret, maid) as a lowercase hex string
Steps
- Take MAID (device ID) as a string.
- Compute HMAC-SHA256 with your provided secret as the key, over the MAID string.
- Output the digest as a lowercase hexadecimal string.
- Send both
maid
anddigest
to our API.
Test Vector
- MAID:
AEBE52E7-03EE-455A-B3C4-E57283966239
- Secret:
s3cr3t123
Expected HMAC-SHA256 (hex):
3e8c7996195df80f8646815a08b552b045238673bff4903a5384e79c0874c1ba
Language Examples
JavaScript (Node.js)
const crypto = require('crypto');
function createDigest(maid, secret) {
return crypto.createHmac('sha256', secret)
.update(maid, 'utf8')
.digest('hex');
}
Python
import hmac, hashlib
def create_digest(maid: str, secret: str) -> str:
return hmac.new(secret.encode("utf-8"),
maid.encode("utf-8"),
hashlib.sha256).hexdigest()
PHP
<?php
function create_digest(string $maid, string $secret): string {
return hash_hmac('sha256', $maid, $secret);
}
?>
Kotlin
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
fun createDigest(maid: String, secret: String): String {
val mac = Mac.getInstance("HmacSHA256")
val keySpec = SecretKeySpec(secret.toByteArray(Charsets.UTF_8), "HmacSHA256")
mac.init(keySpec)
val bytes = mac.doFinal(maid.toByteArray(Charsets.UTF_8))
return bytes.joinToString("") { "%02x".format(it) }
}