//
// Copyright (c) 2013-2025 The SRS Authors
//
// SPDX-License-Identifier: MIT
//

#include <srs_app_rtsp_conn.hpp>

using namespace std;

#include <sys/socket.h>

#include <sstream>

#include <srs_app_config.hpp>
#include <srs_app_factory.hpp>
#include <srs_app_http_hooks.hpp>
#ifdef SRS_RTSP
#include <srs_app_rtsp_source.hpp>
#endif
#include <srs_app_security.hpp>
#include <srs_app_st.hpp>
#include <srs_app_statistic.hpp>
#include <srs_app_utility.hpp>
#include <srs_core_autofree.hpp>
#include <srs_kernel_buffer.hpp>
#include <srs_kernel_error.hpp>
#include <srs_kernel_kbps.hpp>
#include <srs_kernel_log.hpp>
#include <srs_kernel_pithy_print.hpp>
#include <srs_kernel_rtc_rtp.hpp>
#ifdef SRS_RTSP
#include <srs_protocol_rtsp_stack.hpp>
#endif
#include <srs_protocol_st.hpp>
#include <srs_protocol_utility.hpp>

extern SrsPps *_srs_pps_snack;
extern SrsPps *_srs_pps_snack2;
extern SrsPps *_srs_pps_snack3;
extern SrsPps *_srs_pps_snack4;

extern SrsPps *_srs_pps_rnack;
extern SrsPps *_srs_pps_rnack2;

extern SrsPps *_srs_pps_pub;
extern SrsPps *_srs_pps_conn;

ISrsRtspPlayStream::ISrsRtspPlayStream()
{
}

ISrsRtspPlayStream::~ISrsRtspPlayStream()
{
}

SrsRtspPlayStream::SrsRtspPlayStream(ISrsRtspConnection *s, const SrsContextId &cid) : source_(new SrsRtspSource())
{
    cid_ = cid;
    trd_ = NULL;

    req_ = NULL;

    is_started = false;
    session_ = s;

    cache_ssrc0_ = cache_ssrc1_ = cache_ssrc2_ = 0;
    cache_track0_ = cache_track1_ = cache_track2_ = NULL;

    app_factory_ = _srs_app_factory;
    stat_ = _srs_stat;
    rtsp_sources_ = _srs_rtsp_sources;
}

SrsRtspPlayStream::~SrsRtspPlayStream()
{
    srs_freep(trd_);
    srs_freep(req_);

    if (true) {
        std::map<uint32_t, ISrsRtspSendTrack *>::iterator it;
        for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) {
            srs_freep(it->second);
        }
    }

    if (true) {
        std::map<uint32_t, ISrsRtspSendTrack *>::iterator it;
        for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) {
            srs_freep(it->second);
        }
    }

    // update the statistic when client coveried.
    // TODO: FIXME: Should finger out the err.
    stat_->on_disconnect(cid_.c_str(), srs_success);

    app_factory_ = NULL;
    stat_ = NULL;
    rtsp_sources_ = NULL;
}

srs_error_t SrsRtspPlayStream::initialize(ISrsRequest *req, std::map<uint32_t, SrsRtcTrackDescription *> sub_relations)
{
    srs_error_t err = srs_success;

    req_ = req->copy();

    // We must do stat the client before hooks, because hooks depends on it.
    if ((err = stat_->on_client(cid_.c_str(), req_, session_, SrsRtcConnPlay)) != srs_success) {
        return srs_error_wrap(err, "RTSP: stat client");
    }

    if ((err = rtsp_sources_->fetch_or_create(req_, source_)) != srs_success) {
        return srs_error_wrap(err, "RTSP: fetch source failed");
    }

    for (map<uint32_t, SrsRtcTrackDescription *>::iterator it = sub_relations.begin(); it != sub_relations.end(); ++it) {
        uint32_t ssrc = it->first;
        SrsRtcTrackDescription *desc = it->second;

        if (desc->type_ == "audio") {
            ISrsRtspSendTrack *track = app_factory_->create_rtsp_audio_send_track(session_, desc);
            audio_tracks_.insert(make_pair(ssrc, track));
        }

        if (desc->type_ == "video") {
            ISrsRtspSendTrack *track = app_factory_->create_rtsp_video_send_track(session_, desc);
            video_tracks_.insert(make_pair(ssrc, track));
        }
    }

    return err;
}

