1/** 2 * JW8 Chromecast control for JUMP players (watch, pagebuilder, livefeed). 3 * Native google-cast-launcher stays empty until a device is found; JW skins 4 * often force-show .jw-icon-cast. Hide that slot and add exactly one JUMP button. 5 * 6 * Product: the JUMP icon is always shown in Chrome/Edge (HTTPS), even when 7 * no Chromecast receiver is discovered yet. Do not gate the button on Cast SDK. 8 */ 9(function (window, document) { 10 'use strict'; 11 12 if (window.jumpEnableJwChromecast) { 13 return; 14 } 15 16 window.JUMP_JW_CAST_CONFIG = {}; 17 18 var CAST_ICON = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent( 19 '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="#FFFFFF">' + 20 '<path d="M21 3H3c-1.1 0-2 .9-2 2v3h2V5h18v14h-7v2h7c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zM1 18v3h3c0-1.66-1.34-3-3-3zm0-4v2c2.76 0 5 2.24 5 5h2c0-3.87-3.13-7-7-7zm0-4v2c4.97 0 9 4.03 9 9h2c0-6.08-4.93-11-11-11z"/>' + 21 '</svg>' 22 ); 23 24 var enabledPlayers = typeof WeakSet === 'function' ? new WeakSet() : null; 25 26 function isChromecastBrowser() { 27 var ua = navigator.userAgent || ''; 28 if (/iPhone|iPad|iPod/.test(ua)) { 29 return false; 30 } 31 if (/OPR\/|Opera/.test(ua)) { 32 return false; 33 } 34 return /Chrome|Chromium|Edg\//.test(ua); 35 } 36 37 function canShowJumpCastButton() { 38 if (!isChromecastBrowser()) { 39 return false; 40 } 41 return window.isSecureContext !== false; 42 } 43 44 function loadCastSender() { 45 if (document.querySelector('script[src*="cast_sender.js"]')) { 46 return; 47 } 48 var script = document.createElement('script'); 49 script.src = 'https://www.gstatic.com/cv/js/sender/v1/cast_sender.js?loadCastFramework=1'; 50 document.head.appendChild(script); 51 } 52 53 function injectCss() { 54 if (window._jumpJwCastCss) { 55 return; 56 } 57 window._jumpJwCastCss = true; 58 var style = document.createElement('style'); 59 style.setAttribute('data-jump-jw-cast', '1'); 60 style.textContent = [ 61 /* Beat .watch-live-v2 #video-wrapper .jw-icon-cast { display: inline-flex !important } */ 62 '.watch-live-v2 #video-wrapper .jw-icon-cast,', 63 '#video-wrapper.jwplayer .jw-icon-cast,', 64 '.jwplayer .jw-icon-cast,', 65 '.jwplayer .jw-icon-cast google-cast-launcher {', 66 ' display: none !important;', 67 ' visibility: hidden !important;', 68 ' width: 0 !important;', 69 ' min-width: 0 !important;', 70 ' padding: 0 !important;', 71 ' margin: 0 !important;', 72 ' overflow: hidden !important;', 73 ' pointer-events: none !important;', 74 '}', 75 '.jwplayer .jw-icon-airplay {', 76 ' display: inline-flex !important;', 77 ' visibility: visible !important;', 78 ' opacity: 1 !important;', 79 '}', 80 '.jwplayer .jw-icon-airplay .jw-svg-icon { fill: #fff !important; color: #fff !important; }', 81 '.watch-live-v2 #video-wrapper .jw-icon-jump-chromecast,', 82 '.jwplayer .jw-icon-jump-chromecast,', 83 '.jwplayer [button="jump-chromecast"] {', 84 ' display: inline-flex !important;', 85 ' visibility: visible !important;', 86 ' opacity: 1 !important;', 87 ' width: 44px;', 88 ' height: 44px;', 89 ' align-items: center;', 90 ' justify-content: center;', 91 ' cursor: pointer;', 92 ' pointer-events: auto !important;', 93 '}', 94 '.jwplayer .jw-icon-jump-chromecast img,', 95 '.jwplayer .jw-icon-jump-chromecast .jw-svg-icon {', 96 ' width: 24px;', 97 ' height: 24px;', 98 ' fill: #fff !important;', 99 '}', 100 /* JW addButton paints .jw-button-image on top; it ate the click. */ 101 '.jwplayer .jw-icon-jump-chromecast,', 102 '.jwplayer [button="jump-chromecast"] {', 103 ' position: relative;', 104 ' z-index: 6;', 105 '}', 106 '.jwplayer .jw-icon-jump-chromecast *,', 107 '.jwplayer [button="jump-chromecast"] * {', 108 ' pointer-events: none !important;', 109 '}' 110 ].join('\n'); 111 document.head.appendChild(style); 112 } 113 114 function playerRoot(player) { 115 try { 116 if (player && typeof player.getContainer === 'function') { 117 return player.getContainer(); 118 } 119 } catch (e) { /* ignore */ } 120 return null; 121 } 122 123 function jumpCastNodes(player) { 124 var root = playerRoot(player); 125 if (!root) { 126 return []; 127 } 128 var list = root.querySelectorAll('.jw-icon-jump-chromecast, [button="jump-chromecast"]'); 129 var out = []; 130 var seen = []; 131 for (var i = 0; i < list.length; i++) { 132 if (seen.indexOf(list[i]) !== -1) { 133 continue; 134 } 135 seen.push(list[i]); 136 out.push(list[i]); 137 } 138 return out; 139 } 140 141 function hasJumpCastButton(player) { 142 return jumpCastNodes(player).length > 0; 143 } 144 145 function initCastContext() { 146 if (window._jumpCastContextReady) { 147 return true; 148 } 149 if (!window.cast || !cast.framework || !window.chrome || !chrome.cast) { 150 return false;
151 } 152 try { 153 cast.framework.CastContext.getInstance().setOptions({ 154 receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, 155 autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED 156 }); 157 window._jumpCastContextReady = true; 158 return true; 159 } catch (e) { 160 return false; 161 } 162 } 163 164 function openCastPicker(player) { 165 initCastContext(); 166 try { 167 if (window.cast && cast.framework) { 168 var ctx = cast.framework.CastContext.getInstance(); 169 if (ctx.getCurrentSession()) { 170 ctx.endCurrentSession(true); 171 return; 172 } 173 ctx.requestSession().catch(function () { /* user cancel */ }); 174 return; 175 } 176 } catch (e) { /* fall through */ } 177 var root = playerRoot(player) || document; 178 var launcher = root.querySelector('google-cast-launcher'); 179 if (launcher && typeof launcher.click === 'function') { 180 launcher.click(); 181 return; 182 } 183 try { 184 if (player && typeof player.castToggle === 'function') { 185 player.castToggle(); 186 } 187 } catch (e2) { /* ignore */ } 188 } 189 190 function bindCastClicks(player) { 191 var nodes = jumpCastNodes(player); 192 var i; 193 for (i = 0; i < nodes.length; i++) { 194 if (nodes[i]._jumpCastBound) { 195 continue; 196 } 197 nodes[i]._jumpCastBound = true; 198 nodes[i].style.cursor = 'pointer'; 199 nodes[i].addEventListener('click', function (ev) { 200 if (ev) { 201 ev.preventDefault(); 202 ev.stopPropagation(); 203 } 204 openCastPicker(player); 205 }, true); 206 nodes[i].addEventListener('keydown', function (ev) { 207 if (ev && (ev.key === 'Enter' || ev.key === ' ')) { 208 ev.preventDefault(); 209 openCastPicker(player); 210 } 211 }); 212 } 213 } 214 215 function tagAndDedupeJumpCast(player) { 216 var nodes = jumpCastNodes(player); 217 var i; 218 for (i = 0; i < nodes.length; i++) { 219 if ((' ' + nodes[i].className + ' ').indexOf(' jw-icon-jump-chromecast ') === -1) { 220 nodes[i].className += ' jw-icon-jump-chromecast'; 221 } 222 } 223 for (i = 1; i < nodes.length; i++) { 224 if (nodes[i].parentNode) { 225 nodes[i].parentNode.removeChild(nodes[i]); 226 } 227 } 228 return nodes.length > 0; 229 } 230 231 function insertDomCastButton(player) { 232 var root = playerRoot(player); 233 if (!root || hasJumpCastButton(player)) { 234 return tagAndDedupeJumpCast(player); 235 } 236 var bar = root.querySelector('.jw-button-container') 237 || root.querySelector('.jw-controlbar-right-group') 238 || root.querySelector('.jw-controlbar'); 239 if (!bar) { 240 return false; 241 } 242 var btn = document.createElement('div'); 243 btn.className = 'jw-icon jw-button-color jw-reset jw-icon-jump-chromecast'; 244 btn.setAttribute('role', 'button'); 245 btn.setAttribute('tabindex', '0'); 246 btn.setAttribute('aria-label', 'Chromecast'); 247 btn.setAttribute('button', 'jump-chromecast'); 248 btn.title = 'Chromecast'; 249 btn.innerHTML = '<img alt="" src="' + CAST_ICON + '">'; 250 var fullscreen = bar.querySelector('.jw-icon-fullscreen'); 251 if (fullscreen && fullscreen.parentNode === bar) { 252 bar.insertBefore(btn, fullscreen); 253 } else { 254 bar.appendChild(btn); 255 } 256 return true; 257 } 258 259 function addJumpCastButton(player, allowDomFallback) { 260 if (!player || !canShowJumpCastButton()) { 261 return false; 262 } 263 if (tagAndDedupeJumpCast(player)) { 264 bindCastClicks(player); 265 return true; 266 } 267 /* Call addButton once. Never insertDom on the same pass (two icons). */ 268 if (typeof player.addButton === 'function' && !player._jumpCastAddButtonTried) { 269 player._jumpCastAddButtonTried = true; 270 try { 271 player.addButton(CAST_ICON, 'Chromecast', function () { 272 openCastPicker(player); 273 }, 'jump-chromecast'); 274 } catch (e) { /* addButton can throw before controlbar exists */ } 275 if (tagAndDedupeJumpCast(player)) { 276 bindCastClicks(player); 277 return true; 278 } 279 return false;
280 } 281 if (allowDomFallback) { 282 if (insertDomCastButton(player)) { 283 bindCastClicks(player); 284 return true; 285 } 286 return false; 287 } 288 if (tagAndDedupeJumpCast(player)) { 289 bindCastClicks(player); 290 return true; 291 } 292 return false; 293 } 294 295 window.jumpEnableJwChromecast = function (player) { 296 loadCastSender(); 297 injectCss(); 298 var prevCastReady = window.__onGCastApiAvailable; 299 window.__onGCastApiAvailable = function (loaded, errorInfo) { 300 if (loaded) { 301 initCastContext(); 302 } 303 if (typeof prevCastReady === 'function') { 304 prevCastReady(loaded, errorInfo); 305 } 306 }; 307 308 if (!canShowJumpCastButton() || !player) { 309 return; 310 } 311 312 var already = enabledPlayers && enabledPlayers.has(player); 313 if (enabledPlayers) { 314 enabledPlayers.add(player); 315 } 316 317 var attempts = 0; 318 var tryAdd = function () { 319 if (addJumpCastButton(player, attempts >= 8)) { 320 return; 321 } 322 attempts += 1; 323 if (attempts < 40) { 324 setTimeout(tryAdd, 250); 325 } 326 }; 327 tryAdd(); 328 329 if (!already && typeof player.on === 'function') { 330 player.on('ready', function () { 331 addJumpCastButton(player, true); 332 }); 333 } 334 }; 335})(window, document);
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.