diff --git a/Opus/Code/Api/Services/MusicPlayer.cs b/Opus/Code/Api/Services/MusicPlayer.cs index b62a883..42a2d90 100644 --- a/Opus/Code/Api/Services/MusicPlayer.cs +++ b/Opus/Code/Api/Services/MusicPlayer.cs @@ -21,6 +21,7 @@ using Com.Google.Android.Exoplayer2.Trackselection; using Com.Google.Android.Exoplayer2.Upstream; using Com.Google.Android.Exoplayer2.Util; using Newtonsoft.Json; +using Opus.Code.Api; using Opus.DataStructure; using Opus.Fragments; using Opus.Others; @@ -63,7 +64,6 @@ namespace Opus.Api.Services public static bool isRunning = false; private bool generating = false; public static int currentID = 0; - public static int switchPosition; public static bool autoUpdateSeekBar = true; public static bool repeat = false; public static bool useAutoPlay = true; @@ -367,10 +367,10 @@ namespace Opus.Api.Services public async void Play(Song song, long progress = -1, bool addToQueue = true) { if (song.IsParsed == false) - await ParseSong(song, -1); + await new SongParser().ParseSong(song, -1); else if (song.IsParsed == null) { - await ParseSong(song, -1, true); + await new SongParser().ParseSong(song, -1, true); return; } @@ -423,147 +423,6 @@ namespace Opus.Api.Services GenerateAutoPlay(false); } - private static async Task ParseSong(Song song, int position = -1, bool startPlaybackWhenPosible = false, bool forceParse = false) - { - if ((!forceParse && song.IsParsed == true) || !song.IsYt) - { - if (startPlaybackWhenPosible) - instance.Play(song, -1, position == -1); - - return song; - } - - if (song.IsParsed == null) - { - while (song.IsParsed == null) - await Task.Delay(10); - - if (startPlaybackWhenPosible && (await GetItem()).YoutubeID != song.YoutubeID) - instance.Play(song, -1, position == -1); - - return song; //Song is a class, the youtube id will be updated with another method - } - - switchPosition = position; - - try - { - song.IsParsed = null; - YoutubeClient client = new YoutubeClient(); - var mediaStreamInfo = await client.GetVideoMediaStreamInfosAsync(song.YoutubeID); - if (mediaStreamInfo.HlsLiveStreamUrl != null) - { - song.Path = mediaStreamInfo.HlsLiveStreamUrl; - song.IsLiveStream = true; - } - else - { - song.IsLiveStream = false; - - if (mediaStreamInfo.Audio.Count > 0) - song.Path = mediaStreamInfo.Audio.OrderBy(s => s.Bitrate).Last().Url; - else if (mediaStreamInfo.Muxed.Count > 0) - song.Path = mediaStreamInfo.Muxed.OrderBy(x => x.Resolution).Last().Url; - else - { - MainActivity.instance.NotStreamable(song.Title); - return null; - } - } - song.IsParsed = true; - - if (switchPosition != -1) - Queue.instance?.NotifyItemChanged(switchPosition, Resource.Drawable.PublicIcon); - - if (startPlaybackWhenPosible && song.Album != null) - { - if(switchPosition != -1) - { - currentID = switchPosition; - Queue.instance?.RefreshCurrent(); - Player.instance?.RefreshPlayer(); - } - - instance.Play(song, -1, switchPosition == -1); - startPlaybackWhenPosible = false; - } - - Video video = await client.GetVideoAsync(song.YoutubeID); - song.Title = video.Title; - song.Artist = video.Author; - song.Album = await YoutubeManager.GetBestThumb(new string[] { video.Thumbnails.MaxResUrl, video.Thumbnails.StandardResUrl, video.Thumbnails.HighResUrl }); - song.Duration = (int)video.Duration.TotalMilliseconds; - Player.instance?.RefreshPlayer(); - - if (startPlaybackWhenPosible) - { - instance.Play(song, -1, switchPosition == -1); - - if (switchPosition != -1) - { - Queue.instance?.NotifyItemChanged(switchPosition, song.Artist); - Home.instance?.NotifyQueueChanged(switchPosition, song.Artist); - } - } - - if (!song.IsLiveStream) - song.ExpireDate = mediaStreamInfo.ValidUntil; - - if(switchPosition != -1) - UpdateQueueItemDB(song, switchPosition); - - switchPosition = -1; - } - catch (System.Net.Http.HttpRequestException) - { - Console.WriteLine("&Parse time out"); - MainActivity.instance.Timout(); - if (MainActivity.instance != null) - MainActivity.instance.FindViewById(Resource.Id.ytProgress).Visibility = ViewStates.Gone; - song.IsParsed = false; - - if (startPlaybackWhenPosible) - Player.instance?.Ready(); - - switchPosition = -1; - return null; - } - catch(YoutubeExplode.Exceptions.VideoUnplayableException ex) - { - Console.WriteLine("&Parse error: " + ex.Message); - MainActivity.instance.Unplayable(song.Title, ex.Message); - if (MainActivity.instance != null) - MainActivity.instance.FindViewById(Resource.Id.ytProgress).Visibility = ViewStates.Gone; - - song.IsParsed = false; - if (switchPosition != -1) - RemoveFromQueue(switchPosition); //Remove the song from the queue since it can't be played. - - if(startPlaybackWhenPosible) - Player.instance?.Ready(); - - switchPosition = -1; - return null; - } - catch(YoutubeExplode.Exceptions.VideoUnavailableException) - { - MainActivity.instance.NotStreamable(song.Title); - if (MainActivity.instance != null) - MainActivity.instance.FindViewById(Resource.Id.ytProgress).Visibility = ViewStates.Gone; - - song.IsParsed = false; - if (switchPosition != -1) - RemoveFromQueue(switchPosition); //Remove the song from the queue since it can't be played. - - if (startPlaybackWhenPosible) - Player.instance?.Ready(); - - switchPosition = -1; - return null; - } - return song; - } - private async void ParseAndPlay(string action, string videoID, string title, string artist, string thumbnailURL, bool showPlayer = true) { if (MainActivity.instance != null && action == "Play") @@ -589,11 +448,11 @@ namespace Opus.Api.Services Queue.instance?.Refresh(); Player.instance?.RefreshPlayer(); currentID = 0; - await ParseSong(song, 0, true); + await new SongParser().ParseSong(song, 0, true); } else { - Song song = await ParseSong(new Song(title, artist, thumbnailURL, videoID, -1, -1, null, true, false)); + Song song = await new SongParser().ParseSong(new Song(title, artist, thumbnailURL, videoID, -1, -1, null, true, false)); if (song == null) //The song can't be played, do not add it to the queue return; @@ -763,6 +622,7 @@ namespace Opus.Api.Services private void RandomizeQueue() { + SongParser.CancelAll(); Random r = new Random(); if (UseCastPlayer) { @@ -799,6 +659,7 @@ namespace Opus.Api.Services song = new Song(title, artist, thumbnailURI, youtubeID, -1, -1, filePath, true); song.IsLiveStream = isLive; + SongParser.QueueSlotAdded(CurrentID() + 1); queue.Insert(CurrentID() + 1, song); Home.instance?.NotifyQueueInserted(CurrentID() + 1); Queue.instance?.NotifyItemInserted(CurrentID() + 1); @@ -821,6 +682,7 @@ namespace Opus.Api.Services public void AddToQueue(Song song) { + SongParser.QueueSlotAdded(CurrentID() + 1); queue.Insert(CurrentID() + 1, song); Home.instance?.NotifyQueueInserted(CurrentID() + 1); Queue.instance?.NotifyItemInserted(CurrentID() + 1); @@ -843,6 +705,7 @@ namespace Opus.Api.Services public async void AddToQueue(IEnumerable songs) { + SongParser.CancelAll(); queue.AddRange(songs); Home.instance?.RefreshQueue(); Queue.instance?.Refresh(); @@ -851,12 +714,13 @@ namespace Opus.Api.Services if (UseCastPlayer) { foreach (Song song in songs) - RemotePlayer.QueueAppendItem(GetQueueItem(await ParseSong(song)), null); + RemotePlayer.QueueAppendItem(GetQueueItem(await new SongParser().ParseSong(song)), null); } } public static void InsertToQueue(int position, Song song) { + SongParser.QueueSlotAdded(position); queue.Insert(position, song); Home.instance?.NotifyQueueInserted(position); Queue.instance?.NotifyItemInserted(position); @@ -869,6 +733,7 @@ namespace Opus.Api.Services public void InsertToQueue(int position, Song[] songs) { + SongParser.QueueSlotAdded(position); queue.InsertRange(position, songs); Home.instance?.NotifyQueueRangeInserted(position, songs.Length); Queue.instance?.NotifyItemRangeInserted(position, songs.Length); @@ -897,9 +762,7 @@ namespace Opus.Api.Services Queue.instance?.RefreshCurrent(); } - //Switch position is the position of the song that will be played just after the parsing. - if (switchPosition > position) - switchPosition--; + SongParser.QueueSlotRemoved(position); SaveQueueSlot(); queue.RemoveAt(position); @@ -1083,7 +946,7 @@ namespace Opus.Api.Services parseProgress.Visibility = ViewStates.Visible; parseProgress.ScaleY = 6; } - await ParseSong(song, position, !UseCastPlayer, true); + await new SongParser().ParseSong(song, position, !UseCastPlayer, true); if (song == null) //Check if the parse has succeed, the song is set to null if there is an error Player.instance?.Ready(); //Remove player's loading bar since we'll not load this song @@ -1262,7 +1125,7 @@ namespace Opus.Api.Services Song song = autoPlay[0]; if (song.IsParsed != false || !song.IsYt) return; - await ParseSong(song); + await new SongParser().ParseSong(song); } } else @@ -1271,7 +1134,7 @@ namespace Opus.Api.Services if (song.IsParsed != false || !song.IsYt) return; - await ParseSong(song, currentID + 1); + await new SongParser().ParseSong(song, currentID + 1); } } @@ -1290,7 +1153,7 @@ namespace Opus.Api.Services { Song song = await GetItem(); if (song.IsYt && song.IsParsed != true) - await ParseSong(song); + await new SongParser().ParseSong(song); else if(!song.IsYt) { MediaMetadataRetriever meta = new MediaMetadataRetriever(); @@ -1578,7 +1441,7 @@ namespace Opus.Api.Services for (int i = 0; i < queue.Count; i++) { if (queue[i].IsYt && queue[i].IsParsed != true) - queue[i] = await ParseSong(queue[i], i); + queue[i] = await new SongParser().ParseSong(queue[i], i); } if(showToast) diff --git a/Opus/Code/Api/SongParser.cs b/Opus/Code/Api/SongParser.cs new file mode 100644 index 0000000..a0e3a61 --- /dev/null +++ b/Opus/Code/Api/SongParser.cs @@ -0,0 +1,227 @@ +using Android.Views; +using Android.Widget; +using Opus.Api; +using Opus.Api.Services; +using Opus.DataStructure; +using Opus.Fragments; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using YoutubeExplode; +using YoutubeExplode.Models; + +namespace Opus.Code.Api +{ + public class SongParser + { + private static readonly List instances = new List(); + private int queuePosition = -1; + private bool canceled = false; + + public SongParser() + { + instances.Add(this); + } + + + #region queuePosition updates + public static void QueueSlotAdded(int newPos) + { + foreach(SongParser instance in instances) + { + if (newPos < instance.queuePosition) + instance.queuePosition++; + } + } + + public static void QueueSlotMoved(int oldPos, int newPos) + { + foreach (SongParser instance in instances) + { + if (oldPos == instance.queuePosition) + instance.queuePosition = newPos; + else if (oldPos < instance.queuePosition) + instance.queuePosition--; + else if (oldPos > instance.queuePosition && newPos < instance.queuePosition) + instance.queuePosition++; + } + } + + public static void QueueSlotRemoved(int removedPosition) + { + foreach (SongParser instance in instances) + { + if (removedPosition < instance.queuePosition) + instance.queuePosition--; + else if (removedPosition == instance.queuePosition) + instance.Cancel(); + } + } + #endregion + + /// + /// Method that complete the song you give to make it playable with the player. Used only for youtube songs but you can give local songs here, that won't hurt. + /// + /// The song you want to complete + /// If the song is in the queue, set this parameter to the song's queue position. It will give display callbacks. + /// Set this to true if you want to start playing the song before the end of this method. + /// Set this to true if you want this method to retrieve steams info from youtube even if the song said that it is already parsed. + /// + public async Task ParseSong(Song song, int position = -1, bool startPlaybackWhenPosible = false, bool forceParse = false) + { + queuePosition = position; + + if ((!forceParse && song.IsParsed == true) || !song.IsYt) + { + if (startPlaybackWhenPosible) + MusicPlayer.instance.Play(song, -1, queuePosition == -1); + + return song; + } + + if (song.IsParsed == null) + { + while (song.IsParsed == null) + await Task.Delay(10); + + if (canceled) + return song; + + if (startPlaybackWhenPosible && (await MusicPlayer.GetItem()).YoutubeID != song.YoutubeID) + MusicPlayer.instance.Play(song, -1, queuePosition == -1); + + return song; //Song is a class, the youtube id will be updated with another method + } + + try + { + song.IsParsed = null; + YoutubeClient client = new YoutubeClient(); + var mediaStreamInfo = await client.GetVideoMediaStreamInfosAsync(song.YoutubeID); + if (mediaStreamInfo.HlsLiveStreamUrl != null) + { + song.Path = mediaStreamInfo.HlsLiveStreamUrl; + song.IsLiveStream = true; + } + else + { + song.IsLiveStream = false; + + if (mediaStreamInfo.Audio.Count > 0) + song.Path = mediaStreamInfo.Audio.OrderBy(s => s.Bitrate).Last().Url; + else if (mediaStreamInfo.Muxed.Count > 0) + song.Path = mediaStreamInfo.Muxed.OrderBy(x => x.Resolution).Last().Url; + else + { + MainActivity.instance.NotStreamable(song.Title); + return null; + } + + song.ExpireDate = mediaStreamInfo.ValidUntil; + } + song.IsParsed = true; + + if (queuePosition != -1) + Queue.instance?.NotifyItemChanged(queuePosition, Resource.Drawable.PublicIcon); + + if (canceled) + return song; + + if (startPlaybackWhenPosible && song.Album != null) + { + if (queuePosition != -1) + { + MusicPlayer.currentID = queuePosition; + Queue.instance?.RefreshCurrent(); + Player.instance?.RefreshPlayer(); + } + + MusicPlayer.instance.Play(song, -1, queuePosition == -1); + startPlaybackWhenPosible = false; + } + + Video video = await client.GetVideoAsync(song.YoutubeID); + song.Title = video.Title; + song.Artist = video.Author; + song.Album = await YoutubeManager.GetBestThumb(new string[] { video.Thumbnails.MaxResUrl, video.Thumbnails.StandardResUrl, video.Thumbnails.HighResUrl }); + song.Duration = (int)video.Duration.TotalMilliseconds; + + if (queuePosition == MusicPlayer.CurrentID()) + Player.instance?.RefreshPlayer(); + + if (queuePosition != -1) + { + Queue.instance?.NotifyItemChanged(queuePosition, song.Artist); + Home.instance?.NotifyQueueChanged(queuePosition, song.Artist); + } + + if (canceled) + return song; + + if (startPlaybackWhenPosible) + MusicPlayer.instance.Play(song, -1, queuePosition == -1); + } + catch (System.Net.Http.HttpRequestException) + { + Console.WriteLine("&Parse time out"); + MainActivity.instance.Timout(); + if (MainActivity.instance != null) + MainActivity.instance.FindViewById(Resource.Id.ytProgress).Visibility = ViewStates.Gone; + song.IsParsed = false; + + if (startPlaybackWhenPosible) + Player.instance?.Ready(); + } + catch (YoutubeExplode.Exceptions.VideoUnplayableException ex) + { + Console.WriteLine("&Parse error: " + ex.Message); + MainActivity.instance.Unplayable(song.Title, ex.Message); + if (MainActivity.instance != null) + MainActivity.instance.FindViewById(Resource.Id.ytProgress).Visibility = ViewStates.Gone; + + song.IsParsed = false; + if (queuePosition != -1) + MusicPlayer.RemoveFromQueue(queuePosition); //Remove the song from the queue since it can't be played. + + if (startPlaybackWhenPosible) + Player.instance?.Ready(); + } + catch (YoutubeExplode.Exceptions.VideoUnavailableException) + { + MainActivity.instance.NotStreamable(song.Title); + if (MainActivity.instance != null) + MainActivity.instance.FindViewById(Resource.Id.ytProgress).Visibility = ViewStates.Gone; + + song.IsParsed = false; + if (queuePosition != -1) + MusicPlayer.RemoveFromQueue(queuePosition); //Remove the song from the queue since it can't be played. + + if (startPlaybackWhenPosible) + Player.instance?.Ready(); + } + + queuePosition = -1; + instances.Remove(this); + return song; + } + + /// + /// This method will remove playback calls and get requests from the + /// + private void Cancel() + { + instances.Remove(this); + canceled = true; + } + + /// + /// This will cancel all parse (by calling the cancel method on all instances). Use this when the queue has too many changes to track them. + /// + public static void CancelAll() + { + while(instances.Count > 0) + instances[0].Cancel(); + } + } +} \ No newline at end of file diff --git a/Opus/Code/Others/ItemTouchCallback.cs b/Opus/Code/Others/ItemTouchCallback.cs index 8899449..e8e09a3 100644 --- a/Opus/Code/Others/ItemTouchCallback.cs +++ b/Opus/Code/Others/ItemTouchCallback.cs @@ -54,7 +54,7 @@ namespace Opus.Others if (alwaysAllowSwap && (viewHolder.AdapterPosition + 1 == ((QueueAdapter)adapter).ItemCount || viewHolder.AdapterPosition == 0)) return MakeFlag(0, 0); - if (alwaysAllowSwap && (MusicPlayer.CurrentID() + 1 == viewHolder.AdapterPosition || MusicPlayer.switchPosition + 1 == viewHolder.AdapterPosition)) + if (alwaysAllowSwap && /*(*/MusicPlayer.CurrentID() + 1 == viewHolder.AdapterPosition/* || MusicPlayer.switchPosition + 1 == viewHolder.AdapterPosition)*/) return MakeMovementFlags(dragFlag, 0); return MakeMovementFlags(dragFlag, swipeFlag); diff --git a/Opus/Code/UI/Adapter/QueueAdapter.cs b/Opus/Code/UI/Adapter/QueueAdapter.cs index 87554f1..f790499 100644 --- a/Opus/Code/UI/Adapter/QueueAdapter.cs +++ b/Opus/Code/UI/Adapter/QueueAdapter.cs @@ -1,6 +1,5 @@ using Android.App; using Android.Content; -using Android.Content.Res; using Android.Graphics; using Android.Support.Design.Widget; using Android.Support.V7.Widget; @@ -9,6 +8,7 @@ using Android.Text.Style; using Android.Views; using Android.Widget; using Opus.Api.Services; +using Opus.Code.Api; using Opus.DataStructure; using Opus.Others; using Square.Picasso; @@ -326,7 +326,8 @@ namespace Opus.Adapter if (int.TryParse(payloads[0].ToString(), out int payload) && payload == Resource.Drawable.PublicIcon) { - holder.youtubeIcon.SetImageResource(Resource.Drawable.PublicIcon); + if(MusicPlayer.queue[position - 1].IsParsed == true) + holder.youtubeIcon.SetImageResource(Resource.Drawable.PublicIcon); return; } @@ -425,6 +426,8 @@ namespace Opus.Adapter else if (MusicPlayer.CurrentID() == fromPosition) MusicPlayer.currentID = toPosition; + SongParser.QueueSlotMoved(fromPosition, toPosition); + if (MusicPlayer.UseCastPlayer) { int nextItemID = MusicPlayer.RemotePlayer.MediaQueue.ItemCount > toPosition ? MusicPlayer.RemotePlayer.MediaQueue.ItemIdAtIndex(toPosition + 1) : 0; //0 = InvalidItemID = end of the queue diff --git a/Opus/Opus.csproj b/Opus/Opus.csproj index 6a4ea63..79b1039 100644 --- a/Opus/Opus.csproj +++ b/Opus/Opus.csproj @@ -324,6 +324,7 @@ +