// TODO: Remove it for RTSP?
void SrsRtspPlayStream::on_stream_change(SrsRtcSourceDescription *desc)
{
    if (!desc)
        return;

    // Refresh the relation for audio.
    // TODO: FIXME: Match by label?
    if (desc && desc->audio_track_desc_ && audio_tracks_.size() == 1) {
        if (!audio_tracks_.empty()) {
            uint32_t ssrc = desc->audio_track_desc_->ssrc_;
            ISrsRtspSendTrack *track = audio_tracks_.begin()->second;

            if (track->track_desc()->media_->pt_of_publisher_ != desc->audio_track_desc_->media_->pt_) {
                track->track_desc()->media_->pt_of_publisher_ = desc->audio_track_desc_->media_->pt_;
            }

            if (desc->audio_track_desc_->red_ && track->track_desc()->red_ &&
                track->track_desc()->red_->pt_of_publisher_ != desc->audio_track_desc_->red_->pt_) {
                track->track_desc()->red_->pt_of_publisher_ = desc->audio_track_desc_->red_->pt_;
            }

            audio_tracks_.clear();
            audio_tracks_.insert(make_pair(ssrc, track));
        }
    }

    // Refresh the relation for video.
    // TODO: FIMXE: Match by label?
    if (desc && desc->video_track_descs_.size() == 1) {
        if (!video_tracks_.empty()) {
            SrsRtcTrackDescription *vdesc = desc->video_track_descs_.at(0);
            uint32_t ssrc = vdesc->ssrc_;
            ISrsRtspSendTrack *track = video_tracks_.begin()->second;

            if (track->track_desc()->media_->pt_of_publisher_ != vdesc->media_->pt_) {
                track->track_desc()->media_->pt_of_publisher_ = vdesc->media_->pt_;
            }

            if (vdesc->red_ && track->track_desc()->red_ &&
                track->track_desc()->red_->pt_of_publisher_ != vdesc->red_->pt_) {
                track->track_desc()->red_->pt_of_publisher_ = vdesc->red_->pt_;
            }

            video_tracks_.clear();
            video_tracks_.insert(make_pair(ssrc, track));
        }
    }
}

const SrsContextId &SrsRtspPlayStream::context_id()
{
    return cid_;
}

srs_error_t SrsRtspPlayStream::start()
{
    srs_error_t err = srs_success;

    // If player coroutine allocated, we think the player is started.
    // To prevent play multiple times for this play stream.
    // @remark Allow start multiple times, for DTLS may retransmit the final packet.
    if (is_started) {
        return err;
    }

    srs_freep(trd_);
    trd_ = new SrsFastCoroutine("rtsp_sender", this, cid_);

    if ((err = trd_->start()) != srs_success) {
        return srs_error_wrap(err, "rtsp_sender");
    }

    is_started = true;

    return err;
}

void SrsRtspPlayStream::stop()
{
    if (trd_) {
        trd_->stop();
    }
}

srs_error_t SrsRtspPlayStream::cycle()
{
    srs_error_t err = srs_success;

    SrsSharedPtr<SrsRtspSource> &source = source_;
    srs_assert(source.get());

    SrsRtspConsumer *consumer_raw = NULL;
    if ((err = source->create_consumer(consumer_raw)) != srs_success) {
        return srs_error_wrap(err, "create consumer, source=%s", req_->get_stream_url().c_str());
    }

    srs_assert(consumer_raw);
    SrsUniquePtr<SrsRtspConsumer> consumer(consumer_raw);

    consumer->set_handler(this);

    // TODO: FIXME: Dumps the SPS/PPS from gop cache, without other frames.
    if ((err = source->consumer_dumps(consumer.get())) != srs_success) {
        return srs_error_wrap(err, "dumps consumer, url=%s", req_->get_stream_url().c_str());
    }

    // TODO: FIXME: Add cost in ms.
    SrsContextId cid = source->source_id();
    srs_trace("RTSP: start play url=%s, source_id=%s/%s", req_->get_stream_url().c_str(),
              cid.c_str(), source->pre_source_id().c_str());

    SrsUniquePtr<SrsErrorPithyPrint> epp(new SrsErrorPithyPrint());

    // For RTSP, donot use merged write.
    int mw_msgs = 1;
    while (true) {
        if ((err = trd_->pull()) != srs_success) {
            return srs_error_wrap(err, "RTSP sender thread");
        }

        // Wait for amount of packets.
        SrsRtpPacket *pkt = NULL;
        consumer->dump_packet(&pkt);
        if (!pkt) {
            // TODO: FIXME: We should check the quit event.
            consumer->wait(mw_msgs);
            continue;
        }

        // Send-out the RTP packet and do cleanup
        // @remark Note that the pkt might be set to NULL.
        if ((err = send_packet(pkt)) != srs_success) {
            uint32_t nn = 0;
            if (epp->can_print(err, &nn)) {
                srs_warn("play send packets=%u, nn=%u/%u, err: %s", 1, epp->nn_count_, nn, srs_error_desc(err).c_str());
            }
            srs_freep(err);
        }

        // Free the packet.
        // @remark Note that the pkt might be set to NULL.
        srs_freep(pkt);
    }
}

