private async Task <AudioEncodingProperties> GetEncodingPropertiesAsync(KeyValuePair <string, string>[] headers) { //if this happens to be an Icecast 2 server, it'll send the audio information for us. if (headers.Any(x => x.Key.ToLower() == "ice-audio-info")) { //looks like it is indeed an Icecast 2 server. lets strip out the data and be on our way. serverType = ShoutcastServerType.Icecast; /* example: ice-audio-info: ice-bitrate=32;ice-samplerate=32000;ice-channels=2 * "Note that unlike SHOUTcast, it is not necessary to parse ADTS audio frames to obtain the Audio Sample Rate." * from: http://www.indexcom.com/streaming/player/Icecast2.html */ string headerValue = headers.First(x => x.Key.ToLower() == "ice-audio-info").Value; //split the properties and values and parsed them into a usable object. KeyValuePair <string, string>[] propertiesAndValues = headerValue.Split(';') .Select(x => new KeyValuePair <string, string>( x.Substring(0, x.IndexOf("=")).ToLower().Trim(), x.Substring(x.IndexOf("=") + 1))).ToArray(); //grab each value that we need. if (AudioInfo.BitRate == 0) //usually this is sent in the regular headers. grab it if it isn't. { AudioInfo.BitRate = uint.Parse(propertiesAndValues.First(x => x.Key == "ice-bitrate" || x.Key == "bitrate").Value); } if (propertiesAndValues.Any(x => x.Key == "ice-channels" || x.Key == "channels") && propertiesAndValues.Any(x => x.Key == "ice-samplerate" || x.Key == "samplerate")) { AudioInfo.ChannelCount = uint.Parse(propertiesAndValues.First(x => x.Key == "ice-channels" || x.Key == "channels").Value); AudioInfo.SampleRate = uint.Parse(propertiesAndValues.First(x => x.Key == "ice-samplerate" || x.Key == "samplerate").Value); //now just create the appropriate AudioEncodingProperties object. switch (AudioInfo.AudioFormat) { case StreamAudioFormat.MP3: return(AudioEncodingProperties.CreateMp3(AudioInfo.SampleRate, AudioInfo.ChannelCount, AudioInfo.BitRate)); case StreamAudioFormat.AAC: case StreamAudioFormat.AAC_ADTS: return(AudioEncodingProperties.CreateAacAdts(AudioInfo.SampleRate, AudioInfo.ChannelCount, AudioInfo.BitRate)); } } else { //something is missing from audio-info so we need to fallback. return(await ParseEncodingFromMediaAsync()); } } else { return(await ParseEncodingFromMediaAsync()); } return(null); }
public ShoutcastMediaSourceStream(Uri url, ShoutcastServerType stationServerType = ShoutcastServerType.Shoutcast, string relativePath = ";", bool getMetadata = true) { StationInfo = new ShoutcastStationInfo(); streamUrl = url; serverType = stationServerType; socket = new StreamSocket(); AudioInfo = new ServerAudioInfo(); ShouldGetMetadata = getMetadata; this.relativePath = relativePath; }
private async Task <Tuple <bool, KeyValuePair <string, string>[]> > EstablishConnectionAsync() { //http://www.smackfu.com/stuff/programming/shoutcast.html try { await socket.ConnectAsync(new Windows.Networking.HostName(streamUrl.Host), streamUrl.Port.ToString()); socketWriter = new DataWriter(socket.OutputStream); socketReader = new DataReader(socket.InputStream); } catch (Exception ex) { if (MediaStreamSource != null) { MediaStreamSource.NotifyError(MediaStreamSourceErrorStatus.FailedToConnectToServer); } else { throw new Exception("Connection Error", ex); } return(new Tuple <bool, KeyValuePair <string, string>[]>(false, null)); } //todo figure out how to resolve http requests better to get rid of this hack. String httpPath = ""; if (streamUrl.Host.Contains("radionomy.com") || serverType == ShoutcastServerType.Radionomy) { httpPath = streamUrl.LocalPath; serverType = ShoutcastServerType.Radionomy; } else { httpPath = "/" + relativePath; } socketWriter.WriteString("GET " + httpPath + " HTTP/1.1" + Environment.NewLine); if (ShouldGetMetadata) { socketWriter.WriteString("Icy-MetaData: 1" + Environment.NewLine); } socketWriter.WriteString("Host: " + streamUrl.Host + (streamUrl.Port != 80 ? ":" + streamUrl.Port : "") + Environment.NewLine); socketWriter.WriteString("Connection: Keep-Alive" + Environment.NewLine); socketWriter.WriteString("User-Agent: " + (UserAgent ?? "Shoutcast Player (http://github.com/Amrykid/UWPShoutcastMSS)") + Environment.NewLine); socketWriter.WriteString(Environment.NewLine); await socketWriter.StoreAsync(); await socketWriter.FlushAsync(); string response = string.Empty; while (!response.EndsWith(Environment.NewLine + Environment.NewLine)) { await socketReader.LoadAsync(1); response += socketReader.ReadString(1); } //todo support http 2.0. maybe usage of the http client would solve this. if (response.StartsWith("HTTP/1.0 200 OK") || response.StartsWith("HTTP/1.1 200 OK") || response.StartsWith("ICY 200")) { var headers = ParseResponse(response); return(new Tuple <bool, KeyValuePair <string, string>[]>(true, headers)); } else { //wasn't successful. handle each case accordingly. if (response.StartsWith("HTTP /1.0 302") || response.StartsWith("HTTP/1.1 302")) { socketReader.Dispose(); socketWriter.Dispose(); socket.Dispose(); var parsedResponse = ParseHttpResponseToKeyPairArray(response.Split(new string[] { "\r\n" }, StringSplitOptions.None).Skip(1).ToArray()); socket = new StreamSocket(); streamUrl = new Uri(parsedResponse.First(x => x.Key.ToLower() == "location").Value); return(await EstablishConnectionAsync()); } else if (response.StartsWith("HTTP/1.0 404")) { throw new Exception("Station is unavailable."); } else if (response.StartsWith("ICY 401")) //ICY 401 Service Unavailable { if (MediaStreamSource != null) { MediaStreamSource.NotifyError(MediaStreamSourceErrorStatus.FailedToConnectToServer); } else { throw new Exception("Station is unavailable at this time. Maybe they're down for maintainence?"); } return(new Tuple <bool, KeyValuePair <string, string>[]>(false, null)); } else if (response.StartsWith("HTTP/1.1 503")) //HTTP/1.1 503 Server limit reached { throw new Exception("Station is unavailable at this time. The maximum amount of listeners has been reached."); } } return(new Tuple <bool, KeyValuePair <string, string>[]>(false, null)); //not connected and no headers. }