我正在制作一个视频项目。在该项目中,将为用户播放视频,如果用户单击按钮,则视频将更改为下一个视频。点是,下一个视频将从前一个视频停止的点开始播放(因此,如果用户在00:00:30按下NEXT按钮,则下一个视频将从该点播放)。
我面临的问题是,在下一个视频播放之前总会有一些黑屏,我希望更改能够顺利进行,而不会让用户观看黑屏一两秒。
到目前为止,我已尝试使用一个mediaElement和两个mediaElements来解决它:
单个mediaElement
更改代码:
TimeSpan Time = mediaElement1.Position;
mediaElement1.Source = new Uri("NEXT VIDEO LOCATION");
mediaElement1.Position = Time;
mediaElement1.Play();
两个mediaElement
代码:
TimeSpan Time = mediaElement1.Position;
mediaElement2.Source = new Uri("NEXT VIDEO LOCATION");
mediaElement2.Position = Time;
mediaElement1.Visibility = Visibility.Hidden;
mediaElement1.Source = null;
mediaElement2.Visibility = Visibility.Visible;
mediaElement2.Play();
两次尝试都没有视频之间的平滑切换。提前感谢您对此事的任何启示。
答案 0 :(得分:2)
对不起,如果我不是那么清楚。实际上MediaElement
具有Position
属性,但它不是DependancyProperty
且不是Bindable。也无法直接使用事件检索更新的位置。所以我很久以前使用的方式是在StoryBoard中使用MediaTimeLine。您应该查看Microsoft的这篇文章How to: Control a MediaElement by Using a Storyboard。
我用这种方式使用MediaElement在视频文件中搜索。另一种方法是使用名为WPFMediaKit的第三方库,这非常有用。你可以在
WPF MediaKit - For webcam, DVD and custom video support in WPF
第一个例子:使用WPFMediaKit(没有实际播放MP4文件的方法......)
WPF MediaKit中的MediaUriElement
允许您绑定其MediaPosition
,因为它是DependancyProperty
。 MediaUriElement
是.Net MediaElement
的包装,其功能不佳。所以,参考我的第一个答案,我写了一个非常简单的POCO。你可以这样写:
<强> MainWindow.xaml 强>
<Window x:Class="Media.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:media="clr-namespace:WPFMediaKit.DirectShow.Controls;assembly=WPFMediaKit"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Storyboard x:Key="SwitchToSecondPlayerStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="firstPlayer">
<DiscreteObjectKeyFrame KeyTime="0:0:0.7" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="secondPlayer">
<DiscreteObjectKeyFrame KeyTime="0:0:0.7" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="switchButton">
<BeginStoryboard Storyboard="{StaticResource SwitchToSecondPlayerStoryboard}"/>
</EventTrigger>
</Window.Triggers>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="60"/>
</Grid.RowDefinitions>
<media:MediaUriElement x:Name="firstPlayer" Source="firstFile.wmv" LoadedBehavior="Play"/>
<media:MediaUriElement x:Name="secondPlayer" Source="secondFile.wmv" MediaPosition="{Binding Path=MediaPosition, ElementName=firstPlayer}" Visibility="Collapsed" Volume="0"/>
<Button Grid.Row="1" x:Name="switchButton" Content="SWITCH NEXT FILE" HorizontalAlignment="Center" VerticalAlignment="Center" Click="OnSwitchButtonClick"/>
</Grid>
这是 MainWindow.xaml.cs
namespace Media
{
/// <summary>
/// Logique d'interaction pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void OnSwitchButtonClick(object sender, RoutedEventArgs e)
{
//Do whatever you want with the players.
}
}
}
此程序有效,没有“空白屏幕”效果。您只需要改进Storyboard
转换(具有FadeIn效果的示例),但这是原则。
您还会注意到第二个MediaPosition
绑定到第一个MediaPosition
。
这意味着要始终检查MediaPosition
是否低于文件的Duration
以避免错误,但这只是代码内容。
请注意,我简化了代码,避免使用MVVM和其他架构,以便更清晰。
第二个解决方案:使用MediaElement
<强> MainWindow.xaml 强>
<Window x:Class="Media.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Storyboard x:Key="ShowSecond">
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="firstMediaElement">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3" Value="{x:Static Visibility.Collapsed}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="secondMediaElement">
<DiscreteObjectKeyFrame KeyTime="0:0:0.3" Value="{x:Static Visibility.Visible}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Window.Resources>
<Window.Triggers>
<EventTrigger RoutedEvent="ButtonBase.Click" SourceName="btnlaunchNext">
<BeginStoryboard Storyboard="{StaticResource ShowSecond}"/>
</EventTrigger>
</Window.Triggers>
<Grid>
<StackPanel Background="Black">
<MediaElement Name="firstMediaElement" LoadedBehavior="Manual" Source="firstFile.mp4" Width="260" Height="150" Stretch="Fill" />
<MediaElement Name="secondMediaElement" Volume="0" LoadedBehavior="Manual" Source="secondFile.mp4" Visibility="Collapsed" Width="260" Height="150" Stretch="Fill" />
<!-- Buttons to launch videos. -->
<Button x:Name="btnlaunch" Content="PLAY FIRST" Click="OnLaunchFirstButtonClick"/>
<Button x:Name="btnlaunchNext" Content="PLAY NEXT" Click="OnLaunchNextButtonClick"/>
</StackPanel>
</Grid>
</Window>
<强> MainWindow.xaml.cs 强>
using System;
using System.Timers;
using System.Windows;
namespace Media
{
/// <summary>
/// Interaction logic for pour MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public TimeSpan Position { get; set; }
Timer _updateTimer;
public MainWindow()
{
InitializeComponent();
//Timer interval fixed to 1 second to read the actual position of the first media element
this._updateTimer = new Timer(1000);
this._updateTimer.Elapsed += this.UpdateTimerElapsed;
}
//The callback of your update timer
private void UpdateTimerElapsed(object sender, ElapsedEventArgs e)
{
//I use the dispatcher because you call the Position from another thread so it has to be synchronized
Dispatcher.Invoke(new Action(() => this.Position = firstMediaElement.Position));
}
//When stopping the first, you start the second and set its Position
private void OnLaunchNextButtonClick(object sender, RoutedEventArgs e)
{
this.firstMediaElement.Stop();
this.secondMediaElement.Volume = 10;
this.secondMediaElement.Play();
this.secondMediaElement.Position = this.Position;
}
//When you start the first, you have to start the update timer
private void OnLaunchFirstButtonClick(object sender, RoutedEventArgs e)
{
this.firstMediaElement.Play();
this._updateTimer.Start();
}
}
}
这是我能提供的唯一简单解决方案。有不同的方法可以做到这一点,特别是使用MediaTimeline
,但是对于您所面临的问题,实现并不像看起来那么容易。第二个例子,第一个例子是完全编译和运行,并以我想要做的为目标。
希望这会有所帮助。请随时向我提供反馈。
答案 1 :(得分:0)
有没有办法预加载下一个视频?如果是这种情况,您可以尝试在每个MediaElement
上加载两个源,然后在播放时将第一个Position
绑定到第二个MediaElement
。
默认情况下,第二个Collapsed
为Hidden
(最好是visible
关于使用大量图形元素时的效果)并且将成为Source
且其Position
已加载,其MediaElement
已经绑定在第一个{{1}}位置。
希望这有帮助。