bitmovin-player-react
Version:
A React component that creates an instance of Bitmovin player
139 lines (138 loc) • 6.58 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.BitmovinPlayer = void 0;
const jsx_runtime_1 = require("react/jsx-runtime");
const bitmovin_player_1 = require("bitmovin-player");
const bitmovin_player_ui_1 = require("bitmovin-player-ui");
const react_1 = require("react");
exports.BitmovinPlayer = (0, react_1.forwardRef)(function BitmovinPlayer({ config, source, className, playerRef: playerRefProp, customUi }, forwardedRef) {
const rootContainerElementRef = (0, react_1.useRef)(null);
const rootContainerElementRefHandler = (rootContainerElement) => {
setRef(rootContainerElementRef, rootContainerElement);
if (forwardedRef) {
setRef(forwardedRef, rootContainerElement);
}
};
const isInitialSourceEmptyRef = (0, react_1.useRef)(!source);
const isSourceChangedAtLeastOnce = (0, react_1.useRef)(false);
const [player, setPlayer] = (0, react_1.useState)();
const latestPlayerRef = (0, react_1.useRef)();
const proxyPlayerRef = (0, react_1.useCallback)((player) => {
if (!playerRefProp) {
return;
}
setRef(playerRefProp, player);
},
// Only the first `playerRef` is relevant and should be used to avoid
// unnecessary ref callback invocations (in case the `playerRef` is a ref function).
// eslint-disable-next-line react-hooks/exhaustive-deps
[]);
// Initialize the player on mount.
(0, react_1.useEffect)(() => {
const rootContainerElement = rootContainerElementRef.current;
if (!rootContainerElement) {
return;
}
// We create elements manually to workaround the React strict mode.
// In the strict mode the mount hook is invoked twice. Since the destroy method is async
// the next mount hook is invoked before the previous destroy method is finished and the new player instance
// messes up the old one. This workaround ensures that each player instance has its own container and video elements.
// This should be improved in the future if possible.
const { createdPlayerContainerElement, createdVideoElement } = preparePlayerElements(rootContainerElement);
const convertedConfig = convertConfig(config);
const initializedPlayer = initializePlayer(createdPlayerContainerElement, createdVideoElement, convertedConfig);
const uiManager = initializePlayerUi(initializedPlayer, config, customUi);
latestPlayerRef.current = initializedPlayer;
proxyPlayerRef(initializedPlayer);
setPlayer(initializedPlayer);
return () => {
destroyPlayer(initializedPlayer, rootContainerElement, createdPlayerContainerElement, uiManager);
};
}, [config, customUi, proxyPlayerRef]);
// Load or reload the source.
(0, react_1.useEffect)(() => {
if (!player ||
// Skip if the player is not the latest one, this happens in case of HMR.
player !== latestPlayerRef.current) {
return;
}
if (source) {
player.load(source);
setRef(isSourceChangedAtLeastOnce, true);
}
else {
// Skip unloading the player if the source is empty on mount.
// This is useful in case users want to use the player instance ref to load the source manually,
// so this ensures that we do not unload the imperatively loaded source.
// TODO do we need it?
//
// Apart from that, this check ensures that `player.unload` is not called unnecessarily on mount if the source is empty.
// TODO do we actually care?
const shouldSkipUnload = isInitialSourceEmptyRef.current && !isSourceChangedAtLeastOnce.current;
if (!shouldSkipUnload) {
player.unload();
setRef(isSourceChangedAtLeastOnce, true);
}
}
}, [source, player]);
return (0, jsx_runtime_1.jsx)("div", { className: className, ref: rootContainerElementRefHandler });
});
function setRef(ref, value) {
if (typeof ref === 'function') {
ref(value);
}
else {
ref.current = value;
}
}
function initializePlayerUi(player, playerConfig, customUi) {
if (playerConfig.ui === false) {
return;
}
// If a custom UI container is provided, use it instead of the default UI.
if (customUi && 'containerFactory' in customUi) {
return new bitmovin_player_ui_1.UIManager(player, customUi.containerFactory(), playerConfig.ui);
}
// If custom UI variants are provided, use them instead of the default UI.
else if (customUi && 'variantsFactory' in customUi) {
return new bitmovin_player_ui_1.UIManager(player, customUi.variantsFactory(), playerConfig.ui);
}
else {
return bitmovin_player_ui_1.UIFactory.buildDefaultUI(player, playerConfig.ui);
}
}
function convertConfig(originalConfig) {
const convertedConfig = Object.assign({}, originalConfig);
// By default Bitmovin player assumes there is `bitmovinplayer-ui.css` and `bitmovinplayer-ui.js` files hosted on the same domain
// and tries to use them. Since this is a React wrapper it should work standalone.
// Disable loading the default `bitmovinplayer-ui.css` and `bitmovinplayer-ui.js` from the same domain by the player.
convertedConfig.ui = false;
return convertedConfig;
}
function initializePlayer(containerElement, videoElement, convertedConfig) {
const player = new bitmovin_player_1.Player(containerElement, convertedConfig);
player.setVideoElement(videoElement);
return player;
}
function preparePlayerElements(rootContainerElement) {
const createdPlayerContainerElement = document.createElement('div');
const createdVideoElement = document.createElement('video');
rootContainerElement.appendChild(createdPlayerContainerElement);
createdPlayerContainerElement.appendChild(createdVideoElement);
return {
createdPlayerContainerElement,
createdVideoElement,
};
}
function destroyPlayer(player, rootContainerElement, playerContainerElement, uiManager) {
playerContainerElement.style.display = 'none';
const removePlayerContainerElement = () => {
rootContainerElement.removeChild(playerContainerElement);
};
// First destroy the UI manager if it exists
if (uiManager) {
uiManager.release();
}
// Then destroy the player
player.destroy().then(removePlayerContainerElement, removePlayerContainerElement);
}