2553 lines
68 KiB
C
2553 lines
68 KiB
C
|
|
/*
|
|
* 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));
|
|
if (ctx == NULL) {
|
|
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
|
|
"flv live: failed to allocate for ctx");
|
|
|
|
return NGX_ERROR;
|
|
}
|
|
|
|
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); \
|
|
if (s->name.data == NULL) { \
|
|
return NGX_ERROR; \
|
|
} \
|
|
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;
|
|
}
|