fix: improve now playing detection reliability
- Always fetch fresh data from Last.fm for now playing detection - Add confidence-based detection with progress tracking - Implement dynamic polling intervals (10s when playing, 30s idle) - Add marquee animation for now playing text in header - Fix release date formatting to show only year - Add gradient fade effects to marquee edges - Enhanced logging for debugging detection issues 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
e21d5eab9d
commit
103c69664b
5 changed files with 301 additions and 114 deletions
|
|
@ -2,6 +2,7 @@
|
|||
import Avatar from './Avatar.svelte'
|
||||
import SegmentedController from './SegmentedController.svelte'
|
||||
import NavDropdown from './NavDropdown.svelte'
|
||||
import NowPlayingBar from './NowPlayingBar.svelte'
|
||||
import { albumStream } from '$lib/stores/album-stream'
|
||||
import { nowPlayingStream } from '$lib/stores/now-playing-stream'
|
||||
import type { Album } from '$lib/types/lastfm'
|
||||
|
|
@ -23,6 +24,15 @@
|
|||
const nowPlaying = state.albums.find((album) => album.isNowPlaying)
|
||||
currentlyPlayingAlbum = nowPlaying || null
|
||||
isPlayingMusic = !!nowPlaying
|
||||
|
||||
// Debug logging
|
||||
if (nowPlaying) {
|
||||
console.log('Header: Now playing detected:', {
|
||||
artist: nowPlaying.artist.name,
|
||||
album: nowPlaying.name,
|
||||
track: nowPlaying.nowPlayingTrack
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return unsubscribe
|
||||
|
|
@ -32,7 +42,12 @@
|
|||
$effect(() => {
|
||||
const unsubscribe = nowPlayingStream.subscribe((state) => {
|
||||
const hasNowPlaying = Array.from(state.updates.values()).some((update) => update.isNowPlaying)
|
||||
if (!hasNowPlaying && currentlyPlayingAlbum) {
|
||||
console.log('Header: nowPlayingStream update:', {
|
||||
hasNowPlaying,
|
||||
updatesCount: state.updates.size
|
||||
})
|
||||
// Only clear if we explicitly know music stopped
|
||||
if (!hasNowPlaying && currentlyPlayingAlbum && state.updates.size > 0) {
|
||||
// Music stopped
|
||||
currentlyPlayingAlbum = null
|
||||
isPlayingMusic = false
|
||||
|
|
@ -86,27 +101,21 @@
|
|||
href="/about"
|
||||
class="header-link"
|
||||
aria-label="@jedmund"
|
||||
onmouseenter={() => isHoveringAvatar = true}
|
||||
onmouseleave={() => isHoveringAvatar = false}
|
||||
onmouseenter={() => {
|
||||
isHoveringAvatar = true
|
||||
console.log('Header: Hovering avatar, showing now playing?', {
|
||||
isHoveringAvatar: true,
|
||||
isPlayingMusic,
|
||||
currentlyPlayingAlbum: currentlyPlayingAlbum?.name
|
||||
})
|
||||
}}
|
||||
onmouseleave={() => (isHoveringAvatar = false)}
|
||||
>
|
||||
<Avatar />
|
||||
</a>
|
||||
<div class="nav-desktop">
|
||||
{#if isHoveringAvatar && isPlayingMusic && currentlyPlayingAlbum}
|
||||
<div class="now-playing">
|
||||
<span class="music-note">♪</span>
|
||||
{#if getAlbumArtwork(currentlyPlayingAlbum)}
|
||||
<img
|
||||
src={getAlbumArtwork(currentlyPlayingAlbum)}
|
||||
alt="{currentlyPlayingAlbum.name} album cover"
|
||||
class="album-thumbnail"
|
||||
/>
|
||||
{/if}
|
||||
<span class="track-info">
|
||||
{currentlyPlayingAlbum.artist.name} - {currentlyPlayingAlbum.nowPlayingTrack || currentlyPlayingAlbum.name}
|
||||
</span>
|
||||
<span class="music-note">♪</span>
|
||||
</div>
|
||||
<NowPlayingBar album={currentlyPlayingAlbum} {getAlbumArtwork} />
|
||||
{:else}
|
||||
<SegmentedController />
|
||||
{/if}
|
||||
|
|
@ -199,51 +208,4 @@
|
|||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.now-playing {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: $unit-2x;
|
||||
padding: $unit-2x $unit-3x;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-radius: $corner-radius-3xl;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
min-width: 300px;
|
||||
height: 52px;
|
||||
transition: all $transition-fast ease;
|
||||
|
||||
.music-note {
|
||||
font-size: $font-size-md;
|
||||
opacity: 0.8;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.album-thumbnail {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: $corner-radius-sm;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.track-info {
|
||||
flex: 1;
|
||||
font-size: $font-size-sm;
|
||||
font-weight: $font-weight-medium;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
144
src/lib/components/NowPlayingBar.svelte
Normal file
144
src/lib/components/NowPlayingBar.svelte
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
<script lang="ts">
|
||||
import type { Album } from '$lib/types/lastfm'
|
||||
|
||||
interface Props {
|
||||
album: Album
|
||||
getAlbumArtwork: (album: Album) => string
|
||||
}
|
||||
|
||||
let { album, getAlbumArtwork }: Props = $props()
|
||||
|
||||
const trackText = $derived(`${album.artist.name} — ${album.name}${
|
||||
album.appleMusicData?.releaseDate
|
||||
? ` (${new Date(album.appleMusicData.releaseDate).getFullYear()})`
|
||||
: ''
|
||||
} — ${album.nowPlayingTrack || album.name}`)
|
||||
</script>
|
||||
|
||||
<nav class="now-playing-bar">
|
||||
<div class="now-playing-content">
|
||||
{#if getAlbumArtwork(album)}
|
||||
<img src={getAlbumArtwork(album)} alt="{album.name} album cover" class="album-thumbnail" />
|
||||
{/if}
|
||||
<span class="track-info">
|
||||
<span class="now-playing-label">Now playing</span>
|
||||
<div class="marquee-container">
|
||||
<span class="now-playing-title">{trackText}</span>
|
||||
<span class="now-playing-title" aria-hidden="true">{trackText}</span>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<style lang="scss">
|
||||
.now-playing-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: $gray-100;
|
||||
padding: calc($unit-half - 1px) $unit-half;
|
||||
border-radius: 100px;
|
||||
box-shadow: 0 1px 3px $shadow-light;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-sizing: border-box;
|
||||
min-height: 58.4px;
|
||||
width: 404px;
|
||||
}
|
||||
|
||||
.now-playing-label {
|
||||
font-size: $font-size-extra-small;
|
||||
font-weight: $font-weight-med;
|
||||
color: $gray-50;
|
||||
}
|
||||
|
||||
.now-playing-content {
|
||||
display: grid;
|
||||
grid-template-columns: 32px 1fr;
|
||||
align-items: center;
|
||||
gap: $unit-2x;
|
||||
width: 100%;
|
||||
padding: $unit $unit-2x;
|
||||
color: $text-color-subdued;
|
||||
}
|
||||
|
||||
.music-note {
|
||||
font-size: $font-size-small;
|
||||
opacity: 0.8;
|
||||
animation: pulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.album-thumbnail {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.track-info {
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-med;
|
||||
text-align: center;
|
||||
padding-right: 32px; // Balance out the image on the left
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.marquee-container {
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 50px; // Space between repeated text
|
||||
|
||||
// Gradient overlays
|
||||
&::before,
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
width: 30px;
|
||||
z-index: 1;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&::before {
|
||||
left: 0;
|
||||
background: linear-gradient(to right, $gray-100, transparent);
|
||||
}
|
||||
|
||||
&::after {
|
||||
right: 0;
|
||||
background: linear-gradient(to left, $gray-100, transparent);
|
||||
}
|
||||
}
|
||||
|
||||
.now-playing-title {
|
||||
display: inline-block;
|
||||
white-space: nowrap;
|
||||
animation: marquee 15s linear infinite;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
@keyframes marquee {
|
||||
0% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateX(calc(-100% - 50px)); // Include gap in animation
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.4;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -38,11 +38,14 @@ export class LastfmStreamManager {
|
|||
*/
|
||||
async checkForUpdates(): Promise<StreamUpdate> {
|
||||
try {
|
||||
// Always fetch fresh data for now playing detection
|
||||
const freshData = await this.fetchFreshRecentTracks()
|
||||
|
||||
// Fetch recent albums
|
||||
const albums = await this.getRecentAlbums(4)
|
||||
const albums = await this.getRecentAlbums(4, freshData)
|
||||
|
||||
// Process now playing status
|
||||
await this.updateNowPlayingStatus(albums)
|
||||
await this.updateNowPlayingStatus(albums, freshData)
|
||||
|
||||
// Enrich albums with additional data
|
||||
const enrichedAlbums = await this.enrichAlbums(albums)
|
||||
|
|
@ -60,7 +63,7 @@ export class LastfmStreamManager {
|
|||
}
|
||||
|
||||
// Check for now playing updates for non-recent albums
|
||||
const nowPlayingUpdates = await this.getNowPlayingUpdatesForNonRecentAlbums(enrichedAlbums)
|
||||
const nowPlayingUpdates = await this.getNowPlayingUpdatesForNonRecentAlbums(enrichedAlbums, freshData)
|
||||
if (nowPlayingUpdates.length > 0) {
|
||||
update.nowPlayingUpdates = nowPlayingUpdates
|
||||
}
|
||||
|
|
@ -72,25 +75,27 @@ export class LastfmStreamManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch fresh recent tracks from Last.fm (no cache)
|
||||
*/
|
||||
private async fetchFreshRecentTracks(): Promise<any> {
|
||||
logger.music('debug', 'Fetching fresh Last.fm recent tracks for now playing detection')
|
||||
const recentTracksResponse = await this.client.user.getRecentTracks(this.username, {
|
||||
limit: 50,
|
||||
extended: true
|
||||
})
|
||||
// Still cache it for other uses, but always fetch fresh for now playing
|
||||
await this.albumEnricher.cacheRecentTracks(this.username, recentTracksResponse)
|
||||
return recentTracksResponse
|
||||
}
|
||||
|
||||
/**
|
||||
* Get recent albums from Last.fm
|
||||
*/
|
||||
private async getRecentAlbums(limit: number): Promise<Album[]> {
|
||||
// Try cache first
|
||||
const cached = await this.albumEnricher.getCachedRecentTracks(this.username)
|
||||
|
||||
let recentTracksResponse
|
||||
if (cached) {
|
||||
logger.music('debug', 'Using cached Last.fm recent tracks for album stream')
|
||||
recentTracksResponse = cached
|
||||
} else {
|
||||
logger.music('debug', 'Fetching fresh Last.fm recent tracks for album stream')
|
||||
recentTracksResponse = await this.client.user.getRecentTracks(this.username, {
|
||||
limit: 50,
|
||||
extended: true
|
||||
})
|
||||
// Cache the response
|
||||
await this.albumEnricher.cacheRecentTracks(this.username, recentTracksResponse)
|
||||
private async getRecentAlbums(limit: number, recentTracksResponse?: any): Promise<Album[]> {
|
||||
// Use provided fresh data or fetch new
|
||||
if (!recentTracksResponse) {
|
||||
recentTracksResponse = await this.fetchFreshRecentTracks()
|
||||
}
|
||||
|
||||
// Convert tracks to unique albums
|
||||
|
|
@ -119,19 +124,10 @@ export class LastfmStreamManager {
|
|||
/**
|
||||
* Update now playing status using the detector
|
||||
*/
|
||||
private async updateNowPlayingStatus(albums: Album[]): Promise<void> {
|
||||
// Get recent tracks for now playing detection
|
||||
const cached = await this.albumEnricher.getCachedRecentTracks(this.username)
|
||||
|
||||
let recentTracksResponse
|
||||
if (cached) {
|
||||
recentTracksResponse = cached
|
||||
} else {
|
||||
recentTracksResponse = await this.client.user.getRecentTracks(this.username, {
|
||||
limit: 50,
|
||||
extended: true
|
||||
})
|
||||
await this.albumEnricher.cacheRecentTracks(this.username, recentTracksResponse)
|
||||
private async updateNowPlayingStatus(albums: Album[], recentTracksResponse?: any): Promise<void> {
|
||||
// Use provided fresh data or fetch new
|
||||
if (!recentTracksResponse) {
|
||||
recentTracksResponse = await this.fetchFreshRecentTracks()
|
||||
}
|
||||
|
||||
// Process now playing detection
|
||||
|
|
@ -240,18 +236,15 @@ export class LastfmStreamManager {
|
|||
* Get now playing updates for albums not in the recent list
|
||||
*/
|
||||
private async getNowPlayingUpdatesForNonRecentAlbums(
|
||||
recentAlbums: Album[]
|
||||
recentAlbums: Album[],
|
||||
recentTracksResponse?: any
|
||||
): Promise<NowPlayingUpdate[]> {
|
||||
const updates: NowPlayingUpdate[] = []
|
||||
|
||||
// Get all now playing albums
|
||||
const cached = await this.albumEnricher.getCachedRecentTracks(this.username)
|
||||
const recentTracksResponse =
|
||||
cached ||
|
||||
(await this.client.user.getRecentTracks(this.username, {
|
||||
limit: 50,
|
||||
extended: true
|
||||
}))
|
||||
// Use provided fresh data or fetch new
|
||||
if (!recentTracksResponse) {
|
||||
recentTracksResponse = await this.fetchFreshRecentTracks()
|
||||
}
|
||||
|
||||
const nowPlayingMap = await this.nowPlayingDetector.processNowPlayingTracks(
|
||||
recentTracksResponse,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,11 @@ export interface NowPlayingResult {
|
|||
const SCROBBLE_LAG = 3 * 60 * 1000 // 3 minutes to account for Last.fm scrobble delay
|
||||
const TRACK_HISTORY_WINDOW = 60 * 60 * 1000 // Keep 1 hour of track history
|
||||
|
||||
// Confidence thresholds
|
||||
const CONFIDENCE_HIGH = 0.9
|
||||
const CONFIDENCE_MEDIUM = 0.6
|
||||
const CONFIDENCE_LOW = 0.3
|
||||
|
||||
export class NowPlayingDetector {
|
||||
private recentTracks: TrackPlayInfo[] = []
|
||||
|
||||
|
|
@ -45,6 +50,36 @@ export class NowPlayingDetector {
|
|||
this.recentTracks = this.recentTracks.filter((track) => track.scrobbleTime > cutoffTime)
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate confidence score for now playing detection
|
||||
*/
|
||||
private calculateConfidence(
|
||||
scrobbleTime: Date,
|
||||
durationMs: number,
|
||||
now: Date = new Date()
|
||||
): { confidence: number; reason: string } {
|
||||
const elapsed = now.getTime() - scrobbleTime.getTime()
|
||||
const progress = elapsed / durationMs
|
||||
|
||||
// Very confident if within normal playback window
|
||||
if (progress >= 0 && progress <= 1.0) {
|
||||
return { confidence: CONFIDENCE_HIGH, reason: 'within normal playback' }
|
||||
}
|
||||
|
||||
// Medium confidence if slightly over (accounting for buffering/pauses)
|
||||
if (progress > 1.0 && progress <= 1.2) {
|
||||
return { confidence: CONFIDENCE_MEDIUM, reason: 'slightly over duration (buffering/pauses)' }
|
||||
}
|
||||
|
||||
// Low confidence if significantly over but within lag window
|
||||
if (progress > 1.2 && elapsed <= durationMs + SCROBBLE_LAG) {
|
||||
return { confidence: CONFIDENCE_LOW, reason: 'significantly over but within lag window' }
|
||||
}
|
||||
|
||||
// No confidence if too far past expected end
|
||||
return { confidence: 0, reason: 'track ended' }
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an album is currently playing based on track duration
|
||||
*/
|
||||
|
|
@ -74,19 +109,27 @@ export class NowPlayingDetector {
|
|||
)
|
||||
|
||||
if (trackData?.durationMs) {
|
||||
const trackEndTime = new Date(
|
||||
mostRecentTrack.scrobbleTime.getTime() + trackData.durationMs + SCROBBLE_LAG
|
||||
const { confidence, reason } = this.calculateConfidence(
|
||||
mostRecentTrack.scrobbleTime,
|
||||
trackData.durationMs,
|
||||
now
|
||||
)
|
||||
|
||||
if (now < trackEndTime) {
|
||||
// Only consider it playing if confidence is above threshold
|
||||
if (confidence >= CONFIDENCE_LOW) {
|
||||
logger.music(
|
||||
'debug',
|
||||
`Track "${mostRecentTrack.trackName}" is still playing (ends at ${trackEndTime.toLocaleTimeString()})`
|
||||
`Track "${mostRecentTrack.trackName}" detected as playing (confidence: ${confidence}, ${reason})`
|
||||
)
|
||||
return {
|
||||
isNowPlaying: true,
|
||||
nowPlayingTrack: mostRecentTrack.trackName
|
||||
}
|
||||
} else {
|
||||
logger.music(
|
||||
'debug',
|
||||
`Track "${mostRecentTrack.trackName}" confidence too low: ${confidence} (${reason})`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -120,10 +163,15 @@ export class NowPlayingDetector {
|
|||
for (const track of tracks) {
|
||||
if (track.nowPlaying) {
|
||||
hasOfficialNowPlaying = true
|
||||
logger.music('debug', `Last.fm reports "${track.name}" by ${track.artist.name} as now playing`)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasOfficialNowPlaying) {
|
||||
logger.music('debug', 'No official now playing from Last.fm, will use duration-based detection')
|
||||
}
|
||||
|
||||
// Process all tracks
|
||||
for (const track of tracks) {
|
||||
// Store track play information
|
||||
|
|
@ -150,10 +198,18 @@ export class NowPlayingDetector {
|
|||
try {
|
||||
const appleMusicData = await appleMusicDataLookup(album.artistName, album.albumName)
|
||||
if (appleMusicData?.tracks) {
|
||||
logger.music(
|
||||
'debug',
|
||||
`Checking duration-based detection for "${album.albumName}" (${appleMusicData.tracks.length} tracks)`
|
||||
)
|
||||
const result = this.checkAlbumNowPlaying(album.albumName, appleMusicData.tracks)
|
||||
if (result?.isNowPlaying) {
|
||||
album.isNowPlaying = true
|
||||
album.nowPlayingTrack = result.nowPlayingTrack
|
||||
logger.music(
|
||||
'debug',
|
||||
`Duration-based detection: "${album.nowPlayingTrack}" from "${album.albumName}" is now playing`
|
||||
)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
@ -173,6 +229,10 @@ export class NowPlayingDetector {
|
|||
// Update recent tracks
|
||||
this.updateRecentTracks(newRecentTracks)
|
||||
|
||||
// Log summary
|
||||
const nowPlayingCount = Array.from(albums.values()).filter(a => a.isNowPlaying).length
|
||||
logger.music('debug', `Detected ${nowPlayingCount} album(s) as now playing out of ${albums.size} recent albums`)
|
||||
|
||||
// Ensure only one album is marked as now playing
|
||||
return this.ensureSingleNowPlaying(albums, newRecentTracks)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@ import { logger } from '$lib/server/logger'
|
|||
|
||||
const LASTFM_API_KEY = process.env.LASTFM_API_KEY
|
||||
const USERNAME = 'jedmund'
|
||||
const UPDATE_INTERVAL = 30000 // 30 seconds to reduce API load
|
||||
const UPDATE_INTERVAL = 30000 // 30 seconds default
|
||||
const FAST_UPDATE_INTERVAL = 10000 // 10 seconds when music is playing
|
||||
|
||||
export const GET: RequestHandler = async ({ request }) => {
|
||||
const encoder = new TextEncoder()
|
||||
|
|
@ -16,6 +17,8 @@ export const GET: RequestHandler = async ({ request }) => {
|
|||
const streamManager = new LastfmStreamManager(client, USERNAME)
|
||||
let intervalId: NodeJS.Timeout | null = null
|
||||
let isClosed = false
|
||||
let currentInterval = UPDATE_INTERVAL
|
||||
let isPlaying = false
|
||||
|
||||
// Send initial connection message
|
||||
try {
|
||||
|
|
@ -36,6 +39,9 @@ export const GET: RequestHandler = async ({ request }) => {
|
|||
try {
|
||||
const update = await streamManager.checkForUpdates()
|
||||
|
||||
// Check if music is playing
|
||||
let musicIsPlaying = false
|
||||
|
||||
// Send album updates if any
|
||||
if (update.albums && !isClosed) {
|
||||
try {
|
||||
|
|
@ -43,6 +49,8 @@ export const GET: RequestHandler = async ({ request }) => {
|
|||
controller.enqueue(encoder.encode(`event: albums\ndata: ${data}\n\n`))
|
||||
|
||||
const nowPlayingAlbum = update.albums.find((a) => a.isNowPlaying)
|
||||
musicIsPlaying = !!nowPlayingAlbum
|
||||
|
||||
logger.music('debug', 'Sent album update with now playing status:', {
|
||||
totalAlbums: update.albums.length,
|
||||
nowPlayingAlbum: nowPlayingAlbum
|
||||
|
|
@ -59,11 +67,31 @@ export const GET: RequestHandler = async ({ request }) => {
|
|||
try {
|
||||
const data = JSON.stringify(update.nowPlayingUpdates)
|
||||
controller.enqueue(encoder.encode(`event: nowplaying\ndata: ${data}\n\n`))
|
||||
|
||||
// Check if any of the updates indicate music is playing
|
||||
musicIsPlaying = musicIsPlaying || update.nowPlayingUpdates.some(u => u.isNowPlaying)
|
||||
} catch (e) {
|
||||
isClosed = true
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust polling interval based on playing state
|
||||
if (musicIsPlaying !== isPlaying) {
|
||||
isPlaying = musicIsPlaying
|
||||
const newInterval = isPlaying ? FAST_UPDATE_INTERVAL : UPDATE_INTERVAL
|
||||
|
||||
if (newInterval !== currentInterval) {
|
||||
currentInterval = newInterval
|
||||
logger.music('debug', `Adjusting polling interval to ${currentInterval}ms (playing: ${isPlaying})`)
|
||||
|
||||
// Reset interval with new timing
|
||||
if (intervalId) {
|
||||
clearInterval(intervalId)
|
||||
intervalId = setInterval(checkForUpdates, currentInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Send heartbeat to keep connection alive
|
||||
if (!isClosed) {
|
||||
try {
|
||||
|
|
@ -82,7 +110,7 @@ export const GET: RequestHandler = async ({ request }) => {
|
|||
await checkForUpdates()
|
||||
|
||||
// Set up interval
|
||||
intervalId = setInterval(checkForUpdates, UPDATE_INTERVAL)
|
||||
intervalId = setInterval(checkForUpdates, currentInterval)
|
||||
|
||||
// Handle client disconnect
|
||||
request.signal.addEventListener('abort', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue