Skip to content

Commit 53064a4

Browse files
committed
implemented hls key auto-generation
1 parent 8acacd0 commit 53064a4

File tree

3 files changed

+340
-48
lines changed

3 files changed

+340
-48
lines changed

hls/ngx_rtmp_hls_module.c

+207-35
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s);
3434

3535
typedef struct {
3636
uint64_t id;
37+
uint64_t key_id;
3738
double duration;
3839
unsigned active:1;
3940
unsigned discont:1; /* before */
@@ -49,22 +50,26 @@ typedef struct {
4950
typedef struct {
5051
unsigned opened:1;
5152

52-
ngx_file_t file;
53+
ngx_rtmp_mpegts_file_t file;
5354

5455
ngx_str_t playlist;
5556
ngx_str_t playlist_bak;
5657
ngx_str_t var_playlist;
5758
ngx_str_t var_playlist_bak;
5859
ngx_str_t stream;
60+
ngx_str_t keyfile;
5961
ngx_str_t name;
62+
u_char key[16];
6063

6164
uint64_t frag;
6265
uint64_t frag_ts;
66+
uint64_t key_id;
6367
ngx_uint_t nfrags;
6468
ngx_rtmp_hls_frag_t *frags; /* circular 2 * winfrags + 1 */
6569

6670
ngx_uint_t audio_cc;
6771
ngx_uint_t video_cc;
72+
ngx_uint_t key_frags;
6873

6974
uint64_t aframe_base;
7075
uint64_t aframe_num;
@@ -79,6 +84,7 @@ typedef struct {
7984
typedef struct {
8085
ngx_str_t path;
8186
ngx_msec_t playlen;
87+
ngx_int_t frags_per_key;
8288
} ngx_rtmp_hls_cleanup_t;
8389

8490

@@ -103,6 +109,10 @@ typedef struct {
103109
ngx_array_t *variant;
104110
ngx_str_t base_url;
105111
ngx_int_t granularity;
112+
ngx_flag_t keys;
113+
ngx_str_t keys_path;
114+
ngx_str_t keys_url;
115+
ngx_int_t frags_per_key;
106116
} ngx_rtmp_hls_app_conf_t;
107117

108118

@@ -269,6 +279,34 @@ static ngx_command_t ngx_rtmp_hls_commands[] = {
269279
offsetof(ngx_rtmp_hls_app_conf_t, granularity),
270280
NULL },
271281

282+
{ ngx_string("hls_keys"),
283+
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
284+
ngx_conf_set_flag_slot,
285+
NGX_RTMP_APP_CONF_OFFSET,
286+
offsetof(ngx_rtmp_hls_app_conf_t, keys),
287+
NULL },
288+
289+
{ ngx_string("hls_keys_path"),
290+
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
291+
ngx_conf_set_str_slot,
292+
NGX_RTMP_APP_CONF_OFFSET,
293+
offsetof(ngx_rtmp_hls_app_conf_t, keys_path),
294+
NULL },
295+
296+
{ ngx_string("hls_keys_url"),
297+
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
298+
ngx_conf_set_str_slot,
299+
NGX_RTMP_APP_CONF_OFFSET,
300+
offsetof(ngx_rtmp_hls_app_conf_t, keys_url),
301+
NULL },
302+
303+
{ ngx_string("hls_fragments_per_key"),
304+
NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,
305+
ngx_conf_set_num_slot,
306+
NGX_RTMP_APP_CONF_OFFSET,
307+
offsetof(ngx_rtmp_hls_app_conf_t, frags_per_key),
308+
NULL },
309+
272310
ngx_null_command
273311
};
274312

@@ -445,14 +483,14 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
445483
{
446484
static u_char buffer[1024];
447485
ngx_fd_t fd;
448-
u_char *p;
486+
u_char *p, *end;
449487
ngx_rtmp_hls_ctx_t *ctx;
450488
ssize_t n;
451489
ngx_rtmp_hls_app_conf_t *hacf;
452490
ngx_rtmp_hls_frag_t *f;
453491
ngx_uint_t i, max_frag;
454-
ngx_str_t name_part;
455-
const char *sep;
492+
ngx_str_t name_part, key_name_part;
493+
const char *sep, *key_sep;
456494

457495

458496
hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);
@@ -477,15 +515,19 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
477515
}
478516
}
479517

480-
p = ngx_snprintf(buffer, sizeof(buffer),
518+
p = buffer;
519+
end = p + sizeof(buffer);
520+
521+
p = ngx_slprintf(buffer, end,
481522
"#EXTM3U\n"
482523
"#EXT-X-VERSION:3\n"
483524
"#EXT-X-MEDIA-SEQUENCE:%uL\n"
484-
"#EXT-X-TARGETDURATION:%ui\n"
485-
"%s",
486-
ctx->frag, max_frag,
487-
hacf->type == NGX_RTMP_HLS_TYPE_EVENT ?
488-
"#EXT-X-PLAYLIST-TYPE: EVENT\n" : "");
525+
"#EXT-X-TARGETDURATION:%ui\n",
526+
ctx->frag, max_frag);
527+
528+
if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) {
529+
ngx_slprintf(p, end, "#EXT-X-PLAYLIST-TYPE: EVENT\n");
530+
}
489531

