discovery-media-player 0.1.29 → 0.1.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/server/handler.js +87 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "discovery-media-player",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.31",
|
|
4
4
|
"description": "Self-hosted document viewer: per-recipient tracked links, reading analytics, live presentation. The core knows nothing about the application hosting it — everything it borrows arrives through an injected context.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pdf-viewer",
|
package/server/handler.js
CHANGED
|
@@ -241,11 +241,14 @@ var Live=(function(){
|
|
|
241
241
|
function toggleMute(){ MUTED=!MUTED; try{ localStorage.setItem('3dd-present-mute',MUTED?'1':'0'); }catch(e){} if(MUTED)hidePeek(); applyMute(); }
|
|
242
242
|
// Flèche « envoyer » : visible seulement si le champ contient du texte et qu'on peut poster.
|
|
243
243
|
function toggleSend(){ var s=document.getElementById('chatSend'),t=document.getElementById('chatText'); if(!s||!t)return; var can=!(LOCKED&&!canMod()); s.classList.toggle('on', can && (t.value||'').trim().length>0); }
|
|
244
|
-
|
|
244
|
+
// Un SIGNAL, plus une position : la charge utile était crue par l'audience sur un canal public.
|
|
245
|
+
function sendMap(){try{if(ch)ch.send({type:'broadcast',event:'map',payload:{}});}catch(e){}}
|
|
245
246
|
function onMap(fn){_onMap=fn;}
|
|
246
247
|
// État de la présentation diffusé par le présentateur — même canal que la carte. Sert à se
|
|
247
248
|
// passer de la lecture anonyme des tables : l'audience n'a plus besoin de lire la ligne.
|
|
248
|
-
|
|
249
|
+
// Un SIGNAL, pas un état. L'audience relit depuis 0.1.19 et ignore déjà cette charge ; la laisser
|
|
250
|
+
// partir donnait l'illusion qu'elle sert, et invitait le prochain à s'en resservir.
|
|
251
|
+
function sendState(){try{if(ch)ch.send({type:'broadcast',event:'state',payload:{}});}catch(e){}}
|
|
249
252
|
// CHAT EN DIFFUSION. Les messages arrivaient jusqu'ici par la lecture de TABLE en temps réel,
|
|
250
253
|
// qui exige que cette table soit lisible publiquement — donc, avec la clé publiable, les
|
|
251
254
|
// conversations de TOUTES les présentations, pas seulement la sienne. C'était le dernier
|
|
@@ -476,6 +479,10 @@ var Live=(function(){
|
|
|
476
479
|
},{minMs:400});
|
|
477
480
|
}
|
|
478
481
|
var _ordEtat=relireAvec('&state=1',function(d){if(d.state)etatDuServeur(d.state);});
|
|
482
|
+
// ⚠️ Exposé hors de cette fermeture : la carte vit dans un AUTRE bloc de script et doit
|
|
483
|
+
// pouvoir déclencher la relecture. Même raison que window.__presAppliquerEtat — un nom
|
|
484
|
+
// référencé depuis la mauvaise portée part dans un catch muet, et l'audience se fige.
|
|
485
|
+
window.__presRelireEtat=function(){_ordEtat.signaler();};
|
|
479
486
|
function relireEtat(){_ordEtat.signaler();}
|
|
480
487
|
function relireChat(){_ordChat.signaler();}
|
|
481
488
|
var _ordChat=relireAvec('&chat=1',function(d){
|
|
@@ -517,7 +524,10 @@ var Live=(function(){
|
|
|
517
524
|
ch.on('broadcast',{event:'lock'},function(){relireChat();});
|
|
518
525
|
ch.on('broadcast',{event:'state'},function(){relireEtat();});
|
|
519
526
|
ch.on('broadcast',{event:'typing'},function(p){onTyping(p&&p.payload);});
|
|
520
|
-
|
|
527
|
+
// ⚠️ AUCUNE CHARGE NE PASSE. Elle est ignorée plus loin, mais s'arrêter là serait une défense
|
|
528
|
+
// par accident : le jour où quelqu'un rebranche un paramètre, la charge d'un canal public
|
|
529
|
+
// redeviendrait crue sans que rien ne le signale. On coupe le chemin, pas seulement l'usage.
|
|
530
|
+
ch.on('broadcast',{event:'map'},function(){if(_onMap)_onMap();});
|
|
521
531
|
ch.subscribe(function(st){if(st==='SUBSCRIBED'){ch.track({name:me.name,email:me.email,avatar:me.avatar,role:me.role,member:!!me.member,uid:attKey(me)});sendAttend();}});
|
|
522
532
|
_tyIv=setInterval(renderTyping,1500);
|
|
523
533
|
_atIv=setInterval(sendAttend,25000);
|
|
@@ -592,8 +602,34 @@ var Map3DD=(function(){
|
|
|
592
602
|
function state(){ if(!map)return null; if(useG){ var c=map.getCenter(); return {kind:'map',center:[c.lat(),c.lng()],zoom:map.getZoom(),marker:marker?[marker.getPosition().lat(),marker.getPosition().lng()]:null,mapType:map.getMapTypeId()}; }
|
|
593
603
|
var c2=map.getCenter(); return {kind:'map',center:[c2.lat,c2.lng],zoom:map.getZoom(),marker:marker?[marker.getLatLng().lat,marker.getLatLng().lng]:null}; }
|
|
594
604
|
function setCenterZoom(ll,z){ if(useG){ map.setCenter({lat:ll[0],lng:ll[1]}); map.setZoom(z); } else { map.setView(ll,z); } }
|
|
595
|
-
|
|
596
|
-
|
|
605
|
+
// ⚠️ LA POSITION NE VOYAGE PLUS DANS LA DIFFUSION.
|
|
606
|
+
//
|
|
607
|
+
// Elle y voyageait, et l'audience l'appliquait telle quelle. Le canal étant public, n'importe quel
|
|
608
|
+
// participant déplaçait donc la carte de tout le monde, avec les coordonnées de son choix. C'était
|
|
609
|
+
// assumé en 0.1.19 au motif que le signal est « éphémère et sans vérité serveur » — l'argument ne
|
|
610
|
+
// tient pas : PENDANT UN MODE CARTE, CE SIGNAL EST L'IMAGE QUE VOIT L'AUDIENCE. 'typing' peut
|
|
611
|
+
// rester cosmétique, 'map' non. (audit P0-1)
|
|
612
|
+
//
|
|
613
|
+
// Le présentateur persiste sa position (route gatée par JWT), puis émet un signal VIDE. L'audience
|
|
614
|
+
// relit l'état et applique ce que le serveur lui donne. Un participant hostile peut toujours
|
|
615
|
+
// émettre le signal : il provoque une relecture, et n'obtient rien.
|
|
616
|
+
//
|
|
617
|
+
// ⚠️ ORDONNANCEUR ET NON DEBOUNCE, et c'est le cœur du problème. schedPersist repoussait
|
|
618
|
+
// l'écriture de 700 ms à chaque mouvement : pendant un déplacement CONTINU, elle ne partait
|
|
619
|
+
// jamais. C'est précisément pour ça que la position voyageait dans la diffusion. Un ordonnanceur
|
|
620
|
+
// écrit au plus une fois par 500 ms ET sert toujours la dernière position — donc l'audience suit
|
|
621
|
+
// pendant le mouvement, pas seulement à l'arrêt.
|
|
622
|
+
//
|
|
623
|
+
// Le suivi devient PAR PALIERS au lieu d'être continu (environ deux fois par seconde). C'est le
|
|
624
|
+
// prix pour que personne d'autre que le présentateur ne pilote l'écran de l'audience.
|
|
625
|
+
var _ordPersist=null;
|
|
626
|
+
function persistOrd(){ if(!_ordPersist&&window.Player&&Player.live&&Player.live.createScheduler){
|
|
627
|
+
_ordPersist=Player.live.createScheduler(function(fini){ try{ if(map&&persist)persist(state()); }catch(e){} fini(); },{minMs:500}); }
|
|
628
|
+
return _ordPersist; }
|
|
629
|
+
function broadcast(){ if(!map)return; var n=Date.now(); if(n-_bcT<200)return; _bcT=n;
|
|
630
|
+
var o=persistOrd(); if(o)o.signaler();
|
|
631
|
+
try{ if(window.Live) Live.sendMap(); }catch(e){} }
|
|
632
|
+
function schedPersist(){ var o=persistOrd(); if(o)o.signaler(); else { clearTimeout(_psT); _psT=setTimeout(function(){ if(map&&persist)persist(state()); },700); } }
|
|
597
633
|
function enter(content,presenter,persistFn){ isPres=!!presenter; if(persistFn)persist=persistFn;
|
|
598
634
|
if(content&&content.mapType)mapType=content.mapType;
|
|
599
635
|
var wrap=document.getElementById('mapWrap'); var on=wrap&&wrap.classList.contains('on');
|
|
@@ -632,7 +668,14 @@ var Map3DD=(function(){
|
|
|
632
668
|
function goSV(center){ if(!GMAPS_KEY){tempHint('Street View indisponible.');return;} tempHint('Recherche Street View…');
|
|
633
669
|
loadGoogle(function(){ try{ new google.maps.StreetViewService().getPanorama({location:{lat:center[0],lng:center[1]},radius:80},function(data,status){ if(status==='OK'&&data&&data.location){ var ll=data.location.latLng; var content={kind:'streetview',position:[ll.lat(),ll.lng()],pov:{heading:0,pitch:0},zoom:1}; if(persist)persist(content); enterSV(content,true,persist); } else { tempHint('Pas de Street View à cet endroit.'); } }); }catch(e){ tempHint('Street View indisponible.'); } }); }
|
|
634
670
|
function svState(){ if(!pano)return null; var p=pano.getPosition(),v=pano.getPov(); if(!p)return null; return {kind:'streetview',position:[p.lat(),p.lng()],pov:{heading:v.heading,pitch:v.pitch},zoom:pano.getZoom()}; }
|
|
635
|
-
|
|
671
|
+
// Même règle qu'au-dessus : on persiste, on signale, on ne transporte pas la position.
|
|
672
|
+
var _ordSv=null;
|
|
673
|
+
function svOrd(){ if(!_ordSv&&window.Player&&Player.live&&Player.live.createScheduler){
|
|
674
|
+
_ordSv=Player.live.createScheduler(function(fini){ try{ if(persist&&pano)persist(svState()); }catch(e){} fini(); },{minMs:500}); }
|
|
675
|
+
return _ordSv; }
|
|
676
|
+
function svBcast(){ var n=Date.now(); if(n-_svBcT<200)return; _svBcT=n; if(!svState())return;
|
|
677
|
+
var o=svOrd(); if(o)o.signaler();
|
|
678
|
+
try{ if(window.Live)Live.sendMap(); }catch(e){} }
|
|
636
679
|
function svApply(p){ if(!pano||!p)return; try{ if(p.position)pano.setPosition({lat:p.position[0],lng:p.position[1]}); if(p.pov)pano.setPov({heading:p.pov.heading||0,pitch:p.pov.pitch||0}); if(typeof p.zoom!=='undefined')pano.setZoom(p.zoom); }catch(e){} }
|
|
637
680
|
function ensurePano(content){ var el=document.getElementById('svPano'); if(!el||!window.google||!window.google.maps)return;
|
|
638
681
|
var pos=content&&content.position?{lat:content.position[0],lng:content.position[1]}:{lat:48.8584,lng:2.2945};
|
|
@@ -1934,9 +1977,36 @@ ${LEGAL_CSS}
|
|
|
1934
1977
|
function showBar(slug){ var lk=document.getElementById('pbarLink'); if(lk) lk.value=location.origin+'/present/'+slug; var pb=document.getElementById('pbar'); if(pb) pb.style.display='flex'; }
|
|
1935
1978
|
// Le présentateur DIFFUSE l'état qu'il vient de persister. La base reste la vérité (les
|
|
1936
1979
|
// arrivants tardifs la relisent) ; la diffusion évite à l'audience de lire la table.
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1980
|
+
// ⚠️ ÉCRIRE, PUIS SIGNALER — ET PAS L'INVERSE.
|
|
1981
|
+
//
|
|
1982
|
+
// Le signal partait AVANT l'écriture. L'audience relisait donc l'état pendant que la base
|
|
1983
|
+
// portait encore l'ancien, et aucun second signal n'était garanti : la page tournée se perdait
|
|
1984
|
+
// jusqu'au filet de resynchronisation. Le commentaire d'origine affirmait pourtant l'ordre
|
|
1985
|
+
// inverse — il décrivait l'intention, pas le code.
|
|
1986
|
+
//
|
|
1987
|
+
// Depuis 0.1.19 le signal ne sert qu'à dire « relis » : le retarder d'un aller-retour ne coûte
|
|
1988
|
+
// rien, alors que l'émettre trop tôt fait relire pour rien ET perdre le changement.
|
|
1989
|
+
// (audit P0-3)
|
|
1990
|
+
function diffuserEtat(){ if(!PRES||!window.Live)return; try{ Live.sendState(); }catch(e){} }
|
|
1991
|
+
function pushPage(){ if(!PRES)return; clearTimeout(_pushT); _pushT=setTimeout(function(){ if(!PRES)return;
|
|
1992
|
+
fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({action:'present-page',slug:PRES.slug,control:PRES.control,page:cur||1})})
|
|
1993
|
+
.then(function(r){ if(r&&r.ok)diffuserEtat(); })
|
|
1994
|
+
.catch(function(){});
|
|
1995
|
+
},250); }
|
|
1996
|
+
// ⚠️ TROIS GESTES, ET L'ORDRE ÉTAIT LE PIRE DES SIX. On signalait la fin, puis on COUPAIT LE
|
|
1997
|
+
// CANAL, puis on envoyait l'avis de fin. Le signal partait donc avant l'écriture (relecture sur
|
|
1998
|
+
// un état périmé), et la coupure avant l'envoi. L'audience pouvait ne jamais apprendre que la
|
|
1999
|
+
// présentation était terminée — jusqu'au filet, 25 s plus tard, ou jamais si elle s'était
|
|
2000
|
+
// rechargée entre-temps.
|
|
2001
|
+
//
|
|
2002
|
+
// sendBeacon ne s'attend pas, mais il rend la main une fois la requête MISE EN FILE : signaler
|
|
2003
|
+
// juste après, puis couper, respecte l'ordre autant que ce transport le permet.
|
|
2004
|
+
function endPresent(){ if(!PRES)return; var p=PRES; PRES=null; clearInterval(_hbIv); clearCtl(p.slug);
|
|
2005
|
+
var pb=document.getElementById('pbar'); if(pb)pb.style.display='none';
|
|
2006
|
+
try{ var b=JSON.stringify({action:'present-end',slug:p.slug,control:p.control});
|
|
2007
|
+
if(navigator.sendBeacon){navigator.sendBeacon('/api/doc',new Blob([b],{type:'application/json'}));}
|
|
2008
|
+
else {fetch('/api/doc',{method:'POST',headers:{'Content-Type':'application/json'},body:b,keepalive:true});} }catch(e){}
|
|
2009
|
+
try{ if(window.Live){ Live.sendState(); Live.disconnect(); } }catch(e){} }
|
|
1940
2010
|
// Transfert : je passe la main → je cesse de piloter SANS clôturer la présentation (le nouvel owner reprendra).
|
|
1941
2011
|
function stopPilotingLocally(){ if(!PRES)return; var s=PRES.slug; PRES=null; clearInterval(_hbIv); clearCtl(s); var pb=document.getElementById('pbar'); if(pb)pb.style.display='none'; try{ if(window.Live) Live.disconnect(); }catch(e){} }
|
|
1942
2012
|
function liveConnect(slug,control){ try{ if(window.Live){ var P=CFG.present||{}; Live.connect(slug,{name:P.by||'Présentateur',email:P.email||'',avatar:P.av||'',role:'presenter',member:true},control); } }catch(e){} }
|
|
@@ -1967,7 +2037,10 @@ ${LEGAL_CSS}
|
|
|
1967
2037
|
}).catch(function(){});
|
|
1968
2038
|
}
|
|
1969
2039
|
// Carte live : persiste le contenu (present-content, JWT) → l'audience bascule/suit via Realtime.
|
|
1970
|
-
function presentContent(content){ if(!PRES)return;
|
|
2040
|
+
function presentContent(content){ if(!PRES)return; var h={'Content-Type':'application/json'}; var tk=appToken(); if(tk) h['Authorization']='Bearer '+tk;
|
|
2041
|
+
fetch('/api/doc',{method:'POST',headers:h,body:JSON.stringify({action:'present-content',slug:PRES.slug,content:content})})
|
|
2042
|
+
.then(function(r){ if(r&&r.ok)diffuserEtat(); })
|
|
2043
|
+
.catch(function(){}); }
|
|
1971
2044
|
function showMap(){ if(!PRES||!window.Map3DD)return; var wrap=document.getElementById('mapWrap'); if(wrap&&wrap.classList.contains('on')){ Map3DD.enter(null,true,presentContent); return; } var init=Player.presentation.initialMapContent(); presentContent(init); Map3DD.enter(init,true,presentContent); }
|
|
1972
2045
|
function hideMap(){ if(!window.Map3DD)return; Map3DD.exit(); presentContent(null); }
|
|
1973
2046
|
// Mode INTÉGRÉ : la page hôte délègue sa barre de titre à celle-ci. On lui dit
|
|
@@ -2374,7 +2447,10 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
|
|
|
2374
2447
|
(function(){
|
|
2375
2448
|
var slug=${JSON.stringify(pres.slug)};
|
|
2376
2449
|
// Carte live : suivre en direct les mouvements du présentateur (broadcast).
|
|
2377
|
-
|
|
2450
|
+
// ⚠️ ON NE CROIT PLUS LA CHARGE. Le signal dit « quelque chose a bougé » ; ce qui a bougé vient
|
|
2451
|
+
// de la relecture d'état, gatée par l'écriture du présentateur. Un participant hostile peut
|
|
2452
|
+
// émettre : il déclenche une relecture bornée, et n'obtient rien.
|
|
2453
|
+
try{ if(window.Live&&window.Map3DD) Live.onMap(function(){ if(window.__presRelireEtat)window.__presRelireEtat(); }); }catch(e){}
|
|
2378
2454
|
// L'état arrive maintenant par DEUX voies : la table (historique) et la diffusion du
|
|
2379
2455
|
// présentateur (nouvelle). Les deux passent par le même filtre, qui ignore un état déjà
|
|
2380
2456
|
// appliqué — recevoir deux fois la même chose ne doit pas re-rendre la page.
|