使用MAUI实现播放视频

607 阅读3分钟

什么是 MediaElement?

借助 MediaElement,您可以获得强大的控件,允许您在 .NET MAUI 应用程序内播放多媒体。

您可能已经从 Xamarin 社区工具包中了解 MediaElement,它是由社区成员 Peter Foot 的出色工作添加的。虽然这个版本已经相当不错了,但它还有改进的空间,尤其是在 Android 上。

这就是为什么当将 MediaElement 移植到 .NET MAUI 时,我们从头开始重建了一切。这样我们就可以确保保留所有已经好的部分,同时改进那些需要一些爱的东西。

底层引擎

对于 Android,我们选择用作ExoPlayer平台对应项,替换我们用于 Xamarin 的 Android MediaPlayer。通过这种方式,我们可以自动获得许多开箱即用的额外功能,例如播放 HTTP Live Streaming (HLS) 视频、美观的平台传输控件以及许多其他功能。 在 iOS 和 macOS 上,我们使用该平台的方式AVPlayer与使用 Xamarin 的 MediaElement 的方式相同。 Tizen 也没有改变,使用Tizen.Multimedia.Player.

现在 .NET MAUI 构建在 WinUI 而不是 UWP 之上,我们MediaPlayerElement在这里使用 WinUI 的全新功能。虽然这个控件对于 WinUI 来说还很新,但它已经非常完整而且潜力巨大。

对不同媒体格式的支持因平台(以及可能安装的编解码器)而异,但通过使用平台本机媒体播放器,我们可以利用每个操作系统的所有功能和相关的优化性能。

入门

MediaElement 入门很简单。首先您要安装CommunityToolkit.Maui.MediaElementNuGet 包。这是一个独立于主要社区工具包包的包。

安装完成后,进入您的MauiProgram.cs并将以下初始化行添加到MauiAppBuilder

    public static class MauiProgram
    {
        public static MauiApp CreateMauiApp()
        {
            var builder = MauiApp.CreateBuilder();
            builder
                .UseMauiApp<App>()
                // Initialize the .NET MAUI Community Toolkit MediaElement by adding the below line of code
                .UseMauiCommunityToolkitMediaElement()
                .ConfigureFonts(fonts =>
                {
                    fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular");
                    fonts.AddFont("OpenSans-Semibold.ttf", "OpenSansSemibold");
                });


            return builder.Build();
        }
    }

现在准备好开始在应用程序中使用 MediaElement!下面是 XAML 中的一个简单示例。

这个代码直接是现在MainPage.xaml中,只有一个页面

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage 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"
             x:Class="VideoPlayer.MainPage">

    <!--RowDefinitions:设置每行高度
        ColumnDefinitions:设置每列宽度*表示填满-->
    <Grid
        Padding="5"
        RowDefinitions="240,30,50,*,50"
        ColumnDefinitions="*"
        >
        <!--Row和Column都是从0开始-->
        <toolkit:MediaElement 
            Grid.Row="0"
            Grid.Column="0"
            x:Name="mediaElement"
            ShouldAutoPlay="True"
            ShouldShowPlaybackControls="True"
            Source=""
            HeightRequest="240"
            Unloaded="mediaElement_Unloaded"
            StateChanged="mediaElement_StateChanged"
            MediaEnded="mediaElement_MediaEnded"
            MediaOpened="mediaElement_MediaOpened"
            />
        <Label Grid.Row="1" Grid.Column="0" x:Name="labIndex"  FontSize="14" TextColor="DarkCyan" ></Label>
        <HorizontalStackLayout
            Grid.Row="2"
            Grid.Column="0"
            Padding="5"
            Spacing="20"
            >
            <Label x:Name="labTotal" Padding="5"  FontSize="18" ></Label>
            <Button x:Name="btnLast" Padding="10"  Clicked="btnLast_Clicked" Text="上一首"></Button>
            <Button x:Name="btnPlay" Padding="10" Clicked="btnPlay_Clicked" Text="  播放  "></Button>
            <Button x:Name="btnPause" Padding="10" Clicked="btnPause_Clicked" Text="  暂停  "></Button>
            <Button x:Name="btnNext" Padding="10" Clicked="btnNext_Clicked" Text="下一首"></Button>
        </HorizontalStackLayout>

        
        
        <ListView
            Grid.Row="3"
            Grid.Column="0"
            SeparatorColor="Silver" 
            BackgroundColor="Transparent" 
            x:Name="videoList"
            ItemSelected="videoList_ItemSelected"
            ItemTapped="videoList_ItemTapped">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <TextCell  Text="{Binding Name}"></TextCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
        <Button 
            Grid.Row="4"
            Grid.Column="0"
            x:Name="btnPick" Text="Pick" Clicked="btnPick_Clicked"/>
    </Grid>


