use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
use std::thread;
use std::time::{Duration, Instant};
use librtmp2::client::Client;
use librtmp2::server::Server;
use librtmp2::types::*;
static FRAMES_RECEIVED: AtomicUsize = AtomicUsize::new(0);
const SENT_FRAME_BYTE: u8 = 0xAB;
const SENT_FRAME_LEN: usize = 32;
fn on_frame(frame: &Frame) {
if frame.size as usize == SENT_FRAME_LEN {
FRAMES_RECEIVED.fetch_add(1, Ordering::SeqCst);
}
}
fn allow_publish(_conn_id: u64, _app: &str, _stream_name: &str) -> bool {
true
}
fn plain_config() -> ServerConfig {
ServerConfig {
max_connections: 8,
chunk_size: 128,
tls_enabled: 0,
tls_cert_file: std::ptr::null(),
tls_key_file: std::ptr::null(),
tls_ca_file: std::ptr::null(),
tls_insecure: 0,
}
}
#[test]
fn server_client_publish_over_real_sockets() {
FRAMES_RECEIVED.store(0, Ordering::SeqCst);
let mut server = Server::new(plain_config()).unwrap();
server.listen("127.0.0.1:19661").unwrap();
server.on_frame_cb = Some(on_frame);
server.on_publish_cb = Some(allow_publish);
let (setup_tx, setup_rx) = std::sync::mpsc::channel();
let client_thread = thread::spawn(move || {
let mut client = Client::new();
let result = (|| -> std::result::Result<(), librtmp2::types::ErrorCode> {
client.connect("rtmp://127.0.0.1:19661/live/stream1")?;
client.publish()?;
let data = [SENT_FRAME_BYTE; SENT_FRAME_LEN];
let frame = Frame {
frame_type: FrameType::Video,
timestamp: 0,
composition_time: 0,
size: data.len() as u32,
data: data.as_ptr(),
audio_codec: AudioCodec::default(),
audio_sample_rate: 0,
audio_channels: 0,
audio_bit_depth: 0,
audio_fourcc: FourCc::default(),
video_codec: VideoCodec::H264,
video_fourcc: FourCc::default(),
video_frame_type: 1,
is_metadata: 0,
};
client.send_frame(&frame)?;
Ok(())
})();
let _ = setup_tx.send(result.is_ok());
result.unwrap();
thread::sleep(Duration::from_millis(200));
});
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if let Ok(setup_ok) = setup_rx.try_recv() {
assert!(setup_ok, "client setup failed");
}
if FRAMES_RECEIVED.load(Ordering::SeqCst) > 0 || Instant::now() >= deadline {
break;
}
server.poll(20).unwrap();
}
client_thread.join().unwrap();
assert!(
FRAMES_RECEIVED.load(Ordering::SeqCst) > 0,
"server never observed the published frame"
);
}
static OBSERVED_CONN_ID: AtomicU64 = AtomicU64::new(0);
fn record_conn_id(conn_id: u64, _app: &str, _stream_name: &str) -> bool {
OBSERVED_CONN_ID.store(conn_id, Ordering::SeqCst);
true
}
#[test]
fn set_conn_id_base_offsets_first_assigned_conn_id() {
OBSERVED_CONN_ID.store(0, Ordering::SeqCst);
const BASE: u64 = 1 << 40;
let mut server = Server::new(plain_config()).unwrap();
server.set_conn_id_base(BASE);
server.listen("127.0.0.1:19662").unwrap();
server.on_publish_cb = Some(record_conn_id);
let (setup_tx, setup_rx) = std::sync::mpsc::channel();
let client_thread = thread::spawn(move || {
let mut client = Client::new();
let result = (|| -> std::result::Result<(), librtmp2::types::ErrorCode> {
client.connect("rtmp://127.0.0.1:19662/live/stream1")?;
client.publish()?;
Ok(())
})();
let _ = setup_tx.send(result.is_ok());
result.unwrap();
thread::sleep(Duration::from_millis(200));
});
let deadline = Instant::now() + Duration::from_secs(5);
loop {
if let Ok(setup_ok) = setup_rx.try_recv() {
assert!(setup_ok, "client setup failed");
}
if OBSERVED_CONN_ID.load(Ordering::SeqCst) != 0 || Instant::now() >= deadline {
break;
}
server.poll(20).unwrap();
}
client_thread.join().unwrap();
assert_eq!(
OBSERVED_CONN_ID.load(Ordering::SeqCst),
BASE,
"first accepted connection should have conn_id == configured base"
);
}
#[test]
#[should_panic(expected = "conn_id base must be non-zero")]
fn set_conn_id_base_rejects_zero() {
let mut server = Server::new(plain_config()).unwrap();
server.set_conn_id_base(0);
}
#[test]
#[should_panic(expected = "conn_id base must leave room")]
fn set_conn_id_base_rejects_exhausted_counter() {
let mut server = Server::new(plain_config()).unwrap();
server.set_conn_id_base(u64::MAX);
}
#[test]
fn listener_fds_exposes_every_bound_listener_and_stop_clears_them() {
let mut server = Server::new(plain_config()).unwrap();
assert!(server.tls_ctx.is_none());
server.listen("127.0.0.1:0").unwrap();
server.listen("127.0.0.1:0").unwrap();
let fds = server.listener_fds();
assert_eq!(fds.len(), 2, "each listen() call should expose its fd");
assert!(fds.iter().all(|fd| *fd >= 0));
assert_ne!(fds[0], fds[1], "listeners must expose distinct fds");
assert_eq!(server.server_fd, fds[0]);
server.stop();
assert_eq!(server.server_fd, -1);
assert!(server.listener_fds().is_empty());
}
static PLAYER_FRAMES_RECEIVED: AtomicUsize = AtomicUsize::new(0);
fn on_player_frame(frame: &Frame) {
if frame.size as usize == SENT_FRAME_LEN {
PLAYER_FRAMES_RECEIVED.fetch_add(1, Ordering::SeqCst);
}
}
#[test]
fn publisher_and_player_relay_across_different_listeners() {
PLAYER_FRAMES_RECEIVED.store(0, Ordering::SeqCst);
let mut server = Server::new(plain_config()).unwrap();
server.listen("127.0.0.1:19663").unwrap();
server.listen("127.0.0.1:19664").unwrap();
let (setup_tx, setup_rx) = std::sync::mpsc::channel();
let client_thread = thread::spawn(move || {
let result = (|| -> std::result::Result<(), librtmp2::types::ErrorCode> {
let mut publisher = Client::new();
publisher.connect("rtmp://127.0.0.1:19663/live/relaytest")?;
publisher.publish()?;
let mut player = Client::new();
player.on_frame_cb = Some(on_player_frame);
player.connect("rtmp://127.0.0.1:19664/live/relaytest")?;
player.play()?;
thread::sleep(Duration::from_millis(100));
let data = [SENT_FRAME_BYTE; SENT_FRAME_LEN];
let frame = Frame {
frame_type: FrameType::Video,
timestamp: 0,
composition_time: 0,
size: data.len() as u32,
data: data.as_ptr(),
audio_codec: AudioCodec::default(),
audio_sample_rate: 0,
audio_channels: 0,
audio_bit_depth: 0,
audio_fourcc: FourCc::default(),
video_codec: VideoCodec::H264,
video_fourcc: FourCc::default(),
video_frame_type: 1,
is_metadata: 0,
};
publisher.send_frame(&frame)?;
let deadline = Instant::now() + Duration::from_secs(5);
while PLAYER_FRAMES_RECEIVED.load(Ordering::SeqCst) == 0 && Instant::now() < deadline {
player.poll(50)?;
}
Ok(())
})();
let _ = setup_tx.send(result.is_ok());
result.unwrap();
});
let deadline = Instant::now() + Duration::from_secs(6);
loop {
if let Ok(setup_ok) = setup_rx.try_recv() {
assert!(setup_ok, "publisher/player setup failed");
break;
}
if Instant::now() >= deadline {
break;
}
server.poll(20).unwrap();
}
client_thread.join().unwrap();
assert!(
PLAYER_FRAMES_RECEIVED.load(Ordering::SeqCst) > 0,
"player on a different listener never received the publisher's frame \
— relay must be shared across every listener on one Server"
);
}