Skip to content

Commit

Permalink
feat: re-implement YoutubeEmbed with bespoke code
Browse files Browse the repository at this point in the history
  • Loading branch information
harlan-zw committed Apr 21, 2024
1 parent da2c17b commit 0989156
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 86 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
"@nuxt/devtools-ui-kit": "^1.1.5",
"@nuxt/kit": "^3.11.2",
"@types/google.maps": "^3.55.7",
"@types/stripe-v3": "^3.1.33",
"@types/youtube": "^0.0.50",
"@unhead/vue": "^1.9.7",
"consola": "^3.2.3",
"defu": "^6.1.4",
Expand Down Expand Up @@ -84,7 +86,6 @@
"@nuxt/devtools-ui-kit": "^1.1.5",
"@nuxt/module-builder": "^0.5.5",
"@nuxt/test-utils": "3.12.1",
"@types/stripe-v3": "^3.1.33",
"@unhead/schema": "^1.9.7",
"acorn-loose": "^8.4.0",
"bumpp": "^9.4.0",
Expand Down
45 changes: 7 additions & 38 deletions playground/pages/third-parties/youtube.vue
Original file line number Diff line number Diff line change
@@ -1,56 +1,25 @@
<script setup>
import { ref } from 'vue'
const label = ref('Play!')
const videoid = ref('d_IFKP1Ofq0')
function changeVideo() {
videoid.value = 'N8siuNjyV7A'
}
function changeLabel() {
label.value = 'Spiele!'
}
</script>

<template>
<div>
<div>
<YoutubeEmbed
<YouTubeEmbed
:video-id="videoid"
:play-label="label"
/>
</div>
<div class="button-container">
<button
class="button"
@click="changeVideo"
>
change video
</button>
<button
class="button"
@click="changeLabel"
>
change label
</button>
</div>
<UButton
class="mt-5"
@click="changeVideo"
>
change video
</UButton>
</div>
</template>

<style>
.button-container {
margin: 20px 0;
}
.button {
background-color: orange;
border-radius: 8px;
padding: 4px 8px;
cursor: pointer;
}
.button:not(:last-child) {
margin-right: 8px;
}
</style>
14 changes: 11 additions & 3 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,11 @@ export default defineNuxtModule<ModuleOptions>({
key: 'googleMaps',
from: resolve('./runtime/registry/google-maps'),
},
{
name: 'useScriptYouTubeIframe',
key: 'youtubeIframe',
from: resolve('./runtime/registry/youtube-iframe'),
},
]
registry = registry.map((i) => {
i.priority = -1
Expand Down
78 changes: 78 additions & 0 deletions src/runtime/components/YouTubeEmbed.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import type YT from '@types/youtube'
import { useScriptYouTubeIframe } from '../registry/youtube-iframe'
const props = defineProps({
videoId: { type: String, required: true },
// 4:3
width: { type: Number, default: 640 },
height: { type: Number, default: 480 },
})
const emits = defineEmits<{
onReady: [e: YT.PlayerEvent]
onStateChange: [e: YT.PlayerEvent]
onPlaybackQualityChange: [e: YT.PlayerEvent]
onPlaybackRateChange: [e: YT.PlayerEvent]
onError: [e: YT.PlayerEvent]
onApiChange: [e: YT.PlayerEvent]
}>()
const events: (keyof YT.Events)[] = [
'onReady',
'onStateChange',
'onPlaybackQualityChange',
'onPlaybackRateChange',
'onError',
'onApiChange',
]
const wasHovered = ref(false)
const hoverPromise = new Promise<void>((resolve) => {
watch(wasHovered, val => val && resolve())
})
const ready = ref(false)
const { $script } = useScriptYouTubeIframe({
scriptOptions: {
trigger: hoverPromise,
},
})
const elYoutube = ref()
let player: YT.Player
$script.then(async (instance) => {
const YT = await instance.YT
await new Promise<void>((resolve) => {
if (typeof YT.Player === 'undefined')
YT.ready(resolve)
else
resolve()
})
player = new YT.Player(elYoutube.value, {
width: '100%',
videoId: props.videoId,
playerVars: { autoplay: 1, playsinline: 1 },
events: Object.fromEntries(events.map(event => [event, (e: any) => {
// @ts-expect-error untyped
emits(event, e)
}])),
})
})
watch(() => props.videoId, () => {
player?.loadVideoById(props.videoId)
})
const poster = computed(() => `https://i.ytimg.com/vi_webp/${props.videoId}/sddefault.webp`)
</script>

<template>
<div ref="elYoutube" :style="{ width: `${width}px`, height: `${height}px`, position: 'relative' }" @mouseover="wasHovered = true">
<slot :poster="poster">
<img v-if="!ready" :src="poster" title="" :width="width" :height="height">
</slot>
</div>
</template>
44 changes: 0 additions & 44 deletions src/runtime/components/YoutubeEmbed.vue

This file was deleted.

73 changes: 73 additions & 0 deletions src/runtime/registry/youtube-iframe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { object } from 'valibot'
import type YT from '@types/youtube'
import { registryScript } from '../utils'
import { useHead } from '#imports'
import type { RegistryScriptInput } from '#nuxt-scripts'

export interface YouTubeIframeApi {
// YT is a class -> new YT -> new YT.Player
YT: typeof YT
}

declare global {
interface Window extends YouTubeIframeApi {
onYouTubeIframeAPIReady: () => void
}
}

export const YouTubeIframeOptions = object({
// no options afaik
})

export type YouTubeIFrameInput = RegistryScriptInput<typeof YouTubeIframeOptions>

export function useScriptYouTubeIframe<T extends YouTubeIframeApi>(_options: YouTubeIFrameInput) {
let readyPromise: Promise<void> = Promise.resolve()
return registryScript<T, typeof YouTubeIframeOptions>('youtubeIframe', () => ({
scriptInput: {
src: 'https://www.youtube.com/iframe_api',
// opt-out of privacy defaults
// @ts-expect-error TODO add types
crossorigin: null,
},
schema: YouTubeIframeOptions,
scriptOptions: {
use() {
return {
YT: readyPromise.then(() => {
return window.YT
}),
}
},
clientInit: import.meta.server
? undefined
: () => {
readyPromise = new Promise((resolve) => {
window.onYouTubeIframeAPIReady = resolve
})
},
beforeInit() {
useHead({
link: [
{
rel: 'preconnect',
href: 'https://www.youtube-nocookie.com',
},
{
rel: 'preconnect',
href: 'https://www.google.com',
},
{
rel: 'preconnect',
href: 'https://googleads.g.doubleclick.net',
},
{
rel: 'preconnect',
href: 'https://static.doubleclick.net',
},
],
})
},
},
}), _options)
}

0 comments on commit 0989156

Please sign in to comment.