
/*
 * Copyright (C) Winshining
 */

#include <ngx_config.h>
#include <ngx_core.h>
#include <ngx_http.h>
#include "ngx_http_flv_live_module.h"
#include "ngx_rtmp_bandwidth.h"


static ngx_rtmp_play_pt         next_play;
static ngx_rtmp_close_stream_pt next_close_stream;


ngx_rtmp_play_pt                http_flv_live_next_play;
ngx_rtmp_close_stream_pt        http_flv_live_next_close_stream;


static ngx_int_t ngx_http_flv_live_init(ngx_conf_t *cf);
static void *ngx_http_flv_live_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_flv_live_merge_loc_conf(ngx_conf_t *cf,
    void *parent, void *child);


static ngx_int_t ngx_http_flv_live_handler(ngx_http_request_t *r);
static void ngx_http_flv_live_cleanup(void *data);
static ngx_int_t ngx_http_flv_live_init_process(ngx_cycle_t *cycle);

static void ngx_http_flv_live_send_tail(ngx_rtmp_session_t *s);
static ngx_int_t ngx_http_flv_live_send_message(ngx_rtmp_session_t *s,
    ngx_chain_t *out, ngx_uint_t priority);
static ngx_chain_t *ngx_http_flv_live_meta_message(ngx_rtmp_session_t *,
    ngx_chain_t *in);
static ngx_chain_t *ngx_http_flv_live_append_message(ngx_rtmp_session_t *s,
    ngx_rtmp_header_t *h, ngx_rtmp_header_t *lh, ngx_chain_t *in);
static void ngx_http_flv_live_free_message(ngx_rtmp_session_t *s,
    ngx_chain_t *in);
static ngx_int_t ngx_http_flv_live_join(ngx_rtmp_session_t *s, u_char *name,
    unsigned int publisher);
static ngx_chain_t *ngx_http_flv_live_append_shared_bufs(
    ngx_rtmp_core_srv_conf_t *cscf, ngx_rtmp_header_t *h, ngx_chain_t *in,
    ngx_flag_t chunked);

static void ngx_http_flv_live_close_http_request(ngx_rtmp_session_t *s);
static ngx_int_t ngx_http_flv_live_headers_filter(ngx_rtmp_session_t *s);
static ngx_int_t ngx_http_flv_live_header_filter(ngx_rtmp_session_t *s);

#if (nginx_version <= 1003014)
static void ngx_http_do_free_request(ngx_http_request_t *r, ngx_int_t rc);
static void ngx_http_do_log_request(ngx_http_request_t *r);
#endif


typedef struct ngx_http_header_val_s  ngx_http_header_val_t;

typedef ngx_int_t (*ngx_http_set_header_pt)(ngx_http_request_t *r,
    ngx_http_header_val_t *hv, ngx_str_t *value);


typedef struct {
    ngx_str_t                  name;
    ngx_uint_t                 offset;
    ngx_http_set_header_pt     handler;
} ngx_http_set_header_t;


struct ngx_http_header_val_s {
    ngx_http_complex_value_t   value;
    ngx_str_t                  key;
    ngx_http_set_header_pt     handler;
    ngx_uint_t                 offset;
#if (nginx_version >= 1007005)
    ngx_uint_t                 always;  /* unsigned  always:1 */
#endif
};


typedef enum {
    NGX_HTTP_EXPIRES_OFF,
} ngx_http_expires_t;


typedef struct {
    ngx_http_expires_t         expires;
    time_t                     expires_time;
#if (nginx_version >= 1007009)
    ngx_http_complex_value_t  *expires_value;
#endif
    ngx_array_t               *headers;
} ngx_http_headers_conf_t;


extern ngx_module_t    ngx_http_headers_filter_module;


static u_char ngx_http_server_string[] = "Server: nginx" CRLF;
static u_char ngx_http_server_full_string[] = "Server: " NGINX_VER CRLF;
#if (nginx_version >= 1011010)
static u_char ngx_http_server_build_string[] = "Server: " NGINX_VER_BUILD CRLF;
#endif


static ngx_str_t ngx_http_status_lines[] = {

    ngx_string("200 OK"),
    ngx_null_string,  /* "201 Created" */
    ngx_null_string,  /* "202 Accepted" */
    ngx_null_string,  /* "203 Non-Authoritative Information" */
    ngx_null_string,  /* "204 No Content" */
    ngx_null_string,  /* "205 Reset Content" */
    ngx_null_string,  /* "206 Partial Content" */

    /* ngx_null_string, */  /* "207 Multi-Status" */

#define NGX_HTTP_LAST_2XX  207
#define NGX_HTTP_OFF_3XX   (NGX_HTTP_LAST_2XX - 200)

    /* ngx_null_string, */  /* "300 Multiple Choices" */

    ngx_string("301 Moved Permanently"),
    ngx_string("302 Moved Temporarily"),
    ngx_null_string,  /* "303 See Other" */
    ngx_null_string,  /* "304 Not Modified" */
    ngx_null_string,  /* "305 Use Proxy" */
    ngx_null_string,  /* "306 unused" */
    ngx_string("307 Temporary Redirect"),

#define NGX_HTTP_LAST_3XX  308
#define NGX_HTTP_OFF_4XX   (NGX_HTTP_LAST_3XX - 301 + NGX_HTTP_OFF_3XX)

    ngx_string("400 Bad Request"),
    ngx_null_string,  /* "401 Unauthorized" */
    ngx_null_string,  /* "402 Payment Required" */
    ngx_string("403 Forbidden"),
    ngx_string("404 Not Found"),
    ngx_string("405 Not Allowed"),
    ngx_null_string,  /* "406 Not Acceptable" */
    ngx_null_string,  /* "407 Proxy Authentication Required" */
    ngx_null_string,  /* "408 Request Time-out" */
    ngx_null_string,  /* "409 Conflict" */
    ngx_null_string,  /* "410 Gone" */
    ngx_null_string,  /* "411 Length Required" */
    ngx_null_string,  /* "412 Precondition Failed" */
    ngx_null_string,  /* "413 Request Entity Too Large" */
    ngx_null_string,  /* "414 Request-URI Too Large" */
    ngx_null_string,  /* "415 Unsupported Media Type" */
    ngx_null_string,  /* "416 Requested Range Not Satisfiable" */
    ngx_null_string,  /* "417 Expectation Failed" */
    ngx_null_string,  /* "418 unused" */
    ngx_null_string,  /* "419 unused" */
    ngx_null_string,  /* "420 unused" */
    ngx_null_string,  /* "421 Misdirected Request" */

    /* ngx_null_string, */  /* "422 Unprocessable Entity" */
    /* ngx_null_string, */  /* "423 Locked" */
    /* ngx_null_string, */  /* "424 Failed Dependency" */

#define NGX_HTTP_LAST_4XX  422
#define NGX_HTTP_OFF_5XX   (NGX_HTTP_LAST_4XX - 400 + NGX_HTTP_OFF_4XX)

    ngx_string("500 Internal Server Error"),
    ngx_null_string,  /* "501 Not Implemented" */
    ngx_null_string,  /* "502 Bad Gateway" */
    ngx_string("503 Service Temporarily Unavailable"),
    ngx_null_string,  /* "504 Gateway Time-out" */
    ngx_null_string,        /* "505 HTTP Version Not Supported" */
    ngx_null_string,        /* "506 Variant Also Negotiates" */
    ngx_null_string,  /* "507 Insufficient Storage" */

    /* ngx_null_string, */  /* "508 unused" */
    /* ngx_null_string, */  /* "509 unused" */
    /* ngx_null_string, */  /* "510 Not Extended" */

#define NGX_HTTP_LAST_5XX  508

};


extern ngx_rtmp_live_proc_handler_t  ngx_rtmp_live_proc_handler;
static ngx_rtmp_live_proc_handler_t  ngx_http_flv_live_proc_handler = {
    NULL,
    NULL,
    NULL,
    NULL,
    ngx_http_flv_live_send_message,
    ngx_http_flv_live_meta_message,
    ngx_http_flv_live_append_message,
    ngx_http_flv_live_free_message
};

ngx_rtmp_live_proc_handler_t  *ngx_rtmp_live_proc_handlers[] = {
    &ngx_rtmp_live_proc_handler,
    &ngx_http_flv_live_proc_handler
};


static ngx_int_t ngx_http_flv_live_init_handlers(ngx_cycle_t *cycle);


static ngx_int_t ngx_http_flv_live_request(ngx_rtmp_session_t *s,
    ngx_rtmp_header_t *h, ngx_chain_t *in);

static void ngx_http_flv_live_free_request(ngx_rtmp_session_t *s);


static void ngx_http_flv_live_read_handler(ngx_event_t *rev);
static void ngx_http_flv_live_write_handler(ngx_event_t *wev);

static ngx_int_t ngx_http_flv_live_send(ngx_rtmp_session_t *s);
static void ngx_http_flv_live_correct_timestamp(ngx_rtmp_session_t *s,
    ngx_flag_t correct);

static ngx_int_t ngx_http_flv_live_preprocess(ngx_http_request_t *r,
    ngx_rtmp_connection_t *rconn);

