PulseMusic - music player design with Skia and LibVLC
In February 2015, Anish Chandran, a Microsoft UX and Visual designer, posted on Dribble and Behance an original music player UX concept.
In August 2018, Javier Suárez Ruiz implemented and published on GitHub a Xamarin.Forms (iOS/Android) UI implementation of the audio player view of the PulseMusic concept by Anish.
As mentioned in the readme:
The main objective of the sample is to show the creation of the Player’s UI (circular progress, rotating cover, etc.). This App is NOT a real player.
So… let’s turn it into a real player with LibVLCSharp.
This is an extract from the existing PlayerView.xaml:
<controls:CircleProgress
Grid.Row="0"
VerticalOptions="FillAndExpand"
HorizontalOptions="FillAndExpand"
Progress="{Binding Progress}"
LineBackgroundColor ="{StaticResource BlackColor}"
ProgressColor="{StaticResource PlayerRedColor}"
StrokeWidth="12"
Margin="12"/>
<buttonCircle:CircleButton
Command="{Binding PlayCommand}"
FontIcon="FontAwesome"
Icon="{Binding Icon}"
FontSize="{StaticResource FontSize16}"
TextColor="{StaticResource WhiteColor}"
HeightRequest="60"
WidthRequest="60"
BackgroundColor="{StaticResource PlayerRedColor}"
HorizontalOptions="Center"
VerticalOptions="Center" />
<Grid
Grid.Row="1"
Margin="70, -24, 70, 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Label
Grid.Column="0"
Text="{Binding StartTime, Converter={StaticResource TimeSpanToStringConverter}}"
Style="{StaticResource TimeTextStyle}"/>
<controls:ToggleButton
Grid.Column="1"
Checked="False"
Animate="False"
CheckedImage="playonce_on"
UnCheckedImage="playonce_off"/>
<controls:TapImage
Grid.Column="2"
Source="shuffle"/>
<Label
Grid.Column="3"
Text="{Binding RemainTime, Converter={StaticResource TimeSpanToStringConverter}}"
Style="{StaticResource TimeTextStyle}"/>
</Grid>
The Binding
XAML keyword indicates the value is databinded to the corresponding ViewModel. For the demo code, Javier had used an artificial countdown to make the UI seem alive (ticks for position and time). Let’s hook up a real player!
Xamarin.Forms has a pub/sub concept called MessagingCenter, we will use it to propagate LibVLCSharp’s playback events in the app.
readonly MediaPlayer _mp;
const string URL = "https://archive.org/download/ImagineDragons_201410/imagine%20dragons.mp4";
public void Init()
{
// create a libvlc media
_mp.Media = new Media(_libVLC, URL, Media.FromType.FromLocation);
// disable video output, we only need audio
_mp.Media.AddOption(":no-video");
// subscribe to libvlc playback events
_mp.TimeChanged += TimeChanged;
_mp.PositionChanged += PositionChanged;
_mp.LengthChanged += LengthChanged;
_mp.EndReached += EndReached;
_mp.Playing += Playing;
_mp.Paused += Paused;
}
// when the libvlc mediaplayer events fire, publish an event with the MessagingCenter
private void PositionChanged(object sender, MediaPlayerPositionChangedEventArgs e) =>
MessagingCenter.Send(MessengerKeys.App, MessengerKeys.Position, e.Position);
private void Paused(object sender, System.EventArgs e) =>
MessagingCenter.Send(MessengerKeys.App, MessengerKeys.Play, false);
private void Playing(object sender, System.EventArgs e) =>
MessagingCenter.Send(MessengerKeys.App, MessengerKeys.Play, true);
private void EndReached(object sender, System.EventArgs e) =>
MessagingCenter.Send(MessengerKeys.App, MessengerKeys.EndReached);
private void LengthChanged(object sender, MediaPlayerLengthChangedEventArgs e) =>
MessagingCenter.Send(MessengerKeys.App, MessengerKeys.Length, e.Length);
private void TimeChanged(object sender, MediaPlayerTimeChangedEventArgs e) =>
MessagingCenter.Send(MessengerKeys.App, MessengerKeys.Time, e.Time);
We could also use the reverse event stream to control playback (one of several ways to do so).
const long OFFSET = 5000;
// subscribe to UI app events for seeking.
MessagingCenter.Subscribe<string>(MessengerKeys.App,
MessengerKeys.Rewind, vm => _mp.Time -= OFFSET);
MessagingCenter.Subscribe<string>(MessengerKeys.App,
MessengerKeys.Forward, vm => _mp.Time += OFFSET);
Now that our playback service is up and running, we need to use it from the ViewModel (which is bound to the UI). This is one way of doing so:
public override Task LoadAsync()
{
_playbackService.Init();
MessagingCenter.Subscribe<string, float>(MessengerKeys.App,
MessengerKeys.Position, (app, position) => Progress = position);
MessagingCenter.Subscribe<string, long>(MessengerKeys.App,
MessengerKeys.Time, (app, time) =>
{
RemainTime = TimeSpan.FromMilliseconds((double)new decimal(_length - time));
StartTime = TimeSpan.FromMilliseconds((double)new decimal(time));
});
MessagingCenter.Subscribe<string, long>(MessengerKeys.App,
MessengerKeys.Length, (app, length) => _length = length);
MessagingCenter.Subscribe<string>(MessengerKeys.App,
MessengerKeys.EndReached, app => EndReached());
LoadSong();
_playbackService.Play(true);
return base.LoadAsync();
}
Updating ViewModel properties such as Progress
, RemainTime
and StartTime
will automatically refresh the UI and trigger Skia animations accordingly.
Play
and Pause
, as well as seeking, has also been bound from the UI to the libvlc engine using LibVLCSharp
.
Credits:
- Anish Chandran for the PulseMusic design concept.
- Javier Suárez Ruiz for the PulseMusic Xamarin.Forms UI implementation.
Note: I deliberately picked a Creative Commons licensed version of the song “Imagine Dragons - Radioactive” (remix). See https://archive.org/details/ImagineDragons_201410 and its license for more information.
This sample is available on the VideoLAN GitLab.