Merge pull request #484 from Nitix/dev-bugfix/478
Change how we handle audio file buffer, FIX #478
This commit is contained in:
commit
4bca94de04
@ -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; }
|
||||||
|
@ -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 < blockSize)
|
||||||
|
{
|
||||||
|
if (sb.IsNextFileReady())
|
||||||
|
{
|
||||||
|
inStream.Dispose();
|
||||||
|
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 (read == 0)
|
||||||
|
{
|
||||||
|
if (sb.BufferingCompleted)
|
||||||
|
break;
|
||||||
if (attempt++ == 20)
|
if (attempt++ == 20)
|
||||||
{
|
{
|
||||||
voiceClient.Wait();
|
voiceClient.Wait();
|
||||||
|
MusicPlayer.SongCancelSource.Cancel();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
await Task.Delay(100, cancelToken).ConfigureAwait(false);
|
await Task.Delay(100, cancelToken).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
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());
|
||||||
|
if(inStream != null)
|
||||||
inStream.Dispose();
|
inStream.Dispose();
|
||||||
try { File.Delete(filename); } catch { }
|
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);
|
||||||
}
|
}
|
||||||
|
159
NadekoBot/Modules/Music/Classes/SongBuffer.cs
Normal file
159
NadekoBot/Modules/Music/Classes/SongBuffer.cs
Normal 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 { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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" />
|
||||||
|
Loading…
Reference in New Issue
Block a user