Git Product home page Git Product logo

songbird's Introduction

docs-badge next-docs-badge build badge guild-badge crates.io version rust badge

Songbird

Songbird is an async, cross-library compatible voice system for Discord, written in Rust. The library offers:

  • A standalone gateway frontend compatible with serenity and twilight using the "gateway" and "[serenity/twilight]" plus "[rustls/native]" features. You can even run driverless, to help manage your lavalink sessions.
  • A standalone driver for voice calls, via the "driver" feature. If you can create a ConnectionInfo using any other gateway, or language for your bot, then you can run the songbird voice driver.
  • Voice receive and RT(C)P packet handling via the "receive" feature.
  • SIMD-accelerated JSON decoding via the "simd-json" feature.
  • And, by default, a fully featured voice system featuring events, queues, seeking on compatible streams, shared multithreaded audio stream caches, and direct Opus data passthrough from DCA files.

Intents

Songbird's gateway functionality requires you to specify the GUILD_VOICE_STATES intent.

Codec support

Songbird supports all codecs and formats provided by Symphonia (pure-Rust), with Opus support provided by audiopus (an FFI wrapper for libopus).

By default, Songbird will not request any codecs from Symphonia. To change this, in your own project you will need to depend on Symphonia as well.

# Including songbird alone gives you support for Opus via the DCA file format.
[dependencies.songbird]
version = "0.4"
features = ["builtin-queue"]

# To get additional codecs, you *must* add Symphonia yourself.
# This includes the default formats (MKV/WebM, Ogg, Wave) and codecs (FLAC, PCM, Vorbis)...
[dependencies.symphonia]
version = "0.5.2"
features = ["aac", "mp3", "isomp4", "alac"] # ...as well as any extras you need!

Dependencies

Songbird needs a few system dependencies before you can use it.

  • Opus - Audio codec that Discord uses. audiopus will use installed libopus binaries if available via pkgconf on Linux/MacOS, otherwise you will need to install cmake to build opus from source. This is always the case on Windows. For Unix systems, you can install the library with apt install libopus-dev on Ubuntu or pacman -S opus on Arch Linux. If you do not have it installed it will be built for you. However, you will need a C compiler and the GNU autotools installed. Again, these can be installed with apt install build-essential autoconf automake libtool m4 on Ubuntu or pacman -S base-devel on Arch Linux.

This is a required dependency. Songbird cannot work without it.

  • yt-dlp / youtube-dl / (similar forks) - Audio/Video download tool. yt-dlp can be installed according to the installation instructions on the main repo. You can install youtube-dl with Python's package manager, pip, which we recommend for youtube-dl. You can do it with the command pip install youtube_dl. Alternatively, you can install it with your system's package manager, apt install youtube-dl on Ubuntu or pacman -S youtube-dl on Arch Linux.

This is an optional dependency for users, but is required as a dev-dependency. It allows Songbird to download audio/video sources from the Internet from a variety of webpages, which it will convert to the Opus audio format Discord uses.

Examples

Full examples showing various types of functionality and integrations can be found in this crate's examples directory.

Contributing

If you want to help out or file an issue, please look over our contributor guidelines!

Attribution

Songbird's logo is based upon the copyright-free image "Black-Capped Chickadee" by George Gorgas White.

songbird's People

Contributors

alvaroms25 avatar arqunis avatar asg0451 avatar clarity0 avatar cycle-five avatar dasetwas avatar doumanash avatar erk- avatar fee1-dead avatar felixmcfelix avatar gnomeddev avatar james7132 avatar jellywx avatar jontze avatar kane50613 avatar lajp avatar lunarmagpie avatar maspenguin avatar maxall41 avatar miezhiko avatar peppizza avatar reiyw avatar saanuregh avatar sebbl0508 avatar tazz4843 avatar tktcorporation avatar vaporoxx avatar vicky5124 avatar vilgotf avatar wlcx avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

songbird's Issues

Ratelimit to prevent of services blocking bot ip

So in youtube, for example, after too much requests, we have receiving some 429 response code. In Lavalink, thats its resolved by ratelmit config, so you can rotate the ipv6 block ip or something else that you want to do....

Have plans to implement something like this ? Or have any solution for that type of problem ?

API change proposal for Songbird::join and Songbird::join_gateway

Not being able to use ? on Songbird::join and Songbird::join_gateway is bothering me, so I propose that join takes a call as an argument instead of returning a new one.

