Skip to content

Commit

Permalink
feat(FEC-8705): playlist - OTT support (#186)
Browse files Browse the repository at this point in the history
* change `loadPlaylistByEntryList` api to get array of objects instead of strings (backward compatible)
* load a playlist item using `mediaInfo` object instead of `entryId` string
* update a playlist item poster using `addKalturaPoster` api
* update item sources using dedicated api instead of `mergeDeep` which causes side effects (e.g poster overriding)

Depends on kaltura/playkit-js-providers#73
  • Loading branch information
yairans committed Dec 16, 2018
1 parent 7e7b669 commit 6db068b
Show file tree
Hide file tree
Showing 19 changed files with 1,943 additions and 716 deletions.
1,510 changes: 924 additions & 586 deletions docs/api.md

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion flow-typed/types/kaltura-player-options.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// @flow
import {KPPlaylistObject} from './playlist';

declare type KPOptionsObject = {
targetId: string,
logLevel?: string,
Expand All @@ -8,7 +10,7 @@ declare type KPOptionsObject = {
plugins?: PKPluginsConfigObject,
session?: PKSessionConfigObject,
provider: ProviderOptionsObject,
playlist?: KPPlaylistConfigObject,
playlist?: KPPlaylistObject,
ui: UIOptionsObject,
cast?: { [key: string]: any }
};
Expand Down
20 changes: 0 additions & 20 deletions flow-typed/types/playlist-config.js

This file was deleted.

14 changes: 0 additions & 14 deletions flow-typed/types/playlist-countdown-options.js

This file was deleted.

10 changes: 0 additions & 10 deletions flow-typed/types/playlist-item-config.js

This file was deleted.

12 changes: 0 additions & 12 deletions flow-typed/types/playlist-metadata.js

This file was deleted.

10 changes: 0 additions & 10 deletions flow-typed/types/playlist-options.js

This file was deleted.

64 changes: 64 additions & 0 deletions flow-typed/types/playlist.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// @flow
import {PlaylistItem} from '../../src/common/playlist/playlist-item';

/**
* @typedef {Object} KPPlaylistOptions
* @property {boolean} autoContinue - Whether to continue to the next item automatically
*/
type _KPPlaylistOptions = {
autoContinue: boolean
};
declare type KPPlaylistOptions = _KPPlaylistOptions;

/**
* @typedef {Object} KPPlaylistCountdownOptions
* @param {number} [timeToShow] - When the countdown will appear (by default is towards the end)
* @param {number} [duration=10] - How match time the countdown will appear
* @param {boolean} [showing=true] - Whether to show the countdown
*/
type _KPPlaylistCountdownOptions = {
timeToShow?: number,
duration: number,
showing: boolean
};
declare type KPPlaylistCountdownOptions = _KPPlaylistCountdownOptions;

/**
* @typedef {Object} KPPlaylistConfigObject
* @param {KPPlaylistOptions} options - The playlist options
* @param {KPPlaylistCountdownOptions} countdown - The playlist countdown config
* @param {Array<PlaylistItem>} items - The playlist items
*/
type _KPPlaylistConfigObject = {
options: KPPlaylistOptions,
countdown: KPPlaylistCountdownOptions,
items: Array<PlaylistItem>
};
declare type KPPlaylistConfigObject = _KPPlaylistConfigObject;

/**
* @typedef {Object} KPPlaylistConfigObject
* @param {string} id - The playlist id
* @param {ProviderPlaylistMetadataObject} metadata - The playlist metadata
* @param {KPPlaylistOptions} options - The playlist options
* @param {KPPlaylistCountdownOptions} countdown - The playlist countdown config
* @param {Array<PlaylistItem>} items - The playlist items
*/
type _KPPlaylistObject = {
id: string,
metadata: ProviderPlaylistMetadataObject,
poster?: string,
options: KPPlaylistOptions,
countdown: KPPlaylistCountdownOptions,
items: Array<PlaylistItem>
};
declare type KPPlaylistObject = _KPPlaylistObject;

/**
* @typedef {Object} KPPlaylistItemConfigObject
* @property {KPPlaylistCountdownOptions} [countdown] - Countdown options
*/
type _KPPlaylistItemConfigObject = {
countdown?: KPPlaylistCountdownOptions;
};
declare type KPPlaylistItemConfigObject = _KPPlaylistItemConfigObject;
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@
"babel-polyfill": "^6.23.0",
"hls.js": "^0.12.1",
"js-logger": "^1.3.0",
"playkit-js-providers": "https://github.com/kaltura/playkit-js-providers.git#v2.12.0",
"playkit-js-providers": "https://github.com/kaltura/playkit-js-providers.git#master",
"proxy-polyfill": "^0.3.0",
"shaka-player": "https://github.com/kaltura/shaka-player.git#v2.3.3-k-4"
},
Expand Down
2 changes: 1 addition & 1 deletion samples/ovp/playlist-by-id.html
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

try {
var kalturaPlayer = KalturaPlayer.setup(config);
kalturaPlayer.loadPlaylist({playlistId: '0_jr7vpj0w'});
kalturaPlayer.loadPlaylist({playlistId: '0_wckoqjnn'});
} catch (e) {
console.error(e.message)
}
Expand Down
15 changes: 15 additions & 0 deletions src/common/playlist/playlist-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,21 @@ class PlaylistItem {
this._config = config;
}

/**
* Update the playlist item sources
* @param {ProviderMediaConfigSourcesObject} sourcesObject - The sources
* @returns {void}
* @instance
* @memberof PlaylistItem
*/
updateSources(sourcesObject: ProviderMediaConfigSourcesObject): void {
if (this._sources) {
this._sources.hls = sourcesObject.hls;
this._sources.dash = sourcesObject.dash;
this._sources.progressive = sourcesObject.progressive;
}
}

/**
* Playlist item sources
* @type {?ProviderMediaConfigSourcesObject}
Expand Down
91 changes: 75 additions & 16 deletions src/common/playlist/playlist-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {PlaylistEventType} from './playlist-event-type';
import getLogger from '../utils/logger';
import {Playlist} from './playlist';
import {PlaylistItem} from './playlist-item';
import {addKalturaPoster} from 'poster';

/**
* @class PlaylistManager
Expand All @@ -16,50 +17,71 @@ class PlaylistManager {
_player: KalturaPlayer;
_eventManager: EventManager;
_playlist: Playlist;
_playerOptions: KPOptionsObject;
_options: KPPlaylistOptions;
_countdown: KPPlaylistCountdownOptions;
_playerOptions: KPOptionsObject;
_mediaInfoList: Array<ProviderMediaInfoObject>;

constructor(player: KalturaPlayer, options: KPOptionsObject) {
this._player = player;
this._eventManager = new EventManager();
this._playlist = new Playlist();
this._options = {autoContinue: true};
this._countdown = {duration: 10, showing: true};
this._mediaInfoList = [];
this._playerOptions = options;
}

/**
* Config the playlist
* @param {KPPlaylistConfigObject} [config] - The playlist config
* @param {KPPlaylistObject} [config] - The playlist config
* @param {ProviderEntryListObject} [entryList] - Entry list
* @returns {void}
* @instance
* @memberof PlaylistManager
* @private
*/
configure(config: ?KPPlaylistConfigObject) {
configure(config: ?KPPlaylistObject, entryList: ?ProviderEntryListObject) {
if (config) {
this._playlist.configure(config);
Utils.Object.mergeDeep(this._options, config.options);
Utils.Object.mergeDeep(this._countdown, config.countdown);
if (config.items && config.items.find(item => !!item.sources)) {
this._mediaInfoList = config.items.map((item, index) => {
return entryList && entryList.entries && typeof entryList.entries[index] === 'object'
? entryList.entries[index]
: {entryId: item.sources.id};
});
this._player.dispatchEvent(new FakeEvent(PlaylistEventType.PLAYLIST_LOADED, {playlist: this}));
this._addBindings();
this.playNext();
}
}
}

/**
* Load a playlist
* @param {KPPlaylistObject} playlistData - The playlist data
* @param {KPPlaylistConfigObject} [playlistConfig] - The playlist config
* @param {ProviderEntryListObject} [entryList] - Entry list
* @returns {void}
* @instance
* @memberof PlaylistManager
*/
load(playlistData: ProviderPlaylistObject, playlistConfig: ?KPPlaylistConfigObject, entryList: ?ProviderEntryListObject): void {
const mergedPlaylistData: KPPlaylistObject = this._getMergedPlaylistData(playlistData, playlistConfig);
this.configure(mergedPlaylistData, entryList);
}

/**
* Reset the playlist
* @returns {void}
* @instance
* @memberof PlaylistManager
* @private
*/
reset() {
this._eventManager.removeAll();
this._playlist = new Playlist();
this._mediaInfoList = [];
}

/**
Expand Down Expand Up @@ -147,14 +169,24 @@ class PlaylistManager {

/**
* Playlist metadata
* @type {KPPlaylistMetadata}
* @type {ProviderPlaylistMetadataObject}
* @instance
* @memberof PlaylistManager
*/
get metadata(): KPPlaylistMetadata {
get metadata(): ProviderPlaylistMetadataObject {
return this._playlist.metadata;
}

/**
* Playlist poster
* @type {?string}
* @instance
* @memberof PlaylistManager
*/
get poster(): ?string {
return this._playlist.poster;
}

/**
* Playlist countdown
* @type {KPPlaylistCountdownOptions}
Expand All @@ -180,15 +212,40 @@ class PlaylistManager {
return this._options;
}

_getMergedPlaylistData(playlistData: ProviderPlaylistObject, playlistConfig: ?KPPlaylistConfigObject): KPPlaylistObject {
const mergedPlaylistData: KPPlaylistObject = {
id: playlistData.id,
metadata: playlistData.metadata,
poster: (playlistData.poster: string),
options: playlistConfig ? playlistConfig.options : this._options,
countdown: playlistConfig ? playlistConfig.countdown : this.countdown,
items: playlistData.items.map((item, index) => {
const itemData = Utils.Object.copyDeep(item);
Utils.Object.mergeDeep(
itemData.sources,
playlistConfig && playlistConfig.items && playlistConfig.items[index] && playlistConfig.items[index].sources
);
addKalturaPoster(itemData.sources, item.sources, this._player.dimensions);
return {
sources: itemData.sources,
config: playlistConfig && playlistConfig.items && playlistConfig.items[index] && playlistConfig.items[index].config
};
})
};
return mergedPlaylistData;
}

_addBindings() {
this._eventManager.listen(this._player, this._player.Event.Core.PLAYBACK_ENDED, () => this._onPlaybackEnded());
}

_onPlaybackEnded(): void {
if (this._playerOptions.ui.disable || !this.countdown.showing) {
this._playlist.next
? this._options.autoContinue && this.playNext()
: this._player.dispatchEvent(new FakeEvent(PlaylistEventType.PLAYLIST_ENDED));
if (this._playlist.next.item) {
if (this._options.autoContinue && (this._playerOptions.ui.disable || !this.countdown.showing)) {
this.playNext();
}
} else {
this._player.dispatchEvent(new FakeEvent(PlaylistEventType.PLAYLIST_ENDED));
}
}

Expand All @@ -205,11 +262,13 @@ class PlaylistManager {
this._player.setMedia({session: {}, plugins: {}, sources: activeItem.sources});
this._player.dispatchEvent(new FakeEvent(PlaylistEventType.PLAYLIST_ITEM_CHANGED, {index, activeItem}));
return Promise.resolve();
} else if (activeItem.sources && activeItem.sources.id) {
return this._player.loadMedia({entryId: activeItem.sources.id}).then(mediaConfig => {
Utils.Object.mergeDeep(activeItem.sources, mediaConfig.sources);
this._player.dispatchEvent(new FakeEvent(PlaylistEventType.PLAYLIST_ITEM_CHANGED, {index, activeItem}));
});
} else {
if (this._mediaInfoList[index]) {
return this._player.loadMedia(this._mediaInfoList[index]).then(mediaConfig => {
this._playlist.updateItemSources(index, mediaConfig.sources);
this._player.dispatchEvent(new FakeEvent(PlaylistEventType.PLAYLIST_ITEM_CHANGED, {index, activeItem}));
});
}
}
return Promise.reject();
}
Expand Down
Loading

0 comments on commit 6db068b

Please sign in to comment.