typo, new sound, bugix
Sound is not 100% working, it sometimes exits the song before it ends
This commit is contained in:
parent
489caab8cc
commit
78f837bfc8
@ -13,11 +13,10 @@ using VideoLibrary;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Discord.Legacy;
|
using Discord.Legacy;
|
||||||
|
using System.Net;
|
||||||
|
|
||||||
namespace NadekoBot.Modules
|
namespace NadekoBot.Modules {
|
||||||
{
|
class Music : DiscordModule {
|
||||||
class Music : DiscordModule
|
|
||||||
{
|
|
||||||
private static bool exit = true;
|
private static bool exit = true;
|
||||||
|
|
||||||
public static bool NextSong = false;
|
public static bool NextSong = false;
|
||||||
@ -28,10 +27,9 @@ namespace NadekoBot.Modules
|
|||||||
|
|
||||||
public static YouTubeVideo CurrentSong;
|
public static YouTubeVideo CurrentSong;
|
||||||
|
|
||||||
public static bool Exit
|
public static bool Exit {
|
||||||
{
|
|
||||||
get { return exit; }
|
get { return exit; }
|
||||||
set { exit = value;} // if i set this to true, break the song and exit the main loop
|
set { exit = value; } // if i set this to true, break the song and exit the main loop
|
||||||
}
|
}
|
||||||
|
|
||||||
public Music() : base() {
|
public Music() : base() {
|
||||||
@ -46,21 +44,17 @@ namespace NadekoBot.Modules
|
|||||||
//m sh - shuffle songs
|
//m sh - shuffle songs
|
||||||
//m pl - current playlist
|
//m pl - current playlist
|
||||||
|
|
||||||
public override void Install(ModuleManager manager)
|
public override void Install(ModuleManager manager) {
|
||||||
{
|
|
||||||
var client = NadekoBot.client;
|
var client = NadekoBot.client;
|
||||||
manager.CreateCommands("!m", cgb =>
|
manager.CreateCommands("!m", cgb => {
|
||||||
{
|
|
||||||
//queue all more complex commands
|
//queue all more complex commands
|
||||||
commands.ForEach(cmd => cmd.Init(cgb));
|
commands.ForEach(cmd => cmd.Init(cgb));
|
||||||
|
|
||||||
cgb.CreateCommand("n")
|
cgb.CreateCommand("n")
|
||||||
.Alias("next")
|
.Alias("next")
|
||||||
.Description("Goes to the next song in the queue.")
|
.Description("Goes to the next song in the queue.")
|
||||||
.Do(e =>
|
.Do(e => {
|
||||||
{
|
if (Voice != null && Exit == false) {
|
||||||
if (Voice != null && Exit == false)
|
|
||||||
{
|
|
||||||
NextSong = true;
|
NextSong = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -68,10 +62,8 @@ namespace NadekoBot.Modules
|
|||||||
cgb.CreateCommand("s")
|
cgb.CreateCommand("s")
|
||||||
.Alias("stop")
|
.Alias("stop")
|
||||||
.Description("Completely stops the music and unbinds the bot from the channel.")
|
.Description("Completely stops the music and unbinds the bot from the channel.")
|
||||||
.Do(e =>
|
.Do(e => {
|
||||||
{
|
if (Voice != null && Exit == false) {
|
||||||
if (Voice != null && Exit == false)
|
|
||||||
{
|
|
||||||
Exit = true;
|
Exit = true;
|
||||||
SongQueue = new List<YouTubeVideo>();
|
SongQueue = new List<YouTubeVideo>();
|
||||||
}
|
}
|
||||||
@ -80,18 +72,13 @@ namespace NadekoBot.Modules
|
|||||||
cgb.CreateCommand("p")
|
cgb.CreateCommand("p")
|
||||||
.Alias("pause")
|
.Alias("pause")
|
||||||
.Description("Pauses the song")
|
.Description("Pauses the song")
|
||||||
.Do(async e =>
|
.Do(async e => {
|
||||||
{
|
if (Voice != null && Exit == false && CurrentSong != null) {
|
||||||
if (Voice != null && Exit == false && CurrentSong != null)
|
|
||||||
{
|
|
||||||
Pause = !Pause;
|
Pause = !Pause;
|
||||||
if (Pause)
|
if (Pause) {
|
||||||
{
|
await e.Send("Pausing. Run the command again to resume.");
|
||||||
await e.Send( "Pausing. Run the command again to resume.");
|
} else {
|
||||||
}
|
await e.Send("Resuming...");
|
||||||
else
|
|
||||||
{
|
|
||||||
await e.Send( "Resuming...");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -115,113 +102,93 @@ namespace NadekoBot.Modules
|
|||||||
.Alias("yq")
|
.Alias("yq")
|
||||||
.Description("Queue a song using a multi/single word name.\nUsage: `!m q Dream Of Venice`")
|
.Description("Queue a song using a multi/single word name.\nUsage: `!m q Dream Of Venice`")
|
||||||
.Parameter("Query", ParameterType.Unparsed)
|
.Parameter("Query", ParameterType.Unparsed)
|
||||||
.Do(async e =>
|
.Do(async e => {
|
||||||
{
|
|
||||||
var youtube = YouTube.Default;
|
var youtube = YouTube.Default;
|
||||||
var video = youtube.GetAllVideos(Searches.FindYoutubeUrlByKeywords(e.Args[0]))
|
var video = youtube.GetAllVideos(Searches.FindYoutubeUrlByKeywords(e.Args[0]))
|
||||||
.Where(v => v.AdaptiveKind == AdaptiveKind.Audio)
|
.Where(v => v.AdaptiveKind == AdaptiveKind.Audio)
|
||||||
.OrderByDescending(v => v.AudioBitrate).FirstOrDefault();
|
.OrderByDescending(v => v.AudioBitrate).FirstOrDefault();
|
||||||
|
|
||||||
if (video?.Uri != "" && video.Uri != null)
|
if (video?.Uri != "" && video.Uri != null) {
|
||||||
{
|
|
||||||
SongQueue.Add(video);
|
SongQueue.Add(video);
|
||||||
await e.Send( "**Queued** " + video.FullName);
|
await e.Send("**Queued** " + video.FullName);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("lq")
|
cgb.CreateCommand("lq")
|
||||||
.Alias("ls").Alias("lp")
|
.Alias("ls").Alias("lp")
|
||||||
.Description("Lists up to 10 currently queued songs.")
|
.Description("Lists up to 10 currently queued songs.")
|
||||||
.Do(async e =>
|
.Do(async e => {
|
||||||
{
|
await e.Send(SongQueue.Count + " videos currently queued.");
|
||||||
await e.Send( SongQueue.Count + " videos currently queued.");
|
await e.Send(string.Join("\n", SongQueue.Select(v => v.FullName).Take(10)));
|
||||||
await e.Send( string.Join("\n", SongQueue.Select(v => v.FullName).Take(10)));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("sh")
|
cgb.CreateCommand("sh")
|
||||||
.Description("Shuffles the current playlist.")
|
.Description("Shuffles the current playlist.")
|
||||||
.Do(async e =>
|
.Do(async e => {
|
||||||
{
|
if (SongQueue.Count < 2) {
|
||||||
if (SongQueue.Count < 2)
|
await e.Send("Not enough songs in order to perform the shuffle.");
|
||||||
{
|
|
||||||
await e.Send( "Not enough songs in order to perform the shuffle.");
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
SongQueue.Shuffle();
|
SongQueue.Shuffle();
|
||||||
await e.Send( "Songs shuffled!");
|
await e.Send("Songs shuffled!");
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("radio")
|
cgb.CreateCommand("radio")
|
||||||
.Alias("music")
|
.Alias("music")
|
||||||
.Description("Binds to a voice and text channel in order to play music.")
|
.Description("Binds to a voice and text channel in order to play music.")
|
||||||
.Parameter("ChannelName", ParameterType.Unparsed)
|
.Parameter("ChannelName", ParameterType.Unparsed)
|
||||||
.Do(async e =>
|
.Do(async e => {
|
||||||
{
|
if (Voice != null) return;
|
||||||
if (Voice != null) return;
|
VoiceChannel = e.Server.FindChannels(e.GetArg("ChannelName").Trim(), ChannelType.Voice).FirstOrDefault();
|
||||||
VoiceChannel = e.Server.FindChannels(e.GetArg("ChannelName").Trim(), ChannelType.Voice).FirstOrDefault();
|
Voice = await client.Audio().Join(VoiceChannel);
|
||||||
Voice = await client.Audio().Join(VoiceChannel);
|
Exit = false;
|
||||||
Exit = false;
|
NextSong = false;
|
||||||
NextSong = false;
|
Pause = false;
|
||||||
Pause = false;
|
try {
|
||||||
try
|
while (true) {
|
||||||
{
|
|
||||||
while (true)
|
|
||||||
{
|
|
||||||
if (Exit) break;
|
if (Exit) break;
|
||||||
if (SongQueue.Count == 0 || Pause) { Thread.Sleep(100); continue; }
|
if (SongQueue.Count == 0 || Pause) { Thread.Sleep(100); continue; }
|
||||||
if (!LoadNextSong()) break;
|
if (!LoadNextSong()) break;
|
||||||
|
|
||||||
await Task.Run(async () =>
|
await Task.Run(async () => {
|
||||||
{
|
if (Exit) {
|
||||||
if (Exit)
|
|
||||||
{
|
|
||||||
Voice = null;
|
Voice = null;
|
||||||
Exit = false;
|
Exit = false;
|
||||||
await e.Send( "Exiting...");
|
await e.Send("Exiting...");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int blockSize = 3840;
|
|
||||||
byte[] buffer = new byte[3840];
|
|
||||||
|
|
||||||
var msg = await e.Send( "Playing " + Music.CurrentSong.FullName + " [00:00]");
|
var streamer = new AudioStreamer(Music.CurrentSong.Uri);
|
||||||
|
streamer.Start();
|
||||||
|
while (streamer.BytesSentToTranscoder < 100 * 0x1000 || streamer.NetworkDone)
|
||||||
|
await Task.Delay(500);
|
||||||
|
|
||||||
|
int blockSize = 1920 * client.Audio().Config.Channels;
|
||||||
|
byte[] buffer = new byte[blockSize];
|
||||||
|
|
||||||
|
var msg = await e.Send("Playing " + Music.CurrentSong.FullName + " [00:00]");
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
int byteCount;
|
int byteCount;
|
||||||
using (var stream = GetAudioFileStream(Music.CurrentSong.Uri))
|
var m = await e.Send("Downloading song...");
|
||||||
{
|
|
||||||
var m = await e.Send("Downloading song...");
|
|
||||||
var memStream = new MemoryStream();
|
|
||||||
while (true) {
|
|
||||||
byte[] buff = new byte[0x4000 * 10];
|
|
||||||
int read = stream.Read(buff, 0, buff.Length);
|
|
||||||
if (read <= 0) break;
|
|
||||||
memStream.Write(buff, 0, read);
|
|
||||||
}
|
|
||||||
|
|
||||||
e.Send("Song downloaded");
|
while ((byteCount = streamer.PCMOutput.Read(buffer, 0, blockSize)) > 0) {
|
||||||
memStream.Position = 0;
|
Voice.Send(buffer, byteCount);
|
||||||
while ((byteCount = memStream.Read(buffer, 0, blockSize)) > 0)
|
counter += blockSize;
|
||||||
{
|
if (NextSong) {
|
||||||
Voice.Send(buffer, byteCount);
|
NextSong = false;
|
||||||
counter += blockSize;
|
break;
|
||||||
if (NextSong)
|
|
||||||
{
|
|
||||||
NextSong = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (Exit)
|
|
||||||
{
|
|
||||||
Exit = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while (Pause) Thread.Sleep(100);
|
|
||||||
}
|
}
|
||||||
|
if (Exit) {
|
||||||
|
Exit = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
while (Pause) Thread.Sleep(100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Voice.Wait();
|
Voice.Wait();
|
||||||
}
|
} catch (Exception ex) { Console.WriteLine(ex.ToString()); }
|
||||||
catch (Exception ex) { Console.WriteLine(ex.ToString()); }
|
|
||||||
await Voice.Disconnect();
|
await Voice.Disconnect();
|
||||||
Voice = null;
|
Voice = null;
|
||||||
VoiceChannel = null;
|
VoiceChannel = null;
|
||||||
@ -229,10 +196,8 @@ namespace NadekoBot.Modules
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private Stream GetAudioFileStream(string file)
|
private Stream GetAudioFileStream(string file) {
|
||||||
{
|
Process p = Process.Start(new ProcessStartInfo() {
|
||||||
Process p = Process.Start(new ProcessStartInfo()
|
|
||||||
{
|
|
||||||
FileName = "ffmpeg",
|
FileName = "ffmpeg",
|
||||||
Arguments = "-i \"" + Uri.EscapeUriString(file) + "\" -f s16le -ar 48000 -af volume=1 -ac 2 pipe:1 ",
|
Arguments = "-i \"" + Uri.EscapeUriString(file) + "\" -f s16le -ar 48000 -af volume=1 -ac 2 pipe:1 ",
|
||||||
UseShellExecute = false,
|
UseShellExecute = false,
|
||||||
@ -241,8 +206,7 @@ namespace NadekoBot.Modules
|
|||||||
return p.StandardOutput.BaseStream;
|
return p.StandardOutput.BaseStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool LoadNextSong()
|
private bool LoadNextSong() {
|
||||||
{
|
|
||||||
if (SongQueue.Count == 0) {
|
if (SongQueue.Count == 0) {
|
||||||
CurrentSong = null;
|
CurrentSong = null;
|
||||||
return false;
|
return false;
|
||||||
@ -252,4 +216,136 @@ namespace NadekoBot.Modules
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//new stuff
|
||||||
|
class AudioStreamer {
|
||||||
|
string sourceUrl; Channel statusTextChannel;
|
||||||
|
int totalSourceBytes;
|
||||||
|
public bool NetworkDone { get; private set; }
|
||||||
|
public int BytesSentToTranscoder { get; private set; }
|
||||||
|
public Stream PCMOutput { get; private set; }
|
||||||
|
CancellationTokenSource tokenSource1 = new CancellationTokenSource();
|
||||||
|
CancellationTokenSource tokenSource2 = new CancellationTokenSource();
|
||||||
|
Task transcoderTask; Task outputTask;
|
||||||
|
public AudioStreamer(string streamUrl, Channel statusTextChannel = null) {
|
||||||
|
sourceUrl = streamUrl;
|
||||||
|
this.statusTextChannel = statusTextChannel;
|
||||||
|
}
|
||||||
|
public void Start() {
|
||||||
|
Task.Run(async () => {
|
||||||
|
var bufferingStream = GetBufferingStream(sourceUrl);
|
||||||
|
Console.WriteLine("Buffering video..."); // Wait for some data to arrive
|
||||||
|
while (bufferingStream.Length < 1000 || NetworkDone)
|
||||||
|
await Task.Delay(500);
|
||||||
|
Console.WriteLine("buf done");
|
||||||
|
Stream input, pcmOutput;
|
||||||
|
var ffmpegProcess = GetTranscoderStreams(out input, out pcmOutput);
|
||||||
|
PCMOutput = new DualStream(); // Keep pumping network stuff into the transcoder
|
||||||
|
transcoderTask = Task.Run(() => TranscoderFunc(bufferingStream, input, tokenSource1.Token));
|
||||||
|
// Keep pumping transcoder output into the PCMOutput stream
|
||||||
|
outputTask = Task.Run(() => OutputFunc(pcmOutput, PCMOutput, tokenSource2.Token));
|
||||||
|
// Wait until network stuff is all done
|
||||||
|
while (!NetworkDone) await Task.Delay(500);
|
||||||
|
// Then wait until we sent everything to the transcoder
|
||||||
|
while (BytesSentToTranscoder < totalSourceBytes) await Task.Delay(500);
|
||||||
|
// Then wait some more until it did everything and kill it
|
||||||
|
await Task.Delay(5000);
|
||||||
|
try {
|
||||||
|
tokenSource1.Cancel();
|
||||||
|
tokenSource2.Cancel();
|
||||||
|
Console.WriteLine("Killing transcoder...");
|
||||||
|
ffmpegProcess.Kill();
|
||||||
|
} catch { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async Task TranscoderFunc(Stream sourceStream, Stream targetStream, CancellationToken cancellationToken) {
|
||||||
|
byte[] buffer = new byte[0x4000];
|
||||||
|
while (!NetworkDone && !cancellationToken.IsCancellationRequested) {
|
||||||
|
// When there is new stuff available on the network we want to get it instantly
|
||||||
|
int available = totalSourceBytes - BytesSentToTranscoder;
|
||||||
|
if (available > 0) {
|
||||||
|
int read = await sourceStream.ReadAsync(buffer, 0, Math.Min(available, buffer.Length), cancellationToken);
|
||||||
|
if (read > 0) { targetStream.Write(buffer, 0, read);
|
||||||
|
BytesSentToTranscoder += read;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else await Task.Delay(1);
|
||||||
|
}
|
||||||
|
Console.WriteLine("TranscoderFunc stopped");
|
||||||
|
}
|
||||||
|
async Task OutputFunc(Stream sourceStream, Stream targetStream, CancellationToken cancellationToken) {
|
||||||
|
byte[] buffer = new byte[0x4000];
|
||||||
|
while (!cancellationToken.IsCancellationRequested) {
|
||||||
|
// When there is new stuff available on the network we want to get it instantly
|
||||||
|
int read = await sourceStream.ReadAsync(buffer, 0, buffer.Length, cancellationToken);
|
||||||
|
if (read > 0) targetStream.Write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
Console.WriteLine("OutputFunc stopped");
|
||||||
|
}
|
||||||
|
internal static Process GetTranscoderStreams(out Stream input, out Stream pcmOutput) {
|
||||||
|
Process p = Process.Start(new ProcessStartInfo {
|
||||||
|
FileName = "ffmpeg",
|
||||||
|
Arguments = "-i pipe:0 -f s16le -ar 48000 -ac 2 pipe:1",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
RedirectStandardInput = true,
|
||||||
|
CreateNoWindow = true,
|
||||||
|
});
|
||||||
|
pcmOutput = p.StandardOutput.BaseStream;
|
||||||
|
input = p.StandardInput.BaseStream;
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
Stream GetBufferingStream(string streamUrl) {
|
||||||
|
var memoryStream = new DualStream();
|
||||||
|
Task.Run(() => {
|
||||||
|
int byteCounter = 0;
|
||||||
|
try {
|
||||||
|
var webClient = new WebClient();
|
||||||
|
var networkStream = webClient.OpenRead(streamUrl);
|
||||||
|
if (networkStream == null) return;
|
||||||
|
byte[] buffer = new byte[0x1000];
|
||||||
|
while (true) {
|
||||||
|
int read = networkStream.Read(buffer, 0, buffer.Length);
|
||||||
|
if (read <= 0) break;
|
||||||
|
byteCounter += read;
|
||||||
|
totalSourceBytes += read;
|
||||||
|
memoryStream.Write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Console.WriteLine("Exception while reading network stream: " + ex);
|
||||||
|
}
|
||||||
|
NetworkDone = true; Console.WriteLine("net: done. ({0} read)", byteCounter);
|
||||||
|
});
|
||||||
|
return memoryStream;
|
||||||
|
}
|
||||||
|
async void Write(string message) {
|
||||||
|
Console.WriteLine(message);
|
||||||
|
}
|
||||||
|
public void Cancel() {
|
||||||
|
tokenSource1.Cancel();
|
||||||
|
tokenSource2.Cancel();
|
||||||
|
NetworkDone = true;
|
||||||
|
BytesSentToTranscoder = totalSourceBytes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public class DualStream : MemoryStream {
|
||||||
|
long readPosition;
|
||||||
|
long writePosition;
|
||||||
|
public override int Read(byte[] buffer, int offset, int count) {
|
||||||
|
int read;
|
||||||
|
lock (this) {
|
||||||
|
Position = readPosition;
|
||||||
|
read = base.Read(buffer, offset, count);
|
||||||
|
readPosition = Position;
|
||||||
|
}
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
public override void Write(byte[] buffer, int offset, int count) {
|
||||||
|
lock (this) {
|
||||||
|
Position = writePosition;
|
||||||
|
base.Write(buffer, offset, count);
|
||||||
|
writePosition = Position;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -66,7 +66,8 @@ namespace NadekoBot
|
|||||||
|
|
||||||
//add audio service
|
//add audio service
|
||||||
var audio = client.Services.Add<AudioService>(new AudioService(new AudioServiceConfig() {
|
var audio = client.Services.Add<AudioService>(new AudioService(new AudioServiceConfig() {
|
||||||
Channels = 2
|
Channels = 2,
|
||||||
|
EnableEncryption = false
|
||||||
}));
|
}));
|
||||||
|
|
||||||
//install modules
|
//install modules
|
||||||
|
@ -71,7 +71,7 @@ namespace NadekoBot
|
|||||||
{
|
{
|
||||||
await (await NadekoBot.client.GetInvite(code)).Accept();
|
await (await NadekoBot.client.GetInvite(code)).Accept();
|
||||||
await e.Send(e.User.Mention + " I joined it, thanks :)");
|
await e.Send(e.User.Mention + " I joined it, thanks :)");
|
||||||
DEBUG_LOG("Sucessfuly joined server with code " + code);
|
DEBUG_LOG("Successfuly joined server with code " + code);
|
||||||
DEBUG_LOG("Here is a link for you: discord.gg/" + code);
|
DEBUG_LOG("Here is a link for you: discord.gg/" + code);
|
||||||
}
|
}
|
||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
@ -81,9 +81,11 @@ namespace NadekoBot
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void DEBUG_LOG(string text) {
|
public static void DEBUG_LOG(string text) {
|
||||||
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
#pragma warning disable CS4014
|
||||||
NadekoBot.client.GetChannel(119365591852122112).Send(text);
|
//NadekoBot.client.GetChannel(119365591852122112).Send(text);
|
||||||
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
|
//TODO YOU MIGHT WANT TO CHANGE THIS TO LOOK LIKE THE LINE ABOVE
|
||||||
|
Console.WriteLine(text);
|
||||||
|
#pragma warning restore CS4014
|
||||||
}
|
}
|
||||||
|
|
||||||
private void StartCollecting() {
|
private void StartCollecting() {
|
||||||
|
Loading…
Reference in New Issue
Block a user