srs_error_t SrsRtspPlayStream::send_packet(SrsRtpPacket *&pkt)
{
    srs_error_t err = srs_success;

    uint32_t ssrc = pkt->header_.get_ssrc();

    // Try to find track from cache.
    ISrsRtspSendTrack *track = NULL;
    if (cache_ssrc0_ == ssrc) {
        track = cache_track0_;
    } else if (cache_ssrc1_ == ssrc) {
        track = cache_track1_;
    } else if (cache_ssrc2_ == ssrc) {
        track = cache_track2_;
    }

    // Find by original tracks and build fast cache.
    if (!track) {
        if (pkt->is_audio()) {
            map<uint32_t, ISrsRtspSendTrack *>::iterator it = audio_tracks_.find(ssrc);
            if (it != audio_tracks_.end()) {
                track = it->second;
            }
        } else {
            map<uint32_t, ISrsRtspSendTrack *>::iterator it = video_tracks_.find(ssrc);
            if (it != video_tracks_.end()) {
                track = it->second;
            }
        }

        if (track && !cache_ssrc2_) {
            if (!cache_ssrc0_) {
                cache_ssrc0_ = ssrc;
                cache_track0_ = track;
            } else if (!cache_ssrc1_) {
                cache_ssrc1_ = ssrc;
                cache_track1_ = track;
            } else if (!cache_ssrc2_) {
                cache_ssrc2_ = ssrc;
                cache_track2_ = track;
            }
        }
    }

    // Ignore if no track found.
    if (!track) {
        srs_warn("RTSP: Drop for ssrc %u not found", ssrc);
        return err;
    }

    // Consume packet by track.
    if ((err = track->on_rtp(pkt)) != srs_success) {
        return srs_error_wrap(err, "audio track, SSRC=%u, SEQ=%u", ssrc, pkt->header_.get_sequence());
    }

    return err;
}

void SrsRtspPlayStream::set_all_tracks_status(bool status)
{
    std::ostringstream merged_log;

    // set video track status
    if (true) {
        std::map<uint32_t, ISrsRtspSendTrack *>::iterator it;
        for (it = video_tracks_.begin(); it != video_tracks_.end(); ++it) {
            ISrsRtspSendTrack *track = it->second;

            bool previous = track->set_track_status(status);
            merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},";
        }
    }

    // set audio track status
    if (true) {
        std::map<uint32_t, ISrsRtspSendTrack *>::iterator it;
        for (it = audio_tracks_.begin(); it != audio_tracks_.end(); ++it) {
            ISrsRtspSendTrack *track = it->second;

            bool previous = track->set_track_status(status);
            merged_log << "{track: " << track->get_track_id() << ", is_active: " << previous << "=>" << status << "},";
        }
    }

    srs_trace("RTSP: Init tracks %s ok", merged_log.str().c_str());
}

ISrsRtspConnection::ISrsRtspConnection()
{
}

ISrsRtspConnection::~ISrsRtspConnection()
{
}

