Skip to content

Commit 7531af4

Browse files
committed
prove out mux player ad-specific UI.
1 parent d0fdc37 commit 7531af4

File tree

6 files changed

+245
-29
lines changed

6 files changed

+245
-29
lines changed

packages/mux-player/src/index.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,12 @@ function getProps(el: MuxPlayerElement, state?: any): MuxTemplateProps {
176176
extraSourceParams: el.extraSourceParams,
177177
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
178178
adTagUrl: el.adTagUrl,
179+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
180+
adBreak: el.adBreak,
181+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
182+
adBreakTotalAds: el.adBreakTotalAds,
183+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
184+
adBreakAdPosition: el.adBreakAdPosition,
179185
};
180186

181187
return props;
@@ -369,6 +375,17 @@ class MuxPlayerElement extends VideoApiElement implements MuxPlayerElement {
369375

370376
// NOTE: Make sure we re-render when <source> tags are appended so hasSrc is updated.
371377
this.media?.addEventListener('loadstart', () => this.#render());
378+
379+
/** @TODO remove me when migrated to media chrome */
380+
this.media?.addEventListener('adbreakchange', () => {
381+
this.#render();
382+
});
383+
this.media?.addEventListener('adbreakadpositionchange', () => {
384+
this.#render();
385+
});
386+
this.media?.addEventListener('adbreaktotaladschange', () => {
387+
this.#render();
388+
});
372389
}
373390

374391
#setupCSSProperties() {
@@ -1763,6 +1780,21 @@ class MuxPlayerElement extends VideoApiElement implements MuxPlayerElement {
17631780
this.removeAttribute(PlayerAttributes.AD_TAG_URL);
17641781
}
17651782
}
1783+
1784+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
1785+
get adBreak() {
1786+
return this.media?.adBreak ?? false;
1787+
}
1788+
1789+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
1790+
get adBreakTotalAds() {
1791+
return this.media?.adBreakTotalAds;
1792+
}
1793+
1794+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
1795+
get adBreakAdPosition() {
1796+
return this.media?.adBreakAdPosition;
1797+
}
17661798
}
17671799

