discordjs/discord.js · error · Error

Cannot play a resource that has already ended.

Error message

Cannot play a resource that has already ended.

What it means

AudioPlayer.play() refuses to play an AudioResource whose underlying stream has already finished ('ended'). Once a resource is consumed to completion it cannot be replayed, because the stream data is gone; the library throws instead of silently doing nothing.

Source

Thrown at packages/voice/src/audio/AudioPlayer.ts:380

		this.debug?.(`state change:\nfrom ${stringifyState(oldState)}\nto ${stringifyState(newState)}`);
	}

	/**
	 * Plays a new resource on the player. If the player is already playing a resource, the existing resource is destroyed
	 * (it cannot be reused, even in another player) and is replaced with the new resource.
	 *
	 * @remarks
	 * The player will transition to the Playing state once playback begins, and will return to the Idle state once
	 * playback is ended.
	 *
	 * If the player was previously playing a resource and this method is called, the player will not transition to the
	 * Idle state during the swap over.
	 * @param resource - The resource to play
	 * @throws Will throw if attempting to play an audio resource that has already ended, or is being played by another player
	 */
	public play<Metadata>(resource: AudioResource<Metadata>) {
		if (resource.ended) {
			throw new Error('Cannot play a resource that has already ended.');
		}

		if (resource.audioPlayer) {
			if (resource.audioPlayer === this) {
				return;
			}

			throw new Error('Resource is already being played by another audio player.');
		}

		resource.audioPlayer = this;

		// Attach error listeners to the stream that will propagate the error and then return to the Idle
		// state if the resource is still being used.
		const onStreamError = (error: Error) => {
			if (this.state.status !== AudioPlayerStatus.Idle) {
				this.emit('error', new AudioPlayerError(error, this.state.resource));
			}

View on GitHub (pinned to a81ed8a306)

Solutions

  1. Create a fresh AudioResource for each playback instead of reusing one
  2. Cache the source (file path/URL/Readable) and call createAudioResource() again per play
  3. If looping is intended, use the 'loop after finish' pattern: subscribe to the player's Idle state and create a new resource
  4. Check resource.ended before calling play()

Example fix

// before
const resource = createAudioResource('song.mp3');
player.play(resource);
player.on('idle', () => player.play(resource)); // throws second time
// after
player.on('idle', () => player.play(createAudioResource('song.mp3')));
Defensive patterns

Strategy: validation

Validate before calling

function canPlay(resource) { return resource && !resource.ended; }
if (canPlay(res)) player.play(res); else res = createAudioResource(src);

Type guard

function isPlayable<T>(r: AudioResource<T> | undefined): r is AudioResource<T> { return !!r && !r.ended; }

Try / catch

try { player.play(res); } catch (e) { if (e.message.includes('already ended')) res = createAudioResource(src); else throw e; }

Prevention

When it happens

Trigger: Calling player.play(resource) on a resource that already finished playing (resource.ended === true), e.g. reusing a single AudioResource created via createAudioResource() for a second play() call.

Common situations: Replaying a sound effect or song from a cached AudioResource; looping a track by calling play() again with the same resource; playing the same resource on two players sequentially.

Related errors


AI-assisted analysis of discordjs/discord.js@a81ed8a306 (2026-08-30). Data as JSON: /api/errors/e3a585d560387b1d. Report an issue: GitHub.