Reworking the downloader (specialy the playlist part). Solving bugs.

This commit is contained in:
Tristan Roux
2019-05-01 21:55:47 +02:00
parent 9385e55192
commit bca15132ea
13 changed files with 172 additions and 106 deletions

View File

@@ -294,11 +294,13 @@ namespace Opus.Api
/// Open the EditMetadata page for a song.
/// </summary>
/// <param name="item"></param>
public static void EditMetadata(Song item)
/// <param name="queuePosition">Set this to a position of the song in the queue, it will update the queue slot after the edit.</param>
public static void EditMetadata(Song item, int queuePosition = -1)
{
item = CompleteItem(item);
Intent intent = new Intent(Application.Context, typeof(EditMetaData));
intent.PutExtra("Song", item.ToString());
intent.PutExtra("Position", queuePosition);
MainActivity.instance.StartActivity(intent);
}
#endregion

View File

@@ -319,7 +319,9 @@ namespace Opus.Api
if (playlist.LocalID != 0)
{
if (item.LocalID == 0 || item.LocalID == -1)
YoutubeManager.Download(new[] { item }, playlist.Name);
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
YoutubeManager.DownloadFiles(new[] { DownloadFile.From(item, playlist.Name) });
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
else
LocalManager.AddToPlaylist(new[] { item }, playlist.Name, playlist.LocalID);
}
@@ -894,7 +896,9 @@ namespace Opus.Api
if (items != null && items.Length > 0)
{
LocalManager.AddToPlaylist(items, name, playlistID); //Will only add files already downloaded
YoutubeManager.Download(items, name); //Will download missing files and add them (if there was youtube songs in the items array.
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
YoutubeManager.DownloadFiles(items.ToList().ConvertAll(x => DownloadFile.From(x, name))); //Will download missing files and add them (if there was youtube songs in the items array.
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
}
if (syncedPlaylist)

View File

