Discord JS Player流媒体状态异常及播放控制失效问题求助
Discord音乐机器人播放器状态异常问题
问题现象
- 切换到play-dl替代ytdl-core后,启动音频播放时Player状态始终显示为
Buffering,但实际能完整收听歌曲 - 歌曲结束时状态直接从
Playing跳转为Idle - 无法通过pause/resume/stop方法控制播放
状态监听代码
player.on('stateChange', (oldState, newState) => { console.log(oldState.status, newState.status); });
完整命令代码
const { SlashCommandBuilder, hideLinkEmbed } = require('@discordjs/builders'); const { createAudioPlayer, NoSubscriberBehavior, joinVoiceChannel, createAudioResource, subscribe, VoiceConnectionStatus } = require('@discordjs/voice'); const ConfigJSON = require('../config.json'); const ytdl = require('ytdl-core'); const playdl = require('play-dl'); const ytsr = require('ytsr'); const { EmbedBuilder } = require('discord.js'); module.exports = { data: new SlashCommandBuilder() .setName('music') .setDescription('Play a song in a voice channel') .addSubcommand(subcommand => subcommand .setName('play') .setDescription('Play a song in a voice channel') .addStringOption(option => option.setName('query') .setDescription('The song you want to play') .setRequired(true))) .addSubcommand(subcommand => subcommand .setName('pause') .setDescription('Stop the music and leave the voice channel')) .addSubcommand(subcommand => subcommand .setName('resume') .setDescription('Resume the music in the voice channel')) .addSubcommand(subcommand => subcommand .setName('stop') .setDescription('Stop the music and leave the voice channel')), async execute(interaction) { const subcommand = interaction.options.getSubcommand(); const YTapiKey = ConfigJSON.YoutubeAPIKey; const player = new createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play, }, }); if (subcommand === 'play') { interaction.deferReply(); const query = interaction.options.getString('query'); const opts = { maxResults: 1, key: YTapiKey }; const queryResult = await ytsr(query); const Finalquery = String(await queryResult.items[0].url); const streamplay = await playdl.stream(Finalquery); const connection = new joinVoiceChannel({ channelId: interaction.member.voice.channel.id, guildId: interaction.guild.id, adapterCreator: interaction.guild.voiceAdapterCreator, selfDeaf: true, selfMute: false, }); connection.subscribe(player); const resource = createAudioResource(streamplay.stream, { inputType: streamplay.type }); let embed = new EmbedBuilder() .setTitle(queryResult.items[0].title) .setURL(queryResult.items[0].url) .setThumbnail(String(queryResult.items[0].bestThumbnail.url)) .setDescription("Currently playing ....") .setFooter({ text: "Requested by " + interaction.user.username, iconURL: interaction.user.avatarURL() }) .setTimestamp() .setColor(0x00AE86) player.play(resource); await interaction.editReply({ embeds: [embed] }); } if (subcommand === 'pause') { console.log("pause"); console.log(player); await player.pause(); player.on('error', (error) => { console.log(error); }); } if (subcommand === 'resume') { console.log("resume"); console.log(player.state.status); } if (subcommand === 'stop') { console.log("stop"); console.log(player.state.status); } player.on('stateChange', (oldState, newState) => { console.log(oldState.status, newState.status); }); } }
问题根源
每次执行/music命令时,execute函数都会创建全新的AudioPlayer实例。当调用pause/resume时,操作的是新创建的player,而非正在播放歌曲的那个实例,导致状态不匹配、控制失效。
解决方法
将player和voice connection按服务器(guild)存储,确保同一服务器内的所有子命令操作的是同一个实例:
1. 添加全局存储结构
在模块顶部添加两个Map,用于存储每个服务器的播放器和连接:
const { SlashCommandBuilder, hideLinkEmbed } = require('@discordjs/builders'); const { createAudioPlayer, NoSubscriberBehavior, joinVoiceChannel, createAudioResource, VoiceConnectionStatus } = require('@discordjs/voice'); const ConfigJSON = require('../config.json'); const playdl = require('play-dl'); const ytsr = require('ytsr'); const { EmbedBuilder } = require('discord.js'); // 全局存储每个服务器的播放器和连接 const guildPlayers = new Map(); const guildConnections = new Map();
2. 修改play子命令逻辑
检查当前服务器是否已有播放器和连接,没有则创建并存储:
if (subcommand === 'play') { interaction.deferReply(); const query = interaction.options.getString('query'); const guildId = interaction.guild.id; // 获取或创建语音连接 let connection = guildConnections.get(guildId); if (!connection) { connection = joinVoiceChannel({ channelId: interaction.member.voice.channel.id, guildId: guildId, adapterCreator: interaction.guild.voiceAdapterCreator, selfDeaf: true, selfMute: false, }); guildConnections.set(guildId, connection); } // 获取或创建播放器 let player = guildPlayers.get(guildId); if (!player) { player = createAudioPlayer({ behaviors: { noSubscriber: NoSubscriberBehavior.Play, }, }); guildPlayers.set(guildId, player); // 监听状态变化和错误(只绑定一次) player.on('stateChange', (oldState, newState) => { console.log(oldState.status, newState.status); // 播放结束后可选择清理资源 if (newState.status === 'idle') { // connection.destroy(); // guildConnections.delete(guildId); // guildPlayers.delete(guildId); } }); player.on('error', error => { console.error('播放器错误:', error); }); connection.subscribe(player); } // 获取音频流并播放 const opts = { maxResults: 1, key: ConfigJSON.YoutubeAPIKey }; const queryResult = await ytsr(query); const finalQuery = queryResult.items[0].url; const streamplay = await playdl.stream(finalQuery); const resource = createAudioResource(streamplay.stream, { inputType: streamplay.type }); const embed = new EmbedBuilder() .setTitle(queryResult.items[0].title) .setURL(queryResult.items[0].url) .setThumbnail(queryResult.items[0].bestThumbnail.url) .setDescription("正在播放....") .setFooter({ text: `由 ${interaction.user.username} 请求`, iconURL: interaction.user.avatarURL() }) .setTimestamp() .setColor(0x00AE86); player.play(resource); await interaction.editReply({ embeds: [embed] }); }
3. 修改pause/resume/stop子命令
从全局存储中获取当前服务器的播放器,再执行控制操作:
if (subcommand === 'pause') { const player = guildPlayers.get(interaction.guild.id); if (player && player.state.status === 'playing') { player.pause(); await interaction.reply('已暂停播放'); } else { await interaction.reply('当前没有正在播放的歌曲'); } } if (subcommand === 'resume') { const player = guildPlayers.get(interaction.guild.id); if (player && player.state.status === 'paused') { player.unpause(); await interaction.reply('已继续播放'); } else { await interaction.reply('当前没有暂停的歌曲'); } } if (subcommand === 'stop') { const guildId = interaction.guild.id; const player = guildPlayers.get(guildId); const connection = guildConnections.get(guildId); if (player) { player.stop(); guildPlayers.delete(guildId); } if (connection) { connection.destroy(); guildConnections.delete(guildId); } await interaction.reply('已停止播放并退出语音频道'); }
额外说明
- 可根据需求添加队列功能,避免重复播放同一首歌
- 播放结束后清理资源的逻辑可根据机器人使用场景调整,若需支持连续播放则不要立即删除player和connection
内容的提问来源于stack exchange,提问作者EquinoXx
相关产品推荐
相关产品推荐

