public R <PlayResource> GetResourceById(AudioResource resource) { var channel = resource.ResourceId; // request api token string jsonResponse; if (!WebWrapper.DownloadString(out jsonResponse, new Uri($"http://api.twitch.tv/api/channels/{channel}/access_token"), new Tuple <string, string>("Client-ID", twitchClientId))) { return(RResultCode.NoConnection.ToString()); } var jsonDict = (Dictionary <string, object>)Util.Serializer.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}"))) { return(RResultCode.NoConnection.ToString()); } // 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") { return(RResultCode.TwitchMalformedM3u8File.ToString()); } 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") { return(RResultCode.TwitchMalformedM3u8File.ToString()); } 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(CultureInfo.InvariantCulture); string value = infoMatch.Groups[5].Captures[i].Value; switch (key) { case "BANDWIDTH": streamData.Bandwidth = int.Parse(value, CultureInfo.InvariantCulture); 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; } } } // Validation Process if (dataList.Count <= 0) { return(RResultCode.TwitchNoStreamsExtracted.ToString()); } int codec = SelectStream(dataList); if (codec < 0) { return("The stream has no audio_only version."); } return(new PlayResource(dataList[codec].Url, resource.ResourceTitle != null ? resource : resource.WithName($"Twitch channel: {channel}"))); }
public async Task <PlayResource> GetResourceById(ResolveContext?_, AudioResource resource) { var channel = resource.ResourceId; // request api token var access = await WebWrapper .Request($"https://api.twitch.tv/api/channels/{channel}/access_token") .WithHeader("Client-ID", TwitchClientIdPrivate) .AsJson <JsonAccessToken>(); // request m3u8 file if (access is null || access.token is null || access.sig is null) { throw Error.LocalStr(strings.error_media_internal_invalid + " (tokenResult|sigResult)"); } var token = Uri.EscapeUriString(access.token); var sig = access.sig; // guaranteed to be random, chosen by fair dice roll. const int random = 4; var m3u8 = await WebWrapper .Request($"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}") .AsString(); // 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") { throw Error.LocalStr(strings.error_media_internal_missing + " (m3uHeader)"); } 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") { throw Error.LocalStr(strings.error_media_internal_missing + " (m3uStream)"); } 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.ToUpperInvariant(); string value = infoMatch.Groups[5].Captures[i].Value; switch (key) { case "BANDWIDTH": streamData.Bandwidth = int.Parse(value, CultureInfo.InvariantCulture); break; case "CODECS": streamData.Codec = TextUtil.StripQuotes(value); break; case "VIDEO": streamData.QualityType = Enum.TryParse(TextUtil.StripQuotes(value), out StreamQuality quality) ? quality : StreamQuality.unknown; break; } } streamData.Url = reader.ReadLine(); dataList.Add(streamData); break; } } } // Validation Process if (dataList.Count <= 0) { throw Error.LocalStr(strings.error_media_no_stream_extracted); } int codec = SelectStream(dataList); if (codec < 0) { throw Error.LocalStr(strings.error_media_no_stream_extracted); } var selectedStream = dataList[codec]; if (selectedStream.Url == null) { throw Error.LocalStr(strings.error_media_no_stream_extracted); } if (resource.ResourceTitle == null) { resource.ResourceTitle = $"Twitch channel: {channel}"; } return(new PlayResource(selectedStream.Url, resource)); }
public R <PlayResource, LocalStr> GetResourceById(AudioResource resource) { var channel = resource.ResourceId; // request api token if (!WebWrapper.DownloadString(out string jsonResponse, new Uri($"https://api.twitch.tv/api/channels/{channel}/access_token"), ("Client-ID", TwitchClientId))) { return(new LocalStr(strings.error_net_no_connection)); } var jObj = JObject.Parse(jsonResponse); // request m3u8 file var tokenResult = jObj.TryCast <string>("token"); var sigResult = jObj.TryCast <string>("sig"); if (!tokenResult.Ok || !sigResult.Ok) { return(new LocalStr(strings.error_media_internal_invalid + " (tokenResult|sigResult)")); } var token = Uri.EscapeUriString(tokenResult.Value); var sig = sigResult.Value; // guaranteed to be random, chosen by fair dice roll. const int random = 4; if (!WebWrapper.DownloadString(out string 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}"))) { return(new LocalStr(strings.error_net_no_connection)); } // 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") { return(new LocalStr(strings.error_media_internal_missing + " (m3uHeader)")); } 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") { return(new LocalStr(strings.error_media_internal_missing + " (m3uStream)")); } 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(CultureInfo.InvariantCulture); string value = infoMatch.Groups[5].Captures[i].Value; switch (key) { case "BANDWIDTH": streamData.Bandwidth = int.Parse(value, CultureInfo.InvariantCulture); break; case "CODECS": streamData.Codec = TextUtil.StripQuotes(value); break; case "VIDEO": streamData.QualityType = Enum.TryParse(TextUtil.StripQuotes(value), out StreamQuality quality) ? quality : StreamQuality.unknown; break; } } streamData.Url = reader.ReadLine(); dataList.Add(streamData); break; } } } // Validation Process if (dataList.Count <= 0) { return(new LocalStr(strings.error_media_no_stream_extracted)); } int codec = SelectStream(dataList); if (codec < 0) { return(new LocalStr(strings.error_media_no_stream_extracted)); } return(new PlayResource(dataList[codec].Url, resource.ResourceTitle != null ? resource : resource.WithName($"Twitch channel: {channel}"))); }
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; }