Driver: Split receive into its own feature (#141)
Adds the "receive" feature, which is disabled by default. When this is disabled, the UDP receive task is not compiled and not run, and as an optimisation the UDP receive buffer size is set to 0. All related events are also removed. This also removes the UDP Tx task, and moves packet and keepalive sends back into the mixer thread. This allows us to entirely remove channels and various allocations between the mixer and an async task created only for sending data (i.e., fewer memcopies). If "receive" is enabled, UDP sends are now non-blocking due to technical constraints -- failure to send is non-fatal, but *will* drop affected packets. Given that blocking on a UDP send indicates that the OS cannot clear send buffers fast enough, this should alleviate OS load. Closes #131.
This commit is contained in:
@@ -35,13 +35,12 @@ Audio processing remains synchronous for the following reasons:
|
|||||||
Songbird subdivides voice connection handling into several long- and short-lived tasks.
|
Songbird subdivides voice connection handling into several long- and short-lived tasks.
|
||||||
|
|
||||||
* **Core**: Handles and directs commands received from the driver. Responsible for connection/reconnection, and creates network tasks.
|
* **Core**: Handles and directs commands received from the driver. Responsible for connection/reconnection, and creates network tasks.
|
||||||
* **Mixer**: Combines audio sources together, Opus encodes the result, and encrypts the built packets every 20ms. Responsible for handling track commands/state. ***Synchronous***.
|
* **Mixer**: Combines audio sources together, Opus encodes the result, and encrypts the built packets every 20ms. Responsible for handling track commands/state, and transmitting completed voice packets and keepalive messages. ***Synchronous***.
|
||||||
* **Thread Pool**: A dynamically sized thread-pool for I/O tasks. Creates lazy tracks using `Compose` if sync creation is needed, otherwise spawns a tokio task. Seek operations always go to the thread pool. ***Synchronous***.
|
* **Thread Pool**: A dynamically sized thread-pool for I/O tasks. Creates lazy tracks using `Compose` if sync creation is needed, otherwise spawns a tokio task. Seek operations always go to the thread pool. ***Synchronous***.
|
||||||
* **Disposer**: Used by mixer thread to dispose of data with potentially long/blocking `Drop` implementations (i.e., audio sources). ***Synchronous***.
|
* **Disposer**: Used by mixer thread to dispose of data with potentially long/blocking `Drop` implementations (i.e., audio sources). ***Synchronous***.
|
||||||
* **Events**: Stores and runs event handlers, tracks event timing, and handles
|
* **Events**: Stores and runs event handlers, tracks event timing, and handles
|
||||||
* **Websocket**: *Network task.* Sends speaking status updates and keepalives to Discord, and receives client (dis)connect events.
|
* **Websocket**: *Network task.* Sends speaking status updates and keepalives to Discord, and receives client (dis)connect events.
|
||||||
* **UDP Tx**: *Network task.* Responsible for transmitting completed voice packets.
|
* **UDP Rx**: *Optional network task.* Decrypts/decodes received voice packets and statistics information.
|
||||||
* **UDP Rx**: *Network task.* Decrypts/decodes received voice packets and statistics information.
|
|
||||||
|
|
||||||
*Note: all tasks are able to message the permanent tasks via a block of interconnecting channels.*
|
*Note: all tasks are able to message the permanent tasks via a block of interconnecting channels.*
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ audiopus = { optional = true, version = "0.3.0-rc.0" }
|
|||||||
byteorder = { optional = true, version = "1" }
|
byteorder = { optional = true, version = "1" }
|
||||||
dashmap = { optional = true, version = "5" }
|
dashmap = { optional = true, version = "5" }
|
||||||
derivative = "2"
|
derivative = "2"
|
||||||
discortp = { features = ["discord-full"], optional = true, version = "0.5" }
|
discortp = { default-features = false, features = ["discord", "pnet", "rtp"], optional = true, version = "0.5" }
|
||||||
flume = { optional = true, version = "0.10" }
|
flume = { optional = true, version = "0.10" }
|
||||||
futures = "0.3"
|
futures = "0.3"
|
||||||
once_cell = { optional = true, version = "1" }
|
once_cell = { optional = true, version = "1" }
|
||||||
@@ -32,8 +32,9 @@ rubato = { optional = true, version = "0.12" }
|
|||||||
rusty_pool = { optional = true, version = "0.7" }
|
rusty_pool = { optional = true, version = "0.7" }
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
serde-aux = { default-features = false, optional = true, version = "3"}
|
serde-aux = { default-features = false, optional = true, version = "3"}
|
||||||
simd-json = { features = ["serde_impl"], optional = true, version = "0.6.0" }
|
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
simd-json = { features = ["serde_impl"], optional = true, version = "0.6.0" }
|
||||||
|
socket2 = { optional = true, version = "0.4" }
|
||||||
streamcatcher = { optional = true, version = "1" }
|
streamcatcher = { optional = true, version = "1" }
|
||||||
tokio = { default-features = false, optional = true, version = "1.0" }
|
tokio = { default-features = false, optional = true, version = "1.0" }
|
||||||
tokio-tungstenite = { optional = true, version = "0.17" }
|
tokio-tungstenite = { optional = true, version = "0.17" }
|
||||||
@@ -110,6 +111,7 @@ driver = [
|
|||||||
"dep:rusty_pool",
|
"dep:rusty_pool",
|
||||||
"dep:serde-aux",
|
"dep:serde-aux",
|
||||||
"dep:serenity-voice-model",
|
"dep:serenity-voice-model",
|
||||||
|
"dep:socket2",
|
||||||
"dep:streamcatcher",
|
"dep:streamcatcher",
|
||||||
"dep:symphonia",
|
"dep:symphonia",
|
||||||
"dep:symphonia-core",
|
"dep:symphonia-core",
|
||||||
@@ -145,9 +147,10 @@ twilight = ["dep:twilight-gateway","dep:twilight-model"]
|
|||||||
|
|
||||||
# Behaviour altering features.
|
# Behaviour altering features.
|
||||||
builtin-queue = []
|
builtin-queue = []
|
||||||
|
receive = ["discortp?/demux", "discortp?/rtcp"]
|
||||||
|
|
||||||
# Used for docgen/testing/benchmarking.
|
# Used for docgen/testing/benchmarking.
|
||||||
full-doc = ["default", "twilight", "builtin-queue"]
|
full-doc = ["default", "twilight", "builtin-queue", "receive"]
|
||||||
internals = []
|
internals = []
|
||||||
|
|
||||||
[[bench]]
|
[[bench]]
|
||||||
|
|||||||
@@ -12,11 +12,11 @@ The library offers:
|
|||||||
* A standalone driver for voice calls, via the `"driver"` feature. If you can create
|
* 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
|
a `ConnectionInfo` using any other gateway, or language for your bot, then you
|
||||||
can run the songbird voice driver.
|
can run the songbird voice driver.
|
||||||
* And, by default, a fully featured voice system featuring events, queues, RT(C)P packet
|
* Voice receive and RT(C)P packet handling via the `"receive"` feature.
|
||||||
handling, seeking on compatible streams, shared multithreaded audio stream caches,
|
* 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.
|
and direct Opus data passthrough from DCA files.
|
||||||
* To be able to use `simd-json` from serenity, you will need to enable the `simdjson`
|
|
||||||
feature on both songbird and serenity.
|
|
||||||
|
|
||||||
## Intents
|
## Intents
|
||||||
Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.
|
Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use songbird::{
|
|||||||
tracks,
|
tracks,
|
||||||
Config,
|
Config,
|
||||||
};
|
};
|
||||||
use std::io::Cursor;
|
use std::{io::Cursor, net::UdpSocket};
|
||||||
use tokio::runtime::{Handle, Runtime};
|
use tokio::runtime::{Handle, Runtime};
|
||||||
use xsalsa20poly1305::{aead::NewAead, XSalsa20Poly1305 as Cipher, KEY_SIZE};
|
use xsalsa20poly1305::{aead::NewAead, XSalsa20Poly1305 as Cipher, KEY_SIZE};
|
||||||
|
|
||||||
@@ -41,14 +41,12 @@ fn dummied_mixer(
|
|||||||
Receiver<CoreMessage>,
|
Receiver<CoreMessage>,
|
||||||
Receiver<EventMessage>,
|
Receiver<EventMessage>,
|
||||||
Receiver<UdpRxMessage>,
|
Receiver<UdpRxMessage>,
|
||||||
Receiver<UdpTxMessage>,
|
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
let (mix_tx, mix_rx) = flume::unbounded();
|
let (mix_tx, mix_rx) = flume::unbounded();
|
||||||
let (core_tx, core_rx) = flume::unbounded();
|
let (core_tx, core_rx) = flume::unbounded();
|
||||||
let (event_tx, event_rx) = flume::unbounded();
|
let (event_tx, event_rx) = flume::unbounded();
|
||||||
|
|
||||||
let (udp_sender_tx, udp_sender_rx) = flume::unbounded();
|
|
||||||
let (udp_receiver_tx, udp_receiver_rx) = flume::unbounded();
|
let (udp_receiver_tx, udp_receiver_rx) = flume::unbounded();
|
||||||
|
|
||||||
let ic = Interconnect {
|
let ic = Interconnect {
|
||||||
@@ -61,18 +59,23 @@ fn dummied_mixer(
|
|||||||
|
|
||||||
let mut out = Mixer::new(mix_rx, handle, ic, config);
|
let mut out = Mixer::new(mix_rx, handle, ic, config);
|
||||||
|
|
||||||
|
let udp_tx = UdpSocket::bind("0.0.0.0:0").expect("Failed to create send port.");
|
||||||
|
udp_tx
|
||||||
|
.connect("127.0.0.1:5316")
|
||||||
|
.expect("Failed to connect to local dest port.");
|
||||||
|
|
||||||
let fake_conn = MixerConnection {
|
let fake_conn = MixerConnection {
|
||||||
cipher: Cipher::new_from_slice(&vec![0u8; KEY_SIZE]).unwrap(),
|
cipher: Cipher::new_from_slice(&vec![0u8; KEY_SIZE]).unwrap(),
|
||||||
crypto_state: CryptoState::Normal,
|
crypto_state: CryptoState::Normal,
|
||||||
udp_rx: udp_receiver_tx,
|
udp_rx: udp_receiver_tx,
|
||||||
udp_tx: udp_sender_tx,
|
udp_tx,
|
||||||
};
|
};
|
||||||
|
|
||||||
out.conn_active = Some(fake_conn);
|
out.conn_active = Some(fake_conn);
|
||||||
|
|
||||||
out.skip_sleep = true;
|
out.skip_sleep = true;
|
||||||
|
|
||||||
(out, (core_rx, event_rx, udp_receiver_rx, udp_sender_rx))
|
(out, (core_rx, event_rx, udp_receiver_rx))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mixer_float(
|
fn mixer_float(
|
||||||
@@ -85,7 +88,6 @@ fn mixer_float(
|
|||||||
Receiver<CoreMessage>,
|
Receiver<CoreMessage>,
|
||||||
Receiver<EventMessage>,
|
Receiver<EventMessage>,
|
||||||
Receiver<UdpRxMessage>,
|
Receiver<UdpRxMessage>,
|
||||||
Receiver<UdpTxMessage>,
|
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
let mut out = dummied_mixer(handle, softclip);
|
let mut out = dummied_mixer(handle, softclip);
|
||||||
@@ -115,7 +117,6 @@ fn mixer_float_drop(
|
|||||||
Receiver<CoreMessage>,
|
Receiver<CoreMessage>,
|
||||||
Receiver<EventMessage>,
|
Receiver<EventMessage>,
|
||||||
Receiver<UdpRxMessage>,
|
Receiver<UdpRxMessage>,
|
||||||
Receiver<UdpTxMessage>,
|
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
let mut out = dummied_mixer(handle, true);
|
let mut out = dummied_mixer(handle, true);
|
||||||
@@ -143,7 +144,6 @@ fn mixer_opus(
|
|||||||
Receiver<CoreMessage>,
|
Receiver<CoreMessage>,
|
||||||
Receiver<EventMessage>,
|
Receiver<EventMessage>,
|
||||||
Receiver<UdpRxMessage>,
|
Receiver<UdpRxMessage>,
|
||||||
Receiver<UdpTxMessage>,
|
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
// should add a single opus-based track.
|
// should add a single opus-based track.
|
||||||
|
|||||||
@@ -6,3 +6,6 @@ members = [
|
|||||||
"serenity/voice_receive",
|
"serenity/voice_receive",
|
||||||
"twilight",
|
"twilight",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
debug = true
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ tracing-subscriber = "0.2"
|
|||||||
tracing-futures = "0.2"
|
tracing-futures = "0.2"
|
||||||
|
|
||||||
[dependencies.songbird]
|
[dependencies.songbird]
|
||||||
|
features = ["receive"]
|
||||||
path = "../../../"
|
path = "../../../"
|
||||||
|
|
||||||
[dependencies.serenity]
|
[dependencies.serenity]
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 153 KiB |
@@ -113,9 +113,6 @@
|
|||||||
<g transform="matrix(0.942841,-0.272086,0.175081,0.606697,445.215,1481.7)">
|
<g transform="matrix(0.942841,-0.272086,0.175081,0.606697,445.215,1481.7)">
|
||||||
<path d="M212.72,146.182L212.72,132.063L528.222,132.063L528.222,103.825L540.487,139.123L528.222,174.421L528.222,146.182L212.72,146.182Z" style="fill:rgb(255,77,74);"/>
|
<path d="M212.72,146.182L212.72,132.063L528.222,132.063L528.222,103.825L540.487,139.123L528.222,174.421L528.222,146.182L212.72,146.182Z" style="fill:rgb(255,77,74);"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(1.39485,-2.12445e-16,-9.76394e-16,0.631455,377.024,1435.17)">
|
|
||||||
<path d="M212.72,146.182L212.72,132.063L531.858,132.063L531.858,103.825L540.487,139.123L531.858,174.421L531.858,146.182L212.72,146.182Z" style="fill:rgb(255,77,74);"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(1,0,0,1,57.9896,47.9044)">
|
<g transform="matrix(1,0,0,1,57.9896,47.9044)">
|
||||||
<g transform="matrix(1.01835,0,0,1.01835,-11.9813,-27.0542)">
|
<g transform="matrix(1.01835,0,0,1.01835,-11.9813,-27.0542)">
|
||||||
<ellipse cx="829.267" cy="1498.79" rx="134.889" ry="73.117" style="fill:white;stroke:rgb(62,62,62);stroke-width:3.82px;"/>
|
<ellipse cx="829.267" cy="1498.79" rx="134.889" ry="73.117" style="fill:white;stroke:rgb(62,62,62);stroke-width:3.82px;"/>
|
||||||
@@ -132,21 +129,10 @@
|
|||||||
<text x="743.378px" y="1473.58px" style="font-family:'FiraSans-Regular', 'Fira Sans', sans-serif;font-size:50px;">UDP Rx</text>
|
<text x="743.378px" y="1473.58px" style="font-family:'FiraSans-Regular', 'Fira Sans', sans-serif;font-size:50px;">UDP Rx</text>
|
||||||
</g>
|
</g>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(1,0,0,1,438.704,63.0321)">
|
|
||||||
<g transform="matrix(1.01835,0,0,1.01835,-11.9813,-27.0542)">
|
|
||||||
<ellipse cx="829.267" cy="1498.79" rx="134.889" ry="73.117" style="fill:white;stroke:rgb(62,62,62);stroke-width:3.82px;"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(1,0,0,1,9,40.3406)">
|
|
||||||
<text x="747.078px" y="1473.58px" style="font-family:'FiraSans-Regular', 'Fira Sans', sans-serif;font-size:50px;">UDP T<tspan x="877.528px " y="1473.58px ">x</tspan></text>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g>
|
<g>
|
||||||
<g transform="matrix(-0.266822,0.110146,-0.240947,-0.583678,811.395,1831.03)">
|
<g transform="matrix(-0.266822,0.110146,-0.240947,-0.583678,811.395,1831.03)">
|
||||||
<path d="M212.72,146.182L212.72,132.063L498.791,132.063L498.791,103.825L540.487,139.123L498.791,174.421L498.791,146.182L212.72,146.182Z" style="fill:rgb(62,62,62);"/>
|
<path d="M212.72,146.182L212.72,132.063L498.791,132.063L498.791,103.825L540.487,139.123L498.791,174.421L498.791,146.182L212.72,146.182Z" style="fill:rgb(62,62,62);"/>
|
||||||
</g>
|
</g>
|
||||||
<g transform="matrix(0.675184,-0.370199,0.303584,0.553689,739.208,1743.48)">
|
|
||||||
<path d="M212.72,146.182L212.72,132.063L524.856,132.063L524.856,103.825L540.487,139.123L524.856,174.421L524.856,146.182L212.72,146.182Z" style="fill:rgb(62,62,62);"/>
|
|
||||||
</g>
|
|
||||||
<g transform="matrix(0.494195,0.135428,-0.12143,0.443115,829.694,1701.34)">
|
<g transform="matrix(0.494195,0.135428,-0.12143,0.443115,829.694,1701.34)">
|
||||||
<path d="M230.444,146.182L230.444,174.421L212.72,139.123L230.444,103.825L230.444,132.063L523.396,132.063L523.396,103.825L540.487,139.123L523.396,174.421L523.396,146.182L230.444,146.182Z" style="fill:rgb(62,62,62);"/>
|
<path d="M230.444,146.182L230.444,174.421L212.72,139.123L230.444,103.825L230.444,132.063L523.396,132.063L523.396,103.825L540.487,139.123L523.396,174.421L523.396,146.182L230.444,146.182Z" style="fill:rgb(62,62,62);"/>
|
||||||
</g>
|
</g>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
@@ -1,6 +1,8 @@
|
|||||||
|
#[cfg(feature = "receive")]
|
||||||
|
use crate::driver::DecodeMode;
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
use crate::{
|
use crate::{
|
||||||
driver::{retry::Retry, CryptoMode, DecodeMode, MixMode},
|
driver::{retry::Retry, CryptoMode, MixMode},
|
||||||
input::codecs::*,
|
input::codecs::*,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -29,7 +31,8 @@ pub struct Config {
|
|||||||
///
|
///
|
||||||
/// [`CryptoMode::Normal`]: CryptoMode::Normal
|
/// [`CryptoMode::Normal`]: CryptoMode::Normal
|
||||||
pub crypto_mode: CryptoMode,
|
pub crypto_mode: CryptoMode,
|
||||||
#[cfg(feature = "driver")]
|
|
||||||
|
#[cfg(all(feature = "driver", feature = "receive"))]
|
||||||
/// Configures whether decoding and decryption occur for all received packets.
|
/// Configures whether decoding and decryption occur for all received packets.
|
||||||
///
|
///
|
||||||
/// If voice receiving voice packets, generally you should choose [`DecodeMode::Decode`].
|
/// If voice receiving voice packets, generally you should choose [`DecodeMode::Decode`].
|
||||||
@@ -45,6 +48,7 @@ pub struct Config {
|
|||||||
/// [`DecodeMode::Pass`]: DecodeMode::Pass
|
/// [`DecodeMode::Pass`]: DecodeMode::Pass
|
||||||
/// [user speaking events]: crate::events::CoreEvent::SpeakingUpdate
|
/// [user speaking events]: crate::events::CoreEvent::SpeakingUpdate
|
||||||
pub decode_mode: DecodeMode,
|
pub decode_mode: DecodeMode,
|
||||||
|
|
||||||
#[cfg(feature = "gateway")]
|
#[cfg(feature = "gateway")]
|
||||||
/// Configures the amount of time to wait for Discord to reply with connection information
|
/// Configures the amount of time to wait for Discord to reply with connection information
|
||||||
/// if [`Call::join`]/[`join_gateway`] are used.
|
/// if [`Call::join`]/[`join_gateway`] are used.
|
||||||
@@ -58,14 +62,16 @@ pub struct Config {
|
|||||||
/// [`Call::join`]: crate::Call::join
|
/// [`Call::join`]: crate::Call::join
|
||||||
/// [`join_gateway`]: crate::Call::join_gateway
|
/// [`join_gateway`]: crate::Call::join_gateway
|
||||||
pub gateway_timeout: Option<Duration>,
|
pub gateway_timeout: Option<Duration>,
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
/// Configures the maximum amount of time to wait for an attempted voice
|
/// Configures whether the driver will mix and output stereo or mono Opus data
|
||||||
/// connection to Discord.
|
/// over a voice channel.
|
||||||
///
|
///
|
||||||
/// Defaults to [`Stereo`].
|
/// Defaults to [`Stereo`].
|
||||||
///
|
///
|
||||||
/// [`Stereo`]: MixMode::Stereo
|
/// [`Stereo`]: MixMode::Stereo
|
||||||
pub mix_mode: MixMode,
|
pub mix_mode: MixMode,
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
/// Number of concurrently active tracks to allocate memory for.
|
/// Number of concurrently active tracks to allocate memory for.
|
||||||
///
|
///
|
||||||
@@ -79,6 +85,7 @@ pub struct Config {
|
|||||||
/// Changes to this field in a running driver will only ever increase
|
/// Changes to this field in a running driver will only ever increase
|
||||||
/// the capacity of the track store.
|
/// the capacity of the track store.
|
||||||
pub preallocated_tracks: usize,
|
pub preallocated_tracks: usize,
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
/// Connection retry logic for the [`Driver`].
|
/// Connection retry logic for the [`Driver`].
|
||||||
///
|
///
|
||||||
@@ -87,6 +94,7 @@ pub struct Config {
|
|||||||
///
|
///
|
||||||
/// [`Driver`]: crate::driver::Driver
|
/// [`Driver`]: crate::driver::Driver
|
||||||
pub driver_retry: Retry,
|
pub driver_retry: Retry,
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
/// Configures whether or not each mixed audio packet is [soft-clipped] into the
|
/// Configures whether or not each mixed audio packet is [soft-clipped] into the
|
||||||
/// [-1, 1] audio range.
|
/// [-1, 1] audio range.
|
||||||
@@ -101,12 +109,14 @@ pub struct Config {
|
|||||||
///
|
///
|
||||||
/// [soft-clipped]: https://opus-codec.org/docs/opus_api-1.3.1/group__opus__decoder.html#gaff99598b352e8939dded08d96e125e0b
|
/// [soft-clipped]: https://opus-codec.org/docs/opus_api-1.3.1/group__opus__decoder.html#gaff99598b352e8939dded08d96e125e0b
|
||||||
pub use_softclip: bool,
|
pub use_softclip: bool,
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
/// Configures the maximum amount of time to wait for an attempted voice
|
/// Configures the maximum amount of time to wait for an attempted voice
|
||||||
/// connection to Discord.
|
/// connection to Discord.
|
||||||
///
|
///
|
||||||
/// Defaults to 10 seconds. If set to `None`, connections will never time out.
|
/// Defaults to 10 seconds. If set to `None`, connections will never time out.
|
||||||
pub driver_timeout: Option<Duration>,
|
pub driver_timeout: Option<Duration>,
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
#[derivative(Debug = "ignore")]
|
#[derivative(Debug = "ignore")]
|
||||||
/// Registry of the inner codecs supported by the driver, adding audiopus-based
|
/// Registry of the inner codecs supported by the driver, adding audiopus-based
|
||||||
@@ -116,6 +126,7 @@ pub struct Config {
|
|||||||
///
|
///
|
||||||
/// [`CODEC_REGISTRY`]: static@CODEC_REGISTRY
|
/// [`CODEC_REGISTRY`]: static@CODEC_REGISTRY
|
||||||
pub codec_registry: &'static CodecRegistry,
|
pub codec_registry: &'static CodecRegistry,
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
#[derivative(Debug = "ignore")]
|
#[derivative(Debug = "ignore")]
|
||||||
/// Registry of the muxers and container formats supported by the driver.
|
/// Registry of the muxers and container formats supported by the driver.
|
||||||
@@ -142,7 +153,7 @@ impl Default for Config {
|
|||||||
Self {
|
Self {
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
crypto_mode: CryptoMode::Normal,
|
crypto_mode: CryptoMode::Normal,
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(all(feature = "driver", feature = "receive"))]
|
||||||
decode_mode: DecodeMode::Decrypt,
|
decode_mode: DecodeMode::Decrypt,
|
||||||
#[cfg(feature = "gateway")]
|
#[cfg(feature = "gateway")]
|
||||||
gateway_timeout: Some(Duration::from_secs(10)),
|
gateway_timeout: Some(Duration::from_secs(10)),
|
||||||
@@ -179,6 +190,7 @@ impl Config {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
/// Sets this `Config`'s received packet decryption/decoding behaviour.
|
/// Sets this `Config`'s received packet decryption/decoding behaviour.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn decode_mode(mut self, decode_mode: DecodeMode) -> Self {
|
pub fn decode_mode(mut self, decode_mode: DecodeMode) -> Self {
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
|
use super::tasks::udp_rx;
|
||||||
use super::{
|
use super::{
|
||||||
tasks::{message::*, udp_rx, udp_tx, ws as ws_task},
|
tasks::{message::*, ws as ws_task},
|
||||||
Config,
|
Config,
|
||||||
CryptoMode,
|
CryptoMode,
|
||||||
};
|
};
|
||||||
@@ -18,7 +20,8 @@ use crate::{
|
|||||||
use discortp::discord::{IpDiscoveryPacket, IpDiscoveryType, MutableIpDiscoveryPacket};
|
use discortp::discord::{IpDiscoveryPacket, IpDiscoveryType, MutableIpDiscoveryPacket};
|
||||||
use error::{Error, Result};
|
use error::{Error, Result};
|
||||||
use flume::Sender;
|
use flume::Sender;
|
||||||
use std::{net::IpAddr, str::FromStr, sync::Arc};
|
use socket2::Socket;
|
||||||
|
use std::{net::IpAddr, str::FromStr};
|
||||||
use tokio::{net::UdpSocket, spawn, time::timeout};
|
use tokio::{net::UdpSocket, spawn, time::timeout};
|
||||||
use tracing::{debug, info, instrument};
|
use tracing::{debug, info, instrument};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@@ -103,6 +106,16 @@ impl Connection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let udp = UdpSocket::bind("0.0.0.0:0").await?;
|
let udp = UdpSocket::bind("0.0.0.0:0").await?;
|
||||||
|
|
||||||
|
// Optimisation for non-receive case: set rx buffer size to zero.
|
||||||
|
let udp = if cfg!(feature = "receive") {
|
||||||
|
udp
|
||||||
|
} else {
|
||||||
|
let socket = Socket::from(udp.into_std()?);
|
||||||
|
socket.set_recv_buffer_size(0)?;
|
||||||
|
UdpSocket::from_std(socket.into())?
|
||||||
|
};
|
||||||
|
|
||||||
udp.connect((ready.ip, ready.port)).await?;
|
udp.connect((ready.ip, ready.port)).await?;
|
||||||
|
|
||||||
// Follow Discord's IP Discovery procedures, in case NAT tunnelling is needed.
|
// Follow Discord's IP Discovery procedures, in case NAT tunnelling is needed.
|
||||||
@@ -164,22 +177,36 @@ impl Connection {
|
|||||||
info!("WS heartbeat duration {}ms.", hello.heartbeat_interval,);
|
info!("WS heartbeat duration {}ms.", hello.heartbeat_interval,);
|
||||||
|
|
||||||
let (ws_msg_tx, ws_msg_rx) = flume::unbounded();
|
let (ws_msg_tx, ws_msg_rx) = flume::unbounded();
|
||||||
let (udp_sender_msg_tx, udp_sender_msg_rx) = flume::unbounded();
|
#[cfg(feature = "receive")]
|
||||||
let (udp_receiver_msg_tx, udp_receiver_msg_rx) = flume::unbounded();
|
let (udp_receiver_msg_tx, udp_receiver_msg_rx) = flume::unbounded();
|
||||||
|
|
||||||
|
// NOTE: This causes the UDP Socket on "receive" to be non-blocking,
|
||||||
|
// and the standard to be blocking. A UDP send should only WouldBlock if
|
||||||
|
// you're sending more data than the OS can handle (not likely, and
|
||||||
|
// at that point you should scale horizontally).
|
||||||
|
//
|
||||||
|
// If this is a problem for anyone, we can make non-blocking sends
|
||||||
|
// queue up a delayed send up to a limit.
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
let (udp_rx, udp_tx) = {
|
let (udp_rx, udp_tx) = {
|
||||||
let udp_rx = Arc::new(udp);
|
let udp_tx = udp.into_std()?;
|
||||||
let udp_tx = Arc::clone(&udp_rx);
|
let udp_rx = UdpSocket::from_std(udp_tx.try_clone()?)?;
|
||||||
(udp_rx, udp_tx)
|
(udp_rx, udp_tx)
|
||||||
};
|
};
|
||||||
|
#[cfg(not(feature = "receive"))]
|
||||||
|
let udp_tx = udp.into_std()?;
|
||||||
|
|
||||||
let ssrc = ready.ssrc;
|
let ssrc = ready.ssrc;
|
||||||
|
|
||||||
let mix_conn = MixerConnection {
|
let mix_conn = MixerConnection {
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
cipher: cipher.clone(),
|
cipher: cipher.clone(),
|
||||||
|
#[cfg(not(feature = "receive"))]
|
||||||
|
cipher,
|
||||||
crypto_state: config.crypto_mode.into(),
|
crypto_state: config.crypto_mode.into(),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
udp_rx: udp_receiver_msg_tx,
|
udp_rx: udp_receiver_msg_tx,
|
||||||
udp_tx: udp_sender_msg_tx,
|
udp_tx,
|
||||||
};
|
};
|
||||||
|
|
||||||
interconnect
|
interconnect
|
||||||
@@ -200,6 +227,7 @@ impl Connection {
|
|||||||
info.clone(),
|
info.clone(),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
spawn(udp_rx::runner(
|
spawn(udp_rx::runner(
|
||||||
interconnect.clone(),
|
interconnect.clone(),
|
||||||
udp_receiver_msg_rx,
|
udp_receiver_msg_rx,
|
||||||
@@ -207,7 +235,6 @@ impl Connection {
|
|||||||
config.clone(),
|
config.clone(),
|
||||||
udp_rx,
|
udp_rx,
|
||||||
));
|
));
|
||||||
spawn(udp_tx::runner(udp_sender_msg_rx, ssrc, udp_tx));
|
|
||||||
|
|
||||||
Ok(Connection {
|
Ok(Connection {
|
||||||
info,
|
info,
|
||||||
|
|||||||
@@ -3,10 +3,11 @@ use byteorder::{NetworkEndian, WriteBytesExt};
|
|||||||
use discortp::{rtp::RtpPacket, MutablePacket};
|
use discortp::{rtp::RtpPacket, MutablePacket};
|
||||||
use rand::Rng;
|
use rand::Rng;
|
||||||
use std::num::Wrapping;
|
use std::num::Wrapping;
|
||||||
|
#[cfg(any(feature = "receive", test))]
|
||||||
|
use xsalsa20poly1305::Tag;
|
||||||
use xsalsa20poly1305::{
|
use xsalsa20poly1305::{
|
||||||
aead::{AeadInPlace, Error as CryptoError},
|
aead::{AeadInPlace, Error as CryptoError},
|
||||||
Nonce,
|
Nonce,
|
||||||
Tag,
|
|
||||||
XSalsa20Poly1305 as Cipher,
|
XSalsa20Poly1305 as Cipher,
|
||||||
NONCE_SIZE,
|
NONCE_SIZE,
|
||||||
TAG_SIZE,
|
TAG_SIZE,
|
||||||
@@ -110,6 +111,7 @@ impl CryptoMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "receive", test))]
|
||||||
/// Decrypts a Discord RT(C)P packet using the given key.
|
/// Decrypts a Discord RT(C)P packet using the given key.
|
||||||
///
|
///
|
||||||
/// If successful, this returns the number of bytes to be ignored from the
|
/// If successful, this returns the number of bytes to be ignored from the
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ pub mod bench_internals;
|
|||||||
|
|
||||||
pub(crate) mod connection;
|
pub(crate) mod connection;
|
||||||
mod crypto;
|
mod crypto;
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
mod decode_mode;
|
mod decode_mode;
|
||||||
mod mix_mode;
|
mod mix_mode;
|
||||||
pub mod retry;
|
pub mod retry;
|
||||||
@@ -23,6 +24,7 @@ pub(crate) mod test_config;
|
|||||||
use connection::error::{Error, Result};
|
use connection::error::{Error, Result};
|
||||||
pub use crypto::CryptoMode;
|
pub use crypto::CryptoMode;
|
||||||
pub(crate) use crypto::CryptoState;
|
pub(crate) use crypto::CryptoState;
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
pub use decode_mode::DecodeMode;
|
pub use decode_mode::DecodeMode;
|
||||||
pub use mix_mode::MixMode;
|
pub use mix_mode::MixMode;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use super::message::*;
|
|||||||
use crate::ws::Error as WsError;
|
use crate::ws::Error as WsError;
|
||||||
use audiopus::Error as OpusError;
|
use audiopus::Error as OpusError;
|
||||||
use flume::SendError;
|
use flume::SendError;
|
||||||
use std::io::Error as IoError;
|
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
|
||||||
use xsalsa20poly1305::aead::Error as CryptoError;
|
use xsalsa20poly1305::aead::Error as CryptoError;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -10,8 +10,8 @@ pub enum Recipient {
|
|||||||
AuxNetwork,
|
AuxNetwork,
|
||||||
Event,
|
Event,
|
||||||
Mixer,
|
Mixer,
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
UdpRx,
|
UdpRx,
|
||||||
UdpTx,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
@@ -20,6 +20,7 @@ pub type Result<T> = std::result::Result<T, Error>;
|
|||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Crypto(CryptoError),
|
Crypto(CryptoError),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
/// Received an illegal voice packet on the voice UDP socket.
|
/// Received an illegal voice packet on the voice UDP socket.
|
||||||
IllegalVoicePacket,
|
IllegalVoicePacket,
|
||||||
InterconnectFailure(Recipient),
|
InterconnectFailure(Recipient),
|
||||||
@@ -30,15 +31,26 @@ pub enum Error {
|
|||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
pub(crate) fn should_trigger_connect(&self) -> bool {
|
pub(crate) fn should_trigger_connect(&self) -> bool {
|
||||||
matches!(
|
match self {
|
||||||
self,
|
Error::InterconnectFailure(Recipient::AuxNetwork) => true,
|
||||||
Error::InterconnectFailure(Recipient::AuxNetwork | Recipient::UdpRx | Recipient::UdpTx)
|
#[cfg(feature = "receive")]
|
||||||
)
|
Error::InterconnectFailure(Recipient::UdpRx) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn should_trigger_interconnect_rebuild(&self) -> bool {
|
pub(crate) fn should_trigger_interconnect_rebuild(&self) -> bool {
|
||||||
matches!(self, Error::InterconnectFailure(Recipient::Event))
|
matches!(self, Error::InterconnectFailure(Recipient::Event))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This prevents a `WouldBlock` from triggering a full reconnect,
|
||||||
|
// instead simply dropping the packet.
|
||||||
|
pub(crate) fn disarm_would_block(self) -> Result<()> {
|
||||||
|
match self {
|
||||||
|
Self::Io(i) if i.kind() == IoErrorKind::WouldBlock => Ok(()),
|
||||||
|
e => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<CryptoError> for Error {
|
impl From<CryptoError> for Error {
|
||||||
@@ -77,18 +89,13 @@ impl From<SendError<MixerMessage>> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
impl From<SendError<UdpRxMessage>> for Error {
|
impl From<SendError<UdpRxMessage>> for Error {
|
||||||
fn from(_e: SendError<UdpRxMessage>) -> Error {
|
fn from(_e: SendError<UdpRxMessage>) -> Error {
|
||||||
Error::InterconnectFailure(Recipient::UdpRx)
|
Error::InterconnectFailure(Recipient::UdpRx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<SendError<UdpTxMessage>> for Error {
|
|
||||||
fn from(_e: SendError<UdpTxMessage>) -> Error {
|
|
||||||
Error::InterconnectFailure(Recipient::UdpTx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<WsError> for Error {
|
impl From<WsError> for Error {
|
||||||
fn from(e: WsError) -> Error {
|
fn from(e: WsError) -> Error {
|
||||||
Error::Ws(e)
|
Error::Ws(e)
|
||||||
|
|||||||
@@ -1,21 +1,24 @@
|
|||||||
#![allow(missing_docs)]
|
#![allow(missing_docs)]
|
||||||
|
|
||||||
use super::{Interconnect, TrackContext, UdpRxMessage, UdpTxMessage, WsMessage};
|
#[cfg(feature = "receive")]
|
||||||
|
use super::UdpRxMessage;
|
||||||
|
use super::{Interconnect, TrackContext, WsMessage};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
driver::{Bitrate, Config, CryptoState},
|
driver::{Bitrate, Config, CryptoState},
|
||||||
input::{AudioStreamError, Compose, Parsed},
|
input::{AudioStreamError, Compose, Parsed},
|
||||||
};
|
};
|
||||||
use flume::Sender;
|
use flume::Sender;
|
||||||
use std::sync::Arc;
|
use std::{net::UdpSocket, sync::Arc};
|
||||||
use symphonia_core::{errors::Error as SymphoniaError, formats::SeekedTo};
|
use symphonia_core::{errors::Error as SymphoniaError, formats::SeekedTo};
|
||||||
use xsalsa20poly1305::XSalsa20Poly1305 as Cipher;
|
use xsalsa20poly1305::XSalsa20Poly1305 as Cipher;
|
||||||
|
|
||||||
pub struct MixerConnection {
|
pub struct MixerConnection {
|
||||||
pub cipher: Cipher,
|
pub cipher: Cipher,
|
||||||
pub crypto_state: CryptoState,
|
pub crypto_state: CryptoState,
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
pub udp_rx: Sender<UdpRxMessage>,
|
pub udp_rx: Sender<UdpRxMessage>,
|
||||||
pub udp_tx: Sender<UdpTxMessage>,
|
pub udp_tx: UdpSocket,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MixerMessage {
|
pub enum MixerMessage {
|
||||||
|
|||||||
@@ -4,11 +4,13 @@ mod core;
|
|||||||
mod disposal;
|
mod disposal;
|
||||||
mod events;
|
mod events;
|
||||||
mod mixer;
|
mod mixer;
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
mod udp_rx;
|
mod udp_rx;
|
||||||
mod udp_tx;
|
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
pub use self::{core::*, disposal::*, events::*, mixer::*, udp_rx::*, udp_tx::*, ws::*};
|
#[cfg(feature = "receive")]
|
||||||
|
pub use self::udp_rx::*;
|
||||||
|
pub use self::{core::*, disposal::*, events::*, mixer::*, ws::*};
|
||||||
|
|
||||||
use flume::Sender;
|
use flume::Sender;
|
||||||
use tokio::spawn;
|
use tokio::spawn;
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
#![allow(missing_docs)]
|
|
||||||
|
|
||||||
// TODO: do something cheaper.
|
|
||||||
pub type UdpTxMessage = Vec<u8>;
|
|
||||||
@@ -10,7 +10,11 @@ use result::*;
|
|||||||
use state::*;
|
use state::*;
|
||||||
pub use track::*;
|
pub use track::*;
|
||||||
|
|
||||||
use super::{disposal, error::Result, message::*};
|
use super::{
|
||||||
|
disposal,
|
||||||
|
error::{Error, Result},
|
||||||
|
message::*,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
constants::*,
|
constants::*,
|
||||||
driver::MixMode,
|
driver::MixMode,
|
||||||
@@ -26,6 +30,7 @@ use audiopus::{
|
|||||||
Bitrate,
|
Bitrate,
|
||||||
};
|
};
|
||||||
use discortp::{
|
use discortp::{
|
||||||
|
discord::MutableKeepalivePacket,
|
||||||
rtp::{MutableRtpPacket, RtpPacket},
|
rtp::{MutableRtpPacket, RtpPacket},
|
||||||
MutablePacket,
|
MutablePacket,
|
||||||
};
|
};
|
||||||
@@ -73,6 +78,9 @@ pub struct Mixer {
|
|||||||
thread_pool: BlockyTaskPool,
|
thread_pool: BlockyTaskPool,
|
||||||
pub ws: Option<Sender<WsMessage>>,
|
pub ws: Option<Sender<WsMessage>>,
|
||||||
|
|
||||||
|
pub keepalive_deadline: Instant,
|
||||||
|
pub keepalive_packet: [u8; MutableKeepalivePacket::minimum_packet_size()],
|
||||||
|
|
||||||
pub tracks: Vec<InternalTrack>,
|
pub tracks: Vec<InternalTrack>,
|
||||||
track_handles: Vec<TrackHandle>,
|
track_handles: Vec<TrackHandle>,
|
||||||
|
|
||||||
@@ -104,6 +112,7 @@ impl Mixer {
|
|||||||
let soft_clip = SoftClip::new(config.mix_mode.to_opus());
|
let soft_clip = SoftClip::new(config.mix_mode.to_opus());
|
||||||
|
|
||||||
let mut packet = [0u8; VOICE_PACKET_MAX];
|
let mut packet = [0u8; VOICE_PACKET_MAX];
|
||||||
|
let keepalive_packet = [0u8; MutableKeepalivePacket::minimum_packet_size()];
|
||||||
|
|
||||||
let mut rtp = MutableRtpPacket::new(&mut packet[..]).expect(
|
let mut rtp = MutableRtpPacket::new(&mut packet[..]).expect(
|
||||||
"FATAL: Too few bytes in self.packet for RTP header.\
|
"FATAL: Too few bytes in self.packet for RTP header.\
|
||||||
@@ -146,12 +155,14 @@ impl Mixer {
|
|||||||
SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, Layout::Stereo),
|
SignalSpec::new_with_layout(SAMPLE_RATE_RAW as u32, Layout::Stereo),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let deadline = Instant::now();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
bitrate,
|
bitrate,
|
||||||
config,
|
config,
|
||||||
conn_active: None,
|
conn_active: None,
|
||||||
content_prep_sequence: 0,
|
content_prep_sequence: 0,
|
||||||
deadline: Instant::now(),
|
deadline,
|
||||||
disposer,
|
disposer,
|
||||||
encoder,
|
encoder,
|
||||||
interconnect,
|
interconnect,
|
||||||
@@ -165,6 +176,9 @@ impl Mixer {
|
|||||||
thread_pool,
|
thread_pool,
|
||||||
ws: None,
|
ws: None,
|
||||||
|
|
||||||
|
keepalive_deadline: deadline,
|
||||||
|
keepalive_packet,
|
||||||
|
|
||||||
tracks,
|
tracks,
|
||||||
track_handles,
|
track_handles,
|
||||||
|
|
||||||
@@ -213,7 +227,14 @@ impl Mixer {
|
|||||||
// The above action may have invalidated the connection; need to re-check!
|
// The above action may have invalidated the connection; need to re-check!
|
||||||
// Also, if we're in a test mode we should unconditionally run packet mixing code.
|
// Also, if we're in a test mode we should unconditionally run packet mixing code.
|
||||||
if self.conn_active.is_some() || ignore_check {
|
if self.conn_active.is_some() || ignore_check {
|
||||||
if let Err(e) = self.cycle().and_then(|_| self.audio_commands_events()) {
|
if let Err(e) = self
|
||||||
|
.cycle()
|
||||||
|
.and_then(|_| self.audio_commands_events())
|
||||||
|
.and_then(|_| {
|
||||||
|
self.check_and_send_keepalive()
|
||||||
|
.or_else(Error::disarm_would_block)
|
||||||
|
})
|
||||||
|
{
|
||||||
events_failure |= e.should_trigger_interconnect_rebuild();
|
events_failure |= e.should_trigger_interconnect_rebuild();
|
||||||
conn_failure |= e.should_trigger_connect();
|
conn_failure |= e.should_trigger_connect();
|
||||||
|
|
||||||
@@ -313,6 +334,11 @@ impl Mixer {
|
|||||||
rtp.set_sequence(random::<u16>().into());
|
rtp.set_sequence(random::<u16>().into());
|
||||||
rtp.set_timestamp(random::<u32>().into());
|
rtp.set_timestamp(random::<u32>().into());
|
||||||
self.deadline = Instant::now();
|
self.deadline = Instant::now();
|
||||||
|
|
||||||
|
let mut ka = MutableKeepalivePacket::new(&mut self.keepalive_packet[..])
|
||||||
|
.expect("FATAL: Insufficient bytes given to keepalive packet.");
|
||||||
|
ka.set_ssrc(ssrc);
|
||||||
|
self.keepalive_deadline = self.deadline + UDP_KEEPALIVE_GAP;
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
MixerMessage::DropConn => {
|
MixerMessage::DropConn => {
|
||||||
@@ -321,9 +347,12 @@ impl Mixer {
|
|||||||
},
|
},
|
||||||
MixerMessage::ReplaceInterconnect(i) => {
|
MixerMessage::ReplaceInterconnect(i) => {
|
||||||
self.prevent_events = false;
|
self.prevent_events = false;
|
||||||
|
|
||||||
if let Some(ws) = &self.ws {
|
if let Some(ws) = &self.ws {
|
||||||
conn_failure |= ws.send(WsMessage::ReplaceInterconnect(i.clone())).is_err();
|
conn_failure |= ws.send(WsMessage::ReplaceInterconnect(i.clone())).is_err();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
if let Some(conn) = &self.conn_active {
|
if let Some(conn) = &self.conn_active {
|
||||||
conn_failure |= conn
|
conn_failure |= conn
|
||||||
.udp_rx
|
.udp_rx
|
||||||
@@ -357,13 +386,19 @@ impl Mixer {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.config = Arc::new(new_config.clone());
|
self.config = Arc::new(
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
|
new_config.clone(),
|
||||||
|
#[cfg(not(feature = "receive"))]
|
||||||
|
new_config,
|
||||||
|
);
|
||||||
|
|
||||||
if self.tracks.capacity() < self.config.preallocated_tracks {
|
if self.tracks.capacity() < self.config.preallocated_tracks {
|
||||||
self.tracks
|
self.tracks
|
||||||
.reserve(self.config.preallocated_tracks - self.tracks.len());
|
.reserve(self.config.preallocated_tracks - self.tracks.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
if let Some(conn) = &self.conn_active {
|
if let Some(conn) = &self.conn_active {
|
||||||
conn_failure |= conn
|
conn_failure |= conn
|
||||||
.udp_rx
|
.udp_rx
|
||||||
@@ -674,7 +709,7 @@ impl Mixer {
|
|||||||
let send_buffer = self.config.use_softclip.then(|| &softclip_buffer[..]);
|
let send_buffer = self.config.use_softclip.then(|| &softclip_buffer[..]);
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
if let Some(OutputMode::Raw(tx)) = &self.config.override_connection {
|
let send_status = if let Some(OutputMode::Raw(tx)) = &self.config.override_connection {
|
||||||
let msg = match mix_len {
|
let msg = match mix_len {
|
||||||
MixType::Passthrough(len) if len == SILENT_FRAME.len() => OutputMessage::Silent,
|
MixType::Passthrough(len) if len == SILENT_FRAME.len() => OutputMessage::Silent,
|
||||||
MixType::Passthrough(len) => {
|
MixType::Passthrough(len) => {
|
||||||
@@ -693,12 +728,18 @@ impl Mixer {
|
|||||||
};
|
};
|
||||||
|
|
||||||
drop(tx.send(msg.into()));
|
drop(tx.send(msg.into()));
|
||||||
|
|
||||||
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
self.prep_and_send_packet(send_buffer, mix_len)?;
|
self.prep_and_send_packet(send_buffer, mix_len)
|
||||||
}
|
};
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
self.prep_and_send_packet(send_buffer, mix_len)?;
|
let send_status = self.prep_and_send_packet(send_buffer, mix_len);
|
||||||
|
|
||||||
|
send_status.or_else(Error::disarm_would_block)?;
|
||||||
|
|
||||||
|
self.advance_rtp_counters();
|
||||||
|
|
||||||
// Zero out all planes of the mix buffer if any audio was written.
|
// Zero out all planes of the mix buffer if any audio was written.
|
||||||
if matches!(mix_len, MixType::MixedPcm(a) if a > 0) {
|
if matches!(mix_len, MixType::MixedPcm(a) if a > 0) {
|
||||||
@@ -770,25 +811,36 @@ impl Mixer {
|
|||||||
// Test mode: send unencrypted (compressed) packets to local receiver.
|
// Test mode: send unencrypted (compressed) packets to local receiver.
|
||||||
drop(tx.send(self.packet[..index].to_vec().into()));
|
drop(tx.send(self.packet[..index].to_vec().into()));
|
||||||
} else {
|
} else {
|
||||||
conn.udp_tx.send(self.packet[..index].to_vec())?;
|
conn.udp_tx.send(&self.packet[..index])?;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(test))]
|
#[cfg(not(test))]
|
||||||
{
|
{
|
||||||
// Normal operation: send encrypted payload to UDP Tx task.
|
// Normal operation: send encrypted payload to UDP Tx task.
|
||||||
|
conn.udp_tx.send(&self.packet[..index])?;
|
||||||
// TODO: This is dog slow, don't do this.
|
|
||||||
// Can we replace this with a shared ring buffer + semaphore?
|
|
||||||
// or the BBQueue crate?
|
|
||||||
conn.udp_tx.send(self.packet[..index].to_vec())?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn advance_rtp_counters(&mut self) {
|
||||||
let mut rtp = MutableRtpPacket::new(&mut self.packet[..]).expect(
|
let mut rtp = MutableRtpPacket::new(&mut self.packet[..]).expect(
|
||||||
"FATAL: Too few bytes in self.packet for RTP header.\
|
"FATAL: Too few bytes in self.packet for RTP header.\
|
||||||
(Blame: VOICE_PACKET_MAX?)",
|
(Blame: VOICE_PACKET_MAX?)",
|
||||||
);
|
);
|
||||||
rtp.set_sequence(rtp.get_sequence() + 1);
|
rtp.set_sequence(rtp.get_sequence() + 1);
|
||||||
rtp.set_timestamp(rtp.get_timestamp() + MONO_FRAME_SIZE as u32);
|
rtp.set_timestamp(rtp.get_timestamp() + MONO_FRAME_SIZE as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn check_and_send_keepalive(&mut self) -> Result<()> {
|
||||||
|
if let Some(conn) = self.conn_active.as_mut() {
|
||||||
|
if Instant::now() >= self.keepalive_deadline {
|
||||||
|
conn.udp_tx.send(&self.keepalive_packet)?;
|
||||||
|
self.keepalive_deadline += UDP_KEEPALIVE_GAP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ pub mod error;
|
|||||||
mod events;
|
mod events;
|
||||||
pub mod message;
|
pub mod message;
|
||||||
pub mod mixer;
|
pub mod mixer;
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
pub(crate) mod udp_rx;
|
pub(crate) mod udp_rx;
|
||||||
pub(crate) mod udp_tx;
|
|
||||||
pub(crate) mod ws;
|
pub(crate) mod ws;
|
||||||
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ use discortp::{
|
|||||||
PacketSize,
|
PacketSize,
|
||||||
};
|
};
|
||||||
use flume::Receiver;
|
use flume::Receiver;
|
||||||
use std::{collections::HashMap, convert::TryInto, sync::Arc};
|
use std::{collections::HashMap, convert::TryInto};
|
||||||
use tokio::{net::UdpSocket, select};
|
use tokio::{net::UdpSocket, select};
|
||||||
use tracing::{error, instrument, trace, warn};
|
use tracing::{error, instrument, trace, warn};
|
||||||
use xsalsa20poly1305::XSalsa20Poly1305 as Cipher;
|
use xsalsa20poly1305::XSalsa20Poly1305 as Cipher;
|
||||||
@@ -240,7 +240,7 @@ struct UdpRx {
|
|||||||
config: Config,
|
config: Config,
|
||||||
packet_buffer: [u8; VOICE_PACKET_MAX],
|
packet_buffer: [u8; VOICE_PACKET_MAX],
|
||||||
rx: Receiver<UdpRxMessage>,
|
rx: Receiver<UdpRxMessage>,
|
||||||
udp_socket: Arc<UdpSocket>,
|
udp_socket: UdpSocket,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UdpRx {
|
impl UdpRx {
|
||||||
@@ -395,7 +395,7 @@ pub(crate) async fn runner(
|
|||||||
rx: Receiver<UdpRxMessage>,
|
rx: Receiver<UdpRxMessage>,
|
||||||
cipher: Cipher,
|
cipher: Cipher,
|
||||||
config: Config,
|
config: Config,
|
||||||
udp_socket: Arc<UdpSocket>,
|
udp_socket: UdpSocket,
|
||||||
) {
|
) {
|
||||||
trace!("UDP receive handle started.");
|
trace!("UDP receive handle started.");
|
||||||
|
|
||||||
|
|||||||
@@ -1,63 +0,0 @@
|
|||||||
use super::message::*;
|
|
||||||
use crate::constants::*;
|
|
||||||
use discortp::discord::MutableKeepalivePacket;
|
|
||||||
use flume::Receiver;
|
|
||||||
use std::sync::Arc;
|
|
||||||
use tokio::{
|
|
||||||
net::UdpSocket,
|
|
||||||
time::{timeout_at, Instant},
|
|
||||||
};
|
|
||||||
use tracing::{error, instrument, trace};
|
|
||||||
|
|
||||||
struct UdpTx {
|
|
||||||
ssrc: u32,
|
|
||||||
rx: Receiver<UdpTxMessage>,
|
|
||||||
udp_tx: Arc<UdpSocket>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl UdpTx {
|
|
||||||
async fn run(&mut self) {
|
|
||||||
let mut keepalive_bytes = [0u8; MutableKeepalivePacket::minimum_packet_size()];
|
|
||||||
let mut ka = MutableKeepalivePacket::new(&mut keepalive_bytes[..])
|
|
||||||
.expect("FATAL: Insufficient bytes given to keepalive packet.");
|
|
||||||
ka.set_ssrc(self.ssrc);
|
|
||||||
|
|
||||||
let mut ka_time = Instant::now() + UDP_KEEPALIVE_GAP;
|
|
||||||
|
|
||||||
loop {
|
|
||||||
match timeout_at(ka_time, self.rx.recv_async()).await {
|
|
||||||
Err(_) => {
|
|
||||||
trace!("Sending UDP Keepalive.");
|
|
||||||
if let Err(e) = self.udp_tx.send(&keepalive_bytes[..]).await {
|
|
||||||
error!("Fatal UDP keepalive send error: {:?}.", e);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
ka_time += UDP_KEEPALIVE_GAP;
|
|
||||||
},
|
|
||||||
Ok(Ok(p)) =>
|
|
||||||
if let Err(e) = self.udp_tx.send(&p[..]).await {
|
|
||||||
error!("Fatal UDP packet send error: {:?}.", e);
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
Ok(Err(flume::RecvError::Disconnected)) => {
|
|
||||||
break;
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[instrument(skip(udp_msg_rx))]
|
|
||||||
pub(crate) async fn runner(udp_msg_rx: Receiver<UdpTxMessage>, ssrc: u32, udp_tx: Arc<UdpSocket>) {
|
|
||||||
trace!("UDP transmit handle started.");
|
|
||||||
|
|
||||||
let mut txer = UdpTx {
|
|
||||||
ssrc,
|
|
||||||
rx: udp_msg_rx,
|
|
||||||
udp_tx,
|
|
||||||
};
|
|
||||||
|
|
||||||
txer.run().await;
|
|
||||||
|
|
||||||
trace!("UDP transmit handle stopped.");
|
|
||||||
}
|
|
||||||
@@ -3,10 +3,16 @@
|
|||||||
//! [`EventContext`]: super::EventContext
|
//! [`EventContext`]: super::EventContext
|
||||||
mod connect;
|
mod connect;
|
||||||
mod disconnect;
|
mod disconnect;
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
mod rtcp;
|
mod rtcp;
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
mod speaking;
|
mod speaking;
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
mod voice;
|
mod voice;
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
use discortp::{rtcp::Rtcp, rtp::Rtp};
|
use discortp::{rtcp::Rtcp, rtp::Rtp};
|
||||||
|
|
||||||
pub use self::{connect::*, disconnect::*, rtcp::*, speaking::*, voice::*};
|
pub use self::{connect::*, disconnect::*};
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
|
pub use self::{rtcp::*, speaking::*, voice::*};
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
use super::context_data::*;
|
use super::context_data::*;
|
||||||
use crate::ConnectionInfo;
|
use crate::ConnectionInfo;
|
||||||
use discortp::{rtcp::Rtcp, rtp::Rtp};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
pub struct InternalConnect {
|
pub struct InternalConnect {
|
||||||
@@ -15,27 +14,6 @@ pub struct InternalDisconnect {
|
|||||||
pub info: ConnectionInfo,
|
pub info: ConnectionInfo,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
|
||||||
pub struct InternalSpeakingUpdate {
|
|
||||||
pub ssrc: u32,
|
|
||||||
pub speaking: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct InternalVoicePacket {
|
|
||||||
pub audio: Option<Vec<i16>>,
|
|
||||||
pub packet: Rtp,
|
|
||||||
pub payload_offset: usize,
|
|
||||||
pub payload_end_pad: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
||||||
pub struct InternalRtcpPacket {
|
|
||||||
pub packet: Rtcp,
|
|
||||||
pub payload_offset: usize,
|
|
||||||
pub payload_end_pad: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> From<&'a InternalConnect> for ConnectData<'a> {
|
impl<'a> From<&'a InternalConnect> for ConnectData<'a> {
|
||||||
fn from(val: &'a InternalConnect) -> Self {
|
fn from(val: &'a InternalConnect) -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -60,16 +38,42 @@ impl<'a> From<&'a InternalDisconnect> for DisconnectData<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a InternalSpeakingUpdate> for SpeakingUpdateData {
|
#[cfg(feature = "receive")]
|
||||||
|
mod receive {
|
||||||
|
use super::*;
|
||||||
|
use discortp::{rtcp::Rtcp, rtp::Rtp};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||||
|
pub struct InternalSpeakingUpdate {
|
||||||
|
pub ssrc: u32,
|
||||||
|
pub speaking: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct InternalVoicePacket {
|
||||||
|
pub audio: Option<Vec<i16>>,
|
||||||
|
pub packet: Rtp,
|
||||||
|
pub payload_offset: usize,
|
||||||
|
pub payload_end_pad: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct InternalRtcpPacket {
|
||||||
|
pub packet: Rtcp,
|
||||||
|
pub payload_offset: usize,
|
||||||
|
pub payload_end_pad: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> From<&'a InternalSpeakingUpdate> for SpeakingUpdateData {
|
||||||
fn from(val: &'a InternalSpeakingUpdate) -> Self {
|
fn from(val: &'a InternalSpeakingUpdate) -> Self {
|
||||||
Self {
|
Self {
|
||||||
speaking: val.speaking,
|
speaking: val.speaking,
|
||||||
ssrc: val.ssrc,
|
ssrc: val.ssrc,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a InternalVoicePacket> for VoiceData<'a> {
|
impl<'a> From<&'a InternalVoicePacket> for VoiceData<'a> {
|
||||||
fn from(val: &'a InternalVoicePacket) -> Self {
|
fn from(val: &'a InternalVoicePacket) -> Self {
|
||||||
Self {
|
Self {
|
||||||
audio: &val.audio,
|
audio: &val.audio,
|
||||||
@@ -78,9 +82,9 @@ impl<'a> From<&'a InternalVoicePacket> for VoiceData<'a> {
|
|||||||
payload_end_pad: val.payload_end_pad,
|
payload_end_pad: val.payload_end_pad,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> From<&'a InternalRtcpPacket> for RtcpData<'a> {
|
impl<'a> From<&'a InternalRtcpPacket> for RtcpData<'a> {
|
||||||
fn from(val: &'a InternalRtcpPacket) -> Self {
|
fn from(val: &'a InternalRtcpPacket) -> Self {
|
||||||
Self {
|
Self {
|
||||||
packet: &val.packet,
|
packet: &val.packet,
|
||||||
@@ -88,4 +92,8 @@ impl<'a> From<&'a InternalRtcpPacket> for RtcpData<'a> {
|
|||||||
payload_end_pad: val.payload_end_pad,
|
payload_end_pad: val.payload_end_pad,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
|
pub use receive::*;
|
||||||
|
|||||||
@@ -26,24 +26,35 @@ pub enum EventContext<'a> {
|
|||||||
/// [`EventStore::add_event`]: EventStore::add_event
|
/// [`EventStore::add_event`]: EventStore::add_event
|
||||||
/// [`TrackHandle::add_event`]: TrackHandle::add_event
|
/// [`TrackHandle::add_event`]: TrackHandle::add_event
|
||||||
Track(&'a [(&'a TrackState, &'a TrackHandle)]),
|
Track(&'a [(&'a TrackState, &'a TrackHandle)]),
|
||||||
|
|
||||||
/// Speaking state update, typically describing how another voice
|
/// Speaking state update, typically describing how another voice
|
||||||
/// user is transmitting audio data. Clients must send at least one such
|
/// user is transmitting audio data. Clients must send at least one such
|
||||||
/// packet to allow SSRC/UserID matching.
|
/// packet to allow SSRC/UserID matching.
|
||||||
SpeakingStateUpdate(Speaking),
|
SpeakingStateUpdate(Speaking),
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
/// Speaking state transition, describing whether a given source has started/stopped
|
/// Speaking state transition, describing whether a given source has started/stopped
|
||||||
/// transmitting. This fires in response to a silent burst, or the first packet
|
/// transmitting. This fires in response to a silent burst, or the first packet
|
||||||
/// breaking such a burst.
|
/// breaking such a burst.
|
||||||
SpeakingUpdate(SpeakingUpdateData),
|
SpeakingUpdate(SpeakingUpdateData),
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
/// Opus audio packet, received from another stream.
|
/// Opus audio packet, received from another stream.
|
||||||
VoicePacket(VoiceData<'a>),
|
VoicePacket(VoiceData<'a>),
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
/// Telemetry/statistics packet, received from another stream.
|
/// Telemetry/statistics packet, received from another stream.
|
||||||
RtcpPacket(RtcpData<'a>),
|
RtcpPacket(RtcpData<'a>),
|
||||||
|
|
||||||
/// Fired whenever a client disconnects.
|
/// Fired whenever a client disconnects.
|
||||||
ClientDisconnect(ClientDisconnect),
|
ClientDisconnect(ClientDisconnect),
|
||||||
|
|
||||||
/// Fires when this driver successfully connects to a voice channel.
|
/// Fires when this driver successfully connects to a voice channel.
|
||||||
DriverConnect(ConnectData<'a>),
|
DriverConnect(ConnectData<'a>),
|
||||||
|
|
||||||
/// Fires when this driver successfully reconnects after a network error.
|
/// Fires when this driver successfully reconnects after a network error.
|
||||||
DriverReconnect(ConnectData<'a>),
|
DriverReconnect(ConnectData<'a>),
|
||||||
|
|
||||||
/// Fires when this driver fails to connect to, or drops from, a voice channel.
|
/// Fires when this driver fails to connect to, or drops from, a voice channel.
|
||||||
DriverDisconnect(DisconnectData<'a>),
|
DriverDisconnect(DisconnectData<'a>),
|
||||||
}
|
}
|
||||||
@@ -51,8 +62,11 @@ pub enum EventContext<'a> {
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum CoreContext {
|
pub enum CoreContext {
|
||||||
SpeakingStateUpdate(Speaking),
|
SpeakingStateUpdate(Speaking),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
SpeakingUpdate(InternalSpeakingUpdate),
|
SpeakingUpdate(InternalSpeakingUpdate),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
VoicePacket(InternalVoicePacket),
|
VoicePacket(InternalVoicePacket),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
RtcpPacket(InternalRtcpPacket),
|
RtcpPacket(InternalRtcpPacket),
|
||||||
ClientDisconnect(ClientDisconnect),
|
ClientDisconnect(ClientDisconnect),
|
||||||
DriverConnect(InternalConnect),
|
DriverConnect(InternalConnect),
|
||||||
@@ -64,9 +78,12 @@ impl<'a> CoreContext {
|
|||||||
pub(crate) fn to_user_context(&'a self) -> EventContext<'a> {
|
pub(crate) fn to_user_context(&'a self) -> EventContext<'a> {
|
||||||
match self {
|
match self {
|
||||||
Self::SpeakingStateUpdate(evt) => EventContext::SpeakingStateUpdate(*evt),
|
Self::SpeakingStateUpdate(evt) => EventContext::SpeakingStateUpdate(*evt),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
Self::SpeakingUpdate(evt) =>
|
Self::SpeakingUpdate(evt) =>
|
||||||
EventContext::SpeakingUpdate(SpeakingUpdateData::from(evt)),
|
EventContext::SpeakingUpdate(SpeakingUpdateData::from(evt)),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
Self::VoicePacket(evt) => EventContext::VoicePacket(VoiceData::from(evt)),
|
Self::VoicePacket(evt) => EventContext::VoicePacket(VoiceData::from(evt)),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
Self::RtcpPacket(evt) => EventContext::RtcpPacket(RtcpData::from(evt)),
|
Self::RtcpPacket(evt) => EventContext::RtcpPacket(RtcpData::from(evt)),
|
||||||
Self::ClientDisconnect(evt) => EventContext::ClientDisconnect(*evt),
|
Self::ClientDisconnect(evt) => EventContext::ClientDisconnect(*evt),
|
||||||
Self::DriverConnect(evt) => EventContext::DriverConnect(ConnectData::from(evt)),
|
Self::DriverConnect(evt) => EventContext::DriverConnect(ConnectData::from(evt)),
|
||||||
@@ -84,8 +101,11 @@ impl EventContext<'_> {
|
|||||||
pub fn to_core_event(&self) -> Option<CoreEvent> {
|
pub fn to_core_event(&self) -> Option<CoreEvent> {
|
||||||
match self {
|
match self {
|
||||||
Self::SpeakingStateUpdate(_) => Some(CoreEvent::SpeakingStateUpdate),
|
Self::SpeakingStateUpdate(_) => Some(CoreEvent::SpeakingStateUpdate),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
Self::SpeakingUpdate(_) => Some(CoreEvent::SpeakingUpdate),
|
Self::SpeakingUpdate(_) => Some(CoreEvent::SpeakingUpdate),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
Self::VoicePacket(_) => Some(CoreEvent::VoicePacket),
|
Self::VoicePacket(_) => Some(CoreEvent::VoicePacket),
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
Self::RtcpPacket(_) => Some(CoreEvent::RtcpPacket),
|
Self::RtcpPacket(_) => Some(CoreEvent::RtcpPacket),
|
||||||
Self::ClientDisconnect(_) => Some(CoreEvent::ClientDisconnect),
|
Self::ClientDisconnect(_) => Some(CoreEvent::ClientDisconnect),
|
||||||
Self::DriverConnect(_) => Some(CoreEvent::DriverConnect),
|
Self::DriverConnect(_) => Some(CoreEvent::DriverConnect),
|
||||||
|
|||||||
@@ -6,8 +6,26 @@
|
|||||||
///
|
///
|
||||||
/// ## Events from other users
|
/// ## Events from other users
|
||||||
/// Songbird can observe when a user *speaks for the first time* ([`SpeakingStateUpdate`]),
|
/// Songbird can observe when a user *speaks for the first time* ([`SpeakingStateUpdate`]),
|
||||||
/// when a client leaves the session ([`ClientDisconnect`]), voice packets ([`VoicePacket`]), and
|
/// when a client leaves the session ([`ClientDisconnect`]).
|
||||||
/// telemetry data ([`RtcpPacket`]). The format of voice packets is described by [`VoiceData`].
|
///
|
||||||
|
/// When the `"receive"` feature is enabled, songbird can also handle voice packets
|
||||||
|
#[cfg_attr(feature = "receive", doc = "([`VoicePacket`](Self::VoicePacket)),")]
|
||||||
|
#[cfg_attr(not(feature = "receive"), doc = "(`VoicePacket`),")]
|
||||||
|
/// detect speech starting/stopping
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "receive",
|
||||||
|
doc = "([`SpeakingUpdate`](Self::SpeakingUpdate)),"
|
||||||
|
)]
|
||||||
|
#[cfg_attr(not(feature = "receive"), doc = "(`SpeakingUpdate`),")]
|
||||||
|
/// and handle telemetry data
|
||||||
|
#[cfg_attr(feature = "receive", doc = "([`RtcpPacket`](Self::RtcpPacket)).")]
|
||||||
|
#[cfg_attr(not(feature = "receive"), doc = "(`RtcpPacket`).")]
|
||||||
|
/// The format of voice packets is described by
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "receive",
|
||||||
|
doc = "[`VoiceData`](super::context::data::VoiceData)."
|
||||||
|
)]
|
||||||
|
#[cfg_attr(not(feature = "receive"), doc = "`VoiceData`.")]
|
||||||
///
|
///
|
||||||
/// To detect when a user connects, you must correlate gateway (e.g., `VoiceStateUpdate`) events
|
/// To detect when a user connects, you must correlate gateway (e.g., `VoiceStateUpdate`) events
|
||||||
/// from the main part of your bot.
|
/// from the main part of your bot.
|
||||||
@@ -15,15 +33,12 @@
|
|||||||
/// To obtain a user's SSRC, you must use [`SpeakingStateUpdate`] events.
|
/// To obtain a user's SSRC, you must use [`SpeakingStateUpdate`] events.
|
||||||
///
|
///
|
||||||
/// [`EventData`]: super::EventData
|
/// [`EventData`]: super::EventData
|
||||||
/// [`VoiceData`]: super::context::data::VoiceData
|
|
||||||
/// [`SpeakingStateUpdate`]: Self::SpeakingStateUpdate
|
/// [`SpeakingStateUpdate`]: Self::SpeakingStateUpdate
|
||||||
/// [`ClientDisconnect`]: Self::ClientDisconnect
|
/// [`ClientDisconnect`]: Self::ClientDisconnect
|
||||||
/// [`VoicePacket`]: Self::VoicePacket
|
|
||||||
/// [`RtcpPacket`]: Self::RtcpPacket
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum CoreEvent {
|
pub enum CoreEvent {
|
||||||
/// Speaking state update, typically describing how another voice
|
/// Speaking state update from the WS gateway, typically describing how another voice
|
||||||
/// user is transmitting audio data. Clients must send at least one such
|
/// user is transmitting audio data. Clients must send at least one such
|
||||||
/// packet to allow SSRC/UserID matching.
|
/// packet to allow SSRC/UserID matching.
|
||||||
///
|
///
|
||||||
@@ -32,24 +47,34 @@ pub enum CoreEvent {
|
|||||||
/// Note: this will fire when a user starts speaking for the first time,
|
/// Note: this will fire when a user starts speaking for the first time,
|
||||||
/// or changes their capabilities.
|
/// or changes their capabilities.
|
||||||
SpeakingStateUpdate,
|
SpeakingStateUpdate,
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
/// Fires when a source starts speaking, or stops speaking
|
/// Fires when a source starts speaking, or stops speaking
|
||||||
/// (*i.e.*, 5 consecutive silent frames).
|
/// (*i.e.*, 5 consecutive silent frames).
|
||||||
SpeakingUpdate,
|
SpeakingUpdate,
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
/// Fires on receipt of a voice packet from another stream in the voice call.
|
/// Fires on receipt of a voice packet from another stream in the voice call.
|
||||||
///
|
///
|
||||||
/// As RTP packets do not map to Discord's notion of users, SSRCs must be mapped
|
/// As RTP packets do not map to Discord's notion of users, SSRCs must be mapped
|
||||||
/// back using the user IDs seen through client connection, disconnection,
|
/// back using the user IDs seen through client connection, disconnection,
|
||||||
/// or speaking state update.
|
/// or speaking state update.
|
||||||
VoicePacket,
|
VoicePacket,
|
||||||
|
|
||||||
|
#[cfg(feature = "receive")]
|
||||||
/// Fires on receipt of an RTCP packet, containing various call stats
|
/// Fires on receipt of an RTCP packet, containing various call stats
|
||||||
/// such as latency reports.
|
/// such as latency reports.
|
||||||
RtcpPacket,
|
RtcpPacket,
|
||||||
|
|
||||||
/// Fires whenever a user disconnects from the same stream as the bot.
|
/// Fires whenever a user disconnects from the same stream as the bot.
|
||||||
ClientDisconnect,
|
ClientDisconnect,
|
||||||
|
|
||||||
/// Fires when this driver successfully connects to a voice channel.
|
/// Fires when this driver successfully connects to a voice channel.
|
||||||
DriverConnect,
|
DriverConnect,
|
||||||
|
|
||||||
/// Fires when this driver successfully reconnects after a network error.
|
/// Fires when this driver successfully reconnects after a network error.
|
||||||
DriverReconnect,
|
DriverReconnect,
|
||||||
|
|
||||||
/// Fires when this driver fails to connect to, or drops from, a voice channel.
|
/// Fires when this driver fails to connect to, or drops from, a voice channel.
|
||||||
DriverDisconnect,
|
DriverDisconnect,
|
||||||
}
|
}
|
||||||
|
|||||||
12
src/lib.rs
12
src/lib.rs
@@ -13,11 +13,13 @@
|
|||||||
//! `"gateway"` and `"[serenity/twilight]"` plus `"[rustls/native]"` features. You can even run
|
//! `"gateway"` and `"[serenity/twilight]"` plus `"[rustls/native]"` features. You can even run
|
||||||
//! driverless, to help manage your [lavalink] sessions.
|
//! driverless, to help manage your [lavalink] sessions.
|
||||||
//! * A standalone driver for voice calls, via the `"driver"` feature. If you can create
|
//! * 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
|
//! a `ConnectionInfo` using any other gateway, or language for your bot, then you
|
||||||
//! can run the songbird voice driver.
|
//! can run the songbird voice driver.
|
||||||
//! * And, by default, a fully featured voice system featuring events, queues, RT(C)P packet
|
//! * Voice receive and RT(C)P packet handling via the `"receive"` feature.
|
||||||
//! handling, seeking on compatible streams, shared multithreaded audio stream caches,
|
//! * SIMD-accelerated JSON decoding via the `"simd-json"` feature.
|
||||||
//! and direct Opus data passthrough.
|
//! * 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
|
//! ## Intents
|
||||||
//! Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.
|
//! Songbird's gateway functionality requires you to specify the `GUILD_VOICE_STATES` intent.
|
||||||
@@ -101,7 +103,7 @@ pub mod tracks;
|
|||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
mod ws;
|
mod ws;
|
||||||
|
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(all(feature = "driver", feature = "receive"))]
|
||||||
pub use discortp as packet;
|
pub use discortp as packet;
|
||||||
#[cfg(feature = "driver")]
|
#[cfg(feature = "driver")]
|
||||||
pub use serenity_voice_model as model;
|
pub use serenity_voice_model as model;
|
||||||
|
|||||||
Reference in New Issue
Block a user