diff --git a/hls/ngx_rtmp_hls_module.c b/hls/ngx_rtmp_hls_module.c index 13d1e63..3c77944 100644 --- a/hls/ngx_rtmp_hls_module.c +++ b/hls/ngx_rtmp_hls_module.c @@ -73,7 +73,7 @@ typedef struct { ngx_msec_t playlen; ngx_uint_t winfrags; ngx_flag_t continuous; - ngx_flag_t nodelete; + ngx_flag_t nested; ngx_str_t path; ngx_path_t *slot; } ngx_rtmp_hls_app_conf_t; @@ -130,11 +130,11 @@ static ngx_command_t ngx_rtmp_hls_commands[] = { 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_null_command @@ -197,20 +197,54 @@ 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); + if (hacf->path.len == 0) { + return NGX_ERROR; + } + 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) { + 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; + } + } + + if (!hacf->nested) { return NGX_OK; } - ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno, - "hls: error creating target folder: '%V'", &hacf->path); + ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module); - return NGX_ERROR; + 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; } @@ -817,6 +851,7 @@ ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) ngx_rtmp_hls_ctx_t *ctx; u_char *p; ngx_rtmp_hls_frag_t *f; + size_t len; hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module); if (hacf == NULL || !hacf->hls || hacf->path.len == 0) { @@ -864,15 +899,23 @@ ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v) } *ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0; - ctx->playlist.data = ngx_palloc(s->connection->pool, - hacf->path.len + 1 + ctx->name.len + - sizeof(".m3u8")); + len = hacf->path.len + 1 + ctx->name.len + sizeof(".m3u8"); + if (hacf->nested) { + len += ctx->name.len + 1; + } + + ctx->playlist.data = ngx_palloc(s->connection->pool, len); p = ngx_cpymem(ctx->playlist.data, hacf->path.data, hacf->path.len); if (p[-1] != '/') { *p++ = '/'; } + if (hacf->nested) { + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); + *p++ = '/'; + } + p = ngx_cpymem(p, ctx->name.data, ctx->name.len); /* ctx->stream_path holds initial part of stream file path @@ -1417,43 +1460,44 @@ ngx_rtmp_hls_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v) } -static time_t -ngx_rtmp_hls_cleanup(void *data) +static ngx_int_t +ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen) { - ngx_rtmp_hls_cleanup_t *cleanup = data; - ngx_dir_t dir; - time_t next, mtime, max_age; + time_t mtime, max_age; ngx_err_t err; - ngx_str_t name; + ngx_str_t name, spath; u_char *p; - static u_char path[NGX_MAX_PATH + 1]; + 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", - &cleanup->path, cleanup->playlen); + "hls: cleanup path='%V' playlen=%M", + ppath, playlen); - next = cleanup->playlen / 500; - - if (ngx_open_dir(&cleanup->path, &dir) != NGX_OK) { + 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'", &cleanup->path); - return next; + "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) { - ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err, - "hls: cleanup " ngx_read_dir_n - " \"%V\" failed", &cleanup->path); + if (err == NGX_ENOMOREFILES) { + return nentries - nerased; } - break; + 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); @@ -1463,11 +1507,48 @@ ngx_rtmp_hls_cleanup(void *data) 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 = cleanup->playlen / 500; + max_age = playlen / 500; } else if (name.len >= 5 && name.data[name.len - 5] == '.' && name.data[name.len - 4] == 'm' && @@ -1475,7 +1556,7 @@ ngx_rtmp_hls_cleanup(void *data) name.data[name.len - 2] == 'u' && name.data[name.len - 1] == '8') { - max_age = cleanup->playlen / 1000; + max_age = playlen / 1000; } else { ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0, @@ -1483,23 +1564,6 @@ ngx_rtmp_hls_cleanup(void *data) continue; } - p = ngx_snprintf(path, sizeof(path) - 1, "%V/%V", - &cleanup->path, &name); - *p = 0; - - 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 " \"%s\" failed", - path); - - continue; - } - - /*TODO: nested cleanup?*/ - if (!ngx_de_is_file(&dir)) { - continue; - } - mtime = ngx_de_mtime(&dir); if (mtime + max_age > ngx_cached_time->sec) { continue; @@ -1509,14 +1573,25 @@ ngx_rtmp_hls_cleanup(void *data) "hls: cleanup '%V' mtime=%T age=%T", &name, mtime, ngx_cached_time->sec - mtime); - if (ngx_delete_file(path) != NGX_OK) { + if (ngx_delete_file(spath.data) != NGX_OK) { ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno, - "hls: cleanup error '%s'", path); + "hls: cleanup error '%V'", &spath); continue; } - } - return next; + 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; } @@ -1581,7 +1656,7 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf) conf->sync = NGX_CONF_UNSET; conf->playlen = NGX_CONF_UNSET; conf->continuous = NGX_CONF_UNSET; - conf->nodelete = NGX_CONF_UNSET; + conf->nested = NGX_CONF_UNSET; return conf; } @@ -1601,7 +1676,7 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child) 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->nested, prev->nested, 0); if (conf->fraglen) { conf->winfrags = conf->playlen / conf->fraglen;