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: {
enabled: false,
},
};
const player = new LuraPlayerWithCustomUI(
document.getElementById("player")
);
player.setConfig(config);
</script>
</body>
</html>
player.js
class LuraPlayerWithCustomUI {
parentElement;
playerElement;
uiElement;
player;
constructor(element) {
this.parentElement = element;
this.#createPlayerElement();
this.parentElement.appendChild(this.playerElement);
this.player = new lura.Player(this.playerElement);
this.player.addListener(this.#handler.bind(this));
this.#createUIElement();
this.parentElement.appendChild(this.uiElement);
}
setConfig(config) {
this.player.setConfig(config);
}
#handler(e) {
switch (e.type) {
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%";
}
}
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 Type | Use Case |
---|
PLAYING | Updating your play/pause button's state |
PAUSED | Updating your play/pause button's state |
Method | Use 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";
}
}
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 Type | Use Case |
---|
MUTE_CHANGED | Updating your mute/unmute button's state |
Method | Use 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";
}
}
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 Type | Use Case |
---|
VOLUME_CHANGED | Updating your volume input's state |
Method | Use 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 Type | Use Case |
---|
TIME_UPDATED | Updating your time label's and seek bar's current time state |
DURATION_CHANGED | Updating your time label's and seek bar's duration state |
Method | Use 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;
...
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) {
this.seekBar.style.display = "none";
return;
}
if (this.isUserSeeking) return;
this.seekBar.style.display = "block";
this.seekBar.value = this.displayTime;
this.seekBar.max = this.displayDuration;
}
}
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.
Method | Use 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";
}
}
}
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.
Method | Use 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 Type | Use Case |
---|
RATE_CHANGE | Updating your playback speed selector's state |
Method | Use 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 Type | Use Case |
---|
CONFIGURED | Updating the title |
EVENT_METADATA_UPDATED | Updating the title |
Method | Use 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 Type | Use Case |
---|
CONFIGURED | Updating the description |
EVENT_METADATA_UPDATED | Updating the description |
Method | Use 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 Type | Use Case |
---|
CONFIGURED | Updating the poster image |
Method | Use 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 Type | Use Case |
---|
SHOW_CAPTION | Updating 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 Type | Use Case |
---|
CONFIGURED | Updating the tracks |
TRACK_CHANGED | Updating the tracks |
Method | Use 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 Type | Use Case |
---|
CONFIGURED | Updating the casting availability |
CASTING_REQUESTED | Notifying about casting requested |
CASTING_STARTED | Notifying about casting start |
CASTING_ENDED | Notifying about casting end |
Method | Use 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";
}
}