import { useSocketContext } from '@/providers/socket-context' import { usePlayerStore } from '@/stores/playerStore' import { useRoomStore } from '@/stores/roomStore' import { useAccountStore } from '@/stores/accountStore' import { isBilibiliCoverUrl } from '@/lib/cover' import type { VoteAction } from '@music-together/shared' import { defineAbilityFor, EVENTS, TIMING } from '@music-together/shared' import { useCallback, useEffect, useMemo, useRef } from 'react' import { toast } from 'sonner' import { getVoteActionLabel } from './useVote' interface MediaSessionControls { play: () => void pause: () => void next: () => void prev: () => void seek: (time: number) => void } /** * Integrates with the MediaSession API so hardware media keys * (play / pause / next / prev) and the OS media notification bar * control the app's synced playback. * * Mirrors `PlayerControls` permission routing to keep every control * entry point (on-screen buttons, media keys, lock-screen widget) * behaviourally identical: * * - If the user can directly perform the action, the media-session * handler invokes the action (which emits the usual socket event). * - Else if the user can vote, the handler initiates a vote (available * only for pause/resume/next/prev — no seek-vote exists). * - Else the handler is unregistered so the OS hides or greys it out. * * `seekto` is only registered when the user has direct `seek` permission. * Otherwise the optimistic `setCurrentTime` inside `usePlayer.seek` would * leave the UI stuck at a fake position after the server rejected the * request (no rollback exists — the in-page slider avoids this because * it's disabled in the first place). * * Requirements: * - A real `