@@ -2,11 +2,11 @@
using Android.App;
using Android.Content;
using Android.Content.PM;
using Android.Database;
using Android.Graphics;
using Android.Media;
using Android.Net;
using Android.OS;
using Android.Provider;
using Android.Support.Design.Widget;
using Android.Support.V4.App;
using Android.Support.V7.Preferences;
using Android.Support.V7.Widget;
@@ -16,7 +16,6 @@ using Opus.Adapter;
using Opus.DataStructure;
using Opus.Fragments;
using Opus.Others;
using SQLite;
using Square.Picasso;
using System.Collections.Generic;
using System.IO;
@@ -29,8 +28,9 @@ using YoutubeExplode.Models;
using YoutubeExplode.Models.MediaStreams;
using static Android.Provider.MediaStore.Audio;
using Bitmap = Android.Graphics.Bitmap;
using CursorLoader = Android.Support.V4.Content.CursorLoader;
using File = System.IO.File;
using Path = System.IO.Path;
using Picture = TagLib.Picture;
using Playlist = Opus.Fragments.Playlist;
using Stream = System.IO.Stream;
@@ -81,14 +81,49 @@ namespace Opus.Api.Services
return StartCommandResult.Sticky;
}
public async static Task Init()
{
ISharedPreferences prefManager = PreferenceManager.GetDefaultSharedPreferences(Application.Context);
string downloadPath = prefManager.GetString("downloadPath", null);
if (downloadPath == null)
{
Snackbar snackBar = Snackbar.Make(MainActivity.instance.FindViewById(Resource.Id.snackBar), Resource.String.download_path_not_set, Snackbar.LengthLong).SetAction(Resource.String.set_path, (v) =>
{
Intent pref = new Intent(Application.Context, typeof(Preferences));
MainActivity.instance.StartActivity(pref);
});
snackBar.View.FindViewById<TextView>(Resource.Id.snackbar_text).SetTextColor(Color.White);
snackBar.Show();
ISharedPreferencesEditor editor = prefManager.Edit();
editor.PutString("downloadPath", Environment.GetExternalStoragePublicDirectory(Environment.DirectoryMusic).ToString());
editor.Commit();
downloadPath = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryMusic).ToString();
}
if (queue == null)
queue = new List<DownloadFile>();
Context context = Application.Context;
Intent intent = new Intent(context, typeof(Downloader));
context.StartService(intent);
while (instance == null)
await Task.Delay(10);
instance.downloadPath = downloadPath;
instance.maxDownload = prefManager.GetInt("maxDownload", 4);
}
#region Downloading of the queue
public async void StartDownload()
{
await SyncWithPlaylists();
while (downloadCount < maxDownload && queue.Count(x => x.State == DownloadState.None) > 0)
{
#pragma warning disable CS4014
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
Task.Run(() => { DownloadAudio(queue.FindIndex(x => x.State == DownloadState.None), downloadPath); }, cancellation.Token);
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
await Task.Delay(10);
}
}
@@ -276,61 +311,39 @@ namespace Opus.Api.Services
});
}
}
#endregion
public async Task SyncWithPlaylists()
{
ISharedPreferences prefManager = PreferenceManager.GetDefaultSharedPreferences(Application.Context);
bool keepDeleted = prefManager.GetBoolean("keepDeleted", true);
List<string> playlists = queue.ConvertAll(x => x.playlist).Distinct().ToList();
foreach(string playlist in playlists)
await SyncWithPlaylist(playlist, keepDeleted);
}
public async Task SyncWithPlaylist(string playlistName, bool keepDeleted)
{
Playlist.instance?.StartSyncing(playlistName);
long LocalID = await PlaylistManager.GetPlaylistID(playlistName);
await SyncWithPlaylist(LocalID, keepDeleted);
await Task.Run(() =>
{
SQLiteConnection db = new SQLiteConnection(Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal), "SyncedPlaylists.sqlite"));
db.CreateTable<PlaylistItem>();
db.InsertOrReplace(new PlaylistItem(playlistName, LocalID, null));
});
await Task.Delay(1000);
Playlist.instance?.CheckForSync();
}
public async Task SyncWithPlaylist(long LocalID, bool keepDeleted)
#region Playlist downloading
public async void DownloadPlaylist(List<DownloadFile> files, long LocalID, bool keepDeleted)
{
if (LocalID != -1)
{
List<Song> songs = await PlaylistManager.GetTracksFromLocalPlaylist(LocalID);
await Task.Run(() =>
await Task.Run(() =>
{
foreach (Song song in songs)
LocalManager.CompleteItem(song);
});
for (int i = 0; i < queue.Count; i++)
for (int i = 0; i < files.Count; i++)
{
Song song = songs.Find(x => x.YoutubeID == queue[i].videoID);
Song song = songs.Find(x => x.YoutubeID == files[i].videoID);
if (song != null)
{
//Video is already downloaded:
if (queue[i].State == DownloadState.None)
queue[i].State = DownloadState.UpToDate;
if (files[i].State == DownloadState.None)
files[i].State = DownloadState.UpToDate;
currentStrike++;
songs.Remove(song);
}
}
await Task.Run(() =>
queue.AddRange(files);
StartDownload();
await Task.Run(() =>
{
if (Looper.MyLooper() == null)
Looper.Prepare();
@@ -347,9 +360,13 @@ namespace Opus.Api.Services
}
});
}
await Task.Delay(1000);
Playlist.instance?.CheckForSync();
}
#endregion
#region Cancel Handle
void Cancel()
{
cancellation.Cancel(true);
@@ -376,7 +393,9 @@ namespace Opus.Api.Services
cancellation = new CancellationTokenSource();
Playlist.instance?.SyncCanceled();
}
#endregion
#region Notification / Queue callbacks
void UpdateList(int position)
{
DownloadQueue.instance?.RunOnUiThread(() => { DownloadQueue.instance?.ListView.GetAdapter().NotifyItemChanged(position); });
@@ -441,6 +460,7 @@ namespace Opus.Api.Services
StartForeground(notificationID, notification.Build());
}
#endregion
}
}

