/// <summary> /// Parse a HLSStream object from a string /// </summary> /// <param name="m3u8">The string</param> /// <returns>Returns a parsed HLSStream object</returns> private static HLSStream ParseFromString(string m3u8) { string[] lines = m3u8.Split('\n'); HLSStream STREAM = new HLSStream(); List <HLSStreamPart> PARTS = new List <HLSStreamPart>(); for (int i = 0; i < lines.Length; i++) { string line = lines[i]; if (line.Contains("TARGETDURATION")) { STREAM.TargetDuration = line.Split(':')[1]; } if (line.Contains("ALLOW-CACHE")) { STREAM.AllowCache = line.Split(':')[1]; } if (line.Contains("PLAYLIST-TYPE")) { STREAM.PlaylistType = line.Split(':')[1]; } if (line.Contains("KEY")) { STREAM.Key = new HLSEncryptionKey(line.Split(':')[1]); } if (line.Contains("VERSION")) { STREAM.Version = line.Split(':')[1]; } if (line.Contains("MEDIA-SEQUENCE")) { STREAM.MediaSequence = line.Split(':')[1]; } if (line.Contains("EXTINF")) { PARTS.Add(new HLSStreamPart(line, lines[i + 1])); i = i + 2; } } STREAM.Parts = PARTS.ToArray(); return(STREAM); }
/// <summary> /// Parse a HLSStream object from a string /// </summary> /// <param name="m3u8">The string</param> /// <returns>Returns a parsed HLSStream object</returns> private static HLSStream ParseFromString(string m3u8) { string[] lines = m3u8.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); HLSStream STREAM = new HLSStream(); List <HLSStreamPart> PARTS = new List <HLSStreamPart>(); List <HLSPlaylist> Playlist = new List <HLSPlaylist>(); string line; for (int i = 0; i < lines.Length; i++) { line = lines[i]; if (line.Contains("EXTINF")) { /* * 指定每个媒体段(ts)的持续时间,这个仅对其后面的 URI 有效,每两个媒体段 URI 间被这个 tag 分隔开 * 其格式为:#EXTINF:<duration>,<title> */ PARTS.Add(new HLSStreamPart(line, lines[i + 1])); i = i + 1; continue; } else if (line.Contains("EXT-X-ENDLIST")) { break; } else if (line.Contains("EXT-X-STREAM-INF")) { /* * 指定一个包含多媒体信息的 media URI 作为 Playlist,一般做 m3u8 的嵌套使用,它只对紧跟后面的 URI 有效,格式如下:#EXT-X-STREAM-INF:<attribute-list> * 常用的属性如下: * BANDWIDTH:带宽,必须有 * PROGRAM-ID:该值是一个十进制整数,唯一地标识一个在 Playlist 文件范围内的特定的描述。一个 Playlist 文件中可能包含多个有相同 ID 的此 tag * CODECS:指定流的编码类型,不是必须的 * RESOLUTION:分辨率 * AUDIO:这个值必须和 AUDIO 类别的 "EXT-X-MEDIA" 标签中 "GROUP-ID" 属性值相匹配 * VIDEO:同上 */ #region 多码率适配流 var tmpPlaylist = new HLSPlaylist(); //#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=720x406,CODECS="avc1.4d401f,mp4a.40.2" var tmpItems = line.Split("\"".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); var codecsValue = ""; var tmpLine = line; if (tmpItems.Length == 2) { tmpLine = tmpItems[0]; codecsValue = tmpItems[1]; } var items = tmpLine.Split("#:,".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); for (var si = 1 /*跳过EXT-X-STREAM-INF*/; si < items.Length; si++) { var keyValue = items[si].Split("=".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); switch (keyValue[0]) { case "BANDWIDTH": tmpPlaylist.BANDWIDTH = keyValue[1]; break; case "CODECS": tmpPlaylist.CODECS = codecsValue; // keyValue[1]; break; case "PROGRAM-ID": tmpPlaylist.PROGRAM_ID = keyValue[1]; break; case "RESOLUTION": tmpPlaylist.RESOLUTION = keyValue[1]; break; case "AUDIO": tmpPlaylist.AUDIO = keyValue[1]; break; case "VIDEO": tmpPlaylist.VIDEO = keyValue[1]; break; default: break; } } // tmpPlaylist.URI = lines[i + 1]; i = i + 1; Playlist.Add(tmpPlaylist); #endregion continue; } else if (line.Contains("EXT-X-KEY")) { /* * 表示怎么对 media segments 进行解码。其作用范围是下次该 tag 出现前的所有 media URI。 * 格式为:#EXT-X-KEY:<attribute-list> * NONE 或者 AES-128。如果是 NONE,则 URI 以及 IV 属性必须不存在,如果是 AES-128(Advanced Encryption Standard),则 URI 必须存在,IV 可以不存在。 * 对于 AES-128 的情况,keytag 和 URI 属性共同表示了一个 key 文件,通过 URI 可以获得这个 key,如果没有 IV(Initialization Vector),则使用序列号作为 IV 进行编解码,将序列号的高位赋到 16 个字节的 buffer 中,左边补 0;如果有 IV,则将该值当成 16 个字节的 16 进制数。 */ STREAM.Key = new HLSEncryptionKey(line.Substring(line.IndexOf(':') + 1)); continue; } else if (line.Contains("EXT-X-TARGETDURATION")) { /* * 指定当前视频流中的单个切片(即 ts)文件的最大时长(秒)。所以 #EXTINF 中指定的时间长度必须小于或是等于这个最大值。这个 tag 在整个 Playlist 文件中只能出现一次(在嵌套的情况下,一般有真正 * ts url 的 m3u8 才会出现该 tag)。格式为:#EXT-X-TARGETDURATION:<s> */ STREAM.TargetDuration = line.Split(':')[1]; continue; } else if (line.Contains("EXT-X-ALLOW-CACHE")) { STREAM.AllowCache = line.Split(':')[1]; continue; } else if (line.Contains("EXT-X-PLAYLIST-TYPE")) { /* # EXT-X-PLAYLIST-TYPE:<EVENT|VOD> # VOD,即为点播视频,服务器不能改变 Playlist 文件,换句话说就是该视频全部的 ts 文件已经被生成好了 # EVENT,就是实时生成 m3u8 和 ts 文件。服务器不能改变或是删除 Playlist 文件中的任何部分,但是可以向该文件中增加新的一行内容。它的索引文件一直处于动态变化中,播放的时候需要不断下载二级 index 文件 */ STREAM.PlaylistType = line.Split(':')[1]; continue; } else if (line.Contains("EXT-X-VERSION")) { STREAM.Version = line.Split(':')[1]; continue; } else if (line.Contains("EXT-X-MEDIA-SEQUENCE")) { STREAM.MediaSequence = line.Split(':')[1]; continue; } else if (line.Contains("EXTM3U")) { continue; } else if (line.Contains("EXT-X-BYTERANGE")) { /* * 表示媒体段是一个媒体 URI 资源中的一段,只对其后的 media URI 有效, * 格式为:#EXT-X-BYTERANGE:<n>[@o] * n:表示这个区间的大小 * o:表示在 URI 中的 offset */ STREAM.ByteRange = line.Substring(line.IndexOf(':') + 1); continue; } else if (line.Contains("EXT-X-PROGRAM-DATE-TIME"))//#EXT-X-PROGRAM-DATE-TIME:<YYYY-MM-DDThh:mm:ssZ> { STREAM.Program_Date_Time = line.Substring(line.IndexOf(':') + 1); continue; } else if (line.Contains("EXT-X-MEDIA")) { /* #EXT-X-MEDIA:<attribute-list> * 该属性列表中包含:URI、TYPE、GROUP-ID、LANGUAGE、NAME、DEFAULT、AUTOSELECT。 * URI:如果没有,则表示这个 tag 描述的可选择版本在主 PlayList 的 EXT-X-STREAM-INF 中存在 * TYPE:AUDIO and VIDEO * GROUP-ID:具有相同 ID 的 MEDIAtag,组成一组样式 * LANGUAGE:identifies the primary language used in the rendition * NAME:The value is a quoted-string containing a human-readable description of the rendition. If the LANGUAGE attribute is present then this description SHOULD be in that language * DEFAULT:YES 或是 NO,默认是 No,如果是 YES,则客户端会以这种选项来播放,除非用户自己进行选择 * AUTOSELECT:YES 或是 NO,默认是 No,如果是 YES,则客户端会根据当前播放环境来进行选择(用户没有根据自己偏好进行选择的前提下) */ STREAM.Media = line.Substring(line.IndexOf(':') + 1); continue; } else if (line.Contains("EXT-X-DISCONTINUITY"))//当遇到该 tag 的时候说明属性发生了变化 { STREAM.Discontinuity = line.Substring(line.IndexOf(':') + 1); continue; } else if (line.Contains("ZEN-TOTAL-DURATION"))//表示这个 m3u8 所含 ts 的总时间长度 { STREAM.Zen_Total_Duration = line.Substring(line.IndexOf(':') + 1); continue; } } line = null; STREAM.Playlist = Playlist.ToArray(); STREAM.Parts = PARTS.ToArray(); return(STREAM); }
/// <summary> /// Parse a HLSStream object from a string /// </summary> /// <param name="m3u8">The string</param> /// <returns>Returns a parsed HLSStream object</returns> private static HLSStream ParseFromString(string m3u8) { string[] lines = m3u8.Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries); HLSStream STREAM = new HLSStream(); List <HLSStreamPart> PARTS = new List <HLSStreamPart>(); List <HLSPlaylist> Playlist = new List <HLSPlaylist>(); string line; for (int i = 0; i < lines.Length; i++) { line = lines[i]; if (line.Contains("EXTINF")) { PARTS.Add(new HLSStreamPart(line, lines[i + 1])); i = i + 1; continue; } else if (line.Contains("EXT-X-ENDLIST")) { break; } else if (line.Contains("EXTM3U")) { continue; } else if (line.Contains("TARGETDURATION")) { STREAM.TargetDuration = line.Split(':')[1]; continue; } else if (line.Contains("ALLOW-CACHE")) { STREAM.AllowCache = line.Split(':')[1]; continue; } else if (line.Contains("PLAYLIST-TYPE")) { STREAM.PlaylistType = line.Split(':')[1]; continue; } else if (line.Contains("EXT-X-KEY")) { STREAM.Key = new HLSEncryptionKey(line.Substring(line.IndexOf(':') + 1)); continue; } else if (line.Contains("VERSION")) { STREAM.Version = line.Split(':')[1]; continue; } else if (line.Contains("MEDIA-SEQUENCE")) { STREAM.MediaSequence = line.Split(':')[1]; continue; } else if (line.Contains("EXT-X-STREAM-INF")) { #region 多码率适配流 var tmpPlaylist = new HLSPlaylist(); //#EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=800000,RESOLUTION=720x406,CODECS="avc1.4d401f,mp4a.40.2" var tmpItems = line.Split("\"".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); var codecsValue = ""; var tmpLine = line; if (tmpItems.Length == 2) { tmpLine = tmpItems[0]; codecsValue = tmpItems[1]; } var items = tmpLine.Split("#:,".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); for (var si = 1 /*跳过EXT-X-STREAM-INF*/; si < items.Length; si++) { var keyValue = items[si].Split("=".ToCharArray(), StringSplitOptions.RemoveEmptyEntries); switch (keyValue[0]) { case "BANDWIDTH": tmpPlaylist.BANDWIDTH = keyValue[1]; break; case "CODECS": tmpPlaylist.CODECS = codecsValue; // keyValue[1]; break; case "PROGRAM-ID": tmpPlaylist.PROGRAM_ID = keyValue[1]; break; case "RESOLUTION": tmpPlaylist.RESOLUTION = keyValue[1]; break; } } // tmpPlaylist.URI = lines[i + 1]; i = i + 1; Playlist.Add(tmpPlaylist); #endregion continue; } } line = null; STREAM.Playlist = Playlist.ToArray(); STREAM.Parts = PARTS.ToArray(); return(STREAM); }