Merge pull request #484 from Nitix/dev-bugfix/478

Change how we handle audio file buffer, FIX #478
This commit is contained in:
Master Kwoth 2016-08-01 03:28:11 +02:00 committed by GitHub
commit 4bca94de04
4 changed files with 195 additions and 74 deletions

View File

@ -34,7 +34,7 @@ namespace NadekoBot.Modules.Music.Classes
public IReadOnlyCollection<Song> Playlist => playlist; public IReadOnlyCollection<Song> Playlist => playlist;
public Song CurrentSong { get; private set; } public Song CurrentSong { get; private set; }
private CancellationTokenSource SongCancelSource { get; set; } public CancellationTokenSource SongCancelSource { get; private set; }
private CancellationToken cancelToken { get; set; } private CancellationToken cancelToken { get; set; }
public bool Paused { get; set; } public bool Paused { get; set; }

View File

@ -32,7 +32,6 @@ namespace NadekoBot.Modules.Music.Classes
public SongInfo SongInfo { get; } public SongInfo SongInfo { get; }
public string QueuerName { get; set; } public string QueuerName { get; set; }
private bool bufferingCompleted { get; set; } = false;
public MusicPlayer MusicPlayer { get; set; } public MusicPlayer MusicPlayer { get; set; }
public string PrettyCurrentTime() public string PrettyCurrentTime()
@ -73,78 +72,22 @@ namespace NadekoBot.Modules.Music.Classes
return this; return this;
} }
private Task BufferSong(string filename, CancellationToken cancelToken) =>
Task.Factory.StartNew(async () =>
{
Process p = null;
try
{
p = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $"-ss {skipTo} -i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = false,
CreateNoWindow = true,
});
var prebufferSize = 100ul.MiB();
using (var outStream = new FileStream(filename, FileMode.Append, FileAccess.Write, FileShare.Read))
{
byte[] buffer = new byte[81920];
int bytesRead;
while ((bytesRead = await p.StandardOutput.BaseStream.ReadAsync(buffer, 0, buffer.Length, cancelToken).ConfigureAwait(false)) != 0)
{
await outStream.WriteAsync(buffer, 0, bytesRead, cancelToken).ConfigureAwait(false);
while ((ulong)outStream.Length - bytesSent > 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/SCv72y
Linux Guide: https://goo.gl/rRhjCp");
Console.ForegroundColor = oldclr;
}
catch (Exception ex)
{
Console.WriteLine($"Buffering stopped: {ex.Message}");
}
finally
{
Console.WriteLine($"Buffering done.");
if (p != null)
{
try
{
p.Kill();
}
catch { }
p.Dispose();
}
}
}, TaskCreationOptions.LongRunning);
internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken) internal async Task Play(IAudioClient voiceClient, CancellationToken cancelToken)
{ {
var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString()); var filename = Path.Combine(MusicModule.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
var bufferTask = BufferSong(filename, cancelToken).ConfigureAwait(false); SongBuffer sb = new SongBuffer(filename, SongInfo, skipTo);
var bufferTask = sb.BufferSong(cancelToken).ConfigureAwait(false);
var inStream = new FileStream(filename, FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); var inStream = new FileStream(sb.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); ;
bytesSent = 0; bytesSent = 0;
try try
{ {
var prebufferingTask = CheckPrebufferingAsync(inStream, cancelToken); var attempt = 0;
var prebufferingTask = CheckPrebufferingAsync(inStream, sb, cancelToken);
var sw = new Stopwatch(); var sw = new Stopwatch();
sw.Start(); sw.Start();
var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken)); var t = await Task.WhenAny(prebufferingTask, Task.Delay(5000, cancelToken));
@ -162,7 +105,6 @@ Check the guides for your platform on how to setup ffmpeg correctly:
Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed); Console.WriteLine("Prebuffering successfully completed in "+ sw.Elapsed);
const int blockSize = 3840; const int blockSize = 3840;
var attempt = 0;
byte[] buffer = new byte[blockSize]; byte[] buffer = new byte[blockSize];
while (!cancelToken.IsCancellationRequested) while (!cancelToken.IsCancellationRequested)
{ {
@ -173,14 +115,31 @@ Check the guides for your platform on how to setup ffmpeg correctly:
{ {
bytesSent += (ulong)read; bytesSent += (ulong)read;
} }
if (read == 0) if (read < blockSize)
if (attempt++ == 20) {
if (sb.IsNextFileReady())
{ {
voiceClient.Wait(); inStream.Dispose();
break; inStream = new FileStream(sb.GetNextFile(), FileMode.Open, FileAccess.Read, FileShare.Write);
read += inStream.Read(buffer, read, buffer.Length - read);
attempt = 0;
}
if (read == 0)
{
if (sb.BufferingCompleted)
break;
if (attempt++ == 20)
{
voiceClient.Wait();
MusicPlayer.SongCancelSource.Cancel();
break;
}
else
await Task.Delay(100, cancelToken).ConfigureAwait(false);
} }
else else
await Task.Delay(100, cancelToken).ConfigureAwait(false); attempt = 0;
}
else else
attempt = 0; attempt = 0;
@ -195,14 +154,16 @@ Check the guides for your platform on how to setup ffmpeg correctly:
{ {
await bufferTask; await bufferTask;
await Task.Run(() => voiceClient.Clear()); await Task.Run(() => voiceClient.Clear());
inStream.Dispose(); if(inStream != null)
try { File.Delete(filename); } catch { } inStream.Dispose();
Console.WriteLine("l");
sb.CleanFiles();
} }
} }
private async Task CheckPrebufferingAsync(Stream inStream, CancellationToken cancelToken) private async Task CheckPrebufferingAsync(Stream inStream, SongBuffer sb, CancellationToken cancelToken)
{ {
while (!bufferingCompleted && inStream.Length < 2.MiB()) while (!sb.BufferingCompleted && inStream.Length < 2.MiB())
{ {
await Task.Delay(100, cancelToken); await Task.Delay(100, cancelToken);
} }

View File

@ -0,0 +1,159 @@
using NadekoBot.Extensions;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
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
{
public SongBuffer(string basename, SongInfo songInfo, int skipTo)
{
Basename = basename;
SongInfo = songInfo;
SkipTo = skipTo;
}
private string Basename;
private SongInfo SongInfo;
private int SkipTo;
private static int MAX_FILE_SIZE = 20.MiB();
private long FileNumber = -1;
private long NextFileToRead = 0;
public bool BufferingCompleted { get; private set;} = false;
private ulong CurrentBufferSize = 0;
public Task BufferSong(CancellationToken cancelToken) =>
Task.Factory.StartNew(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 -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 >= MAX_FILE_SIZE)
{
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/SCv72y
Linux Guide: https://goo.gl/rRhjCp");
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();
}
}
}, TaskCreationOptions.LongRunning);
/// <summary>
/// Return the next file to read, and delete the old one
/// </summary>
/// <returns>Name of the file to read</returns>
public 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;
}
public bool IsNextFileReady()
{
return NextFileToRead <= FileNumber;
}
public void CleanFiles()
{
for (long i = NextFileToRead - 1 ; i <= FileNumber; i++)
{
try
{
File.Delete(Basename + "-" + i);
}
catch { }
}
}
}
}

View File

@ -231,6 +231,7 @@
<Compile Include="Modules\Music\Classes\MusicControls.cs" /> <Compile Include="Modules\Music\Classes\MusicControls.cs" />
<Compile Include="Modules\Music\Classes\PoopyBuffer.cs" /> <Compile Include="Modules\Music\Classes\PoopyBuffer.cs" />
<Compile Include="Modules\Music\Classes\Song.cs" /> <Compile Include="Modules\Music\Classes\Song.cs" />
<Compile Include="Modules\Music\Classes\SongBuffer.cs" />
<Compile Include="Modules\Music\Classes\SoundCloud.cs" /> <Compile Include="Modules\Music\Classes\SoundCloud.cs" />
<Compile Include="Modules\Permissions\Classes\PermissionChecker.cs" /> <Compile Include="Modules\Permissions\Classes\PermissionChecker.cs" />
<Compile Include="Modules\Permissions\Classes\PermissionHelper.cs" /> <Compile Include="Modules\Permissions\Classes\PermissionHelper.cs" />