Description:
I'm getting highly variable and sometimes extremely long seek times anywhere from about 50ms delay to several seconds to seek without any distinguishable pattern (no correlation with seekΔ, concurrent streams, song, or seemingly anything else). Sending the seek action only takes a few µs always ranging from around 1.4µs to 2.4µs but the time between that being completed and playback starting again seems to often be quite long. The main time sink seems to be between firing the found format marker event and firing the playable event. Though sometimes it seems to be more between the preparing event and the format marker event. But most of the time it is the former. I'm also not getting any CPU or memory spikes while this is happening.
System:
OS: Ubuntu Linux
Architecture: x86_64 Intel
Specs: 8 Cores, 8GB of Memory
Cargo config:
The executable was built in release mode with the following changes in the Cargo.toml file:
lto = "fat"
codegen-units = 1
Steps to reproduce:
Use serenity/voice example and replace main.rs with:
//! Requires the "client", "standard_framework", and "voice" features be enabled in your
//! Cargo.toml, like so:
//!
//! ```toml
//! [dependencies.serenity]
//! git = "https://github.com/serenity-rs/serenity.git"
//! features = ["client", "standard_framework", "voice"]
//! ```
use std::env;
use std::io::SeekFrom;
use std::time::Duration;
// This trait adds the register_songbird and register_songbird_with methods
// to the client builder below, making it easy to install this voice client.
// The voice client can be retrieved in any command using songbird::get(ctx).await.
use songbird::SerenityInit;
// Event related imports to detect track creation failures.
use songbird::events::{Event, EventContext, EventHandler as VoiceEventHandler, TrackEvent};
// To turn user URLs into playable audio, we'll use yt-dlp.
use songbird::input::YoutubeDl;
// YtDl requests need an HTTP client to operate -- we'll create and store our own.
use reqwest::Client as HttpClient;
// Import the Context to handle commands.
use serenity::client::Context;
use serenity::{
async_trait,
client::{Client, EventHandler},
framework::{
standard::{
macros::{command, group},
Args, CommandResult,
},
StandardFramework,
},
model::{channel::Message, gateway::Ready},
prelude::{GatewayIntents, TypeMapKey},
Result as SerenityResult,
};
use serenity::futures::AsyncSeekExt;
use songbird::tracks::TrackHandle;
struct HttpKey;
struct TrackHandleKey;
impl TypeMapKey for HttpKey {
type Value = HttpClient;
}
impl TypeMapKey for TrackHandleKey {
type Value = TrackHandle;
}
struct Handler;
[async_trait]
impl EventHandler for Handler {
async fn ready(&self, _: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
// Configure the client with your Discord bot token in the environment.
let token = env::var("DISCORD_TOKEN").expect("Expected a token in the environment");
let framework = StandardFramework::new().group(&GENERAL_GROUP);
framework.configure(|c| c.prefix("~"));
let intents = GatewayIntents::non_privileged() | GatewayIntents::MESSAGE_CONTENT;
let mut client = Client::builder(&token, intents)
.event_handler(Handler)
.framework(framework)
.register_songbird()
// We insert our own HTTP client here to make use of in
// `~play`. If we wanted, we could supply cookies and auth
// details ahead of time.
//
// Generally, we don't want to make a new Client for every request!
.type_map_insert::<HttpKey>(HttpClient::new())
.await
.expect("Err creating client");
tokio::spawn(async move {
let _ = client
.start()
.await
.map_err(|why| println!("Client ended: {:?}", why));
});
let _signal_err = tokio::signal::ctrl_c().await;
println!("Received Ctrl-C, shutting down.");
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let handler_lock = match manager.get(guild_id) {
Some(handler) => handler,
None => {
check_msg(msg.reply(ctx, "Not in a voice channel").await);
return Ok(());
},
};
let mut handler = handler_lock.lock().await;
if handler.is_deaf() {
check_msg(msg.channel_id.say(&ctx.http, "Already deafened").await);
} else {
if let Err(e) = handler.deafen(true).await {
check_msg(
msg.channel_id
.say(&ctx.http, format!("Failed: {:?}", e))
.await,
);
}
check_msg(msg.channel_id.say(&ctx.http, "Deafened").await);
}
Ok(())
}
[command]
[only_in(guilds)]
async fn join(ctx: &Context, msg: &Message) -> CommandResult {
let (guild_id, channel_id) = {
let guild = msg.guild(&ctx.cache).unwrap();
let channel_id = guild
.voice_states
.get(&msg.author.id)
.and_then(|voice_state| voice_state.channel_id);
(guild.id, channel_id)
};
let connect_to = match channel_id {
Some(channel) => channel,
None => {
check_msg(msg.reply(ctx, "Not in a voice channel").await);
return Ok(());
},
};
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Ok(handler_lock) = manager.join(guild_id, connect_to).await {
// Attach an event handler to see notifications of all track errors.
let mut handler = handler_lock.lock().await;
handler.add_global_event(TrackEvent::Error.into(), TrackErrorNotifier);
}
Ok(())
}
struct TrackErrorNotifier;
[async_trait]
impl VoiceEventHandler for TrackErrorNotifier {
async fn act(&self, ctx: &EventContext<'_>) -> Option {
if let EventContext::Track(track_list) = ctx {
for (state, handle) in *track_list {
println!(
"Track {:?} encountered an error: {:?}",
handle.uuid(),
state.playing
);
}
}
let pos = match args.single::<u64>() {
Ok(url) => url,
Err(_) => {
check_msg(
msg.channel_id
.say(&ctx.http, "Must provide a seek position in seconds")
.await,
);
return Ok(());
},
};
let track_handle = ctx.data.write().await.get::<TrackHandleKey>().cloned().unwrap();
let _ = track_handle.seek(Duration::from_secs(pos));
Ok(())
async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
let url = match args.single::() {
Ok(url) => url,
Err(_) => {
check_msg(
msg.channel_id
.say(&ctx.http, "Must provide a URL to a video or audio")
.await,
);
return Ok(());
},
};
if !url.starts_with("http") {
check_msg(
msg.channel_id
.say(&ctx.http, "Must provide a valid URL")
.await,
);
return Ok(());
}
let guild_id = msg.guild_id.unwrap();
let http_client = {
let data = ctx.data.read().await;
data.get::<HttpKey>()
.cloned()
.expect("Guaranteed to exist in the typemap.")
};
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
let src = YoutubeDl::new(http_client, url);
let track_handle = handler.play_input(src.into());
ctx.data.write().await.insert::<TrackHandleKey>(track_handle);
check_msg(msg.channel_id.say(&ctx.http, "Playing song").await);
} else {
check_msg(
msg.channel_id
.say(&ctx.http, "Not in a voice channel to play in")
.await,
);
}
Ok(())
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
if let Err(e) = handler.mute(false).await {
check_msg(
msg.channel_id
.say(&ctx.http, format!("Failed: {:?}", e))
.await,
);
}
check_msg(msg.channel_id.say(&ctx.http, "Unmuted").await);
} else {
check_msg(
msg.channel_id
.say(&ctx.http, "Not in a voice channel to unmute in")
.await,
);
}
Ok(())
}
/// Checks that a message successfully sent; if not, then logs why to stdout.
fn check_msg(result: SerenityResult) {
if let Err(why) = result {
println!("Error sending message: {:?}", why);
}
}
2. Try a few different seek commands and observe delay
Songbird version:
next
branch - commit: 0044728Rust version (
rustc -V
): 1.69Serenity/Twilight version: serenity
next
branchDescription: I'm getting highly variable and sometimes extremely long seek times anywhere from about 50ms delay to several seconds to seek without any distinguishable pattern (no correlation with seekΔ, concurrent streams, song, or seemingly anything else). Sending the
seek
action only takes a few µs always ranging from around 1.4µs to 2.4µs but the time between that being completed and playback starting again seems to often be quite long. The main time sink seems to be between firing the found format marker event and firing the playable event. Though sometimes it seems to be more between the preparing event and the format marker event. But most of the time it is the former. I'm also not getting any CPU or memory spikes while this is happening.System: OS: Ubuntu Linux Architecture: x86_64 Intel Specs: 8 Cores, 8GB of Memory
Tracks tested: https://www.ee.columbia.edu/~dpwe/sounds/music/africa-toto.wav https://www.kozco.com/tech/c304-2.wav https://firmware-repo-esp32.s3.us-west-1.amazonaws.com/The+Joy+And+Agony+Of+Travel+(On+A+Train+Version).wav https://www2.cs.uic.edu/~i101/SoundFiles/ImperialMarch60.wav
Cargo config: The executable was built in release mode with the following changes in the
Cargo.toml
file:Steps to reproduce:
serenity/voice
example and replacemain.rs
with:// This trait adds the
register_songbird
andregister_songbird_with
methods // to the client builder below, making it easy to install this voice client. // The voice client can be retrieved in any command usingsongbird::get(ctx).await
. use songbird::SerenityInit;// Event related imports to detect track creation failures. use songbird::events::{Event, EventContext, EventHandler as VoiceEventHandler, TrackEvent};
// To turn user URLs into playable audio, we'll use yt-dlp. use songbird::input::YoutubeDl;
// YtDl requests need an HTTP client to operate -- we'll create and store our own. use reqwest::Client as HttpClient;
// Import the
Context
to handle commands. use serenity::client::Context;use serenity::{ async_trait, client::{Client, EventHandler}, framework::{ standard::{ macros::{command, group}, Args, CommandResult, }, StandardFramework, }, model::{channel::Message, gateway::Ready}, prelude::{GatewayIntents, TypeMapKey}, Result as SerenityResult, }; use serenity::futures::AsyncSeekExt; use songbird::tracks::TrackHandle;
struct HttpKey;
struct TrackHandleKey;
impl TypeMapKey for HttpKey { type Value = HttpClient; }
impl TypeMapKey for TrackHandleKey { type Value = TrackHandle; }
struct Handler;
[async_trait]
impl EventHandler for Handler { async fn ready(&self, _: Context, ready: Ready) { println!("{} is connected!", ready.user.name); } }
[group]
[commands(deafen, join, leave, mute, play, ping, undeafen, unmute,seek)]
struct General;
[tokio::main]
async fn main() { tracing_subscriber::fmt::init();
}
[command]
[only_in(guilds)]
async fn deafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();
}
[command]
[only_in(guilds)]
async fn join(ctx: &Context, msg: &Message) -> CommandResult { let (guild_id, channel_id) = { let guild = msg.guild(&ctx.cache).unwrap(); let channel_id = guild .voice_states .get(&msg.author.id) .and_then(|voice_state| voice_state.channel_id);
}
struct TrackErrorNotifier;
[async_trait]
impl VoiceEventHandler for TrackErrorNotifier { async fn act(&self, ctx: &EventContext<'_>) -> Option {
if let EventContext::Track(track_list) = ctx {
for (state, handle) in *track_list {
println!(
"Track {:?} encountered an error: {:?}",
handle.uuid(),
state.playing
);
}
}
}
[command]
[only_in(guilds)]
async fn leave(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();
}
[command]
[only_in(guilds)]
async fn seek(ctx: &Context, msg: &Message,mut args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap();
}
[command]
[only_in(guilds)]
async fn mute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();
}
[command]
async fn ping(ctx: &Context, msg: &Message) -> CommandResult { check_msg(msg.channel_id.say(&ctx.http, "Pong!").await); Ok(()) }
[command]
[only_in(guilds)]
async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult { let url = match args.single::() {
Ok(url) => url,
Err(_) => {
check_msg(
msg.channel_id
.say(&ctx.http, "Must provide a URL to a video or audio")
.await,
);
}
[command]
[only_in(guilds)]
async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();
}
[command]
[only_in(guilds)]
async fn unmute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();
}
/// Checks that a message successfully sent; if not, then logs why to stdout. fn check_msg(result: SerenityResult) {
if let Err(why) = result {
println!("Error sending message: {:?}", why);
}
}