Skip to content

Commit

Permalink
feat: Airplay, Captions, and Cast button props (#587)
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamJaggard authored May 11, 2023
1 parent c843a5e commit 14156f7
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 15 deletions.
12 changes: 12 additions & 0 deletions src/js/media-airplay-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import MediaChromeButton from './media-chrome-button.js';
import { window, document } from './utils/server-safe-globals.js';
import { MediaUIEvents, MediaUIAttributes } from './constants.js';
import { verbs } from './labels/labels.js';
import { getStringAttr, setStringAttr } from './utils/element-utils.js';

const airplayIcon = `<svg aria-hidden="true" viewBox="0 0 26 24">
<path d="M22.13 3H3.87a.87.87 0 0 0-.87.87v13.26a.87.87 0 0 0 .87.87h3.4L9 16H5V5h16v11h-4l1.72 2h3.4a.87.87 0 0 0 .87-.87V3.87a.87.87 0 0 0-.86-.87Zm-8.75 11.44a.5.5 0 0 0-.76 0l-4.91 5.73a.5.5 0 0 0 .38.83h9.82a.501.501 0 0 0 .38-.83l-4.91-5.73Z"/>
Expand Down Expand Up @@ -39,6 +40,17 @@ class MediaAirplayButton extends MediaChromeButton {
super.connectedCallback();
}

/**
* @type {string | undefined} Airplay unavailability state
*/
get mediaAirplayUnavailable() {
return getStringAttr(this, MediaUIAttributes.MEDIA_AIRPLAY_UNAVAILABLE);
}

set mediaAirplayUnavailable(value) {
setStringAttr(this, MediaUIAttributes.MEDIA_AIRPLAY_UNAVAILABLE, value);
}

handleClick() {
const evt = new window.CustomEvent(MediaUIEvents.MEDIA_AIRPLAY_REQUEST, {
composed: true,
Expand Down
72 changes: 67 additions & 5 deletions src/js/media-captions-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@ import MediaChromeButton from './media-chrome-button.js';
import { window, document } from './utils/server-safe-globals.js';
import { MediaUIAttributes } from './constants.js';
import { nouns } from './labels/labels.js';
import { areSubsOn, toggleSubsCaps } from './utils/captions.js';
import {
areSubsOn,
parseTextTracksStr,
stringifyTextTrackList,
toggleSubsCaps,
} from './utils/captions.js';

const ccIconOn = `<svg aria-hidden="true" viewBox="0 0 26 24">
<path d="M22.83 5.68a2.58 2.58 0 0 0-2.3-2.5c-3.62-.24-11.44-.24-15.06 0a2.58 2.58 0 0 0-2.3 2.5c-.23 4.21-.23 8.43 0 12.64a2.58 2.58 0 0 0 2.3 2.5c3.62.24 11.44.24 15.06 0a2.58 2.58 0 0 0 2.3-2.5c.23-4.21.23-8.43 0-12.64Zm-11.39 9.45a3.07 3.07 0 0 1-1.91.57 3.06 3.06 0 0 1-2.34-1 3.75 3.75 0 0 1-.92-2.67 3.92 3.92 0 0 1 .92-2.77 3.18 3.18 0 0 1 2.43-1 2.94 2.94 0 0 1 2.13.78c.364.359.62.813.74 1.31l-1.43.35a1.49 1.49 0 0 0-1.51-1.17 1.61 1.61 0 0 0-1.29.58 2.79 2.79 0 0 0-.5 1.89 3 3 0 0 0 .49 1.93 1.61 1.61 0 0 0 1.27.58 1.48 1.48 0 0 0 1-.37 2.1 2.1 0 0 0 .59-1.14l1.4.44a3.23 3.23 0 0 1-1.07 1.69Zm7.22 0a3.07 3.07 0 0 1-1.91.57 3.06 3.06 0 0 1-2.34-1 3.75 3.75 0 0 1-.92-2.67 3.88 3.88 0 0 1 .93-2.77 3.14 3.14 0 0 1 2.42-1 3 3 0 0 1 2.16.82 2.8 2.8 0 0 1 .73 1.31l-1.43.35a1.49 1.49 0 0 0-1.51-1.21 1.61 1.61 0 0 0-1.29.58A2.79 2.79 0 0 0 15 12a3 3 0 0 0 .49 1.93 1.61 1.61 0 0 0 1.27.58 1.44 1.44 0 0 0 1-.37 2.1 2.1 0 0 0 .6-1.15l1.4.44a3.17 3.17 0 0 1-1.1 1.7Z"/>
Expand All @@ -13,15 +18,14 @@ const ccIconOff = `<svg aria-hidden="true" viewBox="0 0 26 24">
</svg>`;

const slotTemplate = document.createElement('template');
slotTemplate.innerHTML = /*html*/`
slotTemplate.innerHTML = /*html*/ `
<style>
:host([aria-checked="true"]) slot:not([name=on]) > *,
:host([aria-checked="true"]) ::slotted(:not([slot=on])) {
display: none !important;
}
${/* Double negative, but safer if display doesn't equal 'block' */ ''
}
${/* Double negative, but safer if display doesn't equal 'block' */ ''}
:host(:not([aria-checked="true"])) slot:not([name=off]) > *,
:host(:not([aria-checked="true"])) ::slotted(:not([slot=off])) {
display: none !important;
Expand All @@ -36,12 +40,43 @@ const updateAriaChecked = (el) => {
el.setAttribute('aria-checked', areSubsOn(el));
};

/**
* @param {any} el Should be HTMLElement but issues with window shim
* @param {string} attrName
* @returns {Array<Object>} An array of TextTrack-like objects.
*/
const getSubtitlesListAttr = (el, attrName) => {
const attrVal = el.getAttribute(attrName);
return attrVal ? parseTextTracksStr(attrVal) : [];
};

/**
*
* @param {any} el Should be HTMLElement but issues with window shim
* @param {string} attrName
* @param {Array<Object>} list An array of TextTrack-like objects
*/
const setSubtitlesListAttr = (el, attrName, list) => {
// null, undefined, and empty arrays are treated as "no value" here
if (!list) {
el.removeAttribute(attrName);
return;
}

// don't set if the new value is the same as existing
const newValStr = stringifyTextTrackList(list);
const oldValStr = stringifyTextTrackList(el.getAttribute(attrName) ?? '');
if (oldValStr === newValStr) return;

el.setAttribute(attrName, newValStr);
};

/**
* @slot on - An element that will be shown while closed captions or subtitles are on.
* @slot off - An element that will be shown while closed captions or subtitles are off.
*
* @attr {string} mediasubtitleslist - (read-only) A list of all subtitles and captions.
* @attr {boolean} mediasubtitlesshowing - (read-only) A list of the showing subtitles and captions.
* @attr {string} mediasubtitlesshowing - (read-only) A list of the showing subtitles and captions.
*
* @cssproperty [--media-captions-button-display = inline-flex] - `display` property of button.
*/
Expand Down Expand Up @@ -76,6 +111,33 @@ class MediaCaptionsButton extends MediaChromeButton {
super.attributeChangedCallback(attrName, oldValue, newValue);
}

/**
* @type {Array<object>} An array of TextTrack-like objects.
* Objects must have the properties: kind, language, and label.
*/
get mediaSubtitlesList() {
return getSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_LIST);
}

set mediaSubtitlesList(list) {
setSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_LIST, list);
}

