From 18474e869620a3c01b8f4d920c17969eeeb62b52 Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Wed, 2 Feb 2022 16:25:02 -0600 Subject: [PATCH 1/7] Feat/default cc enabled (#148) * feat/default-cc-enabled: implementation of default captions enabled for captions button. * (DO NOT MERGE): added default-showing to advanced example for reference in PR revew. * feat/default-cc-enabled: PR feedback changes. --- examples/advanced.html | 2 +- src/js/media-captions-button.js | 41 +++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/examples/advanced.html b/examples/advanced.html index 6d18f8f0b..74bb0675d 100644 --- a/examples/advanced.html +++ b/examples/advanced.html @@ -49,7 +49,7 @@

Media Chrome Advanced Video Usage Example

- + diff --git a/src/js/media-captions-button.js b/src/js/media-captions-button.js index 0e547975f..3825368bf 100644 --- a/src/js/media-captions-button.js +++ b/src/js/media-captions-button.js @@ -109,6 +109,7 @@ class MediaCaptionsButton extends MediaChromeButton { return [ ...super.observedAttributes, 'no-subtitles-fallback', + 'default-showing', MediaUIAttributes.MEDIA_CAPTIONS_LIST, MediaUIAttributes.MEDIA_CAPTIONS_SHOWING, MediaUIAttributes.MEDIA_SUBTITLES_LIST, @@ -118,6 +119,9 @@ class MediaCaptionsButton extends MediaChromeButton { constructor(options = {}) { super({ slotTemplate, ...options }); + // Internal variable to keep track of when we have some or no captions (or subtitles, if using subtitles fallback) + // Used for `default-showing` behavior. + this._captionsReady = false; } connectedCallback() { @@ -136,6 +140,43 @@ class MediaCaptionsButton extends MediaChromeButton { ) { updateAriaChecked(this); } + if ( + this.hasAttribute('default-showing') && // we want to show captions by default + this.getAttribute('aria-checked') !== 'true' // and we aren't currently showing them + ) { + // Make sure we're only checking against the relevant attributes based on whether or not we are using subtitles fallback + const subtitlesIncluded = !this.hasAttribute('no-subtitles-fallback'); + const relevantAttributes = subtitlesIncluded + ? [ + MediaUIAttributes.MEDIA_CAPTIONS_LIST, + MediaUIAttributes.MEDIA_SUBTITLES_LIST, + ] + : [MediaUIAttributes.MEDIA_CAPTIONS_LIST]; + // If one of the relevant attributes changed... + if (relevantAttributes.includes(attrName)) { + // check if we went + // a) from captions (/subs) not ready to captions (/subs) ready + // b) from captions (/subs) ready to captions (/subs) not ready. + // by using a simple truthy (empty or non-empty) string check on the relevant values + // NOTE: We're using `getAttribute` here instead of `newValue` because we may care about + // multiple attributes. + const nextCaptionsReady = + !!this.getAttribute(MediaUIAttributes.MEDIA_CAPTIONS_LIST) || + !!( + subtitlesIncluded && + this.getAttribute(MediaUIAttributes.MEDIA_SUBTITLES_LIST) + ); + // If the value changed, (re)set the internal prop + if (this._captionsReady !== nextCaptionsReady) { + this._captionsReady = nextCaptionsReady; + // If captions are currently ready, that means we went from unready to ready, so + // use the click handler to dispatch a request to turn captions on + if (this._captionsReady) { + this.handleClick(); + } + } + } + } super.attributeChangedCallback(attrName, oldValue, newValue); } From c37d32f86a6fae7c8645f3fe812a675d37cca940 Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Thu, 3 Feb 2022 14:06:42 -0600 Subject: [PATCH 2/7] Feat/configurable seek (#150) * feat/configurable-seek: Add functionality to configure seek offsets for seek forward and backward buttons. * feat/configurable-seek: update advanced example to demo configurable seek. * feat/configurable-seek: update examples and docs. * feat/configurable-seek: update icons. PR feedback updates. --- docs/media-seek-backward-button.md | 6 ++- docs/media-seek-forward-button.md | 4 +- examples/advanced.html | 4 +- .../media-seek-backward-button.html | 7 ++++ .../media-seek-forward-button.html | 7 ++++ src/js/media-seek-backward-button.js | 28 ++++++++++++-- src/js/media-seek-forward-button.js | 38 ++++++++++++++++--- src/js/utils/element-utils.js | 16 ++++++++ 8 files changed, 97 insertions(+), 13 deletions(-) create mode 100644 src/js/utils/element-utils.js diff --git a/docs/media-seek-backward-button.md b/docs/media-seek-backward-button.md index 1d715802d..571c1905f 100644 --- a/docs/media-seek-backward-button.md +++ b/docs/media-seek-backward-button.md @@ -7,7 +7,11 @@ Button to jump back 30 seconds in the media. # Attributes -_None_ +# Attributes + +| Name | Type | Default Value | Description | +| ------------- | -------- | ------------- | ------------------------------------------------------------------- | +| `seek-offset` | `number` | `30` | Adjusts how much time (in seconds) the playhead should seek forward | # Slots diff --git a/docs/media-seek-forward-button.md b/docs/media-seek-forward-button.md index 3432b5b71..c321158dd 100644 --- a/docs/media-seek-forward-button.md +++ b/docs/media-seek-forward-button.md @@ -7,7 +7,9 @@ Button to jump ahead 30 seconds in the media. # Attributes -_None_ +| Name | Type | Default Value | Description | +| ------------- | -------- | ------------- | -------------------------------------------------------------------- | +| `seek-offset` | `number` | `30` | Adjusts how much time (in seconds) the playhead should seek backward | # Slots diff --git a/examples/advanced.html b/examples/advanced.html index 74bb0675d..910b046a7 100644 --- a/examples/advanced.html +++ b/examples/advanced.html @@ -43,8 +43,8 @@

Media Chrome Advanced Video Usage Example

- - + + diff --git a/examples/control-elements/media-seek-backward-button.html b/examples/control-elements/media-seek-backward-button.html index 0d5751e73..3865e57f3 100644 --- a/examples/control-elements/media-seek-backward-button.html +++ b/examples/control-elements/media-seek-backward-button.html @@ -12,6 +12,13 @@

<media-seek-backward-button>

+

Adjust seek offset (10 seconds)

+

+ <media-seek-backward-button seek-offset="10"> +

+ + +

Alternate content

Back diff --git a/examples/control-elements/media-seek-forward-button.html b/examples/control-elements/media-seek-forward-button.html index 3ac46fd21..742862794 100644 --- a/examples/control-elements/media-seek-forward-button.html +++ b/examples/control-elements/media-seek-forward-button.html @@ -11,6 +11,13 @@

<media-seek-forward-button>

+ +

Adjust seek offset (10 seconds)

+

+ <media-seek-forward-button seek-offset="10"> +

+ +

Alternate content

diff --git a/src/js/media-seek-backward-button.js b/src/js/media-seek-backward-button.js index 3cf4808f4..306a8e9a5 100644 --- a/src/js/media-seek-backward-button.js +++ b/src/js/media-seek-backward-button.js @@ -6,9 +6,12 @@ import { } from './utils/server-safe-globals.js'; import { MediaUIEvents, MediaUIAttributes } from './constants.js'; import { verbs } from './labels/labels.js'; +import { getSlotted, updateIconText } from './utils/element-utils.js'; + +const DEFAULT_SEEK_OFFSET = 30; const backwardIcon = - ''; + ``; const slotTemplate = document.createElement('template'); slotTemplate.innerHTML = ` @@ -16,7 +19,6 @@ slotTemplate.innerHTML = ` `; const DEFAULT_TIME = 0; -const DEFAULT_SEEK_OFFSET = -30; const updateAriaLabel = (el) => { // NOTE: seek direction is described via text, so always use positive numeric representation @@ -25,6 +27,12 @@ const updateAriaLabel = (el) => { el.setAttribute('aria-label', label); }; +const updateSeekIconValue = (el) => { + const svg = getSlotted(el, 'backward'); + const value = el.getAttribute('seek-offset'); + updateIconText(svg, value); +}; + class MediaSeekBackwardButton extends MediaChromeButton { static get observedAttributes() { return [...super.observedAttributes, MediaUIAttributes.MEDIA_CURRENT_TIME]; @@ -36,19 +44,33 @@ class MediaSeekBackwardButton extends MediaChromeButton { connectedCallback() { // NOTE: currently don't support changing the seek value, so only need to set this once on initialization. + if (!this.hasAttribute('seek-offset')) { + this.setAttribute('seek-offset', DEFAULT_SEEK_OFFSET); + } updateAriaLabel(this); + updateSeekIconValue(this); super.connectedCallback(); } + attributeChangedCallback(attrName, _oldValue, newValue) { + if (attrName === 'seek-offset') { + if (newValue == undefined) { + this.setAttribute('seek-offset', DEFAULT_SEEK_OFFSET); + } + updateSeekIconValue(this); + } + } + handleClick() { const currentTimeStr = this.getAttribute( MediaUIAttributes.MEDIA_CURRENT_TIME ); + const seekOffset = +this.getAttribute('seek-offset'); const currentTime = currentTimeStr && !Number.isNaN(+currentTimeStr) ? +currentTimeStr : DEFAULT_TIME; - const detail = Math.max(currentTime + DEFAULT_SEEK_OFFSET, 0); + const detail = Math.max(currentTime - seekOffset, 0); const evt = new window.CustomEvent(MediaUIEvents.MEDIA_SEEK_REQUEST, { composed: true, bubbles: true, diff --git a/src/js/media-seek-forward-button.js b/src/js/media-seek-forward-button.js index fcd46dc84..9951fdc3e 100644 --- a/src/js/media-seek-forward-button.js +++ b/src/js/media-seek-forward-button.js @@ -6,9 +6,12 @@ import { } from './utils/server-safe-globals.js'; import { MediaUIEvents, MediaUIAttributes } from './constants.js'; import { verbs } from './labels/labels.js'; +import { getSlotted, updateIconText } from './utils/element-utils.js'; -const forwardIcon = - ''; +const DEFAULT_SEEK_OFFSET = 30; + +// const forwardIcon = ``; +const forwardIcon = ``; const slotTemplate = document.createElement('template'); slotTemplate.innerHTML = ` @@ -16,18 +19,27 @@ slotTemplate.innerHTML = ` `; const DEFAULT_TIME = 0; -const DEFAULT_SEEK_OFFSET = 30; const updateAriaLabel = (el) => { // NOTE: seek direction is described via text, so always use positive numeric representation - const seekOffset = Math.abs(DEFAULT_SEEK_OFFSET); + const seekOffset = Math.abs(+el.getAttribute('seek-offset')); const label = verbs.SEEK_FORWARD_N_SECS({ seekOffset }); el.setAttribute('aria-label', label); }; +const updateSeekIconValue = (el) => { + const svg = getSlotted(el, 'forward'); + const value = el.getAttribute('seek-offset'); + updateIconText(svg, value); +}; + class MediaSeekForwardButton extends MediaChromeButton { static get observedAttributes() { - return [...super.observedAttributes, MediaUIAttributes.MEDIA_CURRENT_TIME]; + return [ + ...super.observedAttributes, + MediaUIAttributes.MEDIA_CURRENT_TIME, + 'seek-offset', + ]; } constructor(options = {}) { @@ -36,19 +48,33 @@ class MediaSeekForwardButton extends MediaChromeButton { connectedCallback() { // NOTE: currently don't support changing the seek value, so only need to set this once on initialization. + if (!this.hasAttribute('seek-offset')) { + this.setAttribute('seek-offset', DEFAULT_SEEK_OFFSET); + } updateAriaLabel(this); + updateSeekIconValue(this); super.connectedCallback(); } + attributeChangedCallback(attrName, _oldValue, newValue) { + if (attrName === 'seek-offset') { + if (newValue == undefined) { + this.setAttribute('seek-offset', DEFAULT_SEEK_OFFSET); + } + updateSeekIconValue(this); + } + } + handleClick() { const currentTimeStr = this.getAttribute( MediaUIAttributes.MEDIA_CURRENT_TIME ); + const seekOffset = +this.getAttribute('seek-offset'); const currentTime = currentTimeStr && !Number.isNaN(+currentTimeStr) ? +currentTimeStr : DEFAULT_TIME; - const detail = currentTime + DEFAULT_SEEK_OFFSET; + const detail = currentTime + seekOffset; const evt = new window.CustomEvent(MediaUIEvents.MEDIA_SEEK_REQUEST, { composed: true, bubbles: true, diff --git a/src/js/utils/element-utils.js b/src/js/utils/element-utils.js new file mode 100644 index 000000000..a8d29d05c --- /dev/null +++ b/src/js/utils/element-utils.js @@ -0,0 +1,16 @@ +export const updateIconText = (svg, value, selector = '.value') => { + const node = svg.querySelector(selector); + + if (!node) return; + + node.textContent = value; +}; + +export const getAllSlotted = (el, name) => { + const slotSelector = `slot[name="${name}"]`; + const slot = el.shadowRoot.querySelector(slotSelector); + if (!slot) return []; + return slot.children; +}; + +export const getSlotted = (el, name) => getAllSlotted(el, name)[0]; From a1fc52306788dd3ce265fbbd8840a1078a3c58b9 Mon Sep 17 00:00:00 2001 From: Wesley Luyten Date: Thu, 3 Feb 2022 15:10:37 -0500 Subject: [PATCH 3/7] Use css prop background instead background-color (#151) --- docs/styling.md | 66 +++++++++++++++++------------------ src/js/media-chrome-button.js | 6 ++-- src/js/media-chrome-range.js | 6 ++-- src/js/media-text-display.js | 2 +- 4 files changed, 40 insertions(+), 40 deletions(-) diff --git a/docs/styling.md b/docs/styling.md index 49e0092be..bc759652a 100644 --- a/docs/styling.md +++ b/docs/styling.md @@ -19,15 +19,15 @@ Our current styling architecture is still quite nascent and is very likely to un - `` ([docs](./media-seek-backward-button.md)) - `` ([docs](./media-seek-forward-button.md)) -| Name | CSS Property | Default Value | Description | Notes | -| ---------------------------------- | ------------------ | --------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--media-control-background` | `background-color` | `rgba(20,20,30, 0.7)` | background color of the component | Applies to other components as well ([See notes below \*\*](#notes)) | -| `--media-control-hover-background` | `background-color` | `rgba(50,50,70, 0.7)` | background color of the button when hovered | Applied by the `:hover` [pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) Applies to other components as well ([See notes below \*\*](#notes)) | -| `--media-button-icon-width` | `width` | `24px` | default width of button icons | Only applies to `` and `` tags | -| `--media-button-icon-height` | `height` | none | default height of button icons | Only applies to `` and `` tags | -| `--media-icon-color` | `fill` | `#eee` | default fill color of button icons | Only applies to `` and `` tags | -| `--media-button-icon-transform` | `transform` | none | apply a [transform](https://developer.mozilla.org/en-US/docs/Web/CSS/transform) to button icons | Only applies to `` and `` tags | -| `--media-button-icon-transition` | `transform` | none | apply a [transition](https://developer.mozilla.org/en-US/docs/Web/CSS/transition) to button icons | Only applies to `` and `` tags | +| Name | CSS Property | Default Value | Description | Notes | +| ---------------------------------- | ------------ | --------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--media-control-background` | `background` | `rgba(20,20,30, 0.7)` | background color of the component | Applies to other components as well ([See notes below \*\*](#notes)) | +| `--media-control-hover-background` | `background` | `rgba(50,50,70, 0.7)` | background color of the button when hovered | Applied by the `:hover` [pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) Applies to other components as well ([See notes below \*\*](#notes)) | +| `--media-button-icon-width` | `width` | `24px` | default width of button icons | Only applies to `` and `` tags | +| `--media-button-icon-height` | `height` | none | default height of button icons | Only applies to `` and `` tags | +| `--media-icon-color` | `fill` | `#eee` | default fill color of button icons | Only applies to `` and `` tags | +| `--media-button-icon-transform` | `transform` | none | apply a [transform](https://developer.mozilla.org/en-US/docs/Web/CSS/transform) to button icons | Only applies to `` and `` tags | +| `--media-button-icon-transition` | `transform` | none | apply a [transition](https://developer.mozilla.org/en-US/docs/Web/CSS/transition) to button icons | Only applies to `` and `` tags | ## Ranges @@ -36,26 +36,26 @@ Our current styling architecture is still quite nascent and is very likely to un - `` ([docs](./media-time-range.md)) - `` ([docs](./media-volume-range.md)) -| Name | CSS Property | Default Value | Description | Notes | -| ----------------------------------- | ------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `--media-control-background` | `background-color` | `rgba(20,20,30, 0.7)` | background color of the component | Applies to other components as well ([See notes below \*\*](#notes)) | -| `--media-control-hover-background` | `background-color` | `rgba(50,50,70, 0.7)` | background color of the button when hovered | Applied by the `:hover` [pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) Applies to other components as well ([See notes below \*\*](#notes)) | -| `--media-range-thumb-height` | `height` | `10px` | height of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-thumb-width` | `width` | `10px` | width of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-thumb-border` | `border` | `none` | border of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-thumb-border-radius` | `border-radius` | `10px` | border radius of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-thumb-background` | `background` | `#fff` | background color of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-thumb-box-shadow` | `box-shadow` | `1px 1px 1px transparent` | box shadow of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-thumb-transition` | `transition` | `none` | apply a [transition](https://developer.mozilla.org/en-US/docs/Web/CSS/transition) to the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-thumb-transform` | `transform` | `none` | apply a [transform](https://developer.mozilla.org/en-US/docs/Web/CSS/transform) to the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-thumb-opacity` | `opacity` | `1` | opacity of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | -| `--media-range-track-height` | `height` | `4px` | height of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | -| `--media-range-track-width` | `width` | `100%` | width of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | -| `--media-range-track-border` | `border` | `none` | border of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | -| `--media-range-track-border-radius` | `border-radius` | `0` | border radius of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | -| `--media-range-track-background` | `background` | `#eee` | background color of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | -| `--media-range-track-box-shadow` | `box-shadow` | `none` | box shadow of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | -| `--media-range-track-transition` | `transition` | `none` | apply a [transition](https://developer.mozilla.org/en-US/docs/Web/CSS/transition) to the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | +| Name | CSS Property | Default Value | Description | Notes | +| ----------------------------------- | --------------- | ------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--media-control-background` | `background` | `rgba(20,20,30, 0.7)` | background color of the component | Applies to other components as well ([See notes below \*\*](#notes)) | +| `--media-control-hover-background` | `background` | `rgba(50,50,70, 0.7)` | background color of the button when hovered | Applied by the `:hover` [pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) Applies to other components as well ([See notes below \*\*](#notes)) | +| `--media-range-thumb-height` | `height` | `10px` | height of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-thumb-width` | `width` | `10px` | width of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-thumb-border` | `border` | `none` | border of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-thumb-border-radius` | `border-radius` | `10px` | border radius of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-thumb-background` | `background` | `#fff` | background color of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-thumb-box-shadow` | `box-shadow` | `1px 1px 1px transparent` | box shadow of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-thumb-transition` | `transition` | `none` | apply a [transition](https://developer.mozilla.org/en-US/docs/Web/CSS/transition) to the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-thumb-transform` | `transform` | `none` | apply a [transform](https://developer.mozilla.org/en-US/docs/Web/CSS/transform) to the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-thumb-opacity` | `opacity` | `1` | opacity of the underlying slider's drag thumb | Applied via `::-webkit-slider-thumb` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-thumb) | +| `--media-range-track-height` | `height` | `4px` | height of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | +| `--media-range-track-width` | `width` | `100%` | width of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | +| `--media-range-track-border` | `border` | `none` | border of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | +| `--media-range-track-border-radius` | `border-radius` | `0` | border radius of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | +| `--media-range-track-background` | `background` | `#eee` | background color of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | +| `--media-range-track-box-shadow` | `box-shadow` | `none` | box shadow of the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | +| `--media-range-track-transition` | `transition` | `none` | apply a [transition](https://developer.mozilla.org/en-US/docs/Web/CSS/transition) to the underlying slider's track display | Applied via `::-webkit-slider-runnable-track` and similar [pseudo-element selectors](https://developer.mozilla.org/en-US/docs/Web/CSS/::-webkit-slider-runnable-track) | ## Text Displays @@ -63,10 +63,10 @@ Our current styling architecture is still quite nascent and is very likely to un - `` ([docs](./media-time-display.md)) -| Name | CSS Property | Default Value | Description | Notes | -| ----------------------------- | ------------------ | --------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------- | -| `--media-control-background` | `background-color` | `rgba(20,20,30, 0.7)` | background color of the component | Applies to other components as well ([See notes below \*\*](#notes)) | -| `--media-text-content-height` | `height` | `24px` | height of the underlying text container for text-based elements | Also applies to `` ([See §Buttons](#Buttons)) | +| Name | CSS Property | Default Value | Description | Notes | +| ----------------------------- | ------------ | --------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------- | +| `--media-control-background` | `background` | `rgba(20,20,30, 0.7)` | background color of the component | Applies to other components as well ([See notes below \*\*](#notes)) | +| `--media-text-content-height` | `height` | `24px` | height of the underlying text container for text-based elements | Also applies to `` ([See §Buttons](#Buttons)) | ## Indicators diff --git a/src/js/media-chrome-button.js b/src/js/media-chrome-button.js index cb9fb0190..fc19a9ce0 100644 --- a/src/js/media-chrome-button.js +++ b/src/js/media-chrome-button.js @@ -15,7 +15,7 @@ template.innerHTML = ` height: auto; vertical-align: middle; box-sizing: border-box; - background-color: var(--media-control-background, rgba(20,20,30, 0.7)); + background: var(--media-control-background, rgba(20,20,30, 0.7)); padding: 10px; @@ -25,7 +25,7 @@ template.innerHTML = ` color: #ffffff; text-align: center; - transition: background-color 0.15s linear; + transition: background 0.15s linear; pointer-events: auto; cursor: pointer; @@ -43,7 +43,7 @@ template.innerHTML = ` } :host(:hover) { - background-color: var(--media-control-hover-background, rgba(50,50,70, 0.7)); + background: var(--media-control-hover-background, rgba(50,50,70, 0.7)); } svg, img, ::slotted(svg), ::slotted(img) { diff --git a/src/js/media-chrome-range.js b/src/js/media-chrome-range.js index 002030605..5e4d613db 100644 --- a/src/js/media-chrome-range.js +++ b/src/js/media-chrome-range.js @@ -45,8 +45,8 @@ template.innerHTML = ` display: inline-block; vertical-align: middle; box-sizing: border-box; - background-color: var(--media-control-background, rgba(20,20,30, 0.7)); - transition: background-color 0.15s linear; + background: var(--media-control-background, rgba(20,20,30, 0.7)); + transition: background 0.15s linear; height: 44px; width: 100px; padding: 0 10px; @@ -64,7 +64,7 @@ template.innerHTML = ` } :host(:hover) { - background-color: var(--media-control-hover-background, rgba(50,50,60, 0.7)); + background: var(--media-control-hover-background, rgba(50,50,60, 0.7)); } input[type=range] { diff --git a/src/js/media-text-display.js b/src/js/media-text-display.js index 01ba402cf..a84f3b45a 100644 --- a/src/js/media-text-display.js +++ b/src/js/media-text-display.js @@ -15,7 +15,7 @@ template.innerHTML = ` justify-content: center; align-items: center; box-sizing: border-box; - background-color: var(--media-control-background, rgba(20,20,30, 0.7)); + background: var(--media-control-background, rgba(20,20,30, 0.7)); padding: 10px; From 4595debcf1609b2385d0cb03b28678a811cb3a8a Mon Sep 17 00:00:00 2001 From: Steve Heffernan Date: Thu, 3 Feb 2022 15:25:37 -0800 Subject: [PATCH 4/7] Fixed mute button default state (#153) fixes #140 Also updated media controller APIs to include needed functions and centralized logic --- snowpack.dev.config.cjs | 1 + src/js/media-controller.js | 92 +++++++++++++++++++++++++------------- 2 files changed, 63 insertions(+), 30 deletions(-) diff --git a/snowpack.dev.config.cjs b/snowpack.dev.config.cjs index e83cfb06e..d25aab7be 100644 --- a/snowpack.dev.config.cjs +++ b/snowpack.dev.config.cjs @@ -8,6 +8,7 @@ module.exports = { 'src/js': { url: '/dist' }, // Mount "public" to the root URL path ("/*") and serve files with zero transformations: examples: { url: '/examples', static: true, resolve: false }, + sandbox: { url: '/sandbox', static: true, resolve: false }, }, routes: [ { diff --git a/src/js/media-controller.js b/src/js/media-controller.js index d7de164c6..007fb598f 100644 --- a/src/js/media-controller.js +++ b/src/js/media-controller.js @@ -247,26 +247,15 @@ class MediaController extends MediaContainer { // Pass media state to child and associated control elements this._mediaStatePropagators = { 'play,pause': () => { - this.propagateMediaState( - MediaUIAttributes.MEDIA_PAUSED, - this.media.paused - ); + this.propagateMediaState(MediaUIAttributes.MEDIA_PAUSED, this.paused); }, volumechange: () => { - const { muted, volume } = this.media; - - let level = 'high'; - if (volume == 0 || muted) { - level = 'off'; - } else if (volume < 0.5) { - level = 'low'; - } else if (volume < 0.75) { - level = 'medium'; - } - - this.propagateMediaState(MediaUIAttributes.MEDIA_MUTED, muted); - this.propagateMediaState(MediaUIAttributes.MEDIA_VOLUME, volume); - this.propagateMediaState(MediaUIAttributes.MEDIA_VOLUME_LEVEL, level); + this.propagateMediaState(MediaUIAttributes.MEDIA_MUTED, this.muted); + this.propagateMediaState(MediaUIAttributes.MEDIA_VOLUME, this.volume); + this.propagateMediaState( + MediaUIAttributes.MEDIA_VOLUME_LEVEL, + this.volumeLevel + ); }, [fullscreenApi.event]: () => { // Might be in the shadow dom @@ -284,26 +273,26 @@ class MediaController extends MediaContainer { if (e) { isPip = e.type == 'enterpictureinpicture'; } else { - isPip = this.media == this.getRootNode().pictureInPictureElement; + isPip = this.isPip; } this.propagateMediaState(MediaUIAttributes.MEDIA_IS_PIP, isPip); }, 'timeupdate,loadedmetadata': () => { this.propagateMediaState( MediaUIAttributes.MEDIA_CURRENT_TIME, - this.media.currentTime + this.currentTime ); }, 'durationchange,loadedmetadata': () => { this.propagateMediaState( MediaUIAttributes.MEDIA_DURATION, - this.media.duration + this.duration ); }, ratechange: () => { this.propagateMediaState( MediaUIAttributes.MEDIA_PLAYBACK_RATE, - this.media.playbackRate + this.playbackRate ); }, 'waiting,playing': () => { @@ -426,6 +415,7 @@ class MediaController extends MediaContainer { associateElement(element) { if (!element) return; const { associatedElementSubscriptions } = this; + if (associatedElementSubscriptions.has(element)) return; const registerMediaStateReceiver = @@ -503,18 +493,23 @@ class MediaController extends MediaContainer { propagateMediaState( [el], MediaUIAttributes.MEDIA_PAUSED, - this.media.paused + this.paused ); // propagateMediaState([el], MediaUIAttributes.MEDIA_VOLUME_LEVEL, level); propagateMediaState( [el], MediaUIAttributes.MEDIA_MUTED, - this.media.muted + this.muted ); propagateMediaState( [el], MediaUIAttributes.MEDIA_VOLUME, - this.media.volume + this.volume + ); + propagateMediaState( + [el], + MediaUIAttributes.MEDIA_VOLUME_LEVEL, + this.volumeLevel ); // const fullscreenEl = this.getRootNode()[fullscreenApi.element]; // propagateMediaState([el], MediaUIAttributes.MEDIA_IS_FULLSCREEN, fullscreenEl === this); @@ -522,17 +517,17 @@ class MediaController extends MediaContainer { propagateMediaState( [el], MediaUIAttributes.MEDIA_CURRENT_TIME, - this.media.currentTime + this.currentTime ); propagateMediaState( [el], MediaUIAttributes.MEDIA_DURATION, - this.media.duration + this.duration ); propagateMediaState( [el], MediaUIAttributes.MEDIA_PLAYBACK_RATE, - this.media.playbackRate + this.playbackRate ); } } @@ -557,6 +552,12 @@ class MediaController extends MediaContainer { this.dispatchEvent(new window.CustomEvent(MEDIA_PAUSE_REQUEST)); } + get paused() { + if (!this.media) return true; + + return this.media.paused; + } + get muted() { return !!(this.media && this.media.muted); } @@ -578,6 +579,24 @@ class MediaController extends MediaContainer { ); } + get volumeLevel() { + let level = 'high'; + + if (!this.media) return level; + + const { muted, volume } = this.media; + + if (volume === 0 || muted) { + level = 'off'; + } else if (volume < 0.5) { + level = 'low'; + } else if (volume < 0.75) { + level = 'medium'; + } + + return level; + } + requestFullscreen() { this.dispatchEvent(new window.CustomEvent(MEDIA_ENTER_FULLSCREEN_REQUEST)); } @@ -598,6 +617,12 @@ class MediaController extends MediaContainer { ); } + get duration() { + const media = this.media; + + return media ? media.duration : NaN; + } + get playbackRate() { const media = this.media; @@ -632,6 +657,12 @@ class MediaController extends MediaContainer { }); } + get isPip() { + return ( + this.media && this.media == this.getRootNode().pictureInPictureElement + ); + } + requestPictureInPicture() { this.dispatchEvent(new window.CustomEvent(MEDIA_ENTER_PIP_REQUEST)); } @@ -663,8 +694,9 @@ const getMediaUIAttributesFrom = (child) => { ); }; -const isMediaStateReceiver = (child) => - !!getMediaUIAttributesFrom(child).length; +const isMediaStateReceiver = (child) => { + return !!getMediaUIAttributesFrom(child).length; +}; const setAttr = (child, attrName, attrValue) => { if (attrValue == undefined) { From a33737d2d99d3e86af8f03e0cfdcdc3182652302 Mon Sep 17 00:00:00 2001 From: Christian Pillsbury Date: Tue, 1 Feb 2022 13:21:18 -0600 Subject: [PATCH 5/7] improv/icons-cleanup: initial integration of new icons (no other changes). --- examples/advanced.html | 1 + src/js/media-airplay-button.js | 2 +- src/js/media-captions-button.js | 63 +------------------------------ src/js/media-fullscreen-button.js | 10 +---- src/js/media-mute-button.js | 6 +-- src/js/media-pip-button.js | 2 +- src/js/media-play-button.js | 10 ++++- 7 files changed, 18 insertions(+), 76 deletions(-) diff --git a/examples/advanced.html b/examples/advanced.html index 910b046a7..4a2626ccb 100644 --- a/examples/advanced.html +++ b/examples/advanced.html @@ -53,6 +53,7 @@

Media Chrome Advanced Video Usage Example

+
diff --git a/src/js/media-airplay-button.js b/src/js/media-airplay-button.js index 3224c61e1..038603314 100644 --- a/src/js/media-airplay-button.js +++ b/src/js/media-airplay-button.js @@ -6,7 +6,7 @@ import { } from './utils/server-safe-globals.js'; import { MediaUIEvents, MediaUIAttributes } from './constants.js'; import { verbs } from './labels/labels.js'; -const airplayIcon = `Mux Player SVG Icons_v2`; +const airplayIcon = `Mux Player SVG Icons_v3`; const slotTemplate = document.createElement('template'); // Followup task: determine how/where we want to handle checks and style updates for (1) unsupported & (2) unavailable diff --git a/src/js/media-captions-button.js b/src/js/media-captions-button.js index 3825368bf..8c3567274 100644 --- a/src/js/media-captions-button.js +++ b/src/js/media-captions-button.js @@ -8,68 +8,9 @@ import { MediaUIEvents, MediaUIAttributes } from './constants.js'; import { nouns } from './labels/labels.js'; import { splitTextTracksStr } from './utils/captions.js'; -const ccIconOn = ` - -`; +const ccIconOn = `Mux Player SVG Icons_v3`; -const ccIconOff = ` - -`; +const ccIconOff = `Mux Player SVG Icons_v3`; const slotTemplate = document.createElement('template'); slotTemplate.innerHTML = ` diff --git a/src/js/media-fullscreen-button.js b/src/js/media-fullscreen-button.js index 00e5ef41c..ec8aa57bc 100644 --- a/src/js/media-fullscreen-button.js +++ b/src/js/media-fullscreen-button.js @@ -15,15 +15,9 @@ import { import { MediaUIEvents, MediaUIAttributes } from './constants.js'; import { verbs } from './labels/labels.js'; -const enterFullscreenIcon = ``; +const enterFullscreenIcon = `Mux Player SVG Icons_v3`; -const exitFullscreenIcon = ``; +const exitFullscreenIcon = `Mux Player SVG Icons_v3`; const slotTemplate = document.createElement('template'); slotTemplate.innerHTML = ` diff --git a/src/js/media-mute-button.js b/src/js/media-mute-button.js index a8cfad585..83a755287 100644 --- a/src/js/media-mute-button.js +++ b/src/js/media-mute-button.js @@ -8,13 +8,13 @@ import { MediaUIEvents, MediaUIAttributes } from './constants.js'; import { verbs } from './labels/labels.js'; const offIcon = - ''; + 'Mux Player SVG Icons_v3'; const lowIcon = - ''; + 'Mux Player SVG Icons_v3'; const highIcon = - ''; + 'Mux Player SVG Icons_v3'; const slotTemplate = document.createElement('template'); slotTemplate.innerHTML = ` diff --git a/src/js/media-pip-button.js b/src/js/media-pip-button.js index ef8deb356..b4680e5cb 100644 --- a/src/js/media-pip-button.js +++ b/src/js/media-pip-button.js @@ -8,7 +8,7 @@ import { MediaUIEvents, MediaUIAttributes } from './constants.js'; import { verbs } from './labels/labels.js'; const pipIcon = - ''; + 'Mux Player SVG Icons_v3'; const slotTemplate = document.createElement('template'); slotTemplate.innerHTML = ` diff --git a/src/js/media-play-button.js b/src/js/media-play-button.js index 784b85434..949b715d9 100644 --- a/src/js/media-play-button.js +++ b/src/js/media-play-button.js @@ -8,9 +8,15 @@ import { MediaUIEvents, MediaUIAttributes } from './constants.js'; import { verbs } from './labels/labels.js'; const playIcon = - ''; + ` + +`; const pauseIcon = - ''; + ` + +`; const slotTemplate = document.createElement('template'); slotTemplate.innerHTML = ` From fc06cb73359e70a17eef636afe110af0207c4908 Mon Sep 17 00:00:00 2001 From: Wesley Luyten Date: Tue, 1 Feb 2022 19:13:10 -0600 Subject: [PATCH 6/7] Fix icons layout issues --- docs/styling.md | 6 +++--- src/js/media-chrome-button.js | 16 +++++++--------- src/js/media-playback-rate-button.js | 4 ++-- src/js/media-text-display.js | 2 +- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/docs/styling.md b/docs/styling.md index bc759652a..318765164 100644 --- a/docs/styling.md +++ b/docs/styling.md @@ -23,8 +23,8 @@ Our current styling architecture is still quite nascent and is very likely to un | ---------------------------------- | ------------ | --------------------- | ------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `--media-control-background` | `background` | `rgba(20,20,30, 0.7)` | background color of the component | Applies to other components as well ([See notes below \*\*](#notes)) | | `--media-control-hover-background` | `background` | `rgba(50,50,70, 0.7)` | background color of the button when hovered | Applied by the `:hover` [pseudo-class](https://developer.mozilla.org/en-US/docs/Web/CSS/:hover) Applies to other components as well ([See notes below \*\*](#notes)) | -| `--media-button-icon-width` | `width` | `24px` | default width of button icons | Only applies to `` and `` tags | -| `--media-button-icon-height` | `height` | none | default height of button icons | Only applies to `` and `` tags | +| `--media-button-icon-width` | `width` | none | default width of button icons | Only applies to `` and `` tags | +| `--media-button-icon-height` | `height` | `18px` | default height of button icons | Only applies to `` and `` tags | | `--media-icon-color` | `fill` | `#eee` | default fill color of button icons | Only applies to `` and `` tags | | `--media-button-icon-transform` | `transform` | none | apply a [transform](https://developer.mozilla.org/en-US/docs/Web/CSS/transform) to button icons | Only applies to `` and `` tags | | `--media-button-icon-transition` | `transform` | none | apply a [transition](https://developer.mozilla.org/en-US/docs/Web/CSS/transition) to button icons | Only applies to `` and `` tags | @@ -66,7 +66,7 @@ Our current styling architecture is still quite nascent and is very likely to un | Name | CSS Property | Default Value | Description | Notes | | ----------------------------- | ------------ | --------------------- | --------------------------------------------------------------- | -------------------------------------------------------------------- | | `--media-control-background` | `background` | `rgba(20,20,30, 0.7)` | background color of the component | Applies to other components as well ([See notes below \*\*](#notes)) | -| `--media-text-content-height` | `height` | `24px` | height of the underlying text container for text-based elements | Also applies to `` ([See §Buttons](#Buttons)) | +| `--media-text-content-height` | `height` | `18px` | height of the underlying text container for text-based elements | Also applies to `` ([See §Buttons](#Buttons)) | ## Indicators diff --git a/src/js/media-chrome-button.js b/src/js/media-chrome-button.js index fc19a9ce0..28200cdb6 100644 --- a/src/js/media-chrome-button.js +++ b/src/js/media-chrome-button.js @@ -10,10 +10,11 @@ const template = document.createElement('template'); template.innerHTML = ` `; diff --git a/src/js/media-playback-rate-button.js b/src/js/media-playback-rate-button.js index dee2fbc79..24d9e379e 100644 --- a/src/js/media-playback-rate-button.js +++ b/src/js/media-playback-rate-button.js @@ -19,12 +19,12 @@ slotTemplate.innerHTML = ` diff --git a/src/js/media-text-display.js b/src/js/media-text-display.js index a84f3b45a..5614b6e3f 100644 --- a/src/js/media-text-display.js +++ b/src/js/media-text-display.js @@ -20,7 +20,7 @@ template.innerHTML = ` padding: 10px; font-size: 14px; - line-height: 24px; + line-height: 18px; font-family: Arial, sans-serif; text-align: center; color: #ffffff; From ed28dfebba00b75ee0e851f3534f3000c2eb4f35 Mon Sep 17 00:00:00 2001 From: Wesley Luyten Date: Wed, 2 Feb 2022 19:01:52 -0600 Subject: [PATCH 7/7] Add dist folder for NPM consumption in players --- .gitignore | 1 - dist/constants.js | 1 + dist/extras/media-clip-selector/index.js | 117 ++++++++++++++++++ dist/index.js | 1 + dist/labels/labels.js | 1 + dist/media-airplay-button.js | 6 + dist/media-captions-button.js | 17 +++ dist/media-chrome-button.js | 55 +++++++++ dist/media-chrome-range.js | 112 +++++++++++++++++ dist/media-container.js | 99 +++++++++++++++ dist/media-control-bar.js | 20 ++++ dist/media-controller.js | 1 + dist/media-current-time-display.js | 1 + dist/media-duration-display.js | 1 + dist/media-fullscreen-button.js | 17 +++ dist/media-loading-indicator.js | 53 ++++++++ dist/media-mute-button.js | 31 +++++ dist/media-pip-button.js | 17 +++ dist/media-play-button.js | 22 ++++ dist/media-playback-rate-button.js | 15 +++ dist/media-progress-range.js | 1 + dist/media-seek-backward-button.js | 3 + dist/media-seek-forward-button.js | 3 + dist/media-settings-popup.js | 33 +++++ dist/media-text-display.js | 28 +++++ dist/media-thumbnail-preview.js | 18 +++ dist/media-time-display.js | 1 + dist/media-time-range.js | 55 +++++++++ dist/media-title-element.js | 9 ++ dist/media-volume-range.js | 1 + dist/themes/media-theme-netflix.js | 146 +++++++++++++++++++++++ dist/themes/media-theme.js | 1 + dist/utils/captions.js | 1 + dist/utils/defineCustomElement.js | 1 + dist/utils/element-utils.js | 1 + dist/utils/fullscreenApi.js | 1 + dist/utils/server-safe-globals.js | 1 + dist/utils/stringUtils.js | 1 + dist/utils/time.js | 1 + 39 files changed, 893 insertions(+), 1 deletion(-) create mode 100644 dist/constants.js create mode 100644 dist/extras/media-clip-selector/index.js create mode 100644 dist/index.js create mode 100644 dist/labels/labels.js create mode 100644 dist/media-airplay-button.js create mode 100644 dist/media-captions-button.js create mode 100644 dist/media-chrome-button.js create mode 100644 dist/media-chrome-range.js create mode 100644 dist/media-container.js create mode 100644 dist/media-control-bar.js create mode 100644 dist/media-controller.js create mode 100644 dist/media-current-time-display.js create mode 100644 dist/media-duration-display.js create mode 100644 dist/media-fullscreen-button.js create mode 100644 dist/media-loading-indicator.js create mode 100644 dist/media-mute-button.js create mode 100644 dist/media-pip-button.js create mode 100644 dist/media-play-button.js create mode 100644 dist/media-playback-rate-button.js create mode 100644 dist/media-progress-range.js create mode 100644 dist/media-seek-backward-button.js create mode 100644 dist/media-seek-forward-button.js create mode 100644 dist/media-settings-popup.js create mode 100644 dist/media-text-display.js create mode 100644 dist/media-thumbnail-preview.js create mode 100644 dist/media-time-display.js create mode 100644 dist/media-time-range.js create mode 100644 dist/media-title-element.js create mode 100644 dist/media-volume-range.js create mode 100644 dist/themes/media-theme-netflix.js create mode 100644 dist/themes/media-theme.js create mode 100644 dist/utils/captions.js create mode 100644 dist/utils/defineCustomElement.js create mode 100644 dist/utils/element-utils.js create mode 100644 dist/utils/fullscreenApi.js create mode 100644 dist/utils/server-safe-globals.js create mode 100644 dist/utils/stringUtils.js create mode 100644 dist/utils/time.js diff --git a/.gitignore b/.gitignore index 3117af845..7ba16a590 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -dist/ .cache/ node_modules/ yarn-error.log diff --git a/dist/constants.js b/dist/constants.js new file mode 100644 index 000000000..bc11613f7 --- /dev/null +++ b/dist/constants.js @@ -0,0 +1 @@ +export const MediaUIEvents={MEDIA_PLAY_REQUEST:"mediaplayrequest",MEDIA_PAUSE_REQUEST:"mediapauserequest",MEDIA_MUTE_REQUEST:"mediamuterequest",MEDIA_UNMUTE_REQUEST:"mediaunmuterequest",MEDIA_VOLUME_REQUEST:"mediavolumerequest",MEDIA_SEEK_REQUEST:"mediaseekrequest",MEDIA_AIRPLAY_REQUEST:"mediaairplayrequest",MEDIA_ENTER_FULLSCREEN_REQUEST:"mediaenterfullscreenrequest",MEDIA_EXIT_FULLSCREEN_REQUEST:"mediaexitfullscreenrequest",MEDIA_PREVIEW_REQUEST:"mediapreviewrequest",MEDIA_ENTER_PIP_REQUEST:"mediaenterpiprequest",MEDIA_EXIT_PIP_REQUEST:"mediaexitpiprequest",MEDIA_SHOW_TEXT_TRACKS_REQUEST:"mediashowtexttracksrequest",MEDIA_HIDE_TEXT_TRACKS_REQUEST:"mediahidetexttracksrequest",MEDIA_SHOW_CAPTIONS_REQUEST:"mediashowcaptionsrequest",MEDIA_SHOW_SUBTITLES_REQUEST:"mediashowsubtitlesrequest",MEDIA_DISABLE_CAPTIONS_REQUEST:"mediadisablecaptionsrequest",MEDIA_DISABLE_SUBTITLES_REQUEST:"mediadisablesubtitlesrequest",MEDIA_PLAYBACK_RATE_REQUEST:"mediaplaybackraterequest",REGISTER_MEDIA_STATE_RECEIVER:"registermediastatereceiver",UNREGISTER_MEDIA_STATE_RECEIVER:"unregistermediastatereceiver"},MediaUIAttributes={MEDIA_PAUSED:"media-paused",MEDIA_MUTED:"media-muted",MEDIA_VOLUME_LEVEL:"media-volume-level",MEDIA_VOLUME:"media-volume",MEDIA_IS_PIP:"media-is-pip",MEDIA_CAPTIONS_LIST:"media-captions-list",MEDIA_SUBTITLES_LIST:"media-subtitles-list",MEDIA_CAPTIONS_SHOWING:"media-captions-showing",MEDIA_SUBTITLES_SHOWING:"media-subtitles-showing",MEDIA_IS_FULLSCREEN:"media-is-fullscreen",MEDIA_PLAYBACK_RATE:"media-playback-rate",MEDIA_CURRENT_TIME:"media-current-time",MEDIA_DURATION:"media-duration",MEDIA_PREVIEW_IMAGE:"media-preview-image",MEDIA_PREVIEW_COORDS:"media-preview-coords",MEDIA_CHROME_ATTRIBUTES:"media-chrome-attributes",MEDIA_CONTROLLER:"media-controller",MEDIA_LOADING:"media-loading"},TextTrackKinds={SUBTITLES:"subtitles",CAPTIONS:"captions",DESCRIPTIONS:"descriptions",CHAPTERS:"chapters",METADATA:"metadata"},TextTrackModes={DISABLED:"disabled",HIDDEN:"hidden",SHOWING:"showing"},ReadyStates={HAVE_NOTHING:0,HAVE_METADATA:1,HAVE_CURRENT_DATA:2,HAVE_FUTURE_DATA:3,HAVE_ENOUGH_DATA:4}; diff --git a/dist/extras/media-clip-selector/index.js b/dist/extras/media-clip-selector/index.js new file mode 100644 index 000000000..7acd75ff5 --- /dev/null +++ b/dist/extras/media-clip-selector/index.js @@ -0,0 +1,117 @@ +import{defineCustomElement as w}from"../../utils/defineCustomElement.js";import{Window as s,Document as C}from"../../utils/server-safe-globals.js";import{MediaUIEvents as u,MediaUIAttributes as c}from"../../constants.js";const f=C.createElement("template"),v=8,m={100:100,200:200,300:300};function o(p){return Math.max(0,Math.min(1,p))}f.innerHTML=` + +
+ +
+
+
+
+
+
+
+
+
+
+
+`;class E extends s.HTMLElement{static get observedAttributes(){return["thumbnails",c.MEDIA_DURATION,c.MEDIA_CURRENT_TIME]}constructor(){super();const t=this.attachShadow({mode:"open"});this.shadowRoot.appendChild(f.content.cloneNode(!0)),this.draggingEl=null,this.wrapper=this.shadowRoot.querySelector("#selectorContainer"),this.selection=this.shadowRoot.querySelector("#selection"),this.playhead=this.shadowRoot.querySelector("#playhead"),this.leftTrim=this.shadowRoot.querySelector("#leftTrim"),this.spacerFirst=this.shadowRoot.querySelector("#spacerFirst"),this.startHandle=this.shadowRoot.querySelector("#startHandle"),this.spacerMiddle=this.shadowRoot.querySelector("#spacerMiddle"),this.endHandle=this.shadowRoot.querySelector("#endHandle"),this.spacerLast=this.shadowRoot.querySelector("#spacerLast"),this._clickHandler=this.handleClick.bind(this),this._dragStart=this.dragStart.bind(this),this._dragEnd=this.dragEnd.bind(this),this._drag=this.drag.bind(this),this.wrapper.addEventListener("click",this._clickHandler,!1),this.wrapper.addEventListener("touchstart",this._dragStart,!1),s.addEventListener("touchend",this._dragEnd,!1),this.wrapper.addEventListener("touchmove",this._drag,!1),this.wrapper.addEventListener("mousedown",this._dragStart,!1),s.addEventListener("mouseup",this._dragEnd,!1),s.addEventListener("mousemove",this._drag,!1),this.enableThumbnails()}get mediaDuration(){return+this.getAttribute(c.MEDIA_DURATION)}get mediaCurrentTime(){return+this.getAttribute(c.MEDIA_CURRENT_TIME)}getPlayheadBasedOnMouseEvent(t){const e=this.mediaDuration;return e?o(this.getMousePercent(t))*e:void 0}getXPositionFromMouse(t){let e;return["touchstart","touchmove"].includes(t.type)&&(e=t.touches[0].clientX),e||t.clientX}getMousePercent(t){const e=this.wrapper.getBoundingClientRect(),i=(this.getXPositionFromMouse(t)-e.left)/e.width;return o(i)}dragStart(t){t.target===this.startHandle&&(this.draggingEl=this.startHandle),t.target===this.endHandle&&(this.draggingEl=this.endHandle),this.initialX=this.getXPositionFromMouse(t)}dragEnd(t){this.initialX=null,this.draggingEl=null}setSelectionWidth(t,e){let i=t;const a=v*3,n=o(a/e);i=t}handleClick(t){const i=this.getMousePercent(t)*this.mediaDuration;this.isTimestampInBounds(i)&&this.dispatchEvent(new s.CustomEvent(u.MEDIA_SEEK_REQUEST,{composed:!0,bubbles:!0,detail:i}))}mediaCurrentTimeSet(t){const e=o(this.mediaCurrentTime/this.mediaDuration),i=this.wrapper.getBoundingClientRect().width,a=e*i;if(this.playhead.style.left=`${e*100}%`,this.playhead.style.display="block",!this.mediaPaused){const{startTime:n,endTime:d}=this.getCurrentClipBounds();(this.mediaCurrentTimed)&&this.dispatchEvent(new s.CustomEvent(u.MEDIA_SEEK_REQUEST,{composed:!0,bubbles:!0,detail:n}))}}mediaUnsetCallback(t){super.mediaUnsetCallback(t),this.wrapper.removeEventListener("touchstart",this._dragStart),this.wrapper.removeEventListener("touchend",this._dragEnd),this.wrapper.removeEventListener("touchmove",this._drag),this.wrapper.removeEventListener("mousedown",this._dragStart),s.removeEventListener("mouseup",this._dragEnd),s.removeEventListener("mousemove",this._drag)}enableThumbnails(){this.thumbnailPreview=this.shadowRoot.querySelector("media-thumbnail-preview"),this.shadowRoot.querySelector("#thumbnailContainer").classList.add("enabled");let e;const i=()=>{e=l=>{const r=this.mediaDuration;if(!r)return;const h=this.wrapper.getBoundingClientRect(),g=this.getMousePercent(l),b=h.left-this.getBoundingClientRect().left+g*h.width;this.thumbnailPreview.style.left=`${b}px`,this.dispatchEvent(new s.CustomEvent(u.MEDIA_PREVIEW_REQUEST,{composed:!0,bubbles:!0,detail:g*r}))},s.addEventListener("mousemove",e,!1)},a=()=>{s.removeEventListener("mousemove",e)};let n=!1,d=l=>{if(!n&&this.mediaDuration){n=!0,this.thumbnailPreview.style.display="block",i();let r=h=>{h.target!=this&&!this.contains(h.target)&&(this.thumbnailPreview.style.display="none",s.removeEventListener("mousemove",r),n=!1,a())};s.addEventListener("mousemove",r,!1)}this.mediaDuration||(this.thumbnailPreview.style.display="none")};this.addEventListener("mousemove",d,!1)}disableThumbnails(){thumbnailContainer.classList.remove("enabled")}}w("media-clip-selector",E);export default E; diff --git a/dist/index.js b/dist/index.js new file mode 100644 index 000000000..d8f45659c --- /dev/null +++ b/dist/index.js @@ -0,0 +1 @@ +import*as E from"./constants.js";export{E as constants};export{default as labels}from"./labels/labels.js";import*as P from"./utils/time.js";export{P as timeUtils};import r from"./media-airplay-button.js";import m from"./media-chrome-button.js";import i from"./media-controller.js";import s from"./media-chrome-range.js";import n from"./media-control-bar.js";import d from"./media-current-time-display.js";import l from"./media-duration-display.js";import p from"./media-time-display.js";import u from"./media-captions-button.js";import f from"./media-seek-forward-button.js";import c from"./media-fullscreen-button.js";import M from"./media-mute-button.js";import j from"./media-pip-button.js";import b from"./media-play-button.js";import g from"./media-playback-rate-button.js";import y from"./media-progress-range.js";import B from"./media-seek-backward-button.js";import t from"./media-thumbnail-preview.js";import h from"./media-time-range.js";import w from"./media-loading-indicator.js";import C from"./media-title-element.js";import k from"./media-volume-range.js";import{Window as e}from"./utils/server-safe-globals.js";class x extends i{}e.customElements.get("media-chrome")||e.customElements.define("media-chrome",x);class o extends i{constructor(){super();console.warn("MediaChrome: is deprecated. Use .")}}e.customElements.get("media-container")||e.customElements.define("media-container",o);export{r as MediaAirplayButton,m as MediaChromeButton,o as MediaContainer,i as MediaController,s as MediaChromeRange,n as MediaControlBar,d as MediaCurrentTimeDisplay,l as MediaDurationDisplay,p as MediaTimeDisplay,u as MediaCaptionsButton,f as MediaSeekForwardButton,c as MediaFullscreenButton,M as MediaMuteButton,j as MediaPipButton,b as MediaPlayButton,g as MediaPlaybackRateButton,y as MediaProgressRange,B as MediaSeekBackwardButton,t as MediaThumbnailPreview,t as MediaThumbnailPreviewElement,h as MediaTimeRange,C as MediaTitleElement,w as MediaLoadingIndicator,k as MediaVolumeRange}; diff --git a/dist/labels/labels.js b/dist/labels/labels.js new file mode 100644 index 000000000..38c88e3a8 --- /dev/null +++ b/dist/labels/labels.js @@ -0,0 +1 @@ +export const nouns={AUDIO_PLAYER:()=>"audio player",VIDEO_PLAYER:()=>"video player",VOLUME:()=>"volume",SEEK:()=>"seek",CLOSED_CAPTIONS:()=>"closed captions",PLAYBACK_RATE:({playbackRate:e=1}={})=>`current playback rate ${e}`,PLAYBACK_TIME:()=>"playback time",MEDIA_LOADING:()=>"media loading"},verbs={PLAY:()=>"play",PAUSE:()=>"pause",MUTE:()=>"mute",UNMUTE:()=>"unmute",AIRPLAY:()=>"air play",ENTER_FULLSCREEN:()=>"enter fullscreen mode",EXIT_FULLSCREEN:()=>"exit fullscreen mode",ENTER_PIP:()=>"enter picture in picture mode",EXIT_PIP:()=>"exit picture in picture mode",SEEK_FORWARD_N_SECS:({seekOffset:e=30}={})=>`seek forward ${e} seconds`,SEEK_BACK_N_SECS:({seekOffset:e=30}={})=>`seek back ${e} seconds`};export default{...nouns,...verbs}; diff --git a/dist/media-airplay-button.js b/dist/media-airplay-button.js new file mode 100644 index 000000000..42f617d6a --- /dev/null +++ b/dist/media-airplay-button.js @@ -0,0 +1,6 @@ +import o from"./media-chrome-button.js";import{defineCustomElement as r}from"./utils/defineCustomElement.js";import{Window as l,Document as n}from"./utils/server-safe-globals.js";import{MediaUIEvents as i}from"./constants.js";import{verbs as m}from"./labels/labels.js";const u='Mux Player SVG Icons_v3',e=n.createElement("template");e.innerHTML=` + + + ${u} +`;class s extends o{static get observedAttributes(){return[...super.observedAttributes]}constructor(t={}){super({slotTemplate:e,...t})}connectedCallback(){this.setAttribute("aria-label",m.AIRPLAY()),super.connectedCallback()}handleClick(t){const a=new l.CustomEvent(i.MEDIA_AIRPLAY_REQUEST,{composed:!0,bubbles:!0});this.dispatchEvent(a)}}r("media-airplay-button",s);export default s; diff --git a/dist/media-captions-button.js b/dist/media-captions-button.js new file mode 100644 index 000000000..7b2d8b7ef --- /dev/null +++ b/dist/media-captions-button.js @@ -0,0 +1,17 @@ +import E from"./media-chrome-button.js";import{defineCustomElement as T}from"./utils/defineCustomElement.js";import{Window as u,Document as p}from"./utils/server-safe-globals.js";import{MediaUIEvents as A,MediaUIAttributes as t}from"./constants.js";import{nouns as m}from"./labels/labels.js";import{splitTextTracksStr as S}from"./utils/captions.js";const f='Mux Player SVG Icons_v3',C='Mux Player SVG Icons_v3',b=p.createElement("template");b.innerHTML=` + + + ${f} + ${C} +`;const d=s=>{s.setAttribute("aria-checked",h(s))},h=s=>{const e=!!s.getAttribute(t.MEDIA_CAPTIONS_SHOWING),c=!s.hasAttribute("no-subtitles-fallback")&&!!s.getAttribute(t.MEDIA_SUBTITLES_SHOWING);return e||c};class _ extends E{static get observedAttributes(){return[...super.observedAttributes,"no-subtitles-fallback","default-showing",t.MEDIA_CAPTIONS_LIST,t.MEDIA_CAPTIONS_SHOWING,t.MEDIA_SUBTITLES_LIST,t.MEDIA_SUBTITLES_SHOWING]}constructor(e={}){super({slotTemplate:b,...e});this._captionsReady=!1}connectedCallback(){super.connectedCallback(),this.setAttribute("role","switch"),this.setAttribute("aria-label",m.CLOSED_CAPTIONS()),d(this)}attributeChangedCallback(e,c,r){if([t.MEDIA_CAPTIONS_SHOWING,t.MEDIA_SUBTITLES_SHOWING].includes(e)&&d(this),this.hasAttribute("default-showing")&&this.getAttribute("aria-checked")!=="true"){const a=!this.hasAttribute("no-subtitles-fallback");if((a?[t.MEDIA_CAPTIONS_LIST,t.MEDIA_SUBTITLES_LIST]:[t.MEDIA_CAPTIONS_LIST]).includes(e)){const o=!!this.getAttribute(t.MEDIA_CAPTIONS_LIST)||!!(a&&this.getAttribute(t.MEDIA_SUBTITLES_LIST));this._captionsReady!==o&&(this._captionsReady=o,this._captionsReady&&this.handleClick())}}super.attributeChangedCallback(e,c,r)}handleClick(e){var r,a,I,o;if(h(this)){const n=this.getAttribute(t.MEDIA_CAPTIONS_SHOWING);if(n){const l=new u.CustomEvent(A.MEDIA_DISABLE_CAPTIONS_REQUEST,{composed:!0,bubbles:!0,detail:n});this.dispatchEvent(l)}const i=this.getAttribute(t.MEDIA_SUBTITLES_SHOWING);if(i&&!this.hasAttribute("no-subtitles-fallback")){const l=new u.CustomEvent(A.MEDIA_DISABLE_SUBTITLES_REQUEST,{composed:!0,bubbles:!0,detail:i});this.dispatchEvent(l)}}else{const[n]=(a=S((r=this.getAttribute(t.MEDIA_CAPTIONS_LIST))!=null?r:""))!=null?a:[];if(n){const i=new u.CustomEvent(A.MEDIA_SHOW_CAPTIONS_REQUEST,{composed:!0,bubbles:!0,detail:n});this.dispatchEvent(i)}else if(this.hasAttribute("no-subtitles-fallback"))console.error("Attempting to enable closed captions but none are available! Please verify your media content if this is unexpected.");else{const[i]=(o=S((I=this.getAttribute(t.MEDIA_SUBTITLES_LIST))!=null?I:""))!=null?o:[];if(i){const l=new u.CustomEvent(A.MEDIA_SHOW_SUBTITLES_REQUEST,{composed:!0,bubbles:!0,detail:i});this.dispatchEvent(l)}}}}}T("media-captions-button",_);export default _; diff --git a/dist/media-chrome-button.js b/dist/media-chrome-button.js new file mode 100644 index 000000000..2ca0eda93 --- /dev/null +++ b/dist/media-chrome-button.js @@ -0,0 +1,55 @@ +import{MediaUIAttributes as r}from"./constants.js";import{defineCustomElement as b}from"./utils/defineCustomElement.js";import{Window as f,Document as a}from"./utils/server-safe-globals.js";const d=a.createElement("template");d.innerHTML=` + +`;const h=["Enter"," "];class l extends f.HTMLElement{static get observedAttributes(){return[r.MEDIA_CONTROLLER]}constructor(s={}){super();const n=this.attachShadow({mode:"open"}),e=d.content.cloneNode(!0);this.nativeEl=e;let i=s.slotTemplate;i||(i=a.createElement("template"),i.innerHTML=`${s.defaultContent||""}`),this.nativeEl.appendChild(i.content.cloneNode(!0)),n.appendChild(e),this.addEventListener("click",t=>{this.handleClick(t)});const o=t=>{const{key:c}=t;if(!h.includes(c)){this.removeEventListener("keyup",o);return}this.handleClick(t)};this.addEventListener("keydown",t=>{const{metaKey:c,altKey:u,key:m}=t;if(c||u||!h.includes(m)){this.removeEventListener("keyup",o);return}this.addEventListener("keyup",o)})}attributeChangedCallback(s,n,e){var i,o;if(s===r.MEDIA_CONTROLLER){if(n){const t=a.getElementById(n);(i=t==null?void 0:t.unassociateElement)==null||i.call(t,this)}if(e){const t=a.getElementById(e);(o=t==null?void 0:t.associateElement)==null||o.call(t,this)}}}connectedCallback(){var n;this.setAttribute("role","button"),this.setAttribute("tabindex",0);const s=this.getAttribute(r.MEDIA_CONTROLLER);if(s){const e=a.getElementById(s);(n=e==null?void 0:e.associateElement)==null||n.call(e,this)}}disconnectedCallback(){var n;if(this.getAttribute(r.MEDIA_CONTROLLER)){const e=a.getElementById(mediaControllerId);(n=e==null?void 0:e.unassociateElement)==null||n.call(e,this)}}handleClick(){}}b("media-chrome-button",l);export default l; diff --git a/dist/media-chrome-range.js b/dist/media-chrome-range.js new file mode 100644 index 000000000..ccd22e4e6 --- /dev/null +++ b/dist/media-chrome-range.js @@ -0,0 +1,112 @@ +import{MediaUIAttributes as o}from"./constants.js";import{defineCustomElement as g}from"./utils/defineCustomElement.js";import{Window as b,Document as i}from"./utils/server-safe-globals.js";const c=i.createElement("template"),h=` + height: var(--thumb-height); + width: var(--media-range-thumb-width, 10px); + border: var(--media-range-thumb-border, none); + border-radius: var(--media-range-thumb-border-radius, 10px); + background: var(--media-range-thumb-background, #fff); + box-shadow: var(--media-range-thumb-box-shadow, 1px 1px 1px transparent); + cursor: pointer; + transition: var(--media-range-thumb-transition, none); + transform: var(--media-range-thumb-transform, none); + opacity: var(--media-range-thumb-opacity, 1); +`,s=` + width: var(--media-range-track-width, 100%); + min-width: 40px; + height: var(--track-height); + border: var(--media-range-track-border, none); + border-radius: var(--media-range-track-border-radius, 0); + background: var(--media-range-track-background-internal, var(--media-range-track-background, #eee)); + + box-shadow: var(--media-range-track-box-shadow, none); + transition: var(--media-range-track-transition, none); + cursor: pointer; +`;c.innerHTML=` + + +`;class u extends b.HTMLElement{static get observedAttributes(){return[o.MEDIA_CONTROLLER]}constructor(){super();this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(c.content.cloneNode(!0)),this.range=this.shadowRoot.querySelector("#range"),this.range.addEventListener("input",this.updateBar.bind(this))}attributeChangedCallback(a,t,e){var n,d;if(a===o.MEDIA_CONTROLLER){if(t){const r=i.getElementById(t);(n=r==null?void 0:r.unassociateElement)==null||n.call(r,this)}if(e){const r=i.getElementById(e);(d=r==null?void 0:r.associateElement)==null||d.call(r,this)}}}connectedCallback(){var t;const a=this.getAttribute(o.MEDIA_CONTROLLER);if(a){const e=i.getElementById(a);(t=e==null?void 0:e.associateElement)==null||t.call(e,this)}this.updateBar()}disconnectedCallback(){var t;if(this.getAttribute(o.MEDIA_CONTROLLER)){const e=i.getElementById(mediaControllerId);(t=e==null?void 0:e.unassociateElement)==null||t.call(e,this)}}updateBar(){const a=this.getBarColors();let t="linear-gradient(to right, ",e=0;a.forEach(n=>{n[1] + :host { + box-sizing: border-box; + position: relative; + display: inline-block; + background-color: #000; + } + + :host(:not([audio])) *[part~=layer] { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + display: flex; + flex-flow: column nowrap; + align-items: start; + pointer-events: none; + background: none; + } + + :host(:not([audio])) :is([part~=gestures-layer],[part~=media-layer]) { + pointer-events: auto; + } + + :host(:not([audio])) *[part~=layer][part~=centered-layer] { + align-items: center; + justify-content: center; + } + + .spacer { + pointer-events: none; + background: none; + } + + /* Position the media element to fill the container */ + ::slotted([slot=media]) { + width: 100%; + height: 100%; + } + + /* Video specific styles */ + :host(:not([audio])) { + aspect-ratio: var(--media-aspect-ratio, auto 3 / 2); + width: 720px; + } + + :host(:not([audio])) .spacer { + flex-grow: 1; + } + + @supports not (aspect-ratio: 1 / 1) { + :host(:not([audio])) { + height: 480px; + } + } + + /* Safari needs this to actually make the element fill the window */ + :host(:-webkit-full-screen) { + /* Needs to use !important otherwise easy to break */ + width: 100% !important; + height: 100% !important; + } + + /* Hide controls when inactive and not paused and not audio */ + ::slotted(:not([slot=media])) { + opacity: 1; + transition: opacity 0.25s; + visibility: visible; + pointer-events: auto; + } + + ::slotted(media-control-bar) { + align-self: stretch; + } + + :host([user-inactive]:not([${c.MEDIA_PAUSED}]):not([audio])) ::slotted(:not([slot=media]):not([no-auto-hide])) { + opacity: 0; + transition: opacity 1s; + } + + + + + + + + + + + + + + + + + +`;const y=Object.values(c);class b extends n.HTMLElement{constructor(){super();const e=this.attachShadow({mode:"open"});this.shadowRoot.appendChild(p.content.cloneNode(!0));const o=(t,r)=>{const d=this.media;for(let a of t)a.type==="childList"&&(a.removedNodes.forEach(l=>{if(l.slot=="media"&&a.target==this){let s=a.previousSibling&&a.previousSibling.previousElementSibling;if(!s||!d)this.mediaUnsetCallback(l);else{let u=s.slot!=="media";for(;(s=s.previousSibling)!==null;)s.slot=="media"&&(u=!1);u&&this.mediaUnsetCallback(l)}}}),d&&a.addedNodes.forEach(l=>{l==d&&this.handleMediaUpdated(d).then(s=>this.mediaSetCallback(s))}))};new MutationObserver(o).observe(this,{childList:!0,subtree:!0})}static get observedAttributes(){return["autohide"].concat(y)}attributeChangedCallback(e,o,i){e.toLowerCase()=="autohide"&&(this.autohide=i)}get media(){let e=this.querySelector(":scope > [slot=media]");return(e==null?void 0:e.nodeName)=="SLOT"&&(e=e.assignedElements({flatten:!0})[0]),e}mediaSetCallback(e){this._mediaClickPlayToggle=o=>{const i=e.paused?h.MEDIA_PLAY_REQUEST:h.MEDIA_PAUSE_REQUEST;this.dispatchEvent(new n.CustomEvent(i,{composed:!0,bubbles:!0}))}}handleMediaUpdated(e){const o=r=>Promise.resolve(r),i=r=>(console.error(': Media element set with slot="media" does not appear to be compatible.',r),Promise.reject(r));if(!e)return i(e);const t=e.nodeName.toLowerCase();return t.includes("-")?n.customElements.whenDefined(t).then(()=>o(e)):o(e)}mediaUnsetCallback(e){}connectedCallback(){const o=this.getAttribute("audio")!=null?m.AUDIO_PLAYER():m.VIDEO_PLAYER();this.setAttribute("role","region"),this.setAttribute("aria-label",o),this.media&&this.handleMediaUpdated(this.media).then(t=>this.mediaSetCallback(t));const i=()=>{this.removeAttribute("user-inactive"),n.clearTimeout(this.inactiveTimeout),!(this.autohide<0)&&(this.inactiveTimeout=n.setTimeout(()=>{this.setAttribute("user-inactive","user-inactive")},this.autohide*1e3))};this.addEventListener("keyup",t=>{i()}),this.addEventListener("keyup",t=>{this.setAttribute("media-keyboard-control","media-keyboard-control")}),this.addEventListener("mouseup",t=>{this.removeAttribute("media-keyboard-control")}),this.addEventListener("mousemove",t=>{t.target!==this&&(this.removeAttribute("user-inactive"),n.clearTimeout(this.inactiveTimeout),t.target===this.media&&i())}),this.addEventListener("mouseout",t=>{this.autohide>-1&&this.setAttribute("user-inactive","user-inactive")})}set autohide(e){e=Number(e),this._autohide=isNaN(e)?0:e}get autohide(){return this._autohide===void 0?2:this._autohide}}v("media-container-temp",b);export default b; diff --git a/dist/media-control-bar.js b/dist/media-control-bar.js new file mode 100644 index 000000000..36e0bb5f1 --- /dev/null +++ b/dist/media-control-bar.js @@ -0,0 +1,20 @@ +import{MediaUIAttributes as n}from"./constants.js";import{defineCustomElement as m}from"./utils/defineCustomElement.js";import{Window as l,Document as i}from"./utils/server-safe-globals.js";const d=i.createElement("template");d.innerHTML=` + + + +`;class r extends l.HTMLElement{static get observedAttributes(){return[n.MEDIA_CONTROLLER]}constructor(){super();this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(d.content.cloneNode(!0))}attributeChangedCallback(o,s,t){var a,c;if(o===n.MEDIA_CONTROLLER){if(s){const e=i.getElementById(s);(a=e==null?void 0:e.unassociateElement)==null||a.call(e,this)}if(t){const e=i.getElementById(t);(c=e==null?void 0:e.associateElement)==null||c.call(e,this)}}}connectedCallback(){var s;const o=this.getAttribute(n.MEDIA_CONTROLLER);if(o){const t=i.getElementById(o);(s=t==null?void 0:t.associateElement)==null||s.call(t,this)}}disconnectedCallback(){var s;if(this.getAttribute(n.MEDIA_CONTROLLER)){const t=i.getElementById(mediaControllerId);(s=t==null?void 0:t.unassociateElement)==null||s.call(t,this)}}}m("media-control-bar",r);export default r; diff --git a/dist/media-controller.js b/dist/media-controller.js new file mode 100644 index 000000000..34fda09d2 --- /dev/null +++ b/dist/media-controller.js @@ -0,0 +1 @@ +import C from"./media-container.js";import{defineCustomElement as O}from"./utils/defineCustomElement.js";import{Window as E,Document as w}from"./utils/server-safe-globals.js";import{fullscreenApi as S}from"./utils/fullscreenApi.js";import{constToCamel as v}from"./utils/stringUtils.js";import{MediaUIEvents as h,MediaUIAttributes as r,TextTrackKinds as _,TextTrackModes as T}from"./constants.js";import{stringifyTextTrackList as l,getTextTracksList as I,updateTracksModeTo as M}from"./utils/captions.js";const{MEDIA_PLAY_REQUEST:y,MEDIA_PAUSE_REQUEST:Q,MEDIA_MUTE_REQUEST:x,MEDIA_UNMUTE_REQUEST:N,MEDIA_VOLUME_REQUEST:B,MEDIA_ENTER_FULLSCREEN_REQUEST:W,MEDIA_EXIT_FULLSCREEN_REQUEST:H,MEDIA_SEEK_REQUEST:j,MEDIA_PREVIEW_REQUEST:F,MEDIA_ENTER_PIP_REQUEST:G,MEDIA_EXIT_PIP_REQUEST:V,MEDIA_PLAYBACK_RATE_REQUEST:K}=h;class b extends C{constructor(){super();this.mediaStateReceivers=[],this.associatedElementSubscriptions=new Map,this.associatedElements=[],this.associateElement(this);const t={MEDIA_PLAY_REQUEST:()=>this.media.play(),MEDIA_PAUSE_REQUEST:()=>this.media.pause(),MEDIA_MUTE_REQUEST:()=>this.media.muted=!0,MEDIA_UNMUTE_REQUEST:()=>{const e=this.media;e.muted=!1,e.volume===0&&(e.volume=.25)},MEDIA_VOLUME_REQUEST:e=>{const i=this.media,s=e.detail;i.volume=s,s>0&&i.muted&&(i.muted=!1);try{E.localStorage.setItem("media-chrome-pref-volume",s.toString())}catch(n){}},MEDIA_ENTER_FULLSCREEN_REQUEST:()=>{const e=this.getRootNode(),i=this.media;e.pictureInPictureElement&&e.exitPictureInPicture(),super[S.enter]?super[S.enter]():i.webkitEnterFullscreen?i.webkitEnterFullscreen():i.requestFullscreen?i.requestFullscreen():console.warn("MediaChrome: Fullscreen not supported")},MEDIA_EXIT_FULLSCREEN_REQUEST:()=>{w[S.exit]()},MEDIA_ENTER_PIP_REQUEST:()=>{const e=this.getRootNode(),i=this.media;!e.pictureInPictureEnabled||(e[S.element]&&e[S.exit](),i.requestPictureInPicture())},MEDIA_EXIT_PIP_REQUEST:()=>{const e=this.getRootNode();e.exitPictureInPicture&&e.exitPictureInPicture()},MEDIA_SEEK_REQUEST:e=>{const i=this.media,s=e.detail;(i.readyState>0||i.readyState===void 0)&&(i.currentTime=s)},MEDIA_PLAYBACK_RATE_REQUEST:e=>{this.media.playbackRate=e.detail},MEDIA_PREVIEW_REQUEST:e=>{const i=this.media;if(!i)return;const[s]=I(i,{kind:_.METADATA,label:"thumbnails"});if(!(s&&s.cues))return;const n=e.detail,o=Array.prototype.find.call(s.cues,m=>m.startTime>=n);if(!o)return;const p=new URL(o.text),c=new URLSearchParams(p.hash).get("#xywh");this.propagateMediaState(r.MEDIA_PREVIEW_IMAGE,p.href),this.propagateMediaState(r.MEDIA_PREVIEW_COORDS,c.split(",").join(" "))},MEDIA_SHOW_CAPTIONS_REQUEST:e=>{const i=this.captionTracks,{detail:s=[]}=e;M(T.SHOWING,i,s)},MEDIA_DISABLE_CAPTIONS_REQUEST:e=>{const i=this.captionTracks,{detail:s=[]}=e;M(T.DISABLED,i,s)},MEDIA_SHOW_SUBTITLES_REQUEST:e=>{const i=this.subtitleTracks,{detail:s=[]}=e;M(T.SHOWING,i,s)},MEDIA_DISABLE_SUBTITLES_REQUEST:e=>{const i=this.subtitleTracks,{detail:s=[]}=e;M(T.DISABLED,i,s)},MEDIA_AIRPLAY_REQUEST:e=>{const{media:i}=this;if(!!i){if(!(i.webkitShowPlaybackTargetPicker&&E.WebKitPlaybackTargetAvailabilityEvent)){console.warn("received a request to select AirPlay but AirPlay is not supported in this environment");return}i.webkitShowPlaybackTargetPicker()}}};Object.keys(t).forEach(e=>{const i=`_handle${v(e,!0)}`;this[i]=s=>{if(s.stopPropagation(),!this.media){console.warn("MediaController: No media available.");return}t[e](s,this.media)},this.addEventListener(h[e],this[i])}),this._mediaStatePropagators={"play,pause":()=>{this.propagateMediaState(r.MEDIA_PAUSED,this.paused)},volumechange:()=>{this.propagateMediaState(r.MEDIA_MUTED,this.muted),this.propagateMediaState(r.MEDIA_VOLUME,this.volume),this.propagateMediaState(r.MEDIA_VOLUME_LEVEL,this.volumeLevel)},[S.event]:()=>{const e=this.getRootNode()[S.element];this.propagateMediaState(r.MEDIA_IS_FULLSCREEN,e===this)},"enterpictureinpicture,leavepictureinpicture":e=>{let i;e?i=e.type=="enterpictureinpicture":i=this.isPip,this.propagateMediaState(r.MEDIA_IS_PIP,i)},"timeupdate,loadedmetadata":()=>{this.propagateMediaState(r.MEDIA_CURRENT_TIME,this.currentTime)},"durationchange,loadedmetadata":()=>{this.propagateMediaState(r.MEDIA_DURATION,this.duration)},ratechange:()=>{this.propagateMediaState(r.MEDIA_PLAYBACK_RATE,this.playbackRate)},"waiting,playing":()=>{var i;const e=((i=this.media)==null?void 0:i.readyState)<3;this.propagateMediaState(r.MEDIA_LOADING,e)}},this._textTrackMediaStatePropagators={"addtrack,removetrack":()=>{this.propagateMediaState(r.MEDIA_CAPTIONS_LIST,l(this.captionTracks)||void 0),this.propagateMediaState(r.MEDIA_SUBTITLES_LIST,l(this.subtitleTracks)||void 0),this.propagateMediaState(r.MEDIA_CAPTIONS_SHOWING,l(this.showingCaptionTracks)||void 0),this.propagateMediaState(r.MEDIA_SUBTITLES_SHOWING,l(this.showingSubtitleTracks)||void 0)},change:()=>{this.propagateMediaState(r.MEDIA_CAPTIONS_SHOWING,l(this.showingCaptionTracks)||void 0),this.propagateMediaState(r.MEDIA_SUBTITLES_SHOWING,l(this.showingSubtitleTracks)||void 0)}}}mediaSetCallback(t){super.mediaSetCallback(t),Object.keys(this._mediaStatePropagators).forEach(e=>{const i=e.split(","),s=this._mediaStatePropagators[e];i.forEach(n=>{(n==S.event?this.getRootNode():t).addEventListener(n,s)}),s()}),Object.entries(this._textTrackMediaStatePropagators).forEach(([e,i])=>{e.split(",").forEach(n=>{t.textTracks&&t.textTracks.addEventListener(n,i)}),i()});try{const e=E.localStorage.getItem("media-chrome-pref-volume");e!==null&&(t.volume=e)}catch(e){console.debug("Error getting volume pref",e)}}mediaUnsetCallback(t){super.mediaUnsetCallback(t),Object.keys(this._mediaStatePropagators).forEach(e=>{const i=e.split(","),s=this._mediaStatePropagators[e];i.forEach(n=>{(n==S.event?this.getRootNode():t).removeEventListener(n,s)})}),Object.entries(this._textTrackMediaStatePropagators).forEach(([e,i])=>{e.split(",").forEach(n=>{t.textTracks&&t.textTracks.removeEventListener(n,i)}),i()}),this.propagateMediaState(r.MEDIA_PAUSED,!0)}propagateMediaState(t,e){u(this.mediaStateReceivers,t,e)}associateElement(t){if(!t)return;const{associatedElementSubscriptions:e}=this;if(e.has(t))return;const i=this.registerMediaStateReceiver.bind(this),s=this.unregisterMediaStateReceiver.bind(this),n=$(t,i,s);Object.keys(h).forEach(o=>{t.addEventListener(h[o],this[`_handle${v(o,!0)}`])}),e.set(t,n)}unassociateElement(t){if(!t)return;const{associatedElementSubscriptions:e}=this;if(!e.has(t))return;e.get(t)(),e.delete(t),Object.keys(h).forEach(s=>{t.removeEventListener(h[s],this[`_handle${v(s,!0)}`])})}registerMediaStateReceiver(t){if(!t)return;const e=this.mediaStateReceivers;e.indexOf(t)>-1||(e.push(t),this.media&&(u([t],r.MEDIA_CAPTIONS_LIST,l(this.captionTracks)||void 0),u([t],r.MEDIA_SUBTITLES_LIST,l(this.subtitleTracks)||void 0),u([t],r.MEDIA_CAPTIONS_SHOWING,l(this.showingCaptionTracks)||void 0),u([t],r.MEDIA_SUBTITLES_SHOWING,l(this.showingSubtitleTracks)||void 0),u([t],r.MEDIA_PAUSED,this.paused),u([t],r.MEDIA_MUTED,this.muted),u([t],r.MEDIA_VOLUME,this.volume),u([t],r.MEDIA_VOLUME_LEVEL,this.volumeLevel),u([t],r.MEDIA_CURRENT_TIME,this.currentTime),u([t],r.MEDIA_DURATION,this.duration),u([t],r.MEDIA_PLAYBACK_RATE,this.playbackRate)))}unregisterMediaStateReceiver(t){const e=this.mediaStateReceivers,i=e.indexOf(t);i<0||e.splice(i,1)}play(){this.dispatchEvent(new E.CustomEvent(y))}pause(){this.dispatchEvent(new E.CustomEvent(Q))}get paused(){return this.media?this.media.paused:!0}get muted(){return!!(this.media&&this.media.muted)}set muted(t){const e=t?x:N;this.dispatchEvent(new E.CustomEvent(e))}get volume(){const t=this.media;return t?t.volume:1}set volume(t){this.dispatchEvent(new E.CustomEvent(B,{detail:t}))}get volumeLevel(){let t="high";if(!this.media)return t;const{muted:e,volume:i}=this.media;return i===0||e?t="off":i<.5?t="low":i<.75&&(t="medium"),t}requestFullscreen(){this.dispatchEvent(new E.CustomEvent(W))}exitFullscreen(){this.dispatchEvent(new E.CustomEvent(H))}get currentTime(){const t=this.media;return t?t.currentTime:0}set currentTime(t){this.dispatchEvent(new E.CustomEvent(j,{detail:t}))}get duration(){const t=this.media;return t?t.duration:NaN}get playbackRate(){const t=this.media;return t?t.playbackRate:1}set playbackRate(t){this.dispatchEvent(new E.CustomEvent(K,{detail:t}))}get subtitleTracks(){return I(this.media,{kind:_.SUBTITLES})}get captionTracks(){return I(this.media,{kind:_.CAPTIONS})}get showingSubtitleTracks(){return I(this.media,{kind:_.SUBTITLES,mode:T.SHOWING})}get showingCaptionTracks(){return I(this.media,{kind:_.CAPTIONS,mode:T.SHOWING})}get isPip(){return this.media&&this.media==this.getRootNode().pictureInPictureElement}requestPictureInPicture(){this.dispatchEvent(new E.CustomEvent(G))}exitPictureInPicture(){this.dispatchEvent(new E.CustomEvent(V))}requestPreview(t){this.dispatchEvent(new E.CustomEvent(F,{detail:t}))}}const q=Object.values(r),P=a=>{var i,s,n;const{constructor:{observedAttributes:t}}=a,e=(n=(s=(i=a==null?void 0:a.getAttribute)==null?void 0:i.call(a,r.MEDIA_CHROME_ATTRIBUTES))==null?void 0:s.split)==null?void 0:n.call(s,/\s+/);return Array.isArray(t||e)?(t||e).filter(o=>q.includes(o)):[]},D=a=>!!P(a).length,Y=(a,t,e)=>e==null?a.removeAttribute(t):typeof e=="boolean"?e?a.setAttribute(t,""):a.removeAttribute(t):Number.isNaN(e)?a.removeAttribute(t):a.setAttribute(t,e),X=a=>{var t;return!!((t=a.closest)==null?void 0:t.call(a,'*[slot="media"]'))},A=(a,t)=>{if(X(a))return;const e=(s,n)=>{var m,d;D(s)&&n(s);const{children:o=[]}=s!=null?s:{},p=(d=(m=s==null?void 0:s.shadowRoot)==null?void 0:m.children)!=null?d:[];[...o,...p].forEach(f=>A(f,n))},i=a==null?void 0:a.nodeName.toLowerCase();if(i.includes("-")&&!D(a)){E.customElements.whenDefined(i).then(()=>{e(a,t)});return}e(a,t)},u=(a,t,e)=>{a.forEach(i=>{!P(i).includes(t)||Y(i,t,e)})},$=(a,t,e)=>{A(a,t);const i=c=>{var d;const m=(d=c==null?void 0:c.composedPath()[0])!=null?d:c.target;t(m)},s=c=>{var d;const m=(d=c==null?void 0:c.composedPath()[0])!=null?d:c.target;e(m)};a.addEventListener(h.REGISTER_MEDIA_STATE_RECEIVER,i),a.addEventListener(h.UNREGISTER_MEDIA_STATE_RECEIVER,s);const n=(c,m)=>{c.forEach(d=>{const{addedNodes:f=[],removedNodes:L=[],type:U,target:R,attributeName:k}=d;U==="childList"?(Array.prototype.forEach.call(f,g=>A(g,t)),Array.prototype.forEach.call(L,g=>A(g,e))):U==="attributes"&&k===r.MEDIA_CHROME_ATTRIBUTES&&(D(R)?t(R):e(R))})},o=new MutationObserver(n);return o.observe(a,{childList:!0,attributes:!0,subtree:!0}),()=>{A(a,e),o.disconnect(),a.removeEventListener(h.REGISTER_MEDIA_STATE_RECEIVER,i),a.removeEventListener(h.UNREGISTER_MEDIA_STATE_RECEIVER,s)}};O("media-controller",b);export default b; diff --git a/dist/media-current-time-display.js b/dist/media-current-time-display.js new file mode 100644 index 000000000..815d79a4f --- /dev/null +++ b/dist/media-current-time-display.js @@ -0,0 +1 @@ +import a from"./media-text-display.js";import{defineCustomElement as m}from"./utils/defineCustomElement.js";import{formatTime as o}from"./utils/time.js";import{MediaUIAttributes as i}from"./constants.js";class r extends a{static get observedAttributes(){return[...super.observedAttributes,i.MEDIA_CURRENT_TIME]}attributeChangedCallback(t,s,e){t===i.MEDIA_CURRENT_TIME&&(this.container.innerHTML=o(e)),super.attributeChangedCallback(t,s,e)}}m("media-current-time-display",r);export default r; diff --git a/dist/media-duration-display.js b/dist/media-duration-display.js new file mode 100644 index 000000000..108be8d2f --- /dev/null +++ b/dist/media-duration-display.js @@ -0,0 +1 @@ +import a from"./media-text-display.js";import{defineCustomElement as o}from"./utils/defineCustomElement.js";import{formatTime as d}from"./utils/time.js";import{MediaUIAttributes as i}from"./constants.js";class s extends a{static get observedAttributes(){return[...super.observedAttributes,i.MEDIA_DURATION]}attributeChangedCallback(t,r,e){t===i.MEDIA_DURATION&&(this.container.innerHTML=d(e)),super.attributeChangedCallback(t,r,e)}}o("media-duration-display",s);export default s; diff --git a/dist/media-fullscreen-button.js b/dist/media-fullscreen-button.js new file mode 100644 index 000000000..de493f334 --- /dev/null +++ b/dist/media-fullscreen-button.js @@ -0,0 +1,17 @@ +import u from"./media-chrome-button.js";import{defineCustomElement as c}from"./utils/defineCustomElement.js";import{Window as d,Document as m}from"./utils/server-safe-globals.js";import{MediaUIEvents as o,MediaUIAttributes as t}from"./constants.js";import{verbs as l}from"./labels/labels.js";const L='Mux Player SVG Icons_v3',b='Mux Player SVG Icons_v3',a=m.createElement("template");a.innerHTML=` + + + ${L} + ${b} +`;const r=n=>{const s=n.getAttribute(t.MEDIA_IS_FULLSCREEN)!=null?l.EXIT_FULLSCREEN():l.ENTER_FULLSCREEN();n.setAttribute("aria-label",s)};class i extends u{static get observedAttributes(){return[...super.observedAttributes,t.MEDIA_IS_FULLSCREEN]}constructor(e={}){super({slotTemplate:a,...e})}connectedCallback(){r(this),super.connectedCallback()}attributeChangedCallback(e,s,E){e===t.MEDIA_IS_FULLSCREEN&&r(this),super.attributeChangedCallback(e,s,E)}handleClick(e){const s=this.getAttribute(t.MEDIA_IS_FULLSCREEN)!=null?o.MEDIA_EXIT_FULLSCREEN_REQUEST:o.MEDIA_ENTER_FULLSCREEN_REQUEST;this.dispatchEvent(new d.CustomEvent(s,{composed:!0,bubbles:!0}))}}c("media-fullscreen-button",i);export default i; diff --git a/dist/media-loading-indicator.js b/dist/media-loading-indicator.js new file mode 100644 index 000000000..5d089d02d --- /dev/null +++ b/dist/media-loading-indicator.js @@ -0,0 +1,53 @@ +import{MediaUIAttributes as s}from"./constants.js";import{nouns as m}from"./labels/labels.js";import{defineCustomElement as D}from"./utils/defineCustomElement.js";import{Window as f,Document as a}from"./utils/server-safe-globals.js";const g=a.createElement("template"),A=` + + + + + +`;g.innerHTML=` + + +${A} +
${m.MEDIA_LOADING()}
+`;const I=500;class r extends f.HTMLElement{static get observedAttributes(){return[s.MEDIA_CONTROLLER,s.MEDIA_PAUSED,s.MEDIA_LOADING,"loading-delay"]}constructor(){super();const n=this.attachShadow({mode:"open"}),i=g.content.cloneNode(!0);n.appendChild(i)}attributeChangedCallback(n,i,t){var o,d,l;if(n===s.MEDIA_LOADING||n===s.MEDIA_PAUSED){const e=this.getAttribute(s.MEDIA_PAUSED)!=null,h=this.getAttribute(s.MEDIA_LOADING)!=null,c=!e&&h;if(!c)this.loadingDelayHandle&&(clearTimeout(this.loadingDelayHandle),this.loadingDelayHandle=void 0),this.removeAttribute("is-loading");else if(!this.loadingDelayHandle&&c){const u=+((o=this.getAttribute("loading-delay"))!=null?o:I);this.loadingDelayHandle=setTimeout(()=>{this.setAttribute("is-loading",""),this.loadingDelayHandle=void 0},u)}}else if(n===s.MEDIA_CONTROLLER){if(i){const e=a.getElementById(i);(d=e==null?void 0:e.unassociateElement)==null||d.call(e,this)}if(t){const e=a.getElementById(t);(l=e==null?void 0:e.associateElement)==null||l.call(e,this)}}}connectedCallback(){var i;const n=this.getAttribute(s.MEDIA_CONTROLLER);if(n){const t=a.getElementById(n);(i=t==null?void 0:t.associateElement)==null||i.call(t,this)}}disconnectedCallback(){var i;if(this.loadingDelayHandle&&(clearTimeout(this.loadingDelayHandle),this.loadingDelayHandle=void 0),this.getAttribute(s.MEDIA_CONTROLLER)){const t=a.getElementById(mediaControllerId);(i=t==null?void 0:t.unassociateElement)==null||i.call(t,this)}}}D("media-loading-indicator",r);export default r; diff --git a/dist/media-mute-button.js b/dist/media-mute-button.js new file mode 100644 index 000000000..f44d4307f --- /dev/null +++ b/dist/media-mute-button.js @@ -0,0 +1,31 @@ +import M from"./media-chrome-button.js";import{defineCustomElement as r}from"./utils/defineCustomElement.js";import{Window as d,Document as h}from"./utils/server-safe-globals.js";import{MediaUIEvents as l,MediaUIAttributes as t}from"./constants.js";import{verbs as n}from"./labels/labels.js";const u='Mux Player SVG Icons_v3',a='Mux Player SVG Icons_v3',V='Mux Player SVG Icons_v3',E=h.createElement("template");E.innerHTML=` + + + ${u} + ${a} + ${a} + ${V} +`;const i=s=>{const o=s.getAttribute(t.MEDIA_VOLUME_LEVEL)==="off"?n.UNMUTE():n.MUTE();s.setAttribute("aria-label",o)};class L extends M{static get observedAttributes(){return[...super.observedAttributes,t.MEDIA_VOLUME_LEVEL]}constructor(e={}){super({slotTemplate:E,...e})}connectedCallback(){i(this),super.connectedCallback()}attributeChangedCallback(e,o,m){e===t.MEDIA_VOLUME_LEVEL&&i(this),super.attributeChangedCallback(e,o,m)}handleClick(e){const o=this.getAttribute(t.MEDIA_VOLUME_LEVEL)==="off"?l.MEDIA_UNMUTE_REQUEST:l.MEDIA_MUTE_REQUEST;this.dispatchEvent(new d.CustomEvent(o,{composed:!0,bubbles:!0}))}}r("media-mute-button",L);export default L; diff --git a/dist/media-pip-button.js b/dist/media-pip-button.js new file mode 100644 index 000000000..d0fe1d965 --- /dev/null +++ b/dist/media-pip-button.js @@ -0,0 +1,17 @@ +import d from"./media-chrome-button.js";import{defineCustomElement as m}from"./utils/defineCustomElement.js";import{Window as b,Document as c}from"./utils/server-safe-globals.js";import{MediaUIEvents as n,MediaUIAttributes as t}from"./constants.js";import{verbs as a}from"./labels/labels.js";const l='Mux Player SVG Icons_v3',i=c.createElement("template");i.innerHTML=` + + + ${l} + ${l} +`;const r=o=>{const s=o.getAttribute(t.MEDIA_IS_PIP)!=null?a.EXIT_PIP():a.ENTER_PIP();o.setAttribute("aria-label",s)};class u extends d{static get observedAttributes(){return[...super.observedAttributes,t.MEDIA_IS_PIP]}constructor(e={}){super({slotTemplate:i,...e})}connectedCallback(){r(this),super.connectedCallback()}attributeChangedCallback(e,s,I){e===t.MEDIA_IS_PIP&&r(this),super.attributeChangedCallback(e,s,I)}handleClick(e){const s=this.getAttribute(t.MEDIA_IS_PIP)!=null?n.MEDIA_EXIT_PIP_REQUEST:n.MEDIA_ENTER_PIP_REQUEST;this.dispatchEvent(new b.CustomEvent(s,{composed:!0,bubbles:!0}))}}m("media-pip-button",u);export default u; diff --git a/dist/media-play-button.js b/dist/media-play-button.js new file mode 100644 index 000000000..a63b596fe --- /dev/null +++ b/dist/media-play-button.js @@ -0,0 +1,22 @@ +import d from"./media-chrome-button.js";import{Window as p,Document as m}from"./utils/server-safe-globals.js";import{defineCustomElement as E}from"./utils/defineCustomElement.js";import{MediaUIEvents as l,MediaUIAttributes as t}from"./constants.js";import{verbs as a}from"./labels/labels.js";const c=` + +`,A=` + +`,n=m.createElement("template");n.innerHTML=` + + + ${c} + ${A} +`;const i=o=>{const s=o.getAttribute(t.MEDIA_PAUSED)!=null?a.PLAY():a.PAUSE();o.setAttribute("aria-label",s)};class r extends d{static get observedAttributes(){return[...super.observedAttributes,t.MEDIA_PAUSED]}constructor(e={}){super({slotTemplate:n,...e})}connectedCallback(){i(this),super.connectedCallback()}attributeChangedCallback(e,s,u){e===t.MEDIA_PAUSED&&i(this),super.attributeChangedCallback(e,s,u)}handleClick(e){const s=this.getAttribute(t.MEDIA_PAUSED)!=null?l.MEDIA_PLAY_REQUEST:l.MEDIA_PAUSE_REQUEST;this.dispatchEvent(new p.CustomEvent(s,{composed:!0,bubbles:!0}))}}E("media-play-button",r);export default r; diff --git a/dist/media-playback-rate-button.js b/dist/media-playback-rate-button.js new file mode 100644 index 000000000..b13fe0d5c --- /dev/null +++ b/dist/media-playback-rate-button.js @@ -0,0 +1,15 @@ +import u from"./media-chrome-button.js";import{defineCustomElement as A}from"./utils/defineCustomElement.js";import{Window as h,Document as d}from"./utils/server-safe-globals.js";import{MediaUIEvents as p,MediaUIAttributes as a}from"./constants.js";import{nouns as E}from"./labels/labels.js";const c=[1,1.25,1.5,1.75,2],n=1,l=d.createElement("template");l.innerHTML=` + + + +`;class m extends u{static get observedAttributes(){return[...super.observedAttributes,a.MEDIA_PLAYBACK_RATE,"rates"]}constructor(i={}){super({slotTemplate:l,...i});this._rates=c,this.container=this.shadowRoot.querySelector("#container"),this.container.innerHTML=`${n}x`}attributeChangedCallback(i,o,e){if(i==="rates"){const s=(e!=null?e:"").trim().split(/\s*,?\s+/).map(t=>Number(t)).filter(t=>!Number.isNaN(t)).sort((t,r)=>t-r);this._rates=s.length?s:c;return}if(i===a.MEDIA_PLAYBACK_RATE){const s=e?+e:Number.NaN,t=Number.isNaN(s)?n:s;this.container.innerHTML=`${t}x`,this.setAttribute("aria-label",E.PLAYBACK_RATE({playbackRate:t}));return}super.attributeChangedCallback(i,o,e)}handleClick(i){var t,r;const o=+this.getAttribute(a.MEDIA_PLAYBACK_RATE)||n,e=(r=(t=this._rates.find(b=>b>o))!=null?t:this._rates[0])!=null?r:n,s=new h.CustomEvent(p.MEDIA_PLAYBACK_RATE_REQUEST,{composed:!0,bubbles:!0,detail:e});this.dispatchEvent(s)}}A("media-playback-rate-button",m);export default m; diff --git a/dist/media-progress-range.js b/dist/media-progress-range.js new file mode 100644 index 000000000..c48925d6c --- /dev/null +++ b/dist/media-progress-range.js @@ -0,0 +1 @@ +import r from"./media-time-range.js";import{defineCustomElement as s}from"./utils/defineCustomElement.js";class e extends r{constructor(){super();console.warn("MediaChrome: is deprecated. Use instead.")}}s("media-progress-range",e);export default e; diff --git a/dist/media-seek-backward-button.js b/dist/media-seek-backward-button.js new file mode 100644 index 000000000..989c422e7 --- /dev/null +++ b/dist/media-seek-backward-button.js @@ -0,0 +1,3 @@ +import d from"./media-chrome-button.js";import{defineCustomElement as f}from"./utils/defineCustomElement.js";import{Window as m,Document as b}from"./utils/server-safe-globals.js";import{MediaUIEvents as E,MediaUIAttributes as r}from"./constants.js";import{verbs as h}from"./labels/labels.js";import{getSlotted as k,updateIconText as p}from"./utils/element-utils.js";const o=30,A=``,i=b.createElement("template");i.innerHTML=` + ${A} +`;const v=0,w=s=>{const t=Math.abs(o),e=h.SEEK_BACK_N_SECS({seekOffset:t});s.setAttribute("aria-label",e)},n=s=>{const t=k(s,"backward"),e=s.getAttribute("seek-offset");p(t,e)};class l extends d{static get observedAttributes(){return[...super.observedAttributes,r.MEDIA_CURRENT_TIME]}constructor(t={}){super({slotTemplate:i,...t})}connectedCallback(){this.hasAttribute("seek-offset")||this.setAttribute("seek-offset",o),w(this),n(this),super.connectedCallback()}attributeChangedCallback(t,e,a){t==="seek-offset"&&(a==null&&this.setAttribute("seek-offset",o),n(this))}handleClick(){const t=this.getAttribute(r.MEDIA_CURRENT_TIME),e=+this.getAttribute("seek-offset"),a=t&&!Number.isNaN(+t)?+t:v,c=Math.max(a-e,0),u=new m.CustomEvent(E.MEDIA_SEEK_REQUEST,{composed:!0,bubbles:!0,detail:c});this.dispatchEvent(u)}}f("media-seek-backward-button",l);export default l; diff --git a/dist/media-seek-forward-button.js b/dist/media-seek-forward-button.js new file mode 100644 index 000000000..bbca536ed --- /dev/null +++ b/dist/media-seek-forward-button.js @@ -0,0 +1,3 @@ +import c from"./media-chrome-button.js";import{defineCustomElement as d}from"./utils/defineCustomElement.js";import{Window as m,Document as b}from"./utils/server-safe-globals.js";import{MediaUIEvents as E,MediaUIAttributes as i}from"./constants.js";import{verbs as A}from"./labels/labels.js";import{getSlotted as h,updateIconText as p}from"./utils/element-utils.js";const o=30,k=``,a=b.createElement("template");a.innerHTML=` + ${k} +`;const T=0,v=e=>{const t=Math.abs(+e.getAttribute("seek-offset")),s=A.SEEK_FORWARD_N_SECS({seekOffset:t});e.setAttribute("aria-label",s)},n=e=>{const t=h(e,"forward"),s=e.getAttribute("seek-offset");p(t,s)};class l extends c{static get observedAttributes(){return[...super.observedAttributes,i.MEDIA_CURRENT_TIME,"seek-offset"]}constructor(t={}){super({slotTemplate:a,...t})}connectedCallback(){this.hasAttribute("seek-offset")||this.setAttribute("seek-offset",o),v(this),n(this),super.connectedCallback()}attributeChangedCallback(t,s,r){t==="seek-offset"&&(r==null&&this.setAttribute("seek-offset",o),n(this))}handleClick(){const t=this.getAttribute(i.MEDIA_CURRENT_TIME),s=+this.getAttribute("seek-offset"),u=(t&&!Number.isNaN(+t)?+t:T)+s,f=new m.CustomEvent(E.MEDIA_SEEK_REQUEST,{composed:!0,bubbles:!0,detail:u});this.dispatchEvent(f)}}d("media-seek-forward-button",l);export default l; diff --git a/dist/media-settings-popup.js b/dist/media-settings-popup.js new file mode 100644 index 000000000..3cddc3f12 --- /dev/null +++ b/dist/media-settings-popup.js @@ -0,0 +1,33 @@ +import{defineCustomElement as o}from"./utils/defineCustomElement.js";import{Document as t,Window as i}from"./utils/server-safe-globals.js";const e=t.createElement("template");e.innerHTML=` + + + + + + + + + + Hello1 + Hello1 + Hello1 + Hello1 + + + Normal + 1.5 + +`;class m extends i.HTMLElement{constructor(){super();const a=this.attachShadow({mode:"open"});this.shadowRoot.appendChild(e.content.cloneNode(!0))}}o("media-settings-popup",m);export default m; diff --git a/dist/media-text-display.js b/dist/media-text-display.js new file mode 100644 index 000000000..ee8e96cb9 --- /dev/null +++ b/dist/media-text-display.js @@ -0,0 +1,28 @@ +import{MediaUIAttributes as o}from"./constants.js";import{defineCustomElement as f}from"./utils/defineCustomElement.js";import{Window as h,Document as i}from"./utils/server-safe-globals.js";const r=i.createElement("template");r.innerHTML=` + + + + +`;class d extends h.HTMLElement{static get observedAttributes(){return[o.MEDIA_CONTROLLER]}constructor(){super();this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(r.content.cloneNode(!0)),this.container=this.shadowRoot.querySelector("#container")}attributeChangedCallback(s,n,t){var a,c;if(s===o.MEDIA_CONTROLLER){if(n){const e=i.getElementById(n);(a=e==null?void 0:e.unassociateElement)==null||a.call(e,this)}if(t){const e=i.getElementById(t);(c=e==null?void 0:e.associateElement)==null||c.call(e,this)}}}connectedCallback(){var n;const s=this.getAttribute(o.MEDIA_CONTROLLER);if(s){const t=i.getElementById(s);(n=t==null?void 0:t.associateElement)==null||n.call(t,this)}}disconnectedCallback(){var n;if(this.getAttribute(o.MEDIA_CONTROLLER)){const t=i.getElementById(mediaControllerId);(n=t==null?void 0:t.unassociateElement)==null||n.call(t,this)}}}f("media-text-display",d);export default d; diff --git a/dist/media-thumbnail-preview.js b/dist/media-thumbnail-preview.js new file mode 100644 index 000000000..a34b62360 --- /dev/null +++ b/dist/media-thumbnail-preview.js @@ -0,0 +1,18 @@ +import{Window as g,Document as c}from"./utils/server-safe-globals.js";import{defineCustomElement as p}from"./utils/defineCustomElement.js";import{MediaUIAttributes as o}from"./constants.js";const m=c.createElement("template");m.innerHTML=` + + +`;class E extends g.HTMLElement{static get observedAttributes(){return[o.MEDIA_CONTROLLER,"time",o.MEDIA_PREVIEW_IMAGE,o.MEDIA_PREVIEW_COORDS]}constructor(){super();this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(m.content.cloneNode(!0))}connectedCallback(){var i;const n=this.getAttribute(o.MEDIA_CONTROLLER);if(n){const t=c.getElementById(n);(i=t==null?void 0:t.associateElement)==null||i.call(t,this)}}disconnectedCallback(){var i;if(this.getAttribute(o.MEDIA_CONTROLLER)){const t=c.getElementById(mediaControllerId);(i=t==null?void 0:t.unassociateElement)==null||i.call(t,this)}}attributeChangedCallback(n,i,t){var s,a;if(["time",o.MEDIA_PREVIEW_IMAGE,o.MEDIA_PREVIEW_COORDS].includes(n)&&this.update(),n===o.MEDIA_CONTROLLER){if(i){const e=c.getElementById(i);(s=e==null?void 0:e.unassociateElement)==null||s.call(e,this)}if(t){const e=c.getElementById(t);(a=e==null?void 0:e.associateElement)==null||a.call(e,this)}}}update(){const n=this.getAttribute(o.MEDIA_PREVIEW_COORDS),i=this.getAttribute(o.MEDIA_PREVIEW_IMAGE);if(!(n&&i))return;const t=this.offsetWidth,s=this.shadowRoot.querySelector("img"),[a,e,u,l]=n.split(/\s+/).map(I=>+I),r=i,d=t/u||1,h=()=>{s.style.width=`${d*s.naturalWidth}px`,s.style.height=`${d*s.naturalHeight}px`};s.src!==r&&(s.onload=h,s.src=r,h()),h(),s.style.left=`-${d*a}px`,s.style.top=`-${d*e}px`}}p("media-thumbnail-preview",E);export default E; diff --git a/dist/media-time-display.js b/dist/media-time-display.js new file mode 100644 index 000000000..805d08def --- /dev/null +++ b/dist/media-time-display.js @@ -0,0 +1 @@ +import l from"./media-text-display.js";import{defineCustomElement as c}from"./utils/defineCustomElement.js";import{formatAsTimePhrase as u,formatTime as A}from"./utils/time.js";import{MediaUIAttributes as e}from"./constants.js";import{nouns as T}from"./labels/labels.js";const d=" / ",E=(t,{timesSep:r=d}={})=>{const n=t.getAttribute("remaining")!=null,s=t.getAttribute("show-duration")!=null,i=+t.getAttribute(e.MEDIA_CURRENT_TIME),a=+t.getAttribute(e.MEDIA_DURATION),o=n?A(0-(a-i)):A(i);return s?`${o}${r}${A(a)}`:o},I="video not loaded, unknown time.",M=t=>{if(!(t.hasAttribute(e.MEDIA_CURRENT_TIME)&&t.hasAttribute(e.MEDIA_DURATION))){t.setAttribute("aria-valuetext",I);return}const r=t.getAttribute("remaining")!=null,n=t.getAttribute("show-duration")!=null,s=+t.getAttribute(e.MEDIA_CURRENT_TIME),i=+t.getAttribute(e.MEDIA_DURATION),a=r?u(0-(i-s)):u(s);if(!n){t.setAttribute("aria-valuetext",a);return}const o=u(i),b=`${a} of ${o}`;t.setAttribute("aria-valuetext",b)};class m extends l{static get observedAttributes(){return[...super.observedAttributes,e.MEDIA_CURRENT_TIME,e.MEDIA_DURATION,"remaining","show-duration"]}connectedCallback(){this.setAttribute("role","progressbar"),this.setAttribute("aria-label",T.PLAYBACK_TIME()),this.setAttribute("tabindex",0),super.connectedCallback()}attributeChangedCallback(r,n,s){if([e.MEDIA_CURRENT_TIME,e.MEDIA_DURATION,"remaining","show-duration"].includes(r)){const i=E(this);M(this),this.container.innerHTML=i}super.attributeChangedCallback(r,n,s)}}c("media-time-display",m);export default m; diff --git a/dist/media-time-range.js b/dist/media-time-range.js new file mode 100644 index 000000000..e957c3064 --- /dev/null +++ b/dist/media-time-range.js @@ -0,0 +1,55 @@ +import A from"./media-chrome-range.js";import{defineCustomElement as I}from"./utils/defineCustomElement.js";import{Window as r,Document as C}from"./utils/server-safe-globals.js";import{MediaUIEvents as h,MediaUIAttributes as n}from"./constants.js";import{nouns as w}from"./labels/labels.js";import{formatAsTimePhrase as c}from"./utils/time.js";const T="video not loaded, unknown time.",f=d=>{const e=d.range,t=c(+e.value),i=c(+e.max),l=t&&i?`${t} of ${i}`:T;e.setAttribute("aria-valuetext",l)},b=C.createElement("template");b.innerHTML=` + +
+ +
+`;class p extends A{static get observedAttributes(){return[...super.observedAttributes,"thumbnails",n.MEDIA_DURATION,n.MEDIA_CURRENT_TIME,n.MEDIA_PREVIEW_IMAGE]}constructor(){super();this.shadowRoot.appendChild(b.content.cloneNode(!0)),this.range.addEventListener("input",()=>{const t=this.range.value,i=new r.CustomEvent(h.MEDIA_SEEK_REQUEST,{composed:!0,bubbles:!0,detail:t});this.dispatchEvent(i)}),this.enableThumbnails()}connectedCallback(){this.range.setAttribute("aria-label",w.SEEK()),super.connectedCallback()}attributeChangedCallback(e,t,i){e===n.MEDIA_CURRENT_TIME&&(this.range.value=+i,f(this),this.updateBar()),e===n.MEDIA_DURATION&&(this.range.max=Math.floor(+i),f(this),this.updateBar()),super.attributeChangedCallback(e,t,i)}getBarColors(){let e=super.getBarColors();if(!this.mediaBuffered||!this.mediaBuffered.length||this.mediaDuration<=0)return e;const t=this.mediaBuffered,i=t[t.length-1][1]/this.mediaDuration*100;return e.splice(1,0,["var(--media-time-buffered-color, #777)",i]),e}enableThumbnails(){this.thumbnailPreview=this.shadowRoot.querySelector("media-thumbnail-preview"),this.shadowRoot.querySelector("#thumbnailContainer").classList.add("enabled");let t;const i=()=>{t=u=>{const s=+this.getAttribute(n.MEDIA_DURATION);if(!s)return;const a=this.range.getBoundingClientRect();let o=(u.clientX-a.left)/a.width;o=Math.max(0,Math.min(1,o));const E=a.left-this.getBoundingClientRect().left+o*a.width;this.thumbnailPreview.style.left=`${E}px`;const v=o*s,M=new r.CustomEvent(h.MEDIA_PREVIEW_REQUEST,{composed:!0,bubbles:!0,detail:v});this.dispatchEvent(M)},r.addEventListener("mousemove",t,!1)},l=()=>{r.removeEventListener("mousemove",t)};let m=!1,g=u=>{const s=this.getAttribute(n.MEDIA_DURATION);if(!m&&s){m=!0,i();let a=o=>{o.target!=this&&!this.contains(o.target)&&(r.removeEventListener("mousemove",a),m=!1,l())};r.addEventListener("mousemove",a,!1)}};this.addEventListener("mousemove",g,!1)}}I("media-time-range",p);export default p; diff --git a/dist/media-title-element.js b/dist/media-title-element.js new file mode 100644 index 000000000..44bc4789a --- /dev/null +++ b/dist/media-title-element.js @@ -0,0 +1,9 @@ +import{defineCustomElement as o}from"./utils/defineCustomElement.js";import{Window as s,Document as l}from"./utils/server-safe-globals.js";const e=l.createElement("template");e.innerHTML=` + + + +`;class t extends s.HTMLElement{constructor(){super();this.attachShadow({mode:"open"}),this.shadowRoot.appendChild(e.content.cloneNode(!0))}}o("media-title-bar",t);export default t; diff --git a/dist/media-volume-range.js b/dist/media-volume-range.js new file mode 100644 index 000000000..979b12d85 --- /dev/null +++ b/dist/media-volume-range.js @@ -0,0 +1 @@ +import{Window as i}from"./utils/server-safe-globals.js";import u from"./media-chrome-range.js";import{defineCustomElement as l}from"./utils/defineCustomElement.js";import{MediaUIAttributes as n,MediaUIEvents as m}from"./constants.js";import{nouns as d}from"./labels/labels.js";const c=100,E=t=>{var s;if(t.getAttribute(n.MEDIA_MUTED)!=null)return 0;const r=+((s=t.getAttribute(n.MEDIA_VOLUME))!=null?s:1);return Math.round(r*t.range.max)},M=({value:t,max:e})=>`${Math.round(t/e*100)}%`;class a extends u{static get observedAttributes(){return[...super.observedAttributes,n.MEDIA_VOLUME,n.MEDIA_MUTED]}constructor(){super();this.range.max=c,this.range.addEventListener("input",()=>{const r=this.range.value/this.range.max,s=new i.CustomEvent(m.MEDIA_VOLUME_REQUEST,{composed:!0,bubbles:!0,detail:r});this.dispatchEvent(s)})}connectedCallback(){this.range.setAttribute("aria-label",d.VOLUME()),super.connectedCallback()}attributeChangedCallback(e,r,s){if(e===n.MEDIA_VOLUME||e===n.MEDIA_MUTED){const o=E(this);this.range.value=o,this.range.setAttribute("aria-valuetext",M(this.range)),this.updateBar()}super.attributeChangedCallback(e,r,s)}}l("media-volume-range",a);export default a; diff --git a/dist/themes/media-theme-netflix.js b/dist/themes/media-theme-netflix.js new file mode 100644 index 000000000..948c4abd3 --- /dev/null +++ b/dist/themes/media-theme-netflix.js @@ -0,0 +1,146 @@ +import a from"./media-theme.js";const o=` + + + + + + + + + + + + + +
+
+

My Title

P2:E4 Episode 4 +
+
+ + + + +
+
+`;class e extends a{constructor(t={}){super(o,{...t})}}customElements.get("media-theme-netflix")||customElements.define("media-theme-netflix",e);export default e; diff --git a/dist/themes/media-theme.js b/dist/themes/media-theme.js new file mode 100644 index 000000000..f59734a7b --- /dev/null +++ b/dist/themes/media-theme.js @@ -0,0 +1 @@ +class m extends HTMLElement{constructor(e="",t={}){super();t=Object.assign({},t),this.template=e;const n=document.createElement("template");n.innerHTML=e;const o=this.attachShadow({mode:"open"});o.appendChild(n.content.cloneNode(!0)),this.mediaController=o.querySelector("media-controller")}}customElements.get("media-theme")||customElements.define("media-theme",m);export default m; diff --git a/dist/utils/captions.js b/dist/utils/captions.js new file mode 100644 index 000000000..e4b864c8f --- /dev/null +++ b/dist/utils/captions.js @@ -0,0 +1 @@ +export const splitTextTracksStr=(t="")=>t.split(/\s+/),parseTextTrackStr=(t="")=>{const[r,e]=t.split(":"),o=e?decodeURIComponent(e):void 0;return{language:r,label:o}},parseTextTracksStr=(t="",r={})=>splitTextTracksStr(t).map(e=>{const o=parseTextTrackStr(e);return{...r,...o}}),parseTracks=t=>Array.isArray(t)?t.map(r=>typeof r=="string"?parseTextTrackStr(r):r):typeof t=="string"?parseTextTracksStr(t):[t],formatTextTrackObj=({label:t,language:r}={})=>t?`${r}:${encodeURIComponent(t)}`:r,stringifyTextTrackList=(t=[])=>Array.prototype.map.call(t,formatTextTrackObj).join(" "),isMatchingPropOf=(t,r)=>e=>e[t]===r,textTrackObjAsPred=t=>{const r=Object.entries(t).map(([e,o])=>isMatchingPropOf(e,o));return e=>r.every(o=>o(e))},updateTracksModeTo=(t,r=[],e=[])=>{const o=parseTracks(e).map(textTrackObjAsPred),s=n=>o.some(p=>p(n));Array.from(r).filter(s).forEach(n=>{n.mode=t})},getTextTracksList=(t,r=()=>!0)=>{if(!(t==null?void 0:t.textTracks))return[];const e=typeof r=="function"?r:textTrackObjAsPred(r);return Array.from(t.textTracks).filter(e)}; diff --git a/dist/utils/defineCustomElement.js b/dist/utils/defineCustomElement.js new file mode 100644 index 000000000..8dbccc9a9 --- /dev/null +++ b/dist/utils/defineCustomElement.js @@ -0,0 +1 @@ +import{Window as s}from"./server-safe-globals.js";export function defineCustomElement(e,o){s.customElements.get(e)||(s.customElements.define(e,o),s[o.name]=o)} diff --git a/dist/utils/element-utils.js b/dist/utils/element-utils.js new file mode 100644 index 000000000..f2029f09b --- /dev/null +++ b/dist/utils/element-utils.js @@ -0,0 +1 @@ +export const updateIconText=(t,e,n=".value")=>{const o=t.querySelector(n);!o||(o.textContent=e)},getAllSlotted=(t,e)=>{const n=`slot[name="${e}"]`,o=t.shadowRoot.querySelector(n);return o?o.children:[]},getSlotted=(t,e)=>getAllSlotted(t,e)[0]; diff --git a/dist/utils/fullscreenApi.js b/dist/utils/fullscreenApi.js new file mode 100644 index 000000000..b3574bacc --- /dev/null +++ b/dist/utils/fullscreenApi.js @@ -0,0 +1 @@ +import{Document as e}from"./server-safe-globals.js";export const fullscreenApi={enter:"requestFullscreen",exit:"exitFullscreen",event:"fullscreenchange",element:"fullscreenElement",error:"fullscreenerror"};e.fullscreenElement===void 0&&(fullscreenApi.enter="webkitRequestFullScreen",fullscreenApi.exit=e.webkitExitFullscreen!=null?"webkitExitFullscreen":"webkitCancelFullScreen",fullscreenApi.event="webkitfullscreenchange",fullscreenApi.element="webkitFullscreenElement",fullscreenApi.error="webkitfullscreenerror"); diff --git a/dist/utils/server-safe-globals.js b/dist/utils/server-safe-globals.js new file mode 100644 index 000000000..3b3bcd286 --- /dev/null +++ b/dist/utils/server-safe-globals.js @@ -0,0 +1 @@ +const n={HTMLElement:function(){this.addEventListener=()=>{},this.removeEventListener=()=>{},this.dispatchEvent=()=>{}},customElements:{get:function(){},define:function(){},whenDefined:function(){}},CustomEvent:function(){}},t={createElement:function(){return new n.HTMLElement}};export const isServer=typeof window=="undefined"||typeof window.customElements=="undefined",Window=isServer?n:window,Document=isServer?t:window.document; diff --git a/dist/utils/stringUtils.js b/dist/utils/stringUtils.js new file mode 100644 index 000000000..032bf112c --- /dev/null +++ b/dist/utils/stringUtils.js @@ -0,0 +1 @@ +export function dashedToCamel(t){return t.split("-").map(function(e,o){return(o?e[0].toUpperCase():e[0].toLowerCase())+e.slice(1).toLowerCase()}).join("")}export function constToCamel(t,e=!1){return t.split("_").map(function(o,r){return(r||e?o[0].toUpperCase():o[0].toLowerCase())+o.slice(1).toLowerCase()}).join("")} diff --git a/dist/utils/time.js b/dist/utils/time.js new file mode 100644 index 000000000..1addd8cdb --- /dev/null +++ b/dist/utils/time.js @@ -0,0 +1 @@ +const m=t=>typeof t=="number"&&!Number.isNaN(t)&&Number.isFinite(t),u=[{singular:"hour",plural:"hours"},{singular:"minute",plural:"minutes"},{singular:"second",plural:"seconds"}],s=(t,i)=>{const n=t===1?u[i].singular:u[i].plural;return`${t} ${n}`};export const formatAsTimePhrase=t=>{if(!m(t))return"";const i=Math.abs(t),n=i!==t,r=new Date(0,0,0,0,0,i,0);return`${[r.getHours(),r.getMinutes(),r.getSeconds()].map((o,f)=>o&&s(o,f)).filter(o=>o).join(", ")}${n?" remaining":""}`};export function formatTime(t,i){let n=!1;t<0&&(n=!0,t=0-t),t=t<0?0:t;let r=Math.floor(t%60),e=Math.floor(t/60%60),a=Math.floor(t/3600);const l=Math.floor(i/60%60),o=Math.floor(i/3600);return(isNaN(t)||t===Infinity)&&(a=e=r="-"),a=a>0||o>0?a+":":"",e=((a||l>=10)&&e<10?"0"+e:e)+":",r=r<10?"0"+r:r,(n?"-":"")+a+e+r}