Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(FEC-13946): Player core - Use shaka preload mechanism during playlist playback #792

Merged
merged 11 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
"@playkit-js/playkit-js-providers": "2.40.5",
"@playkit-js/playkit-js-ui": "0.79.1-canary.0-24968c4",
"hls.js": "^1.5.8",
"shaka-player": "4.7.0"
"shaka-player": "4.8.11"
},
"devDependencies": {
"@babel/core": "^7.22.20",
Expand Down
62 changes: 62 additions & 0 deletions src/common/playlist/playlist-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import { Playlist } from './playlist';
import { PlaylistItem } from './playlist-item';
import { mergeProviderPluginsConfig } from '../utils/setup-helpers';
import { PlaylistOptions, PlaylistCountdownOptions, KalturaPlayerConfig, PluginsConfig, KPPlaylistObject, PlaylistConfigObject } from '../../types';
import { addClientTag, addReferrer, addStartAndEndTime, updateSessionIdInUrl } from '../utils/kaltura-params';
import { SessionIdGenerator } from '../utils/session-id-generator';

/**
* @class PlaylistManager
Expand Down Expand Up @@ -332,6 +334,25 @@ class PlaylistManager {
}
this._player.configure({ playback });
this._playlist.activeItemIndex = index;

const promises: Promise<any>[] = [];

if (this._playlist.items[index - 1]) {
promises.push(this.prepareEntry(index - 1));
}
if (this._playlist.items[index + 1]) {
promises.push(this.prepareEntry(index + 1));
}

Promise.all(promises).then((mediaConfigs) => {
let cachedUrls = [];
for (const mediaConfig of mediaConfigs) {
cachedUrls = cachedUrls.concat(mediaConfig.sources.dash.map((dashSource) => dashSource.url));
}

this._player.setCachedUrls(cachedUrls);
});

if (activeItem.isPlayable()) {
this._resetProviderPluginsConfig();
const mergedPluginsConfigAndFromApp = mergeProviderPluginsConfig(activeItem.plugins, this._player.config.plugins);
Expand Down Expand Up @@ -365,6 +386,21 @@ class PlaylistManager {
return this._player.loadMedia(this._mediaInfoList[index]).then((mediaConfig) => {
this._playlist.updateItemSources(index, mediaConfig.sources);
this._playlist.updateItemPlugins(index, mediaConfig.plugins);

let sessionId = this._player.sessionIdCache?.get(mediaConfig.sources.id);
if (!sessionId) {
sessionId = SessionIdGenerator.next();
this._player.sessionIdCache?.set(mediaConfig.sources.id, sessionId);
}

mediaConfig.sources.dash = mediaConfig.sources.dash.map((source) => {
source.url = updateSessionIdInUrl(this._player, source.url, sessionId);
source.url = addReferrer(source.url);
source.url = addClientTag(source.url, mediaConfig.productVersion);
source.url = addStartAndEndTime(source.url, mediaConfig.sources);
return source;
});

// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
this._player.dispatchEvent(
Expand All @@ -388,6 +424,32 @@ class PlaylistManager {
public destroy(): void {
this._eventManager.destroy();
}

private prepareEntry(index: number): Promise<any> {
if (this._playlist.items[index].isPlayable()) return Promise.resolve({ sources: this._playlist.items[index].sources });

return this._player.provider.getMediaConfig(this._mediaInfoList[index]).then((providerMediaConfig) => {
const mediaConfig = Utils.Object.copyDeep(providerMediaConfig);
this._playlist.updateItemSources(index, mediaConfig.sources);
this._playlist.updateItemPlugins(index, mediaConfig.plugins);

let sessionId = this._player.sessionIdCache?.get(mediaConfig.sources.id);
if (!sessionId) {
sessionId = SessionIdGenerator.next();
this._player.sessionIdCache?.set(mediaConfig.sources.id, sessionId);
}

mediaConfig.sources.dash = mediaConfig.sources.dash.map((source) => {
source.url = updateSessionIdInUrl(this._player, source.url, sessionId);
source.url = addReferrer(source.url);
source.url = addClientTag(source.url, mediaConfig.productVersion);
source.url = addStartAndEndTime(source.url, mediaConfig.sources);
return source;
});

return Promise.resolve(mediaConfig);
});
}
}

export { PlaylistManager };
48 changes: 26 additions & 22 deletions src/common/utils/kaltura-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { PKSourcesConfigObject, PlayerStreamTypes, StreamType, Utils } from '@pl
import { getServerUIConf } from './setup-helpers';
import { KalturaPlayer } from '../../kaltura-player';
import { PartialKPOptionsObject } from '../../types';
import { SessionIdGenerator } from './session-id-generator';

const PLAY_MANIFEST = 'playmanifest/';
const PLAY_SESSION_ID = 'playSessionId=';
Expand All @@ -28,6 +29,9 @@ function handleSessionId(player: KalturaPlayer, playerConfig: PartialKPOptionsOb
} else {
// on first playback
addSessionId(playerConfig);
if (player?.playlist?.items?.length && playerConfig.sources?.id) {
player.sessionIdCache?.set(playerConfig.sources.id, playerConfig.session!.id as string);
}
}
}

Expand All @@ -37,9 +41,7 @@ function handleSessionId(player: KalturaPlayer, playerConfig: PartialKPOptionsOb
* @private
*/
function addSessionId(playerConfig: PartialKPOptionsObject): void {
const primaryGUID = Utils.Generator.guid();
const secondGUID = Utils.Generator.guid();
setSessionId(playerConfig, primaryGUID + ':' + secondGUID);
setSessionId(playerConfig, SessionIdGenerator.next());
}

/**
Expand All @@ -49,19 +51,18 @@ function addSessionId(playerConfig: PartialKPOptionsObject): void {
* @private
*/
function updateSessionId(player: KalturaPlayer, playerConfig: PartialKPOptionsObject): void {
const secondGuidInSessionIdRegex = /:((?:[a-z0-9]|-)*)/i;
const secondGuidInSessionId = secondGuidInSessionIdRegex.exec(
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
player.config.session.id
);
if (secondGuidInSessionId && secondGuidInSessionId[1]) {
setSessionId(
playerConfig,
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
player.config.session.id.replace(secondGuidInSessionId[1], Utils.Generator.guid())
);
const entryId = playerConfig.sources?.id;

if (!player?.playlist?.items?.length) {
setSessionId(playerConfig, SessionIdGenerator.next());
} else if (entryId) {
if (player.sessionIdCache?.get(entryId)) {
setSessionId(playerConfig, player.sessionIdCache?.get(entryId));
} else {
const sessionId = SessionIdGenerator.next();
player.sessionIdCache?.set(entryId, sessionId);
setSessionId(playerConfig, sessionId);
}
}
}

Expand All @@ -83,13 +84,16 @@ function setSessionId(playerConfig: PartialKPOptionsObject, sessionId: string):
* @return {string} - the url with the new sessionId
* @private
*/
function updateSessionIdInUrl(url: string, sessionId?: string, paramName: string = PLAY_SESSION_ID): string {
function updateSessionIdInUrl(player: KalturaPlayer | null, url: string, sessionId?: string, paramName: string = PLAY_SESSION_ID): string {
if (sessionId) {
const sessionIdInUrlRegex = new RegExp(paramName + '((?:[a-z0-9]|-)*:(?:[a-z0-9]|-)*)', 'i');
const sessionIdInUrl = sessionIdInUrlRegex.exec(url);
// this url has session id (has already been played)
if (sessionIdInUrl && sessionIdInUrl[1]) {
// this url has session id (has already been played)
url = url.replace(sessionIdInUrl[1], sessionId);
// session id should be the same for the same entry
if (!player?.playlist?.items?.length) {
url = url.replace(sessionIdInUrl[1], sessionId);
}
} else {
url += getQueryStringParamDelimiter(url) + paramName + sessionId;
}
Expand Down Expand Up @@ -201,15 +205,15 @@ function addKalturaParams(player: KalturaPlayer, playerConfig: PartialKPOptionsO
// @ts-ignore
!source.localSource
) {
source.url = updateSessionIdInUrl(source.url, sessionId);
source.url = updateSessionIdInUrl(player, source.url, sessionId);
source.url = addReferrer(source.url);
source.url = addClientTag(source.url, productVersion);
source.url = addStartAndEndTime(source.url, sources);
}
if (source['drmData'] && source['drmData'].length) {
source['drmData'].forEach((drmData) => {
if (typeof drmData.licenseUrl === 'string' && [UDRM_DOMAIN, CUSTOM_DATA, SIGNATURE].every((t) => drmData.licenseUrl.includes(t))) {
drmData.licenseUrl = updateSessionIdInUrl(drmData.licenseUrl, sessionId, DRM_SESSION_ID);
drmData.licenseUrl = updateSessionIdInUrl(player, drmData.licenseUrl, sessionId, DRM_SESSION_ID);
drmData.licenseUrl = addClientTag(drmData.licenseUrl, productVersion);
drmData.licenseUrl = addReferrer(drmData.licenseUrl);
drmData.licenseUrl = addUIConfId(drmData.licenseUrl, playerConfig);
Expand All @@ -221,4 +225,4 @@ function addKalturaParams(player: KalturaPlayer, playerConfig: PartialKPOptionsO
});
}

export { addKalturaParams, handleSessionId, updateSessionIdInUrl, getReferrer, addReferrer, addClientTag, addUIConfId };
export { addKalturaParams, handleSessionId, updateSessionIdInUrl, getReferrer, addReferrer, addClientTag, addUIConfId, addStartAndEndTime };
17 changes: 17 additions & 0 deletions src/common/utils/session-id-cache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class SessionIdCache {
private cache = new Map();

public set(key: string, value: string): void {
this.cache.set(key, value);
}

public get(key: string): string {
return this.cache.get(key);
}

public clear(): void {
this.cache.clear();
}
}

export { SessionIdCache };
28 changes: 28 additions & 0 deletions src/common/utils/session-id-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Utils } from '@playkit-js/playkit-js';

class SessionIdGenerator {
private static _value: string = '';

private static init(): void {
SessionIdGenerator._value = `${Utils.Generator.guid()}:${Utils.Generator.guid()}`;
}

public static next(): string {
if (!SessionIdGenerator._value) {
this.init();
return SessionIdGenerator._value;
}

const next = SessionIdGenerator._value;

const secondGuidInSessionIdRegex = /:((?:[a-z0-9]|-)*)/i;
const secondGuidInSessionId = secondGuidInSessionIdRegex.exec(next);
if (secondGuidInSessionId && secondGuidInSessionId[1]) {
SessionIdGenerator._value = next.replace(secondGuidInSessionId[1], Utils.Generator.guid());
}

return next;
}
}

export { SessionIdGenerator };
3 changes: 2 additions & 1 deletion src/common/utils/setup-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import SessionStorageManager from '../storage/session-storage-manager';
import { BaseStorageManager } from '../storage/base-storage-manager';
import { BasePlugin } from '../plugins';
import { KalturaPlayerConfig, LegacyPartialKPOptionsObject, PartialKPOptionsObject, PluginsConfig, PlaybackConfig } from '../../types';
import { SessionIdGenerator } from './session-id-generator';

const setupMessages: Array<any> = [];
const CONTAINER_CLASS_NAME: string = 'kaltura-player-container';
Expand Down Expand Up @@ -99,7 +100,7 @@ function validateProviderConfig(options: KalturaPlayerConfig): void {
source.url = addProductVersion(source.url, productVersion);
source.url = addReferrer(source.url);
source.url = addClientTag(source.url, productVersion);
source.url = updateSessionIdInUrl(source.url, Utils.Generator.guid() + ':' + Utils.Generator.guid());
source.url = updateSessionIdInUrl(null, source.url, SessionIdGenerator.next());
navigator.sendBeacon && navigator.sendBeacon(source.url);
}
}
Expand Down
12 changes: 12 additions & 0 deletions src/kaltura-player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
MediaCapabilitiesObject
} from './types';
import getErrorCategory from './common/utils/error-helper';
import { SessionIdCache } from './common/utils/session-id-cache';

export class KalturaPlayer extends FakeEventTarget {
private static _logger: any = getLogger('KalturaPlayer' + Utils.Generator.uniqueId(5));
Expand Down Expand Up @@ -106,10 +107,12 @@ export class KalturaPlayer extends FakeEventTarget {
private _serviceProvider: ServiceProvider;
private _isVisible: boolean = false;
private _autoPaused: boolean = false;
private _sessionIdCache: SessionIdCache | null = null;

constructor(options: KalturaPlayerConfig) {
super();
const { sources, plugins } = options;
this._sessionIdCache = new SessionIdCache();
this._configEvaluator = new ConfigEvaluator();
this._configEvaluator.evaluatePluginsConfig(plugins, options);
this._playbackStart = false;
Expand Down Expand Up @@ -369,6 +372,7 @@ export class KalturaPlayer extends FakeEventTarget {
if (targetContainer && targetContainer.parentNode) {
Utils.Dom.removeChild(targetContainer.parentNode, targetContainer);
}
this._sessionIdCache?.clear();
}

public isLive(): boolean {
Expand Down Expand Up @@ -1166,4 +1170,12 @@ export class KalturaPlayer extends FakeEventTarget {
public async getMediaCapabilities(hevcConfig?: HEVCConfigObject): Promise<MediaCapabilitiesObject> {
return getMediaCapabilities(hevcConfig);
}

public setCachedUrls(urls: string[]): void {
this._localPlayer.setCachedUrls(urls);
}

public get sessionIdCache(): SessionIdCache | null {
return this._sessionIdCache;
}
}
Loading
Loading