代码拉取完成,页面将自动刷新
同步操作将从 winshining/nginx-http-flv-module 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
/*
* 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, safe_status;
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);
}
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;
}
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, *pt;
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;
}
timestamp = 0;
pt = (u_char *) ×tamp;
p = b->pos + 4;
pt[2] = *p++;
pt[1] = *p++;
pt[0] = *p++;
pt[3] = *p++;
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;
p[2] = *pt++;
p[1] = *pt++;
p[0] = *pt++;
p[3] = *pt++;
}
}
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, *p,
#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;
p = (u_char *) &tag_size;
*pos++ = p[3];
*pos++ = p[2];
*pos++ = p[1];
*pos++ = p[0];
/* 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 */
p = (u_char *) &data_size;
*pos++ = p[2];
*pos++ = p[1];
*pos++ = p[0];
/* timestamp, 3B + ext, 1B */
p = (u_char *) &h->timestamp;
*pos++ = p[2];
*pos++ = p[1];
*pos++ = p[0];
*pos++ = p[3];
/* 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;
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。