public RResultCode GetResourceById(string id, string name, out AudioResource resource) { var channel = id; // request api token string jsonResponse; if (!WebWrapper.DownloadString(out jsonResponse, new Uri($"http://api.twitch.tv/api/channels/{channel}/access_token"))) { resource = null; return(RResultCode.NoConnection); } var jsonDict = (Dictionary <string, object>)jsonParser.DeserializeObject(jsonResponse); // request m3u8 file var token = Uri.EscapeUriString(jsonDict["token"].ToString()); var sig = jsonDict["sig"]; // guaranteed to be random, chosen by fair dice roll. var random = 4; string m3u8; if (!WebWrapper.DownloadString(out m3u8, new Uri($"http://usher.twitch.tv/api/channel/hls/{channel}.m3u8?player=twitchweb&&token={token}&sig={sig}&allow_audio_only=true&allow_source=true&type=any&p={random}"))) { resource = null; return(RResultCode.NoConnection); } // parse m3u8 file var dataList = new List <StreamData>(); using (var reader = new System.IO.StringReader(m3u8)) { var header = reader.ReadLine(); if (string.IsNullOrEmpty(header) || header != "#EXTM3U") { resource = null; return(RResultCode.TwitchMalformedM3u8File); } while (true) { var blockInfo = reader.ReadLine(); if (string.IsNullOrEmpty(blockInfo)) { break; } var match = m3u8ExtMatch.Match(blockInfo); if (!match.Success) { continue; } switch (match.Groups[1].Value) { case "EXT-X-TWITCH-INFO": break; // Ignore twitch info line case "EXT-X-MEDIA": string streamInfo = reader.ReadLine(); Match infoMatch; if (string.IsNullOrEmpty(streamInfo) || !(infoMatch = m3u8ExtMatch.Match(streamInfo)).Success || infoMatch.Groups[1].Value != "EXT-X-STREAM-INF") { resource = null; return(RResultCode.TwitchMalformedM3u8File); } var streamData = new StreamData(); // #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000,CODECS="mp4a.40.2",VIDEO="audio_only" for (int i = 0; i < infoMatch.Groups[3].Captures.Count; i++) { string key = infoMatch.Groups[4].Captures[i].Value.ToUpper(); string value = infoMatch.Groups[5].Captures[i].Value; switch (key) { case "BANDWIDTH": streamData.Bandwidth = int.Parse(value); break; case "CODECS": streamData.Codec = TextUtil.StripQuotes(value); break; case "VIDEO": StreamQuality quality; if (Enum.TryParse(TextUtil.StripQuotes(value), out quality)) { streamData.QualityType = quality; } else { streamData.QualityType = StreamQuality.unknown; } break; } } streamData.Url = reader.ReadLine(); dataList.Add(streamData); break; default: break; } } } resource = new TwitchResource(channel, name ?? $"Twitch channel: {channel}", dataList); return(dataList.Count > 0 ? RResultCode.Success : RResultCode.TwitchNoStreamsExtracted); }
public RResultCode GetResourceById(string id, string name, out AudioResource resource) { var channel = id; // request api token string jsonResponse; if (!WebWrapper.DownloadString(out jsonResponse, new Uri($"http://api.twitch.tv/api/channels/{channel}/access_token"))) { resource = null; return RResultCode.NoConnection; } var jsonDict = (Dictionary<string, object>)jsonParser.DeserializeObject(jsonResponse); // request m3u8 file var token = Uri.EscapeUriString(jsonDict["token"].ToString()); var sig = jsonDict["sig"]; // guaranteed to be random, chosen by fair dice roll. var random = 4; string m3u8; if (!WebWrapper.DownloadString(out m3u8, new Uri($"http://usher.twitch.tv/api/channel/hls/{channel}.m3u8?player=twitchweb&&token={token}&sig={sig}&allow_audio_only=true&allow_source=true&type=any&p={random}"))) { resource = null; return RResultCode.NoConnection; } // parse m3u8 file var dataList = new List<StreamData>(); using (var reader = new System.IO.StringReader(m3u8)) { var header = reader.ReadLine(); if (string.IsNullOrEmpty(header) || header != "#EXTM3U") { resource = null; return RResultCode.TwitchMalformedM3u8File; } while (true) { var blockInfo = reader.ReadLine(); if (string.IsNullOrEmpty(blockInfo)) break; var match = m3u8ExtMatch.Match(blockInfo); if (!match.Success) continue; switch (match.Groups[1].Value) { case "EXT-X-TWITCH-INFO": break; // Ignore twitch info line case "EXT-X-MEDIA": string streamInfo = reader.ReadLine(); Match infoMatch; if (string.IsNullOrEmpty(streamInfo) || !(infoMatch = m3u8ExtMatch.Match(streamInfo)).Success || infoMatch.Groups[1].Value != "EXT-X-STREAM-INF") { resource = null; return RResultCode.TwitchMalformedM3u8File; } var streamData = new StreamData(); // #EXT-X-STREAM-INF:PROGRAM-ID=1,BANDWIDTH=128000,CODECS="mp4a.40.2",VIDEO="audio_only" for (int i = 0; i < infoMatch.Groups[3].Captures.Count; i++) { string key = infoMatch.Groups[4].Captures[i].Value.ToUpper(); string value = infoMatch.Groups[5].Captures[i].Value; switch (key) { case "BANDWIDTH": streamData.Bandwidth = int.Parse(value); break; case "CODECS": streamData.Codec = TextUtil.StripQuotes(value); break; case "VIDEO": StreamQuality quality; if (Enum.TryParse(TextUtil.StripQuotes(value), out quality)) streamData.QualityType = quality; else streamData.QualityType = StreamQuality.unknown; break; } } streamData.Url = reader.ReadLine(); dataList.Add(streamData); break; default: break; } } } resource = new TwitchResource(channel, name ?? $"Twitch channel: {channel}", dataList); return dataList.Count > 0 ? RResultCode.Success : RResultCode.TwitchNoStreamsExtracted; }