How do i use the js code from https://developers.google.com/youtube/iframe_api_reference in svelte? Running the code in a script tag like their snippet seems to behave differently to running it in svelte script block. Namely it doesnt recognise "YT" that is being loaded from the iframe api. The html code given as is works, but i want to be able to access the player to change the video id, check the current time etc, inside my svelte script tag.
The given youtube code is:
<!DOCTYPE html>
<html>
<body>
<!-- 1. The <iframe> (and video player) will replace this <div> tag. -->
<div id="player"></div>
<script>
// 2. This code loads the IFrame Player API code asynchronously.
var tag = document.createElement('script');
tag.src = "https://www.youtube.com/iframe_api";
var firstScriptTag = document.getElementsByTagName('script')[0];
firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);
// 3. This function creates an <iframe> (and YouTube player)
// after the API code downloads.
var player;
function onYouTubeIframeAPIReady() {
player = new YT.Player('player', {
height: '390',
width: '640',
videoId: 'M7lc1UVf-VE',
playerVars: {
'playsinline': 1
},
events: {
'onReady': onPlayerReady,
'onStateChange': onPlayerStateChange
}
});
}
// 4. The API will call this function when the video player is ready.
function onPlayerReady(event) {
event.target.playVideo();
}
// 5. The API calls this function when the player's state changes.
// The function indicates that when playing a video (state=1),
// the player should play for six seconds and then stop.
var done = false;
function onPlayerStateChange(event) {
if (event.data == YT.PlayerState.PLAYING && !done) {
setTimeout(stopVideo, 6000);
done = true;
}
}
function stopVideo() {
player.stopVideo();
}
</script>
</body>
</html>
After a discussion in the svelte discord server, I have the following solution. Hopefully this can help anyone else going through the pain i just did trying to work this out.
I create a youtube component (feel free to modify any of the properties in the YT.Player()
:
<script>
import { onMount } from 'svelte';
export let player;
export let initialVideoId = '';
const ytPlayerId = 'youtube-player';
onMount(() => {
function load() {
player = new YT.Player(ytPlayerId, {
height: '100%',
width: '100%',
videoId: initialVideoId,
playerVars: { autoplay: 1 }
});
}
if (window.YT) {
load();
} else {
window.onYouTubeIframeAPIReady = load;
}
});
</script>
<svelte:head>
<script src="https://www.youtube.com/iframe_api"></script>
</svelte:head>
<div id={ytPlayerId} />
The key things to note here are:
the script tag in <svelte:head>
injects the iframe api code.
VScode (or whatever editor) will underline a bunch of things in red like Cannot find name 'YT'
or Property 'onYouTubeIframeAPIReady' does not exist on type 'Window & typeof globalThis'
. You can ignore these as these reference code that gets injected from the iframe api code, and will function properly when actually running in the browser
The if (window.YT)...
statement is very important - the first time the embed is mounted, window.YT property will not exist, so window.onYouTubeIframeAPIReady = load;
what this does is when the loaded iframe api code runs, it will attempt to execute window.onYouTubeIframeAPIReady(). Since this function gets set to load, then the player = new YT.player(...
as per youtube's example code in their docs linked in the OP.
However, if you navigate away to another page on your site and then return to the page with the embed, since sveltekit sites act like an SPA, all the code loaded from the script tag remains. What ends up happening is that some logic in the loaded iframe api code causes the onYouTubeIframeAPIReady function to not be run again, and thus a new player element is not created. If you refreshed the page and thus removed all the loaded code, the embed would load properly again. To solve this, if window.YT
exists (which is some property that the loaded iframe api code creates), then we can directly create a new player by calling load()
and we can be certain that the loaded api code has already been added to our page and the player will function properly.
Then we can import the component within a page and also access the player object by binding to it:
<script>
import Youtube from '$lib/youtube.svelte';
let player
const toggle = () => {
console.log('changing video id')
player.loadVideoById("dQw4w9WgXcQ");
}
</script>
<Youtube bind:player/>
<button on:click={toggle}>change video</button>
Using player
you can then access any of the youtube embed api provided functions like loadVideoById or getCurrentTime. In the above example, clicking on the button changes the video to the given youtube video id.
Also, i have elected to disable ssr for the page using the embed, given the multiple references to window. I have had some occurrences where leaving ssr enabled has resulted in errors, but it was inconsistent and i didn't bother trying to find out exactly what caused them.
On a side note, the YT.Player()
initialisation accepts % values for height and width, so i have found it easiest to set them to 100% and then put the embed inside a wrapper element which you can size however you want with css directly.