基於WPF開發影片播放器

老码识途呀發表於2024-11-18

在實際應用中,影片播放功能在很多軟體中都會用到,將音訊和影片整合到應用程式中不僅可以增強使用者體驗,還能起到事半功倍的效果。今天本文以一個簡單的小例子,簡述如何透過WPF中的MediaElement開發影片播放器,僅供學習分享使用,如有不足之處,還請指正!

涉及知識點

在本例項中,開發影片播放器,主要用到MediaElement控制元件,而MediaElement 是受 Layout 支援的 UIElement,並可用作許多控制元件的內容。涉及知識點如下:

  • LoadedBehavior,UnloadedBehavior 主要用於控制IsLoaded屬性為true和false時的影片播放行為,即影片載入成功和沒有載入時的播放行為。此兩個屬性為MediaState型別,共有五種值供選擇,Manual,Play,Stop,Pause,Close。 例如,預設 LoadedBehavior 為 Play,預設 UnloadedBehavior 為 Close。 這意味著,一旦載入 MediaElement 並且預滾完成,媒體開始播放。 一旦播放完畢,媒體就會關閉並且釋出所有媒體資源。
  • Source,主要用於指定影片播放源路徑,Uri型別。
  • Play、Pause 和 Stop 方法分別用於播放、暫停和停止媒體。 更改 MediaElement 的 Position 屬性可讓你在媒體中跳轉。 最後,Volume 和 SpeedRatio 屬性用於調整媒體的音量和播放速度。

注意:MediaElement 的 LoadedBehavior 屬性必須設定為 Manual 才能以互動方式停止、暫停和播放媒體。

依賴庫

所謂麻雀雖小,五臟俱全,本例項雖然是一個影片播放器的小例子,但是也遵循MVVM的設計思想。主要採用CommunityToolkit.Mvvm,可透過Nuget包管理器進行安裝,如下所示:

基於WPF開發影片播放器

關於CommunityToolkit.Mvvm的使用方法,可參考相關文件,在此不在贅述。

UI佈局

雖然MediaElement控制元件可以實現影片的播放,但是要實現完整的功能,還需要其他的頁面佈局控制元件。在本例項中,主要分為三個組成部分:

  • 播放區,主要用於顯示影片的播放內容,時長,播放進度等內容
  • 影片列表,主要用於顯示播放的影片的列表,以及手動新增影片到列表中,雙擊列表項進行播放等。
  • 影片控制,主要用於控制影片的播放,暫停,停止,速度,音量等內容。

影片播放器UI原始碼如下所示:

