219 lines
7.4 KiB
C#

using NadekoBot.Extensions;
using NLog;
using System;
using System.Diagnostics;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
namespace NadekoBot.Modules.Music.Classes
{
/// <summary>
/// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space.
/// It also help for large music by deleting files that are already seen.
/// </summary>
class SongBuffer : Stream
{
public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo, int maxFileSize)
{
MusicPlayer = musicPlayer;
Basename = basename;
SongInfo = songInfo;
SkipTo = skipTo;
MaxFileSize = maxFileSize;
CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
_log = LogManager.GetCurrentClassLogger();
}
MusicPlayer MusicPlayer { get; }
private string Basename { get; }
private SongInfo SongInfo { get; }
private int SkipTo { get; }
private int MaxFileSize { get; } = 2.MiB();
private long FileNumber = -1;
private long NextFileToRead = 0;
public bool BufferingCompleted { get; private set; } = false;
private ulong CurrentBufferSize = 0;
private FileStream CurrentFileStream;
private Logger _log;
public Task BufferSong(CancellationToken cancelToken) =>
Task.Run(async () =>
{
Process p = null;
FileStream outStream = null;
try
{
p = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $"-ss {SkipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -vn -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
CreateNoWindow = true,
});
byte[] buffer = new byte[81920];
int currentFileSize = 0;
ulong prebufferSize = 100ul.MiB();
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
while (!p.HasExited) //Also fix low bandwidth
{
int bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false);
if (currentFileSize >= MaxFileSize)
{
try
{
outStream.Dispose();
}
catch { }
outStream = new FileStream(Basename + "-" + ++FileNumber, FileMode.Append, FileAccess.Write, FileShare.Read);
currentFileSize = bytesRead;
}
else
{
currentFileSize += bytesRead;
}
CurrentBufferSize += Convert.ToUInt64(bytesRead);
await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false);
while (CurrentBufferSize > prebufferSize)
await Task.Delay(100, cancelToken);
}
BufferingCompleted = true;
}
catch (System.ComponentModel.Win32Exception)
{
var oldclr = Console.ForegroundColor;
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(@"You have not properly installed or configured FFMPEG.
Please install and configure FFMPEG to play music.
Check the guides for your platform on how to setup ffmpeg correctly:
Windows Guide: https://goo.gl/OjKk8F
Linux Guide: https://goo.gl/ShjCUo");
Console.ForegroundColor = oldclr;
}
catch (Exception ex)
{
Console.WriteLine($"Buffering stopped: {ex.Message}");
}
finally
{
if (outStream != null)
outStream.Dispose();
Console.WriteLine($"Buffering done.");
if (p != null)
{
try
{
p.Kill();
}
catch { }
p.Dispose();
}
}
});
/// <summary>
/// Return the next file to read, and delete the old one
/// </summary>
/// <returns>Name of the file to read</returns>
private string GetNextFile()
{
string filename = Basename + "-" + NextFileToRead;
if (NextFileToRead != 0)
{
try
{
CurrentBufferSize -= Convert.ToUInt64(new FileInfo(Basename + "-" + (NextFileToRead - 1)).Length);
File.Delete(Basename + "-" + (NextFileToRead - 1));
}
catch { }
}
NextFileToRead++;
return filename;
}
private bool IsNextFileReady()
{
return NextFileToRead <= FileNumber;
}
private void CleanFiles()
{
for (long i = NextFileToRead - 1; i <= FileNumber; i++)
{
try
{
File.Delete(Basename + "-" + i);
}
catch { }
}
}
//Stream part
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => (long)CurrentBufferSize;
public override long Position { get; set; } = 0;
public override void Flush() { }
public override int Read(byte[] buffer, int offset, int count)
{
int read = CurrentFileStream.Read(buffer, offset, count);
if (read < count)
{
if (!BufferingCompleted || IsNextFileReady())
{
CurrentFileStream.Dispose();
CurrentFileStream = new FileStream(GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
read += CurrentFileStream.Read(buffer, read + offset, count - read);
}
if (read < count)
Array.Clear(buffer, read, count - read);
}
return read;
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotImplementedException();
}
public override void SetLength(long value)
{
throw new NotImplementedException();
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotImplementedException();
}
public new void Dispose()
{
CurrentFileStream.Dispose();
MusicPlayer.SongCancelSource.Cancel();
CleanFiles();
base.Dispose();
}
}
}