490532
n = ngx_write_fd(fd, buffer, p - buffer);
491533
if (n < 0) {
@@ -497,20 +539,38 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
497539
}
498540

499541
sep = hacf->nested ? (hacf->base_url.len ? "/" : "") : "-";
542+
key_sep = hacf->nested ? (hacf->keys_url.len ? "/" : "") : "-";
500543

501544
name_part.len = 0;
502545
if (!hacf->nested || hacf->base_url.len) {
503546
name_part = ctx->name;
504547
}
505548

549+
key_name_part.len = 0;
550+
if (!hacf->nested || hacf->keys_url.len) {
551+
key_name_part = ctx->name;
552+
}
553+
506554
for (i = 0; i < ctx->nfrags; i++) {
507555
f = ngx_rtmp_hls_get_frag(s, i);
508556

509-
p = ngx_snprintf(buffer, sizeof(buffer),
510-
"%s"
557+
p = buffer;
558+
end = p + sizeof(buffer);
559+
560+
if (f->discont) {
561+
p = ngx_slprintf(p, end, "#EXT-X-DISCONTINUITY\n");
562+
}
563+
564+
if (hacf->keys && (i == 0 || f->id != f->key_id)) {
565+
p = ngx_slprintf(p, end, "#EXT-X-KEY:METHOD=AES-128,"
566+
"URI=\"%V%V%s%uL.key\",IV=0x%032XL",
567+
&hacf->keys_url, &key_name_part,
568+
key_sep, f->key_id);
569+
}
570+
571+
p = ngx_slprintf(p, end,
511572
"#EXTINF:%.3f,\n"
512573
"%V%V%s%uL.ts\n",
513-
f->discont ? "#EXT-X-DISCONTINUITY\n" : "",
514574
f->duration, &hacf->base_url, &name_part, sep, f->id);
515575

516576
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
@@ -762,10 +822,9 @@ ngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s)
762822
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
763823
"hls: close fragment n=%uL", ctx->frag);
764824

765-
ngx_close_file(ctx->file.fd);
825+
ngx_rtmp_mpegts_close_file(&ctx->file);
766826

767827
ctx->opened = 0;
768-
ctx->file.fd = NGX_INVALID_FILE;
769828

770829
ngx_rtmp_hls_next_frag(s);
771830

@@ -780,6 +839,7 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
780839
ngx_int_t discont)
781840
{
782841
uint64_t id;
842+
ngx_fd_t fd;
783843
ngx_uint_t g;
784844
ngx_rtmp_hls_ctx_t *ctx;
785845
ngx_rtmp_hls_frag_t *f;
@@ -803,32 +863,69 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
803863
id = (uint64_t) (id / g) * g;
804864
}
805865

