281 lines
12 KiB
JavaScript
281 lines
12 KiB
JavaScript
import {
|
|
stateMediator as defaultStateMediator,
|
|
prepareStateOwners
|
|
} from "./state-mediator.js";
|
|
import { areValuesEq } from "./util.js";
|
|
import { requestMap as defaultRequestMap } from "./request-map.js";
|
|
const createMediaStore = ({
|
|
media,
|
|
fullscreenElement,
|
|
documentElement,
|
|
stateMediator = defaultStateMediator,
|
|
requestMap = defaultRequestMap,
|
|
options = {},
|
|
monitorStateOwnersOnlyWithSubscriptions = true
|
|
}) => {
|
|
const callbacks = [];
|
|
const stateOwners = {
|
|
// Spreading options here since folks should not rely on holding onto references
|
|
// for any app-level logic wrt options.
|
|
options: { ...options }
|
|
};
|
|
let state = Object.freeze({
|
|
mediaPreviewTime: void 0,
|
|
mediaPreviewImage: void 0,
|
|
mediaPreviewCoords: void 0,
|
|
mediaPreviewChapter: void 0
|
|
});
|
|
const updateState = (nextStateDelta) => {
|
|
if (nextStateDelta == void 0)
|
|
return;
|
|
if (areValuesEq(nextStateDelta, state)) {
|
|
return;
|
|
}
|
|
state = Object.freeze({
|
|
...state,
|
|
...nextStateDelta
|
|
});
|
|
callbacks.forEach((cb) => cb(state));
|
|
};
|
|
const updateStateFromFacade = () => {
|
|
const nextState = Object.entries(stateMediator).reduce(
|
|
(nextState2, [stateName, { get }]) => {
|
|
nextState2[stateName] = get(stateOwners);
|
|
return nextState2;
|
|
},
|
|
{}
|
|
);
|
|
updateState(nextState);
|
|
};
|
|
const stateUpdateHandlers = {};
|
|
let nextStateOwners = void 0;
|
|
const updateStateOwners = async (nextStateOwnersDelta, nextSubscriberCount) => {
|
|
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p;
|
|
const pendingUpdate = !!nextStateOwners;
|
|
nextStateOwners = {
|
|
...stateOwners,
|
|
...nextStateOwners != null ? nextStateOwners : {},
|
|
...nextStateOwnersDelta
|
|
};
|
|
if (pendingUpdate)
|
|
return;
|
|
await prepareStateOwners(...Object.values(nextStateOwnersDelta));
|
|
const shouldTeardownFromSubscriberCount = callbacks.length > 0 && nextSubscriberCount === 0 && monitorStateOwnersOnlyWithSubscriptions;
|
|
const mediaChanged = stateOwners.media !== nextStateOwners.media;
|
|
const textTracksChanged = ((_a = stateOwners.media) == null ? void 0 : _a.textTracks) !== ((_b = nextStateOwners.media) == null ? void 0 : _b.textTracks);
|
|
const videoRenditionsChanged = ((_c = stateOwners.media) == null ? void 0 : _c.videoRenditions) !== ((_d = nextStateOwners.media) == null ? void 0 : _d.videoRenditions);
|
|
const audioTracksChanged = ((_e = stateOwners.media) == null ? void 0 : _e.audioTracks) !== ((_f = nextStateOwners.media) == null ? void 0 : _f.audioTracks);
|
|
const remoteChanged = ((_g = stateOwners.media) == null ? void 0 : _g.remote) !== ((_h = nextStateOwners.media) == null ? void 0 : _h.remote);
|
|
const rootNodeChanged = stateOwners.documentElement !== nextStateOwners.documentElement;
|
|
const teardownMedia = !!stateOwners.media && (mediaChanged || shouldTeardownFromSubscriberCount);
|
|
const teardownTextTracks = !!((_i = stateOwners.media) == null ? void 0 : _i.textTracks) && (textTracksChanged || shouldTeardownFromSubscriberCount);
|
|
const teardownVideoRenditions = !!((_j = stateOwners.media) == null ? void 0 : _j.videoRenditions) && (videoRenditionsChanged || shouldTeardownFromSubscriberCount);
|
|
const teardownAudioTracks = !!((_k = stateOwners.media) == null ? void 0 : _k.audioTracks) && (audioTracksChanged || shouldTeardownFromSubscriberCount);
|
|
const teardownRemote = !!((_l = stateOwners.media) == null ? void 0 : _l.remote) && (remoteChanged || shouldTeardownFromSubscriberCount);
|
|
const teardownRootNode = !!stateOwners.documentElement && (rootNodeChanged || shouldTeardownFromSubscriberCount);
|
|
const teardownSomething = teardownMedia || teardownTextTracks || teardownVideoRenditions || teardownAudioTracks || teardownRemote || teardownRootNode;
|
|
const shouldSetupFromSubscriberCount = callbacks.length === 0 && nextSubscriberCount === 1 && monitorStateOwnersOnlyWithSubscriptions;
|
|
const setupMedia = !!nextStateOwners.media && (mediaChanged || shouldSetupFromSubscriberCount);
|
|
const setupTextTracks = !!((_m = nextStateOwners.media) == null ? void 0 : _m.textTracks) && (textTracksChanged || shouldSetupFromSubscriberCount);
|
|
const setupVideoRenditions = !!((_n = nextStateOwners.media) == null ? void 0 : _n.videoRenditions) && (videoRenditionsChanged || shouldSetupFromSubscriberCount);
|
|
const setupAudioTracks = !!((_o = nextStateOwners.media) == null ? void 0 : _o.audioTracks) && (audioTracksChanged || shouldSetupFromSubscriberCount);
|
|
const setupRemote = !!((_p = nextStateOwners.media) == null ? void 0 : _p.remote) && (remoteChanged || shouldSetupFromSubscriberCount);
|
|
const setupRootNode = !!nextStateOwners.documentElement && (rootNodeChanged || shouldSetupFromSubscriberCount);
|
|
const setupSomething = setupMedia || setupTextTracks || setupVideoRenditions || setupAudioTracks || setupRemote || setupRootNode;
|
|
const somethingToDo = teardownSomething || setupSomething;
|
|
if (!somethingToDo) {
|
|
Object.entries(nextStateOwners).forEach(
|
|
([stateOwnerName, stateOwner]) => {
|
|
stateOwners[stateOwnerName] = stateOwner;
|
|
}
|
|
);
|
|
updateStateFromFacade();
|
|
nextStateOwners = void 0;
|
|
return;
|
|
}
|
|
Object.entries(stateMediator).forEach(
|
|
([
|
|
stateName,
|
|
{
|
|
get,
|
|
mediaEvents = [],
|
|
textTracksEvents = [],
|
|
videoRenditionsEvents = [],
|
|
audioTracksEvents = [],
|
|
remoteEvents = [],
|
|
rootEvents = [],
|
|
stateOwnersUpdateHandlers = []
|
|
}
|
|
]) => {
|
|
if (!stateUpdateHandlers[stateName]) {
|
|
stateUpdateHandlers[stateName] = {};
|
|
}
|
|
const handler = (event) => {
|
|
const nextValue = get(stateOwners, event);
|
|
updateState({ [stateName]: nextValue });
|
|
};
|
|
let prevHandler;
|
|
prevHandler = stateUpdateHandlers[stateName].mediaEvents;
|
|
mediaEvents.forEach((eventType) => {
|
|
if (prevHandler && teardownMedia) {
|
|
stateOwners.media.removeEventListener(eventType, prevHandler);
|
|
stateUpdateHandlers[stateName].mediaEvents = void 0;
|
|
}
|
|
if (setupMedia) {
|
|
nextStateOwners.media.addEventListener(eventType, handler);
|
|
stateUpdateHandlers[stateName].mediaEvents = handler;
|
|
}
|
|
});
|
|
prevHandler = stateUpdateHandlers[stateName].textTracksEvents;
|
|
textTracksEvents.forEach((eventType) => {
|
|
var _a2, _b2;
|
|
if (prevHandler && teardownTextTracks) {
|
|
(_a2 = stateOwners.media.textTracks) == null ? void 0 : _a2.removeEventListener(
|
|
eventType,
|
|
prevHandler
|
|
);
|
|
stateUpdateHandlers[stateName].textTracksEvents = void 0;
|
|
}
|
|
if (setupTextTracks) {
|
|
(_b2 = nextStateOwners.media.textTracks) == null ? void 0 : _b2.addEventListener(
|
|
eventType,
|
|
handler
|
|
);
|
|
stateUpdateHandlers[stateName].textTracksEvents = handler;
|
|
}
|
|
});
|
|
prevHandler = stateUpdateHandlers[stateName].videoRenditionsEvents;
|
|
videoRenditionsEvents.forEach((eventType) => {
|
|
var _a2, _b2;
|
|
if (prevHandler && teardownVideoRenditions) {
|
|
(_a2 = stateOwners.media.videoRenditions) == null ? void 0 : _a2.removeEventListener(
|
|
eventType,
|
|
prevHandler
|
|
);
|
|
stateUpdateHandlers[stateName].videoRenditionsEvents = void 0;
|
|
}
|
|
if (setupVideoRenditions) {
|
|
(_b2 = nextStateOwners.media.videoRenditions) == null ? void 0 : _b2.addEventListener(
|
|
eventType,
|
|
handler
|
|
);
|
|
stateUpdateHandlers[stateName].videoRenditionsEvents = handler;
|
|
}
|
|
});
|
|
prevHandler = stateUpdateHandlers[stateName].audioTracksEvents;
|
|
audioTracksEvents.forEach((eventType) => {
|
|
var _a2, _b2;
|
|
if (prevHandler && teardownAudioTracks) {
|
|
(_a2 = stateOwners.media.audioTracks) == null ? void 0 : _a2.removeEventListener(
|
|
eventType,
|
|
prevHandler
|
|
);
|
|
stateUpdateHandlers[stateName].audioTracksEvents = void 0;
|
|
}
|
|
if (setupAudioTracks) {
|
|
(_b2 = nextStateOwners.media.audioTracks) == null ? void 0 : _b2.addEventListener(
|
|
eventType,
|
|
handler
|
|
);
|
|
stateUpdateHandlers[stateName].audioTracksEvents = handler;
|
|
}
|
|
});
|
|
prevHandler = stateUpdateHandlers[stateName].remoteEvents;
|
|
remoteEvents.forEach((eventType) => {
|
|
var _a2, _b2;
|
|
if (prevHandler && teardownRemote) {
|
|
(_a2 = stateOwners.media.remote) == null ? void 0 : _a2.removeEventListener(
|
|
eventType,
|
|
prevHandler
|
|
);
|
|
stateUpdateHandlers[stateName].remoteEvents = void 0;
|
|
}
|
|
if (setupRemote) {
|
|
(_b2 = nextStateOwners.media.remote) == null ? void 0 : _b2.addEventListener(eventType, handler);
|
|
stateUpdateHandlers[stateName].remoteEvents = handler;
|
|
}
|
|
});
|
|
prevHandler = stateUpdateHandlers[stateName].rootEvents;
|
|
rootEvents.forEach((eventType) => {
|
|
if (prevHandler && teardownRootNode) {
|
|
stateOwners.documentElement.removeEventListener(
|
|
eventType,
|
|
prevHandler
|
|
);
|
|
stateUpdateHandlers[stateName].rootEvents = void 0;
|
|
}
|
|
if (setupRootNode) {
|
|
nextStateOwners.documentElement.addEventListener(
|
|
eventType,
|
|
handler
|
|
);
|
|
stateUpdateHandlers[stateName].rootEvents = handler;
|
|
}
|
|
});
|
|
const prevHandlerTeardown = stateUpdateHandlers[stateName].stateOwnersUpdateHandlers;
|
|
stateOwnersUpdateHandlers.forEach((fn) => {
|
|
if (prevHandlerTeardown && teardownSomething) {
|
|
prevHandlerTeardown();
|
|
}
|
|
if (setupSomething) {
|
|
stateUpdateHandlers[stateName].stateOwnersUpdateHandlers = fn(
|
|
handler,
|
|
nextStateOwners
|
|
);
|
|
}
|
|
});
|
|
}
|
|
);
|
|
Object.entries(nextStateOwners).forEach(([stateOwnerName, stateOwner]) => {
|
|
stateOwners[stateOwnerName] = stateOwner;
|
|
});
|
|
updateStateFromFacade();
|
|
nextStateOwners = void 0;
|
|
};
|
|
updateStateOwners({ media, fullscreenElement, documentElement, options });
|
|
return {
|
|
// note that none of these cases directly interact with the media element, root node, full screen element, etc.
|
|
// note these "actions" could just be the events if we wanted, especially if we normalize on "detail" for
|
|
// any payload-relevant values
|
|
// This is roughly equivalent to our used to be in our state requests dictionary object, though much of the
|
|
// "heavy lifting" is now moved into the facade `set()`
|
|
dispatch(action) {
|
|
const { type, detail } = action;
|
|
if (requestMap[type]) {
|
|
updateState(requestMap[type](stateMediator, stateOwners, action));
|
|
return;
|
|
}
|
|
if (type === "mediaelementchangerequest") {
|
|
updateStateOwners({ media: detail });
|
|
} else if (type === "fullscreenelementchangerequest") {
|
|
updateStateOwners({ fullscreenElement: detail });
|
|
} else if (type === "documentelementchangerequest") {
|
|
updateStateOwners({ documentElement: detail });
|
|
} else if (type === "optionschangerequest") {
|
|
Object.entries(detail != null ? detail : {}).forEach(([optionName, optionValue]) => {
|
|
stateOwners.options[optionName] = optionValue;
|
|
});
|
|
}
|
|
},
|
|
getState() {
|
|
return state;
|
|
},
|
|
subscribe(callback) {
|
|
updateStateOwners({}, callbacks.length + 1);
|
|
callbacks.push(callback);
|
|
callback(state);
|
|
return () => {
|
|
const idx = callbacks.indexOf(callback);
|
|
if (idx >= 0) {
|
|
updateStateOwners({}, callbacks.length - 1);
|
|
callbacks.splice(idx, 1);
|
|
}
|
|
};
|
|
}
|
|
};
|
|
};
|
|
var media_store_default = createMediaStore;
|
|
export {
|
|
media_store_default as default
|
|
};
|