@@ -17,7 +17,7 @@ public override string ToString()
17
17
18
18
sb . AppendLine ( "#EXTM3U" ) ;
19
19
20
- if ( FileMetadata ? . ToString ( ) is { Length : > 0 } metadataString )
20
+ if ( FileMetadata ? . ToString ( ) is { Length : > 0 } metadataString )
21
21
{
22
22
sb . AppendLine ( metadataString ) ;
23
23
}
@@ -54,44 +54,49 @@ public enum PlaylistType
54
54
private const string TWITCH_INFO_KEY = "#EXT-X-TWITCH-INFO:" ;
55
55
56
56
// Generic M3U headers
57
- public uint Version { get ; internal set ; }
58
- public uint StreamTargetDuration { get ; internal set ; }
59
- public PlaylistType Type { get ; internal set ; } = PlaylistType . Unknown ;
60
- public uint MediaSequence { get ; internal set ; }
57
+ public uint ? Version { get ; init ; }
58
+ public uint ? StreamTargetDuration { get ; init ; }
59
+ public PlaylistType ? Type { get ; init ; }
60
+ public uint ? MediaSequence { get ; init ; }
61
61
62
62
// Twitch specific
63
- public uint TwitchLiveSequence { get ; internal set ; }
64
- public decimal TwitchElapsedSeconds { get ; internal set ; }
65
- public decimal TwitchTotalSeconds { get ; internal set ; }
63
+ public uint ? TwitchLiveSequence { get ; init ; }
64
+ public decimal ? TwitchElapsedSeconds { get ; init ; }
65
+ public decimal ? TwitchTotalSeconds { get ; init ; }
66
66
67
67
// Other headers that we don't have dedicated properties for. Useful for debugging.
68
- private readonly List < KeyValuePair < string , string > > _unparsedValues = new ( ) ;
68
+ private List < KeyValuePair < string , string > > _unparsedValues = new ( ) ;
69
69
public IReadOnlyList < KeyValuePair < string , string > > UnparsedValues => _unparsedValues ;
70
70
71
71
public override string ToString ( )
72
72
{
73
73
var sb = new StringBuilder ( ) ;
74
74
var itemSeparator = Environment . NewLine ;
75
75
76
- StringBuilderHelpers . AppendIfNotDefault ( sb , TARGET_VERSION_KEY , Version , itemSeparator ) ;
77
- StringBuilderHelpers . AppendIfNotDefault ( sb , TARGET_DURATION_KEY , StreamTargetDuration , itemSeparator ) ;
78
- if ( Type != PlaylistType . Unknown )
79
- {
80
- sb . Append ( PLAYLIST_TYPE_KEY ) ;
81
- sb . Append ( Type . AsString ( ) ) ;
82
- sb . Append ( itemSeparator ) ;
83
- }
76
+ if ( Version . HasValue )
77
+ sb . AppendKeyValue ( TARGET_VERSION_KEY , Version . Value , itemSeparator ) ;
78
+
79
+ if ( StreamTargetDuration . HasValue )
80
+ sb . AppendKeyValue ( TARGET_DURATION_KEY , StreamTargetDuration . Value , itemSeparator ) ;
81
+
82
+ if ( Type . HasValue )
83
+ sb . AppendKeyValue ( PLAYLIST_TYPE_KEY , Type . Value . AsString ( ) , itemSeparator ) ;
84
+
85
+ if ( MediaSequence . HasValue )
86
+ sb . AppendKeyValue ( MEDIA_SEQUENCE_KEY , MediaSequence . Value , itemSeparator ) ;
87
+
88
+ if ( TwitchLiveSequence . HasValue )
89
+ sb . AppendKeyValue ( TWITCH_LIVE_SEQUENCE_KEY , TwitchLiveSequence . Value , itemSeparator ) ;
84
90
85
- StringBuilderHelpers . AppendIfNotDefault ( sb , MEDIA_SEQUENCE_KEY , MediaSequence , itemSeparator ) ;
86
- StringBuilderHelpers . AppendIfNotDefault ( sb , TWITCH_LIVE_SEQUENCE_KEY , TwitchLiveSequence , itemSeparator ) ;
87
- StringBuilderHelpers . AppendIfNotDefault ( sb , TWITCH_ELAPSED_SECS_KEY , TwitchElapsedSeconds , itemSeparator ) ;
88
- StringBuilderHelpers . AppendIfNotDefault ( sb , TWITCH_TOTAL_SECS_KEY , TwitchTotalSeconds , itemSeparator ) ;
91
+ if ( TwitchElapsedSeconds . HasValue )
92
+ sb . AppendKeyValue ( TWITCH_ELAPSED_SECS_KEY , TwitchElapsedSeconds . Value , itemSeparator ) ;
93
+
94
+ if ( TwitchTotalSeconds . HasValue )
95
+ sb . AppendKeyValue ( TWITCH_TOTAL_SECS_KEY , TwitchTotalSeconds . Value , itemSeparator ) ;
89
96
90
97
foreach ( var ( key , value ) in _unparsedValues )
91
98
{
92
- sb . Append ( key ) ;
93
- sb . Append ( value ) ;
94
- sb . Append ( itemSeparator ) ;
99
+ sb . AppendKeyValue ( key , value , itemSeparator ) ;
95
100
}
96
101
97
102
if ( sb . Length == 0 )
@@ -125,10 +130,7 @@ public override string ToString()
125
130
sb . AppendLine ( PartInfo . ToString ( ) ) ;
126
131
127
132
if ( ProgramDateTime != default )
128
- {
129
- sb . Append ( "#EXT-X-PROGRAM-DATE-TIME:" ) ;
130
- sb . AppendLine ( ProgramDateTime . ToString ( "O" ) ) ;
131
- }
133
+ sb . AppendKeyValue ( "#EXT-X-PROGRAM-DATE-TIME:" , ProgramDateTime . ToString ( "O" ) , default ) ;
132
134
133
135
if ( ByteRange != default )
134
136
sb . AppendLine ( ByteRange . ToString ( ) ) ;
@@ -188,21 +190,17 @@ public override string ToString()
188
190
ReadOnlySpan < char > keyValueSeparator = stackalloc char [ ] { ',' } ;
189
191
190
192
if ( Type != MediaType . Unknown )
191
- {
192
- sb . Append ( "TYPE=" ) ;
193
- sb . Append ( Type . AsString ( ) ) ;
194
- sb . Append ( keyValueSeparator ) ;
195
- }
193
+ sb . AppendKeyValue ( "TYPE=" , Type . AsString ( ) , keyValueSeparator ) ;
194
+
195
+ if ( ! string . IsNullOrWhiteSpace ( GroupId ) )
196
+ sb . AppendKeyQuoteValue ( "GROUP-ID=" , GroupId , keyValueSeparator ) ;
196
197
197
- StringBuilderHelpers . AppendStringIfNotNullOrEmpty ( sb , "GROUP-ID=" , GroupId , keyValueSeparator ) ;
198
- StringBuilderHelpers . AppendStringIfNotNullOrEmpty ( sb , "NAME=" , Name , keyValueSeparator ) ;
198
+ if ( ! string . IsNullOrWhiteSpace ( Name ) )
199
+ sb . AppendKeyQuoteValue ( "NAME=" , Name , keyValueSeparator ) ;
199
200
200
- sb . Append ( "AUTOSELECT=" ) ;
201
- sb . Append ( BooleanToWord ( AutoSelect ) ) ;
202
- sb . Append ( keyValueSeparator ) ;
201
+ sb . AppendKeyValue ( "AUTOSELECT=" , BooleanToWord ( AutoSelect ) , keyValueSeparator ) ;
203
202
204
- sb . Append ( "DEFAULT=" ) ;
205
- sb . Append ( BooleanToWord ( Default ) ) ;
203
+ sb . AppendKeyValue ( "DEFAULT=" , BooleanToWord ( Default ) , default ) ;
206
204
207
205
return sb . ToString ( ) ;
208
206
@@ -248,12 +246,23 @@ public override string ToString()
248
246
var sb = new StringBuilder ( STREAM_INFO_KEY ) ;
249
247
ReadOnlySpan < char > keyValueSeparator = stackalloc char [ ] { ',' } ;
250
248
251
- StringBuilderHelpers . AppendIfNotDefault ( sb , "PROGRAM-ID=" , ProgramId , keyValueSeparator ) ;
252
- StringBuilderHelpers . AppendIfNotDefault ( sb , "BANDWIDTH=" , Bandwidth , keyValueSeparator ) ;
253
- StringBuilderHelpers . AppendStringIfNotNullOrEmpty ( sb , "CODECS=" , Codecs , keyValueSeparator ) ;
254
- StringBuilderHelpers . AppendIfNotDefault ( sb , "RESOLUTION=" , Resolution , keyValueSeparator ) ;
255
- StringBuilderHelpers . AppendStringIfNotNullOrEmpty ( sb , "VIDEO=" , Video , keyValueSeparator ) ;
256
- StringBuilderHelpers . AppendIfNotDefault ( sb , "FRAME-RATE=" , Framerate , default ) ;
249
+ if ( ProgramId != default )
250
+ sb . AppendKeyValue ( "PROGRAM-ID=" , ProgramId , keyValueSeparator ) ;
251
+
252
+ if ( Bandwidth != default )
253
+ sb . AppendKeyValue ( "BANDWIDTH=" , Bandwidth , keyValueSeparator ) ;
254
+
255
+ if ( ! string . IsNullOrWhiteSpace ( Codecs ) )
256
+ sb . AppendKeyQuoteValue ( "CODECS=" , Codecs , keyValueSeparator ) ;
257
+
258
+ if ( Resolution != default )
259
+ sb . AppendKeyValue ( "RESOLUTION=" , Resolution , keyValueSeparator ) ;
260
+
261
+ if ( ! string . IsNullOrWhiteSpace ( Video ) )
262
+ sb . AppendKeyQuoteValue ( "VIDEO=" , Video , keyValueSeparator ) ;
263
+
264
+ if ( Framerate != default )
265
+ sb . AppendKeyValue ( "FRAME-RATE=" , Framerate , default ) ;
257
266
258
267
return sb . ToString ( ) ;
259
268
}
@@ -284,76 +293,60 @@ public override string ToString()
284
293
sb . Append ( ',' ) ;
285
294
286
295
if ( Live )
287
- {
288
296
sb . Append ( "live" ) ;
289
- }
290
297
291
298
return sb . ToString ( ) ;
292
299
}
293
300
}
294
301
}
302
+ }
295
303
296
- private static class StringBuilderHelpers
304
+ internal static class StringBuilderExtensions
305
+ {
306
+ public static void AppendKeyValue ( this StringBuilder sb , string keyName , int value , ReadOnlySpan < char > end )
297
307
{
298
- public static void AppendIfNotDefault ( StringBuilder sb , string keyName , uint value , ReadOnlySpan < char > end )
299
- {
300
- if ( value == default )
301
- return ;
302
-
303
- sb . Append ( keyName ) ;
304
- sb . Append ( value ) ;
305
- sb . Append ( end ) ;
306
- }
307
-
308
- public static void AppendIfNotDefault ( StringBuilder sb , string keyName , int value , ReadOnlySpan < char > end )
309
- {
310
- if ( value == default )
311
- return ;
312
-
313
- sb . Append ( keyName ) ;
314
- sb . Append ( value ) ;
315
- sb . Append ( end ) ;
316
- }
308
+ sb . Append ( keyName ) ;
309
+ sb . Append ( value ) ;
310
+ sb . Append ( end ) ;
311
+ }
317
312
318
- public static void AppendIfNotDefault ( StringBuilder sb , string keyName , decimal value , ReadOnlySpan < char > end )
319
- {
320
- if ( value == default )
321
- return ;
313
+ public static void AppendKeyValue ( this StringBuilder sb , string keyName , decimal value , ReadOnlySpan < char > end )
314
+ {
315
+ sb . Append ( keyName ) ;
316
+ sb . Append ( value . ToString ( CultureInfo . InvariantCulture ) ) ;
317
+ sb . Append ( end ) ;
318
+ }
322
319
323
- sb . Append ( keyName ) ;
324
- sb . Append ( value . ToString ( CultureInfo . InvariantCulture ) ) ;
325
- sb . Append ( end ) ;
326
- }
320
+ public static void AppendKeyValue ( this StringBuilder sb , string keyName , M3U8 . Stream . ExtStreamInfo . StreamResolution value , ReadOnlySpan < char > end )
321
+ {
322
+ sb . Append ( keyName ) ;
323
+ sb . Append ( value . ToString ( ) ) ;
324
+ sb . Append ( end ) ;
325
+ }
327
326
328
- public static void AppendIfNotDefault ( StringBuilder sb , string keyName , Stream . ExtStreamInfo . StreamResolution value , ReadOnlySpan < char > end )
329
- {
330
- if ( value == default )
331
- return ;
327
+ public static void AppendKeyValue ( this StringBuilder sb , string keyName , string value , ReadOnlySpan < char > end )
328
+ {
329
+ sb . Append ( keyName ) ;
330
+ sb . Append ( value ) ;
331
+ sb . Append ( end ) ;
332
+ }
332
333
333
- sb . Append ( keyName ) ;
334
- sb . Append ( value . ToString ( ) ) ;
335
- sb . Append ( end ) ;
336
- }
334
+ public static void AppendKeyQuoteValue ( this StringBuilder sb , string keyName , string value , ReadOnlySpan < char > end )
335
+ {
336
+ sb . Append ( keyName ) ;
337
337
338
- public static void AppendStringIfNotNullOrEmpty ( StringBuilder sb , string keyName , string value , ReadOnlySpan < char > end )
338
+ if ( ! keyName . EndsWith ( '"' ) )
339
339
{
340
- if ( string . IsNullOrEmpty ( value ) )
341
- return ;
342
-
343
- sb . Append ( keyName ) ;
344
-
345
- if ( ! keyName . EndsWith ( '"' ) )
346
- {
347
- sb . Append ( '"' ) ;
348
- }
349
- sb . Append ( value ) ;
350
340
sb . Append ( '"' ) ;
351
- sb . Append ( end ) ;
352
341
}
342
+
343
+ sb . Append ( value ) ;
344
+ sb . Append ( '"' ) ;
345
+ sb . Append ( end ) ;
353
346
}
354
347
}
355
348
356
- public static class EnumExtensions
349
+ internal static class EnumExtensions
357
350
{
358
351
public static string AsString ( this M3U8 . Stream . ExtMediaInfo . MediaType mediaType )
359
352
{
0 commit comments