{"record":{"id":"e3a585d560387b1d","repo":"discordjs/discord.js","slug":"cannot-play-a-resource-that-has-already-ended","errorCode":null,"errorMessage":"Cannot play a resource that has already ended.","messagePattern":"Cannot play a resource that has already ended\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/voice/src/audio/AudioPlayer.ts","lineNumber":380,"sourceCode":"\t\tthis.debug?.(`state change:\\nfrom ${stringifyState(oldState)}\\nto ${stringifyState(newState)}`);\n\t}\n\n\t/**\n\t * Plays a new resource on the player. If the player is already playing a resource, the existing resource is destroyed\n\t * (it cannot be reused, even in another player) and is replaced with the new resource.\n\t *\n\t * @remarks\n\t * The player will transition to the Playing state once playback begins, and will return to the Idle state once\n\t * playback is ended.\n\t *\n\t * If the player was previously playing a resource and this method is called, the player will not transition to the\n\t * Idle state during the swap over.\n\t * @param resource - The resource to play\n\t * @throws Will throw if attempting to play an audio resource that has already ended, or is being played by another player\n\t */\n\tpublic play<Metadata>(resource: AudioResource<Metadata>) {\n\t\tif (resource.ended) {\n\t\t\tthrow new Error('Cannot play a resource that has already ended.');\n\t\t}\n\n\t\tif (resource.audioPlayer) {\n\t\t\tif (resource.audioPlayer === this) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthrow new Error('Resource is already being played by another audio player.');\n\t\t}\n\n\t\tresource.audioPlayer = this;\n\n\t\t// Attach error listeners to the stream that will propagate the error and then return to the Idle\n\t\t// state if the resource is still being used.\n\t\tconst onStreamError = (error: Error) => {\n\t\t\tif (this.state.status !== AudioPlayerStatus.Idle) {\n\t\t\t\tthis.emit('error', new AudioPlayerError(error, this.state.resource));\n\t\t\t}","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/discordjs/discord.js/blob/a81ed8a306d37fdc746e26a634b6a42164ba2c8c/packages/voice/src/audio/AudioPlayer.ts#L362-L398","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Create a fresh AudioResource for each playback instead of reusing one","Cache the source (file path/URL/Readable) and call createAudioResource() again per play","If looping is intended, use the 'loop after finish' pattern: subscribe to the player's Idle state and create a new resource","Check resource.ended before calling play()"],"exampleFix":"// before\nconst resource = createAudioResource('song.mp3');\nplayer.play(resource);\nplayer.on('idle', () => player.play(resource)); // throws second time\n// after\nplayer.on('idle', () => player.play(createAudioResource('song.mp3')));","handlingStrategy":"validation","validationCode":"function canPlay(resource) { return resource && !resource.ended; }\nif (canPlay(res)) player.play(res); else res = createAudioResource(src);","typeGuard":"function isPlayable<T>(r: AudioResource<T> | undefined): r is AudioResource<T> { return !!r && !r.ended; }","tryCatchPattern":"try { player.play(res); } catch (e) { if (e.message.includes('already ended')) res = createAudioResource(src); else throw e; }","preventionTips":["Never reuse AudioResource instances; recreate per playback","Store the source (path/URL) not the resource in caches/queues","Rebuild resources inside the player 'idle' handler for looping","Assert !resource.ended before play in dev builds"],"tags":["audio","stream-lifecycle","discord-voice"],"backgroundTag":"audio-resource-already-ended","analyzedSha":"a81ed8a306d37fdc746e26a634b6a42164ba2c8c","analyzedAt":"2026-08-30T04:07:22.193Z","schemaVersion":2},"datasetVersion":"2026-08-30T08:17:16.595Z"}