static ngx_rtmp_session_t *ngx_http_flv_live_init_connection(
    ngx_http_request_t *r, ngx_rtmp_connection_t *rconn);
static ngx_rtmp_session_t *ngx_http_flv_live_init_session(
    ngx_http_request_t *r, ngx_rtmp_addr_conf_t *add_conf);
static ngx_int_t ngx_http_flv_live_connect_init(ngx_rtmp_session_t *s,
    ngx_str_t *app, ngx_str_t *stream);


static ngx_http_module_t ngx_http_flv_live_module_ctx = {
    NULL,
    ngx_http_flv_live_init,            /* postconfiguration */
    NULL,
    NULL,
    NULL,
    NULL,
    ngx_http_flv_live_create_loc_conf, /* create location configuration */
    ngx_http_flv_live_merge_loc_conf   /* merge location configuration */
};


static ngx_command_t ngx_http_flv_live_commands[] = {
    { ngx_string("flv_live"),
      NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
      ngx_conf_set_flag_slot,
      NGX_HTTP_LOC_CONF_OFFSET,
      offsetof(ngx_http_flv_live_conf_t, flv_live),
      NULL },

    ngx_null_command
};


ngx_module_t ngx_http_flv_live_module = {
    NGX_MODULE_V1,
    &ngx_http_flv_live_module_ctx,
    ngx_http_flv_live_commands,
    NGX_HTTP_MODULE,
    NULL,
    NULL,
    ngx_http_flv_live_init_process,
    NULL,
    NULL,
    NULL,
    NULL,
    NGX_MODULE_V1_PADDING
};


static ngx_int_t
ngx_http_flv_live_init(ngx_conf_t *cf)
{
    ngx_http_handler_pt       *h;
    ngx_http_core_main_conf_t *cmcf;

    cmcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_core_module);

    /* insert in the NGX_HTTP_CONTENT_PHASE */
    h = ngx_array_push(&cmcf->phases[NGX_HTTP_CONTENT_PHASE].handlers);
    if (h == NULL) {
        return NGX_ERROR;
    }

    *h = ngx_http_flv_live_handler;

    return NGX_OK;
}


static void *
ngx_http_flv_live_create_loc_conf(ngx_conf_t *cf)
{
    ngx_http_flv_live_conf_t  *conf;

    conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_flv_live_conf_t));
    if (conf == NULL) {
        return NULL;
    }

    conf->flv_live = NGX_CONF_UNSET;

    return (void *) conf;
}


static char *
ngx_http_flv_live_merge_loc_conf(ngx_conf_t *cf,
    void *parent, void *child)
{
    ngx_http_flv_live_conf_t *prev = parent;
    ngx_http_flv_live_conf_t *conf = child;

    ngx_conf_merge_value(conf->flv_live, prev->flv_live, 0);

    return NGX_CONF_OK;
}


ngx_int_t
ngx_http_flv_live_init_handlers(ngx_cycle_t *cycle)
{
    ngx_rtmp_core_main_conf_t *cmcf;
    ngx_rtmp_handler_pt       *h;

    cmcf = ngx_rtmp_cycle_get_module_main_conf(cycle, ngx_rtmp_core_module);
    if (cmcf == NULL) {
        return NGX_OK;
    }

    /* rtmp live conf aready exsits, so add additional event handlers */
    h = ngx_array_push(&cmcf->events[NGX_HTTP_FLV_LIVE_REQUEST]);
    *h = ngx_http_flv_live_request;

    next_play = http_flv_live_next_play;
    next_close_stream = http_flv_live_next_close_stream;

    http_flv_live_next_play = NULL;
    http_flv_live_next_close_stream = NULL;

    return NGX_OK;
}


static ngx_int_t
ngx_http_flv_live_init_process(ngx_cycle_t *cycle)
{
    return ngx_http_flv_live_init_handlers(cycle);
}


/*
 * chunk format:
 * hex1\r\n
 * content1(hex1)\r\n
 * hex2\r\n
 * content2(hex2)\r\n
 * ...
 * 0\r\n\r\n
 */
ngx_int_t
ngx_http_flv_live_send_header(ngx_rtmp_session_t *s)
{
    ngx_rtmp_core_srv_conf_t        *cscf;
    ngx_http_core_loc_conf_t        *clcf;
    ngx_http_request_t              *r;
    ngx_rtmp_live_ctx_t             *live_ctx;
    ngx_rtmp_codec_ctx_t            *codec_ctx;
    ngx_list_part_t                 *part;
    ngx_table_elt_t                 *e, *header;
    u_char                          *p;
    ngx_chain_t                      cl_flv_hdr, *pkt;
    ngx_buf_t                        buf_flv_hdr;
    ngx_uint_t                       i;
    ngx_str_t                        chunked_flv_header;
    ngx_str_t                        consec_flv_header;
    u_char                           chunked_flv_header_data[18];
    ngx_flag_t                       connection_header;

    /**
     * |F|L|V|ver|00000101|header_size|0|0|0|0|, ngx_http_flv_module.c
     * for more details, please refer to http://www.adobe.com/devnet/f4v.html
     **/
    u_char flv_header[] = "FLV\x1\0\0\0\0\x9\0\0\0\0";

    r = s->data;

    r->headers_out.status = NGX_HTTP_OK;

    ngx_str_set(&r->headers_out.content_type, "video/x-flv");

    /* fill HTTP header 'Connection' according to headers_in */
    r->keepalive = 0;

    connection_header = 0;
    part = &r->headers_in.headers.part;
    header = part->elts;

    for (i = 0; /* void */; i++) {
        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            header = part->elts;
            i = 0;
        }

        if (header[i].hash == 0) {
            continue;
        }

        if (ngx_strcasecmp(header[i].key.data, (u_char *) "connection") == 0) {
            connection_header = 1;
            if (ngx_strcasecmp(header[i].value.data, (u_char *) "keep-alive")
                == 0)
            {
                r->keepalive = 1;
            }

            break;
        }
    }

    if (!connection_header && r->http_version == NGX_HTTP_VERSION_11) {
        r->keepalive = 1;
    }

    live_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (live_ctx && !live_ctx->active) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                "flv live: try to send header when session not active");

        return NGX_ERROR;
    }

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    codec_ctx = ngx_rtmp_get_module_ctx(s->publisher, ngx_rtmp_codec_module);
    if (codec_ctx->video_codec_id != 0) {
        flv_header[4] |= 0x1;
    }

    if (codec_ctx->audio_codec_id != 0
        && codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_UNCOMPRESSED)
    {
        flv_header[4] |= (0x1 << 2);
    }

    if (clcf->chunked_transfer_encoding &&
        r->http_version == NGX_HTTP_VERSION_11)
    {
        r->chunked = 1;

        p = chunked_flv_header_data;
        *p++ = 'd';
        *p++ = CR;
        *p++ = LF;
        ngx_memmove(p, flv_header, 13);
        p += 13;
        *p++ = CR;
        *p++ = LF;
        chunked_flv_header.data = chunked_flv_header_data;
        chunked_flv_header.len = 18;

        buf_flv_hdr.pos = chunked_flv_header.data;
        buf_flv_hdr.last = chunked_flv_header.data + chunked_flv_header.len;
    } else {
        consec_flv_header.data = flv_header;
        consec_flv_header.len = 13;

        buf_flv_hdr.pos = consec_flv_header.data;
        buf_flv_hdr.last = consec_flv_header.data + consec_flv_header.len;
    }

    e = r->headers_out.expires;
    if (e == NULL) {

        e = ngx_list_push(&r->headers_out.headers);
        if (e == NULL) {
            return NGX_ERROR;
        }

        r->headers_out.expires = e;

        e->hash = 1;
        ngx_str_set(&e->key, "Expires");
    }

    e->value.data = (u_char *) "-1";
    e->value.len = ngx_strlen("-1");

    if (ngx_http_flv_live_headers_filter(s) == NGX_ERROR) {
        return NGX_ERROR;
    }

    buf_flv_hdr.start = buf_flv_hdr.pos;
    buf_flv_hdr.end = buf_flv_hdr.last;

    cl_flv_hdr.buf = &buf_flv_hdr;
    cl_flv_hdr.next = NULL;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    pkt = ngx_rtmp_append_shared_bufs(cscf, NULL, &cl_flv_hdr);
    if (pkt == NULL) {
        return NGX_ERROR;
    }

    ngx_http_flv_live_send_message(s, pkt, 0);
    ngx_rtmp_free_shared_chain(cscf, pkt);

    return NGX_OK;
}


/**
 * for adding non-standard HTTP headers 
 **/
