如何阻止WPF MediaKit的MediaClosed事件触发并恢复视频播放?
这个问题我之前做WPF视频播放功能时也踩过坑!核心原因很简单:当你切换到另一个用户控件时,承载MediaUriElement的那个用户控件会被从可视化树中移除,WPF会自动触发MediaClosed事件并释放媒体资源,等你切回去的时候,控件状态或资源已经丢失,自然没法继续播放。下面给你几个实用的解决方案,按需选择:
这是我最推荐的方案,体验最好也最省心。思路是提前把两个用户控件的实例创建好,切换时只是让ContentPresenter切换显示的实例,而不是销毁旧的、创建新的。这样MediaUriElement的实例一直存在,播放状态也会完整保留。
示例代码:
// 提前初始化两个用户控件实例,作为类的成员变量 private readonly MediaUserControl _mediaControl = new MediaUserControl(); private readonly OtherUserControl _otherControl = new OtherUserControl(); // 切换到媒体控件的按钮点击事件 private void SwitchToMediaBtn_Click(object sender, RoutedEventArgs e) { MyContentPresenter.Content = _mediaControl; // 第一次播放就设置Uri,后续切回来直接续播 if (_mediaControl.MediaElement.Source == null) { _mediaControl.MediaElement.Source = new Uri(@"C:\你的视频路径.mp4"); } else { _mediaControl.MediaElement.Play(); } } // 切换到其他控件的按钮点击事件 private void SwitchToOtherBtn_Click(object sender, RoutedEventArgs e) { // 切换前先暂停视频,保留当前播放位置 _mediaControl.MediaElement.Pause(); MyContentPresenter.Content = _otherControl; }
这个方案的好处是视频的播放位置、音量、进度等状态全都会保留,切回来一点就续播,不需要重新加载资源,用户体验拉满。唯一要注意的是如果控件占用内存特别大,可能会有轻微内存开销,但一般视频控件完全没问题。
如果因为资源限制或特殊逻辑必须销毁用户控件,那可以在切换前记录当前播放位置和视频Uri,切回来时重新创建控件,加载视频后跳转到之前的位置继续播放。
示例代码:
// 记录当前视频的Uri和播放位置 private Uri _currentVideoUri; private TimeSpan _lastPlayPosition = TimeSpan.Zero; // 切换到其他控件的事件 private void SwitchToOtherBtn_Click(object sender, RoutedEventArgs e) { var mediaControl = MyContentPresenter.Content as MediaUserControl; if (mediaControl != null) { // 先暂停,再记录状态 mediaControl.MediaElement.Pause(); _lastPlayPosition = mediaControl.MediaElement.Position; _currentVideoUri = mediaControl.MediaElement.Source; } // 销毁旧的媒体控件,创建新的其他控件 MyContentPresenter.Content = new OtherUserControl(); } // 切回媒体控件的事件 private void SwitchToMediaBtn_Click(object sender, RoutedEventArgs e) { var mediaControl = new MediaUserControl(); mediaControl.MediaElement.Source = _currentVideoUri; // 必须等视频加载完成(MediaOpened触发)才能设置播放位置 mediaControl.MediaElement.MediaOpened += (s, args) => { mediaControl.MediaElement.Position = _lastPlayPosition; mediaControl.MediaElement.Play(); }; MyContentPresenter.Content = mediaControl; }
注意:一定要在MediaOpened事件里设置播放位置,因为视频还没加载完成时,设置Position是无效的。如果希望每次切回来都从头播放,只需要把_lastPlayPosition重置为TimeSpan.Zero就行。
如果以上两种方案都没法用,比如不能修改控件创建逻辑,那可以尝试在MediaClosed事件里做拦截,但这个方法有潜在的内存泄漏风险,除非万不得已别用。
示例代码:
private void MediaUriElement_MediaClosed(object sender, EventArgs e) { var mediaElement = sender as MediaUriElement; // 重新设置Source,避免资源被完全释放 if (_currentVideoUri != null) { mediaElement.Source = _currentVideoUri; mediaElement.Position = _lastPlayPosition; mediaElement.Pause(); // 先暂停,等切回来再播放 } }
这个方法相当于强制让媒体资源不被释放,但可能会导致内存占用一直居高不下,所以谨慎使用。
内容的提问来源于stack exchange,提问作者James Hao

