C# MediaElement -- 为什么 Play() 有时会在切换源后无提示地失败?

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

我在自定义用户控件中为我一直在制作的视频播放器控件设置了一个 MediaElement -- play/pause 按钮、滑块、剩余时间等。我有 ScrubbingEnabled 设置为 True 以便我可以根据 SO post here 向用户显示视频的第一帧,并使用 Slider 元素允许用户擦除视频。

问题: 我使用绑定来切换视频播放器的来源。有时,如果我在播放视频时 切换视频 MediaElement 会停止响应 Play() 命令。没有给出错误,即使在 MediaFailed 事件中也是如此。每次调用 Play()(或 Pause() 然后 Play())都会失败。我可以在 MediaElement 失败后切换视频源,然后它会重新开始工作。

XAML:

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

相关控制代码:

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

如果我让视频一直自动播放,播放器会在 100% 的时间内正常工作,这很奇怪。不幸的是,我需要在设置源时暂停视频。 有谁知道如何避免 MediaElement 在交换视频、显示加载视频的第一帧等时的随机、隐藏故障?

有非常相似的问题 here and here,但我的问题有不同的症状,因为我只使用 1 MediaElement

奇怪的是,如果您将 ScrubbingEnabled 设置为 FalseMediaElement 不再随机停止响应。禁用 ScrubbingEnabled 中断显示第一帧并使 Slider 元素不能正常工作,所以这里是如何保留这些功能,同时没有,呃,损坏的 MediaElement.

显示第一帧:

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

在拖动滑块时启用视频拖拽:

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