Reputation: 97
I am working on a video project. In that project there will be a video playing for the user, and if the user clicks a button- that video is changed to the next one. Point is, the next video will play from the point the previous one stopped in (So if the user presses the NEXT button at 00:00:30 the next video will play from that point).
The problem I am facing is that there are always a few moments of black screen until the next video will play, and I want the change to be smooth without the user watching a black screen for a second or two.
So far, I've tried to solve it with one mediaElement and with two mediaElements:
The single mediaElement
change code:
TimeSpan Time = mediaElement1.Position;
mediaElement1.Source = new Uri("NEXT VIDEO LOCATION");
mediaElement1.Position = Time;
mediaElement1.Play();
Two mediaElement
s code:
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();
At both tries there is no smooth switch between the videos. Thanks in advance for any light on that matter.
Upvotes: 1
Views: 3997
Reputation: 124
Sorry if I was not as clear as I would. In fact the MediaElement
has the Position
property but it is not a DependancyProperty
and is not Bindable. There is also no way to retrieve the updated position with an event directly. So the way I used long time ago was the use of the MediaTimeLine in a StoryBoard. You should take a look on this post How to: Control a MediaElement by Using a Storyboard from Microsoft.
I used this way to seek in a video file using a MediaElement. The other way was the use of a third party library named WPFMediaKit which is very useful. You can find it there
WPF MediaKit - For webcam, DVD and custom video support in WPF
FIRST EXAMPLE : Using WPFMediaKit (no actual way to play MP4 files...)
The MediaUriElement
in the WPF MediaKit allows you to bind its MediaPosition
because it is a DependancyProperty
. The MediaUriElement
is a wrapper around the .Net MediaElement
which is poor in functionnalities. So, refering to my first answer, I write a very simple POCO. You could write something like this :
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>
And here is the 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.
}
}
}
This program works and there is no "blank screen" effect. You just have to improve the Storyboard
transition (example with a FadeIn effect), but this is the principle.
You would also note that the MediaPosition
of the second is bound to the MediaPosition
of the first one.
This implies to always check that the MediaPosition
is lower than the Duration
of the file to avoid errors, but this is just code stuff.
Note that I simplified my code avoiding the usage of MVVM and other architecture things in order to keep it clearer.
SECOND SOLUTION : Workaround using 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();
}
}
}
These are the only simple solutions I can provide. There are different ways to do this, particularly using MediaTimeline
, but the implementation is not as easy as it seems, regarding the problem you are facing. The second example, as the first is fully compiling and running and targets what you wanted to do I think.
Hope this helps. Feel free to give me your feedback.
Upvotes: 2
Reputation: 124
Is there any way to preload the next video ? If it is the case, you could try to load both sources on each MediaElement
and then bind the Position
of the first to the second one while playing.
The second MediaElement
is Collapsed
by default (preferable from Hidden
regarding performance when using heavy graphical elements) and will become visible
and its Source
is already loaded and its Position
already bound on the first MediaElement
position.
Hope this helps.
Upvotes: 0