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