Custom UI example

Custom UI example

This example demonstrates how you can build a custom UI based on our API calls.

There are some important technical informations about a custom UI implementation:

  • there is no progress event from the player, so you should query the progress periodically (using the `playing` event to disable unnecessary querying will increase performance)
  • `progress` and `duration` is in seconds, so every percentage related progress data (like a seekbar) should be calculated from this two data
  • if you would like to build a full custom UI on the player, use the `controls=false&showtitle=false` url params to hide the default IBM Video Streaming UI components
  • autoplay permission must be delegated on cross-origin iframes since Chrome 66 for video elements by policy. Therefore, the iframe tag must contain an `allow="autoplay"` feature policy attribute to allow play calls from a cross-orgin parent to the player iframe. For more information visit https://developers.google.com/web/updates/2017/09/autoplay-policy-changes#iframe
Custom UI pieces:
Play Pause
Volume:
00:00 --:--

HTML

<div class="player-container">
    <iframe id="UstreamPlayer" src="//www.ustream.tv/embed/12762028?html5ui" frameborder="0" allow="autoplay" allowfullscreen webkitallowfullscreen scrolling="no"></iframe>
</div>

<div class="custom-ui-container">
	<span class="button play">Play</span>
	<span class="button pause">Pause</span>
	<div class="volume-container">
		Volume:
		<div class="volume">
			<div class="slider"></div>
		</div>
	</div>

	<div class="seekbar">
		<span class="timeLabel currentTime">00:00</span>
		<span class="timeLabel duration">--:--</span>
		<div class="progress"></div>
	</div>
</div>

Javascript


//IBM Video Streaming Embed API instance
var embedApi = UstreamEmbed('UstreamPlayer');
//video duration will be stored in this variable
var videoDuration = 0;
//we will query the progress with an interval, we store it to be able to cancel it any time
var progressInterval = false;

var seekbarElement = document.querySelector('.seekbar');
var seekProgressElement = document.querySelector('.seekbar .progress');
var volumeElement = document.querySelector('.volume');
var currentTimeElement = document.querySelector('.currentTime');
var durationElement = document.querySelector('.duration');

/**
 * API Methods
 */

// PLAY
document.querySelector('.play').addEventListener('click', function () {
  embedApi.callMethod('play');
});

// PAUSE
document.querySelector('.pause').addEventListener('click', function () {
  embedApi.callMethod('pause');
});

//VOLUME
document.querySelector('.volume').addEventListener('click', function (e) {
  //Calculate the desired volume based on the click event position (this is just an example solution)
  var volumePercentage = e.offsetX / volumeElement.offsetWidth * 100;

  embedApi.callMethod('volume', volumePercentage);
  document.querySelector('.volume .slider').style.width = volumePercentage + '%';
});

// SEEK
seekbarElement.addEventListener('click', function (e) {
  //Calculate the desired progress based on the click event position (this is just an example solution)
  var progressPercentage = e.layerX / seekbarElement.offsetWidth;

  embedApi.callMethod('seek', videoDuration * progressPercentage);
});

/**
 * API Events
 */

//duration is needed to calculate % progress
embedApi.addListener('duration', function (eventType, duration) {
  videoDuration = duration;

  durationElement.innerHTML = formatTime(videoDuration);
});

//this is not mandatory, but its unnecessary to query the progress when the playback is stopped.
embedApi.addListener('playing', function (eventType, playing) {
  if (playing) {
    startProgressInterval();
  } else {
    stopProgressInterval();
  }
});

// Progress is not fired with an event from the player because of performance optimization purposes
// you have to query it with the interval you like.
function startProgressInterval() {
  stopProgressInterval();

  progressInterval = setInterval(function () {
    embedApi.getProperty('progress', function (progress) {
      updateSeekbar(progress);
    });
  }, 200);
}

function stopProgressInterval () {
  clearInterval(progressInterval);
}

function updateSeekbar (progress) {
  if (!videoDuration) return;

  seekProgressElement.style.width = (progress / videoDuration) * 100 + '%';
  currentTimeElement.innerHTML = formatTime(progress);
}

//format seconds to hh:mm:ss
function formatTime (sec) {
  var time = new Date(sec * 1000),
      hour = 60*60;

  time = time.toISOString();
  time = time.substr(11, 8);
  if (+sec < hour) {
    time = time.substr(3);
  }

  if (+sec > 24*hour) {
    time = Math.floor(sec / hour) + time.substr(2);
  }

  return time;
}

CSS


.player-container {
	//this padding-bottom workaround is the 16:9 (9/16) ratio's value for responsive support
	padding-bottom: 56.25%;
	position: relative;
	width: 100%;
	margin: 0 0 20px;
}

.player-container iframe {
	position: absolute;
	top: 0;
	right: 0;
	width: 100%;
	height: 100%;
}

.custom-ui-container {
	margin: 5px 0;
	color: #fff;
	font-size: 12px;
	line-height: 30px;
}

.custom-ui-container .seekbar {
	background: rgba(22, 22, 22, 0.8);
	height: 30px;
	margin: 5px 0;
	user-select: none;
	cursor: pointer;
}

.custom-ui-container .timeLabel {
	float: left;
	padding: 0 10px;
}

.custom-ui-container .timeLabel.duration {
	float: right;
}

.custom-ui-container .progress {
	background: #38f;
	width: 0;
	height: 100%;
}

.custom-ui-container .volume-container {
	display: inline-block;
	padding-left: 20px;
	color: #2d2d2d;
}

.custom-ui-container .volume {
	display: inline-block;
	width: 80px;
	height: 20px;
	vertical-align: middle;
	background: rgba(22, 22, 22, 0.8);
	cursor: pointer;
}

.custom-ui-container .volume .slider {
	width: 50%;
	height: 100%;
	background: #38f;
}