Skip to content

Commit

Permalink
fix(player): improve stream type detection
Browse files Browse the repository at this point in the history
  • Loading branch information
mihar-22 committed Oct 17, 2023
1 parent b1f2bc5 commit 7659ac1
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 26 deletions.
33 changes: 30 additions & 3 deletions packages/vidstack/src/components/provider/source-select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
type ReadSignal,
type WriteSignal,
} from 'maverick.js';
import { isArray, isString } from 'maverick.js/std';
import { isArray, isString, noop } from 'maverick.js/std';

import type { MediaContext, MediaPlayerProps, MediaSrc } from '../../core';
import {
Expand All @@ -16,7 +16,10 @@ import {
VideoProviderLoader,
type MediaProviderLoader,
} from '../../providers';
import { resolveStreamTypeFromHLSManifest } from '../../utils/hls';
import { isHLSSrc } from '../../utils/mime';
import { getRequestCredentials, preconnect } from '../../utils/network';
import { isHLSSupported } from '../../utils/support';

let warned = __DEV__ ? new Set<any>() : undefined;

Expand Down Expand Up @@ -184,15 +187,39 @@ export class SourceSelection {

private _onLoadSource() {
const provider = this._media.$provider(),
source = this._media.$state.source();
source = this._media.$state.source(),
crossorigin = peek(this._media.$state.crossorigin);

if (isSameSrc(provider?.currentSrc, source)) {
return;
}

if (this._media.$state.canLoad()) {
const abort = new AbortController();

if (isHLSSrc(source)) {
// Determined using `HLSProvider` if `hls.js` supported.
if (!isHLSSupported()) {
resolveStreamTypeFromHLSManifest(source.src as string, {
credentials: getRequestCredentials(crossorigin),
signal: abort.signal,
})
.then((streamType) => {
this._media.delegate._dispatch('stream-type-change', {
detail: streamType,
});
})
.catch(noop);
}
} else {
this._media.delegate._dispatch('stream-type-change', {
detail: 'on-demand',
});
}

peek(() => provider?.loadSource(source, peek(this._media.$state.preload)));
return;

return () => abort.abort();
}

try {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { effect, onDispose, peek, signal, type ReadSignal } from 'maverick.js';
import { noop } from 'maverick.js/std';
import type { VTTCue } from 'media-captions';

import { useMediaContext, type MediaContext } from '../../../core/api/media-context';
import { getRequestCredentials } from '../../../utils/network';

Expand Down Expand Up @@ -73,6 +74,7 @@ export class ThumbnailsLoader {
}

return () => {
controller.abort();
this.$cues.set([]);
};
}
Expand Down
10 changes: 10 additions & 0 deletions packages/vidstack/src/core/state/media-state-manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,20 @@ export class MediaStateManager extends MediaPlayerController {
['provider-change'](event: ME.MediaProviderChangeEvent) {
const prevProvider = this._media.$provider(),
newProvider = event.detail;

if (prevProvider?.type === newProvider?.type) return;

prevProvider?.destroy?.();
this._media.$provider.set(event.detail);

if (prevProvider && event.detail === null) this._resetMediaState(event);

this['stream-type-change'](
this.createEvent('stream-type-change', {
detail: 'unknown',
trigger: event,
}),
);
}

['provider-loader-change'](event: ME.MediaProviderLoaderChangeEvent) {
Expand Down
4 changes: 2 additions & 2 deletions packages/vidstack/src/providers/hls/hls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,12 +144,12 @@ export class HLSController {
private _onLevelLoaded(eventType: string, data: HLS.LevelLoadedData): void {
if (this._ctx.$state.canPlay()) return;

const { type, live, totalduration: duration } = data.details;
const { type, live, totalduration: duration, targetduration } = data.details;
const event = new DOMEvent(eventType, { detail: data });

this._ctx.delegate._dispatch('stream-type-change', {
detail: live
? type === 'EVENT' && Number.isFinite(duration)
? type === 'EVENT' && Number.isFinite(duration) && targetduration >= 10
? 'live:dvr'
: 'live'
: 'on-demand',
Expand Down
12 changes: 3 additions & 9 deletions packages/vidstack/src/providers/hls/loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { isString } from 'maverick.js/std';

import type { MediaSrc } from '../../core';
import { HLS_VIDEO_EXTENSIONS, HLS_VIDEO_TYPES } from '../../utils/mime';
import { isHLSSrc } from '../../utils/mime';
import { preconnect } from '../../utils/network';
import { isHLSSupported } from '../../utils/support';
import type { MediaProviderLoader } from '../types';
Expand All @@ -18,12 +16,8 @@ export class HLSProviderLoader
preconnect('https://cdn.jsdelivr.net', 'preconnect');
}

override canPlay({ src, type }: MediaSrc) {
return (
HLSProviderLoader.supported &&
isString(src) &&
(HLS_VIDEO_EXTENSIONS.test(src) || HLS_VIDEO_TYPES.has(type))
);
override canPlay(src: MediaSrc) {
return HLSProviderLoader.supported && isHLSSrc(src);
}

override async load(context) {
Expand Down
10 changes: 0 additions & 10 deletions packages/vidstack/src/providers/html/html–media-events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ export class HTMLMediaEvents {
}

private _onLoadedMetadata(event: Event) {
this._onStreamTypeChange();
this._attachCanPlayListeners();

// Sync volume state before metadata.
Expand All @@ -192,14 +191,6 @@ export class HTMLMediaEvents {
};
}

private _onStreamTypeChange() {
if (this._ctx.$state.live()) return;
const isLive = !Number.isFinite(this._media.duration);
this._delegate._dispatch('stream-type-change', {
detail: isLive ? 'live' : 'on-demand',
});
}

private _onPlay(event: Event) {
if (!this._ctx.$state.canPlay) return;
this._delegate._dispatch('play', { trigger: event });
Expand All @@ -214,7 +205,6 @@ export class HTMLMediaEvents {
}

private _onCanPlay(event: Event) {
this._onStreamTypeChange();
this._delegate._ready(this._getCanPlayDetail(), event);
}

Expand Down
45 changes: 45 additions & 0 deletions packages/vidstack/src/utils/hls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { MediaStreamType } from '../core';

export function resolveStreamTypeFromHLSManifest(
manifestSrc: string,
requestInit?: RequestInit,
): Promise<MediaStreamType> {
return fetch(manifestSrc, requestInit)
.then((res) => res.text())
.then((manifest) => {
const renditionURI = resolveHLSRenditionURI(manifest);
if (renditionURI) return resolveStreamTypeFromHLSManifest(renditionURI, requestInit);

const streamType = /EXT-X-PLAYLIST-TYPE:\s*VOD/.test(manifest) ? 'on-demand' : 'live';

if (
streamType === 'live' &&
resolveTargetDuration(manifest) >= 10 &&
(/#EXT-X-DVR-ENABLED:\s*true/.test(manifest) || manifest.includes('#EXT-X-DISCONTINUITY'))
) {
return 'live:dvr';
}

return streamType;
});
}

function resolveHLSRenditionURI(manifest: string) {
const matches = manifest.match(/#EXT-X-STREAM-INF:[^\n]+(\n[^\n]+)*/g);
return matches ? matches[0].split('\n')[1].trim() : null;
}

function resolveTargetDuration(manifest: string): number {
const lines = manifest.split('\n');

for (const line of lines) {
if (line.startsWith('#EXT-X-TARGETDURATION')) {
const duration = parseFloat(line.split(':')[1]);
if (!isNaN(duration)) {
return duration;
}
}
}

return -1;
}
4 changes: 3 additions & 1 deletion packages/vidstack/src/utils/mime.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { isString } from 'maverick.js/std';

import type { MediaSrc } from '../core';

// https://github.com/cookpete/react-player/blob/master/src/patterns.js#L16
Expand Down Expand Up @@ -46,7 +48,7 @@ export const HLS_VIDEO_TYPES = new Set<string>([
]);

export function isHLSSrc({ src, type }: MediaSrc): boolean {
return (typeof src === 'string' && HLS_VIDEO_EXTENSIONS.test(src)) || HLS_VIDEO_TYPES.has(type);
return (isString(src) && HLS_VIDEO_EXTENSIONS.test(src)) || HLS_VIDEO_TYPES.has(type);
}

export function isMediaStream(src: unknown): src is MediaStream {
Expand Down
6 changes: 5 additions & 1 deletion packages/vidstack/src/utils/support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export function getSourceBuffer(): typeof SourceBuffer | undefined {
return __SERVER__ ? undefined : window?.SourceBuffer ?? window?.WebKitSourceBuffer;
}

let _isHLSSupported: boolean | null = null;

/**
* Whether `hls.js` is supported in this environment.
*
Expand All @@ -115,6 +117,8 @@ export function getSourceBuffer(): typeof SourceBuffer | undefined {
export function isHLSSupported(): boolean {
if (__SERVER__) return false;

if (_isHLSSupported !== null) return _isHLSSupported;

const MediaSource = getMediaSource();

if (isUndefined(MediaSource)) return false;
Expand All @@ -134,5 +138,5 @@ export function isHLSSupported(): boolean {
isFunction(SourceBuffer.prototype.appendBuffer) &&
isFunction(SourceBuffer.prototype.remove));

return !!isTypeSupported && !!isSourceBufferValid;
return (_isHLSSupported = !!isTypeSupported && !!isSourceBufferValid);
}

0 comments on commit 7659ac1

Please sign in to comment.