Skip to main content

netget/server/rtmp/
mod.rs

1//! RTMP live server. Rust owns the handshake, chunking, AMF0 commands, the NetConnection and
2//! NetStream flow and the live relay from each publisher to its players (sequence headers and
3//! metadata cached, players joining at a keyframe, timestamps rebased per player); the handler
4//! admits connections, publishers and players and can push AMF0 data messages into a stream.
5pub 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
38/// Media and data relayed to one player: (type, publisher timestamp, payload).
39type 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    /// Fan out to every player; a player whose queue is full is too slow to keep and is dropped.
71    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    /// "app/stream" → live stream
80    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
182/// Ask the handler; Ok(None) is an rtmp_accept, Ok(Some(reason)) a refusal, Err a failure.
183async 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
241/// What one connection is doing with one of its message streams.
242enum 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    /// Player state per message stream: (relay receiver, timestamp base, waiting for a keyframe)
256    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    /// The stream a play or publish names, as (stream name, "app/stream" key). A client that
316    /// carries the whole path as its app and sends an empty name (MediaMTX's gortmplib, for
317    /// rtmp://host/live/cam) names the same stream as app "live" with stream "cam".
318    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        // A dedicated reader: chunk reassembly is not cancel-safe, so it never sits in a select.
341        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    /// Forward relayed media to one player, holding video until the first keyframe and
466    /// rebasing timestamps so the player starts at zero.
467    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        // A publisher's connection sends into every player of its stream; a player's into itself.
527        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    /// Handle one message; false to close.
588    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                // PingRequest → PingResponse with the same timestamp.
603                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                    // A live stream has no length; getStreamLength's answer is a number (0).
742                    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    /// The publisher left: players get StreamEOF-like silence (their relay stays open for a
1132    /// republish), and the handler is told what was published.
1133    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}