static ngx_int_t
ngx_http_flv_live_headers_filter(ngx_rtmp_session_t *s)
{
    ngx_str_t                 value;
    ngx_uint_t                i;
#if (nginx_version >= 1007005)
    ngx_uint_t                safe_status;
#endif
    ngx_http_header_val_t    *h;
    ngx_http_headers_conf_t  *conf;
    ngx_http_request_t       *r;

    r = s->data;

    conf = ngx_http_get_module_loc_conf(r, ngx_http_headers_filter_module);

    /* force */
    conf->expires = NGX_HTTP_EXPIRES_OFF;

    if (conf->headers == NULL) {
        return ngx_http_flv_live_header_filter(s);
    }

#if (nginx_version >= 1007005)
    switch (r->headers_out.status) {

    case NGX_HTTP_OK:
    case NGX_HTTP_CREATED:
    case NGX_HTTP_NO_CONTENT:
    case NGX_HTTP_PARTIAL_CONTENT:
    case NGX_HTTP_MOVED_PERMANENTLY:
    case NGX_HTTP_MOVED_TEMPORARILY:
    case NGX_HTTP_SEE_OTHER:
    case NGX_HTTP_NOT_MODIFIED:
    case NGX_HTTP_TEMPORARY_REDIRECT:
        safe_status = 1;
        break;

    default:
        safe_status = 0;
    }
#endif

    if (conf->headers) {
        h = conf->headers->elts;
        for (i = 0; i < conf->headers->nelts; i++) {

#if (nginx_version >= 1007005)
            if (!safe_status && !h[i].always) {
                continue;
            }
#endif

            if (ngx_http_complex_value(r, &h[i].value, &value) != NGX_OK) {
                return NGX_ERROR;
            }

            if (h[i].handler(r, &h[i], &value) != NGX_OK) {
                return NGX_ERROR;
            }
        }
    }

    return ngx_http_flv_live_header_filter(s);
}


static ngx_int_t
ngx_http_flv_live_header_filter(ngx_rtmp_session_t *s)
{
    u_char                    *p;
    size_t                     len;
    ngx_str_t                  *status_line;
    ngx_buf_t                 *b;
    ngx_uint_t                 status, i;
    ngx_chain_t                out, *pkt;
    ngx_list_part_t           *part;
    ngx_table_elt_t           *header;
    ngx_http_core_loc_conf_t  *clcf;
    ngx_rtmp_core_srv_conf_t  *cscf;
    ngx_http_request_t        *r;

    r = s->data;

    if (r->header_sent) {
        return NGX_OK;
    }

    r->header_sent = 1;

    if (r->chunked && r->http_version < NGX_HTTP_VERSION_11) {
        ngx_log_error(NGX_LOG_WARN, s->connection->log, 0,
                      "flv live: chunked only supported by HTTP/1.1");

        r->chunked = 0;
    }

    len = sizeof("HTTP/1.x ") - 1 + sizeof(CRLF) - 1
          /* the end of the header */
          + sizeof(CRLF) - 1;

    /* status line */

    if (r->headers_out.status_line.len) {
        len += r->headers_out.status_line.len;
        status_line = &r->headers_out.status_line;
#if (NGX_SUPPRESS_WARN)
        status = 0;
#endif

    } else {

        status = r->headers_out.status;

        if (status >= NGX_HTTP_OK
            && status < NGX_HTTP_LAST_2XX)
        {
            /* 2XX */

            status -= NGX_HTTP_OK;
            status_line = &ngx_http_status_lines[status];
            len += ngx_http_status_lines[status].len;

        } else if (status >= NGX_HTTP_MOVED_PERMANENTLY
                   && status < NGX_HTTP_LAST_3XX)
        {
            /* 3XX */

            status = status - NGX_HTTP_MOVED_PERMANENTLY + NGX_HTTP_OFF_3XX;
            status_line = &ngx_http_status_lines[status];
            len += ngx_http_status_lines[status].len;

        } else if (status >= NGX_HTTP_BAD_REQUEST
                   && status < NGX_HTTP_LAST_4XX)
        {
            /* 4XX */
            status = status - NGX_HTTP_BAD_REQUEST
                            + NGX_HTTP_OFF_4XX;

            status_line = &ngx_http_status_lines[status];
            len += ngx_http_status_lines[status].len;

        } else if (status >= NGX_HTTP_INTERNAL_SERVER_ERROR
                   && status < NGX_HTTP_LAST_5XX)
        {
            /* 5XX */
            status = status - NGX_HTTP_INTERNAL_SERVER_ERROR
                            + NGX_HTTP_OFF_5XX;

            status_line = &ngx_http_status_lines[status];
            len += ngx_http_status_lines[status].len;

        } else {
            len += NGX_INT_T_LEN + 1 /* SP */;
            status_line = NULL;
        }

        if (status_line && status_line->len == 0) {
            status = r->headers_out.status;
            len += NGX_INT_T_LEN + 1 /* SP */;
            status_line = NULL;
        }
    }

    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

    if (r->headers_out.server == NULL) {
#if (nginx_version >= 1011010)
        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
            len += sizeof(ngx_http_server_full_string) - 1;

        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
            len += sizeof(ngx_http_server_build_string) - 1;

        } else {
            len += sizeof(ngx_http_server_string) - 1;
        }
#else
        len += clcf->server_tokens ? sizeof(ngx_http_server_full_string) - 1 :
                                     sizeof(ngx_http_server_string) - 1;
#endif
    }

    if (r->headers_out.date == NULL) {
        len += sizeof("Date: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
    }

    if (r->headers_out.content_type.len) {
        len += sizeof("Content-Type: ") - 1
               + r->headers_out.content_type.len + 2;

        if (r->headers_out.content_type_len == r->headers_out.content_type.len
            && r->headers_out.charset.len)
        {
            len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
        }
    }

    if (r->headers_out.content_length == NULL
        && r->headers_out.content_length_n >= 0)
    {
        len += sizeof("Content-Length: ") - 1 + NGX_OFF_T_LEN + 2;
    }

    if (r->headers_out.last_modified == NULL
        && r->headers_out.last_modified_time != -1)
    {
        len += sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT" CRLF) - 1;
    }

    if (r->chunked) {
        len += sizeof("Transfer-Encoding: chunked" CRLF) - 1;
    }

    if (r->keepalive) {
        len += sizeof("Connection: keep-alive" CRLF) - 1;

        /*
         * MSIE and Opera ignore the "Keep-Alive: timeout=<N>" header.
         * MSIE keeps the connection alive for about 60-65 seconds.
         * Opera keeps the connection alive very long.
         * Mozilla keeps the connection alive for N plus about 1-10 seconds.
         * Konqueror keeps the connection alive for about N seconds.
         */

        if (clcf->keepalive_header) {
            len += sizeof("Keep-Alive: timeout=") - 1 + NGX_TIME_T_LEN + 2;
        }

    } else {
        len += sizeof("Connection: close" CRLF) - 1;
    }

    part = &r->headers_out.headers.part;
    header = part->elts;

    for (i = 0; /* void */; i++) {

        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            header = part->elts;
            i = 0;
        }

        if (header[i].hash == 0) {
            continue;
        }

        len += header[i].key.len + sizeof(": ") - 1 + header[i].value.len
               + sizeof(CRLF) - 1;
    }

    b = ngx_create_temp_buf(r->pool, len);
    if (b == NULL) {
        return NGX_ERROR;
    }

    /* "HTTP/1.x " */
    if (r->http_version == NGX_HTTP_VERSION_10) {
        b->last = ngx_cpymem(b->last, "HTTP/1.0 ", sizeof("HTTP/1.x ") - 1);
    } else {
        b->last = ngx_cpymem(b->last, "HTTP/1.1 ", sizeof("HTTP/1.x ") - 1);
    }

    /* status line */
    if (status_line) {
        b->last = ngx_copy(b->last, status_line->data, status_line->len);

    } else {
        b->last = ngx_sprintf(b->last, "%03ui ", status);
    }
    *b->last++ = CR; *b->last++ = LF;

    if (r->headers_out.server == NULL) {
#if (nginx_version >= 1011010)
        if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) {
            p = ngx_http_server_full_string;
            len = sizeof(ngx_http_server_full_string) - 1;

        } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) {
            p = ngx_http_server_build_string;
            len = sizeof(ngx_http_server_build_string) - 1;

        } else {
#else
        if (clcf->server_tokens) {
            p = (u_char *) ngx_http_server_full_string;
            len = sizeof(ngx_http_server_full_string) - 1;

        } else {
#endif
            p = ngx_http_server_string;
            len = sizeof(ngx_http_server_string) - 1;
        }

        b->last = ngx_cpymem(b->last, p, len);
    }

    if (r->headers_out.date == NULL) {
        b->last = ngx_cpymem(b->last, "Date: ", sizeof("Date: ") - 1);
        b->last = ngx_cpymem(b->last, ngx_cached_http_time.data,
                             ngx_cached_http_time.len);

        *b->last++ = CR; *b->last++ = LF;
    }

    if (r->headers_out.content_type.len) {
        b->last = ngx_cpymem(b->last, "Content-Type: ",
                             sizeof("Content-Type: ") - 1);
        p = b->last;
        b->last = ngx_copy(b->last, r->headers_out.content_type.data,
                           r->headers_out.content_type.len);

        if (r->headers_out.content_type_len == r->headers_out.content_type.len
            && r->headers_out.charset.len)
        {
            b->last = ngx_cpymem(b->last, "; charset=",
                                 sizeof("; charset=") - 1);
            b->last = ngx_copy(b->last, r->headers_out.charset.data,
                               r->headers_out.charset.len);

            /* update r->headers_out.content_type for possible logging */

            r->headers_out.content_type.len = b->last - p;
            r->headers_out.content_type.data = p;
        }

        *b->last++ = CR; *b->last++ = LF;
    }

    if (r->chunked) {
        b->last = ngx_cpymem(b->last, "Transfer-Encoding: chunked" CRLF,
                             sizeof("Transfer-Encoding: chunked" CRLF) - 1);
    }

    if (r->keepalive) {
        b->last = ngx_cpymem(b->last, "Connection: keep-alive" CRLF,
                             sizeof("Connection: keep-alive" CRLF) - 1);

        if (clcf->keepalive_header) {
            b->last = ngx_sprintf(b->last, "Keep-Alive: timeout=%T" CRLF,
                                  clcf->keepalive_header);
        }

    } else {
        b->last = ngx_cpymem(b->last, "Connection: close" CRLF,
                             sizeof("Connection: close" CRLF) - 1);
    }

    part = &r->headers_out.headers.part;
    header = part->elts;

    for (i = 0; /* void */; i++) {

        if (i >= part->nelts) {
            if (part->next == NULL) {
                break;
            }

            part = part->next;
            header = part->elts;
            i = 0;
        }

        if (header[i].hash == 0) {
            continue;
        }

        b->last = ngx_copy(b->last, header[i].key.data, header[i].key.len);
        *b->last++ = ':'; *b->last++ = ' ';

        b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len);
        *b->last++ = CR; *b->last++ = LF;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                   "%*s", (size_t) (b->last - b->pos), b->pos);

    /* the end of HTTP header */
    *b->last++ = CR;
    *b->last++ = LF;

    r->header_size = b->last - b->pos;

    out.buf = b;
    out.next = NULL;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    pkt = ngx_rtmp_append_shared_bufs(cscf, NULL, &out);
    if (pkt == NULL) {
        return NGX_ERROR;
    }

    ngx_http_flv_live_send_message(s, pkt, 0);
    ngx_rtmp_free_shared_chain(cscf, pkt);

    return NGX_OK;
}


