Skip to content

Commit

Permalink
feat: Media Time Range and Volume Range props (#599)
Browse files Browse the repository at this point in the history
  • Loading branch information
AdamJaggard authored May 11, 2023
1 parent 7126ddf commit 61d6ddd
Show file tree
Hide file tree
Showing 2 changed files with 168 additions and 41 deletions.
158 changes: 122 additions & 36 deletions src/js/media-time-range.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,16 @@ import { window, document } from './utils/server-safe-globals.js';
import { MediaUIEvents, MediaUIAttributes } from './constants.js';
import { nouns } from './labels/labels.js';
import { formatAsTimePhrase } from './utils/time.js';
import { getOrInsertCSSRule, closestComposedNode } from './utils/element-utils.js';
import {
getOrInsertCSSRule,
closestComposedNode,
getBooleanAttr,
setBooleanAttr,
getNumericAttr,
setNumericAttr,
getStringAttr,
setStringAttr,
} from './utils/element-utils.js';

const DEFAULT_MISSING_TIME_PHRASE = 'video not loaded, unknown time.';

Expand All @@ -18,7 +27,7 @@ const updateAriaValueText = (el) => {
};

const template = document.createElement('template');
template.innerHTML = /*html*/`
template.innerHTML = /*html*/ `
<style>
:host {
--media-preview-border-radius: 3px;
Expand All @@ -28,7 +37,7 @@ template.innerHTML = /*html*/`
#preview-rail,
#current-rail {
${/* 1% of parent element and upscale by 100 in the translateX() */''}
${/* 1% of parent element and upscale by 100 in the translateX() */ ''}
width: 1%;
position: absolute;
left: 0;
Expand Down Expand Up @@ -138,7 +147,7 @@ template.innerHTML = /*html*/`
* @attr {boolean} medialoading - (read-only) Present if the media is loading.
* @attr {string} mediacurrenttime - (read-only) Set to the current media time.
* @attr {string} mediapreviewimage - (read-only) Set to the timeline preview image URL.
* @attr {string} mediapreviewcoords - (read-only) Set to the active preview image coordinates.
* @attr {string} mediapreviewtime - (read-only) Set to the timeline preview time.
*
* @csspart box - A CSS part that selects both the preview and current box elements.
* @csspart preview-box - A CSS part that selects the preview box element.
Expand Down Expand Up @@ -267,14 +276,14 @@ class MediaTimeRange extends MediaChromeRange {
}
}
if (attrName === MediaUIAttributes.MEDIA_DURATION) {
this.range.max = this.mediaSeekableEnd ?? this.mediaDuration ?? 1000;
this.range.max = this.#mediaSeekableEnd ?? this.mediaDuration ?? 1000;
updateAriaValueText(this);
this.updateBar();
this.updateCurrentBox();
}
if (attrName === MediaUIAttributes.MEDIA_SEEKABLE) {
this.range.min = this.mediaSeekableStart ?? 0;
this.range.max = this.mediaSeekableEnd ?? this.mediaDuration ?? 1000;
this.range.min = this.#mediaSeekableStart ?? 0;
this.range.max = this.#mediaSeekableEnd ?? this.mediaDuration ?? 1000;
updateAriaValueText(this);
this.updateBar();
}
Expand All @@ -291,29 +300,65 @@ class MediaTimeRange extends MediaChromeRange {
super.attributeChangedCallback(attrName, oldValue, newValue);
}

/**
* @type {boolean} Is the media paused
*/
get mediaPaused() {
return this.hasAttribute(MediaUIAttributes.MEDIA_PAUSED);
return getBooleanAttr(this, MediaUIAttributes.MEDIA_PAUSED);
}

set mediaPaused(value) {
setBooleanAttr(this, MediaUIAttributes.MEDIA_PAUSED, value);
}

/**
* @type {boolean} Is the media loading
*/
get mediaLoading() {
return this.hasAttribute(MediaUIAttributes.MEDIA_LOADING);
return getBooleanAttr(this, MediaUIAttributes.MEDIA_LOADING);
}

set mediaLoading(value) {
setBooleanAttr(this, MediaUIAttributes.MEDIA_LOADING, value);
}

/**
* @type {number | undefined}
*/
get mediaDuration() {
const attrVal = this.getAttribute(MediaUIAttributes.MEDIA_DURATION);
return attrVal != null ? +attrVal : undefined;
return getNumericAttr(this, MediaUIAttributes.MEDIA_DURATION);
}

set mediaDuration(value) {
setNumericAttr(this, MediaUIAttributes.MEDIA_DURATION, value);
}

/**
* @type {number | undefined}
*/
get mediaCurrentTime() {
const attrVal = this.getAttribute(MediaUIAttributes.MEDIA_CURRENT_TIME);
return attrVal != null ? +attrVal : undefined;
return getNumericAttr(this, MediaUIAttributes.MEDIA_CURRENT_TIME);
}

set mediaCurrentTime(value) {
setNumericAttr(this, MediaUIAttributes.MEDIA_CURRENT_TIME, value);
}

/**
* @type {number}
*/
get mediaPlaybackRate() {
const attrVal = this.getAttribute(MediaUIAttributes.MEDIA_PLAYBACK_RATE);
return attrVal != null ? +attrVal : 1;
return getNumericAttr(this, MediaUIAttributes.MEDIA_PLAYBACK_RATE) ?? 1;
}

set mediaPlaybackrate(value) {
setNumericAttr(this, MediaUIAttributes.MEDIA_PLAYBACK_RATE, value);
}

/**
* @type {Array<Array<number>>} An array of ranges, each range being an array of two numbers.
* e.g. [[1, 2], [3, 4]]
*/
get mediaBuffered() {
const buffered = this.getAttribute(MediaUIAttributes.MEDIA_BUFFERED);
if (!buffered) return [];
Expand All @@ -322,23 +367,69 @@ class MediaTimeRange extends MediaChromeRange {
.map((timePair) => timePair.split(':').map((timeStr) => +timeStr));
}

set mediaBuffered(list) {
if (!list) {
this.removeAttribute(MediaUIAttributes.MEDIA_BUFFERED);
return;
}
const strVal = list.map((n1, n2) => `${n1}:${n2}`).join(' ');
this.setAttribute(MediaUIAttributes.MEDIA_BUFFERED, strVal);
}

/**
* Range of values that can be seeked to
* @type {Array<number> | undefined} An array of two numbers [start, end]
*/
get mediaSeekable() {
const seekable = this.getAttribute(MediaUIAttributes.MEDIA_SEEKABLE);
if (!seekable) return undefined;
// Only currently supports a single, contiguous seekable range (CJP)
return seekable.split(':').map((time) => +time);
}

get mediaSeekableEnd() {
set mediaSeekable(range) {
if (range == null) {
this.removeAttribute(MediaUIAttributes.MEDIA_SEEKABLE);
return;
}
this.setAttribute(MediaUIAttributes.MEDIA_SEEKABLE, range.join(':'));
}

/**
* @type {number | undefined}
*/
get #mediaSeekableEnd() {
const [, end] = this.mediaSeekable ?? [];
return end;
}

get mediaSeekableStart() {
get #mediaSeekableStart() {
const [start] = this.mediaSeekable ?? [];
return start;
}

/**
* @type {string | undefined} The url of the preview image
*/
get mediaPreviewImage() {
return getStringAttr(this, MediaUIAttributes.MEDIA_PREVIEW_IMAGE);
}

set mediaPreviewImage(value) {
setStringAttr(this, MediaUIAttributes.MEDIA_PREVIEW_IMAGE, value);
}

/**
* @type {number | undefined}
*/
get mediaPreviewTime() {
return getNumericAttr(this, MediaUIAttributes.MEDIA_PREVIEW_TIME);
}

set mediaPreviewTime(value) {
setNumericAttr(this, MediaUIAttributes.MEDIA_PREVIEW_TIME, value);
}

/* Add a buffered progress bar */
getBarColors() {
let colorsArray = super.getBarColors();
Expand Down Expand Up @@ -376,10 +467,7 @@ class MediaTimeRange extends MediaChromeRange {

const boxRatio = this.range.value / (this.range.max - this.range.min);
const boxPos = this.#getBoxPosition(this.#currentBox, boxRatio);
const { style } = getOrInsertCSSRule(
this.shadowRoot,
'#current-rail'
);
const { style } = getOrInsertCSSRule(this.shadowRoot, '#current-rail');
style.transform = `translateX(${boxPos})`;
}

Expand Down Expand Up @@ -413,7 +501,7 @@ class MediaTimeRange extends MediaChromeRange {

this.updatePointerBar(evt);

const duration = +this.getAttribute(MediaUIAttributes.MEDIA_DURATION);
const duration = this.mediaDuration;
// If no duration we can't calculate which time to show
if (!duration) return;

Expand All @@ -424,10 +512,7 @@ class MediaTimeRange extends MediaChromeRange {
mouseRatio = Math.max(0, Math.min(1, mouseRatio));

const boxPos = this.#getBoxPosition(this.#previewBox, mouseRatio);
const { style } = getOrInsertCSSRule(
this.shadowRoot,
'#preview-rail'
);
const { style } = getOrInsertCSSRule(this.shadowRoot, '#preview-rail');
style.transform = `translateX(${boxPos})`;

const detail = mouseRatio * duration;
Expand All @@ -436,10 +521,10 @@ class MediaTimeRange extends MediaChromeRange {
{ composed: true, bubbles: true, detail }
);
this.dispatchEvent(mediaPreviewEvt);
}
};

// Trigger when the mouse moves over the range
#rangeEntered = false
#rangeEntered = false;

#offRangeHandler = (evt) => {
if (
Expand All @@ -451,20 +536,21 @@ class MediaTimeRange extends MediaChromeRange {
this.#rangeEntered = false;
this.#stopTrackingMouse();
}
}
};

#trackMouse = () => {
window.addEventListener('pointermove', this.#pointermoveHandler, false);
}
};

#stopTrackingMouse = () => {
window.removeEventListener('pointermove', this.#pointermoveHandler);
const endEvt = new window.CustomEvent(
MediaUIEvents.MEDIA_PREVIEW_REQUEST,
{ composed: true, bubbles: true, detail: null }
);
const endEvt = new window.CustomEvent(MediaUIEvents.MEDIA_PREVIEW_REQUEST, {
composed: true,
bubbles: true,
detail: null,
});
this.dispatchEvent(endEvt);
}
};

