Skip to content

[iOS] VoiceOver support in the video player - #2258

Open
zoli211 wants to merge 7 commits into
jellyfin:mainfrom
zoli211:player-voiceover
Open

zoli211 wants to merge 7 commits into
jellyfin:mainfrom
zoli211:player-voiceover

Conversation

@zoli211

@zoli211 zoli211 commented Aug 27, 2026

Copy link
Copy Markdown

Fixes #1733

Contributes to #1738

Summary

The video player was close to unusable with VoiceOver. Once the playback controls faded there was no element left to focus, no way to bring them back, and usually no way out of the player. VoiceOver also read straight through to the item view behind it, the controls that were on screen didn't say what they were, and the progress bar couldn't be moved at all.

There is one commit per fix.

The player is presented with a custom presentation controller rather than a system one, so it doesn't inherit the isolation a modal presentation provides. The outermost presented view is now marked modal while the player is up.

An accessibility element stands in for the video, placed beneath the controls, and toggles them when activated. This is the approach from #1741, whose accessibilityToggleOverlay I kept and credited on the commit. While the controls are hidden the element collapses to a corner target rather than tracing a focus ring around the whole screen.

The decorative elements are hidden. The dimming, the gradient that covers the bottom 55% of the screen, GestureView, the scrubbing preview and the slow scrubbing indicator were all silent elements to swipe past. isVisible now hides from accessibility as well as setting opacity, so faded controls aren't focusable either.

The controls no longer disappear from under someone still reading them. With an assistive technology running they only fade with focus resting on the video, which is how they are dismissed there.

The jump buttons read "15s" without saying which way they jump, and the timestamps were digit patterns like 1:23:45, read as a run of separate numbers. Both are labelled now, and a spokenRuntime format style spells the durations out. Gestures that report only through a toast are announced.

The progress bar is a ProgressView driven by a drag gesture, so it carried no value and no way to move it. It is now a single adjustable element that seeks by the same interval as the jump buttons.

Testing

  • Built and ran on an iPhone 16 Pro, iOS 26.6, with VoiceOver on
  • SwiftFormat, SwiftLint (--strict) and FindUnusedStrings pass

Not covered

The tvOS half of #1738. The timestamps in the tvOS player are untouched, and the only change reaching tvOS at all is the shared isVisible modifier.

zoli211 and others added 7 commits September 2, 2026 10:38
`RuntimeFormatStyle` produces digit patterns like 1:23:45. VoiceOver
reads those as a run of separate numbers rather than a length of time,
so accessibility values need a spelled out counterpart.

Contributes to jellyfin#1738.
The player is presented with a custom presentation controller rather
than a system one, so it doesn't inherit the isolation a modal
presentation provides and VoiceOver keeps reaching the item view behind
it. Mark the outermost presented view as modal while the player is up,
and move focus into the player when it appears.
Everything behind the controls is decorative: the video surface itself,
the dimming, and the gradient, which alone claims the bottom 55% of the
screen. Left visible they are silent elements to swipe past. The same
goes for the scrubbing preview and the slow scrubbing indicator.

`GestureView` has no label to give, so it reads as an unnamed button
that still fires its tap gesture when activated. Every gesture it
carries is offered elsewhere through a named control, so hide it and
wrap the representable to keep the modifier out of its callers.

Hiding a view with `isVisible` only set its opacity, leaving faded
controls focusable but invisible.
The playback controls are shown and hidden with a tap gesture, which
assistive technologies intercept before it reaches the player. The video
has no accessibility element of its own, so once the controls fade there
is nothing left to focus and no way to bring them back, which usually
leaves no way out of the player either. Fixes jellyfin#1733.

Add an element standing in for the video, placed beneath the controls so
that exploring by touch finds a control where there is one and the video
everywhere else. Activating it toggles the controls.

While the controls are hidden the element is a corner target rather than
the full screen, since a full screen element traces its focus ring
around the whole video for no reason, and focus moves to it because it
is the only thing left to reach.

Playback moves the position every second, and a value that changes under
focus is read out again on every change, so the announced position is
held still while the element has focus.

Co-authored-by: samglt <191187090+samglt@users.noreply.github.com>
The controls fade after five seconds regardless of what is happening, so
they disappear out from under someone still swiping through them.

While an assistive technology is running, only let them fade with focus
resting on the video, since that is how they are dismissed there.
Holding the fade timer is not enough on its own, as button presses and
overlay changes poke it back to life, so the fade itself checks too.
The jump buttons carried their interval as their label, so VoiceOver read
"15s" without saying which way it jumps. The timestamps were built from
raw digit patterns, read as a run of separate numbers rather than a
length of time, and neither said what tapping it does. Contributes
to jellyfin#1738.

The episode title and its season and episode label were two elements in
the toolbar, read one after the other.

Several gestures report only through a toast, which is purely visual, so
announce those as well.
The progress bar is a `ProgressView` driven by a drag gesture rather than
a slider, so it carried no value and no way to move it. Assistive
technology users could tell how far playback had got only by opening the
controls and reading the timestamps, and could not seek at all.

Expose it as one adjustable element. Swiping up and down seeks by the
same interval as the jump buttons, so the step follows whatever was
already chosen there. The scrubbed value moves before the seek is
requested, otherwise the position announced is a step behind. The track
is ten points tall, so widen its accessibility hit area.
@zoli211 zoli211 changed the title [iOS] VoiceOver support in the video player VoiceOver support in the video player, and reading the subtitles aloud Sep 2, 2026
@JPKribs JPKribs added enhancement New feature or request playback This item is related to content playback labels Sep 2, 2026

@LePips LePips left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for wanting to contribute accessibility settings. However, I will have to request to remove the subtitle reading. I have been working on some exciting developments that would enable you to contribute that in the future.

@zoli211

zoli211 commented Sep 2, 2026

Copy link
Copy Markdown
Author

Hey! If I submit subtitle reading as a separate feature PR, would it be possible to get it into the repo? Right now, there’s no way to consume any content with subtitles using VoiceOver, and even information from forced subtitles gets lost. It feels like a pretty basic accessibility feature to have. Thanks so much!

@zoli211 zoli211 changed the title VoiceOver support in the video player, and reading the subtitles aloud [iOS] VoiceOver support in the video player Sep 4, 2026
@zoli211

zoli211 commented Sep 4, 2026

Copy link
Copy Markdown
Author

Removed — the last commit is gone, the PR is back to the player fixes only. Happy to wait for what you're working on and bring the subtitle reading separately once it lands.

@JPKribs JPKribs added accessibility Settings, changes, or issues to assist with accessibility and removed enhancement New feature or request labels Sep 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

accessibility Settings, changes, or issues to assist with accessibility playback This item is related to content playback

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[iOS] Player overlay not accessible from VoiceOver

3 participants