1pub mod actions;
6pub mod amf0;
7pub mod chunk;
8
9use crate::llm::action_helper::call_llm;
10use crate::llm::actions::protocol_trait::ActionResult;
11use crate::logging::emit::Log;
12use crate::protocol::{Event, SpawnContext};
13use crate::server::accept_bounded::{accept_bounded, ConnectionLimiter, DEFAULT_MAX_CONNECTIONS};
14use crate::server::connection::ConnectionId;
15use crate::state::client_handles::{ClientCommand, ClientSendOutcome};
16use crate::state::server::{ConnectionState, ConnectionStatus, ProtocolConnectionInfo};
17use crate::utils::clock::Instant;
18use anyhow::{bail, Context, Result};
19use chunk::Message;
20use serde_json::{json, Value};
21use std::collections::HashMap;
22use std::net::SocketAddr;
23use std::sync::{Arc, Mutex};
24use std::time::Duration;
25use tokio::io::AsyncWriteExt;
26use tokio::net::TcpStream;
27use tokio::sync::mpsc;
28
29pub const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(10);
30pub const IDLE_TIMEOUT: Duration = Duration::from_secs(60);
31pub const MAX_PLAYERS: usize = 64;
32const OUT_CHUNK_SIZE: usize = 4096;
33const WINDOW: u32 = 2_500_000;
34const PLAYER_QUEUE: usize = 1024;
35
36pub use crate::utils::task_guard::AbortOnDrop;
37
38type Relay = (u8, u32, Arc<Vec<u8>>);
40
41#[derive(Default)]
42struct Stats {
43 started: Option<Instant>,
44 video: u64,
45 audio: u64,
46 keyframes: u64,
47 bytes: u64,
48}
49
50struct Live {
51 publisher: Option<ConnectionId>,
52 metadata: Option<Arc<Vec<u8>>>,
53 video_header: Option<Arc<Vec<u8>>>,
54 audio_header: Option<Arc<Vec<u8>>>,
55 players: HashMap<ConnectionId, mpsc::Sender<Relay>>,
56 stats: Stats,
57}
58
59impl Live {
60 fn new() -> Self {
61 Self {
62 publisher: None,
63 metadata: None,
64 video_header: None,
65 audio_header: None,
66 players: HashMap::new(),
67 stats: Stats::default(),
68 }
69 }
70 fn relay(&mut self, item: Relay) {
72 self.players
73 .retain(|_, tx| tx.try_send(item.clone()).is_ok());
74 }
75}
76
77struct Shared {
78 ctx: SpawnContext,
79 streams: Mutex<HashMap<String, Live>>,
81 idle: Duration,
82}
83
84pub async fn spawn(ctx: SpawnContext) -> Result<SocketAddr> {
85 let p = ctx.startup_params.as_ref();
86 let idle = p
87 .map(|p| p.get_optional_u64("idle_timeout_secs"))
88 .transpose()?
89 .flatten()
90 .unwrap_or(IDLE_TIMEOUT.as_secs());
91 anyhow::ensure!(
92 (1..=3600).contains(&idle),
93 "idle_timeout_secs must be 1..=3600"
94 );
95 let listener = tokio::net::TcpListener::bind(ctx.legacy_listen_addr()).await?;
96 let local = listener.local_addr()?;
97 Log::new(Some(&ctx.status_tx)).info(format!("RTMP server at rtmp://{local}/"));
98 let shared = Arc::new(Shared {
99 ctx: ctx.clone(),
100 streams: Mutex::default(),
101 idle: Duration::from_secs(idle),
102 });
103 let server_id = ctx.server_id;
104 let accept = tokio::spawn(async move {
105 let limiter = ConnectionLimiter::new(DEFAULT_MAX_CONNECTIONS);
106 loop {
107 let (stream, peer, permit) = match accept_bounded(
108 &listener,
109 &limiter,
110 b"",
111 "RTMP",
112 Some(&shared.ctx.status_tx),
113 )
114 .await
115 {
116 Ok(v) => v,
117 Err(_) => break,
118 };
119 let id = ConnectionId::new(shared.ctx.state.get_next_unified_id().await);
120 let now = Instant::now();
121 shared
122 .ctx
123 .state
124 .add_connection_to_server(
125 server_id,
126 ConnectionState {
127 id,
128 remote_addr: peer,
129 local_addr: local,
130 bytes_sent: 0,
131 bytes_received: 0,
132 packets_sent: 0,
133 packets_received: 0,
134 last_activity: now,
135 status: ConnectionStatus::Active,
136 status_changed_at: now,
137 protocol_info: ProtocolConnectionInfo::empty(),
138 },
139 )
140 .await;
141 let child = shared.clone();
142 shared
143 .ctx
144 .state
145 .spawn_server_task(server_id, async move {
146 let _permit = permit;
147 let mut conn = Conn::new(&child, id);
148 if let Err(e) = conn.run(stream).await {
149 Log::new(Some(&child.ctx.status_tx))
150 .debug(format!("RTMP connection {id}: {e:#}"));
151 }
152 conn.cleanup().await;
153 child
154 .ctx
155 .state
156 .remove_peer_handle(server_id, id.as_u32())
157 .await;
158 child
159 .ctx
160 .state
161 .update_connection_status(server_id, id, ConnectionStatus::Closed)
162 .await;
163 let _ = child.ctx.status_tx.send("__UPDATE_UI__".into());
164 })
165 .await;
166 }
167 });
168 ctx.state.register_server_task(server_id, accept).await;
169 Ok(local)
170}
171
172fn outcome(ctx: &SpawnContext, id: ConnectionId, operation: &str, decision: &str) {
173 let summary = format!("RTMP connection {id} operation={operation} decision={decision}");
174 let log = Log::new(Some(&ctx.status_tx));
175 if decision.starts_with("fail_closed_") || decision == "model_silent" {
176 log.error(summary);
177 } else {
178 log.info(summary);
179 }
180}
181
182async fn ask(
184 shared: &Shared,
185 id: ConnectionId,
186 event: Event,
187 operation: &str,
188) -> Result<Option<String>, String> {
189 let ctx = &shared.ctx;
190 let result = match call_llm(
191 &ctx.llm_client,
192 &ctx.state,
193 ctx.server_id,
194 Some(id),
195 &event,
196 &actions::RtmpProtocol,
197 )
198 .await
199 {
200 Ok(r) => r,
201 Err(e) => {
202 outcome(ctx, id, operation, "fail_closed_llm_error");
203 return Err(crate::utils::WireFailure::classify(&e).text().to_owned());
204 }
205 };
206 let mut answers = Vec::new();
207 let mut pending = result.protocol_results;
208 while let Some(r) = pending.pop() {
209 match r {
210 ActionResult::Custom { data, .. } => answers.push(data),
211 ActionResult::Multiple(items) => pending.extend(items),
212 _ => {}
213 }
214 }
215 match (result.failures.is_empty(), answers.as_slice()) {
216 (true, [a]) if a["type"] == "rtmp_accept" || a["type"] == "rtmp_ignore" => {
217 outcome(ctx, id, operation, "model_answer");
218 Ok(None)
219 }
220 (true, [a]) if a["type"] == "rtmp_reject" => {
221 outcome(ctx, id, operation, "model_reject");
222 Ok(Some(
223 a["description"].as_str().unwrap_or("refused").to_owned(),
224 ))
225 }
226 (true, []) => {
227 outcome(ctx, id, operation, "model_silent");
228 Err("the server could not decide this request".into())
229 }
230 _ => {
231 outcome(ctx, id, operation, "fail_closed_invalid_reply");
232 Err("the server could not decide this request".into())
233 }
234 }
235}
236
237fn status(code: &str, level: &str, description: &str) -> Value {
238 json!({"level": level, "code": code, "description": description})
239}
240
241enum Role {
243 Publishing(String),
244 Playing(String),
245}
246
247struct Conn<'a> {
248 shared: &'a Shared,
249 id: ConnectionId,
250 w: Option<tokio::io::WriteHalf<TcpStream>>,
251 writer: chunk::Writer,
252 app: Option<String>,
253 next_stream: u32,
254 roles: HashMap<u32, Role>,
255 players: HashMap<u32, (mpsc::Receiver<Relay>, Option<u32>, bool)>,
257 peer_window: u32,
258 acked: u64,
259}
260
261impl<'a> Conn<'a> {
262 fn new(shared: &'a Shared, id: ConnectionId) -> Self {
263 Self {
264 shared,
265 id,
266 w: None,
267 writer: chunk::Writer::default(),
268 app: None,
269 next_stream: 1,
270 roles: HashMap::new(),
271 players: HashMap::new(),
272 peer_window: WINDOW,
273 acked: 0,
274 }
275 }
276
277 async fn send(&mut self, csid: u32, m: &Message) -> Result<()> {
278 let bytes = self.writer.encode(csid, m)?;
279 let w = self.w.as_mut().context("not connected")?;
280 w.write_all(&bytes).await?;
281 self.shared
282 .ctx
283 .state
284 .update_connection_stats(
285 self.shared.ctx.server_id,
286 self.id,
287 None,
288 Some(bytes.len() as u64),
289 None,
290 Some(1),
291 )
292 .await;
293 Ok(())
294 }
295
296 async fn command(&mut self, stream_id: u32, values: &[Value]) -> Result<()> {
297 let payload = amf0::encode(values)?;
298 self.send(
299 3,
300 &Message {
301 type_id: chunk::COMMAND_AMF0,
302 stream_id,
303 timestamp: 0,
304 payload,
305 },
306 )
307 .await
308 }
309
310 async fn on_status(&mut self, stream_id: u32, info: Value) -> Result<()> {
311 self.command(stream_id, &[json!("onStatus"), json!(0), Value::Null, info])
312 .await
313 }
314
315 fn target(&self, name: &str) -> Option<(String, String)> {
319 let app = self.app.as_deref().unwrap_or_default();
320 let name = name.split('?').next().unwrap_or_default();
321 let (stream, key) = if name.is_empty() {
322 let (_, last) = app.rsplit_once('/')?;
323 (last.to_owned(), app.to_owned())
324 } else {
325 (name.to_owned(), format!("{app}/{name}"))
326 };
327 (!stream.is_empty()
328 && stream.len() <= 256
329 && !crate::utils::sanitize::has_controls(&stream))
330 .then_some((stream, key))
331 }
332
333 async fn run(&mut self, mut stream: TcpStream) -> Result<()> {
334 let ctx = &self.shared.ctx;
335 tokio::time::timeout(HANDSHAKE_TIMEOUT, chunk::accept(&mut stream))
336 .await
337 .context("handshake timed out")??;
338 let (mut r, w) = tokio::io::split(stream);
339 self.w = Some(w);
340 let (msg_tx, mut msgs) = mpsc::channel::<Result<(Message, u64)>>(64);
342 let reader = AbortOnDrop(tokio::spawn(async move {
343 let mut reader = chunk::Reader::default();
344 loop {
345 let m = reader.read(&mut r).await;
346 let failed = m.is_err();
347 if let Ok(m) = &m {
348 if m.type_id == chunk::SET_CHUNK_SIZE {
349 if let Err(e) = reader.set_chunk_size(&m.payload) {
350 let _ = msg_tx.send(Err(e)).await;
351 return;
352 }
353 }
354 }
355 if msg_tx.send(m.map(|m| (m, reader.bytes))).await.is_err() || failed {
356 return;
357 }
358 }
359 }));
360 let _reader = reader;
361 let mut commands: Option<mpsc::Receiver<ClientCommand>> = None;
362 loop {
363 enum Wake {
364 Message(Option<Result<(Message, u64)>>),
365 Relay(u32, Option<Relay>),
366 Command(Option<ClientCommand>),
367 Idle,
368 }
369 let idle = self.shared.idle;
370 let wake = {
371 let players = &mut self.players;
372 let relay = async move {
373 if players.is_empty() {
374 std::future::pending::<(u32, Option<Relay>)>().await
375 } else {
376 let futs = players.iter_mut().map(|(sid, (rx, _, _))| {
377 Box::pin(async move { (*sid, rx.recv().await) })
378 });
379 futures::future::select_all(futs).await.0
380 }
381 };
382 let command = async {
383 match commands.as_mut() {
384 Some(c) => c.recv().await,
385 None => std::future::pending().await,
386 }
387 };
388 tokio::select! {
389 m = tokio::time::timeout(idle, msgs.recv()) => match m { Ok(m) => Wake::Message(m), Err(_) => Wake::Idle },
390 (sid, item) = relay => Wake::Relay(sid, item),
391 c = command => Wake::Command(c),
392 }
393 };
394 match wake {
395 Wake::Idle => {
396 if self.players.is_empty() {
397 bail!("idle for {:?}", self.shared.idle);
398 }
399 }
400 Wake::Message(None) => return Ok(()),
401 Wake::Message(Some(Err(e))) => return Err(e),
402 Wake::Message(Some(Ok((m, total)))) => {
403 ctx.state
404 .update_connection_stats(
405 ctx.server_id,
406 self.id,
407 Some(m.payload.len() as u64),
408 None,
409 Some(1),
410 None,
411 )
412 .await;
413 if total - self.acked >= self.peer_window as u64 {
414 self.acked = total;
415 self.send(
416 2,
417 &chunk::control(chunk::ACK, (total as u32).to_be_bytes().to_vec()),
418 )
419 .await?;
420 }
421 let joined = self.app.is_some();
422 if !self.on_message(m).await? {
423 return Ok(());
424 }
425 if !joined && self.app.is_some() && commands.is_none() {
426 commands = Some(
427 crate::server::peer_support::register_peer_channel(
428 &ctx.state,
429 ctx.server_id,
430 self.id.as_u32(),
431 )
432 .await,
433 );
434 }
435 }
436 Wake::Relay(sid, None) => {
437 self.players.remove(&sid);
438 }
439 Wake::Relay(sid, Some((type_id, ts, payload))) => {
440 self.deliver(sid, type_id, ts, payload).await?
441 }
442 Wake::Command(None) => commands = None,
443 Wake::Command(Some(cmd)) => {
444 let outcome = self.injected(&cmd.action).await;
445 ctx.state
446 .record_access_log(
447 crate::state::AccessLogOwner::Server(ctx.server_id.as_u32()),
448 "RTMP",
449 Some(self.id.as_u32()),
450 "injected_action",
451 json!({"type": cmd.action["type"], "handler": cmd.action["handler"]}),
452 vec![serde_json::to_value(&outcome).unwrap_or(Value::Null)],
453 )
454 .await;
455 let done = matches!(outcome, ClientSendOutcome::Disconnected);
456 let _ = cmd.reply_tx.send(Ok(outcome));
457 if done {
458 return Ok(());
459 }
460 }
461 }
462 }
463 }
464
465 async fn deliver(
468 &mut self,
469 sid: u32,
470 type_id: u8,
471 ts: u32,
472 payload: Arc<Vec<u8>>,
473 ) -> Result<()> {
474 let Some((_, base, waiting)) = self.players.get_mut(&sid) else {
475 return Ok(());
476 };
477 if type_id == chunk::VIDEO && *waiting {
478 if payload.first().is_some_and(|b| (b >> 4) & 0x07 == 1) {
479 *waiting = false;
480 } else {
481 return Ok(());
482 }
483 }
484 let base = *base.get_or_insert(ts);
485 let timestamp = ts.wrapping_sub(base);
486 let csid = match type_id {
487 chunk::AUDIO => 4,
488 chunk::VIDEO => 6,
489 _ => 5,
490 };
491 self.send(
492 csid,
493 &Message {
494 type_id,
495 stream_id: sid,
496 timestamp,
497 payload: payload.to_vec(),
498 },
499 )
500 .await
501 }
502
503 async fn injected(&mut self, a: &Value) -> ClientSendOutcome {
504 match a["type"].as_str() {
505 Some("disconnect") => return ClientSendOutcome::Disconnected,
506 Some("rtmp_send_data") => {}
507 _ => {
508 return ClientSendOutcome::Rejected {
509 error: "an RTMP connection accepts rtmp_send_data or disconnect".into(),
510 }
511 }
512 }
513 if let Err(e) = actions::check_answer(a) {
514 return ClientSendOutcome::Rejected {
515 error: e.to_string(),
516 };
517 }
518 let payload = match amf0::encode(&[a["handler"].clone(), a["data"].clone()]) {
519 Ok(p) => Arc::new(p),
520 Err(e) => {
521 return ClientSendOutcome::Rejected {
522 error: e.to_string(),
523 }
524 }
525 };
526 let publishing: Vec<String> = self
528 .roles
529 .values()
530 .filter_map(|r| {
531 if let Role::Publishing(k) = r {
532 Some(k.clone())
533 } else {
534 None
535 }
536 })
537 .collect();
538 if let Some(key) = publishing.first() {
539 let mut streams = self
540 .shared
541 .streams
542 .lock()
543 .unwrap_or_else(|e| e.into_inner());
544 let reached = streams.get_mut(key).map(|l| {
545 let ts = l
546 .stats
547 .started
548 .map(|s| s.elapsed().as_millis() as u32)
549 .unwrap_or(0);
550 l.relay((chunk::DATA_AMF0, ts, payload.clone()));
551 l.players.len()
552 });
553 return ClientSendOutcome::Sent {
554 bytes_sent: reached.unwrap_or(0),
555 };
556 }
557 let sids: Vec<u32> = self.players.keys().copied().collect();
558 for sid in &sids {
559 if let Err(e) = self
560 .send(
561 5,
562 &Message {
563 type_id: chunk::DATA_AMF0,
564 stream_id: *sid,
565 timestamp: 0,
566 payload: payload.to_vec(),
567 },
568 )
569 .await
570 {
571 return ClientSendOutcome::Rejected {
572 error: e.to_string(),
573 };
574 }
575 }
576 if sids.is_empty() {
577 ClientSendOutcome::Rejected {
578 error: "this connection neither publishes nor plays a stream".into(),
579 }
580 } else {
581 ClientSendOutcome::Sent {
582 bytes_sent: payload.len(),
583 }
584 }
585 }
586
587 async fn on_message(&mut self, m: Message) -> Result<bool> {
589 match m.type_id {
590 chunk::SET_CHUNK_SIZE | chunk::ACK | chunk::SET_PEER_BANDWIDTH => {}
591 chunk::ABORT => {}
592 chunk::WINDOW_ACK_SIZE => {
593 let v = u32::from_be_bytes(
594 m.payload
595 .get(..4)
596 .context("short Window Acknowledgement Size")?
597 .try_into()?,
598 );
599 self.peer_window = v.max(4096);
600 }
601 chunk::USER_CONTROL => {
602 if m.payload.get(..2) == Some(&[0, 6]) && m.payload.len() >= 6 {
604 let t = u32::from_be_bytes(m.payload[2..6].try_into()?);
605 self.send(2, &chunk::user_control(7, t)).await?;
606 }
607 }
608 chunk::COMMAND_AMF3 | chunk::DATA_AMF3 => bail!("AMF3 messages are not supported"),
609 chunk::COMMAND_AMF0 => return self.on_command(&m).await,
610 chunk::DATA_AMF0 => self.on_data(&m)?,
611 chunk::AUDIO | chunk::VIDEO => self.on_media(&m)?,
612 other => Log::new(Some(&self.shared.ctx.status_tx)).debug(format!(
613 "RTMP connection {}: ignored message type {other}",
614 self.id
615 )),
616 }
617 Ok(true)
618 }
619
620 async fn on_command(&mut self, m: &Message) -> Result<bool> {
621 let values = amf0::decode_all(&m.payload)?;
622 let name = values
623 .first()
624 .and_then(Value::as_str)
625 .unwrap_or_default()
626 .to_owned();
627 let tx = values.get(1).cloned().unwrap_or(json!(0));
628 let arg = |i: usize| {
629 values
630 .get(i)
631 .and_then(Value::as_str)
632 .unwrap_or_default()
633 .to_owned()
634 };
635 if name != "connect" && self.app.is_none() {
636 bail!("{name} before connect");
637 }
638 match name.as_str() {
639 "connect" => {
640 let obj = values
641 .get(2)
642 .and_then(Value::as_object)
643 .cloned()
644 .unwrap_or_default();
645 let app = obj
646 .get("app")
647 .and_then(Value::as_str)
648 .unwrap_or_default()
649 .trim_matches('/')
650 .to_owned();
651 if self.app.is_some()
652 || app.is_empty()
653 || app.len() > 256
654 || crate::utils::sanitize::has_controls(&app)
655 {
656 self.command(
657 0,
658 &[
659 json!("_error"),
660 tx,
661 Value::Null,
662 status(
663 "NetConnection.Connect.Rejected",
664 "error",
665 "connect needs an app name, once",
666 ),
667 ],
668 )
669 .await?;
670 return Ok(false);
671 }
672 let event = Event::new(
673 &actions::CONNECT_EVENT,
674 json!({"app": app, "tc_url": obj.get("tcUrl"), "flash_ver": obj.get("flashVer")}),
675 );
676 let refusal = match ask(self.shared, self.id, event, "connect").await {
677 Ok(None) => None,
678 Ok(Some(r)) => Some(r),
679 Err(r) => Some(r),
680 };
681 if let Some(description) = refusal {
682 self.command(
683 0,
684 &[
685 json!("_error"),
686 tx,
687 Value::Null,
688 status("NetConnection.Connect.Rejected", "error", &description),
689 ],
690 )
691 .await?;
692 return Ok(false);
693 }
694 self.app = Some(app);
695 self.send(
696 2,
697 &chunk::control(chunk::WINDOW_ACK_SIZE, WINDOW.to_be_bytes().to_vec()),
698 )
699 .await?;
700 let mut bw = WINDOW.to_be_bytes().to_vec();
701 bw.push(2);
702 self.send(2, &chunk::control(chunk::SET_PEER_BANDWIDTH, bw))
703 .await?;
704 self.send(
705 2,
706 &chunk::control(
707 chunk::SET_CHUNK_SIZE,
708 (OUT_CHUNK_SIZE as u32).to_be_bytes().to_vec(),
709 ),
710 )
711 .await?;
712 self.writer.chunk_size = OUT_CHUNK_SIZE;
713 let mut info = status(
714 "NetConnection.Connect.Success",
715 "status",
716 "Connection succeeded.",
717 );
718 info["objectEncoding"] = json!(0);
719 self.command(
720 0,
721 &[
722 json!("_result"),
723 tx,
724 json!({"fmsVer": "FMS/3,0,1,123", "capabilities": 31}),
725 info,
726 ],
727 )
728 .await?;
729 }
730 "createStream" => {
731 let sid = self.next_stream;
732 if sid > 16 {
733 bail!("more than 16 message streams on one connection");
734 }
735 self.next_stream += 1;
736 self.command(0, &[json!("_result"), tx, Value::Null, json!(sid)])
737 .await?;
738 }
739 "releaseStream" | "FCPublish" | "FCUnpublish" | "getStreamLength" => {
740 if tx.as_f64().is_some_and(|t| t != 0.0) {
741 let answer = if name == "getStreamLength" {
743 json!(0)
744 } else {
745 Value::Null
746 };
747 self.command(0, &[json!("_result"), tx, Value::Null, answer])
748 .await?;
749 }
750 if name == "FCUnpublish" {
751 self.end_publishing(&arg(3)).await;
752 }
753 }
754 "publish" => {
755 let (stream, kind) = (
756 arg(3),
757 values
758 .get(4)
759 .and_then(Value::as_str)
760 .unwrap_or("live")
761 .to_owned(),
762 );
763 let Some((stream, key)) = self
764 .target(&stream)
765 .filter(|_| !self.roles.contains_key(&m.stream_id))
766 else {
767 self.on_status(
768 m.stream_id,
769 status("NetStream.Publish.BadName", "error", "invalid stream name"),
770 )
771 .await?;
772 return Ok(true);
773 };
774 let busy = self
775 .shared
776 .streams
777 .lock()
778 .unwrap_or_else(|e| e.into_inner())
779 .get(&key)
780 .is_some_and(|l| l.publisher.is_some());
781 if busy {
782 outcome(&self.shared.ctx, self.id, "publish", "protocol_refusal");
783 self.on_status(
784 m.stream_id,
785 status(
786 "NetStream.Publish.BadName",
787 "error",
788 "the stream is already being published",
789 ),
790 )
791 .await?;
792 return Ok(true);
793 }
794 let event = Event::new(
795 &actions::PUBLISH_EVENT,
796 json!({"app": self.app, "stream": stream, "type": kind}),
797 );
798 match ask(self.shared, self.id, event, "publish").await {
799 Ok(None) => {
800 let taken = {
801 let mut streams = self
802 .shared
803 .streams
804 .lock()
805 .unwrap_or_else(|e| e.into_inner());
806 let live = streams.entry(key.clone()).or_insert_with(Live::new);
807 if live.publisher.is_some() {
808 true
809 } else {
810 live.publisher = Some(self.id);
811 live.stats = Stats {
812 started: Some(Instant::now()),
813 ..Default::default()
814 };
815 live.metadata = None;
816 live.video_header = None;
817 live.audio_header = None;
818 false
819 }
820 };
821 if taken {
822 self.on_status(
823 m.stream_id,
824 status(
825 "NetStream.Publish.BadName",
826 "error",
827 "the stream is already being published",
828 ),
829 )
830 .await?;
831 return Ok(true);
832 }
833 self.roles.insert(m.stream_id, Role::Publishing(key));
834 self.send(2, &chunk::user_control(0, m.stream_id)).await?;
835 self.on_status(
836 m.stream_id,
837 status(
838 "NetStream.Publish.Start",
839 "status",
840 &format!("{stream} is now published."),
841 ),
842 )
843 .await?;
844 }
845 Ok(Some(description)) | Err(description) => {
846 self.on_status(
847 m.stream_id,
848 status("NetStream.Publish.Denied", "error", &description),
849 )
850 .await?;
851 }
852 }
853 }
854 "play" => {
855 let stream = arg(3);
856 let Some((stream, key)) = self
857 .target(&stream)
858 .filter(|_| !self.roles.contains_key(&m.stream_id))
859 else {
860 self.on_status(
861 m.stream_id,
862 status(
863 "NetStream.Play.StreamNotFound",
864 "error",
865 "invalid stream name",
866 ),
867 )
868 .await?;
869 return Ok(true);
870 };
871 let live_now = self
872 .shared
873 .streams
874 .lock()
875 .unwrap_or_else(|e| e.into_inner())
876 .get(&key)
877 .is_some_and(|l| l.publisher.is_some());
878 let event = Event::new(
879 &actions::PLAY_EVENT,
880 json!({"app": self.app, "stream": stream, "live": live_now}),
881 );
882 match ask(self.shared, self.id, event, "play").await {
883 Ok(None) => {
884 let (tx_relay, rx) = mpsc::channel::<Relay>(PLAYER_QUEUE);
885 let cached = {
886 let mut streams = self
887 .shared
888 .streams
889 .lock()
890 .unwrap_or_else(|e| e.into_inner());
891 let live = streams.entry(key.clone()).or_insert_with(Live::new);
892 if live.players.len() >= MAX_PLAYERS {
893 None
894 } else {
895 live.players.insert(self.id, tx_relay);
896 Some((
897 live.metadata.clone(),
898 live.video_header.clone(),
899 live.audio_header.clone(),
900 ))
901 }
902 };
903 let Some((metadata, video, audio)) = cached else {
904 self.on_status(
905 m.stream_id,
906 status(
907 "NetStream.Play.Failed",
908 "error",
909 "the stream has its maximum number of players",
910 ),
911 )
912 .await?;
913 return Ok(true);
914 };
915 self.roles.insert(m.stream_id, Role::Playing(key));
916 self.send(2, &chunk::user_control(0, m.stream_id)).await?;
917 self.on_status(
918 m.stream_id,
919 status("NetStream.Play.Reset", "status", "Playing and resetting."),
920 )
921 .await?;
922 self.on_status(
923 m.stream_id,
924 status(
925 "NetStream.Play.Start",
926 "status",
927 &format!("Started playing {stream}."),
928 ),
929 )
930 .await?;
931 let access =
932 amf0::encode(&[json!("|RtmpSampleAccess"), json!(true), json!(true)])?;
933 self.send(
934 5,
935 &Message {
936 type_id: chunk::DATA_AMF0,
937 stream_id: m.stream_id,
938 timestamp: 0,
939 payload: access,
940 },
941 )
942 .await?;
943 for (type_id, item) in [
944 (chunk::DATA_AMF0, metadata),
945 (chunk::VIDEO, video),
946 (chunk::AUDIO, audio),
947 ] {
948 if let Some(p) = item {
949 let csid = if type_id == chunk::VIDEO {
950 6
951 } else if type_id == chunk::AUDIO {
952 4
953 } else {
954 5
955 };
956 self.send(
957 csid,
958 &Message {
959 type_id,
960 stream_id: m.stream_id,
961 timestamp: 0,
962 payload: p.to_vec(),
963 },
964 )
965 .await?;
966 }
967 }
968 self.players.insert(m.stream_id, (rx, None, true));
969 }
970 Ok(Some(description)) | Err(description) => {
971 self.on_status(
972 m.stream_id,
973 status("NetStream.Play.StreamNotFound", "error", &description),
974 )
975 .await?;
976 }
977 }
978 }
979 "deleteStream" | "closeStream" => {
980 let sid = if name == "deleteStream" {
981 values.get(3).and_then(Value::as_f64).unwrap_or(0.0) as u32
982 } else {
983 m.stream_id
984 };
985 self.end_stream(sid).await;
986 }
987 _ => {
988 if tx.as_f64().is_some_and(|t| t != 0.0) {
989 self.command(
990 0,
991 &[
992 json!("_error"),
993 tx,
994 Value::Null,
995 status(
996 "NetConnection.Call.Failed",
997 "error",
998 &format!("{name} is not supported"),
999 ),
1000 ],
1001 )
1002 .await?;
1003 }
1004 }
1005 }
1006 Ok(true)
1007 }
1008
1009 fn publishing_key(&self, stream_id: u32) -> Option<String> {
1010 match self.roles.get(&stream_id) {
1011 Some(Role::Publishing(k)) => Some(k.clone()),
1012 _ => None,
1013 }
1014 }
1015
1016 fn on_data(&mut self, m: &Message) -> Result<()> {
1017 let Some(key) = self.publishing_key(m.stream_id) else {
1018 return Ok(());
1019 };
1020 let values = amf0::decode_all(&m.payload)?;
1021 let (handler, rest) = match values.first().and_then(Value::as_str) {
1022 Some("@setDataFrame") => (
1023 values
1024 .get(1)
1025 .and_then(Value::as_str)
1026 .unwrap_or_default()
1027 .to_owned(),
1028 &values[2.min(values.len())..],
1029 ),
1030 Some(h) => (h.to_owned(), &values[1..]),
1031 None => return Ok(()),
1032 };
1033 let mut out = vec![json!(handler)];
1034 out.extend(rest.iter().cloned());
1035 let payload = Arc::new(match rest.first().and_then(Value::as_object) {
1036 Some(meta) if handler == "onMetaData" => {
1037 let mut p = amf0::encode(&[json!("onMetaData")])?;
1038 p.extend(amf0::encode_ecma(meta)?);
1039 p
1040 }
1041 _ => amf0::encode(&out)?,
1042 });
1043 let mut streams = self
1044 .shared
1045 .streams
1046 .lock()
1047 .unwrap_or_else(|e| e.into_inner());
1048 if let Some(live) = streams.get_mut(&key) {
1049 if handler == "onMetaData" {
1050 live.metadata = Some(payload.clone());
1051 }
1052 live.relay((chunk::DATA_AMF0, m.timestamp, payload));
1053 }
1054 Ok(())
1055 }
1056
1057 fn on_media(&mut self, m: &Message) -> Result<()> {
1058 let Some(key) = self.publishing_key(m.stream_id) else {
1059 return Ok(());
1060 };
1061 let payload = Arc::new(m.payload.clone());
1062 let mut streams = self
1063 .shared
1064 .streams
1065 .lock()
1066 .unwrap_or_else(|e| e.into_inner());
1067 let Some(live) = streams.get_mut(&key) else {
1068 return Ok(());
1069 };
1070 live.stats.bytes += m.payload.len() as u64;
1071 let b0 = m.payload.first().copied().unwrap_or(0);
1072 if m.type_id == chunk::VIDEO {
1073 live.stats.video += 1;
1074 let enhanced = b0 & 0x80 != 0;
1075 let sequence_header = if enhanced {
1076 b0 & 0x0f == 0
1077 } else {
1078 matches!(b0 & 0x0f, 7 | 12) && m.payload.get(1) == Some(&0)
1079 };
1080 if (b0 >> 4) & 0x07 == 1 {
1081 live.stats.keyframes += 1;
1082 }
1083 if sequence_header {
1084 live.video_header = Some(payload.clone());
1085 return Ok(());
1086 }
1087 } else {
1088 live.stats.audio += 1;
1089 if b0 >> 4 == 10 && m.payload.get(1) == Some(&0) {
1090 live.audio_header = Some(payload.clone());
1091 return Ok(());
1092 }
1093 }
1094 live.relay((m.type_id, m.timestamp, payload));
1095 Ok(())
1096 }
1097
1098 async fn end_stream(&mut self, sid: u32) {
1099 match self.roles.remove(&sid) {
1100 Some(Role::Publishing(key)) => self.finish_publish(&key).await,
1101 Some(Role::Playing(key)) => {
1102 self.players.remove(&sid);
1103 if let Some(l) = self
1104 .shared
1105 .streams
1106 .lock()
1107 .unwrap_or_else(|e| e.into_inner())
1108 .get_mut(&key)
1109 {
1110 l.players.remove(&self.id);
1111 }
1112 }
1113 None => {}
1114 }
1115 }
1116
1117 async fn end_publishing(&mut self, stream: &str) {
1118 let Some((_, key)) = self.target(stream) else {
1119 return;
1120 };
1121 let sid = self
1122 .roles
1123 .iter()
1124 .find(|(_, r)| matches!(r, Role::Publishing(k) if *k == key))
1125 .map(|(s, _)| *s);
1126 if let Some(sid) = sid {
1127 self.end_stream(sid).await;
1128 }
1129 }
1130
1131 async fn finish_publish(&mut self, key: &str) {
1134 let summary = {
1135 let mut streams = self
1136 .shared
1137 .streams
1138 .lock()
1139 .unwrap_or_else(|e| e.into_inner());
1140 let Some(live) = streams.get_mut(key) else {
1141 return;
1142 };
1143 if live.publisher != Some(self.id) {
1144 return;
1145 }
1146 live.publisher = None;
1147 let s = std::mem::take(&mut live.stats);
1148 json!({
1149 "app": key.split('/').next(), "stream": key.split_once('/').map(|x| x.1),
1150 "duration_ms": s.started.map(|t| t.elapsed().as_millis() as u64).unwrap_or(0),
1151 "video_messages": s.video, "audio_messages": s.audio, "keyframes": s.keyframes, "media_bytes": s.bytes,
1152 "players": live.players.len(),
1153 "has_video_header": live.video_header.is_some(), "has_audio_header": live.audio_header.is_some(),
1154 })
1155 };
1156 let event = Event::new(&actions::PUBLISH_ENDED_EVENT, summary);
1157 let _ = ask(self.shared, self.id, event, "publish-ended").await;
1158 }
1159
1160 async fn cleanup(&mut self) {
1161 let sids: Vec<u32> = self.roles.keys().copied().collect();
1162 for sid in sids {
1163 self.end_stream(sid).await;
1164 }
1165 }
1166}