Skip to main content

Make Your Own UI

This guide is a starting guide for making your own user interface. Subsections will include events and methods to use for applications. This guide will not cover up styling and layouts, but rather core usage of Lura Player SDK for making customized user experience.

Start by creating a configuration file for your player, and add a "controls" section in your configuration file to specify the settings related to the user interface. Inside the "controls" section, include the "enabled" parameter and set it to false. This parameter determines whether the UI should be loaded or not. After disabling the controls, you need to process the events, and change your user interface accordingly.

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div
id="player"
style="width: 640px; height: 360px; position: relative"
></div>
<script src="https://w3.mp.lura.live/lura-player/web/latest/lura-player.js"></script>
<script src="./player.js"></script>
<script>
const config = {
lura: {
appKey: "<YOUR_APP_KEY>",
assetId: "<YOUR_ASSET_ID>",
},
controls: {
// This is needed for making your own UI
enabled: false,
},
};
const player = new LuraPlayerWithCustomUI(
document.getElementById("player")
);
player.setConfig(config);
</script>
</body>
</html>
player.js
class LuraPlayerWithCustomUI {
// UI Elements
parentElement;
playerElement;
uiElement;

// Player
player;

constructor(element) {
this.parentElement = element;
this.#createPlayerElement();
// This is where the player will be populated
this.parentElement.appendChild(this.playerElement);
this.player = new lura.Player(this.playerElement);
this.player.addListener(this.#handler.bind(this));
// This is where you add your ui elements to
this.#createUIElement();
this.parentElement.appendChild(this.uiElement);
}
setConfig(config) {
this.player.setConfig(config);
}
#handler(e) {
switch (e.type) {
// This is where you will listen to events
case "CONFIGURED":
break;
}
}
#createPlayerElement() {
this.playerElement = document.createElement("div");
this.playerElement.style.display = "block";
this.playerElement.style.position = "absolute";
this.playerElement.style["background-color"] = "black";
this.playerElement.style.width = "100%";
this.playerElement.style.height = "100%";
}
#createUIElement() {
this.uiElement = document.createElement("div");
this.uiElement.style.display = "flex";
this.uiElement.style["flex-direction"] = "row";
this.uiElement.style["align-items"] = "flex-end";
this.uiElement.style.position = "absolute";
this.uiElement.style.width = "100%";
this.uiElement.style.height = "100%";
}
}

Play/Pause Button

For a play/pause button, you can create a playPauseButton element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
PLAYINGUpdating your play/pause button's state
PAUSEDUpdating your play/pause button's state
MethodUse Case
play()Requesting the video to play.
pause()Requesting the video to pause.
isPaused()Returns whether the video is paused.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
playPauseButton;
player;
...
constructor(element) {
...
this.#createPlayPauseButton();
this.uiElement.appendChild(this.playPauseButton);
...
}
#handler(e) {
switch (e.type) {
...
case "PLAYING":
this.#updatePlayPauseButton();
break;
case "PAUSED":
this.#updatePlayPauseButton();
break;
...
}
}
...
#createPlayPauseButton() {
this.playPauseButton = document.createElement("button");
this.playPauseButton.innerText = "Play";
this.playPauseButton.addEventListener("click", () => {
this.player.isPaused() ? this.player.play(): this.player.pause();
});
}
#updatePlayPauseButton() {
this.playPauseButton.innerText = this.player.isPaused() ? "Play" : "Pause";
}
}

Mute/Unmute Button

For a mute/unmute button, you can create a muteUnmuteButton element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
MUTE_CHANGEDUpdating your mute/unmute button's state
MethodUse Case
setMuted()Setting the muted state of the video element.
isMuted()Returns whether the video is muted.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
muteUnmuteButton;
player;
...
constructor(element) {
...
this.#createMuteUnmuteButton();
this.uiElement.appendChild(this.muteUnmuteButton);
...
}
#handler(e) {
switch (e.type) {
...
case "MUTE_CHANGED":
this.#updateMuteUnmuteButton();
break;
...
}
}
...
#createMuteUnmuteButton() {
this.muteUnmuteButton = document.createElement("button");
this.muteUnmuteButton.innerText = "Mute";
this.muteUnmuteButton.addEventListener("click", () => {
this.player.setMuted(!this.player.isMuted());
});
}
#updateMuteUnmuteButton() {
this.muteUnmuteButton.innerText = this.player.isMuted() ? "Unmute" : "Mute";
}
}

