From: aman.jeph Date: Fri, 16 Jul 2021 10:11:37 +0000 (+0530) Subject: Adding album color extraction source X-Git-Tag: submit/tizen/20210915.141722~6 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=461f0c2bc09049b1fda30bdbc9af1abbac55ad36;p=profile%2Fiot%2Fapps%2Fdotnet%2Fmusic-player.git Adding album color extraction source Updating Lyrics View source Change-Id: Ieab3630ff97ff95b74d88d008baaa036aaa9432a Signed-off-by: aman.jeph --- diff --git a/Core/ColorExtractor.cs b/Core/ColorExtractor.cs new file mode 100644 index 0000000..db4a86d --- /dev/null +++ b/Core/ColorExtractor.cs @@ -0,0 +1,39 @@ +using System.Threading; +using System.Threading.Tasks; +using Tizen.NUI; +using MusicPlayer.Common; + +namespace MusicPlayer.Core +{ + static class ColorExtractor + { + public static async Task GetPaletteAsync(string imagePath) + { + Tizen.Log.Debug(AppConstants.LogTag, "Main Thread Id: " + Thread.CurrentThread.ManagedThreadId); + return await Task.Run(() => GeneratePalette(imagePath)); + } + + private static Palette GeneratePalette(string imagePath) + { + PixelBuffer pixelBuffer = ImageLoading.LoadImageFromFile(imagePath); + if(pixelBuffer == null) + { + Tizen.Log.Error(AppConstants.LogTag, "Pixel buffer is null"); + return null; + } + + Palette palette = null; + + try + { + palette = Palette.Generate(pixelBuffer); + } + catch(System.ArgumentNullException e) + { + Tizen.Log.Error(AppConstants.LogTag, "ArgumentNullException: " + e.Message); + } + + return palette; + } + } +} diff --git a/ViewModels/LyricsViewModel.cs b/ViewModels/LyricsViewModel.cs index 2c2df15..6307f28 100644 --- a/ViewModels/LyricsViewModel.cs +++ b/ViewModels/LyricsViewModel.cs @@ -1,9 +1,11 @@ using Tizen.Multimedia; using MusicPlayer.Models; +using MusicPlayer.Common; +using Tizen.NUI; namespace MusicPlayer.ViewModels { - class LyricsViewModel + class LyricsViewModel : PropertyNotifier { internal LyricsModel lyricsModel; @@ -34,11 +36,29 @@ namespace MusicPlayer.ViewModels } } + private Color lyricsBackgroundColor; + + public Color LyricsBackgroundColor + { + get => lyricsBackgroundColor; + set => SetProperty(ref lyricsBackgroundColor, value); + } + private string GetLyrics(string path) { var metadataExtractor = new MetadataExtractor(path); Metadata metadata = metadataExtractor.GetMetadata(); - return metadata.UnsyncLyrics; + string lyrics = metadata.UnsyncLyrics; + if(string.IsNullOrEmpty(lyrics)) + { + LyricsBackgroundColor = Color.Transparent; + return string.Empty; + } + else + { + LyricsBackgroundColor = UIColors.LyricsBackground; + return lyrics; + } } } } diff --git a/ViewModels/PlayerViewModel.cs b/ViewModels/PlayerViewModel.cs index c497ae9..6ccc60c 100755 --- a/ViewModels/PlayerViewModel.cs +++ b/ViewModels/PlayerViewModel.cs @@ -6,12 +6,14 @@ using MusicPlayer.Common; using MusicPlayer.Core; using Tizen.NUI; using Tizen.Multimedia; +using System.Threading.Tasks; namespace MusicPlayer.ViewModels { class PlayerViewModel : PropertyNotifier { private const int PlaybackTimerDuration = 1000; + private const int MinPaletteColorCount = 2; internal PlayerModel playerModel; internal LyricsViewModel lyricsViewModel; @@ -74,6 +76,14 @@ namespace MusicPlayer.ViewModels set => SetProperty(ref hasNextTrack, value); } + private PropertyMap playerBackground; + + public PropertyMap PlayerBackground + { + get => playerBackground; + set => SetProperty(ref playerBackground, value); + } + public void SetPlayingList(ListViewModel trackListVM) { playingListViewModel.SetTrackListViewModel(trackListVM); @@ -95,6 +105,9 @@ namespace MusicPlayer.ViewModels HasPreviousTrack = playingListViewModel.HasPrev(); HasNextTrack = playingListViewModel.HasNext(); + // SetBackground + SetExtractedBackground(track.ThumbnailPath); + PlayerController.Instance.Uri = track.FilePath; StartPlayback(); } @@ -175,7 +188,7 @@ namespace MusicPlayer.ViewModels public void UpdatePlayerPosition(float value) { - int currentPosition = (int)(playerModel.CurrentTrack.Duration * value); + int currentPosition = (int)(playerModel.CurrentTrack.DurationInMS * value); PlayerController.Instance.SetPosition(currentPosition); playerModel.PlayingTime = TimeSpan.FromMilliseconds(currentPosition).ToString(AppConstants.TimeFormat); playbackTimer.Start(); @@ -188,7 +201,7 @@ namespace MusicPlayer.ViewModels public void SetElapsedTime(float value) { - int currentPosition = (int)(playerModel.CurrentTrack.Duration * value); + int currentPosition = (int)(playerModel.CurrentTrack.DurationInMS * value); playerModel.PlayingTime = TimeSpan.FromMilliseconds(currentPosition).ToString(AppConstants.TimeFormat); } @@ -239,7 +252,7 @@ namespace MusicPlayer.ViewModels private void UpdatePlayingTime() { int position = PlayerController.Instance.GetPosition(); - playerModel.ElapsedTime = position / (float)playerModel.CurrentTrack.Duration; + playerModel.ElapsedTime = position / (float)playerModel.CurrentTrack.DurationInMS; playerModel.PlayingTime = TimeSpan.FromMilliseconds(position).ToString(AppConstants.TimeFormat); } @@ -283,5 +296,97 @@ namespace MusicPlayer.ViewModels VolumeLevel = e.Level; } } + + private ImageVisual CreateImageVisual() + { + ImageVisual visual = new ImageVisual() + { + URL = Resources.GetImagePath() + "gradient_music_light.png", + }; + return visual; + } + + private GradientVisual CreateGradientVisual(PropertyArray stopColor) + { + GradientVisual gradientVisual = new GradientVisual() + { + StartPosition = new Vector2(0.0f, -1.0f), + EndPosition = new Vector2(0.0f, 1.0f), + StopColor = stopColor, + SpreadMethod = GradientVisualSpreadMethodType.Pad, + }; + return gradientVisual; + } + + private PropertyArray GetGradientStopColors(Palette palette) + { + PropertyArray propertyArray = new PropertyArray(); + if(palette == null) + { + Tizen.Log.Error(AppConstants.LogTag, "Color palatte from background is null"); + return propertyArray; + } + + Palette.Swatch lightMutedSwatch = palette.GetLightMutedSwatch(); + Palette.Swatch darkMutedSwatch = palette.GetDarkMutedSwatch(); + if(lightMutedSwatch != null && darkMutedSwatch != null) + { + propertyArray.PushBack(new PropertyValue(lightMutedSwatch.GetRgb())); + propertyArray.PushBack(new PropertyValue(darkMutedSwatch.GetRgb())); + return propertyArray; + } + + Palette.Swatch lightVibrantSwatch = palette.GetLightVibrantSwatch(); + Palette.Swatch darkVibrantSwatch = palette.GetDarkVibrantSwatch(); + if(lightVibrantSwatch != null && darkVibrantSwatch != null) + { + propertyArray.PushBack(new PropertyValue(lightVibrantSwatch.GetRgb())); + propertyArray.PushBack(new PropertyValue(darkVibrantSwatch.GetRgb())); + return propertyArray; + } + + Palette.Swatch mutedSwatch = palette.GetMutedSwatch(); + Palette.Swatch vibrantSwatch = palette.GetVibrantSwatch(); + if(mutedSwatch != null && vibrantSwatch != null) + { + propertyArray.PushBack(new PropertyValue(mutedSwatch.GetRgb())); + propertyArray.PushBack(new PropertyValue(vibrantSwatch.GetRgb())); + return propertyArray; + } + + IReadOnlyCollection swatches = palette.GetSwatches(); + foreach(Palette.Swatch swatch in swatches) + { + if(propertyArray.Count() >= MinPaletteColorCount) + { + return propertyArray; + } + if(swatch != null) + { + propertyArray.PushBack(new PropertyValue(swatch.GetRgb())); + } + } + return propertyArray; + } + + private async void SetExtractedBackground(string path) + { + Tizen.Log.Debug(AppConstants.LogTag, "Path for the color image thumbnail" + path); + Palette palette = await ColorExtractor.GetPaletteAsync(path); + PropertyArray stopColor = GetGradientStopColors(palette); + if (stopColor.Count() < MinPaletteColorCount) + { + Tizen.Log.Info(AppConstants.LogTag, "Palette or palatte values not valid, adding default gradient"); + ImageVisual imageVisual = CreateImageVisual(); + PlayerBackground = imageVisual.OutputVisualMap; + return; + } + else + { + Tizen.Log.Info(AppConstants.LogTag, "setting palette color"); + GradientVisual gradientVisual = CreateGradientVisual(stopColor); + PlayerBackground = gradientVisual.OutputVisualMap; + } + } } } diff --git a/ViewModels/PlayingListViewModel.cs b/ViewModels/PlayingListViewModel.cs index 84c0c5f..6358715 100755 --- a/ViewModels/PlayingListViewModel.cs +++ b/ViewModels/PlayingListViewModel.cs @@ -72,7 +72,7 @@ namespace MusicPlayer.ViewModels // Need to check for same VM and in case same VM just update the playing track Tizen.Log.Info(AppConstants.LogTag, "Setting Current Playing list"); tracklistViewModel = trackListVM; - currentIndex = 0; + currentIndex = -1; RepeatMode = RepeatMode.Off; ShuffleMode = ShuffleMode.Off; OnItemsSourceChanged(new EventArgs()); @@ -134,7 +134,7 @@ namespace MusicPlayer.ViewModels } } - public string Next() + public Track Next() { if(currentIndex < 0 ) { @@ -143,7 +143,7 @@ namespace MusicPlayer.ViewModels } if(RepeatMode == RepeatMode.RepeatOne) { - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + return tracklistViewModel[shuffleList[currentIndex]]; } else if(RepeatMode == RepeatMode.Off) { @@ -154,7 +154,7 @@ namespace MusicPlayer.ViewModels else { currentIndex += 1; - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + return tracklistViewModel[shuffleList[currentIndex]]; } } else @@ -162,17 +162,17 @@ namespace MusicPlayer.ViewModels if (currentIndex + 1 >= shuffleList.Count) { currentIndex = 0; - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + return tracklistViewModel[shuffleList[currentIndex]]; } else { currentIndex += 1; - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + return tracklistViewModel[shuffleList[currentIndex]]; } } } - public string Prev() + public Track Prev() { if (currentIndex < 0) { @@ -181,7 +181,7 @@ namespace MusicPlayer.ViewModels } if (RepeatMode == RepeatMode.RepeatOne) { - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + return tracklistViewModel[shuffleList[currentIndex]]; } else if (RepeatMode == RepeatMode.Off) { @@ -192,7 +192,7 @@ namespace MusicPlayer.ViewModels else { currentIndex -= 1; - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + return tracklistViewModel[shuffleList[currentIndex]]; } } else @@ -200,20 +200,22 @@ namespace MusicPlayer.ViewModels if (currentIndex - 1 < 0) { currentIndex = shuffleList.Count-1; - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + return tracklistViewModel[shuffleList[currentIndex]]; } else { currentIndex -= 1; - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + return tracklistViewModel[shuffleList[currentIndex]]; } } } public bool HasNext() { - if (currentIndex < 0 && currentIndex > shuffleList.Count) + if (IsTrackIndexInvalid()) + { return false; + } if(RepeatMode == RepeatMode.RepeatOne || RepeatMode == RepeatMode.RepeatAll) { return true; @@ -226,8 +228,10 @@ namespace MusicPlayer.ViewModels public bool HasPrev() { - if (currentIndex < 0 && currentIndex > shuffleList.Count) + if (IsTrackIndexInvalid()) + { return false; + } if (RepeatMode == RepeatMode.RepeatOne || RepeatMode == RepeatMode.RepeatAll) { return true; @@ -238,20 +242,24 @@ namespace MusicPlayer.ViewModels } } - public string Current() + public Track Current() { - if (currentIndex < 0 && currentIndex > shuffleList.Count) + if (IsTrackIndexInvalid()) + { return null; + } else - return tracklistViewModel[shuffleList[currentIndex]].FilePath; + { + return tracklistViewModel[shuffleList[currentIndex]]; + } } public void SetPlayingTrack(string mediaId) { - int count = 0; - for(count = 0; count< tracklistViewModel.Count; ++count) + int count; + for (count = 0; count< tracklistViewModel.Count; ++count) { - if (mediaId == tracklistViewModel[0].Id) + if (mediaId == tracklistViewModel[count].Id) { currentIndex = count; return; @@ -259,5 +267,29 @@ namespace MusicPlayer.ViewModels } currentIndex = -1; } + + public void SetPlayingTrack(Track track) + { + int count; + for (count = 0; count < tracklistViewModel.Count; ++count) + { + if (track == tracklistViewModel[count]) + { + Tizen.Log.Debug(AppConstants.LogTag, "Current track index is " + count); + currentIndex = count; + return; + } + } + currentIndex = -1; + } + + private bool IsTrackIndexInvalid() + { + if(currentIndex < 0 || currentIndex > shuffleList.Count) + { + return true; + } + return false; + } } } diff --git a/Views/LyricsView.cs b/Views/LyricsView.cs index 234fe9a..f36473a 100644 --- a/Views/LyricsView.cs +++ b/Views/LyricsView.cs @@ -47,19 +47,22 @@ namespace MusicPlayer.Views Position2D = new Position2D(LyricsViewMargin, LyricsViewMargin), Size2D = new Size2D(LyricsViewSize, LyricsViewSize), ScrollingDirection = ScrollableBase.Direction.Vertical, + BackgroundColor = Color.Transparent, }; + scrollView.BindingContext = lyricsViewModel; + scrollView.SetBinding(View.BackgroundColorProperty, "LyricsBackgroundColor"); base.Add(scrollView); lyricsLabel = new TextLabel() { WidthResizePolicy = ResizePolicyType.FillToParent, - BackgroundColor = UIColors.LyricsBackground, TextColor = Color.White, MultiLine = true, LineWrapMode = LineWrapMode.Word, PointSize = 25.0f, HorizontalAlignment = HorizontalAlignment.Center, }; + lyricsLabel.BindingContext = lyricsViewModel.lyricsModel; lyricsLabel.SetBinding(TextLabel.TextProperty, "Lyrics"); scrollView.Add(lyricsLabel); } diff --git a/Views/PlayerView.cs b/Views/PlayerView.cs index 1aeb833..06ee773 100755 --- a/Views/PlayerView.cs +++ b/Views/PlayerView.cs @@ -1,18 +1,20 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Tizen.NUI.BaseComponents; -using Tizen.NUI; +using Tizen.NUI; +using Tizen.Multimedia; using Tizen.NUI.Components; -using MusicPlayer.Common; +using Tizen.NUI.BaseComponents; using Tizen.NUI.Binding; +using MusicPlayer.Common; using MusicPlayer.ViewModels; -using Tizen.Multimedia; namespace MusicPlayer.Views { class PlayerView : View { + private enum PlayerViewState + { + AlbumArt, + TrackList, + } private const int LayoutPadding = 64; private const int IconSize = 48; private const int TopBarSize = 120; @@ -23,12 +25,15 @@ namespace MusicPlayer.Views private const int ArtistLabelHeight = 36; private const int TopBarButtonsY = (TopBarSize / 2 - IconSize / 2); + private View playerBackgroundView; private View leftView; private View rightView; + private View rightViewBackground; private Button backButton; private Button moreButton; private View controlsView; + private View sliderView; private Button playButton; private Button prevButton; private Button nextButton; @@ -52,6 +57,8 @@ namespace MusicPlayer.Views private PlayerViewModel viewModel; + private PlayerViewState viewState; + public PlayerView(PlayerViewModel viewModel) : base() { this.viewModel = viewModel; @@ -60,8 +67,13 @@ namespace MusicPlayer.Views WidthResizePolicy = ResizePolicyType.FillToParent; HeightResizePolicy = ResizePolicyType.FillToParent; + AddPlayerBackground(); + + viewState = PlayerViewState.AlbumArt; + leftView = CreateLeftView(); rightView = CreateRightView(); + AddRightViewBackground(); AddTopButtons(); AddControlView(); @@ -69,10 +81,21 @@ namespace MusicPlayer.Views AddPlaybackSlider(); AddListActionButtons(); AddThumbnail(); - //AddPlayingListView(); AddLyricsView(); } + private void AddPlayerBackground() + { + playerBackgroundView = new View(); + playerBackgroundView.Size2D = new Size2D(Window.Instance.WindowSize.Width, Window.Instance.WindowSize.Height); + WidthResizePolicy = ResizePolicyType.FillToParent; + HeightResizePolicy = ResizePolicyType.FillToParent; + base.Add(playerBackgroundView); + playerBackgroundView.BackgroundColor = Color.Transparent; + playerBackgroundView.BindingContext = viewModel; + playerBackgroundView.SetBinding(BackgroundProperty, "PlayerBackground"); + } + private Button CreateButton(ImageViewStyle style, int x, int y) { ButtonStyle buttonStyle = new ButtonStyle() @@ -126,7 +149,7 @@ namespace MusicPlayer.Views { View rightView = new View() { - BackgroundColor = Color.Green, + BackgroundColor = Color.Transparent, HeightResizePolicy = ResizePolicyType.FillToParent, SizeWidth = Window.Instance.WindowSize.Width / 2, Position2D = new Position2D(Window.Instance.WindowSize.Width / 2, 0), @@ -135,6 +158,34 @@ namespace MusicPlayer.Views return rightView; } + private void AddRightViewBackground() + { + rightViewBackground = new View(); + rightViewBackground.BackgroundColor = Color.Transparent; + rightViewBackground.SizeWidth = Window.Instance.WindowSize.Width / 2; + rightViewBackground.SizeHeight = Window.Instance.WindowSize.Height; + rightViewBackground.WidthResizePolicy = ResizePolicyType.FillToParent; + rightViewBackground.HeightResizePolicy = ResizePolicyType.FillToParent; + rightView.Add(rightViewBackground); + rightViewBackground.BindingContext = viewModel; + rightViewBackground.SetBinding(BackgroundProperty, "PlayerBackground"); + rightViewBackground.Hide(); + } + + private void UpdatePlayerViewBackground(PlayerViewState state) + { + if (state == PlayerViewState.AlbumArt) + { + rightViewBackground.Hide(); + playerBackgroundView.Show(); + } + else + { + playerBackgroundView.Hide(); + rightViewBackground.Show(); + } + } + private void AddTopButtons() { backButton = CreateButton(Resources.GetImagePath() + "back_button.png", LayoutPadding, TopBarButtonsY); @@ -479,7 +530,7 @@ namespace MusicPlayer.Views private void AddPlaybackSlider() { - View sliderView = new View() + sliderView = new View() { Size2D = new Size2D(1792, 80), Position2D = new Position2D(64, 950), @@ -490,6 +541,29 @@ namespace MusicPlayer.Views AddPlaybackSlider(sliderView); AddCurrentTimeLabel(sliderView); AddTotalTimeLabel(sliderView); + UpdatePlaybackSliderPosition(viewState); + } + + private void UpdatePlaybackSliderPosition(PlayerViewState state) + { + if(state == PlayerViewState.AlbumArt) + { + sliderView.Size2D = new Size2D(1792, 80); + sliderView.Position2D = new Position2D(64, 950); + currentTime.Size2D = new Size2D(400, 32); + currentTime.Position2D = new Position2D(0, 48); + totalTime.Size2D = new Size2D(400, 32); + totalTime.Position2D = new Position2D(1392, 48); + } + else + { + sliderView.Size2D = new Size2D(640, 80); + sliderView.Position2D = new Position2D(1056, 950); + currentTime.Size2D = new Size2D(180, 32); + currentTime.Position2D = new Position2D(0, 48); + totalTime.Size2D = new Size2D(180, 32); + totalTime.Position2D = new Position2D(460, 48); + } } private void AddListActionButtons() @@ -503,6 +577,27 @@ namespace MusicPlayer.Views rightView.Add(actionButtonView); listButton = CreateButton(Resources.GetImagePath() + "playing_queue.png", 0, 0); + listButton.Clicked += (object sender, ClickedEventArgs e) => + { + if(viewState == PlayerViewState.AlbumArt) + { + Tizen.Log.Debug(AppConstants.LogTag, "Adding Playing list view"); + viewState = PlayerViewState.TrackList; + RemoveLyricsView(); + AddPlayingListView(); + thumb.Show(); + } + else + { + Tizen.Log.Debug(AppConstants.LogTag, "Adding album art view"); + viewState = PlayerViewState.AlbumArt; + RemovePlayingListView(); + AddLyricsView(); + thumb.Hide(); + } + UpdatePlaybackSliderPosition(viewState); + UpdatePlayerViewBackground(viewState); + }; actionButtonView.Add(listButton); playlistButton = CreateButton(Resources.GetImagePath() + "addtoplaylist.png", 88, 0); @@ -522,6 +617,7 @@ namespace MusicPlayer.Views }; thumb.SetBinding(ImageView.ResourceUrlProperty, "ThumbnailPath"); rightView.Add(thumb); + thumb.Hide(); } private void AddPlayingListView() @@ -532,6 +628,18 @@ namespace MusicPlayer.Views currentListView.Size2D = new Size2D(832, 900); currentListView.Position2D = new Position2D(64, 108); leftView.Add(currentListView); + currentListView.Show(); + } + + private void RemovePlayingListView() + { + if(currentListView == null) + { + Tizen.Log.Error(AppConstants.LogTag, "current listview is null, can't remove it"); + return; + } + leftView.Remove(currentListView); + currentListView.Hide(); } private void AddLyricsView() @@ -542,6 +650,18 @@ namespace MusicPlayer.Views lyricsView.Size2D = new Size2D(784, 784); lyricsView.Position2D = new Position2D(88, 120); leftView.Add(lyricsView); + lyricsView.Show(); + } + + private void RemoveLyricsView() + { + if(lyricsView == null) + { + Tizen.Log.Error(AppConstants.LogTag, "lyricsview is null, can't remove it"); + return; + } + leftView.Remove(lyricsView); + lyricsView.Hide(); } } }