729 lines
28 KiB
JavaScript
729 lines
28 KiB
JavaScript
var __accessCheck = (obj, member, msg) => {
|
|
if (!member.has(obj))
|
|
throw TypeError("Cannot " + msg);
|
|
};
|
|
var __privateGet = (obj, member, getter) => {
|
|
__accessCheck(obj, member, "read from private field");
|
|
return getter ? getter.call(obj) : member.get(obj);
|
|
};
|
|
var __privateAdd = (obj, member, value) => {
|
|
if (member.has(obj))
|
|
throw TypeError("Cannot add the same private member more than once");
|
|
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
};
|
|
var __privateSet = (obj, member, value, setter) => {
|
|
__accessCheck(obj, member, "write to private field");
|
|
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
return value;
|
|
};
|
|
var __privateMethod = (obj, member, method) => {
|
|
__accessCheck(obj, member, "access private method");
|
|
return method;
|
|
};
|
|
var _rootNode, _animation, _boxes, _previewTime, _previewBox, _currentBox, _boxPaddingLeft, _boxPaddingRight, _mediaChaptersCues, _toggleRangeAnimation, toggleRangeAnimation_fn, _shouldRangeAnimate, shouldRangeAnimate_fn, _updateRange, _getElementRects, getElementRects_fn, _getBoxPosition, getBoxPosition_fn, _getBoxShiftPosition, getBoxShiftPosition_fn, _handlePointerMove, handlePointerMove_fn, _previewRequest, previewRequest_fn, _seekRequest, seekRequest_fn;
|
|
import { globalThis, document } from "./utils/server-safe-globals.js";
|
|
import { MediaChromeRange } from "./media-chrome-range.js";
|
|
import "./media-preview-thumbnail.js";
|
|
import "./media-preview-time-display.js";
|
|
import "./media-preview-chapter-display.js";
|
|
import { MediaUIEvents, MediaUIAttributes } from "./constants.js";
|
|
import { nouns } from "./labels/labels.js";
|
|
import { isValidNumber } from "./utils/utils.js";
|
|
import { formatAsTimePhrase } from "./utils/time.js";
|
|
import { isElementVisible } from "./utils/element-utils.js";
|
|
import { RangeAnimation } from "./utils/range-animation.js";
|
|
import {
|
|
getOrInsertCSSRule,
|
|
containsComposedNode,
|
|
closestComposedNode,
|
|
getBooleanAttr,
|
|
setBooleanAttr,
|
|
getNumericAttr,
|
|
setNumericAttr,
|
|
getStringAttr,
|
|
setStringAttr
|
|
} from "./utils/element-utils.js";
|
|
const DEFAULT_MISSING_TIME_PHRASE = "video not loaded, unknown time.";
|
|
const updateAriaValueText = (el) => {
|
|
const range = el.range;
|
|
const currentTimePhrase = formatAsTimePhrase(+calcTimeFromRangeValue(el));
|
|
const totalTimePhrase = formatAsTimePhrase(+el.mediaSeekableEnd);
|
|
const fullPhrase = !(currentTimePhrase && totalTimePhrase) ? DEFAULT_MISSING_TIME_PHRASE : `${currentTimePhrase} of ${totalTimePhrase}`;
|
|
range.setAttribute("aria-valuetext", fullPhrase);
|
|
};
|
|
const template = document.createElement("template");
|
|
template.innerHTML = /*html*/
|
|
`
|
|
<style>
|
|
:host {
|
|
--media-box-border-radius: 4px;
|
|
--media-box-padding-left: 10px;
|
|
--media-box-padding-right: 10px;
|
|
--media-preview-border-radius: var(--media-box-border-radius);
|
|
--media-box-arrow-offset: var(--media-box-border-radius);
|
|
--_control-background: var(--media-control-background, var(--media-secondary-color, rgb(20 20 30 / .7)));
|
|
--_preview-background: var(--media-preview-background, var(--_control-background));
|
|
|
|
${/* 1% rail width trick was off in Safari, contain: layout seems to
|
|
prevent the horizontal overflow as well. */
|
|
""}
|
|
contain: layout;
|
|
}
|
|
|
|
#buffered {
|
|
background: var(--media-time-range-buffered-color, rgb(255 255 255 / .4));
|
|
position: absolute;
|
|
height: 100%;
|
|
will-change: width;
|
|
}
|
|
|
|
#preview-rail,
|
|
#current-rail {
|
|
width: 100%;
|
|
position: absolute;
|
|
left: 0;
|
|
bottom: 100%;
|
|
pointer-events: none;
|
|
will-change: transform;
|
|
}
|
|
|
|
[part~="box"] {
|
|
width: min-content;
|
|
${/* absolute position is needed here so the box doesn't overflow the bounds */
|
|
""}
|
|
position: absolute;
|
|
bottom: 100%;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
transform: translateX(-50%);
|
|
}
|
|
|
|
[part~="current-box"] {
|
|
display: var(--media-current-box-display, var(--media-box-display, flex));
|
|
margin: var(--media-current-box-margin, var(--media-box-margin, 0 0 5px));
|
|
visibility: hidden;
|
|
}
|
|
|
|
[part~="preview-box"] {
|
|
display: var(--media-preview-box-display, var(--media-box-display, flex));
|
|
margin: var(--media-preview-box-margin, var(--media-box-margin, 0 0 5px));
|
|
transition-property: var(--media-preview-transition-property, visibility, opacity);
|
|
transition-duration: var(--media-preview-transition-duration-out, .25s);
|
|
transition-delay: var(--media-preview-transition-delay-out, 0s);
|
|
visibility: hidden;
|
|
opacity: 0;
|
|
}
|
|
|
|
:host(:is([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}], [${MediaUIAttributes.MEDIA_PREVIEW_TIME}])[dragging]) [part~="preview-box"] {
|
|
transition-duration: var(--media-preview-transition-duration-in, .5s);
|
|
transition-delay: var(--media-preview-transition-delay-in, .25s);
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
:host(:is([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}], [${MediaUIAttributes.MEDIA_PREVIEW_TIME}]):hover) [part~="preview-box"] {
|
|
transition-duration: var(--media-preview-transition-duration-in, .5s);
|
|
transition-delay: var(--media-preview-transition-delay-in, .25s);
|
|
visibility: visible;
|
|
opacity: 1;
|
|
}
|
|
}
|
|
|
|
media-preview-thumbnail,
|
|
::slotted(media-preview-thumbnail) {
|
|
visibility: hidden;
|
|
${/* delay changing these CSS props until the preview box transition is ended */
|
|
""}
|
|
transition: visibility 0s .25s;
|
|
transition-delay: calc(var(--media-preview-transition-delay-out, 0s) + var(--media-preview-transition-duration-out, .25s));
|
|
background: var(--media-preview-thumbnail-background, var(--_preview-background));
|
|
box-shadow: var(--media-preview-thumbnail-box-shadow, 0 0 4px rgb(0 0 0 / .2));
|
|
max-width: var(--media-preview-thumbnail-max-width, 180px);
|
|
max-height: var(--media-preview-thumbnail-max-height, 160px);
|
|
min-width: var(--media-preview-thumbnail-min-width, 120px);
|
|
min-height: var(--media-preview-thumbnail-min-height, 80px);
|
|
border: var(--media-preview-thumbnail-border);
|
|
border-radius: var(--media-preview-thumbnail-border-radius,
|
|
var(--media-preview-border-radius) var(--media-preview-border-radius) 0 0);
|
|
}
|
|
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}][dragging]) media-preview-thumbnail,
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}][dragging]) ::slotted(media-preview-thumbnail) {
|
|
transition-delay: var(--media-preview-transition-delay-in, .25s);
|
|
visibility: visible;
|
|
}
|
|
|
|
@media (hover: hover) {
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}]:hover) media-preview-thumbnail,
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}]:hover) ::slotted(media-preview-thumbnail) {
|
|
transition-delay: var(--media-preview-transition-delay-in, .25s);
|
|
visibility: visible;
|
|
}
|
|
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_TIME}]:hover) {
|
|
--media-time-range-hover-display: block;
|
|
}
|
|
}
|
|
|
|
media-preview-chapter-display,
|
|
::slotted(media-preview-chapter-display) {
|
|
font-size: var(--media-font-size, 13px);
|
|
line-height: 17px;
|
|
min-width: 0;
|
|
visibility: hidden;
|
|
${/* delay changing these CSS props until the preview box transition is ended */
|
|
""}
|
|
transition: min-width 0s, border-radius 0s, margin 0s, padding 0s, visibility 0s;
|
|
transition-delay: calc(var(--media-preview-transition-delay-out, 0s) + var(--media-preview-transition-duration-out, .25s));
|
|
background: var(--media-preview-chapter-background, var(--_preview-background));
|
|
border-radius: var(--media-preview-chapter-border-radius,
|
|
var(--media-preview-border-radius) var(--media-preview-border-radius)
|
|
var(--media-preview-border-radius) var(--media-preview-border-radius));
|
|
padding: var(--media-preview-chapter-padding, 3.5px 9px);
|
|
margin: var(--media-preview-chapter-margin, 0 0 5px);
|
|
text-shadow: var(--media-preview-chapter-text-shadow, 0 0 4px rgb(0 0 0 / .75));
|
|
}
|
|
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}]) media-preview-chapter-display,
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}]) ::slotted(media-preview-chapter-display) {
|
|
transition-delay: var(--media-preview-transition-delay-in, .25s);
|
|
border-radius: var(--media-preview-chapter-border-radius, 0);
|
|
padding: var(--media-preview-chapter-padding, 3.5px 9px 0);
|
|
margin: var(--media-preview-chapter-margin, 0);
|
|
min-width: 100%;
|
|
}
|
|
|
|
media-preview-chapter-display[${MediaUIAttributes.MEDIA_PREVIEW_CHAPTER}],
|
|
::slotted(media-preview-chapter-display[${MediaUIAttributes.MEDIA_PREVIEW_CHAPTER}]) {
|
|
visibility: visible;
|
|
}
|
|
|
|
media-preview-chapter-display:not([aria-valuetext]),
|
|
::slotted(media-preview-chapter-display:not([aria-valuetext])) {
|
|
display: none;
|
|
}
|
|
|
|
media-preview-time-display,
|
|
::slotted(media-preview-time-display),
|
|
media-time-display,
|
|
::slotted(media-time-display) {
|
|
font-size: var(--media-font-size, 13px);
|
|
line-height: 17px;
|
|
min-width: 0;
|
|
${/* delay changing these CSS props until the preview box transition is ended */
|
|
""}
|
|
transition: min-width 0s, border-radius 0s;
|
|
transition-delay: calc(var(--media-preview-transition-delay-out, 0s) + var(--media-preview-transition-duration-out, .25s));
|
|
background: var(--media-preview-time-background, var(--_preview-background));
|
|
border-radius: var(--media-preview-time-border-radius,
|
|
var(--media-preview-border-radius) var(--media-preview-border-radius)
|
|
var(--media-preview-border-radius) var(--media-preview-border-radius));
|
|
padding: var(--media-preview-time-padding, 3.5px 9px);
|
|
margin: var(--media-preview-time-margin, 0);
|
|
text-shadow: var(--media-preview-time-text-shadow, 0 0 4px rgb(0 0 0 / .75));
|
|
transform: translateX(min(
|
|
max(calc(50% - var(--_box-width) / 2),
|
|
calc(var(--_box-shift, 0))),
|
|
calc(var(--_box-width) / 2 - 50%)
|
|
));
|
|
}
|
|
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}]) media-preview-time-display,
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_IMAGE}]) ::slotted(media-preview-time-display) {
|
|
transition-delay: var(--media-preview-transition-delay-in, .25s);
|
|
border-radius: var(--media-preview-time-border-radius,
|
|
0 0 var(--media-preview-border-radius) var(--media-preview-border-radius));
|
|
min-width: 100%;
|
|
}
|
|
|
|
:host([${MediaUIAttributes.MEDIA_PREVIEW_TIME}]:hover) {
|
|
--media-time-range-hover-display: block;
|
|
}
|
|
|
|
[part~="arrow"],
|
|
::slotted([part~="arrow"]) {
|
|
display: var(--media-box-arrow-display, inline-block);
|
|
transform: translateX(min(
|
|
max(calc(50% - var(--_box-width) / 2 + var(--media-box-arrow-offset)),
|
|
calc(var(--_box-shift, 0))),
|
|
calc(var(--_box-width) / 2 - 50% - var(--media-box-arrow-offset))
|
|
));
|
|
${/* border-color has to come before border-top-color! */
|
|
""}
|
|
border-color: transparent;
|
|
border-top-color: var(--media-box-arrow-background, var(--_control-background));
|
|
border-width: var(--media-box-arrow-border-width,
|
|
var(--media-box-arrow-height, 5px) var(--media-box-arrow-width, 6px) 0);
|
|
border-style: solid;
|
|
justify-content: center;
|
|
height: 0;
|
|
}
|
|
</style>
|
|
<div id="preview-rail">
|
|
<slot name="preview" part="box preview-box">
|
|
<media-preview-thumbnail></media-preview-thumbnail>
|
|
<media-preview-chapter-display></media-preview-chapter-display>
|
|
<media-preview-time-display></media-preview-time-display>
|
|
<slot name="preview-arrow"><div part="arrow"></div></slot>
|
|
</slot>
|
|
</div>
|
|
<div id="current-rail">
|
|
<slot name="current" part="box current-box">
|
|
${/* Example: add the current time w/ arrow to the playhead
|
|
<media-time-display slot="current"></media-time-display>
|
|
<div part="arrow" slot="current"></div> */
|
|
""}
|
|
</slot>
|
|
</div>
|
|
`;
|
|
const calcRangeValueFromTime = (el, time = el.mediaCurrentTime) => {
|
|
const startTime = Number.isFinite(el.mediaSeekableStart) ? el.mediaSeekableStart : 0;
|
|
const endTime = Number.isFinite(el.mediaDuration) ? el.mediaDuration : el.mediaSeekableEnd;
|
|
if (Number.isNaN(endTime))
|
|
return 0;
|
|
const value = (time - startTime) / (endTime - startTime);
|
|
return Math.max(0, Math.min(value, 1));
|
|
};
|
|
const calcTimeFromRangeValue = (el, value = el.range.valueAsNumber) => {
|
|
const startTime = Number.isFinite(el.mediaSeekableStart) ? el.mediaSeekableStart : 0;
|
|
const endTime = Number.isFinite(el.mediaDuration) ? el.mediaDuration : el.mediaSeekableEnd;
|
|
if (Number.isNaN(endTime))
|
|
return 0;
|
|
return value * (endTime - startTime) + startTime;
|
|
};
|
|
class MediaTimeRange extends MediaChromeRange {
|
|
constructor() {
|
|
super();
|
|
__privateAdd(this, _toggleRangeAnimation);
|
|
__privateAdd(this, _shouldRangeAnimate);
|
|
__privateAdd(this, _getElementRects);
|
|
/**
|
|
* Get the position, max and min for the box in percentage.
|
|
* It's important this is in percentage so when the player is resized
|
|
* the box will move accordingly.
|
|
*/
|
|
__privateAdd(this, _getBoxPosition);
|
|
__privateAdd(this, _getBoxShiftPosition);
|
|
__privateAdd(this, _handlePointerMove);
|
|
__privateAdd(this, _previewRequest);
|
|
__privateAdd(this, _seekRequest);
|
|
__privateAdd(this, _rootNode, void 0);
|
|
__privateAdd(this, _animation, void 0);
|
|
__privateAdd(this, _boxes, void 0);
|
|
__privateAdd(this, _previewTime, void 0);
|
|
__privateAdd(this, _previewBox, void 0);
|
|
__privateAdd(this, _currentBox, void 0);
|
|
__privateAdd(this, _boxPaddingLeft, void 0);
|
|
__privateAdd(this, _boxPaddingRight, void 0);
|
|
__privateAdd(this, _mediaChaptersCues, void 0);
|
|
__privateAdd(this, _updateRange, (value) => {
|
|
if (this.dragging)
|
|
return;
|
|
if (isValidNumber(value)) {
|
|
this.range.valueAsNumber = value;
|
|
}
|
|
this.updateBar();
|
|
});
|
|
this.container.appendChild(template.content.cloneNode(true));
|
|
const track = this.shadowRoot.querySelector("#track");
|
|
track.insertAdjacentHTML("afterbegin", '<div id="buffered" part="buffered"></div>');
|
|
__privateSet(this, _boxes, this.shadowRoot.querySelectorAll('[part~="box"]'));
|
|
__privateSet(this, _previewBox, this.shadowRoot.querySelector('[part~="preview-box"]'));
|
|
__privateSet(this, _currentBox, this.shadowRoot.querySelector('[part~="current-box"]'));
|
|
const computedStyle = getComputedStyle(this);
|
|
__privateSet(this, _boxPaddingLeft, parseInt(
|
|
computedStyle.getPropertyValue("--media-box-padding-left")
|
|
));
|
|
__privateSet(this, _boxPaddingRight, parseInt(
|
|
computedStyle.getPropertyValue("--media-box-padding-right")
|
|
));
|
|
__privateSet(this, _animation, new RangeAnimation(this.range, __privateGet(this, _updateRange), 60));
|
|
}
|
|
static get observedAttributes() {
|
|
return [
|
|
...super.observedAttributes,
|
|
MediaUIAttributes.MEDIA_PAUSED,
|
|
MediaUIAttributes.MEDIA_DURATION,
|
|
MediaUIAttributes.MEDIA_SEEKABLE,
|
|
MediaUIAttributes.MEDIA_CURRENT_TIME,
|
|
MediaUIAttributes.MEDIA_PREVIEW_IMAGE,
|
|
MediaUIAttributes.MEDIA_PREVIEW_TIME,
|
|
MediaUIAttributes.MEDIA_PREVIEW_CHAPTER,
|
|
MediaUIAttributes.MEDIA_BUFFERED,
|
|
MediaUIAttributes.MEDIA_PLAYBACK_RATE,
|
|
MediaUIAttributes.MEDIA_LOADING,
|
|
MediaUIAttributes.MEDIA_ENDED
|
|
];
|
|
}
|
|
connectedCallback() {
|
|
var _a;
|
|
super.connectedCallback();
|
|
this.range.setAttribute("aria-label", nouns.SEEK());
|
|
__privateMethod(this, _toggleRangeAnimation, toggleRangeAnimation_fn).call(this);
|
|
__privateSet(this, _rootNode, this.getRootNode());
|
|
(_a = __privateGet(this, _rootNode)) == null ? void 0 : _a.addEventListener("transitionstart", this);
|
|
}
|
|
disconnectedCallback() {
|
|
var _a;
|
|
super.disconnectedCallback();
|
|
__privateMethod(this, _toggleRangeAnimation, toggleRangeAnimation_fn).call(this);
|
|
(_a = __privateGet(this, _rootNode)) == null ? void 0 : _a.removeEventListener("transitionstart", this);
|
|
__privateSet(this, _rootNode, null);
|
|
}
|
|
attributeChangedCallback(attrName, oldValue, newValue) {
|
|
super.attributeChangedCallback(attrName, oldValue, newValue);
|
|
if (oldValue == newValue)
|
|
return;
|
|
if (attrName === MediaUIAttributes.MEDIA_CURRENT_TIME || attrName === MediaUIAttributes.MEDIA_PAUSED || attrName === MediaUIAttributes.MEDIA_ENDED || attrName === MediaUIAttributes.MEDIA_LOADING || attrName === MediaUIAttributes.MEDIA_DURATION || attrName === MediaUIAttributes.MEDIA_SEEKABLE) {
|
|
__privateGet(this, _animation).update({
|
|
start: calcRangeValueFromTime(this),
|
|
duration: this.mediaSeekableEnd - this.mediaSeekableStart,
|
|
playbackRate: this.mediaPlaybackRate
|
|
});
|
|
__privateMethod(this, _toggleRangeAnimation, toggleRangeAnimation_fn).call(this);
|
|
updateAriaValueText(this);
|
|
} else if (attrName === MediaUIAttributes.MEDIA_BUFFERED) {
|
|
this.updateBufferedBar();
|
|
}
|
|
if (attrName === MediaUIAttributes.MEDIA_DURATION || attrName === MediaUIAttributes.MEDIA_SEEKABLE) {
|
|
this.mediaChaptersCues = __privateGet(this, _mediaChaptersCues);
|
|
this.updateBar();
|
|
}
|
|
}
|
|
get mediaChaptersCues() {
|
|
return __privateGet(this, _mediaChaptersCues);
|
|
}
|
|
set mediaChaptersCues(value) {
|
|
var _a;
|
|
__privateSet(this, _mediaChaptersCues, value);
|
|
this.updateSegments(
|
|
(_a = __privateGet(this, _mediaChaptersCues)) == null ? void 0 : _a.map((c) => ({
|
|
start: calcRangeValueFromTime(this, c.startTime),
|
|
end: calcRangeValueFromTime(this, c.endTime)
|
|
}))
|
|
);
|
|
}
|
|
/**
|
|
* Is the media paused
|
|
*/
|
|
get mediaPaused() {
|
|
return getBooleanAttr(this, MediaUIAttributes.MEDIA_PAUSED);
|
|
}
|
|
set mediaPaused(value) {
|
|
setBooleanAttr(this, MediaUIAttributes.MEDIA_PAUSED, value);
|
|
}
|
|
/**
|
|
* Is the media loading
|
|
*/
|
|
get mediaLoading() {
|
|
return getBooleanAttr(this, MediaUIAttributes.MEDIA_LOADING);
|
|
}
|
|
set mediaLoading(value) {
|
|
setBooleanAttr(this, MediaUIAttributes.MEDIA_LOADING, value);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
get mediaDuration() {
|
|
return getNumericAttr(this, MediaUIAttributes.MEDIA_DURATION);
|
|
}
|
|
set mediaDuration(value) {
|
|
setNumericAttr(this, MediaUIAttributes.MEDIA_DURATION, value);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
get mediaCurrentTime() {
|
|
return getNumericAttr(this, MediaUIAttributes.MEDIA_CURRENT_TIME);
|
|
}
|
|
set mediaCurrentTime(value) {
|
|
setNumericAttr(this, MediaUIAttributes.MEDIA_CURRENT_TIME, value);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
get mediaPlaybackRate() {
|
|
return getNumericAttr(this, MediaUIAttributes.MEDIA_PLAYBACK_RATE, 1);
|
|
}
|
|
set mediaPlaybackRate(value) {
|
|
setNumericAttr(this, MediaUIAttributes.MEDIA_PLAYBACK_RATE, value);
|
|
}
|
|
/**
|
|
* 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 [];
|
|
return buffered.split(" ").map((timePair) => timePair.split(":").map((timeStr) => +timeStr));
|
|
}
|
|
set mediaBuffered(list) {
|
|
if (!list) {
|
|
this.removeAttribute(MediaUIAttributes.MEDIA_BUFFERED);
|
|
return;
|
|
}
|
|
const strVal = list.map((tuple) => tuple.join(":")).join(" ");
|
|
this.setAttribute(MediaUIAttributes.MEDIA_BUFFERED, strVal);
|
|
}
|
|
/**
|
|
* Range of values that can be seeked to
|
|
* An array of two numbers [start, end]
|
|
*/
|
|
get mediaSeekable() {
|
|
const seekable = this.getAttribute(MediaUIAttributes.MEDIA_SEEKABLE);
|
|
if (!seekable)
|
|
return void 0;
|
|
return seekable.split(":").map((time) => +time);
|
|
}
|
|
set mediaSeekable(range) {
|
|
if (range == null) {
|
|
this.removeAttribute(MediaUIAttributes.MEDIA_SEEKABLE);
|
|
return;
|
|
}
|
|
this.setAttribute(MediaUIAttributes.MEDIA_SEEKABLE, range.join(":"));
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
get mediaSeekableEnd() {
|
|
var _a;
|
|
const [, end = this.mediaDuration] = (_a = this.mediaSeekable) != null ? _a : [];
|
|
return end;
|
|
}
|
|
get mediaSeekableStart() {
|
|
var _a;
|
|
const [start = 0] = (_a = this.mediaSeekable) != null ? _a : [];
|
|
return start;
|
|
}
|
|
/**
|
|
* 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);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
get mediaPreviewTime() {
|
|
return getNumericAttr(this, MediaUIAttributes.MEDIA_PREVIEW_TIME);
|
|
}
|
|
set mediaPreviewTime(value) {
|
|
setNumericAttr(this, MediaUIAttributes.MEDIA_PREVIEW_TIME, value);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
get mediaEnded() {
|
|
return getBooleanAttr(this, MediaUIAttributes.MEDIA_ENDED);
|
|
}
|
|
set mediaEnded(value) {
|
|
setBooleanAttr(this, MediaUIAttributes.MEDIA_ENDED, value);
|
|
}
|
|
/* Add a buffered progress bar */
|
|
updateBar() {
|
|
super.updateBar();
|
|
this.updateBufferedBar();
|
|
this.updateCurrentBox();
|
|
}
|
|
updateBufferedBar() {
|
|
var _a;
|
|
const buffered = this.mediaBuffered;
|
|
if (!buffered.length) {
|
|
return;
|
|
}
|
|
let relativeBufferedEnd;
|
|
if (!this.mediaEnded) {
|
|
const currentTime = this.mediaCurrentTime;
|
|
const [, bufferedEnd = this.mediaSeekableStart] = (_a = buffered.find(
|
|
([start, end]) => start <= currentTime && currentTime <= end
|
|
)) != null ? _a : [];
|
|
relativeBufferedEnd = calcRangeValueFromTime(this, bufferedEnd);
|
|
} else {
|
|
relativeBufferedEnd = 1;
|
|
}
|
|
const { style } = getOrInsertCSSRule(this.shadowRoot, "#buffered");
|
|
style.setProperty("width", `${relativeBufferedEnd * 100}%`);
|
|
}
|
|
updateCurrentBox() {
|
|
const currentSlot = this.shadowRoot.querySelector(
|
|
'slot[name="current"]'
|
|
);
|
|
if (!currentSlot.assignedElements().length)
|
|
return;
|
|
const currentRailRule = getOrInsertCSSRule(
|
|
this.shadowRoot,
|
|
"#current-rail"
|
|
);
|
|
const currentBoxRule = getOrInsertCSSRule(
|
|
this.shadowRoot,
|
|
'[part~="current-box"]'
|
|
);
|
|
const rects = __privateMethod(this, _getElementRects, getElementRects_fn).call(this, __privateGet(this, _currentBox));
|
|
const boxPos = __privateMethod(this, _getBoxPosition, getBoxPosition_fn).call(this, rects, this.range.valueAsNumber);
|
|
const boxShift = __privateMethod(this, _getBoxShiftPosition, getBoxShiftPosition_fn).call(this, rects, this.range.valueAsNumber);
|
|
currentRailRule.style.transform = `translateX(${boxPos})`;
|
|
currentRailRule.style.setProperty("--_range-width", `${rects.range.width}`);
|
|
currentBoxRule.style.setProperty("--_box-shift", `${boxShift}`);
|
|
currentBoxRule.style.setProperty("--_box-width", `${rects.box.width}px`);
|
|
currentBoxRule.style.setProperty("visibility", "initial");
|
|
}
|
|
handleEvent(evt) {
|
|
super.handleEvent(evt);
|
|
switch (evt.type) {
|
|
case "input":
|
|
__privateMethod(this, _seekRequest, seekRequest_fn).call(this);
|
|
break;
|
|
case "pointermove":
|
|
__privateMethod(this, _handlePointerMove, handlePointerMove_fn).call(this, evt);
|
|
break;
|
|
case "pointerup":
|
|
case "pointerleave":
|
|
__privateMethod(this, _previewRequest, previewRequest_fn).call(this, null);
|
|
break;
|
|
case "transitionstart":
|
|
if (containsComposedNode(evt.target, this)) {
|
|
setTimeout(() => __privateMethod(this, _toggleRangeAnimation, toggleRangeAnimation_fn).call(this), 0);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
_rootNode = new WeakMap();
|
|
_animation = new WeakMap();
|
|
_boxes = new WeakMap();
|
|
_previewTime = new WeakMap();
|
|
_previewBox = new WeakMap();
|
|
_currentBox = new WeakMap();
|
|
_boxPaddingLeft = new WeakMap();
|
|
_boxPaddingRight = new WeakMap();
|
|
_mediaChaptersCues = new WeakMap();
|
|
_toggleRangeAnimation = new WeakSet();
|
|
toggleRangeAnimation_fn = function() {
|
|
if (__privateMethod(this, _shouldRangeAnimate, shouldRangeAnimate_fn).call(this)) {
|
|
__privateGet(this, _animation).start();
|
|
} else {
|
|
__privateGet(this, _animation).stop();
|
|
}
|
|
};
|
|
_shouldRangeAnimate = new WeakSet();
|
|
shouldRangeAnimate_fn = function() {
|
|
return this.isConnected && !this.mediaPaused && !this.mediaLoading && !this.mediaEnded && this.mediaSeekableEnd > 0 && isElementVisible(this);
|
|
};
|
|
_updateRange = new WeakMap();
|
|
_getElementRects = new WeakSet();
|
|
getElementRects_fn = function(box) {
|
|
var _a;
|
|
const bounds = (_a = this.getAttribute("bounds") ? closestComposedNode(this, `#${this.getAttribute("bounds")}`) : this.parentElement) != null ? _a : this;
|
|
const boundsRect = bounds.getBoundingClientRect();
|
|
const rangeRect = this.range.getBoundingClientRect();
|
|
const width = box.offsetWidth;
|
|
const min = -(rangeRect.left - boundsRect.left - width / 2);
|
|
const max = boundsRect.right - rangeRect.left - width / 2;
|
|
return {
|
|
box: { width, min, max },
|
|
bounds: boundsRect,
|
|
range: rangeRect
|
|
};
|
|
};
|
|
_getBoxPosition = new WeakSet();
|
|
getBoxPosition_fn = function(rects, ratio) {
|
|
let position = `${ratio * 100}%`;
|
|
const { width, min, max } = rects.box;
|
|
if (!width)
|
|
return position;
|
|
if (!Number.isNaN(min)) {
|
|
const pad = `var(--media-box-padding-left)`;
|
|
const minPos = `calc(1 / var(--_range-width) * 100 * ${min}% + ${pad})`;
|
|
position = `max(${minPos}, ${position})`;
|
|
}
|
|
if (!Number.isNaN(max)) {
|
|
const pad = `var(--media-box-padding-right)`;
|
|
const maxPos = `calc(1 / var(--_range-width) * 100 * ${max}% - ${pad})`;
|
|
position = `min(${position}, ${maxPos})`;
|
|
}
|
|
return position;
|
|
};
|
|
_getBoxShiftPosition = new WeakSet();
|
|
getBoxShiftPosition_fn = function(rects, ratio) {
|
|
const { width, min, max } = rects.box;
|
|
const pointerX = ratio * rects.range.width;
|
|
if (pointerX < min + __privateGet(this, _boxPaddingLeft)) {
|
|
const offset = rects.range.left - rects.bounds.left - __privateGet(this, _boxPaddingLeft);
|
|
return `${pointerX - width / 2 + offset}px`;
|
|
}
|
|
if (pointerX > max - __privateGet(this, _boxPaddingRight)) {
|
|
const offset = rects.bounds.right - rects.range.right - __privateGet(this, _boxPaddingRight);
|
|
return `${pointerX + width / 2 - offset - rects.range.width}px`;
|
|
}
|
|
return 0;
|
|
};
|
|
_handlePointerMove = new WeakSet();
|
|
handlePointerMove_fn = function(evt) {
|
|
const isOverBoxes = [...__privateGet(this, _boxes)].some(
|
|
(b) => evt.composedPath().includes(b)
|
|
);
|
|
if (!this.dragging && (isOverBoxes || !evt.composedPath().includes(this))) {
|
|
__privateMethod(this, _previewRequest, previewRequest_fn).call(this, null);
|
|
return;
|
|
}
|
|
const duration = this.mediaSeekableEnd;
|
|
if (!duration)
|
|
return;
|
|
const previewRailRule = getOrInsertCSSRule(
|
|
this.shadowRoot,
|
|
"#preview-rail"
|
|
);
|
|
const previewBoxRule = getOrInsertCSSRule(
|
|
this.shadowRoot,
|
|
'[part~="preview-box"]'
|
|
);
|
|
const rects = __privateMethod(this, _getElementRects, getElementRects_fn).call(this, __privateGet(this, _previewBox));
|
|
let pointerRatio = (evt.clientX - rects.range.left) / rects.range.width;
|
|
pointerRatio = Math.max(0, Math.min(1, pointerRatio));
|
|
const boxPos = __privateMethod(this, _getBoxPosition, getBoxPosition_fn).call(this, rects, pointerRatio);
|
|
const boxShift = __privateMethod(this, _getBoxShiftPosition, getBoxShiftPosition_fn).call(this, rects, pointerRatio);
|
|
previewRailRule.style.transform = `translateX(${boxPos})`;
|
|
previewRailRule.style.setProperty("--_range-width", `${rects.range.width}`);
|
|
previewBoxRule.style.setProperty("--_box-shift", `${boxShift}`);
|
|
previewBoxRule.style.setProperty("--_box-width", `${rects.box.width}px`);
|
|
const diff = Math.round(__privateGet(this, _previewTime)) - Math.round(pointerRatio * duration);
|
|
if (Math.abs(diff) < 1 && pointerRatio > 0.01 && pointerRatio < 0.99)
|
|
return;
|
|
__privateSet(this, _previewTime, pointerRatio * duration);
|
|
__privateMethod(this, _previewRequest, previewRequest_fn).call(this, __privateGet(this, _previewTime));
|
|
};
|
|
_previewRequest = new WeakSet();
|
|
previewRequest_fn = function(detail) {
|
|
this.dispatchEvent(
|
|
new globalThis.CustomEvent(MediaUIEvents.MEDIA_PREVIEW_REQUEST, {
|
|
composed: true,
|
|
bubbles: true,
|
|
detail
|
|
})
|
|
);
|
|
};
|
|
_seekRequest = new WeakSet();
|
|
seekRequest_fn = function() {
|
|
__privateGet(this, _animation).stop();
|
|
const detail = calcTimeFromRangeValue(this);
|
|
this.dispatchEvent(
|
|
new globalThis.CustomEvent(MediaUIEvents.MEDIA_SEEK_REQUEST, {
|
|
composed: true,
|
|
bubbles: true,
|
|
detail
|
|
})
|
|
);
|
|
};
|
|
if (!globalThis.customElements.get("media-time-range")) {
|
|
globalThis.customElements.define("media-time-range", MediaTimeRange);
|
|
}
|
|
var media_time_range_default = MediaTimeRange;
|
|
export {
|
|
media_time_range_default as default
|
|
};
|