Merge pull request #593 from Nitix/audio

SongBuffer is now a stream, and the Song will now wait correctly
This commit is contained in:
Master Kwoth 2016-08-26 16:55:19 +02:00 committed by GitHub
commit e64dd6548f
2 changed files with 90 additions and 26 deletions

View File

@ -41,7 +41,7 @@ namespace NadekoBot.Modules.Music.Classes
return $"【{(int)time.TotalMinutes}m {time.Seconds}s】"; return $"【{(int)time.TotalMinutes}m {time.Seconds}s】";
} }
const int milliseconds = 10; const int milliseconds = 20;
const int samplesPerFrame = (48000 / 1000) * milliseconds; const int samplesPerFrame = (48000 / 1000) * milliseconds;
const int frameBytes = 3840; //16-bit, 2 channels const int frameBytes = 3840; //16-bit, 2 channels
@ -84,10 +84,8 @@ namespace NadekoBot.Modules.Music.Classes
{ {
var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString()); var filename = Path.Combine(Music.MusicDataPath, DateTime.Now.UnixTimestamp().ToString());
SongBuffer sb = new SongBuffer(filename, SongInfo, skipTo); SongBuffer inStream = new SongBuffer(MusicPlayer, filename, SongInfo, skipTo);
var bufferTask = sb.BufferSong(cancelToken).ConfigureAwait(false); var bufferTask = inStream.BufferSong(cancelToken).ConfigureAwait(false);
var inStream = new FileStream(sb.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write); ;
bytesSent = 0; bytesSent = 0;
@ -95,7 +93,7 @@ namespace NadekoBot.Modules.Music.Classes
{ {
var attempt = 0; var attempt = 0;
var prebufferingTask = CheckPrebufferingAsync(inStream, sb, cancelToken); var prebufferingTask = CheckPrebufferingAsync(inStream, 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));
@ -112,9 +110,10 @@ namespace NadekoBot.Modules.Music.Classes
sw.Stop(); sw.Stop();
_log.Debug("Prebuffering successfully completed in "+ sw.Elapsed); _log.Debug("Prebuffering successfully completed in "+ sw.Elapsed);
var outStream = voiceClient.CreatePCMStream(960); var outStream = voiceClient.CreatePCMStream(960);
int nextTime = Environment.TickCount + milliseconds;
byte[] buffer = new byte[frameBytes]; byte[] buffer = new byte[frameBytes];
while (!cancelToken.IsCancellationRequested) while (!cancelToken.IsCancellationRequested)
{ {
@ -128,16 +127,9 @@ namespace NadekoBot.Modules.Music.Classes
} }
if (read < frameBytes) if (read < frameBytes)
{ {
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) if (inStream.BufferingCompleted)
break; break;
if (attempt++ == 20) if (attempt++ == 20)
{ {
@ -156,10 +148,14 @@ namespace NadekoBot.Modules.Music.Classes
while (this.MusicPlayer.Paused) while (this.MusicPlayer.Paused)
await Task.Delay(200, cancelToken).ConfigureAwait(false); await Task.Delay(200, cancelToken).ConfigureAwait(false);
//buffer = AdjustVolume(buffer, MusicPlayer.Volume);
buffer = AdjustVolume(buffer, MusicPlayer.Volume);
if (read != frameBytes) continue; if (read != frameBytes) continue;
nextTime = unchecked(nextTime + milliseconds);
int delayMillis = unchecked(nextTime - Environment.TickCount);
if (delayMillis > 0)
await Task.Delay(delayMillis, cancelToken).ConfigureAwait(false);
await outStream.WriteAsync(buffer, 0, read); await outStream.WriteAsync(buffer, 0, read);
await Task.Delay(10);
} }
} }
finally finally
@ -167,13 +163,12 @@ namespace NadekoBot.Modules.Music.Classes
await bufferTask; await bufferTask;
if(inStream != null) if(inStream != null)
inStream.Dispose(); inStream.Dispose();
sb.CleanFiles();
} }
} }
private async Task CheckPrebufferingAsync(Stream inStream, SongBuffer sb, CancellationToken cancelToken) private async Task CheckPrebufferingAsync(SongBuffer inStream, CancellationToken cancelToken)
{ {
while (!sb.BufferingCompleted && inStream.Length < 10.MiB()) while (!inStream.BufferingCompleted && inStream.Length < 10.MiB())
{ {
await Task.Delay(100, cancelToken); await Task.Delay(100, cancelToken);
} }

View File

@ -15,23 +15,27 @@ namespace NadekoBot.Modules.Music.Classes
/// Create a buffer for a song file. It will create multiples files to ensure, that radio don't fill up disk space. /// 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. /// It also help for large music by deleting files that are already seen.
/// </summary> /// </summary>
class SongBuffer class SongBuffer : Stream
{ {
public SongBuffer(string basename, SongInfo songInfo, int skipTo) public SongBuffer(MusicPlayer musicPlayer, string basename, SongInfo songInfo, int skipTo)
{ {
MusicPlayer = musicPlayer;
Basename = basename; Basename = basename;
SongInfo = songInfo; SongInfo = songInfo;
SkipTo = skipTo; SkipTo = skipTo;
CurrentFileStream = new FileStream(this.GetNextFile(), FileMode.OpenOrCreate, FileAccess.Read, FileShare.Write);
} }
MusicPlayer MusicPlayer;
private string Basename; private string Basename;
private SongInfo SongInfo; private SongInfo SongInfo;
private int SkipTo; private int SkipTo;
private static int MAX_FILE_SIZE = 20.MiB(); private static int MAX_FILE_SIZE = 2.MiB();
private long FileNumber = -1; private long FileNumber = -1;
@ -41,6 +45,8 @@ namespace NadekoBot.Modules.Music.Classes
private ulong CurrentBufferSize = 0; private ulong CurrentBufferSize = 0;
private FileStream CurrentFileStream;
public Task BufferSong(CancellationToken cancelToken) => public Task BufferSong(CancellationToken cancelToken) =>
Task.Factory.StartNew(async () => Task.Factory.StartNew(async () =>
{ {
@ -122,7 +128,7 @@ Check the guides for your platform on how to setup ffmpeg correctly:
/// Return the next file to read, and delete the old one /// Return the next file to read, and delete the old one
/// </summary> /// </summary>
/// <returns>Name of the file to read</returns> /// <returns>Name of the file to read</returns>
public string GetNextFile() private string GetNextFile()
{ {
string filename = Basename + "-" + NextFileToRead; string filename = Basename + "-" + NextFileToRead;
@ -139,12 +145,12 @@ Check the guides for your platform on how to setup ffmpeg correctly:
return filename; return filename;
} }
public bool IsNextFileReady() private bool IsNextFileReady()
{ {
return NextFileToRead <= FileNumber; return NextFileToRead <= FileNumber;
} }
public void CleanFiles() private void CleanFiles()
{ {
for (long i = NextFileToRead - 1 ; i <= FileNumber; i++) for (long i = NextFileToRead - 1 ; i <= FileNumber; i++)
{ {
@ -155,5 +161,68 @@ Check the guides for your platform on how to setup ffmpeg correctly:
catch { } 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
{
return 0;
}
set
{
}
}
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);
}
}
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();
}
} }
} }