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

chore: add env-capabilities tests #32

Merged
merged 1 commit into from
Oct 9, 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
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