17681800
export function getVideoAttribute(el: MuxPlayerElement, name: string) {
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { MediaTextDisplay } from 'media-chrome/dist/media-text-display.js';
2+
import { getNumericAttr, getStringAttr, setNumericAttr, setStringAttr } from 'media-chrome/dist/utils/element-utils.js';
3+
import { globalThis } from 'media-chrome/dist/utils/server-safe-globals.js';
4+
import { MediaUIAttributes as MediaUIAttributesBase } from 'media-chrome/dist/constants.js';
5+
// import { nouns } from 'media-chrome/dist/labels/labels.js';
6+
7+
const MediaUIAttributes = {
8+
...MediaUIAttributesBase,
9+
MEDIA_AD_BREAK_TOTAL_ADS: 'mediaadbreaktotalads',
10+
MEDIA_AD_BREAK_AD_POSITION: 'mediaadbreakadposition',
11+
} as const;
12+
13+
export const Attributes = {
14+
PREFIX: 'prefix',
15+
};
16+
17+
const CombinedAttributes = [
18+
...Object.values(Attributes),
19+
MediaUIAttributes.MEDIA_AD_BREAK_TOTAL_ADS,
20+
MediaUIAttributes.MEDIA_AD_BREAK_AD_POSITION,
21+
];
22+
23+
// Todo: Use data locals: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toLocaleTimeString
24+
25+
const DEFAULT_COUNT_SEP = 'of';
26+
const DEFAULT_PREFIX = 'Advertisement';
27+
28+
const formatLabel = (el: MediaAdCountDisplay, { countSep = DEFAULT_COUNT_SEP } = {}): string => {
29+
const prefixPart = el.prefix ? `${el.prefix}: ` : '';
30+
return `${prefixPart}${el.mediaAdBreakAdPosition} ${countSep} ${el.mediaAdBreakTotalAds}`;
31+
};
32+
33+
// const DEFAULT_MISSING_TIME_PHRASE = 'video not loaded, unknown time.';
34+
35+
const updateAriaValueText = (el: MediaAdCountDisplay): void => {
36+
const fullPhrase = formatLabel(el);
37+
el.setAttribute('aria-valuetext', fullPhrase);
38+
};
39+
40+
/**
41+
* @attr {string} prefix - the prefix string for the display. 'Advertisement' by default.
42+
* @attr {number} mediaadbreaktotalads - (read-only) total number of ads in the current ad break
43+
* @attr {number} mediaadbreakadposition - (read-only) current ad index playing in the current ad break
44+
*/
45+
class MediaAdCountDisplay extends MediaTextDisplay {
46+
#slot: HTMLSlotElement;
47+
48+
static get observedAttributes(): string[] {
49+
return [...super.observedAttributes, ...CombinedAttributes, 'disabled'];
50+
}
51+
52+
constructor() {
53+
super();
54+
55+
this.#slot = this.shadowRoot?.querySelector('slot') as HTMLSlotElement;
56+
this.#slot.innerHTML = `${formatLabel(this)}`;
57+
}
58+
59+
connectedCallback(): void {
60+
if (!this.hasAttribute('disabled')) {
61+
this.enable();
62+
}
63+
64+
/** @TODO Implement these */
65+
this.setAttribute('role', 'progressbar');
66+
this.setAttribute('aria-label', 'FILL ME IN');
67+
68+
super.connectedCallback();
69+
}
70+
71+
disconnectedCallback(): void {
72+
this.disable();
73+
super.disconnectedCallback();
74+
}
75+
76+
attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string | null): void {
77+
if (CombinedAttributes.includes(attrName)) {
78+
this.update();
79+
} else if (attrName === 'disabled' && newValue !== oldValue) {
80+
if (newValue == null) {
81+
this.enable();
82+
} else {
83+
this.disable();
84+
}
85+
}
86+
87+
super.attributeChangedCallback(attrName, oldValue, newValue);
88+
}
89+
90+
enable(): void {
91+
this.tabIndex = 0;
92+
}
93+
94+
disable(): void {
95+
this.tabIndex = -1;
96+
}
97+
98+
// Own props
99+
100+
/**
101+
* Describe me
102+
*/
103+
get prefix(): string {
104+
return getStringAttr(this, Attributes.PREFIX, DEFAULT_PREFIX);
105+
}
106+
107+
set prefix(val: string | undefined) {
108+
/** @TODO inaccurate type def in media chrome. Accepts/expects nullish. (CJP) */
109+
/** @ts-ignore */
110+
setStringAttr(this, Attributes.PREFIX, val);
111+
}
112+
113+
// Props derived from media UI attributes
114+
115+
/**
116+
* Describe me
117+
*/
118+
get mediaAdBreakTotalAds(): number | undefined {
119+
return getNumericAttr(this, MediaUIAttributes.MEDIA_AD_BREAK_TOTAL_ADS);
120+
}
121+
122+
set mediaAdBreakTotalAds(val: number | undefined) {
123+
/** @TODO inaccurate type def in media chrome. Accepts/expects nullish. (CJP) */
124+
/** @ts-ignore */
125+
setNumericAttr(this, MediaUIAttributes.MEDIA_AD_BREAK_TOTAL_ADS, val);
126+
}
127+
128+
/**
129+
* Describe me
130+
*/
131+
get mediaAdBreakAdPosition(): number | undefined {
132+
return getNumericAttr(this, MediaUIAttributes.MEDIA_AD_BREAK_AD_POSITION);
133+
}
134+
135+
set mediaAdBreakAdPosition(val: number | undefined) {
136+
/** @TODO inaccurate type def in media chrome. Accepts/expects nullish. (CJP) */
137+
/** @ts-ignore */
138+
setNumericAttr(this, MediaUIAttributes.MEDIA_AD_BREAK_AD_POSITION, val);
139+
}
140+
141+
update(): void {
142+
const label = formatLabel(this);
143+
updateAriaValueText(this);
144+
// Only update if it changed, timeupdate events are called a few times per second.
145+
if (label !== this.#slot.innerHTML) {
146+
this.#slot.innerHTML = label;
147+
}
148+
}
149+
}
150+
151+
if (!globalThis.customElements.get('media-ad-count-display')) {
152+
globalThis.customElements.define('media-ad-count-display', MediaAdCountDisplay);
153+
}
154+
155+
export default MediaAdCountDisplay;