806-
*ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uL.ts", id) = 0;
866+
ngx_sprintf(ctx->stream.data + ctx->stream.len, "%uL.ts%Z", id);
867+
868+
if (hacf->keys) {
869+
if (ctx->key_frags-- <= 1) {
870+
ctx->key_frags = hacf->frags_per_key;
871+
ctx->key_id = id;
872+
873+
if (RAND_bytes(ctx->key, 16) < 0) {
874+
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
875+
"hls: failed to create key");
876+
return NGX_ERROR;
877+
}
878+
879+
ngx_sprintf(ctx->keyfile.data + ctx->keyfile.len, "%uL.ts%Z", id);
880+
881+
fd = ngx_open_file(ctx->keyfile.data, NGX_FILE_RDONLY,
882+
NGX_FILE_OPEN, 0);
807883

808-
ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
809-
"hls: open fragment file='%s', frag=%uL, n=%ui, time=%uL, "
810-
"discont=%i",
811-
ctx->stream.data, ctx->frag, ctx->nfrags, ts, discont);
884+
if (fd == NGX_INVALID_FILE) {
885+
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
886+
"hls: failed to open key file '%s'",
887+
ctx->keyfile.data);
888+
return NGX_ERROR;
889+
}
812890

813-
ngx_memzero(&ctx->file, sizeof(ctx->file));
891+
if (ngx_write_fd(fd, ctx->key, 16) != 16) {
892+
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
893+
"hls: failed to write key file '%s'",
894+
ctx->keyfile.data);
895+
ngx_close_file(fd);
896+
return NGX_ERROR;
897+
}
814898

815-
ctx->file.log = s->connection->log;
899+
ngx_close_file(fd);
816900

817-
ngx_str_set(&ctx->file.name, "hls");
901+
} else if (ngx_set_file_time(ctx->keyfile.data, 0, ngx_cached_time->sec)
902+
!= NGX_OK)
903+
{
904+
ngx_log_error(NGX_LOG_ALERT, s->connection->log, ngx_errno,
905+
ngx_set_file_time_n " '%s' failed",
906+
ctx->keyfile.data);
907+
}
908+
}
818909

819-
ctx->file.fd = ngx_open_file(ctx->stream.data, NGX_FILE_WRONLY,
820-
NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);
910+
ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
911+
"hls: open fragment file='%s', keyfile='%s', "
912+
"frag=%uL, n=%ui, time=%uL, discont=%i",
913+
ctx->stream.data,
914+
ctx->keyfile.data ? ctx->keyfile.data : (u_char *) "",
915+
ctx->frag, ctx->nfrags, ts, discont);
821916

822-
if (ctx->file.fd == NGX_INVALID_FILE) {
823-
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
824-
"hls: error creating fragment file");
917+
if (ngx_rtmp_mpegts_open_file(&ctx->file, ctx->stream.data,
918+
s->connection->log)
919+
!= NGX_OK)
920+
{
825921
return NGX_ERROR;
826922
}
827923

828-
if (ngx_rtmp_mpegts_write_header(&ctx->file) != NGX_OK) {
829-
ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,
830-
"hls: error writing fragment header");
831-
ngx_close_file(ctx->file.fd);
924+
if (hacf->keys &&
925+
ngx_rtmp_mpegts_init_encryption(&ctx->file, ctx->key, 16, id) != NGX_OK)
926+
{
927+
ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
928+
"hls: failed to initialize hls encryption");
832929
return NGX_ERROR;
833930
}
834931

@@ -1258,9 +1355,35 @@ ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
12581355

12591356
*p = 0;
12601357

1261-
ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
1262-
"hls: playlist='%V' playlist_bak='%V' stream_pattern='%V'",
1263-
&ctx->playlist, &ctx->playlist_bak, &ctx->stream);
1358+
/* key path */
1359+
1360+
if (hacf->keys) {
1361+
len = hacf->keys_path.len + 1 + ctx->name.len + NGX_INT64_LEN
1362+
+ sizeof(".key");
1363+
1364+
ctx->keyfile.data = ngx_palloc(s->connection->pool, len);
1365+
if (ctx->keyfile.data == NULL) {
1366+
return NGX_ERROR;
1367+
}
1368+
1369+
p = ngx_cpymem(ctx->keyfile.data, hacf->keys_path.data,
1370+
hacf->keys_path.len);
1371+
1372+
if (p[-1] != '/') {
1373+
*p++ = '/';
1374+
}
1375+
1376+
p = ngx_cpymem(p, ctx->name.data, ctx->name.len);
1377+
*p++ = (hacf->nested ? '/' : '-');
1378+
1379+
ctx->keyfile.len = p - ctx->keyfile.data;
1380+
}
1381+
1382+
ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
1383+
"hls: playlist='%V' playlist_bak='%V' "
1384+
"stream_pattern='%V' keyfile_pattern='%V'",
1385+
&ctx->playlist, &ctx->playlist_bak,
1386+
&ctx->stream, &ctx->keyfile);
12641387