Volume Input

For a volume input, you can create a volumeInput element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
VOLUME_CHANGEDUpdating your volume input's state
MethodUse Case
setVolume()Setting the volume of the video element.
getVolume()Returns the volume of the video.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
muteUnmuteButton;
player;
...
constructor(element) {
...
this.#createVolumeInput();
this.uiElement.appendChild(this.volumeInput);
...
}
#handler(e) {
switch (e.type) {
...
case "VOLUME_CHANGED":
this.#updateMuteUnmuteButton();
break;
...
}
}
...
#createVolumeInput() {
this.volumeInput = document.createElement("input");
this.volumeInput.type = "range";
this.volumeInput.max = "1";
this.volumeInput.min = "0";
this.volumeInput.step = "any";
this.volumeInput.value = this.player.getVolume();
this.volumeInput.addEventListener("input", (e) => {
this.player.setVolume(this.volumeInput.value);
});
}
#updateVolumeInput() {
this.volumeInput.value = this.player.getVolume();
}
}

Time Label & Seek Bar

For a time label and a seek bar, you can create a timeLabel element and a seekBar element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
TIME_UPDATEDUpdating your time label's and seek bar's current time state
DURATION_CHANGEDUpdating your time label's and seek bar's duration state
MethodUse Case
seek()Seeks to the given time in seconds.
getCurrentTime()Returns the current time of the video.
getDuration()Returns the duration of the video.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
muteUnmuteButton;
player;
...
// States
displayTime;
displayDuration;
isUserSeeking;
isAdPlaying;

constructor(element) {
...
this.#createTimeLabel();
this.uiElement.appendChild(this.timeLabel);
this.#createSeekBar();
this.uiElement.appendChild(this.seekBar);
...
}
#handler(e) {
switch (e.type) {
...
case "TIME_UPDATED":
if (e.data.type === "ad") {
this.isAdPlaying = true;
this.displayTime = e.data.ad.adTime;
this.displayDuration = e.data.ad.adDuration;
} else {
this.isAdPlaying = false;
this.displayTime = this.player.getCurrentTime();
this.displayDuration = this.player.getDuration();
}
this.#updateTimeLabel();
this.#updateSeekBar();
break;
case "DURATION_CHANGED":
this.displayDuration = this.player.getDuration();
this.#updateTimeLabel();
this.#updateSeekBar();
break;
case "SEEKED":
this.isUserSeeking = false;
break;
...
}
}
...
#createTimeLabel() {
this.timeLabel = document.createElement("label");
this.timeLabel.style.color = "white";
this.timeLabel.style["text-shadow"] = "0px 0px 3px #000000";
this.timeLabel.innerText = "0 / 0";
}
#updateTimeLabel() {
if (this.displayDuration === Infinity) {
this.timeLabel.innerText = "Live";
} else {
this.timeLabel.innerText =
this.displayTime.toFixed(0) + " / " + this.displayDuration.toFixed(0);
}
}
#createSeekBar() {
this.seekBar = document.createElement("input");
this.seekBar.type = "range";
this.seekBar.max = this.displayDuration;
this.seekBar.min = "0";
this.seekBar.step = "any";
this.seekBar.value = this.player.getCurrentTime();
this.seekBar.addEventListener("input", () => {
if (this.isAdPlaying) return;
this.isUserSeeking = true;
});
this.seekBar.addEventListener("change", () => {
if (this.isAdPlaying) return;
this.player.seek(this.seekBar.value);
});
}
#updateSeekBar() {
if (this.displayDuration === Infinity) {
// For live
this.seekBar.style.display = "none";
return;
}
// For VOD
if (this.isUserSeeking) return;
this.seekBar.style.display = "block";
this.seekBar.value = this.displayTime;
this.seekBar.max = this.displayDuration;
}
}

