Skip to content

Commit

Permalink
chore: add env-capabilities tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Dzianis Dashkevich committed Oct 9, 2024
1 parent 5850096 commit f652636
Show file tree
Hide file tree
Showing 5 changed files with 408 additions and 97 deletions.
4 changes: 2 additions & 2 deletions packages/playback/src/lib/player.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
PlayerErrorEvent,
VolumeChangedEvent,
} from './events/player-events';
import type { CapabilitiesProbeResult, IEnvCapabilitiesProvider } from './types/env-capabilities.declarations';
import type { ICapabilitiesProbeResult, IEnvCapabilitiesProvider } from './types/env-capabilities.declarations';
import type { ILoadLocalSource, ILoadRemoteSource, IPlayerSource } from './types/source.declarations';
import type { IPipeline, IPipelineFactoryConfiguration, IPipelineLoader } from './types/pipeline.declarations';
import { PlaybackState } from './consts/playback-state';
Expand Down Expand Up @@ -274,7 +274,7 @@ export class Player {
/**
* Probe env capabilities
*/
public probeEnvCapabilities(): Promise<CapabilitiesProbeResult> {
public probeEnvCapabilities(): Promise<ICapabilitiesProbeResult> {
return this.envCapabilitiesProvider_.probe();
}

Expand Down
21 changes: 16 additions & 5 deletions packages/playback/src/lib/service-locator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import type { PlayerConfiguration } from './types/configuration.declarations';
import type { IStore } from './types/store.declarations';
import type { IEventEmitter } from './types/event-emitter.declarations';
import type { EventTypeToEventMap } from './types/mappers/event-type-to-event-map.declarations';
import type { IEnvCapabilitiesProvider } from './types/env-capabilities.declarations';
import type { IEnvCapabilitiesContext, IEnvCapabilitiesProvider } from './types/env-capabilities.declarations';
import type { INetworkManager } from './types/network.declarations';
import type { InterceptorTypeToInterceptorMap } from './types/mappers/interceptor-type-to-interceptor-map.declarations';
import type { NetworkManagerDependencies } from './network/network-manager';
Expand All @@ -29,7 +29,7 @@ export class ServiceLocator {
public readonly networkManager: INetworkManager;

public constructor() {
const { console, fetch } = window;
const { console, fetch, location, navigator, isSecureContext, MediaSource, document } = window;

this.configurationManager = this.createConfigurationManager_();

Expand All @@ -39,7 +39,15 @@ export class ServiceLocator {

this.interceptorsStorage = this.createInterceptorsStorage_();
this.eventEmitter = this.createEventEmitter_();
this.envCapabilitiesProvider = this.createEnvCapabilitiesProvider_();
this.envCapabilitiesProvider = this.createEnvCapabilitiesProvider_(
{
location,
navigator,
isSecureContext,
MediaSource,
},
document.createElement('video')
);
this.networkManager = this.createNetworkManager_({
logger: this.logger.createSubLogger('NetworkManager'),
eventEmitter: this.eventEmitter,
Expand Down Expand Up @@ -68,8 +76,11 @@ export class ServiceLocator {
return new EventEmitter<EventTypeToEventMap>();
}

protected createEnvCapabilitiesProvider_(): IEnvCapabilitiesProvider {
return new EnvCapabilitiesProvider();
protected createEnvCapabilitiesProvider_(
context: IEnvCapabilitiesContext,
videoElement: HTMLVideoElement
): IEnvCapabilitiesProvider {
return new EnvCapabilitiesProvider(context, videoElement);
}

protected createNetworkManager_(dependencies: NetworkManagerDependencies): INetworkManager {
Expand Down
112 changes: 60 additions & 52 deletions packages/playback/src/lib/types/env-capabilities.declarations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,38 @@ import type { StreamingProtocol } from '../consts/streaming-protocol';
import type { Container } from '../consts/container';
import type { AudioCodecs, VideoCodecs } from '../consts/codecs';

export interface KeySystemCapabilities {
export interface IEnvCapabilitiesContext {
readonly location: Location;
readonly navigator: Navigator;
readonly isSecureContext: boolean;
readonly MediaSource?: { isTypeSupported: (type: string) => boolean };
readonly transmuxer?: { isTypeSupported: (type: string) => boolean };
}

export interface IKeySystemCapabilities {
persistent: boolean;
basic: boolean;
}

export interface EmeCapabilities {
[KeySystem.Widevine]: KeySystemCapabilities;
[KeySystem.Playready]: KeySystemCapabilities;
[KeySystem.Fairplay]: KeySystemCapabilities;
[KeySystem.FairplayLegacy]: KeySystemCapabilities;
export interface IEmeCapabilities {
[KeySystem.Widevine]: IKeySystemCapabilities;
[KeySystem.Playready]: IKeySystemCapabilities;
[KeySystem.Fairplay]: IKeySystemCapabilities;
[KeySystem.FairplayLegacy]: IKeySystemCapabilities;
}

export interface StreamingProtocolCapabilities {
export interface IStreamingProtocolCapabilities {
mse: boolean;
native: boolean;
}

export interface StreamingCapabilities {
[StreamingProtocol.Hls]: StreamingProtocolCapabilities;
[StreamingProtocol.Dash]: StreamingProtocolCapabilities;
[StreamingProtocol.Hss]: StreamingProtocolCapabilities;
[StreamingProtocol.Hls]: IStreamingProtocolCapabilities;
[StreamingProtocol.Dash]: IStreamingProtocolCapabilities;
[StreamingProtocol.Hss]: IStreamingProtocolCapabilities;
}

export interface CodecCapabilities {
export interface ICodecCapabilities {
mse: boolean;
native: boolean;
transmuxer: boolean;
Expand All @@ -35,98 +43,98 @@ export interface CodecCapabilities {
// lets consider baseline profile support here
}

export interface Mp4VideoCodecsCapabilities {
export interface IMp4VideoCodecsCapabilities {
// 'video/mp4; codecs="avc1.42E01E"'
[VideoCodecs.H264]: CodecCapabilities;
[VideoCodecs.H264]: ICodecCapabilities;
// 'video/mp4; codecs="hvc1.1.6.L93.90"'
[VideoCodecs.H265]: CodecCapabilities;
[VideoCodecs.H265]: ICodecCapabilities;
// 'video/mp4; codecs="vp09.00.10.08"'
[VideoCodecs.Vp9]: CodecCapabilities;
[VideoCodecs.Vp9]: ICodecCapabilities;
}

export interface Mp4AudioCodecsCapabilities {
export interface IMp4AudioCodecsCapabilities {
// 'audio/mp4; codecs="mp4a.40.2"'
[AudioCodecs.Aac]: CodecCapabilities;
[AudioCodecs.Aac]: ICodecCapabilities;
// 'audio/mp4; codecs="ac-3"'
[AudioCodecs.Ac3]: CodecCapabilities;
[AudioCodecs.Ac3]: ICodecCapabilities;
// 'audio/mp4; codecs="ec-3"'
[AudioCodecs.Ec3]: CodecCapabilities;
[AudioCodecs.Ec3]: ICodecCapabilities;
// 'audio/mp4; codecs="opus"'
[AudioCodecs.Opus]: CodecCapabilities;
[AudioCodecs.Opus]: ICodecCapabilities;
// 'audio/mp4; codecs="flac"'
[AudioCodecs.Flac]: CodecCapabilities;
[AudioCodecs.Flac]: ICodecCapabilities;
}

export interface OggVideoCodecsCapabilities {
export interface IOggVideoCodecsCapabilities {
// 'video/ogg; codecs="theora"'
[VideoCodecs.Theora]: CodecCapabilities;
[VideoCodecs.Theora]: ICodecCapabilities;
// 'video/ogg; codecs="vp8"'
[VideoCodecs.Vp8]: CodecCapabilities;
[VideoCodecs.Vp8]: ICodecCapabilities;
// 'video/ogg; codecs="vp9"'
[VideoCodecs.Vp9]: CodecCapabilities;
[VideoCodecs.Vp9]: ICodecCapabilities;
}

export interface OggAudioCodecsCapabilities {
export interface IOggAudioCodecsCapabilities {
// 'audio/ogg; codecs="flac"'
[AudioCodecs.Flac]: CodecCapabilities;
[AudioCodecs.Flac]: ICodecCapabilities;
// 'audio/ogg; codecs="opus"'
[AudioCodecs.Opus]: CodecCapabilities;
[AudioCodecs.Opus]: ICodecCapabilities;
// 'audio/ogg; codecs="vorbis"'
[AudioCodecs.Vorbis]: CodecCapabilities;
[AudioCodecs.Vorbis]: ICodecCapabilities;
}

export interface WebMVideoCodecsCapabilities {
export interface IWebMVideoCodecsCapabilities {
// 'video/webm; codecs="vp8"'
[VideoCodecs.Vp8]: CodecCapabilities;
[VideoCodecs.Vp8]: ICodecCapabilities;
// 'video/webm; codecs="vp9"'
[VideoCodecs.Vp9]: CodecCapabilities;
[VideoCodecs.Vp9]: ICodecCapabilities;
}

export interface WebMAudioCodecsCapabilities {
export interface IWebMAudioCodecsCapabilities {
// 'audio/webm; codecs="vorbis"'
[AudioCodecs.Vorbis]: CodecCapabilities;
[AudioCodecs.Vorbis]: ICodecCapabilities;
// 'audio/webm; codecs="opus"'
[AudioCodecs.Opus]: CodecCapabilities;
[AudioCodecs.Opus]: ICodecCapabilities;
}

export interface Mpeg2tsVideoCodecsCapabilities {
export interface IMpeg2tsVideoCodecsCapabilities {
// 'video/mp2t; codecs="avc1.42E01E"'
[VideoCodecs.H264]: CodecCapabilities;
[VideoCodecs.H264]: ICodecCapabilities;
// 'video/mp2t; codecs="hvc1.1.6.L93.90"'
[VideoCodecs.H265]: CodecCapabilities;
[VideoCodecs.H265]: ICodecCapabilities;
}

// Should use video/mp2t for audio codecs as well,
// see: https://w3c.github.io/mse-byte-stream-format-mp2t/#mime-parameters
export interface Mpeg2tsAudioCodecsCapabilities {
export interface IMpeg2tsAudioCodecsCapabilities {
// 'video/mp2t; codecs="mp4a.40.2"'
[AudioCodecs.Aac]: CodecCapabilities;
[AudioCodecs.Aac]: ICodecCapabilities;
// 'video/mp2t; codecs="ac-3"'
[AudioCodecs.Ac3]: CodecCapabilities;
[AudioCodecs.Ac3]: ICodecCapabilities;
// 'video/mp2t; codecs="ec-3"'
[AudioCodecs.Ec3]: CodecCapabilities;
[AudioCodecs.Ec3]: ICodecCapabilities;
}

export interface ContainerCapabilities<V, A> {
export interface IContainerCapabilities<V, A> {
video: V;
audio: A;
}

export interface MediaCapabilities {
[Container.Mp4]: ContainerCapabilities<Mp4VideoCodecsCapabilities, Mp4AudioCodecsCapabilities>;
[Container.Ogg]: ContainerCapabilities<OggVideoCodecsCapabilities, OggAudioCodecsCapabilities>;
[Container.WebM]: ContainerCapabilities<WebMVideoCodecsCapabilities, WebMAudioCodecsCapabilities>;
[Container.Mpeg2Ts]: ContainerCapabilities<Mpeg2tsVideoCodecsCapabilities, Mpeg2tsAudioCodecsCapabilities>;
export interface IMediaCapabilities {
[Container.Mp4]: IContainerCapabilities<IMp4VideoCodecsCapabilities, IMp4AudioCodecsCapabilities>;
[Container.Ogg]: IContainerCapabilities<IOggVideoCodecsCapabilities, IOggAudioCodecsCapabilities>;
[Container.WebM]: IContainerCapabilities<IWebMVideoCodecsCapabilities, IWebMAudioCodecsCapabilities>;
[Container.Mpeg2Ts]: IContainerCapabilities<IMpeg2tsVideoCodecsCapabilities, IMpeg2tsAudioCodecsCapabilities>;
}

export interface CapabilitiesProbeResult {
export interface ICapabilitiesProbeResult {
isSecureContext: boolean;
isHttps: boolean;
eme: EmeCapabilities;
eme: IEmeCapabilities;
streaming: StreamingCapabilities;
media: MediaCapabilities;
media: IMediaCapabilities;
}

export interface IEnvCapabilitiesProvider {
probe(): Promise<CapabilitiesProbeResult>;
probe(): Promise<ICapabilitiesProbeResult>;
}
71 changes: 33 additions & 38 deletions packages/playback/src/lib/utils/env-capabilities.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type {
CapabilitiesProbeResult,
CodecCapabilities,
ICapabilitiesProbeResult,
ICodecCapabilities,
IEnvCapabilitiesContext,
IEnvCapabilitiesProvider,
} from '../types/env-capabilities.declarations';
import { KeySystem } from '../consts/key-system';
Expand All @@ -25,14 +26,16 @@ const persistentEmeConfig = {
};

export class EnvCapabilitiesProvider implements IEnvCapabilitiesProvider {
private cache_: CapabilitiesProbeResult | null = null;
private readonly context_: IEnvCapabilitiesContext;
private readonly videoElement_: HTMLVideoElement;

public async probe(): Promise<CapabilitiesProbeResult> {
if (this.cache_) {
return this.cache_;
}
public constructor(context: IEnvCapabilitiesContext, videoElement: HTMLVideoElement) {
this.context_ = context;
this.videoElement_ = videoElement;
}

const probeResult: CapabilitiesProbeResult = {
public async probe(): Promise<ICapabilitiesProbeResult> {
const probeResult: ICapabilitiesProbeResult = {
isSecureContext: false,
isHttps: false,
eme: {
Expand Down Expand Up @@ -98,46 +101,38 @@ export class EnvCapabilitiesProvider implements IEnvCapabilitiesProvider {
},
};

const videoElement = document.createElement('video');

await Promise.all([
this.probeSecureContext_(probeResult),
this.probeIsHttps_(probeResult),
this.probeEmeCapabilities_(probeResult),
this.probeMediaCapabilities_(probeResult, videoElement),
this.probeStreamingProtocolsCapabilities_(probeResult, videoElement),
this.probeMediaCapabilities_(probeResult),
this.probeStreamingProtocolsCapabilities_(probeResult),
]);

this.cache_ = probeResult as CapabilitiesProbeResult;
return this.cache_;
return probeResult;
}

private async probeStreamingProtocolsCapabilities_(
probeResult: CapabilitiesProbeResult,
videoElement: HTMLVideoElement
): Promise<void> {
protected async probeStreamingProtocolsCapabilities_(probeResult: ICapabilitiesProbeResult): Promise<void> {
// we consider that we support hls and dash in any MSE context
// we do not support ManagedMediaSource right now
// we do not support HSS in MSE context, so it should always be false
probeResult.streaming.hls.mse = 'MediaSource' in window;
probeResult.streaming.dash.mse = 'MediaSource' in window;
probeResult.streaming.hls.mse = 'MediaSource' in this.context_;
probeResult.streaming.dash.mse = 'MediaSource' in this.context_;
probeResult.streaming.hss.mse = false;

probeResult.streaming.hls.native =
Boolean(videoElement.canPlayType(HlsVndMpegMimeType)) || Boolean(videoElement.canPlayType(HlsXMpegMimeType));
Boolean(this.videoElement_.canPlayType(HlsVndMpegMimeType)) ||
Boolean(this.videoElement_.canPlayType(HlsXMpegMimeType));

probeResult.streaming.dash.native = Boolean(videoElement.canPlayType(DashMimeType));
probeResult.streaming.hss.native = Boolean(videoElement.canPlayType(HssMimeType));
probeResult.streaming.dash.native = Boolean(this.videoElement_.canPlayType(DashMimeType));
probeResult.streaming.hss.native = Boolean(this.videoElement_.canPlayType(HssMimeType));
}

private async probeMediaCapabilities_(
probeResult: CapabilitiesProbeResult,
videoElement: HTMLVideoElement
): Promise<void> {
const getCodecsCapabilities = (probeData: { mime: string; transmuxer?: boolean }): CodecCapabilities => {
const { mime, transmuxer = false } = probeData;
const mse = window.MediaSource && window.MediaSource.isTypeSupported(mime);
const native = Boolean(videoElement.canPlayType(mime));
protected async probeMediaCapabilities_(probeResult: ICapabilitiesProbeResult): Promise<void> {
const getCodecsCapabilities = (probeData: { mime: string }): ICodecCapabilities => {
const { mime } = probeData;
const mse = Boolean(this.context_.MediaSource && this.context_.MediaSource.isTypeSupported(mime));
const native = Boolean(this.videoElement_.canPlayType(mime));
const transmuxer = Boolean(this.context_.transmuxer && this.context_.transmuxer.isTypeSupported(mime));

return { mse, native, transmuxer };
};
Expand Down Expand Up @@ -229,15 +224,15 @@ export class EnvCapabilitiesProvider implements IEnvCapabilitiesProvider {
});
}

private async probeSecureContext_(probeResult: CapabilitiesProbeResult): Promise<void> {
probeResult.isSecureContext = window.isSecureContext;
protected async probeSecureContext_(probeResult: ICapabilitiesProbeResult): Promise<void> {
probeResult.isSecureContext = this.context_.isSecureContext;
}

private async probeIsHttps_(probeResult: CapabilitiesProbeResult): Promise<void> {
probeResult.isHttps = window.location.protocol === 'https:';
protected async probeIsHttps_(probeResult: ICapabilitiesProbeResult): Promise<void> {
probeResult.isHttps = this.context_.location.protocol === 'https:';
}

private async probeEmeCapabilities_(probeResult: CapabilitiesProbeResult): Promise<void> {
private async probeEmeCapabilities_(probeResult: ICapabilitiesProbeResult): Promise<void> {
const keySystemsValues = Object.values(KeySystem);

const basicPromises = keySystemsValues.map(async (keySystem) => {
Expand All @@ -259,7 +254,7 @@ export class EnvCapabilitiesProvider implements IEnvCapabilitiesProvider {

private async probeEmeConfig_(keySystem: string, config: MediaKeySystemConfiguration): Promise<boolean> {
try {
await navigator.requestMediaKeySystemAccess(keySystem, [config]);
await this.context_.navigator.requestMediaKeySystemAccess(keySystem, [config]);
return true;
} catch (e) {
return false;
Expand Down
Loading

0 comments on commit f652636

Please sign in to comment.