/**
* @type {Array<object>} An array of TextTrack-like objects.
* Objects must have the properties: kind, language, and label.
*/
get mediaSubtitlesShowing() {
return getSubtitlesListAttr(
this,
MediaUIAttributes.MEDIA_SUBTITLES_SHOWING
);
}

set mediaSubtitlesShowing(list) {
setSubtitlesListAttr(this, MediaUIAttributes.MEDIA_SUBTITLES_SHOWING, list);
}

handleClick() {
toggleSubsCaps(this);
}
Expand Down
47 changes: 38 additions & 9 deletions src/js/media-cast-button.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,32 @@ import MediaChromeButton from './media-chrome-button.js';
import { window, document } from './utils/server-safe-globals.js';
import { MediaUIEvents, MediaUIAttributes } from './constants.js';
import { verbs } from './labels/labels.js';
import {
getBooleanAttr,
setBooleanAttr,
getStringAttr,
setStringAttr,
} from './utils/element-utils.js';

const enterIcon = `<svg aria-hidden="true" viewBox="0 0 24 24"><g><path class="cast_caf_icon_arch0" d="M1,18 L1,21 L4,21 C4,19.3 2.66,18 1,18 L1,18 Z"/><path class="cast_caf_icon_arch1" d="M1,14 L1,16 C3.76,16 6,18.2 6,21 L8,21 C8,17.13 4.87,14 1,14 L1,14 Z"/><path class="cast_caf_icon_arch2" d="M1,10 L1,12 C5.97,12 10,16.0 10,21 L12,21 C12,14.92 7.07,10 1,10 L1,10 Z"/><path class="cast_caf_icon_box" d="M21,3 L3,3 C1.9,3 1,3.9 1,5 L1,8 L3,8 L3,5 L21,5 L21,19 L14,19 L14,21 L21,21 C22.1,21 23,20.1 23,19 L23,5 C23,3.9 22.1,3 21,3 L21,3 Z"/></g></svg>`;

