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 mediaElements 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.

有帮助吗?

解决方案

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.

其他提示

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.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top