@@ -34,6 +34,7 @@ static ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s);
34
34
35
35
typedef struct {
36
36
uint64_t id ;
37
+ uint64_t key_id ;
37
38
double duration ;
38
39
unsigned active :1 ;
39
40
unsigned discont :1 ; /* before */
@@ -49,22 +50,26 @@ typedef struct {
49
50
typedef struct {
50
51
unsigned opened :1 ;
51
52
52
- ngx_file_t file ;
53
+ ngx_rtmp_mpegts_file_t file ;
53
54
54
55
ngx_str_t playlist ;
55
56
ngx_str_t playlist_bak ;
56
57
ngx_str_t var_playlist ;
57
58
ngx_str_t var_playlist_bak ;
58
59
ngx_str_t stream ;
60
+ ngx_str_t keyfile ;
59
61
ngx_str_t name ;
62
+ u_char key [16 ];
60
63
61
64
uint64_t frag ;
62
65
uint64_t frag_ts ;
66
+ uint64_t key_id ;
63
67
ngx_uint_t nfrags ;
64
68
ngx_rtmp_hls_frag_t * frags ; /* circular 2 * winfrags + 1 */
65
69
66
70
ngx_uint_t audio_cc ;
67
71
ngx_uint_t video_cc ;
72
+ ngx_uint_t key_frags ;
68
73
69
74
uint64_t aframe_base ;
70
75
uint64_t aframe_num ;
@@ -79,6 +84,7 @@ typedef struct {
79
84
typedef struct {
80
85
ngx_str_t path ;
81
86
ngx_msec_t playlen ;
87
+ ngx_int_t frags_per_key ;
82
88
} ngx_rtmp_hls_cleanup_t ;
83
89
84
90
@@ -103,6 +109,10 @@ typedef struct {
103
109
ngx_array_t * variant ;
104
110
ngx_str_t base_url ;
105
111
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 ;
106
116
} ngx_rtmp_hls_app_conf_t ;
107
117
108
118
@@ -269,6 +279,34 @@ static ngx_command_t ngx_rtmp_hls_commands[] = {
269
279
offsetof(ngx_rtmp_hls_app_conf_t , granularity ),
270
280
NULL },
271
281
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
+
272
310
ngx_null_command
273
311
};
274
312
@@ -445,14 +483,14 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
445
483
{
446
484
static u_char buffer [1024 ];
447
485
ngx_fd_t fd ;
448
- u_char * p ;
486
+ u_char * p , * end ;
449
487
ngx_rtmp_hls_ctx_t * ctx ;
450
488
ssize_t n ;
451
489
ngx_rtmp_hls_app_conf_t * hacf ;
452
490
ngx_rtmp_hls_frag_t * f ;
453
491
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 ;
456
494
457
495
458
496
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)
477
515
}
478
516
}
479
517
480
- p = ngx_snprintf (buffer , sizeof (buffer ),
518
+ p = buffer ;
519
+ end = p + sizeof (buffer );
520
+
521
+ p = ngx_slprintf (buffer , end ,
481
522
"#EXTM3U\n"
482
523
"#EXT-X-VERSION:3\n"
483
524
"#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
+ }
489
531
490
532
n = ngx_write_fd (fd , buffer , p - buffer );
491
533
if (n < 0 ) {
@@ -497,20 +539,38 @@ ngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)
497
539
}
498
540
499
541
sep = hacf -> nested ? (hacf -> base_url .len ? "/" : "" ) : "-" ;
542
+ key_sep = hacf -> nested ? (hacf -> keys_url .len ? "/" : "" ) : "-" ;
500
543
501
544
name_part .len = 0 ;
502
545
if (!hacf -> nested || hacf -> base_url .len ) {
503
546
name_part = ctx -> name ;
504
547
}
505
548
549
+ key_name_part .len = 0 ;
550
+ if (!hacf -> nested || hacf -> keys_url .len ) {
551
+ key_name_part = ctx -> name ;
552
+ }
553
+
506
554
for (i = 0 ; i < ctx -> nfrags ; i ++ ) {
507
555
f = ngx_rtmp_hls_get_frag (s , i );
508
556
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 ,
511
572
"#EXTINF:%.3f,\n"
512
573
"%V%V%s%uL.ts\n" ,
513
- f -> discont ? "#EXT-X-DISCONTINUITY\n" : "" ,
514
574
f -> duration , & hacf -> base_url , & name_part , sep , f -> id );
515
575
516
576
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)
762
822
ngx_log_debug1 (NGX_LOG_DEBUG_RTMP , s -> connection -> log , 0 ,
763
823
"hls: close fragment n=%uL" , ctx -> frag );
764
824
765
- ngx_close_file ( ctx -> file . fd );
825
+ ngx_rtmp_mpegts_close_file ( & ctx -> file );
766
826
767
827
ctx -> opened = 0 ;
768
- ctx -> file .fd = NGX_INVALID_FILE ;
769
828
770
829
ngx_rtmp_hls_next_frag (s );
771
830
@@ -780,6 +839,7 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
780
839
ngx_int_t discont )
781
840
{
782
841
uint64_t id ;
842
+ ngx_fd_t fd ;
783
843
ngx_uint_t g ;
784
844
ngx_rtmp_hls_ctx_t * ctx ;
785
845
ngx_rtmp_hls_frag_t * f ;
@@ -803,32 +863,69 @@ ngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,
803
863
id = (uint64_t ) (id / g ) * g ;
804
864
}
805
865
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 );
807
883
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
+ }
812
890
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
+ }
814
898
815
- ctx -> file . log = s -> connection -> log ;
899
+ ngx_close_file ( fd ) ;
816
900
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
+ }
818
909
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 );
821
916
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
+ {
825
921
return NGX_ERROR ;
826
922
}
827
923
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" );
832
929
return NGX_ERROR ;
833
930
}
834
931
@@ -1258,9 +1355,35 @@ ngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
1258
1355
1259
1356
* p = 0 ;
1260
1357
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 );
1264
1387
1265
1388
if (hacf -> continuous ) {
1266
1389
ngx_rtmp_hls_restore_stream (s );
@@ -1973,6 +2096,13 @@ ngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)
1973
2096
{
1974
2097
max_age = playlen / 1000 ;
1975
2098
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
+
1976
2106
} else {
1977
2107
ngx_log_debug1 (NGX_LOG_DEBUG_RTMP , ngx_cycle -> log , 0 ,
1978
2108
"hls: cleanup skip unknown file type '%V'" , & name );
@@ -2088,6 +2218,8 @@ ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)
2088
2218
conf -> audio_buffer_size = NGX_CONF_UNSET_SIZE ;
2089
2219
conf -> cleanup = NGX_CONF_UNSET ;
2090
2220
conf -> granularity = NGX_CONF_UNSET ;
2221
+ conf -> keys = NGX_CONF_UNSET ;
2222
+ conf -> frags_per_key = NGX_CONF_UNSET ;
2091
2223
2092
2224
return conf ;
2093
2225
}
@@ -2122,6 +2254,10 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
2122
2254
ngx_conf_merge_value (conf -> cleanup , prev -> cleanup , 1 );
2123
2255
ngx_conf_merge_str_value (conf -> base_url , prev -> base_url , "" );
2124
2256
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 );
2125
2261
2126
2262
if (conf -> fraglen ) {
2127
2263
conf -> winfrags = conf -> playlen / conf -> fraglen ;
@@ -2160,6 +2296,42 @@ ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)
2160
2296
}
2161
2297
}
2162
2298
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
+
2163
2335
ngx_conf_merge_str_value (conf -> path , prev -> path , "" );
2164
2336
2165
2337
return NGX_CONF_OK ;
0 commit comments