Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions examples/nextjs/app/wistia-video/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ export default function Page() {
<Player
as={WistiaVideo}
src="https://wesleyluyten.wistia.com/medias/oifkgmxnkb"
config={{
playerColor: '#f5f6f7',
}}
/>
</section>
</>
Expand Down
48 changes: 47 additions & 1 deletion packages/wistia-video-element/test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ test('load promise', async function (t) {
await video.loadComplete;

t.ok(
loadComplete != video.loadComplete,
loadComplete !== video.loadComplete,
'creates a new promise after new src'
);

Expand All @@ -86,6 +86,52 @@ test('play promise', async function (t) {
t.ok(!video.paused, 'is playing after video.play()');
});

test('passes config into Wistia options', async function (t) {
const previousWistia = globalThis.Wistia;
const previousWq = globalThis._wq;

try {
globalThis.Wistia = {};

const fakeApi = {
elem: () => document.createElement('video'),
duration: () => 0,
play: () => {},
bigPlayButtonEnabled: () => {},
releaseChromeless: () => {},
requestChromeless: () => {},
};

let pushed;
globalThis._wq = {
push: (payload) => {
pushed = payload;
payload.onReady(fakeApi);
},
};

const video = await fixture(`<wistia-video controls muted></wistia-video>`);

// Ensure user config takes precedence over element-derived defaults.
video.config = {
chromeless: true,
playButton: false,
playerColor: 'ff0000',
};

video.src = 'https://wesleyluyten.wistia.com/medias/oifkgmxnkb';
await video.loadComplete;

t.ok(pushed?.options, 'push payload includes options');
t.equal(pushed.options.chromeless, true, 'config overrides default chromeless');
t.equal(pushed.options.playButton, false, 'config overrides default playButton');
t.equal(pushed.options.playerColor, 'ff0000', 'config is forwarded into options');
} finally {
globalThis.Wistia = previousWistia;
globalThis._wq = previousWq;
}
});

function delay(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
Expand Down
55 changes: 50 additions & 5 deletions packages/wistia-video-element/wistia-video-element.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,55 @@
export type WistiaPluginConfig = Record<string, unknown>;

export type WistiaEmbedOptions = {
autoPlay?: boolean;
controlsVisibleOnLoad?: boolean;
copyLinkAndThumbnailEnabled?: boolean;
doNotTrack?: boolean;
email?: string;
endVideoBehavior?: 'default' | 'reset' | 'loop';
fakeFullscreen?: boolean;
fitStrategy?: 'contain' | 'cover' | 'fill' | 'none';
fullscreenButton?: boolean;
muted?: boolean;
playbackRateControl?: boolean;
playbar?: boolean;
playButton?: boolean;
playerColor?: string;
playlistLinks?: boolean;
playlistLoop?: boolean;
playsinline?: boolean;
playPauseNotifier?: boolean;
playSuspendedOffScreen?: boolean;
preload?: 'metadata' | 'auto' | 'none' | boolean;
qualityControl?: boolean;
qualityMax?: 224 | 360 | 540 | 720 | 1080 | 3840;
qualityMin?: 224 | 360 | 540 | 720 | 1080 | 3840;
resumable?: boolean | 'auto';
seo?: boolean;
settingsControl?: boolean;
silentAutoPlay?: boolean | 'allow';
smallPlayButton?: boolean;
stillUrl?: string;
time?: number | string;
thumbnailAltText?: string;
videoFoam?:
| boolean
| {
minWidth?: number;
maxWidth?: number;
minHeight?: number;
maxHeight?: number;
};
volume?: number;
volumeControl?: boolean;
wmode?: string;
plugin?: WistiaPluginConfig;
};

export default class CustomVideoElement extends HTMLVideoElement {
static readonly observedAttributes: string[];
attributeChangedCallback(
attrName: string,
oldValue?: string | null,
newValue?: string | null
): void;
attributeChangedCallback(attrName: string, oldValue?: string | null, newValue?: string | null): void;
connectedCallback(): void;
disconnectedCallback(): void;
config: WistiaEmbedOptions | null;
}
35 changes: 33 additions & 2 deletions packages/wistia-video-element/wistia-video-element.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,38 @@ if (templateShadowDOM) {
`;
}

function upgradeProperty(el, prop) {
// 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
if (Object.hasOwn(el, prop)) {
const value = el[prop];
// Delete the set property from this instance.
delete el[prop];
// Set the value again via the (prototype) setter on this class.
el[prop] = value;
}
}

class WistiaVideoElement extends SuperVideoElement {
static template = templateShadowDOM;
static skipAttributes = ['src'];

#config = null;

constructor() {
super();
upgradeProperty(this, 'config');
}

get config() {
return this.#config;
}

set config(value) {
this.#config = value;
}

get nativeEl() {
return this.api?.elem() ?? this.querySelector('video');
}
Expand All @@ -58,6 +86,7 @@ class WistiaVideoElement extends SuperVideoElement {
chromeless: !this.controls,
playButton: this.controls,
muted: this.defaultMuted,
...(this.#config ?? {}),
};

// Sadly the setup/render will not work in the shadow DOM.
Expand Down Expand Up @@ -119,14 +148,16 @@ async function loadScript(src, globalName) {
if (!globalName) return import(/* webpackIgnore: true */ src);
if (loadScriptCache[src]) return loadScriptCache[src];
if (self[globalName]) return self[globalName];
return (loadScriptCache[src] = new Promise((resolve, reject) => {
const promise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.defer = true;
script.src = src;
script.onload = () => resolve(self[globalName]);
script.onerror = reject;
document.head.append(script);
}));
});
loadScriptCache[src] = promise;
return promise;
}

let idCounter = 0;
Expand Down