Add phone playback reporting and pause sharing to the API
Phones that read system media sessions report playback themselves:
- POST /me/devices issues a per-phone token that can only call
POST /devices/now-playing; revocable, hashed at rest, 10 per user
- Reports name the app ("via Spotify"); unknown apps use the OS label
- Push-reported playback expires after 9 minutes of silence, since
phones send a heartbeat every 4 minutes while playing
Users can pause sharing (PUT /me/sharing: 1h, 8h, indefinitely). While
paused, connections aren't polled, phone reports are dropped, nothing is
added to history, and the current track switches to "played … ago".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* Android music apps whose media sessions we share by default (package → display name).
|
||||
* Anything else (video, podcasts, browsers) is ignored unless the user opts in, so
|
||||
* friends don't see YouTube videos or audiobooks in their feed.
|
||||
*/
|
||||
export const ANDROID_MUSIC_APPS: Record<string, string> = {
|
||||
'com.spotify.music': 'Spotify',
|
||||
'com.google.android.apps.youtube.music': 'YouTube Music',
|
||||
'com.apple.android.music': 'Apple Music',
|
||||
'com.aspiro.tidal': 'TIDAL',
|
||||
'deezer.android.app': 'Deezer',
|
||||
'com.soundcloud.android': 'SoundCloud',
|
||||
'com.amazon.mp3': 'Amazon Music',
|
||||
'com.pandora.android': 'Pandora',
|
||||
'com.qobuz.music': 'Qobuz',
|
||||
'tv.plex.labs.plexamp': 'Plexamp',
|
||||
'com.unicornsonlsd.finamp': 'Finamp',
|
||||
'app.symfonium.android': 'Symfonium',
|
||||
'com.bandcamp.android': 'Bandcamp',
|
||||
'com.anghami': 'Anghami',
|
||||
'com.jio.media.jiobeats': 'JioSaavn',
|
||||
'com.maxmpz.audioplayer': 'Poweramp',
|
||||
'org.videolan.vlc': 'VLC',
|
||||
'com.kapp.youtube.final': 'YMusic',
|
||||
'app.revanced.android.apps.youtube.music': 'YouTube Music',
|
||||
};
|
||||
|
||||
export const registerDeviceSchema = z.object({
|
||||
platform: z.enum(['android', 'ios']),
|
||||
name: z.string().trim().min(1).max(100),
|
||||
});
|
||||
|
||||
const reportedTrack = z.object({
|
||||
title: z.string().trim().min(1).max(500),
|
||||
artist: z.string().trim().max(500),
|
||||
album: z.string().trim().max(500).nullish(),
|
||||
durationMs: z.number().int().positive().max(24 * 3600_000).nullish(),
|
||||
});
|
||||
|
||||
/** What a phone reports when playback changes in a music app. */
|
||||
export const devicePlaybackSchema = z
|
||||
.object({
|
||||
/** Android package name (or iOS bundle id) of the playing app. */
|
||||
app: z.string().trim().min(1).max(200),
|
||||
/** The app's label as the OS reports it; used for apps we don't know. */
|
||||
appName: z.string().trim().max(100).nullish(),
|
||||
state: z.enum(['playing', 'paused', 'stopped']),
|
||||
track: reportedTrack.nullish(),
|
||||
positionMs: z.number().int().min(0).nullish(),
|
||||
})
|
||||
.refine((v) => v.state === 'stopped' || v.track, { message: 'track is required unless stopped', path: ['track'] });
|
||||
|
||||
export type DevicePlaybackInput = z.infer<typeof devicePlaybackSchema>;
|
||||
|
||||
export interface DeviceDTO {
|
||||
id: string;
|
||||
platform: 'android' | 'ios';
|
||||
name: string;
|
||||
lastSeenAt: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
Reference in New Issue
Block a user