so

fn join(songbird: Songbird, channel_id: ChannelId, guild_id: GuildId) -> Result<()> {
  let (_, res) = songbird.join(guild_id, channel_id).await;
  res
}

would be

fn join(songbird: Songbird, channel_id: ChannelId, guild_id: GuildId) -> Result<()> {
  let call = songbird.get_or_insert(guild_id);
  songbird.join(channel_id, call).await
}

Thoughts?

channel closed error when using handle.enable/disable_loop

I'm trying to make a command to enable/disable a loop on the current track playing, however I keep getting the error SendError(TrackCommand::Loop(Infinite)), channel closed

code:

if let Some(handler_lock) = manager.get(guild_id) {
        let handler = handler_lock.lock().await;
        let queue = handler.queue();

        if let Some(handle) = queue.current() {
            if let LoopState::Infinite = handle.get_info()?.await?.loops {
                if let Err(why) = handle.disable_loop() {
                    tracing::error!("{:?}, {}", why, why);
                }

                msg.reply(ctx, "Stopped loop").await?;
            } else {
                if let Err(why) = handle.enable_loop() {
                    tracing::error!("{:?}, {}", why, why);
                }

                msg.reply(ctx, "Enabled loop").await?;
            }
        }
    }

produces: ERROR music_bot::commands::loop_command: SendError(TrackCommand::Loop(Infinite)), channel closed

`CoreEvent::ClientConnect` not firing

Songbird version: v0.2.0

Rust version (rustc -V): v1.56.0

Serenity/Twilight version: Serenity: v0.1.0

Description:
Greetings! I am having some issues with CoreEvent::ClientConnect. It doesn't seem to fire for me, and my event handler doesn't get invoked. CoreEvent::ClientDisconnect works flawlessly however, using the same event handler.

Steps to reproduce:
I first join a channel, and then request the bot to join as well. I then leave the channel, and it receives the ClientDisconnect event. However, when I rejoin again, it doesn't receive a ClientConnect event.

Code

Event handler

pub(crate) struct GlobalEvent {
    pub channel: mpsc::Sender<QueueUpdate>,
}

#[async_trait]
impl EventHandler for GlobalEvent {
    #[instrument(skip(self, ctx))]
    async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
        let update = match ctx {
            EventContext::ClientConnect(client) => {
                QueueUpdate::ClientConnected(UserId(client.user_id.0))
            }
            EventContext::ClientDisconnect(client) => {
                QueueUpdate::ClientDisconnected(UserId(client.user_id.0))
            }
            _ => {
                error!(event = ?ctx, "Unhandled event!");
                return None;
            }
        };

        if let Err(e) = self.channel.send(update).await {
            error!("{:?}", e);
        }

        None
    }
}

Events being added

let mut call = self.handler.lock().await;

call.add_global_event(
    Event::Core(CoreEvent::ClientConnect),
    GlobalEvent {
        channel: self.update_sender.clone(),
    },
);

call.add_global_event(
    Event::Core(CoreEvent::ClientDisconnect),
    GlobalEvent {
        channel: self.update_sender.clone(),
    },
);

Can't seek lazy Restartable near beginning

Songbird version: 0.2.0

Rust version: rustc 1.55.0 (c8dfcfe04 2021-09-06)

Serenity version: 0.10.9

Description:

When using Restartable::ytdl() with lazy = true, attempting to seek a track before it starts playing will return SeekUnsupported. This is despite the track reporting that it is seekable. Also, if an event near triggered near the beginning of the track tries to seek the track, it will fail silently.

Steps to reproduce:

Create a lazy Restartable and try to seek it.

let source = Restartable::ytdl(url.to_owned(), true).await.unwrap();
let (mut track, track_handle) = songbird::tracks::create_player(source.into());

// Try to seek
dbg!(track_handle.is_seekable());
track.seek_time(Duration::from_secs(10)).unwrap();

Seek fails even though the track reports that it is seekable.

[src/main.rs:218] track_handle.is_seekable() = true
thread 'tokio-runtime-worker' panicked at 'called `Result::unwrap()` on an `Err` value: SeekUnsupported', src/main.rs:219:42
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Try to add an seeking event at the start of the track.

let source = Restartable::ytdl(url.to_owned(), true).await.unwrap();
let (mut track, track_handle) = songbird::tracks::create_player(source.into());

