import '../../styles/presence.css'; import './Player.css'; import html from './Player.html'; import { auth, burnInStreamUrl, getPlaybackMarkers, getPlaybackOptions, partyStreamUrl, subtitleUrl } from '../../api/embyApi'; import { getParty, leaveParty, connectParty, issueCastToken, revokeCastToken } from '../../api/party'; import type { PartyConnection } from '../../api/party'; import { navigate } from '../../router'; import type { CleanupFn, RouteParams } from '../../router'; import { colorForName, escapeHtml, formatDuration, initialsFor } from '../../tiles'; import { LOADING_HINT } from '../../loading'; import { showToast } from '../../toast'; import type { BurnInChangedMsg, ItemChangedMsg, PartyPublicState, PauseRequestedMsg, PlaybackMarkersDto, PlaybackOptionsDto, ResumeRequestedMsg, SocketMember, StreamOptionsChangedMsg, TextSubtitleChangedMsg } from '../../types'; import { joinMusicParty, leaveMusicParty } from '../../musicBar/MusicBar'; import { initCast, localDriver, onCastAvailabilityChanged, onCastSessionEnded, startCast, stopCast } from '../../cast'; import type { PlaybackDriver } from '../../cast'; import { clearMediaSession, setMediaSessionActionHandlers, setMediaSessionMetadata, setMediaSessionNextAction, setMediaSessionPlaybackState, setMediaSessionPositionState } from '../../mediaSession'; const TICKS_PER_SECOND = 10000000; // Emby/.NET convention: 1 tick = 100ns const DRIFT_TOLERANCE_SECONDS = 1.5; const REQUEST_COOLDOWN_MS = 5000; // matches the server's own throttle in SocketGateway - shared by both request types, each throttled independently const REQUEST_HIGHLIGHT_MS = 4000; // how long the banner + the orange badge stay up const CONTROLS_HIDE_DELAY_MS = 3000; // how long #bottomBar stays fully expanded after the last bit of activity, before collapsing to just its seek bar // hls.js's own defaults (10s manifest/level/frag load timeouts) assume a // file server or a source already being direct-played/remuxed - too tight // for this app, where every load/reload (an audio/quality change, the // initial load itself) chains through our own proxy into a FRESH Emby // transcode (see CatalogService.negotiateStreamTarget/PartyStreamService). // Confirmed for real: a forced re-encode (this used to be how subtitles // worked too, before they moved to a native - see // negotiateStreamTarget's own comment for why) measurably took Emby // longer than 10s to produce a first segment for, // and hls.js gave up with a fatal fragLoadTimeOut after its default 4 // retries - not a hang, a real but slow transcode start being mistaken // for a dead server. Same config duplicated in musicBar/MusicBar.ts (its // own HLS_CONFIG) rather than shared - the two players' hls.js setup is // already independently duplicated for the same reason (see that file's // own loadTrack). const HLS_CONFIG: HlsConfig = { manifestLoadingTimeOut: 30000, manifestLoadingMaxRetry: 2, levelLoadingTimeOut: 30000, fragLoadingTimeOut: 60000 }; // "Quality" is really "max transcoding bitrate", the same simplification // Emby's/Jellyfin's own web clients use - not true adaptive-bitrate // switching. See docs/architecture.md. const QUALITY_PRESETS = [ { label: 'Auto (qualité source)', value: '' }, { label: '1080p (~8 Mbps)', value: '8000000' }, { label: '720p (~4 Mbps)', value: '4000000' }, { label: '480p (~1.5 Mbps)', value: '1500000' } ]; // Plain inline SVG rather than a Unicode glyph (unlike the settings gear) // specifically so this small a badge stays a crisp two-bar shape regardless // of the platform's emoji font - pause/play glyphs default to emoji // presentation on several platforms, which looks muddy at 10px. const PAUSE_ICON_SVG = ` `; // #fullscreenBtn's icon, swapped in place (updateFullscreenBtn) rather // than kept as two separate elements - same "one small SVG string // constant" pattern as PAUSE_ICON_SVG above, just two of them since this // one's shape genuinely changes (outward vs inward corners), not just a // class toggle. const ENTER_FULLSCREEN_SVG = ` `; const EXIT_FULLSCREEN_SVG = ` `; // #playPauseBtn's icon, swapped in place (updatePlayPauseBtn) - the // custom equivalent of the browser's own native