Skip to content

Commit

Permalink
音频中继示例
Browse files Browse the repository at this point in the history
  • Loading branch information
gehongyan committed Sep 10, 2024
1 parent b486525 commit 1bfd488
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 7 deletions.
101 changes: 101 additions & 0 deletions samples/Kook.Net.Samples.Audio/Modules/MusicModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,106 @@ public async Task ListAsync()
await ReplyTextAsync(string.Join(Environment.NewLine, queue.Select((x, i) => $"[{i}] {x}")));
}

[Command("relay", RunMode = RunMode.Async)]
[RequireContext(ContextType.Guild)]
public async Task RelayAsync(SocketVoiceChannel targetChannel)
{
if (Context.Message.Source is not MessageSource.User) return;
if (Context.Channel is not SocketVoiceChannel voiceChannel)
{
await ReplyTextAsync("You muse use this command in a voice channel.");
return;
}

IAudioClient? sourceClient = await voiceChannel.ConnectAsync();
if (sourceClient is null)
{
await ReplyTextAsync("Failed to connect to the voice channel.");
return;
}
await Task.Delay(TimeSpan.FromSeconds(3));
IAudioClient? targetClient = await targetChannel.ConnectAsync();
if (targetClient is null)
{
await ReplyTextAsync("Failed to connect to the target voice channel.");
return;
}
bool hasStarted = false;

CancellationTokenSource relayCancellationTokenSource = new();
sourceClient.StreamCreated += (ssrc, stream) =>
{
if (hasStarted)
return Task.CompletedTask;
hasStarted = true;
Task.Run(async () =>

Check warning on line 269 in samples/Kook.Net.Samples.Audio/Modules/MusicModule.cs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest) / Build and Test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 269 in samples/Kook.Net.Samples.Audio/Modules/MusicModule.cs

View workflow job for this annotation

GitHub Actions / Build and Test (ubuntu-latest) / Build and Test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 269 in samples/Kook.Net.Samples.Audio/Modules/MusicModule.cs

View workflow job for this annotation

GitHub Actions / Build and Test (windows-latest) / Build and Test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 269 in samples/Kook.Net.Samples.Audio/Modules/MusicModule.cs

View workflow job for this annotation

GitHub Actions / Build and Test (windows-latest) / Build and Test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 269 in samples/Kook.Net.Samples.Audio/Modules/MusicModule.cs

View workflow job for this annotation

GitHub Actions / Build and Test (macOS-latest) / Build and Test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.

Check warning on line 269 in samples/Kook.Net.Samples.Audio/Modules/MusicModule.cs

View workflow job for this annotation

GitHub Actions / Build and Test (macOS-latest) / Build and Test

This async method lacks 'await' operators and will run synchronously. Consider using the 'await' operator to await non-blocking API calls, or 'await Task.Run(...)' to do CPU-bound work on a background thread.
{
try
{
Console.WriteLine($"Stream created for SSRC: {ssrc}.");
_ = Task.Run(async () => await PushStreamAsync(targetClient, stream, relayCancellationTokenSource.Token));
}
catch (Exception e)
{
Console.WriteLine(e);
}
});
return Task.CompletedTask;
};
sourceClient.StreamDestroyed += ssrc =>
{
relayCancellationTokenSource.Cancel();
Console.WriteLine($"Stream destroyed for {ssrc}.");
return Task.CompletedTask;
};

CancellationTokenSource cancellationTokenSource = new(TimeSpan.FromSeconds(10));
await PushStreamAsync(sourceClient, cancellationTokenSource.Token);

await ReplyTextAsync("Relaying.");

}

private async Task PushStreamAsync(IAudioClient audioClient, Stream stream, CancellationToken cancellationToken)
{
await using AudioOutStream kook = audioClient.CreatePcmStream(AudioApplication.Music);
try
{
await stream.CopyToAsync(kook, cancellationToken);
}
finally
{
await kook.FlushAsync(cancellationToken);
}
}

private async Task PushStreamAsync(IAudioClient audioClient, CancellationToken cancellationToken)
{
// Just push some audio data to the audio client to start receiving audio data.
using Process? ffmpeg = Process.Start(new ProcessStartInfo
{
FileName = "ffmpeg",
Arguments = $"""-hide_banner -loglevel panic -i "https://sao.fm/api/fm/?id=734" -ac 2 -f s16le -ar 48000 -""",
UseShellExecute = false,
RedirectStandardOutput = true,
});
if (ffmpeg is null) return;
await using Stream output = ffmpeg.StandardOutput.BaseStream;
await using AudioOutStream kook = audioClient.CreatePcmStream(AudioApplication.Music);
try
{
await output.CopyToAsync(kook, cancellationToken);
}
catch (Exception e)
{
Console.WriteLine(e);
}
finally
{
await kook.FlushAsync(cancellationToken);
}
}

