Ниже — общее правильное руководство по реализации перемотки (seek) в WPF‑плеере на базе библиотеки, работающей через FFmpeg (в вашем случае — Flyleaf). Я не знаю точных сигнатур API вашей версии Flyleaf, поэтому привожу подходы и примерный код, которые легко адаптировать под конкретные методы библиотеки.
Основные идеи
- Перематывать нужно не при каждом изменении значения слайдера (особенно во время drag), а по окончании перетаскивания -> это убережёт от постоянных дорогих операций демультиплексирования/декодирования.
- Перед seek следует приостановить воспроизведение (или остановить рендер), выполнить seek в демультиплексоре/декодере, сбросить (flush) декодеры и рендер, затем возобновить воспроизведение (если было).
- Если требуется «точная» кадровая перемотка (frame‑accurate), это дороже: обычно делается seek на ближайший ключевой кадр, затем декодируются последующие кадры до нужной PTS.
- Обновление положения слайдера от плеера нужно отключать во время перетаскивания, чтобы UI не прыгал.
Поведение UI (рекомендуемый UX)
- ThumbDragStarted: запоминаете состояние (играли ли вы), ставите флаг isSeeking = true, приостанавливаете обновления позиции.
- ThumbDragDelta: можно показывать превью времени (и, при желании, превью кадра — но это дорого).
- ThumbDragCompleted: выполняете один seek на выбранное время; после завершения — сбрасываете isSeeking и возобновляете воспроизведение если нужно.
Примерный шаблон кода (WPF, C# — адаптируйте вызовы под Flyleaf API)
(замените media.SeekAsync/PlayAsync/PositionChanged на реальные методы вашей библиотеки)
private bool _wasPlaying;
private bool _isSeeking;
// подписка на события слайдера (Thumb.DragStarted/DragCompleted) или используйте Slider.DragStarted/DragCompleted
private void Slider_DragStarted(object sender, DragStartedEventArgs e)
{
_isSeeking = true;
_wasPlaying = media.IsPlaying; // или media.State == Playing
if (_wasPlaying)
media.Pause(); // или await media.PauseAsync();
}
private void Slider_DragCompleted(object sender, DragCompletedEventArgs e)
{
var seconds = TimeSpan.FromSeconds(seekSlider.Value);
_ = SeekToAsync(seconds); // fire-and-forget или await при необходимости
}
private async Task SeekToAsync(TimeSpan position)
{
try
{
// Если API асинхронный:
await media.SeekAsync(position); // <-- адаптировать
// Если асинхронного метода нет, выполняйте в background потоке, чтобы не блокировать UI:
// await Task.Run(() => media.Seek(position));
}
catch (Exception ex)
{
// логировать
}
finally
{
_isSeeking = false;
if (_wasPlaying)
await media.PlayAsync(); // или media.Play();
}
}
// Обновление слайдера из позиции плеера
private void OnMediaPositionChanged(object sender, TimeSpan newPosition)
{
if (_isSeeking) return;
// обновляем seekSlider.Value = newPosition.TotalSeconds;
}
Практические замечания и рекомендации
- Используйте DragStarted/DragCompleted вместо ValueChanged, чтобы не делать сотни seek во время перетаскивания.
- Если хотите интерактивную перемотку (когда во время перетаскивания вы хотите видеть кадры), реализуйте «throttle»/«debounce»: выполнять seek не чаще, чем, например, раз в 200–300 мс.
- После seek обязательно очистите (flush) внутренние очереди декодера и рендерера, иначе можно увидеть «старые» кадры.
- Для точной перемотки: если библиотека поддерживает флаги AVSEEK_FLAG_FRAME или точный seek, используйте их; иначе делайте seek к ближайшему ключевому кадру (обычно быстрее), затем декодируйте до нужного PTS.
- Синхронизация аудио: обычно нужно остановить/перемотать аудио декодер и таймер воспроизведения, иначе звук может "прыгать".
- Обработайте случаи, когда пользователь многократно быстро перетаскивает — используйте CancellationToken для прерывания предыдущего seek и выполнения только последнего.
- Показывайте индикатор «буферизации» или спиннер при длительном seek, чтобы пользователь понимал, что происходит.
Примеры особых вызовов (возможные вариации API)
- media.Seek(TimeSpan) — синхронно.
- await media.SeekAsync(TimeSpan) — асинхронно.
- media.Position = TimeSpan — если библиотека поддерживает установку позиции напрямую.
- media.Seek(position, SeekFlags.Backward | SeekFlags.Frame) — если доступны флаги FFmpeg.
Если хотите, я могу:
- Посмотреть ваш текущий код слайдера и обработчиков (вставьте фрагмент) и показать, как его изменить.
- Подстроить пример под конкретные методы Flyleaf (если напишете точные имена методов/классов, которые используете).
Напишите, пожалуйста, фрагмент кода, который у вас сейчас, и версию Flyleaf — тогда дам точный пример с вызовами именно этой библиотеки.