discovery-media-player 0.1.117 → 0.1.118

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discovery-media-player",
3
- "version": "0.1.117",
3
+ "version": "0.1.118",
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",
@@ -94,6 +94,17 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
94
94
  (function(){
95
95
  var CFG=${cfg};
96
96
  var PDF=null, total=0, cur=CFG.page||1, ready=false;
97
+ // ⚠️ DEUX GÉNÉRATIONS, PARCE QU'IL Y A DEUX COURSES DISTINCTES (P1 audit externe).
98
+ //
99
+ // docGen : le présentateur change de document pendant qu'un chargement est en cours. Le
100
+ // callback de l'ANCIEN document arrivait après le nouveau et posait ses pages par-dessus.
101
+ // renderGen : deux getPage() en vol (changement de page rapide, redimensionnement) peuvent
102
+ // se résoudre DANS LE DÉSORDRE — pdf.js ne garantit aucun ordre entre deux pages. Le plus lent
103
+ // gagnait, et l'audience restait sur une page que personne n'avait demandée.
104
+ //
105
+ // Une génération par course : les confondre ferait qu'un simple changement de page invaliderait
106
+ // le document, ou l'inverse.
107
+ var docGen=0, renderGen=0, tacheRendu=null, chargement=null;
97
108
  var stage=document.getElementById('stage'), pageEl=document.getElementById('page');
98
109
  function hideLoader(){ var l=document.getElementById('load'); if(l){ l.classList.add('hide'); setTimeout(function(){ if(l.parentNode)l.parentNode.removeChild(l); },450);} }
99
110
  function ended(){ document.getElementById('ended').style.display='flex';
@@ -115,7 +126,14 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
115
126
  pgi.innerHTML=''; pgi.appendChild(el);
116
127
  return;
117
128
  }
129
+ // ⚠️ ON ANNULE LE RENDU PRÉCÉDENT AVANT D'EN LANCER UN AUTRE. Le RenderTask était jeté :
130
+ // deux rendus peignaient alors en parallèle, et le plus lent finissait par l'emporter.
131
+ if(tacheRendu){ try{ tacheRendu.cancel(); }catch(e){} tacheRendu=null; }
132
+ var monDoc=docGen, monRendu=++renderGen;
118
133
  PDF.getPage(n).then(function(page){
134
+ // Le document a changé, ou une page plus récente a été demandée pendant que celle-ci
135
+ // arrivait : ce résultat n'est plus celui qu'on attend. On ne le pose pas.
136
+ if(monDoc!==docGen || monRendu!==renderGen) return;
119
137
  var availW=Math.max(120, stage.clientWidth-40), availH=Math.max(120, stage.clientHeight-40);
120
138
  var v1=page.getViewport({scale:1});
121
139
  var scale=Math.min(availW/v1.width, availH/v1.height); // fit entier → une page à l'écran
@@ -123,9 +141,18 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
123
141
  var cv=document.createElement('canvas'); cv.width=Math.floor(vp.width*dpr); cv.height=Math.floor(vp.height*dpr);
124
142
  cv.setAttribute('role','img'); cv.setAttribute('aria-label','Page '+n+(total>1?' sur '+total:''));
125
143
  cv.style.width=vp.width+'px'; cv.style.height=vp.height+'px';
126
- page.render({canvasContext:cv.getContext('2d'),viewport:vp,transform:dpr!==1?[dpr,0,0,dpr,0,0]:null});
127
- pageEl.innerHTML=''; pageEl.appendChild(cv);
128
- });
144
+ var t=page.render({canvasContext:cv.getContext('2d'),viewport:vp,transform:dpr!==1?[dpr,0,0,dpr,0,0]:null});
145
+ tacheRendu=t;
146
+ // ⚠️ ON NE PUBLIE QU'APRÈS LE RENDU. L'ancien code posait le canvas AVANT que pdf.js n'ait
147
+ // peint : la page apparaissait blanche puis se remplissait, et un rendu périmé pouvait
148
+ // encore écraser le bon. On revérifie la génération APRÈS l'attente — c'est là qu'elle a pu
149
+ // changer, pas avant.
150
+ return t.promise.then(function(){
151
+ if(monDoc!==docGen || monRendu!==renderGen) return;
152
+ if(tacheRendu===t) tacheRendu=null;
153
+ pageEl.innerHTML=''; pageEl.appendChild(cv);
154
+ });
155
+ }).catch(function(){ /* rendu annulé ou page illisible : le rendu courant fait foi */ });
129
156
  }
