UNPKG

bitmovin-player-react

Version:

A React component that creates an instance of Bitmovin player

139 lines (138 loc) 6.58 kB
"use strict"; 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); }