refactor(core)!: move media capability predicates to core layer (#1705)

Co-authored-by: Rahim <rahim.alwer@gmail.com>
This commit is contained in:
Wesley Luyten
2026-06-18 15:00:26 -07:00
committed by GitHub
co-authored by Rahim
parent accf4bfa34
commit f0f9eed544
25 changed files with 46 additions and 36 deletions
+5
View File
@@ -34,6 +34,11 @@
"development": "./dist/dev/dom/media/*/index.js",
"default": "./dist/default/dom/media/*/index.js"
},
"./media/predicate": {
"types": "./dist/dev/media/predicate.d.ts",
"development": "./dist/dev/media/predicate.js",
"default": "./dist/default/media/predicate.js"
},
"./i18n": {
"types": "./dist/dev/i18n.d.ts",
"development": "./dist/dev/i18n.js",
+2
View File
@@ -1,3 +1,5 @@
export { EMPTY_REMOTE, EMPTY_TEXT_TRACKS, EMPTY_TIME_RANGES } from './media/constants';
export * from './media/predicate';
export * from './media/state';
export * from './media/types';
export * from './ui/airplay-button/airplay-button-core';
@@ -1,4 +1,4 @@
import type { RemotePlaybackLike, TextTrackListLike, TimeRangeLike } from '../../core/media/types';
import type { RemotePlaybackLike, TextTrackListLike, TimeRangeLike } from './types';
/** A frozen, empty `TimeRanges`-like value for hosts with no ranges. */
export const EMPTY_TIME_RANGES: TimeRangeLike = Object.freeze({
@@ -1,5 +1,6 @@
import { isFunction, isObject, isUndefined } from '@videojs/utils/predicate';
import { EMPTY_REMOTE, EMPTY_TEXT_TRACKS, EMPTY_TIME_RANGES } from './constants';
import type {
MediaBufferCapability,
MediaErrorCapability,
@@ -14,8 +15,7 @@ import type {
MediaVideoDimensionsCapability,
MediaVideoRenditionCapability,
MediaVolumeCapability,
} from '../../core/media/types';
import { EMPTY_REMOTE, EMPTY_TEXT_TRACKS, EMPTY_TIME_RANGES } from './constants';
} from './types';
export function hasMetadata(media: MediaSourceCapability): boolean {
return media.readyState >= 1;
@@ -109,11 +109,17 @@ export function isMediaLiveCapable(value: unknown): value is MediaLiveCapability
return !isUndefined(media.liveEdgeStart) && !isUndefined(media.targetLiveWindow);
}
export function isQuerySelectorAllCapable<T extends string>(
/** Framework-agnostic `NodeList`-like shape returned by `querySelectorAll`. */
export interface NodeListLike<Element> {
readonly length: number;
readonly [index: number]: Element;
item(index: number): Element | null;
[Symbol.iterator](): Iterator<Element>;
}
export function isQuerySelectorAllCapable<Element = unknown>(
value: unknown
): value is {
querySelectorAll: (selectors: T) => NodeListOf<HTMLElementTagNameMap[Extract<T, keyof HTMLElementTagNameMap>]>;
} {
): value is { querySelectorAll: (selectors: string) => NodeListLike<Element> } {
return (
isObject(value) && 'querySelectorAll' in value && isFunction((value as Record<string, unknown>).querySelectorAll)
);
-1
View File
@@ -8,7 +8,6 @@ export * from './hotkey/aria';
export * from './hotkey/coordinator';
export * from './hotkey/hotkey';
export * from './hotkey/hotkey-events';
export * from './media/predicate';
export * from './media/types';
export * from './store/features';
export * from './store/selectors';
+1 -1
View File
@@ -1,4 +1,5 @@
import type { EventListenerFor, EventType, QueriedElement } from '@videojs/utils/dom';
import { EMPTY_REMOTE, EMPTY_TEXT_TRACKS, EMPTY_TIME_RANGES } from '../../core/media/constants';
import {
type EventLike,
type MediaFull,
@@ -8,7 +9,6 @@ import {
type TextTrackKind,
type TextTrackLike,
} from '../../core/media/types';
import { EMPTY_REMOTE, EMPTY_TEXT_TRACKS, EMPTY_TIME_RANGES } from './constants';
import { getComponents, getOwner, getProp, setProp } from './utils';
export { addComponent, getComponents, getOwner, getProp, setProp } from './utils';
+1 -1
View File
@@ -1,7 +1,7 @@
import { isNull, isString, isUndefined } from '@videojs/utils/predicate';
import VimeoPlayer, { type LoadVideoOptions, type VimeoEmbedParameters, type VimeoUrl } from '@vimeo/player';
import { EMPTY_TEXT_TRACKS, EMPTY_TIME_RANGES } from '../../../core/media/constants';
import type { ErrorLike, MediaPreloadType, TextTrackListLike, Video } from '../../../core/media/types';
import { EMPTY_TEXT_TRACKS, EMPTY_TIME_RANGES } from '../constants';
import { MediaPlayedRangesMixin } from '../media-played-ranges';
export type { default as VimeoPlayerApi } from '@vimeo/player';
@@ -1,8 +1,7 @@
import { listen, serializeTimeRanges } from '@videojs/utils/dom';
import { isMediaBufferCapable } from '../../../core/media/predicate';
import type { MediaBufferState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { isMediaBufferCapable } from '../../media/predicate';
export const bufferFeature = definePlayerFeature({
name: 'buffer',
@@ -1,10 +1,9 @@
import { listen } from '@videojs/utils/dom';
import { isNull } from '@videojs/utils/predicate';
import { isMediaPauseCapable, isMediaRemotePlaybackCapable } from '../../../core/media/predicate';
import type { MediaControlsState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { findGestureCoordinator } from '../../gesture/coordinator';
import { isMediaPauseCapable, isMediaRemotePlaybackCapable } from '../../media/predicate';
import { isRemotePlaybackConnected, isRemotePlaybackConnecting } from '../../presentation/remote-playback';
const IDLE_DELAY = 2000;
@@ -1,8 +1,7 @@
import { listen } from '@videojs/utils/dom';
import { isMediaErrorCapable } from '../../../core/media/predicate';
import type { MediaErrorState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { isMediaErrorCapable } from '../../media/predicate';
export const errorFeature = definePlayerFeature({
name: 'error',
+1 -2
View File
@@ -1,8 +1,7 @@
import { listen } from '@videojs/utils/dom';
import { isMediaLiveCapable } from '../../../core/media/predicate';
import type { MediaLiveState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { isMediaLiveCapable } from '../../media/predicate';
/**
* Player feature exposing `liveEdgeStart` and `targetLiveWindow` in store
@@ -1,8 +1,7 @@
import { listen } from '@videojs/utils/dom';
import { isMediaPlaybackRateCapable } from '../../../core/media/predicate';
import type { MediaPlaybackRateState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { isMediaPlaybackRateCapable } from '../../media/predicate';
const DEFAULT_RATES: readonly number[] = [0.2, 0.5, 0.7, 1, 1.2, 1.5, 1.7, 2];
@@ -1,8 +1,7 @@
import { listen } from '@videojs/utils/dom';
import { isMediaPauseCapable, isMediaSeekCapable, isMediaSourceCapable } from '../../../core/media/predicate';
import type { MediaPlaybackState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { isMediaPauseCapable, isMediaSeekCapable, isMediaSourceCapable } from '../../media/predicate';
export const playbackFeature = definePlayerFeature({
name: 'playback',
@@ -1,9 +1,8 @@
import { listen } from '@videojs/utils/dom';
import { isMediaVideoDimensionsCapable, isMediaVideoRenditionCapable } from '../../../core/media/predicate';
import type { MediaQualityState, MediaVideoRendition } from '../../../core/media/state';
import type { VideoRenditionLike, VideoRenditionListLike } from '../../../core/media/types';
import { definePlayerFeature } from '../../feature';
import { isMediaVideoDimensionsCapable, isMediaVideoRenditionCapable } from '../../media/predicate';
const QUALITY_AUTO_VALUE = 'auto';
@@ -1,8 +1,7 @@
import { isWebKitAirPlayCapable, listen, type WebkitAvailabilityEvent } from '@videojs/utils/dom';
import { isMediaRemotePlaybackCapable } from '../../../core/media/predicate';
import type { MediaRemotePlaybackState, RemotePlaybackConnectionState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { isMediaRemotePlaybackCapable } from '../../media/predicate';
import { exitFullscreen, isFullscreen } from '../../presentation/fullscreen';
import { isRemotePlaybackConnected, requestRemotePlayback } from '../../presentation/remote-playback';
@@ -1,8 +1,7 @@
import { listen } from '@videojs/utils/dom';
import { isMediaSourceCapable } from '../../../core/media/predicate';
import type { MediaSourceState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { isMediaSourceCapable } from '../../media/predicate';
export const sourceFeature = definePlayerFeature({
name: 'source',
@@ -1,9 +1,8 @@
import { listen } from '@videojs/utils/dom';
import { isMediaBufferCapable, isMediaSeekCapable, isMediaStreamTypeCapable } from '../../../core/media/predicate';
import type { MediaStreamTypeState } from '../../../core/media/state';
import { type MediaStreamType, MediaStreamTypes } from '../../../core/media/types';
import { definePlayerFeature } from '../../feature';
import { isMediaBufferCapable, isMediaSeekCapable, isMediaStreamTypeCapable } from '../../media/predicate';
export const streamTypeFeature = definePlayerFeature({
name: 'streamType',
@@ -1,9 +1,8 @@
import { findTrackElement, getTextTrackList, isCaptionOrSubtitleTrack, listen } from '@videojs/utils/dom';
import { isMediaTextTrackCapable, isQuerySelectorAllCapable } from '../../../core/media/predicate';
import type { MediaTextCue, MediaTextTrack, MediaTextTrackState } from '../../../core/media/state';
import type { TextTrackLike } from '../../../core/media/types';
import { definePlayerFeature } from '../../feature';
import { isMediaTextTrackCapable, isQuerySelectorAllCapable } from '../../media/predicate';
function getTrackId(track: TextTrackLike, index: number): string {
return track.id || `track:${index}:${track.kind}:${track.language}:${track.label}`;
@@ -111,7 +110,7 @@ export const textTrackFeature = definePlayerFeature({
// Listen for <track> load events on tracks that don't have cues yet.
// `addtrack` fires before cues are parsed — we need the `load` event
// on the <track> element to know when cues are ready.
const tracks = (isQuerySelectorAllCapable<'track'>(media) && media.querySelectorAll('track')) || [];
const tracks = (isQuerySelectorAllCapable<HTMLTrackElement>(media) && media.querySelectorAll('track')) || [];
const shadowTracks = (media instanceof HTMLElement && media.shadowRoot?.querySelectorAll('track')) || [];
for (const trackEl of [...tracks, ...shadowTracks]) {
+6 -1
View File
@@ -1,8 +1,13 @@
import { listen, onEvent } from '@videojs/utils/dom';
import { noop } from '@videojs/utils/function';
import {
hasMetadata,
isMediaBufferCapable,
isMediaSeekCapable,
isMediaSourceCapable,
} from '../../../core/media/predicate';
import type { MediaTimeState } from '../../../core/media/state';
import { definePlayerFeature } from '../../feature';
import { hasMetadata, isMediaBufferCapable, isMediaSeekCapable, isMediaSourceCapable } from '../../media/predicate';
import { signalKeys } from '../signal-keys';
export const timeFeature = definePlayerFeature({
@@ -1,8 +1,8 @@
import { listen } from '@videojs/utils/dom';
import { isMediaVolumeCapable } from '../../../core/media/predicate';
import type { MediaVolumeState } from '../../../core/media/state';
import type { MediaFeatureAvailability } from '../../../core/media/types';
import { definePlayerFeature } from '../../feature';
import { isMediaVolumeCapable } from '../../media/predicate';
/** Volume to restore when unmuting at zero. */
const UNMUTE_VOLUME = 0.25;
+1
View File
@@ -14,6 +14,7 @@ const createConfig = (mode: PackageBuildMode): UserConfig => ({
...packageBuildConfig(mode, 'neutral'),
entry: {
index: './src/core/index.ts',
'media/predicate': './src/core/media/predicate.ts',
i18n: './src/core/i18n/index.ts',
...localeEntries,
dom: './src/dom/index.ts',
+2 -1
View File
@@ -1,5 +1,7 @@
// Core
export * from '@videojs/core/dom';
// Media predicates
export * from '@videojs/core/media/predicate';
export type {
Destroyable,
PropertyDeclaration,
@@ -10,7 +12,6 @@ export type {
} from '@videojs/element';
// Element — reactive primitives for users extending MediaElement
export { DestroyMixin, ReactiveElement } from '@videojs/element';
// Store
export type { Comparator, Selector } from '@videojs/store';
export { createSelector, shallowEqual } from '@videojs/store';
+2
View File
@@ -3,6 +3,8 @@
export type { IndicatorStatus, InputAction, InputIndicatorLabels } from '@videojs/core';
// Core
export * from '@videojs/core/dom';
// Media predicates
export * from '@videojs/core/media/predicate';
// Store
export type { Comparator, Selector } from '@videojs/store';
export { createSelector, shallowEqual } from '@videojs/store';