Commit d72262d7 authored by Kourser's avatar Kourser
Browse files

feat(player): add app-level volume control

A dedicated slider in the player, persisted and applied on top of the
system volume, so playback can go quieter than the lowest hardware
volume step (e.g. falling asleep with earphones). Applied to both audio
backends (AVPlayer and the AVAudioEngine mixer) so it works for
streaming and downloaded episodes alike, and survives engine switches.

Co-Authored-By: Claude (RCA)
parent e65ae5e2
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -7,6 +7,11 @@ adopte le [versionnage sémantique](https://semver.org/lang/fr/).

## [Non publié]

### Ajouté
- **Volume de l'application** : curseur dédié dans le lecteur, appliqué en plus
  du volume système — pour écouter plus bas que le premier cran matériel
  (streaming et épisodes téléchargés).

### À venir
- Intégrations système : Widget, CarPlay, Apple Watch, Siri / Raccourcis.
- Langues de l'UE manquantes (maltais, irlandais, letton, lituanien, estonien) —
+4 −0
Original line number Diff line number Diff line
@@ -66,6 +66,10 @@ public final class AVAudioPlayerEngine: AudioPlayerEngine {
        }
    }

    public func setVolume(_ volume: Float) {
        player.volume = min(1, max(0, volume))
    }

    private func handleTick(_ time: CMTime) {
        let seconds = time.seconds
        if seconds.isFinite {
+5 −0
Original line number Diff line number Diff line
@@ -19,11 +19,16 @@ public protocol AudioPlayerEngine: AnyObject {
    /// Applies per-band equalizer gains (dB), or `nil` to bypass. Engines that
    /// don't support it ignore this (default no-op).
    func setEqualizer(_ gains: [Float]?)
    /// Sets the app-level playback volume (0 = silent, 1 = full), applied on
    /// top of the system volume. Engines that don't support it ignore this
    /// (default no-op).
    func setVolume(_ volume: Float)
}

public extension AudioPlayerEngine {
    func setSkipSilence(_ enabled: Bool) {}
    func setEqualizer(_ gains: [Float]?) {}
    func setVolume(_ volume: Float) {}
}

@MainActor
+6 −0
Original line number Diff line number Diff line
@@ -163,6 +163,12 @@ public final class PlaybackController {
        engine.setEqualizer(gains)
    }

    /// Sets the app-level playback volume (0...1), applied on top of the
    /// system volume.
    public func setVolume(_ volume: Float) {
        engine.setVolume(volume)
    }

    private func clamp(_ time: TimeInterval) -> TimeInterval {
        let lower = max(0, time)
        return duration > 0 ? min(lower, duration) : lower
+4 −0
Original line number Diff line number Diff line
@@ -122,6 +122,10 @@ public final class SilenceSkippingEngine: AudioPlayerEngine {
        applyEqualizer()
    }

    public func setVolume(_ volume: Float) {
        engine.mainMixerNode.outputVolume = min(1, max(0, volume))
    }

    /// Applies the stored gains to the EQ bands, or bypasses the unit when off.
    private func applyEqualizer() {
        guard let gains = eqGains else { eq.bypass = true; return }
Loading