</ContentPage>

MainPage.xmal.cs:

using System.Collections.ObjectModel;
using VideoPlayer.Models;

namespace VideoPlayer
{
    public partial class MainPage : ContentPage
    {
        /// <summary>
        /// 目前mediaElement不支持列表所以需要自己设置列表
        /// </summary>
        private List<Video> videoSources = new List<Video> { };
        private int currentVideoIndex;
        public MainPage()
        {
            InitializeComponent();
            btnPlay.IsVisible = false;
            labTotal.Text = "Total:0";
        }

        /// <summary>
        /// 打开文件选择器选择文件
        /// </summary>
        private async void Test()
        {
            //多选方法
            var filePickerResults = await FilePicker.PickMultipleAsync(new PickOptions
            {
                FileTypes = FilePickerFileType.Videos,
                PickerTitle = "Select a file"
            });
            if (filePickerResults != null)
            {
                videoSources=new List<Video>();
                foreach (var filePickerResult in filePickerResults)
                {
                    videoSources.Add(new Video(Path.GetFileNameWithoutExtension(filePickerResult.FileName), filePickerResult.FullPath));
                }
                mediaElement.Source = videoSources[currentVideoIndex].Path;
            }

            videoList.ItemsSource = new ObservableCollection<Video>(videoSources);
            labTotal.Text = "Total:" +videoSources.Count.ToString();


        }

        private void btnPick_Clicked(object sender, EventArgs e)
        {
            Test();
        }

        private void mediaElement_Unloaded(object sender, EventArgs e)
        {
            // 释放资源
            mediaElement.Handler?.DisconnectHandler();
        }
        /// <summary>
        /// 切换逻辑
        /// </summary>
        private void Next()
        {
            if (currentVideoIndex >= videoSources.Count)
                currentVideoIndex = 0;

            mediaElement.Source = videoSources[currentVideoIndex].Path;
        }

        /// <summary>
        /// 播放状态事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mediaElement_StateChanged(object sender, CommunityToolkit.Maui.Core.Primitives.MediaStateChangedEventArgs e)
        {
            if(e.NewState == CommunityToolkit.Maui.Core.Primitives.MediaElementState.Paused)
            {
                btnPlay.IsVisible = true;
                btnPause.IsVisible = false;
            }else if(e.NewState == CommunityToolkit.Maui.Core.Primitives.MediaElementState.Playing)
            {
                btnPlay.IsVisible = false;
                btnPause.IsVisible = true;
            }
        }
        /// <summary>
        /// 播放结束事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mediaElement_MediaEnded(object sender, EventArgs e)
        {
            currentVideoIndex++;
            Next();
        }

        /// <summary>
        /// 列表Item选择事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void videoList_ItemSelected(object sender, SelectedItemChangedEventArgs e)
        {
            if(videoList.SelectedItem != null)
            {
                currentVideoIndex = e.SelectedItemIndex;
                Next();
            }
           
            
        }
        /// <summary>
        /// 选择事件触发后触发,重置SelectedItem
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void videoList_ItemTapped(object sender, ItemTappedEventArgs e)
        {
            videoList.SelectedItem = null;
        }
        /// <summary>
        /// 上一首
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnLast_Clicked(object sender, EventArgs e)
        {
            currentVideoIndex--;
            Next();
        }
        /// <summary>
        /// 播放按钮事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnPlay_Clicked(object sender, EventArgs e)
        {
            btnPlay.IsVisible = false;
            btnPause.IsVisible = true;
            mediaElement.Play();
        }
        /// <summary>
        /// medidaElement播放事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void mediaElement_MediaOpened(object sender, EventArgs e)
        {
            labIndex.Text = "当前播放:"+ videoSources[currentVideoIndex].Name;
            //btnPlay.IsVisible = false;
        }
        /// <summary>
        /// 暂停
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnPause_Clicked(object sender, EventArgs e)
        {
            btnPlay.IsVisible = true;
            btnPause.IsVisible = false;
            mediaElement.Pause();
        }
        /// <summary>
        /// 下一首
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnNext_Clicked(object sender, EventArgs e)
        {
            currentVideoIndex++;
            Next();
        }
    }

}

最终效果

8R035SI_}4JDFI57RCS29.jpg

总体来讲MAUI还有很长的路要走

仓库地址:gitee

MediaElement文档