SrsRtspConnection::SrsRtspConnection(ISrsResourceManager *cm, ISrsProtocolReadWriter *skt, std::string cip, int port)
{
    manager_ = cm;
    cid_ = _srs_context->generate_id();
    _srs_context->set_id(cid_);

    // Initialize timeout management fields from SrsRtspConnection2
    last_stun_time = 0;
    session_timeout = 0;
    disposing_ = false;

    request_ = new SrsRequest();
    request_->ip_ = cip;
    ip_ = cip;
    port_ = port;
    rtsp_ = new SrsRtspStack(skt);
    trd_ = new SrsSTCoroutine("rtsp", this, _srs_context->get_id());

    // Initialize merged SrsRtspSession members
    skt_ = skt;
    source_ = NULL;
    player_ = NULL;

    cache_iov_ = new iovec();
    cache_iov_->iov_base = new char[kRtpPacketSize];
    cache_iov_->iov_len = kRtpPacketSize;
    cache_buffer_ = new SrsBuffer((char *)cache_iov_->iov_base, kRtpPacketSize);

    delta_ = new SrsEphemeralDelta();
    security_ = new SrsSecurity();

    rtsp_manager_ = _srs_rtsp_manager;
    stat_ = _srs_stat;
    config_ = _srs_config;
    rtsp_sources_ = _srs_rtsp_sources;
    hooks_ = _srs_hooks;
}

void SrsRtspConnection::assemble()
{
    rtsp_manager_->subscribe(this);
}

SrsRtspConnection::~SrsRtspConnection()
{
    rtsp_manager_->unsubscribe(this);

    srs_freep(request_);
    srs_freep(rtsp_);
    srs_freep(trd_);

    // Cleanup merged SrsRtspSession members
    for (std::map<uint32_t, SrsRtcTrackDescription *>::iterator it = tracks_.begin(); it != tracks_.end(); ++it) {
        srs_freep(it->second);
    }
    tracks_.clear();

    for (std::map<uint32_t, ISrsStreamWriter *>::iterator it = networks_.begin(); it != networks_.end(); ++it) {
        srs_freep(it->second);
    }
    networks_.clear();

    srs_freep(delta_);
    srs_freep(security_);
    srs_freep(player_);

    if (true) {
        char *iov_base = (char *)cache_iov_->iov_base;
        srs_freepa(iov_base);
        srs_freep(cache_iov_);
    }
    srs_freep(cache_buffer_);

    rtsp_manager_ = NULL;
    stat_ = NULL;
    config_ = NULL;
    rtsp_sources_ = NULL;
    hooks_ = NULL;
}

srs_error_t SrsRtspConnection::do_send_packet(SrsRtpPacket *pkt)
{
    srs_error_t err = srs_success;

    uint32_t ssrc = pkt->header_.get_ssrc();
    ISrsStreamWriter *network = networks_[ssrc];
    if (!network) {
        return srs_error_new(ERROR_RTSP_NO_TRACK, "network not found for ssrc: %u", ssrc);
    }

    iovec *iov = cache_iov_;
    cache_buffer_->skip(-1 * cache_buffer_->pos());

    // Marshal packet to bytes in iovec.
    if (true) {
        if ((err = pkt->encode(cache_buffer_)) != srs_success) {
            return srs_error_wrap(err, "encode packet");
        }
        iov->iov_len = cache_buffer_->pos();
    }

    ssize_t write = 0;
    if ((err = network->write(iov->iov_base, iov->iov_len, &write)) != srs_success) {
        return srs_error_wrap(err, "send rtp packet");
    }

    delta_->add_delta(0, write);

    return err;
}

ISrsKbpsDelta *SrsRtspConnection::delta()
{
    return delta_;
}

std::string SrsRtspConnection::desc()
{
    return "Rtsp";
}

const SrsContextId &SrsRtspConnection::get_id()
{
    return cid_;
}

std::string SrsRtspConnection::remote_ip()
{
    return ip_;
}

void SrsRtspConnection::expire()
{
    trd_->interrupt();
}

srs_error_t SrsRtspConnection::start()
{
    srs_error_t err = srs_success;

    if ((err = trd_->start()) != srs_success) {
        return srs_error_wrap(err, "coroutine");
    }

    return err;
}

srs_error_t SrsRtspConnection::cycle()
{
    srs_error_t err = srs_success;

    // Serve the client.
    err = do_cycle();

    // Update statistic when done.
    stat_->kbps_add_delta(get_id().c_str(), delta());

    do_teardown();

    // Notify manager to remove it.
    // Note that we create this object, so we use manager to remove it.
    manager_->remove(this);

    // success.
    if (err == srs_success) {
        srs_trace("RTSP: client finished.");
        return err;
    }

    // It maybe success with message.
    if (srs_error_code(err) == ERROR_SUCCESS) {
        srs_trace("RTSP: client finished%s.", srs_error_summary(err).c_str());
        srs_freep(err);
        return err;
    }

    // client close peer.
    // TODO: FIXME: Only reset the error when client closed it.
    if (srs_is_client_gracefully_close(err)) {
        srs_warn("RTSP: client disconnect peer. ret=%d", srs_error_code(err));
    } else if (srs_is_server_gracefully_close(err)) {
        srs_warn("RTSP: server disconnect. ret=%d", srs_error_code(err));
    } else {
        srs_error("RTSP: serve error %s", srs_error_desc(err).c_str());
    }

    srs_freep(err);
    return srs_success;
}