static void
ngx_http_flv_live_send_tail(ngx_rtmp_session_t *s)
{
    ngx_rtmp_core_srv_conf_t *cscf;
    ngx_chain_t               cl_resp_hdr, *pkt;
    ngx_buf_t                 buf_resp_hdr;

    const ngx_str_t response_tail = ngx_string("0" CRLF CRLF);

    buf_resp_hdr.pos = response_tail.data;
    buf_resp_hdr.last = response_tail.data + response_tail.len;
    buf_resp_hdr.start = buf_resp_hdr.pos;
    buf_resp_hdr.end = buf_resp_hdr.last;

    cl_resp_hdr.buf = &buf_resp_hdr;
    cl_resp_hdr.next = NULL;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    pkt = ngx_rtmp_append_shared_bufs(cscf, NULL, &cl_resp_hdr);
    ngx_http_flv_live_send_message(s, pkt, 0);
    ngx_rtmp_free_shared_chain(cscf, pkt);
}


static ngx_int_t
ngx_http_flv_live_send_message(ngx_rtmp_session_t *s,
    ngx_chain_t *out, ngx_uint_t priority)
{
    ngx_uint_t                      nmsg;

    nmsg = (s->out_last + s->out_queue - s->out_pos) % s->out_queue + 1;

    if (priority > 3) {
        priority = 3;
    }

    /* drop packet?
     * Note we always leave 1 slot free */
    if (nmsg + priority * s->out_queue / 4 >= s->out_queue) {
        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "flv live: HTTP drop message bufs=%ui, priority=%ui",
                nmsg, priority);

        return NGX_AGAIN;
    }

    s->out[s->out_last++] = out;
    s->out_last %= s->out_queue;

    ngx_rtmp_acquire_shared_chain(out);

    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "flv live: HTTP send nmsg=%ui, priority=%ui #%ui",
            nmsg, priority, s->out_last);

    if (priority && s->out_buffer && nmsg < s->out_cork) {
        return NGX_OK;
    }

    if (!s->connection->write->active) {
        ngx_http_flv_live_write_handler(s->connection->write);
    }

    return NGX_OK;
}


static ngx_int_t
ngx_http_flv_live_request(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
    ngx_chain_t *in)
{
    static ngx_rtmp_play_t       v;

    ngx_int_t                    rc;
    ngx_http_request_t          *r;
    ngx_http_flv_live_ctx_t     *ctx;

    r = s->data;
    ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);

    rc = ngx_http_flv_live_connect_init(s, &ctx->app, &ctx->stream);
    if (rc != NGX_OK) {
        return NGX_ERROR;
    }

    if (s->notify_connect) {
        return NGX_OK;
    }

    ngx_memzero(&v, sizeof(ngx_rtmp_play_t));

    ngx_memcpy(v.name, ctx->stream.data, ngx_min(ctx->stream.len,
            sizeof(v.name) - 1));
    ngx_memcpy(v.args, s->args.data, ngx_min(s->args.len,
            sizeof(v.args) - 1));

    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
           "flv live: name='%s' args='%s' start=%i duration=%i "
           "reset=%i silent=%i",
           v.name, v.args, (ngx_int_t) v.start,
           (ngx_int_t) v.duration, (ngx_int_t) v.reset,
           (ngx_int_t) v.silent);

    return ngx_rtmp_play(s, &v);
}


/* +--------------+                              +-------------+
 * |   Client     |              |               |    Server   |
 * +------+-------+              |               +------+------+
 *        |               Handshaking done              |
 *        |                      |                      |
 *        |                      |                      |
 *        |----------- Command Message(connect) ------->|
 *        |                                             |
 *        |<------- Window Acknowledgement Size --------|
 *        |                                             |
 *        |<----------- Set Peer Bandwidth -------------|
 *        |                                             |
 *        |-------- Window Acknowledgement Size ------->|
 *        |                                             |
 *        |<------ User Control Message(StreamBegin) ---|
 *        |                                             |
 *        |<------------ Command Message ---------------|
 *        |        (_result- connect response)          |
 *
 * omit the user control message feedback
 */
void
ngx_http_flv_live_set_status(ngx_rtmp_session_t *s, unsigned active)
{
    ngx_rtmp_live_ctx_t        *ctx;

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);

    ctx->active = active;

    ctx->cs[0].active = 0;
    ctx->cs[0].dropped = 0;

    ctx->cs[1].active = 0;
    ctx->cs[1].dropped = 0;
}


static ngx_int_t
ngx_http_flv_live_join(ngx_rtmp_session_t *s, u_char *name,
    unsigned int publisher)
{
    ngx_rtmp_live_ctx_t            *ctx;
    ngx_rtmp_live_stream_t        **stream;
    ngx_rtmp_live_app_conf_t       *lacf;

    /* only for subscribers */
    if (publisher) {
        return NGX_DECLINED;
    }

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        return NGX_DECLINED;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (ctx && ctx->stream) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "flv live: already joined");

        return NGX_DECLINED;
    }

    if (ctx == NULL) {
        ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_live_ctx_t));
        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_live_module);
    }

    ngx_memzero(ctx, sizeof(*ctx));

    ctx->session = s;

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "flv live: join '%s'", name);

    stream = ngx_rtmp_live_get_stream(s, name, lacf->idle_streams);

    if (stream == NULL ||
        !(publisher || (*stream)->publishing || lacf->idle_streams))
    {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                "flv live: stream not found");

        return NGX_ERROR;
    }

    ctx->stream = *stream;
    ctx->publishing = publisher;
    ctx->next = (*stream)->ctx;
    ctx->protocol = NGX_RTMP_PROTOCOL_HTTP;

    (*stream)->ctx = ctx;

    if (ctx->stream->pub_ctx) {
        s->publisher = ctx->stream->pub_ctx->session;
    }

    if (lacf->buflen) {
        s->out_buffer = 1;
    }

    ctx->cs[0].csid = NGX_RTMP_CSID_VIDEO;
    ctx->cs[1].csid = NGX_RTMP_CSID_AUDIO;

    if (!ctx->publishing && ctx->stream->active) {
        ngx_http_flv_live_set_status(s, 1);
    }

    return NGX_OK;
}


ngx_int_t
ngx_http_flv_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)
{
    ngx_rtmp_live_app_conf_t        *lacf;
    ngx_http_request_t              *r;

    r = s->data;
    if (r == NULL) {
        goto next;
    }

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL || !lacf->live) {
        goto next;
    }

#if (nginx_version >= 1013001)
        /**
         * when playing from pull, the downstream requests on the most
         * of time return before the upstream requests, flv.js always
         * sends HTTP header 'Connection: keep-alive', but Nginx has
         * deleted r->blocked in ngx_http_finalize_request, that causes
         * ngx_http_set_keepalive to run the cleanup handlers to close
         * the connection between downstream and server, so play fails
         **/
        r->keepalive = 0;
#endif

    /* join stream as a subscriber */

    if (ngx_http_flv_live_join(s, v->name, 0) == NGX_ERROR) {
        return NGX_ERROR;
    }

    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "flv live play: name='%s' start=%uD duration=%uD reset=%d",
            v->name, (uint32_t) v->start,
            (uint32_t) v->duration, (uint32_t) v->reset);

