fix(Remote playback): kludge to scrub after stream move
This commit is contained in:
@@ -303,8 +303,8 @@ async playbackModeIsTransferring() : Promise<boolean> {
|
||||
/**
|
||||
* Transfer playback from local device to a remote Jellyfin session
|
||||
*/
|
||||
async playbackModeTransferToRemote(sessionId: string) : Promise<null> {
|
||||
return await TAURI_INVOKE("playback_mode_transfer_to_remote", { sessionId });
|
||||
async playbackModeTransferToRemote(sessionId: string, position: number | null) : Promise<null> {
|
||||
return await TAURI_INVOKE("playback_mode_transfer_to_remote", { sessionId, position });
|
||||
},
|
||||
/**
|
||||
* Get remote session status (for polling position/duration)
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<!-- TRACES: UR-010 | JA-021, JA-025 | DR-037 -->
|
||||
<script lang="ts">
|
||||
import { get } from "svelte/store";
|
||||
import { sessions, controllableSessions, selectedSession } from "$lib/stores";
|
||||
import { playbackMode, isTransferring, transferError } from "$lib/stores/playbackMode";
|
||||
import { playbackPosition } from "$lib/stores/player";
|
||||
import type { Session } from "$lib/api/types";
|
||||
|
||||
interface Props {
|
||||
@@ -14,8 +16,8 @@
|
||||
|
||||
async function handleSessionSelect(session: Session) {
|
||||
try {
|
||||
// Transfer playback to remote session
|
||||
await playbackMode.transferToRemote(session.id);
|
||||
// Transfer playback to remote session, resuming at our current position
|
||||
await playbackMode.transferToRemote(session.id, get(playbackPosition));
|
||||
|
||||
if (onSelectSession) {
|
||||
onSelectSession(session);
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
|
||||
import { type UnlistenFn } from "@tauri-apps/api/event";
|
||||
import { commands, events, type PlayerStatusEvent, type SleepTimerMode } from "$lib/api/bindings";
|
||||
import { player, playbackPosition } from "$lib/stores/player";
|
||||
import { player, playbackPosition, currentMedia } from "$lib/stores/player";
|
||||
import { queue, currentQueueItem } from "$lib/stores/queue";
|
||||
import { playbackMode } from "$lib/stores/playbackMode";
|
||||
import { sleepTimer } from "$lib/stores/sleepTimer";
|
||||
@@ -163,9 +163,16 @@ async function handleStateChanged(state: string, _mediaId: string | null): Promi
|
||||
}
|
||||
|
||||
if (state === "playing" && currentItem) {
|
||||
// Use 0 for position/duration - will be updated by position_update events
|
||||
// Preserve the current position when the same track is already loaded
|
||||
// (e.g. resuming from pause, or a spurious PlaybackRestart). Only reset
|
||||
// to 0 when switching to a different track. position_update events keep
|
||||
// it fresh either way, but resetting unconditionally caused the time to
|
||||
// flash to 0:00 on pause/resume.
|
||||
const previous = get(currentMedia);
|
||||
const isSameTrack = previous?.id === currentItem.id;
|
||||
const startPosition = isSameTrack ? get(playbackPosition) : 0;
|
||||
const initialDuration = currentItem.runTimeTicks ? currentItem.runTimeTicks / 10000000 : 0;
|
||||
player.setPlaying(currentItem, 0, initialDuration);
|
||||
player.setPlaying(currentItem, startPosition, initialDuration);
|
||||
|
||||
// Trigger preloading of upcoming tracks in the background
|
||||
preloadUpcomingTracks().catch((e) => {
|
||||
|
||||
+14
-3
@@ -175,10 +175,20 @@ function createMusicStore() {
|
||||
const genres = await repo.getGenres(libraryId);
|
||||
if (genres.length === 0) return;
|
||||
|
||||
const hasCounts = genres.some(g => g.albumCount != null);
|
||||
// Counts are only *useful* if they actually differentiate genres. Some
|
||||
// servers return Fields=ItemCounts but populate every genre with the same
|
||||
// value (or 0), which leaves the list in its original alphabetical order
|
||||
// after "ranking" — so diversity selection seeds on the first genre and we
|
||||
// get a wall of A-genres ("avangard", "avan-gard", ...) with no Rock.
|
||||
// Treat that as "no usable counts" and fall through to the probe path,
|
||||
// which ranks by genres' real album counts instead.
|
||||
const positiveCounts = genres
|
||||
.map(g => g.albumCount)
|
||||
.filter((c): c is number => c != null && c > 0);
|
||||
const hasUsefulCounts = new Set(positiveCounts).size > 1;
|
||||
|
||||
let genreRows: GenreRow[];
|
||||
if (hasCounts) {
|
||||
if (hasUsefulCounts) {
|
||||
// Rank by reported count, pick a diverse subset, then fetch only those.
|
||||
const ranked = [...genres].sort(
|
||||
(a, b) => (b.albumCount ?? 0) - (a.albumCount ?? 0)
|
||||
@@ -188,7 +198,8 @@ function createMusicStore() {
|
||||
row => row.items.length > 0
|
||||
);
|
||||
} else {
|
||||
// No counts (offline, or a server that ignores Fields=ItemCounts).
|
||||
// No usable counts (offline, a server that ignores Fields=ItemCounts,
|
||||
// or one that returns uniform/zero counts — see hasUsefulCounts above).
|
||||
// The genre list is alphabetical, so probing the first N would only
|
||||
// ever surface A-genres. Sample at an even stride across the whole
|
||||
// list instead, so the probe pool spans A→Z; then drop empties, rank
|
||||
|
||||
@@ -167,6 +167,7 @@ describe("playbackMode store", () => {
|
||||
|
||||
expect(mockInvoke).toHaveBeenCalledWith("playback_mode_transfer_to_remote", {
|
||||
sessionId: "session-456",
|
||||
position: null,
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -73,7 +73,10 @@ function createPlaybackModeStore() {
|
||||
* - Polls remote session until track loads
|
||||
* - Stops local playback
|
||||
*/
|
||||
async function transferToRemote(sessionId: string | null | undefined): Promise<void> {
|
||||
async function transferToRemote(
|
||||
sessionId: string | null | undefined,
|
||||
currentPosition?: number,
|
||||
): Promise<void> {
|
||||
console.log("[PlaybackMode] Transferring to remote session:", sessionId);
|
||||
update((s) => ({ ...s, isTransferring: true, transferError: null }));
|
||||
|
||||
@@ -88,10 +91,17 @@ function createPlaybackModeStore() {
|
||||
};
|
||||
|
||||
try {
|
||||
// Pass the caller's current local position so the remote resumes where we
|
||||
// are. The backend can't reliably read this itself: on Linux video plays in
|
||||
// the HTML5 <video> element and the MPV backend reports 0. A null override
|
||||
// falls back to the backend position (correct for Linux audio via MPV).
|
||||
const positionOverride =
|
||||
currentPosition !== undefined && currentPosition > 0 ? currentPosition : null;
|
||||
|
||||
// Rust handles everything - just wait for it to complete
|
||||
// It includes its own 5-second timeout for track loading
|
||||
console.log("[PlaybackMode] About to invoke playback_mode_transfer_to_remote with sessionId:", sessionId);
|
||||
await commands.playbackModeTransferToRemote(sessionId ?? "");
|
||||
console.log("[PlaybackMode] About to invoke playback_mode_transfer_to_remote with sessionId:", sessionId, "position:", positionOverride);
|
||||
await commands.playbackModeTransferToRemote(sessionId ?? "", positionOverride);
|
||||
console.log("[PlaybackMode] Invoke completed successfully");
|
||||
|
||||
if (aborted) {
|
||||
|
||||
Reference in New Issue
Block a user