View File

@@ -21,7 +21,9 @@ using System.Threading.Tasks;
using TagLib;
using YoutubeExplode;
using YoutubeExplode.Models;
using static Android.Provider.MediaStore.Audio;
using CursorLoader = Android.Support.V4.Content.CursorLoader;
using Playlist = Opus.Fragments.Playlist;
using PlaylistItem = Opus.DataStructure.PlaylistItem;
namespace Opus.Api
@@ -85,61 +87,44 @@ namespace Opus.Api
#region Downloader
/// <summary>
/// Download every youtube song in the items array and add them to the local playlist that you named in the second var.
/// Download every youtube song in the items array.
/// </summary>
/// <param name="items"></param>
/// <param name="playlist"></param>
public static void Download(Song[] items, string playlist)
public static void Download(Song[] items)
{
string[] names = items.ToList().Where(x => x.LocalID == -1).ToList().ConvertAll(x => x.Title).ToArray();
string[] videoIDs = items.ToList().Where(x => x.LocalID == -1).ToList().ConvertAll(x => x.YoutubeID).ToArray();
DownloadFiles(names, videoIDs, playlist);
DownloadFiles(names, videoIDs);
}
/// <summary>
/// Download songs using there id and there name (for the queue). Then, they will be added to the playlist that you named.
/// Download songs using there id and there name (for the queue).
/// </summary>
/// <param name="names"></param>
/// <param name="videoIDs"></param>
/// <param name="playlist"></param>
public static async void DownloadFiles(string[] names, string[] videoIDs, string playlist)
public static async void DownloadFiles(string[] names, string[] videoIDs)
{
ISharedPreferences prefManager = PreferenceManager.GetDefaultSharedPreferences(Android.App.Application.Context);
string downloadPath = prefManager.GetString("downloadPath", null);
if (downloadPath == null)
{
Snackbar snackBar = Snackbar.Make(MainActivity.instance.FindViewById(Resource.Id.snackBar), Resource.String.download_path_not_set, Snackbar.LengthLong).SetAction(Resource.String.set_path, (v) =>
{
Intent pref = new Intent(Android.App.Application.Context, typeof(Preferences));
MainActivity.instance.StartActivity(pref);
});
snackBar.View.FindViewById<TextView>(Resource.Id.snackbar_text).SetTextColor(Color.White);
snackBar.Show();
ISharedPreferencesEditor editor = prefManager.Edit();
editor.PutString("downloadPath", Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryMusic).ToString());
editor.Commit();
downloadPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryMusic).ToString();
}
Context context = Android.App.Application.Context;
Intent intent = new Intent(context, typeof(Downloader));
context.StartService(intent);
while (Downloader.instance == null)
await Task.Delay(10);
List<DownloadFile> files = new List<DownloadFile>();
for (int i = 0; i < names.Length; i++)
{
if (videoIDs[i] != null && videoIDs[i] != "")
files.Add(new DownloadFile(names[i], videoIDs[i], playlist));
files.Add(new DownloadFile(names[i], videoIDs[i], null));
}
Downloader.instance.downloadPath = downloadPath;
Downloader.instance.maxDownload = prefManager.GetInt("maxDownload", 4);
await DownloadFiles(files);
}
public static async Task DownloadFiles(IEnumerable<DownloadFile> files)
{
if (!await MainActivity.instance.GetReadPermission())
return;
if (!await MainActivity.instance.GetWritePermission())
return;
await Downloader.Init();
Downloader.queue.AddRange(files);
Downloader.instance.StartDownload();
}
@@ -147,10 +132,11 @@ namespace Opus.Api
/// <summary>
/// Download a playlist or update the local playlist with new songs.
/// </summary>
/// <param name="playlist">The name of the playlist (local one)</param>
/// <param name="playlistID">The youtube id of your playlist</param>
/// <param name="playlistName">The name of the playlist (local one)</param>
/// <param name="YoutubeID">The youtube id of your playlist</param>
/// <param name="keepPlaylistSynced">True if you want to add the playlist in the keep synced database (warning, this wont add the playlist to the saved ones) </param>
/// <param name="showToast">True if you want this method to display that the download has started</param>
public static async void DownloadPlaylist(string playlist, string playlistID, bool showToast = true)
public static async void DownloadPlaylist(string playlistName, string YoutubeID, bool keepPlaylistSynced, bool showToast)
{
if (!await MainActivity.instance.WaitForYoutube())
return;
@@ -164,13 +150,36 @@ namespace Opus.Api
if (showToast)
Toast.MakeText(Android.App.Application.Context, Resource.String.syncing, ToastLength.Short).Show();
long LocalID = await PlaylistManager.GetPlaylistID(playlistName);
if(LocalID == -1)
{
ContentValues value = new ContentValues();
value.Put(Playlists.InterfaceConsts.Name, playlistName);
MainActivity.instance.ContentResolver.Insert(Playlists.ExternalContentUri, value);
LocalID = await PlaylistManager.GetPlaylistID(playlistName);
}
Playlist.instance?.StartSyncing(playlistName);
if (keepPlaylistSynced)
{
await Task.Run(() =>
{
SQLiteConnection db = new SQLiteConnection(System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Personal), "SyncedPlaylists.sqlite"));
db.CreateTable<PlaylistItem>();
db.InsertOrReplace(new PlaylistItem(playlistName, LocalID, YoutubeID));
});
}
List<string> names = new List<string>();
List<string> videoIDs = new List<string>();
string nextPageToken = "";
while (nextPageToken != null)
{
var ytPlaylistRequest = YoutubeService.PlaylistItems.List("snippet, contentDetails");
ytPlaylistRequest.PlaylistId = playlistID;
ytPlaylistRequest.PlaylistId = YoutubeID;
ytPlaylistRequest.MaxResults = 50;
ytPlaylistRequest.PageToken = nextPageToken;
@@ -186,7 +195,19 @@ namespace Opus.Api
}
if (names.Count > 0)
DownloadFiles(names.ToArray(), videoIDs.ToArray(), playlist);
{
await Downloader.Init();
List<DownloadFile> files = new List<DownloadFile>();
for (int i = 0; i < names.Count; i++)
{
if (videoIDs[i] != null && videoIDs[i] != "")
files.Add(new DownloadFile(names[i], videoIDs[i], playlistName));
}
ISharedPreferences prefManager = PreferenceManager.GetDefaultSharedPreferences(Android.App.Application.Context);
Downloader.instance.DownloadPlaylist(files, LocalID, prefManager.GetBoolean("keepDeleted", true));
}
}
/// <summary>
@@ -219,7 +240,7 @@ namespace Opus.Api
{
if (item.YoutubeID != null)
{
DownloadPlaylist(item.Name, item.YoutubeID, false);
DownloadPlaylist(item.Name, item.YoutubeID, false, false);
}
}
}