next:
    return next_play(s, v);
}


static void
ngx_http_flv_live_close_http_request(ngx_rtmp_session_t *s)
{
    ngx_http_request_t         *r;

    r = s->data;
    if (r && r->connection && !r->connection->destroyed) {
        if (r->chunked) {
            ngx_http_flv_live_send_tail(s);
        }
    }
}


ngx_int_t
ngx_http_flv_live_close_stream(ngx_rtmp_session_t *s,
    ngx_rtmp_close_stream_t *v)
{
    ngx_rtmp_live_ctx_t        *ctx, **cctx, *unlink;
    ngx_http_request_t         *r;
    ngx_rtmp_live_app_conf_t   *lacf;
    ngx_rtmp_live_stream_t    **stream;
    ngx_flag_t                  passive;

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        goto next;
    }

    passive = 0;

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (ctx == NULL) {
        goto next;
    }

    if (ctx->stream == NULL) {
        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "flv live: not joined");

        goto next;
    }

    if (ctx->protocol == NGX_RTMP_PROTOCOL_RTMP) {
        /* close RTMP live play */
        if (!ctx->publishing) {
            goto next;
        }

        /* close all http flv live streams */
        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                "flv live: push closed '%s', close live streams subscribed",
                        ctx->stream->name);

        passive = 1;
    }

    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "flv live: leave '%s'", ctx->stream->name);

    if (passive) {
        for (cctx = &ctx->stream->ctx; *cctx; /* void */) {
            if ((*cctx)->protocol == NGX_RTMP_PROTOCOL_HTTP &&
                !lacf->idle_streams)
            {
                ngx_http_flv_live_close_http_request((*cctx)->session);

                if (!(*cctx)->publishing && (*cctx)->stream->active) {
                    ngx_http_flv_live_set_status((*cctx)->session, 0);
                }

                ngx_http_flv_live_free_request((*cctx)->session);
                ngx_rtmp_finalize_session((*cctx)->session);

                unlink = *cctx;

                *cctx = (*cctx)->next;

                unlink->next = NULL;
            } else {
                cctx = &(*cctx)->next;
            }
        }
    } else {
        for (cctx = &ctx->stream->ctx; *cctx; /* void */) {
            if (*cctx == ctx) {
                if (!ctx->publishing && ctx->stream->active) {
                    ngx_http_flv_live_set_status(s, 0);
                }

                *cctx = ctx->next;

                if (ctx->stream->pub_ctx == NULL &&
                    ctx->stream->ctx == NULL)
                {
                    stream = ngx_rtmp_live_get_stream(s, ctx->stream->name, 0);
                    if (stream) {
                        *stream = (*stream)->next;

                        ctx->stream->next = lacf->free_streams;
                        lacf->free_streams = ctx->stream;
                    }
                }

                ctx->next = NULL;
                ctx->stream = NULL;

                ngx_http_flv_live_free_request(s);
                s->connection->destroyed = 1;

                break;
            } else {
                cctx = &(*cctx)->next;
            }
        }
    }

    /**
     * close only http requests here, the other
     * requests were left for next_close_stream
     **/

next:
    if (s->notify_connect || s->notify_play) {
        r = s->data;
        if (r) {
            ngx_http_flv_live_free_request(s);
            s->connection->destroyed = 1;
        }
    }

    return next_close_stream(s, v);
}


static void
ngx_http_flv_live_free_request(ngx_rtmp_session_t *s)
{
    ngx_connection_t               *c;
    ngx_http_request_t             *r;
    ngx_http_cleanup_t            **cln;

    r = s->data;
    if (r) {
        for (cln = &r->cleanup; *cln; /* void */) {
            if ((*cln)->handler == ngx_http_flv_live_cleanup) {
                *cln = (*cln)->next;
                break;
            }

            cln = &(*cln)->next;
        }

        c = r->connection;

#if (nginx_version <= 1003014)
        ngx_http_do_free_request(r, 0);
#else
        ngx_http_free_request(r, 0);
#endif

#if (NGX_HTTP_SSL)
        if (c->ssl) {
            ngx_ssl_shutdown(c);
        }
#endif

        /* for later processing */
        c->destroyed = 0;
    }
}


#if (nginx_version <= 1003014)
static void
ngx_http_do_free_request(ngx_http_request_t *r, ngx_int_t rc)
{
    ngx_log_t                 *log;
    ngx_pool_t                *pool;
    struct linger              linger;
    ngx_http_cleanup_t        *cln;
    ngx_http_log_ctx_t        *ctx;
    ngx_http_core_loc_conf_t  *clcf;

    log = r->connection->log;

    ngx_log_debug0(NGX_LOG_DEBUG_HTTP, log, 0, "http close request");

    if (r->pool == NULL) {
        ngx_log_error(NGX_LOG_ALERT, log, 0, "http request already closed");
        return;
    }

    cln = r->cleanup;
    r->cleanup = NULL;

    while (cln) {
        if (cln->handler) {
            cln->handler(cln->data);
        }

        cln = cln->next;
    }

#if (NGX_STAT_STUB)

    if (r->stat_reading) {
        (void) ngx_atomic_fetch_add(ngx_stat_reading, -1);
    }

    if (r->stat_writing) {
        (void) ngx_atomic_fetch_add(ngx_stat_writing, -1);
    }

#endif

    if (rc > 0 && (r->headers_out.status == 0 || r->connection->sent == 0)) {
        r->headers_out.status = rc;
    }

    log->action = "logging request";

    ngx_http_do_log_request(r);

    log->action = "closing request";

    if (r->connection->timedout) {
        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);

        if (clcf->reset_timedout_connection) {
            linger.l_onoff = 1;
            linger.l_linger = 0;
 
            if (setsockopt(r->connection->fd, SOL_SOCKET, SO_LINGER,
                           (const void *) &linger, sizeof(struct linger)) == -1)
            {
                ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno,
                              "setsockopt(SO_LINGER) failed");
            }
        }
    }

    /* the various request strings were allocated from r->pool */
    ctx = log->data;
    ctx->request = NULL;

    r->request_line.len = 0;

    r->connection->destroyed = 1;

    /*
     * Setting r->pool to NULL will increase probability to catch double close
     * of request since the request object is allocated from its own pool.
     */

    pool = r->pool;
    r->pool = NULL;

    ngx_destroy_pool(pool);
}


static void
ngx_http_do_log_request(ngx_http_request_t *r)
{
    ngx_uint_t                  i, n;
    ngx_http_handler_pt        *log_handler;
    ngx_http_core_main_conf_t  *cmcf;

    cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module);

    log_handler = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.elts;
    n = cmcf->phases[NGX_HTTP_LOG_PHASE].handlers.nelts;

    for (i = 0; i < n; i++) {
        log_handler[i](r);
    }
}
#endif


static void
ngx_http_flv_live_read_handler(ngx_event_t *rev)
{
    ngx_connection_t           *c;
    ngx_http_request_t         *r;
    ngx_rtmp_session_t         *s;
    ngx_int_t                   n;
    ngx_http_flv_live_ctx_t    *ctx;
    u_char                      buf[NGX_BUFF_MAX_SIZE];

    c = rev->data;
    if (c->destroyed) {
        return;
    }

    r = c->data;
    ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);
    s = ctx->s;

    for ( ;; ) {
        n = c->recv(c, buf, sizeof(buf));

        if (n == NGX_AGAIN) {
            ngx_add_timer(c->read, s->timeout);

            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
                ngx_rtmp_finalize_session(s);
            }

            break;
        } else if (n == 0 || n == -1) {
            ngx_rtmp_finalize_session(s);
            break;
        }
    }
}


static void
ngx_http_flv_live_write_handler(ngx_event_t *wev)
{
    ngx_connection_t           *c;
    ngx_http_request_t         *r;
    ngx_rtmp_session_t         *s;
    ngx_int_t                   n;
    ngx_rtmp_live_ctx_t        *lctx;
    ngx_rtmp_core_srv_conf_t   *cscf;
    ngx_http_flv_live_ctx_t    *ctx;

    c = wev->data;
    if (c->destroyed) {
        return;
    }

    r = c->data;
    ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);
    s = ctx->s;

    if (wev->timedout) {
        ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT,
                "flv live: client timed out");
        c->timedout = 1;
        ngx_rtmp_finalize_session(s);
        return;
    }

    if (wev->timer_set) {
        ngx_del_timer(wev);
    }

    if (s->out_chain == NULL && s->out_pos != s->out_last) {
        s->out_chain = s->out[s->out_pos];
        s->out_bpos = s->out_chain->buf->pos;
    }

    while (s->out_chain) {
        n = ngx_http_flv_live_send(s);

        if (n == NGX_AGAIN || n == 0) {
            ngx_add_timer(c->write, s->timeout);
            if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
                ngx_rtmp_finalize_session(s);
            }
            return;
        }

        if (n < 0) {
            ngx_rtmp_finalize_session(s);
            return;
        }

        s->out_bytes += n;
        s->ping_reset = 1;
        ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, n);
        s->out_bpos += n;

        if (s->out_bpos == s->out_chain->buf->last) {
            s->out_chain = s->out_chain->next;
            if (s->out_chain == NULL) {
                cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
                ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos]);
                s->out[s->out_pos] = NULL;
                ++s->out_pos;
                s->out_pos %= s->out_queue;
                if (s->out_pos == s->out_last) {
                    break;
                }
                s->out_chain = s->out[s->out_pos];
            }
            s->out_bpos = s->out_chain->buf->pos;
        }
    }

    lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);
    if (lctx && !lctx->publishing && !wev->timer_set) {
        ngx_add_timer(wev, s->timeout);
    }

    if (wev->active) {
        ngx_del_event(wev, NGX_WRITE_EVENT, 0);
    }

    ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events);
}


