/**
* Copyright IBM Corp. 2020, 2024
*
* This source code is licensed under the Apache-2.0 license found in the
* LICENSE file in the root directory of this source tree.
*/
import { AnalyticsAPI } from '../Analytics';
import root from 'window-or-global';
/**
* Sets the Kaltura Partner ID, set by environment variable "KALTURA_PARTNER_ID"
*
* @type {number}
* @private
*/
const _partnerId =
(process &&
(process.env.REACT_APP_KALTURA_PARTNER_ID ||
process.env.KALTURA_PARTNER_ID)) ||
1773841;
/**
* Sets the Kaltura UIConf ID, set by environment variable "KALTURA_UICONF_ID"
*
* @type {number}
* @private
*/
const _uiConfId =
(process &&
(process.env.REACT_APP_KALTURA_UICONF_ID ||
process.env.KALTURA_UICONF_ID)) ||
27941801;
/**
* @type {string} _embedUrl The API URL to call
* @private
*/
const _embedUrl = `https://cdnapisec.kaltura.com/p/${_partnerId}/sp/${_partnerId}00/embedIframeJs/uiconf_id/${_uiConfId}/partner_id/${_partnerId}`;
/**
* @type {string} _thumbnailUrl
* @private
*/
const _thumbnailUrl = `https://cdnsecakmi.kaltura.com/p/${_partnerId}/thumbnail/entry_id/`;
/**
* Number of times to retry the script ready loop before failing
*
* @type {number}
* @private
*/
const _timeoutRetries = 50;
/**
* Tracks the number of attempts for the script ready loop
*
* @type {number}
* @private
*/
let _attempt = 0;
/**
* Tracks the script status
*
* @type {boolean} _scriptLoading to track the script loading or not
* @private
*/
let _scriptLoading = false;
/**
* Timeout loop to check script state is the _scriptLoaded state or _scriptLoading state
*
* @param {Function} resolve Resolve function
* @param {Function} reject Reject function
* @private
*/
function _scriptReady(resolve, reject) {
/**
*
* @param {object} root.kWidget if exists then resolve
*/
if (root.kWidget) {
_scriptLoading = false;
resolve();
} else if (_scriptLoading) {
_attempt++;
if (_attempt < _timeoutRetries) {
setTimeout(() => {
_scriptReady(resolve, reject);
}, 100);
} else {
reject();
}
} else {
_loadScript();
_scriptReady(resolve, reject);
}
}
/**
* Returns boolean if the _scriptLoading and _scriptLoaded flag is false
*
* @private
*/
function _loadScript() {
_scriptLoading = true;
const script = document.createElement('script');
script.src = _embedUrl;
script.async = true;
document.body.appendChild(script);
}
/**
*
* Object to cache media data
*
* @private
*/
let mediaData = {};
/**
* KalturaPlayerAPI class with methods of checking script state and
* embed media meta data and api data
*
* In order to set the Partner ID/UIConf ID, set the following environment
* variables:
*
* - KALTURA_PARTNER_ID
* - KALTURA_UICONF_ID
*/
class KalturaPlayerAPI {
/**
*
* Gets the full _scriptReady state
*
* @returns {Promise<*>} Promise kaltura media player file
*/
static checkScript() {
return new Promise((resolve, reject) => {
_scriptReady(resolve, reject);
});
}
/**
* Creates thumbnail image url with customizable params
*
* @param {object} params param object
* @param {string} params.mediaId media id
* @param {string} params.height specify height in pixels
* @param {string} params.width specify width in pixels
* @returns {string} url of thumbnail image
* @example
* import { KalturaPlayerAPI } from '@carbon/ibmdotcom-services';
*
* function thumbnail() {
* const thumbnailData = {
* mediaId: '1_9h94wo6b',
* height: '240',
* width: '320'
* }
* const thumbnailUrl = KalturaPlayerAPI.getThumbnailUrl(thumbnailData);
* }
*/
static getThumbnailUrl({ mediaId, height, width }) {
let url = _thumbnailUrl + mediaId;
if (height) {
url = url + `/height/${height}`;
}
if (width) {
url = url + `/width/${width}`;
}
return url;
}
/**
* Gets the embed meta data
*
* @param {string} mediaId The mediaId we're embedding the placeholder for.
* @param {string} targetId The targetId the ID where we're putting the placeholder.
* @param {object} flashvars Determine any extra param or plugin for the player.
* @param {boolean} useIbmMetrics Whether or not should IBM Metrics events be fired.
* @param {Function} customReadyCallback Determine any extra functions that should be executed
* on player readyCallback.
* @returns {object} object
* @example
* import { KalturaPlayerAPI } from '@carbon/ibmdotcom-services';
*
* function embedMyVideo() {
* const elem = document.getElementById('foo');
* const videoid = '12345';
* KalturaPlayerAPI.embedMedia(videoid, elem);
* }
*/
static async embedMedia(
mediaId,
targetId,
flashvars = {},
useIbmMetrics = true,
customReadyCallback = () => {}
) {
const fireEvent = this.fireEvent;
return await this.checkScript().then(() => {
const promiseKWidget = new Promise((resolve) => {
const defaultFlashVars = {
autoPlay: true,
closedCaptions: {
plugin: true,
},
titleLabel: {
plugin: true,
align: 'left',
text: '{mediaProxy.entry.name}',
},
ibm: {
template: 'idl',
},
};
let isCustomCreated;
if (
!document.getElementById(targetId) &&
document.querySelector('cds-tabs-extended-media')
) {
const newVideoDiv = document.createElement('div');
newVideoDiv.classList.add(`bx--video-player__video`);
newVideoDiv.setAttribute('id', targetId);
document.body.append(newVideoDiv);
isCustomCreated = true;
}
root.kWidget.embed({
targetId: targetId,
wid: '_' + _partnerId,
uiconf_id: _uiConfId,
entry_id: mediaId,
flashvars: {
...defaultFlashVars,
...flashvars,
},
params: {
wmode: 'transparent',
},
// Ready callback is issued for this player:
readyCallback: function (playerId) {
const kdp = document.getElementById(playerId);
if (useIbmMetrics) {
kdp.addJsListener('playerPaused.ibm', () => {
fireEvent({ playerState: 1, kdp, mediaId });
});
kdp.addJsListener('playerPlayed.ibm', () => {
fireEvent({ playerState: 2, kdp, mediaId });
});
kdp.addJsListener('playerPlayEnd.ibm', () => {
fireEvent({ playerState: 3, kdp, mediaId });
});
kdp.addJsListener('IbmCtaEvent.ibm', (ctaData) => {
const customMetricsData = ctaData?.customMetricsData || {};
fireEvent({
playerState: 101,
kdp,
mediaId,
customMetricsData,
});
});
}
customReadyCallback(kdp);
resolve(kdp);
},
});
if (isCustomCreated) {
const previousVideoDiv = document
.querySelector('cds-tabs-extended-media')
.shadowRoot.querySelector(
`.bx--accordion__item--active cds-video-player`
).lastChild;
previousVideoDiv.parentElement.appendChild(
document.getElementById(targetId)
);
}
});
return {
kWidget() {
return promiseKWidget;
},
};
});
}
/**
* Fires a metrics event when the media was played.
* Pass events to common metrics event.
*
* @param {object} param params
* @param {number} param.playerState state detecting different user actions
* @param {object} param.kdp media object
* @param {string} param.mediaId id of the media
* @param {object} param.customMetricsData any extra parameter for custom events
*/
static fireEvent({ playerState, kdp, mediaId, customMetricsData = {} }) {
// If media was played and timestamp is 0, it should be "launched" state.
var currentTime = Math.round(kdp.evaluate('{video.player.currentTime}'));
if (playerState === 2 && currentTime === 0) {
playerState = 0;
}
const eventData = {
playerType: 'kaltura',
title: kdp.evaluate('{mediaProxy.entry.name}'),
currentTime: currentTime,
duration: kdp.evaluate('{mediaProxy.entry.duration}'),
playerState: playerState,
mediaId: mediaId,
customMetricsData,
};
AnalyticsAPI.videoPlayerStats(eventData);
}
/**
* Gets the api data
*
* @param {string} mediaId The mediaId we're embedding the placeholder for.
* @returns {object} object
* @example
* import { KalturaPlayerAPI } from '@carbon/ibmdotcom-services';
*
* async function getMyVideoInfo(id) {
* const data = await KalturaPlayerAPI.api(id);
* console.log(data);
* }
*/
static async api(mediaId) {
return await this.checkScript().then(() => {
if (mediaData && mediaData[mediaId]) {
return mediaData[mediaId];
} else {
return new Promise((resolve) => {
return new root.kWidget.api({ wid: '_' + _partnerId }).doRequest(
{
service: 'media',
action: 'get',
entryId: mediaId,
},
function (jsonObj) {
mediaData[jsonObj.id] = jsonObj;
resolve(jsonObj);
}
);
});
}
});
}
/**
* Convert media duration from milliseconds and seconds to HH:MM:SS
*
* @param {string} duration media duration in seconds
* @param {boolean} fromMilliseconds the duration argument is expressed in milliseconds rather than seconds
* @returns {string} converted duration
*/
static getMediaDuration(duration = 0, fromMilliseconds) {
if (fromMilliseconds) {
let seconds = Math.floor((duration / 1000) % 60);
const minutes = Math.floor((duration / (1000 * 60)) % 60);
let hours = Math.floor((duration / (1000 * 60 * 60)) % 24);
hours = hours > 0 ? hours + ':' : '';
seconds = seconds < 10 ? '0' + seconds : seconds;
return duration && '(' + hours + minutes + ':' + seconds + ')';
}
const parsedTime = root?.kWidget?.seconds2Measurements(duration) || {};
let hours = parsedTime?.hours || 0;
let minutes = parsedTime?.minutes || 0;
let seconds = parsedTime?.seconds || 0;
minutes = (hours > 0 ? '0' + minutes : minutes).toString().slice(-2);
hours = hours > 0 ? hours + ':' : '';
seconds = ('0' + seconds).slice(-2);
return hours + minutes + ':' + seconds;
}
static getMediaDurationFormatted(duration = 0, fromMilliseconds) {
let ms = duration;
if (!fromMilliseconds) {
ms = duration * 1000;
}
const s = Math.floor((ms / 1000) % 60);
const m = Math.floor((ms / (1000 * 60)) % 60);
const h = Math.floor((ms / (1000 * 60 * 60)) % 24);
const seconds = KalturaPlayerAPI.formatTime(s, 'second');
const minutes = h || m ? KalturaPlayerAPI.formatTime(m, 'minute') : '';
const hours = h ? KalturaPlayerAPI.formatTime(h, 'hour') : '';
return `${hours} ${minutes} ${seconds}`.trim();
}
static formatTime(number, unit) {
const locale =
root.document.documentElement.lang || root.navigator.language;
return new Intl.NumberFormat(locale, {
style: 'unit',
// @ts-ignore: TS lacking support for standard option
unitDisplay: 'long',
unit,
}).format(number);
}
}
export default KalturaPlayerAPI;