Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Airplay, Captions, and Cast button props #587

Merged
merged 31 commits into from
May 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
2b0b0b5
props for airplay button
AdamJaggard May 3, 2023
fefbaac
media captions button props
AdamJaggard May 3, 2023
dcf6bd2
media captions account for null and comments
AdamJaggard May 3, 2023
b68cd4d
airplay button handle null
AdamJaggard May 3, 2023
d58130a
cast to string for anything remaining
AdamJaggard May 3, 2023
bc66b6b
cast button props
AdamJaggard May 3, 2023
c62e3cc
fix attr type
AdamJaggard May 4, 2023
2237983
treat subtitlesshowing the same as subtitleslist, move to utils in sa…
AdamJaggard May 5, 2023
d896e0f
remove random slashes
AdamJaggard Apr 20, 2023
e1762c8
remove informal public getters mediaSeekableEnd and mediaSeekableStart
AdamJaggard Apr 20, 2023
7e0901e
define attribute lists up front
AdamJaggard Apr 21, 2023
d92d6c4
lint
AdamJaggard Apr 21, 2023
9ea3c87
new setters and jsdoc comments
AdamJaggard Apr 21, 2023
5014127
type private prop
AdamJaggard Apr 21, 2023
0478f38
remove unneeded manual updates
AdamJaggard Apr 25, 2023
074f5a7
simplify attribute toggling
AdamJaggard Apr 25, 2023
678a9bd
don't update attribute if value hasn't changed
AdamJaggard Apr 25, 2023
83a321f
use @type and remove unnecessary setter annotations
AdamJaggard Apr 25, 2023
d7ebc21
allow undefined in mediaSeekable setter
AdamJaggard Apr 25, 2023
a234c08
allow undefined for setting current time
AdamJaggard May 3, 2023
fd207d2
update current time getter and setter
AdamJaggard May 4, 2023
1af0798
update duration getter and setter
AdamJaggard May 4, 2023
1bda9fa
element attr utility functions
AdamJaggard May 4, 2023
0a040b3
prettier lint
AdamJaggard May 4, 2023
2f5d477
string attr utils
AdamJaggard May 9, 2023
4bab806
use new utils
AdamJaggard May 9, 2023
33f55d6
fix import
AdamJaggard May 10, 2023
88193bc
comments
AdamJaggard May 10, 2023
d9be1c6
captions -> subtitles
AdamJaggard May 10, 2023
0f53da7
accept array of texttrack-like objects
AdamJaggard May 11, 2023
9b87af6
remove getAttribute
AdamJaggard May 11, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) ?? '');
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: There's probably no need to stringify the attribute value as it's already stringified.

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}`);
}