// Add event
track_handle
    .add_event(
        Event::Delayed(Duration::from_millis(5)),
        Seeker {
            seek_to: Duration::from_secs(20),
        },
    )
    .unwrap();

// Play track
handler.play(track);
struct Seeker {
    seek_to: Duration,
}

#[async_trait]
impl VoiceEventHandler for Seeker {
    async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
        if let EventContext::Track(&[(_, track)]) = ctx {
            track.seek_time(self.seek_to).unwrap();
            event!(Level::INFO, "Seek to {:?}", self.seek_to);
        }
        None
    }
}

The event seeks without errors, but actual audio output is not seeked.

[2021-09-28T22:42:26Z INFO  discord_bot] Seek to 20s

How do I create an input from a command?

Following the docs, I got this far:

let child = Command::new("ffmpeg")
    .args(...)
    .args(&["-f", "opus", "-"])
    .output().unwrap();
let reader = Reader::Vec(Cursor::new(child.stdout));
let track = call.play_only_source(Input::new(
    true,
    reader,
    Codec::Opus(OpusDecoderState::new().unwrap()),
    songbird::input::Container::Dca { first_frame: 0 },
    None,
));

However when the file starts playing, I get this error:

thread '<unnamed>' panicked at 'range end index 26447 out of range for slice of length 1432', ~/.cargo/registry/src/github.com-1ecc6299db9ec823/songbird-0.1.8/src/input/mod.rs:323:34

I suspect the issue is with the first_frame argument, but I can't find what I should put there from the docs or from google.

Plans to publish a crate?

Will there be a songbird crate on crates.io eventually? Should probably reserve the name now since it seems to be available

songbird::driver::tasks::udp_rx: Failed to decode received packet: Opus(BufferTooSmall)

Hello. I'm attempting to use this library to receive voice data. However, all the VoicePackets I receive are either empty (when talking) or silent (as expected after I stop talking).

I enabled tracing and also added some logging to audiopus in order to debug this issue. It looks like the failure is caused by a BufferTooSmall error from Opus. Indeed when I replace the buffer size of STEREO_FRAME_SIZE with 9600 in udp_rx.rs the packets are successfully decoded.

I have included a sample Opus packet that fails to decode. Hopefully this can help get to the root cause of the issue.

Speaking state update: user Some(UserId(XXX)) has SSRC XXX, using MICROPHONE
asked to decode Opus packet Packet([123, 131, 47, 46, 11, 32, 223, 10, 132, 144, 147, 61, 250, 196, 24, 255, 86, 235, 228, 71, 243, 149, 78, 24, 128, 150, 72, 7, 206, 93, 149, 61, 231, 176, 14, 4, 251, 236, 76, 161, 243, 240, 254, 63, 150, 186, 156, 239, 217, 149, 78, 10, 136, 24, 76, 222, 60, 244, 212, 213, 126, 89, 217, 60, 149, 38, 195, 197, 112, 44, 116, 98, 82, 143, 103, 252, 247, 122, 206, 191, 213, 191, 197, 172, 61, 140, 89, 87, 177, 77, 11, 180, 232, 51, 126, 101, 251, 10, 136, 17, 103, 209, 212, 138, 70, 9, 157, 122, 0, 3, 213, 115, 44, 134, 54, 104, 224, 211, 152, 139, 227, 72, 166, 93, 244, 174, 159, 241, 41, 150, 96, 44, 85, 230, 71, 166, 249, 186, 168, 77, 83, 206])
Nov 29 00:02:56.230 ERROR songbird::driver::tasks::udp_rx: Failed to decode received packet: Opus(BufferTooSmall).

Thanks for your work on this library.

Migrate mixing/decoding toolchain to Symphonia

This issue proposes that we finally move away from (purely) IPC-based decoding to the codec and framing stack in development in Symphonia.

This would be an involved piece of work, involving (at least):

  • Write an audiopus-based Symphonia codec, and port the DCA framing mechanism. The audiopus-based codec cannot be upstreamed, as Symphonia requires pure-Rust implementations. Similarly, this is not on their current roadmap.
  • Implement DASH handling for youtube and other multimedia handlers extracted from youtube-dl.
  • Investigate upstreaming MKV support to Symphonia. The MKV muxer underlies formats such as WebM (used primarily by youtube et al.).
  • Ensure that Opus packet passthrough can be performed through Symphonia.
  • Port our stream-cacheing wrappers to this ecosystem.

