Playback fix
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m28s
Traceability Validation / Check Requirement Traces (push) Successful in 22s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 18m37s
Build & Release / Run Tests (push) Successful in 4m12s
Build & Release / Build Linux (push) Successful in 16m20s
Build & Release / Build Android (push) Successful in 18m57s
Build & Release / Create Release (push) Successful in 13s
🏗️ Build and Test JellyTau / Run Tests (push) Successful in 4m28s
Traceability Validation / Check Requirement Traces (push) Successful in 22s
🏗️ Build and Test JellyTau / Build Android APK (push) Successful in 18m37s
Build & Release / Run Tests (push) Successful in 4m12s
Build & Release / Build Linux (push) Successful in 16m20s
Build & Release / Build Android (push) Successful in 18m57s
Build & Release / Create Release (push) Successful in 13s
This commit is contained in:
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* Unified frontend player API (the boundary).
|
||||
*
|
||||
* This is the single write-side entry point for playback. Every UI component
|
||||
* that wants to *control* the player calls a method here; nothing else should
|
||||
* invoke `commands.player*` directly. The Rust `PlayerController` remains the
|
||||
* single source of truth — these methods only send intent-level commands and
|
||||
* let state flow back through `PlayerStatusEvent` → `playerEvents.ts` → the
|
||||
* `player`/`queue` stores.
|
||||
*
|
||||
* Reads stay on the established stores: this module re-exports the read-only
|
||||
* derived + merged (remote-session-aware) stores so UI can import state and
|
||||
* actions from one place, in both local and remote modes.
|
||||
*
|
||||
* TRACES: UR-005 | DR-001, DR-009
|
||||
*/
|
||||
|
||||
import { get } from "svelte/store";
|
||||
import { commands } from "$lib/api/bindings";
|
||||
import type {
|
||||
PlayTracksContext,
|
||||
PlayAlbumTrackRequest,
|
||||
PlayItemRequest,
|
||||
} from "$lib/api/bindings";
|
||||
import { auth } from "$lib/stores/auth";
|
||||
|
||||
/**
|
||||
* Resolve the current repository handle, throwing a clear error if the user is
|
||||
* not authenticated. Centralizes the `auth.getRepository().getHandle()` dance
|
||||
* that was previously duplicated across every context-play call site.
|
||||
*/
|
||||
function requireHandle(): string {
|
||||
const authState = get(auth);
|
||||
if (!authState.isAuthenticated) {
|
||||
throw new Error("User not authenticated");
|
||||
}
|
||||
const repo = auth.getRepository();
|
||||
if (!repo) {
|
||||
throw new Error("No repository available");
|
||||
}
|
||||
return repo.getHandle();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Transport controls (no repository handle required)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function play() {
|
||||
await commands.playerPlay();
|
||||
}
|
||||
|
||||
async function pause() {
|
||||
await commands.playerPause();
|
||||
}
|
||||
|
||||
async function toggle() {
|
||||
await commands.playerToggle();
|
||||
}
|
||||
|
||||
async function stop() {
|
||||
await commands.playerStop();
|
||||
}
|
||||
|
||||
async function seek(positionSeconds: number) {
|
||||
await commands.playerSeek(positionSeconds);
|
||||
}
|
||||
|
||||
async function next() {
|
||||
await commands.playerNext();
|
||||
}
|
||||
|
||||
async function previous() {
|
||||
await commands.playerPrevious();
|
||||
}
|
||||
|
||||
async function skipTo(index: number) {
|
||||
await commands.playerSkipTo(index);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Queue mode controls
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function toggleShuffle() {
|
||||
await commands.playerToggleShuffle();
|
||||
}
|
||||
|
||||
async function cycleRepeat() {
|
||||
await commands.playerCycleRepeat();
|
||||
}
|
||||
|
||||
async function removeFromQueue(index: number) {
|
||||
await commands.playerRemoveFromQueue(index);
|
||||
}
|
||||
|
||||
async function moveInQueue(fromIndex: number, toIndex: number) {
|
||||
await commands.playerMoveInQueue(fromIndex, toIndex);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Volume
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function setVolume(volume: number) {
|
||||
await commands.playerSetVolume(volume);
|
||||
}
|
||||
|
||||
async function toggleMute() {
|
||||
await commands.playerToggleMute();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Track selection (video)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
async function setSubtitleTrack(streamIndex: number | null) {
|
||||
await commands.playerSetSubtitleTrack(streamIndex);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Context-aware playback (repository handle required — resolved internally)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Play a set of tracks by ID with an explicit queue context. The backend
|
||||
* fetches all metadata and builds the queue; the frontend queue store updates
|
||||
* from the resulting `queue_changed` event.
|
||||
*/
|
||||
async function playTracks(request: {
|
||||
trackIds: string[];
|
||||
startIndex: number;
|
||||
shuffle: boolean;
|
||||
context: PlayTracksContext;
|
||||
startPosition?: number;
|
||||
}) {
|
||||
await commands.playerPlayTracks(requireHandle(), request);
|
||||
}
|
||||
|
||||
/** Play a single track within its album context (more efficient than playTracks). */
|
||||
async function playAlbumTrack(request: PlayAlbumTrackRequest) {
|
||||
await commands.playerPlayAlbumTrack(requireHandle(), request);
|
||||
}
|
||||
|
||||
/** Play a single explicit media item (used by the video path). */
|
||||
async function playItem(request: PlayItemRequest) {
|
||||
return commands.playerPlayItem(request);
|
||||
}
|
||||
|
||||
/** Add a single track to the queue by ID. */
|
||||
async function addTrackById(trackId: string, position: "next" | "end" = "end") {
|
||||
await commands.playerAddTrackById(requireHandle(), { trackId, position });
|
||||
}
|
||||
|
||||
/** Add multiple tracks to the queue by ID. */
|
||||
async function addTracksByIds(
|
||||
trackIds: string[],
|
||||
position: "next" | "end" = "end"
|
||||
) {
|
||||
await commands.playerAddTracksByIds(requireHandle(), { trackIds, position });
|
||||
}
|
||||
|
||||
/**
|
||||
* The unified player facade. Import this and call its methods instead of
|
||||
* reaching for `commands.player*` in UI code.
|
||||
*/
|
||||
export const playerController = {
|
||||
play,
|
||||
pause,
|
||||
toggle,
|
||||
stop,
|
||||
seek,
|
||||
next,
|
||||
previous,
|
||||
skipTo,
|
||||
toggleShuffle,
|
||||
cycleRepeat,
|
||||
removeFromQueue,
|
||||
moveInQueue,
|
||||
setVolume,
|
||||
toggleMute,
|
||||
setSubtitleTrack,
|
||||
playTracks,
|
||||
playAlbumTrack,
|
||||
playItem,
|
||||
addTrackById,
|
||||
addTracksByIds,
|
||||
};
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Read-side re-exports: UI reads state from ONE place, in both local & remote
|
||||
// modes. These remain the single source of truth fed by playerEvents.ts.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
export {
|
||||
playerState,
|
||||
currentMedia,
|
||||
isPlaying,
|
||||
isPaused,
|
||||
isLoading,
|
||||
playbackPosition,
|
||||
playbackDuration,
|
||||
volume,
|
||||
isMuted,
|
||||
mergedMedia,
|
||||
mergedIsPlaying,
|
||||
mergedPosition,
|
||||
mergedDuration,
|
||||
mergedVolume,
|
||||
} from "$lib/stores/player";
|
||||
|
||||
export {
|
||||
queueItems,
|
||||
currentQueueIndex,
|
||||
currentQueueItem,
|
||||
isShuffle,
|
||||
repeatMode,
|
||||
hasNext,
|
||||
hasPrevious,
|
||||
} from "$lib/stores/queue";
|
||||
Reference in New Issue
Block a user