Skip to main content

active_call/media/track/
rtc.rs

1use super::track_codec::TrackCodec;
2use crate::{
3    event::{EventSender, SessionEvent},
4    media::AudioFrame,
5    media::{
6        processor::ProcessorChain,
7        track::{Track, TrackConfig, TrackId, TrackPacketSender},
8    },
9};
10use anyhow::Result;
11use async_trait::async_trait;
12use audio_codec::CodecType;
13use bytes::Bytes;
14use futures::{FutureExt, StreamExt, stream::FuturesUnordered};
15use rustrtc::{
16    AudioCapability, IceCandidate, IceServer, MediaKind, PeerConnection, PeerConnectionEvent,
17    PeerConnectionState, RtcConfiguration, RtpCodecParameters, SdpType, TransportMode,
18    config::MediaCapabilities,
19    media::{
20        MediaStreamTrack, SampleStreamSource, frame::AudioFrame as RtcAudioFrame, sample_track,
21        track::SampleStreamTrack,
22    },
23};
24use std::{
25    sync::{
26        Arc,
27        atomic::{AtomicBool, Ordering},
28    },
29    time::{Duration, Instant},
30};
31use tokio::sync::Mutex;
32use tokio_util::sync::CancellationToken;
33use tracing::{debug, info};
34
35#[derive(Clone)]
36pub struct RtcTrackConfig {
37    pub mode: TransportMode,
38    pub ice_servers: Option<Vec<IceServer>>,
39    pub external_ip: Option<String>,
40    pub rtp_port_range: Option<(u16, u16)>,
41    pub bind_ip: Option<String>,
42    pub preferred_codec: Option<CodecType>,
43    pub codecs: Vec<CodecType>,
44    pub payload_type: Option<u8>,
45    pub enable_latching: Option<bool>,
46    pub enable_ice_lite: Option<bool>,
47}
48
49impl Default for RtcTrackConfig {
50    fn default() -> Self {
51        Self {
52            mode: TransportMode::WebRtc, // Default WebRTC behavior
53            ice_servers: None,
54            external_ip: None,
55            rtp_port_range: None,
56            bind_ip: None,
57            preferred_codec: None,
58            codecs: Vec::new(),
59            payload_type: None,
60            enable_latching: None,
61            enable_ice_lite: None,
62        }
63    }
64}
65
66pub struct RtcTrack {
67    track_id: TrackId,
68    track_config: TrackConfig,
69    rtc_config: RtcTrackConfig,
70    processor_chain: ProcessorChain,
71    packet_sender: Arc<Mutex<Option<TrackPacketSender>>>,
72    event_sender: Arc<Mutex<Option<EventSender>>>,
73    media_ready_sent: Arc<AtomicBool>,
74    cancel_token: CancellationToken,
75    local_source: Option<Arc<SampleStreamSource>>,
76    encoder: TrackCodec,
77    ssrc: u32,
78    payload_type: Option<u8>,
79    pub peer_connection: Option<Arc<PeerConnection>>,
80    next_rtp_timestamp: u32,
81    next_rtp_sequence_number: u16,
82    last_packet_time: Option<Instant>,
83    last_remote_sdp: Option<String>,
84    need_marker: bool,
85}
86
87impl RtcTrack {
88    pub fn new(
89        cancel_token: CancellationToken,
90        id: TrackId,
91        track_config: TrackConfig,
92        rtc_config: RtcTrackConfig,
93    ) -> Self {
94        let processor_chain = ProcessorChain::new(track_config.samplerate);
95        Self {
96            track_id: id,
97            track_config,
98            rtc_config,
99            processor_chain,
100            packet_sender: Arc::new(Mutex::new(None)),
101            event_sender: Arc::new(Mutex::new(None)),
102            media_ready_sent: Arc::new(AtomicBool::new(false)),
103            cancel_token,
104            local_source: None,
105            encoder: TrackCodec::new(),
106            ssrc: 0,
107            payload_type: None,
108            peer_connection: None,
109            next_rtp_timestamp: 0,
110            next_rtp_sequence_number: 0,
111            last_packet_time: None,
112            last_remote_sdp: None,
113            need_marker: false,
114        }
115    }
116
117    pub fn with_ssrc(mut self, ssrc: u32) -> Self {
118        self.ssrc = ssrc;
119        self
120    }
121
122    pub fn create_audio_track(
123        _codec: CodecType,
124        _stream_id: Option<String>,
125    ) -> (Arc<SampleStreamSource>, Arc<SampleStreamTrack>) {
126        let (source, track, _) = sample_track(rustrtc::media::MediaKind::Audio, 100);
127        (Arc::new(source), track)
128    }
129
130    pub async fn local_description(&self) -> Result<String> {
131        let pc = self
132            .peer_connection
133            .as_ref()
134            .ok_or_else(|| anyhow::anyhow!("No PeerConnection"))?;
135        let offer = pc.create_offer().await?;
136        pc.set_local_description(offer.clone())?;
137        Ok(offer.to_sdp_string())
138    }
139
140    pub async fn create(&mut self) -> Result<()> {
141        if self.peer_connection.is_some() {
142            return Ok(());
143        }
144
145        let mut config = RtcConfiguration::default();
146        if self.ssrc != 0 {
147            config.ssrc_start = self.ssrc;
148        }
149        config.transport_mode = self.rtc_config.mode.clone();
150
151        if let Some(ice_servers) = &self.rtc_config.ice_servers {
152            config.ice_servers = ice_servers.clone();
153        }
154
155        if let Some(external_ip) = &self.rtc_config.external_ip {
156            config.external_ip = Some(external_ip.clone());
157        }
158        if let Some(bind_ip) = &self.rtc_config.bind_ip {
159            config.bind_ip = Some(bind_ip.clone());
160        }
161        if let Some((rtp_start_port, rtp_end_port)) = self.rtc_config.rtp_port_range {
162            config.rtp_start_port = Some(rtp_start_port);
163            config.rtp_end_port = Some(rtp_end_port);
164        }
165        config.enable_ice_lite = self.rtc_config.enable_ice_lite.unwrap_or(false);
166        config.enable_latching = self
167            .rtc_config
168            .enable_latching
169            .unwrap_or_else(|| self.rtc_config.mode == TransportMode::Rtp);
170
171        if !self.rtc_config.codecs.is_empty() {
172            let mut caps = MediaCapabilities::default();
173            caps.audio.clear();
174
175            for codec in &self.rtc_config.codecs {
176                let cap = match codec {
177                    CodecType::PCMU => AudioCapability::pcmu(),
178                    CodecType::PCMA => AudioCapability::pcma(),
179                    CodecType::G722 => AudioCapability::g722(),
180                    CodecType::G729 => AudioCapability::g729(),
181                    CodecType::TelephoneEvent => AudioCapability::telephone_event(),
182                    CodecType::Opus => AudioCapability::opus(),
183                };
184                caps.audio.push(cap);
185            }
186            config.media_capabilities = Some(caps);
187        }
188
189        let peer_connection = Arc::new(PeerConnection::new(config));
190        self.peer_connection = Some(peer_connection.clone());
191
192        let default_codec = CodecType::G722;
193        let codec = self.rtc_config.preferred_codec.unwrap_or(default_codec);
194
195        let (source, track) = Self::create_audio_track(codec, Some(self.track_id.clone()));
196        self.local_source = Some(source);
197
198        let payload_type = self
199            .rtc_config
200            .payload_type
201            .unwrap_or_else(|| codec.payload_type());
202
203        self.payload_type = Some(payload_type);
204
205        let params = RtpCodecParameters {
206            clock_rate: codec.clock_rate(),
207            channels: codec.channels() as u8,
208            payload_type,
209            ..Default::default()
210        };
211
212        peer_connection.add_track_with_stream_id(track, self.track_id.clone(), params)?;
213
214        // Spawn Handler Logic
215        self.spawn_handlers(
216            peer_connection.clone(),
217            self.track_id.clone(),
218            self.processor_chain.clone(),
219            payload_type,
220            self.event_sender.clone(),
221            self.media_ready_sent.clone(),
222        );
223
224        Ok(())
225    }
226
227    fn spawn_handlers(
228        &self,
229        pc: Arc<PeerConnection>,
230        track_id: TrackId,
231        processor_chain: ProcessorChain,
232        default_payload_type: u8,
233        event_sender: Arc<Mutex<Option<EventSender>>>,
234        media_ready_sent: Arc<AtomicBool>,
235    ) {
236        let cancel_token = self.cancel_token.clone();
237        let packet_sender = self.packet_sender.clone();
238        let pc_event = pc.clone();
239        let pc_stats = pc.clone();
240        let pc_state = pc.clone();
241        let track_id_log = track_id.clone();
242        let is_rtp_media = matches!(
243            self.rtc_config.mode,
244            TransportMode::Rtp | TransportMode::Srtp
245        );
246        let is_webrtc = self.rtc_config.mode != TransportMode::Rtp;
247
248        crate::spawn(async move {
249            info!(track_id=%track_id_log, "RtcTrack event/stats loop started");
250
251            let mut events = futures::stream::unfold(pc_event, |pc| async move {
252                pc.recv().await.map(|ev| (ev, pc))
253            })
254            .boxed();
255
256            let mut state_rx = if is_webrtc {
257                Some(pc_state.subscribe_peer_state())
258            } else {
259                None
260            };
261
262            let mut stats_interval = tokio::time::interval(Duration::from_secs(5));
263            let mut event_count = 0;
264            let mut workers = FuturesUnordered::new();
265
266            loop {
267                tokio::select! {
268                    _ = cancel_token.cancelled() => {
269                        debug!(track_id=%track_id_log, "RtcTrack loop cancelled");
270                        break;
271                    }
272
273                    Some(event) = events.next() => {
274                        event_count += 1;
275                        let event_type = match &event {
276                            PeerConnectionEvent::Track(_) => "Track",
277                            PeerConnectionEvent::DataChannel(_) => "DataChannel",
278                        };
279                        debug!(track_id=%track_id_log, "Received PeerConnectionEvent #{}: {}", event_count, event_type);
280
281                        if let PeerConnectionEvent::Track(transceiver) = event {
282                            if let Some(receiver) = transceiver.receiver() {
283                                let track = receiver.track();
284                                if is_rtp_media {
285                                    let maybe_sender = event_sender.lock().await.clone();
286                                    if let Some(sender) = maybe_sender {
287                                        if media_ready_sent
288                                            .compare_exchange(false, true, Ordering::SeqCst, Ordering::SeqCst)
289                                            .is_ok()
290                                        {
291                                            let result = sender.send(SessionEvent::MediaReady {
292                                                track_id: track_id_log.clone(),
293                                                timestamp: crate::media::get_timestamp(),
294                                            });
295                                            if result.is_err() {
296                                                media_ready_sent.store(false, Ordering::SeqCst);
297                                            }
298                                        }
299                                    }
300                                }
301                                info!(track_id=%track_id_log, "New track received");
302
303                                let (f1, f2) = Self::create_track_workers(
304                                    track,
305                                    packet_sender.clone(),
306                                    track_id_log.clone(),
307                                    processor_chain.clone(),
308                                    default_payload_type,
309                                );
310                                workers.push(f1);
311                                workers.push(f2);
312                            }
313                        }
314                    }
315
316                    _ = workers.next(), if !workers.is_empty() => {}
317
318                    _ = stats_interval.tick() => {
319                        match pc_stats.get_stats().await {
320                            Ok(stats) => {
321                                info!(track_id=%track_id_log, %stats, "RTCP Stats");
322                            }
323                            Err(e) => {
324                                debug!(track_id=%track_id_log, "Failed to get stats: {:?}", e);
325                            }
326                        }
327                    }
328
329                    // Handle state changes for transports that expose them.
330                    res = async {
331                        if let Some(rx) = state_rx.as_mut() {
332                            rx.changed().await
333                        } else {
334                            std::future::pending().await
335                        }
336                    } => {
337                        if res.is_ok() {
338                            if let Some(rx) = state_rx.as_ref() {
339                                let s = *rx.borrow();
340                                debug!(track_id=%track_id_log, "peer connection state changed: {:?}", s);
341                                match s {
342                                    PeerConnectionState::Disconnected
343                                    | PeerConnectionState::Closed
344                                    | PeerConnectionState::Failed => {
345                                        info!(
346                                            track_id = %track_id_log,
347                                            "peer connection is {:?}, try to close", s
348                                        );
349                                        cancel_token.cancel();
350                                        pc_state.close();
351                                        break;
352                                    }
353                                    _ => {}
354                                }
355                            }
356                        }
357                    }
358                }
359            }
360            debug!(track_id=%track_id_log, "RtcTrack event/stats loop ended, total events: {}", event_count);
361        });
362    }
363
364    fn create_track_workers(
365        track: Arc<SampleStreamTrack>,
366        packet_sender_arc: Arc<Mutex<Option<TrackPacketSender>>>,
367        track_id: TrackId,
368        processor_chain: ProcessorChain,
369        default_payload_type: u8,
370    ) -> (
371        std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>,
372        std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send>>,
373    ) {
374        let (tx, rx) = tokio::sync::mpsc::unbounded_channel::<rustrtc::media::frame::AudioFrame>();
375
376        // Processing Worker
377        let track_id_proc = track_id.clone();
378        let packet_sender_proc = packet_sender_arc.clone();
379        let processor_chain_proc = processor_chain.clone();
380        let proc_fut = Self::run_processing_worker(
381            rx,
382            track_id_proc,
383            packet_sender_proc,
384            processor_chain_proc,
385            default_payload_type,
386        );
387
388        // Receiving Worker
389        let track_id_recv = track_id.clone();
390        let recv_fut = Self::run_receiving_worker(track, tx, track_id_recv);
391
392        (proc_fut.boxed(), recv_fut.boxed())
393    }
394
395    async fn run_processing_worker(
396        mut rx: tokio::sync::mpsc::UnboundedReceiver<rustrtc::media::frame::AudioFrame>,
397        track_id: TrackId,
398        packet_sender: Arc<Mutex<Option<TrackPacketSender>>>,
399        mut processor_chain: ProcessorChain,
400        default_payload_type: u8,
401    ) {
402        info!(track_id=%track_id, "RtcTrack processing worker started");
403        while let Some(frame) = rx.recv().await {
404            let res = std::panic::AssertUnwindSafe(Self::process_audio_frame(
405                frame,
406                &track_id,
407                &packet_sender,
408                &mut processor_chain,
409                default_payload_type,
410            ))
411            .catch_unwind()
412            .await;
413
414            if let Err(cause) = res {
415                let msg = if let Some(s) = cause.downcast_ref::<&str>() {
416                    *s
417                } else if let Some(s) = cause.downcast_ref::<String>() {
418                    &s[..]
419                } else {
420                    "Unknown panic"
421                };
422                tracing::error!(track_id=%track_id, "RtcTrack processing worker PANIC: {}", msg);
423                break;
424            }
425        }
426        info!(track_id=%track_id, "RtcTrack processing worker stopped");
427    }
428
429    async fn run_receiving_worker(
430        track: Arc<SampleStreamTrack>,
431        tx: tokio::sync::mpsc::UnboundedSender<rustrtc::media::frame::AudioFrame>,
432        track_id: TrackId,
433    ) {
434        let mut samples =
435            futures::stream::unfold(
436                track,
437                |t| async move { t.recv().await.ok().map(|s| (s, t)) },
438            )
439            .boxed();
440
441        while let Some(sample) = samples.next().await {
442            if let rustrtc::media::frame::MediaSample::Audio(frame) = sample {
443                if let Err(_) = tx.send(frame) {
444                    break;
445                }
446            } else {
447                debug!(track_id=%track_id, "Received non-audio sample");
448            }
449        }
450        info!(track_id=%track_id, "RtcTrack receiving worker stopped");
451    }
452
453    async fn process_audio_frame(
454        frame: rustrtc::media::frame::AudioFrame,
455        track_id: &TrackId,
456        packet_sender: &Arc<Mutex<Option<TrackPacketSender>>>,
457        processor_chain: &mut ProcessorChain,
458        default_payload_type: u8,
459    ) {
460        let packet_sender = packet_sender.lock().await;
461        if let Some(sender) = packet_sender.as_ref() {
462            let payload_type = frame.payload_type.unwrap_or(default_payload_type);
463            let src_codec = match processor_chain.codec.get_codec_for_pt(payload_type) {
464                Some(c) => c,
465                None => {
466                    debug!(track_id=%track_id, "Unknown payload type {}, skipping frame", payload_type);
467                    return;
468                }
469            };
470
471            let mut af = AudioFrame {
472                track_id: track_id.clone(),
473                samples: crate::media::Samples::RTP {
474                    payload_type,
475                    payload: frame.data.to_vec(),
476                    sequence_number: frame.sequence_number.unwrap_or(0),
477                },
478                timestamp: crate::media::get_timestamp(),
479                sample_rate: src_codec.samplerate(),
480                channels: src_codec.channels(),
481                ..Default::default()
482            };
483            if let Err(e) = processor_chain.process_frame(&mut af) {
484                debug!(track_id=%track_id, "processor_chain process_frame error: {:?}", e);
485            }
486
487            sender.send(af).ok();
488        }
489    }
490
491    pub fn parse_sdp_payload_types(&mut self, sdp_type: SdpType, sdp_str: &str) -> Result<()> {
492        use crate::media::negotiate::parse_rtpmap;
493        let sdp = rustrtc::SessionDescription::parse(sdp_type, sdp_str)?;
494
495        if let Some(media) = sdp
496            .media_sections
497            .iter()
498            .find(|m| m.kind == MediaKind::Audio)
499        {
500            for attr in &media.attributes {
501                if attr.key == "rtpmap" {
502                    if let Some(value) = &attr.value {
503                        if let Ok((pt, codec, _, _)) = parse_rtpmap(value) {
504                            self.encoder.set_payload_type(pt, codec.clone());
505                            self.processor_chain.codec.set_payload_type(pt, codec);
506                        }
507                    }
508                }
509            }
510
511            // Negotiate primary audio codec
512            let mut negotiated = None;
513
514            // When parsing an answer, prefer our configured codec order among accepted codecs.
515            // Offer parsing is provisional; the final outgoing PT is set from the answer.
516            if sdp_type == rustrtc::sdp::SdpType::Answer && !self.rtc_config.codecs.is_empty() {
517                for preferred_codec in &self.rtc_config.codecs {
518                    if *preferred_codec == CodecType::TelephoneEvent {
519                        continue;
520                    }
521                    for fmt in &media.formats {
522                        if let Ok(pt) = fmt.parse::<u8>() {
523                            let codec = self.encoder.get_codec_for_pt(pt);
524                            if let Some(c) = codec {
525                                if c == *preferred_codec {
526                                    negotiated = Some((pt, c));
527                                    break;
528                                }
529                            }
530                        }
531                    }
532                    if negotiated.is_some() {
533                        break;
534                    }
535                }
536            }
537
538            // Fallback: use the first codec in the SDP (matches offerer's preference if we are answerer)
539            if negotiated.is_none() {
540                for fmt in &media.formats {
541                    if let Ok(pt) = fmt.parse::<u8>() {
542                        let codec = self.encoder.get_codec_for_pt(pt);
543                        if let Some(codec) = codec {
544                            if codec != CodecType::TelephoneEvent {
545                                negotiated = Some((pt, codec));
546                                break;
547                            }
548                        }
549                    }
550                }
551            }
552
553            if let Some((pt, codec)) = negotiated {
554                info!(track_id=%self.track_id, "Negotiated primary audio PT {} ({:?})", pt, codec);
555                self.payload_type = Some(pt);
556            }
557        }
558        Ok(())
559    }
560
561    fn normalize_sdp(sdp: &str) -> String {
562        sdp.lines()
563            .map(|line| {
564                if line.starts_with("o=") {
565                    let parts: Vec<&str> = line.split_whitespace().collect();
566                    if parts.len() >= 3 {
567                        return format!("o= {} {}", parts[1], parts[2]);
568                    }
569                }
570                line.to_string()
571            })
572            .filter(|line| {
573                !line.starts_with("t=") &&  // timing line can vary
574                !line.starts_with("a=ssrc:") &&  // SSRC attributes (but SSRC change shows in o= version)
575                !line.starts_with("a=msid:") &&  // media stream ID
576                !line.trim().is_empty()
577            })
578            .collect::<Vec<_>>()
579            .join("\n")
580    }
581
582    async fn update_remote_description_internal(
583        &mut self,
584        answer: &String,
585        force_update: bool,
586        sdp_type: rustrtc::SdpType,
587    ) -> Result<()> {
588        info!(
589            track_id=%self.track_id,
590            "update_remote_description_internal called. force={}, last_sdp_is_some={}, mode={:?}, sdp_type={:?}",
591            force_update,
592            self.last_remote_sdp.is_some(),
593            self.rtc_config.mode,
594            sdp_type
595        );
596
597        if let Some(pc) = &self.peer_connection {
598            if !force_update {
599                if let Some(ref last_sdp) = self.last_remote_sdp {
600                    if Self::normalize_sdp(last_sdp) == Self::normalize_sdp(answer) {
601                        debug!(track_id=%self.track_id, "SDP unchanged, skipping update_remote_description");
602                        return Ok(());
603                    }
604                }
605            } else {
606                debug!(track_id=%self.track_id, "Force update requested, skipping SDP comparison");
607            }
608
609            let _is_first_remote_sdp = self.last_remote_sdp.is_none();
610
611            let sdp_obj = rustrtc::SessionDescription::parse(sdp_type, answer)?;
612            match pc.set_remote_description(sdp_obj.clone()).await {
613                Ok(_) => {
614                    debug!(track_id=%self.track_id, "set_remote_description succeeded");
615                    self.last_remote_sdp = Some(answer.clone());
616                }
617                Err(e) => {
618                    if self.rtc_config.mode == TransportMode::Rtp {
619                        info!(track_id=%self.track_id, "set_remote_description failed ({}), attempting to re-sync state for SIP update", e);
620
621                        if let Some(current_local) = pc.local_description() {
622                            let sdp = current_local.to_sdp_string();
623                            for line in sdp.lines() {
624                                if line.starts_with("a=ssrc:") {
625                                    info!(track_id=%self.track_id, "SSRC before re-sync: {}", line);
626                                }
627                            }
628                        }
629
630                        let offer = pc.create_offer().await?;
631
632                        let sdp = offer.to_sdp_string();
633                        for line in sdp.lines() {
634                            if line.starts_with("a=ssrc:") {
635                                info!(track_id=%self.track_id, "SSRC in new offer (re-sync): {}", line);
636                            }
637                        }
638
639                        pc.set_local_description(offer)?;
640                        pc.set_remote_description(sdp_obj).await?;
641                        self.last_remote_sdp = Some(answer.clone());
642                        info!(track_id=%self.track_id, "successfully re-synced WebRTC state for SIP update");
643                    } else {
644                        return Err(e.into());
645                    }
646                }
647            }
648
649            // Track events will be handled by the event loop after SSRC latching
650
651            // Extract negotiated payload types from SDP string
652            self.parse_sdp_payload_types(sdp_type, answer)?;
653        }
654        Ok(())
655    }
656}
657
658#[async_trait]
659impl Track for RtcTrack {
660    fn ssrc(&self) -> u32 {
661        self.ssrc
662    }
663    fn id(&self) -> &TrackId {
664        &self.track_id
665    }
666    fn config(&self) -> &TrackConfig {
667        &self.track_config
668    }
669    fn processor_chain(&mut self) -> &mut ProcessorChain {
670        &mut self.processor_chain
671    }
672
673    async fn handshake(&mut self, offer: String, _: Option<Duration>) -> Result<String> {
674        info!(track_id=%self.track_id, "rtc handshake start");
675        self.create().await?;
676
677        let pc = self.peer_connection.clone().ok_or_else(|| {
678            anyhow::anyhow!("No PeerConnection available for track {}", self.track_id)
679        })?;
680
681        debug!(track_id=%self.track_id, "Before set_remote_description: transceivers count = {}", pc.get_transceivers().len());
682        for (i, t) in pc.get_transceivers().iter().enumerate() {
683            debug!(track_id=%self.track_id, "  Transceiver #{}: kind={:?}, mid={:?}, direction={:?}",
684                i, t.kind(), t.mid(), t.direction());
685        }
686
687        let sdp = rustrtc::SessionDescription::parse(rustrtc::SdpType::Offer, &offer)?;
688        pc.set_remote_description(sdp.clone()).await?;
689
690        debug!(track_id=%self.track_id, "After set_remote_description: transceivers count = {}", pc.get_transceivers().len());
691        for (i, t) in pc.get_transceivers().iter().enumerate() {
692            debug!(track_id=%self.track_id, "  Transceiver #{}: kind={:?}, mid={:?}, direction={:?}, has_receiver={}",
693                i, t.kind(), t.mid(), t.direction(), t.receiver().is_some());
694        }
695
696        // For RTP mode: Wait for PeerConnectionEvent::Track after SSRC latching completes
697        // For WebRTC mode: The event loop will handle Track events
698        info!(track_id=%self.track_id, "Waiting for Track events (SSRC latching for RTP mode)");
699
700        self.parse_sdp_payload_types(rustrtc::SdpType::Offer, &offer)?;
701
702        let mut answer = pc.create_answer().await?;
703        crate::media::negotiate::intersect_answer(&sdp, &mut answer);
704        self.parse_sdp_payload_types(rustrtc::SdpType::Answer, &answer.to_sdp_string())?;
705
706        pc.set_local_description(answer.clone())?;
707
708        if self.rtc_config.mode != TransportMode::Rtp {
709            pc.wait_for_gathering_complete().await;
710        }
711
712        let final_answer = pc
713            .local_description()
714            .ok_or(anyhow::anyhow!("No local description"))?;
715
716        Ok(final_answer.to_sdp_string())
717    }
718
719    async fn update_remote_description(&mut self, answer: &String) -> Result<()> {
720        self.update_remote_description_internal(answer, false, rustrtc::SdpType::Answer)
721            .await
722    }
723
724    async fn update_remote_description_force(&mut self, answer: &String) -> Result<()> {
725        self.update_remote_description_internal(answer, true, rustrtc::SdpType::Answer)
726            .await
727    }
728
729    async fn update_remote_description_provisional(&mut self, answer: &String) -> Result<()> {
730        // SIP 183 early media: apply as a provisional answer so signaling
731        // state stays in HaveLocalOffer, leaving room for the real 200 OK
732        // answer to complete negotiation. Tagging this as a full Answer (as
733        // the final answer does) would move state to Stable early, so the
734        // real answer would then be rejected as an invalid re-application
735        // and only recover via the SDP-mismatch re-sync fallback below —
736        // losing/disrupting the media path for the ringing window.
737        self.update_remote_description_internal(answer, false, rustrtc::SdpType::Pranswer)
738            .await
739    }
740
741    async fn start(
742        &mut self,
743        event_sender: EventSender,
744        packet_sender: TrackPacketSender,
745    ) -> Result<()> {
746        *self.packet_sender.lock().await = Some(packet_sender.clone());
747        *self.event_sender.lock().await = Some(event_sender.clone());
748        let token_clone = self.cancel_token.clone();
749        let event_sender_clone = event_sender.clone();
750        let track_id = self.track_id.clone();
751        let ssrc = self.ssrc;
752
753        if self.rtc_config.mode != TransportMode::Rtp {
754            let start_time = crate::media::get_timestamp();
755            crate::spawn(async move {
756                token_clone.cancelled().await;
757                let _ = event_sender_clone.send(SessionEvent::TrackEnd {
758                    track_id,
759                    timestamp: crate::media::get_timestamp(),
760                    duration: crate::media::get_timestamp() - start_time,
761                    ssrc,
762                    play_id: None,
763                    auto_hangup: None,
764                });
765            });
766        }
767
768        Ok(())
769    }
770
771    async fn stop(&self) -> Result<()> {
772        self.cancel_token.cancel();
773        if let Some(pc) = &self.peer_connection {
774            pc.close();
775        }
776        Ok(())
777    }
778
779    async fn send_packet(&mut self, packet: &AudioFrame) -> Result<()> {
780        let packet = packet.clone();
781
782        if let Some(source) = &self.local_source {
783            match &packet.samples {
784                crate::media::Samples::PCM { samples } => {
785                    let payload_type = self.get_payload_type();
786                    let (_, encoded) = self.encoder.encode(payload_type, packet.clone());
787                    let target_codec = self
788                        .encoder
789                        .get_codec_for_pt(payload_type)
790                        .ok_or_else(|| anyhow::anyhow!("Invalid codec type: {}", payload_type))?;
791                    if !encoded.is_empty() {
792                        let clock_rate = target_codec.clock_rate();
793
794                        let now = Instant::now();
795                        if let Some(last_time) = self.last_packet_time {
796                            let elapsed = now.duration_since(last_time);
797                            if elapsed.as_millis() > 50 {
798                                let gap_increment =
799                                    (elapsed.as_millis() as u32 * clock_rate) / 1000;
800                                self.next_rtp_timestamp += gap_increment;
801                                self.need_marker = true;
802                            }
803                        }
804
805                        self.last_packet_time = Some(now);
806
807                        let timestamp_increment = (samples.len() as u64 * clock_rate as u64
808                            / packet.sample_rate as u64
809                            / self.track_config.channels as u64)
810                            as u32;
811                        let rtp_timestamp = self.next_rtp_timestamp;
812                        self.next_rtp_timestamp += timestamp_increment;
813                        let sequence_number = self.next_rtp_sequence_number;
814                        self.next_rtp_sequence_number += 1;
815
816                        let mut marker = false;
817                        if self.need_marker {
818                            marker = true;
819                            self.need_marker = false;
820                        }
821
822                        let frame = RtcAudioFrame {
823                            data: Bytes::from(encoded),
824                            clock_rate,
825                            payload_type: Some(payload_type),
826                            sequence_number: Some(sequence_number),
827                            rtp_timestamp,
828                            marker,
829                            ..Default::default()
830                        };
831                        source.try_send_audio(frame).ok();
832                    }
833                }
834                crate::media::Samples::RTP {
835                    payload,
836                    payload_type,
837                    sequence_number,
838                } => {
839                    let target_codec = self
840                        .encoder
841                        .get_codec_for_pt(*payload_type)
842                        .ok_or_else(|| anyhow::anyhow!("Invalid codec type: {}", payload_type))?;
843                    let clock_rate = target_codec.clock_rate();
844
845                    let now = Instant::now();
846                    if let Some(last_time) = self.last_packet_time {
847                        let elapsed = now.duration_since(last_time);
848                        if elapsed.as_millis() > 50 {
849                            let gap_increment = (elapsed.as_millis() as u32 * clock_rate) / 1000;
850                            self.next_rtp_timestamp += gap_increment;
851                            self.need_marker = true;
852                        }
853                    }
854                    self.last_packet_time = Some(now);
855
856                    let increment = match *payload_type {
857                        0 | 8 | 18 => payload.len() as u32,
858                        9 => payload.len() as u32,
859                        111 => (clock_rate / 50) as u32,
860                        _ => (clock_rate / 50) as u32,
861                    };
862
863                    let rtp_timestamp = self.next_rtp_timestamp;
864                    self.next_rtp_timestamp += increment;
865                    let sequence_number = *sequence_number;
866
867                    let mut marker = false;
868                    if self.need_marker {
869                        marker = true;
870                        self.need_marker = false;
871                    }
872
873                    let frame = RtcAudioFrame {
874                        data: Bytes::from(payload.clone()),
875                        clock_rate,
876                        payload_type: Some(*payload_type),
877                        sequence_number: Some(sequence_number),
878                        rtp_timestamp,
879                        marker,
880                        ..Default::default()
881                    };
882                    source.try_send_audio(frame).ok();
883                }
884                _ => {}
885            }
886        }
887        Ok(())
888    }
889
890    fn add_ice_candidate(
891        &self,
892        candidate: &str,
893        // single audio m-line per track, so unused here
894        _sdp_mid: Option<&str>,
895        _sdp_mline_index: Option<u32>,
896    ) -> Result<()> {
897        let pc = self.peer_connection.as_ref().ok_or_else(|| {
898            anyhow::anyhow!("No PeerConnection available for track {}", self.track_id)
899        })?;
900        pc.add_ice_candidate(IceCandidate::from_sdp(candidate)?)?;
901        Ok(())
902    }
903}
904
905impl RtcTrack {
906    fn get_payload_type(&self) -> u8 {
907        if let Some(pt) = self.payload_type {
908            return pt;
909        }
910
911        self.rtc_config.payload_type.unwrap_or_else(|| {
912            match self.rtc_config.preferred_codec.unwrap_or(CodecType::G722) {
913                CodecType::PCMU => 0,
914                CodecType::PCMA => 8,
915                CodecType::Opus => 111,
916                CodecType::G722 => 9,
917                CodecType::G729 => 18,
918                _ => 111,
919            }
920        })
921    }
922}
923
924#[cfg(test)]
925mod tests {
926    use super::*;
927    use crate::media::track::TrackConfig;
928
929    #[test]
930    fn test_parse_sdp_payload_types() {
931        let track_id = "test-track".to_string();
932        let cancel_token = CancellationToken::new();
933        let mut track = RtcTrack::new(
934            cancel_token,
935            track_id,
936            TrackConfig::default(),
937            RtcTrackConfig::default(),
938        );
939
940        // Case 1: Multiple audio codecs, telephone-event at the end. Primary should be PCMA (8)
941        let sdp1 = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\nm=audio 1234 RTP/AVP 8 0 101\r\na=rtpmap:8 PCMA/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:101 telephone-event/8000\r\n";
942        track
943            .parse_sdp_payload_types(rustrtc::SdpType::Offer, sdp1)
944            .expect("parse offer");
945        assert_eq!(track.get_payload_type(), 8);
946
947        // Case 2: telephone-event at the beginning, should skip it and pick PCMU (0)
948        let mut rtc_config = RtcTrackConfig::default();
949        rtc_config.preferred_codec = Some(CodecType::PCMU);
950        let mut track2 = RtcTrack::new(
951            CancellationToken::new(),
952            "test-track-2".to_string(),
953            TrackConfig::default(),
954            rtc_config,
955        );
956
957        let sdp2 = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\nm=audio 1234 RTP/AVP 101 0 8\r\na=rtpmap:101 telephone-event/8000\r\na=rtpmap:0 PCMU/8000\r\na=rtpmap:8 PCMA/8000\r\n";
958        track2
959            .parse_sdp_payload_types(rustrtc::SdpType::Offer, sdp2)
960            .expect("parse offer");
961        assert_eq!(track2.get_payload_type(), 0);
962
963        // Case 3: Opus with dynamic payload type 111
964        let sdp3 = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\nm=audio 1234 RTP/AVP 111 101\r\na=rtpmap:111 opus/48000/2\r\na=rtpmap:101 telephone-event/8000\r\n";
965        track
966            .parse_sdp_payload_types(rustrtc::SdpType::Offer, sdp3)
967            .expect("parse offer");
968        assert_eq!(track.get_payload_type(), 111);
969
970        // Case 4: Linphone can offer G729 first, but the final answer decides
971        // the outgoing payload type.
972        let mut rtc_config = RtcTrackConfig::default();
973        rtc_config.preferred_codec = Some(CodecType::PCMU);
974        rtc_config.codecs = vec![CodecType::PCMU, CodecType::PCMA];
975        let mut track4 = RtcTrack::new(
976            CancellationToken::new(),
977            "test-track-4".to_string(),
978            TrackConfig::default(),
979            rtc_config,
980        );
981
982        let sdp4 = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\nm=audio 1234 RTP/AVP 18 0 101\r\na=fmtp:18 annexb=yes\r\na=rtpmap:101 telephone-event/8000\r\n";
983        track4
984            .parse_sdp_payload_types(rustrtc::SdpType::Offer, sdp4)
985            .expect("parse offer");
986        assert_eq!(track4.get_payload_type(), 18);
987
988        let answer4 = "v=0\r\no=- 0 0 IN IP4 127.0.0.1\r\ns=-\r\nc=IN IP4 127.0.0.1\r\nt=0 0\r\nm=audio 1234 RTP/AVP 0\r\na=rtpmap:0 PCMU/8000\r\n";
989        track4
990            .parse_sdp_payload_types(rustrtc::SdpType::Answer, answer4)
991            .expect("parse answer");
992        assert_eq!(track4.get_payload_type(), 0);
993    }
994
995    #[tokio::test]
996    async fn test_rtp_mode_handshake_spawns_handler() {
997        use rustrtc::TransportMode;
998
999        let track_id = "test-track-sip".to_string();
1000        let cancel = CancellationToken::new();
1001        let track_config = TrackConfig::default();
1002        let mut rtc_config = RtcTrackConfig::default();
1003        rtc_config.mode = TransportMode::Rtp;
1004        rtc_config.preferred_codec = Some(CodecType::PCMU);
1005        rtc_config.codecs = vec![CodecType::PCMU, CodecType::TelephoneEvent];
1006
1007        let mut track = RtcTrack::new(cancel, track_id, track_config, rtc_config);
1008
1009        // Standard SIP/SDP offer
1010        let offer = "v=0\r\n\
1011o=- 123456 123456 IN IP4 172.0.0.1\r\n\
1012s=-\r\n\
1013c=IN IP4 172.0.0.1\r\n\
1014t=0 0\r\n\
1015m=audio 10000 RTP/AVP 0 101\r\n\
1016a=rtpmap:0 PCMU/8000\r\n\
1017a=rtpmap:101 telephone-event/8000\r\n\
1018a=sendrecv\r\n";
1019
1020        // This should not panic and should set up the transceiver
1021        let res = track.handshake(offer.to_string(), None).await;
1022        assert!(res.is_ok(), "handshake failed: {res:?}");
1023
1024        // We can inspect the PeerConnection to ensure it has a transceiver with a receiver
1025        if let Some(pc) = &track.peer_connection {
1026            let transceivers = pc.get_transceivers();
1027            // With the fix, we expect the logic to have iterated these transceivers.
1028            // In RTP/Receive mode, we should have 1 transceiver with a receiver.
1029            assert_eq!(transceivers.len(), 1);
1030            assert!(transceivers[0].receiver().is_some());
1031        } else {
1032            panic!("PeerConnection not initialized");
1033        }
1034    }
1035
1036    /// Build an RTP-mode RtcTrack that has already generated its local offer,
1037    /// returning the track together with its peer connection.
1038    async fn rtp_track_with_local_offer(id: &str) -> RtcTrack {
1039        let mut rtc_config = RtcTrackConfig::default();
1040        rtc_config.mode = rustrtc::TransportMode::Rtp;
1041        rtc_config.preferred_codec = Some(CodecType::PCMU);
1042        rtc_config.codecs = vec![CodecType::PCMU, CodecType::PCMA];
1043
1044        let mut track = RtcTrack::new(
1045            CancellationToken::new(),
1046            id.to_string(),
1047            TrackConfig {
1048                codec: CodecType::PCMU,
1049                samplerate: 8000,
1050                ..Default::default()
1051            },
1052            rtc_config,
1053        );
1054        track.create().await.expect("create peer connection");
1055        track.local_description().await.expect("local offer");
1056        track
1057    }
1058
1059    const PCMU_SDP_1: &str = "v=0\r\n\
1060        o=- 0 0 IN IP4 127.0.0.1\r\n\
1061        s=-\r\n\
1062        c=IN IP4 127.0.0.1\r\n\
1063        t=0 0\r\n\
1064        m=audio 10000 RTP/AVP 0\r\n\
1065        a=rtpmap:0 PCMU/8000\r\n";
1066
1067    const PCMU_SDP_2: &str = "v=0\r\n\
1068        o=- 0 1 IN IP4 127.0.0.1\r\n\
1069        s=-\r\n\
1070        c=IN IP4 127.0.0.1\r\n\
1071        t=0 0\r\n\
1072        m=audio 20000 RTP/AVP 0\r\n\
1073        a=rtpmap:0 PCMU/8000\r\n";
1074
1075    /// SIP 183 early media must be applied as a provisional answer (Pranswer),
1076    /// keeping the signaling state in HaveLocalOffer, so the final 200 OK can
1077    /// still complete the negotiation as a full Answer.
1078    #[tokio::test]
1079    async fn test_pranswer_keeps_local_offer_until_final_answer() {
1080        use rustrtc::SignalingState;
1081
1082        let mut track = rtp_track_with_local_offer("test-pranswer").await;
1083        let pc = track.peer_connection.clone().expect("peer connection");
1084
1085        assert_eq!(
1086            pc.signaling_state(),
1087            SignalingState::HaveLocalOffer,
1088            "after local offer"
1089        );
1090
1091        track
1092            .update_remote_description_provisional(&PCMU_SDP_1.to_string())
1093            .await
1094            .expect("apply 183 as provisional answer");
1095
1096        assert_eq!(
1097            pc.signaling_state(),
1098            SignalingState::HaveLocalOffer,
1099            "183 (Pranswer) must not finalize negotiation"
1100        );
1101
1102        track
1103            .update_remote_description_force(&PCMU_SDP_2.to_string())
1104            .await
1105            .expect("apply 200 OK as final answer");
1106
1107        assert_eq!(
1108            pc.signaling_state(),
1109            SignalingState::Stable,
1110            "final 200 OK answer must stabilize signaling"
1111        );
1112    }
1113
1114    /// When the 200 OK carries no body after early media, the early SDP is
1115    /// re-applied as a final Answer. Even though it is byte-for-byte the same
1116    /// SDP as the provisional answer, the forced update must still transition
1117    /// the signaling state from HaveLocalOffer to Stable.
1118    #[tokio::test]
1119    async fn test_pranswer_finalized_with_force_even_when_sdp_unchanged() {
1120        use rustrtc::SignalingState;
1121
1122        let mut track = rtp_track_with_local_offer("test-pranswer-force").await;
1123        let pc = track.peer_connection.clone().expect("peer connection");
1124
1125        track
1126            .update_remote_description_provisional(&PCMU_SDP_1.to_string())
1127            .await
1128            .expect("apply 183 as provisional answer");
1129        assert_eq!(pc.signaling_state(), SignalingState::HaveLocalOffer);
1130
1131        // Final answer resolves to the same SDP (empty 200 OK body fallback).
1132        track
1133            .update_remote_description_force(&PCMU_SDP_1.to_string())
1134            .await
1135            .expect("re-apply early SDP as final answer");
1136
1137        assert_eq!(
1138            pc.signaling_state(),
1139            SignalingState::Stable,
1140            "forced final answer must stabilize even with unchanged SDP"
1141        );
1142    }
1143}