Fullscreen Button

For a fullscreen button, you can create a fullscreenButton element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

MethodUse Case
requestFullscreen()Requests Lura Player to enter fullscreen.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
fullscreenButton;
player;
...

constructor(element) {
...
this.#createFullscreenButton();
this.uiElement.appendChild(this.fullscreenButton);
...
}
...
#createFullscreenButton() {
document.addEventListener(
"fullscreenchange",
this.#updateFullscreenButton.bind(this)
);
document.addEventListener(
"webkitfullscreenchange",
this.#updateFullscreenButton.bind(this)
);
this.fullscreenButton = document.createElement("button");
this.fullscreenButton.innerText = "Enter FS";
this.fullscreenButton.addEventListener("click", () => {
if (document.fullscreenElement) {
if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
} else if (document.mozExitFullscreen) {
document.mozExitFullscreen();
} else if (document.msExitFullscreen) {
document.msExitFullscreen();
} else if (document.exitFullscreen) {
document.exitFullscreen();
}
} else {
if (this.parentElement.webkitRequestFullscreen) {
this.parentElement.webkitRequestFullscreen();
} else if (this.parentElement.mozRequestFullScreen) {
this.parentElement.mozRequestFullScreen();
} else if (this.parentElement.msRequestFullscreen) {
this.parentElement.msRequestFullscreen();
} else if (this.parentElement.requestFullscreen) {
this.parentElement.requestFullscreen();
} else {
this.player.requestFullscreen();
}
}
});
}
#updateFullscreenButton() {
if (document.fullscreenElement) {
this.fullscreenButton.innerText = "Exit FS";
} else {
this.fullscreenButton.innerText = "Enter FS";
}
}
}

Picture In Picture Button

For a picture in picture button, you can create a pictureInPictureButton element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

MethodUse Case
requestPictureInPicture()Requests Lura Player to enter picture in picture.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
pictureInPictureButton;
player;
...

constructor(element) {
...
this.#createPictureInPictureButton();
this.uiElement.appendChild(this.pictureInPictureButton);
...
}
...
#createPictureInPictureButton() {
this.pictureInPictureButton = document.createElement("button");
this.pictureInPictureButton.innerText = "Toggle PiP";
this.pictureInPictureButton.addEventListener("click", () => {
if (document.pictureInPictureElement) {
document.exitPictureInPicture();
} else if (document.pictureInPictureEnabled) {
this.player.requestPictureInPicture();
}
});
}
}

Playback Speed Selector

For a playback speed selector, you can create a playbackSpeedSelector element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
RATE_CHANGEUpdating your playback speed selector's state
MethodUse Case
setPlaybackSpeed()Sets the playback speed of the video.
getPlaybackSpeed()Returns the playback speed of the video.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
playbackSpeedSelector;
player;
...
constructor(element) {
...
this.#createPlaybackSpeedSelector();
this.uiElement.appendChild(this.playbackSpeedSelector);
...
}
#handler(e) {
switch (e.type) {
...
case "RATE_CHANGE":
this.#updatePlaybackSpeedSelector();
break;
...
}
}
...
#createPlaybackSpeedSelector() {
this.playbackSpeedSelector = document.createElement("select");
for (let i = 1; i <= 8; i++) {
const option = document.createElement("option");
option.value = 0.25 * i;
option.text = 0.25 * i;
if (0.25 * i === this.player.getPlaybackSpeed()) {
option.selected = true;
}
this.playbackSpeedSelector.appendChild(option);
}
this.playbackSpeedSelector.addEventListener("change", (e) => {
this.player.setPlaybackSpeed(
this.playbackSpeedSelector.selectedOptions[0].value
);
});
}
#updatePlaybackSpeedSelector() {
const index = [...this.playbackSpeedSelector.options].findIndex(
(e) => Number.parseFloat(e.value) === this.player.getPlaybackSpeed()
);
if (index !== -1) {
this.playbackSpeedSelector.selectedIndex = index;
}
}
}

