Skip to content

Commit

Permalink
feat: inferred stream type (#592)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
cjpillsbury and luwes authored Apr 12, 2023
1 parent 1d79b9e commit db4cc9f
Show file tree
Hide file tree
Showing 26 changed files with 646 additions and 833 deletions.
23 changes: 23 additions & 0 deletions errors/deprecated-stream-type.md
Original file line number Diff line number Diff line change
@@ -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)
6 changes: 4 additions & 2 deletions errors/invalid-stream-type.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
18 changes: 14 additions & 4 deletions examples/nextjs-with-typescript/components/renderers.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Fragment } from "react";
import { Fragment, ReactNode } from "react";

export const toWordsFromCamel = (string: string) => {
const first = string[0].toUpperCase();
Expand Down Expand Up @@ -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 <code>{renderValue}</code>;
};

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 (
<div style={{ display: "flex", flexDirection: "column", alignItems: "flex-start" }}>
Expand All @@ -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}
/>
<label htmlFor={`${name}-${enumValue}-control`}><code>{JSON.stringify(enumValue)}</code></label>
<label htmlFor={`${name}-${enumValue}-control`}>{formatter(enumValue)}</label>
</Fragment>
)
})}
Expand Down
25 changes: 19 additions & 6 deletions examples/nextjs-with-typescript/pages/MuxPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -42,21 +41,17 @@ 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,
audio,
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,
Expand Down Expand Up @@ -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}
Expand Down Expand Up @@ -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)
? <code>{JSON.stringify(enumValue)}</code>
: <><code>{JSON.stringify(enumValue)}</code> (deprecated)</>
}
/>
<EnumRenderer
value={state.targetLiveWindow}
name="targetLiveWindow"
onChange={genericOnChange}
values={[Infinity, 0, NaN]}
/>
<EnumRenderer
value={state.defaultStreamType}
name="defaultStreamType"
onChange={genericOnChange}
values={['on-demand', 'live', 'unknown']}
/>
<EnumRenderer
value={getPlayerSize(stylesState.width)}
Expand Down
4 changes: 4 additions & 0 deletions packages/mux-player-react/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
| `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` |
| `storyboardSrc` | `string` (URL) | Full URL string for the storyboard asset. Typically derived from the `playbackId`, setting this attribute will override the derived storyboard. | `undefined` |
| `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 `streamType` before any `playbackId` has been loaded. Used along with `targetLiveWindow` 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 `streamType` to determine what UI/controls to show. | (inferred from `playbackId` and/or `streamType`, 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 |
| `maxResolution` | `"720p"` | Specify the max-resolution you want delivered for this video. The only valid option is the string `720p` | "" |
Expand All @@ -41,6 +43,8 @@
| `ref` | [React `ref`](https://reactjs.org/docs/refs-and-the-dom.html) | A [React `ref`](https://reactjs.org/docs/refs-and-the-dom.html) to the underlying [`MuxPlayerElement`](../mux-player/REFERENCE.md) web component | `undefined` |

<!-- UNDOCUMENTED
// NEW STREAM TYPE VALUES
| `streamType` | `"on-demand" \| "live" \| "unknown"` | The type of stream associated with your Mux Asset. Used along with `targetLiveWindow` to determine what UI/controls to show. | (inferred from `playbackId`, otherwise `"unknown"`) |
| `preferCmcd` | `"query" \| "header"` | Preference for how CMCD data is sent provided in Mux Video requests. Defaults to query params for performance. | `"query"` |
| `experimentalCmcd` | `boolean` | Enables CMCD usage for media asset requests (playlists, segments). | `false` |
| `playsInline` | `boolean` | Identical to the native `playsInline` property (property equivalent to [`<video playsinline/>` attribute](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/video#attr-playsinline) | `false` |
Expand Down
5 changes: 4 additions & 1 deletion packages/mux-player-react/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ type MuxMediaPropTypes = {
customDomain: string;
playbackId: string;
preferPlayback: ValueOf<PlaybackTypes> | undefined;
streamType: ValueOf<StreamTypes> | 'vod';
// NOTE: Explicitly adding deprecated values here for now to avoid fully breaking changes in TS envs (CJP)
streamType: ValueOf<StreamTypes> | 'll-live' | 'live:dvr' | 'll-live:dvr';
defaultStreamType: ValueOf<StreamTypes>;
targetLiveWindow: number;
startTime: number;
storyboardSrc: string;
preferCmcd: ValueOf<CmcdTypes> | undefined;
Expand Down
1 change: 0 additions & 1 deletion packages/mux-player/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"
></mux-player>
```

Expand Down
Loading

5 comments on commit db4cc9f

@vercel
Copy link

@vercel vercel bot commented on db4cc9f Apr 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

elements-demo-vanilla – ./examples/vanilla-ts-esm

elements-demo-vanilla-mux.vercel.app
elements-demo-vanilla.vercel.app
elements-demo-vanilla-git-main-mux.vercel.app

@vercel
Copy link

@vercel vercel bot commented on db4cc9f Apr 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

elements-demo-svelte-kit – ./examples/svelte-kit

elements-demo-svelte-kit-mux.vercel.app
elements-demo-svelte-kit-git-main-mux.vercel.app
elements-demo-svelte-kit.vercel.app

@vercel
Copy link

@vercel vercel bot commented on db4cc9f Apr 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

elements-demo-nextjs – ./examples/nextjs-with-typescript

elements-demo-nextjs-git-main-mux.vercel.app
elements-demo-nextjs-mux.vercel.app
elements-demo-nextjs.vercel.app

@vercel
Copy link

@vercel vercel bot commented on db4cc9f Apr 12, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

elements-demo-vue – ./examples/vue-with-typescript

elements-demo-vue.vercel.app
elements-demo-vue-git-main-mux.vercel.app
elements-demo-vue-mux.vercel.app

@vercel
Copy link

@vercel vercel bot commented on db4cc9f Apr 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

elements-demo-create-react-app – ./examples/create-react-app-with-typescript

elements-demo-create-react-app.vercel.app
elements-demo-create-react-app-git-main-mux.vercel.app
elements-demo-create-react-app-mux.vercel.app

Please sign in to comment.