Updating the parsing method, make it work with every queue change possible.

This commit is contained in:
Anonymus Raccoon
2019-06-03 20:35:21 +02:00
parent 862d51f134
commit 9d36ad3a4f
5 changed files with 252 additions and 158 deletions

View File

@@ -21,6 +21,7 @@ using Com.Google.Android.Exoplayer2.Trackselection;
using Com.Google.Android.Exoplayer2.Upstream; using Com.Google.Android.Exoplayer2.Upstream;
using Com.Google.Android.Exoplayer2.Util; using Com.Google.Android.Exoplayer2.Util;
using Newtonsoft.Json; using Newtonsoft.Json;
using Opus.Code.Api;
using Opus.DataStructure; using Opus.DataStructure;
using Opus.Fragments; using Opus.Fragments;
using Opus.Others; using Opus.Others;
@@ -63,7 +64,6 @@ namespace Opus.Api.Services
public static bool isRunning = false; public static bool isRunning = false;
private bool generating = false; private bool generating = false;
public static int currentID = 0; public static int currentID = 0;
public static int switchPosition;
public static bool autoUpdateSeekBar = true; public static bool autoUpdateSeekBar = true;
public static bool repeat = false; public static bool repeat = false;
public static bool useAutoPlay = true; 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) public async void Play(Song song, long progress = -1, bool addToQueue = true)
{ {
if (song.IsParsed == false) if (song.IsParsed == false)
await ParseSong(song, -1); await new SongParser().ParseSong(song, -1);
else if (song.IsParsed == null) else if (song.IsParsed == null)
{ {
await ParseSong(song, -1, true); await new SongParser().ParseSong(song, -1, true);
return; return;
} }
@@ -423,147 +423,6 @@ namespace Opus.Api.Services
GenerateAutoPlay(false); GenerateAutoPlay(false);
} }
private static async Task<Song> 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<ProgressBar>(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<ProgressBar>(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<ProgressBar>(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) private async void ParseAndPlay(string action, string videoID, string title, string artist, string thumbnailURL, bool showPlayer = true)
{ {
if (MainActivity.instance != null && action == "Play") if (MainActivity.instance != null && action == "Play")
@@ -589,11 +448,11 @@ namespace Opus.Api.Services
Queue.instance?.Refresh(); Queue.instance?.Refresh();
Player.instance?.RefreshPlayer(); Player.instance?.RefreshPlayer();
currentID = 0; currentID = 0;
await ParseSong(song, 0, true); await new SongParser().ParseSong(song, 0, true);
} }
else 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 if (song == null) //The song can't be played, do not add it to the queue
return; return;
@@ -763,6 +622,7 @@ namespace Opus.Api.Services
private void RandomizeQueue() private void RandomizeQueue()
{ {
SongParser.CancelAll();
Random r = new Random(); Random r = new Random();
if (UseCastPlayer) if (UseCastPlayer)
{ {
@@ -799,6 +659,7 @@ namespace Opus.Api.Services
song = new Song(title, artist, thumbnailURI, youtubeID, -1, -1, filePath, true); song = new Song(title, artist, thumbnailURI, youtubeID, -1, -1, filePath, true);
song.IsLiveStream = isLive; song.IsLiveStream = isLive;
SongParser.QueueSlotAdded(CurrentID() + 1);
queue.Insert(CurrentID() + 1, song); queue.Insert(CurrentID() + 1, song);
Home.instance?.NotifyQueueInserted(CurrentID() + 1); Home.instance?.NotifyQueueInserted(CurrentID() + 1);
Queue.instance?.NotifyItemInserted(CurrentID() + 1); Queue.instance?.NotifyItemInserted(CurrentID() + 1);
@@ -821,6 +682,7 @@ namespace Opus.Api.Services
public void AddToQueue(Song song) public void AddToQueue(Song song)
{ {
SongParser.QueueSlotAdded(CurrentID() + 1);
queue.Insert(CurrentID() + 1, song); queue.Insert(CurrentID() + 1, song);
Home.instance?.NotifyQueueInserted(CurrentID() + 1); Home.instance?.NotifyQueueInserted(CurrentID() + 1);
Queue.instance?.NotifyItemInserted(CurrentID() + 1); Queue.instance?.NotifyItemInserted(CurrentID() + 1);
@@ -843,6 +705,7 @@ namespace Opus.Api.Services
public async void AddToQueue(IEnumerable<Song> songs) public async void AddToQueue(IEnumerable<Song> songs)
{ {
SongParser.CancelAll();
queue.AddRange(songs); queue.AddRange(songs);
Home.instance?.RefreshQueue(); Home.instance?.RefreshQueue();
Queue.instance?.Refresh(); Queue.instance?.Refresh();
@@ -851,12 +714,13 @@ namespace Opus.Api.Services
if (UseCastPlayer) if (UseCastPlayer)
{ {
foreach (Song song in songs) 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) public static void InsertToQueue(int position, Song song)
{ {
SongParser.QueueSlotAdded(position);
queue.Insert(position, song); queue.Insert(position, song);
Home.instance?.NotifyQueueInserted(position); Home.instance?.NotifyQueueInserted(position);
Queue.instance?.NotifyItemInserted(position); Queue.instance?.NotifyItemInserted(position);
@@ -869,6 +733,7 @@ namespace Opus.Api.Services
public void InsertToQueue(int position, Song[] songs) public void InsertToQueue(int position, Song[] songs)
{ {
SongParser.QueueSlotAdded(position);
queue.InsertRange(position, songs); queue.InsertRange(position, songs);
Home.instance?.NotifyQueueRangeInserted(position, songs.Length); Home.instance?.NotifyQueueRangeInserted(position, songs.Length);
Queue.instance?.NotifyItemRangeInserted(position, songs.Length); Queue.instance?.NotifyItemRangeInserted(position, songs.Length);
@@ -897,9 +762,7 @@ namespace Opus.Api.Services
Queue.instance?.RefreshCurrent(); Queue.instance?.RefreshCurrent();
} }
//Switch position is the position of the song that will be played just after the parsing. SongParser.QueueSlotRemoved(position);
if (switchPosition > position)
switchPosition--;
SaveQueueSlot(); SaveQueueSlot();
queue.RemoveAt(position); queue.RemoveAt(position);
@@ -1083,7 +946,7 @@ namespace Opus.Api.Services
parseProgress.Visibility = ViewStates.Visible; parseProgress.Visibility = ViewStates.Visible;
parseProgress.ScaleY = 6; 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 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 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]; Song song = autoPlay[0];
if (song.IsParsed != false || !song.IsYt) if (song.IsParsed != false || !song.IsYt)
return; return;
await ParseSong(song); await new SongParser().ParseSong(song);
} }
} }
else else
@@ -1271,7 +1134,7 @@ namespace Opus.Api.Services
if (song.IsParsed != false || !song.IsYt) if (song.IsParsed != false || !song.IsYt)
return; 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(); Song song = await GetItem();
if (song.IsYt && song.IsParsed != true) if (song.IsYt && song.IsParsed != true)
await ParseSong(song); await new SongParser().ParseSong(song);
else if(!song.IsYt) else if(!song.IsYt)
{ {
MediaMetadataRetriever meta = new MediaMetadataRetriever(); MediaMetadataRetriever meta = new MediaMetadataRetriever();
@@ -1578,7 +1441,7 @@ namespace Opus.Api.Services
for (int i = 0; i < queue.Count; i++) for (int i = 0; i < queue.Count; i++)
{ {
if (queue[i].IsYt && queue[i].IsParsed != true) 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) if(showToast)

227
Opus/Code/Api/SongParser.cs Normal file
View File

@@ -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<SongParser> instances = new List<SongParser>();
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
/// <summary>
/// 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.
/// </summary>
/// <param name="song">The song you want to complete</param>
/// <param name="position">If the song is in the queue, set this parameter to the song's queue position. It will give display callbacks.</param>
/// <param name="startPlaybackWhenPosible">Set this to true if you want to start playing the song before the end of this method.</param>
/// <param name="forceParse">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.</param>
/// <returns></returns>
public async Task<Song> 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<ProgressBar>(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<ProgressBar>(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<ProgressBar>(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;
}
/// <summary>
/// This method will remove playback calls and get requests from the
/// </summary>
private void Cancel()
{
instances.Remove(this);
canceled = true;
}
/// <summary>
/// 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.
/// </summary>
public static void CancelAll()
{
while(instances.Count > 0)
instances[0].Cancel();
}
}
}

View File

@@ -54,7 +54,7 @@ namespace Opus.Others
if (alwaysAllowSwap && (viewHolder.AdapterPosition + 1 == ((QueueAdapter)adapter).ItemCount || viewHolder.AdapterPosition == 0)) if (alwaysAllowSwap && (viewHolder.AdapterPosition + 1 == ((QueueAdapter)adapter).ItemCount || viewHolder.AdapterPosition == 0))
return MakeFlag(0, 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, 0);
return MakeMovementFlags(dragFlag, swipeFlag); return MakeMovementFlags(dragFlag, swipeFlag);

View File

@@ -1,6 +1,5 @@
using Android.App; using Android.App;
using Android.Content; using Android.Content;
using Android.Content.Res;
using Android.Graphics; using Android.Graphics;
using Android.Support.Design.Widget; using Android.Support.Design.Widget;
using Android.Support.V7.Widget; using Android.Support.V7.Widget;
@@ -9,6 +8,7 @@ using Android.Text.Style;
using Android.Views; using Android.Views;
using Android.Widget; using Android.Widget;
using Opus.Api.Services; using Opus.Api.Services;
using Opus.Code.Api;
using Opus.DataStructure; using Opus.DataStructure;
using Opus.Others; using Opus.Others;
using Square.Picasso; using Square.Picasso;
@@ -326,7 +326,8 @@ namespace Opus.Adapter
if (int.TryParse(payloads[0].ToString(), out int payload) && payload == Resource.Drawable.PublicIcon) 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; return;
} }
@@ -425,6 +426,8 @@ namespace Opus.Adapter
else if (MusicPlayer.CurrentID() == fromPosition) else if (MusicPlayer.CurrentID() == fromPosition)
MusicPlayer.currentID = toPosition; MusicPlayer.currentID = toPosition;
SongParser.QueueSlotMoved(fromPosition, toPosition);
if (MusicPlayer.UseCastPlayer) if (MusicPlayer.UseCastPlayer)
{ {
int nextItemID = MusicPlayer.RemotePlayer.MediaQueue.ItemCount > toPosition ? MusicPlayer.RemotePlayer.MediaQueue.ItemIdAtIndex(toPosition + 1) : 0; //0 = InvalidItemID = end of the queue int nextItemID = MusicPlayer.RemotePlayer.MediaQueue.ItemCount > toPosition ? MusicPlayer.RemotePlayer.MediaQueue.ItemIdAtIndex(toPosition + 1) : 0; //0 = InvalidItemID = end of the queue

View File

@@ -324,6 +324,7 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Code\Api\SongParser.cs" />
<Compile Include="Code\DataStructure\SearchableList.cs" /> <Compile Include="Code\DataStructure\SearchableList.cs" />
<Compile Include="Code\UI\Adapter\BaseCursor.cs" /> <Compile Include="Code\UI\Adapter\BaseCursor.cs" />
<Compile Include="Code\Api\LocalManager.cs" /> <Compile Include="Code\Api\LocalManager.cs" />