const exitIcon = `<svg aria-hidden="true" viewBox="0 0 24 24"><g><path class="cast_caf_icon_arch0" d="M1,18 L1,21 L4,21 C4,19.3 2.66,18 1,18 L1,18 Z"/><path class="cast_caf_icon_arch1" d="M1,14 L1,16 C3.76,16 6,18.2 6,21 L8,21 C8,17.13 4.87,14 1,14 L1,14 Z"/><path class="cast_caf_icon_arch2" d="M1,10 L1,12 C5.97,12 10,16.0 10,21 L12,21 C12,14.92 7.07,10 1,10 L1,10 Z"/><path class="cast_caf_icon_box" d="M21,3 L3,3 C1.9,3 1,3.9 1,5 L1,8 L3,8 L3,5 L21,5 L21,19 L14,19 L14,21 L21,21 C22.1,21 23,20.1 23,19 L23,5 C23,3.9 22.1,3 21,3 L21,3 Z"/><path class="cast_caf_icon_boxfill" d="M5,7 L5,8.63 C8,8.6 13.37,14 13.37,17 L19,17 L19,7 Z"/></g></svg>`;

const slotTemplate = document.createElement('template');
slotTemplate.innerHTML = /*html*/`
slotTemplate.innerHTML = /*html*/ `
<style>
:host([${MediaUIAttributes.MEDIA_IS_CASTING}]) slot:not([name=exit]) > *,
:host([${MediaUIAttributes.MEDIA_IS_CASTING}]) ::slotted(:not([slot=exit])) {
display: none !important;
}
${/* Double negative, but safer if display doesn't equal 'block' */ ''}
:host(:not([${MediaUIAttributes.MEDIA_IS_CASTING}])) slot:not([name=enter]) > *,
:host(:not([${MediaUIAttributes.MEDIA_IS_CASTING}])) ::slotted(:not([slot=enter])) {
:host(:not([${
MediaUIAttributes.MEDIA_IS_CASTING
}])) slot:not([name=enter]) > *,
:host(:not([${
MediaUIAttributes.MEDIA_IS_CASTING
}])) ::slotted(:not([slot=enter])) {
display: none !important;
}
</style>
Expand All @@ -27,11 +37,8 @@ slotTemplate.innerHTML = /*html*/`
`;

const updateAriaLabel = (el) => {
const isCast =
el.getAttribute(MediaUIAttributes.MEDIA_IS_CASTING) != null;
const label = isCast
? verbs.EXIT_CAST()
: verbs.ENTER_CAST();
const isCast = el.getAttribute(MediaUIAttributes.MEDIA_IS_CASTING) != null;
const label = isCast ? verbs.EXIT_CAST() : verbs.ENTER_CAST();
el.setAttribute('aria-label', label);
};

Expand Down Expand Up @@ -69,9 +76,31 @@ class MediaCastButton extends MediaChromeButton {
super.attributeChangedCallback(attrName, oldValue, newValue);
}

/**
* @type {boolean} Are we currently casting
*/
get mediaIsCasting() {
return getBooleanAttr(this, MediaUIAttributes.MEDIA_IS_CASTING);
}

set mediaIsCasting(value) {
setBooleanAttr(this, MediaUIAttributes.MEDIA_IS_CASTING, value);
}

/**
* @type {string | undefined} Cast unavailability state
*/
get mediaCastUnavailable() {
return getStringAttr(this, MediaUIAttributes.MEDIA_CAST_UNAVAILABLE);
}

set mediaCastUnavailable(value) {
setStringAttr(this, MediaUIAttributes.MEDIA_CAST_UNAVAILABLE, value);
}

handleClick() {
const eventName =
this.getAttribute(MediaUIAttributes.MEDIA_IS_CASTING) != null
this.mediaIsCasting != null
? MediaUIEvents.MEDIA_EXIT_CAST_REQUEST
: MediaUIEvents.MEDIA_ENTER_CAST_REQUEST;
this.dispatchEvent(
Expand Down
27 changes: 26 additions & 1 deletion src/js/utils/element-utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ export function getBooleanAttr(el, attrName) {
}

/**
*
* @param {any} el (Should be an HTMLElement, but need any for SSR cases)
* @param {string} attrName
* @param {boolean} value
Expand All @@ -129,3 +128,29 @@ export function setBooleanAttr(el, attrName, value) {

el.toggleAttribute(attrName, value);
}

/**
* @param {any} el (Should be an HTMLElement, but need any for SSR cases)
* @param {string} attrName
*/
export function getStringAttr(el, attrName) {
return el.getAttribute(attrName) ?? undefined;
}

/**
* @param {*} el (Should be an HTMLElement, but need any for SSR cases)
* @param {string} attrName
* @param {string} value
*/
export function setStringAttr(el, attrName, value) {
// avoid triggering a set if no change
if (getStringAttr(el, attrName) == value) return;

// also handles undefined
if (value == null) {
el.removeAttribute(attrName);
return;
}

el.setAttribute(attrName, `${value}`);
}

1 comment on commit 14156f7

@vercel
Copy link

@vercel vercel bot commented on 14156f7 May 11, 2023

Choose a reason for hiding this comment

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

Please sign in to comment.