Title

For a title element, you can create a titleElement element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
CONFIGUREDUpdating the title
EVENT_METADATA_UPDATEDUpdating the title
MethodUse Case
getConfig()Returns the processed configuration.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
titleElement;
player;
...
constructor(element) {
...
this.#createTitleElement();
this.uiElement.appendChild(this.titleElement);
...
}
#handler(e) {
switch (e.type) {
...
case "CONFIGURED":
this.#updateTitleElement();
break;
case "EVENT_METADATA_UPDATED":
this.#updateTitleElement(e.data.metadata);
break;
...
}
}
...
#createTitleElement() {
this.titleElement = document.createElement("label");
this.titleElement.style.color = "white";
this.titleElement.style["text-shadow"] = "0px 0px 3px #000000";
}
#updateTitleElement(metadata) {
this.titleElement.innerText = metadata?.title ?? this.player.getConfig()?.content?.title ?? "";
}
}

Description

For a description element, you can create a descriptionElement element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
CONFIGUREDUpdating the description
EVENT_METADATA_UPDATEDUpdating the description
MethodUse Case
getConfig()Returns the processed configuration.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
descriptionElement;
player;
...
constructor(element) {
...
this.#createDescriptionElement();
this.uiElement.appendChild(this.descriptionElement);
...
}
#handler(e) {
switch (e.type) {
...
case "CONFIGURED":
this.#updateDescriptionElement();
break;
case "EVENT_METADATA_UPDATED":
this.#updateDescriptionElement(e.data.metadata);
break;
...
}
}
...
#createDescriptionElement() {
this.descriptionElement = document.createElement("label");
this.descriptionElement.style.color = "white";
this.descriptionElement.style["text-shadow"] = "0px 0px 3px #000000";
}
#updateDescriptionElement(metadata) {
this.descriptionElement.innerText = metadata?.description ?? this.player.getConfig()?.content?.description ?? "";
}
}

Poster image

For a poster element, you can create a posterElement element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
CONFIGUREDUpdating the poster image
MethodUse Case
getConfig()Returns the processed configuration.
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
posterElement;
player;
...
constructor(element) {
...
this.#createPosterElement();
this.uiElement.appendChild(this.posterElement);
...
}
#handler(e) {
switch (e.type) {
...
case "CONFIGURED":
this.#updatePosterElement();
break;
...
}
}
...
#createPosterElement() {
this.posterElement = document.createElement("img");
}
#updatePosterElement() {
const posterMimeTypes = [
"image/jpg",
"image/jpeg",
"image/webp",
"image/apng",
"image/avif",
]
this.posterElement.src = this.player.getConfig().content?.media?.find(e => posterMimeTypes.includes(e.type))?.url
}
}

Caption Display

For a caption display element, you can create a captionDisplay element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following event to update its state.

Event TypeUse Case
SHOW_CAPTIONUpdating the caption text
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
captionDisplay;
player;
...
constructor(element) {
...
this.#createCaptionDisplayElement();
this.uiElement.appendChild(this.captionDisplay);
...
}
#handler(e) {
switch (e.type) {
...
case "SHOW_CAPTION":
this.#updateCaptionDisplayElement(e.data.text);
break;
...
}
}
...
#createCaptionDisplayElement() {
this.captionDisplay = document.createElement("p");
}
#updateCaptionDisplayElement(text) {
this.captionDisplay.innerText = text;
}
}

Track Selector

For a track selector, you can create a audioTrackSelector element, and a captionTrackSelector element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
CONFIGUREDUpdating the tracks
TRACK_CHANGEDUpdating the tracks
MethodUse Case
getTracks()Returns the available tracks.
useTrack()Uses the given track.

