1/** 2 * rnc-player-core.js â logique commune aux players RNC (live + VOD). 3 * Moteur maison remplaçant RMP : chargement Google IMA (pub VAST/GAM), 4 * events GA, debug, et exécution d'un pré-roll réutilisable. 5 * 6 * Config par site injectée par le plugin PHP dans window.RNC_PLAYER_CONFIG : 7 * adTag URL du tag VAST (GAM) du site â '' = lecture sans pub. 8 * Le littéral __timestamp__ y est remplacé par Date.now() à chaque requête. 9 * debug true = logs console préfixés [rnc-player]. 10 * live config du module live-audio (voir rnc-player-live-audio.js). 11 * 12 * Exposé en global : window.RNCPlayer 13 */ 14window.RNCPlayer = (function () { 15 'use strict'; 16 17 var CFG = window.RNC_PLAYER_CONFIG || {}; 18 // Logs : config du site, OU interrupteur local pour diagnostiquer en prod 19 // sans redéploiement : localStorage.setItem('rnc-debug','1') dans la console. 20 var DEBUG = !!CFG.debug; 21 try { if (window.localStorage && localStorage.getItem('rnc-debug') === '1') { DEBUG = true; } } catch (_) {} 22 23 function log() { 24 if (DEBUG && window.console && console.log) { 25 console.log.apply(console, ['[rnc-player]'].concat([].slice.call(arguments))); 26 } 27 } 28 29 var IMA_SDK = 'https://imasdk.googleapis.com/js/sdkloader/ima3.js'; 30 var AD_TAG = CFG.adTag || ''; 31 32 function adTag() { 33 return AD_TAG.replace('__timestamp__', Date.now()); 34 } 35 36 function ga(event, title) { 37 log('GA â', event, title || ''); 38 try { 39 if (typeof window.gtag === 'function') { 40 window.gtag('event', event, { video_title: title || '' }); 41 } else if (window.dataLayer && window.dataLayer.push) { 42 window.dataLayer.push({ event: 'rnc_player', player_event: event, video_title: title || '' }); 43 } 44 } catch (e) { /* GA absent */ } 45 } 46 47 // --- Google IMA (chargé une fois, tôt, pour être prêt au clic) --- 48 var imaOk = false, imaTried = false; 49 function loadIMA() { 50 if (!AD_TAG) { log('IMA: aucun tag configuré â pas de pub'); return; } 51 if (window.google && window.google.ima) { imaOk = true; return; } 52 if (imaTried) { return; } 53 imaTried = true; 54 log('IMA: chargement du SDKâ¦'); 55 var s = document.createElement('script'); 56 s.src = IMA_SDK; s.async = true; 57 s.onload = function () { 58 imaOk = !!(window.google && window.google.ima); 59 log('IMA:', imaOk ? ('chargé â version ' + google.ima.VERSION) : 'google.ima absent'); 60 }; 61 s.onerror = function () { imaOk = false; log('IMA: BLOQUà (adblock/DNS/navigateur) â lecture sans pub'); }; 62 document.head.appendChild(s); 63 } 64 function imaReady() { return imaOk; } 65 66 // --- HLS (.m3u8 â live TV Cloudflare Stream, VOD Stream éventuel) --- 67 // hls.js est vendu avec le plugin et chargé PARESSEUSEMENT : seulement au 68 // premier attachSource() d'une URL .m3u8 sur un navigateur sans HLS natif. 69 // CFG.hlsJs = URL du fichier (injectée par le plugin PHP). 70 var hlsLoading = null; 71 function loadHls() { 72 if (window.Hls) { return Promise.resolve(window.Hls); } 73 if (hlsLoading) { return hlsLoading; } 74 hlsLoading = new Promise(function (resolve, reject) { 75 if (!CFG.hlsJs) { reject(new Error('hlsJs non configuré')); return; } 76 log('hls: chargement de hls.jsâ¦'); 77 var s = document.createElement('script'); 78 s.src = CFG.hlsJs; s.async = true; 79 s.onload = function () { window.Hls ? resolve(window.Hls) : reject(new Error('Hls absent')); }; 80 s.onerror = function () { reject(new Error('hls.js introuvable')); }; 81 document.head.appendChild(s); 82 }); 83 return hlsLoading; 84 } 85 86 function isHlsUrl(url) { return /\.m3u8($|\?)/.test(url || ''); } 87 88 /** 89 * Branche une source sur un média. URL .m3u8 â HLS natif (Safari/iOS) ou 90 * hls.js ; sinon src direct. onReady est appelé quand la source est prête à 91 * être jouée (manifest analysé pour hls.js). Retourne { destroy } pour 92 * libérer l'instance hls.js (arrête le buffering du live). 93 */ 94 function attachSource(media, url, onReady) { 95 if (!isHlsUrl(url)) { 96 media.src = url; 97 if (onReady) { onReady(); } 98 return { destroy: function () { media.removeAttribute('src'); media.load(); } }; 99 } 100 if (media.canPlayType('application/vnd.apple.mpegurl')) { 101 log('hls: natif (Safari)'); 102 media.src = url; 103 if (onReady) { onReady(); } 104 return { destroy: function () { media.removeAttribute('src'); media.load(); } }; 105 } 106 var hls = null, dead = false;
107 loadHls().then(function (Hls) { 108 if (dead) { return; } 109 if (!Hls.isSupported()) { log('hls: MSE indisponible â src direct (échec probable)'); media.src = url; if (onReady) { onReady(); } return; } 110 hls = new Hls(); 111 hls.on(Hls.Events.MANIFEST_PARSED, function () { log('hls: manifest analysé'); if (onReady) { onReady(); } }); 112 hls.on(Hls.Events.ERROR, function (_e, data) { 113 if (!data.fatal) { return; } 114 log('hls: erreur fatale', data.type, 'â', data.details); 115 if (data.type === Hls.ErrorTypes.NETWORK_ERROR) { hls.startLoad(); } 116 else if (data.type === Hls.ErrorTypes.MEDIA_ERROR) { hls.recoverMediaError(); } 117 else { hls.destroy(); } 118 }); 119 hls.loadSource(url); 120 hls.attachMedia(media); 121 }, function (e) { 122 log('hls:', e && e.message, 'â src direct (échec probable)'); 123 media.src = url; 124 if (onReady) { onReady(); } 125 }); 126 return { 127 destroy: function () { 128 dead = true; 129 if (hls) { try { hls.destroy(); } catch (_) {} hls = null; } 130 } 131 }; 132 } 133 134 // --- Exclusivité de lecture : UN SEUL lecteur actif à la fois sur la page --- 135 // (ex. page d'extrait : le live du header et le lecteur d'extrait cohabitent). 136 // Chaque lecteur s'enregistre avec sa fonction de mise au repos (pause du média 137 // + annulation d'une éventuelle pub en cours), et appelle handle.claim() au 138 // moment où l'utilisateur le démarre â tous les autres se mettent au repos. 139 var players = []; 140 function registerPlayer(standDown) { 141 var h = { 142 standDown: standDown, 143 claim: function () { 144 for (var i = 0; i < players.length; i++) { 145 if (players[i] !== h) { try { players[i].standDown(); } catch (_) {} } 146 } 147 } 148 }; 149 players.push(h); 150 return h; 151 } 152 153 /** 154 * Joue un pré-roll VAST puis rend la main au contenu. 155 * DOIT être appelé dans un geste utilisateur (clic) pour IMA/autoplay mobile. 156 * @param {Object} o 157 * o.container élément conteneur d'annonce (div) 158 * o.content élément média repris après la pub (<audio>/<video>) 159 * o.gaTitle titre pour les events GA 160 * o.sizeEl élément mesuré pour dimensionner la pub (ex. la scène 16:9) ; 161 * suivi sur resize. Défaut : 640Ã360. 162 * o.onPause appelé quand la pub prend la main (mettre le contenu en pause) 163 * o.onAdStarted appelé au démarrage de chaque annonce avec l'objet Ad IMA 164 * (permet de distinguer pub vidéo / audio et d'ouvrir la scène) 165 * o.onResume appelé pour (re)démarrer le contenu (fin de pub OU pas de pub/erreur) 166 * @returns {Object} { stop, resize, cancel } pour piloter la pub en cours (ou null). 167 * cancel() = abandon SILENCIEUX : détruit la pub sans déclencher onResume 168 * (â ï¸ adsManager.stop() déclencherait CONTENT_RESUME â redémarrage du contenu). 169 */ 170 function runPreroll(o) { 171 if (!imaOk) { log('pub: IMA indisponible â contenu direct'); o.onResume(); return null; } 172 var adsManager = null, cancelled = false; 173 function adSize() { 174 var w = 0, h = 0; 175 if (o.sizeEl) { 176 w = o.sizeEl.clientWidth || (o.sizeEl.parentNode ? o.sizeEl.parentNode.clientWidth : 0); 177 h = o.sizeEl.clientHeight; // hauteur réelle de la scène (son ratio suit le poster) 178 } 179 if (!w) { w = 640; } 180 if (!h) { h = Math.round(w * 9 / 16); } 181 return { w: w, h: h }; 182 } 183 function resize() { 184 if (!adsManager) { return; } 185 var s = adSize(); 186 try { adsManager.resize(s.w, s.h, google.ima.ViewMode.NORMAL); } catch (_) {} 187 } 188 function unbind() { window.removeEventListener('resize', resize); } 189 window.addEventListener('resize', resize); 190 try { 191 var adDisplayContainer = new google.ima.AdDisplayContainer(o.container, o.content); 192 adDisplayContainer.initialize(); // dans le geste utilisateur 193 var adsLoader = new google.ima.AdsLoader(adDisplayContainer); 194 adsLoader.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onErr, false); 195 adsLoader.addEventListener(google.ima.AdsManagerLoadedEvent.Type.ADS_MANAGER_LOADED, onLoaded, false); 196 var req = new google.ima.AdsRequest(); 197 req.adTagUrl = adTag(); 198 var s0 = adSize(); 199 req.linearAdSlotWidth = s0.w; req.linearAdSlotHeight = s0.h; 200 req.nonLinearAdSlotWidth = s0.w; req.nonLinearAdSlotHeight = 90; 201 log('pub: requête'); 202 adsLoader.requestAds(req); 203 } catch (e) { 204 log('pub: exception â contenu direct', e && e.message); 205 unbind(); 206 o.onResume(); 207 return null; 208 } 209 210 function onLoaded(e) { 211 if (cancelled) { log('pub: annulée avant chargement'); return; } 212 adsManager = e.getAdsManager(o.content); 213 adsManager.addEventListener(google.ima.AdErrorEvent.Type.AD_ERROR, onErr); 214 adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_PAUSE_REQUESTED, function () { if (cancelled) { return; } log('pub: pause contenu'); if (o.onPause) o.onPause(); }); 215 adsManager.addEventListener(google.ima.AdEvent.Type.CONTENT_RESUME_REQUESTED, function () { if (cancelled) { return; } log('pub: reprise contenu'); unbind(); o.onResume(); }); 216 adsManager.addEventListener(google.ima.AdEvent.Type.LOADED, function () { log('pub: chargée'); }); 217 adsManager.addEventListener(google.ima.AdEvent.Type.STARTED, function (e2) { 218 log('pub: démarrée'); ga('adimpression', o.gaTitle); 219 if (o.onAdStarted) { try { o.onAdStarted(e2.getAd()); } catch (_) {} } 220 resize(); // la scène a pu s'ouvrir/changer de taille au signalement de la pub 221 }); 222 adsManager.addEventListener(google.ima.AdEvent.Type.CLICK, function () { ga('adclick', o.gaTitle); }); 223 adsManager.addEventListener(google.ima.AdEvent.Type.ALL_ADS_COMPLETED, function () { log('pub: toutes complétées'); if (o.onAllDone) o.onAllDone(); });
224 try { 225 var s = adSize(); 226 adsManager.init(s.w, s.h, google.ima.ViewMode.NORMAL); 227 adsManager.start(); 228 } catch (err) { log('pub: start échec â', err && err.message); unbind(); o.onResume(); } 229 } 230 function onErr(e) { 231 if (cancelled) { return; } 232 log('pub: erreur â', (e && e.getError) ? e.getError().toString() : e); 233 ga('adloaderror', o.gaTitle); 234 if (adsManager) { try { adsManager.destroy(); } catch (_) {} } 235 unbind(); 236 o.onResume(); 237 } 238 239 return { 240 stop: function () { if (adsManager) { try { adsManager.stop(); } catch (_) {} } }, 241 resize: resize, 242 cancel: function () { 243 cancelled = true; 244 unbind(); 245 if (adsManager) { try { adsManager.destroy(); } catch (_) {} } 246 } 247 }; 248 } 249 250 loadIMA(); // eager 251 252 return { 253 CONFIG: CFG, 254 DEBUG: DEBUG, 255 log: log, 256 ga: ga, 257 adTag: adTag, 258 loadIMA: loadIMA, 259 imaReady: imaReady, 260 isHlsUrl: isHlsUrl, 261 attachSource: attachSource, 262 registerPlayer: registerPlayer, 263 runPreroll: runPreroll 264 }; 265})();
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.