private static readonly Regex neteaseSongPageRegex = new(@"^https://music.163.com(/#)?/song\?id=(?<id>\d+)(&.*)?$", RegexOptions.Compiled);
private static readonly Regex qqSongPageRegex = new(@"^https://y.qq.com/n/ryqq/songDetail/(?<id>\w+)(&.*)?$", RegexOptions.Compiled);

Expand Down Expand Up @@ -298,3 +398,4 @@ public async Task ListAsync()
return null;
}
}

2 changes: 1 addition & 1 deletion src/Kook.Net.Core/Entities/Roles/IRole.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
namespace Kook;

/// <summary>
/// 表示一个基于网关的可授予服务器用户的角色
/// 表示一个通用的可授予服务器用户的角色
/// </summary>
public interface IRole : IEntity<uint>, IDeletable, IMentionable, IComparable<IRole>
{
Expand Down
14 changes: 9 additions & 5 deletions src/Kook.Net.Rest/Net/DefaultRestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ public DefaultRestClient(string baseUrl, bool useProxy = false)

_client = new HttpClient(new HttpClientHandler
{
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, UseCookies = false, UseProxy = useProxy
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
UseCookies = false,
UseProxy = useProxy
});
SetHeader("accept-encoding", "gzip, deflate");

Expand All @@ -60,8 +62,8 @@ private void Dispose(bool disposing)
{
if (!_isDisposed)
{
if (disposing) _client.Dispose();

if (disposing)
_client.Dispose();
_isDisposed = true;
}
}
Expand All @@ -83,7 +85,8 @@ public async Task<RestResponse> SendAsync(HttpMethod method, string endpoint, Ca
string uri = Path.Combine(_baseUrl, endpoint);
using (HttpRequestMessage restRequest = new(method, uri))
{
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
if (reason != null)
restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));

if (requestHeaders != null)
foreach (KeyValuePair<string, IEnumerable<string>> header in requestHeaders)
Expand All @@ -99,7 +102,8 @@ public async Task<RestResponse> SendAsync(HttpMethod method, string endpoint, st
{
string uri = Path.Combine(_baseUrl, endpoint);
using HttpRequestMessage restRequest = new(method, uri);
if (reason != null) restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));
if (reason != null)
restRequest.Headers.Add("X-Audit-Log-Reason", Uri.EscapeDataString(reason));

if (requestHeaders != null)
foreach (KeyValuePair<string, IEnumerable<string>> header in requestHeaders)
Expand Down
4 changes: 4 additions & 0 deletions src/Kook.Net.WebSocket/Audio/Streams/InputStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,11 @@ public override async Task<RtpFrame> ReadFrameAsync(CancellationToken cancellati
public override void WriteHeader(ushort seq, uint timestamp, bool missed)
{
if (_hasHeader)
{
_hasHeader = false;
throw new InvalidOperationException("Header received with no payload");
}

_hasHeader = true;
_nextSeq = seq;
_nextTimestamp = timestamp;
Expand Down
6 changes: 5 additions & 1 deletion src/Kook.Net.WebSocket/Audio/Streams/OpusDecodeStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ public OpusDecodeStream(AudioStream next)
public override void WriteHeader(ushort seq, uint timestamp, bool missed)
{
if (_hasHeader)
throw new InvalidOperationException("Header received with no payload.");
{
_hasHeader = false;
throw new InvalidOperationException("Header received with no payload");
}

_hasHeader = true;

_nextMissed = missed;
Expand Down
3 changes: 3 additions & 0 deletions src/Kook.Net.WebSocket/Audio/Streams/RtpWriteStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,10 @@ public RtpWriteStream(AudioStream next, uint ssrc, byte payloadType, int bufferS
public override void WriteHeader(ushort seq, uint timestamp, bool missed)
{
if (_hasHeader)
{
_hasHeader = false;
throw new InvalidOperationException("Header received with no payload");
}

_hasHeader = true;
_nextSeq = seq;
Expand Down

0 comments on commit 1bfd488

Please sign in to comment.