RocketChat/Rocket.Chat · warning

Failed to resume playback after URL recovery:

Error message

Failed to resume playback after URL recovery:

What it means

Raised inside the useReloadOnError media hook for audio/video file attachments: when the signed media URL expired, the hook fetched a fresh redirect URL, swapped node.src, and on 'canplay' called node.play() to resume at the previous position. The play() promise rejected (browser autoplay policy or a still-invalid URL), the rejection is caught and logged, and playback stays paused even though the stream itself recovered.

Source

Thrown at apps/meteor/client/components/message/content/attachments/file/hooks/useReloadOnError.ts:112

		}

		const wasPlaying = !node.paused;
		const { currentTime } = node;

		try {
			const { redirectUrl: newUrl, expires: newExpiresAt } = await getRedirectURLInfo(url);
			setExpiresAt(newExpiresAt);
			node.src = newUrl || url;

			const onCanPlay = async () => {
				node.removeEventListener('canplay', onCanPlay);

				node.currentTime = currentTime;
				if (wasPlaying) {
					try {
						await node.play();
					} catch (playError) {
						console.warn('Failed to resume playback after URL recovery:', playError);
					} finally {
						isRecovering.current = false;
					}
				}
			};

			const onMetaDataLoaded = () => {
				node.removeEventListener('loadedmetadata', onMetaDataLoaded);
				isRecovering.current = false;
				cleanup?.();
			};

			node.addEventListener('canplay', onCanPlay, { once: true });
			node.addEventListener('loadedmetadata', onMetaDataLoaded, { once: true });
			node.load();
		} catch (err) {
			console.error('Error during URL recovery:', err);
			isRecovering.current = false;

View on GitHub (pinned to b2c16d5842)

Solutions

  1. Catch NotAllowedError specifically and retry muted - muted autoplay is generally allowed - then surface an unmute control
  2. Otherwise render a resume/play button so a single user gesture resumes at node.currentTime
  3. Pre-emptively refresh the URL before expiresAt instead of waiting for the error/stalled event
  4. Verify the redirect endpoint still returns a fresh redirectUrl for the file when recovery runs

Example fix

// before
try {
  await node.play();
} catch (playError) {
  console.warn('Failed to resume playback after URL recovery:', playError);
}

// after
try {
  await node.play();
} catch (playError) {
  if ((playError as DOMException).name === 'NotAllowedError') {
    node.muted = true;
    try {
      await node.play(); // muted autoplay is permitted
      showUnmuteIndicator(node);
      return;
    } catch { /* fall through */ }
  }
  showResumeButton(node, currentTime); // one user gesture resumes playback
}
Defensive patterns

Strategy: try-catch

Type guard

const isAutoplayBlocked = (error: unknown): error is DOMException =>
  error instanceof DOMException && error.name === 'NotAllowedError';

Try / catch

try {
  await node.play();
} catch (playError) {
  if (isAutoplayBlocked(playError)) {
    node.muted = true; // muted autoplay is permitted
    await node.play().catch(() => showResumeButton(node));
  } else {
    showResumeButton(node); // NotSupportedError etc: URL still bad, let the user retry
  }
}

Prevention

When it happens

Trigger: Autoplay policy blocks programmatic play() without a user gesture after the source was reloaded (NotAllowedError in Chrome/Safari/Firefox), or the recovered URL is again invalid/expired (NotSupportedError). Only fires when the media was playing when the error/stall happened (wasPlaying true).

Common situations: Long-running tabs where S3 presigned/upload URLs expire; backgrounded browsers refusing unmuted autoplay; users who hit play, switched tabs, and the auto-recovery ran without a gesture; corporate browsers with strict autoplay settings.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/73bafd33d5136a07. Report an issue: GitHub.