Dosu LogoDosu Logo
Ask
Join our Discord
ScreenboxPublic
Starpine
DocumentsScreenbox
Seek and Scrubbing
Seek and Scrubbing
Type
Topic
Status
Published
Created
Aug 5, 2026
Updated
Aug 5, 2026
Created by
Dosu Bot
Updated by
Dosu Bot

Seek and Scrubbing#

Screenbox's seek pipeline has three distinct layers: (1) a UI control (SeekBar) that captures pointer events and wheel input, (2) SeekBarViewModel that throttles/debounces seek requests and manages the Time observable, and (3) VlcMediaPlayer that translates position writes into VlcPlayer.Time calls against LibVLC. There are no hard-coded fast-seek/precise-seek mode flags in the codebase; codec-level tuning (e.g. --avcodec-skip-frame) must be passed via the user-configurable GlobalArguments setting or per-media options.


SeekBar (UI Layer)#

SeekBar.xaml.cs is a UWP UserControl wrapping a Slider. It forwards three categories of input to SeekBarViewModel:

InputHandlerViewModel call
Pointer pressed / releasedPointerPressedEventHandler / PointerReleasedEventHandlerOnSeekBarPointerEvent(bool) — sets _timeChangeOverride
Slider value changedSeekBarSlider.ValueChanged (XAML binding)OnSeekBarValueChanged
Mouse wheelSeekBarSlider_OnPointerWheelChangedOnSeekBarPointerWheelChanged

The control also drives a preview tooltip that shows a time-at-position label while the pointer hovers, updating it on every PointerMoved event via UpdatePreviewTime.


SeekBarViewModel (Throttle / Debounce Layer)#

SeekBarViewModel is the central coordinator. Key fields:

  • _seekTimer (DispatcherQueueTimer) — debounces actual seeks to LibVLC by 50 ms .
  • _timeChangeOverride — set to true while the user is dragging the thumb; suppresses position feedback from PositionChanged so the UI doesn't fight the user .
  • _originalPositionTimer — debounces the capture of the pre-seek position (used to report jump distance in OSD overlays) .

SetPlayerPosition — the seek gate#

SetPlayerPosition(TimeSpan position, bool debounce)

SetPlayerPosition is the single point that writes to MediaPlayer.Position:

  • If debounce=true → calls _seekTimer.Debounce(…, 50 ms). Subsequent writes within 50 ms collapse into one LibVLC seek.
  • If debounce=false → cancels any pending debounce timer and seeks immediately.

When debounce=false is used#

CallerDebounce
OnSeekBarValueChangedtrue during drag; false if paused or large jump (>400 ms from current)
ChangeTimeRequestMessage receiverPassed through from message sender
SeekToChapter commandfalse — instant chapter jump
RestoreLastPositionfalse — startup position restore

Slider value-change heuristic#

OnSeekBarValueChanged only triggers a seek when the new slider value differs from Time by more than 50 ms (to distinguish user input from programmatic updates via the OneWay binding). It then decides whether to forward the seek based on:

  • shouldUpdate: new position differs from current player position by >400 ms
  • shouldOverride: _timeChangeOverride is active AND difference >100 ms
  • paused: player is paused or buffering (always seek immediately when paused)

Mouse-wheel seek steps#

OnSeekBarPointerWheelChanged maps modifier keys to step sizes:

ModifierStep
None±5 000 ms
Ctrl±10 000 ms
Shift±2 000 ms

Position feedback loop#

OnPositionChanged skips updating Time whenever _seekTimer.IsRunning or _timeChangeOverride is active — preventing the seek bar from snapping back to the player's current (pre-seek) position during an in-flight debounce.

Messaging#

Two MVVM Toolkit messages interact with seeks:

  • ChangeTimeRequestMessage — any component can request a seek with (TimeSpan value, bool isOffset, bool debounce). The reply is a PositionChangedResult .
  • TimeChangeOverrideMessage — sets _timeChangeOverride externally, e.g. from gesture handlers .

VlcMediaPlayer (LibVLC Layer)#

VlcMediaPlayer.Position setter translates a TimeSpan to VlcPlayer.Time = ms (millisecond integer). Notable edge cases:

  • If VlcPlayer.Length < 0 (media not ready), the write is silently dropped .
  • If the player state is VLCState.Ended and the new position is not NaturalDuration, Replay() is called first (stop + play) before seeking .
  • When paused, LibVLC does not fire TimeChanged, so VlcMediaPlayer manually fires PositionChanged after a paused seek .

Seekability is tracked via the SeekableChanged LibVLC event, which sets CanSeek and bubbles up to SeekBarViewModel.IsSeekable.

Frame stepping uses VlcPlayer.NextFrame() (forward) and a fixed 42 ms backward step , not frame-accurate reverse navigation.


LibVLC Initialization & Seek-Related Options#

PlayerService.InitializeLibVlc sets only three global flags: --verbose, --aout=winstore, and --no-osd. There are no built-in fast-seek or codec-skip options.

Users can inject any LibVLC option (including seek-tuning flags like --avcodec-skip-frame=bidir) through the GlobalArguments setting, which is appended to the initialization arguments at startup.

Configurable seek step durations (rewind and fast-forward) are stored in ISettingsService as PlayerRewindStep and PlayerFastForwardStep (default: 5 s).


Key Files#

FileRole
SeekBar.xaml.csUI control, pointer/wheel event routing
SeekBarViewModel.csThrottle/debounce, position state, messaging
VlcMediaPlayer.csLibVLC Position → VlcPlayer.Time translation
PlayerService.csLibVLC initialization; media creation with per-item options
Documents
Media File Activation
Media Library Change Tracking
Seek and Scrubbing
SQLite Database Management
Storage Library Permissions
Video Rendering