Skip to content

Commit

Permalink
feat: dup track append, add source tag support (#962)
Browse files Browse the repository at this point in the history
fix #944
fix #948

adds support for child `<source>` tags
  • Loading branch information
luwes committed Jul 30, 2024
1 parent 6b83bbc commit 735cb9b
Show file tree
Hide file tree
Showing 2 changed files with 50 additions and 34 deletions.
14 changes: 8 additions & 6 deletions packages/mux-player/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import type {
MinResolutionValue,
RenditionOrderValue,
} from '@mux/playback-core';
import VideoApiElement, { initVideoApi } from './video-api';
import VideoApiElement from './video-api';
import {
getPlayerVersion,
toPropName,
Expand Down Expand Up @@ -110,7 +110,7 @@ function getProps(el: MuxPlayerElement, state?: any): MuxTemplateProps {
// Give priority to playbackId derrived asset URL's if playbackId is set.
src: !el.playbackId && el.src,
playbackId: el.playbackId,
hasSrc: !!el.playbackId || !!el.src,
hasSrc: !!el.playbackId || !!el.src || !!el.currentSrc,
poster: el.poster,
storyboard: el.storyboard,
storyboardSrc: el.getAttribute(PlayerAttributes.STORYBOARD_SRC),
Expand Down Expand Up @@ -341,18 +341,20 @@ class MuxPlayerElement extends VideoApiElement implements MuxPlayerElement {
logger.error(`<media-controller> failed to upgrade!`);
}

initVideoApi(this);
this.init();

this.#setUpThemeAttributes();
this.#setUpErrors();
this.#setUpCaptionsButton();
this.#userInactive = this.mediaController?.hasAttribute(MediaControllerAttributes.USER_INACTIVE) ?? true;
this.#setUpCaptionsMovement();

// NOTE: Make sure we re-render when stream type changes to ensure other props-driven
// template details get updated appropriately (e.g. thumbnails track) (CJP)
this.media?.addEventListener('streamtypechange', () => {
this.#render();
});
this.media?.addEventListener('streamtypechange', () => this.#render());

// NOTE: Make sure we re-render when <source> tags are appended so hasSrc is updated.
this.media?.addEventListener('loadstart', () => this.#render());
}

#setupCSSProperties() {
Expand Down
70 changes: 42 additions & 28 deletions packages/mux-player/src/video-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,6 @@ const AllowedVideoAttributeNames = Object.values(AllowedVideoAttributes).filter(
);
const CustomVideoAttributesNames = Object.values(CustomVideoAttributes);

/**
* Gets called from mux-player when mux-video is rendered and upgraded.
* We might just merge VideoApiElement in MuxPlayerElement and remove this?
*/
export function initVideoApi(el: VideoApiElement) {
el.querySelectorAll(':scope > track').forEach((track) => {
el.media?.append(track.cloneNode());
});

// The video events are dispatched on the VideoApiElement instance.
// This makes it possible to add event listeners before the element is upgraded.
AllowedVideoEvents.forEach((type) => {
el.media?.addEventListener(type, (evt) => {
el.dispatchEvent(new Event(evt.type));
});
});
}

// NOTE: Some of these are defined in MuxPlayerElement. We may want to apply a
// `Pick<>` on these to also enforce consistency (CJP).
type PartialHTMLVideoElement = Omit<
Expand All @@ -96,7 +78,6 @@ type PartialHTMLVideoElement = Omit<
| 'requestPictureInPicture'
| 'requestVideoFrameCallback'
| 'controls'
| 'currentSrc'
| 'disableRemotePlayback'
| 'mediaKeys'
| 'networkState'
Expand Down Expand Up @@ -148,6 +129,8 @@ interface VideoApiElement extends PartialHTMLVideoElement, HTMLElement {
}

class VideoApiElement extends globalThis.HTMLElement implements VideoApiElement {
#mediaChildrenMap = new WeakMap();

static get observedAttributes() {
return [...AllowedVideoAttributeNames, ...CustomVideoAttributesNames];
}
Expand All @@ -160,30 +143,44 @@ class VideoApiElement extends globalThis.HTMLElement implements VideoApiElement
constructor() {
super();

this.querySelectorAll(':scope > track').forEach((track) => {
this.media?.append(track.cloneNode());
});

// Watch for child adds/removes and update the native element if necessary
/** @type {(mutationList: MutationRecord[]) => void} */
const mutationCallback = (mutationsList: MutationRecord[]) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
// Child being removed
mutation.removedNodes.forEach((node) => {
const track = this.media?.querySelector(`track[src="${(node as HTMLTrackElement).src}"]`);
if (track) this.media?.removeChild(track);
this.#mediaChildrenMap.get(node)?.remove();
});

mutation.addedNodes.forEach((node) => {
this.media?.append(node.cloneNode());
const element = node as HTMLElement;
if (!element?.slot) {
this.media?.append(getOrInsertNodeClone(this.#mediaChildrenMap, node));
}
});
}
}
};

const observer = new MutationObserver(mutationCallback);
observer.observe(this, { childList: true, subtree: true });

// The video events are dispatched on the VideoApiElement instance.
// This makes it possible to add event listeners before the element is upgraded.
AllowedVideoEvents.forEach((type) => {
this.media?.addEventListener(type, (evt) => {
this.dispatchEvent(new Event(evt.type));
});
});
}

/**
* Gets called from mux-player when mux-video is rendered and upgraded.
* We might just merge VideoApiElement in MuxPlayerElement and remove this?
*/
init() {
this.querySelectorAll(':scope > :not([slot])').forEach((child) => {
this.media?.append(getOrInsertNodeClone(this.#mediaChildrenMap, child));
});
}

attributeChangedCallback(attrName: string, oldValue: string | null, newValue: string) {
Expand Down Expand Up @@ -221,6 +218,10 @@ class VideoApiElement extends globalThis.HTMLElement implements VideoApiElement
this.media?.pause();
}

load() {
this.media?.load();
}

requestCast(options: CastOptions) {
return this.media?.requestCast(options);
}
Expand Down Expand Up @@ -277,6 +278,10 @@ class VideoApiElement extends globalThis.HTMLElement implements VideoApiElement
return this.media?.videoHeight ?? 0;
}

get currentSrc() {
return this.media?.currentSrc ?? '';
}

get currentTime() {
return this.media?.currentTime ?? 0;
}
Expand Down Expand Up @@ -410,4 +415,13 @@ function getVideoAttribute(el: VideoApiElement, name: string) {
return el.media ? el.media.getAttribute(name) : el.getAttribute(name);
}

function getOrInsertNodeClone(map: WeakMap<Node, Node>, node: Node) {
let clone = map.get(node);
if (!clone) {
clone = node.cloneNode();
map.set(node, clone);
}
return clone;
}

export default VideoApiElement;

0 comments on commit 735cb9b

Please sign in to comment.