srs_error_t SrsRtspConnection::do_cycle()
{
    srs_error_t err = srs_success;
    srs_trace("RTSP: client ip=%s, port=%d", ip_.c_str(), port_);

    // consume all rtsp messages.
    while (true) {
        if ((err = trd_->pull()) != srs_success) {
            return srs_error_wrap(err, "rtsp cycle");
        }

        SrsRtspRequest *req_raw = NULL;
        if ((err = rtsp_->recv_message(&req_raw)) != srs_success) {
            return srs_error_wrap(err, "recv message");
        }

        if ((err = on_rtsp_request(req_raw)) != srs_success) {
            return srs_error_wrap(err, "on rtsp request");
        }
    }

    return err;
}

srs_error_t SrsRtspConnection::on_rtsp_request(SrsRtspRequest *req_raw)
{
    srs_error_t err = srs_success;

    SrsUniquePtr<SrsRtspRequest> req(req_raw);

    if (req->is_options()) {
        srs_trace("RTSP: OPTIONS cseq=%ld, url=%s, client=%s:%d", req->seq_, req->uri_.c_str(), ip_.c_str(), port_);
        SrsUniquePtr<SrsRtspOptionsResponse> res(new SrsRtspOptionsResponse((int)req->seq_));
        if ((err = rtsp_->send_message(res.get())) != srs_success) {
            return srs_error_wrap(err, "response option");
        }
    } else if (req->is_describe()) {
        // create session.
        if (session_id_.empty()) {
            SrsRand rand;
            session_id_ = rand.gen_str(8);
        }

        SrsUniquePtr<SrsRtspDescribeResponse> res(new SrsRtspDescribeResponse((int)req->seq_));
        res->session_ = session_id_;

        std::string sdp;
        if ((err = do_describe(req.get(), sdp)) != srs_success) {
            res->status_ = SRS_CONSTS_RTSP_InternalServerError;
            if (srs_error_code(err) == ERROR_RTSP_NO_TRACK) {
                res->status_ = SRS_CONSTS_RTSP_NotFound;
            } else if (srs_error_code(err) == ERROR_SYSTEM_SECURITY_DENY) {
                res->status_ = SRS_CONSTS_RTSP_Forbidden;
            }
            srs_warn("RTSP: DESCRIBE failed: %s", srs_error_desc(err).c_str());
            srs_freep(err);
        }

        res->sdp_ = sdp;
        if ((err = rtsp_->send_message(res.get())) != srs_success) {
            return srs_error_wrap(err, "response describe");
        }

        // Filter the \r\n to \\r\\n for JSON.
        std::string local_sdp_escaped = srs_strings_replace(sdp.c_str(), "\r\n", "\\r\\n");
        srs_trace("RTSP: DESCRIBE cseq=%ld, session=%s, sdp: %s", req->seq_, session_id_.c_str(), local_sdp_escaped.c_str());
    } else if (req->is_setup()) {
        srs_assert(req->transport_);

        SrsUniquePtr<SrsRtspSetupResponse> res(new SrsRtspSetupResponse((int)req->seq_));
        res->session_ = session_id_;

        uint32_t ssrc = 0;
        if ((err = do_setup(req.get(), &ssrc)) != srs_success) {
            if (srs_error_code(err) == ERROR_RTSP_TRANSPORT_NOT_SUPPORTED) {
                res->status_ = SRS_CONSTS_RTSP_UnsupportedTransport;
                srs_warn("RTSP: SETUP failed: %s", srs_error_summary(err).c_str());
            } else {
                res->status_ = SRS_CONSTS_RTSP_InternalServerError;
                srs_warn("RTSP: SETUP failed: %s", srs_error_desc(err).c_str());
            }
            srs_freep(err);
        }

        res->transport_->copy(req->transport_);
        res->session_ = session_id_;
        res->ssrc_ = srs_strconv_format_int(ssrc);
        res->client_port_min_ = req->transport_->client_port_min_;
        res->client_port_max_ = req->transport_->client_port_max_;
        // TODO: FIXME: listen local port
        res->local_port_min_ = 0;
        res->local_port_max_ = 0;
        if ((err = rtsp_->send_message(res.get())) != srs_success) {
            return srs_error_wrap(err, "response setup");
        }
        srs_trace("RTSP: SETUP cseq=%ld, session=%s, transport=%s/%s/%s, ssrc=%u, client_port=%d-%d",
                  req->seq_, session_id_.c_str(), req->transport_->transport_.c_str(), req->transport_->profile_.c_str(),
                  req->transport_->lower_transport_.c_str(), ssrc, req->transport_->client_port_min_, req->transport_->client_port_max_);
    } else if (req->is_play()) {
        SrsUniquePtr<SrsRtspResponse> res(new SrsRtspResponse((int)req->seq_));
        res->session_ = session_id_;
        if ((err = rtsp_->send_message(res.get())) != srs_success) {
            return srs_error_wrap(err, "response record");
        }

        if ((err = do_play(req.get(), this)) != srs_success) {
            return srs_error_wrap(err, "prepare play");
        }
        srs_trace("RTSP: PLAY cseq=%ld, session=%s, streaming started", req->seq_, session_id_.c_str());
    } else if (req->is_teardown()) {
        SrsUniquePtr<SrsRtspResponse> res(new SrsRtspResponse((int)req->seq_));
        res->session_ = session_id_;
        if ((err = rtsp_->send_message(res.get())) != srs_success) {
            return srs_error_wrap(err, "response teardown");
        }

        if ((err = do_teardown()) != srs_success) {
            return srs_error_wrap(err, "teardown");
        }
        srs_trace("RTSP: TEARDOWN cseq=%ld, session=%s, streaming stopped", req->seq_, session_id_.c_str());
    }

    return err;
}

