···298298 (a) => existingMap.get(a.id)?.isPreload !== a.isPreload,
299299 );
300300301301- if (hasNewIds || hasPreloadChanges) {
301301+ const hasUrlChanges = resolvedAudio.some(
302302+ (a) => existingMap.get(a.id)?.url !== a.url,
303303+ );
304304+305305+ if (hasNewIds || hasPreloadChanges || hasUrlChanges) {
302306 this.#items.value = resolvedAudio;
307307+ }
308308+309309+ // When only the URL changed for an existing item (e.g. tab leadership handoff invalidated
310310+ // a blob URL), the same <de-audio-item> element is reused via `keyed`. lit-html will
311311+ // update <source src> but the browser won't reload on its own — call audio.load() if the
312312+ // element hasn't successfully loaded yet so it picks up the fresh URL.
313313+ if (hasUrlChanges && !hasNewIds) {
314314+ for (const a of resolvedAudio) {
315315+ if (existingMap.has(a.id) && existingMap.get(a.id)?.url !== a.url) {
316316+ this.#withAudioNode(a.id, (audio) => {
317317+ if (audio.readyState === 0 || audio.error) audio.load();
318318+ });
319319+ }
320320+ }
303321 }
304322305323 if (args.play) this.play(args.play);