diff --git a/examples/nextjs-with-typescript/components/renderers.tsx b/examples/nextjs-with-typescript/components/renderers.tsx index e106474a4..71b0e9ec6 100644 --- a/examples/nextjs-with-typescript/components/renderers.tsx +++ b/examples/nextjs-with-typescript/components/renderers.tsx @@ -44,6 +44,62 @@ export const BooleanRenderer = ({ ); }; +export const OptionalBooleanRenderer = ({ + name, + value, + label, + onChange, + formatter = DefaultEnumFormatter +}: { + name: string; + value: boolean | undefined; + label?: string; + removeFalse?: boolean; + onChange: (obj: any) => void; + formatter?: (enumValue: boolean) => ReactNode; +}) => { + const labelStr = label ?? toWordsFromKeyName(name); + const values = [true, false]; + return ( +
+ +
+ { + + console.log("Selecting value:", undefined, toChangeObject(name, undefined)); + onChange(toChangeObject(name, undefined))}} + value="" + checked={value === undefined} + /> + + {values.map((enumValue, i) => { + return ( + + { + const changeValue = enumValue; + console.log("Selecting value:", changeValue, toChangeObject(name, changeValue)); + onChange(toChangeObject(name, changeValue)); + }} + value={enumValue.toString()} + checked={value === enumValue} + /> + + + ); + })} +
+
+ ); +}; + export const NumberRenderer = ({ name, value, diff --git a/examples/nextjs-with-typescript/pages/MuxPlayer.tsx b/examples/nextjs-with-typescript/pages/MuxPlayer.tsx index 16e8ac476..ec736c830 100644 --- a/examples/nextjs-with-typescript/pages/MuxPlayer.tsx +++ b/examples/nextjs-with-typescript/pages/MuxPlayer.tsx @@ -654,6 +654,11 @@ function MuxPlayerPage({ location }: Props) { min={0} step={1} /> + diff --git a/examples/nextjs-with-typescript/pages/mux-video-react.tsx b/examples/nextjs-with-typescript/pages/mux-video-react.tsx index fd20dd3b9..09ec6fe48 100644 --- a/examples/nextjs-with-typescript/pages/mux-video-react.tsx +++ b/examples/nextjs-with-typescript/pages/mux-video-react.tsx @@ -1,14 +1,21 @@ import Head from 'next/head'; import { useRef, useState } from "react"; import MuxVideo from "@mux/mux-video/react"; +import MuxPlayerElement from '@mux/mux-player'; +import { EnumRenderer, OptionalBooleanRenderer } from '../components/renderers'; +import MuxVideoElement from '@mux/mux-video'; const INITIAL_AUTOPLAY = false; const INITIAL_MUTED = false; +const INITIAL_CAP_LEVEL_TO_PLAYER_SIZE = undefined; +const INITIAL_PREFER_PLAYBACK = undefined; function MuxVideoPage() { - const mediaElRef = useRef(null); + const mediaElRef = useRef(null); const [autoplay, setAutoplay] = useState<"muted" | boolean>(INITIAL_AUTOPLAY); const [muted, setMuted] = useState(INITIAL_MUTED); + const [preferPlayback, setPreferPlayback] = useState(INITIAL_PREFER_PLAYBACK); + const [capLevelToPlayerSize, setCapLevelToPlayerSize] = useState(INITIAL_CAP_LEVEL_TO_PLAYER_SIZE); const [paused, setPaused] = useState(true); return ( @@ -32,12 +39,13 @@ function MuxVideoPage() { // }} // envKey="mux-data-env-key" controls + capLevelToPlayerSize={capLevelToPlayerSize} autoplay={autoplay} muted={muted} maxResolution="2160p" minResolution="540p" renditionOrder="desc" - preferPlayback="native" + preferPlayback={preferPlayback} onPlay={() => { setPaused(false); }} @@ -47,6 +55,10 @@ function MuxVideoPage() { />
+
+ setPreferPlayback(preferPlayback as MuxPlayerElement["preferPlayback"])} + values={['mse', 'native']} + /> + setCapLevelToPlayerSize(capLevelToPlayerSize)} + />
); diff --git a/packages/mux-player-react/src/types.ts b/packages/mux-player-react/src/types.ts index 102e7b6ce..83ca28159 100644 --- a/packages/mux-player-react/src/types.ts +++ b/packages/mux-player-react/src/types.ts @@ -118,6 +118,7 @@ export type MuxPlayerProps = { theme?: string; themeProps?: { [k: string]: any }; fullscreenElement?: string; + capLevelToPlayerSize?: boolean; onAbort?: GenericEventListener; onCanPlay?: GenericEventListener; onCanPlayThrough?: GenericEventListener; diff --git a/packages/mux-player/src/base.ts b/packages/mux-player/src/base.ts index fdafe58f7..ba9f23a93 100644 --- a/packages/mux-player/src/base.ts +++ b/packages/mux-player/src/base.ts @@ -188,6 +188,7 @@ function getProps(el: MuxPlayerElement, state?: any): MuxTemplateProps { proudlyDisplayMuxBadge: el.hasAttribute(PlayerAttributes.PROUDLY_DISPLAY_MUX_BADGE), castReceiver: el.castReceiver, disablePseudoEnded: el.hasAttribute(PlayerAttributes.DISABLE_PSEUDO_ENDED), + disableCapLevelToPlayerSize: el.disableCapLevelToPlayerSize, ...state, // NOTE: since the attribute value is used as the "source of truth" for the property getter, // moving this below the `...state` spread so it resolves to the default value when unset (CJP) @@ -1883,6 +1884,29 @@ class MuxPlayerElement extends VideoApiElement implements IMuxPlayerElement { this.setAttribute(PlayerAttributes.PROUDLY_DISPLAY_MUX_BADGE, ''); } } + + get capLevelToPlayerSize(): boolean | undefined { + if (this._hls) { + return this._hls.capLevelToPlayerSize; + } + return this._hlsConfig?.capLevelToPlayerSize; + } + + set capLevelToPlayerSize(val: boolean | undefined) { + this._hlsConfig = { ...this._hlsConfig, capLevelToPlayerSize: val }; + } + + get disableCapLevelToPlayerSize(): boolean { + return this.hasAttribute(MuxVideoAttributes.DISABLE_CAP_LEVEL_TO_PLAYER_SIZE); + } + + set disableCapLevelToPlayerSize(val: boolean | undefined) { + if (!val) { + this.removeAttribute(MuxVideoAttributes.DISABLE_CAP_LEVEL_TO_PLAYER_SIZE); + } else { + this.setAttribute(MuxVideoAttributes.DISABLE_CAP_LEVEL_TO_PLAYER_SIZE, ''); + } + } } export function getVideoAttribute(el: MuxPlayerElement, name: string) { diff --git a/packages/mux-player/src/template.ts b/packages/mux-player/src/template.ts index cc9bba675..d7c911dda 100644 --- a/packages/mux-player/src/template.ts +++ b/packages/mux-player/src/template.ts @@ -141,6 +141,7 @@ export const content = (props: MuxTemplateProps) => html` exportparts="video" disable-pseudo-ended="${props.disablePseudoEnded ?? false}" max-auto-resolution="${props.maxAutoResolution ?? false}" + cap-level-to-player-size="${props.capLevelToPlayerSize ?? false}" > ${props.storyboard ? html`` diff --git a/packages/mux-player/src/types.ts b/packages/mux-player/src/types.ts index 8e1debe4e..89bcc08b4 100644 --- a/packages/mux-player/src/types.ts +++ b/packages/mux-player/src/types.ts @@ -68,6 +68,7 @@ export type MuxTemplateProps = Partial & { /** Allow playback with ad blocker */ allowAdBlocker?: boolean; disablePseudoEnded?: boolean; + capLevelToPlayerSize?: boolean; }; export type DialogOptions = { diff --git a/packages/mux-video/src/ads/react.ts b/packages/mux-video/src/ads/react.ts index 7aed9e7de..172b29849 100644 --- a/packages/mux-video/src/ads/react.ts +++ b/packages/mux-video/src/ads/react.ts @@ -20,6 +20,7 @@ const ReactPropToAttrNameMap: Record = { playsInline: 'playsinline', disablePictureInPicture: 'disablepictureinpicture', disableRemotePlayback: 'disableremoteplayback', + capLevelToPlayerSize: 'cap-level-to-player-size', }; function toAttributeName(propName: string) { diff --git a/packages/mux-video/src/base.ts b/packages/mux-video/src/base.ts index 7c4f09c0d..cd5fca7f3 100644 --- a/packages/mux-video/src/base.ts +++ b/packages/mux-video/src/base.ts @@ -82,6 +82,7 @@ export const Attributes = { LIVE_EDGE_OFFSET: 'live-edge-offset', TYPE: 'type', LOGO: 'logo', + DISABLE_CAP_LEVEL_TO_PLAYER_SIZE: 'disable-cap-level-to-player-size', } as const; const AttributeNameValues = Object.values(Attributes); @@ -516,6 +517,29 @@ export class MuxVideoBaseElement extends CustomVideoElement implements IMuxVideo } } + get capLevelToPlayerSize(): boolean | undefined { + if (this._hls) { + return this._hls.capLevelToPlayerSize; + } + return this._hlsConfig?.capLevelToPlayerSize; + } + + set capLevelToPlayerSize(val: boolean | undefined) { + this._hlsConfig = { ...this._hlsConfig, capLevelToPlayerSize: val }; + } + + get disableCapLevelToPlayerSize(): boolean { + return this.hasAttribute(Attributes.DISABLE_CAP_LEVEL_TO_PLAYER_SIZE); + } + + set disableCapLevelToPlayerSize(val: boolean | undefined) { + if (!val) { + this.removeAttribute(Attributes.DISABLE_CAP_LEVEL_TO_PLAYER_SIZE); + } else { + this.setAttribute(Attributes.DISABLE_CAP_LEVEL_TO_PLAYER_SIZE, ''); + } + } + get drmToken() { return this.getAttribute(Attributes.DRM_TOKEN) ?? undefined; } @@ -900,6 +924,15 @@ export class MuxVideoBaseElement extends CustomVideoElement implements IMuxVideo } break; } + /*case Attributes.CAP_LEVEL_TO_PLAYER_SIZE: { + if (newValue == null || newValue !== oldValue) { + const capLevelToPlayerSize = this.capLevelToPlayerSize; + if (this._hls) { + this._hls.config.capLevelToPlayerSize = capLevelToPlayerSize ?? false; + } + } + break; + }*/ } } diff --git a/packages/mux-video/src/react.ts b/packages/mux-video/src/react.ts index a320360bf..46b95474e 100644 --- a/packages/mux-video/src/react.ts +++ b/packages/mux-video/src/react.ts @@ -20,6 +20,7 @@ const ReactPropToAttrNameMap: Record = { playsInline: 'playsinline', disablePictureInPicture: 'disablepictureinpicture', disableRemotePlayback: 'disableremoteplayback', + capLevelToPlayerSize: 'cap-level-to-player-size', }; function toAttributeName(propName: string) { diff --git a/packages/playback-core/src/index.ts b/packages/playback-core/src/index.ts index 0b2a6b97e..062837bc9 100644 --- a/packages/playback-core/src/index.ts +++ b/packages/playback-core/src/index.ts @@ -2,7 +2,7 @@ import type { ValueOf, PlaybackCore, MuxMediaProps, MuxMediaPropsInternal, MuxMe import mux, { ErrorEvent } from 'mux-embed'; import Hls from './hls'; import type { HlsInterface } from './hls'; -import type { ErrorData, HlsConfig } from 'hls.js'; +import type { CapLevelController, ErrorData, HlsConfig } from 'hls.js'; import { MediaError, MuxErrorCategory, MuxErrorCode, errorCategoryToTokenNameOrPrefix } from './errors'; import { setupAutoplay } from './autoplay'; import { setupPreload } from './preload'; @@ -705,6 +705,7 @@ export const setupHls = ( | 'tokens' | 'drmTypeCb' | 'maxAutoResolution' + | 'disableCapLevelToPlayerSize' > >, mediaEl: HTMLMediaElement @@ -728,7 +729,6 @@ export const setupHls = ( backBufferLength: 30, renderTextTracksNatively: false, liveDurationInfinity: true, - capLevelToPlayerSize: true, capLevelOnFPSDrop: true, }; const streamTypeConfig = getStreamTypeConfig(streamType); @@ -743,8 +743,24 @@ export const setupHls = ( } : undefined; - const capLevelControllerObj = - _hlsConfig.capLevelToPlayerSize == null ? { capLevelController: MinCapLevelController } : {}; + const capLevelControllerObj: { + capLevelController?: typeof CapLevelController; + capLevelToPlayerSize?: boolean; + } = {}; + + // If capLevelToPlayerSize is not explicitly set we enable MinCapLevelController + if (_hlsConfig.capLevelToPlayerSize == null) { + capLevelControllerObj.capLevelController = MinCapLevelController; + capLevelControllerObj.capLevelToPlayerSize = true; + } else { + capLevelControllerObj.capLevelController = undefined; + capLevelControllerObj.capLevelToPlayerSize = _hlsConfig.capLevelToPlayerSize; + } + if (props.disableCapLevelToPlayerSize) { + capLevelControllerObj.capLevelController = undefined; + capLevelControllerObj.capLevelToPlayerSize = false; + } + const hls = new Hls({ // Kind of like preload metadata, but causes spinner. @@ -763,13 +779,19 @@ export const setupHls = ( xhr.open('GET', urlObj); }, - ...capLevelControllerObj, ...defaultConfig, + ...capLevelControllerObj, ...streamTypeConfig, ...drmConfig, ..._hlsConfig, }) as HlsInterface; + console.log("capLevelToPlayerSize summary", { + hlsConfigCapLevelToPlayerSize: _hlsConfig.capLevelToPlayerSize, + disableCapLevelToPlayerSize: props.disableCapLevelToPlayerSize, + capLevelControllerObj: capLevelControllerObj, + hlsCapLevelToPlayerSize: hls.capLevelToPlayerSize, + }); if (capLevelControllerObj.capLevelController === MinCapLevelController) { if (maxAutoResolution !== undefined) { MinCapLevelController.setMaxAutoResolution(hls, maxAutoResolution); diff --git a/packages/playback-core/src/types.ts b/packages/playback-core/src/types.ts index e0612b08c..9428bb410 100644 --- a/packages/playback-core/src/types.ts +++ b/packages/playback-core/src/types.ts @@ -174,6 +174,8 @@ export type MuxMediaPropTypes = { _hlsConfig?: Partial; autoPlay?: Autoplay; autoplay?: Autoplay; + capLevelToPlayerSize?: boolean; + disableCapLevelToPlayerSize?: boolean; beaconCollectionDomain: Options['beaconCollectionDomain']; customDomain: string; debug: Options['debug'] & Hls['config']['debug'];