12651388
if (hacf->continuous) {
12661389
ngx_rtmp_hls_restore_stream(s);
@@ -1973,6 +2096,13 @@ ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
19732096
{
19742097
max_age = playlen / 1000;
19752098

2099+
} else if (name.len >= 4 && name.data[name.len - 3] == '.' &&
2100+
name.data[name.len - 2] == 'k' &&
2101+
name.data[name.len - 2] == 'e' &&
2102+
name.data[name.len - 1] == 'y')
2103+
{
2104+
max_age = playlen / 500;
2105+
19762106
} else {
19772107
ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,
19782108
"hls: cleanup skip unknown file type '%V'", &name);
@@ -2088,6 +2218,8 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
20882218
conf->audio_buffer_size = NGX_CONF_UNSET_SIZE;
20892219
conf->cleanup = NGX_CONF_UNSET;
20902220
conf->granularity = NGX_CONF_UNSET;
2221+
conf->keys = NGX_CONF_UNSET;
2222+
conf->frags_per_key = NGX_CONF_UNSET;
20912223

20922224
return conf;
20932225
}
@@ -2122,6 +2254,10 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
21222254
ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1);
21232255
ngx_conf_merge_str_value(conf->base_url, prev->base_url, "");
21242256
ngx_conf_merge_value(conf->granularity, prev->granularity, 0);
2257+
ngx_conf_merge_value(conf->keys, prev->keys, 0);
2258+
ngx_conf_merge_str_value(conf->keys_path, prev->keys_path, "");
2259+
ngx_conf_merge_str_value(conf->keys_url, prev->keys_url, "");
2260+
ngx_conf_merge_value(conf->frags_per_key, prev->frags_per_key, 0);
21252261

21262262
if (conf->fraglen) {
21272263
conf->winfrags = conf->playlen / conf->fraglen;
@@ -2160,6 +2296,42 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
21602296
}
21612297
}
21622298

2299+
if (conf->keys_path.len == 0) {
2300+
conf->keys_path = conf->path;
2301+
}
2302+
2303+
if (conf->keys && conf->keys_path.len && conf->cleanup &&
2304+
ngx_strcmp(conf->keys_path.data, conf->path.data) == 0 &&
2305+
conf->type != NGX_RTMP_HLS_TYPE_EVENT)
2306+
{
2307+
if (conf->keys_path.data[conf->path.len - 1] == '/') {
2308+
conf->keys_path.len--;
2309+
}
2310+
2311+
cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));
2312+
if (cleanup == NULL) {
2313+
return NGX_CONF_ERROR;
2314+
}
2315+
2316+
cleanup->path = conf->keys_path;
2317+
cleanup->playlen = conf->playlen;
2318+
2319+
conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot));
2320+
if (conf->slot == NULL) {
2321+
return NGX_CONF_ERROR;
2322+
}
2323+
2324+
conf->slot->manager = ngx_rtmp_hls_cleanup;
2325+
conf->slot->name = conf->path;
2326+
conf->slot->data = cleanup;
2327+
conf->slot->conf_file = cf->conf_file->file.name.data;
2328+
conf->slot->line = cf->conf_file->line;
2329+
2330+
if (ngx_add_path(cf, &conf->slot) != NGX_OK) {
2331+
return NGX_CONF_ERROR;
2332+
}
2333+
}
2334+
21632335
ngx_conf_merge_str_value(conf->path, prev->path, "");
21642336

21652337
return NGX_CONF_OK;

0 commit comments

Comments
 (0)