Search code examples
c#wpfvideomediaelement

C# MediaElement -- Why does Play() sometimes silently fail after switching the source?


I have a MediaElement set up in a custom UserControl for a video player control that I've been making -- play/pause button, slider, time remaining, etc. I have ScrubbingEnabled set to True so that I can show the first frame of the video to the user per the SO post here, and also use a Slider element to allow the user to scrub the video.

Problem: I use a binding to switch the video player's source. On occasion, if I switch videos while a video is playing, the MediaElement stops responding to Play() commands. No errors are given, even in the MediaFailed event. Calling Play() (or Pause() then Play()) fails every time. I can switch the video source after the MediaElement fails, and then it will start working again.

XAML:

<MediaElement LoadedBehavior="Manual" ScrubbingEnabled="True" 
              UnloadedBehavior="Stop"
              MediaOpened="VideoPlayer_MediaOpened" x:Name="VideoPlayer"/>

Pertinent control code:

public static DependencyProperty VideoSourceProperty =
            DependencyProperty.Register("VideoSource", typeof(string), typeof(MediaElementVideoPlayer),
                new FrameworkPropertyMetadata(null,
                    FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure,
                    new PropertyChangedCallback(OnSourceChanged)));

private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    MediaElementVideoPlayer player = d as MediaElementVideoPlayer;
    if (player != null)
    {
        player.Dispatcher.Invoke(() => {
            if (player.VideoSource != null && player.VideoSource != "")
            {
                if (player._isPlaying)
                {
                    player.VideoPlayer.Stop();
                    var uriSource = new Uri(@"/ImageResources/vid-play.png", UriKind.Relative);
                    player.PlayPauseImage.Source = new BitmapImage(uriSource);
                    player._isPlaying = false;
                } 
                player.VideoPlayer.Source = new Uri(player.VideoSource);
            }
        });
    }
}

private void VideoPlayer_MediaOpened(object sender, RoutedEventArgs e)
{
    Dispatcher.Invoke(() =>
    {
        VideoPlayer.Pause();
        VideoPlayer.Position = TimeSpan.FromTicks(0);
        Player.IsMuted = false;
        TimeSlider.Minimum = 0;
        // Set the time slider values & time label
        if (VideoPlayer.NaturalDuration != null && VideoPlayer.NaturalDuration != Duration.Automatic)
        {
            TimeSlider.Maximum = VideoPlayer.NaturalDuration.TimeSpan.TotalSeconds;
            TimeSlider.Value = 0;
            double totalSeconds = VideoPlayer.NaturalDuration.TimeSpan.TotalSeconds;
            _durationString = Utilities.numberSecondsToString((int)totalSeconds, true, true);
            TimeLabel.Content = "- / " + _durationString;
        }
    });
}

If I tell videos to auto-play all the time, the player works 100% of the time, strangely enough. Unfortunately, I need videos to be paused when the source is set. Does anyone know how to avoid the MediaElement's random, hidden failure while still swapping videos, showing the first frame of the loaded video, etc.?

There are eerily similar questions here and here, but my problem has different symptoms since I am only using 1 MediaElement.


Solution

  • Strangely enough, if you set ScrubbingEnabled to False, the MediaElement no longer stops responding at random times. Disabling ScrubbingEnabled breaks showing the first frame and makes the Slider element not function as nicely, so here's how to keep the those features while not having a, er, broken MediaElement.

    Showing the first frame:

    private static void OnSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        MediaElementVideoPlayer player = d as MediaElementVideoPlayer;
        if (player != null)
        {
            player.Dispatcher.Invoke(() => {
                if (player.VideoSource != null && player.VideoSource != "")
                {
                    if (player._isPlaying)
                    {
                        player.VideoPlayer.Stop();
                        var uriSource = new Uri(@"/ImageResources/vid-play.png", UriKind.Relative);
                        player.PlayPauseImage.Source = new BitmapImage(uriSource);
                        player._isPlaying = false;
                    } 
                    player.VideoPlayer.Source = new Uri(player.VideoSource);
                    // Start the video playing so that it will show the first frame. 
                    // We're going to pause it immediately so that it doesn't keep playing.
                    player.VideoPlayer.IsMuted = true; // so sound won't be heard
                    player.VideoPlayer.Play();
                }
            });
        }
    }
    
    private void VideoPlayer_MediaOpened(object sender, RoutedEventArgs e)
    {
        Dispatcher.Invoke(() =>
        {
            // the video thumbnail is now showing!
            VideoPlayer.Pause();
            // reset video to initial position
            VideoPlayer.Position = TimeSpan.FromTicks(0);
            Player.IsMuted = false; // unmute video
            TimeSlider.Minimum = 0;
            // Set the time slider values & time label
            if (VideoPlayer.NaturalDuration != null && VideoPlayer.NaturalDuration != Duration.Automatic)
            {
                TimeSlider.Maximum = VideoPlayer.NaturalDuration.TimeSpan.TotalSeconds;
                TimeSlider.Value = 0;
                double totalSeconds = VideoPlayer.NaturalDuration.TimeSpan.TotalSeconds;
                _durationString = Utilities.numberSecondsToString((int)totalSeconds, true, true);
                TimeLabel.Content = "- / " + _durationString;
            }
        });
    }
    

    Enable scrubbing for the video while the slider is being dragged:

    // ValueChanged for Slider
    private void TimeSlider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
    {
        Dispatcher.Invoke(() => {
            TimeSpan videoPosition = TimeSpan.FromSeconds(TimeSlider.Value);
            VideoPlayer.Position = videoPosition;
            TimeLabel.Content = Utilities.numberSecondsToString((int)VideoPlayer.Position.TotalSeconds, true, true) + " / " + _durationString;
        });
    }
    
    // DragStarted event
    private void slider_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e)
    {
        Dispatcher.Invoke(() => {
            VideoPlayer.ScrubbingEnabled = true;
            VideoPlayer.Pause();
        });
    }
    
    // DragCompleted event
    private void slider_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
    {
        Dispatcher.Invoke(() => {
            VideoPlayer.ScrubbingEnabled = false;
            TimeSpan videoPosition = TimeSpan.FromSeconds(TimeSlider.Value);
            VideoPlayer.Position = videoPosition;
            if (_isPlaying) // if was playing when drag started, resume playing
                VideoPlayer.Play();
            else
                VideoPlayer.Pause();
        });
    }