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-12850): ECDN Fallback Option Upon Inaccessible API Gateway #201

Merged
merged 12 commits into from
Feb 2, 2023
14 changes: 12 additions & 2 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ var provider = new playkit.providers.ott.Provider(config);
> {
> serviceUrl: string,
> cdnUrl: string,
> useApiCaptions: boolean
> useApiCaptions: boolean,
> replaceECDNAllUrls: boolean // optional
lianbenjamin marked this conversation as resolved.
Show resolved Hide resolved
> }
> ```
>
Expand All @@ -110,7 +111,8 @@ var provider = new playkit.providers.ott.Provider(config);
> {
> serviceUrl: "//www.kaltura.com/api_v3",
> cdnUrl: "//cdnapisec.kaltura.com",
> useApiCaptions: true
> useApiCaptions: true,
> replaceECDNAllUrls: true
> }
> ```
>
Expand All @@ -132,6 +134,14 @@ var provider = new playkit.providers.ott.Provider(config);
> > ##### Default: true
> >
> > ##### Description: Show captions on platforms that don't support inband captions (for example: playing using Flash). This flag is for the OVP provider, and can be turned off by setting its value to `false`.
>
> > ### config.env.replaceECDNAllUrls
> >
> > ##### Type: `boolean`
> >
> > ##### Default: true
> >
> > ##### Description: Defines whether to modify captions and poster URLs, in addition to play manifest URLs. This flag is for OVP provider, and can be turned off by setting its value to `false`.

##

Expand Down
3 changes: 2 additions & 1 deletion flow-typed/types/env-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ declare type ProviderEnvConfigObject = {
serviceUrl: string,
cdnUrl?: string,
analyticsServiceUrl?: string,
useApiCaptions?: boolean
useApiCaptions?: boolean,
replaceECDNAllUrls?: boolean
};
3 changes: 2 additions & 1 deletion src/k-provider/ovp/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ const defaultConfig: Object = {
format: 1
},
useApiCaptions: true,
loadThumbnailWithKs: false
loadThumbnailWithKs: false,
replaceECDNAllUrls: true
lianbenjamin marked this conversation as resolved.
Show resolved Hide resolved
};

