Files
2026-02-21 16:45:37 +08:00

1062 lines
25 KiB
C#

using System;
using System.Collections;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;
using Crosstales.Radio.Model;
using Crosstales.Radio.Model.Enum;
using Crosstales.Radio.Util;
using NAudio.Wave;
using NLayer;
using NVorbis;
using UnityEngine;
namespace Crosstales.Radio
{
[ExecuteInEditMode]
[RequireComponent(typeof(AudioSource))]
[HelpURLAttribute("https://www.crosstales.com/media/data/assets/radio/api/class_crosstales_1_1_radio_1_1_radio_player.html")]
public class RadioPlayer : BasePlayer
{
[Header("Radio Station")]
[Tooltip("Radio station for this RadioPlayer.")]
public RadioStation Station;
[Tooltip("Play the radio on start on/off (default: false).")]
[Header("Behaviour Settings")]
public bool PlayOnStart;
[Tooltip("Starts and stops the RadioPlayer depending on the focus and running state (default: false).")]
public bool HandleFocus;
[Tooltip("Size of cache stream in KB (default: 512).")]
[Header("General Settings")]
public int CacheStreamSize = 512;
[Tooltip("Enable or disable legacy mode. This disables all record informations, but is more stable (default: false).")]
public bool LegacyMode;
[Tooltip("Capture the encoded PCM-stream from this RadioPlayer (default: false).")]
public bool CaptureDataStream;
private bool error;
private string errorMessage;
private MpegFile nLayerReader;
private Mp3FileReader nAudioReader;
private VorbisReader nVorbisReader;
private int oggCacheCleanFrameCount;
private bool stopped = true;
private bool bufferAvailable;
private bool playback;
private bool restarted;
private float maxPlayTime;
private Thread worker;
private RecordInfo recordInfo = new RecordInfo();
private RecordInfo nextRecordInfo = new RecordInfo();
private RecordInfo lastNextRecordInfo;
private float nextRecordDelay;
private Stream ms;
private static bool loggedUnsupportedPlatform;
private static int playCounter;
private static bool ignoreExcludeCodec;
private bool wasRunning;
public override RadioStation RadioStation
{
get
{
return Station;
}
set
{
Station = value;
}
}
public override bool isLegacyMode
{
get
{
return LegacyMode;
}
set
{
LegacyMode = value;
}
}
public override bool isCaptureDataStream
{
get
{
return CaptureDataStream;
}
set
{
CaptureDataStream = value;
}
}
public override AudioSource Source { get; protected set; }
public override AudioCodec Codec { get; protected set; }
public override float PlayTime { get; protected set; }
public override float BufferProgress { get; protected set; }
public override bool isPlayback
{
get
{
return playback;
}
}
public override bool isAudioPlaying
{
get
{
return playback && !isBuffering;
}
}
public override bool isBuffering
{
get
{
return !bufferAvailable;
}
}
public override float RecordPlayTime { get; protected set; }
public override RecordInfo RecordInfo
{
get
{
return recordInfo;
}
}
public override RecordInfo NextRecordInfo
{
get
{
return nextRecordInfo;
}
}
public override float NextRecordDelay
{
get
{
return nextRecordDelay;
}
}
public override long CurrentBufferSize
{
get
{
if (ms != null)
{
return ms.Length - ms.Position;
}
return 0L;
}
}
public override long CurrentDownloadSpeed
{
get
{
if (ms != null && PlayTime > 0f)
{
return (long)((float)ms.Length / PlayTime);
}
return 0L;
}
}
public override MemoryCacheStream DataStream { get; protected set; }
public override int Channels { get; protected set; }
public override int SampleRate { get; protected set; }
public static bool isPlaying
{
get
{
return playCounter > 0;
}
}
public void Awake()
{
Channels = 2;
SampleRate = 44100;
oggCacheCleanFrameCount = UnityEngine.Random.Range(Constants.OGG_CLEAN_INTERVAL_MIN, Constants.OGG_CLEAN_INTERVAL_MAX);
Source = GetComponent<AudioSource>();
Source.playOnAwake = false;
Source.Stop();
}
public void Start()
{
if (PlayOnStart && !Helper.isEditorMode)
{
Play();
}
}
public void Update()
{
if (isAudioPlaying && !restarted)
{
if (lastNextRecordInfo == null || !lastNextRecordInfo.Equals(NextRecordInfo))
{
lastNextRecordInfo = NextRecordInfo;
onNextRecordChange(Station, NextRecordInfo, nextRecordDelay);
}
float num = Time.unscaledDeltaTime * Source.pitch;
Context.TotalPlayTime += num;
Station.TotalPlayTime += num;
PlayTime += num;
RecordPlayTime += num;
nextRecordDelay -= num;
onAudioPlayTimeUpdate(Station, PlayTime);
onRecordPlayTimeUpdate(Station, RecordInfo, RecordPlayTime);
onNextRecordDelayUpdate(Station, NextRecordInfo, nextRecordDelay);
if (PlayTime > maxPlayTime)
{
restarted = true;
Restart();
}
}
}
public void OnDisable()
{
Stop();
}
public void OnValidate()
{
if (Station != null)
{
if (Station.Bitrate <= 0)
{
Station.Bitrate = Config.DEFAULT_BITRATE;
}
else
{
Station.Bitrate = Helper.NearestBitrate(Station.Bitrate, Station.Format);
}
if (Station.ChunkSize <= 0)
{
Station.ChunkSize = Config.DEFAULT_CHUNKSIZE;
}
else if (Station.ChunkSize > Config.MAX_CACHESTREAMSIZE)
{
Station.ChunkSize = Config.MAX_CACHESTREAMSIZE;
}
if (Station.BufferSize <= 0)
{
Station.BufferSize = Config.DEFAULT_BUFFERSIZE;
}
else
{
if (Station.Format == AudioFormat.MP3)
{
if (Station.BufferSize < Config.DEFAULT_BUFFERSIZE / 4)
{
Station.BufferSize = Config.DEFAULT_BUFFERSIZE / 4;
}
}
else if (Station.Format == AudioFormat.OGG && Station.BufferSize < 64)
{
Station.BufferSize = 64;
}
if (Station.BufferSize < Station.ChunkSize)
{
Station.BufferSize = Station.ChunkSize;
}
else if (Station.BufferSize > Config.MAX_CACHESTREAMSIZE)
{
Station.BufferSize = Config.MAX_CACHESTREAMSIZE;
}
}
}
if (CacheStreamSize <= 0)
{
CacheStreamSize = Config.DEFAULT_CACHESTREAMSIZE;
}
else if (Station != null && CacheStreamSize <= Station.BufferSize)
{
CacheStreamSize = Station.BufferSize;
}
else if (CacheStreamSize > Config.MAX_CACHESTREAMSIZE)
{
CacheStreamSize = Config.MAX_CACHESTREAMSIZE;
}
}
public void OnApplicationFocus(bool hasFocus)
{
if (!Application.runInBackground && HandleFocus)
{
if (!hasFocus)
{
wasRunning = playback;
Stop();
}
else if (wasRunning)
{
Play();
}
}
}
public override void Play()
{
if (Station == null || Station.Name == null)
{
Debug.LogError("Station == null || Station.Name == null; check Radios_user.txt");
}
else if (Helper.isSupportedPlatform)
{
if (stopped)
{
if (Helper.isInternetAvailable)
{
if (Helper.isSane(ref Station))
{
Codec = Helper.AudioCodecForAudioFormat(Station.Format);
if (Codec == AudioCodec.None)
{
errorMessage = string.Concat(Station, Environment.NewLine, "Audio format not supported - can't play station: ", Station.Format);
Debug.LogError(errorMessage);
onErrorInfo(Station, errorMessage);
}
else if (!ignoreExcludeCodec && Station.ExcludedCodec == Codec)
{
errorMessage = string.Concat(Station, Environment.NewLine, "Excluded codec matched - can't play station: ", Codec);
Debug.LogError(errorMessage);
onErrorInfo(Station, errorMessage);
}
else
{
StartCoroutine(playAudioFromUrl());
}
}
else
{
errorMessage = string.Concat(Station, Environment.NewLine, "Could not start playback. Please verify the station settings.");
Debug.LogError(errorMessage);
onErrorInfo(Station, errorMessage);
}
}
else
{
errorMessage = "No internet connection available! Can't play (stream) any stations!";
Debug.LogError(errorMessage);
onErrorInfo(Station, errorMessage);
}
}
else
{
errorMessage = string.Concat(Station, Environment.NewLine, "Station is already playing!");
Debug.LogWarning(errorMessage);
onErrorInfo(Station, errorMessage);
}
}
else
{
logUnsupportedPlatform();
}
}
public override void Stop()
{
playback = false;
if (Source != null)
{
Source.Stop();
Source.clip = null;
}
stopped = true;
}
public override void Silence()
{
Source.volume = 0f;
}
public override void Restart(float invokeDelay = 0.4f)
{
Stop();
Invoke("Play", invokeDelay);
}
public string ToShortString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append("Station='");
stringBuilder.Append(Station);
stringBuilder.Append(Constants.TEXT_TOSTRING_DELIMITER);
stringBuilder.Append("PlayOnStart='");
stringBuilder.Append(PlayOnStart);
stringBuilder.Append(Constants.TEXT_TOSTRING_DELIMITER);
stringBuilder.Append("CacheStreamSize='");
stringBuilder.Append(CacheStreamSize);
stringBuilder.Append(Constants.TEXT_TOSTRING_DELIMITER_END);
return stringBuilder.ToString();
}
public void Load()
{
Debug.LogWarning("Not implemented!");
}
public void Save()
{
Debug.LogWarning("Not implemented!");
}
private IEnumerator playAudioFromUrl()
{
onPlaybackStart(Station);
playback = false;
restarted = false;
error = false;
errorMessage = string.Empty;
PlayTime = 0f;
RecordPlayTime = 0f;
BufferProgress = 0f;
bufferAvailable = false;
float _bufferCurrentProgress = 0f;
recordInfo = new RecordInfo();
nextRecordInfo = new RecordInfo();
nextRecordDelay = 0f;
onBufferingStart(Station);
onBufferingProgressUpdate(Station, BufferProgress);
using (Stream stream = (ms = new MemoryCacheStream(CacheStreamSize * 1024, Config.MAX_CACHESTREAMSIZE * 1024)))
{
if (LegacyMode)
{
worker = new Thread((ThreadStart)delegate
{
readStreamLegacy(ref Station, ref playback, ref ms, ref error, ref errorMessage);
});
}
else
{
worker = new Thread((ThreadStart)delegate
{
readStream(ref Station, ref playback, ref ms, ref error, ref errorMessage, ref nextRecordInfo, ref nextRecordDelay);
});
}
worker.Start();
do
{
yield return null;
}
while (!playback && !stopped && !error);
int bufferSize = Station.BufferSize * 1024 + Station.ChunkSize * 1024;
do
{
BufferProgress = (float)ms.Length / (float)bufferSize;
if (BufferProgress != _bufferCurrentProgress)
{
onBufferingProgressUpdate(Station, BufferProgress);
_bufferCurrentProgress = BufferProgress;
}
yield return null;
}
while (playback && !stopped && ms.Length < bufferSize);
BufferProgress = 1f;
onBufferingProgressUpdate(Station, BufferProgress);
bufferAvailable = true;
onBufferingEnd(Station);
if (playback && !stopped)
{
bool _success = false;
try
{
if (Codec == AudioCodec.MP3_NLayer)
{
nLayerReader = new MpegFile(ms);
SampleRate = nLayerReader.SampleRate;
Channels = nLayerReader.Channels;
}
else if (Codec == AudioCodec.MP3_NAudio)
{
nAudioReader = new Mp3FileReader(ms);
SampleRate = nAudioReader.WaveFormat.SampleRate;
Channels = nAudioReader.WaveFormat.Channels;
}
else if (Codec == AudioCodec.OGG_NVorbis)
{
nVorbisReader = new VorbisReader(ms, false);
SampleRate = nVorbisReader.SampleRate;
Channels = nVorbisReader.Channels;
}
_success = true;
}
catch (Exception ex)
{
Debug.LogError(string.Concat(Station, Environment.NewLine, "Could not read data from url!", Environment.NewLine, ex));
}
if (!_success)
{
error = true;
errorMessage = string.Concat(Station, Environment.NewLine, "Could not play the stream -> Please try another station!");
Debug.LogError(errorMessage);
playback = false;
}
else
{
if (Codec == AudioCodec.OGG_NVorbis)
{
ms.Position = 0L;
}
maxPlayTime = int.MaxValue / SampleRate - 240;
DataStream = new MemoryCacheStream(131072, 524288);
AudioClip clip = AudioClip.Create(Station.Name, int.MaxValue, Channels, SampleRate, true, readPCMData);
Source.clip = clip;
Source.Play();
onAudioStart(Station);
}
do
{
yield return null;
if (Codec == AudioCodec.OGG_NVorbis && Time.frameCount % oggCacheCleanFrameCount == 0)
{
if (Constants.DEV_DEBUG)
{
Debug.Log("Clean cache: " + oggCacheCleanFrameCount + " - " + PlayTime);
}
Mdct.ClearSetupCache();
}
}
while (playback && !stopped);
Source.Stop();
Source.clip = null;
if (_success)
{
onAudioEnd(Station);
}
if (Codec == AudioCodec.MP3_NLayer)
{
if (nLayerReader != null)
{
nLayerReader.Dispose();
nLayerReader = null;
}
}
else if (Codec == AudioCodec.MP3_NAudio)
{
if (nAudioReader != null)
{
nAudioReader.Dispose();
nAudioReader = null;
}
}
else if (Codec == AudioCodec.OGG_NVorbis && nVorbisReader != null)
{
nVorbisReader.Dispose();
nVorbisReader = null;
Mdct.ClearSetupCache();
}
}
}
if (DataStream != null)
{
DataStream.Dispose();
}
if (error)
{
onErrorInfo(Station, errorMessage);
}
onPlaybackEnd(Station);
}
private void readPCMData(float[] data)
{
if (playback && !stopped && bufferAvailable)
{
if (Codec == AudioCodec.MP3_NLayer)
{
if (nLayerReader != null)
{
try
{
if (nLayerReader.ReadSamples(data, 0, data.Length) <= 0)
{
logNoMoreData();
}
}
catch (Exception ex)
{
logDataError(ex);
}
}
}
else if (Codec == AudioCodec.MP3_NAudio)
{
if (nAudioReader != null)
{
byte[] array = new byte[data.Length * 2];
try
{
int count;
if ((count = nAudioReader.Read(array, 0, array.Length)) > 0)
{
Buffer.BlockCopy(Helper.ConvertByteArrayToFloatArray(array, count), 0, data, 0, data.Length * 4);
}
else
{
logNoMoreData();
}
}
catch (Exception ex2)
{
logDataError(ex2);
}
}
}
else if (Codec == AudioCodec.OGG_NVorbis && nVorbisReader != null)
{
try
{
if (nVorbisReader.ReadSamples(data, 0, data.Length) <= 0)
{
logNoMoreData();
}
}
catch (Exception ex3)
{
logDataError(ex3);
}
}
}
else
{
Buffer.BlockCopy(new float[data.Length], 0, data, 0, data.Length * 4);
}
if (CaptureDataStream && DataStream != null)
{
byte[] array2 = Helper.ConvertFloatArrayToByteArray(data, data.Length);
DataStream.Write(array2, 0, array2.Length);
}
}
private void readStream(ref RadioStation _station, ref bool _playback, ref Stream _ms, ref bool _error, ref string _errorMessage, ref RecordInfo _nextRecordInfo, ref float _nextRecordDelay)
{
if (_station.Url.StartsWith(Constants.PREFIX_HTTP) || _station.Url.StartsWith(Constants.PREFIX_HTTPS))
{
try
{
ServicePointManager.ServerCertificateValidationCallback = Helper.RemoteCertificateValidationCallback;
using (CTWebClient cTWebClient = new CTWebClient(int.MaxValue))
{
HttpWebRequest httpWebRequest = (HttpWebRequest)cTWebClient.CTGetWebRequest(_station.Url);
httpWebRequest.Headers.Clear();
httpWebRequest.Headers.Add("GET", "/ HTTP/1.1");
httpWebRequest.Headers.Add("Icy-MetaData", "1");
httpWebRequest.UserAgent = "WinampMPEG/5.09";
using (HttpWebResponse httpWebResponse = (HttpWebResponse)httpWebRequest.GetResponse())
{
int num = ((!string.IsNullOrEmpty(httpWebResponse.GetResponseHeader("icy-metaint"))) ? Convert.ToInt32(httpWebResponse.GetResponseHeader("icy-metaint")) : int.MaxValue);
if (Constants.DEV_DEBUG)
{
Debug.LogWarning("metaint: " + num);
}
using (Stream stream = httpWebResponse.GetResponseStream())
{
if (stream != null)
{
int num2 = 0;
byte[] array = new byte[_station.ChunkSize * 1024];
_playback = true;
Context.TotalDataRequests++;
_station.TotalDataRequests++;
int num3 = 0;
int num4 = 0;
bool flag = true;
_nextRecordDelay = 0f;
do
{
int num5;
if ((num5 = stream.Read(array, 0, array.Length)) > 0)
{
Context.TotalDataSize += num5;
_station.TotalDataSize += num5;
num3 = 0;
if (num > 0 && num5 + num4 > num)
{
int num6 = 0;
while (num6 < num5 && _playback)
{
if (num4 == num)
{
num4 = 0;
_ms.Write(array, num3, num6 - num3);
num3 = num6;
num2 = Convert.ToInt32(array[num6]) * 16;
num6++;
num3++;
if (num2 > 0)
{
if (num2 + num3 <= num5)
{
byte[] array2 = new byte[num2];
Array.Copy(array, num6, array2, 0, num2);
_nextRecordInfo = new RecordInfo(Encoding.UTF8.GetString(array2));
if (!flag)
{
_nextRecordDelay = (float)(_ms.Length - _ms.Position) / (float)(Station.Bitrate * 125);
}
else
{
flag = false;
}
num6 += num2;
num3 += num2;
if (Constants.DEV_DEBUG)
{
Debug.LogWarning("RecordInfo read: " + _nextRecordInfo);
}
}
else
{
if (Constants.DEV_DEBUG)
{
Debug.LogError("Info-frame outside of the buffer!");
}
num6 = num5;
num4 = num5 - (num2 + num3);
}
}
}
else
{
num4++;
num6++;
}
}
if (num3 < num5)
{
_ms.Write(array, num3, num5 - num3);
}
}
else
{
num4 += num5;
_ms.Write(array, 0, num5);
}
}
}
while (_playback);
}
return;
}
}
}
}
catch (Exception ex)
{
_error = true;
_errorMessage = _station.Name + Environment.NewLine + "Could not read url after " + Helper.FormatSecondsToHourMinSec(PlayTime) + "!" + Environment.NewLine + ex;
Debug.LogError(_errorMessage);
_playback = false;
return;
}
}
readStreamLegacy(ref _station, ref _playback, ref _ms, ref _error, ref _errorMessage);
}
private void readStreamLegacy(ref RadioStation _station, ref bool _playback, ref Stream _ms, ref bool _error, ref string _errorMessage)
{
try
{
ServicePointManager.ServerCertificateValidationCallback = Helper.RemoteCertificateValidationCallback;
using (CTWebClient cTWebClient = new CTWebClient(int.MaxValue))
{
using (WebResponse webResponse = cTWebClient.CTGetWebRequest(_station.Url).GetResponse())
{
using (Stream stream = webResponse.GetResponseStream())
{
if (stream == null)
{
return;
}
byte[] array = new byte[_station.ChunkSize * 1024];
_playback = true;
Context.TotalDataRequests++;
_station.TotalDataRequests++;
do
{
int num;
if ((num = stream.Read(array, 0, array.Length)) > 0)
{
Context.TotalDataSize += num;
_station.TotalDataSize += num;
if (_playback)
{
_ms.Write(array, 0, num);
}
}
}
while (_playback);
}
}
}
}
catch (Exception ex)
{
_error = true;
_errorMessage = _station.Name + Environment.NewLine + "Could not read url after " + Helper.FormatSecondsToHourMinSec(PlayTime) + "!" + Environment.NewLine + ex;
Debug.LogError(_errorMessage);
_playback = false;
}
}
private void logNoMoreData()
{
error = true;
errorMessage = Station.Name + Environment.NewLine + "No more data to read after " + Helper.FormatSecondsToHourMinSec(PlayTime) + "! Please restart this station or choose another one.";
Debug.LogError(errorMessage);
playback = false;
}
private void logDataError(Exception ex)
{
error = true;
errorMessage = Station.Name + Environment.NewLine + "Could not read audio after " + Helper.FormatSecondsToHourMinSec(PlayTime) + "! This is typically a sign of a buffer underun -> Please try to increment the 'ChunkSize' and 'BufferSize':" + Environment.NewLine + ex;
Debug.LogError(errorMessage);
playback = false;
}
private void logUnsupportedPlatform()
{
if (!loggedUnsupportedPlatform)
{
errorMessage = "'Radio' is not supported on your platform!";
Debug.LogWarning(errorMessage);
onErrorInfo(Station, errorMessage);
}
}
private void onPlaybackStart(RadioStation station)
{
stopped = false;
playCounter++;
if (Config.DEBUG)
{
Debug.Log("onPlaybackStart: " + station);
}
if (_playbackStart != null)
{
_playbackStart(station);
}
}
private void onPlaybackEnd(RadioStation station)
{
stopped = true;
playCounter--;
if (Config.DEBUG)
{
Debug.Log("onPlaybackEnd: " + station);
}
if (recordInfo != null)
{
recordInfo.Duration = RecordPlayTime;
recordInfo = new RecordInfo();
}
if (_playbackEnd != null)
{
_playbackEnd(station);
}
}
private void onBufferingStart(RadioStation station)
{
if (Config.DEBUG)
{
Debug.Log("onBufferingStart: " + station);
}
if (_bufferingStart != null)
{
_bufferingStart(station);
}
}
private void onBufferingEnd(RadioStation station)
{
if (Config.DEBUG)
{
Debug.Log("onBufferingEnd: " + station);
}
if (_bufferingEnd != null)
{
_bufferingEnd(station);
}
}
private void onBufferingProgressUpdate(RadioStation station, float progress)
{
if (_bufferingProgressUpdate != null)
{
_bufferingProgressUpdate(station, progress);
}
}
private void onAudioStart(RadioStation station)
{
if (Config.DEBUG)
{
Debug.Log("onAudioStart: " + station);
}
if (_audioStart != null)
{
_audioStart(station);
}
}
private void onAudioEnd(RadioStation station)
{
if (Config.DEBUG)
{
Debug.Log("onAudioEnd: " + station);
}
if (_audioEnd != null)
{
_audioEnd(station);
}
}
private void onAudioPlayTimeUpdate(RadioStation station, float playtime)
{
if (_audioPlayTimeUpdate != null)
{
_audioPlayTimeUpdate(station, playtime);
}
}
private void onErrorInfo(RadioStation station, string info)
{
if (Config.DEBUG)
{
Debug.Log(string.Concat("onErrorInfo: ", station, " - ", info));
}
if (_errorInfo != null)
{
_errorInfo(station, info);
}
}
private void onRecordChange(RadioStation station, RecordInfo newRecord)
{
if (!newRecord.Equals(recordInfo))
{
if (Config.DEBUG)
{
Debug.Log(string.Concat("onRecordChange: ", station, " - ", newRecord));
}
if (recordInfo != null)
{
recordInfo.Duration = RecordPlayTime;
}
recordInfo = newRecord;
RecordPlayTime = 0f;
if (!string.IsNullOrEmpty(recordInfo.Info))
{
Station.PlayedRecords.Add(recordInfo);
}
if (_recordChange != null)
{
_recordChange(station, recordInfo);
}
}
}
private void onRecordPlayTimeUpdate(RadioStation station, RecordInfo record, float playtime)
{
if (_recordPlayTimeUpdate != null)
{
_recordPlayTimeUpdate(station, record, playtime);
}
}
private void onNextRecordChange(RadioStation station, RecordInfo nextRecord, float delay)
{
if (Config.DEBUG)
{
Debug.Log(string.Concat("onNextRecordChange: ", station, " - ", nextRecord));
}
if (_nextRecordChange != null)
{
_nextRecordChange(station, nextRecord, delay);
}
}
private void onNextRecordDelayUpdate(RadioStation station, RecordInfo nextRecord, float delay)
{
if (delay > 0f)
{
if (_nextRecordDelayUpdate != null)
{
_nextRecordDelayUpdate(station, nextRecord, delay);
}
}
else
{
onRecordChange(station, nextRecord);
}
}
public override string ToString()
{
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(GetType().Name);
stringBuilder.Append(Constants.TEXT_TOSTRING_START);
stringBuilder.Append("Station='");
stringBuilder.Append(Station);
stringBuilder.Append(Constants.TEXT_TOSTRING_DELIMITER);
stringBuilder.Append("PlayOnStart='");
stringBuilder.Append(PlayOnStart);
stringBuilder.Append(Constants.TEXT_TOSTRING_DELIMITER);
stringBuilder.Append("CacheStreamSize='");
stringBuilder.Append(CacheStreamSize);
stringBuilder.Append(Constants.TEXT_TOSTRING_DELIMITER_END);
stringBuilder.Append(Constants.TEXT_TOSTRING_END);
return stringBuilder.ToString();
}
}
}