@@ -10,22 +10,33 @@
static ngx_rtmp_publish_pt next_publish;
-static ngx_rtmp_delete_stream_pt next_delete_stream;
+static ngx_rtmp_close_stream_pt next_close_stream;
+static ngx_rtmp_stream_begin_pt next_stream_begin;
+static ngx_rtmp_stream_eof_pt next_stream_eof;
+static char *ngx_rtmp_hls_path(ngx_conf_t *cf, ngx_command_t *cmd,
+ void *conf);
static ngx_int_t ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf);
static void * ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf);
static char * ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf,
void *parent, void *child);
+static ngx_int_t ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s);
#define NGX_RTMP_HLS_BUFSIZE (1024*1024)
-
#define NGX_RTMP_HLS_DIR_ACCESS 0744
typedef struct {
- unsigned publishing:1;
+ uint64_t id;
+ double duration;
+ unsigned active:1;
+ unsigned discont:1; /* before */
+} ngx_rtmp_hls_frag_t;
+
+
+typedef struct {
unsigned opened:1;
ngx_file_t file;
@@ -36,6 +47,9 @@
ngx_str_t name;
uint64_t frag;
+ uint64_t frag_ts;
+ ngx_uint_t nfrags;
+ ngx_rtmp_hls_frag_t *frags; /* circular 2 * winfrags + 1 */
ngx_uint_t audio_cc;
ngx_uint_t video_cc;
@@ -43,25 +57,60 @@
uint64_t aframe_base;
uint64_t aframe_num;
- uint64_t offset;
+ ngx_buf_t *aframe;
+ uint64_t aframe_pts;
} ngx_rtmp_hls_ctx_t;
typedef struct {
+ ngx_str_t path;
+ ngx_msec_t playlen;
+} ngx_rtmp_hls_cleanup_t;
+
+
+typedef struct {
ngx_flag_t hls;
ngx_msec_t fraglen;
+ ngx_msec_t max_fraglen;
ngx_msec_t muxdelay;
ngx_msec_t sync;
ngx_msec_t playlen;
- ngx_int_t factor;
ngx_uint_t winfrags;
- ngx_uint_t nfrags;
ngx_flag_t continuous;
- ngx_flag_t nodelete;
+ ngx_flag_t nested;
ngx_str_t path;
+ ngx_uint_t naming;
+ ngx_uint_t slicing;
+ ngx_path_t *slot;
+ ngx_msec_t max_audio_delay;
+ size_t audio_buffer_size;
} ngx_rtmp_hls_app_conf_t;
+#define NGX_RTMP_HLS_NAMING_SEQUENTIAL 1
+#define NGX_RTMP_HLS_NAMING_TIMESTAMP 2
+#define NGX_RTMP_HLS_NAMING_SYSTEM 3
+
+
+#define NGX_RTMP_HLS_SLICING_PLAIN 1
+#define NGX_RTMP_HLS_SLICING_ALIGNED 2
+
+
+static ngx_conf_enum_t ngx_rtmp_hls_naming_slots[] = {
+ { ngx_string("sequential"), NGX_RTMP_HLS_NAMING_SEQUENTIAL },
+ { ngx_string("timestamp"), NGX_RTMP_HLS_NAMING_TIMESTAMP },
+ { ngx_string("system"), NGX_RTMP_HLS_NAMING_SYSTEM },
+ { ngx_null_string, 0 }
+};
+
+
+static ngx_conf_enum_t ngx_rtmp_hls_slicing_slots[] = {
+ { ngx_string("plain"), NGX_RTMP_HLS_SLICING_PLAIN },
+ { ngx_string("aligned"), NGX_RTMP_HLS_SLICING_ALIGNED },
+ { ngx_null_string, 0 }
+};
+
+
static ngx_command_t ngx_rtmp_hls_commands[] = {
{ ngx_string("hls"),
@@ -78,11 +127,18 @@
offsetof(ngx_rtmp_hls_app_conf_t, fraglen),
NULL },
+ { ngx_string("hls_max_fragment"),
+ NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_RTMP_APP_CONF_OFFSET,
+ offsetof(ngx_rtmp_hls_app_conf_t, max_fraglen),
+ NULL },
+
{ ngx_string("hls_path"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
- ngx_conf_set_str_slot,
+ ngx_rtmp_hls_path,
NGX_RTMP_APP_CONF_OFFSET,
- offsetof(ngx_rtmp_hls_app_conf_t, path),
+ 0,
NULL },
{ ngx_string("hls_playlist_length"),
@@ -113,18 +169,39 @@
offsetof(ngx_rtmp_hls_app_conf_t, continuous),
NULL },
- { ngx_string("hls_nodelete"),
+ { ngx_string("hls_nested"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
ngx_conf_set_flag_slot,
NGX_RTMP_APP_CONF_OFFSET,
- offsetof(ngx_rtmp_hls_app_conf_t, nodelete),
+ offsetof(ngx_rtmp_hls_app_conf_t, nested),
NULL },
- { ngx_string("hls_playlist_factor"),
+ { ngx_string("hls_fragment_naming"),
+ NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_enum_slot,
+ NGX_RTMP_APP_CONF_OFFSET,
+ offsetof(ngx_rtmp_hls_app_conf_t, naming),
+ &ngx_rtmp_hls_naming_slots },
+
+ { ngx_string("hls_fragment_slicing"),
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
- ngx_conf_set_num_slot,
+ ngx_conf_set_enum_slot,
NGX_RTMP_APP_CONF_OFFSET,
- offsetof(ngx_rtmp_hls_app_conf_t, factor),
+ offsetof(ngx_rtmp_hls_app_conf_t, slicing),
+ &ngx_rtmp_hls_slicing_slots },
+
+ { ngx_string("hls_max_audio_delay"),
+ NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_msec_slot,
+ NGX_RTMP_APP_CONF_OFFSET,
+ offsetof(ngx_rtmp_hls_app_conf_t, max_audio_delay),
+ NULL },
+
+ { ngx_string("hls_audio_buffer_size"),
+ NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_size_slot,
+ NGX_RTMP_APP_CONF_OFFSET,
+ offsetof(ngx_rtmp_hls_app_conf_t, audio_buffer_size),
NULL },
ngx_null_command
@@ -162,66 +239,109 @@
};
-#define ngx_rtmp_hls_frag(hacf, f) (hacf->nfrags ? (f) % hacf->nfrags : (f))
+static ngx_int_t
+ngx_rtmp_hls_create_parent_dir(ngx_rtmp_session_t *s)
+{
+ ngx_rtmp_hls_app_conf_t *hacf;
+ ngx_rtmp_hls_ctx_t *ctx;
+ ngx_err_t err;
+ size_t len;
+ static u_char path[NGX_MAX_PATH + 1];
+ hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
-static void
-ngx_rtmp_hls_chain2buffer(ngx_buf_t *out, ngx_chain_t *in, size_t skip)
-{
- size_t size;
+ if (hacf->path.len == 0) {
+ return NGX_ERROR;
+ }
- for (; in; in = in->next) {
-
- size = in->buf->last - in->buf->pos;
- if (size < skip) {
- skip -= size;
- continue;
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: creating target folder: '%V'", &hacf->path);
+
+ if (ngx_create_dir(hacf->path.data, NGX_RTMP_HLS_DIR_ACCESS) != NGX_OK) {
+ err = ngx_errno;
+ if (err != NGX_EEXIST) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, err,
+ "hls: error creating target folder: '%V'",
+ &hacf->path);
+ return NGX_ERROR;
}
+ }
- out->last = ngx_cpymem(out->last, in->buf->pos + skip,
- ngx_min(size - skip,
- (size_t) (out->end - out->last)));
- skip = 0;
+ if (!hacf->nested) {
+ return NGX_OK;
}
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+
+ len = hacf->path.len;
+ if (hacf->path.data[len - 1] == '/') {
+ len--;
+ }
+
+ *ngx_snprintf(path, sizeof(path) - 1, "%*s/%V", len, hacf->path.data,
+ &ctx->name) = 0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: creating nested folder: '%s'", path);
+
+ if (ngx_create_dir(path, NGX_RTMP_HLS_DIR_ACCESS) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
+ "hls: error creating nested folder: '%s'", path);
+ return NGX_ERROR;
+ }
+
+ return NGX_OK;
}
-static ngx_int_t
-ngx_rtmp_hls_create_parent_dir(ngx_rtmp_session_t *s)
+static ngx_rtmp_hls_frag_t *
+ngx_rtmp_hls_get_frag(ngx_rtmp_session_t *s, ngx_int_t n)
{
- ngx_rtmp_hls_app_conf_t *hacf;
+ ngx_rtmp_hls_ctx_t *ctx;
+ ngx_rtmp_hls_app_conf_t *hacf;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
- ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: creating target folder: '%V'", &hacf->path);
+ return &ctx->frags[(ctx->frag + n) % (hacf->winfrags * 2 + 1)];
+}
- if (ngx_create_dir(hacf->path.data, NGX_RTMP_HLS_DIR_ACCESS) == NGX_OK) {
- return NGX_OK;
- }
- ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
- "hls: error creating target folder: '%V'", &hacf->path);
+static void
+ngx_rtmp_hls_next_frag(ngx_rtmp_session_t *s)
+{
+ ngx_rtmp_hls_ctx_t *ctx;
+ ngx_rtmp_hls_app_conf_t *hacf;
+
+ hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
- return NGX_ERROR;
+ if (ctx->nfrags == hacf->winfrags) {
+ ctx->frag++;
+ } else {
+ ctx->nfrags++;
+ }
}
static ngx_int_t
-ngx_rtmp_hls_update_playlist(ngx_rtmp_session_t *s)
+ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
{
static u_char buffer[1024];
int fd;
u_char *p;
ngx_rtmp_hls_ctx_t *ctx;
ssize_t n;
- uint64_t ffrag;
ngx_rtmp_hls_app_conf_t *hacf;
ngx_int_t nretry;
+ ngx_rtmp_hls_frag_t *f;
+ ngx_uint_t i, max_frag;
+ static ngx_str_t empty = ngx_null_string;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+
nretry = 0;
retry:
@@ -244,15 +364,21 @@
return NGX_ERROR;
}
- ffrag = ctx->frag > hacf->winfrags ?
- ctx->frag - (uint64_t) hacf->winfrags : 1;
+ max_frag = hacf->fraglen / 1000;
+
+ for (i = 0; i < ctx->nfrags; i++) {
+ f = ngx_rtmp_hls_get_frag(s, i);
+ if (f->duration > max_frag) {
+ max_frag = f->duration + .5;
+ }
+ }
p = ngx_snprintf(buffer, sizeof(buffer),
- "#EXTM3U\r\n"
- "#EXT-X-MEDIA-SEQUENCE:%uL\r\n"
- "#EXT-X-TARGETDURATION:%ui\r\n"
- "#EXT-X-ALLOW-CACHE:NO\r\n\r\n",
- ctx->frag, (ngx_uint_t) (hacf->fraglen / 1000));
+ "#EXTM3U\n"
+ "#EXT-X-VERSION:3\n"
+ "#EXT-X-MEDIA-SEQUENCE:%uL\n"
+ "#EXT-X-TARGETDURATION:%ui\n",
+ ctx->frag, max_frag);
n = write(fd, buffer, p - buffer);
if (n < 0) {
@@ -262,12 +388,22 @@
return NGX_ERROR;
}
- for (; ffrag < ctx->frag; ++ffrag) {
+ for (i = 0; i < ctx->nfrags; i++) {
+ f = ngx_rtmp_hls_get_frag(s, i);
+
p = ngx_snprintf(buffer, sizeof(buffer),
- "#EXTINF:%i,\r\n"
- "%V-%uL.ts\r\n",
- (ngx_int_t) (hacf->fraglen / 1000), &ctx->name,
- ngx_rtmp_hls_frag(hacf, ffrag));
+ "%s"
+ "#EXTINF:%.3f,\n"
+ "%V%s%uL.ts\n",
+ f->discont ? "#EXT-X-DISCONTINUITY\n" : "",
+ f->duration,
+ hacf->nested ? &empty : &ctx->name,
+ hacf->nested ? "" : "-", f->id);
+
+ ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: fragment frag=%uL, n=%ui/%ui, duration=%.3f, "
+ "discont=%i",
+ ctx->frag, i + 1, ctx->nfrags, f->duration, f->discont);
n = write(fd, buffer, p - buffer);
if (n < 0) {
@@ -469,48 +605,79 @@
}
-static void
-ngx_rtmp_hls_next_frag(ngx_rtmp_session_t *s)
+static uint64_t
+ngx_rtmp_hls_get_fragment_id(ngx_rtmp_session_t *s, uint64_t ts)
{
- ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
- ngx_int_t nretry;
+ ngx_rtmp_hls_app_conf_t *hacf;
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
- if (ctx == NULL || hacf == NULL) {
- return;
+ switch (hacf->naming) {
+
+ case NGX_RTMP_HLS_NAMING_TIMESTAMP:
+ return ts;
+
+ case NGX_RTMP_HLS_NAMING_SYSTEM:
+ return ngx_current_msec;
+
+ default: /* NGX_RTMP_HLS_NAMING_SEQUENTIAL */
+ return ctx->frag + ctx->nfrags;
}
+}
- if (ctx->opened) {
- ngx_close_file(ctx->file.fd);
- ctx->opened = 0;
+
+static ngx_int_t
+ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s, ngx_int_t discont)
+{
+ ngx_rtmp_hls_ctx_t *ctx;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+ if (!ctx->opened) {
+ return NGX_OK;
}
- if (hacf->nfrags == 0 && ctx->frag > 2 * hacf->winfrags &&
- !hacf->nodelete)
- {
- *ngx_sprintf(ctx->stream.data + ctx->stream.len, "-%uL.ts",
- ngx_rtmp_hls_frag(hacf, ctx->frag - 2 * hacf->winfrags))
- = 0;
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: close fragment n=%uL, discont=%i",
+ ctx->frag, discont);
- ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: delete frag '%s'", ctx->stream.data);
+ ngx_close_file(ctx->file.fd);
- ngx_delete_file(ctx->stream.data);
+ ctx->opened = 0;
+ ctx->file.fd = NGX_INVALID_FILE;
+
+ ngx_rtmp_hls_next_frag(s);
+ ngx_rtmp_hls_write_playlist(s);
+
+ return NGX_OK;
+}
+
+
+static ngx_int_t
+ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
+ ngx_int_t discont)
+{
+ ngx_rtmp_hls_ctx_t *ctx;
+ ngx_rtmp_hls_frag_t *f;
+ ngx_uint_t nretry;
+ uint64_t id;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+ if (ctx->opened) {
+ return NGX_OK;
}
- ctx->frag++;
+ id = ngx_rtmp_hls_get_fragment_id(s, ts);
- *ngx_sprintf(ctx->stream.data + ctx->stream.len, "-%uL.ts",
- ngx_rtmp_hls_frag(hacf, ctx->frag)) = 0;
+ *ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uL.ts", id) = 0;
- ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: open frag '%s', frag=%uL",
- ctx->stream.data, ctx->frag);
+ ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: open fragment file='%s', frag=%uL, n=%uL, time=%uL, "
+ "discont=%i",
+ ctx->stream.data, ctx->frag, ctx->nfrags, ts, discont);
ngx_memzero(&ctx->file, sizeof(ctx->file));
@@ -536,23 +703,34 @@
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: error creating fragment file");
- return;
+ return NGX_ERROR;
}
if (ngx_rtmp_mpegts_write_header(&ctx->file) != NGX_OK) {
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
"hls: error writing fragment header");
ngx_close_file(ctx->file.fd);
- return;
+ return NGX_ERROR;
}
ctx->opened = 1;
- ngx_rtmp_hls_update_playlist(s);
-}
+ f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);
+
+ ngx_memzero(f, sizeof(*f));
+ f->active = 1;
+ f->discont = discont;
+ f->id = id;
-#define NGX_RTMP_HLS_RESTORE_PREFIX "#EXTM3U\r\n#EXT-X-MEDIA-SEQUENCE:"
+ ctx->frag_ts = ts;
+
+ /* start fragment with audio to make iPhone happy */
+
+ ngx_rtmp_hls_flush_audio(s);
+
+ return NGX_OK;
+}
static void
@@ -561,8 +739,14 @@
ngx_rtmp_hls_ctx_t *ctx;
ngx_file_t file;
ssize_t ret;
- u_char buffer[sizeof(NGX_RTMP_HLS_RESTORE_PREFIX) -
- 1 + NGX_INT64_LEN];
+ size_t len;
+ off_t offset;
+ u_char *p, *last, *end, *next, *pa;
+ ngx_rtmp_hls_frag_t *f;
+ double duration;
+ ngx_int_t discont;
+ uint64_t mag;
+ static u_char buffer[4096];
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
@@ -578,30 +762,117 @@
return;
}
- ret = ngx_read_file(&file, buffer, sizeof(buffer), 0);
- if (ret <= 0) {
- goto done;
- }
+ offset = 0;
+ ctx->nfrags = 0;
+ f = NULL;
+ duration = 0;
+ discont = 0;
- if ((size_t) ret < sizeof(NGX_RTMP_HLS_RESTORE_PREFIX)) {
- goto done;
- }
+ for ( ;; ) {
- if (ngx_strncmp(buffer, NGX_RTMP_HLS_RESTORE_PREFIX,
- sizeof(NGX_RTMP_HLS_RESTORE_PREFIX) - 1))
- {
- ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
- "hls: failed to restore stream");
- goto done;
- }
+ ret = ngx_read_file(&file, buffer, sizeof(buffer), offset);
+ if (ret <= 0) {
+ goto done;
+ }
+
+ p = buffer;
+ end = buffer + ret;
+
+ for ( ;; ) {
+ last = ngx_strlchr(p, end, '\n');
+
+ if (last == NULL) {
+ if (p == buffer) {
+ goto done;
+ }
+ break;
+ }
+
+ next = last + 1;
+ offset += (next - p);
- buffer[ret] = 0;
+ if (p != last && last[-1] == '\r') {
+ last--;
+ }
- ctx->frag = strtod((const char *)
- &buffer[sizeof(NGX_RTMP_HLS_RESTORE_PREFIX) - 1], NULL);
+ len = (size_t) (last - p);
- ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: restored frag=%uL", ctx->frag);
+
+#define NGX_RTMP_MSEQ "#EXT-X-MEDIA-SEQUENCE:"
+#define NGX_RTMP_MSEQ_LEN (sizeof(NGX_RTMP_MSEQ) - 1)
+
+
+ if (ngx_memcmp(p, NGX_RTMP_MSEQ, NGX_RTMP_MSEQ_LEN) == 0) {
+
+ ctx->frag = strtod((const char *) &p[NGX_RTMP_MSEQ_LEN], NULL);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: restore sequence frag=%uL", ctx->frag);
+ }
+
+
+#define NGX_RTMP_EXTINF "#EXTINF:"
+#define NGX_RTMP_EXTINF_LEN (sizeof(NGX_RTMP_EXTINF) - 1)
+
+
+ if (ngx_memcmp(p, NGX_RTMP_EXTINF, NGX_RTMP_EXTINF_LEN) == 0) {
+
+ duration = strtod((const char *) &p[NGX_RTMP_EXTINF_LEN], NULL);
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: restore durarion=%.3f", duration);
+ }
+
+
+#define NGX_RTMP_DISCONT "#EXT-X-DISCONTINUITY"
+#define NGX_RTMP_DISCONT_LEN (sizeof(NGX_RTMP_DISCONT) - 1)
+
+
+ if (ngx_memcmp(p, NGX_RTMP_DISCONT, NGX_RTMP_DISCONT_LEN) == 0) {
+
+ discont = 1;
+
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: discontinuity");
+ }
+
+ /* find '.ts\r' */
+
+ if (p + 4 <= last &&
+ last[-3] == '.' && last[-2] == 't' && last[-1] == 's')
+ {
+ f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);
+
+ ngx_memzero(f, sizeof(*f));
+
+ f->duration = duration;
+ f->discont = discont;
+ f->active = 1;
+ f->id = 0;
+
+ discont = 0;
+
+ mag = 1;
+ for (pa = last - 4; pa >= p; pa--) {
+ if (*pa < '0' || *pa > '9') {
+ break;
+ }
+ f->id += (*pa - '0') * mag;
+ mag *= 10;
+ }
+
+ ngx_rtmp_hls_next_frag(s);
+
+ ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: restore fragment '%*s' id=%uL, "
+ "duration=%.3f, frag=%uL, nfrags=%ui",
+ len, p, f->id, f->duration, ctx->frag,
+ ctx->nfrags);
+ }
+
+ p = next;
+ }
+ }
done:
ngx_close_file(file.fd);
@@ -613,8 +884,10 @@
{
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
- size_t len;
u_char *p;
+ ngx_rtmp_hls_frag_t *f;
+ ngx_buf_t *b;
+ size_t len;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
if (hacf == NULL || !hacf->hls || hacf->path.len == 0) {
@@ -630,25 +903,57 @@
v->name, v->type);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+
if (ctx == NULL) {
- ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_hls_ctx_t));
+
+ ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_hls_ctx_t));
ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_hls_module);
+
+ } else {
+
+ f = ctx->frags;
+ b = ctx->aframe;
+
+ ngx_memzero(ctx, sizeof(ngx_rtmp_hls_ctx_t));
+
+ ctx->frags = f;
+ ctx->aframe = b;
+
+ if (b) {
+ b->pos = b->last = b->start;
+ }
+ }
+
+ if (ctx->frags == NULL) {
+ ctx->frags = ngx_pcalloc(s->connection->pool,
+ sizeof(ngx_rtmp_hls_frag_t) *
+ (hacf->winfrags * 2 + 1));
+ if (ctx->frags == NULL) {
+ return NGX_ERROR;
+ }
}
- ngx_memzero(ctx, sizeof(ngx_rtmp_hls_ctx_t));
+ if (ngx_strstr(v->name, "..")) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: bad stream name: '%s'", v->name);
+ return NGX_ERROR;
+ }
- /*TODO: escaping does not solve the problem*/
+ ctx->name.len = ngx_strlen(v->name);
+ ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len + 1);
+
+ if (ctx->name.data == NULL) {
+ return NGX_ERROR;
+ }
- len = ngx_strlen(v->name);
- ctx->name.len = len + (ngx_uint_t) ngx_escape_uri(NULL, v->name, len,
- NGX_ESCAPE_URI_COMPONENT);
- ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len);
+ *ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0;
- ngx_escape_uri(ctx->name.data, v->name, len, NGX_ESCAPE_URI_COMPONENT);
+ len = hacf->path.len + 1 + ctx->name.len + sizeof(".m3u8");
+ if (hacf->nested) {
+ len += sizeof("/index") - 1;
+ }
- ctx->playlist.data = ngx_palloc(s->connection->pool,
- hacf->path.len + 1 + ctx->name.len +
- sizeof(".m3u8"));
+ ctx->playlist.data = ngx_palloc(s->connection->pool, len);
p = ngx_cpymem(ctx->playlist.data, hacf->path.data, hacf->path.len);
if (p[-1] != '/') {
@@ -661,16 +966,25 @@
* however the space for the whole stream path
* is allocated */
- ctx->stream.len = p - ctx->playlist.data;
+ ctx->stream.len = p - ctx->playlist.data + 1;
ctx->stream.data = ngx_palloc(s->connection->pool,
- ctx->stream.len + 1 + NGX_INT64_LEN + sizeof(".ts"));
+ ctx->stream.len + NGX_INT64_LEN +
+ sizeof(".ts"));
- ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len);
+ ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1);
+ ctx->stream.data[ctx->stream.len - 1] = (hacf->nested ? '/' : '-');
+
/* playlist path */
- p = ngx_cpymem(p, ".m3u8", sizeof(".m3u8") - 1);
+ if (hacf->nested) {
+ p = ngx_cpymem(p, "/index.m3u8", sizeof("/index.m3u8") - 1);
+ } else {
+ p = ngx_cpymem(p, ".m3u8", sizeof(".m3u8") - 1);
+ }
+
ctx->playlist.len = p - ctx->playlist.data;
+
*p = 0;
/* playlist bak (new playlist) path */
@@ -693,15 +1007,13 @@
ngx_rtmp_hls_restore_stream(s);
}
- ctx->publishing = 1;
-
next:
return next_publish(s, v);
}
static ngx_int_t
-ngx_rtmp_hls_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v)
+ngx_rtmp_hls_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)
{
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
@@ -710,22 +1022,17 @@
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
- if (hacf == NULL || !hacf->hls || ctx == NULL || !ctx->publishing) {
+ if (hacf == NULL || !hacf->hls || ctx == NULL) {
goto next;
}
ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: delete");
+ "hls: delete stream");
- ctx->publishing = 0;
-
- if (ctx->opened) {
- ngx_close_file(ctx->file.fd);
- ctx->opened = 0;
- }
+ ngx_rtmp_hls_close_fragment(s, 1);
next:
- return next_delete_stream(s, v);
+ return next_close_stream(s, v);
}
@@ -762,7 +1069,15 @@
return NGX_ERROR;
}
- (*objtype)--;
+ if (*objtype > 4) {
+
+ /*
+ * Mark all extended profiles as LC
+ * to make Android as happy as possible.
+ */
+
+ *objtype = 2;
+ }
*srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7);
if (*srindex == 0x0f) {
@@ -782,29 +1097,115 @@
static void
-ngx_rtmp_hls_set_frag(ngx_rtmp_session_t *s, uint64_t ts)
+ngx_rtmp_hls_update_fragment(ngx_rtmp_session_t *s, uint64_t ts,
+ ngx_int_t boundary, ngx_uint_t flush_rate)
{
- ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
- uint64_t frag;
+ ngx_rtmp_hls_app_conf_t *hacf;
+ ngx_rtmp_hls_frag_t *f;
+ ngx_msec_t ts_frag_len;
+ ngx_int_t same_frag;
+ ngx_buf_t *b;
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
- frag = ts / hacf->fraglen / 90;
+ f = NULL;
- if (frag == ctx->frag && ctx->opened) {
- return;
+ if (ctx->opened) {
+
+ f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);
+ f->duration = (ts - ctx->frag_ts) / 90000.;
+
+ if (f->duration * 1000 > hacf->max_fraglen) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: max fragment length reached");
+ boundary = 1;
+ }
}
- if (frag != ctx->frag + 1) {
- ctx->offset += (ctx->frag + 1) * (uint64_t) hacf->fraglen * 90 - ts;
- ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: time gap offset=%uL", ctx->offset);
+ switch (hacf->slicing) {
+ case NGX_RTMP_HLS_SLICING_PLAIN:
+ if (f && f->duration < hacf->fraglen / 1000.) {
+ boundary = 0;
+ }
+ break;
+
+ case NGX_RTMP_HLS_SLICING_ALIGNED:
+
+ ts_frag_len = hacf->fraglen * 90;
+ same_frag = ctx->frag_ts / ts_frag_len == ts / ts_frag_len;
+
+ if (f && same_frag) {
+ boundary = 0;
+ }
+
+ if (f == NULL && (ctx->frag_ts == 0 || same_frag)) {
+ ctx->frag_ts = ts;
+ boundary = 0;
+ }
+
+ break;
}
- ngx_rtmp_hls_next_frag(s);
+ if (boundary) {
+ ngx_rtmp_hls_close_fragment(s, 0);
+ ngx_rtmp_hls_open_fragment(s, ts, !f);
+ }
+
+ b = ctx->aframe;
+ if (ctx->opened && b && b->last > b->pos &&
+ ctx->aframe_pts + (uint64_t) hacf->max_audio_delay * 90 / flush_rate
+ < ts)
+ {
+ ngx_rtmp_hls_flush_audio(s);
+ }
+}
+
+
+static ngx_int_t
+ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s)
+{
+ ngx_rtmp_hls_ctx_t *ctx;
+ ngx_rtmp_mpegts_frame_t frame;
+ ngx_int_t rc;
+ ngx_buf_t *b;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+
+ if (!ctx->opened) {
+ return NGX_OK;
+ }
+
+ b = ctx->aframe;
+
+ if (b == NULL || b->pos == b->last) {
+ return NGX_OK;
+ }
+
+ ngx_memzero(&frame, sizeof(frame));
+
+ frame.dts = ctx->aframe_pts;
+ frame.pts = frame.dts;
+ frame.cc = ctx->audio_cc;
+ frame.pid = 0x101;
+ frame.sid = 0xc0;
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: flush audio pts=%uL", frame.pts);
+
+ rc = ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, b);
+
+ if (rc != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: audio flush failed");
+ }
+
+ ctx->audio_cc = frame.cc;
+ b->pos = b->last = b->start;
+
+ return rc;
}
@@ -815,13 +1216,12 @@
ngx_rtmp_hls_app_conf_t *hacf;
ngx_rtmp_hls_ctx_t *ctx;
ngx_rtmp_codec_ctx_t *codec_ctx;
- uint64_t dts;
- int64_t ddts;
- ngx_rtmp_mpegts_frame_t frame;
- ngx_buf_t out;
+ uint64_t pts, est_pts;
+ int64_t dpts;
+ size_t bsize;
+ ngx_buf_t *b;
u_char *p;
ngx_uint_t objtype, srindex, chconf, size;
- static u_char buffer[NGX_RTMP_HLS_BUFSIZE];
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
@@ -841,22 +1241,66 @@
return NGX_OK;
}
- ngx_memzero(&frame, sizeof(frame));
+ b = ctx->aframe;
- frame.dts = (uint64_t) h->timestamp * 90 + ctx->offset;
- frame.cc = ctx->audio_cc;
- frame.pid = 0x101;
- frame.sid = 0xc0;
+ if (b == NULL) {
- ngx_memzero(&out, sizeof(out));
+ b = ngx_pcalloc(s->connection->pool, sizeof(ngx_buf_t));
+ if (b == NULL) {
+ return NGX_ERROR;
+ }
- out.start = buffer;
- out.end = buffer + sizeof(buffer);
- out.pos = out.start;
- out.last = out.pos;
+ ctx->aframe = b;
- p = out.last;
- out.last += 7;
+ b->start = ngx_palloc(s->connection->pool, hacf->audio_buffer_size);
+ if (b->start == NULL) {
+ return NGX_ERROR;
+ }
+
+ b->end = b->start + hacf->audio_buffer_size;
+ b->pos = b->last = b->start;
+ }
+
+ size = h->mlen - 2 + 7;
+ pts = (uint64_t) h->timestamp * 90;
+
+ if (b->start + size > b->end) {
+ ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+ "hls: too big audio frame");
+ return NGX_OK;
+ }
+
+ /*
+ * start new fragment here if
+ * there's no video at all, otherwise
+ * do it in video handler
+ */
+
+ ngx_rtmp_hls_update_fragment(s, pts, codec_ctx->avc_header == NULL, 2);
+
+ if (b->last + size > b->end) {
+ ngx_rtmp_hls_flush_audio(s);
+ }
+
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: audio pts=%uL", pts);
+
+ p = b->last;
+ b->last += 5;
+
+ /* copy payload */
+
+ for (; in; in = in->next) {
+
+ bsize = in->buf->last - in->buf->pos;
+ if (b->last + bsize > b->end) {
+ break;
+ }
+
+ b->last = ngx_cpymem(b->last, in->buf->pos, bsize);
+ }
+
+ /* make up ADTS header */
if (ngx_rtmp_hls_parse_aac_header(s, &objtype, &srindex, &chconf)
!= NGX_OK)
@@ -865,70 +1309,55 @@
"hls: aac header error");
return NGX_OK;
}
-
- size = h->mlen - 2 + 7;
+
+ /* we have 5 free bytes + 2 bytes of RTMP frame header */
p[0] = 0xff;
p[1] = 0xf1;
- p[2] = (objtype << 6) | (srindex << 2) | (chconf & 0x04);
+ p[2] = ((objtype - 1) << 6) | (srindex << 2) | ((chconf & 0x04) >> 2);
p[3] = ((chconf & 0x03) << 6) | ((size >> 11) & 0x03);
p[4] = (size >> 3);
p[5] = (size << 5) | 0x1f;
p[6] = 0xfc;
- ngx_rtmp_hls_chain2buffer(&out, in, 2);
+ if (p != b->start) {
+ ctx->aframe_num++;
+ return NGX_OK;
+ }
- if (hacf->sync && codec_ctx->sample_rate) {
+ ctx->aframe_pts = pts;
- /* TODO: We assume here AAC frame size is 1024
- * Need to handle AAC frames with frame size of 960 */
+ if (!hacf->sync || codec_ctx->sample_rate == 0) {
+ return NGX_OK;
+ }
- dts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 /
- codec_ctx->sample_rate;
- ddts = (int64_t) (dts - frame.dts);
+ /* align audio frames */
- ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: sync stat ddts=%L (%.5fs)",
- ddts, ddts / 90000.);
+ /* TODO: We assume here AAC frame size is 1024
+ * Need to handle AAC frames with frame size of 960 */
- if (ddts > (int64_t) hacf->sync * 90 ||
- ddts < (int64_t) hacf->sync * -90)
- {
- ctx->aframe_base = frame.dts;
- ctx->aframe_num = 0;
+ est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 /
+ codec_ctx->sample_rate;
+ dpts = (int64_t) (est_pts - pts);
- ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: sync breakup ddts=%L (%.5fs)",
- ddts, ddts / 90000.);
- } else {
- frame.dts = dts;
- }
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: audio sync dpts=%L (%.5fs)",
+ dpts, dpts / 90000.);
+ if (dpts <= (int64_t) hacf->sync * 90 &&
+ dpts >= (int64_t) hacf->sync * -90)
+ {
ctx->aframe_num++;
- }
-
- frame.pts = frame.dts;
-
- /* Fragment is restarted in video handler.
- * However if video stream is missing then do it here */
-
- if (codec_ctx->avc_header == NULL) {
- ngx_rtmp_hls_set_frag(s, frame.dts);
- }
-
- if (!ctx->opened) {
+ ctx->aframe_pts = est_pts;
return NGX_OK;
}
- ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
- "hls: audio dts=%uL", frame.dts);
-
- if (ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, &out) != NGX_OK) {
- ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
- "hls: audio frame failed");
- }
+ ctx->aframe_base = pts;
+ ctx->aframe_num = 1;
- ctx->audio_cc = frame.cc;
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hls: audio sync gap dpts=%L (%.5fs)",
+ dpts, dpts / 90000.);
return NGX_OK;
}
@@ -978,11 +1407,11 @@
u_char *p;
uint8_t fmt, ftype, htype, nal_type, src_nal_type;
uint32_t len, rlen;
- ngx_buf_t out;
+ ngx_buf_t out, *b;
uint32_t cts;
ngx_rtmp_mpegts_frame_t frame;
ngx_uint_t nal_bytes;
- ngx_int_t aud_sent, sps_pps_sent, rc;
+ ngx_int_t aud_sent, sps_pps_sent, rc, boundary;
static u_char buffer[NGX_RTMP_HLS_BUFSIZE];
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
@@ -1074,6 +1503,13 @@
"hls: h264 NAL type=%ui, len=%uD",
(ngx_uint_t) nal_type, len);
+ if (nal_type >= 7 && nal_type <= 9) {
+ if (ngx_rtmp_hls_copy(s, NULL, &p, len - 1, &in) != NGX_OK) {
+ return NGX_ERROR;
+ }
+ continue;
+ }
+
if (!aud_sent) {
switch (nal_type) {
case 1:
@@ -1141,15 +1577,22 @@
ngx_memzero(&frame, sizeof(frame));
frame.cc = ctx->video_cc;
- frame.dts = (uint64_t) h->timestamp * 90 + ctx->offset;
+ frame.dts = (uint64_t) h->timestamp * 90;
frame.pts = frame.dts + cts * 90;
frame.pid = 0x100;
frame.sid = 0xe0;
frame.key = (ftype == 1);
- if (frame.key) {
- ngx_rtmp_hls_set_frag(s, frame.dts);
- }
+ /*
+ * start new fragment if
+ * - we have video key frame AND
+ * - we have audio buffered or have no audio at all
+ */
+
+ b = ctx->aframe;
+ boundary = frame.key && (codec_ctx->aac_header == NULL || b->last > b->pos);
+
+ ngx_rtmp_hls_update_fragment(s, frame.dts, boundary, 1);
if (!ctx->opened) {
return NGX_OK;
@@ -1169,10 +1612,227 @@
}
+static void
+ngx_rtmp_hls_discontinue(ngx_rtmp_session_t *s)
+{
+ ngx_rtmp_hls_ctx_t *ctx;
+
+ ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);
+
+ if (ctx == NULL || !ctx->opened) {
+ return;
+ }
+
+ ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
+ "hld: discontinue");
+
+ ngx_close_file(ctx->file.fd);
+ ctx->opened = 0;
+}
+
+
+static ngx_int_t
+ngx_rtmp_hls_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)
+{
+ ngx_rtmp_hls_discontinue(s);
+
+ return next_stream_begin(s, v);
+}
+
+
+static ngx_int_t
+ngx_rtmp_hls_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)
+{
+ ngx_rtmp_hls_discontinue(s);
+
+ return next_stream_eof(s, v);
+}
+
+
+static ngx_int_t
+ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
+{
+ ngx_dir_t dir;
+ time_t mtime, max_age;
+ ngx_err_t err;
+ ngx_str_t name, spath;
+ u_char *p;
+ ngx_int_t nentries, nerased;
+ u_char path[NGX_MAX_PATH + 1];
+
+ ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
+ "hls: cleanup path='%V' playlen=%M",
+ ppath, playlen);
+
+ if (ngx_open_dir(ppath, &dir) != NGX_OK) {
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno,
+ "hls: cleanup open dir failed '%V'", ppath);
+ return NGX_ERROR;
+ }
+
+ nentries = 0;
+ nerased = 0;
+
+ for ( ;; ) {
+ ngx_set_errno(0);
+
+ if (ngx_read_dir(&dir) == NGX_ERROR) {
+ err = ngx_errno;
+
+ if (err == NGX_ENOMOREFILES) {
+ return nentries - nerased;
+ }
+
+ ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err,
+ "hls: cleanup " ngx_read_dir_n
+ " \"%V\" failed", ppath);
+ return NGX_ERROR;
+ }
+
+ name.data = ngx_de_name(&dir);
+ if (name.data[0] == '.') {
+ continue;
+ }
+
+ name.len = ngx_de_namelen(&dir);
+
+ p = ngx_snprintf(path, sizeof(path) - 1, "%V/%V", ppath, &name);
+ *p = 0;
+
+ spath.data = path;
+ spath.len = p - path;
+
+ nentries++;
+
+ if (ngx_de_info(path, &dir) == NGX_FILE_ERROR) {
+ ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,
+ "hls: cleanup " ngx_de_info_n " \"%V\" failed",
+ &spath);
+
+ continue;
+ }
+
+ if (ngx_de_is_dir(&dir)) {
+
+ if (ngx_rtmp_hls_cleanup_dir(&spath, playlen) == 0) {
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
+ "hls: cleanup dir '%V'", &name);
+
+ if (ngx_delete_dir(spath.data) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
+ "hls: cleanup dir error '%V'", &spath);
+ } else {
+ nerased++;
+ }
+ }
+
+ continue;
+ }
+
+ if (!ngx_de_is_file(&dir)) {
+ continue;
+ }
+
+ if (name.len >= 3 && name.data[name.len - 3] == '.' &&
+ name.data[name.len - 2] == 't' &&
+ name.data[name.len - 1] == 's')
+ {
+ max_age = playlen / 500;
+
+ } else if (name.len >= 5 && name.data[name.len - 5] == '.' &&
+ name.data[name.len - 4] == 'm' &&
+ name.data[name.len - 3] == '3' &&
+ name.data[name.len - 2] == 'u' &&
+ name.data[name.len - 1] == '8')
+ {
+ max_age = playlen / 1000;
+
+ } else {
+ ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
+ "hls: cleanup skip unknown file type '%V'", &name);
+ continue;
+ }
+
+ mtime = ngx_de_mtime(&dir);
+ if (mtime + max_age > ngx_cached_time->sec) {
+ continue;
+ }
+
+ ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
+ "hls: cleanup '%V' mtime=%T age=%T",
+ &name, mtime, ngx_cached_time->sec - mtime);
+
+ if (ngx_delete_file(spath.data) != NGX_OK) {
+ ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,
+ "hls: cleanup error '%V'", &spath);
+ continue;
+ }
+
+ nerased++;
+ }
+}
+
+
+static time_t
+ngx_rtmp_hls_cleanup(void *data)
+{
+ ngx_rtmp_hls_cleanup_t *cleanup = data;
+
+ ngx_rtmp_hls_cleanup_dir(&cleanup->path, cleanup->playlen);
+
+ return cleanup->playlen / 500;
+}
+
+
+static char *
+ngx_rtmp_hls_path(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+ ngx_rtmp_hls_app_conf_t *hacf = conf;
+
+ ngx_rtmp_hls_cleanup_t *cleanup;
+ ngx_str_t *value;
+
+ if (hacf->slot) {
+ return "is duplicate";
+ }
+
+ value = cf->args->elts;
+ hacf->path = value[1];
+
+ if (hacf->path.data[hacf->path.len - 1] == '/') {
+ hacf->path.len--;
+ }
+
+ cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));
+ if (cleanup == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ cleanup->path = hacf->path;
+
+ hacf->slot = ngx_pcalloc(cf->pool, sizeof(*hacf->slot));
+ if (hacf->slot == NULL) {
+ return NGX_CONF_ERROR;
+ }
+
+ hacf->slot->manager = ngx_rtmp_hls_cleanup;
+ hacf->slot->name = hacf->path;
+ hacf->slot->data = cleanup;
+ hacf->slot->conf_file = cf->conf_file->file.name.data;
+ hacf->slot->line = cf->conf_file->line;
+
+ if (ngx_add_path(cf, &hacf->slot) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ return NGX_CONF_OK;
+}
+
+
static void *
ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
{
- ngx_rtmp_hls_app_conf_t *conf;
+ ngx_rtmp_hls_app_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_hls_app_conf_t));
if (conf == NULL) {
@@ -1181,12 +1841,16 @@
conf->hls = NGX_CONF_UNSET;
conf->fraglen = NGX_CONF_UNSET;
+ conf->max_fraglen = NGX_CONF_UNSET;
conf->muxdelay = NGX_CONF_UNSET;
conf->sync = NGX_CONF_UNSET;
conf->playlen = NGX_CONF_UNSET;
conf->continuous = NGX_CONF_UNSET;
- conf->nodelete = NGX_CONF_UNSET;
- conf->factor = NGX_CONF_UNSET;
+ conf->nested = NGX_CONF_UNSET;
+ conf->naming = NGX_CONF_UNSET_UINT;
+ conf->slicing = NGX_CONF_UNSET_UINT;
+ conf->max_audio_delay = NGX_CONF_UNSET;
+ conf->audio_buffer_size = NGX_CONF_UNSET;
return conf;
}
@@ -1195,22 +1859,38 @@
static char *
ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
{
- ngx_rtmp_hls_app_conf_t *prev = parent;
- ngx_rtmp_hls_app_conf_t *conf = child;
+ ngx_rtmp_hls_app_conf_t *prev = parent;
+ ngx_rtmp_hls_app_conf_t *conf = child;
+ ngx_rtmp_hls_cleanup_t *cleanup;
ngx_conf_merge_value(conf->hls, prev->hls, 0);
ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000);
+ ngx_conf_merge_msec_value(conf->max_fraglen, prev->max_fraglen,
+ conf->fraglen * 10);
ngx_conf_merge_msec_value(conf->muxdelay, prev->muxdelay, 700);
- ngx_conf_merge_msec_value(conf->sync, prev->sync, 300);
+ ngx_conf_merge_msec_value(conf->sync, prev->sync, 2);
ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000);
ngx_conf_merge_str_value(conf->path, prev->path, "");
ngx_conf_merge_value(conf->continuous, prev->continuous, 1);
- ngx_conf_merge_value(conf->nodelete, prev->nodelete, 0);
- ngx_conf_merge_value(conf->factor, prev->factor, 2);
+ ngx_conf_merge_value(conf->nested, prev->nested, 0);
+ ngx_conf_merge_uint_value(conf->naming, prev->naming,
+ NGX_RTMP_HLS_NAMING_SEQUENTIAL);
+ ngx_conf_merge_uint_value(conf->slicing, prev->slicing,
+ NGX_RTMP_HLS_SLICING_PLAIN);
+ ngx_conf_merge_msec_value(conf->max_audio_delay, prev->max_audio_delay,
+ 300);
+ ngx_conf_merge_size_value(conf->audio_buffer_size, prev->audio_buffer_size,
+ NGX_RTMP_HLS_BUFSIZE);
if (conf->fraglen) {
conf->winfrags = conf->playlen / conf->fraglen;
- conf->nfrags = conf->winfrags * conf->factor;
+ }
+
+ if (conf->slot) {
+ cleanup = conf->slot->data;
+ if (cleanup->playlen < conf->playlen) {
+ cleanup->playlen = conf->playlen;
+ }
}
return NGX_CONF_OK;
@@ -1234,8 +1914,14 @@
next_publish = ngx_rtmp_publish;
ngx_rtmp_publish = ngx_rtmp_hls_publish;
- next_delete_stream = ngx_rtmp_delete_stream;
- ngx_rtmp_delete_stream = ngx_rtmp_hls_delete_stream;
+ next_close_stream = ngx_rtmp_close_stream;
+ ngx_rtmp_close_stream = ngx_rtmp_hls_close_stream;
+
+ next_stream_begin = ngx_rtmp_stream_begin;
+ ngx_rtmp_stream_begin = ngx_rtmp_hls_stream_begin;
+
+ next_stream_eof = ngx_rtmp_stream_eof;
+ ngx_rtmp_stream_eof = ngx_rtmp_hls_stream_eof;
return NGX_OK;
}
|