Song elasping functionality implemented, can seek to a certain point of a song via the scroll bar, Song metadata are displayed, and continuing working on the Player view. #8, #24, #25, #27, #33
This commit is contained in:
@@ -30,7 +30,7 @@ namespace Mear.Playback
|
||||
|
||||
|
||||
#region Methods
|
||||
public static async Task StreamSongDemoAsync(Song song)
|
||||
public static async Task<Song> StreamSongDemoAsync(Song song)
|
||||
{
|
||||
string tmpFile = Path.GetTempPath() + "track.mp3";
|
||||
|
||||
@@ -55,14 +55,17 @@ namespace Mear.Playback
|
||||
var response = client.DownloadData(request);
|
||||
|
||||
await CrossMediaManager.Current.Play(tmpFile);
|
||||
var title = CrossMediaManager.Current.MediaQueue.Current.Title;
|
||||
var ttl = CrossMediaManager.Current.MediaQueue.Title;
|
||||
song.SongPath = tmpFile;
|
||||
|
||||
return song;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
@@ -8,6 +9,7 @@ using RestSharp;
|
||||
using Mear.Constants.API;
|
||||
using Mear.Models;
|
||||
using Mear.Repositories.Database;
|
||||
using Mear.Utilities;
|
||||
|
||||
namespace Mear.Repositories.Remote
|
||||
{
|
||||
@@ -51,6 +53,33 @@ namespace Mear.Repositories.Remote
|
||||
|
||||
return null;
|
||||
}
|
||||
public void DownloadSong(Song song)
|
||||
{
|
||||
try
|
||||
{
|
||||
var dirMgr = new DirectoryManager(song);
|
||||
var path = dirMgr.CreateSongPath(song);
|
||||
using (var writer = File.OpenWrite(path))
|
||||
{
|
||||
var client = new RestClient(API.ApiUrl);
|
||||
var apiEndpoint = $@"api/{API.APIVersion}/song/data/{song.Id}"; ;
|
||||
var request = new RestRequest(apiEndpoint, Method.GET);
|
||||
|
||||
DBTokenRepository tkRepo = new DBTokenRepository();
|
||||
var token = tkRepo.RetrieveToken();
|
||||
request.AddHeader("Authorization", $"Bearer {token.AccessToken}");
|
||||
|
||||
request.ResponseWriter = (responseStream) =>
|
||||
responseStream.CopyTo(writer);
|
||||
|
||||
var response = client.DownloadData(request);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using Mear.Models;
|
||||
|
||||
namespace Mear.Utilities
|
||||
{
|
||||
public class DirectoryManager
|
||||
{
|
||||
#region Fields
|
||||
private Song _song;
|
||||
private string _path;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public DirectoryManager(Song song)
|
||||
{
|
||||
_song = song;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public string CreateSongPath(Song song)
|
||||
{
|
||||
try
|
||||
{
|
||||
var artist = song.Artist;
|
||||
var album = song.Album;
|
||||
var title = song.Title;
|
||||
|
||||
if (!ArtistDirectoryExist(song))
|
||||
{
|
||||
Directory.CreateDirectory(ArtistPath(song));
|
||||
}
|
||||
if (!AlbumDirectoryExist(song))
|
||||
{
|
||||
Directory.CreateDirectory(AlbumPath(song));
|
||||
}
|
||||
if (SongPathExist(song))
|
||||
{
|
||||
// TODO: Song exists
|
||||
return SongPath(song);
|
||||
}
|
||||
else
|
||||
{
|
||||
return SongPath(song);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
private string RootPath(Song song)
|
||||
{
|
||||
var path = $@"{Environment.GetFolderPath(Environment.SpecialFolder.Personal)}/";
|
||||
|
||||
return path;
|
||||
}
|
||||
private string ArtistPath(Song song)
|
||||
{
|
||||
var path = RootPath(song);
|
||||
path += $@"{song.Artist}/";
|
||||
|
||||
return path;
|
||||
}
|
||||
private string AlbumPath(Song song)
|
||||
{
|
||||
var path = ArtistPath(song);
|
||||
path += $@"{song.Album}/";
|
||||
|
||||
return path;
|
||||
}
|
||||
private string SongPath(Song song)
|
||||
{
|
||||
var path = AlbumPath(song);
|
||||
path += $@"{song.Filename}";
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
private bool ArtistDirectoryExist(Song song)
|
||||
{
|
||||
var path = RootPath(song);
|
||||
path += song.Artist;
|
||||
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
private bool AlbumDirectoryExist(Song song)
|
||||
{
|
||||
var path = AlbumPath(song);
|
||||
|
||||
return Directory.Exists(path);
|
||||
}
|
||||
private bool SongPathExist(Song song)
|
||||
{
|
||||
var path = SongPath(song);
|
||||
|
||||
return File.Exists(path);
|
||||
}
|
||||
|
||||
private void Initialize()
|
||||
{
|
||||
_path = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + @"/";
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
|
||||
using TagLib;
|
||||
|
||||
using Mear.Models;
|
||||
|
||||
using IOFile = System.IO.File;
|
||||
|
||||
namespace Mear.Utilities
|
||||
{
|
||||
public class SongMetadataRetriever
|
||||
@@ -35,6 +38,37 @@ namespace Mear.Utilities
|
||||
|
||||
return null;
|
||||
}
|
||||
public Stream ExtractCovertArtStream(Song song)
|
||||
{
|
||||
var strm = new FileStream(song.SongPath, FileMode.Open);
|
||||
|
||||
try
|
||||
{
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
}
|
||||
|
||||
return strm;
|
||||
}
|
||||
public string ExtractCoverArtData(Song song)
|
||||
{
|
||||
var imgData = string.Empty;
|
||||
try
|
||||
{
|
||||
var d = IOFile.ReadAllBytes(song.SongPath);
|
||||
/**
|
||||
*/
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
}
|
||||
|
||||
return imgData;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Mear.Utilities
|
||||
{
|
||||
public class TimeFormat
|
||||
{
|
||||
#region Fields
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
public string ConvertToSongTime(int seconds)
|
||||
{
|
||||
var curTime = string.Empty;
|
||||
var dur = seconds;
|
||||
var min = TimeSpan.FromSeconds((double) dur).Minutes;
|
||||
var remainingSec = dur % 60;
|
||||
|
||||
if (remainingSec < 10)
|
||||
{
|
||||
curTime = $"{min}:0{remainingSec}";
|
||||
}
|
||||
else
|
||||
{
|
||||
curTime = $"{min}:{remainingSec}";
|
||||
}
|
||||
|
||||
return curTime;
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Xamarin.Forms;
|
||||
|
||||
using Mear.Models;
|
||||
|
||||
namespace Mear.ViewModels
|
||||
{
|
||||
public class MearPlayerViewModel : BaseViewModel
|
||||
{
|
||||
#region Fields
|
||||
private ObservableCollection<Song> _song;
|
||||
#endregion
|
||||
|
||||
|
||||
#region Properties
|
||||
public ObservableCollection<Song> SongItem
|
||||
{
|
||||
get => _song;
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Constructors
|
||||
public MearPlayerViewModel()
|
||||
{
|
||||
}
|
||||
public MearPlayerViewModel(Song song)
|
||||
{
|
||||
_song = new ObservableCollection<Song>();
|
||||
_song.Clear();
|
||||
_song.Add(song);
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region Methods
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
@@ -4,67 +4,92 @@
|
||||
xmlns:d="http://xamarin.com/schemas/2014/forms/design"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Mear.Views.MearPlayerView" >
|
||||
x:Class="Mear.Views.MearPlayerView"
|
||||
xmlns:vm="clr-namespace:Mear.ViewModels"
|
||||
Title="{Binding Title}">
|
||||
|
||||
|
||||
<!--
|
||||
<ContentPage.Content>
|
||||
<StackLayout>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
-->
|
||||
<StackLayout>
|
||||
<StackLayout Padding="10" VerticalOptions="EndAndExpand">
|
||||
<StackLayout VerticalOptions="End">
|
||||
<Image x:Name="SongCover"
|
||||
Grid.Row="1"
|
||||
Source="" />
|
||||
Source="" />
|
||||
</StackLayout>
|
||||
<StackLayout BindableLayout.ItemsSource="{Binding SongItem}"
|
||||
VerticalOptions="End">
|
||||
<BindableLayout.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackLayout HorizontalOptions="Center">
|
||||
<Label x:Name="SongTitle"
|
||||
Text="{Binding Title}"
|
||||
d:Text="{Binding .}" />
|
||||
<Label x:Name="ArtistName"
|
||||
Text="{Binding Artist}" />
|
||||
<Label x:Name="AlbumName"
|
||||
Text="{Binding Album}"/>
|
||||
</StackLayout>
|
||||
<!--
|
||||
<StackLayout>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackLayout Grid.Row="2">
|
||||
<Label x:Name="SongTitle"
|
||||
Text="Title" />
|
||||
<Label x:Name="ArtistName"
|
||||
Text="Artist" />
|
||||
<Label x:Name="AlbumName"
|
||||
Text="Album"/>
|
||||
</StackLayout>
|
||||
<StackLayout Orientation="Horizontal"
|
||||
Grid.Row="3">
|
||||
<Button x:Name="Repeat"
|
||||
Text="Rep"
|
||||
Clicked="Repeat_Clicked" />
|
||||
<Button x:Name="Shuffle"
|
||||
Text="Shf"
|
||||
Clicked="Shuffle_Clicked" />
|
||||
</StackLayout>
|
||||
<StackLayout Orientation="Horizontal"
|
||||
Grid.Row="4">
|
||||
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
-->
|
||||
</DataTemplate>
|
||||
</BindableLayout.ItemTemplate>
|
||||
</StackLayout>
|
||||
<StackLayout Orientation="Horizontal"
|
||||
Grid.Row="3">
|
||||
<Button x:Name="Repeat"
|
||||
Text="Rep"
|
||||
Clicked="Repeat_Clicked" />
|
||||
<Button x:Name="Shuffle"
|
||||
Text="Shf"
|
||||
Clicked="Shuffle_Clicked" />
|
||||
</StackLayout>
|
||||
<StackLayout Orientation="Horizontal">
|
||||
<Label x:Name="StartTime"
|
||||
Text="0:00" />
|
||||
<ProgressBar x:Name="SongProgress"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
Progress="0.7" />
|
||||
<Label x:Name="EndTime"
|
||||
Text="0:00" />
|
||||
</StackLayout>
|
||||
<StackLayout Orientation="Horizontal"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
Grid.Row="5"
|
||||
Spacing="5">
|
||||
<Button x:Name="Previous"
|
||||
Text="Prev"
|
||||
Clicked="Previous_Clicked" />
|
||||
<Button x:Name="Play"
|
||||
Text="Ply"
|
||||
Clicked="Play_Clicked" />
|
||||
<Button x:Name="Next"
|
||||
Text="Nxt"
|
||||
Clicked="Next_Clicked" />
|
||||
</StackLayout>
|
||||
</Grid>
|
||||
</StackLayout>
|
||||
Text="0:00" />
|
||||
<Slider x:Name="SongProgress"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
Minimum="0"
|
||||
Maximum="100"
|
||||
WidthRequest="275"
|
||||
DragCompleted="SongProgress_DragCompleted"
|
||||
Scale="1" />
|
||||
<Label x:Name="EndTime"
|
||||
Text="0:00" />
|
||||
</StackLayout>
|
||||
<StackLayout Orientation="Horizontal"
|
||||
HorizontalOptions="CenterAndExpand"
|
||||
Spacing="5">
|
||||
<Button x:Name="Previous"
|
||||
Text="Prev"
|
||||
Clicked="Previous_Clicked" />
|
||||
<Button x:Name="Play"
|
||||
Text="Ply"
|
||||
Clicked="Play_Clicked" />
|
||||
<Button x:Name="Next"
|
||||
Text="Nxt"
|
||||
Clicked="Next_Clicked" />
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
</StackLayout>
|
||||
<!--
|
||||
</ContentPage.Content>
|
||||
-->
|
||||
<ContentPage.ToolbarItems>
|
||||
<ToolbarItem Name="Download" Order="Secondary" Priority="1" Clicked="Download_Clicked" />
|
||||
</ContentPage.ToolbarItems>
|
||||
</ContentPage>
|
||||
@@ -1,13 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using MediaManager;
|
||||
using Xamarin.Forms;
|
||||
using Xamarin.Forms.Xaml;
|
||||
|
||||
using Mear.Models;
|
||||
using Mear.Repositories.Remote;
|
||||
using Mear.Utilities;
|
||||
using Mear.ViewModels;
|
||||
|
||||
namespace Mear.Views
|
||||
{
|
||||
@@ -15,6 +21,7 @@ namespace Mear.Views
|
||||
public partial class MearPlayerView : ContentPage
|
||||
{
|
||||
#region Fields
|
||||
private MearPlayerViewModel _viewModel;
|
||||
private Song _song;
|
||||
#endregion
|
||||
|
||||
@@ -27,14 +34,19 @@ namespace Mear.Views
|
||||
public MearPlayerView()
|
||||
{
|
||||
InitializeComponent();
|
||||
Initialize();
|
||||
|
||||
BindingContext = _viewModel = new MearPlayerViewModel();
|
||||
}
|
||||
public MearPlayerView(Song song)
|
||||
{
|
||||
InitializeComponent();
|
||||
_song = song;
|
||||
Initialize();
|
||||
InitializeControls();
|
||||
|
||||
BackgroundSongElasping();
|
||||
BackgroundSongCoverUpdate();
|
||||
|
||||
BindingContext = _viewModel = new MearPlayerViewModel(song);
|
||||
}
|
||||
#endregion
|
||||
|
||||
@@ -42,27 +54,88 @@ namespace Mear.Views
|
||||
#region Methods
|
||||
private void Initialize()
|
||||
{
|
||||
SongTitle.Text = "";
|
||||
ArtistName.Text = "";
|
||||
AlbumName.Text = "";
|
||||
EndTime.Text = "";
|
||||
}
|
||||
private void InitializeControls()
|
||||
{
|
||||
SongTitle.Text = _song.Title;
|
||||
ArtistName.Text = _song.Artist;
|
||||
AlbumName.Text = _song.Album;
|
||||
EndTime.Text = $"{_song.Duration}";
|
||||
var songCnvrt = new TimeFormat();
|
||||
var dur = _song.Duration;
|
||||
var endTime = songCnvrt.ConvertToSongTime(dur.Value);
|
||||
|
||||
EndTime.Text = endTime;
|
||||
}
|
||||
|
||||
#region Background
|
||||
private async Task BackgroundSongElasping()
|
||||
{
|
||||
try
|
||||
{
|
||||
new Thread(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
var ttlSec = (int)CrossMediaManager.Current.Position.TotalSeconds;
|
||||
var cnvrt = new TimeFormat();
|
||||
var curPos = cnvrt.ConvertToSongTime(ttlSec);
|
||||
StartTime.Text = $"{curPos}";
|
||||
double progVal = ((double)ttlSec) / ((double)_song.Duration);
|
||||
progVal *= 100;
|
||||
SongProgress.Value = progVal;
|
||||
});
|
||||
|
||||
await Task.Delay(500);
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
}
|
||||
}
|
||||
private async Task BackgroundSongCoverUpdate()
|
||||
{
|
||||
try
|
||||
{
|
||||
new Thread(async () =>
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
Device.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
if (SongCover.Source.IsEmpty)
|
||||
{
|
||||
var meta = new SongMetadataRetriever();
|
||||
var data = meta.ExtractCoverArtData(_song);
|
||||
}
|
||||
});
|
||||
|
||||
await Task.Delay(1000);
|
||||
}
|
||||
}).Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var msg = ex.Message;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Events
|
||||
private void Previous_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
private void Play_Clicked(object sender, EventArgs e)
|
||||
private async void Play_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
|
||||
if (CrossMediaManager.Current.IsPlaying())
|
||||
{
|
||||
await CrossMediaManager.Current.Pause();
|
||||
}
|
||||
else
|
||||
{
|
||||
await CrossMediaManager.Current.Play();
|
||||
}
|
||||
}
|
||||
private void Next_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
@@ -76,7 +149,23 @@ namespace Mear.Views
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
|
||||
private async void SongProgress_DragCompleted(object sender, EventArgs e)
|
||||
{
|
||||
var progVal = SongProgress.Value;
|
||||
double newPos = (progVal / 100) * ((double)_song.Duration);
|
||||
await CrossMediaManager.Current.SeekTo(TimeSpan.FromSeconds(newPos));
|
||||
}
|
||||
|
||||
private void Download_Clicked(object sender, EventArgs e)
|
||||
{
|
||||
var songRepo = new RemoteSongRepository();
|
||||
songRepo.DownloadSong(_song);
|
||||
}
|
||||
#endregion
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
@@ -49,8 +49,8 @@ namespace Mear.Views
|
||||
try
|
||||
{
|
||||
var song = (Song)SongListView.SelectedItem;
|
||||
await MearPlayer.StreamSongDemoAsync(song);
|
||||
await Navigation.PushModalAsync(new MearPlayerView(song));
|
||||
song = await MearPlayer.StreamSongDemoAsync(song);
|
||||
await Navigation.PushModalAsync(new NavigationPage(new MearPlayerView(song)));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user