Deadpikle
Deadpikle

Reputation: 366

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.

Upvotes: 3

Views: 2105

Answers (2)

Anonymous
Anonymous

Reputation: 1

I have also been experiencing MediaFailed (MILAVERR_UnexpectedWmpFailure HRESULT:0x8898050C) since introducing the ScrubbingEnabled property. Reliability has improved since disabling it during Play() and only enabling it during Stop(). That is; ScrubbingEnabled must be disabled when switching files.

Upvotes: 0

Deadpikle
Deadpikle

Reputation: 366

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();
    });
}

Upvotes: 3

Related Questions