Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: implement Mux badge #988

Merged
merged 19 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions examples/nextjs-with-typescript/pages/MuxPlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ const DEFAULT_INITIAL_STATE: Partial<MuxPlayerProps> = Object.freeze({
streamType: undefined,
storyboardSrc: undefined,
theme: undefined,
proudlyDisplayMuxBadge: undefined,
});

const SMALL_BREAKPOINT = 700;
Expand Down Expand Up @@ -329,6 +330,7 @@ function MuxPlayerPage({ location }: Props) {
defaultDuration={state.defaultDuration}
playbackRate={state.playbackRate}
playbackRates={state.playbackRates}
proudlyDisplayMuxBadge={state.proudlyDisplayMuxBadge}
davekiss marked this conversation as resolved.
Show resolved Hide resolved
onPlay={(evt: Event) => {
onPlay(evt);
// dispatch(updateProps({ paused: false }));
Expand Down
2 changes: 2 additions & 0 deletions examples/nextjs-with-typescript/pages/MuxPlayerLazy.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ const DEFAULT_INITIAL_STATE: Partial<MuxPlayerProps> = Object.freeze({
tokens: undefined,
playbackId: undefined,
streamType: undefined,
proudlyDisplayMuxBadge: undefined,
});

const reducer = (state: Partial<{ [k: string]: any }>, action): Partial<{ [k: string]: any }> => {
Expand Down Expand Up @@ -348,6 +349,7 @@ function MuxPlayerPage({ location }: Props) {
audio={state.audio}
primaryColor={state.primaryColor}
secondaryColor={state.secondaryColor}
proudlyDisplayMuxBadge={state.proudlyDisplayMuxBadge}
defaultShowRemainingTime={state.defaultShowRemainingTime}
defaultHiddenCaptions={state.defaultHiddenCaptions}
/** @TODO This doesn't appear to work? (CJP) */
Expand Down
6 changes: 6 additions & 0 deletions examples/vanilla-ts-esm/public/mux-player.html
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ <h2>Live</h2>
title="My amazing video"
></mux-player>

<mux-player
stream-type="on-demand"
playback-id="Sc89iWAyNkhJ3P1rQ02nrEdCFTnfT01CZ2KmaEcxXfB008"
proudly-display-mux-badge
></mux-player>

<a href="../">Browse Elements</a>
</body>
</html>
1 change: 1 addition & 0 deletions packages/mux-player-react/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
| `castCustomData` | `object` (JSON-serializable) | [Custom Data](https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.media.MediaInfo#customData) to send to your Google cast receiver on initial load. If none is provided, various Mux key/value pairs will be sent. | Mux-specific object |
| `playerInitTime` | `number` (timestamp) | Overrides the default [player initialization time](https://docs.mux.com/guides/make-your-data-actionable-with-metadata#optional-configurable-metadata), used by Mux Data for time-based [quality-of-experience (QOE) metrics](https://docs.mux.com/guides/understand-metric-definitions). It will be inferred from instantiation time by default. | Varies |
| `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` |
| `proudlyDisplayMuxBadge`| `boolean` | Controls whether to show the Mux badge in the player UI | `false` |

<!-- UNDOCUMENTED
// NEW STREAM TYPE VALUES
Expand Down
1 change: 1 addition & 0 deletions packages/mux-player-react/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ export type MuxPlayerProps = {
renditionOrder?: RenditionOrderValue;
programStartTime?: number;
programEndTime?: number;
proudlyDisplayMuxBadge?: boolean;
assetStartTime?: number;
assetEndTime?: number;
metadataVideoId?: string;
Expand Down
1 change: 1 addition & 0 deletions packages/mux-player/REFERENCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@
| `liveEdgeOffset` <sub><sup>Read only</sup></sub> | `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 |
| `proudlyDisplayMuxBadge` | `boolean` | Display the Mux badge in the player controls. | `false` |
davekiss marked this conversation as resolved.
Show resolved Hide resolved
| `maxResolution` | `"720p" \| "1080p" \| "1440p" \| "2160p"` | Specify the maximum resolution you want delivered for this video. | N/A |
| `minResolution` | `"480p" \| "540p" \| "720p" \| "1080p" \| "1440p" \| "2160p"` | Specify the minimum resolution you want delivered for this video. | N/A |
| `renditionOrder` | `"desc"` | Change the order in which renditions are provided in the src playlist. Can impact initial segment loads. Currently only support `"desc"` for descending order | N/A |
Expand Down
18 changes: 18 additions & 0 deletions packages/mux-player/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const PlayerAttributes = {
NO_VOLUME_PREF: 'no-volume-pref',
CAST_RECEIVER: 'cast-receiver',
NO_TOOLTIPS: 'no-tooltips',
PROUDLY_DISPLAY_MUX_BADGE: 'proudly-display-mux-badge',
};

davekiss marked this conversation as resolved.
Show resolved Hide resolved
const ThemeAttributeNames = [
Expand Down Expand Up @@ -172,6 +173,7 @@ function getProps(el: MuxPlayerElement, state?: any): MuxTemplateProps {
title: el.getAttribute(PlayerAttributes.TITLE),
novolumepref: el.hasAttribute(PlayerAttributes.NO_VOLUME_PREF),
castReceiver: el.castReceiver,
proudlyDisplayMuxBadge: el.hasAttribute(PlayerAttributes.PROUDLY_DISPLAY_MUX_BADGE),
...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)
Expand Down Expand Up @@ -755,6 +757,10 @@ class MuxPlayerElement extends VideoApiElement implements MuxPlayerElement {
}
break;
}
case PlayerAttributes.PROUDLY_DISPLAY_MUX_BADGE: {
this.#render({ proudlyDisplayMuxBadge: newValue !== null });
break;
}
davekiss marked this conversation as resolved.
Show resolved Hide resolved
case MuxVideoAttributes.STREAM_TYPE: {
if (newValue && ![StreamTypes.LIVE, StreamTypes.ON_DEMAND, StreamTypes.UNKNOWN].includes(newValue as any)) {
// Handle deprecated values by translating to new properties for the time being.
Expand Down Expand Up @@ -1847,6 +1853,18 @@ class MuxPlayerElement extends VideoApiElement implements MuxPlayerElement {
}
this.setAttribute(PlayerAttributes.NO_TOOLTIPS, '');
}

get proudlyDisplayMuxBadge() {
return this.hasAttribute(PlayerAttributes.PROUDLY_DISPLAY_MUX_BADGE);
}

set proudlyDisplayMuxBadge(val: boolean) {
if (!val) {
this.removeAttribute(PlayerAttributes.PROUDLY_DISPLAY_MUX_BADGE);
} else {
this.setAttribute(PlayerAttributes.PROUDLY_DISPLAY_MUX_BADGE, '');
}
}
}

export function getVideoAttribute(el: MuxPlayerElement, name: string) {
Expand Down
1 change: 1 addition & 0 deletions packages/mux-player/src/template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ export const content = (props: MuxTemplateProps) => html`
defaultduration="${props.defaultDuration ?? false}"
hideduration="${props.hideDuration ?? false}"
title="${props.title ?? false}"
proudlydisplaymuxbadge="${props.proudlyDisplayMuxBadge ?? false}"
exportparts="${partsListStr}"
onclose="${props.onCloseErrorDialog}"
onfocusin="${props.onFocusInErrorDialog}"
Expand Down
107 changes: 107 additions & 0 deletions packages/mux-player/src/themes/gerwig/gerwig.html
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,80 @@
--bottom-playback-rate-menu-button: none;
--bottom-pip-button: none;
}

[part='mux-badge'] {
position: absolute;
bottom: 10px;
right: 10px;
z-index: 2;
opacity: 0.6;
transition:
opacity 0.2s ease-in-out,
bottom 0.2s ease-in-out;
}

[part='mux-badge']:hover {
opacity: 1;
}

[part='mux-badge'] a {
font-size: 14px;
font-family: var(--_font-family);
color: var(--_primary-color);
text-decoration: none;
display: flex;
align-items: center;
gap: 5px;
}

[part='mux-badge'] .mux-badge-text {
transition: opacity 0.5s ease-in-out;
opacity: 0;
}

[part='mux-badge'] .mux-badge-logo {
width: 40px;
height: auto;
display: inline-block;
}

[part='mux-badge'] .mux-badge-logo svg {
width: 100%;
height: 100%;
fill: white;
}

media-controller:not([userinactive]):not([mediahasplayed]) [part='mux-badge'],
media-controller:not([userinactive]) [part='mux-badge'],
media-controller[mediahasplayed][mediapaused] [part='mux-badge'] {
transition: bottom 0.1s ease-in-out;
}

media-controller[userinactive]:not([mediapaused]) [part='mux-badge'] {
transition: bottom 0.2s ease-in-out 0.62s;
}

media-controller:not([userinactive]) [part='mux-badge'] .mux-badge-text,
media-controller[mediahasplayed][mediapaused] [part='mux-badge'] .mux-badge-text {
opacity: 1;
}

media-controller[userinactive]:not([mediapaused]) [part='mux-badge'] .mux-badge-text {
opacity: 0;
}

media-controller[userinactive]:not([mediapaused]) [part='mux-badge'] {
bottom: 10px;
}

media-controller:not([userinactive]):not([mediahasplayed]) [part='mux-badge'] {
bottom: 10px;
}

media-controller:not([userinactive])[mediahasplayed] [part='mux-badge'],
media-controller[mediahasplayed][mediapaused] [part='mux-badge'] {
bottom: calc(28px + var(--media-control-height, 0px) + var(--media-control-padding, 0px) * 2);
}
</style>

<template partial="TitleDisplay">
Expand Down Expand Up @@ -888,6 +962,35 @@
</media-rendition-menu>
</template>

<template partial="MuxBadge">
<div part="mux-badge">
<a href="https://www.mux.com/player" target="_blank">
<span class="mux-badge-text">Powered by</span>
<div class="mux-badge-logo">
<svg
viewBox="0 0 1600 500"
style="fill-rule: evenodd; clip-rule: evenodd; stroke-linejoin: round; stroke-miterlimit: 2"
>
<g>
<path
d="M994.287,93.486c-17.121,-0 -31,-13.879 -31,-31c0,-17.121 13.879,-31 31,-31c17.121,-0 31,13.879 31,31c0,17.121 -13.879,31 -31,31m0,-93.486c-34.509,-0 -62.484,27.976 -62.484,62.486l0,187.511c0,68.943 -56.09,125.033 -125.032,125.033c-68.942,-0 -125.03,-56.09 -125.03,-125.033l0,-187.511c0,-34.51 -27.976,-62.486 -62.485,-62.486c-34.509,-0 -62.484,27.976 -62.484,62.486l0,187.511c0,137.853 112.149,250.003 249.999,250.003c137.851,-0 250.001,-112.15 250.001,-250.003l0,-187.511c0,-34.51 -27.976,-62.486 -62.485,-62.486"
style="fill-rule: nonzero"
></path>
<path
d="M1537.51,468.511c-17.121,-0 -31,-13.879 -31,-31c0,-17.121 13.879,-31 31,-31c17.121,-0 31,13.879 31,31c0,17.121 -13.879,31 -31,31m-275.883,-218.509l-143.33,143.329c-24.402,24.402 -24.402,63.966 0,88.368c24.402,24.402 63.967,24.402 88.369,-0l143.33,-143.329l143.328,143.329c24.402,24.4 63.967,24.402 88.369,-0c24.403,-24.402 24.403,-63.966 0.001,-88.368l-143.33,-143.329l0.001,-0.004l143.329,-143.329c24.402,-24.402 24.402,-63.965 0,-88.367c-24.402,-24.402 -63.967,-24.402 -88.369,-0l-143.329,143.328l-143.329,-143.328c-24.402,-24.401 -63.967,-24.402 -88.369,-0c-24.402,24.402 -24.402,63.965 0,88.367l143.329,143.329l0,0.004Z"
style="fill-rule: nonzero"
></path>
<path
d="M437.511,468.521c-17.121,-0 -31,-13.879 -31,-31c0,-17.121 13.879,-31 31,-31c17.121,-0 31,13.879 31,31c0,17.121 -13.879,31 -31,31m23.915,-463.762c-23.348,-9.672 -50.226,-4.327 -68.096,13.544l-143.331,143.329l-143.33,-143.329c-17.871,-17.871 -44.747,-23.216 -68.096,-13.544c-23.349,9.671 -38.574,32.455 -38.574,57.729l0,375.026c0,34.51 27.977,62.486 62.487,62.486c34.51,-0 62.486,-27.976 62.486,-62.486l0,-224.173l80.843,80.844c24.404,24.402 63.965,24.402 88.369,-0l80.843,-80.844l0,224.173c0,34.51 27.976,62.486 62.486,62.486c34.51,-0 62.486,-27.976 62.486,-62.486l0,-375.026c0,-25.274 -15.224,-48.058 -38.573,-57.729"
style="fill-rule: nonzero"
></path>
</g>
</svg>
</div>
</a>
</div>
</template>

<media-controller
part="controller"
defaultstreamtype="{{defaultstreamtype ?? 'on-demand'}}"
Expand All @@ -902,6 +1005,7 @@
defaultduration="{{defaultduration ?? false}}"
keyboardforwardseekoffset="{{forwardseekoffset}}"
keyboardbackwardseekoffset="{{backwardseekoffset}}"
proudlydisplaymuxbadge="{{proudlydisplaymuxbadge}}"
davekiss marked this conversation as resolved.
Show resolved Hide resolved
exportparts="layer, media-layer, poster-layer, vertical-layer, centered-layer, gesture-layer"
style="--_pre-playback-place:{{preplaybackplace ?? 'center'}}"
>
Expand All @@ -919,6 +1023,9 @@
<template if="breakpointsm">{{>PrePlayButton section="center"}}</template>
</div>

<!-- Mux Badge -->
<template if="proudlydisplaymuxbadge"> {{>MuxBadge}} </template>

<!-- Autoplay centered unmute button -->
<!--
todo: figure out how show this with available state variables
Expand Down
1 change: 1 addition & 0 deletions packages/mux-player/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export type MuxTemplateProps = Partial<MuxPlayerProps> & {
title: string;
defaultStreamType?: ValueOf<StreamTypes>;
castReceiver: string | undefined;
proudlyDisplayMuxBadge?: boolean;
};

export type DialogOptions = {
Expand Down
43 changes: 43 additions & 0 deletions packages/mux-player/test/player.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,49 @@ describe('<mux-player>', () => {
assert.equal(muxVideo.preload, defaultPreload, `muxVideo default preload is ${defaultPreload}`);
});

it('should show/hide Mux badge based on proudly-display-mux-badge attribute', async function () {
this.timeout(5000);

// Create player without the attribute
const playerWithoutBadge = await fixture(`<mux-player
playback-id="DS00Spx1CV902MCtPj5WknGlR102V5HFkDe"
stream-type="on-demand"
></mux-player>`);

// Assert that the Mux badge is not present
let muxBadge = playerWithoutBadge.shadowRoot
.querySelector('media-theme')
.shadowRoot.querySelector('[part="mux-badge"]');
assert.notExists(muxBadge, 'Mux badge should not be present when attribute is not set');

// Create player with the attribute
const playerWithBadge = await fixture(`<mux-player
playback-id="DS00Spx1CV902MCtPj5WknGlR102V5HFkDe"
stream-type="on-demand"
proudly-display-mux-badge
></mux-player>`);

// Assert that the Mux badge is present
muxBadge = playerWithBadge.shadowRoot.querySelector('media-theme').shadowRoot.querySelector('[part="mux-badge"]');
assert.exists(muxBadge, 'Mux badge should be present when attribute is set');

// Remove the attribute dynamically
playerWithBadge.removeAttribute('proudly-display-mux-badge');
await playerWithBadge.updateComplete;

// Assert that the Mux badge is removed
muxBadge = playerWithBadge.shadowRoot.querySelector('media-theme').shadowRoot.querySelector('[part="mux-badge"]');
assert.notExists(muxBadge, 'Mux badge should be removed when attribute is removed');

// Add the attribute back dynamically
playerWithBadge.setAttribute('proudly-display-mux-badge', '');
await playerWithBadge.updateComplete;

// Assert that the Mux badge is added back
muxBadge = playerWithBadge.shadowRoot.querySelector('media-theme').shadowRoot.querySelector('[part="mux-badge"]');
assert.exists(muxBadge, 'Mux badge should be added back when attribute is set again');
});

it('poster is forwarded to the media-poster-image element', async function () {
const player = await fixture(`<mux-player
stream-type="on-demand"
Expand Down
Loading