There are several gaps here, but it's also an exciting project that should help us leverage their existing (and forthcoming) codec implementations.

Inputs: Async Restartables

Restartable sources are currently recreated on-demand using an async thread: this design was arrived at after some iteration, but the old system (which attempted to handle this synchronously) has left some blocking calls as mitigations.

To fix this, the Restart trait should be modified to take an async function, and Restartable::* should generally be made async to remove the blocking calls. Into implementations for functions should be similarly altered/extended.

This design mismatch causes single-threaded executor issues, see #12.

playlist stop my bot

Songbird version: (version)
songbird = {version = "0.2.0", features = ["builtin-queue"]}

Rust version (rustc -V): (version)
rustc 1.57.0-nightly (497ee321a 2021-09-09)

Serenity/Twilight version: (version)
serenity = {version = "0.10.7", features = ["client", "framework", "standard_framework", "voice", "rustls_backend"]}

Output of ffmpeg -version, youtube-dl --version (if relevant):
ffmpeg: ffmpeg version 3.4.8-0ubuntu0.2
youtube-dl: 2021.06.06

Description:
Excuse me for google translate from Japanese,
And I'm also a beginner on github.

When I add a playlist song using input :: ytdl (& url) and the first song finishes, all songbird commands stop. Is this a bug?

Also, is there a way to put all the songs in the playlist into the queue at once at this time?

Discord sending opcode 14 for client_disconnect

Unsure if this is an error with this library or serenity, however the error originated from songbird, It seems discord, out of nowhere, is sending opcode 14 for client_disconnect, as well as the usual 13, as indicated by
songbird::driver::tasks::ws: Unexpected JSON Error("invalid value: integer `14`, expected opcode in [0--9] + [12--13]", line: 1, column: 8)
This warning occurs when i disconnect from a voice channel and leave the bot alone in it

Docs: Clarify failure modes for loop commands