View File

@@ -17,6 +17,11 @@
this.videoID = videoID;
this.playlist = playlist;
}
public static DownloadFile From(Song song, string playlistName)
{
return new DownloadFile(song.Title, song.YoutubeID, playlistName);
}
}
public enum DownloadState

View File

@@ -206,7 +206,7 @@ namespace Opus.Adapter
}),
new BottomSheetAction(Resource.Drawable.Download, MainActivity.instance.Resources.GetString(Resource.String.download), (sender, eventArg) =>
{
YoutubeManager.Download(new[] { item }, null);
YoutubeManager.Download(new[] { item });
bottomSheet.Dismiss();
})
});
@@ -285,7 +285,7 @@ namespace Opus.Adapter
}),
new BottomSheetAction(Resource.Drawable.Download, MainActivity.instance.Resources.GetString(Resource.String.download), (sender, eventArg) =>
{
YoutubeManager.Download(new[] { item }, null);
YoutubeManager.Download(new[] { item });
bottomSheet.Dismiss();
})
});

View File

@@ -12,6 +12,7 @@ using Android.Util;
using Android.Views;
using Android.Widget;
using Opus.Api;
using Opus.Api.Services;
using Opus.DataStructure;
using Opus.Others;
using Square.Picasso;
@@ -29,6 +30,7 @@ namespace Opus.Fragments
{
public static EditMetaData instance;
public Song song;
private int queuePosition;
private TextView title, artist, album, youtubeID;
private ImageView albumArt;
@@ -51,6 +53,7 @@ namespace Opus.Fragments
instance = this;
song = (Song)Intent.GetStringExtra("Song");
queuePosition = Intent.GetIntExtra("Position", -1);
Android.Support.V7.Widget.Toolbar toolbar = FindViewById<Android.Support.V7.Widget.Toolbar>(Resource.Id.backToolbar);
DisplayMetrics metrics = new DisplayMetrics();
@@ -167,6 +170,8 @@ namespace Opus.Fragments
{
if(await ValidateChanges(true))
Finish();
Player.instance?.RefreshPlayer();
}
async Task<bool> ValidateChanges(bool displayActionIfMountFail = false)
@@ -208,9 +213,18 @@ namespace Opus.Fragments
System.Console.WriteLine("&Writing tags");
meta.Tag.Title = title.Text;
song.Title = title.Text;
meta.Tag.Performers = new string[] { artist.Text };
song.Artist = artist.Text;
meta.Tag.Album = album.Text;
song.Album = album.Text;
meta.Tag.Comment = youtubeID.Text;
if (queuePosition != -1 && MusicPlayer.queue.Count > queuePosition)
{
MusicPlayer.queue[queuePosition] = song;
Player.instance?.RefreshPlayer();
Queue.instance.NotifyItemChanged(queuePosition);
}
if (ytThumbUri != null)
{

View File

@@ -186,12 +186,15 @@ namespace Opus.Fragments
}
int YoutubeIndex = YoutubePlaylists.FindIndex(x => x.Name == playlistName);
YoutubePlaylists[YoutubeIndex].SyncState = SyncState.Loading;
PlaylistHolder holder = (PlaylistHolder)ListView.GetChildViewHolder(ListView.GetChildAt(LocalPlaylists.Count + YoutubeIndex));
holder.sync.Visibility = ViewStates.Gone;
holder.SyncLoading.Visibility = ViewStates.Visible;
if (MainActivity.Theme == 1)
holder.SyncLoading.IndeterminateTintList = ColorStateList.ValueOf(Color.White);
if(YoutubeIndex != -1)
{
YoutubePlaylists[YoutubeIndex].SyncState = SyncState.Loading;
PlaylistHolder holder = (PlaylistHolder)ListView.GetChildViewHolder(ListView.GetChildAt(LocalPlaylists.Count + YoutubeIndex));
holder.sync.Visibility = ViewStates.Gone;
holder.SyncLoading.Visibility = ViewStates.Visible;
if (MainActivity.Theme == 1)
holder.SyncLoading.IndeterminateTintList = ColorStateList.ValueOf(Color.White);
}
}
private void SyncError()
@@ -434,7 +437,7 @@ namespace Opus.Fragments
{
actions.AddRange(new BottomSheetAction[]{ new BottomSheetAction(Resource.Drawable.Sync, Resources.GetString(Resource.String.sync_now), (sender, eventArg) =>
{
YoutubeManager.DownloadPlaylist(item.Name, item.YoutubeID);
YoutubeManager.DownloadPlaylist(item.Name, item.YoutubeID, true, true);
bottomSheet.Dismiss();
}),
new BottomSheetAction(Resource.Drawable.SyncDisabled, Resources.GetString(Resource.String.stop_sync), (sender, eventArg) =>
@@ -464,7 +467,7 @@ namespace Opus.Fragments
{
actions.Add(new BottomSheetAction(Resource.Drawable.Sync, Resources.GetString(Resource.String.sync), (sender, eventArg) =>
{
YoutubeManager.DownloadPlaylist(item.Name, item.YoutubeID);
YoutubeManager.DownloadPlaylist(item.Name, item.YoutubeID, true, true);
bottomSheet.Dismiss();
}));

View File

@@ -172,7 +172,7 @@ namespace Opus.Fragments
switch (menuItem.ItemId)
{
case Resource.Id.download:
YoutubeManager.DownloadPlaylist(item.Name, item.YoutubeID);
YoutubeManager.DownloadPlaylist(item.Name, item.YoutubeID, true, true);
break;
case Resource.Id.fork:
@@ -527,7 +527,7 @@ namespace Opus.Fragments
}),
new BottomSheetAction(Resource.Drawable.Download, Resources.GetString(Resource.String.download), (sender, eventArg) =>
{
YoutubeManager.Download(new[] { song }, null);
YoutubeManager.Download(new[] { song });
bottomSheet.Dismiss();
})
});

View File

@@ -280,10 +280,7 @@ namespace Opus.Fragments
prefButton.Summary = pref.GetInt("maxDownload", 2).ToString();
if(Downloader.instance != null && Downloader.queue.Count > 0)
{
Downloader.instance.maxDownload = pref.GetInt("maxDownload", 4);
Downloader.instance.StartDownload();
}
});
builder.SetNegativeButton(Resources.GetString(Resource.String.cancel), (s, eventArg) => { });
builder.Show();

