continued work on music
This commit is contained in:
parent
ba7fb089d2
commit
29a0dbdfce
@ -1,13 +1,10 @@
|
|||||||
using Discord;
|
using Discord;
|
||||||
using Discord.Audio;
|
using Discord.Audio;
|
||||||
|
using NadekoBot.Extensions;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Discord.Commands;
|
|
||||||
using MusicModule = NadekoBot.Modules.Music;
|
|
||||||
using System.Collections;
|
|
||||||
using NadekoBot.Extensions;
|
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace NadekoBot.Classes.Music {
|
namespace NadekoBot.Classes.Music {
|
||||||
|
|
||||||
@ -16,16 +13,18 @@ namespace NadekoBot.Classes.Music {
|
|||||||
Normal,
|
Normal,
|
||||||
Local
|
Local
|
||||||
}
|
}
|
||||||
public class Song {
|
|
||||||
public StreamState State { get; internal set; }
|
|
||||||
|
|
||||||
private Song() { }
|
public enum StreamState {
|
||||||
|
Resolving,
|
||||||
|
Queued,
|
||||||
|
Buffering, //not using it atm
|
||||||
|
Playing,
|
||||||
|
Completed
|
||||||
|
}
|
||||||
|
|
||||||
internal Task Play(CancellationToken cancelToken) {
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
public class MusicPlayer {
|
public class MusicPlayer {
|
||||||
|
public static int MaximumPlaylistSize => 50;
|
||||||
|
|
||||||
private IAudioClient _client { get; set; }
|
private IAudioClient _client { get; set; }
|
||||||
|
|
||||||
private List<Song> _playlist = new List<Song>();
|
private List<Song> _playlist = new List<Song>();
|
||||||
@ -40,18 +39,31 @@ namespace NadekoBot.Classes.Music {
|
|||||||
|
|
||||||
public float Volume { get; private set; }
|
public float Volume { get; private set; }
|
||||||
|
|
||||||
public MusicPlayer(IAudioClient client) {
|
public Action<Song> OnCompleted = delegate { };
|
||||||
if (client == null)
|
public Action<Song> OnStarted = delegate { };
|
||||||
throw new ArgumentNullException(nameof(client));
|
|
||||||
_client = client;
|
public Channel PlaybackVoiceChannel { get; private set; }
|
||||||
|
|
||||||
|
public MusicPlayer(Channel startingVoiceChannel, float defaultVolume) {
|
||||||
|
if (startingVoiceChannel == null)
|
||||||
|
throw new ArgumentNullException(nameof(startingVoiceChannel));
|
||||||
|
if (startingVoiceChannel.Type != ChannelType.Voice)
|
||||||
|
throw new ArgumentException("Channel must be of type voice");
|
||||||
|
|
||||||
|
PlaybackVoiceChannel = startingVoiceChannel;
|
||||||
SongCancelSource = new CancellationTokenSource();
|
SongCancelSource = new CancellationTokenSource();
|
||||||
cancelToken = SongCancelSource.Token;
|
cancelToken = SongCancelSource.Token;
|
||||||
|
|
||||||
Task.Run(async () => {
|
Task.Run(async () => {
|
||||||
while (_client?.State == ConnectionState.Connected) {
|
while (_client?.State != ConnectionState.Disconnected &&
|
||||||
|
_client?.State != ConnectionState.Disconnecting) {
|
||||||
|
|
||||||
CurrentSong = GetNextSong();
|
CurrentSong = GetNextSong();
|
||||||
if (CurrentSong != null) {
|
if (CurrentSong != null) {
|
||||||
try {
|
try {
|
||||||
await CurrentSong.Play(cancelToken);
|
_client = await PlaybackVoiceChannel.JoinAudio();
|
||||||
|
OnStarted(CurrentSong);
|
||||||
|
CurrentSong.Play(_client, cancelToken);
|
||||||
}
|
}
|
||||||
catch (OperationCanceledException) {
|
catch (OperationCanceledException) {
|
||||||
Console.WriteLine("Song canceled");
|
Console.WriteLine("Song canceled");
|
||||||
@ -59,19 +71,17 @@ namespace NadekoBot.Classes.Music {
|
|||||||
catch (Exception ex) {
|
catch (Exception ex) {
|
||||||
Console.WriteLine($"Exception in PlaySong: {ex}");
|
Console.WriteLine($"Exception in PlaySong: {ex}");
|
||||||
}
|
}
|
||||||
|
OnCompleted(CurrentSong);
|
||||||
SongCancelSource = new CancellationTokenSource();
|
SongCancelSource = new CancellationTokenSource();
|
||||||
cancelToken = SongCancelSource.Token;
|
cancelToken = SongCancelSource.Token;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
await Task.Delay(1000);
|
await Task.Delay(1000);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
await Stop();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Next() {
|
public void Next() {
|
||||||
if(!SongCancelSource.IsCancellationRequested)
|
if (!SongCancelSource.IsCancellationRequested)
|
||||||
SongCancelSource.Cancel();
|
SongCancelSource.Cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,22 +97,25 @@ namespace NadekoBot.Classes.Music {
|
|||||||
catch {
|
catch {
|
||||||
Console.WriteLine("This shouldn't happen");
|
Console.WriteLine("This shouldn't happen");
|
||||||
}
|
}
|
||||||
|
Console.WriteLine("Disconnecting");
|
||||||
await _client?.Disconnect();
|
await _client?.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TogglePause() => Paused = !Paused;
|
||||||
|
|
||||||
public void Shuffle() {
|
public void Shuffle() {
|
||||||
lock (_playlist) {
|
lock (_playlist) {
|
||||||
_playlist.Shuffle();
|
_playlist.Shuffle();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetVolume(float volume) {
|
public int SetVolume(int volume) {
|
||||||
if (volume < 0)
|
if (volume < 0)
|
||||||
volume = 0;
|
volume = 0;
|
||||||
if (volume > 150)
|
if (volume > 150)
|
||||||
volume = 150;
|
volume = 150;
|
||||||
|
|
||||||
Volume = volume / 100.0f;
|
return (int)(Volume = volume / 100.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Song GetNextSong() {
|
private Song GetNextSong() {
|
||||||
@ -139,118 +152,17 @@ namespace NadekoBot.Classes.Music {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
internal Task MoveToVoiceChannel(Channel voiceChannel) {
|
||||||
private CommandEventArgs _e;
|
if (_client?.State != ConnectionState.Connected)
|
||||||
public bool NextSong { get; set; } = false;
|
throw new InvalidOperationException("Can't move while bot is not connected to voice channel.");
|
||||||
public IAudioClient Voice { get; set; }
|
PlaybackVoiceChannel = voiceChannel;
|
||||||
|
return PlaybackVoiceChannel.JoinAudio();
|
||||||
public bool Pause { get; set; } = false;
|
|
||||||
public List<StreamRequest> SongQueue { get; set; } = new List<StreamRequest>();
|
|
||||||
public StreamRequest CurrentSong { get; set; } = null;
|
|
||||||
public float Volume { get; set; } = .5f;
|
|
||||||
|
|
||||||
public bool IsPaused { get; internal set; } = false;
|
|
||||||
public bool Stopped { get; private set; }
|
|
||||||
|
|
||||||
public Channel VoiceChannel { get; set; } = null;
|
|
||||||
|
|
||||||
public IAudioClient VoiceClient { get; set; } = null;
|
|
||||||
|
|
||||||
private readonly object _voiceLock = new object();
|
|
||||||
|
|
||||||
public MusicPlayer() {
|
|
||||||
Task.Run(async () => {
|
|
||||||
while (true) {
|
|
||||||
if (!Stopped) {
|
|
||||||
if (CurrentSong == null) {
|
|
||||||
if (SongQueue.Count > 0)
|
|
||||||
await LoadNextSong();
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (CurrentSong.State == StreamState.Completed || NextSong) {
|
|
||||||
NextSong = false;
|
|
||||||
await LoadNextSong();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (VoiceClient == null)
|
|
||||||
break;
|
|
||||||
await Task.Delay(500);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void AddSong(StreamRequest streamRequest) {
|
internal void ClearQueue() {
|
||||||
lock (_voiceLock) {
|
lock (playlistLock) {
|
||||||
Stopped = false;
|
_playlist.Clear();
|
||||||
this.SongQueue.Add(streamRequest);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public MusicPlayer(Channel voiceChannel, CommandEventArgs e, float? vol) : this() {
|
|
||||||
if (voiceChannel == null)
|
|
||||||
throw new ArgumentNullException(nameof(voiceChannel));
|
|
||||||
if (vol != null)
|
|
||||||
Volume = (float)vol;
|
|
||||||
VoiceChannel = voiceChannel;
|
|
||||||
_e = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task LoadNextSong() {
|
|
||||||
CurrentSong?.Stop();
|
|
||||||
CurrentSong = null;
|
|
||||||
if (SongQueue.Count != 0) {
|
|
||||||
lock (_voiceLock) {
|
|
||||||
CurrentSong = SongQueue[0];
|
|
||||||
SongQueue.RemoveAt(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Stop();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (VoiceClient == null) {
|
|
||||||
Console.WriteLine($"Joining voice channel [{DateTime.Now.Second}]");
|
|
||||||
//todo add a new event, to tell people nadeko is trying to join
|
|
||||||
VoiceClient = await Task.Run(async () => await VoiceChannel.JoinAudio());
|
|
||||||
Console.WriteLine($"Joined voicechannel [{DateTime.Now.Second}]");
|
|
||||||
}
|
|
||||||
await Task.Factory.StartNew(async () => await CurrentSong?.Start(), TaskCreationOptions.LongRunning).Unwrap();
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
Console.WriteLine($"Starting failed: {ex}");
|
|
||||||
CurrentSong?.Stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal void Stop(bool leave = false) {
|
|
||||||
Stopped = true;
|
|
||||||
SongQueue.Clear();
|
|
||||||
try {
|
|
||||||
CurrentSong?.Stop();
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
CurrentSong = null;
|
|
||||||
if (leave) {
|
|
||||||
VoiceClient?.Disconnect();
|
|
||||||
VoiceClient = null;
|
|
||||||
|
|
||||||
MusicPlayer throwAwayValue;
|
|
||||||
MusicModule.musicPlayers.TryRemove(_e.Server, out throwAwayValue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public int SetVolume(int value) {
|
|
||||||
if (value < 0)
|
|
||||||
value = 0;
|
|
||||||
if (value > 150)
|
|
||||||
value = 150;
|
|
||||||
this.Volume = value / 100f;
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
internal bool TogglePause() => IsPaused = !IsPaused;
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
191
NadekoBot/Classes/Music/Song.cs
Normal file
191
NadekoBot/Classes/Music/Song.cs
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
using Discord.Audio;
|
||||||
|
using NadekoBot.Extensions;
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using VideoLibrary;
|
||||||
|
|
||||||
|
namespace NadekoBot.Classes.Music {
|
||||||
|
|
||||||
|
|
||||||
|
public class SongInfo {
|
||||||
|
public string Provider { get; internal set; }
|
||||||
|
public string Title { get; internal set; }
|
||||||
|
public string Uri { get; internal set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Song {
|
||||||
|
public StreamState State { get; internal set; }
|
||||||
|
public object PrettyName =>
|
||||||
|
$"**【 {SongInfo.Title.TrimTo(55)} 】**`{(SongInfo.Provider ?? "-")}`";
|
||||||
|
public SongInfo SongInfo { get; }
|
||||||
|
|
||||||
|
private Song(SongInfo songInfo) {
|
||||||
|
this.SongInfo = songInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Play(IAudioClient voiceClient, CancellationToken cancelToken) {
|
||||||
|
var p = Process.Start(new ProcessStartInfo {
|
||||||
|
FileName = "ffmpeg",
|
||||||
|
Arguments = $"-i {SongInfo.Uri} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet",
|
||||||
|
UseShellExecute = false,
|
||||||
|
RedirectStandardOutput = true,
|
||||||
|
});
|
||||||
|
Task.Delay(2000); //give it 2 seconds to get some dataz
|
||||||
|
int blockSize = 3840; // 1920 for mono
|
||||||
|
byte[] buffer = new byte[blockSize];
|
||||||
|
int read;
|
||||||
|
while (!cancelToken.IsCancellationRequested) {
|
||||||
|
read = p.StandardOutput.BaseStream.Read(buffer, 0, blockSize);
|
||||||
|
if (read == 0)
|
||||||
|
break; //nothing to read
|
||||||
|
voiceClient.Send(buffer, 0, read);
|
||||||
|
}
|
||||||
|
voiceClient.Wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async Task<Song> ResolveSong(string query, MusicType musicType = MusicType.Normal) {
|
||||||
|
if (string.IsNullOrWhiteSpace(query))
|
||||||
|
throw new ArgumentNullException(nameof(query));
|
||||||
|
|
||||||
|
if (musicType != MusicType.Local && IsRadioLink(query)) {
|
||||||
|
musicType = MusicType.Radio;
|
||||||
|
query = await HandleStreamContainers(query) ?? query;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (musicType == MusicType.Local) {
|
||||||
|
return new Song(new SongInfo {
|
||||||
|
Uri = "\"" + Path.GetFullPath(query) + "\"",
|
||||||
|
Title = Path.GetFileNameWithoutExtension(query),
|
||||||
|
Provider = "Local File",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (musicType == MusicType.Radio) {
|
||||||
|
return new Song(new SongInfo {
|
||||||
|
Uri = query,
|
||||||
|
Title = $"{query}",
|
||||||
|
Provider = "Radio Stream",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (SoundCloud.Default.IsSoundCloudLink(query)) {
|
||||||
|
var svideo = await SoundCloud.Default.GetVideoAsync(query);
|
||||||
|
return new Song(new SongInfo {
|
||||||
|
Title = svideo.FullName,
|
||||||
|
Provider = "SoundCloud",
|
||||||
|
Uri = svideo.StreamLink,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var links = await SearchHelper.FindYoutubeUrlByKeywords(query);
|
||||||
|
if (links == String.Empty)
|
||||||
|
throw new OperationCanceledException("Not a valid youtube query.");
|
||||||
|
var allVideos = await Task.Factory.StartNew(async () => await YouTube.Default.GetAllVideosAsync(links)).Unwrap();
|
||||||
|
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
|
||||||
|
var video = videos
|
||||||
|
.Where(v => v.AudioBitrate < 192)
|
||||||
|
.OrderByDescending(v => v.AudioBitrate)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (video == null) // do something with this error
|
||||||
|
throw new Exception("Could not load any video elements based on the query.");
|
||||||
|
return new Song(new SongInfo {
|
||||||
|
Title = video.Title.Substring(0, video.Title.Length - 10), // removing trailing "- You Tube"
|
||||||
|
Provider = "YouTube",
|
||||||
|
Uri = video.Uri,
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
Console.WriteLine($"Failed resolving the link.{ex.Message}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task<string> HandleStreamContainers(string query) {
|
||||||
|
string file = null;
|
||||||
|
try {
|
||||||
|
file = await SearchHelper.GetResponseAsync(query);
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
if (query.Contains(".pls")) {
|
||||||
|
//File1=http://armitunes.com:8000/
|
||||||
|
//Regex.Match(query)
|
||||||
|
try {
|
||||||
|
var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
|
||||||
|
var res = m.Groups["url"]?.ToString();
|
||||||
|
return res?.Trim();
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Console.WriteLine($"Failed reading .pls:\n{file}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (query.Contains(".m3u")) {
|
||||||
|
/*
|
||||||
|
# This is a comment
|
||||||
|
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
|
||||||
|
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
|
||||||
|
var res = m.Groups["url"]?.ToString();
|
||||||
|
return res?.Trim();
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Console.WriteLine($"Failed reading .m3u:\n{file}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (query.Contains(".asx")) {
|
||||||
|
//<ref href="http://armitunes.com:8000"/>
|
||||||
|
try {
|
||||||
|
var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
|
||||||
|
var res = m.Groups["url"]?.ToString();
|
||||||
|
return res?.Trim();
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Console.WriteLine($"Failed reading .asx:\n{file}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (query.Contains(".xspf")) {
|
||||||
|
/*
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<playlist version="1" xmlns="http://xspf.org/ns/0/">
|
||||||
|
<trackList>
|
||||||
|
<track><location>file:///mp3s/song_1.mp3</location></track>
|
||||||
|
*/
|
||||||
|
try {
|
||||||
|
var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
|
||||||
|
var res = m.Groups["url"]?.ToString();
|
||||||
|
return res?.Trim();
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Console.WriteLine($"Failed reading .xspf:\n{file}");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsRadioLink(string query) {
|
||||||
|
return (query.StartsWith("http") ||
|
||||||
|
query.StartsWith("ww"))
|
||||||
|
&&
|
||||||
|
(query.Contains(".pls") ||
|
||||||
|
query.Contains(".m3u") ||
|
||||||
|
query.Contains(".asx") ||
|
||||||
|
query.Contains(".xspf"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
using System;
|
/*
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Discord;
|
using Discord;
|
||||||
@ -10,13 +11,6 @@ using NadekoBot.Extensions;
|
|||||||
using VideoLibrary;
|
using VideoLibrary;
|
||||||
|
|
||||||
namespace NadekoBot.Classes.Music {
|
namespace NadekoBot.Classes.Music {
|
||||||
public enum StreamState {
|
|
||||||
Resolving,
|
|
||||||
Queued,
|
|
||||||
Buffering, //not using it atm
|
|
||||||
Playing,
|
|
||||||
Completed
|
|
||||||
}
|
|
||||||
|
|
||||||
public class StreamRequest {
|
public class StreamRequest {
|
||||||
public Server Server { get; }
|
public Server Server { get; }
|
||||||
@ -54,50 +48,7 @@ namespace NadekoBot.Classes.Music {
|
|||||||
|
|
||||||
public async Task Resolve() {
|
public async Task Resolve() {
|
||||||
string uri = null;
|
string uri = null;
|
||||||
try {
|
|
||||||
if (this.LinkType == MusicType.Local) {
|
|
||||||
uri = "\"" + Path.GetFullPath(Query) + "\"";
|
|
||||||
Title = Path.GetFileNameWithoutExtension(Query);
|
|
||||||
Provider = "Local File";
|
|
||||||
}
|
|
||||||
else if (this.LinkType == MusicType.Radio) {
|
|
||||||
uri = Query;
|
|
||||||
Title = $"{Query}";
|
|
||||||
Provider = "Radio Stream";
|
|
||||||
}
|
|
||||||
else if (SoundCloud.Default.IsSoundCloudLink(Query)) {
|
|
||||||
var svideo = await SoundCloud.Default.GetVideoAsync(Query);
|
|
||||||
Title = svideo.FullName;
|
|
||||||
Provider = "SoundCloud";
|
|
||||||
uri = svideo.StreamLink;
|
|
||||||
Console.WriteLine(uri);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var links = await SearchHelper.FindYoutubeUrlByKeywords(Query);
|
|
||||||
if (links == String.Empty)
|
|
||||||
throw new OperationCanceledException("Not a valid youtube query.");
|
|
||||||
var allVideos = await Task.Factory.StartNew(async () => await YouTube.Default.GetAllVideosAsync(links)).Unwrap();
|
|
||||||
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
|
|
||||||
var video = videos
|
|
||||||
.Where(v => v.AudioBitrate < 192)
|
|
||||||
.OrderByDescending(v => v.AudioBitrate)
|
|
||||||
.FirstOrDefault();
|
|
||||||
|
|
||||||
if (video == null) // do something with this error
|
|
||||||
throw new Exception("Could not load any video elements based on the query.");
|
|
||||||
|
|
||||||
Title = video.Title.Substring(0, video.Title.Length - 10); // removing trailing "- You Tube"
|
|
||||||
Provider = "YouTube";
|
|
||||||
uri = video.Uri;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
privateState = StreamState.Completed;
|
|
||||||
if (OnResolvingFailed != null)
|
|
||||||
OnResolvingFailed(ex.Message);
|
|
||||||
Console.WriteLine($"Failed resolving the link.{ex.Message}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
musicStreamer = new MusicStreamer(this, uri);
|
musicStreamer = new MusicStreamer(this, uri);
|
||||||
musicStreamer.OnCompleted += () => {
|
musicStreamer.OnCompleted += () => {
|
||||||
@ -389,3 +340,4 @@ namespace NadekoBot.Classes.Music {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
@ -20,13 +20,13 @@ namespace NadekoBot.Commands {
|
|||||||
if(cnt == 1) {
|
if(cnt == 1) {
|
||||||
try {
|
try {
|
||||||
var mp = Modules.Music.musicPlayers.FirstOrDefault();
|
var mp = Modules.Music.musicPlayers.FirstOrDefault();
|
||||||
return mp.Value.CurrentSong.Title;
|
return mp.Value.CurrentSong.SongInfo.Title;
|
||||||
} catch { }
|
} catch { }
|
||||||
}
|
}
|
||||||
return cnt.ToString();
|
return cnt.ToString();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{"%queued%", () => Modules.Music.musicPlayers.Sum(kvp=>kvp.Value.SongQueue.Count).ToString() },
|
{"%queued%", () => Modules.Music.musicPlayers.Sum(kvp=>kvp.Value.Playlist.Count).ToString() },
|
||||||
{"%trivia%", () => Commands.Trivia.runningTrivias.Count.ToString() }
|
{"%trivia%", () => Commands.Trivia.runningTrivias.Count.ToString() }
|
||||||
};
|
};
|
||||||
private object playingPlaceholderLock => new object();
|
private object playingPlaceholderLock => new object();
|
||||||
|
@ -369,7 +369,7 @@ namespace NadekoBot.Modules {
|
|||||||
.Description("Shows some basic stats for Nadeko.")
|
.Description("Shows some basic stats for Nadeko.")
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
var t = Task.Run(() => {
|
var t = Task.Run(() => {
|
||||||
return NadekoStats.Instance.GetStats() + "`" + Music.GetMusicStats() + "`";
|
return NadekoStats.Instance.GetStats(); //+ "`" + Music.GetMusicStats() + "`";
|
||||||
});
|
});
|
||||||
|
|
||||||
await e.Channel.SendMessage(await t);
|
await e.Channel.SendMessage(await t);
|
||||||
|
@ -16,13 +16,7 @@ namespace NadekoBot.Modules {
|
|||||||
class Music : DiscordModule {
|
class Music : DiscordModule {
|
||||||
|
|
||||||
public static ConcurrentDictionary<Server, MusicPlayer> musicPlayers = new ConcurrentDictionary<Server, MusicPlayer>();
|
public static ConcurrentDictionary<Server, MusicPlayer> musicPlayers = new ConcurrentDictionary<Server, MusicPlayer>();
|
||||||
public static ConcurrentDictionary<ulong, float> musicVolumes = new ConcurrentDictionary<ulong, float>();
|
public static ConcurrentDictionary<ulong, float> defaultMusicVolumes = new ConcurrentDictionary<ulong, float>();
|
||||||
|
|
||||||
internal static string GetMusicStats() {
|
|
||||||
var stats = musicPlayers.Where(kvp => kvp.Value?.SongQueue.Count > 0 || kvp.Value?.CurrentSong != null);
|
|
||||||
int cnt;
|
|
||||||
return $"Playing {cnt = stats.Count()} songs".SnPl(cnt) + $", {stats.Sum(kvp => kvp.Value?.SongQueue?.Count ?? 0)} queued.";
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer setgameTimer => new Timer();
|
Timer setgameTimer => new Timer();
|
||||||
|
|
||||||
@ -34,7 +28,7 @@ namespace NadekoBot.Modules {
|
|||||||
setgameTimer.Elapsed += (s, e) => {
|
setgameTimer.Elapsed += (s, e) => {
|
||||||
try {
|
try {
|
||||||
int num = musicPlayers.Where(kvp => kvp.Value.CurrentSong != null).Count();
|
int num = musicPlayers.Where(kvp => kvp.Value.CurrentSong != null).Count();
|
||||||
NadekoBot.client.SetGame($"{num} songs".SnPl(num) + $", {musicPlayers.Sum(kvp => kvp.Value.SongQueue.Count())} queued");
|
NadekoBot.client.SetGame($"{num} songs".SnPl(num) + $", {musicPlayers.Sum(kvp => kvp.Value.Playlist.Count())} queued");
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
};
|
};
|
||||||
@ -53,28 +47,32 @@ namespace NadekoBot.Modules {
|
|||||||
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(async e => {
|
.Do(e => {
|
||||||
if (!musicPlayers.ContainsKey(e.Server)) return;
|
MusicPlayer musicPlayer;
|
||||||
await musicPlayers[e.Server].LoadNextSong();
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer)) return;
|
||||||
|
musicPlayer.Next();
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("s")
|
cgb.CreateCommand("s")
|
||||||
.Alias("stop")
|
.Alias("stop")
|
||||||
.Description("Completely stops the music, unbinds the bot from the channel, and cleans up files.")
|
.Description("Completely stops the music, unbinds the bot from the channel, and cleans up files.")
|
||||||
.Do(e => {
|
.Do(async e => {
|
||||||
if (!musicPlayers.ContainsKey(e.Server)) return;
|
MusicPlayer musicPlayer;
|
||||||
musicPlayers[e.Server].Stop(true);
|
if (!musicPlayers.TryRemove(e.Server, out musicPlayer)) return;
|
||||||
|
await musicPlayer.Stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("p")
|
cgb.CreateCommand("p")
|
||||||
.Alias("pause")
|
.Alias("pause")
|
||||||
.Description("Pauses or Unpauses the song.")
|
.Description("Pauses or Unpauses the song.")
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
if (!musicPlayers.ContainsKey(e.Server)) return;
|
MusicPlayer musicPlayer;
|
||||||
if (musicPlayers[e.Server].TogglePause())
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer)) return;
|
||||||
await e.Channel.SendMessage("🎵`Music player paused.`");
|
musicPlayer.TogglePause();
|
||||||
|
if (musicPlayer.Paused)
|
||||||
|
await e.Channel.SendMessage("🎵`Music musicPlayer paused.`");
|
||||||
else
|
else
|
||||||
await e.Channel.SendMessage("🎵`Music player unpaused.`");
|
await e.Channel.SendMessage("🎵`Music musicPlayer unpaused.`");
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("q")
|
cgb.CreateCommand("q")
|
||||||
@ -82,47 +80,49 @@ namespace NadekoBot.Modules {
|
|||||||
.Description("Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**.\n**Usage**: `!m q Dream Of Venice`")
|
.Description("Queue a song using keywords or a link. Bot will join your voice channel. **You must be in a voice channel**.\n**Usage**: `!m q Dream Of Venice`")
|
||||||
.Parameter("query", ParameterType.Unparsed)
|
.Parameter("query", ParameterType.Unparsed)
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
await QueueSong(e, e.GetArg("query"));
|
await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("query"));
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("lq")
|
cgb.CreateCommand("lq")
|
||||||
.Alias("ls").Alias("lp")
|
.Alias("ls").Alias("lp")
|
||||||
.Description("Lists up to 15 currently queued songs.")
|
.Description("Lists up to 15 currently queued songs.")
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
if (musicPlayers.ContainsKey(e.Server) == false) {
|
MusicPlayer musicPlayer;
|
||||||
await e.Channel.SendMessage("🎵 No active music player.");
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer)) {
|
||||||
|
await e.Channel.SendMessage("🎵 No active music musicPlayer.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var player = musicPlayers[e.Server];
|
string toSend = "🎵 **" + musicPlayer.Playlist.Count + "** `videos currently queued.` ";
|
||||||
string toSend = "🎵 **" + player.SongQueue.Count + "** `videos currently queued.` ";
|
if (musicPlayer.Playlist.Count >= MusicPlayer.MaximumPlaylistSize)
|
||||||
if (player.SongQueue.Count >= 50)
|
|
||||||
toSend += "**Song queue is full!**\n";
|
toSend += "**Song queue is full!**\n";
|
||||||
int number = 1;
|
int number = 1;
|
||||||
await e.Channel.SendMessage(toSend + string.Join("\n", player.SongQueue.Take(15).Select(v => $"`{number++}.` {v.FullPrettyName}")));
|
await e.Channel.SendMessage(toSend + string.Join("\n", musicPlayer.Playlist.Take(15).Select(v => $"`{number++}.` {v.PrettyName}")));
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("np")
|
cgb.CreateCommand("np")
|
||||||
.Alias("playing")
|
.Alias("playing")
|
||||||
.Description("Shows the song currently playing.")
|
.Description("Shows the song currently playing.")
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
if (musicPlayers.ContainsKey(e.Server) == false) return;
|
MusicPlayer musicPlayer;
|
||||||
var player = musicPlayers[e.Server];
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer))
|
||||||
await e.Channel.SendMessage($"🎵`Now Playing` {player.CurrentSong.FullPrettyName}");
|
return;
|
||||||
|
await e.Channel.SendMessage($"🎵`Now Playing` {musicPlayer.CurrentSong.PrettyName}");
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("vol")
|
cgb.CreateCommand("vol")
|
||||||
.Description("Sets the music volume 0-150%")
|
.Description("Sets the music volume 0-150%")
|
||||||
.Parameter("val", ParameterType.Required)
|
.Parameter("val", ParameterType.Required)
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
if (musicPlayers.ContainsKey(e.Server) == false) return;
|
MusicPlayer musicPlayer;
|
||||||
var player = musicPlayers[e.Server];
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer))
|
||||||
|
return;
|
||||||
var arg = e.GetArg("val");
|
var arg = e.GetArg("val");
|
||||||
int volume;
|
int volume;
|
||||||
if (!int.TryParse(arg, out volume)) {
|
if (!int.TryParse(arg, out volume)) {
|
||||||
await e.Channel.SendMessage("Volume number invalid.");
|
await e.Channel.SendMessage("Volume number invalid.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
volume = player.SetVolume(volume);
|
volume = musicPlayer.SetVolume(volume);
|
||||||
await e.Channel.SendMessage($"🎵 `Volume set to {volume}%`");
|
await e.Channel.SendMessage($"🎵 `Volume set to {volume}%`");
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,52 +137,56 @@ namespace NadekoBot.Modules {
|
|||||||
await e.Channel.SendMessage("Volume number invalid.");
|
await e.Channel.SendMessage("Volume number invalid.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
musicVolumes.AddOrUpdate(e.Server.Id, volume / 100, (key, newval) => volume / 100);
|
defaultMusicVolumes.AddOrUpdate(e.Server.Id, volume / 100, (key, newval) => volume / 100);
|
||||||
await e.Channel.SendMessage($"🎵 `Default volume set to {volume}%`");
|
await e.Channel.SendMessage($"🎵 `Default volume set to {volume}%`");
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("min").Alias("mute")
|
cgb.CreateCommand("min").Alias("mute")
|
||||||
.Description("Sets the music volume to 0%")
|
.Description("Sets the music volume to 0%")
|
||||||
.Do(e => {
|
.Do(e => {
|
||||||
if (musicPlayers.ContainsKey(e.Server) == false) return;
|
MusicPlayer musicPlayer;
|
||||||
var player = musicPlayers[e.Server];
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer))
|
||||||
player.SetVolume(0);
|
return;
|
||||||
|
musicPlayer.SetVolume(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("max")
|
cgb.CreateCommand("max")
|
||||||
.Description("Sets the music volume to 100% (real max is actually 150%).")
|
.Description("Sets the music volume to 100% (real max is actually 150%).")
|
||||||
.Do(e => {
|
.Do(e => {
|
||||||
if (musicPlayers.ContainsKey(e.Server) == false) return;
|
MusicPlayer musicPlayer;
|
||||||
var player = musicPlayers[e.Server];
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer))
|
||||||
player.SetVolume(100);
|
return;
|
||||||
|
musicPlayer.SetVolume(100);
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("half")
|
cgb.CreateCommand("half")
|
||||||
.Description("Sets the music volume to 50%.")
|
.Description("Sets the music volume to 50%.")
|
||||||
.Do(e => {
|
.Do(e => {
|
||||||
if (musicPlayers.ContainsKey(e.Server) == false) return;
|
MusicPlayer musicPlayer;
|
||||||
var player = musicPlayers[e.Server];
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer))
|
||||||
player.SetVolume(50);
|
return;
|
||||||
|
musicPlayer.SetVolume(50);
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("sh")
|
cgb.CreateCommand("sh")
|
||||||
.Description("Shuffles the current playlist.")
|
.Description("Shuffles the current playlist.")
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
if (musicPlayers.ContainsKey(e.Server) == false) return;
|
MusicPlayer musicPlayer;
|
||||||
var player = musicPlayers[e.Server];
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer))
|
||||||
if (player.SongQueue.Count < 2) {
|
return;
|
||||||
await e.Channel.SendMessage("Not enough songs in order to perform the shuffle.");
|
if (musicPlayer.Playlist.Count < 2) {
|
||||||
|
await e.Channel.SendMessage("💢 Not enough songs in order to perform the shuffle.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
player.SongQueue.Shuffle();
|
musicPlayer.Shuffle();
|
||||||
await e.Channel.SendMessage("🎵 `Songs shuffled.`");
|
await e.Channel.SendMessage("🎵 `Songs shuffled.`");
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("setgame")
|
cgb.CreateCommand("setgame")
|
||||||
.Description("Sets the game of the bot to the number of songs playing.**Owner only**")
|
.Description("Sets the game of the bot to the number of songs playing.**Owner only**")
|
||||||
|
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
if (NadekoBot.OwnerID != e.User.Id)
|
|
||||||
return;
|
|
||||||
setgameEnabled = !setgameEnabled;
|
setgameEnabled = !setgameEnabled;
|
||||||
if (setgameEnabled)
|
if (setgameEnabled)
|
||||||
setgameTimer.Start();
|
setgameTimer.Start();
|
||||||
@ -204,10 +208,9 @@ namespace NadekoBot.Modules {
|
|||||||
//todo TEMPORARY SOLUTION, USE RESOLVE QUEUE IN THE FUTURE
|
//todo TEMPORARY SOLUTION, USE RESOLVE QUEUE IN THE FUTURE
|
||||||
var msg = await e.Channel.SendMessage($"🎵 `Attempting to queue {ids.Count} songs".SnPl(ids.Count) + "...`");
|
var msg = await e.Channel.SendMessage($"🎵 `Attempting to queue {ids.Count} songs".SnPl(ids.Count) + "...`");
|
||||||
foreach (var id in ids) {
|
foreach (var id in ids) {
|
||||||
Task.Run(async () => await QueueSong(e, id, true));
|
await QueueSong(e.Channel, e.User.VoiceChannel, id, true);
|
||||||
await Task.Delay(150);
|
|
||||||
}
|
}
|
||||||
msg?.Edit("🎵 `Playlist queue complete.`");
|
await msg.Edit("🎵 `Playlist queue complete.`");
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("lopl")
|
cgb.CreateCommand("lopl")
|
||||||
@ -221,7 +224,7 @@ namespace NadekoBot.Modules {
|
|||||||
try {
|
try {
|
||||||
var fileEnum = System.IO.Directory.EnumerateFiles(e.GetArg("directory")).Take(50);
|
var fileEnum = System.IO.Directory.EnumerateFiles(e.GetArg("directory")).Take(50);
|
||||||
foreach (var file in fileEnum) {
|
foreach (var file in fileEnum) {
|
||||||
await Task.Run(async () => await QueueSong(e, file, true, MusicType.Local)).ConfigureAwait(false);
|
await QueueSong(e.Channel, e.User.VoiceChannel, file, true, MusicType.Local);
|
||||||
}
|
}
|
||||||
await e.Channel.SendMessage("🎵 `Directory queue complete.`");
|
await e.Channel.SendMessage("🎵 `Directory queue complete.`");
|
||||||
}
|
}
|
||||||
@ -236,7 +239,7 @@ namespace NadekoBot.Modules {
|
|||||||
await e.Channel.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.");
|
await e.Channel.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining it.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
await QueueSong(e, e.GetArg("radio_link"), musicType: MusicType.Radio);
|
await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("radio_link"), musicType: MusicType.Radio);
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("lo")
|
cgb.CreateCommand("lo")
|
||||||
@ -247,17 +250,17 @@ namespace NadekoBot.Modules {
|
|||||||
var arg = e.GetArg("path");
|
var arg = e.GetArg("path");
|
||||||
if (string.IsNullOrWhiteSpace(arg))
|
if (string.IsNullOrWhiteSpace(arg))
|
||||||
return;
|
return;
|
||||||
await QueueSong(e, e.GetArg("path"), musicType: MusicType.Local);
|
await QueueSong(e.Channel, e.User.VoiceChannel, e.GetArg("path"), musicType: MusicType.Local);
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("mv")
|
cgb.CreateCommand("mv")
|
||||||
.Description("Moves the bot to your voice channel. (works only if music is already playing)")
|
.Description("Moves the bot to your voice channel. (works only if music is already playing)")
|
||||||
.Do(async e => {
|
.Do(e => {
|
||||||
MusicPlayer mc;
|
MusicPlayer musicPlayer;
|
||||||
if (e.User.VoiceChannel == null || e.User.VoiceChannel.Server != e.Server || !musicPlayers.TryGetValue(e.Server, out mc))
|
var voiceChannel = e.User.VoiceChannel;
|
||||||
|
if (voiceChannel == null || voiceChannel.Server != e.Server || !musicPlayers.TryGetValue(e.Server, out musicPlayer))
|
||||||
return;
|
return;
|
||||||
mc.VoiceChannel = e.User.VoiceChannel;
|
musicPlayer.MoveToVoiceChannel(voiceChannel);
|
||||||
mc.VoiceClient = await mc.VoiceChannel.JoinAudio();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("rm")
|
cgb.CreateCommand("rm")
|
||||||
@ -265,12 +268,12 @@ namespace NadekoBot.Modules {
|
|||||||
.Parameter("num", ParameterType.Required)
|
.Parameter("num", ParameterType.Required)
|
||||||
.Do(async e => {
|
.Do(async e => {
|
||||||
var arg = e.GetArg("num");
|
var arg = e.GetArg("num");
|
||||||
MusicPlayer mc;
|
MusicPlayer musicPlayer;
|
||||||
if (!musicPlayers.TryGetValue(e.Server, out mc)) {
|
if (!musicPlayers.TryGetValue(e.Server, out musicPlayer)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (arg?.ToLower() == "all") {
|
if (arg?.ToLower() == "all") {
|
||||||
mc.SongQueue?.Clear();
|
musicPlayer.ClearQueue();
|
||||||
await e.Channel.SendMessage($"🎵`Queue cleared!`");
|
await e.Channel.SendMessage($"🎵`Queue cleared!`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -278,193 +281,60 @@ namespace NadekoBot.Modules {
|
|||||||
if (!int.TryParse(arg, out num)) {
|
if (!int.TryParse(arg, out num)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (num <= 0 || num > mc.SongQueue.Count)
|
if (num <= 0 || num > musicPlayer.Playlist.Count)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
mc.SongQueue.RemoveAt(num - 1);
|
musicPlayer.RemoveSongAt(num - 1);
|
||||||
await e.Channel.SendMessage($"🎵**Track at position `#{num}` has been removed.**");
|
await e.Channel.SendMessage($"🎵**Track at position `#{num}` has been removed.**");
|
||||||
});
|
});
|
||||||
|
|
||||||
cgb.CreateCommand("debug")
|
cgb.CreateCommand("debug")
|
||||||
.Description("Writes some music data to console. **BOT OWNER ONLY**")
|
.Description("Writes some music data to console. **BOT OWNER ONLY**")
|
||||||
|
.AddCheck(Classes.Permissions.SimpleCheckers.OwnerOnly())
|
||||||
.Do(e => {
|
.Do(e => {
|
||||||
if (NadekoBot.OwnerID != e.User.Id)
|
|
||||||
return;
|
|
||||||
var output = "SERVER_NAME---SERVER_ID-----USERCOUNT----QUEUED\n" +
|
var output = "SERVER_NAME---SERVER_ID-----USERCOUNT----QUEUED\n" +
|
||||||
string.Join("\n", musicPlayers.Select(kvp => kvp.Key.Name + "--" + kvp.Key.Id + " --" + kvp.Key.Users.Count() + "--" + kvp.Value.SongQueue.Count));
|
string.Join("\n", musicPlayers.Select(kvp => kvp.Key.Name + "--" + kvp.Key.Id + " --" + kvp.Key.Users.Count() + "--" + kvp.Value.Playlist.Count));
|
||||||
Console.WriteLine(output);
|
Console.WriteLine(output);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task QueueSong(CommandEventArgs e, string query, bool silent = false, MusicType musicType = MusicType.Normal) {
|
private async Task QueueSong(Channel TextCh, Channel VoiceCh, string query, bool silent = false, MusicType musicType = MusicType.Normal) {
|
||||||
if (e.User.VoiceChannel?.Server != e.Server) {
|
if (VoiceCh == null || VoiceCh.Server != TextCh.Server) {
|
||||||
if (!silent)
|
if(!silent)
|
||||||
await e.Channel.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining.");
|
await TextCh.SendMessage("💢 You need to be in a voice channel on this server.\n If you are already in a voice channel, try rejoining.");
|
||||||
return;
|
throw new ArgumentNullException(nameof(VoiceCh));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
|
if (string.IsNullOrWhiteSpace(query) || query.Length < 3)
|
||||||
return;
|
throw new ArgumentException("💢 Invalid query for queue song.", nameof(query));
|
||||||
|
MusicPlayer musicPlayer = null;
|
||||||
query = query.Trim();
|
if (!musicPlayers.TryGetValue(TextCh.Server, out musicPlayer)) {
|
||||||
if (musicType != MusicType.Local && IsRadioLink(query)) {
|
|
||||||
musicType = MusicType.Radio;
|
|
||||||
query = await HandleStreamContainers(query) ?? query;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (musicPlayers.ContainsKey(e.Server) == false) {
|
|
||||||
float? vol = null;
|
float? vol = null;
|
||||||
float throwAway;
|
float throwAway;
|
||||||
if (musicVolumes.TryGetValue(e.Server.Id, out throwAway))
|
if (defaultMusicVolumes.TryGetValue(TextCh.Server.Id, out throwAway))
|
||||||
vol = throwAway;
|
vol = throwAway;
|
||||||
|
musicPlayer = new MusicPlayer(VoiceCh) {
|
||||||
if (!musicPlayers.TryAdd(e.Server, new MusicPlayer(e.User.VoiceChannel, e, vol))) {
|
OnCompleted = async (song) => {
|
||||||
await e.Channel.SendMessage("Failed to create a music player for this server.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var player = musicPlayers[e.Server];
|
|
||||||
|
|
||||||
if (player.SongQueue.Count >= 50) return;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
var sr = new StreamRequest(e, query, player, musicType);
|
await TextCh.SendMessage($"🎵`Finished`{song.PrettyName}");
|
||||||
|
|
||||||
if (sr == null)
|
|
||||||
throw new NullReferenceException("StreamRequest is null.");
|
|
||||||
|
|
||||||
Message qmsg = null;
|
|
||||||
Message msg = null;
|
|
||||||
if (!silent) {
|
|
||||||
try {
|
|
||||||
qmsg = await e.Channel.SendMessage("🎵 `Searching / Resolving...`");
|
|
||||||
sr.OnResolvingFailed += async (err) => {
|
|
||||||
try {
|
|
||||||
await qmsg.Delete();
|
|
||||||
await e.Channel.Send($"💢 🎵 `Resolving failed` for **{query}**");
|
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
|
},
|
||||||
|
OnStarted = async (song) => {
|
||||||
|
try {
|
||||||
|
var msgTxt = $"🎵`Playing`{song.PrettyName} `Vol: {(int)(musicPlayer.Volume * 100)}%`";
|
||||||
|
await TextCh.SendMessage(msgTxt);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
},
|
||||||
};
|
};
|
||||||
sr.OnQueued += async () => {
|
musicPlayers.TryAdd(TextCh.Server, musicPlayer);
|
||||||
try {
|
|
||||||
await qmsg.Delete();
|
|
||||||
await e.Channel.Send($"🎵`Queued`{sr.FullPrettyName}");
|
|
||||||
}
|
}
|
||||||
catch { }
|
var resolvedSong = await Song.ResolveSong(query, musicType);
|
||||||
};
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
sr.OnCompleted += async () => {
|
|
||||||
try {
|
|
||||||
MusicPlayer mc;
|
|
||||||
if (musicPlayers.TryGetValue(e.Server, out mc)) {
|
|
||||||
if (mc.SongQueue.Count == 0)
|
|
||||||
mc.Stop();
|
|
||||||
}
|
|
||||||
await e.Channel.SendMessage($"🎵`Finished`{sr.FullPrettyName}");
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
};
|
|
||||||
|
|
||||||
sr.OnStarted += async () => {
|
if(!silent)
|
||||||
try {
|
await TextCh.Send($"🎵`Queued`{resolvedSong.PrettyName}");
|
||||||
var msgTxt = $"🎵`Playing`{sr.FullPrettyName} `Vol: {(int)(player.Volume * 100)}%`";
|
musicPlayer.AddSong(resolvedSong);
|
||||||
if (qmsg != null)
|
|
||||||
await qmsg.Delete();
|
|
||||||
await e.Channel.SendMessage(msgTxt);
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
};
|
|
||||||
|
|
||||||
await sr.Resolve();
|
|
||||||
}
|
|
||||||
catch (Exception ex) {
|
|
||||||
await e.Channel.SendMessage($"💢 {ex.Message}");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool IsRadioLink(string query) =>
|
|
||||||
(query.StartsWith("http") ||
|
|
||||||
query.StartsWith("ww"))
|
|
||||||
&&
|
|
||||||
(query.Contains(".pls") ||
|
|
||||||
query.Contains(".m3u") ||
|
|
||||||
query.Contains(".asx") ||
|
|
||||||
query.Contains(".xspf"));
|
|
||||||
|
|
||||||
private async Task<string> HandleStreamContainers(string query) {
|
|
||||||
string file = null;
|
|
||||||
try {
|
|
||||||
file = await SearchHelper.GetResponseAsync(query);
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
if (query.Contains(".pls")) {
|
|
||||||
//File1=http://armitunes.com:8000/
|
|
||||||
//Regex.Match(query)
|
|
||||||
try {
|
|
||||||
var m = Regex.Match(file, "File1=(?<url>.*?)\\n");
|
|
||||||
var res = m.Groups["url"]?.ToString();
|
|
||||||
return res?.Trim();
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Console.WriteLine($"Failed reading .pls:\n{file}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (query.Contains(".m3u")) {
|
|
||||||
/*
|
|
||||||
# This is a comment
|
|
||||||
C:\xxx4xx\xxxxxx3x\xx2xxxx\xx.mp3
|
|
||||||
C:\xxx5xx\x6xxxxxx\x7xxxxx\xx.mp3
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
var m = Regex.Match(file, "(?<url>^[^#].*)", RegexOptions.Multiline);
|
|
||||||
var res = m.Groups["url"]?.ToString();
|
|
||||||
return res?.Trim();
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Console.WriteLine($"Failed reading .m3u:\n{file}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
else if (query.Contains(".asx")) {
|
|
||||||
//<ref href="http://armitunes.com:8000"/>
|
|
||||||
try {
|
|
||||||
var m = Regex.Match(file, "<ref href=\"(?<url>.*?)\"");
|
|
||||||
var res = m.Groups["url"]?.ToString();
|
|
||||||
return res?.Trim();
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Console.WriteLine($"Failed reading .asx:\n{file}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (query.Contains(".xspf")) {
|
|
||||||
/*
|
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<playlist version="1" xmlns="http://xspf.org/ns/0/">
|
|
||||||
<trackList>
|
|
||||||
<track><location>file:///mp3s/song_1.mp3</location></track>
|
|
||||||
*/
|
|
||||||
try {
|
|
||||||
var m = Regex.Match(file, "<location>(?<url>.*?)</location>");
|
|
||||||
var res = m.Groups["url"]?.ToString();
|
|
||||||
return res?.Trim();
|
|
||||||
}
|
|
||||||
catch {
|
|
||||||
Console.WriteLine($"Failed reading .xspf:\n{file}");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return query;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,6 +118,7 @@
|
|||||||
<Compile Include="Classes\DBHandler.cs" />
|
<Compile Include="Classes\DBHandler.cs" />
|
||||||
<Compile Include="Classes\FlowersHandler.cs" />
|
<Compile Include="Classes\FlowersHandler.cs" />
|
||||||
<Compile Include="Classes\Music\MusicControls.cs" />
|
<Compile Include="Classes\Music\MusicControls.cs" />
|
||||||
|
<Compile Include="Classes\Music\Song.cs" />
|
||||||
<Compile Include="Classes\Music\StreamRequest.cs" />
|
<Compile Include="Classes\Music\StreamRequest.cs" />
|
||||||
<Compile Include="Classes\Music\SoundCloud.cs" />
|
<Compile Include="Classes\Music\SoundCloud.cs" />
|
||||||
<Compile Include="Classes\Permissions\PermissionChecker.cs" />
|
<Compile Include="Classes\Permissions\PermissionChecker.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user