diff --git a/src/Discord.Net.Core/Audio/AudioOutStream.cs b/src/Discord.Net.Core/Audio/AudioOutStream.cs new file mode 100644 index 000000000..dd91b71ee --- /dev/null +++ b/src/Discord.Net.Core/Audio/AudioOutStream.cs @@ -0,0 +1,16 @@ +using System.IO; +using System.Threading; +using System.Threading.Tasks; + +namespace Discord.Audio +{ + public abstract class AudioOutStream : Stream + { + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + + public virtual void Clear() { } + public virtual Task ClearAsync(CancellationToken cancelToken) { return Task.Delay(0); } + } +} diff --git a/src/Discord.Net.Core/Audio/IAudioClient.cs b/src/Discord.Net.Core/Audio/IAudioClient.cs index 38e25d7b5..dfc6f60bd 100644 --- a/src/Discord.Net.Core/Audio/IAudioClient.cs +++ b/src/Discord.Net.Core/Audio/IAudioClient.cs @@ -22,26 +22,26 @@ namespace Discord.Audio /// /// Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively. /// - Stream CreateOpusStream(int samplesPerFrame, int bufferMillis = 1000); + AudioOutStream CreateOpusStream(int samplesPerFrame, int bufferMillis = 1000); /// /// Creates a new outgoing stream accepting Opus-encoded data. This is a direct stream with no internal timer. /// /// Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively. /// - Stream CreateDirectOpusStream(int samplesPerFrame); + AudioOutStream CreateDirectOpusStream(int samplesPerFrame); /// /// Creates a new outgoing stream accepting PCM (raw) data. /// /// Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively. /// /// - Stream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null, int bufferMillis = 1000); + AudioOutStream CreatePCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null, int bufferMillis = 1000); /// /// Creates a new direct outgoing stream accepting PCM (raw) data. This is a direct stream with no internal timer. /// /// Samples per frame. Must be 120, 240, 480, 960, 1920 or 2880, representing 2.5, 5, 10, 20, 40 or 60 milliseconds respectively. /// /// - Stream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null); + AudioOutStream CreateDirectPCMStream(int samplesPerFrame, int channels = 2, int? bitrate = null); } } diff --git a/src/Discord.Net.WebSocket/Audio/AudioClient.cs b/src/Discord.Net.WebSocket/Audio/AudioClient.cs index dd01aa2f9..784cdf194 100644 --- a/src/Discord.Net.WebSocket/Audio/AudioClient.cs +++ b/src/Discord.Net.WebSocket/Audio/AudioClient.cs @@ -170,25 +170,25 @@ namespace Discord.Audio await Discord.ApiClient.SendVoiceStateUpdateAsync(Guild.Id, null, false, false).ConfigureAwait(false); } - public Stream CreateOpusStream(int samplesPerFrame, int bufferMillis) + public AudioOutStream CreateOpusStream(int samplesPerFrame, int bufferMillis) { CheckSamplesPerFrame(samplesPerFrame); var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, bufferMillis, _cancelTokenSource.Token); return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc); } - public Stream CreateDirectOpusStream(int samplesPerFrame) + public AudioOutStream CreateDirectOpusStream(int samplesPerFrame) { CheckSamplesPerFrame(samplesPerFrame); var target = new DirectAudioTarget(ApiClient); return new RTPWriteStream(target, _secretKey, samplesPerFrame, _ssrc); } - public Stream CreatePCMStream(int samplesPerFrame, int channels, int? bitrate, int bufferMillis) + public AudioOutStream CreatePCMStream(int samplesPerFrame, int channels, int? bitrate, int bufferMillis) { CheckSamplesPerFrame(samplesPerFrame); var target = new BufferedAudioTarget(ApiClient, samplesPerFrame, bufferMillis, _cancelTokenSource.Token); return new OpusEncodeStream(target, _secretKey, channels, samplesPerFrame, _ssrc, bitrate); } - public Stream CreateDirectPCMStream(int samplesPerFrame, int channels, int? bitrate) + public AudioOutStream CreateDirectPCMStream(int samplesPerFrame, int channels, int? bitrate) { CheckSamplesPerFrame(samplesPerFrame); var target = new DirectAudioTarget(ApiClient); diff --git a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs index 300cd194c..7ba95c591 100644 --- a/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs +++ b/src/Discord.Net.WebSocket/Audio/Streams/RTPWriteStream.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; namespace Discord.Audio { - internal class RTPWriteStream : Stream + internal class RTPWriteStream : AudioOutStream { private readonly IAudioTarget _target; private readonly byte[] _nonce, _secretKey; @@ -14,10 +14,6 @@ namespace Discord.Audio protected readonly byte[] _buffer; - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - internal RTPWriteStream(IAudioTarget target, byte[] secretKey, int samplesPerFrame, uint ssrc) { _target = target; @@ -40,6 +36,7 @@ namespace Discord.Audio } public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested(); unchecked { if (_nonce[3]++ == byte.MaxValue) @@ -63,7 +60,16 @@ namespace Discord.Audio } public override async Task FlushAsync(CancellationToken cancellationToken) { - await _target.FlushAsync().ConfigureAwait(false); + await _target.FlushAsync(cancellationToken).ConfigureAwait(false); + } + + public override void Clear() + { + ClearAsync(CancellationToken.None).GetAwaiter().GetResult(); + } + public override async Task ClearAsync(CancellationToken cancelToken) + { + await _target.ClearAsync(cancelToken).ConfigureAwait(false); } public override long Length { get { throw new NotSupportedException(); } } diff --git a/src/Discord.Net.WebSocket/Audio/Targets/BufferedAudioTarget.cs b/src/Discord.Net.WebSocket/Audio/Targets/BufferedAudioTarget.cs index 65ab537e0..b27c5c8b3 100644 --- a/src/Discord.Net.WebSocket/Audio/Targets/BufferedAudioTarget.cs +++ b/src/Discord.Net.WebSocket/Audio/Targets/BufferedAudioTarget.cs @@ -87,17 +87,25 @@ namespace Discord.Audio Buffer.BlockCopy(data, 0, buffer, 0, count); _queuedFrames.Enqueue(new Frame(buffer, count)); } - - public async Task FlushAsync() + + public async Task FlushAsync(CancellationToken cancelToken) { while (true) { + cancelToken.ThrowIfCancellationRequested(); if (_queuedFrames.Count == 0) return; - await Task.Delay(250).ConfigureAwait(false); + await Task.Delay(250, cancelToken).ConfigureAwait(false); } } - + public Task ClearAsync(CancellationToken cancelToken) + { + Frame ignored; + do + cancelToken.ThrowIfCancellationRequested(); + while (_queuedFrames.TryDequeue(out ignored)); + return Task.Delay(0); + } protected void Dispose(bool disposing) { if (disposing) diff --git a/src/Discord.Net.WebSocket/Audio/Targets/DirectAudioTarget.cs b/src/Discord.Net.WebSocket/Audio/Targets/DirectAudioTarget.cs index c5b0983d0..2440fc0a8 100644 --- a/src/Discord.Net.WebSocket/Audio/Targets/DirectAudioTarget.cs +++ b/src/Discord.Net.WebSocket/Audio/Targets/DirectAudioTarget.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Discord.Audio { @@ -13,7 +14,9 @@ namespace Discord.Audio public Task SendAsync(byte[] buffer, int count) => _client.SendAsync(buffer, count); - public Task FlushAsync() + public Task FlushAsync(CancellationToken cancelToken) + => Task.Delay(0); + public Task ClearAsync(CancellationToken cancelToken) => Task.Delay(0); } } diff --git a/src/Discord.Net.WebSocket/Audio/Targets/IAudioTarget.cs b/src/Discord.Net.WebSocket/Audio/Targets/IAudioTarget.cs index 8c4384550..1aa0d4ade 100644 --- a/src/Discord.Net.WebSocket/Audio/Targets/IAudioTarget.cs +++ b/src/Discord.Net.WebSocket/Audio/Targets/IAudioTarget.cs @@ -1,10 +1,12 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Discord.Audio { internal interface IAudioTarget { Task SendAsync(byte[] buffer, int count); - Task FlushAsync(); + Task FlushAsync(CancellationToken cancelToken); + Task ClearAsync(CancellationToken cancelToken); } }