Currently, TrackHandle::enable_loop (and disable, etc.) are guaranteed to error on use against a non-seekable Input. This is implemented as a safety measure, but is not documented (encountered in #9).

Docs should be modified to explain this: by clearer commenting, and by ensuring the same guards exist on Tracks.

Add Typemap to TrackState

I think it would be useful to allow users of the library to store data alongside the handle in a TrackQueue, such as storing the metadata of the song, to allow for easier retrieval, instead of having to manage 2 queues at once.

Stack overflow when quitting the runtime

Finally found the time to verify this. Use the voice example from songbird with serenity and change the client start to

tokio::spawn(async move {
    let _ = client.start().await.map_err(|why| println!("Client ended: {:?}", why));
});

tokio::signal::ctrl_c().await;
println!("Received Ctrl-C, shutting down.");

After voice is connected (~join) one tokio-runtime-worker will overflow its stack upon Ctrl+C to exit the runtime, apparently by indefinitely recursing songbird::driver::Driver::restart_inner() -> mute -> send.

Stack trace is here, as rusts internal backtrace will not work any more.

Crashing code example is here

Suspected busy-wait on a Mutex blocking bot

An issue I keep running into.

Information:

  • serenity 0.10.5 next
  • songbird 0.1.6 next
  • tokio 1.5.0
  • Rustc 1.51.0
  • glibc 2.31
  • Ubuntu 20.04 Linux 5.4.0-70-generic

What happens:

After running for a while, the bot goes offline and begins to consume the maximum CPU allotted to it. Using perf top, this consumption can be fed back to a mutex, and a number of other calls (one called 'Sleep::new_timeout' from tokio):

a
b

The memory also spikes a little bit, but there appears to be no active leaking occuring.

The bot is in around 8'000 guilds now, and runs autosharded. It usually goes down once every 12-36 hours. The server it is running on has more than enough memory/CPU available to the bot (average total system load 0.5-2.0 out of 12.0) and only runs other Discord bots and a couple of websites

My bot that is having this is at https://github.com/jellywx/soundfx-rs

Crash with no error messsage

I'm running my bot from an alpine docker image, and when I run the line

    let handler = manager.join(guild_id, channel_id).await.0;

The container crashes and I get this output:

discord_1  | [INFO  songbird::handler] update
discord_1  | [TRACE songbird::handler] -- update
discord_1  | [TRACE songbird::handler] -- join
discord_1  | [TRACE songbird::driver::tasks] Driver started.
discord_1  | [INFO  songbird::driver::tasks] runner; config=Config { crypto_mode: Normal, decode_mode: Decrypt, gateway_timeout
: Some(10s), preallocated_tracks: 1, driver_retry: Retry { strategy: Backoff(ExponentialBackoff { min: 250ms, max: 10s, jitter:
 0.1 }), retry_limit: Some(5) }, driver_timeout: Some(10s) }
discord_1  | [TRACE songbird::driver::tasks] Event processor started.
discord_1  | [INFO  songbird::driver::tasks::events] runner
discord_1  | [TRACE songbird::driver::tasks] Mixer started.
myproject_discord_1 exited with code 139

Sometimes runner is the last line and sometimes Mixer started is.

I'm not sure how to go about debugging this... There's no error message, and I tried looking through some code but couldn't get an idea as to why this is happening.

InterconnectError when calling manager.remove

ERROR runner{config=Config { crypto_mode: Normal, decode_mode: Decrypt, preallocated_tracks: 1 }}: songbird::driver::tasks::mixer: Mixer thread cycle: InterconnectFailure(Event) thread '<unnamed>' panicked at 'FATAL: No way to rebuild driver core from mixer.: "SendError(..)"', /home/spencer/.cargo/git/checkouts/songbird-bfb5fd0fe254c2b1/f222ce9/src/driver/tasks/mixer.rs:234:22 note: run with RUST_BACKTRACE=1 environment variable to display a backtrace'

I am unsure as to what is causing this error, but it seems to occur everytime i call manager.remove. It doesn't seem to cause any problems, (that I know of), but regardless, it still exists

Track start event?

Is there an event i can register to get fired on a track starting, similar to that of a track ending

Ytdl functions should take AsRef<str>

As in the title. This issue was raised over Discord, and is an inconsistency/slight ergonomics hitch.

The relevant functions are:

  • songbird::input::ytdl
  • songbird::input::ytdl_search
  • songbird::input::restartable::Restartable::ytdl_search

Modifying these functions to take a generic <S: AsRef<str>> parameter should be simple enough change, and would be a breaking change which still allows passing &strs. The Restartable call should not require complex trait/lifetime bounds, as the path is immediately fed into a string format.

This change would target the "next" branch.

Calling `songbird::remove` from Periodic Event Handler/Separate Thread Hangs

Songbird version: 0.2.0
Serenity version: 0.10.9
Rust version: 1.55.0

Overview

  • Goal: Disconnect bot from voice channel after inactivity
  • Issue: When calling remove or leave from an event handler, the bot does not disconnect from voice. At the same time, it no longer responds to any other voice commands issued (eg a stop, play, etc). When running the same code from a serenity command, however, the bot disconnects as expected.

To rule out any event handler/async_trait related issues, I tried just spawning a tokio::task to run the event handler's code. This too failed to disconnect the bot; which seems to narrow the problem down to something with Songbird::remove on an instance of Songbird that is clone'd outside of a Serenity command's scope.

Perhaps my approach to disconnecting from voice after a period of time is wrong altogether?

Code Example

First attempt with Periodic Global Event Handler:

Click here
handler.add_global_event(
    Event::Periodic(Duration::from_secs(30), None),
    ChannelDisconnect {manager, guild: guild_id},
);

// Channel Disconnect impl
struct ChannelDisconnect {
  manager: Arc<Songbird>,
  guild: GuildId,
}

#[async_trait]
impl EventHandler for ChannelDisconnect {
  async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
     match self.manager.get(self.guild) {
      None => None,
      Some(handler_lock) => {
        let handler = handler_lock.lock().await;
        if handler.queue().is_empty() {
          let _dc = self.manager.remove(self.guild).await;
        }
        None
      }
    }
  }
}

Second attempt with tokio

Click here
let chan = ChannelDisconnect {manager: manager.clone(), guild: guild_id.clone()};
task::spawn(async move {
  let interval = time::interval(Duration::from_secs(30));
  interval.tick().await;
  loop {
     interval.tick().await;
     match chan.manager.get(chan.guild) {
      None => None,
      Some(handler_lock) => {
        let handler = handler_lock.lock().await;
        if handler.queue().is_empty() {
          let _dc = chan.manager.remove(chan.guild).await;
        }
        None
      }
    }
  }
});

