// Prevent DASH.js from automatically attaching to video sources by default. // Whoever thought this is a good idea?! if (typeof window !== 'undefined' && !window.dashjs) { window.dashjs = { skipAutoCreate: true, isDefaultSubject: true }; } export default function (playerInstance, options) { playerInstance.initialiseStreamers = () => { playerInstance.detachStreamers(); switch (playerInstance.displayOptions.layoutControls.mediaType) { case 'application/dash+xml': // MPEG-DASH if (!playerInstance.dashScriptLoaded && (!window.dashjs || window.dashjs.isDefaultSubject)) { playerInstance.dashScriptLoaded = true; import(/* webpackChunkName: "dashjs" */ 'dashjs').then((it) => { window.dashjs = it.default; playerInstance.initialiseDash(); }); } else { playerInstance.initialiseDash(); } break; case 'application/x-mpegurl': // HLS const { displayOptions, domRef } = playerInstance; const { player } = domRef; const { hls } = displayOptions; // Doesn't load hls.js if player can play it natively if (player.canPlayType('application/x-mpegurl') && !hls.overrideNative) { playerInstance.debugMessage('Native HLS support found, skipping hls.js'); break; } if (!playerInstance.hlsScriptLoaded && !window.Hls) { playerInstance.hlsScriptLoaded = true; import(/* webpackChunkName: "hlsjs" */ 'hls.js').then((it) => { window.Hls = it.default; playerInstance.initialiseHls(); }); } else { playerInstance.initialiseHls(); } break; } }; playerInstance.initialiseDash = () => { if (typeof (window.MediaSource || window.WebKitMediaSource) === 'function') { // If false we want to override the autoPlay, as it comes from postRoll const playVideo = !playerInstance.autoplayAfterAd ? playerInstance.autoplayAfterAd : playerInstance.displayOptions.layoutControls.autoPlay; const defaultOptions = { 'debug': { 'logLevel': typeof FP_DEBUG !== 'undefined' && FP_DEBUG === true ? dashjs.Debug.LOG_LEVEL_DEBUG : dashjs.Debug.LOG_LEVEL_FATAL } }; const dashPlayer = dashjs.MediaPlayer().create(); const options = playerInstance.displayOptions.modules.configureDash(defaultOptions); dashPlayer.updateSettings(options); playerInstance.displayOptions.modules.onBeforeInitDash(dashPlayer); dashPlayer.initialize(playerInstance.domRef.player, playerInstance.originalSrc, playVideo); dashPlayer.on('streamInitializing', () => { playerInstance.toggleLoader(true); }); dashPlayer.on('canPlay', () => { playerInstance.toggleLoader(false); }); dashPlayer.on('playbackPlaying', () => { playerInstance.toggleLoader(false); }); playerInstance.displayOptions.modules.onAfterInitDash(dashPlayer); playerInstance.dashPlayer = dashPlayer; } else { playerInstance.nextSource(); console.log('[FP_WARNING] Media type not supported by this browser using DASH.js. (application/dash+xml)'); } }; playerInstance.initialiseHls = () => { if (typeof Hls !== 'undefined' && Hls.isSupported()) { playerInstance.debugMessage('Initializing hls.js'); const defaultOptions = { debug: typeof FP_DEBUG !== 'undefined' && FP_DEBUG === true, startPosition: 0, p2pConfig: { logLevel: false, }, enableWebVTT: false, enableCEA708Captions: false, }; const options = playerInstance.displayOptions.modules.configureHls(defaultOptions); const hls = new Hls(options); playerInstance.displayOptions.modules.onBeforeInitHls(hls); hls.attachMedia(playerInstance.domRef.player); hls.loadSource(playerInstance.originalSrc); playerInstance.displayOptions.modules.onAfterInitHls(hls); playerInstance.hlsPlayer = hls; if (!playerInstance.firstPlayLaunched && playerInstance.displayOptions.layoutControls.autoPlay) { playerInstance.domRef.player.play(); } playerInstance.createHLSVideoSourceSwitch(); } else { playerInstance.nextSource(); console.log('[FP_WARNING] Media type not supported by this browser using HLS.js. (application/x-mpegURL)'); } }; playerInstance.createHLSVideoSourceSwitch = () => { playerInstance.hlsPlayer.on(Hls.Events.MANIFEST_PARSED, function () { try { const levels = createHLSLevels(); const sortedLevels = sortLevels(levels); playerInstance.videoSources = sortedLevels; // <=2 because of the added auto function if (sortedLevels.length <= 2) return; const sourceChangeButton = playerInstance.domRef.wrapper.querySelector('.fluid_control_video_source'); toggleSourceChangeButtonVisibility(sortedLevels, sourceChangeButton); const sourceChangeList = createSourceChangeList(sortedLevels); attachSourceChangeList(sourceChangeButton, sourceChangeList); // Set initial level based on persisted quality or default to auto setInitialLevel(sortedLevels); } catch (err) { console.error(err); } }); }; function createHLSLevels() { const HLSLevels = playerInstance.hlsPlayer.levels .map((level, index) => ({ id: index, title: String(level.width), isHD: level.videoRange === 'HDR', bitrate: level.bitrate })); const autoLevel = { id: -1, title: 'auto', isHD: false, bitrate: 0 }; return [...HLSLevels, autoLevel]; } function toggleSourceChangeButtonVisibility(levels, sourceChangeButton) { if (levels.length > 1) { sourceChangeButton.style.display = 'inline-block'; } else { sourceChangeButton.style.display = 'none'; } } function createSourceChangeList(levels) { const sourceChangeList = document.createElement('div'); sourceChangeList.className = 'fluid_video_sources_list'; sourceChangeList.style.display = 'none'; levels.forEach(level => { const sourceChangeDiv = createSourceChangeItem(level); sourceChangeList.appendChild(sourceChangeDiv); }); return sourceChangeList; } function createSourceChangeItem(level) { const sourceSelectedClass = getSourceSelectedClass(level); const hdIndicator = level.isHD ? `` : ''; const sourceChangeDiv = document.createElement('div'); sourceChangeDiv.className = `fluid_video_source_list_item js-source_${level.title}`; sourceChangeDiv.innerHTML = `${level.title}${hdIndicator}`; sourceChangeDiv.addEventListener('click', event => onSourceChangeClick(event, level)); return sourceChangeDiv; } function getSourceSelectedClass(level) { const matchingLevels = playerInstance.videoSources.filter(source => source.title === playerInstance.fluidStorage.fluidQuality); // If there are multiple matching levels, use the first one if (matchingLevels.length > 1) { if (matchingLevels[0].id === level.id) { return "source_selected"; } } else if (matchingLevels.length === 1) { return matchingLevels[0].id === level.id ? "source_selected" : ""; } // Fallback to auto selection if no persistent level exists if (!matchingLevels.length && level.title === 'auto') { return "source_selected"; } return ""; } function onSourceChangeClick(event, selectedLevel) { event.stopPropagation(); setPlayerDimensions(); const videoChangedTo = event.currentTarget; clearSourceSelectedIcons(); videoChangedTo.firstChild.classList.add('source_selected'); playerInstance.videoSources.forEach(source => { if (source.title === videoChangedTo.innerText.replace(/(\r\n\t|\n|\r\t)/gm, '')) { playerInstance.hlsPlayer.currentLevel = selectedLevel.id; playerInstance.fluidStorage.fluidQuality = selectedLevel.title; } }); playerInstance.openCloseVideoSourceSwitch(); } // While changing source the player size can flash, we want to set the pixel dimensions then back to 100% afterwards function setPlayerDimensions() { playerInstance.domRef.player.style.width = `${playerInstance.domRef.player.clientWidth}px`; playerInstance.domRef.player.style.height = `${playerInstance.domRef.player.clientHeight}px`; } function clearSourceSelectedIcons() { const sourceIcons = playerInstance.domRef.wrapper.getElementsByClassName('source_button_icon'); Array.from(sourceIcons).forEach(icon => icon.classList.remove('source_selected')); } function attachSourceChangeList(sourceChangeButton, sourceChangeList) { sourceChangeButton.appendChild(sourceChangeList); sourceChangeButton.removeEventListener('click', playerInstance.openCloseVideoSourceSwitch); sourceChangeButton.addEventListener('click', playerInstance.openCloseVideoSourceSwitch); } function setInitialLevel(levels) { // Check if a persistency level exists and set the current level accordingly const persistedLevel = levels.find(level => level.title === playerInstance.fluidStorage.fluidQuality); if (persistedLevel) { playerInstance.hlsPlayer.currentLevel = persistedLevel.id; } else { // Default to 'auto' if no persisted level is found const autoLevel = levels.find(level => level.title === 'auto'); playerInstance.hlsPlayer.currentLevel = autoLevel.id; } } function sortLevels(levels) { return [...levels].sort((a, b) => { // First sort by width in descending order if (b.width !== a.width) { return b.width - a.width; } // If width is the same, sort by bitrate in descending order return b.bitrate - a.bitrate; }); } playerInstance.detachStreamers = () => { if (playerInstance.dashPlayer) { playerInstance.dashPlayer.reset(); playerInstance.dashPlayer = false; } else if (playerInstance.hlsPlayer) { playerInstance.hlsPlayer.detachMedia(); playerInstance.hlsPlayer = false; } }; }