@playkit-js/playkit-js
Version:
[](https://github.com/kaltura/playkit-js/actions/workflows/run_canary.yaml) [ • 11.3 kB
text/typescript
import { FakeEvent } from '../../../event/fake-event';
import { FakeEventTarget } from '../../../event/fake-event-target';
import Error from '../../../error/error';
import { CustomEventType, Html5EventType } from '../../../event/event-type';
import getLogger from '../../../utils/logger';
import Track from '../../../track/track';
import TextStyle from '../../../track/text-style';
import VideoTrack from '../../../track/video-track';
import AudioTrack from '../../../track/audio-track';
import PKTextTrack from '../../../track/text-track';
import { EventManager } from '../../../event/event-manager';
import ImageTrack from '../../../track/image-track';
import { ThumbnailInfo } from '../../../thumbnail/thumbnail-info';
import { IMediaSourceAdapter } from '../../../types';
import { PKABRRestrictionObject, PKDrmDataObject, PKMediaSourceCapabilities, PKMediaSourceObject } from '../../../types';
import { ILogger } from 'js-logger';
const CURRENT_OR_NEXT_SEGMENT_COUNT: number = 2;
export default class BaseMediaSourceAdapter extends FakeEventTarget implements IMediaSourceAdapter {
/**
* The id of the adapter.
* @member {string} id
* @static
* @private
*/
public static id: string = 'BaseAdapter';
/**
* Passing the getLogger function to the actual media source adapter.
* @type {Function}
* @static
*/
public static getLogger: (name?: string) => ILogger = getLogger;
protected static _logger = BaseMediaSourceAdapter.getLogger(BaseMediaSourceAdapter.id);
/**
* The adapter config.
* @member {Object} _config
* @private
*/
protected _config: any;
/**
* The source object.
* @member {PKMediaSourceObject} _sourceObj
* @private
*/
protected _sourceObj?: PKMediaSourceObject;
/**
* The dom video element.
* @member {HTMLVideoElement} _videoElement
* @private
*/
protected _videoElement: HTMLVideoElement;
/**
* The adapter capabilities
* @private
*/
protected _capabilities: PKMediaSourceCapabilities = { fpsControl: false };
/**
* The event manager of the adapter.
* @type {EventManager}
* @private
*/
protected _eventManager: EventManager;
/**
* The load promise
* @member {Promise<{tracks: Array<Track>}>} - _loadPromise
* @type {Promise<{tracks: Array<Track>}>}
* @private
*/
protected _loadPromise: Promise<{ tracks: Array<Track> }> | undefined;
/**
* The duration change handler.
* @type {Function}
* @private
*/
private _onDurationChanged: (...args: any) => any;
/**
* Checks if the media source adapter is supported.
* @function isSupported
* @returns {boolean} - Whether the media source adapter is supported.
* @static
*/
public static isSupported(): boolean {
return true;
}
/**
* check for media source supported on browser
* @static
* @returns {boolean} - Whether the media source is supported.
*/
public static isMSESupported(): boolean {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const mediaSource = window.MediaSource || window.WebKitMediaSource;
// isTypeSupported isn't exist or not a function for old MSE implementation
return !!mediaSource && typeof mediaSource.isTypeSupported === 'function';
}
/**
* @constructor
* @param {HTMLVideoElement} videoElement - The video element which bind to media source adapter.
* @param {PKMediaSourceObject} source - The source object.
* @param {Object} config - The media source adapter configuration.
*/
constructor(videoElement: HTMLVideoElement, source: PKMediaSourceObject, config: any = {}) {
super();
this._videoElement = videoElement;
this._sourceObj = source;
this._config = config;
this._onDurationChanged = (): void => {
if (this.isLive() && this._videoElement.paused) {
this._trigger(Html5EventType.TIME_UPDATE);
}
};
this._eventManager = new EventManager();
this._handleLiveTimeUpdate();
}
/**
* Destroys the media source adapter.
* @function destroy
* @returns {void}
*/
public destroy(): Promise<void> {
this._sourceObj = undefined;
this._config = {};
this.disableNativeTextTracks();
this._videoElement.removeEventListener(Html5EventType.DURATION_CHANGE as keyof HTMLVideoElementEventMap, this._onDurationChanged);
this._eventManager.destroy();
return Promise.resolve();
}
/**
* Triggers the appropriate track changed event.
* @param {Track} track - The selected track.
* @private
* @returns {void}
*/
protected _onTrackChanged(track: VideoTrack | AudioTrack | PKTextTrack | ImageTrack): void {
if (track instanceof VideoTrack) {
BaseMediaSourceAdapter._logger.debug('Video track changed', track);
this._trigger(CustomEventType.VIDEO_TRACK_CHANGED, { selectedVideoTrack: track });
} else if (track instanceof AudioTrack) {
BaseMediaSourceAdapter._logger.debug('Audio track changed', track);
this._trigger(CustomEventType.AUDIO_TRACK_CHANGED, { selectedAudioTrack: track });
} else if (track instanceof PKTextTrack) {
BaseMediaSourceAdapter._logger.debug('Text track changed', track);
this._trigger(CustomEventType.TEXT_TRACK_CHANGED, { selectedTextTrack: track });
} else if (track instanceof ImageTrack) {
BaseMediaSourceAdapter._logger.debug('Image track changed', track);
this._trigger(CustomEventType.IMAGE_TRACK_CHANGED, { selectedImageTrack: track });
}
}
/**
* Dispatch an adapter event forward.
* @param {string} name - The name of the event.
* @param {?Object} payload - The event payload.
* @returns {void}
*/
protected _trigger(name: string, payload?: any): void {
this.dispatchEvent(new FakeEvent(name, payload));
}
/** Must implemented methods by the derived media source adapter **/
// eslint-disable-next-line
public static canPlayType(mimeType: string, preferNative: boolean): boolean {
return BaseMediaSourceAdapter._throwNotImplementedError('static canPlayType');
}
// eslint-disable-next-line
public applyTextTrackStyles(sheet: CSSStyleSheet, styles: TextStyle, containerId: string, engineClassName?: string): void {
return BaseMediaSourceAdapter._throwNotImplementedError('applyTextTrackStyles');
}
public load(): Promise<{ tracks: Track[] }> {
return BaseMediaSourceAdapter._throwNotImplementedError('load');
}
// eslint-disable-next-line
public selectVideoTrack(videoTrack: VideoTrack): void {
return BaseMediaSourceAdapter._throwNotImplementedError('selectVideoTrack');
}
// eslint-disable-next-line
public selectAudioTrack(audioTrack: AudioTrack): void {
BaseMediaSourceAdapter._throwNotImplementedError('selectAudioTrack');
}
// eslint-disable-next-line
public selectTextTrack(textTrack: PKTextTrack): void {
BaseMediaSourceAdapter._throwNotImplementedError('selectTextTrack');
}
// eslint-disable-next-line
public selectImageTrack(imageTrack: ImageTrack): void {}
public hideTextTrack(): void {
BaseMediaSourceAdapter._throwNotImplementedError('hideTextTrack');
}
public enableAdaptiveBitrate(): void {
BaseMediaSourceAdapter._throwNotImplementedError('enableAdaptiveBitrate');
}
public isAdaptiveBitrateEnabled(): boolean {
return BaseMediaSourceAdapter._throwNotImplementedError('isAdaptiveBitrateEnabled');
}
// eslint-disable-next-line
public applyABRRestriction(restrictions: PKABRRestrictionObject): void {
return BaseMediaSourceAdapter._throwNotImplementedError('applyABRRestriction');
}
protected _getLiveEdge(): number {
return BaseMediaSourceAdapter._throwNotImplementedError('_getLiveEdge');
}
public seekToLiveEdge(): void {
BaseMediaSourceAdapter._throwNotImplementedError('seekToLiveEdge');
}
public isLive(): boolean {
return BaseMediaSourceAdapter._throwNotImplementedError('isLive');
}
public isOnLiveEdge(): boolean {
if (this.getSegmentDuration() === 0) {
//If no segment duration, we cannot estimate live edge
return true;
}
return this.liveDuration - this._videoElement.currentTime <= this.getSegmentDuration() * CURRENT_OR_NEXT_SEGMENT_COUNT;
}
// eslint-disable-next-line
public setMaxBitrate(bitrate: number): void {
return;
}
public attachMediaSource(): void {}
public detachMediaSource(): void {}
/**
* Handling live time update (as is not triggered when video is paused, but the current time changed)
* @function _handleLiveTimeUpdate
* @returns {void}
* @private
*/
private _handleLiveTimeUpdate(): void {
this._videoElement.addEventListener(Html5EventType.DURATION_CHANGE, this._onDurationChanged);
}
/**
* Disables all the existing text tracks.
* @public
* @returns {void}
*/
public disableNativeTextTracks(): void {
Array.from(this._videoElement.textTracks).forEach((track) => {
if (PKTextTrack.isNativeTextTrack(track) && !PKTextTrack.isExternalTrack(track)) {
track.mode = PKTextTrack.MODE.DISABLED;
}
});
}
/**
* Checks if the adapter can recover from an error triggered by the video element error
* @param {Event} event - the html5 video element error
* @returns {boolean} - if it can recover or not
* @public
*/
// eslint-disable-next-line
public handleMediaError(event?: MediaError): boolean {
return false;
}
public getStartTimeOfDvrWindow(): number {
return BaseMediaSourceAdapter._throwNotImplementedError('getStartTimeOfDvrWindow');
}
// eslint-disable-next-line
public getThumbnail(time: number): ThumbnailInfo | null {
return null;
}
public getSegmentDuration(): number {
return BaseMediaSourceAdapter._throwNotImplementedError('getSegmentDuration');
}
public get liveDuration(): number {
return BaseMediaSourceAdapter._throwNotImplementedError('liveDuration');
}
/**
* throw a run time error
* @param {string} name of the unimplemented function
* @returns {any} void/string/boolean
*/
private static _throwNotImplementedError(name: string): any {
throw new Error(Error.Severity.CRITICAL, Error.Category.PLAYER, Error.Code.RUNTIME_ERROR_METHOD_NOT_IMPLEMENTED, name);
}
/**
* Getter for the src that the adapter plays on the video element.
* In case the adapter preformed a load it will return the manifest url.
* @public
* @returns {string} - The src url.
*/
public get src(): string {
if (this._loadPromise && this._sourceObj) {
return this._sourceObj.url;
}
return '';
}
/**
* Setter for the src that the adapter plays on the video element.
* @param {string} source - The src url.
* @public
* @returns {void}
*/
public set src(source: string) {
if (!this._loadPromise && this._sourceObj) {
this._sourceObj.url = source;
}
}
/**
* @public
* @return {PKMediaSourceCapabilities} - The adapter capabilities.
*/
public get capabilities(): PKMediaSourceCapabilities {
return this._capabilities;
}
public get targetBuffer(): number {
return NaN;
}
public getDrmInfo(): PKDrmDataObject | null {
return null;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
public setCachedUrls(cachedUrls: string[]): void {}
}