<Window x:Class="DemoMedia.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
        xmlns:local="clr-namespace:DemoMedia"
        mc:Ignorable="d"
        Title="Windows Media Player" Height="450" Width="800" Background="AliceBlue">
    <Window.Resources>
        <ResourceDictionary Source="\Resources\Icons.xaml"></ResourceDictionary>
    </Window.Resources>
    <i:Interaction.Triggers>
        <i:EventTrigger EventName="Loaded">
            <i:InvokeCommandAction Command="{Binding LoadedCommand}" PassEventArgsToCommand="True"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition Width="Auto" MinWidth="150" MaxWidth="200"></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
                <RowDefinition Height="Auto"></RowDefinition>
            </Grid.RowDefinitions>
            <Border Grid.Row="0" Background="AliceBlue"></Border>
            <MediaElement Grid.Row="0" Name="mediaElement" MinHeight="300" MinWidth="300"
                          Source="{Binding Model.CurSource}"
                          LoadedBehavior="Manual" UnloadedBehavior="Manual" Stretch="Uniform">
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MediaOpened">
                        <i:InvokeCommandAction Command="{Binding MediaOpenedCommand}"/>
                    </i:EventTrigger>
                    <i:EventTrigger EventName="MediaEnded">
                        <i:InvokeCommandAction Command="{Binding MediaEndedCommand}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </MediaElement>
            <Button Command="{Binding PlayCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0.6" Visibility="{Binding Model.PlayButtonVisibility}" Cursor="Hand">
                <Button.Template>
                    <ControlTemplate>
                        <Border Background="AliceBlue">
                            <Path Data="{StaticResource play}" Fill="#3259CE" Stroke="#3259CE" Width="30" Height="30" Stretch="Fill" ></Path>
                        </Border>
                    </ControlTemplate>
                </Button.Template>
            </Button>
            <Grid Grid.Row="1" VerticalAlignment="Bottom" Margin="2 0 2 4">
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                    <ColumnDefinition></ColumnDefinition>
                    <ColumnDefinition Width="Auto"></ColumnDefinition>
                </Grid.ColumnDefinitions>
                <TextBlock Grid.Column="0" Text="00:00" VerticalAlignment="Bottom"></TextBlock>
                <ProgressBar Grid.Column="1"  Height="5" Background="Black"  Foreground="White" Value="{Binding Model.Position}" Minimum="0" Maximum="{Binding Model.MediaMaximum}"></ProgressBar>
                <StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Bottom">
                    <!--<TextBlock  Text="時長:"></TextBlock>-->
                    <TextBlock  Text="{Binding Model.TimeLen}"></TextBlock>
                </StackPanel>
            </Grid>
            <StackPanel Grid.Row="2" Orientation="Horizontal" HorizontalAlignment="Center">
                <Button x:Name="btnPlay" Margin="5" Command="{Binding PlayCommand}">
                    <Button.Template>
                        <ControlTemplate>
                            <Border Background="AliceBlue">
                                <Path Data="{StaticResource play}" Fill="#3259CE" Stroke="#3259CE" Width="30" Height="30" Stretch="Fill"></Path>
                            </Border>
                        </ControlTemplate>
                    </Button.Template>
                </Button>
                <Button x:Name="btnPause" Margin="5"  Command="{Binding PauseCommand}">
                    <Button.Template>
                        <ControlTemplate>
                            <Border Background="AliceBlue">
                                <Path Data="{StaticResource pause}" Fill="#3259CE" Stroke="#3259CE" Width="30" Height="30" Stretch="Fill"></Path>
                            </Border>
                        </ControlTemplate>
                    </Button.Template>
                </Button>
                <Button x:Name="btnStop" Margin="5"  Command="{Binding StopCommand}">
                    <Button.Template>
                        <ControlTemplate>
                            <Border Background="AliceBlue">
                                <Path Data="{StaticResource stop}" Fill="#3259CE" Stroke="#3259CE" Width="30" Height="30" Stretch="Fill"></Path>
                            </Border>
                        </ControlTemplate>
                    </Button.Template>
                </Button>
                <TextBlock Foreground="#3259CE" VerticalAlignment="Center" Margin="5" Text="音量"></TextBlock>
                <Slider Name="volumeSlider" VerticalAlignment="Center" Value="{Binding ElementName=mediaElement, Path=Volume, Mode=TwoWay}" 
                        Minimum="0" Maximum="1" Width="70"/>
                <TextBlock Foreground="#3259CE" Margin="5"  VerticalAlignment="Center" Text="速度"></TextBlock>
                <Slider Name="speedRatioSlider" VerticalAlignment="Center"  Value="{Binding ElementName=mediaElement, Path=SpeedRatio, Mode=TwoWay}"  Width="70" />
            </StackPanel>
        </Grid>
        <GridSplitter Grid.Column="0" HorizontalAlignment="Right" Width="2"></GridSplitter>
        <Grid Grid.Column="1" Background="AliceBlue">
            <Grid.RowDefinitions>
                <RowDefinition Height="Auto" MinHeight="30"></RowDefinition>
                <RowDefinition></RowDefinition>
            </Grid.RowDefinitions>
            <Border Background="Transparent" BorderBrush="White" BorderThickness="1" Grid.Row="0"></Border>
            <Grid Grid.Row="0" Margin="2">
                <TextBlock Text="播放列表" VerticalAlignment="Center" HorizontalAlignment="Left"></TextBlock>
                <Button x:Name="btnOpen" Width="18" Height="18" Command="{Binding BrowserCommand}" Margin="5" HorizontalAlignment="Right">
                    <Button.Template>
                        <ControlTemplate>
                            <Border Background="AliceBlue">
                                <Path Data="{StaticResource browser}" Stroke="#27A2DF" Fill="White" Width="18" Height="18" Stretch="Fill"></Path>
                            </Border>
                        </ControlTemplate>
                    </Button.Template>
                </Button>
            </Grid>
            <ListView Grid.Row="1" Margin="2" ItemsSource="{Binding Model.Movies}" BorderThickness="0" SelectionMode="Single" Background="AliceBlue">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Orientation="Horizontal">
                            <TextBlock Text="{Binding Id, StringFormat='[00]'}" Margin="2 3"></TextBlock>
                            <TextBlock Text="{Binding Name}" Margin="3 3" ToolTip="{Binding Url}"></TextBlock>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
                <i:Interaction.Triggers>
                    <i:EventTrigger EventName="MouseDoubleClick">
                        <i:InvokeCommandAction Command="{Binding MouseDoubleCommand}" CommandParameter="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=ListView}, Path=SelectedItem}"/>
                    </i:EventTrigger>
                </i:Interaction.Triggers>
            </ListView>
        </Grid>
    </Grid>
</Window>

