forked from DDVTECH/mistserver
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathhls_support.cpp
779 lines (682 loc) · 35.3 KB
/
hls_support.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
#include "hls_support.h"
#include "langcodes.h" /*LTS*/
#include "stream.h"
#include <cstdlib>
#include <iomanip>
namespace HLS{
// TODO: Prefix could be made better
// Needed for grouping renditions in master manifest
const std::string groupIdPrefix = "vid-";
// max partial fragment duration in s
const float partDurationMax = (partDurationMaxMs + 4) / 1000.0;
// TODO: Advance Part Limit
/*If the _HLS_msn is greater than the Media Sequence Number of the last
Media Segment in the current Playlist plus two, or if the _HLS_partIf the _HLS_msn is greater than
the Media Sequence Number of the last Media Segment in the current Playlist plus two, or if the
_HLS_part exceeds the last Partial Segment in the current Playlist by the Advance Part Limit, then
the server SHOULD immediately return Bad Request, such as HTTP 400 exceeds the last Partial
Segment in the current Playlist by the Advance Part Limit, then the server SHOULD immediately
return Bad Request, such as HTTP 400*/
// NOTE: Not implementing this gives freedom to play with jitter duration
const uint32_t advancePartLimit =
std::min((int)std::ceil(3.0 / partDurationMax), 3); // Ref: RFC8216, 6.2.5.2
const struct{
bool pdu; ///< Playlist Delta Update
bool pduV2; ///< TODO: Playlist Delta Update v2: skips EXT-X-DATERANGE
bool bpr; ///< Blocking Playlist Reload
bool parts; ///< Partial Fragments
bool tags; ///< True if any of the above is true
}serverSupport ={
#ifdef NOLLHLS
false, false, false, false, false,
#else
true, false, true, true, true,
#endif
};
/// lastms calculation incorporating jitter duration
/// Always ensures the (lastms <= current time - jitter duration)
uint64_t getLastms(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect,
const size_t trackIdx, const uint64_t streamStartTime){
std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin();
uint64_t maxJitter = 0;
u_int64_t minKeepAway = 0;
for (; it != userSelect.end(); it++){
minKeepAway = M.getMinKeepAway(it->first);
if (minKeepAway > maxJitter){maxJitter = minKeepAway;}
}
return std::min(M.getLastms(trackIdx), Util::unixMS() - streamStartTime - minKeepAway);
}
/// Calculate HLS media playlist version compatibility
/// \return version number
uint16_t calcManifestVersion(const std::string &hlsSkip){
// Server and Client support skipping media segments
if (serverSupport.tags && serverSupport.pdu && (hlsSkip.compare("YES") == 0)){return 9;}
// Server and Client support skipping ext-x-daterange along with media segments
if (serverSupport.tags && serverSupport.pduV2 && (hlsSkip.compare("v2") == 0)){return 10;}
// Default, lowest version supported
return 6;
}
/// returns the main track id provided in master manifest if valid
/// else returns the current valid main track id
size_t getTimingTrackId(const DTSC::Meta &M, const std::string &mTrack, const size_t mSelTrack){
return (mTrack.size() && (M.getValidTracks().count(atoll(mTrack.c_str()))))
? atoll(mTrack.c_str())
: mSelTrack;
}
/// Return live edge fragment duration
uint64_t getLastFragDur(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect, const TrackData &trackData, const uint64_t hlsMsnNr,
const DTSC::Fragments &fragments, const DTSC::Keys &keys){
return std::min(
getLastms(M, userSelect, trackData.timingTrackId, trackData.systemBoot + trackData.bootMsOffset),
getLastms(M, userSelect, trackData.requestTrackId,
trackData.systemBoot + trackData.bootMsOffset)) -
keys.getTime(fragments.getFirstKey(hlsMsnNr));
}
/// Waits until the requested fragment & partial fragment are available
/// Returns 400 if specific part is requested without a specific MSN
/// Returns 400 if requested MSN > the real live edge MSN plus two
/// Returns 503 if time spent in BPR > 3x Target Duration
uint32_t blockPlaylistReload(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect, const TrackData &trackData,
const HlsSpecData &hlsSpecData, const DTSC::Fragments &fragments,
const DTSC::Keys &keys){
// Return if forced noLLHLS
if (trackData.noLLHLS){return 0;}
// Check BPR request validity
if (hlsSpecData.hlsMsn.empty() && hlsSpecData.hlsPart.size()){return 400;}
if (atol(hlsSpecData.hlsMsn.c_str()) > (fragments.getEndValid() - 1 + 2)){return 400;}
// BPR logic only if live & _HLS_msn requested
if (trackData.isLive && hlsSpecData.hlsMsn.size()){
DEBUG_MSG(5, "Requesting media playlist: Track %zu, MSN %s, part: %s",
trackData.timingTrackId, hlsSpecData.hlsMsn.c_str(), hlsSpecData.hlsPart.c_str());
uint64_t hlsMsnNr = atol(hlsSpecData.hlsMsn.c_str());
uint64_t hlsPartNr = atol(hlsSpecData.hlsPart.c_str()) + 1; // base 1
// if hlsPart empty (HLS spec) OR if fragment hlsMsn is complete
// THEN request part 1 of MSN++
if (hlsSpecData.hlsPart.empty()){hlsPartNr = 1;}
if (fragments.getDuration(hlsMsnNr)){
hlsMsnNr++;
hlsPartNr = 1;
}
uint64_t lastFragmentDur = getLastFragDur(M, userSelect, trackData, hlsMsnNr, fragments, keys);
std::ldiv_t res = std::ldiv(lastFragmentDur, partDurationMaxMs);
DEBUG_MSG(5, "req MSN %" PRIu64 " fin MSN %zu, req Part %" PRIu64 " fin Part %ld", hlsMsnNr,
(fragments.getEndValid() - 2), hlsPartNr, res.quot);
// BPR Time limit = 3x Target Duration (per HLS spec)
// + Jitter duration (per Mist feature)
// + 1x Target Duration (extra margin of safety for jitters)
int64_t bprTimeLimit = (4 * trackData.targetDurationMax * 1000) +
std::max(M.getMinKeepAway(trackData.timingTrackId),
M.getMinKeepAway(trackData.requestTrackId));
while (hlsPartNr > res.quot){
if (bprTimeLimit < 1){return 503;}
DEBUG_MSG(5, "Part Block: req %" PRIu64 " fin %ld", hlsPartNr, res.quot);
Util::wait(partDurationMaxMs - res.rem + 25);
bprTimeLimit -= (partDurationMaxMs - res.rem + 25);
lastFragmentDur = getLastFragDur(M, userSelect, trackData, hlsMsnNr, fragments, keys);
res = std::ldiv(lastFragmentDur, partDurationMaxMs);
}
}
return 0;
}
/// Populate FragmentData struct to be used for media manifest generation
void populateFragmentData(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect, FragmentData &fragData, const TrackData &trackData,
const DTSC::Fragments &fragments, const DTSC::Keys &keys){
fragData.lastMs = std::min(
getLastms(M, userSelect, trackData.requestTrackId, trackData.systemBoot + trackData.bootMsOffset),
getLastms(M, userSelect, trackData.timingTrackId, trackData.systemBoot + trackData.bootMsOffset));
fragData.firstFrag = fragments.getFirstValid();
if (trackData.isLive){
fragData.lastFrag = M.getFragmentIndexForTime(trackData.timingTrackId, fragData.lastMs);
if (fragments.getEndValid() > fragData.lastFrag){
fragData.lastFrag = fragments.getEndValid();
}
}else{
// Override to last fragment if VOD
fragData.lastFrag = fragments.getEndValid() - 1;
}
fragData.currentFrag = fragData.firstFrag;
fragData.startTime = keys.getTime(fragments.getFirstKey(fragData.currentFrag));
fragData.duration = fragments.getDuration(fragData.currentFrag);
// Playlist length limit logic:
// Part 1: Limit any playlist with listlimit config
if (trackData.listLimit &&
(fragData.lastFrag - fragData.currentFrag > trackData.listLimit + 2)){
fragData.currentFrag = fragData.lastFrag - trackData.listLimit;
}
// Part 2: Limit a playlist depending on initial MSN data
// see the NOTE at HLS::getLiveLengthLimit(args)
if (trackData.isLive && (fragData.lastFrag - fragData.currentFrag) > 2){
fragData.currentFrag = std::max(trackData.initMsn, fragData.currentFrag + 2);
}
}
/// Encryption logic to LLHLS playlist
void hlsManifestMediaEncriptionTags(const DTSC::Meta &M, std::stringstream &result,
const size_t timingTid){
if (M.getEncryption(timingTid) == ""){
result << "\r\n#EXT-X-KEY:METHOD=NONE";
}else{
// NOTE:
// Defined encryption methods: NONE, AES-128, and SAMPLE-AES
std::string method = M.getEncryption(timingTid);
std::string uri = "asd";
result << "\r\n#EXT-X-KEY:METHOD=" << method;
result << ",URI=\"" << uri << "\"";
// if (version >= 5){
// result << "\",KEYFORMAT=\"com.apple.streamingkeydelivery\"";
// result << "";
//}
}
}
void addMsnTag(std::stringstream &result, const uint64_t msn){
result << "#EXT-X-MEDIA-SEQUENCE:" << msn << "\r\n";
}
/// Returns skip boundary duration, calculated as 6x max target duration
uint32_t hlsSkipBoundary(const uint32_t targetDurationMax){return targetDurationMax * 6;}
/// Calculates the number full fragments that can be skipped from printing in the manifest
/// and MUST REPLACE the associated tags
void addMediaSkipTag(std::stringstream &result, FragmentData &fragData,
const TrackData &trackData, const uint16_t version){
// NOTE: Skips supported from version >= 9
// Version >=9 supports SKIPPED-SEGMENTS
// TODO: Support for Version 10 playlists
// NOTE: Not implemented only because there is no immediate demand from anyone.
// Adds support for RECENTLY-REMOVED-DATERANGES
if (version >= 9){
uint32_t skips = 0;
const uint32_t skipsFromEnd =
hlsSkipBoundary(trackData.targetDurationMax) / trackData.targetDurationMax + 2;
if ((fragData.lastFrag - fragData.currentFrag) > skipsFromEnd){
skips = ((fragData.lastFrag - fragData.currentFrag) - skipsFromEnd);
}
if (version >= 10){
// TODO: Implement logic for skip calculations date ranges
skips += 0;
}
if (skips){
result << "#EXT-X-SKIP:SKIPPED-SEGMENTS=" << skips << "\r\n";
// TODO: Update with version 10 playlist implementation
// result << ",RECENTLY-REMOVED-DATERANGES=";
fragData.currentFrag += skips;
}
}
}
/// Append result with tags that indicates the server supports LLHLS delivery
void addServerSupportTags(std::stringstream &result, const TrackData &trackData){
if (trackData.noLLHLS || !trackData.isLive){return;}
// TODO: Make ifdef
if (serverSupport.tags){
result << "#EXT-X-SERVER-CONTROL:";
if (serverSupport.bpr){result << "CAN-BLOCK-RELOAD=YES,";}
if (serverSupport.pdu){
result << "CAN-SKIP-UNTIL=" << hlsSkipBoundary(trackData.targetDurationMax) << ",";
}
if (serverSupport.pduV2){result << "CAN-SKIP-DATERANGES=YES,";}
if (serverSupport.parts){
result << "PART-HOLD-BACK=" << partDurationMax * 3; // atleast 3x
result << "\r\n#EXT-X-PART-INF:PART-TARGET=" << partDurationMax;
}
result << "\r\n";
}
}
void addTargetDuration(std::stringstream &result, const uint32_t targetDurationMax){
result << "#EXT-X-TARGETDURATION:" << targetDurationMax << "\r\n";
}
/// Appends result with encrytion / drm data
void addEncriptionTags(std::stringstream &result, const std::string &encryptMethod){
// TODO: Add support for media encryption
if (encryptMethod.size()){
// NOTE:
// Defined encryption methods: NONE, AES-128, and SAMPLE-AES
std::string uri = "asd";
result << "#EXT-X-KEY:METHOD=" << encryptMethod;
result << ",URI=\"" << uri << "\"\r\n";
// if (version >= 5){
// result << "\",KEYFORMAT=\"com.apple.streamingkeydelivery\"";
// result << "";
//}
}
}
void addInitTags(std::stringstream &result, const TrackData &trackData){
// No init data for TS
if (trackData.mediaFormat == ".ts"){return;}
result << "#EXT-X-MAP:URI=\"" << trackData.urlPrefix << "init" << trackData.mediaFormat;
if (trackData.sessionId.size()){result << "?tkn=" << trackData.sessionId;}
result << "\"\r\n";
}
void addMediaBasicTags(std::stringstream &result, const uint16_t version){
result << "#EXTM3U\r\n";
result << "#EXT-X-VERSION:" << version << "\r\n";
}
/// Append result with media meta tags that are in the beginning of the manifest
void addStartingMetaTags(std::stringstream &result, FragmentData &fragData,
const TrackData &trackData, const HlsSpecData &hlsSpecData){
const uint16_t version = calcManifestVersion(hlsSpecData.hlsSkip);
addMediaBasicTags(result, version);
addServerSupportTags(result, trackData);
addInitTags(result, trackData);
addEncriptionTags(result, trackData.encryptMethod);
addTargetDuration(result, trackData.targetDurationMax);
addMsnTag(result, trackData.isLive ? fragData.currentFrag : fragData.firstFrag);
// NOTE: DO NOT move the SKIP tag. Order must be respected per HLS spec.
addMediaSkipTag(result, fragData, trackData, version);
}
/// Appends result with prependStr and timestamp calculated from current time in ms
void addDateTimeTag(std::stringstream &result, const std::string &prependStr,
const uint64_t unixMs){
result << prependStr << Util::getUTCStringMillis(unixMs) << "\r\n";
}
/// Add segment tag to LLHLS playlist
void addFragmentTag(std::stringstream &result, const FragmentData &fragData,
const TrackData &trackData){
result << "#EXTINF:" << std::fixed << std::setprecision(3) << fragData.duration / 1000.0
<< ",\r\n";
// NOTE: HLS spec says it isn't mandatory to add date time tag for every fragment.
// Tests show that there is definitely an influence on consistency for live streams.
// Printing the tag for every fragment tag was the best.
if (trackData.isLive){
addDateTimeTag(result, "#EXT-X-PROGRAM-DATE-TIME:",
trackData.systemBoot + trackData.bootMsOffset + fragData.startTime);
}
result << trackData.urlPrefix << "chunk_" << fragData.startTime << trackData.mediaFormat;
result << "?msn=" << fragData.currentFrag;
result << "&mTrack=" << trackData.timingTrackId;
result << "&dur=" << fragData.duration;
if (trackData.sessionId.size()){result << "&tkn=" << trackData.sessionId;}
result << "\r\n";
}
/// Add partial segment tag to LLHLS playlist
void addPartialTag(std::stringstream &result, const DTSC::Meta &M, const DTSC::Keys &keys,
const FragmentData &fragData, const TrackData &trackData,
const uint32_t partCount, const uint32_t duration){
result << "#EXT-X-PART:DURATION=" << duration / 1000.0;
result << ",URI=\"" << trackData.urlPrefix;
result << "chunk_" << fragData.startTime << "." << partCount << trackData.mediaFormat;
result << "?msn=" << fragData.currentFrag;
result << "&mTrack=" << trackData.timingTrackId;
result << "&dur=" << duration;
if (trackData.sessionId.size()){result << "&tkn=" << trackData.sessionId;}
result << "\"";
// NOTE: INDEPENDENT tags, specified ONLY for VIDEO tracks, indicate the first partial fragment
// closest to the before (live edge - PART-HOLD-BACK) time that a client starts playback from.
if (trackData.isVideo){
uint64_t partStartTime = fragData.startTime + partCount * partDurationMaxMs;
uint32_t partKeyIdx = M.getKeyIndexForTime(trackData.timingTrackId, partStartTime);
uint64_t partKeyIdxTime = M.getTimeForKeyIndex(trackData.timingTrackId, partKeyIdx);
if (partKeyIdxTime == partStartTime){result << ",INDEPENDENT=YES";}
}
result << "\r\n";
}
/// Appends result with partial fragment tags if supported/requested
void addPartialFragmentTags(std::stringstream &result, const DTSC::Meta &M,
FragmentData &fragData, const TrackData &trackData,
const DTSC::Keys &keys){
if (trackData.noLLHLS){return;}
// return if VOD, or no support for server tags, or no support for partial fragments
if (!(trackData.isLive && serverSupport.tags && serverSupport.parts)){return;}
// if fragment is last-but-4th or later
// OR if fragment is 3 target durations from the end
if ((fragData.lastFrag - fragData.currentFrag < 5) ||
((fragData.lastMs - fragData.startTime) <= 3 * trackData.targetDurationMax * 1000)){
std::ldiv_t durationData = std::ldiv(fragData.duration, partDurationMaxMs);
// General case: all partial segments with duration equal to partDurationMax
uint32_t partCount = 0;
for (partCount = 0; partCount < durationData.quot; partCount++){
addPartialTag(result, M, keys, fragData, trackData, partCount, partDurationMaxMs);
}
// Special case: last partial segment (duration < partDurationMaxMs) in any fragment not at
// live edge
if (durationData.rem && (fragData.lastFrag - fragData.currentFrag > 1)){
addPartialTag(result, M, keys, fragData, trackData, partCount, durationData.rem);
}
fragData.partNum = partCount;
}
}
/// Appends result with partial fragment tags, date-time tag and fragment tag for the current
/// fragment
void addMediaTags(std::stringstream &result, const DTSC::Meta &M, FragmentData &fragData,
const TrackData &trackData, const DTSC::Keys &keys){
addPartialFragmentTags(result, M, fragData, trackData, keys);
// do not add the last fragment media tag for the live streams
if (trackData.isLive && (fragData.currentFrag == fragData.lastFrag - 1)){return;}
addFragmentTag(result, fragData, trackData);
}
/// Appends result with partial fragment tags, date-time tags and fragment tags for all fragments
void addMediaFragments(std::stringstream &result, const DTSC::Meta &M, FragmentData &fragData,
const TrackData &trackData, const DTSC::Fragments &fragments,
const DTSC::Keys &keys){
for (; fragData.currentFrag < fragData.lastFrag; fragData.currentFrag++){
fragData.startTime = keys.getTime(fragments.getFirstKey(fragData.currentFrag));
// adjust fragment start time for vod
if (!trackData.isLive){fragData.startTime -= M.getFirstms(trackData.timingTrackId);}
fragData.duration = fragments.getDuration(fragData.currentFrag);
// NOTE: If duration invalid, it's the last fragment, so calculate duration from live edge
// Needed for LLHLS
if (!fragData.duration){fragData.duration = fragData.lastMs - fragData.startTime;}
addMediaTags(result, M, fragData, trackData, keys);
}
}
void addVodEndingTags(std::stringstream &result){result << "#EXT-X-ENDLIST\r\n";}
/// Append result with information on alternate renditions, only for LLHLS
void addAltRenditionReports(std::stringstream &result, const DTSC::Meta &M,
const std::map<size_t, Comms::Users> &userSelect,
const FragmentData &fragData, const TrackData &trackData){
DTSC::Fragments fragments(M.fragments(trackData.timingTrackId));
std::ldiv_t altPart =
std::ldiv(fragments.getDuration(fragData.currentFrag - 2), partDurationMaxMs);
std::map<size_t, Comms::Users>::const_iterator it = userSelect.end();
for (; it != userSelect.end(); it++){
if (it->first == trackData.timingTrackId){continue;}
result << "#EXT-X-RENDITION-REPORT:";
result << "URI=\"" << it->first << "/index.m3u8\"";
if (fragData.partNum){
result << ",LAST-MSN=" << fragData.currentFrag - 1;
result << ",LAST-PART=" << fragData.partNum - 1 << "\r\n";
}else{
result << ",LAST-MSN=" << fragData.currentFrag - 2;
result << ",LAST-PART=" << ((altPart.quot - 1) + (altPart.rem ? 1 : 0)) << "\r\n";
}
}
}
/// Append result with hinted part, only for LLHLS
void addPreloadHintTag(std::stringstream &result, const FragmentData &fragData,
const TrackData &trackData){
result << "#EXT-X-PRELOAD-HINT:TYPE=PART,URI=\"" << trackData.urlPrefix << "chunk_";
result << fragData.startTime << "." << fragData.partNum << trackData.mediaFormat;
result << "?msn=" << fragData.currentFrag - 1;
result << "&mTrack=" << trackData.timingTrackId;
result << "&dur=" << partDurationMaxMs;
if (trackData.sessionId.size()){result << "&tkn=" << trackData.sessionId;}
result << "\"\r\n";
}
/// Append result with live ending tags (supports llhls tags)
void addLiveEndingTags(std::stringstream &result, const DTSC::Meta &M,
const std::map<size_t, Comms::Users> &userSelect,
const FragmentData &fragData, const TrackData &trackData){
if (trackData.noLLHLS){return;}
if (serverSupport.tags && serverSupport.parts){
addPreloadHintTag(result, fragData, trackData);
addAltRenditionReports(result, M, userSelect, fragData, trackData);
}
}
/// Add respective ending tags for VOD and Live streams
void addEndingTags(std::stringstream &result, const DTSC::Meta &M,
const std::map<size_t, Comms::Users> &userSelect, const FragmentData &fragData,
const TrackData &trackData){
trackData.isLive ? addLiveEndingTags(result, M, userSelect, fragData, trackData)
: addVodEndingTags(result);
}
/// Check if keyframes of given trackId are aligned with that of the main track
/// returns true if aligned
/// keyframe alginment is a MUST for LLHLS track switch
bool checkFramesAlignment(std::stringstream &result, const DTSC::Meta &M,
const MasterData &masterData, const size_t trackId){
bool keyFramesAligned =
masterData.mainTrack == trackId || M.keyTimingsMatch(masterData.mainTrack, trackId);
if (!keyFramesAligned){
result << "## NOTE: Track " << trackId
<< " is available, but ignored because it is not aligned with track "
<< masterData.mainTrack << ".\r\n";
}
return keyFramesAligned;
}
/// Adds EXT-X-MEDIA tag for a given trackId
void addExtXMediaTags(std::stringstream &result, const DTSC::Meta &M,
const MasterData &masterData, const size_t trackId,
const std::string &mediaType, const std::string &grpid,
const uint64_t iFrag){
std::string lang = "";
lang = M.getLang(trackId).empty() ? "und" : M.getLang(trackId);
std::string name = M.getCodec(trackId) + "-";
if (lang == "und"){
char intStr[10];
snprintf(intStr, 10, "%zu", trackId);
name += intStr;
}else{
name += lang;
}
result << "#EXT-X-MEDIA:TYPE=" << mediaType;
result << ",GROUP-ID=\"" << grpid << "\"";
result << ",LANGUAGE=\"" << lang;
if (lang == "und"){result << "-" << trackId;}
result << "\"";
result << ",NAME=\"" << name << "\",URI=\"" << trackId << "/index.m3u8";
result << "?mTrack=" << masterData.mainTrack;
result << "&iMsn=" << iFrag;
if (masterData.sessId.size()){result << "&tkn=" << masterData.sessId;}
if (masterData.noLLHLS){result << "&llhls=0";}
result << "\"\r\n";
}
/// Add HLS basic tags for master manifest
void addMasterBasicTags(std::stringstream &result){
result.str(std::string()); // reset the stream to empty
result << "#EXTM3U\r\n#EXT-X-INDEPENDENT-SEGMENTS\r\n";
}
void addInfTrackTag(std::stringstream &result, const MasterData &masterData,
const std::set<size_t> &aTracks, const size_t tid, const uint64_t iFrag,
const bool keyFramesAligned, const bool isVideo){
result << (keyFramesAligned ? "" : "## DISABLED: ");
result << tid;
if (isVideo && masterData.isTS && aTracks.size() == 1){result << "_" << *aTracks.begin();}
result << "/index.m3u8";
result << "?mTrack=" << masterData.mainTrack;
result << "&iMsn=" << iFrag;
if (masterData.sessId.size()){result << "&tkn=" << masterData.sessId;}
if (masterData.noLLHLS){result << "&llhls=0";}
result << "\r\n";
}
void addInfBWidthTag(std::stringstream &result, const uint64_t bWidth){
result << std::fixed << std::setprecision(0);
result << ",BANDWIDTH=" << bWidth * 1.3 << ",AVERAGE-BANDWIDTH=" << bWidth * 1.1 << "\r\n";
}
void addInfResolFrameRate(std::stringstream &result, const DTSC::Meta &M,
const std::string &resolution, const size_t trackId){
result << ",RESOLUTION=" << resolution;
if (M.getFpks(trackId)){result << ",FRAME-RATE=" << (float)M.getFpks(trackId) / 1000;}
}
void addInfCodecsTag(std::stringstream &result, const DTSC::Meta &M, const size_t tid,
const std::string &audCodecsStr){
result << "CODECS=\"" << Util::codecString(M.getCodec(tid), M.getInit(tid));
result << audCodecsStr << "\"";
}
/// creates group id based on the resolution of the track
void getGroupId(std::stringstream &grpid, const DTSC::Meta &M, const size_t tid){
grpid.str(std::string()); // reset the stream to empty
grpid << groupIdPrefix << M.getWidth(tid) << "x" << M.getHeight(tid);
}
void addInfMainTag(std::stringstream &result){result << "#EXT-X-STREAM-INF:";}
/// add #EXT-X-STREAM-INF for audio only streams
void addAudInfStreamTags(std::stringstream &result, const DTSC::Meta &M,
const MasterData &masterData, const std::set<size_t> &aTracks,
const uint64_t iFrag){
if (aTracks.size()){
for (std::set<size_t>::iterator ita = aTracks.begin(); ita != aTracks.end(); ita++){
uint64_t bWidth = M.getBps(*ita);
bWidth = (bWidth < 5 ? 5 : bWidth) * 8;
addInfMainTag(result);
addInfCodecsTag(result, M, *ita, "");
addInfBWidthTag(result, bWidth);
addInfTrackTag(result, masterData, aTracks, *ita, iFrag, true, false);
}
}
}
/// Add #EXT-X-STREAM-INF tags for video groups
void addVidInfStreamTags(std::stringstream &result, const DTSC::Meta &M,
const MasterData &masterData, const std::set<std::string> &aCodecs,
const std::set<size_t, std::less<size_t> > &vTracks,
const std::set<size_t> &aTracks,
const std::multimap<std::string, size_t> &vidGroups,
const uint64_t asBWidth, const uint64_t iFrag,
const uint32_t sTracksSize){
// Create a comma separated string containing all audio codecs
std::string audCodecsStr = ""; // comma separated string of "audioCodecs"
if (aCodecs.size()){
for (std::set<std::string>::iterator it = aCodecs.begin(); it != aCodecs.end(); ++it){
audCodecsStr += ",";
audCodecsStr += *it;
}
}
std::string assocGroupTag = "";
// add associate group tags
if ((!masterData.isTS && aTracks.size()) || (masterData.isTS && aTracks.size() > 1)){
assocGroupTag += "AUDIO=\"aud\",";
}
if (sTracksSize){assocGroupTag += "SUBTITLES=\"sub\",";}
for (std::set<size_t>::iterator itr = vTracks.begin(); itr != vTracks.end(); itr++){
std::map<std::string, size_t>::const_iterator it = vidGroups.begin();
while (it != vidGroups.end()){
if (*itr == it->second){break;}
it++;
}
if (it == vidGroups.end()){continue;}
bool keyFramesAligned = checkFramesAlignment(result, M, masterData, it->second);
if (keyFramesAligned){
uint64_t bWidth = M.getBps(it->second);
bWidth = ((bWidth < 5 ? 5 : bWidth) + asBWidth) * 8;
addInfMainTag(result);
result << assocGroupTag;
addInfCodecsTag(result, M, it->second, audCodecsStr);
addInfResolFrameRate(result, M, it->first.substr(groupIdPrefix.size()), it->second);
addInfBWidthTag(result, bWidth);
addInfTrackTag(result, masterData, aTracks, it->second, iFrag, keyFramesAligned, true);
}
}
}
/// Adds EXT-X-MEDIA:TYPE=SUBTITLES tags to the manifest
uint64_t addSubTags(std::stringstream &result, const DTSC::Meta &M, const MasterData &masterData,
const std::set<size_t> &sTracks, const uint64_t iFrag){
uint64_t subBWidth = 0;
for (std::set<size_t>::iterator its = sTracks.begin(); its != sTracks.end(); its++){
addExtXMediaTags(result, M, masterData, *its, "SUBTITLES", "sub", iFrag);
subBWidth = std::max(subBWidth, M.getBps(*its));
}
return subBWidth;
}
/// Adds EXT-X-MEDIA:TYPE=AUDIO tags to the manifest
uint64_t addAudTags(std::stringstream &result, std::set<std::string> &aCodecs,
const DTSC::Meta &M, const MasterData &masterData,
const std::set<size_t> &aTracks, const uint64_t iFrag,
const uint32_t vTracksLength){
// if video tracks available, audio tracks as EXT-X-MEDIA, else as EXT-X-STREAM-INF
uint64_t audBWidth = 0;
if (vTracksLength){
for (std::set<size_t>::iterator ita = aTracks.begin(); ita != aTracks.end(); ita++){
if (!masterData.isTS || (masterData.isTS && aTracks.size() > 1)){
addExtXMediaTags(result, M, masterData, *ita, "AUDIO", "aud", iFrag);
}
aCodecs.insert(Util::codecString(M.getCodec(*ita), M.getInit(*ita)));
audBWidth = std::max(audBWidth, M.getBps(*ita));
}
}
return audBWidth;
}
/// Adds EXT-X-MEDIA:TYPE=VIDEO tags to the manifest
void addVidTags(std::stringstream &result, std::stringstream &grpid, const DTSC::Meta &M,
const MasterData &masterData, const std::set<size_t, std::less<size_t> > &vTracks,
const std::multimap<std::string, size_t> &vidGroups, const uint64_t iFrag,
const uint32_t aTracksSize){
// if audio tracks available, video tracks are EXT-X-STREAM-INF
std::set<size_t>::iterator itv = aTracksSize ? vTracks.end() : vTracks.begin();
for (; itv != vTracks.end(); itv++){
getGroupId(grpid, M, *itv);
if (vidGroups.find(grpid.str()) != vidGroups.end() && vidGroups.count(grpid.str()) == 1){
continue;
}
if (checkFramesAlignment(result, M, masterData, *itv)){
addExtXMediaTags(result, M, masterData, *itv, "VIDEO", grpid.str(), iFrag);
}
}
}
/// Sorts all tracks into video, audio & subtitle sets & generate rendition groups
void sortTracks(const DTSC::Meta &M, const std::map<size_t, Comms::Users> &userSelect,
std::stringstream &grpid, std::set<size_t, std::less<size_t> > &vTracks,
std::set<size_t> &aTracks, std::set<size_t> &sTracks,
std::multimap<std::string, size_t> &vidGroups){
std::map<size_t, Comms::Users>::const_iterator it = userSelect.begin();
for (; it != userSelect.end(); it++){
if (M.getType(it->first) == "video"){
vTracks.insert(it->first);
getGroupId(grpid, M, it->first);
vidGroups.insert(std::pair<std::string, size_t>(grpid.str(), it->first));
}
if (M.getType(it->first) == "audio"){aTracks.insert(it->first);}
if (M.getCodec(it->first) == "subtitle"){sTracks.insert(it->first);}
}
}
/// This is a hack to ensure the LLHLS playback starts as close as possible to the live edge
u_int16_t getLiveLengthLimit(const MasterData &masterData){
// NOTE:
// TL;DR: Apple cleints receive the shortest media playlist to ensure a consistent playback at
// least possible latency.
// Long story: After experimentation, it was found that Apple clients start streaming
// consistently at least latency when the first playlist is short, i.e., ~1 full fragment (+
// partial fragment if any) short. From that point, the playlist can grow with the stream.
// TODO: remove this when the above issue with apple clients is observed no more.
return (masterData.userAgent.find(" Mac OS ") != std::string::npos) ? 3 : 6;
}
/// Get the first fragment number to be printed in the playlist
u_int64_t getInitFragment(const DTSC::Meta &M, const MasterData &masterData){
if (M.getLive()){
DTSC::Fragments fragments(M.fragments(masterData.mainTrack));
DTSC::Keys keys(M.keys(masterData.mainTrack));
u_int64_t iFrag = std::max(fragments.getEndValid() -
(masterData.noLLHLS ? 10 : getLiveLengthLimit(masterData)),
fragments.getFirstValid());
uint64_t minDur =
M.getLastms(masterData.mainTrack) - keys.getTime(fragments.getFirstKey(iFrag));
if (minDur < HLS::partDurationMaxMs * 3){iFrag--;}
return iFrag;
}else{
return 0;
}
}
/// Appends master manifest to result
void addMasterManifest(std::stringstream &result, const DTSC::Meta &M,
const std::map<size_t, Comms::Users> &userSelect,
const MasterData &masterData){
std::set<size_t, std::less<size_t> > vTracks;
std::set<size_t> aTracks;
std::set<size_t> sTracks;
std::stringstream grpid; ///< used for vidGroups.
std::multimap<std::string, size_t> vidGroups; ///< stores 1 video track id from a groupid
std::set<std::string> aCodecs; ///< a set to store unique audio codecs
sortTracks(M, userSelect, grpid, vTracks, aTracks, sTracks, vidGroups);
const uint64_t iFrag = getInitFragment(M, masterData);
addMasterBasicTags(result);
addVidTags(result, grpid, M, masterData, vTracks, vidGroups, iFrag, aTracks.size());
uint64_t audBWidth = addAudTags(result, aCodecs, M, masterData, aTracks, iFrag, vTracks.size());
uint64_t subBWidth = addSubTags(result, M, masterData, sTracks, iFrag);
if (vidGroups.size()){
addVidInfStreamTags(result, M, masterData, aCodecs, vTracks, aTracks, vidGroups,
audBWidth + subBWidth, iFrag, sTracks.size());
}else{
addAudInfStreamTags(result, M, masterData, aTracks, iFrag);
}
}
/// returns the end time for a given partial fragment
/// returns 0 for a hinted part which never got created
uint64_t getPartTargetTime(const DTSC::Meta &M, const uint32_t idx, const uint32_t mTrack,
const uint64_t startTime, const uint64_t msn, const uint32_t part){
DTSC::Fragments fragments(M.fragments(mTrack));
// Estimate the target end time for a given part
// 50 ms is margin of safety to accommodate inconsistencies
const uint64_t calcTargetTime = startTime + (part + 1) * partDurationMaxMs + 50;
uint64_t lastms = std::min(M.getLastms(mTrack), M.getLastms(idx));
uint16_t count = 0;
// wait until estimated target end time is <= lastms for the track
while (calcTargetTime > lastms && count++ < 50){
Util::wait(calcTargetTime - lastms);
lastms = std::min(M.getLastms(mTrack), M.getLastms(idx));
}
// Duration maybe invalid, indicating msn is not complete
// But the part is ready. So return the end time
uint64_t duration = fragments.getDuration(msn);
if (!duration){return startTime + ((part + 1) * partDurationMaxMs);}
// If duration valid, MSN is fully finished
// Possible that the last partial fragment duration < partDurationMaxMs
// Find the exact duration of the last partial fragment
uint64_t partTargetTime =
std::min(startTime + duration, startTime + ((part + 1) * partDurationMaxMs));
if (duration && (partTargetTime - startTime) > duration){return 0;}
return partTargetTime;
}
}// namespace HLS