From 30cfc95fa2a4dd04eeae2fa65141da3f08f839d8 Mon Sep 17 00:00:00 2001 From: dirtydishes Date: Tue, 26 May 2026 22:22:37 -0400 Subject: [PATCH] Soften center play pause indicator glass --- Dreamio/NativePlayerViewController.swift | 55 ++- ...2026-05-26-center-play-pause-contrast.html | 422 ++++++++++++++++++ 2 files changed, 468 insertions(+), 9 deletions(-) create mode 100644 docs/turns/2026-05-26-center-play-pause-contrast.html diff --git a/Dreamio/NativePlayerViewController.swift b/Dreamio/NativePlayerViewController.swift index 90419a1..652c253 100644 --- a/Dreamio/NativePlayerViewController.swift +++ b/Dreamio/NativePlayerViewController.swift @@ -77,6 +77,7 @@ final class NativePlayerViewController: UIViewController { private let audioButton = NativePlayerViewController.iconButton(systemName: "waveform.circle", label: "Audio Track") private let captionsButton = NativePlayerViewController.iconButton(systemName: "captions.bubble", label: "Subtitles") + private let centerPlayPauseIndicator = NativePlayerViewController.centerGlassIndicator() private let centerPlayPauseButton = NativePlayerViewController.iconButton(systemName: "play.fill", label: "Toggle Playback", pointSize: 34) private let elapsedLabel: UILabel = { @@ -326,6 +327,7 @@ final class NativePlayerViewController: UIViewController { view.addSubview(tapSurfaceView) view.addSubview(loadingContainer) view.addSubview(failureContainer) + view.addSubview(centerPlayPauseIndicator) view.addSubview(centerPlayPauseButton) view.addSubview(controlsContainer) view.addSubview(scrubTimeBubble) @@ -345,7 +347,10 @@ final class NativePlayerViewController: UIViewController { captionsButton.showsMenuAsPrimaryAction = true playPauseButton.layer.cornerRadius = 28 centerPlayPauseButton.layer.cornerRadius = 34 + centerPlayPauseButton.backgroundColor = .clear + centerPlayPauseButton.layer.borderWidth = 0 centerPlayPauseButton.alpha = 0 + centerPlayPauseIndicator.alpha = 0 scrubber.addTarget(self, action: #selector(scrubbingStarted), for: .touchDown) scrubber.addTarget(self, action: #selector(scrubberChanged), for: .valueChanged) scrubber.addTarget(self, action: #selector(scrubbingEnded), for: [.touchUpInside, .touchUpOutside, .touchCancel]) @@ -413,10 +418,14 @@ final class NativePlayerViewController: UIViewController { failureContainer.widthAnchor.constraint(lessThanOrEqualTo: view.safeAreaLayoutGuide.widthAnchor, constant: -40), failureContainer.widthAnchor.constraint(lessThanOrEqualToConstant: 420), - centerPlayPauseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor), - centerPlayPauseButton.centerYAnchor.constraint(equalTo: view.centerYAnchor), - centerPlayPauseButton.widthAnchor.constraint(equalToConstant: 68), - centerPlayPauseButton.heightAnchor.constraint(equalToConstant: 68), + centerPlayPauseIndicator.centerXAnchor.constraint(equalTo: view.centerXAnchor), + centerPlayPauseIndicator.centerYAnchor.constraint(equalTo: view.centerYAnchor), + centerPlayPauseIndicator.widthAnchor.constraint(equalToConstant: 68), + centerPlayPauseIndicator.heightAnchor.constraint(equalToConstant: 68), + centerPlayPauseButton.centerXAnchor.constraint(equalTo: centerPlayPauseIndicator.centerXAnchor), + centerPlayPauseButton.centerYAnchor.constraint(equalTo: centerPlayPauseIndicator.centerYAnchor), + centerPlayPauseButton.widthAnchor.constraint(equalTo: centerPlayPauseIndicator.widthAnchor), + centerPlayPauseButton.heightAnchor.constraint(equalTo: centerPlayPauseIndicator.heightAnchor), closeButton.widthAnchor.constraint(equalToConstant: 44), closeButton.heightAnchor.constraint(equalToConstant: 44), @@ -827,18 +836,46 @@ final class NativePlayerViewController: UIViewController { private func flashCenterPlayPauseIcon() { centerPlayPauseButton.setImage(UIImage(systemName: backend.isPlaying ? "pause.fill" : "play.fill"), for: .normal) - guard !UIAccessibility.isReduceMotionEnabled else { return } - centerPlayPauseButton.transform = CGAffineTransform(scaleX: 0.86, y: 0.86) - UIView.animate(withDuration: 0.16, animations: { + if UIAccessibility.isReduceMotionEnabled { + centerPlayPauseIndicator.transform = .identity + centerPlayPauseButton.transform = .identity + centerPlayPauseIndicator.alpha = 1 + centerPlayPauseButton.alpha = 1 + UIView.animate(withDuration: 0.18, delay: 0.55, options: [.curveEaseOut]) { + self.centerPlayPauseIndicator.alpha = 0 + self.centerPlayPauseButton.alpha = 0 + } + return + } + let initialScale = CGAffineTransform(scaleX: 0.86, y: 0.86) + centerPlayPauseIndicator.transform = initialScale + centerPlayPauseButton.transform = initialScale + UIView.animate(withDuration: 0.18, delay: 0, options: [.curveEaseOut]) { + self.centerPlayPauseIndicator.alpha = 1 self.centerPlayPauseButton.alpha = 1 + self.centerPlayPauseIndicator.transform = .identity self.centerPlayPauseButton.transform = .identity - }) { _ in - UIView.animate(withDuration: 0.22, delay: 0.28, options: [.curveEaseOut]) { + } completion: { _ in + UIView.animate(withDuration: 0.28, delay: 0.32, options: [.curveEaseOut]) { + self.centerPlayPauseIndicator.alpha = 0 self.centerPlayPauseButton.alpha = 0 } } } + private static func centerGlassIndicator() -> UIVisualEffectView { + let view = glassPanel(cornerRadius: 34) + view.backgroundColor = UIColor.white.withAlphaComponent(0.08) + view.layer.borderColor = UIColor.white.withAlphaComponent(0.18).cgColor + view.layer.borderWidth = 0.75 + view.layer.shadowColor = UIColor.black.cgColor + view.layer.shadowOpacity = 0.28 + view.layer.shadowRadius = 16 + view.layer.shadowOffset = CGSize(width: 0, height: 6) + view.isUserInteractionEnabled = false + return view + } + private static func glassPanel(cornerRadius: CGFloat) -> UIVisualEffectView { let effect: UIVisualEffect if #available(iOS 26.0, *) { diff --git a/docs/turns/2026-05-26-center-play-pause-contrast.html b/docs/turns/2026-05-26-center-play-pause-contrast.html new file mode 100644 index 0000000..ff51049 --- /dev/null +++ b/docs/turns/2026-05-26-center-play-pause-contrast.html @@ -0,0 +1,422 @@ + + + + + + Improve Center Play/Pause Indicator Contrast + + + + + + +
+
+

Dreamio turn document

+

Improve Center Play/Pause Indicator Contrast

+

Improved the native player center play/pause flash so it remains visible over varied video content.

+
+ 2026-05-26 + Native player controls + Contrast fix +
+
+ +
+
+

Summary

+

Improved the native player center play/pause flash so it remains visible over varied video content.

+
+ +
+

Changes Made

+
    +
  • Made the center play/pause flash larger, darker, and more defined with a stronger border and shadow.
  • +
  • Kept the indicator visible for longer during the flash animation and added a reduced-motion fade path.
  • +
+
+ +
+

Context

+

The center-screen play/pause indicator was too transparent, making it hard to see against bright or busy video frames.

+
+ +
+

Important Implementation Details

+
    +
  • The existing Liquid Glass-style icon button remains in use, but the transient center instance now gets a high-contrast black backing instead of the low-alpha shared control treatment.
  • +
  • The hit target and visual footprint increased from 68 to 80 points while keeping the indicator centered.
  • +
  • Reduced Motion users now still receive a non-scaling visible flash instead of skipping the indicator entirely.
  • +
+
+ +
+

Relevant Diff Snippets

+
+
+

Dreamio/NativePlayerViewController.swift · center play/pause indicator contrast

+
+
Dreamio/NativePlayerViewController.swift
-6+20
343 unmodified lines
344
345
346
347
348
349
350
64 unmodified lines
415
416
417
418
419
420
421
422
404 unmodified lines
827
828
829
830
831
832
833
834
835
836
837
838
839
343 unmodified lines
audioButton.showsMenuAsPrimaryAction = true
captionsButton.showsMenuAsPrimaryAction = true
playPauseButton.layer.cornerRadius = 28
centerPlayPauseButton.layer.cornerRadius = 34
centerPlayPauseButton.alpha = 0
scrubber.addTarget(self, action: #selector(scrubbingStarted), for: .touchDown)
scrubber.addTarget(self, action: #selector(scrubberChanged), for: .valueChanged)
64 unmodified lines
+
centerPlayPauseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
centerPlayPauseButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
centerPlayPauseButton.widthAnchor.constraint(equalToConstant: 68),
centerPlayPauseButton.heightAnchor.constraint(equalToConstant: 68),
+
closeButton.widthAnchor.constraint(equalToConstant: 44),
closeButton.heightAnchor.constraint(equalToConstant: 44),
404 unmodified lines
+
private func flashCenterPlayPauseIcon() {
centerPlayPauseButton.setImage(UIImage(systemName: backend.isPlaying ? "pause.fill" : "play.fill"), for: .normal)
guard !UIAccessibility.isReduceMotionEnabled else { return }
centerPlayPauseButton.transform = CGAffineTransform(scaleX: 0.86, y: 0.86)
UIView.animate(withDuration: 0.16, animations: {
self.centerPlayPauseButton.alpha = 1
self.centerPlayPauseButton.transform = .identity
}) { _ in
UIView.animate(withDuration: 0.22, delay: 0.28, options: [.curveEaseOut]) {
self.centerPlayPauseButton.alpha = 0
}
}
343 unmodified lines
344
345
346
347
348
349
350
351
352
353
354
355
356
357
64 unmodified lines
422
423
424
425
426
427
428
429
404 unmodified lines
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
343 unmodified lines
audioButton.showsMenuAsPrimaryAction = true
captionsButton.showsMenuAsPrimaryAction = true
playPauseButton.layer.cornerRadius = 28
centerPlayPauseButton.layer.cornerRadius = 40
centerPlayPauseButton.backgroundColor = UIColor.black.withAlphaComponent(0.58)
centerPlayPauseButton.layer.borderColor = UIColor.white.withAlphaComponent(0.38).cgColor
centerPlayPauseButton.layer.borderWidth = 1.5
centerPlayPauseButton.layer.shadowColor = UIColor.black.cgColor
centerPlayPauseButton.layer.shadowOpacity = 0.45
centerPlayPauseButton.layer.shadowRadius = 18
centerPlayPauseButton.layer.shadowOffset = CGSize(width: 0, height: 8)
centerPlayPauseButton.alpha = 0
scrubber.addTarget(self, action: #selector(scrubbingStarted), for: .touchDown)
scrubber.addTarget(self, action: #selector(scrubberChanged), for: .valueChanged)
64 unmodified lines
+
centerPlayPauseButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
centerPlayPauseButton.centerYAnchor.constraint(equalTo: view.centerYAnchor),
centerPlayPauseButton.widthAnchor.constraint(equalToConstant: 80),
centerPlayPauseButton.heightAnchor.constraint(equalToConstant: 80),
+
closeButton.widthAnchor.constraint(equalToConstant: 44),
closeButton.heightAnchor.constraint(equalToConstant: 44),
404 unmodified lines
+
private func flashCenterPlayPauseIcon() {
centerPlayPauseButton.setImage(UIImage(systemName: backend.isPlaying ? "pause.fill" : "play.fill"), for: .normal)
if UIAccessibility.isReduceMotionEnabled {
centerPlayPauseButton.transform = .identity
centerPlayPauseButton.alpha = 1
UIView.animate(withDuration: 0.18, delay: 0.55, options: [.curveEaseOut]) {
self.centerPlayPauseButton.alpha = 0
}
return
}
centerPlayPauseButton.transform = CGAffineTransform(scaleX: 0.82, y: 0.82)
UIView.animate(withDuration: 0.16, animations: {
self.centerPlayPauseButton.alpha = 1
self.centerPlayPauseButton.transform = .identity
}) { _ in
UIView.animate(withDuration: 0.26, delay: 0.34, options: [.curveEaseOut]) {
self.centerPlayPauseButton.alpha = 0
}
}
+
+
+
+
+

Diffs are generated with @pierre/diffs/ssr at documentation time. Each file diff stays inside its own shell so the page remains readable as a static HTML artifact.

+
+ +
+

Expected Impact for End-Users

+

Users should be able to quickly confirm play or pause state changes from the middle of the screen, even over bright scenes or high-motion content.

+
+ +
+

Validation

+
    +
  • xcodebuild -workspace Dreamio.xcworkspace -scheme Dreamio -sdk iphonesimulator -configuration Debug build — succeeded.
  • +
+
+ +
+

Issues, Limitations, and Mitigations

+
    +
  • No known issues. This is a visual contrast adjustment scoped to the native player overlay.
  • +
+
+ +
+

New Changes as of 2026-05-26 22:22

+

Summary of changes

+

Softened the center play/pause flash from a black badge with a strong outline into a separate liquid-glass blur plate behind a clear icon button.

+

Why this change was made

+

The previous contrast fix made the transient center indicator visible, but overcorrected with a heavy black fill and outline. The revised treatment keeps the indicator readable while better matching the native Liquid Glass direction.

+

Code diffs

+
+
Dreamio/NativePlayerViewController.swift — center indicator glass treatment
+
+ private let centerPlayPauseIndicator = NativePlayerViewController.centerGlassIndicator()
+...
++ view.addSubview(centerPlayPauseIndicator)
+  view.addSubview(centerPlayPauseButton)
+...
+- centerPlayPauseButton.backgroundColor = UIColor.black.withAlphaComponent(0.58)
+- centerPlayPauseButton.layer.borderColor = UIColor.white.withAlphaComponent(0.38).cgColor
+- centerPlayPauseButton.layer.borderWidth = 1.5
++ centerPlayPauseButton.backgroundColor = .clear
++ centerPlayPauseButton.layer.borderWidth = 0
++ centerPlayPauseIndicator.alpha = 0
+...
++ private static func centerGlassIndicator() -> UIVisualEffectView {
++     let view = glassPanel(cornerRadius: 34)
++     view.backgroundColor = UIColor.white.withAlphaComponent(0.08)
++     view.layer.borderColor = UIColor.white.withAlphaComponent(0.18).cgColor
++     view.layer.borderWidth = 0.75
++     view.isUserInteractionEnabled = false
++     return view
++ }
+
+

Related issues or PRs

+

No new Beads issue or PR was created for this small visual follow-up.

+
+ +
+

Follow-up Work

+
    +
  • None currently identified.
  • +
+
+
+
+ +