在本例項中,為了UI美觀,所有按鈕都採用圖示顯示,而圖示的實現方式採用幾何圖形,具體實現方式可參考相關文章。

核心原始碼

在本例項中,業務邏輯和UI分離,主要實現手動新增影片,及影片的播放,暫停,停止,音量,進度等功能。

瀏覽影片並加入到播放列表,如下所示:

private void Browser()
{
	OpenFileDialog dialog = new OpenFileDialog();
	dialog.Title = "請選擇要播放的影片";
	dialog.Filter = "MP4|*.mp4";
	dialog.Multiselect = true;
	if (dialog.ShowDialog()==true)
	{
		var files=dialog.FileNames;
		foreach (var file in files)
		{
			var maxId = this.Model.Movies.Count>0 ? this.Model.Movies.Max(x => x.Id):0;
			if (this.Model.Movies.FirstOrDefault(item => item.Url == file) == null)
			{
				this.Model.Movies.Add(new Movie()
				{
					Id = maxId + 1,
					Name = Path.GetFileNameWithoutExtension(file),
					Url = file
				});
			}
		}
	}
}

影片播放功能,開始播放後,單獨開啟一個執行緒,用來重新整理播放的進度,如下所示:

private void Play()
{
	//影片播放,如果沒有,則開啟選擇資料夾
	if (this.Model.CurMovie == null)
	{
		this.Browser();
		if (this.Model.Movies.Count < 1)
		{
			return;
		}
		this.Model.CurMovie = this.Model.Movies.Last();
	}
	this.Model.CurSource = new Uri(this.Model.CurMovie.Url, UriKind.RelativeOrAbsolute);
	if (this.media.NaturalDuration != Duration.Automatic && this.media.Position.TotalSeconds == this.media.NaturalDuration.TimeSpan.TotalSeconds)
	{
		this.media.Position=new TimeSpan(0,0,0);
	}
	this.media.Play();
	this.IsRunning = true;
	this.Model.PlayButtonVisibility = Visibility.Collapsed;
	//
	this.mediaTask = Task.Run(() =>
	{
		while (this.IsRunning)
		{
			Application.Current.Dispatcher.Invoke(() =>
			{
				this.Model.Position = this.media.Position.TotalSeconds;
			});
			Thread.Sleep(100);
		}
	});
}

注意,MediaElement的Position並非依賴屬性,無法進行繫結,所以採用後臺執行緒重新整理的方式進行實現。

影片暫停功能,透過MediaElement的Pause方法即可控制影片的暫停,如下所示:

private void Pause()
{
	if (this.Model.CurMovie == null)
	{
		return;
	}
	this.media.Pause();
	this.IsRunning = false;
}

影片停止功能,透過MediaElement的Stop方法即可控制影片的停止,如下所示:

private void Stop()
{
	if (this.Model.CurMovie == null)
	{
		return;
	}
	this.media.Stop();
	this.IsRunning= false;
}

注意,暫停功能是將播放位置保持在當前位置;停止功能是將影片的播放位置重置到初始位置。

影片開啟事件,即當影片開始時觸發的路由事件,如下所示:

private void MediaOpened()
{
	if (this.media.NaturalDuration.TimeSpan.TotalMinutes < 60)
	{
		this.Model.TimeLen = this.media.NaturalDuration.TimeSpan.ToString(@"mm\:ss");
	}
	else
	{
		this.Model.TimeLen = this.media.NaturalDuration.TimeSpan.ToString(@"hh\:mm\:ss");
	}
	this.Model.MediaMaximum = this.media.NaturalDuration.TimeSpan.TotalSeconds;
}

注意:在這裡,影片開始時,初始化播放時長等內容。

影片結束事件,當影片播放結束時觸發的路由事件,如下所示:

private void MediaEnded()
{
	this.Model.PlayButtonVisibility = Visibility.Visible;
	this.IsRunning = false;
}

注意,影片播放結束時,將播放狀態置為flase,不在監控播放位置。

影片播放器效果

影片播放器初始化頁面,如下圖所示:

基於WPF開發影片播放器

新增影片到播放列表,如下所示:

基於WPF開發影片播放器

影片播放效果截圖,如下所示:

基於WPF開發影片播放器

原始碼下載

WPF版本的影片播放器,可透過兩種方式獲取原始碼:

1. 關注“老碼識途”公眾號,並回復關鍵字“WMEDIA”進行獲取,如下所示:

基於WPF開發影片播放器

2. 透過Gitee倉庫【下載網址:https://gitee.com/ahsiang/wpf-media】進行下載,如下所示:

基於WPF開發影片播放器

以上就是《基於WPF開發影片播放器》的全部內容,旨在拋磚引玉,一起學習,共同進步!

相關文章