spotify state resume/capture

This commit is contained in:
yohlo
2025-09-12 11:34:21 -05:00
parent 0169468114
commit cf09014d50
9 changed files with 366 additions and 16 deletions

View File

@@ -1,6 +1,5 @@
import { createFileRoute, redirect, useRouter } from "@tanstack/react-router";
import { createFileRoute, redirect } from "@tanstack/react-router";
import {
tournamentKeys,
tournamentQueries,
useTournament,
} from "@/features/tournaments/queries";
@@ -11,9 +10,6 @@ import { useMemo } from "react";
import { BracketData } from "@/features/bracket/types";
import { Match } from "@/features/matches/types";
import BracketView from "@/features/bracket/components/bracket-view";
import { startMatch } from "@/features/matches/server";
import { useServerMutation } from "@/lib/tanstack-query/hooks";
import { useQueryClient } from "@tanstack/react-query";
import { SpotifyControlsBar } from "@/features/spotify/components";
export const Route = createFileRoute("/_authed/admin/tournaments/run/$id")({
@@ -30,6 +26,7 @@ export const Route = createFileRoute("/_authed/admin/tournaments/run/$id")({
},
loader: ({ context }) => ({
fullWidth: true,
withPadding: false,
showSpotifyPanel: true,
header: {
withBackButton: true,
@@ -78,7 +75,7 @@ function RouteComponent() {
}, [tournament.matches]);
return (
<Container size="md">
<Container size="md" px={0}>
<SpotifyControlsBar />
{tournament.matches?.length ? (
<BracketView bracket={bracket} showControls />

View File

@@ -0,0 +1,59 @@
import { createServerFileRoute } from '@tanstack/react-start/server'
import { SpotifyWebApiClient } from '@/lib/spotify/client'
import type { SpotifyPlaybackSnapshot } from '@/lib/spotify/types'
export const ServerRoute = createServerFileRoute('/api/spotify/capture').methods({
POST: async ({ request }: { request: Request }) => {
try {
// Get access token from cookies
const cookies = request.headers.get('Cookie') || ''
const accessTokenMatch = cookies.match(/spotify_access_token=([^;]+)/)
if (!accessTokenMatch) {
return new Response(
JSON.stringify({ error: 'No access token found' }),
{
status: 401,
headers: { 'Content-Type': 'application/json' }
}
)
}
const accessToken = decodeURIComponent(accessTokenMatch[1])
const spotifyClient = new SpotifyWebApiClient(accessToken)
// Create a snapshot of the current playback state
const snapshot = await spotifyClient.createPlaybackSnapshot()
if (!snapshot) {
return new Response(
JSON.stringify({ error: 'No active playback to capture' }),
{
status: 400,
headers: { 'Content-Type': 'application/json' }
}
)
}
return new Response(
JSON.stringify({ snapshot }),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
)
} catch (error) {
console.error('Spotify capture error:', error)
const errorMessage = error instanceof Error ? error.message : 'Failed to capture playback state'
return new Response(
JSON.stringify({ error: errorMessage }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
)
}
},
})

View File

@@ -0,0 +1,72 @@
import { createServerFileRoute } from '@tanstack/react-start/server'
import { SpotifyWebApiClient } from '@/lib/spotify/client'
import type { SpotifyPlaybackSnapshot } from '@/lib/spotify/types'
export const ServerRoute = createServerFileRoute('/api/spotify/resume').methods({
POST: async ({ request }: { request: Request }) => {
try {
// Get access token from cookies
const cookies = request.headers.get('Cookie') || ''
const accessTokenMatch = cookies.match(/spotify_access_token=([^;]+)/)
if (!accessTokenMatch) {
return new Response(
JSON.stringify({ error: 'No access token found' }),
{
status: 401,
headers: { 'Content-Type': 'application/json' }
}
)
}
const accessToken = decodeURIComponent(accessTokenMatch[1])
const spotifyClient = new SpotifyWebApiClient(accessToken)
// Parse the request body to get the snapshot
const body = await request.json()
const { snapshot } = body as { snapshot: SpotifyPlaybackSnapshot }
if (!snapshot) {
return new Response(
JSON.stringify({ error: 'No snapshot provided' }),
{
status: 400,
headers: { 'Content-Type': 'application/json' }
}
)
}
// Restore the playback state from the snapshot
await spotifyClient.restorePlaybackSnapshot(snapshot)
return new Response(
JSON.stringify({ success: true }),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
)
} catch (error) {
console.error('Spotify resume error:', error)
let errorMessage = 'Failed to resume playback state'
// Handle common Spotify Premium requirement error
if (error instanceof Error) {
if (error.message.includes('Premium') || error.message.includes('403')) {
errorMessage = 'Spotify Premium required for playback control'
} else {
errorMessage = error.message
}
}
return new Response(
JSON.stringify({ error: errorMessage }),
{
status: 500,
headers: { 'Content-Type': 'application/json' }
}
)
}
},
})