export default class OVPConfiguration {
Expand Down
24 changes: 2 additions & 22 deletions src/k-provider/ovp/provider-parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ class OVPProviderParser {
OVPProviderParser._logger.warn(message);
return null;
}
mediaSource.url = OVPProviderParser._applyRegexAction(playbackContext, playUrl);
mediaSource.url = playUrl;
mediaSource.id = entryId + '_' + deliveryProfileId + ',' + format;
if (kalturaSource.hasDrmData()) {
const drmParams: Array<Drm> = [];
Expand Down Expand Up @@ -375,7 +375,7 @@ class OVPProviderParser {
OVPProviderParser._logger.warn(`failed to create play url from source, discarding source: (${entryId}_${deliveryProfileId}), ${format}.`);
return null;
} else {
mediaSource.url = OVPProviderParser._applyRegexAction(playbackContext, playUrl);
mediaSource.url = playUrl;
if (flavor.height && flavor.width) {
videoSources.push(mediaSource);
} else {
Expand Down Expand Up @@ -447,26 +447,6 @@ class OVPProviderParser {
static getErrorMessages(response: OVPMediaEntryLoaderResponse): Array<KalturaAccessControlMessage> {
return response.playBackContextResult.getErrorMessages();
}

/**
* Applies the request host regex on the url
* @function _applyRegexAction
* @param {KalturaPlaybackContext} playbackContext - The playback context
* @param {string} playUrl - The original url
* @returns {string} - The request host regex applied url
* @static
* @private
*/
static _applyRegexAction(playbackContext: KalturaPlaybackContext, playUrl: string): string {
const regexAction = playbackContext.getRequestHostRegexAction();
if (regexAction) {
const regex = new RegExp(regexAction.pattern, 'i');
if (playUrl.match(regex)) {
return playUrl.replace(regex, regexAction.replacement + '/');
}
}
return playUrl;
}
}

export const addKsToUrl = OVPProviderParser.addKsToUrl;
Expand Down
5 changes: 4 additions & 1 deletion src/k-provider/ovp/provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import BaseProvider from '../common/base-provider';
import MediaEntry from '../../entities/media-entry';
import OVPEntryListLoader from './loaders/entry-list-loader';
import Error from '../../util/error/error';
import RegexActionHandler from './regex-action-handler';

export default class OVPProvider extends BaseProvider<OVPProviderMediaInfoObject> {
_filterOptionsConfig: ProviderFilterOptionsObject = {redirectFromEntryId: true};
Expand Down Expand Up @@ -59,7 +60,9 @@ export default class OVPProvider extends BaseProvider<OVPProviderMediaInfoObject
return this._dataLoader.fetchData().then(
response => {
try {
resolve(this._parseDataFromResponse(response));
const mediaConfig = this._parseDataFromResponse(response);
const finalMediaConfig = RegexActionHandler.handleRegexAction(mediaConfig, response);
lianbenjamin marked this conversation as resolved.
Show resolved Hide resolved
resolve(finalMediaConfig);
} catch (err) {
reject(err);
}
Expand Down
147 changes: 147 additions & 0 deletions src/k-provider/ovp/regex-action-handler.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
//@flow
import getLogger from '../../util/logger';
import OVPConfiguration from './config';
import {KalturaAccessControlModifyRequestHostRegexAction} from './response-types';
import OVPMediaEntryLoader from './loaders/media-entry-loader';

class RegexActionHandler {
static _logger = getLogger('RegexActionHandler');

/**
* Applies the request host regex on the url
* @function _applyRegexAction
* @param {KalturaAccessControlModifyRequestHostRegexAction} regexAction - The regex action
* @param {string} url - The url to modify
* @returns {string} - The request host regex applied url
* @static
* @private
*/
static _applyRegexAction(regexAction: KalturaAccessControlModifyRequestHostRegexAction, url: string): string {
if (regexAction) {
const regex = new RegExp(regexAction.pattern, 'i');
if (url.match(regex)) {
return url.replace(regex, regexAction.replacement + '/');
}
}
return url;
}

/**
* Ping the ECDN url and modify urls if needed
* @function _pingECDNAndReplaceUrls
* @param {ProviderMediaConfigObject} mediaConfig - The media config
* @param {KalturaAccessControlModifyRequestHostRegexAction} regexAction - The regex action
* @param {string} urlPing - The url to ping
* @returns {Promise<ProviderMediaConfigObject>} - The media config with old or modified urls
* @static
* @private
*/
static _pingECDNAndReplaceUrls(
mediaConfig: ProviderMediaConfigObject,
regexAction: KalturaAccessControlModifyRequestHostRegexAction,
urlPing: string
): Promise<ProviderMediaConfigObject> {
return new Promise(resolve => {
const req = new XMLHttpRequest();
lianbenjamin marked this conversation as resolved.
Show resolved Hide resolved
req.open('GET', urlPing);
req.timeout = regexAction.checkAliveTimeoutMs;
req.onreadystatechange = () => {
if (req.readyState === 4) {
if (req.status === 200) {
RegexActionHandler._modifyUrls(mediaConfig, regexAction);
}
resolve(mediaConfig);
}
};
req.ontimeout = () => {
RegexActionHandler._logger.warn(`Got timeout while pinging the ECDN url. ping url: ${urlPing}`);
resolve(mediaConfig);
};
req.send();
});
}

/**
* Handles regex action
* @function handleRegexAction
* @param {ProviderMediaConfigObject} mediaConfig - The media config
* @param {Map<string, Function>} data - The response data
* @returns {ProviderMediaConfigObject} - The media config with old or modified urls
* @static
*/
static async handleRegexAction(mediaConfig: ProviderMediaConfigObject, data: Map<string, Function>): ProviderMediaConfigObject {
let cdnUrl = OVPConfiguration.get()?.cdnUrl || '';
SivanA-Kaltura marked this conversation as resolved.
Show resolved Hide resolved
const regexAction = RegexActionHandler._extractRegexActionFromData(data);
if (regexAction && regexAction.pattern && regexAction.replacement) {
SivanA-Kaltura marked this conversation as resolved.
Show resolved Hide resolved
const regExp = new RegExp(regexAction.pattern, 'i');
if (cdnUrl.match(regExp)) {
cdnUrl = cdnUrl.replace(regExp, regexAction.replacement);
if (regexAction.checkAliveTimeoutMs && regexAction.checkAliveTimeoutMs > 0) {
SivanA-Kaltura marked this conversation as resolved.
Show resolved Hide resolved
let urlToPing = cdnUrl + '/api_v3/service/system/action/ping/format/1';
const finalMediaConfig = await RegexActionHandler._pingECDNAndReplaceUrls(mediaConfig, regexAction, urlToPing);
SivanA-Kaltura marked this conversation as resolved.
Show resolved Hide resolved
return finalMediaConfig;
} else {
RegexActionHandler._modifyUrls(mediaConfig, regexAction);
return mediaConfig;
}
}
} else {
return mediaConfig;
}
}

/**
* Modify the urls
* @function _modifyUrls
* @param {ProviderMediaConfigObject} mediaConfig - The media config
* @param {KalturaAccessControlModifyRequestHostRegexAction} regexAction - The regex action
* @returns {void}
* @static
* @private
*/
static _modifyUrls(mediaConfig: ProviderMediaConfigObject, regexAction: KalturaAccessControlModifyRequestHostRegexAction) {
lianbenjamin marked this conversation as resolved.
Show resolved Hide resolved
RegexActionHandler._logger.debug(`Starting to modify urls...`);
const sources = mediaConfig.sources;
if (sources.hls.length > 0) {
sources.hls.forEach(src => (src.url = RegexActionHandler._applyRegexAction(regexAction, src.url)));
SivanA-Kaltura marked this conversation as resolved.
Show resolved Hide resolved
}
if (sources.dash.length > 0) {
sources.dash.forEach(src => (src.url = RegexActionHandler._applyRegexAction(regexAction, src.url)));
}
if (sources.progressive.length > 0) {
sources.progressive.forEach(src => (src.url = RegexActionHandler._applyRegexAction(regexAction, src.url)));
}
if (sources.image.length > 0) {
sources.image.forEach(src => (src.url = RegexActionHandler._applyRegexAction(regexAction, src.url)));
}
if (OVPConfiguration.get().replaceECDNAllUrls) {
if (sources.captions.length > 0) {
sources.captions.forEach(src => (src.url = RegexActionHandler._applyRegexAction(regexAction, src.url)));
}
if (sources.poster) {
sources.poster = RegexActionHandler._applyRegexAction(regexAction, sources.poster);
}
}
RegexActionHandler._logger.debug(`Finished modifying urls`);
}

/**
* Extracts the regex action from the data response
* @function _extractRegexActionFromData
* @param {Map<string, Function>} data - The response data
* @returns {KalturaAccessControlModifyRequestHostRegexAction} regexAction - The regex action
* @static
* @private
*/
static _extractRegexActionFromData(data: any): ?KalturaAccessControlModifyRequestHostRegexAction {
if (data.has(OVPMediaEntryLoader.id)) {
lianbenjamin marked this conversation as resolved.
Show resolved Hide resolved
const mediaLoader = data.get(OVPMediaEntryLoader.id);
if (mediaLoader && mediaLoader.response) {
SivanA-Kaltura marked this conversation as resolved.
Show resolved Hide resolved
const response = (mediaLoader: OVPMediaEntryLoader).response;
return response.playBackContextResult.getRequestHostRegexAction();
}
}
}
}

export default RegexActionHandler;
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ export class KalturaAccessControlModifyRequestHostRegexAction extends KalturaRul
* @type {number}
*/
replacmenServerNodeId: number;

/**
* @member - checkAliveTimeout in milliseconds
* @type {number}
*/
checkAliveTimeoutMs: number;
/**
* @constructor
* @param {Object} data - The response
Expand All @@ -27,5 +31,6 @@ export class KalturaAccessControlModifyRequestHostRegexAction extends KalturaRul
this.pattern = data.pattern;
this.replacement = data.replacement;
this.replacmenServerNodeId = data.replacmenServerNodeId;
this.checkAliveTimeoutMs = data.checkAliveTimeoutMs;
}
}