void SrsRtspConnection::on_before_dispose(ISrsResource *c)
{
    if (disposing_) {
        return;
    }

    SrsRtspConnection *session = dynamic_cast<SrsRtspConnection *>(c);
    if (session == this) {
        disposing_ = true;
    }

    if (session && session == this) {
        _srs_context->set_id(cid_);
        srs_trace("RTSP: session detach from [%s](%s), disposing=%d", c->get_id().c_str(),
                  c->desc().c_str(), disposing_);
    }
}

void SrsRtspConnection::on_disposing(ISrsResource *c)
{
    if (disposing_) {
        return;
    }
}

void SrsRtspConnection::switch_to_context()
{
    _srs_context->set_id(cid_);
}

const SrsContextId &SrsRtspConnection::context_id()
{
    return cid_;
}

bool SrsRtspConnection::is_alive()
{
    return last_stun_time + session_timeout > srs_time_now_cached();
}

void SrsRtspConnection::alive()
{
    last_stun_time = srs_time_now_cached();
}

srs_error_t SrsRtspConnection::do_describe(SrsRtspRequest *req, std::string &sdp)
{
    srs_error_t err = srs_success;
    srs_net_url_parse_rtmp_url(req->uri_, request_->tcUrl_, request_->stream_);

    srs_net_url_parse_tcurl(request_->tcUrl_, request_->schema_, request_->host_, request_->vhost_,
                            request_->app_, request_->stream_, request_->port_, request_->param_);

    // discovery vhost, resolve the vhost from config
    SrsConfDirective *parsed_vhost = config_->get_vhost(request_->vhost_);
    if (parsed_vhost) {
        request_->vhost_ = parsed_vhost->arg0();
    }

    if ((err = security_->check(SrsRtcConnPlay, ip_, request_)) != srs_success) {
        return srs_error_wrap(err, "RTSP: security check");
    }

    if ((err = http_hooks_on_play(request_)) != srs_success) {
        return srs_error_wrap(err, "RTSP: http_hooks_on_play");
    }

    if ((err = rtsp_sources_->fetch_or_create(request_, source_)) != srs_success) {
        return srs_error_wrap(err, "create source");
    }

    SrsSdp local_sdp;
    local_sdp.version_ = "0";
    local_sdp.username_ = "SRS RTSP Server";
    local_sdp.session_id_ = "0";
    local_sdp.session_version_ = "0";
    local_sdp.nettype_ = "IN";
    local_sdp.addrtype_ = "IP4";
    local_sdp.unicast_address_ = "0.0.0.0";
    local_sdp.session_name_ = "Play";
    local_sdp.control_ = req->uri_;
    local_sdp.ice_lite_ = ""; // Disable this line.

    uint32_t track_id = 0;
    SrsRtcTrackDescription *audio_desc = source_->audio_desc();
    if (audio_desc) {
        SrsRtcTrackDescription *audio_track_desc = audio_desc->copy();
        audio_track_desc->id_ = srs_strconv_format_int(track_id);
        tracks_.insert(std::make_pair(audio_track_desc->ssrc_, audio_track_desc));

        SrsMediaDesc media_audio("audio");
        media_audio.port_ = 0;           // Port 0 indicates no UDP transport available
        media_audio.protos_ = "RTP/AVP"; // MUST be RTP/AVP
        media_audio.control_ = req->uri_ + "/trackID=" + srs_strconv_format_int(track_id);
        media_audio.recvonly_ = true;
        media_audio.rtcp_mux_ = true;

        media_audio.payload_types_.push_back(SrsMediaPayloadType(audio_track_desc->media_->pt_));
        SrsMediaPayloadType &ps_audio = media_audio.payload_types_.at(0);
        ps_audio.encoding_name_ = audio_track_desc->media_->name_;
        ps_audio.clock_rate_ = audio_track_desc->media_->sample_;

        // if the payload is opus, and the encoding_param_ is channel
        SrsAudioPayload *ap = dynamic_cast<SrsAudioPayload *>(audio_track_desc->media_);
        if (ap) {
            ps_audio.encoding_param_ = srs_strconv_format_int(ap->channel_);

            // Append the AAC config hex to the fmtp line.
            if (ap->name_ == "MPEG4-GENERIC" && !ap->aac_config_hex_.empty()) {
                // streamtype=5 - Mandatory (indicates audio stream)
                // mode=AAC-hbr - Mandatory (AAC High Bit Rate mode)
                // sizelength=13 - Mandatory, defaults to 13 bits for AAC
                // indexlength=3 - Mandatory, defaults to 3 bits
                // profile-level-id=1 - Optional, defaults to 1 (AAC Main Profile)
                // indexdeltalength=3 - Optional, defaults to 3 bits
                ps_audio.format_specific_param_ = "streamtype=5;mode=AAC-hbr;sizelength=13;indexlength=3";
                ps_audio.format_specific_param_ += ";config=" + ap->aac_config_hex_;
                srs_trace("RTSP: Added AAC fmtp: %s", ps_audio.format_specific_param_.c_str());
            }
        }

        local_sdp.media_descs_.push_back(media_audio);
        track_id++;
    }

    SrsRtcTrackDescription *video_desc = source_->video_desc();
    if (video_desc) {
        SrsRtcTrackDescription *video_track_desc = video_desc->copy();
        video_track_desc->id_ = srs_strconv_format_int(track_id);
        tracks_.insert(std::make_pair(video_track_desc->ssrc_, video_track_desc));

        SrsMediaDesc media_video("video");
        media_video.port_ = 0;           // Port 0 indicates no UDP transport available
        media_video.protos_ = "RTP/AVP"; // MUST be RTP/AVP
        media_video.control_ = req->uri_ + "/trackID=" + srs_strconv_format_int(track_id);
        media_video.recvonly_ = true;
        media_video.rtcp_mux_ = true;

        media_video.payload_types_.push_back(SrsMediaPayloadType(video_track_desc->media_->pt_));
        SrsMediaPayloadType &ps_video = media_video.payload_types_.at(0);
        ps_video.encoding_name_ = video_track_desc->media_->name_;
        ps_video.clock_rate_ = video_track_desc->media_->sample_;

        local_sdp.media_descs_.push_back(media_video);
        track_id++;
    }

    if (track_id == 0) {
        return srs_error_new(ERROR_RTSP_NO_TRACK, "no track found");
    }

    std::ostringstream ss;
    if ((err = local_sdp.encode(ss)) != srs_success) {
        return srs_error_wrap(err, "encode sdp");
    }

    sdp = ss.str();
    return srs_success;
}

