WPF中可以使用MediaElement控件来进行音视频播放,然后需要做个进度条啥的,但是MediaElement.Position(进度)和MediaElement.NaturalDuration居然都不是依赖属性,简直不能忍!
好吧,首先说说比较传统的做法(winform?)
slider用来显示进度以及调整进度,tb1显示当前进度的时间值,tb2显示视频的时长。
player_Loaded 事件中使用DispatcherTimer来定时获取当前视频的播放进度,
player_MediaOpened 事件中获取当前视频的时长(只有在视频加载完成后才可以获取到)
slider_ValueChanged 事件中执行对视频进度的调整
xaml: <MediaElement Name="player" Source="e:\MVVMLight (1).wmv" Loaded="player_Loaded" MediaOpened="player_MediaOpened"/> <Slider Grid.Row="1" Margin="10" Name="slider" ValueChanged="slider_ValueChanged"/> <WrapPanel Grid.Row="1" Margin="0,40,0,0"> <TextBlock Name="tb1" Width="120"/> <TextBlock Name="tb2" Width="120"/> </WrapPanel> 后台代码: private void player_Loaded(object sender, RoutedEventArgs e) { DispatcherTimer timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromMilliseconds(1000); timer.Tick += (ss, ee) => { //显示当前视频进度 var ts = player.Position; tb1.Text = string.Format("{0:00}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds);slider.Value = ts.TotalMilliseconds; }; timer.Start(); } private void player_MediaOpened(object sender, RoutedEventArgs e) { //显示视频的时长 var ts = player.NaturalDuration.TimeSpan; tb2.Text = string.Format("{0:00}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds); slider.Maximum = ts.TotalMilliseconds; } private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e) { //调整视频进度 var ts = TimeSpan.FromMilliseconds(e.NewValue); player.Position = ts; }
下面再来看看使用依赖属性的做法:
添加一个类,继承MediaElement
public class MediaElementExt : MediaElement, INotifyPropertyChanged { private DispatcherTimer timer; private const string CurrentTimeProperty = "CurrentTime"; /// <summary> /// 当前播放进度 /// </summary> public double CurrentTime { get { return this.Position.TotalMilliseconds; } set { //进度条拖动太频繁太久,性能跟不上,所以设置一个时间阀,跳过某些时段 if ((DateTime.Now - _lastChangedTime).TotalMilliseconds > 50) { this.Position = TimeSpan.FromMilliseconds(value); _lastChangedTime = DateTime.Now; } } } /// <summary> /// 记录最后修改进度的时间, /// </summary> private DateTime _lastChangedTime = DateTime.Now; private const string DurationTimeProperty = "DurationTime"; /// <summary> /// 当前视频时长 /// </summary> public double DurationTime { get { if (this.NaturalDuration.HasTimeSpan) return this.NaturalDuration.TimeSpan.TotalMilliseconds; return double.NaN; } } public MediaElementExt() { timer = new DispatcherTimer(); timer.Interval = TimeSpan.FromMilliseconds(1000); timer.Tick += timer_Tick; this.MediaOpened += (ss, ee) => { //触发PropertyChanged DurationTime this.RaisePropertyChanged(DurationTimeProperty); timer.Start(); }; //发生错误和视频播放完毕 停止计时器 this.MediaEnded += (ss, ee) => { timer.Stop(); }; this.MediaFailed += (ss, ee) => { timer.Stop(); }; } void timer_Tick(object sender, EventArgs e) { //定时触发PropertyChanged CurrentTime this.RaisePropertyChanged(CurrentTimeProperty); } public event PropertyChangedEventHandler PropertyChanged; public void RaisePropertyChanged(string propertyName) { if (this.PropertyChanged != null) { this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } }
<Grid DataContext="{Binding ElementName=player}"> <Grid.RowDefinitions> <RowDefinition Height="8*"/> <RowDefinition Height="2*"/> </Grid.RowDefinitions> <!--<MediaElement/>--> <local:MediaElementExt x:Name="player" Source="e:\MVVMLight (1).wmv" /> <Slider Name="slider" Grid.Row="1" Value="{Binding CurrentTime,Mode=TwoWay}" Maximum="{Binding DurationTime,Mode=OneWay}" Margin="10"/> <WrapPanel Grid.Row="1" Margin="0,40,0,0"> <TextBlock Text="{Binding Value, Converter={StaticResource TimeSpanConverter}, Mode=OneWay,ElementName=slider}" Width="120"/> <TextBlock Text="{Binding DurationTime, Mode=OneWay,Converter={StaticResource TimeSpanConverter}}" Width="120"/> </WrapPanel> </Grid> --- xmlns:local="clr-namespace:WPF_Player"
public class TimeSpanConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { var ts = TimeSpan.FromMilliseconds((double)value); return string.Format("{0:00}:{1:00}:{2:00}", ts.Hours, ts.Minutes, ts.Seconds); } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { throw new NotImplementedException(); } }
xaml中的转换器:
<local:TimeSpanConverter x:Key="TimeSpanConverter"/>
其实两种方法实现的都差不多,都需要计时器定时的去获取当前视频的进度,但第二种方面显然要优雅一些。
文章就写到这儿,如有疏漏错误,欢迎指正。