Skip to content

Commit

Permalink
feat(FEC-10768): expose in-stream DASH thumbnails (#415)
Browse files Browse the repository at this point in the history
  • Loading branch information
Dan Ziv authored Feb 23, 2021
1 parent 15ecfbc commit 9581fa1
Show file tree
Hide file tree
Showing 12 changed files with 1,311 additions and 1,510 deletions.
2,306 changes: 954 additions & 1,352 deletions docs/api.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@
},
"dependencies": {
"@babel/polyfill": "^7.0.0",
"@playkit-js/playkit-js": "0.68.0-canary.791b3c4",
"@playkit-js/playkit-js-dash": "1.21.3-canary.98282f5",
"@playkit-js/playkit-js": "0.68.0-canary.3b1d78e",
"@playkit-js/playkit-js-dash": "1.22.0-canary.03b577c",
"@playkit-js/playkit-js-hls": "1.23.1-canary.cb04819",
"@playkit-js/playkit-js-ui": "0.63.0",
"@playkit-js/playkit-js-ui": "0.64.0-canary.482e1c2",
"hls.js": "^0.14.17",
"intersection-observer": "^0.12.0",
"playkit-js-providers": "https://github.com/kaltura/playkit-js-providers.git#v2.28.0",
Expand Down
120 changes: 120 additions & 0 deletions samples/ovp/dash-in-stream-thumbnails.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no'>
<title>Title</title>
<link rel='stylesheet' type='text/css' href='./style.css' />
<script src='./kaltura-ovp-player.js' type='text/javascript'></script>
</head>
<body>
<div id='player-placeholder'></div>
<script>
window.DEBUG_KALTURA_PLAYER = true;

const configureStream = (format, engine = 'html5') => {
kalturaPlayer.configure({
playback: {
streamPriority: [{
engine,
format
}]
}
});
};

const setFormatMedia = (format, mimetype, media, play) => {
const sources = mediaConfig.sources[format];
media = Array.isArray(media) ? media : [media];
media.forEach(url => {
const id = sources.length;
sources.push({
id,
url,
mimetype
});
});
if (play) {
configureStream(format);
}
};


const setHlsMedia = (media, play = false) => {
setFormatMedia('hls', 'application/x-mpegURL', media, play);
};

const setDashMedia = (media, play = false) => {
setFormatMedia('dash', 'application/dash+xml', media, play);
};

const setProgressiveMedia = (media, play = false) => {
setFormatMedia('progressive', 'video/mp4', media, play);
};

const setMedia = () => {
if (kalturaPlayer.config.playback?.streamPriority) {
kalturaPlayer.setMedia(mediaConfig);
} else {
const sources = Object.keys(mediaConfig.sources).map(key => ({
key,
value: mediaConfig.sources[key]
}));
const {key} = sources.find(source => source.value.length > 0);
configureStream(key);
kalturaPlayer.setMedia(mediaConfig);
}
};

const setupConfig = {
targetId: 'player-placeholder',
provider: {
partnerId: 1091,
env: {
cdnUrl: 'https://qa-apache-php7.dev.kaltura.com/',
serviceUrl: 'https://qa-apache-php7.dev.kaltura.com/api_v3'
}
}
};

const mediaConfig = {
dvr: 1,
plugins: {},
sources: {
hls: [],
dash: [],
progressive: []
}
};

try {
var kalturaPlayer = KalturaPlayer.setup(setupConfig);

setHlsMedia(
[/* fill your custom hls urls */],
false /* pass true if you want to force hls playback */
);
setDashMedia(
[
'http://pf5.broadpeak-vcdn.com/bpk-tv/tvr/default/index.mpd'
'http://dash.edgesuite.net/akamai/bbb_30fps/bbb_with_multiple_tiled_thumbnails.mpd',
'http://dash.edgesuite.net/akamai/bbb_30fps/bbb_with_tiled_thumbnails.mpd',
'http://dash.edgesuite.net/akamai/bbb_30fps/bbb_with_4_tiles_thumbnails.mpd',
'http://dash.edgesuite.net/akamai/bbb_30fps/bbb_with_tiled_thumbnails_2.mpd',
'http://dash.edgesuite.net/akamai/bbb_30fps/bbb_with_thumbnails.mpd',
'https://5.135.222.74/bpk-tv/live01/output1/index.mpd'
],
false /* pass true if you want to force dash playback */
);
setProgressiveMedia(
[/* fill your custom progressive urls */],
false /* pass true if you want to force progressive playback */
);

setMedia();
} catch (e) {
console.error(e.message);
}
</script>
</body>
</html>
85 changes: 85 additions & 0 deletions src/common/thumbnail-manager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
// @flow
import {Utils, ThumbnailInfo, MediaType} from '@playkit-js/playkit-js';
import evaluate from './utils/evaluate';

const DefaultThumbnailConfig: Object = {
thumbsWidth: 164,
thumbsHeight: 92,
thumbsSlices: 100
};

const THUMBNAIL_REGEX = /.*\/p\/\d+\/(?:[a-zA-Z]+\/\d+\/)*thumbnail\/entry_id\/\w+\/.*\d+/;
const THUMBNAIL_SERVICE_TEMPLATE: string = '{{thumbnailUrl}}/width/{{thumbsWidth}}/height/{{thumbsHeight}}/vid_slices/{{thumbsSlices}}/ks/{{ks}}';

class ThumbnailManager {
_player: Player;
_thumbnailConfig: ?SeekbarConfig;

constructor(player: Player, uiConfig: KPUIOptionsObject, mediaConfig: KPMediaConfig) {
this._player = player;
this._thumbnailConfig = this._buildKalturaThumbnailConfig(uiConfig, mediaConfig);
}

getThumbnail(time: number): ?ThumbnailInfo {
if (this._isUsingKalturaThumbnail()) {
return this._convertKalturaThumbnailToThumbnailInfo(time);
}
return this._player.getThumbnail(time);
}

getKalturaThumbnailConfig(): ?SeekbarConfig {
return this._thumbnailConfig;
}

_isUsingKalturaThumbnail = (): boolean => {
return !!(this._thumbnailConfig && this._thumbnailConfig.thumbsSprite);
};

_convertKalturaThumbnailToThumbnailInfo = (time: number): ?ThumbnailInfo => {
if (this._thumbnailConfig) {
const {thumbsSprite, thumbsWidth, thumbsHeight, thumbsSlices} = this._thumbnailConfig;
const duration = this._player.duration / thumbsSlices;
const thumbnailInfo = {
x: Math.floor(time / duration) * thumbsWidth,
y: thumbsHeight,
url: thumbsSprite,
height: thumbsHeight,
width: thumbsWidth
};
return new ThumbnailInfo(thumbnailInfo);
}
};

_buildKalturaThumbnailConfig = (uiConfig: KPUIOptionsObject, mediaConfig: KPMediaConfig): ?SeekbarConfig => {
const seekbarConfig = Utils.Object.getPropertyPath(uiConfig, 'components.seekbar');
const posterUrl = mediaConfig.sources && mediaConfig.sources.poster;
const isVod = mediaConfig.sources && mediaConfig.sources.type === MediaType.VOD;
const ks = mediaConfig.session && mediaConfig.session.ks;
const thumbnailConfig = Utils.Object.mergeDeep(DefaultThumbnailConfig, seekbarConfig);
const thumbsSprite = isVod ? this._getThumbSlicesUrl(posterUrl, ks, thumbnailConfig) : '';
return {
thumbsSprite,
...thumbnailConfig
};
};

_getThumbSlicesUrl = (posterUrl: string | Array<Object>, ks: ?string, thumbnailConfig: Object): string => {
if (typeof posterUrl === 'string') {
if (THUMBNAIL_REGEX.test(posterUrl)) {
try {
const model: Object = {
thumbnailUrl: posterUrl,
ks,
...thumbnailConfig
};
return evaluate(THUMBNAIL_SERVICE_TEMPLATE, model);
} catch (e) {
return '';
}
}
}
return '';
};
}

export {ThumbnailManager, THUMBNAIL_REGEX, DefaultThumbnailConfig};
24 changes: 3 additions & 21 deletions src/common/ui-wrapper.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
// @flow
import {UIManager} from '@playkit-js/playkit-js-ui';
import {Env, Utils, getLogger} from '@playkit-js/playkit-js';
import {DEFAULT_THUMBS_SLICES, DEFAULT_THUMBS_WIDTH, getThumbSlicesUrl} from './utils/thumbs';
import {Env, getLogger, Utils} from '@playkit-js/playkit-js';
import {KalturaPlayer} from '../kaltura-player';

/**
Expand Down Expand Up @@ -91,10 +90,9 @@ class UIWrapper {
this.setConfig({hasError: false}, 'engine');
}

setSeekbarConfig(mediaConfig: KPMediaConfig, uiConfig: KPUIOptionsObject): void {
setSeekbarConfig(uiConfig: KPUIOptionsObject, thumbnailConfig: ?SeekbarConfig): void {
const seekbarConfig = Utils.Object.getPropertyPath(uiConfig, 'components.seekbar');
const previewThumbnailConfig = getPreviewThumbnailConfig(mediaConfig, seekbarConfig);
this.setConfig(Utils.Object.mergeDeep({}, previewThumbnailConfig, seekbarConfig), 'seekbar');
this.setConfig(Utils.Object.mergeDeep({}, thumbnailConfig, seekbarConfig), 'seekbar');
}

setLoadingSpinnerState(show: boolean): void {
Expand Down Expand Up @@ -142,20 +140,4 @@ function appendPlayerViewToTargetContainer(targetId: string, view: HTMLElement):
}
}

/**
* Gets the preview thumbnail config for the ui seekbar component.
* @private
* @param {KPMediaConfig} mediaConfig - The player media config.
* @param {SeekbarConfig} seekbarConfig - The seek bar config.
* @returns {SeekbarConfig} - The seekbar component config.
*/
function getPreviewThumbnailConfig(mediaConfig: KPMediaConfig, seekbarConfig: SeekbarConfig): SeekbarConfig {
const previewThumbnailConfig: SeekbarConfig = {
thumbsSprite: getThumbSlicesUrl(mediaConfig, seekbarConfig),
thumbsWidth: DEFAULT_THUMBS_WIDTH,
thumbsSlices: DEFAULT_THUMBS_SLICES
};
return previewThumbnailConfig;
}

export {UIWrapper};
37 changes: 0 additions & 37 deletions src/common/utils/thumbs.js

This file was deleted.

40 changes: 28 additions & 12 deletions src/kaltura-player.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// @flow
import {EventType as UIEventType} from '@playkit-js/playkit-js-ui';
import {Provider} from 'playkit-js-providers';
import {supportLegacyOptions, maybeSetStreamPriority, hasYoutubeSource, mergeProviderPluginsConfig} from './common/utils/setup-helpers';
import {hasYoutubeSource, maybeSetStreamPriority, mergeProviderPluginsConfig, supportLegacyOptions} from './common/utils/setup-helpers';
import {addKalturaParams} from './common/utils/kaltura-params';
import {ViewabilityManager, ViewabilityType, VISIBILITY_CHANGE} from './common/utils/viewability-manager';
import {ConfigEvaluator} from './common/plugins';
import {BasePlugin, ConfigEvaluator, PluginManager} from './common/plugins';
import {addKalturaPoster} from 'poster';
import './assets/style.css';
import {UIWrapper} from './common/ui-wrapper';
Expand All @@ -14,27 +14,27 @@ import {CastEventType} from './common/cast/cast-event-type';
import {RemotePlayerManager} from './common/cast/remote-player-manager';
import {BaseRemotePlayer} from './common/cast/base-remote-player';
import {RemoteSession} from './common/cast/remote-session';
import {ControllerProvider, AdsController} from './common/controllers';
import {BasePlugin} from './common/plugins';
import {PluginManager} from './common/plugins';
import {AdsController, ControllerProvider} from './common/controllers';
import {getDefaultRedirectOptions} from 'player-defaults';
import {
AdEventType,
AutoPlayType,
Error,
EventManager,
EventType as CoreEventType,
AdEventType,
FakeEvent,
FakeEventTarget,
getLogger,
loadPlayer,
LogLevel,
registerEngineDecoratorProvider,
TextStyle,
ThumbnailInfo,
Track,
Utils,
registerEngineDecoratorProvider,
getLogger,
LogLevel,
AutoPlayType
Utils
} from '@playkit-js/playkit-js';
import {PluginReadinessMiddleware} from './common/plugins/plugin-readiness-middleware';
import {ThumbnailManager} from './common/thumbnail-manager';

class KalturaPlayer extends FakeEventTarget {
static _logger: any = getLogger('KalturaPlayer' + Utils.Generator.uniqueId(5));
Expand Down Expand Up @@ -164,7 +164,8 @@ class KalturaPlayer extends FakeEventTarget {
addKalturaParams(this, playerConfig);
maybeSetStreamPriority(this, playerConfig);
if (!hasYoutubeSource(playerConfig.sources)) {
this._uiWrapper.setSeekbarConfig(mediaConfig, this._localPlayer.config.ui);
this._thumbnailManager = new ThumbnailManager(this._localPlayer, this.config.ui, mediaConfig);
this._uiWrapper.setSeekbarConfig(this.config.ui, this._thumbnailManager.getKalturaThumbnailConfig());
}
this.configure(playerConfig);
}
Expand Down Expand Up @@ -469,6 +470,21 @@ class KalturaPlayer extends FakeEventTarget {
this._localPlayer.setLogLevel(level, name);
}

getThumbnail(time?: number): ?ThumbnailInfo {
if (this._thumbnailManager) {
if (!time) {
// If time isn't supplied, return thumbnail for player's current time
if (!isNaN(this.currentTime)) {
time = this.currentTime;
} else {
return null;
}
}
time = this.isLive() ? time + this.getStartTimeOfDvrWindow() : time;
return this._thumbnailManager.getThumbnail(time);
}
}

set textStyle(style: TextStyle): void {
this._localPlayer.textStyle = style;
}
Expand Down
2 changes: 1 addition & 1 deletion src/ovp/poster.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @flow
import {THUMBNAIL_REGEX} from '../common/utils/thumbs';
import {THUMBNAIL_REGEX} from '../common/thumbnail-manager';

/**
* Add poster with player dimensions to thumbnail API call
Expand Down
Loading

0 comments on commit 9581fa1

Please sign in to comment.