packages/mux-player/src/template.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import 'media-chrome/dist/media-theme-element.js';
22
// @ts-ignore
33
import cssStr from './styles.css';
44
import './dialog';
5+
import './media-chrome/ads/media-ad-count-display';
56
import { getStreamTypeFromAttr } from './helpers';
67
import { html } from './html';
78
import { i18n, stylePropsToString } from './utils';
@@ -83,6 +84,13 @@ export const partsListStr = Object.values(Parts).join(', ');
8384
export const content = (props: MuxTemplateProps) => html`
8485
<media-theme
8586
template="${props.themeTemplate || false}"
87+
mediaadbreak="${/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */ props.adBreak ?? false}"
88+
mediaadbreaktotalads="${
89+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */ props.adBreakTotalAds ?? false
90+
}"
91+
mediaadbreakadposition="${
92+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */ props.adBreakAdPosition ?? false
93+
}"
8694
defaultstreamtype="${props.defaultStreamType ?? false}"
8795
hotkeys="${getHotKeys(props) || false}"
8896
nohotkeys="${props.noHotKeys || !props.hasSrc || props.isDialogOpen || false}"

packages/mux-player/src/themes/gerwig/gerwig.html

Lines changed: 44 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,17 @@
888888
</media-rendition-menu>
889889
</template>
890890

891+
<template partial="AdCountDisplay">
892+
<!-- <template if="mediaadbreaktotalads"> -->
893+
<media-ad-count-display
894+
part="top title display"
895+
class="title-display"
896+
mediaadbreaktotalads="{{mediaadbreaktotalads}}"
897+
mediaadbreakadposition="{{mediaadbreakadposition}}"
898+
></media-ad-count-display>
899+
<!-- </template> -->
900+
</template>
901+
891902
<media-controller
892903
part="controller"
893904
defaultstreamtype="{{defaultstreamtype ?? 'on-demand'}}"
@@ -937,33 +948,43 @@
937948
<div role="button" class="autoplay-unmute-btn">Unmute</div>
938949
</div> -->
939950

940-
<template if="streamtype == 'on-demand'">
941-
<template if="breakpointsm">
942-
<media-control-bar part="control-bar top" slot="top-chrome">{{>TitleDisplay}} </media-control-bar>
951+
<template if="!mediaadbreak">
952+
<template if="streamtype == 'on-demand'">
953+
<template if="breakpointsm">
954+
<media-control-bar part="control-bar top" slot="top-chrome">{{>TitleDisplay}} </media-control-bar>
955+
</template>
956+
{{>TimeRange}}
957+
<media-control-bar part="control-bar bottom">
958+
{{>PlayButton}} {{>SeekBackwardButton}} {{>SeekForwardButton}} {{>TimeDisplay}} {{>MuteButton}}
959+
{{>VolumeRange}}
960+
<div class="spacer"></div>
961+
{{>RenditionMenu}} {{>PlaybackRateMenu}} {{>AudioTrackMenu}} {{>CaptionsMenu}}
962+
{{>AirplayButton}} {{>CastButton}} {{>PipButton}} {{>FullscreenButton}}
963+
</media-control-bar>
943964
</template>
944-
{{>TimeRange}}
945-
<media-control-bar part="control-bar bottom">
946-
{{>PlayButton}} {{>SeekBackwardButton}} {{>SeekForwardButton}} {{>TimeDisplay}} {{>MuteButton}}
947-
{{>VolumeRange}}
948-
<div class="spacer"></div>
949-
{{>RenditionMenu}} {{>PlaybackRateMenu}} {{>AudioTrackMenu}} {{>CaptionsMenu}} {{>AirplayButton}}
950-
{{>CastButton}} {{>PipButton}} {{>FullscreenButton}}
951-
</media-control-bar>
952-
</template>
953965

954-
<template if="streamtype == 'live'">
955-
<media-control-bar part="control-bar top" slot="top-chrome">
956-
{{>LiveButton}}
957-
<template if="breakpointsm"> {{>TitleDisplay}} </template>
958-
</media-control-bar>
959-
<template if="targetlivewindow > 0">{{>TimeRange}}</template>
966+
<template if="streamtype == 'live'">
967+
<media-control-bar part="control-bar top" slot="top-chrome">
968+
{{>LiveButton}}
969+
<template if="breakpointsm"> {{>TitleDisplay}} </template>
970+
</media-control-bar>
971+
<template if="targetlivewindow > 0">{{>TimeRange}}</template>
972+
<media-control-bar part="control-bar bottom">
973+
{{>PlayButton}}
974+
<template if="targetlivewindow > 0">{{>SeekBackwardButton}} {{>SeekForwardButton}}</template>
975+
{{>MuteButton}} {{>VolumeRange}}
976+
<div class="spacer"></div>
977+
{{>RenditionMenu}} {{>AudioTrackMenu}} {{>CaptionsMenu}} {{>AirplayButton}} {{>CastButton}}
978+
{{>PipButton}} {{>FullscreenButton}}
979+
</media-control-bar>
980+
</template>
981+
</template>
982+
<template if="mediaadbreak">
983+
<media-control-bar noautohide part="control-bar top" slot="top-chrome"> {{>AdCountDisplay}} </media-control-bar>
960984
<media-control-bar part="control-bar bottom">
961-
{{>PlayButton}}
962-
<template if="targetlivewindow > 0">{{>SeekBackwardButton}} {{>SeekForwardButton}}</template>
963-
{{>MuteButton}} {{>VolumeRange}}
985+
{{>PlayButton}} {{>TimeDisplay}} {{>MuteButton}} {{>VolumeRange}}
964986
<div class="spacer"></div>
965-
{{>RenditionMenu}} {{>AudioTrackMenu}} {{>CaptionsMenu}} {{>AirplayButton}} {{>CastButton}} {{>PipButton}}
966-
{{>FullscreenButton}}
987+
{{>PipButton}} {{>FullscreenButton}}
967988
</media-control-bar>
968989
</template>
969990
</template>

packages/mux-player/src/types.d.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ export type MuxTemplateProps = Partial<MuxPlayerProps> & {
5757
castReceiver: string | undefined;
5858
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
5959
adTagUrl: string | undefined;
60+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
61+
adBreak: boolean;
62+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
63+
adBreakTotalAds: number | undefined;
64+
/** @TODO Move to separate/extended, ads-only impl/module? (CJP) */
65+
adBreakAdPosition: number | undefined;
6066
};
6167

6268
export type DialogOptions = {

types/media-chrome.d.ts

Lines changed: 0 additions & 6 deletions
This file was deleted.

0 commit comments

Comments
 (0)