srs_error_t SrsRtspConnection::do_setup(SrsRtspRequest *req, uint32_t *pssrc)
{
    srs_error_t err = srs_success;

    uint32_t ssrc = 0;
    if ((err = get_ssrc_by_stream_id(req->stream_id_, &ssrc)) != srs_success) {
        return srs_error_wrap(err, "get ssrc by stream_id");
    }

    // Only support TCP transport, reject UDP
    // This ensures better firewall/NAT compatibility and eliminates port allocation complexity
    if (req->transport_->lower_transport_ != "TCP") {
        return srs_error_new(ERROR_RTSP_TRANSPORT_NOT_SUPPORTED,
                             "UDP transport not supported, only TCP/interleaved mode is supported");
    }

    SrsRtspTcpNetwork *network = new SrsRtspTcpNetwork(skt_, req->transport_->interleaved_min_);
    networks_[ssrc] = network;

    *pssrc = ssrc;

    return srs_success;
}

srs_error_t SrsRtspConnection::do_play(SrsRtspRequest *req, SrsRtspConnection *conn)
{
    srs_error_t err = srs_success;

    srs_freep(player_);
    player_ = new SrsRtspPlayStream(conn, cid_);

    if ((err = player_->initialize(request_, tracks_)) != srs_success) {
        srs_freep(player_);
        return srs_error_wrap(err, "SrsRtspPlayStream init");
    }
    player_->set_all_tracks_status(true);
    if ((err = player_->start()) != srs_success) {
        return srs_error_wrap(err, "start play");
    }

    srs_trace("RTSP: Subscriber url=%s established", req->uri_.c_str());

    return err;
}