Track metadata in TrackHandle

Being able to access track metadata from TrackHandle struct would be very useful, like in use cases such as listing queue or transitioning from one song to another.

Mixer optimisations

A few missing mixer optimisations marked as ToDos, in order of importance:

  • Mixer->UDP transmission task handover. Currently, a new Vec is allocated and sent for each packet. This should be a pair of channels to pass prealloc'd Vecs back and forth, or a shared memory buffer with a semaphore or similar mechanism to indicate which packet each side should care about. The former is safer, but probably slower. There are other optimisations we can use here, such as abusing the fact that most of the packet body is already written.
  • Make opus_frame_backing a view onto the active packet buffer being readied for send. This will save an extra copy per packet when frame passthrough is happening.
  • Store finished tracks as tombstone entries. This prevents copies due to array reordering on element deletion, but requires that we keep a free list on the side and exactly mirror this behaviour in the event thread. O(1) (non order preserving) deletion will suffice, unless we adopt some SoA layout in the driver in future.

Tokio 1.0

Serenity had tokio 1.0 merged recently, so this library should be free to update as well

Very high CPU usage vs. serenity::voice module

I'm seeing very high CPU climb from Songbird vs. using the old serenity::voice module. Where serenity::voice sits at 0.3-0.6, Songbird can climb during runtime to 1.7 in just a couple of hours.

I am not changing the configuration on Songbird, although setting 'Decode' to Pass has the same issue.
I have used both

    .register_songbird_with({
            let songbird = songbird::Songbird::serenity();

            songbird.set_config(Config {
                crypto_mode: CryptoMode::Normal,
                decode_mode: DecodeMode::Pass,
                preallocated_tracks: 0,
            });

            songbird
        })

and just

    .register_songbird_with()

, both having the same issue. My code for playing audio is as follows

async fn play_audio(
    sound: &mut Sound,
    guild: GuildData,
    call_handler: &mut MutexGuard<'_, Call>,
    mysql_pool: MySqlPool,
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
    {
        let (track, track_handler) =
            create_player(sound.store_sound_source(mysql_pool.clone()).await?);

        let _ = track_handler.set_volume(guild.volume as f32 / 100.0);

        call_handler.play(track);
    }

    Ok(())
}

...and I have defined this too for joining channels

async fn join_channel(
    ctx: &Context,
    guild: Guild,
    channel_id: ChannelId,
) -> (Arc<Mutex<Call>>, JoinResult<()>) {
    let songbird = songbird::get(ctx).await.unwrap();
    let current_user = ctx.cache.current_user_id().await;

    let current_voice_state = guild
        .voice_states
        .get(&current_user)
        .and_then(|voice_state| voice_state.channel_id);

    if current_voice_state == Some(channel_id) {
        let call_opt = songbird.get(guild.id);

        if let Some(call) = call_opt {
            (call, Ok(()))
        } else {
            songbird.join(guild.id, channel_id).await
        }
    } else {
        songbird.join(guild.id, channel_id).await
    }
}

...in case this is an issue in one of those rather than a general issue. I haven't got any profiling means set up right now to see where the issue is coming from much better.
The CPU usage increases constantly during runtime. There's no 'process leak'; under top, all the consumption is coming from the bot and not from any left over FFMPEG sessions or anything

songbird version 0.1.4
serenity version 0.10.2
Rust version 1.49
OS: Ubuntu 18.04 headless, Linux kernel 4.15.0-135-generic

Example using received audio data?

I'm hacking on this example (https://github.com/serenity-rs/songbird/blob/current/examples/serenity/voice_receive/src/main.rs), and I can receive and buffer voice audio, but I can't figure out what format it's in or how to use it.

I've got some of these https://serenity-rs.github.io/songbird/current/songbird/events/context_data/struct.VoiceData.html#structfield.audio but what is this? How can I turn this into, say, a .wav file?

I've been banging my head on this one for a couple hours so any help would be greatly appreciated!

Calling SongBird::join() while already in the voice channel causes a deadlock

Songbird version: 0.1.6

Rust version (rustc -V): 1.51

Serenity/Twilight version: 10.5

Features: (Songbird & Serenity)

[
    "driver-tokio-02",
    "gateway-tokio-02",
    "serenity-rustls-tokio-02",
]

Description:
Calling SongBird::join() while already in the voice channel causes a deadlock. The second call to join will never return.
Steps to reproduce:

