Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 | 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x | import 'castable-video';
/**
* Custom Video Element
* The goal is to create an element that works just like the video element
* but can be extended/sub-classed, because native elements cannot be
* extended today across browsers.
*/
// The onevent like props are weirdly set on the HTMLElement prototype with other
// generic events making it impossible to pick these specific to HTMLMediaElement.
export const VideoEvents = [
'abort',
'canplay',
'canplaythrough',
'durationchange',
'emptied',
'encrypted',
'ended',
'error',
'loadeddata',
'loadedmetadata',
'loadstart',
'pause',
'play',
'playing',
'progress',
'ratechange',
'seeked',
'seeking',
'stalled',
'suspend',
'timeupdate',
'volumechange',
'waiting',
'waitingforkey',
'resize',
'enterpictureinpicture',
'leavepictureinpicture',
'castchange',
'entercast',
'leavecast',
];
const template = document.createElement('template');
// Could you get styles to apply by passing a global button from global to shadow?
template.innerHTML = `
<style>
:host {
display: inline-block;
line-height: 0;
width: auto;
height: auto;
}
video {
max-width: 100%;
max-height: 100%;
min-width: 100%;
min-height: 100%;
}
</style>
<video is="castable-video" part="video" crossorigin></video>
<slot></slot>
`;
class CustomVideoElement extends HTMLElement {
#isInit;
constructor() {
super();
this.attachShadow({ mode: 'open' });
// If the custom element is defined before the <custom-video> HTML is parsed
// no attributes will be available in the constructor (construction process).
// Wait until initializing attributes in the attributeChangedCallback.
// If this element is connected to the DOM, the attributes will be available.
if (this.isConnected) {
this.#init();
}
}
#init() {
if (this.#isInit) return;
this.#isInit = true;
this.shadowRoot.append(template.content.cloneNode(true));
this.nativeEl = this.shadowRoot.querySelector('video');
// The video events are dispatched on the CustomVideoElement instance.
// This makes it possible to add event listeners before the element is upgraded.
VideoEvents.forEach((type) => {
this.nativeEl.addEventListener(type, (evt) => {
this.dispatchEvent(new CustomEvent(evt.type, { detail: evt.detail }));
});
});
// An unnamed <slot> will be filled with all of the custom element's
// top-level child nodes that do not have the slot attribute.
const slotEl = this.shadowRoot.querySelector('slot');
slotEl.addEventListener('slotchange', () => {
slotEl.assignedElements().forEach((el) => {
if (!['track', 'source'].includes(el.localName)) return;
this.nativeEl.append(el);
});
});
// Initialize all the attribute properties
// This is required before attributeChangedCallback is called after construction
// so the initial state of all the attributes are forwarded to the native element.
// Don't call attributeChangedCallback directly here because the extending class
// could have overridden attributeChangedCallback leading to unexpected results.
Array.prototype.forEach.call(this.attributes, (attrNode) => {
this.#forwardAttribute(attrNode.name, null, attrNode.value);
});
// Neither Chrome or Firefox support setting the muted attribute
// after using document.createElement.
// One way to get around this would be to build the native tag as a string.
// But just fixing it manually for now.
// Apparently this may also be an issue with <input checked> for buttons
if (this.nativeEl.defaultMuted) {
this.nativeEl.muted = true;
}
}
// observedAttributes is required to trigger attributeChangedCallback
// for any attributes on the custom element.
// Attributes need to be the lowercase word, e.g. crossorigin, not crossOrigin
static get observedAttributes() {
let attrs = [];
const kebabCase = (name) => {
return name.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
};
// Instead of manually creating a list of all observed attributes, observe
// any getter/setter prop name (lowercase or kebab-case for custom builtins)
Object.getOwnPropertyNames(this.prototype).forEach((propName) => {
let isFunc = false;
// Non-func properties throw errors because it's not an instance
try {
if (typeof this.prototype[propName] === 'function') {
isFunc = true;
}
} catch (e) {}
// Exclude functions and constants
if (!isFunc && propName !== propName.toUpperCase()) {
attrs.push(propName.toLowerCase(), kebabCase(propName));
}
});
// Include any attributes from the super class (recursive)
const supAttrs = Object.getPrototypeOf(this).observedAttributes;
if (supAttrs) {
attrs = attrs.concat(supAttrs);
}
return attrs;
}
attributeChangedCallback(attrName, oldValue, newValue) {
// Initialize right after construction when the attributes become available.
this.#init();
this.#forwardAttribute(attrName, oldValue, newValue);
}
// We need to handle sub-class custom attributes differently from
// attrs meant to be passed to the internal native el.
#forwardAttribute(attrName, oldValue, newValue) {
// Find the matching prop for custom attributes
const ownProps = Object.getOwnPropertyNames(Object.getPrototypeOf(this));
const propName = arrayFindAnyCase(ownProps, attrName);
// Check if this is the original custom native elemnt or a subclass
const isBaseElement = Object.getPrototypeOf(this.constructor).toString().indexOf('function HTMLElement') === 0;
// If this is a subclass custom attribute we want to set the
// matching property on the subclass
if (propName && !isBaseElement) {
// Boolean props should never start as null
if (typeof this[propName] == 'boolean') {
// null is returned when attributes are removed i.e. boolean attrs
if (newValue === null) {
this[propName] = false;
} else {
// The new value might be an empty string, which is still true
// for boolean attributes
this[propName] = true;
}
} else {
this[propName] = newValue;
}
} else {
// When this is the original Custom Element, or the subclass doesn't
// have a matching prop, pass it through.
if (newValue === null) {
this.nativeEl.removeAttribute(attrName);
} else {
// Ignore a few that don't need to be passed through just in case
// it creates unexpected behavior.
if (['id', 'class'].indexOf(attrName) === -1) {
this.nativeEl.setAttribute(attrName, newValue);
}
}
}
}
connectedCallback() {
this.#init();
}
}
// Map all native element properties to the custom element
// so that they're applied to the native element.
// Skipping HTMLElement because of things like "attachShadow"
// causing issues. Most of those props still need to apply to
// the custom element.
let nativeElProps = [];
// Can't check typeof directly on element prototypes without
// throwing Illegal Invocation errors, so creating an element
// to check on instead.
const nativeElTest = document.createElement('video', {
is: 'castable-video',
});
// Deprecated props throw warnings if used, so exclude them
const deprecatedProps = ['webkitDisplayingFullscreen', 'webkitSupportsFullscreen'];
// Walk the prototype chain up to HTMLElement.
// This will grab all super class props in between.
// i.e. VideoElement and MediaElement
for (
let proto = Object.getPrototypeOf(nativeElTest);
proto && proto !== HTMLElement.prototype;
proto = Object.getPrototypeOf(proto)
) {
Object.getOwnPropertyNames(proto).forEach((key) => {
if (deprecatedProps.indexOf(key) === -1) {
nativeElProps.push(key);
}
});
}
// Passthrough native el functions from the custom el to the native el
nativeElProps.forEach((prop) => {
if (prop in CustomVideoElement.prototype) return;
const type = typeof nativeElTest[prop];
if (type == 'function') {
// Function
CustomVideoElement.prototype[prop] = function () {
return this.nativeEl[prop].apply(this.nativeEl, arguments);
};
} else {
// Getter
let config = {
get() {
return this.nativeEl[prop];
},
};
if (prop !== prop.toUpperCase()) {
// Setter (not a CONSTANT)
config.set = function (val) {
this.nativeEl[prop] = val;
};
}
Object.defineProperty(CustomVideoElement.prototype, prop, config);
}
});
function arrayFindAnyCase(arr, word) {
let found = null;
arr.forEach((item) => {
if (item.toLowerCase() == word.toLowerCase()) {
found = item;
}
});
return found;
}
if (!globalThis.customElements.get('custom-video')) {
globalThis.customElements.define('custom-video', CustomVideoElement);
globalThis.CustomVideoElement = CustomVideoElement;
}
export default CustomVideoElement;
|