Keep fallback active while adding queued media
This commit is contained in:
@@ -76,6 +76,7 @@ export class MediaPlayer {
|
||||
private activeEntryId: number | null = null;
|
||||
private activeUrl: string | null = null;
|
||||
private fallbackSuppressedUntilStart = false;
|
||||
private fallbackPendingUrl: string | null = null;
|
||||
private browserUrl: string | null = null;
|
||||
private browserFallback = new ChromiumFallback((url) => {
|
||||
if (this.browserUrl === url) {
|
||||
@@ -384,7 +385,10 @@ export class MediaPlayer {
|
||||
}
|
||||
|
||||
public async append(url: string) {
|
||||
await this.loadFile(url, "append-play");
|
||||
const mode = this.browserUrl || this.fallbackPendingUrl || this.browserFallback.isRunning
|
||||
? "append"
|
||||
: "append-play";
|
||||
await this.loadFile(url, mode);
|
||||
}
|
||||
|
||||
public async replace(url: string) {
|
||||
@@ -740,6 +744,7 @@ export class MediaPlayer {
|
||||
const hadBrowser = !!this.browserUrl || this.browserFallback.isRunning;
|
||||
this.activeAttemptGeneration++;
|
||||
this.fallbackSuppressedUntilStart = false;
|
||||
this.fallbackPendingUrl = null;
|
||||
this.browserUrl = null;
|
||||
if (hadBrowser) void this.browserFallback.stop();
|
||||
const entryId = typeof response.playlist_entry_id === "number"
|
||||
@@ -805,6 +810,7 @@ export class MediaPlayer {
|
||||
if (this.fallbackCandidates.has(file) &&
|
||||
(entryId === this.activeEntryId || (this.activeEntryId === null && !this.fallbackSuppressedUntilStart)) &&
|
||||
attemptGeneration === this.activeAttemptGeneration) {
|
||||
this.fallbackPendingUrl = file;
|
||||
void this.startBrowserFallback(file, attemptGeneration, entryId);
|
||||
}
|
||||
}
|
||||
@@ -851,6 +857,10 @@ export class MediaPlayer {
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Failed to start Chromium fallback for ${url}:`, error);
|
||||
} finally {
|
||||
if (generation === this.activeAttemptGeneration && this.fallbackPendingUrl === url) {
|
||||
this.fallbackPendingUrl = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -860,6 +870,7 @@ export class MediaPlayer {
|
||||
this.activeEntryId = null;
|
||||
this.activeUrl = null;
|
||||
this.currentFile = null;
|
||||
this.fallbackPendingUrl = null;
|
||||
this.browserUrl = null;
|
||||
await this.browserFallback.stop();
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ const { MediaPlayer } = require("../build/MediaPlayer");
|
||||
const makePlayer = (url = "https://example.test/watch?v=abc", playlist = [{ id: 42, filename: url }]) => {
|
||||
const player = Object.create(MediaPlayer.prototype);
|
||||
const starts = [];
|
||||
const commands = [];
|
||||
let stops = 0;
|
||||
const state = {
|
||||
eventSubscribers: [],
|
||||
@@ -38,13 +39,14 @@ const makePlayer = (url = "https://example.test/watch?v=abc", playlist = [{ id:
|
||||
player.browserFallback.isRunning = false;
|
||||
},
|
||||
};
|
||||
player.writeCommand = async (command) => {
|
||||
player.writeCommand = async (command, args) => {
|
||||
commands.push([command, args]);
|
||||
if (command === "loadfile") return { error: "success" };
|
||||
if (command === "get_property") return { data: playlist };
|
||||
return { error: "success" };
|
||||
};
|
||||
player.fetchMetadataAndNotify = async () => undefined;
|
||||
return { player, starts, get stops() { return stops; }, set playlist(value) { playlist = value; } };
|
||||
return { player, starts, commands, get stops() { return stops; }, set playlist(value) { playlist = value; } };
|
||||
};
|
||||
|
||||
const begin = (player, entryId = 42) => player.receiveData(`${JSON.stringify({ event: "start-file", playlist_entry_id: entryId })}\n`);
|
||||
@@ -84,12 +86,13 @@ test("actual MPV error opens the exact original URL and mirrors indeterminate st
|
||||
test("queue append leaves active A eligible when queued B is added", async () => {
|
||||
const a = "https://example.test/a";
|
||||
const b = "https://example.test/b";
|
||||
const { player, starts } = makePlayer(a, [{ id: 42, filename: a }]);
|
||||
const { player, starts, commands } = makePlayer(a, [{ id: 42, filename: a }]);
|
||||
player.fallbackCandidates.add(a);
|
||||
player.entryCandidates.set(42, a);
|
||||
begin(player, 42);
|
||||
await player.append(b);
|
||||
await settle();
|
||||
assert.equal(commands.filter(([name]) => name === "loadfile").at(-1)[1][1], "append-play");
|
||||
send(player, { reason: "error", file_error: "failed A" }, 42);
|
||||
await settle();
|
||||
assert.deepEqual(starts, [a]);
|
||||
@@ -97,6 +100,22 @@ test("queue append leaves active A eligible when queued B is added", async () =>
|
||||
assert.equal(player.fallbackCandidates.has(b), true);
|
||||
});
|
||||
|
||||
test("queue append during fallback startup keeps browser A open without starting B", async () => {
|
||||
const a = "https://example.test/a";
|
||||
const b = "https://example.test/b";
|
||||
const { player, starts, commands } = makePlayer(a, [{ id: 42, filename: a }]);
|
||||
player.fallbackCandidates.add(a);
|
||||
player.entryCandidates.set(42, a);
|
||||
begin(player, 42);
|
||||
send(player, { reason: "error", file_error: "failed A" }, 42);
|
||||
await player.append(b);
|
||||
await settle();
|
||||
const load = commands.filter(([name]) => name === "loadfile").at(-1);
|
||||
assert.equal(load[1][1], "append");
|
||||
assert.deepEqual(starts, [a]);
|
||||
assert.equal(player.browserUrl, a);
|
||||
});
|
||||
|
||||
test("append then skipTo keeps the selected entry eligible for its actual MPV error", async () => {
|
||||
const url = "https://example.test/shared-original?x=1&y=2";
|
||||
const { player, starts } = makePlayer(url, [{ id: 42, filename: url }]);
|
||||
|
||||
Reference in New Issue
Block a user