130
157
  function boot(){
131
158
  import(CFG.pdfjs).then(function(m){
@@ -170,35 +197,67 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
170
197
  // réponse n est jamais « celui que le banc sait voir » : on garde le champ qui fait foi, puis on
171
198
  // rend le banc capable de le distinguer — un cas où l URL ment et où seul le nom dit vrai.
172
199
  function estImage(){ return Player.viewer.isImageDocument(CFG.fileName, CFG.docUrl); }
173
- function chargerImage(){
200
+ function chargerImage(monDoc){
174
201
  var im=new Image();
175
202
  im.alt='Document présenté';
176
203
  im.onload=function(){
204
+ // Le présentateur a pu rebasculer pendant le téléchargement : cette image n'est plus celle
205
+ // du document courant.
206
+ if(monDoc!==docGen) return;
177
207
  total=1; ready=true; PDF={image:im};
178
208
  var tot=document.getElementById('tot'); if(tot)tot.textContent=1;
179
209
  show(1); hideLoader();
180
210
  if(!CFG.active) ended();
181
211
  };
182
- im.onerror=function(){ var l=document.getElementById('load'); if(l)l.querySelector('.lsub').textContent="Document indisponible."; };
212
+ im.onerror=function(){ if(monDoc!==docGen) return; var l=document.getElementById('load'); if(l)l.querySelector('.lsub').textContent="Document indisponible."; };
183
213
  im.src=CFG.fileUrl;
184
214
  }
215
+ // ⚠️ ON LIBÈRE L'ANCIEN DOCUMENT. Un PDFDocumentProxy retient ses pages, ses canvas et son
216
+ // worker : enchaîner les bascules sans détruire faisait croître la mémoire sans borne, et c'est
217
+ // le mode de panne d'une présentation longue — celle qui dure, pas celle qu'on teste.
218
+ function libererDocument(){
219
+ if(tacheRendu){ try{ tacheRendu.cancel(); }catch(e){} tacheRendu=null; }
220
+ if(chargement){ try{ chargement.destroy(); }catch(e){} chargement=null; }
221
+ if(PDF && !PDF.image && typeof PDF.destroy==='function'){ try{ PDF.destroy(); }catch(e){} }
222
+ PDF=null;
223
+ }
185
224
  function load(){
186
- if(estImage()){ chargerImage(); return; }
187
- pdfjsLib.getDocument({url:CFG.fileUrl,isEvalSupported:false}).promise.then(function(pdf){
225
+ var monDoc=docGen;
226
+ if(estImage()){ chargerImage(monDoc); return; }
227
+ var t=pdfjsLib.getDocument({url:CFG.fileUrl,isEvalSupported:false});
228
+ chargement=t;
229
+ t.promise.then(function(pdf){
230
+ // Une bascule est survenue pendant le chargement : ce document n'est plus attendu, et le
231
+ // garder fuirait. On le détruit plutôt que de simplement l'ignorer.
232
+ if(monDoc!==docGen){ try{ pdf.destroy(); }catch(e){} return; }
188
233
  PDF=pdf; total=pdf.numPages; ready=true;
189
234
  var tot=document.getElementById('tot'); if(tot)tot.textContent=total;
190
235
  show(cur); hideLoader();
191
236
  if(!CFG.active) ended();
192
- }).catch(function(){ var l=document.getElementById('load'); if(l)l.querySelector('.lsub').textContent="Document indisponible."; });
237
+ }).catch(function(){ if(monDoc!==docGen) return; var l=document.getElementById('load'); if(l)l.querySelector('.lsub').textContent="Document indisponible."; });
193
238
  }
194
239
  // Le présentateur a changé de document (même session) → recharger le PDF servi par le proxy (URL identique →
195
240
  // cache-buster obligatoire), remettre à la page 1, mettre à jour le titre.
241
+ // ⚠️ TOUT CE QUI DÉCRIT LE DOCUMENT CHANGE ENSEMBLE, fileName COMPRIS — c'était le P1.
242
+ //
243
+ // fileName n'était PAS mis à jour, alors que c'est lui qui fait foi pour décider image ou PDF
244
+ // (estImage, et le commentaire plus bas dit pourquoi). Après une bascule, le nom restait celui
245
+ // du document précédent : un PDF→PNG envoyait donc le nouveau PNG à pdf.js, et un PNG→PDF
246
+ // chargeait le nouveau PDF avec new Image(). Dans les deux cas : « Document indisponible ».
247
+ //
248
+ // Une mise à jour partielle d'un état qui se lit comme un TOUT est la forme du défaut : il n'y a
249
+ // pas de moment où CFG doit décrire un document par son URL et un autre par son nom.
196
250
  function switchDoc(row){
251
+ docGen++; // invalide tout callback du document précédent
252
+ libererDocument();
197
253
  var t=document.getElementById('ptitle'); if(t) t.textContent=row.doc_title||row.file_name||'Document';
198
254
  var pg=document.getElementById('page'); if(pg) pg.innerHTML='';
199
255
  cur=1; total=0; ready=false;
200
256
  var tot=document.getElementById('tot'); if(tot)tot.textContent='—';
201
257
  var cu=document.getElementById('cur'); if(cu)cu.textContent='1';
258
+ if(row.file_url) CFG.docUrl=row.file_url;
259
+ CFG.fileName=row.file_name||'';
260
+ CFG.title=row.doc_title||row.file_name||'Document';
202
261
  CFG.fileUrl='/api/doc?present='+encodeURIComponent(CFG.slug)+'&file=1&v='+encodeURIComponent(row.updated_at||String(row.current_page||1));
203
262
  load();
204
263
  }
@@ -229,7 +288,9 @@ function presentHtml(pres, nonce, logoUrl, supaUrl, supaKey) {
229
288
  if(a.kind==='ended'){ ended(); return; }
230
289
  if(a.kind==='show-map'){ if(window.Map3DD){ if(a.content.kind==='streetview') Map3DD.enterSV(a.content,false); else Map3DD.enter(a.content,false); } return; }
231
290
  if(a.kind==='leave-map'){ if(window.Map3DD) Map3DD.exit(); }
232
- if(a.kind==='switch-doc'){ CFG.docUrl=a.url; switchDoc(row); return; }
291
+ // switchDoc pose LUI-MÊME docUrl/fileName/titre : les poser ici, en partie, était
292
+ // exactement ce qui laissait fileName en arrière.
293
+ if(a.kind==='switch-doc'){ switchDoc(row); return; }
233
294
  if(a.kind==='show-page'){ show(a.page); }
234
295
  }
235
296
  }