srs_error_t SrsRtspConnection::do_teardown()
{
    if (player_) {
        player_->stop();
        srs_freep(player_);
    }

    return srs_success;
}

srs_error_t SrsRtspConnection::http_hooks_on_play(ISrsRequest *req)
{
    srs_error_t err = srs_success;

    if (!config_->get_vhost_http_hooks_enabled(req->vhost_)) {
        return err;
    }

    // the http hooks will cause context switch,
    // so we must copy all hooks for the on_connect may freed.
    // @see https://github.com/ossrs/srs/issues/475
    std::vector<std::string> hooks;

    if (true) {
        SrsConfDirective *conf = config_->get_vhost_on_play(req->vhost_);

        if (!conf) {
            return err;
        }

        hooks = conf->args_;
    }

    for (int i = 0; i < (int)hooks.size(); i++) {
        std::string url = hooks.at(i);
        if ((err = hooks_->on_play(url, req)) != srs_success) {
            return srs_error_wrap(err, "on_play %s", url.c_str());
        }
    }

    return err;
}

srs_error_t SrsRtspConnection::get_ssrc_by_stream_id(uint32_t stream_id, uint32_t *ssrc)
{
    for (std::map<uint32_t, SrsRtcTrackDescription *>::iterator it = tracks_.begin(); it != tracks_.end(); ++it) {
        if (it->second->id_ == srs_strconv_format_int(stream_id)) {
            *ssrc = it->second->ssrc_;
            return srs_success;
        }
    }
    return srs_error_new(ERROR_RTSP_NO_TRACK, "track not found for stream_id: %u", stream_id);
}

SrsRtspTcpNetwork::SrsRtspTcpNetwork(ISrsProtocolReadWriter *skt, int ch) : skt_(skt), channel_(ch)
{
}

SrsRtspTcpNetwork::~SrsRtspTcpNetwork()
{
}

srs_error_t SrsRtspTcpNetwork::write(void *buf, size_t size, ssize_t *nwrite)
{
    srs_error_t err = srs_success;

    srs_assert(size <= 65535);

    // Encode and send 4 bytes size, in network order.
    const int kRtpTcpPacketHeaderSize = 4;
    char header[kRtpTcpPacketHeaderSize];

    // Use SrsBuffer to handle endianness properly
    SrsBuffer hb(header, kRtpTcpPacketHeaderSize);
    hb.write_1bytes(0x24);              // Magic byte '$'
    hb.write_1bytes(uint8_t(channel_)); // Channel number
    hb.write_2bytes(uint16_t(size));    // Packet size in network order

    if ((err = skt_->write(header, kRtpTcpPacketHeaderSize, NULL)) != srs_success) {
        return srs_error_wrap(err, "RTSP tcp write len(%d)", size);
    }

    if ((err = skt_->write(buf, size, nwrite)) != srs_success) {
        return srs_error_wrap(err, "RTSP send rtp packet");
    }

    // Add the size of the header to the write count.
    *nwrite += kRtpTcpPacketHeaderSize;

    return err;
}
