From db4cc9f60660f2b860ecce0a5a5ef2a3a09cc40b Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Wed, 12 Apr 2023 18:50:09 -0500 Subject: [PATCH] feat: inferred stream type (#592) Implementing inferred stream types and related (`targetLiveWindow`, `liveEdgeStart`). This PR removes the requirement for setting stream type, which can now be inferred directly from the media. Note that, since this process is asynchronous and does not begin until the media metadata is requested (aka will not immediately begin if `preload="none"`), this means that, for our current UI designs, folks will see an "on demand" UI until the `streamType` and related has been inferred (at least by default). Additionally, for this iteration, we will continue to support/translate "invalid" stream types to their appropriate corresponding properties/attributes. See https://github.com/cjpillsbury/elements/blob/feat/inferred-stream-type/errors/deprecated-stream-type.md for how to migrate now-deprecated stream types. Finally, there is now a distinct `default-stream-type`/`defaultStreamType` attribute/property for cases where we want a different UI to show by default. I've treated any corresponding "`default-target-live-window`" attr/prop as out of scope for this iteration, as there is no corresponding implementation within Media Chrome. --------- Co-authored-by: Wesley Luyten --- errors/deprecated-stream-type.md | 23 + errors/invalid-stream-type.md | 6 +- .../components/renderers.tsx | 18 +- .../pages/MuxPlayer.tsx | 25 +- packages/mux-player-react/REFERENCE.md | 4 + packages/mux-player-react/src/index.tsx | 5 +- packages/mux-player/README.md | 1 - packages/mux-player/REFERENCE.md | 9 + packages/mux-player/src/helpers.ts | 14 + packages/mux-player/src/index.ts | 142 ++-- packages/mux-player/src/media-theme-mux.html | 2 + packages/mux-player/src/template.ts | 19 +- packages/mux-player/src/types.d.ts | 2 + packages/mux-player/test/player.test.js | 97 ++- packages/mux-player/test/template.test.js | 9 +- packages/mux-video/README.md | 14 +- packages/mux-video/package.json | 3 +- packages/mux-video/src/index.ts | 80 ++- packages/mux-video/test/index.test.js | 80 +++ packages/playback-core/src/index.ts | 192 +++++- packages/playback-core/src/types.ts | 16 +- packages/playback-core/src/util.ts | 13 +- shared/assets/media-assets.json | 32 +- shared/castable-video/castable-video.js | 640 ------------------ shared/castable-video/package.json | 28 - yarn.lock | 5 + 26 files changed, 646 insertions(+), 833 deletions(-) create mode 100644 errors/deprecated-stream-type.md delete mode 100644 shared/castable-video/castable-video.js delete mode 100644 shared/castable-video/package.json diff --git a/errors/deprecated-stream-type.md b/errors/deprecated-stream-type.md new file mode 100644 index 000000000..da2a0c2d1 --- /dev/null +++ b/errors/deprecated-stream-type.md @@ -0,0 +1,23 @@ +# Deprecated Stream Type + +#### Why This Error Occurred + +The provided `stream-type` is deprecated. + +#### Possible Ways to Fix It + +If you are a viewer of this video there is not much you can do. The owner of the +video will have to fix this issue. + +If you are the owner of this video, `stream-type` can now be inferred based on the `playback-id` and is +no longer required. However, if you would still like to explicitly declare the `stream-type` (e.g. to +avoid an initial render of the wrong UI), here are the recommended refactors: + +- `stream-type="ll-live"` - Replace with `stream-type="live"` (we will infer that the `playback-id` is low latency HLS) +- `stream-type="live:dvr"` or `stream-type="ll-live:dvr"` - Refactor as `stream-type="live"` & `target-live-window="Infinity"` + +### Useful Links + +- [Mux Player Attributes](https://github.com/muxinc/elements/tree/main/packages/mux-player#attributes) +- [Mux Player React Props](https://github.com/muxinc/elements/tree/main/packages/mux-player-react#props) +- [Play your videos](https://docs.mux.com/guides/video/play-your-videos) diff --git a/errors/invalid-stream-type.md b/errors/invalid-stream-type.md index aa66c0523..0175f3ad7 100644 --- a/errors/invalid-stream-type.md +++ b/errors/invalid-stream-type.md @@ -9,11 +9,13 @@ The provided `stream-type` is invalid. If you are a viewer of this video there is not much you can do. The owner of the video will have to fix this issue. -If you are the owner of this video make sure the `stream-type` is one of the -following: `on-demand`, `live`, `ll-live`, `live:dvr`, or `ll-live:dvr`. +If you are the owner of this video, `stream-type` is no longer required, so you may consider removing it. +If you still have a use case where you would like to explicitly define the `stream-type`, make sure the `stream-type` or +`default-stream-type` attribute is one of the following: `on-demand`, `live`. ### Useful Links - [Mux Player Attributes](https://github.com/muxinc/elements/tree/main/packages/mux-player#attributes) - [Mux Player React Props](https://github.com/muxinc/elements/tree/main/packages/mux-player-react#props) +- [Deprecated Stream Types](https://github.com/muxinc/elements/tree/main/errors/deprecated-stream-type) - [Play your videos](https://docs.mux.com/guides/video/play-your-videos) diff --git a/examples/nextjs-with-typescript/components/renderers.tsx b/examples/nextjs-with-typescript/components/renderers.tsx index c18508d26..6dbf93a3a 100644 --- a/examples/nextjs-with-typescript/components/renderers.tsx +++ b/examples/nextjs-with-typescript/components/renderers.tsx @@ -1,4 +1,4 @@ -import { Fragment } from "react"; +import { Fragment, ReactNode } from "react"; export const toWordsFromCamel = (string: string) => { const first = string[0].toUpperCase(); @@ -116,13 +116,23 @@ export const ColorRenderer = ({ ); }; +/** @TODO Consider refactoring to an actual react (functional) component (CJP) */ +export const DefaultEnumFormatter = (enumValue) => { + let renderValue = JSON.stringify(enumValue); + if (renderValue === 'null' && enumValue !== null) { + renderValue = enumValue?.toString(); + } + return {renderValue}; +}; + export const EnumRenderer = ({ name, value, label, onChange, values, -}: { name: string; value: any | undefined; label?: string; onChange: (obj: any) => void; values: any[] }) => { + formatter = DefaultEnumFormatter +}: { name: string; value: any | undefined; label?: string; onChange: (obj: any) => void; values: any[], formatter?: (enumValue: any) => ReactNode }) => { const labelStr = label ?? toWordsFromCamel(name); return (
@@ -142,10 +152,10 @@ export const EnumRenderer = ({ id={`${name}-${enumValue}-control`} type="radio" onChange={() => onChange({ [name]: values[i] })} - value={enumValue} + value={typeof enumValue === 'string' ? enumValue : enumValue?.toString()} checked={value === enumValue} /> - + ) })} diff --git a/examples/nextjs-with-typescript/pages/MuxPlayer.tsx b/examples/nextjs-with-typescript/pages/MuxPlayer.tsx index a517cd3cb..aeff4ba1c 100644 --- a/examples/nextjs-with-typescript/pages/MuxPlayer.tsx +++ b/examples/nextjs-with-typescript/pages/MuxPlayer.tsx @@ -6,7 +6,6 @@ import "@mux/mux-player/themes/minimal"; import "@mux/mux-player/themes/microvideo"; import { useEffect, useReducer, useRef, useState } from "react"; import mediaAssetsJSON from "@mux/assets/media-assets.json"; -import type MuxPlayerElement from "@mux/mux-player"; import { useRouter } from "next/router"; import type { NextParsedUrlQuery } from "next/dist/server/request-meta"; import type { GetServerSideProps } from "next"; @@ -42,7 +41,6 @@ const toMetadataFromMediaAsset = (mediaAsset: typeof mediaAssetsJSON[0], mediaAs const toPlayerPropsFromJSON = (mediaAsset: typeof mediaAssetsJSON[0] | undefined, mediaAssets: typeof mediaAssetsJSON) => { const { 'playback-id': playbackId, - // 'stream-type': streamType, tokens, 'custom-domain': customDomain, 'storyboard-src': storyboardSrc, @@ -50,13 +48,10 @@ const toPlayerPropsFromJSON = (mediaAsset: typeof mediaAssetsJSON[0] | undefined description: title, placeholder, } = mediaAsset ?? {}; - // NOTE: Inferred type is "string" from JSON (CJP) - const streamType = mediaAsset?.['stream-type'] as MuxPlayerProps["streamType"]; const metadata = mediaAsset ? toMetadataFromMediaAsset(mediaAsset, mediaAssets) : undefined; return { playbackId, - streamType, audio, tokens, customDomain, @@ -360,6 +355,8 @@ function MuxPlayerPage({ location }: Props) { maxResolution={state.maxResolution} preload={state.preload} streamType={state.streamType} + targetLiveWindow={state.targetLiveWindow} + defaultStreamType={state.defaultStreamType} audio={state.audio} primaryColor={state.primaryColor} secondaryColor={state.secondaryColor} @@ -423,7 +420,23 @@ function MuxPlayerPage({ location }: Props) { value={state.streamType} name="streamType" onChange={genericOnChange} - values={['on-demand', 'live', 'll-live', 'live:dvr', 'll-live:dvr']} + values={['on-demand', 'live', 'll-live', 'live:dvr', 'll-live:dvr', 'unknown']} + formatter={(enumValue) => ['on-demand', 'live', 'unknown'].includes(enumValue) + ? {JSON.stringify(enumValue)} + : <>{JSON.stringify(enumValue)} (deprecated) + } + /> + + ` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-playsinline) | `false` | diff --git a/packages/mux-player-react/src/index.tsx b/packages/mux-player-react/src/index.tsx index 412072086..e4e6074b9 100644 --- a/packages/mux-player-react/src/index.tsx +++ b/packages/mux-player-react/src/index.tsx @@ -47,7 +47,10 @@ type MuxMediaPropTypes = { customDomain: string; playbackId: string; preferPlayback: ValueOf | undefined; - streamType: ValueOf | 'vod'; + // NOTE: Explicitly adding deprecated values here for now to avoid fully breaking changes in TS envs (CJP) + streamType: ValueOf | 'll-live' | 'live:dvr' | 'll-live:dvr'; + defaultStreamType: ValueOf; + targetLiveWindow: number; startTime: number; storyboardSrc: string; preferCmcd: ValueOf | undefined; diff --git a/packages/mux-player/README.md b/packages/mux-player/README.md index 0a04c6705..4f957c3dc 100644 --- a/packages/mux-player/README.md +++ b/packages/mux-player/README.md @@ -30,7 +30,6 @@ yarn add @mux/mux-player playback-id="DS00Spx1CV902MCtPj5WknGlR102V5HFkDe" metadata-video-title="Big Buck Bunny" metadata-viewer-user-id="user-id-1234" - stream-type="on-demand" > ``` diff --git a/packages/mux-player/REFERENCE.md b/packages/mux-player/REFERENCE.md index eeedc07f1..d0d59f1a4 100644 --- a/packages/mux-player/REFERENCE.md +++ b/packages/mux-player/REFERENCE.md @@ -23,6 +23,8 @@ | `beacon-collection-domain` | `string` (domain name) | Assigns a custom domain to be used for [Mux Data](https://docs.mux.com/guides/data/monitor-html5-video-element#features) collection. | N/A | | `custom-domain` | `string` (domain name) | Assigns a custom domain to be used for [Mux Video](https://docs.mux.com/guides/video/use-a-custom-domain-for-streaming#use-your-own-domain-for-delivering-videos-and-images). | N/A | | `stream-type` | `"on-demand" \| "live" \| "ll-live" \| "live:dvr" \| "ll-live:dvr"` | The type of stream associated with your Mux Asset. Used to determine what UI/controls to show and what optimizations to make for playback. | `"on-demand"` | +| `default-stream-type` | `"on-demand" \| "live"` | The default assumed `stream-type` before any `playback-id` has been loaded. Used along with `target-live-window` to determine what UI/controls to show by default. | `on-demand` | +| `target-live-window` | `number`| An offset representing the seekable range for live content, where `Infinity` means the entire live content is seekable (aka "standard DVR"). Used along with `stream-type` to determine what UI/controls to show. | (inferred from `playback-id` and/or `stream-type`, otherwise `NaN`) | | `start-time` | `number` (seconds) | Specify where in the media's timeline you want playback to start. | `0` | | `default-hidden-captions` | `boolean` | Hide captions by default instead of showing them on initial load (when available) | `false` | | `primary-color` | `string` (Any valid CSS color style) | The primary color used by the player's UI | N/A | @@ -43,6 +45,8 @@ | `placeholder` | `string` (URI) | Image to show as various assets load. Typically a [data URI](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URLs) when used | N/A | @@ -106,6 +110,9 @@ | `debug` | `boolean` | Enables debug mode for the underlying playback engine (currently hls.js) and mux-embed, providing additional information in the console. NOTE: Must be set before `playbackId` to fully apply to debug logging contexts. | `false` | | `disableCookies` | `boolean` | Disables cookies used by Mux Data. For more, check out the [Mux Docs](https://docs.mux.com/guides/data/monitor-html5-video-element#disable-cookies). | `false` | | `streamType` | `"on-demand" \| "live" \| "ll-live" \| "live:dvr" \| "ll-live:dvr"` | The type of stream associated with your Mux Asset. Used to determine what UI/controls to show and what optimizations to make for playback. | `"on-demand"` | +| `defaultStreamType` | `"on-demand" \| "live"` | The default assumed `stream-type` before any `playback-id` has been loaded. Used along with `target-live-window` to determine what UI/controls to show by default. | `on-demand` | +| `targetLiveWindow` | `number`| An offset representing the seekable range for live content, where `Infinity` means the entire live content is seekable (aka "standard DVR"). Used along with `stream-type` to determine what UI/controls to show. | (inferred from `playback-id` and/or `stream-type`, otherwise `NaN`) | +| `liveEdgeOffset` Read only | `number` | the earliest playback time that will be treated as playing "at the live edge" for live content. | (inferred from `playback-id` and/or `stream-type`, otherwise `NaN`) | | `startTime` | `number` (seconds) | Specify where in the media's timeline you want playback to start. | `0` | | `preferPlayback` | `"mse" \| "native"` | Specify if Mux Player should try to use Media Source Extension or native playback (if available). If no value is provided, Mux Player will choose based on what's deemed optimal for content and playback environment. | Varies | | `metadata` | `object`\* | An object for configuring any metadata you'd like to send to [Mux Data](https://docs.mux.com/guides/data/make-your-data-actionable-with-metadata). If any `metadata-*` attributes are set, they will take precedence. | `undefined` | @@ -119,6 +126,8 @@ | `activeCuePoint` Read only | `{ time: number; value: any; }` | The current active CuePoint, determined based on the player's `currentTime`. | `undefined` |