Five faults found while auditing the play/pause and position path for a desktop report of the pause icon showing over a seek bar that was not moving. They are one commit because they are one file's worth of tangled state, and two of them do not compile apart. The finished callback did not know which chain it came from. It is dispatched as a goroutine from the beep callback and then queues for p.mu, so a user pressing Next in the last second of a track had it wake up holding the lock for a player that had loaded something else -- and rewind it, stop it, and hand a stale finish to the queue's auto-advance. updateStreamers now stamps a chainID and the callback carries the one it was registered with. (#123) It also emitted PlaybackFinished and PlaybackStateChanged(stopped) *after* releasing p.mu, alone in this file, so a Play() taking the lock in that gap emitted `playing` first and the stale `stopped` landed last -- the button showing play over a track that was audibly running. Both emits are back under the lock. (#123) A source that failed mid-track was reported to the queue as a natural end, so a broken file auto-advanced in silence and was counted as played. The handler takes the reason now: the player cannot name the track, because the metadata is the queue's, so the queue emits PlaybackFailed and skips recording the play. (#123) p.format was assigned once, in the constructor, to the *speaker's* rate, and never again -- so it claimed 44.1 kHz for every file. The replay-after-finish path resamples from it, meaning a finished track played a second time was resampled from a rate the decoder never produced: audibly wrong speed and pitch, and the length and position fallbacks wrong with it. The fixtures are 22050 Hz, which is what lets a test see this at all. (#124) p.trackLengthMs was written only when the database had a row and cleared only by UnloadTrack, so a file with no row inherited the previous track's duration -- and every position report is scaled by it, so the bar reported one track's progress on another's scale. (#125) Queue.OnPlaybackFinished indexed q.tracks[currentIndex] having checked only that the queue was non-empty. currentIndex is -1 whenever the queue has been exhausted, and onQueueExhausted deliberately leaves the finished track loaded -- so playing it from there and letting it end panicked, on a goroutine with no caller to recover it. (#126) The position readers guarded the decoder with the speaker lock, which the read-ahead goroutine has no reason to hold and never takes -- so Position() raced readAhead's Stream() on every position emit, once a second for the whole of playback. srcMu is the lock that excludes that goroutine, and taking it naively deadlocks, because seekLocked already holds it and then emits the landing position from inside that region. seekSourceLocked is that region extracted, so the lock is released before anything is emitted. Found by the race detector, via the test added here for the chain guard: the existing suite never loads a file outside the integration guard, so make test was green over it. (#127) OnPlaybackFinished picks up //wails:ignore along with its error parameter: v3's generator segfaults on a bound method taking an error, and this was never IPC. That removes a binding the frontend could have called to force an auto-advance. Closes #123 Closes #124 Closes #125 Closes #126 Closes #127
196 lines
6.6 KiB
TypeScript
196 lines
6.6 KiB
TypeScript
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
|
// This file is automatically generated. DO NOT EDIT
|
|
|
|
/**
|
|
* Queue manages an ordered list of tracks for playback.
|
|
* @module
|
|
*/
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore: Unused imports
|
|
import { Call as $Call, CancellablePromise as $CancellablePromise } from "@wailsio/runtime";
|
|
|
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
// @ts-ignore: Unused imports
|
|
import * as $models from "./models.js";
|
|
|
|
/**
|
|
* AddTrack appends a track to the end of the queue.
|
|
* If the queue was empty, it loads the added track in a paused state.
|
|
*/
|
|
export function AddTrack(filePath: string): $CancellablePromise<void> {
|
|
return $Call.ByID(3566864127, filePath);
|
|
}
|
|
|
|
/**
|
|
* AddTracks appends multiple tracks to the end of the queue.
|
|
* If the queue was empty, it loads the first added track in a paused state.
|
|
*/
|
|
export function AddTracks(filePaths: string[] | null): $CancellablePromise<void> {
|
|
return $Call.ByID(980962916, filePaths);
|
|
}
|
|
|
|
/**
|
|
* Clear removes all tracks from the queue, stops playback, and
|
|
* resets the queue state. It persists the cleared state and
|
|
* notifies the frontend.
|
|
*/
|
|
export function Clear(): $CancellablePromise<void> {
|
|
return $Call.ByID(4166243766);
|
|
}
|
|
|
|
/**
|
|
* CompactAfterLibraryRemoval reloads queue state from the database
|
|
* after a library removal has cascade-deleted queue_tracks rows.
|
|
* It resets currentIndex to 0 (or -1 if empty), clears shuffleOrder,
|
|
* unloads the current track if it was removed, and emits QueueChanged.
|
|
*/
|
|
export function CompactAfterLibraryRemoval(): $CancellablePromise<void> {
|
|
return $Call.ByID(3620379645);
|
|
}
|
|
|
|
/**
|
|
* CycleRepeat cycles through repeat modes: off -> all -> one -> off.
|
|
*/
|
|
export function CycleRepeat(): $CancellablePromise<void> {
|
|
return $Call.ByID(3510519482);
|
|
}
|
|
|
|
/**
|
|
* EmitCurrentState emits the current queue state to the frontend.
|
|
* This is called after the frontend DOM is ready.
|
|
*/
|
|
export function EmitCurrentState(): $CancellablePromise<void> {
|
|
return $Call.ByID(1245022280);
|
|
}
|
|
|
|
/**
|
|
* GetState returns the current queue state for the frontend.
|
|
*/
|
|
export function GetState(): $CancellablePromise<$models.State> {
|
|
return $Call.ByID(3472178852);
|
|
}
|
|
|
|
/**
|
|
* InsertNext inserts a track right after the currently playing track.
|
|
* If the queue was empty, it loads the inserted track in a paused state.
|
|
*/
|
|
export function InsertNext(filePath: string): $CancellablePromise<void> {
|
|
return $Call.ByID(1498463573, filePath);
|
|
}
|
|
|
|
/**
|
|
* InsertNextTracks inserts multiple tracks as a contiguous block after the current track.
|
|
* If the queue was empty, it loads the first inserted track in a paused state.
|
|
*/
|
|
export function InsertNextTracks(filePaths: string[] | null): $CancellablePromise<void> {
|
|
return $Call.ByID(2824705517, filePaths);
|
|
}
|
|
|
|
/**
|
|
* InsertTracksAt inserts multiple tracks at the given index.
|
|
* If the queue was empty, it loads the first inserted track in a paused state.
|
|
*/
|
|
export function InsertTracksAt(filePaths: string[] | null, index: number): $CancellablePromise<void> {
|
|
return $Call.ByID(2550945669, filePaths, index);
|
|
}
|
|
|
|
/**
|
|
* MoveQueueTracks moves tracks at the given indices to a new position
|
|
* as a contiguous block. The toIndex is the target position in the
|
|
* original (pre-move) array.
|
|
*/
|
|
export function MoveQueueTracks(fromIndices: number[] | null, toIndex: number): $CancellablePromise<void> {
|
|
return $Call.ByID(2149753655, fromIndices, toIndex);
|
|
}
|
|
|
|
/**
|
|
* Next advances to the next track. If the player was paused, the next
|
|
* track is loaded but not played. In RepeatOne mode, the current track
|
|
* is replayed instead of advancing.
|
|
*/
|
|
export function Next(): $CancellablePromise<void> {
|
|
return $Call.ByID(1968784044);
|
|
}
|
|
|
|
/**
|
|
* Play handles a play request by either resuming the current track or
|
|
* starting playback from the beginning of the queue. When a track is
|
|
* already active (currentIndex != -1) the player is told to resume;
|
|
* otherwise playback starts from the first track (or a random one when
|
|
* shuffle is enabled).
|
|
*/
|
|
export function Play(): $CancellablePromise<void> {
|
|
return $Call.ByID(3197379127);
|
|
}
|
|
|
|
/**
|
|
* PlayIndex jumps to and plays the track at the given index.
|
|
*/
|
|
export function PlayIndex(index: number): $CancellablePromise<void> {
|
|
return $Call.ByID(200440797, index);
|
|
}
|
|
|
|
/**
|
|
* Previous goes to the previous track (or restarts current if >3s in).
|
|
* If the player was paused, the track is loaded but not played.
|
|
* In RepeatOne mode, the current track is replayed instead of navigating.
|
|
*/
|
|
export function Previous(): $CancellablePromise<void> {
|
|
return $Call.ByID(708701476);
|
|
}
|
|
|
|
/**
|
|
* RemoveTrack removes a track at the given position from the queue.
|
|
*/
|
|
export function RemoveTrack(position: number): $CancellablePromise<void> {
|
|
return $Call.ByID(6811080, position);
|
|
}
|
|
|
|
/**
|
|
* RemoveTracks removes multiple tracks at the given positions from the queue.
|
|
* Positions are deduplicated, validated, and removed in descending order so
|
|
* that indices remain stable during removal.
|
|
*/
|
|
export function RemoveTracks(positions: number[] | null): $CancellablePromise<void> {
|
|
return $Call.ByID(1587232097, positions);
|
|
}
|
|
|
|
/**
|
|
* RestoreState loads the queue state from the database.
|
|
*/
|
|
export function RestoreState(): $CancellablePromise<void> {
|
|
return $Call.ByID(1652105446);
|
|
}
|
|
|
|
/**
|
|
* SaveState persists the queue state to the database. Unlike every
|
|
* other write here it waits for the writer: its callers are shutdown
|
|
* and the tests, both of which need the row to exist on return.
|
|
*/
|
|
export function SaveState(): $CancellablePromise<void> {
|
|
return $Call.ByID(2913531465);
|
|
}
|
|
|
|
/**
|
|
* SetQueue replaces the entire queue with new tracks and starts playing.
|
|
* When shuffleStart is true and shuffle mode is active, a random first
|
|
* track is chosen instead of the one at startIndex. This is intended for
|
|
* "Play All" type actions where no specific track was selected.
|
|
* It uses a two-phase approach: the first batch of tracks (up to
|
|
* initialBatchSize) is resolved immediately so playback begins and the
|
|
* queue panel is populated without delay. The remaining tracks are then
|
|
* resolved in the background. A generation counter ensures stale
|
|
* background work is discarded if SetQueue is called again.
|
|
*/
|
|
export function SetQueue(filePaths: string[] | null, startIndex: number, shuffleStart: boolean, source: $models.Source): $CancellablePromise<void> {
|
|
return $Call.ByID(1990450094, filePaths, startIndex, shuffleStart, source);
|
|
}
|
|
|
|
/**
|
|
* ToggleShuffle toggles shuffle mode on/off.
|
|
*/
|
|
export function ToggleShuffle(): $CancellablePromise<void> {
|
|
return $Call.ByID(612647562);
|
|
}
|