Add a “Now Playing” widget to your website that shows what you’re listening to on YouTube Music in real-time.
For modern frameworks (Next.js, React, Vue, Svelte), the best way to integrate is using the official Supabase client. This gives you instant updates via WebSocket and is more efficient than polling.
npm install @supabase/supabase-js
"use client";
import { useEffect, useState } from "react";
import { createClient } from "@supabase/supabase-js";
const USER_ID = "YOUR_USER_ID";
const supabase = createClient(
"https://uczcbonklkmgzdtqeulx.supabase.co",
"sb_publishable_SS1XoSstAPQjgehDEvZfaw_QAmep2_a"
);
export default function NowPlaying() {
const [song, setSong] = useState<any>(null);
useEffect(() => {
// 1. Fetch initial state
const fetchNow = async () => {
const { data } = await supabase
.from("now_playing")
.select("*")
.eq("user_id", USER_ID)
.single();
if (data) setSong(data);
};
fetchNow();
// 2. Subscribe to real-time changes
const channel = supabase
.channel("now_playing")
.on(
"postgres_changes",
{
event: "UPDATE",
schema: "public",
table: "now_playing",
filter: `user_id=eq.${USER_ID}`,
},
(payload) => setSong(payload.new)
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, []);
if (!song) return <p>Not listening right now</p>;
return (
<div>
<p>{song.is_playing ? "🎵 Now Playing" : "🎵 Last Played"}</p>
<p><strong>{song.title}</strong> — {song.artist}</p>
{song.album_art && <img src={song.album_art} alt="" width={48} />}
</div>
);
}
import { useEffect, useState } from "react";
import { createClient } from "@supabase/supabase-js";
const USER_ID = "YOUR_USER_ID";
const supabase = createClient(
"https://uczcbonklkmgzdtqeulx.supabase.co",
"sb_publishable_SS1XoSstAPQjgehDEvZfaw_QAmep2_a"
);
export default function NowPlaying() {
const [song, setSong] = useState(null);
useEffect(() => {
supabase
.from("now_playing")
.select("*")
.eq("user_id", USER_ID)
.single()
.then(({ data }) => setSong(data));
const subscription = supabase
.channel("now_playing")
.on(
"postgres_changes",
{
event: "UPDATE",
schema: "public",
table: "now_playing",
filter: `user_id=eq.${USER_ID}`,
},
(payload) => setSong(payload.new)
)
.subscribe();
return () => {
supabase.removeChannel(subscription);
};
}, []);
// Render logic...
}
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
import { createClient } from '@supabase/supabase-js'
const USER_ID = 'YOUR_USER_ID'
const supabase = createClient(
'https://uczcbonklkmgzdtqeulx.supabase.co',
'sb_publishable_SS1XoSstAPQjgehDEvZfaw_QAmep2_a'
)
const song = ref(null)
let subscription
onMounted(async () => {
// Initial fetch
const { data } = await supabase
.from('now_playing')
.select('*')
.eq('user_id', USER_ID)
.single()
if (data) song.value = data
// Real-time subscription
subscription = supabase
.channel('now_playing')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'now_playing',
filter: `user_id=eq.${USER_ID}`
},
(payload) => {
song.value = payload.new
}
)
.subscribe()
})
onUnmounted(() => {
if (subscription) supabase.removeChannel(subscription)
})
</script>
<script>
import { onMount, onDestroy } from 'svelte';
import { createClient } from '@supabase/supabase-js';
const USER_ID = 'YOUR_USER_ID';
const supabase = createClient(
'https://uczcbonklkmgzdtqeulx.supabase.co',
'sb_publishable_SS1XoSstAPQjgehDEvZfaw_QAmep2_a'
);
let song = null;
let subscription;
onMount(async () => {
const { data } = await supabase
.from('now_playing')
.select('*')
.eq('user_id', USER_ID)
.single();
if (data) song = data;
subscription = supabase
.channel('now_playing')
.on(
'postgres_changes',
{
event: 'UPDATE',
schema: 'public',
table: 'now_playing',
filter: `user_id=eq.${USER_ID}`
},
(payload) => {
song = payload.new;
}
)
.subscribe();
});
onDestroy(() => {
if (subscription) supabase.removeChannel(subscription);
});
</script>
{#if song}
<div>
<p>{song.is_playing ? "🎵 Now Playing" : "🎵 Last Played"}</p>
<p><strong>{song.title}</strong> — {song.artist}</p>
</div>
{:else}
<p>Not listening right now</p>
{/if}
If you have a static site or don’t want to install the Supabase client, you can poll the standard REST API.
Endpoint:
GET https://uczcbonklkmgzdtqeulx.supabase.co/rest/v1/now_playing?user_id=eq.YOUR_USER_ID&select=*
Header:
apikey: sb_publishable_SS1XoSstAPQjgehDEvZfaw_QAmep2_a
<div id="now-playing"></div>
<!-- ... styles ... -->
<script>
const USER_ID = "YOUR_USER_ID";
const API_URL = `https://uczcbonklkmgzdtqeulx.supabase.co/rest/v1/now_playing?user_id=eq.${USER_ID}&select=*`;
const API_KEY = "sb_publishable_SS1XoSstAPQjgehDEvZfaw_QAmep2_a";
async function fetchNowPlaying() {
const res = await fetch(API_URL, { headers: { apikey: API_KEY } });
const [song] = await res.json();
if (song) {
// ... render logic ...
document.getElementById("now-playing").innerHTML = `🎵 ${song.title} — ${song.artist}`;
}
}
fetchNowPlaying();
setInterval(fetchNowPlaying, 5000); // Poll every 5 seconds
</script>
By default, this extension uses a shared Supabase database provided by the developer. If you prefer to own your data completely and host your own backend:
supabase-setup.sql from this repo.now_playing table and policies.now_playing table if it isn’t automatically enabled.cp config.example.js config.js
config.js and replace:
YOUR_PROJECT_ID with your Supabase project URLYOUR_SUPABASE_ANON_KEY with your project’s anon/public keyconfig.js is gitignored, so your keys stay local.chrome://extensions/Now you have full control over your data, rate limits, and authentication!