I have a ContentView that wraps my MediaElement to use as audio player. Here is how I implemented it.
namespace Solution.MobileApp.Components;
public partial class AudioPlayer : ContentView
{
public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(MediaSource), typeof(AudioPlayer), null);
public MediaSource Source
{
get => (MediaSource)GetValue(SourceProperty);
set => SetValue(SourceProperty, value);
}
public AudioPlayer()
{
InitializeComponent();
BindingContext = this;
mediaElement.PropertyChanged += OnPropertyChanged;
}
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == MediaElement.DurationProperty.PropertyName)
{
positionSlider.Maximum = mediaElement.Duration.TotalSeconds;
}
}
private void OnMediaOpened(object sender, EventArgs e)
{ }
private void OnStateChanged(object sender, MediaStateChangedEventArgs e)
{ }
private void OnMediaFailed(object sender, MediaFailedEventArgs e)
{ }
private void OnMediaEnded(object sender, EventArgs e)
{
eventManager.HandleEvent(this, EventArgs.Empty, "MediaEnded");
mediaElement.Play();
}
private void OnPositionChanged(object sender, MediaPositionChangedEventArgs e)
{
positionSlider.Value = e.Position.TotalSeconds;
}
private void OnSeekCompleted(object sender, EventArgs e)
{ }
private void OnPlayClicked(object sender, EventArgs e)
{
mediaElement.Play();
}
private void OnPauseClicked(object sender, EventArgs e)
{
mediaElement.Pause();
}
private void OnStopClicked(object sender, EventArgs e)
{
mediaElement.Stop();
}
private void OnMuteClicked(object sender, EventArgs e)
{
mediaElement.ShouldMute = !mediaElement.ShouldMute;
}
private void OnUnloaded(object sender, EventArgs e)
{
// Stop and cleanup MediaElement when we navigate away
mediaElement.Handler?.DisconnectHandler();
}
private void OnSliderDragCompleted(object sender, EventArgs e)
{
ArgumentNullException.ThrowIfNull(sender);
var newValue = ((Slider)sender).Value;
mediaElement.SeekTo(TimeSpan.FromSeconds(newValue));
mediaElement.Play();
}
private void OnSliderDragStarted(object sender, EventArgs e)
{
mediaElement.Pause();
}
}
<?xml version="1.0" encoding="utf-8" ?>
<ContentView xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:converters="clr-namespace:Solution.MobileApp.Converters"
x:Class="Solution.MobileApp.Components.AudioPlayer"
Unloaded="OnUnloaded">
<ContentView.Resources>
<toolkit:TimeSpanToSecondsConverter x:Key="TimeSpanConverter" />
<converters:SecondsToStringConverter x:Key="SecondsToStringConverter" />
</ContentView.Resources>
<ScrollView>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="0" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<toolkit:MediaElement
Grid.Row="0"
x:Name="mediaElement"
ShouldAutoPlay="True"
Source="{Binding Source}"
MediaEnded="OnMediaEnded"
MediaFailed="OnMediaFailed"
MediaOpened="OnMediaOpened"
PositionChanged="OnPositionChanged"
StateChanged="OnStateChanged"
SeekCompleted="OnSeekCompleted"/>
<HorizontalStackLayout Grid.Row="1" Padding="0,0,0,15">
<Label HorizontalOptions="Center">
<Label.Text>
<MultiBinding StringFormat="Current State: {0}">
<Binding Path="CurrentState" Source="{x:Reference mediaElement}" />
</MultiBinding>
</Label.Text>
</Label>
</HorizontalStackLayout>
<Grid Grid.Row="2" Padding="0,10,0,10" ColumnDefinitions="*,*,*,*" ColumnSpacing="5">
<Button Grid.Column="0" Text="Play" Clicked="OnPlayClicked" />
<Button Grid.Column="1" Text="Pause" Clicked="OnPauseClicked" />
<Button Grid.Column="2" Text="Stop" Clicked="OnStopClicked" />
<Button Grid.Column="3" Text="Mute" Clicked="OnMuteClicked">
<Button.Triggers>
<DataTrigger TargetType="Button"
Binding="{Binding ShouldMute, Source={x:Reference mediaElement}}"
Value="True">
<Setter Property="Text" Value="Unmute" />
</DataTrigger>
<DataTrigger TargetType="Button"
Binding="{Binding ShouldMute, Source={x:Reference mediaElement}}"
Value="False">
<Setter Property="Text" Value="Mute" />
</DataTrigger>
</Button.Triggers>
</Button>
</Grid>
<VerticalStackLayout Grid.Row="3" Padding="0,10,0,10">
<Slider x:Name="positionSlider"
MinimumTrackColor="Gray"
DragStarted="OnSliderDragStarted"
DragCompleted="OnSliderDragCompleted"/>
<HorizontalStackLayout Padding="0,10,0,10">
<Label HorizontalOptions="Center">
<Label.Text>
<MultiBinding StringFormat="{}Position: {0}/{1}">
<Binding Path="Position" Source="{x:Reference mediaElement}" Converter="{StaticResource SecondsToStringConverter}" />
<Binding Path="Duration" Source="{x:Reference mediaElement}" Converter="{StaticResource SecondsToStringConverter}" />
</MultiBinding>
</Label.Text>
</Label>
</HorizontalStackLayout>
</VerticalStackLayout>
<HorizontalStackLayout Grid.Row="4" Padding="0,10,0,10">
<Label>
<Label.FormattedText>
<FormattedString>
<Span Text="Volume:" />
<Span Text="{Binding Source={x:Reference mediaElement}, Path=Volume, StringFormat='{}{0:P0}'}" />
</FormattedString>
</Label.FormattedText>
</Label>
<Slider Maximum="1.0"
Minimum="0.0"
MinimumTrackColor="Red"
MaximumTrackColor="Gray"
Margin="10,0,10,0"
WidthRequest="300">
<Slider.Value>
<Binding Path="Volume" Source="{x:Reference mediaElement}" />
</Slider.Value>
</Slider>
</HorizontalStackLayout>
</Grid>
</ScrollView>
</ContentView>
In my page I am using it like this:
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:pages="clr-namespace:Solution.MobileApp.Pages"
xmlns:views="clr-namespace:Solution.MobileApp.Pages.Tabs"
xmlns:component="clr-namespace:Solution.MobileApp.Components"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:viewModels="clr-namespace:Solution.MobileApp.ViewModels"
x:TypeArguments="viewModels:MusicPageViewModel"
x:DataType="viewModels:MusicPageViewModel"
x:Class="Solution.MobileApp.Pages.Tabs.MusicPage"
Title="MediaElement">
<VerticalStackLayout>
<Label Text="Welcome to PLAYLIST PAGE!"
VerticalOptions="Center"
HorizontalOptions="Center" />
<component:AudioPlayer x:Name="audioPlayer" />
</VerticalStackLayout>
</pages:BasePage>
public partial class MusicPageViewModel : BaseViewModel
{
[ObservableProperty]
private MediaSource source;
private List<string> songs = new List<string>
{
"Mostantol.mp3",
"Vihar.mp3"
};
public MusicPageViewModel()
{
source = MediaSource.FromResource(songs[0]);
}
public string NextSong() => songs[1];
}
public partial class MusicPage : BasePage<MusicPageViewModel>
{
private readonly ILogger logger;
public MusicPage(MusicPageViewModel viewModel, ILogger<MusicPage> logger) : base(viewModel)
{
this.logger = logger;
InitializeComponent();
audioPlayer.Source = viewModel.Source;
}
public void OnMediaEnded(object sender, EventArgs e)
{
audioPlayer.Source = BindingContext.NextSong();
}
}
Now my idea is that I would like to define an property (probably later more) on the AudioPlayer component, to have a property of event type (ex. OnMediaEndedEvent) that I can trigger in the MediaElement on OnMediaEnded event?
What I can see in debug that the event is triggered, the AudioPlayer component BindingContext is got a new value (Vihar.mp3), but the time song not started, the player state is stopped.
thnx
I made a mistake in my ViewModel:
public string NextSong() => songs[1];
should be
public MediaSource NextSong() => MediaSource.FromResource(songs[1]);