serenity-rs / songbird

An async Rust library for the Discord voice API
ISC License
384 stars 110 forks source link

Highly variable extended seek times #187

Closed maxall41 closed 1 year ago

maxall41 commented 1 year ago

Songbird version: next branch - commit: 0044728

Rust version (rustc -V): 1.69

Serenity/Twilight version: serenity next branch

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

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:

lto = "fat"
codegen-units = 1

Steps to reproduce:

  1. 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); } }

[group]

[commands(deafen, join, leave, mute, play, ping, undeafen, unmute,seek)]

struct General;

[tokio::main]

async fn main() { tracing_subscriber::fmt::init();

// 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.");

}

[command]

[only_in(guilds)]

async fn deafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();

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 ); } }

    None
}

}

[command]

[only_in(guilds)]

async fn leave(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();

let manager = songbird::get(ctx)
    .await
    .expect("Songbird Voice client placed in at initialisation.")
    .clone();
let has_handler = manager.get(guild_id).is_some();

if has_handler {
    if let Err(e) = manager.remove(guild_id).await {
        check_msg(
            msg.channel_id
                .say(&ctx.http, format!("Failed: {:?}", e))
                .await,
        );
    }

    check_msg(msg.channel_id.say(&ctx.http, "Left voice channel").await);
} else {
    check_msg(msg.reply(ctx, "Not in a voice channel").await);
}

Ok(())

}

[command]

[only_in(guilds)]

async fn seek(ctx: &Context, msg: &Message,mut args: Args) -> CommandResult { let guild_id = msg.guild_id.unwrap();

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(())

}

[command]

[only_in(guilds)]

async fn mute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();

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_mute() {
    check_msg(msg.channel_id.say(&ctx.http, "Already muted").await);
} else {
    if let Err(e) = handler.mute(true).await {
        check_msg(
            msg.channel_id
                .say(&ctx.http, format!("Failed: {:?}", e))
                .await,
        );
    }

    check_msg(msg.channel_id.say(&ctx.http, "Now muted").await);
}

Ok(())

}

[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, );

        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(())

}

[command]

[only_in(guilds)]

async fn undeafen(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();

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.deafen(false).await {
        check_msg(
            msg.channel_id
                .say(&ctx.http, format!("Failed: {:?}", e))
                .await,
        );
    }

    check_msg(msg.channel_id.say(&ctx.http, "Undeafened").await);
} else {
    check_msg(
        msg.channel_id
            .say(&ctx.http, "Not in a voice channel to undeafen in")
            .await,
    );
}

Ok(())

}

[command]

[only_in(guilds)]

async fn unmute(ctx: &Context, msg: &Message) -> CommandResult { let guild_id = msg.guild_id.unwrap();

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
maxall41 commented 1 year ago

Nevermind. Turns out columbia's servers are just really terrible and are either rate limited or are straight out of the 90s.