serenity-rs / songbird

An async Rust library for the Discord voice API
ISC License
391 stars 111 forks source link

No audio playing from standalone call #239

Closed boomermath closed 4 months ago

boomermath commented 4 months ago

Songbird version: 0.4.1

Rust version (rustc -V): 1.7.8

Serenity/Twilight version: N/A

Output of ffmpeg -version, yt-dlp --version (if relevant): N/A ...

Description:

I am running songbird on its own, without serenity/twilight, as a standalone voice server. I am using kafka to read voice state/voice server updates from discord, which I then pass to Call::standalone as connection info. Code is in Github repo.

A single call is created as shown:

 let cloned_connection_info = connection_info.clone();
                println!("{:?}", cloned_connection_info);
                tokio::spawn(async move {
                    let mut c = Call::standalone(
                        cloned_connection_info.guild_id,
                        cloned_connection_info.user_id,
                    );
                    c.connect(cloned_connection_info);
                    let music =
                        Input::from(File::new("/home/boomermath/discord_jukebox/music.mp3"));
                    c.play_input(music.into());
                });

...

Steps to reproduce:

  1. Run code in repo
  2. Join VC and send voice state and voice server update to kafka, which is then consumed by songbird voice server
  3. Create standalone call
  4. Debug output shows WS Thread finished when I try to connect and play from file, output before that seems normal

After playing around with the library code a bit, the code in src/driver/tasks/ws.rs in the run method of AuxNetworks is terminated due to a flume RecvError ...

FelixMcFelix commented 4 months ago

I think you may be misinterpreting the shape of the API. If you're running a dedicated voice server to which you pass ConnectionInfo, you instead want to use Driver::new and Driver::connect.

Call::standalone is a bit of legacy cruft we should do away with (emphasis mine):

Creates a new, standalone Call which is not connected via WebSocket to the Gateway. ... For most use cases you do not want this.

I think its existence predates songbird itself, and I currently know of no one who uses it.

FelixMcFelix commented 4 months ago

Some other things worth trying:

boomermath commented 4 months ago

Updated code:

#[async_trait]
impl EventHandler for EventHandlerCustom {
    async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
        println!("{:?}", ctx);
        if let Some(evt) = ctx.to_core_event() {
            return Option::Some(Event::Core(evt));
        }

        return Option::None;
    }
}

 if check_connection_info(&connection_info) {
                let cloned_connection_info = connection_info.clone();
                println!("{:?}", cloned_connection_info);
                tokio::spawn(async move {
                    let mut driver = Driver::new(Config::default());
                    driver.add_global_event(
                        Event::Core(CoreEvent::DriverConnect),
                        EventHandlerCustom {},
                    );
                    driver.add_global_event(
                        Event::Core(CoreEvent::DriverReconnect),
                        EventHandlerCustom {},
                    );
                    driver.add_global_event(
                        Event::Core(CoreEvent::DriverDisconnect),
                        EventHandlerCustom {},
                    );

                    let connect_res = driver.connect(cloned_connection_info).await;

                    match connect_res {
                        Ok(_c) => {
                            println!("Connected!");
                        }
                        Err(e) => println!("{:?}", e),
                    }

                    let music =
                        Input::from(File::new("/home/boomermath/discord_jukebox/music.mp3"));
                    driver.play_input(music.into());
                });

The event listener only prints out the the DriverConnect event like so:

DriverConnect(ConnectData { channel_id: Some(ChannelId(810276988090581015)), guild_id: GuildId(810276987465760800), session_id: "4f43a792dcaac44babd67d48d8a46234", server: "us-east4912.discord.media:443", ssrc: 81474 })

with no DriverDisconnect event, but the songbird::driver::connection prints out Disconnected, followed by songbird::driver:tasks::ws printing out changed speaking state microphone. Tracing shows the message "WS Thread finished" after disconnect/speaking state msgs. No errors from driver connect

FelixMcFelix commented 4 months ago

Could you please try and store the Driver in a global (or at least longer-lived) Arc<RwLock<HashMap>> ? It looks like it's being dropped when that task ends, which then makes the sudden (no-event) WS closure expected behaviour -- the thread pool etc. all shut down when the last reference to a call is lost to prevent resource leaks.

boomermath commented 4 months ago

Creating a global hashmap works! Thank you so much for your help!

boomermath commented 4 months ago

Closing