1/* 2 * REVOLT.TV Floating Video Player 3 * This script is used to embed the revolt.tv promo video player on a website. 4 * 5 * COPYRIGHT (C) 2024 YOUR NAME. ALL RIGHTS RESERVED. 6 */ 7 8/* eslint-disable no-undef */ 9import { getPlayerProp } from '../templates/shorts/shorts.js'; 10import { applyPrivacyToAdTag, resolveAdMode } from './privacy.js'; 11import { suppressedAdvertising } from './jw-ads.js'; 12 13const PLAYLIST_ID = 'STIJxPse'; 14const PLAYER_DELAY = 3000; // 3 seconds delay before showing player on page 15const params = new URLSearchParams(window.location.search); 16 17// Load stylesheet from /styles/revolt-player.css 18const styleSheet = document.createElement('link'); 19styleSheet.rel = 'stylesheet'; 20styleSheet.href = '/styles/reels-revolt-player.css'; 21document.head.appendChild(styleSheet); 22 23async function embedReelsRevoltPlayer() { 24 // Player wrapper div 25 const containerDiv = document.createElement('div'); 26 containerDiv.classList.add('reels-revolt-player-container'); 27 28 // Heading div 29 const headingDiv = document.createElement('div'); 30 headingDiv.classList.add('reels-revolt-player-heading'); 31 32 // Mobile only link div 33 const mobileLinkDiv = document.createElement('div'); 34 mobileLinkDiv.classList.add('reels-revolt-player-mobile-link'); 35 const mobileLinkElement = document.createElement('a'); 36 37 mobileLinkDiv.appendChild(mobileLinkElement); 38 headingDiv.appendChild(mobileLinkDiv); 39 containerDiv.appendChild(headingDiv); 40 41 const playerDiv = document.createElement('div'); 42 playerDiv.id = 'reels-revolt-player'; 43 containerDiv.appendChild(playerDiv); 44 45 const linkDiv = document.createElement('div'); 46 linkDiv.classList.add('reels-revolt-player-link'); 47 48 const linkElement = document.createElement('a'); 49 linkDiv.appendChild(linkElement); 50 51 const nextImageLinkElement = document.createElement('a'); 52 nextImageLinkElement.href = 'https://www.revolt.tv'; // Default url used before video starts 53 nextImageLinkElement.classList.add('reels-revolt-player-next'); 54 const nextImage = document.createElement('img'); 55 nextImage.src = '/icons/revolt-player-next.png'; 56 nextImage.alt = 'Watch Episode'; 57 nextImageLinkElement.appendChild(nextImage); 58 linkDiv.appendChild(nextImageLinkElement); 59 60 const learnMoreButton = document.createElement('a'); 61 learnMoreButton.classList.add('learn-more-btn'); 62 learnMoreButton.innerHTML = 'Learn More'; 63 64 const handleAdRedirection = (event, adClickURL) => { 65 if (event.currentTarget.innerText === learnMoreButton.innerHTML) { 66 window.open(adClickURL, '_blank'); 67 } 68 }; 69 70 containerDiv.appendChild(linkDiv); 71 72 document.body.appendChild(containerDiv); 73 74 // Fetch safetags from the current page 75 let safetags = ''; 76 const maxRetries = 10; 77 const retryInterval = 250; // 500ms 78 function getSafetags() { 79 return new Promise((resolve) => { 80 let retryCount = 0; 81 82 function checkSafetags() { 83 if (window.safetag_lists && window.safetag_lists.Exclude) { 84 if (window.safetag_lists.Exclude.length > 0) { 85 safetags = window.safetag_lists.Exclude.join(','); 86 safetags = encodeURIComponent(safetags); 87 } 88 // eslint-disable-next-line no-console 89 console.log('player safetags (encoded)', safetags); 90 91 resolve(); 92 } else if (retryCount < maxRetries) { 93 retryCount += 1; 94 setTimeout(checkSafetags, retryInterval); 95 } else { 96 // eslint-disable-next-line no-console 97 console.warn('Failed to get safetags after maximum retries'); 98 resolve(); 99 } 100 } 101 102 checkSafetags(); 103 }); 104 } 105 106 const playerDelay = typeof jwplayer === 'undefined' ? 1000 : 0; 107 setTimeout(async () => { 108 getSafetags(); 109 110 const param = { 111 safetags: safetags || '', 112 }; 113 114 // Resolve ad-serving eligibility BEFORE building/stamping tags below, so 115 // the schedule decision and the tag's privacy stamps read the same 116 // consent/opt-out snapshot instead of racing each other. Only attach an 117 // ad schedule when the visitor has Targeting (C0004) consent or is an 118 // eligible US opt-out visitor â otherwise the player never requests 119 // GAM/VAST ad tags. 120 const { load, nonPersonalized } = await resolveAdMode(); 121 122 let preRolladTagUrl; 123 let midRolladTagUrl; 124 125 preRolladTagUrl = 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21787171759/revolt.tv/site/preroll_ai_float_640x480v&description_url={currentPage}&sz=640x480&gdfp_req=1&output=xml_vast2&unviewed_position_start=1&env=vp&cust_params=safetag_exclude_list%3D{safetags}%26platform%3Dfloating';
126 midRolladTagUrl = 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21787171759/revolt.tv/site/midroll_ai_float_640x480v&description_url={currentPage}&sz=640x480&gdfp_req=1&output=xml_vast2&unviewed_position_start=1&env=vp&cust_params=safetag_exclude_list%3D{safetags}%26platform%3Dfloating'; 127 128 preRolladTagUrl = preRolladTagUrl.replace('{currentPage}', encodeURIComponent(window.location.href)); 129 midRolladTagUrl = midRolladTagUrl.replace('{currentPage}', encodeURIComponent(window.location.href)); 130 131 // Replace placeholders dynamically 132 Object.keys(param).forEach((key) => { 133 preRolladTagUrl = preRolladTagUrl.replace(`{${key}}`, param[key]); 134 midRolladTagUrl = midRolladTagUrl.replace(`{${key}}`, param[key]); 135 }); 136 137 const rngCorrelator = Math.floor(Math.random() * 9999999).toString(); 138 preRolladTagUrl = `${preRolladTagUrl}&correlator=${rngCorrelator}`; 139 midRolladTagUrl = `${midRolladTagUrl}&correlator=${rngCorrelator}`; 140 141 // Remove unused parameters in one go using regex 142 preRolladTagUrl = preRolladTagUrl.replace(/%26[^=]+%3D(&|$)/g, '$1').replace(/(&|\?)$/, ''); 143 midRolladTagUrl = midRolladTagUrl.replace(/%26[^=]+%3D(&|$)/g, '$1').replace(/(&|\?)$/, ''); 144 145 // Stamp the tags with the SAME opt-out determination resolveAdMode() 146 // above already made, rather than an independent (and possibly 147 // inconsistent) second read of GPC/usprivacy 148 preRolladTagUrl = applyPrivacyToAdTag(preRolladTagUrl, nonPersonalized); 149 midRolladTagUrl = applyPrivacyToAdTag(midRolladTagUrl, nonPersonalized); 150 const playerConfig = { 151 playlist: `https://cdn.jwplayer.com/v2/playlists/${PLAYLIST_ID}?format=json`, 152 autostart: true, 153 mute: true, 154 }; 155 if (window.location.pathname !== '/shorts') { 156 // Always set the key. Leaving it unset on the blocked path let JW fall 157 // back to its cloud-hosted schedule and serve raw personalized tags to 158 // exactly the visitors this gate excludes. 159 playerConfig.advertising = !load ? suppressedAdvertising('vast') : { 160 client: 'vast', 161 schedule: [ 162 { offset: 'pre', tag: preRolladTagUrl }, 163 { offset: 30, tag: midRolladTagUrl }, 164 { offset: 60, tag: midRolladTagUrl }, 165 { offset: 90, tag: midRolladTagUrl }, 166 { offset: 180, tag: midRolladTagUrl }, 167 { offset: 150, tag: midRolladTagUrl }, 168 { offset: 210, tag: midRolladTagUrl }, 169 { offset: 120, tag: midRolladTagUrl }, 170 { offset: 240, tag: midRolladTagUrl }, 171 { offset: 270, tag: midRolladTagUrl }, 172 ], 173 }; 174 } 175 const shortsadconfig = await getPlayerProp(); 176 177 if (window.location.pathname === '/shorts') { 178 playerConfig.advertising = shortsadconfig.advertising ?? suppressedAdvertising(); 179 } 180 const player = window.jwplayer('reels-revolt-player').setup(playerConfig); 181 window.revoltFloatingPlayer = player; 182 window.revoltFloatingPlayerWasPlaying = false; 183 184 window.pauseFloatingPlayer = () => { 185 window.revoltFloatingPlayerWasPlaying = true; 186 window.revoltFloatingPlayer.pause(true); 187 }; 188 189 window.resumeFloatingPlayer = () => { 190 if (window.revoltFloatingPlayerWasPlaying) { 191 window.revoltFloatingPlayer.play(true); 192 window.revoltFloatingPlayerWasPlaying = false; 193 } 194 }; 195 196 function stylePlayIcon() { 197 // JW Player's volume/mute button class 198 const muteButton = document.querySelector('.jw-icon.jw-icon-volume'); 199 if (muteButton) { 200 muteButton.style.display = 'flex'; 201 muteButton.style.zIndex = '9'; // stay on top 202 } 203 } 204 205 function styleAdVolumeIcon() { 206 const volumeIcon = document.querySelector('.jwplayer.jw-flag-ads .jw-icon-volume'); 207 if (volumeIcon) { 208 volumeIcon.style.top = '0px'; 209 volumeIcon.style.display = 'flex'; 210 } 211 } 212 213 function togglePlayIconLowPowerMode(show) { 214 setTimeout(() => { 215 const playIcon = document.querySelector( 216 '.jw-icon.jw-icon-display.jw-button-color.jw-reset', 217 ); 218 if (!playIcon) return; 219 220 playIcon.style.setProperty('display', show ? 'flex' : 'none', 'important'); 221 }, show ? 1000 : 0); 222 } 223 224 player.on('ready', () => { 225 if (window.location.pathname === '/shorts') { 226 player.setControls(true); 227 // Show play icon (Low Power Mode / iOS) 228 togglePlayIconLowPowerMode(true); 229 mobileLinkElement.style.display = '-webkit-box'; 230 mobileLinkElement.innerText = player.getPlaylistItem().title; 231 mobileLinkElement.href = player.getPlaylistItem().articleURL; 232 } 233 }); 234 235 player.on('play', () => { 236 if (window.location.pathname === '/shorts') { 237 stylePlayIcon(); 238 // Hide play icon 239 togglePlayIconLowPowerMode(window.innerWidth > 711); 240 } 241 window.reelsPlayers?.forEach((p) => { 242 if (p.getState() === 'playing') { 243 p.pause(true); 244 } 245 }); 246 mobileLinkElement.style.display = '-webkit-box'; 247 mobileLinkElement.innerText = player.getPlaylistItem().title; 248 mobileLinkElement.href = player.getPlaylistItem().articleURL; 249 }); 250 251 player.on('adPlay', () => { 252 if (window.location.pathname === '/shorts') { 253 styleAdVolumeIcon(); 254 } 255 window.reelsPlayers?.forEach((p) => { 256 if (p.getState() === 'playing') { 257 p.pause(true); 258 } 259 }); 260 }); 261 262 // Advertisment Start Event - Update title, link 263 jwplayer('reels-revolt-player').on('adImpression', (event) => { 264 if (window.location.pathname === '/shorts') { 265 const videoView = document.querySelector(`#${playerDiv.id}_googima .jw-ads-view video`); 266 videoView.style.zIndex = 9; 267 const controlbar = document.querySelector('.jw-controlbar'); 268 controlbar.style.zIndex = 99; 269 } 270 const adClickURL = window.location.pathname === '/shorts' ? event?.ima?.ad?.data?.clickThroughUrl : event.clickThroughUrl; 271 272 // Heading background class toggle for mobile 273 if (!headingDiv.classList.contains('reels-revolt-player-active-ad')) { 274 headingDiv.classList.add('reels-revolt-player-active-ad'); 275 } 276 mobileLinkDiv.addEventListener('click', (clickEvent) => handleAdRedirection(clickEvent, adClickURL)); 277 278 mobileLinkElement.style.display = 'none'; 279 mobileLinkDiv.appendChild(learnMoreButton); 280 281 // Hide links 282 if (!linkDiv.classList.contains('hide-link')) { 283 linkDiv.classList.add('hide-link'); 284 } 285 }); 286 287 // Advertisement End Event - Update title, link 288 jwplayer('reels-revolt-player').on('adComplete adSkipped', () => { 289 // Heading background class toggle for mobile 290 if (headingDiv.classList.contains('reels-revolt-player-active-ad')) { 291 headingDiv.classList.remove('reels-revolt-player-active-ad'); 292 } 293 294 mobileLinkDiv.removeEventListener('click', () => handleAdRedirection()); 295 296 mobileLinkElement.style.display = '-webkit-box'; 297 learnMoreButton.remove(); 298 // Show links 299 if (linkDiv.classList.contains('hide-link')) { 300 linkDiv.classList.remove('hide-link'); 301 } 302 303 mobileLinkElement.innerText = player.getPlaylistItem().title; 304 305 // Set link to show url and video title 306 const playlistItem = jwplayer('reels-revolt-player').getPlaylistItem(); 307 linkElement.textContent = player.getPlaylistItem().title; 308 linkElement.href = player.getPlaylistItem().articleURL; 309 310 mobileLinkElement.textContent = playlistItem.seriesTitle; 311 mobileLinkElement.href = player.getPlaylistItem().articleURL; 312 nextImageLinkElement.href = player.getPlaylistItem().articleURL; 313 }); 314 }, playerDelay); 315} 316 317// Call revolt floating video player 318const additionalPlayerDelay = window.revoltAddDelay || 0; 319const totalPlayerDelay = window.location.pathname.includes('/shorts') ? 100 : PLAYER_DELAY + additionalPlayerDelay; 320setTimeout(() => { 321 embedReelsRevoltPlayer(); 322 const headerWrapper = document.querySelector('.header-wrapper'); 323 const playerContainer = document.querySelector('.reels-revolt-player-container'); 324 325 if ((window.location.pathname.includes('/clips') || window.location.pathname.includes('/reels') || window.location.pathname.includes('/shorts')) && !params.has('v5')) { 326 playerContainer.style.top = 'unset'; 327 } else if ((window.location.pathname.includes('/clips') || window.location.pathname.includes('/reels') || window.location.pathname.includes('/shorts')) && params.has('v5')) { 328 playerContainer.style.top = '100%'; 329 } 330 // Move playerContainer right after headerWrapper 331 headerWrapper.appendChild(playerContainer); 332 333 if (playerContainer) { 334 const headerTitle = document.querySelector('.article-head-wrapper'); 335 headerTitle.style.marginTop = '100px'; 336 } 337}, totalPlayerDelay);
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.