Modify the serenity voice example to call join twice in the join command. The second one should return an error or be idempotent and succeed. Either way it shouldn't deadlock the bot.

voice_manager.join(guild_id, voice_channel_to_join).await;
voice_manager.join(guild_id, voice_channel_to_join).await;

Fix cross compilation on `stable-x86_64-pc-windows-gnu`

Songbird version: 0.1.6

Rust version (rustc -V): rustc 1.52.1 (9bc8c42bb 2021-05-09)

Serenity/Twilight version: (version)

Output of ffmpeg -version, youtube-dl --version (if relevant):
Not Relavent

Description:
I was trying to work on compiling a serenity bot with the x86_64-pc-windows-gnu target but got stuck on audiopus-sys failing to compile, I think due to handling platform features incorrectly.

I managed to fix this by updating audiopus to 0.3.0-rc.0 in songbird. I can open a PR if it is wanted. The changes are breaking however as cmake is now a required dependency of audiopus.

Related serenity issue: serenity-rs/serenity#1001.
It looks like that user tried to build on the windows gnu toolchain instead. I tried to replicate the issue, but got a different error than they did. However, updating audiopus seems to fix this issue as well.

Steps to reproduce:

  1. Clone the current branch of this repository on Windows 10 with the toolchain and msys2 installed, with the appropriate compiler tools installed and available through the PATH.
  2. Run cargo test --all-features --target x86_64-pc-windows-gnu
  3. Wait for the compilation to fail:
PS D:\Github\songbird> cargo test --all-features --target x86_64-pc-windows-gnu
    Updating crates.io index
   Compiling songbird v0.1.6 (D:\Github\songbird)
   Compiling term v0.4.6
   Compiling native-tls v0.2.7
   Compiling mio v0.7.11
   Compiling audiopus_sys v0.1.8
   Compiling tracing v0.1.26
   Compiling rustls v0.19.1
   Compiling rustls v0.18.1
   Compiling poly1305 v0.6.2
   Compiling salsa20 v0.7.2
   Compiling mio v0.6.23
   Compiling audiopus v0.2.0
error: could not find native static library `opus`, perhaps an -L flag is missing?

error: aborting due to previous error

error: could not compile `audiopus_sys`

To learn more, run the command again with --verbose.
warning: build failed, waiting for other jobs to finish...
error: build failed

Missed and delayed events when adding multiple track events

Songbird version: 0.2.0

Rust version: rustc 1.55.0 (c8dfcfe04 2021-09-06)

Serenity version: 0.10.9

Description:

Setting multiple events on one track causes some strange behavior and missed events.

I am trying to add events that will skip certain parts of a track by adding a timed event, which when triggered will seek forward and then add another timed event to skip the next part. But I am running this issue where events are not being triggered. Is there a better way to do this?

Steps to reproduce:

Create an event that logs whenever it is triggered.

#[derive(Debug)]
struct EventTester {
    message: String,
}

#[async_trait]
impl VoiceEventHandler for EventTester {
    async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
        event!(Level::INFO, "EventHandler: {}", self.message);
        None
    }
}

Play a song and add some events.

let song = handler.play_source(source);

song.add_event(
    Event::Delayed(Duration::from_secs(1)),
    EventTester {
        message: "delayed 1".into(),
    },
)
.unwrap();
song.add_event(
    Event::Delayed(Duration::from_secs(2)),
    EventTester {
        message: "delayed 2".into(),
    },
)
.unwrap();
song.add_event(
    Event::Periodic(Duration::from_secs(1), Some(Duration::from_secs(0))),
    EventTester {
        message: "periodic 1".into(),
    },
)
.unwrap();
song.add_event(
    Event::Periodic(Duration::from_secs(2), Some(Duration::from_secs(0))),
    EventTester {
        message: "periodic 2".into(),
    },
)
.unwrap();

The 1 second delay event is only run after the 2 second delay event, and the 1 second periodic event is lost completely.

[2021-09-27T18:10:29Z INFO  discord_bot] EventHandler: delayed 2 
[2021-09-27T18:10:29Z INFO  discord_bot] EventHandler: delayed 1 
[2021-09-27T18:10:29Z INFO  discord_bot] EventHandler: periodic 2 
[2021-09-27T18:10:31Z INFO  discord_bot] EventHandler: periodic 2 
[2021-09-27T18:10:33Z INFO  discord_bot] EventHandler: periodic 2 
[2021-09-27T18:10:35Z INFO  discord_bot] EventHandler: periodic 2 
[2021-09-27T18:10:37Z INFO  discord_bot] EventHandler: periodic 2 
[2021-09-27T18:10:39Z INFO  discord_bot] EventHandler: periodic 2