#rangepointermoveHandler = () => {
const mediaDurationStr = this.getAttribute(
Expand All @@ -476,7 +562,7 @@ class MediaTimeRange extends MediaChromeRange {

window.addEventListener('pointermove', this.#offRangeHandler, false);
}
}
};

#enableBoxes() {
this.addEventListener('pointermove', this.#rangepointermoveHandler, false);
Expand Down
51 changes: 46 additions & 5 deletions src/js/media-volume-range.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import { window } from './utils/server-safe-globals.js';
import MediaChromeRange from './media-chrome-range.js';
import { MediaUIAttributes, MediaUIEvents } from './constants.js';
import { nouns } from './labels/labels.js';
import {
getBooleanAttr,
getNumericAttr,
getStringAttr,
setBooleanAttr,
setNumericAttr,
setStringAttr,
} from './utils/element-utils.js';

const DEFAULT_MAX_VOLUME = 100;
const DEFAULT_VOLUME = 1;

const toVolume = (el) => {
const muted = el.getAttribute(MediaUIAttributes.MEDIA_MUTED) != null;
if (muted) return 0;

const volume = +(el.getAttribute(MediaUIAttributes.MEDIA_VOLUME) ?? 1);
return Math.round(volume * el.range.max);
if (el.mediaMuted) return 0;
return Math.round(el.mediaVolume * el.range.max);
};

const formatAsPercentString = ({ value, max }) =>
Expand Down Expand Up @@ -70,6 +76,41 @@ class MediaVolumeRange extends MediaChromeRange {
}
super.attributeChangedCallback(attrName, oldValue, newValue);
}

/**
* @type {number}
*/
get mediaVolume() {
return (
getNumericAttr(this, MediaUIAttributes.MEDIA_VOLUME) ?? DEFAULT_VOLUME
);
}

set mediaVolume(value) {
setNumericAttr(this, MediaUIAttributes.MEDIA_VOLUME, value);
}

/**
* @type {boolean} Is the media currently muted
*/
get mediaMuted() {
return getBooleanAttr(this, MediaUIAttributes.MEDIA_MUTED);
}

set mediaMuted(value) {
setBooleanAttr(this, MediaUIAttributes.MEDIA_MUTED, value);
}

/**
* @type {string | undefined} The volume unavailability state
*/
get mediaVolumeUnavailable() {
return getStringAttr(this, MediaUIAttributes.MEDIA_VOLUME_UNAVAILABLE);
}

set mediaVolumeUnavailable(value) {
setStringAttr(this, MediaUIAttributes.MEDIA_VOLUME_UNAVAILABLE, value);
}
}

if (!window.customElements.get('media-volume-range')) {
Expand Down

1 comment on commit 61d6ddd

@vercel
Copy link

@vercel vercel bot commented on 61d6ddd 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.