186 lines
9.7 KiB
JavaScript
186 lines
9.7 KiB
JavaScript
/* DONA_YOUTUBE_V82: public YouTube tutorial viewer; no API key by default. */
|
||
(() => {
|
||
'use strict';
|
||
if (window.DonaYouTubeV82) return;
|
||
const ID = 'donaYoutubeV82';
|
||
const videoIdOK = id => typeof id === 'string' && /^[A-Za-z0-9_-]{11}$/.test(id);
|
||
const $ = id => document.getElementById(id);
|
||
const el = (tag, cls, value) => {
|
||
const n = document.createElement(tag);
|
||
if (cls) n.className = cls;
|
||
if (value !== undefined) n.textContent = String(value);
|
||
return n;
|
||
};
|
||
const button = (title, fn, cls='dy-btn') => {
|
||
const b = el('button', cls, title); b.type='button'; b.addEventListener('click',fn); return b;
|
||
};
|
||
let player=null, ready=false, active=false, items=[], chosen='', requestBusy=false;
|
||
let apiPromise=null, playerVersion=0;
|
||
const label = s => { if($('dyStatus')) $('dyStatus').textContent=s||''; };
|
||
const postState = state => fetch('/api/dona/youtube/player-state', {
|
||
method:'POST', credentials:'same-origin',
|
||
headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},
|
||
body:JSON.stringify({active:state})
|
||
}).catch(()=>{});
|
||
|
||
function initialize(){
|
||
const root = document.getElementById('alfredMainV1');
|
||
if(!root){setTimeout(initialize,200);return;}
|
||
const head = root.querySelector('.av-head-actions');
|
||
if(head){
|
||
const trigger=button('▶',()=>open(),'av-round dy-open-button');
|
||
trigger.title='Ouvrir YouTube';trigger.setAttribute('aria-label','Ouvrir le lecteur YouTube');
|
||
trigger.id='donaYouTubeOpen';head.prepend(trigger);
|
||
}
|
||
const overlay=el('div','dy-overlay');overlay.id=ID;overlay.hidden=true;
|
||
const panel=el('section','dy-panel');
|
||
panel.setAttribute('role','dialog');panel.setAttribute('aria-modal','true');panel.setAttribute('aria-label','Lecteur YouTube de Dona');
|
||
const top=el('header','dy-header');
|
||
const h=el('div');h.append(el('span','dy-eyebrow','DONA · YOUTUBE'),el('h1','','Tutoriels et vidéos'));
|
||
top.append(h,button('×',close,'dy-close'));panel.append(top);
|
||
const search=el('form','dy-search');search.id='dySearchForm';
|
||
const input=el('input','dy-input');input.id='dyQuery';input.type='search';input.maxLength=200;
|
||
input.placeholder='Chercher une vidéo YouTube…';input.setAttribute('aria-label','Rechercher une vidéo');input.required=true;
|
||
const submit=button('Rechercher',()=>{},'dy-btn dy-primary');submit.type='submit';submit.id='dySubmit';
|
||
search.append(input,submit);search.addEventListener('submit',e=>{e.preventDefault();searchFor(input.value);});panel.append(search);
|
||
const status=el('p','dy-status');status.id='dyStatus';status.setAttribute('role','status');status.setAttribute('aria-live','polite');panel.append(status);
|
||
const body=el('div','dy-body');
|
||
const viewer=el('div','dy-viewer');
|
||
const stage=el('div','dy-stage');stage.id='dyStage';
|
||
stage.append(el('div','dy-placeholder','Sélectionne un tutoriel pour afficher sa vidéo.'));
|
||
viewer.append(stage);
|
||
const controls=el('div','dy-controls');
|
||
controls.append(button('↶ 30 s',()=>control({action:'seek',seconds:-30})),
|
||
button('▶',()=>control({action:'play'})),button('Ⅱ',()=>control({action:'pause'})),
|
||
button('30 s ↷',()=>control({action:'seek',seconds:30})),
|
||
button('⛶',()=>control({action:'fullscreen'})));
|
||
viewer.append(controls);
|
||
const link=el('a','dy-external','Voir sur YouTube ↗');link.id='dyExternal';link.hidden=true;link.target='_blank';link.rel='noopener noreferrer';
|
||
viewer.append(link);
|
||
const results=el('div','dy-results');
|
||
results.append(el('h2','','Vidéos trouvées'));
|
||
const cards=el('div','dy-results-list');cards.id='dyResults';results.append(cards);
|
||
body.append(viewer,results);panel.append(body);
|
||
panel.append(el('p','dy-note','Vidéos publiques YouTube · Lecture depuis le site officiel · Sans connexion à un compte. Certaines vidéos peuvent refuser l’intégration.'));
|
||
overlay.append(panel);document.body.append(overlay);
|
||
overlay.addEventListener('click',e=>{if(e.target===overlay)close();});
|
||
window.addEventListener('keydown',e=>{if(e.key==='Escape'&&active)close();});
|
||
bindSocket();
|
||
}
|
||
|
||
function open(){active=true;$(ID).hidden=false;document.body.classList.add('dy-active');postState(true);}
|
||
function close(){
|
||
active=false;$(ID).hidden=true;document.body.classList.remove('dy-active');postState(false);
|
||
// Stop playback when leaving YouTube; avoid hidden audio.
|
||
try { if(player&&typeof player.stopVideo==='function')player.stopVideo(); } catch(_){}
|
||
}
|
||
function loadAPI(){
|
||
if(window.YT && typeof window.YT.Player==='function')return Promise.resolve();
|
||
if(apiPromise)return apiPromise;
|
||
apiPromise=new Promise((resolve,reject)=>{
|
||
const previous=window.onYouTubeIframeAPIReady;
|
||
window.onYouTubeIframeAPIReady=()=>{try{if(typeof previous==='function')previous();}catch(_){ } resolve();};
|
||
const script=document.createElement('script');script.src='https://www.youtube.com/iframe_api';
|
||
script.async=true;script.onerror=()=>reject(new Error('Lecteur YouTube inaccessible.'));
|
||
document.head.append(script);
|
||
setTimeout(()=>{if(!(window.YT&&window.YT.Player))reject(new Error('Le lecteur YouTube ne répond pas.'));},15000);
|
||
});
|
||
return apiPromise;
|
||
}
|
||
async function loadVideo(id){
|
||
if(!videoIdOK(id))return;
|
||
chosen=id;open();
|
||
const item=items.find(x=>x.id===id);
|
||
$('dyExternal').href='https://www.youtube.com/watch?v='+id;
|
||
$('dyExternal').hidden=false;
|
||
$('dyStage').replaceChildren();
|
||
const mount=el('div','dy-frame');mount.id='dyPlayer';$('dyStage').append(mount);
|
||
const version=++playerVersion;
|
||
ready=false;
|
||
try { if(player&&typeof player.destroy==='function')player.destroy(); }catch(_){ }
|
||
player=null;
|
||
label(item ? 'Vidéo sélectionnée : '+item.title : 'Chargement de la vidéo…');
|
||
try {
|
||
await loadAPI();if(version!==playerVersion)return;
|
||
player=new window.YT.Player('dyPlayer', {
|
||
width:'100%', height:'100%', videoId:id,
|
||
playerVars:{autoplay:0,controls:1,playsinline:1,rel:0,origin:window.location.origin},
|
||
events:{
|
||
onReady:()=>{ready=true;label('Appuie sur Lecture pour démarrer la vidéo.');},
|
||
onError:(event)=>label('Cette vidéo ne peut pas être lue ici (code '+event.data+'). Utilise « Voir sur YouTube ».')
|
||
}
|
||
});
|
||
} catch(err) {
|
||
if(version===playerVersion){
|
||
$('dyStage').replaceChildren(el('p','dy-placeholder','Le lecteur intégré est indisponible. Ouvre cette vidéo sur YouTube.'));
|
||
label(err.message||'Lecteur indisponible.');
|
||
}
|
||
}
|
||
}
|
||
function control(command){
|
||
if(!active||!command||!command.action)return;
|
||
if(command.action==='close'){close();return;}
|
||
if(!ready||!player){label('Le lecteur n’est pas encore prêt.');return;}
|
||
try {
|
||
switch(command.action){
|
||
case 'play':player.playVideo();break;
|
||
case 'pause':player.pauseVideo();break;
|
||
case 'seek':{
|
||
const offset=Number(command.seconds)||0;
|
||
player.seekTo(Math.max(0,(Number(player.getCurrentTime())||0)+offset),true);break;
|
||
}
|
||
case 'fullscreen':{
|
||
const frame=$('dyStage').querySelector('iframe');
|
||
if(frame&&frame.requestFullscreen)frame.requestFullscreen();break;
|
||
}
|
||
}
|
||
} catch(e){label('Commande vidéo indisponible : '+e.message);}
|
||
}
|
||
function showResults(payload){
|
||
if(!payload||payload.type!=='dona_youtube')return;
|
||
items=(Array.isArray(payload.videos)?payload.videos:[]).filter(v=>v&&videoIdOK(v.id)).slice(0,6);
|
||
$('dyQuery').value=String(payload.query||'').slice(0,200);
|
||
const list=$('dyResults');list.replaceChildren();
|
||
for(const v of items){
|
||
const b=button('',()=>loadVideo(v.id),'dy-video-item');b.textContent='';
|
||
const img=el('img');img.loading='lazy';img.alt='Miniature : '+String(v.title||'Vidéo');
|
||
img.src='https://i.ytimg.com/vi/'+v.id+'/hqdefault.jpg';
|
||
const txt=el('span','dy-video-title');txt.append(el('strong','',v.title||'Vidéo YouTube'));
|
||
if(v.channel)txt.append(el('small','',v.channel));b.append(img,txt);list.append(b);
|
||
}
|
||
open();
|
||
if(payload.error)label(payload.error);
|
||
else if(items.length)label(items.length+' vidéo(s) trouvée(s). Choisis celle qui t’intéresse.');
|
||
else label('Aucune vidéo exploitable trouvée. Essaie une autre formulation.');
|
||
if(items.length)loadVideo(items[0].id);
|
||
else {chosen='';ready=false;playerVersion++;try{player?.destroy?.()}catch(_){ }player=null;
|
||
$('dyStage').replaceChildren(el('p','dy-placeholder','Aucun résultat : tente une autre recherche.'));
|
||
$('dyExternal').hidden=true;
|
||
}
|
||
}
|
||
async function searchFor(query){
|
||
query=String(query||'').trim();
|
||
if(query.length<2||query.length>200){label('Saisis entre 2 et 200 caractères.');return;}
|
||
if(requestBusy)return;
|
||
requestBusy=true;$('dySubmit').disabled=true;open();label('Dona recherche des vidéos via Hermes…');
|
||
try {
|
||
const res=await fetch('/api/dona/youtube/search',{
|
||
method:'POST',credentials:'same-origin',headers:{'Content-Type':'application/json','X-Alfred-Request':'1'},
|
||
body:JSON.stringify({query})
|
||
});
|
||
const data=await res.json();
|
||
if(!res.ok||!data.ok)throw new Error(data.error||'Recherche impossible');
|
||
showResults({type:'dona_youtube',query:data.query,videos:data.videos});
|
||
}catch(e){label(e.message||'Recherche indisponible.');}
|
||
finally{requestBusy=false;$('dySubmit').disabled=false;}
|
||
}
|
||
function bindSocket(){
|
||
const socket=window.alfredSocket;
|
||
if(!socket){setTimeout(bindSocket,250);return;}
|
||
socket.on('alfred_render',payload=>{if(payload?.type==='dona_youtube')showResults(payload);});
|
||
socket.on('dona_youtube_control',command=>control(command));
|
||
}
|
||
window.DonaYouTubeV82={open,close,control,showResults,search:searchFor};
|
||
initialize();
|
||
})();
|