From: aman.jeph Date: Fri, 25 Jun 2021 06:15:25 +0000 (+0530) Subject: Updating player view model and common X-Git-Tag: submit/tizen/20210915.141722~9 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=c3773e3b90e2eb171b471ae6619324dacce4fc1e;p=profile%2Fiot%2Fapps%2Fdotnet%2Fmusic-player.git Updating player view model and common Change-Id: Ic25be459461fdce9fe2c8007ceb6858b0ebb6af0 Signed-off-by: aman.jeph --- diff --git a/Common/AppConstants.cs b/Common/AppConstants.cs index 98cfef0..3dfb522 100755 --- a/Common/AppConstants.cs +++ b/Common/AppConstants.cs @@ -7,5 +7,8 @@ // KeyCodes public const string BackKeyCode = "XF86Back"; public const string EscapeKeyCode = "Escape"; + + // string literals + public const string TimeFormat = @"mm\:ss"; } } diff --git a/Common/PlayerCommon.cs b/Common/PlayerCommon.cs index a2703c7..2d51a56 100755 --- a/Common/PlayerCommon.cs +++ b/Common/PlayerCommon.cs @@ -24,4 +24,31 @@ namespace MusicPlayer.Common On, Off, } + + public enum EventType + { + Error, + PlaybackCompleted, + } + + public class PlayerEventArgs : EventArgs + { + private readonly EventType type; + private readonly string description; + + internal PlayerEventArgs(EventType eventType, string desc) + { + type = eventType; + description = desc; + } + + public EventType Type + { + get => type; + } + public string Description + { + get => description; + } + } } diff --git a/Models/PlayerModel.cs b/Models/PlayerModel.cs index 1d37e93..383f42e 100755 --- a/Models/PlayerModel.cs +++ b/Models/PlayerModel.cs @@ -1,70 +1,85 @@ using MusicPlayer.Common; +using System; namespace MusicPlayer.Models { class PlayerModel : PropertyNotifier { - private Track currentTrack; - private string trackName; - private string trackArtist; - private int trackLength; - private int trackPlayingTime; //TODO need to use with mmplayer - private string thumbnailPath; - private PlayingStatus playingStatus; - public PlayerModel() { } - public PlayerModel(Track track) - { - CurrentTrack = track; - } + + private Track currentTrack; public Track CurrentTrack { + get => currentTrack; set { currentTrack = value; TrackName = currentTrack.TrackTitle; TrackArtist = currentTrack.ArtistName; - TrackLength = currentTrack.Duration; + TrackLength = TimeSpan.FromMilliseconds(currentTrack.Duration).ToString(AppConstants.TimeFormat); + PlayingTime = TimeSpan.FromMilliseconds(0).ToString(AppConstants.TimeFormat); ThumbnailPath = currentTrack.ThumbnailPath; PlayingStatus = PlayingStatus.None; } - get - { - return currentTrack; - } } + private string trackName; + public string TrackName { get => trackName; set => SetProperty(ref trackName, value); } + private string trackArtist; + public string TrackArtist { get => trackArtist; set => SetProperty(ref trackArtist, value); } - public int TrackLength + private string trackLength; + + public string TrackLength { get => trackLength; set => SetProperty(ref trackLength, value); } + private string thumbnailPath; + public string ThumbnailPath { get => thumbnailPath; set => SetProperty(ref thumbnailPath, value); } + private PlayingStatus playingStatus; + public PlayingStatus PlayingStatus { get => playingStatus; set { playingStatus = value; } } + + private string playingTime; + + public string PlayingTime + { + get => playingTime; + set => SetProperty(ref playingTime, value); + } + + private float elapsedTime; + + public float ElapsedTime + { + get => elapsedTime; + set => SetProperty(ref elapsedTime, value); + } } } diff --git a/ViewModels/PlayerViewModel.cs b/ViewModels/PlayerViewModel.cs index 5c2b290..1702584 100755 --- a/ViewModels/PlayerViewModel.cs +++ b/ViewModels/PlayerViewModel.cs @@ -3,38 +3,124 @@ using System.Collections.Generic; using System.Text; using MusicPlayer.Models; using MusicPlayer.Common; +using MusicPlayer.Core; +using Tizen.NUI; +using Tizen.Multimedia; namespace MusicPlayer.ViewModels { class PlayerViewModel : PropertyNotifier { + private const int PlaybackTimerDuration = 1000; + internal PlayerModel playerModel; - internal PlayingListViewModel playingListViewModel; internal LyricsViewModel lyricsViewModel; - private string playPauseUrl; + private Timer playbackTimer; + private bool isVolumeChanging; public PlayerViewModel() { lyricsViewModel = new LyricsViewModel(); playingListViewModel = new PlayingListViewModel(); playerModel = new PlayerModel(); + playerModel.ElapsedTime = 0.0f; PlayPauseUrl = Resources.GetImagePath() + "play.png"; + PlayerController.Instance.PlayerEventOccurred += OnPlayerEventOccurred; + playbackTimer = new Timer(PlaybackTimerDuration); + playbackTimer.Tick += OnPlaybackTimerTick; + isVolumeChanging = false; + VolumeLevel = AudioManager.VolumeController.Level[AudioVolumeType.Media]; + AudioManager.VolumeController.Changed += OnVolumeLevelChanged; + HasPreviousTrack = playingListViewModel.HasPrev(); + HasNextTrack = playingListViewModel.HasNext(); } + internal PlayingListViewModel playingListViewModel; public PlayingListViewModel CurrentPlayingListViewModel { get => playingListViewModel; } + private string playPauseUrl; + public string PlayPauseUrl + { + get => playPauseUrl; + set => SetProperty(ref playPauseUrl, value); + } + + private float volumeLevel; + + public float VolumeLevel + { + get => volumeLevel; + set => SetProperty(ref volumeLevel, value); + } + + private bool hasPreviousTrack; + + public bool HasPreviousTrack + { + get => hasPreviousTrack; + set => SetProperty(ref hasPreviousTrack, value); + } + + private bool hasNextTrack; + + public bool HasNextTrack + { + get => hasNextTrack; + set => SetProperty(ref hasNextTrack, value); + } + + public void SetPlayingList(TrackListViewModel trackListVM) + { + playingListViewModel.SetTrackListViewModel(trackListVM); + } + public void SetCurrentTrack(Track track) { + Tizen.Log.Info(AppConstants.LogTag, "Setting Current track"); playerModel.CurrentTrack = track; lyricsViewModel.CurrentTrack = track; + //TO DO need to set index properly + Track current = playingListViewModel.Current(); + if (current == null) + { + Tizen.Log.Error(AppConstants.LogTag, "Current track is null so setting new track"); + playingListViewModel.SetPlayingTrack(track); + } + // updating prev/next button state + HasPreviousTrack = playingListViewModel.HasPrev(); + HasNextTrack = playingListViewModel.HasNext(); + + PlayerController.Instance.Uri = track.FilePath; + StartPlayback(); } - public void SetPlayingList(TrackListViewModel trackListVM) + public void NextButtonClicked() { - playingListViewModel.SetTrackListViewModel(trackListVM); + Track nextTrack = playingListViewModel.Next(); + if (nextTrack == null) + { + Tizen.Log.Error(AppConstants.LogTag, "nextTrack is null"); + StopPlayback(); + return; + } + Tizen.Log.Debug(AppConstants.LogTag, "Next track is: " + nextTrack.TrackTitle); + SetCurrentTrack(nextTrack); + } + + public void PrevButtonClicked() + { + Track previousTrack = playingListViewModel.Prev(); + if (previousTrack == null) + { + Tizen.Log.Error(AppConstants.LogTag, "previousTrack is null"); + StopPlayback(); + return; + } + Tizen.Log.Debug(AppConstants.LogTag, "Prev track is: " + previousTrack.TrackTitle); + SetCurrentTrack(previousTrack); } public void ShuffleChanged() @@ -53,9 +139,10 @@ namespace MusicPlayer.ViewModels break; i++; } - RepeatMode new_mode = RepeatMode.Off; - RepeatMode [] repeatArr = (RepeatMode [])values; - if(i+1 >= values.Length) + + RepeatMode[] repeatArr = (RepeatMode[])values; + RepeatMode new_mode; + if (i + 1 >= values.Length) { new_mode = repeatArr[0]; } @@ -66,23 +153,132 @@ namespace MusicPlayer.ViewModels playingListViewModel.RepeatMode = new_mode; } - public string PlayPauseUrl - { - get => playPauseUrl; - set => SetProperty(ref playPauseUrl, value); - } - public void PlayingStatusChanged() { - if(playerModel.PlayingStatus == PlayingStatus.Playing) + if (playerModel.PlayingStatus == PlayingStatus.Playing) { playerModel.PlayingStatus = PlayingStatus.Paused; PlayPauseUrl = Resources.GetImagePath() + "play.png"; + Pause(); + playbackTimer.Stop(); } else { playerModel.PlayingStatus = PlayingStatus.Playing; PlayPauseUrl = Resources.GetImagePath() + "pause.png"; + Resume(); + playbackTimer.Start(); + } + } + + public void UpdatePlayerPosition(float value) + { + int currentPosition = (int)(playerModel.CurrentTrack.Duration * value); + PlayerController.Instance.SetPosition(currentPosition); + playerModel.PlayingTime = TimeSpan.FromMilliseconds(currentPosition).ToString(AppConstants.TimeFormat); + playbackTimer.Start(); + } + + public void StopPlaybackTimer() + { + playbackTimer.Stop(); + } + + public void SetElapsedTime(float value) + { + int currentPosition = (int)(playerModel.CurrentTrack.Duration * value); + playerModel.PlayingTime = TimeSpan.FromMilliseconds(currentPosition).ToString(AppConstants.TimeFormat); + } + + public void VolumeChangeStarted() + { + isVolumeChanging = true; + } + + public void VolumeChangeFinished(int value) + { + VolumeLevelChanged(value); + isVolumeChanging = false; + } + public void VolumeLevelChanged(int value) + { + AudioManager.VolumeController.Level[AudioVolumeType.Media] = value; + } + + private bool OnPlaybackTimerTick(object source, Timer.TickEventArgs e) + { + UpdatePlayingTime(); + return true; + } + + private void OnPlayerEventOccurred(object sender, PlayerEventArgs e) + { + Tizen.Log.Error(AppConstants.LogTag, "Player Event Occurred:"); + Tizen.Log.Error(AppConstants.LogTag, "\t"+e.Type.ToString()); + Tizen.Log.Error(AppConstants.LogTag, "\t:"+e.Description); + if(e.Type == EventType.PlaybackCompleted) + { + PlayNext(); + } + } + + private void PlayNext() + { + if(playingListViewModel.RepeatMode == RepeatMode.Off) + { + Tizen.Log.Debug(AppConstants.LogTag, "Repeat is off, can't play next"); + StopPlayback(); + UpdatePlayingTime(); + return; + } + NextButtonClicked(); + } + + private void UpdatePlayingTime() + { + int position = PlayerController.Instance.GetPosition(); + playerModel.ElapsedTime = position / (float)playerModel.CurrentTrack.Duration; + playerModel.PlayingTime = TimeSpan.FromMilliseconds(position).ToString(AppConstants.TimeFormat); + } + + private void StartPlayback() + { + playerModel.PlayingStatus = PlayingStatus.Playing; + PlayPauseUrl = Resources.GetImagePath() + "pause.png"; + Play(); + playbackTimer.Start(); + } + + private void StopPlayback() + { + playerModel.PlayingStatus = PlayingStatus.Stopped; + PlayPauseUrl = Resources.GetImagePath() + "play.png"; + Stop(); + playbackTimer.Stop(); + } + + private void Play() + { + PlayerController.Instance.Play(); + } + private void Pause() + { + PlayerController.Instance.Pause(); + } + private void Resume() + { + PlayerController.Instance.Resume(); + } + private void Stop() + { + PlayerController.Instance.Stop(); + } + + private void OnVolumeLevelChanged(object sender, VolumeChangedEventArgs e) + { + if(e.Type == AudioVolumeType.Media && isVolumeChanging == false) + { + VolumeLevel = e.Level; } } } diff --git a/Views/PlayerView.cs b/Views/PlayerView.cs index d975dd6..1aeb833 100755 --- a/Views/PlayerView.cs +++ b/Views/PlayerView.cs @@ -7,6 +7,7 @@ using Tizen.NUI.Components; using MusicPlayer.Common; using Tizen.NUI.Binding; using MusicPlayer.ViewModels; +using Tizen.Multimedia; namespace MusicPlayer.Views { @@ -20,6 +21,7 @@ namespace MusicPlayer.Views private const int ControlViewMargin = 315; private const int TitleLabelHeight = 48; private const int ArtistLabelHeight = 36; + private const int TopBarButtonsY = (TopBarSize / 2 - IconSize / 2); private View leftView; private View rightView; @@ -71,7 +73,23 @@ namespace MusicPlayer.Views AddLyricsView(); } - private Button CreatButton(string url, int x, int y) + private Button CreateButton(ImageViewStyle style, int x, int y) + { + ButtonStyle buttonStyle = new ButtonStyle() + { + Icon = style, + IsSelectable = false, + IsEnabled = true, + }; + Button button = new Button(buttonStyle) + { + Size2D = new Size2D(IconSize, IconSize), + Position2D = new Position2D(x, y), + }; + return button; + } + + private Button CreateButton(string url, int x, int y) { ButtonStyle buttonStyle = new ButtonStyle() { @@ -119,10 +137,10 @@ namespace MusicPlayer.Views private void AddTopButtons() { - backButton = CreatButton(Resources.GetImagePath() + "back_button.png", LayoutPadding, (TopBarSize / 2 - IconSize / 2)); + backButton = CreateButton(Resources.GetImagePath() + "back_button.png", LayoutPadding, TopBarButtonsY); leftView.Add(backButton); - moreButton = CreatButton(Resources.GetImagePath() + "more_button.png", Window.Instance.WindowSize.Width / 2 - LayoutPadding - IconSize, (TopBarSize / 2 - IconSize / 2)); + moreButton = CreateButton(Resources.GetImagePath() + "more_button.png", Window.Instance.WindowSize.Width / 2 - LayoutPadding - IconSize, TopBarButtonsY); rightView.Add(moreButton); } @@ -137,7 +155,7 @@ namespace MusicPlayer.Views rightView.Add(controlsView); } - private void AddTextControlElements() + private void AddTitleLabel() { titleLabel = new TextLabel() { @@ -152,7 +170,10 @@ namespace MusicPlayer.Views }; titleLabel.SetBinding(TextLabel.TextProperty, "TrackName"); controlsView.Add(titleLabel); + } + private void AddArtistLabel() + { artistLabel = new TextLabel() { Size2D = new Size2D(ControlViewWidth, ArtistLabelHeight), @@ -168,43 +189,104 @@ namespace MusicPlayer.Views controlsView.Add(artistLabel); } - private void AddButtonControlElements() + private void AddTextControlElements() { - shuffleButton = CreatButton(Resources.GetImagePath() + "shuffle.png", 0, 196); + AddTitleLabel(); + AddArtistLabel(); + } + + private void AddShuffleButton() + { + shuffleButton = CreateButton(Resources.GetImagePath() + "shuffle.png", 0, 196); shuffleButton.Icon.BindingContext = viewModel.playingListViewModel; shuffleButton.Icon.SetBinding(ImageView.ResourceUrlProperty, "ShuffleUrl"); + // TODO need to implement command instead shuffleButton.Clicked += (object sender, ClickedEventArgs e) => { viewModel.ShuffleChanged(); }; controlsView.Add(shuffleButton); + } - prevButton = CreatButton(Resources.GetImagePath() + "prev.png", 168, 196); + private void AddPreviousButton() + { + ImageViewStyle prevIconStyle = new ImageViewStyle() + { + ResourceUrl = new Selector + { + Normal = Resources.GetImagePath() + "prev.png", + Disabled = Resources.GetImagePath() + "prev_disabled.png" + }, + }; + prevButton = CreateButton(prevIconStyle, 168, 196); + // TODO need to implement command instead + prevButton.Clicked += (object sender, ClickedEventArgs e) => + { + viewModel.PrevButtonClicked(); + }; + prevButton.BindingContext = viewModel; + prevButton.SetBinding(Button.IsEnabledProperty, "HasPreviousTrack"); controlsView.Add(prevButton); + } - playButton = CreatButton(Resources.GetImagePath() + "play.png", 296, 196); + private void AddPlayButton() + { + playButton = CreateButton(Resources.GetImagePath() + "play.png", 296, 196); playButton.Icon.BindingContext = viewModel; playButton.Icon.SetBinding(ImageView.ResourceUrlProperty, "PlayPauseUrl"); controlsView.Add(playButton); + // TODO need to implement command instead playButton.Clicked += (object sender, ClickedEventArgs e) => { viewModel.PlayingStatusChanged(); }; + } + + private void AddNextButton() + { + ImageViewStyle nextIconStyle = new ImageViewStyle() + { + ResourceUrl = new Selector + { + Normal = Resources.GetImagePath() + "next.png", + Disabled = Resources.GetImagePath() + "next_disabled.png", + }, + }; - nextButton = CreatButton(Resources.GetImagePath() + "next.png", 424, 196); + nextButton = CreateButton(nextIconStyle, 424, 196); + // TODO need to implement command instead + nextButton.Clicked += (object sender, ClickedEventArgs e) => + { + viewModel.NextButtonClicked(); + }; + nextButton.BindingContext = viewModel; + nextButton.SetBinding(Button.IsEnabledProperty, "HasNextTrack"); controlsView.Add(nextButton); + } - repeatButton = CreatButton(Resources.GetImagePath() + "repeat.png", 592, 196); + private void AddRepeatButton() + { + repeatButton = CreateButton(Resources.GetImagePath() + "repeat.png", 592, 196); repeatButton.Icon.BindingContext = viewModel.playingListViewModel; repeatButton.Icon.SetBinding(ImageView.ResourceUrlProperty, "RepeatUrl"); controlsView.Add(repeatButton); + // TODO need to implement command instead repeatButton.Clicked += (object sender, ClickedEventArgs e) => { viewModel.RepeatChanged(); }; } - private void AddVolumeSliderElements() + private void AddButtonControlElements() + { + AddShuffleButton(); + AddPreviousButton(); + AddPlayButton(); + AddNextButton(); + AddRepeatButton(); + } + + private void AddLeftVolumeIcon() { leftVolumeIcon = new ImageView(Resources.GetImagePath() + "left_sound_icon.png") { @@ -212,14 +294,20 @@ namespace MusicPlayer.Views Position2D = new Position2D(0, 336), }; controlsView.Add(leftVolumeIcon); + } + private void AddRightVolumeIcon() + { rightVolumeIcon = new ImageView(Resources.GetImagePath() + "right_sound_icon.png") { Size2D = new Size2D(IconSize, IconSize), Position2D = new Position2D(592, 336), }; controlsView.Add(rightVolumeIcon); + } + private SliderStyle CreateVolumeSliderStyle() + { SliderStyle volumeSliderStyle = new SliderStyle() { IndicatorType = Slider.IndicatorType.Image, @@ -239,8 +327,28 @@ namespace MusicPlayer.Views }, }, }; + return volumeSliderStyle; + } - volumeSlider = new Slider(volumeSliderStyle); + private void AddVolumeSliderEventHandlers() + { + volumeSlider.SlidingStarted += (object sender, SliderSlidingStartedEventArgs e) => + { + viewModel.VolumeChangeStarted(); + }; + volumeSlider.ValueChanged += (object sender, SliderValueChangedEventArgs e) => + { + viewModel.VolumeLevelChanged((int)e.CurrentValue); + }; + volumeSlider.SlidingFinished += (object sender, SliderSlidingFinishedEventArgs e) => + { + viewModel.VolumeChangeFinished((int)e.CurrentValue); + }; + } + + private void AddVolumeSlider() + { + volumeSlider = new Slider(CreateVolumeSliderStyle()); volumeSlider.Indicator = Slider.IndicatorType.Text; volumeSlider.TrackThickness = 4; volumeSlider.ThumbImageURLSelector = new StringSelector @@ -250,12 +358,22 @@ namespace MusicPlayer.Views }; volumeSlider.Size2D = new Size2D(496, 48); volumeSlider.Position2D = new Position2D(72, 336); - volumeSlider.ThumbSize = new Size(36, 36); + volumeSlider.ThumbSize = new Tizen.NUI.Size(36, 36); volumeSlider.Direction = Slider.DirectionType.Horizontal; volumeSlider.MinValue = 0; - volumeSlider.MaxValue = 100; - volumeSlider.CurrentValue = 10; + volumeSlider.MaxValue = AudioManager.VolumeController.MaxLevel[AudioVolumeType.Media]; + volumeSlider.CurrentValue = AudioManager.VolumeController.Level[AudioVolumeType.Media]; controlsView.Add(volumeSlider); + volumeSlider.BindingContext = viewModel; + volumeSlider.SetBinding(Slider.CurrentValueProperty, "VolumeLevel"); + AddVolumeSliderEventHandlers(); + } + + private void AddVolumeSliderElements() + { + AddLeftVolumeIcon(); + AddRightVolumeIcon(); + AddVolumeSlider(); } private void AddControlElements() @@ -265,14 +383,8 @@ namespace MusicPlayer.Views AddVolumeSliderElements(); } - private void AddPlaybackSlider() + private SliderStyle CreatePlaybackSliderStyle() { - View sliderView = new View() - { - Size2D = new Size2D(1792, 80), - Position2D = new Position2D(64, 950), - BackgroundColor = Color.Transparent, - }; SliderStyle playbackSliderStyle = new SliderStyle() { IndicatorType = Slider.IndicatorType.Image, @@ -292,7 +404,28 @@ namespace MusicPlayer.Views }, }, }; - playbackSlider = new Slider(playbackSliderStyle) + return playbackSliderStyle; + } + + private void AddPlaybackSliderEventHandler() + { + playbackSlider.SlidingStarted += (object sender, SliderSlidingStartedEventArgs e) => + { + viewModel.StopPlaybackTimer(); + }; + playbackSlider.ValueChanged += (object sender, SliderValueChangedEventArgs e) => + { + viewModel.SetElapsedTime(e.CurrentValue); + }; + playbackSlider.SlidingFinished += (object sender, SliderSlidingFinishedEventArgs e) => + { + viewModel.UpdatePlayerPosition(e.CurrentValue); + }; + } + + private void AddPlaybackSlider(View sliderView) + { + playbackSlider = new Slider(CreatePlaybackSliderStyle()) { ThumbImageURLSelector = new StringSelector { @@ -303,10 +436,17 @@ namespace MusicPlayer.Views MaxValue = 1.0f, WidthResizePolicy = ResizePolicyType.FillToParent, SizeHeight = 44, - ThumbSize = new Size(36, 36), + ThumbSize = new Tizen.NUI.Size(36, 36), Direction = Slider.DirectionType.Horizontal, + IsCreateByXaml = true, }; + playbackSlider.SetBinding(Slider.CurrentValueProperty, "ElapsedTime"); sliderView.Add(playbackSlider); + AddPlaybackSliderEventHandler(); + } + + private void AddCurrentTimeLabel(View sliderView) + { currentTime = new TextLabel() { Size2D = new Size2D(400, 32), @@ -317,19 +457,39 @@ namespace MusicPlayer.Views Text = "00::00:00", HorizontalAlignment = HorizontalAlignment.Begin, }; + currentTime.SetBinding(TextLabel.TextProperty, "PlayingTime"); sliderView.Add(currentTime); + } + + private void AddTotalTimeLabel(View sliderView) + { totalTime = new TextLabel() { Size2D = new Size2D(400, 32), - Position2D = new Position2D(1792-400, 48), + Position2D = new Position2D(1792 - 400, 48), TextColor = UIColors.HEX001447, PixelSize = 24, FontFamily = "BreezeSans", HorizontalAlignment = HorizontalAlignment.End, Text = "59:59:59", }; + totalTime.SetBinding(TextLabel.TextProperty, "TrackLength"); sliderView.Add(totalTime); - this.Add(sliderView); + } + + private void AddPlaybackSlider() + { + View sliderView = new View() + { + Size2D = new Size2D(1792, 80), + Position2D = new Position2D(64, 950), + BackgroundColor = Color.Transparent, + }; + Add(sliderView); + + AddPlaybackSlider(sliderView); + AddCurrentTimeLabel(sliderView); + AddTotalTimeLabel(sliderView); } private void AddListActionButtons() @@ -342,13 +502,13 @@ namespace MusicPlayer.Views }; rightView.Add(actionButtonView); - listButton = CreatButton(Resources.GetImagePath() + "playing_queue.png", 0, 0); + listButton = CreateButton(Resources.GetImagePath() + "playing_queue.png", 0, 0); actionButtonView.Add(listButton); - playlistButton = CreatButton(Resources.GetImagePath() + "addtoplaylist.png", 88, 0); + playlistButton = CreateButton(Resources.GetImagePath() + "addtoplaylist.png", 88, 0); actionButtonView.Add(playlistButton); - favouriteButton = CreatButton(Resources.GetImagePath() + "favourite_off.png", 176, 0); + favouriteButton = CreateButton(Resources.GetImagePath() + "favourite_off.png", 176, 0); actionButtonView.Add(favouriteButton); }