Optionally you can also use the isAutoBitrate and setAutoBitrate methods to display or set the auto bitrate selection property.

player.js
class LuraPlayerWithCustomUI {
...
uiElement;
tracks;
audioTrackSelector;
captionTrackSelector;
player;
...
constructor(element) {
...
this.#createAudioTrackSelector();
this.#createTextTrackSelector();

this.uiElement.appendChild(this.audioTrackSelector);
this.uiElement.appendChild(this.captionTrackSelector);
...
}
#handler(e) {
switch (e.type) {
...
case "CONFIGURED":
this.#updateTracks();
this.#updateAudioTrackSelector();
this.#updateTextTrackSelector();
break;
case "TRACK_CHANGED":
this.#updateTracks();
this.#updateAudioTrackSelector();
this.#updateTextTrackSelector();
break;
...
}
}
...
#updateTracks() {
this.tracks = this.player.getTracks();
}
#createAudioTrackSelector() {
this.audioTrackSelector = document.createElement("select");
this.audioTrackSelector.addEventListener("change", () => {
this.player.useTrack(
this.tracks.audio[this.audioTrackSelector.selectedOptions[0].value]
);
});
}
#updateAudioTrackSelector() {
this.audioTrackSelector.innerHTML = "";
for (let i = 0; i < this.tracks?.audio?.lenght ?? 0; i++) {
const option = document.createElement("option");
option.value = i;
option.text = this.tracks.audio[i].label;
this.audioTrackSelector.appendChild(option);
}
}
#createTextTrackSelector() {
this.captionTrackSelector = document.createElement("select");
this.captionTrackSelector.addEventListener("change", () => {
this.player.useTrack(
this.tracks.caption[this.captionTrackSelector.selectedOptions[0].value]
);
});
}
#updateTextTrackSelector() {
this.captionTrackSelector.innerHTML = "";
for (let i = 0; i < this.tracks?.caption?.lenght ?? 0; i++) {
const option = document.createElement("option");
option.value = i;
option.text = this.tracks.caption[i].label;
this.captionTrackSelector.appendChild(option);
}
}
}

Casting

For casting, you can create a castingElement element in the LuraPlayerWithCustomUI class, create it in the constructor and use the following methods and events to update its state.

Event TypeUse Case
CONFIGUREDUpdating the casting availability
CASTING_REQUESTEDNotifying about casting requested
CASTING_STARTEDNotifying about casting start
CASTING_ENDEDNotifying about casting end
MethodUse Case
getCastingAvailability()Returns the casting availability
requestCasting()Requests casting from a cast target
isCasting()Returns whether the cast target is casting or not
exitCasting()Tries to exit casting from the cast target
player.js
class LuraPlayerWithCustomUI {
...
uiElement;
castingElement;
castTarget;
player;
...
constructor(element) {
...
this.#createCastingElement();
this.uiElement.appendChild(this.castingElement);
...
}
#handler(e) {
switch (e.type) {
...
case "CONFIGURED":
this.#updateCastingElement();
break;
case "CASTING_STARTED":
this.#updateCastingElement(true);
break;
case "CASTING_ENDED":
this.#updateCastingElement(false);
break;
...
}
}
...
#createCastingElement() {
this.castingElement = document.createElement("button");
const castingAvailability = this.player.getCastingAvailability();
if (castingAvailability[lura.unified.CastTargets.AIRPLAY]) {
this.castTarget = lura.unified.CastTargets.AIRPLAY;
} else if (castingAvailability[lura.unified.CastTargets.CHROMECAST]) {
this.castTarget = lura.unified.CastTargets.CHROMECAST;
}
this.castingElement.addEventListener("click", () => {
if (this.player.isCasting(this.castTarget)) {
this.player.exitCasting(this.castTarget);
} else {
this.player.requestCasting(this.castTarget);
}
})
}
#updateCastingElement(isCasting) {
this.castingElement.style.color = isCasting ? "blue" : "white";
}
}