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:
| Input | Handler | ViewModel call |
|---|---|---|
| Pointer pressed / released | PointerPressedEventHandler / PointerReleasedEventHandler | OnSeekBarPointerEvent(bool) β sets _timeChangeOverride |
| Slider value changed | SeekBarSlider.ValueChanged (XAML binding) | OnSeekBarValueChanged |
| Mouse wheel | SeekBarSlider_OnPointerWheelChanged | OnSeekBarPointerWheelChanged |
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 totruewhile the user is dragging the thumb; suppresses position feedback fromPositionChangedso 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#
| Caller | Debounce |
|---|---|
OnSeekBarValueChanged | true during drag; false if paused or large jump (>400 ms from current) |
ChangeTimeRequestMessage receiver | Passed through from message sender |
SeekToChapter command | false β instant chapter jump |
RestoreLastPosition | false β 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 msshouldOverride:_timeChangeOverrideis active AND difference >100 mspaused: player is paused or buffering (always seek immediately when paused)
Mouse-wheel seek steps#
OnSeekBarPointerWheelChanged maps modifier keys to step sizes:
| Modifier | Step |
|---|---|
| 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 aPositionChangedResult.TimeChangeOverrideMessageβ sets_timeChangeOverrideexternally, 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.Endedand the new position is notNaturalDuration,Replay()is called first (stop + play) before seeking . - When paused, LibVLC does not fire
TimeChanged, soVlcMediaPlayermanually firesPositionChangedafter 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#
| File | Role |
|---|---|
SeekBar.xaml.cs | UI control, pointer/wheel event routing |
SeekBarViewModel.cs | Throttle/debounce, position state, messaging |
VlcMediaPlayer.cs | LibVLC Position β VlcPlayer.Time translation |
PlayerService.cs | LibVLC initialization; media creation with per-item options |