UNPKG

fluid-player

Version:

Fluid Player is a free HTML5 video player

439 lines (359 loc) 18.2 kB
export default function (playerInstance, options) { // Define the subtitle size levels playerInstance.subtitleSizeLevels = [50, 75, 100, 150, 200]; // in percentage playerInstance.currentSubtitleSizeIndex = playerInstance.subtitleSizeLevels.indexOf(100); // Default to 100% playerInstance.subtitleFetchParse = (subtitleItem) => { playerInstance.sendRequest( subtitleItem.url, false, playerInstance.displayOptions.vastOptions.vastTimeout, function () { const convertVttRawData = function (vttRawData) { if (!( (typeof vttRawData.cues !== 'undefined') && (vttRawData.cues.length) )) { return []; } const result = []; for (let i = 0; i < vttRawData.cues.length; i++) { let tempThumbnailData = vttRawData.cues[i].text.split('#'); result.push({ startTime: vttRawData.cues[i].startTime, endTime: vttRawData.cues[i].endTime, text: vttRawData.cues[i].text, cue: vttRawData.cues[i] }) } return result; }; const xmlHttpReq = this; if ((xmlHttpReq.readyState === 4) && (xmlHttpReq.status !== 200)) { //The response returned an error. return; } if (!((xmlHttpReq.readyState === 4) && (xmlHttpReq.status === 200))) { return; } const textResponse = xmlHttpReq.responseText; const parser = new WebVTT.Parser(window, WebVTT.StringDecoder()); const cues = []; const regions = []; // TODO: unused? parser.oncue = function (cue) { cues.push(cue); }; parser.onregion = function (region) { regions.push(region); }; parser.parse(textResponse); parser.flush(); playerInstance.subtitlesData = cues; } ); }; playerInstance.createSubtitlesSwitch = () => { const subtitlesOff = 'OFF'; playerInstance.subtitlesData = []; if (!playerInstance.displayOptions.layoutControls.subtitlesEnabled) { // No other video subtitles playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles').style.display = 'none'; return; } const tracks = []; tracks.push({'label': subtitlesOff, 'url': 'na', 'lang': subtitlesOff}); const tracksList = playerInstance.domRef.player.querySelectorAll('track'); [].forEach.call(tracksList, function (track) { if (track.kind === 'metadata' && track.src) { tracks.push({'label': track.label, 'url': track.src, 'lang': track.srclang, 'default': track.default}); } }); playerInstance.subtitlesTracks = tracks; const subtitlesChangeButton = playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles'); subtitlesChangeButton.style.display = 'inline-block'; let appendSubtitleChange = false; const subtitlesChangeList = document.createElement('div'); subtitlesChangeList.className = 'fluid_subtitles_list'; subtitlesChangeList.style.display = 'none'; playerInstance.addSubtitleSizeControls(subtitlesChangeList, subtitlesChangeButton); let hasSelectedSubtitle = false; const hasDefault = !!playerInstance.subtitlesTracks.find(track => track.default); playerInstance.subtitlesTracks.forEach(function (subtitle) { let subtitleSelected = '' const subtitlesOnByDefault = playerInstance.displayOptions.layoutControls.subtitlesOnByDefault; if (!hasSelectedSubtitle && (subtitlesOnByDefault && subtitle.default || (!hasDefault && subtitle.label !== subtitlesOff) || playerInstance.subtitlesTracks.length === 1) || !subtitlesOnByDefault && subtitle.label === subtitlesOff ) { subtitleSelected = 'subtitle_selected'; playerInstance.subtitleFetchParse(subtitle); hasSelectedSubtitle = true; } const subtitlesChangeDiv = document.createElement('div'); subtitlesChangeDiv.className = 'fluid_subtitle_list_item'; subtitlesChangeDiv.innerHTML = '<span class="subtitle_button_icon ' + subtitleSelected + '"></span>' + subtitle.label; subtitlesChangeDiv.addEventListener('click', function (event) { event.stopPropagation(); const subtitleChangedTo = this; const subtitleIcons = playerInstance.domRef.wrapper.getElementsByClassName('subtitle_button_icon'); for (let i = 0; i < subtitleIcons.length; i++) { subtitleIcons[i].className = subtitleIcons[i].className.replace("subtitle_selected", ""); } subtitleChangedTo.firstChild.className += ' subtitle_selected'; playerInstance.subtitlesTracks.forEach(function (subtitle) { if (subtitle.label === subtitleChangedTo.innerText.replace(/(\r\n\t|\n|\r\t)/gm, "")) { if (subtitle.label === subtitlesOff) { playerInstance.subtitlesData = []; } else { playerInstance.subtitleFetchParse(subtitle); } } }); playerInstance.openCloseSubtitlesSwitch(); }); subtitlesChangeList.appendChild(subtitlesChangeDiv); appendSubtitleChange = true; }); if (appendSubtitleChange) { subtitlesChangeButton.appendChild(subtitlesChangeList); subtitlesChangeButton.removeEventListener('click', handleSubtitlesChange); subtitlesChangeButton.addEventListener('click', handleSubtitlesChange); } else { // Didn't give any subtitle options playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles').style.display = 'none'; } playerInstance.domRef.player.removeEventListener('timeupdate', videoPlayerSubtitlesUpdate); playerInstance.domRef.player.addEventListener('timeupdate', videoPlayerSubtitlesUpdate); }; function handleSubtitlesChange(event) { const subtitlesChangeButton = playerInstance.domRef.wrapper.querySelector('.fluid_control_subtitles'); let hasOpenMenuOrSubMenu = false; // Check if menu or any submenu is open for (let i = 0; i < subtitlesChangeButton.children.length; i++) { if (subtitlesChangeButton.children[i].style.display === 'block') { hasOpenMenuOrSubMenu = true; subtitlesChangeButton.children[i].style.display = 'none'; } } // Only open subtitle menu if no menu or submenu (direct child) is visible if (!hasOpenMenuOrSubMenu) { playerInstance.openCloseSubtitlesSwitch(); } } //attach subtitles to show based on time //this function is for rendering of subtitles when content is playing function videoPlayerSubtitlesUpdate() { playerInstance.renderSubtitles(); } /** * Creates and appends the subtitle size button and menu * * @param {HTMLElement} subtitlesChangeList The container for the subtitles change list * @param {HTMLElement} subtitlesChangeButton The button to toggle the subtitles menu */ playerInstance.addSubtitleSizeControls = (subtitlesChangeList, subtitlesChangeButton) => { const subtitlesSizeButton = playerInstance.createSubtitleSizeButton(); const subtitlesSizeMenu = playerInstance.createSubtitleSizeMenu(); subtitlesSizeMenu.style.display = 'none'; subtitlesChangeList.appendChild(subtitlesSizeButton); subtitlesChangeButton.appendChild(subtitlesSizeMenu); }; playerInstance.renderSubtitles = () => { const videoPlayer = playerInstance.domRef.player; //if content is playing then no subtitles let currentTime = Math.floor(videoPlayer.currentTime); let subtitlesAvailable = false; let subtitlesContainer = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_container'); if (playerInstance.isCurrentlyPlayingAd) { subtitlesContainer.innerHTML = ''; return; } for (let i = 0; i < playerInstance.subtitlesData.length; i++) { if (currentTime >= (playerInstance.subtitlesData[i].startTime) && currentTime <= (playerInstance.subtitlesData[i].endTime)) { subtitlesContainer.innerHTML = ''; subtitlesContainer.appendChild(WebVTT.convertCueToDOMTree(window, playerInstance.subtitlesData[i].text)); subtitlesAvailable = true; } } if (!subtitlesAvailable) { subtitlesContainer.innerHTML = ''; } }; playerInstance.openCloseSubtitlesSwitch = () => { const subtitleChangeList = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_list'); if (playerInstance.isCurrentlyPlayingAd) { subtitleChangeList.style.display = 'none'; return; } if (subtitleChangeList.style.display === 'none') { subtitleChangeList.style.display = 'block'; const mouseOut = function (event) { subtitleChangeList.removeEventListener('mouseleave', mouseOut); subtitleChangeList.style.display = 'none'; }; subtitleChangeList.addEventListener('mouseleave', mouseOut); } else { subtitleChangeList.style.display = 'none'; } playerInstance.setMenusMaxHeight(); }; playerInstance.createSubtitles = () => { const divSubtitlesContainer = document.createElement('div'); divSubtitlesContainer.className = 'fluid_subtitles_container'; playerInstance.domRef.player.parentNode.insertBefore(divSubtitlesContainer, playerInstance.domRef.player.nextSibling); if (!playerInstance.displayOptions.layoutControls.subtitlesEnabled) { return; } import(/* webpackChunkName: "vttjs" */ 'videojs-vtt.js').then((it) => { window.WebVTT = it.WebVTT || it.default.WebVTT; playerInstance.createSubtitlesSwitch(); }); }; /** * Resize the subtitles font size * * @param size size in percentage, 100% is the default size */ playerInstance.resizeSubtitles = (size) => { const subtitlesContainer = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_container'); const fontSizeInEm = size / 100; // Convert percentage to em subtitlesContainer.style.fontSize = `${fontSizeInEm}em`; } /** * Adjusts the subtitle size to the next or previous level. * * @param {'+'|'-'} operation '+' to increase, '-' to decrease */ playerInstance.adjustSubtitleSizeByOperation = (operation) => { // Validate the operation and the current subtitle size index if (operation === '+' && playerInstance.currentSubtitleSizeIndex < playerInstance.subtitleSizeLevels.length - 1) { playerInstance.currentSubtitleSizeIndex++; } else if (operation === '-' && playerInstance.currentSubtitleSizeIndex > 0) { playerInstance.currentSubtitleSizeIndex--; } else { return; // invalid operation } const newSize = playerInstance.subtitleSizeLevels[playerInstance.currentSubtitleSizeIndex]; playerInstance.resizeSubtitles(newSize); // Update the checkmark in the subtitle size menu playerInstance.updateSubtitleSizeMenuSelection(); }; /** * Updates the subtitle size menu to show the correct checkmark * based on the current subtitle size index */ playerInstance.updateSubtitleSizeMenuSelection = () => { const sizeMenu = playerInstance.domRef.wrapper.querySelector('.fluid_subtitle_size_menu'); if (!sizeMenu) return; // Clear all existing checkmarks const sizeIcons = sizeMenu.querySelectorAll('.subtitle_button_icon'); for (let i = 0; i < sizeIcons.length; i++) { sizeIcons[i].className = sizeIcons[i].className.replace("subtitle_size_selected", ""); } // Get all size buttons (skip the back button) const sizeButtons = Array.from(sizeMenu.querySelectorAll('.fluid_subtitle_size_button')) .filter(button => !button.classList.contains('fluid_sub_menu_button')); // Add the checkmark to the current size button if it exists if (sizeButtons[playerInstance.currentSubtitleSizeIndex]) { const icon = sizeButtons[playerInstance.currentSubtitleSizeIndex].querySelector('.subtitle_button_icon'); if (icon) { icon.className += ' subtitle_size_selected'; } } }; /** * Creates the subtitle size button element * * @returns {HTMLElement} The subtitle size button element */ playerInstance.createSubtitleSizeButton = () => { const subtitleSizeMenuButton = document.createElement('div'); subtitleSizeMenuButton.className = 'fluid_subtitle_list_item fluid_sub_menu_button arrow-right'; subtitleSizeMenuButton.innerHTML = 'Font Size'; subtitleSizeMenuButton.addEventListener('click', (event) => { event.stopPropagation(); playerInstance.toggleSubtitleSizeMenu(); playerInstance.openCloseSubtitlesSwitch(); }); return subtitleSizeMenuButton; } /** * Creates the subtitle size menu element * * @returns {HTMLElement} The subtitle size menu element */ playerInstance.createSubtitleSizeMenu = () => { const subtitleSizeMenu = document.createElement('div'); subtitleSizeMenu.className = 'fluid_subtitle_size_menu'; // Add the "Back" button const backButton = playerInstance.createBackButtonForSubtitleSizeMenu(); subtitleSizeMenu.appendChild(backButton); // Add the font size options playerInstance.subtitleSizeLevels.forEach((size, index) => { const subtitleSizeButton = document.createElement('div'); subtitleSizeButton.className = 'fluid_subtitle_size_button'; const isSelected = size === 100; // Default size is 100% subtitleSizeButton.innerHTML = '<span class="subtitle_button_icon ' + (isSelected ? 'subtitle_size_selected' : '') + '"></span>' + size + '%'; subtitleSizeButton.addEventListener('click', (event) => { // Stop the click event from propagating to parent elements, // preventing unintended actions like toggling the entire menu. event.stopPropagation(); playerInstance.toggleSubtitleSizeMenu(); playerInstance.resizeSubtitles(size); playerInstance.currentSubtitleSizeIndex = index; // Update the checkmark for all size buttons playerInstance.updateSubtitleSizeMenuSelection(); }); subtitleSizeMenu.appendChild(subtitleSizeButton); }); return subtitleSizeMenu; }; /** * Creates a "Back" button for the subtitle size menu. */ playerInstance.createBackButtonForSubtitleSizeMenu = () => { const backButton = document.createElement('div'); backButton.className = 'fluid_subtitle_size_button fluid_sub_menu_button arrow-left'; backButton.innerHTML = 'Font Size'; backButton.addEventListener('click', (event) => { event.stopPropagation(); playerInstance.toggleSubtitleSizeMenu(); playerInstance.openCloseSubtitlesSwitch(); }); return backButton; }; /** * Toggles the visibility of the subtitle size menu */ playerInstance.toggleSubtitleSizeMenu = () => { const subtitleSizeMenu = playerInstance.domRef.wrapper.querySelector('.fluid_subtitle_size_menu'); const mouseOut = function (event) { subtitleSizeMenu.removeEventListener('mouseleave', mouseOut); subtitleSizeMenu.style.display = 'none'; }; if (subtitleSizeMenu.style.display === 'block') { subtitleSizeMenu.style.display = 'none'; subtitleSizeMenu.removeEventListener('mouseleave', mouseOut); } else { subtitleSizeMenu.style.display = 'block'; subtitleSizeMenu.addEventListener('mouseleave', mouseOut); } playerInstance.setMenusMaxHeight(); }; /** * Calculate and set the max height of the all menus for small screens */ playerInstance.setMenusMaxHeight = () => { const playerHeight = playerInstance.domRef.wrapper.offsetHeight; const controlsHeight = playerInstance.domRef.wrapper.querySelector('.fluid_controls_container').offsetHeight; const maxHeight = playerHeight - controlsHeight; const subtitleSizeMenu = playerInstance.domRef.wrapper.querySelector('.fluid_subtitle_size_menu'); const subtitleChangeList = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_list'); subtitleSizeMenu.style.maxHeight = `${maxHeight}px`; subtitleChangeList.style.maxHeight = `${maxHeight}px`; }; playerInstance.repositionSubtitlesContainer = (size) => { const subtitlesContainer = playerInstance.domRef.wrapper.querySelector('.fluid_subtitles_container'); subtitlesContainer.style.bottom = size; }; }