I have set up a BindableProperty for Media Elemement that works just fine. But I would like to access the events from the page where I am using the custom class. I have the various bools and string working as well as the methods mostly. I just need to get events working. Here is some sample code.
MediaControl.xaml:
<?xml version="1.0" encoding="utf-8" ?>
<ContentView
x:Class="NerdNewsNavigator2.Controls.MediaControl"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:item="clr-namespace:NerdNewsNavigator2.Controls"
xmlns:page="clr-namespace:NerdNewsNavigator2.View"
xmlns:toolkit="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:vm="clr-namespace:NerdNewsNavigator2.ViewModel"
Unloaded="ContentView_Unloaded">
<Grid>
<Grid.GestureRecognizers>
<TapGestureRecognizer Command="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=MovedCommand}" />
<SwipeGestureRecognizer Direction="Up" Swiped="SwipeGestureRecognizer_Swiped" />
<SwipeGestureRecognizer Direction="Down" Swiped="SwipeGestureRecognizer_Swiped" />
</Grid.GestureRecognizers>
<toolkit:MediaElement
x:Name="mediaElement"
ShouldAutoPlay="True"
ShouldKeepScreenOn="True"
ShouldShowPlaybackControls="False" />
<Frame
BackgroundColor="Black"
IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=SetFullScreen}"
IsVisible="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=SetFullScreen}"
Opacity="0.5" />
<Grid IsEnabled="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=SetFullScreen}" IsVisible="{Binding Source={RelativeSource AncestorType={x:Type vm:BaseViewModel}}, Path=SetFullScreen}">
<ImageButton
x:Name="btnFullScreen"
Margin="10"
BackgroundColor="Transparent"
Clicked="BtnFullScreen_Clicked"
HeightRequest="40"
HorizontalOptions="End"
Source="whitefs.png"
VerticalOptions="Start"
WidthRequest="40" />
<VerticalStackLayout VerticalOptions="End">
<HorizontalStackLayout HorizontalOptions="Center">
<ImageButton
x:Name="BtnRewind"
Margin="10"
BackgroundColor="Transparent"
BindingContext="{x:Reference mediaElement}"
Clicked="BtnRewind_Clicked"
HeightRequest="40"
HorizontalOptions="Center"
Source="rewind.png"
VerticalOptions="End"
WidthRequest="40" />
<ImageButton
x:Name="BtnPLay"
Margin="10"
BackgroundColor="Transparent"
BindingContext="{x:Reference mediaElement}"
Clicked="BtnPlay_Clicked"
HeightRequest="40"
HorizontalOptions="Center"
Source="pause.png"
VerticalOptions="End"
WidthRequest="40" />
<ImageButton
x:Name="BtnForward"
Margin="10"
BackgroundColor="Transparent"
BindingContext="{x:Reference mediaElement}"
Clicked="BtnForward_Clicked"
HeightRequest="40"
HorizontalOptions="Center"
Source="fastforward.png"
VerticalOptions="End"
WidthRequest="40" />
<ImageButton
x:Name="ImageButtonMute"
Margin="10"
BackgroundColor="Transparent"
Clicked="OnMuteClicked"
HeightRequest="40"
Source="muted.png"
WidthRequest="40">
<ImageButton.Triggers>
<DataTrigger
Binding="{Binding ShouldMute, Source={x:Reference mediaElement}}"
TargetType="ImageButton"
Value="True" />
<DataTrigger
Binding="{Binding ShouldMute, Source={x:Reference mediaElement}}"
TargetType="ImageButton"
Value="False" />
</ImageButton.Triggers>
</ImageButton>
</HorizontalStackLayout>
<HorizontalStackLayout HorizontalOptions="Start" VerticalOptions="End">
<Label
Margin="5"
FontSize="12"
HorizontalOptions="Center"
Text="{Binding Source={RelativeSource AncestorType={x:Type item:MediaControl}}, Path=PlayPosition}"
TextColor="White" />
</HorizontalStackLayout>
<Slider
x:Name="PositionSlider"
Margin="10"
DragCompleted="Slider_DragCompleted"
DragStarted="Slider_DragStarted"
MaximumTrackColor="LightGray"
MinimumTrackColor="Red" />
</VerticalStackLayout>
</Grid>
</Grid>
</ContentView>
Abbreviated code showing the properties and Bindable Properties. MediaControl.xaml.cs:
using Application = Microsoft.Maui.Controls.Application;
using Platform = Microsoft.Maui.ApplicationModel.Platform;
#if ANDROID
using Views = AndroidX.Core.View;
#endif
#if WINDOWS
using Microsoft.UI;
using Microsoft.UI.Windowing;
using WinRT;
using Microsoft.Maui.Controls;
using CommunityToolkit.Maui.Core.Primitives;
#endif
namespace NerdNewsNavigator2.Controls;
public partial class MediaControl : ContentView
{
#region Properties and Bindable Properties
/// <summary>
/// Initilizes a new instance of the <see cref="Position"/> class
/// </summary>
private Position Pos { get; set; } = new();
public string PlayPosition { get; set; }
public Page CurrentPage { get; set; }
private bool _fullScreen = false;
#if WINDOWS
private static MauiWinUIWindow CurrentWindow { get; set; }
#endif
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Name), typeof(MediaElement), typeof(MediaControl), propertyChanged: (bindable, oldValue, newValue) =>
{
var control = (MediaControl)bindable;
control.mediaElement.ShouldAutoPlay = (bool)newValue;
control.mediaElement.ShouldKeepScreenOn = (bool)newValue;
control.mediaElement.Source = newValue as MediaSource;
control.mediaElement.ShouldShowPlaybackControls = (bool)newValue;
control.mediaElement.StateChanged += (System.EventHandler<MediaStateChangedEventArgs>)newValue;
control.mediaElement.MediaOpened += (System.EventHandler)newValue;
});
public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(MediaSource), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.Source = newValue as MediaSource;
});
public TimeSpan Position
{
get => (TimeSpan)GetValue(TitleProperty);
}
public MediaSource Source
{
get => GetValue(SourceProperty) as MediaSource;
set => SetValue(SourceProperty, value);
}
public MediaElement Name
{
get => GetValue(TitleProperty) as MediaElement;
set => SetValue(TitleProperty, value);
}
public bool ShouldShowPlaybackControls
{
get => (bool)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public bool ShouldAutoPlay
{
get => (bool)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
public bool ShouldKeepScreenOn
{
get => (bool)GetValue(TitleProperty);
set => SetValue(TitleProperty, value);
}
#endregion
public MediaControl()
{
InitializeComponent();
PlayPosition = string.Empty;
mediaElement.PropertyChanged += MediaElement_PropertyChanged;
mediaElement.PositionChanged += ChangedPosition;
CurrentPage = Shell.Current.CurrentPage;
}
public void SeekTo(TimeSpan position)
{
mediaElement.SeekTo(position);
}
public void Stop()
{
mediaElement.Stop();
}
#region Events
#nullable enable
private void MediaElement_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == MediaElement.DurationProperty.PropertyName)
{
PositionSlider.Maximum = mediaElement.Duration.TotalSeconds;
}
}
private void OnPositionChanged(object? sender, MediaPositionChangedEventArgs e)
{
PositionSlider.Value = e.Position.TotalSeconds;
}
private void SwipeGestureRecognizer_Swiped(object sender, SwipedEventArgs e)
{
#if WINDOWS
CurrentWindow = BaseViewModel.CurrentWindow;
#endif
if (e.Direction == SwipeDirection.Up)
{
SetFullScreen();
}
if (e.Direction == SwipeDirection.Down)
{
RestoreScreen();
}
}
private void Slider_DragCompleted(object? sender, EventArgs e)
{
ArgumentNullException.ThrowIfNull(sender);
var newValue = ((Slider)sender).Value;
mediaElement.SeekTo(TimeSpan.FromSeconds(newValue));
mediaElement.Play();
}
#nullable disable
private void Slider_DragStarted(object sender, EventArgs e)
{
mediaElement.Pause();
}
private void ChangedPosition(object sender, EventArgs e)
{
var playDuration = BaseViewModel.TimeConverter(mediaElement.Duration);
var position = BaseViewModel.TimeConverter(mediaElement.Position);
PlayPosition = $"{position}/{playDuration}";
OnPropertyChanged(nameof(PlayPosition));
}
#endregion
#region Buttons
private void BtnRewind_Clicked(object sender, EventArgs e)
{
var time = mediaElement.Position - TimeSpan.FromSeconds(15);
mediaElement.Pause();
mediaElement.SeekTo(time);
mediaElement.Play();
}
private void BtnForward_Clicked(object sender, EventArgs e)
{
var time = mediaElement.Position + TimeSpan.FromSeconds(15);
mediaElement.Pause();
mediaElement.SeekTo(time);
mediaElement.Play();
}
Here is example of event trigger method that I want to call from content page TabletPlayPodcastPage.xaml:
/// <summary>
/// Manages IOS seeking for <see cref="mediaElement"/> with <see cref="Pos"/> at start of playback.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
public async void SeekIOS(object? sender, MediaStateChangedEventArgs e)
{
if (sender == null)
{
return;
}
Pos.Title = Preferences.Default.Get("New_Url", string.Empty);
Pos.SavedPosition = TimeSpan.Zero;
var positionList = await App.PositionData.GetAllPositions();
foreach (var item in positionList)
{
if (Pos.Title == item.Title)
{
Pos.SavedPosition = item.SavedPosition;
Debug.WriteLine($"Retrieved Saved position from database is: {item.Title} - {item.SavedPosition}");
}
}
if (e.NewState == MediaElementState.Playing)
{
mediaElement.SeekTo(Pos.SavedPosition);
mediaElement.ShouldKeepScreenOn = true;
Debug.WriteLine("Media playback started. ShouldKeepScreenOn is set to true.");
}
}
Here is example calling function I want to use:
/// <summary>
/// A method that starts <see cref="MediaElement"/> event for <see cref="TabletPlayPodcastPage"/>
/// </summary>
public void Load()
{
#if WINDOWS || ANDROID
mediaElement.MediaOpened += Seek;
#endif
#if IOS || MACCATALYST
mediaElement.StateChanged += SeekIOS;
#endif
}
Trying to call mediaElement.StateChanged += SeekIOS gives an error CS1061 Error Message Link
If I use Visual studio quick actions I get this function that gets rid of error but does not let me access the event.
public Action<object, EventArgs> MediaOpened { get; internal set; }
I no longer get an error message but the event does not trigger either. I am looking for a way to call the event from another page that uses MediaControl. I created all of this to reduce code and all the functions and behaviors I want to have working do work and this is about reducing code and making it tidier.
Well It ended up I managed to figure it out myself. I looked at the media element code itself and realized through trial and error this solution
public static readonly BindableProperty TitleProperty = BindableProperty.Create(nameof(Name), typeof(MediaElement), typeof(MediaControl), propertyChanged: (bindable, oldValue, newValue) =>
{
var control = (MediaControl)bindable;
control.mediaElement.ShouldAutoPlay = (bool)newValue;
control.mediaElement.ShouldKeepScreenOn = (bool)newValue;
control.mediaElement.Source = newValue as MediaSource;
control.mediaElement.ShouldShowPlaybackControls = (bool)newValue;
control.mediaElement.PositionChanged += (EventHandler<MediaPositionChangedEventArgs>)newValue;
control.mediaElement.StateChanged += (EventHandler<MediaStateChangedEventArgs>)newValue;
control.mediaElement.MediaOpened += (EventHandler)newValue;
});
public static readonly BindableProperty SourceProperty = BindableProperty.Create(nameof(Source), typeof(MediaSource), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.Source = newValue as MediaSource;
});
public static readonly BindableProperty StateChangedProperty = BindableProperty.Create(nameof(StateChanged), typeof(EventHandler<MediaStateChangedEventArgs>), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.StateChanged += (EventHandler<MediaStateChangedEventArgs>)newValue;
});
public static readonly BindableProperty MediaOpenedProperty = BindableProperty.Create(nameof(MediaOpened), typeof(EventHandler), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.MediaOpened += (EventHandler)newValue;
});
public static readonly BindableProperty ShouldKeepScreenOnProperty = BindableProperty.Create(nameof(ShouldKeepScreenOn), typeof(bool), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.ShouldKeepScreenOn = (bool)newValue;
});
public static readonly BindableProperty PositionProperty = BindableProperty.Create(nameof(Position), typeof(TimeSpan), typeof(MediaElement), TimeSpan.Zero);
public static readonly BindableProperty ShouldAutoPlayProperty = BindableProperty.Create(nameof(ShouldAutoPlay), typeof(bool), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.ShouldAutoPlay = (bool)newValue;
});
public static readonly BindableProperty ShouldShowPlaybackControlsProperty = BindableProperty.Create(nameof(ShouldShowPlaybackControls), typeof(bool), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.ShouldShowPlaybackControls = (bool)newValue;
});
public static readonly BindableProperty PositionChangedProperty = BindableProperty.Create(nameof(PositionChanged), typeof(EventHandler<MediaPositionChangedEventArgs>), typeof(MediaControl), propertyChanged: (bindableProperty, oldValue, newValue) =>
{
var control = (MediaControl)bindableProperty;
control.mediaElement.PositionChanged += (EventHandler<MediaPositionChangedEventArgs>)newValue;
});
public EventHandler MediaOpened
{
get => GetValue(MediaOpenedProperty) as EventHandler;
set => SetValue(MediaOpenedProperty, value);
}
public EventHandler<MediaStateChangedEventArgs> StateChanged
{
get => GetValue(StateChangedProperty) as EventHandler<MediaStateChangedEventArgs>;
set => SetValue(StateChangedProperty, value);
}
public EventHandler<MediaPositionChangedEventArgs> PositionChanged
{
get => GetValue(PositionChangedProperty) as EventHandler<MediaPositionChangedEventArgs>;
set => SetValue(PositionChangedProperty, value);
}
public MediaSource Source
{
get => GetValue(SourceProperty) as MediaSource;
set => SetValue(SourceProperty, value);
}
public TimeSpan Position => mediaElement.Position;
public MediaElement Name
{
get => GetValue(TitleProperty) as MediaElement;
set => SetValue(TitleProperty, value);
}
public bool ShouldShowPlaybackControls
{
get => (bool)GetValue(ShouldShowPlaybackControlsProperty);
set => SetValue(ShouldShowPlaybackControlsProperty, value);
}
public bool ShouldAutoPlay
{
get => (bool)GetValue(ShouldAutoPlayProperty);
set => SetValue(ShouldAutoPlayProperty, value);
}
public bool ShouldKeepScreenOn
{
get => (bool)GetValue(ShouldKeepScreenOnProperty);
set => SetValue(ShouldKeepScreenOnProperty, value);
}
This lets me use all of the types of properties for media element I need to. It is not all of them. But I was able to move all my page specific code back to the actual pages and my ContentView class is no longer cluttered with code for other pages.