diff --git a/components-eleventy/video/player.js b/components-eleventy/video/player.js index b505a23..8ba495b 100644 --- a/components-eleventy/video/player.js +++ b/components-eleventy/video/player.js @@ -19,28 +19,31 @@ export default async function ( video, options = {} ) { class="video-canvas w-screen flex flex-col justify-center items-center bg-black pt-16" style="left:50%;right:50%;margin-left:-50vw;margin-right:-50vw;" > +
-
+
- - - - M1 Macs + Windows 10 GAMING and PERFORMANCE Improvements with Parallels 16.5! - -
-
-
- - - -
-
-
-

M1 Macs + Windows 10 GAMING and PERFORMANCE Improvements with Parallels 16.5!

+ + + + M1 Macs + Windows 10 GAMING and PERFORMANCE Improvements with Parallels 16.5! + +
+
+
+ + + +
+
+
+

M1 Macs + Windows 10 GAMING and PERFORMANCE Improvements with Parallels 16.5!

+
-
diff --git a/helpers/lite-youtube.js b/helpers/lite-youtube.js index 29639cc..52f5cee 100644 --- a/helpers/lite-youtube.js +++ b/helpers/lite-youtube.js @@ -20,24 +20,35 @@ class LiteYTEmbed extends HTMLElement { // Always call super first in constructor super() - // write element functionality in here - - // console.log('canAutoplay', canAutoplay) - + this._uid = Date.now() } connectedCallback() { this.videoId = this.getAttribute('videoid') + this.videoDataScript = this.querySelector('.video-data') + this.video = JSON.parse( this.videoDataScript.innerHTML ) + + this.playerContainer = this.querySelector('.player-container') + this.playerPoster = this.querySelector('.player-poster') + + // console.log('canAutoplay from connectedCallback', canAutoplay) + + console.log('video', this.video) + console.log('this.playerContainer', this.playerContainer) + + + // Start watchers here - console.log('canAutoplay from connectedCallback', canAutoplay) // On hover (or tap), warm up the TCP connections we're (likely) about to use. - this.addEventListener('pointerover', LiteYTEmbed.warmConnections, {once: true}) + this.playerContainer.addEventListener('pointerover', this.warmConnections, {once: true}) // Once the user clicks, add the real iframe and drop our play button // TODO: In the future we could be like amp-youtube and silently swap in the iframe during idle time // We'd want to only do this for in-viewport or near-viewport ones: https://github.com/ampproject/amphtml/pull/5003 - this.addEventListener('click', e => this.addIframe()) + this.playerPoster.addEventListener('click', e => this.addIframe()) + + } // // TODO: Support the the user changing the [videoid] attribute @@ -47,15 +58,15 @@ class LiteYTEmbed extends HTMLElement { /** * Add a to the head */ - static addPrefetch(kind, url, as) { - const linkEl = document.createElement('link') - linkEl.rel = kind - linkEl.href = url - if (as) { - linkEl.as = as - } - document.head.append(linkEl) - } + // static c(kind, url, as) { + // const linkEl = document.createElement('link') + // linkEl.rel = kind + // linkEl.href = url + // if (as) { + // linkEl.as = as + // } + // document.head.append(linkEl) + // } /** * Begin pre-connecting to warm up the iframe load @@ -66,9 +77,192 @@ class LiteYTEmbed extends HTMLElement { * Maybe `` would work, but it's unsupported: http://crbug.com/593267 * But TBH, I don't think it'll happen soon with Site Isolation and split caches adding serious complexity. */ - static warmConnections() { + // static warmConnections() { + // if (LiteYTEmbed.preconnected) return + + // // The iframe document and most of its subresources come right off youtube.com + // LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com') + // // The botguard script is fetched off from google.com + // LiteYTEmbed.addPrefetch('preconnect', 'https://www.google.com') + + // // Not certain if these ad related domains are in the critical path. Could verify with domain-specific throttling. + // LiteYTEmbed.addPrefetch('preconnect', 'https://googleads.g.doubleclick.net') + // LiteYTEmbed.addPrefetch('preconnect', 'https://static.doubleclick.net') + + // LiteYTEmbed.preconnected = true + // } + + addIframe() { + const classNames = 'absolute inset-0 h-full w-full object-cover' + + // https://www.youtube-nocookie.com/embed/${video.id}?enablejsapi=1&autoplay=1&modestbranding=1&playsinline=1 + + // const params = new URLSearchParams(this.getAttribute('params') || []) + // params.append('autoplay', '1') + + const iframeEl = document.createElement('iframe') + + iframeEl.width = '100%' + iframeEl.height = '100%' + // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include + // iframeEl.title = this.playLabel + iframeEl.id = this.frameId() + iframeEl.classList.add(...classNames.split(' ')) + iframeEl.frameborder = '0' + iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture' + iframeEl.allowFullscreen = true + // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL + // https://stackoverflow.com/q/64959723/89484 + iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.video.id)}?enablejsapi=1&autoplay=1&modestbranding=1&playsinline=1` + + // this.append(iframeEl) + this.playerContainer.innerHTML = '' + this.playerContainer.append( iframeEl ) + + this.classList.add('lyt-activated') + + // Set focus for a11y + this.querySelector('iframe').focus() + } + + + // Computed methods + + posterSources () { + const webpSource = { + ...this.video.thumbnail, + srcset: this.video.thumbnail.srcset.replaceAll('ytimg.com/vi/', 'ytimg.com/vi_webp/').replace(/.png|.jpg|.jpeg/g, '.webp') + } + + return { + webp: webpSource, + jpeg: this.video.thumbnail + } + } + + frameId () { + return `youtube-player-${this.video.id}-${this._uid}` + } + + timestamps () { + return this.video.timestamps.map( timestamp => { + const [ minutes, seconds ] = timestamp.time.split(':') + + return { + ...timestamp, + inSeconds: (minutes * 60) + Number(seconds) + } + }) + } + + hasTimestamps () { + return this.timestamps.length > 0 + } + + hasPlayer () { + return this.player !== null + } + + activeTimestamp () { + const currentTime = this.playerTime// / 100 + + const reversesTimestamps = [ + ...this.timestamps + ] + + // reversesTimestamps.reverse() + + let foundTimestamp = null + + for (const timestamp of reversesTimestamps) { + const hasStarted = currentTime > 1 + const currentTimeisAfterPreviousTimestamp = (foundTimestamp !== null) ? currentTime > foundTimestamp.inSeconds : true + // const isPastCurrentTime = currentTime > timestamp.inSeconds + // const isBeforeCurrentTime = currentTime > timestamp.inSeconds + const currentTimeIsBeforeThisTimestamp = currentTime < timestamp.inSeconds + + if (currentTimeisAfterPreviousTimestamp && currentTimeIsBeforeThisTimestamp) { + return foundTimestamp + } + + foundTimestamp = timestamp + } + + // No active timestamp + return null + } + + + scrollRow ( timestamp ) { + + // If timestamp button doesn't exist + // then stop + if (!this.$refs[`timestamp-${timestamp.time}`]) return + + const timestampsScroller = this.$refs['timestamps-scroll-container'] + const [ timestampButton ] = this.$refs[`timestamp-${timestamp.time}`] + + // https://stackoverflow.com/a/63773123/1397641 + const newScrollPosition = timestampButton.offsetLeft - timestampsScroller.offsetLeft + + timestampsScroller.scroll({ left: newScrollPosition, behavior: 'smooth' }) + } + + async detectAutoplay () { + + if ( !process.client ) return { willAutoplay: false } + + const { default: canAutoPlay } = await import('can-autoplay') + + const willAutoplay = await canAutoPlay.video() + // const willAutoplayMuted = await canAutoPlay.video({ muted: true, inline: true }) + + return { + willAutoplay: willAutoplay.result + } + } + + async seekTo (timestampInSeconds) { + + if (this.playerLoaded === false) { + await this.startPlayerLoad() + } + + this.player.seekTo(timestampInSeconds) + } + + // async playVideo() { + + // if (this.playerLoaded === false) { + // await this.startPlayerLoad() + // } + + // this.$nextTick(() => { + // // console.log('this.player', JSON.stringify(this.player)) + // this.player.playVideo() + // }) + // }, + + static addPrefetch(kind, url, as) { + console.log('prefetching', url) + + const linkEl = document.createElement('link') + + linkEl.rel = kind + linkEl.href = url + + if (as) { + linkEl.as = as; + } + + document.head.append(linkEl) + } + + warmConnections () { if (LiteYTEmbed.preconnected) return + console.log('Warming connections') + // The iframe document and most of its subresources come right off youtube.com LiteYTEmbed.addPrefetch('preconnect', 'https://www.youtube-nocookie.com') // The botguard script is fetched off from google.com @@ -81,26 +275,123 @@ class LiteYTEmbed extends HTMLElement { LiteYTEmbed.preconnected = true } - addIframe() { - const params = new URLSearchParams(this.getAttribute('params') || []) - params.append('autoplay', '1') + async startPlayerLoad () { + this.playerLoaded = true - const iframeEl = document.createElement('iframe') - iframeEl.width = 560 - iframeEl.height = 315 - // No encoding necessary as [title] is safe. https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#:~:text=Safe%20HTML%20Attributes%20include - iframeEl.title = this.playLabel - iframeEl.allow = 'accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture' - iframeEl.allowFullscreen = true - // AFAIK, the encoding here isn't necessary for XSS, but we'll do it only because this is a URL - // https://stackoverflow.com/q/64959723/89484 - iframeEl.src = `https://www.youtube-nocookie.com/embed/${encodeURIComponent(this.videoId)}?${params.toString()}` - this.append(iframeEl) + await this.initializePlayer() - this.classList.add('lyt-activated') + // this.$nextTick(() => { + // this.initializePlayer() + // }) + } - // Set focus for a11y - this.querySelector('iframe').focus() + async initializePlayer () { + // console.log('Youtube Embed API Ready') + + // Clear player + this.player = null + + // Clear progession interval + clearInterval(this.progressInterval) + + // If there are no timestamps + // then stop + if (!this.hasTimestamps) { + this.playerLoaded = true + return + } + + if (typeof YT === 'undefined') { + await this.initializeApi() + } + + const stateHandlers = { + // unstarted + '-1': () => null, + // ended + '0': () => null, + // playing + '1': this.onPlayerPlaying, + // paused + '2': this.onPlayerPaused, + // buffering + '3': () => null, + // video cued + '4': () => null, + } + + // console.log('frame', this.$refs['frame']) + // console.log('frame id', this.$refs['frame'].id) + + const onReady = () => new Promise( resolve => { + + this.player = new YT.Player(this.$refs['frame'].id, { + events: { + 'onReady': readyEvent => { + this.onPlayerReady( readyEvent ) + + resolve( readyEvent ) + }, + 'onStateChange': event => { + // console.log('state changed', event) + + const stateHandler = stateHandlers[String(event.data)] + // console.log('stateHandler', stateHandler) + stateHandler(event) + } + } + }) + + }) + + await onReady() + + // console.log('Youtube Player API ready', JSON.stringify(this.player)) + } + + initializeApi () { + return new Promise( resolve => { + const tag = document.createElement('script') + tag.id = `youtube-api-script-${this._uid}` + tag.src = 'https://www.youtube.com/iframe_api' + + const firstScriptTag = document.getElementsByTagName('script')[0] + firstScriptTag.parentNode.insertBefore(tag, firstScriptTag) + + + window.onYouTubeIframeAPIReady = resolve + }) + } + + onPlayerPlaying () { + console.log('Player playing') + this.playing = true + + this.progressInterval = setInterval(() => { + // console.log('this.player.getCurrentTime()', this.player.getCurrentTime()) + + // If player is empty + // then stop + if (this.player === null) { + clearInterval(this.progressInterval) + return + } + + // console.log('this.player', this.player.hasOwnProperty('getCurrentTime')) + + this.playerTime = this.player.getCurrentTime() + }, 500) + } + + onPlayerPaused () { + console.log('Player paused') + this.playing = false + + clearInterval(this.progressInterval) + } + + onPlayerReady (event) { + console.log('Player is ready', event, this.player ) } } // Register custom element diff --git a/pages-eleventy/tv.11ty.js b/pages-eleventy/tv.11ty.js index 7e007f1..a6f41e2 100644 --- a/pages-eleventy/tv.11ty.js +++ b/pages-eleventy/tv.11ty.js @@ -90,7 +90,7 @@ class TV { // console.log('video.payload', Object.keys(video.payload)) - const playerHtml = await this.boundComponent(VideoPlayer)() + const playerHtml = await this.boundComponent(VideoPlayer)( video ) const rowHtml = await this.boundComponent(VideoRow)( relatedVideos )