View File

@@ -201,7 +201,7 @@ public class Queue : Fragment, RecyclerView.IOnItemTouchListener, PopupMenu.IOnM
}),
new BottomSheetAction(Resource.Drawable.Download, Resources.GetString(Resource.String.download), (sender, eventArg) =>
{
YoutubeManager.Download(new[] { item }, null);
YoutubeManager.Download(new[] { item });
bottomSheet.Dismiss();
})
});

View File

@@ -399,7 +399,7 @@ namespace Opus.Fragments
new BottomSheetAction(Resource.Drawable.PlaylistAdd, Resources.GetString(Resource.String.add_to_playlist), (sender, eventArg) => { PlaylistManager.AddSongToPlaylistDialog(item); bottomSheet.Dismiss(); }),
new BottomSheetAction(Resource.Drawable.Download, Resources.GetString(Resource.String.download), (sender, eventArg) =>
{
YoutubeManager.Download(new[] { item }, null);
YoutubeManager.Download(new[] { item });
bottomSheet.Dismiss();
})
});
@@ -434,7 +434,7 @@ namespace Opus.Fragments
}),
new BottomSheetAction(Resource.Drawable.Download, MainActivity.instance.Resources.GetString(Resource.String.download), (sender, eventArg) =>
{
YoutubeManager.DownloadPlaylist(item.Name, item.YoutubeID);
YoutubeManager.DownloadPlaylist(item.Name, item.YoutubeID, true, false);
bottomSheet.Dismiss();
})
};

View File

@@ -425,7 +425,7 @@ namespace Opus
}),
new BottomSheetAction(Resource.Drawable.Download, Resources.GetString(Resource.String.download), (sender, eventArg) =>
{
YoutubeManager.Download(new[]{ item }, null);
YoutubeManager.Download(new[]{ item });
bottomSheet.Dismiss();
}),
new BottomSheetAction(Resource.Drawable.OpenInBrowser, Resources.GetString(Resource.String.open_youtube), (sender, eventArg) =>
@@ -440,7 +440,7 @@ namespace Opus
{
actions.Add(new BottomSheetAction(Resource.Drawable.Edit, Resources.GetString(Resource.String.edit_metadata), (sender, eventArg) =>
{
LocalManager.EditMetadata(item);
LocalManager.EditMetadata(item, MusicPlayer.CurrentID());
bottomSheet.Dismiss();
}));
}