static ngx_int_t
ngx_http_flv_live_send(ngx_rtmp_session_t *s)
{
    ngx_int_t            n;
    ngx_connection_t    *c;
    ngx_http_request_t  *r;

    c = s->connection;
    r = s->data;

    if (r->chunked) {
        if (s->out_chain == s->out[s->out_pos]) {
            n = c->send(c, s->out_bpos, s->out_chain->buf->last - s->out_bpos);
            if (n == NGX_AGAIN || n == 0 || n < 0) {
                return n;
            }

            if (n != s->out_chain->buf->last - s->out_bpos ||
                s->out_chain->next == NULL)
            {
                return n;
            }

            s->out_chain = s->out_chain->next;

            s->out_bytes += n;
            s->out_bpos = s->out_chain->buf->pos;
            s->ping_reset = 1;
            ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, n);
        }
    }

    ngx_http_flv_live_correct_timestamp(s, 1);
    n = c->send(c, s->out_bpos, s->out_chain->buf->last - s->out_bpos);
    ngx_http_flv_live_correct_timestamp(s, 0);

    return n;
}


static void
ngx_http_flv_live_correct_timestamp(ngx_rtmp_session_t *s, ngx_flag_t correct)
{
    uint8_t       type;
    uint32_t      timestamp;
    u_char       *p;
    ngx_chain_t  *cl;
    ngx_buf_t    *b;

    cl = s->out_chain;
    if (cl == NULL) {
        return;
    }

    b = cl->buf;
    if (b->start + NGX_RTMP_MAX_CHUNK_HEADER != b->pos) {
        type = b->pos[0] & 0x1f;

        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
                       "flv live: type=%uD, correct=%uD, offset_timestamp=%uD",
                       type, correct, s->offset_timestamp);

        if (type != NGX_RTMP_MSG_VIDEO && type != NGX_RTMP_MSG_AUDIO) {
            return;
        }

        p = b->pos + 4;
        timestamp = ngx_rtmp_n3_to_h4(p);
        timestamp |= ((uint32_t) p[3] << 24);

        if (correct) {
            timestamp -= s->offset_timestamp;
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
                           "flv live: correct timestamp=%uD", timestamp);
        } else {
            timestamp += s->offset_timestamp;
            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
                           "flv live: recover timestamp=%uD", timestamp);
        }

        p = b->pos + 4;
        ngx_rtmp_h4_to_n3(p, timestamp);
        p += 3;
        *p++ = (u_char) (timestamp >> 24);
    }
}


static ngx_int_t
ngx_http_flv_live_preprocess(ngx_http_request_t *r,
    ngx_rtmp_connection_t *rconn)
{
    ngx_http_flv_live_ctx_t     *ctx;
    ngx_listening_t             *ls;
    struct sockaddr             *local_sockaddr;

    struct sockaddr_in          *ls_sin, *sin;
#if (NGX_HAVE_INET6)
    struct sockaddr_in6         *ls_sin6, *sin6;
#endif

    ngx_rtmp_in_addr_t          *addr;
#if (NGX_HAVE_INET6)
    ngx_rtmp_in6_addr_t         *addr6;
#endif

    ngx_rtmp_port_t             *rport;

    ngx_str_t                    arg_app = ngx_string("app");
    ngx_str_t                    arg_stream = ngx_string("stream");
    ngx_str_t                    arg_port = ngx_string("port");
    ngx_int_t                    in_port;
    ngx_uint_t                   i, n;
    ngx_flag_t                   port_match, addr_match;
    unsigned short               sa_family;

    ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);

    /**
     * if requested args are escaped, for example, urls in the
     * history list of vlc for Android (or all mobile platforms)
     **/
    if (r->args.len == 0 && r->uri.len) {
        ngx_http_split_args(r, &r->uri, &r->args);
    }

    if (ngx_http_arg(r, arg_port.data, arg_port.len, &ctx->port) != NGX_OK) {
        /* no port in args */
        ctx->port.len = ngx_strlen("1935");
        ctx->port.data = ngx_pcalloc(r->pool, ctx->port.len + 1);
        if (ctx->port.data == NULL) {
            return NGX_ERROR;
        }

        ngx_memcpy(ctx->port.data, (const void *) "1935", ctx->port.len);
        in_port = 1935;
    } else {
        in_port = ngx_atoi(ctx->port.data, ctx->port.len);
        if (in_port == NGX_ERROR || (in_port < 0 || in_port > 65535)) {
            return NGX_ERROR;
        }
    }

    in_port = htons(in_port);

    port_match = 1;
    addr_match = 1;

    ls = ngx_cycle->listening.elts;
    for (n = 0; n < ngx_cycle->listening.nelts; ++n, ++ls) {
        if (ls->handler == ngx_rtmp_init_connection) {
            local_sockaddr = r->connection->local_sockaddr;
            sa_family = local_sockaddr->sa_family;

            if (sa_family != ls->sockaddr->sa_family) {
#if (NGX_HAVE_INET6)
                if (ls->sockaddr->sa_family == AF_INET6) {
                    if (ls->ipv6only) {
#endif
                        continue;
#if (NGX_HAVE_INET6)
                    } else {
                        if (local_sockaddr->sa_family != AF_INET) {
                            continue;
                        }

                        sa_family = AF_INET6;
                    }
                }
#endif
            }

            switch (sa_family) {

#if (NGX_HAVE_INET6)
            case AF_INET6:
                ls_sin6 = (struct sockaddr_in6 *) ls->sockaddr;
                if (in_port != ls_sin6->sin6_port) {
                    port_match = 0;
                }

                break;
#endif

            default:
                ls_sin = (struct sockaddr_in *) ls->sockaddr;
                if (in_port != ls_sin->sin_port) {
                    port_match = 0;
                }
            }

            if (!port_match) {
                port_match = 1;
                continue;
            }

            rport = ls->servers;

            if (rport->naddrs > 1) {
                /**
                 * listen xxx.xxx.xxx.xxx:port
                 * listen port
                 **/
                switch (sa_family) {

#if (NGX_HAVE_INET6)
                case AF_INET6:
                    sin6 = (struct sockaddr_in6 *) ls->sockaddr;

                    addr6 = rport->addrs;

                    /* the last address is "*" */

                    for (i = 0; i < rport->naddrs - 1; i++) {
                        if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16)
                            == 0)
                        {
                            break;
                        }
                    }

                    rconn->addr_conf = &addr6[i].conf;

                    break;
#endif

                default:
                    sin = (struct sockaddr_in *) ls->sockaddr;

                    addr = rport->addrs;

                    /* the last address is "*" */

                    for (i = 0; i < rport->naddrs - 1; i++) {
                        if (addr[i].addr == sin->sin_addr.s_addr) {
                            break;
                        }
                    }

                    rconn->addr_conf = &addr[i].conf;
                }
            } else {
                switch (sa_family) {

#if (NGX_HAVE_INET6)
                case AF_INET6:
                    sin6 = (struct sockaddr_in6 *) ls->sockaddr;

                    addr6 = rport->addrs;
                    if (ngx_memcmp(&addr6[0].addr6, &sin6->sin6_addr, 16)) {
                        addr_match = 0;
                    } else {
                        rconn->addr_conf = &addr6[0].conf;
                    }

                    break;
#endif

                default:
                    sin = (struct sockaddr_in *) ls->sockaddr;

                    addr = rport->addrs;
                    if (addr[0].addr != sin->sin_addr.s_addr) {
                        addr_match = 0;
                    } else {
                        rconn->addr_conf = &addr[0].conf;
                    }
                }
            }

            if (!addr_match) {
                addr_match = 1;
                continue;
            }

            break;
        }
    }

    if (n == ngx_cycle->listening.nelts) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                "flv live: failed to find configured port: '%V'", &ctx->port);

        return NGX_ERROR;
    }

    if (ngx_http_arg(r, arg_app.data, arg_app.len, &ctx->app) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "flv live: app args MUST be specified");

        return NGX_ERROR;
    }

    if (ngx_http_arg(r, arg_stream.data, arg_stream.len,
                     &ctx->stream) != NGX_OK)
    {
        ctx->stream.data = (u_char *) "";
        ctx->stream.len = 0;
    }

    return NGX_OK;
}