Allow connection resumption on bot (re)start

A discord user raised that automatically rejoining all calls on connection might be useful, i.e., in event of a bot crash or other failure needing a restart. This feature would be configurable, and disabled by default.

There was some discussion on Discord on how this could be done from 2022-01-24:

FelixMcFelix โ€” 24/01/2022
in config.rs: add a new field, gated by #[cfg(feature = "gateway-core")]. Default to false? You should also add a setter method to Config.

in manager.rs: modify impl VoiceGatewayManager (server_update, state_update) to check this new field in the config. If new field is true, do a get_or_insert (rather than get)? You need to make the same change to Songbird::process for twilight.

What I'm unclear on is what discord actually send you when the bot comes back up: if they send you both voice_state_update and voice_server_update then you need to set call.connection to Some(...) before the calls to Call::update_server and Call::update_state. If not, then you can probably just call Call::join with the received channel id. 
you might need to do some of your own testing on that last part
Since Config is non-exhaustive, this should be good for the current branch

Add configurable Driver reconnection support

Currently, driver connection logic lacks both timeouts and reconnect strategies. As v0.2.0 allows non-breaking addition of new Config fields, we can add both:

  • Timeouts to driver connection attempts, and
  • Reconnection strategies (Unlimited/Limited, RetryEvery(Duration)/ExponentialBackoff(...))

This will likely need some alteration of the related error types and events, to signal connection failures and offer a chance for an event handler to intervene.

Errors: Leaky types

Currently, some error types expose dependencies with a correct but user-unfriendly signature, or leak implementation details. A particularly bad case of this is TrackResult.

For instance, SendError denotes a finished track and is abused to include illegal operations: something better might be to offer TrackError::{Finished, InvalidOperation, ...}.

The API should be looked over for similar such cases,

Some sounds don't play following an underflow error

My bot allows users to upload sounds, and then play them back again later. Currently, there's a strange issue where sometimes a sound will 'break' and the bot will no longer be able to play it, even between restarts. The error produced is a panic:

Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]: thread 'tokio-runtime-worker' panicked at 'underflow when converting float to duration', /home/jude/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/time.rs:702:13
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]: stack backtrace:
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    0: rust_begin_unwind
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:              at /rustc/9bc8c42bb2f19e745a63f3445f1ac248fb015e53/library/std/src/panicking.rs:493:5
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    1: core::panicking::panic_fmt
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:              at /rustc/9bc8c42bb2f19e745a63f3445f1ac248fb015e53/library/core/src/panicking.rs:92:14
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    2: core::panicking::panic
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:              at /rustc/9bc8c42bb2f19e745a63f3445f1ac248fb015e53/library/core/src/panicking.rs:50:5
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    3: songbird::input::metadata::Metadata::from_ffprobe_json
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    4: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    5: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    6: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    7: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    8: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:    9: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   10: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   11: <core::future::from_generator::GenFuture<T> as core::future::future::Future>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   12: tokio::runtime::task::harness::poll_future
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   13: tokio::runtime::task::harness::Harness<T,S>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   14: std::thread::local::LocalKey<T>::with
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   15: tokio::runtime::thread_pool::worker::Context::run_task
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   16: tokio::runtime::thread_pool::worker::Context::run
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   17: tokio::macros::scoped_tls::ScopedKey<T>::set
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   18: tokio::runtime::thread_pool::worker::run
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   19: tokio::loom::std::unsafe_cell::UnsafeCell<T>::with_mut
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   20: <std::panic::AssertUnwindSafe<F> as core::ops::function::FnOnce<()>>::call_once
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   21: tokio::runtime::task::harness::Harness<T,S>::poll
Jun 24 14:46:35 jellywx.com soundfx-rs[2153237]:   22: tokio::runtime::blocking::pool::Inner::run

The oddity is, that when the sound file is dowloaded to my local machine, it will play fine through a different bot instance, and if the sound is reuploaded from the original or converted source, it'll fix and start working again. All sounds use the same format after upload (they are converted when the user uploads them to OPUS).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.