web-dev-qa-db-fra.com

Comment obtenir le niveau de volume audio et les notifications de changement de volume sur iOS?

J'écris une application très simple qui émet un son en appuyant sur un bouton. Étant donné que ce bouton n'a pas beaucoup de sens lorsque l'appareil est réglé sur silence, je veux le désactiver lorsque le volume audio de l'appareil est à zéro. (Et réactivez-le ensuite lorsque le volume est à nouveau augmenté.)

Je cherche un moyen de travail (et sûr pour l'AppStore) pour détecter le paramètre de volume actuel et obtenir une notification/un rappel lorsque le niveau de volume change. Je ne souhaite pas modifier le réglage du volume .

Tout cela est implémenté dans mon ViewController où ledit bouton est utilisé. J'ai testé cela avec un iPhone 4 exécutant iOS 4.0.1 et 4.0.2 ainsi qu'un iPhone 3G exécutant 4.0.1. Construit avec iOS SDK 4.0.2 avec llvm 1.5. (L'utilisation de gcc ou llvm-gcc n'améliore rien.) Il n'y a aucun problème lors de l'implémentation de la construction dans les deux cas, ni erreurs ni avertissements. L'analyseur statique est également satisfait.

Voici ce que j'ai essayé jusqu'à présent, le tout sans succès.

En suivant la documentation des services audio d'Apple, je devrais enregistrer un AudioSessionAddPropertyListener pour kAudioSessionProperty_CurrentHardwareOutputVolume qui devrait fonctionner comme ceci:

// Registering for Volume Change notifications
AudioSessionInitialize(NULL, NULL, NULL, NULL);
returnvalue = AudioSessionAddPropertyListener (

kAudioSessionProperty_CurrentHardwareOutputVolume ,
      audioVolumeChangeListenerCallback,
      self
);

returnvalue est 0, ce qui signifie que l'enregistrement du rappel a fonctionné.

Malheureusement, je ne reçois jamais de rappel de ma fonction audioVolumeChangeListenerCallback lorsque j'appuie sur les boutons de volume de mon appareil, sur le sélecteur de casque ou sur le commutateur de sonnerie silencieuse.

Lorsque vous utilisez exactement le même code pour vous inscrire à kAudioSessionProperty_AudioRouteChange (qui est utilisé comme exemple de projet analogue dans les vidéos de la WWDC, la documentation pour les développeurs et sur de nombreux sites sur les sites Web). En fait , je reçois un rappel lorsque changer l'itinéraire audio (en branchant/déconnectant un casque ou en amarrant l'appareil).

Un utilisateur nommé Doug a ouvert un fil de discussion intitulé le volume de l'iPhone a changé l'événement pour le volume déjà max où il a affirmé qu'il utilisait avec succès de cette façon (à moins que le volume ne change pas réellement car il est déjà réglé au maximum). Pourtant, cela ne fonctionne pas pour moi.

Une autre manière que j'ai essayée est de m'inscrire à NSNotificationCenter comme ceci.

// sharedAVSystemController 
AudioSessionInitialize(NULL, NULL, NULL, NULL);
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter addObserver:self
                                         selector:@selector(volumeChanged:) 
                                             name:@"AVSystemController_SystemVolumeDidChangeNotification" 
                                           object:nil];

Cela devrait informer ma méthode volumeChanged de tout changement de SystemVolume mais ce n'est pas le cas.

Puisque la croyance commune me dit que si l'on travaille trop dur pour réaliser quelque chose avec Cocoa, on fait quelque chose de fondamentalement mauvais, je m'attends à manquer quelque chose ici. Il est difficile de croire qu'il n'y a aucun moyen simple pour obtenir le courant niveau de volume, mais je n'ai pas pu en trouver un en utilisant la documentation d'Apple, un exemple de code, Google, Apple Forums des développeurs ou en regardant des vidéos de la WWDC 2010.

56
MacLemon

Y a-t-il une chance que vous ayez mal fait votre signature pour la méthode volumeChanged:? Cela a fonctionné pour moi, jeté dans mon appdelegate:

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[NSNotificationCenter defaultCenter]
     addObserver:self
     selector:@selector(volumeChanged:)
     name:@"AVSystemController_SystemVolumeDidChangeNotification"
     object:nil];
}

- (void)volumeChanged:(NSNotification *)notification
{
    float volume =
    [[[notification userInfo]
      objectForKey:@"AVSystemController_AudioVolumeNotificationParameter"]
     floatValue];

    // Do stuff with volume
}

Ma méthode volumeChanged: est frappée chaque fois que le bouton est enfoncé, même si le volume ne change pas en conséquence (car il est déjà à max/min).

67
Sandy

L'API AudioSession utilisée par certaines réponses ici est obsolète à partir d'iOS 7. Elle a été remplacée par AVAudioSession, qui expose une propriété outputVolume pour le volume de sortie à l'échelle du système. Cela peut être observé en utilisant KVO pour recevoir des notifications lorsque le volume change, comme indiqué dans la documentation:

Une valeur comprise entre 0,0 et 1,0, 0,0 représentant le volume minimum et 1,0 représentant le volume maximum.

Le volume de sortie à l'échelle du système ne peut être réglé directement que par l'utilisateur; pour contrôler le volume dans votre application, utilisez la classe MPVolumeView.

Vous pouvez observer les modifications de la valeur de cette propriété en utilisant l'observation des valeurs-clés.

Vous devez vous assurer que la session audio de votre application est active pour que cela fonctionne:

let audioSession = AVAudioSession.sharedInstance()
do {
    try audioSession.setActive(true)
    startObservingVolumeChanges()
} catch {
    print(“Failed to activate audio session")
}

Donc, si tout ce dont vous avez besoin est d'interroger le volume système actuel:

let volume = audioSession.outputVolume

Ou nous pouvons être informés des changements comme ceci:

private struct Observation {
    static let VolumeKey = "outputVolume"
    static var Context = 0

}

func startObservingVolumeChanges() {
    audioSession.addObserver(self, forKeyPath: Observation.VolumeKey, options: [.Initial, .New], context: &Observation.Context)
}

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if context == &Observation.Context {
        if keyPath == Observation.VolumeKey, let volume = (change?[NSKeyValueChangeNewKey] as? NSNumber)?.floatValue {
            // `volume` contains the new system output volume...
            print("Volume: \(volume)")
        }
    } else {
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}

N'oubliez pas pour arrêter l'observation avant d'être désalloué:

func stopObservingVolumeChanges() {
    audioSession.removeObserver(self, forKeyPath: Observation.VolumeKey, context: &Observation.Context)
}
49
Stuart
-(float) getVolumeLevel
{
    MPVolumeView *slide = [MPVolumeView new];
    UISlider *volumeViewSlider;

    for (UIView *view in [slide subviews]){
        if ([[[view class] description] isEqualToString:@"MPVolumeSlider"]) {
            volumeViewSlider = (UISlider *) view;
        }
    }

    float val = [volumeViewSlider value];
    [slide release];

    return val;
}

Cela devrait vous donner le niveau de volume actuel. 1 est le volume maximum, 0 n'est pas un volume. Remarque: aucun élément d'interface utilisateur ne doit être affiché pour que cela fonctionne. Notez également que le niveau de volume actuel est relatif aux écouteurs ou aux haut-parleurs (ce qui signifie que les deux niveaux de volume sont différents, ce qui vous permet de choisir celui que l'appareil utilise actuellement. Cela ne répond pas à votre question concernant la réception de notifications lorsque le volume change.

6
Mike

avez-vous commencé la session audio avec AudioSessionSetActive

4
Karsten

Version Swift 3 de l'excellente réponse de Stuart:

let audioSession = AVAudioSession.sharedInstance()

do {
    try audioSession.setActive(true)
    startObservingVolumeChanges()
} 
catch {
    print("Failed to activate audio session")
}

let volume = audioSession.outputVolume

private struct Observation {
    static let VolumeKey = "outputVolume"
    static var Context = 0
}

func startObservingVolumeChanges() {
    audioSession.addObserver(self, forKeyPath: Observation.VolumeKey, options: [.Initial, .New], context: &Observation.Context)
}

override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if context == &Observation.Context {
        if keyPath == Observation.VolumeKey, let volume = (change?[NSKeyValueChangeNewKey] as? NSNumber)?.floatValue {
            // `volume` contains the new system output volume...
            print("Volume: \(volume)")
        }
    } else {
        super.observeValueForKeyPath(keyPath, ofObject: object, change: change, context: context)
    }
}
2
Joshua C. Lerner

Ajout à la réponse de Stuart en utilisant AVAudioSession pour tenir compte de certains changements dans Swift 3. J'espère que le code indiquera clairement où va chaque composant.

override func viewWillAppear(_ animated: Bool) {
    listenVolumeButton()
}

func listenVolumeButton(){
   let audioSession = AVAudioSession.sharedInstance()
   do{
       try audioSession.setActive(true)
       let vol = audioSession.outputVolume
       print(vol.description) //gets initial volume
     }
   catch{
       print("Error info: \(error)")
   }
   audioSession.addObserver(self, forKeyPath: "outputVolume", options: 
   NSKeyValueObservingOptions.new, context: nil)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if keyPath == "outputVolume"{
        let volume = (change?[NSKeyValueChangeKey.newKey] as 
        NSNumber)?.floatValue
        print("volume " + volume!.description)
    }
}

 override func viewWillDisappear(_ animated: Bool) {
     audioSession.removeObserver(self, forKeyPath: "outputVolume")
 }
1
Abundance

Swift 4

func startObservingVolumeChanges() {
    avAudioSession.addObserver(self, forKeyPath: Observation.VolumeKey, options: [.initial, .new], context: &Observation.Context)
}

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
    if context == &Observation.Context {
        if keyPath == Observation.VolumeKey, let volume = (change?[NSKeyValueChangeKey.newKey] as? NSNumber)?.floatValue {
            print("\(logClassName): Volume: \(volume)")
        }
    } else {
        super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
    }
}

func stopObservingVolumeChanges() {
    avAudioSession.removeObserver(self, forKeyPath: Observation.VolumeKey, context: &Observation.Context)
}

puis vous appelez

var avAudioSession = AVAudioSession.sharedInstance()
try? avAudioSession.setActive(true)
startObservingVolumeChanges()
1
Reimond Hill

Je pense que cela dépend d'autres implémentations. Si vous utilisez par exemple le curseur pour contrôler le volume du son, vous pouvez effectuer une action de vérification par UIControlEventValueChanged et si vous obtenez une valeur 0, vous pouvez définir le bouton caché ou désactivé.

Quelque chose comme:

[MusicsliderCtl addTarget:self action:@selector(checkZeroVolume:)forControlEvents:UIControlEventValueChanged];

où void checkZeroVolume pourrait faire la comparaison du volume réel car il est déclenché après tout changement de volume.

1
Vanya

Allez dans Paramètres-> Sons et cochez "Changer avec les boutons". S'il est éteint, le volume du système ne changera pas lorsque vous appuyez sur les boutons de volume. C'est peut-être la raison pour laquelle vous n'avez pas été averti.

0
fantaxy