ngx_rtmp_session_t *
ngx_http_flv_live_init_connection(ngx_http_request_t *r,
    ngx_rtmp_connection_t *rconn)
{
    ngx_rtmp_session_t        *s;
    ngx_connection_t          *c;
    void                      *data;

    c = r->connection;

    /* the default server configuration for the address:port */
    rconn->conf_ctx = rconn->addr_conf->default_server->ctx;

    ++ngx_rtmp_naccepted;

    data = c->data;
    c->data = rconn;

    ngx_log_error(NGX_LOG_INFO, c->log, 0,
            "flv live: *%ui client connected '%V'", c->number, &c->addr_text);

    s = ngx_http_flv_live_init_session(r, rconn->addr_conf);
    c->data = data;

    if (s == NULL) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                "flv live: failed to init connection for session");

        return NULL;
    }

    /* only auto-pushed connections are
     * done through unix socket */

    s->auto_pushed = 0;

    c->write->handler = ngx_http_flv_live_write_handler;
    c->read->handler = ngx_http_flv_live_read_handler;

    if (c->write->active) {
        if (ngx_del_event(c->write, NGX_WRITE_EVENT, 0) != NGX_OK) {
            return NULL;
        }
    }

    return s;
}


static ngx_rtmp_session_t *
ngx_http_flv_live_init_session(ngx_http_request_t *r,
    ngx_rtmp_addr_conf_t *addr_conf)
{
    ngx_rtmp_session_t             *s;
    ngx_rtmp_core_srv_conf_t       *cscf;
    ngx_rtmp_error_log_ctx_t       *ctx;
    ngx_connection_t               *c;

    c = r->connection;

    s = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_session_t));
    if (s == NULL) {
        /* let other handlers process */
        goto failed;
    }

    s->rtmp_connection = c->data;

    s->main_conf = addr_conf->default_server->ctx->main_conf;
    s->srv_conf = addr_conf->default_server->ctx->srv_conf;

    s->addr_text = &addr_conf->addr_text;

    s->connection = c;

    ctx = ngx_palloc(c->pool, sizeof(ngx_rtmp_error_log_ctx_t));
    if (ctx == NULL) {
        goto failed;
    }

    ctx->client = &c->addr_text;
    ctx->session = s;

    c->log->connection = c->number;
    c->log->handler = ngx_rtmp_log_error;
    c->log->data = ctx;
    c->log->action = NULL;

    c->log_error = NGX_ERROR_INFO;

    s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_rtmp_max_module);
    if (s->ctx == NULL) {
        goto failed;
    }

    s->out_pool = ngx_create_pool(4096, c->log);
    if (s->out_pool == NULL) {
        goto failed;
    }

    s->out = ngx_pcalloc(s->out_pool, sizeof(ngx_chain_t *)
                         * ((ngx_rtmp_core_srv_conf_t *)
                            addr_conf->default_server->ctx->srv_conf
                            [ngx_rtmp_core_module.ctx_index])->out_queue);
    if (s->out == NULL) {
        goto failed;
    }

    s->in_streams_pool = ngx_create_pool(4096, c->log);
    if (s->in_streams_pool == NULL) {
        goto failed;
    }

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    s->out_queue = cscf->out_queue;
    s->out_cork = cscf->out_cork;
    s->in_streams = ngx_pcalloc(s->in_streams_pool, sizeof(ngx_rtmp_stream_t)
            * cscf->max_streams);
    if (s->in_streams == NULL) {
        goto failed;
    }

#if (nginx_version >= 1007005)
    ngx_queue_init(&s->posted_dry_events);
#endif

    s->epoch = ngx_current_msec;
    s->timeout = cscf->timeout;
    s->buflen = cscf->buflen;
    ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE);

    if (ngx_rtmp_fire_event(s, NGX_RTMP_CONNECT, NULL, NULL) != NGX_OK) {
        goto failed;
    }

    s->data = (void *) r;

    return s;

failed:
    if (s && s->out_pool) {
        ngx_destroy_pool(s->out_pool);
        s->out_pool = NULL;
    }

    if (s && s->in_streams_pool) {
        ngx_destroy_pool(s->in_streams_pool);
        s->in_streams_pool = NULL;
    }

    return NULL;
}


static ngx_int_t
ngx_http_flv_live_connect_init(ngx_rtmp_session_t *s, ngx_str_t *app,
    ngx_str_t *stream)
{
    ngx_rtmp_connect_t           v;
    ngx_connection_t            *c;
    ngx_http_request_t          *r;
    u_char                       name[NGX_RTMP_MAX_NAME];

    r = s->data;
    c = s->connection;

    ngx_memzero(&v, sizeof(ngx_rtmp_connect_t));

    ngx_memcpy(v.app, app->data, ngx_min(app->len, sizeof(v.app) - 1));
    ngx_memcpy(v.args, r->args.data, ngx_min(r->args.len, sizeof(v.args) - 1));
    ngx_memcpy(v.flashver, "flv_live 1.1", ngx_strlen("flv_live 1.1"));

    *ngx_snprintf(v.tc_url, NGX_RTMP_MAX_URL, "http://%V/%V",
            &r->headers_in.host->value, app) = 0;

#define NGX_RTMP_SET_STRPAR(name)                                          \
    s->name.len = ngx_strlen(v.name);                                      \
    s->name.data = ngx_palloc(c->pool, s->name.len);                       \
    ngx_memcpy(s->name.data, v.name, s->name.len)

    NGX_RTMP_SET_STRPAR(app);
    NGX_RTMP_SET_STRPAR(args);
    NGX_RTMP_SET_STRPAR(flashver);
    NGX_RTMP_SET_STRPAR(tc_url);

#undef NGX_RTMP_SET_STRPAR

    ngx_memzero(name, NGX_RTMP_MAX_NAME);
    ngx_memcpy(name, stream->data,
               ngx_min(stream->len, NGX_RTMP_MAX_NAME - 1));

    if (ngx_rtmp_process_request_line(s, name, v.args,
            (const u_char *) "flv live connect") != NGX_OK)
    {
        return NGX_ERROR;
    }

    if (ngx_rtmp_process_virtual_host(s) != NGX_OK) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv live: failed to process virtual host");

        return NGX_ERROR;
    }

    s->stream.len = stream->len;
    s->stream.data = ngx_pstrdup(c->pool, stream);

    return ngx_rtmp_connect(s, &v);
}


static ngx_chain_t *
ngx_http_flv_live_meta_message(ngx_rtmp_session_t *s, ngx_chain_t *in)
{
    ngx_rtmp_core_srv_conf_t        *cscf;
    ngx_http_request_t              *r;
    ngx_chain_t                     *meta, *iter, *out;
    u_char                          *p, *save;
    uint8_t                          fmt;
    uint32_t                         csid;
    ngx_int_t                        thsize;
    ngx_rtmp_header_t                ch;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    if (cscf == NULL) {
        return NULL;
    }

    r = s->data;
    if (r == NULL || (r->connection && r->connection->destroyed)) {
        return NULL;
    }

    /* remove RTMP header in meta */
    meta = in;
    p = meta->buf->pos;
    save = meta->buf->pos;
    if (meta->buf->last == p) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv live: no meta");

        return NULL;
    }

    fmt = (*p >> 6) & 0x03;
    if (fmt) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv live: non-type 0 format chunk message header");

        return NULL;
    }

    csid = *p++ & 0x3f;
    if (csid == 0) {
        if (meta->buf->last - p < 1) {
            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                          "flv live: incorrect basic header 2");

            return NULL;
        }

        p += 1;
    } else if (csid == 1) {
        if (meta->buf->last - p < 2) {
            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                          "flv live: incorrect basic header 3");

            return NULL;
        }

        p += 2;
    }

    thsize = p - meta->buf->pos;

    /*
     * Chunk Message Header - Type 0
     * |timestamp(3B)|msg len(3B)|msg type id(1B)|msg stream id(4B)|
     */
    if (meta->buf->last - p <= 11) {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                      "flv live: incorrect chunk message header");

        return NULL;
    }

    p += 11;
    meta->buf->pos = p;

    for (iter = meta->next; iter; iter = iter->next) {
        iter->buf->pos += thsize;
    }

    ch.timestamp = 0;
    ch.type = NGX_RTMP_MSG_AMF_META;

    out = ngx_http_flv_live_append_message(s, &ch, NULL, meta);

    in->buf->pos = save;
    for (iter = meta->next; iter; iter = iter->next) {
        iter->buf->pos -= thsize;
    }

    return out;
}


static ngx_chain_t *
ngx_http_flv_live_append_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
    ngx_rtmp_header_t *lh, ngx_chain_t *in)
{
    ngx_rtmp_core_srv_conf_t        *cscf;
    ngx_http_request_t              *r;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    if (cscf == NULL) {
        return NULL;
    }

    r = s->data;
    if (r == NULL || r->connection == NULL || r->connection->destroyed) {
        return NULL;
    }

    if (h->type == NGX_RTMP_MSG_VIDEO || h->type == NGX_RTMP_MSG_AUDIO) {
        if (!s->offset_timestamp_set) {
            s->offset_timestamp_set = 1;
            s->offset_timestamp = h->timestamp;
        } else if (h->timestamp == 0) {
            s->offset_timestamp = 0;
        }

        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
                       "flv live: timestamp=%uD, offset_timestamp=%uD",
                       h->timestamp, s->offset_timestamp);
    }

    return ngx_http_flv_live_append_shared_bufs(cscf, h, in, r->chunked);
}


