UNPKG

lc-youtube-player

Version:
1 lines 15.3 kB
window.__ytApiReadyPromise=new Promise(t=>{if(window.YT&&window.YT.Player)return t();const e=document.createElement("script");e.src="https://www.youtube.com/iframe_api",document.head.appendChild(e),window.onYouTubeIframeAPIReady=()=>t()});class LCYouTube extends HTMLElement{static get observedAttributes(){return["video","playlist","index","autoplay"]}constructor(){super(),this._video="",this._playlist="",this._index=0,this._autoplay=!1,this._userInteracted=!1,this._player=null,this._duration=0,this._timer=null,this._lastTap=0,this.attachShadow({mode:"open"}),this.shadowRoot.innerHTML='\n <style>\n :host{display:block}\n .yt-wrap{position:relative;max-width:1920px;margin:auto;background:#000;aspect-ratio:16/9;border-radius:5px}\n .overlay,.controls,#player,.live-badge{border-radius:inherit}\n iframe{border-radius:inherit}\n iframe{position:absolute;inset:0;width:100%;height:100%;border:0}\n .overlay{position:absolute;inset:0;display:flex;align-items:center;justify-content:center;background:linear-gradient(to bottom, rgba(0,0,0,.35), rgba(0,0,0,.65));cursor:pointer;z-index:3}\n .overlay.playing{ background: transparent; }\n .overlay.playing .play{ display: none; }\n .overlay .play{width:84px;height:84px;border-radius:50%;background:#fff;display:grid;place-items:center;box-shadow:0 8px 30px rgba(0,0,0,.4)}\n .overlay .play:after{content:"";display:block;width:0;height:0;border-left:28px solid #000;border-top:18px solid transparent;border-bottom:18px solid transparent;margin-left:6px}\n .controls{position:absolute;left:0;right:0;bottom:0;padding:10px;display:flex;gap:10px;align-items:center;background:linear-gradient(to top, rgba(0,0,0,.55), rgba(0,0,0,0));z-index:4;user-select:none;opacity:0;pointer-events:none;transition: opacity .2s ease}\n .yt-wrap:hover .controls,.yt-wrap.show-controls .controls{opacity:1;pointer-events:auto}\n .btn,.time,.vol,.fs{color:#fff;font:500 14px/1 system-ui, -apple-system, Segoe UI, Roboto, sans-serif}\n .btn{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:10px;background:rgba(255,255,255,.12);cursor:pointer}\n .btn:hover{background:rgba(255,255,255,.2)}\n .progress{position:relative;flex:1;height:6px;background:rgba(255,255,255,.25);border-radius:999px;cursor:pointer}\n .progress .bar{position:absolute;left:0;top:0;height:100%;width:0;background:#fff;border-radius:999px}\n .progress .seek{position:absolute;left:0;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;border-radius:50%;background:#fff}\n .time{min-width:110px;text-align:center;opacity:.9}\n .vol{display:flex;align-items:center;gap:6px;background:rgba(255,255,255,.12);padding:6px 10px;border-radius:10px}\n .vol input{accent-color:#fff}\n .fs{cursor:pointer;background:rgba(255,255,255,.12);padding:6px 10px;border-radius:10px}\n .live-badge{position:absolute;top:10px;left:10px;display:none;align-items:center;gap:8px;padding:6px 10px;border-radius:999px;background:rgba(255,0,0,.85);color:#fff;font:600 12px/1 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;letter-spacing:.3px;z-index:5;pointer-events:none}\n .live-badge .dot{width:8px;height:8px;border-radius:50%;background:#fff;box-shadow:0 0 0 0 rgba(255,255,255,0.9);animation: pulse 1.2s ease-out infinite}\n .sound-hint{position:absolute;top:15px;right:15px;background:rgba(0,0,0,.65);color:#fff;padding:6px 12px;border-radius:8px;font:500 13px/1 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;cursor:pointer;z-index:6;user-select:none}\n .sound-hint.hide{display:none}\n .error-mask{position:absolute;inset:0;display:none;align-items:center;justify-content:center;background:#000;color:#fff;font:600 16px/1.4 system-ui,-apple-system,Segoe UI,Roboto,sans-serif;z-index:7;text-align:center;padding:20px;border-radius:inherit}\n .error-mask.show{display:flex}\n @keyframes pulse{0%{box-shadow:0 0 0 0 rgba(255,255,255,0.9)}70%{box-shadow:0 0 0 10px rgba(255,255,255,0)}100%{box-shadow:0 0 0 0 rgba(255,255,255,0)}}\n .root{ -webkit-touch-callout:none; -webkit-user-select:none; -moz-user-select:none; user-select:none }\n </style>\n <div class="root">\n <div class="yt-wrap" id="wrap">\n <div class="overlay" id="overlay" aria-label="Reproducir/Pausar" role="button" tabindex="0">\n <div class="play" title="Reproducir"></div>\n </div>\n <div id="player"></div>\n <div class="error-mask" id="errorMask">Video no disponible</div>\n <div class="live-badge" id="live"><span class="dot"></span> EN VIVO</div>\n <div class="sound-hint hide" id="soundHint">🔊 Toca para activar sonido</div>\n <div class="controls" id="controls">\n <div class="btn" id="prev" title="Anterior"></div>\n <div class="btn" id="playPause" title="Reproducir/Pausar">▶︎</div>\n <div class="btn" id="next" title="Siguiente"></div>\n <div class="progress" id="progress" title="Buscar">\n <div class="bar" id="bar"></div>\n <div class="seek" id="seek"></div>\n </div>\n <div class="time" id="time">00:00 / 00:00</div>\n <div class="vol">🔊 <input type="range" id="volume" min="0" max="100" value="80" /></div>\n <div class="fs btn" id="fs" title="Pantalla completa"></div>\n </div>\n </div>\n </div>\n '}_parseBool(t){if(null==t)return!1;const e=String(t).toLowerCase().trim();return"1"===e||"true"===e||"yes"===e||""===e}_parseListId(t){if(!t)return"";try{if(/^https?:\/\//i.test(t)){const e=new URL(t,window.location.href).searchParams.get("list");if(e)return e}}catch(t){}return/^(PL|UU|OL|RD)[A-Za-z0-9_-]+$/.test(t)?t:""}connectedCallback(){this._video=this.getAttribute("video")||"",this._playlist=this._parseListId(this.getAttribute("playlist")||"");let t=parseInt(this.getAttribute("index")||"0",10);-1===t?(this._startAtLast=!0,this._index=0):(this._startAtLast=!1,this._index=t>0?t-1:0),this._autoplay=this._parseBool(this.getAttribute("autoplay"))||this.hasAttribute("autoplay"),this._cacheEls(),this._bindUI(),this._mountPlayer()}disconnectedCallback(){this._teardown()}attributeChangedCallback(t,e,i){if("video"===t&&e!==i&&(this._video=i||"",this._player&&this._player.loadVideoById(this._video),this._isLive=!1),"playlist"===t&&e!==i){if(this._playlist=this._parseListId(i||""),this._player&&this._playlist){const t={list:this._playlist,listType:"playlist",index:this._index};try{this._autoplay?this._player.loadPlaylist(t):this._player.cuePlaylist(t)}catch(t){}}this._updatePlaylistNav()}if("index"===t&&e!==i){let t=parseInt(i||"0",10);if(-1===t?(this._startAtLast=!0,this._index=0):(this._startAtLast=!1,this._index=t>0?t-1:0),this._player&&this._playlist)if(this._startAtLast&&"function"==typeof this._player.getPlaylist){const t=this._player.getPlaylist(),e=Array.isArray(t)?t.length-1:0;try{this._player.playVideoAt(e)}catch(t){}}else try{this._player.playVideoAt(this._index)}catch(t){}this._updatePlaylistNav()}if("autoplay"===t&&e!==i){if(this._autoplay=this._parseBool(i)||this.hasAttribute("autoplay"),this._player&&this._autoplay)try{this._player.mute(),this._player.playVideo()}catch(t){}this._updatePlaylistNav()}}_cacheEls(){const t=this.shadowRoot;this.$wrap=t.getElementById("wrap"),this.$overlay=t.getElementById("overlay"),this.$player=t.getElementById("player"),this.$live=t.getElementById("live"),this.$soundHint=t.getElementById("soundHint"),this.$progress=t.getElementById("progress"),this.$bar=t.getElementById("bar"),this.$seek=t.getElementById("seek"),this.$time=t.getElementById("time"),this.$vol=t.getElementById("volume"),this.$fs=t.getElementById("fs"),this.$playBtn=t.getElementById("playPause"),this.$error=t.getElementById("errorMask"),this.$controls=t.getElementById("controls"),this.$prev=t.getElementById("prev"),this.$next=t.getElementById("next")}_showError(){try{this._stopTimer()}catch(t){}this.$overlay&&(this.$overlay.style.display="none"),this.$controls&&(this.$controls.style.display="none"),this.$error&&this.$error.classList.add("show")}_detectLive(){try{if(this._playlist)return!1;const t=this._player?.getDuration?.()||0,e=this._player?.getPlayerState?.();return 0===t&&(e===YT.PlayerState.PLAYING||e===YT.PlayerState.BUFFERING||e===YT.PlayerState.PAUSED)}catch(t){return!1}}_setLiveUI(t){this._isLive=!!t,this.$live&&(this.$live.style.display=this._isLive?"inline-flex":"none"),this.$progress&&(this.$progress.style.display="")}_bindUI(){this._controlsTimer=null;const t=()=>{this.$wrap.classList.add("show-controls"),clearTimeout(this._controlsTimer),this._controlsTimer=setTimeout(()=>this.$wrap.classList.remove("show-controls"),2e3)};if(this._blinkControls=t,["mousemove","touchstart"].forEach(e=>this.$wrap.addEventListener(e,t)),this.$soundHint&&(this._autoplay?this.$soundHint.classList.remove("hide"):this.$soundHint.classList.add("hide")),this._autoplay&&this.$soundHint&&this.$soundHint.addEventListener("click",t=>{t.stopPropagation();try{this._player?.isMuted&&this._player.isMuted()&&this._player.unMute(),this._player?.setVolume?.(parseInt(this.$vol.value,10)),this._player?.playVideo?.()}catch(t){}this.$soundHint.classList.add("hide"),this._userInteracted=!0}),this.$overlay.addEventListener("click",()=>{if(!this._userInteracted){this._userInteracted=!0;try{this._player.isMuted&&this._player.isMuted()&&(this._player.unMute(),this._player.setVolume(parseInt(this.$vol.value,10)))}catch(t){}}this.$soundHint&&this.$soundHint.classList.add("hide");const t=this._player?.getPlayerState?.();t===YT.PlayerState.PLAYING?this._player.pauseVideo():this._player.playVideo()}),this.$overlay.addEventListener("keydown",t=>{"Space"!==t.code&&" "!==t.key||(t.preventDefault(),this.$overlay.click())}),this.$playBtn.addEventListener("click",()=>{if(!this._userInteracted){this._userInteracted=!0;try{this._player.isMuted&&this._player.isMuted()&&(this._player.unMute(),this._player.setVolume(parseInt(this.$vol.value,10)))}catch(t){}}this.$soundHint&&this.$soundHint.classList.add("hide");const t=this._player?.getPlayerState?.();t===YT.PlayerState.PLAYING?this._player.pauseVideo():this._player.playVideo()}),this.$progress.addEventListener("click",t=>{const e=this.$progress.getBoundingClientRect(),i=t.clientX-e.left,s=Math.min(1,Math.max(0,i/e.width)),a=this._duration&&this._duration>0?this._duration:this._player?.getCurrentTime?.()||0;if(a>0){const t=a*s;this._player.seekTo(t,!0),this._immediateUI(t)}}),this.$overlay.addEventListener("dblclick",e=>{const i=this.$overlay.getBoundingClientRect(),s=e.clientX-i.left,a=i.width/2,r=this._player.getCurrentTime(),n=s<a?-10:10,l=Math.max(0,r+n);this._player.seekTo(l,!0),this._immediateUI(l),t()}),this.$overlay.addEventListener("touchend",e=>{const i=Date.now(),s=i-this._lastTap;if(this._lastTap=i,s<300){const i=e.changedTouches[0],s=this.$overlay.getBoundingClientRect(),a=i.clientX-s.left,r=s.width/2,n=this._player.getCurrentTime(),l=a<r?-10:10,o=Math.max(0,n+l);this._player.seekTo(o,!0),this._immediateUI(o),t()}}),this.$vol.addEventListener("input",()=>{this._player.setVolume(parseInt(this.$vol.value,10))}),this.$fs.addEventListener("click",()=>{const t=this.$wrap,e=t.requestFullscreen||t.webkitRequestFullscreen||t.msRequestFullscreen;e&&e.call(t)}),this.$wrap.addEventListener("contextmenu",t=>t.preventDefault()),this.$prev&&this.$next){const t=this._playlist?"inline-flex":"none";this.$prev.style.display=t,this.$next.style.display=t}this.$prev&&this.$prev.addEventListener("click",()=>{if(!this.$prev.disabled)try{this._player.previousVideo()}catch(t){}}),this.$next&&this.$next.addEventListener("click",()=>{if(!this.$next.disabled)try{this._player.nextVideo()}catch(t){}})}async _mountPlayer(){if(!this._video&&!this._playlist)return;await window.__ytApiReadyPromise;const t={playerVars:{controls:0,modestbranding:1,rel:0,disablekb:1,fs:0,playsinline:1,iv_load_policy:3,autoplay:this._autoplay?1:0}};this._playlist||(t.videoId=this._video),this._player=new YT.Player(this.$player,{...t,events:{onReady:()=>this._onReady(),onStateChange:t=>this._onStateChange(t),onError:()=>this._showError()}})}_onReady(){if(this._player.setVolume(parseInt(this.$vol.value,10)),this._autoplay)try{this._player.mute()}catch(t){}if(this._duration=this._player.getDuration()||0,this._playlist)try{const t={listType:"playlist",list:this._playlist,index:this._index};this._autoplay?this._player.loadPlaylist(t):this._player.cuePlaylist(t)}catch(t){}else if(this._autoplay)try{this._player.playVideo()}catch(t){}if(this._updateUI(),this._setLiveUI(this._detectLive()),this._startAtLast&&this._playlist&&"function"==typeof this._player.getPlaylist){const t=()=>{const e=this._player.getPlaylist();if(Array.isArray(e)&&e.length>0){const t=e.length-1;try{this._player.playVideoAt(t)}catch(t){}this._startAtLast=!1}else setTimeout(t,100)};t()}this._updatePlaylistNav()}_onStateChange(t){if(this._setLiveUI(this._detectLive()),t.data===YT.PlayerState.CUED&&this._autoplay)try{this._player.playVideo()}catch(t){}t.data===YT.PlayerState.PLAYING?(this.$overlay.classList.add("playing"),this._startTimer(),this._blinkControls(),this.$playBtn.textContent="❚❚"):(t.data===YT.PlayerState.PAUSED||t.data===YT.PlayerState.ENDED)&&(this.$overlay.classList.remove("playing"),this._stopTimer(),this._blinkControls(),this.$playBtn.textContent="▶︎"),this._updatePlaylistNav()}_updateUI(){if(!this._player||"function"!=typeof this._player.getCurrentTime)return;this._setLiveUI(this._detectLive());const t=this._player.getCurrentTime()||0;this._duration=this._player.getDuration()||this._duration||0,!this._isLive||this._duration&&0!==this._duration||(this._duration=Math.max(this._duration,t));const e=this._duration?t/this._duration:0;this.$bar.style.width=100*e+"%",this.$seek.style.left=100*e+"%",this.$time.textContent=this._isLive?`${this._fmt(t)} / EN VIVO`:`${this._fmt(t)} / ${this._fmt(this._duration)}`}_immediateUI(t){!this._isLive||this._duration&&0!==this._duration||(this._duration=Math.max(this._duration,t||0));const e=this._duration?t/this._duration:0;this.$bar.style.width=100*e+"%",this.$seek.style.left=100*e+"%",this.$time.textContent=`${this._fmt(t)} / ${this._fmt(this._duration)}`,setTimeout(()=>this._updateUI(),120)}_startTimer(){this._timer||(this._timer=setInterval(()=>this._updateUI(),250))}_stopTimer(){clearInterval(this._timer),this._timer=null}_fmt(t){t=Math.max(0,Math.floor(t||0));return`${String(Math.floor(t/60)).padStart(2,"0")}:${String(t%60).padStart(2,"0")}`}_teardown(){try{this._stopTimer(),this._player&&this._player.destroy&&this._player.destroy()}catch(t){}}_updatePlaylistNav(){if(!this._player||!this._playlist)return this.$prev&&(this.$prev.disabled=!0),void(this.$next&&(this.$next.disabled=!0));let t=0,e=0;try{if("function"==typeof this._player.getPlaylistIndex&&(t=this._player.getPlaylistIndex()),"function"==typeof this._player.getPlaylist){const t=this._player.getPlaylist();Array.isArray(t)&&(e=t.length)}}catch(t){}this.$prev&&(this.$prev.disabled=t<=0),this.$next&&(this.$next.disabled=0===e||t>=e-1)}}customElements.define("lc-youtube",LCYouTube);