vimeo-video-element
Version:
A custom element for the Vimeo player with an API that matches the `<video>` API
527 lines (526 loc) • 15.1 kB
JavaScript
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var vimeo_video_element_exports = {};
__export(vimeo_video_element_exports, {
default: () => vimeo_video_element_default
});
module.exports = __toCommonJS(vimeo_video_element_exports);
var import_player_es = __toESM(require("@vimeo/player/dist/player.es.js"), 1);
const EMBED_BASE = "https://player.vimeo.com/video";
const MATCH_SRC = /vimeo\.com\/(?:video\/)?(\d+)(?:\/([\w-]+))?/;
function getTemplateHTML(attrs, props = {}) {
const iframeAttrs = {
src: serializeIframeUrl(attrs, props),
frameborder: 0,
width: "100%",
height: "100%",
allow: "accelerometer; fullscreen; autoplay; encrypted-media; gyroscope; picture-in-picture"
};
if (props.config) {
iframeAttrs["data-config"] = JSON.stringify(props.config);
}
return (
/*html*/
`
<style>
:host {
display: inline-block;
min-width: 300px;
min-height: 150px;
position: relative;
}
iframe {
position: absolute;
top: 0;
left: 0;
}
:host(:not([controls])) {
pointer-events: none;
}
</style>
<iframe${serializeAttributes(iframeAttrs)}></iframe>
`
);
}
function serializeIframeUrl(attrs, props) {
if (!attrs.src) return;
const matches = attrs.src.match(MATCH_SRC);
const srcId = matches && matches[1];
const hParam = matches && matches[2];
const params = {
// ?controls=true is enabled by default in the iframe
controls: attrs.controls === "" ? null : 0,
autoplay: attrs.autoplay,
loop: attrs.loop,
muted: attrs.muted,
playsinline: attrs.playsinline,
preload: attrs.preload ?? "metadata",
transparent: false,
autopause: attrs.autopause,
h: hParam,
// This param is required when the video is Unlisted.
...props.config
};
return `${EMBED_BASE}/${srcId}?${serialize(params)}`;
}
class VimeoVideoElement extends (globalThis.HTMLElement ?? class {
}) {
static getTemplateHTML = getTemplateHTML;
static shadowRootOptions = { mode: "open" };
static observedAttributes = [
"autoplay",
"controls",
"crossorigin",
"loop",
"muted",
"playsinline",
"poster",
"preload",
"src"
];
loadComplete = new PublicPromise();
#loadRequested;
#hasLoaded;
#isInit;
#currentTime = 0;
#duration = NaN;
#muted = false;
#paused = !this.autoplay;
#playbackRate = 1;
#progress = 0;
#readyState = 0;
#seeking = false;
#volume = 1;
#videoWidth = NaN;
#videoHeight = NaN;
#config = null;
constructor() {
super();
this.#upgradeProperty("config");
}
requestFullscreen() {
var _a, _b;
return (_b = (_a = this.api) == null ? void 0 : _a.requestFullscreen) == null ? void 0 : _b.call(_a);
}
exitFullscreen() {
var _a, _b;
return (_b = (_a = this.api) == null ? void 0 : _a.exitFullscreen) == null ? void 0 : _b.call(_a);
}
requestPictureInPicture() {
var _a, _b;
return (_b = (_a = this.api) == null ? void 0 : _a.requestPictureInPicture) == null ? void 0 : _b.call(_a);
}
exitPictureInPicture() {
var _a, _b;
return (_b = (_a = this.api) == null ? void 0 : _a.exitPictureInPicture) == null ? void 0 : _b.call(_a);
}
get config() {
return this.#config;
}
set config(value) {
this.#config = value;
}
async load() {
var _a;
if (this.#loadRequested) return;
const isFirstLoad = !this.#hasLoaded;
if (this.#hasLoaded) this.loadComplete = new PublicPromise();
this.#hasLoaded = true;
await (this.#loadRequested = Promise.resolve());
this.#loadRequested = null;
this.#currentTime = 0;
this.#duration = NaN;
this.#muted = false;
this.#paused = !this.autoplay;
this.#playbackRate = 1;
this.#progress = 0;
this.#readyState = 0;
this.#seeking = false;
this.#volume = 1;
this.#readyState = 0;
this.#videoWidth = NaN;
this.#videoHeight = NaN;
this.dispatchEvent(new Event("emptied"));
let oldApi = this.api;
this.api = null;
if (!this.src) {
return;
}
this.dispatchEvent(new Event("loadstart"));
const options = {
autoplay: this.autoplay,
controls: this.controls,
loop: this.loop,
muted: this.defaultMuted,
playsinline: this.playsInline,
preload: this.preload ?? "metadata",
transparent: false,
autopause: this.hasAttribute("autopause"),
...this.#config
};
const onLoaded = async () => {
this.#readyState = 1;
this.dispatchEvent(new Event("loadedmetadata"));
if (this.api) {
this.#muted = await this.api.getMuted();
this.#volume = await this.api.getVolume();
this.dispatchEvent(new Event("volumechange"));
this.#duration = await this.api.getDuration();
this.dispatchEvent(new Event("durationchange"));
}
this.dispatchEvent(new Event("loadcomplete"));
this.loadComplete.resolve();
};
if (this.#isInit) {
this.api = oldApi;
await this.api.loadVideo({
...options,
url: this.src
});
await onLoaded();
await this.loadComplete;
return;
}
this.#isInit = true;
let iframe = (_a = this.shadowRoot) == null ? void 0 : _a.querySelector("iframe");
if (isFirstLoad && iframe) {
this.#config = JSON.parse(iframe.getAttribute("data-config") || "{}");
}
if (!this.shadowRoot) {
this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = getTemplateHTML(namedNodeMapToObject(this.attributes), this);
iframe = this.shadowRoot.querySelector("iframe");
}
this.api = new import_player_es.default(iframe);
const onceLoaded = () => {
this.api.off("loaded", onceLoaded);
onLoaded();
};
this.api.on("loaded", onceLoaded);
this.api.on("bufferstart", () => {
if (this.#paused) {
this.#paused = false;
this.dispatchEvent(new Event("play"));
}
this.dispatchEvent(new Event("waiting"));
});
this.api.on("play", () => {
if (!this.#paused) return;
this.#paused = false;
this.dispatchEvent(new Event("play"));
});
this.api.on("playing", () => {
this.#readyState = 3;
this.#paused = false;
this.dispatchEvent(new Event("playing"));
});
this.api.on("seeking", () => {
this.#seeking = true;
this.dispatchEvent(new Event("seeking"));
});
this.api.on("seeked", () => {
this.#seeking = false;
this.dispatchEvent(new Event("seeked"));
});
this.api.on("pause", () => {
this.#paused = true;
this.dispatchEvent(new Event("pause"));
});
this.api.on("ended", () => {
this.#paused = true;
this.dispatchEvent(new Event("ended"));
});
this.api.on("ratechange", ({ playbackRate }) => {
this.#playbackRate = playbackRate;
this.dispatchEvent(new Event("ratechange"));
});
this.api.on("volumechange", async ({ volume }) => {
this.#volume = volume;
if (this.api) {
this.#muted = await this.api.getMuted();
}
this.dispatchEvent(new Event("volumechange"));
});
this.api.on("durationchange", ({ duration }) => {
this.#duration = duration;
this.dispatchEvent(new Event("durationchange"));
});
this.api.on("timeupdate", ({ seconds }) => {
this.#currentTime = seconds;
this.dispatchEvent(new Event("timeupdate"));
});
this.api.on("progress", ({ seconds }) => {
this.#progress = seconds;
this.dispatchEvent(new Event("progress"));
});
this.api.on("resize", ({ videoWidth, videoHeight }) => {
this.#videoWidth = videoWidth;
this.#videoHeight = videoHeight;
this.dispatchEvent(new Event("resize"));
});
await this.loadComplete;
}
async attributeChangedCallback(attrName, oldValue, newValue) {
if (oldValue === newValue) return;
switch (attrName) {
case "autoplay":
case "controls":
case "src": {
this.load();
return;
}
}
await this.loadComplete;
switch (attrName) {
case "loop": {
this.api.setLoop(this.loop);
break;
}
}
}
async play() {
var _a;
this.#paused = false;
this.dispatchEvent(new Event("play"));
await this.loadComplete;
try {
await ((_a = this.api) == null ? void 0 : _a.play());
} catch (error) {
this.#paused = true;
this.dispatchEvent(new Event("pause"));
throw error;
}
}
async pause() {
var _a;
await this.loadComplete;
return (_a = this.api) == null ? void 0 : _a.pause();
}
get ended() {
return this.#currentTime >= this.#duration;
}
get seeking() {
return this.#seeking;
}
get readyState() {
return this.#readyState;
}
get videoWidth() {
return this.#videoWidth;
}
get videoHeight() {
return this.#videoHeight;
}
get src() {
return this.getAttribute("src");
}
set src(val) {
if (this.src == val) return;
this.setAttribute("src", val);
}
get paused() {
return this.#paused;
}
get duration() {
return this.#duration;
}
get autoplay() {
return this.hasAttribute("autoplay");
}
set autoplay(val) {
if (this.autoplay == val) return;
this.toggleAttribute("autoplay", Boolean(val));
}
get buffered() {
if (this.#progress > 0) {
return createTimeRanges(0, this.#progress);
}
return createTimeRanges();
}
get controls() {
return this.hasAttribute("controls");
}
set controls(val) {
if (this.controls == val) return;
this.toggleAttribute("controls", Boolean(val));
}
get currentTime() {
return this.#currentTime;
}
set currentTime(val) {
if (this.currentTime == val) return;
this.#currentTime = val;
this.loadComplete.then(() => {
var _a;
(_a = this.api) == null ? void 0 : _a.setCurrentTime(val).catch(() => {
});
});
}
get defaultMuted() {
return this.hasAttribute("muted");
}
set defaultMuted(val) {
if (this.defaultMuted == val) return;
this.toggleAttribute("muted", Boolean(val));
}
get loop() {
return this.hasAttribute("loop");
}
set loop(val) {
if (this.loop == val) return;
this.toggleAttribute("loop", Boolean(val));
}
get muted() {
return this.#muted;
}
set muted(val) {
if (this.muted == val) return;
this.#muted = val;
this.loadComplete.then(() => {
var _a;
(_a = this.api) == null ? void 0 : _a.setMuted(val).catch(() => {
});
});
}
get playbackRate() {
return this.#playbackRate;
}
set playbackRate(val) {
if (this.playbackRate == val) return;
this.#playbackRate = val;
this.loadComplete.then(() => {
var _a;
(_a = this.api) == null ? void 0 : _a.setPlaybackRate(val).catch(() => {
});
});
}
get playsInline() {
return this.hasAttribute("playsinline");
}
set playsInline(val) {
if (this.playsInline == val) return;
this.toggleAttribute("playsinline", Boolean(val));
}
get poster() {
return this.getAttribute("poster");
}
set poster(val) {
if (this.poster == val) return;
this.setAttribute("poster", `${val}`);
}
get volume() {
return this.#volume;
}
set volume(val) {
if (this.volume == val) return;
this.#volume = val;
this.loadComplete.then(() => {
var _a;
(_a = this.api) == null ? void 0 : _a.setVolume(val).catch(() => {
});
});
}
// This is a pattern to update property values that are set before
// the custom element is upgraded.
// https://web.dev/custom-elements-best-practices/#make-properties-lazy
#upgradeProperty(prop) {
if (Object.prototype.hasOwnProperty.call(this, prop)) {
const value = this[prop];
delete this[prop];
this[prop] = value;
}
}
}
function serializeAttributes(attrs) {
let html = "";
for (const key in attrs) {
const value = attrs[key];
if (value === "") html += ` ${escapeHtml(key)}`;
else html += ` ${escapeHtml(key)}="${escapeHtml(`${value}`)}"`;
}
return html;
}
function escapeHtml(str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, "`");
}
function serialize(props) {
return String(new URLSearchParams(boolToBinary(props)));
}
function boolToBinary(props) {
let p = {};
for (let key in props) {
let val = props[key];
if (val === true || val === "") p[key] = 1;
else if (val === false) p[key] = 0;
else if (val != null) p[key] = val;
}
return p;
}
function namedNodeMapToObject(namedNodeMap) {
let obj = {};
for (let attr of namedNodeMap) {
obj[attr.name] = attr.value;
}
return obj;
}
class PublicPromise extends Promise {
constructor(executor = () => {
}) {
let res, rej;
super((resolve, reject) => {
executor(resolve, reject);
res = resolve;
rej = reject;
});
this.resolve = res;
this.reject = rej;
}
}
function createTimeRanges(start, end) {
if (Array.isArray(start)) {
return createTimeRangesObj(start);
} else if (start == null || end == null || start === 0 && end === 0) {
return createTimeRangesObj([[0, 0]]);
}
return createTimeRangesObj([[start, end]]);
}
function createTimeRangesObj(ranges) {
Object.defineProperties(ranges, {
start: {
value: (i) => ranges[i][0]
},
end: {
value: (i) => ranges[i][1]
}
});
return ranges;
}
if (globalThis.customElements && !globalThis.customElements.get("vimeo-video")) {
globalThis.customElements.define("vimeo-video", VimeoVideoElement);
}
var vimeo_video_element_default = VimeoVideoElement;