/*
 * Brief format:
 * |Tag|PreviousTagSize|
 * Detailed format:
 * |Reserved(2b)+Filter(1b)+TagType(5b)|DataLength(3B)|TimeStamp(3B)|
 * TimeStampExt(1B)|StreamID(3B)|Data(DataLengthB)|PreviousTagSize|
 */
static ngx_chain_t *
ngx_http_flv_live_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,
    ngx_rtmp_header_t *h, ngx_chain_t *in, ngx_flag_t chunked)
{
    ngx_chain_t        *tag, *chunk_head, *chunk_tail, chunk,
                       *iter, *last_in, **tail, prev_tag_size;
    u_char             *pos,
#if !(NGX_WIN32)
                        chunk_item[ngx_strlen("0000000000000000" CRLF) + 1];
#else
                        chunk_item[19];
#endif
    uint32_t            data_size, size;
    off_t               tag_size;
    ngx_buf_t           prev_tag_size_buf, chunk_buf;

    for (data_size = 0, iter = in, last_in = iter; iter; iter = iter->next) {
        last_in = iter;
        data_size += (iter->buf->last - iter->buf->pos);
    }

    tail = &last_in->next;
    *tail = &prev_tag_size;

    tag_size = data_size + NGX_FLV_TAG_HEADER_SIZE;

    prev_tag_size.buf = &prev_tag_size_buf;
    prev_tag_size.next = NULL;

    prev_tag_size_buf.start = (u_char *) &size;
    prev_tag_size_buf.end = prev_tag_size_buf.start + sizeof(uint32_t);
    prev_tag_size_buf.pos = prev_tag_size_buf.start;
    prev_tag_size_buf.last = prev_tag_size_buf.end;

    pos = prev_tag_size_buf.pos;
    *(uint32_t *) pos = htonl(tag_size);
    pos += 4;

    /* ngx_rtmp_alloc_shared_buf returns the memory:
     * |4B|sizeof(ngx_chain_t)|sizeof(ngx_buf_t)|NGX_RTMP_MAX_CHUNK_HEADER|
     * chunk_size|
     * the tag->buf->pos points to the addr of last part of memory
     */
    tag = ngx_rtmp_append_shared_bufs(cscf, NULL, in);
    if (tag == NULL) {
        return NULL;
    }

    /* it links to the local variable, unlink it */
    *tail = NULL;

    tag->buf->pos -= NGX_FLV_TAG_HEADER_SIZE;
    pos = tag->buf->pos;

    /* type, 5bits */
    *pos++ = (u_char) (h->type & 0x1f);

    /* data length, 3B */
    ngx_rtmp_h4_to_n3(pos, data_size);
    pos += 3;

    /* timestamp, 3B + ext, 1B */
    ngx_rtmp_h4_to_n3(pos, h->timestamp);
    pos += 3;
    *pos++ = (u_char) (h->timestamp >> 24);

    /* streamId, 3B, always be 0 */
    *pos++ = 0;
    *pos++ = 0;
    *pos++ = 0;

    /* add chunk header and tail */
    if (chunked) {
        /* 4 is the size of previous tag size itself */
        *ngx_sprintf(chunk_item, "%xO" CRLF, tag_size + 4) = 0;

        chunk_buf.start = chunk_item;
        chunk_buf.pos = chunk_buf.start;
        chunk_buf.end = chunk_buf.start + ngx_strlen(chunk_item);
        chunk_buf.last = chunk_buf.end;

        chunk.buf = &chunk_buf;
        chunk.next = NULL;

        chunk_head = ngx_rtmp_append_shared_bufs(cscf, NULL, &chunk);
        if (chunk_head == NULL) {
            return NULL;
        }

        for (iter = tag, last_in = iter; iter; iter = iter->next) {
            last_in = iter;
        }

        /* save the memory, very likely */
#if !(NGX_WIN32)
        if (__builtin_expect(last_in->buf->last + 2 <= last_in->buf->end, 1)) {
#else
        if (last_in->buf->last + 2 <= last_in->buf->end) {
#endif
            *last_in->buf->last++ = CR;
            *last_in->buf->last++ = LF;
        } else {
            *ngx_sprintf(chunk_item, CRLF) = 0;
            chunk_buf.start = chunk_item;
            chunk_buf.pos = chunk_buf.start;
            chunk_buf.end = chunk_buf.start + ngx_strlen(chunk_item);
            chunk_buf.last = chunk_buf.end;

            chunk.buf = &chunk_buf;
            chunk.next = NULL;

            chunk_tail = ngx_rtmp_append_shared_bufs(cscf, NULL, &chunk);
            if (chunk_tail == NULL) {
                return NULL;
            }

            tail = &last_in->next;
            *tail = chunk_tail;
        }

        chunk_head->next = tag;

        return chunk_head;
    }

    return tag;
}


static void
ngx_http_flv_live_free_message(ngx_rtmp_session_t *s, ngx_chain_t *in)
{
    ngx_rtmp_core_srv_conf_t *cscf;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);
    if (cscf == NULL) {
        return;
    }

    ngx_rtmp_free_shared_chain(cscf, in);
}


static void
ngx_http_flv_live_close_session_handler(ngx_rtmp_session_t *s)
{
    ngx_connection_t               *c;
    ngx_rtmp_core_srv_conf_t       *cscf;

    c = s->connection;

    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);

    ngx_log_error(NGX_LOG_INFO, c->log, 0, "flv live: close session");

    ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL);

    if (s->ping_evt.timer_set) {
        ngx_del_timer(&s->ping_evt);
    }

    if (s->in_old_pool) {
        ngx_destroy_pool(s->in_old_pool);
    }

    if (s->in_pool) {
        ngx_destroy_pool(s->in_pool);
    }

    while (s->out_pos != s->out_last) {
        ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos]);

        s->out_pos++;
        s->out_pos %= s->out_queue;
    }

    if (s->in_streams_pool) {
        ngx_destroy_pool(s->in_streams_pool);
    }

    if (s->out_pool) {
        ngx_destroy_pool(s->out_pool);
    }
}


static void
ngx_http_flv_live_cleanup(void *data)
{
    ngx_rtmp_session_t       *s;

    s = data;

    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
            "flv live: close connection");

    ngx_http_flv_live_close_session_handler(s);
}


static ngx_int_t
ngx_http_flv_live_handler(ngx_http_request_t *r)
{
    ngx_int_t                        rc;
    ngx_http_flv_live_conf_t        *hfcf;
    ngx_http_cleanup_t              *cln;
    ngx_http_flv_live_ctx_t         *ctx;
    ngx_rtmp_session_t              *s;
    ngx_rtmp_connection_t           *rconn;

    if (ngx_exiting || ngx_terminate) {
        return NGX_HTTP_CLOSE;
    }

    hfcf = ngx_http_get_module_loc_conf(r, ngx_http_flv_live_module);
    if (!hfcf->flv_live) {
        return NGX_DECLINED;
    }

    if (!(r->method & (NGX_HTTP_GET))) {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "flv live: HTTP method was not \"GET\"");

        return NGX_HTTP_NOT_ALLOWED;
    }

    if (r->http_version == NGX_HTTP_VERSION_9
#if (NGX_HTTP_V2)
        || r->http_version == NGX_HTTP_VERSION_20
#endif
       )
    {
        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                      "flv live: HTTP version 0.9 or 2.0 not supported");

        return NGX_HTTP_NOT_ALLOWED;
    }

    if (r->uri.data[r->uri.len - 1] == '/') {
        return NGX_DECLINED;
    }

    rc = ngx_http_discard_request_body(r);

    if (rc != NGX_OK) {
        return rc;
    }

    ctx = ngx_http_get_module_ctx(r, ngx_http_flv_live_module);
    if (ctx == NULL) {
        ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_flv_live_ctx_t));

        if (ctx == NULL) {
            return NGX_HTTP_INTERNAL_SERVER_ERROR;
        }

        ngx_http_set_ctx(r, ctx, ngx_http_flv_live_module);
    }

    rconn = ngx_pcalloc(r->pool, sizeof(ngx_rtmp_connection_t));
    if (rconn == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    if (ngx_http_flv_live_preprocess(r, rconn) != NGX_OK) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    s = ngx_http_flv_live_init_connection(r, rconn);
    if (s == NULL) {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    ctx->s = s;

    /* live, ranges not allowed */
    r->allow_ranges = 0;
    r->read_event_handler = ngx_http_test_reading;

    cln = ngx_http_cleanup_add(r, 0);
    if (cln == NULL) {
        return NGX_DECLINED;
    }

    cln->handler = ngx_http_flv_live_cleanup;
    cln->data = s;

    if (ngx_rtmp_fire_event(s, NGX_HTTP_FLV_LIVE_REQUEST, NULL, NULL)
        != NGX_OK)
    {
        return NGX_HTTP_INTERNAL_SERVER_ERROR;
    }

    r->main->count++;

    return NGX_DONE;
}
