using System; using System.IO; using System.Text; using Crosstales.Radio.Model; using Crosstales.Radio.Util; using UnityEngine; namespace Crosstales.Radio.Tool { [RequireComponent(typeof(AudioSource))] [HelpURLAttribute("https://crosstales.com/media/data/assets/radio/api/class_crosstales_1_1_radio_1_1_tool_1_1_stream_saver.html")] public class StreamSaver : MonoBehaviour { [Tooltip("Origin Player.")] public BasePlayer Player; [Tooltip("Silence the origin (default: true).")] public bool SilenceSource = true; [Tooltip("Output path for the audio files.")] public string OutputPath; [Tooltip("Record delay in seconds before start saving the audio (default: 0).")] [Range(0f, 20f)] public float RecordStartDelay; [Range(0f, 20f)] [Tooltip("Record delay in seconds before stop saving the audio (default: 0).")] public float RecordStopDelay; [Tooltip("Add the station name to the audio files (default: true).")] public bool AddStationName = true; [Tooltip("Add the current timestamp to the audio files (default: false).")] public bool AddTimestamp; private FileStream fileStream; private const int HEADER_SIZE = 44; private const float RESCALE_FACTOR = 32767f; private AudioSource audioSource; private bool recOutput; private bool stopped = true; private long dataPosition; private string fileName; public bool isSilenceSource { get { return SilenceSource; } set { SilenceSource = value; } } public void Awake() { audioSource = GetComponent(); audioSource.playOnAwake = false; audioSource.Stop(); } public void Start() { if (Player == null) { Debug.LogWarning("No 'Player' added to the StreamSaver!"); } else { Player.isCaptureDataStream = true; Player.isLegacyMode = false; } if (string.IsNullOrEmpty(OutputPath)) { Debug.LogWarning("No 'OutputPath' added to the StreamSaver, saving in the project root!"); } } public void Update() { if (Player != null && Player.isAudioPlaying) { if (stopped) { stopped = false; dataPosition = Player.DataStream.Position; AudioClip clip = AudioClip.Create(Player.RadioStation.Name, int.MaxValue, Player.Channels, Player.SampleRate, true, readPCMData); audioSource.clip = clip; audioSource.Play(); if (SilenceSource) { Player.Silence(); } } } else if (!stopped) { audioSource.Stop(); audioSource.clip = null; stopped = true; } } public void OnEnable() { if (Player != null) { Player.OnAudioEnd += onAudioEnd; Player.OnNextRecordChange += onNextRecordChange; } } public void OnDisable() { if (Player != null) { Player.OnAudioEnd -= onAudioEnd; Player.OnNextRecordChange -= onNextRecordChange; } closeFile(); audioSource.Stop(); audioSource.clip = null; stopped = true; } public void OnValidate() { if (!string.IsNullOrEmpty(OutputPath)) { OutputPath = Helper.ValidatePath(OutputPath); } } private void openFile() { if (Config.DEBUG) { Debug.Log("openFile: " + fileName); } if (fileStream != null && fileStream.CanWrite) { closeFile(); } try { Directory.CreateDirectory(Path.GetDirectoryName(fileName)); fileStream = new FileStream(fileName, FileMode.Create); byte value = 0; for (int i = 0; i < 44; i++) { fileStream.WriteByte(value); } recOutput = true; } catch (Exception ex) { Debug.LogError("Could not open file '" + fileName + "': " + ex); } } private void convertAndWrite(float[] dataSource) { if (fileStream != null && fileStream.CanWrite) { short[] array = new short[dataSource.Length]; byte[] array2 = new byte[dataSource.Length * 2]; byte[] array3 = new byte[2]; for (int i = 0; i < dataSource.Length; i++) { array[i] = (short)(dataSource[i] * 32767f); array3 = BitConverter.GetBytes(array[i]); array3.CopyTo(array2, i * 2); } try { fileStream.Write(array2, 0, array2.Length); } catch (Exception ex) { Debug.LogError("Could write to file '" + fileName + "': " + ex); } } } private void closeFile() { if (Config.DEBUG) { Debug.Log("closeFile"); } recOutput = false; if (fileStream == null || !fileStream.CanWrite) { return; } try { fileStream.Seek(0L, SeekOrigin.Begin); byte[] bytes = Encoding.UTF8.GetBytes("RIFF"); fileStream.Write(bytes, 0, 4); byte[] bytes2 = BitConverter.GetBytes(fileStream.Length - 8); fileStream.Write(bytes2, 0, 4); byte[] bytes3 = Encoding.UTF8.GetBytes("WAVE"); fileStream.Write(bytes3, 0, 4); byte[] bytes4 = Encoding.UTF8.GetBytes("fmt "); fileStream.Write(bytes4, 0, 4); byte[] bytes5 = BitConverter.GetBytes(16); fileStream.Write(bytes5, 0, 4); ushort value = 1; byte[] bytes6 = BitConverter.GetBytes(value); fileStream.Write(bytes6, 0, 2); byte[] bytes7 = BitConverter.GetBytes(Player.Channels); fileStream.Write(bytes7, 0, 2); byte[] bytes8 = BitConverter.GetBytes(Player.SampleRate); fileStream.Write(bytes8, 0, 4); byte[] bytes9 = BitConverter.GetBytes(Player.SampleRate * Player.Channels * 2); fileStream.Write(bytes9, 0, 4); ushort value2 = (ushort)(Player.Channels * 2); fileStream.Write(BitConverter.GetBytes(value2), 0, 2); ushort value3 = 16; byte[] bytes10 = BitConverter.GetBytes(value3); fileStream.Write(bytes10, 0, 2); byte[] bytes11 = Encoding.UTF8.GetBytes("data"); fileStream.Write(bytes11, 0, 4); byte[] bytes12 = BitConverter.GetBytes(fileStream.Length - 44); fileStream.Write(bytes12, 0, 4); } catch (Exception ex) { Debug.LogError("Could write header for file '" + fileName + "': " + ex); } finally { try { fileStream.Close(); } catch (Exception ex2) { Debug.LogError("Could close file '" + fileName + "': " + ex2); } } } private void readPCMData(float[] data) { if (Player.isAudioPlaying && Player.DataStream != null) { byte[] array = new byte[data.Length * 2]; long position = Player.DataStream.Position; Player.DataStream.Position = dataPosition; int num; if ((num = Player.DataStream.Read(array, 0, array.Length)) > 0) { float[] array2 = Helper.ConvertByteArrayToFloatArray(array, num); Buffer.BlockCopy(array2, 0, data, 0, data.Length * 4); if (recOutput) { convertAndWrite(array2); } dataPosition += num; } Player.DataStream.Position = position; } else { Buffer.BlockCopy(new float[data.Length], 0, data, 0, data.Length * 4); } } private void onAudioEnd(RadioStation station) { if (Config.DEBUG) { Debug.Log("onAudioEnd"); } closeFile(); } private void onNextRecordChange(RadioStation station, RecordInfo nextRecord, float delay) { if (Config.DEBUG) { Debug.Log("onNextRecordChange: " + delay); } if (delay > 0f) { Invoke("closeFile", delay - RecordStopDelay - 0.2f); } fileName = Helper.ValidateFile(string.Concat(str1: (!string.IsNullOrEmpty(nextRecord.Artist) && !string.IsNullOrEmpty(nextRecord.Title)) ? (((!AddStationName) ? string.Empty : (station.Name + " - ")) + ((!AddTimestamp) ? string.Empty : (DateTime.Now.ToString("yyyyMMdd HH_mm_ss") + " - ")) + nextRecord.Artist + " - " + nextRecord.Title + ".wav") : (station.Name + " - " + DateTime.Now.ToString("yyyyMMdd HH_mm_ss") + ".wav"), str0: OutputPath)); Invoke("openFile", delay + RecordStartDelay + 0.2f); } } }