42

MPMusicPlayerController setVolume is deprecated since iOS 7

Is there any other way to change system music volume? Preferably without user interaction. Its important feature: to increase volume automatically for any alarm clock from AppStore.

Stumbler
  • 2,056
  • 7
  • 35
  • 61
Maxim Kholyavkin
  • 4,463
  • 2
  • 37
  • 82
  • 8
    I also depend on this feature as my users want to be able to set and save the volume my app opens with. This seems a bizarre thing to remove from the control of developers. – amergin Oct 08 '13 at 08:23
  • There is AVAudioPlayer: http://stackoverflow.com/questions/5222464/using-mpmediaitems-with-avaudioplayer. The question for me is whether it can play a song from the iPod library. – Richard Lovejoy Oct 29 '13 at 18:26
  • Agreed, it is very weird that Apple have deprecated these methods without providing a proper replacement. – Ric Santos Oct 25 '14 at 00:12
  • I have noticed that in iOS 8.3, the MPMusicPlayerController volume slider in my app is not displayed at all. It was being displayed in earlier iOS versions. No changes were made to the app. Is anyone experiencing this problem in their apps? – kzia May 03 '15 at 14:41
  • @kzia please create separate question for this issue – Maxim Kholyavkin May 04 '15 at 05:26

8 Answers8

51

To answer you question exactly: Yes there is other way to change system volume without user interaction.

Until recent times I used to think that changing volume using MPVolumeView programmatically is possible only using private API. But I have just verified, that changing the value of volumeSlider and faking slider's touchUP event works:

MPVolumeView* volumeView = [[MPVolumeView alloc] init];

//find the volumeSlider
UISlider* volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
    if ([view.class.description isEqualToString:@"MPVolumeSlider"]){
        volumeViewSlider = (UISlider*)view;
        break;
    }
}

[volumeViewSlider setValue:1.0f animated:YES];
[volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];

(When slider receives touchUP event, it will invoke _commitVolumeChange method on itself, which will change the system volume)

ambientlight
  • 7,212
  • 3
  • 49
  • 61
  • 1
    Sorry if I am stupid. I am trying to paste your code into my js function. Dreamweaver throws me a red syntax arrow. Is this js code or something else? – Garavani Sep 04 '14 at 10:10
  • 1
    @Garavani Its an objective-c code. And it's not that straightforward to integrate it with js. Please find more information online in tutorials like this one - http://www.bignerdranch.com/blog/javascriptcore-example/ – ambientlight Sep 04 '14 at 12:48
  • Thanks Hurden! „Not straight forward“ does this mean that in your eyes it is possible to control volume of sound via JS in a home screen used web app running on iOS 7.1.2 on iPad? It will be a lot of painstaking research for me as a beginner. So maybe it would be better if someone says: No, no way to do so. – Garavani Sep 04 '14 at 16:30
  • @Garavani, I have no experience using JS in iOS app, all I know is that you can interact with objective-c code in JS, but anyway you won't be able to do it from your Dreamweaver, you will have to write an app and define the callbacks in it in such a way, so that the action in your browser will trigger an app to do something, but you have understand obj-c for that at least. – ambientlight Sep 04 '14 at 16:51
  • Ok, thanks so far! I will try to learn this, too. Learning, learning, learning ;-) – Garavani Sep 04 '14 at 16:58
  • @5Ke, that is the description of MPVolumeView's subview, not MPVolumeView instance itself – ambientlight Sep 29 '14 at 09:19
  • 1
    This code, as written, will introduce an issue: Potential leak of an object stored into 'volumeView' – Ashton Honnecke Jan 14 '15 at 22:04
  • 1
    To resolve: `Potential leak of an object stored into 'volumeView'` Move volumeView into a property like so: `@property (nonatomic, retain) MPVolumeView* volumeView;` Then access it this way from the loop: `for (UIView *view in [self.volumeView subviews]){` – Ashton Honnecke Jan 14 '15 at 22:12
  • Great this works! but could my app be rejected for using this method? – DrCachetes Apr 02 '15 at 15:05
  • 1
    @DrCachetes As long as you don't use private API calls, it won't. – ambientlight Apr 02 '15 at 15:14
  • will this work on other devices? i mean about its compatibility – MetaSnarf Sep 16 '15 at 06:12
  • what devices are you referring to? – ambientlight Sep 16 '15 at 06:13
  • earlier versions of iOs. – MetaSnarf Sep 16 '15 at 06:26
  • to clarify: iOS 7.0+ you will need to use this approach, because .volume accessor on MPMusicPlayerController is deprecated, for less then iOS7 just simple use that accessor on MPMusicPlayerController. – ambientlight Sep 16 '15 at 07:21
  • I wouldn't recommend using this solution. While this works in iOS 7,8 and 9, there is no guarantee that it will work in the next version. This is undocumented. Apple mentions [here](https://developer.apple.com/library/ios/documentation/AVFoundation/Reference/AVAudioSession_ClassReference/index.html#//apple_ref/occ/instp/AVAudioSession/outputVolume) that `The system wide output volume can be set directly only by the user; to provide volume control in your app, use the MPVolumeView class.` Therefore instead of doing this programatically, show that slider to the user and let him adjust the volume – Alex Feb 29 '16 at 16:19
  • Also, I believe that using this solution presents a risk of your app getting rejected by Apple, since you're faking user interaction. The deprecation of `[MPMusicPlayerController systemMusicPlayer].volume = newVolume;` in iOS 7 suggests that only the user should have control of the system volume. – Alex Feb 29 '16 at 16:26
  • @Alex used in two apps, no problems with review, since It doesn't use any of the private APIs. This is a workaround, since you do need to change system volume in a number of cases. – ambientlight Feb 29 '16 at 16:28
  • 2
    @ambientlight I understand that it doesn't use private APIs directly, but faking user interaction as a workaround to trigger that private API `_commitVolumeChange` seems a bit risky to me. Thanks for the suggestion though! :) – Alex Feb 29 '16 at 16:32
13

Until Apple sees fit to rescind this decision there are two remedies I have discovered:

  • Keep using the volume property, it is still working under iOS 7.0.2
  • Use AVAudioSession.outputVolume to read the volume when your app wakes and pop up an alert containing an MPVolumeView if the volume is lower than (or higher than) a user specified value. At least your user knows that their alarm (or whatever) will play quietly and has the opportunity to adjust the volume. Alternately you could just display very clearly the volume level so they get no surprises.
Kamchatka
  • 3,597
  • 4
  • 38
  • 69
amergin
  • 3,106
  • 32
  • 44
9

Just do this:

let masterVolumeSlider: MPVolumeView = MPVolumeView()

if let view = masterVolumeSlider.subviews.first as? UISlider{

    view.value = 1.0

}
Jon Vogel
  • 5,244
  • 1
  • 39
  • 54
  • An 'import MediaPlayer' is also needed – Mike Jul 18 '15 at 19:34
  • `masterVolumeSlider.alpha = 0.01; self.view.addSubview(masterVolumeSlider)` also allowed me to reduce it without showing the OS volume meter – Rich Fox Jul 27 '15 at 19:46
  • I like this answer the best because it is elegant and doesn't require use of internal class names. It seems most likely to not break in the future. – Chris Garrett Apr 21 '16 at 13:14
4

@Hurden, I wrote a Swift code to implement the MPVolumeSlider:

for view in mpVolumeView.subviews {
  let uiview: UIView = view as UIView
  //println("\(uiview.description)")
  if uiview.description.rangesOfString("MPVolumeSlider").first != nil {
    mpVolumeSilder = (uiview as UISlider)
    currentDeviceVolume = mpVolumeSilder!.value
    return
  }
}

The func rangeOfString extension String can be found here Swift: A pure Swift method for returning ranges of a String instance (Xcode 6 Beta 5)

And use this code to let the gesture and mpvolumeslider work together

@IBAction func handlePan(recognizer: UIPanGestureRecognizer) {
  let translation = recognizer.translationInView(self.view)
  let dx = (translation.x-lastTranslationX)
  let volumeChanged = Float(dx / mpVolumeView.frame.width)
  currentDeviceVolume = currentDeviceVolume + Float(volumeChanged)

  if currentDeviceVolume > 1 {
    currentDeviceVolume = 1
  } else if currentDeviceVolume < 0 {
    currentDeviceVolume = 0
  }

  mpVolumeSilder!.value = currentDeviceVolume

  if recognizer.state == .Changed {
    lastTranslationX = translation.x
  }
  if recognizer.state == .Ended || recognizer.state == .Began {
    lastTranslationX = 0
  }
}
charles.cc.hsu
  • 689
  • 11
  • 15
2

Apparently there is a way to change the system volume without displaying anything at all.

And best of all it works on iOS 11.

Here's how I've achieved it:

1) Create two variables in desired ViewController

let volumeView = MPVolumeView()
var slider: UISlider?

2) Add the code below into your viewDidLoad

volumeView.alpha = 0.01
self.view.addSubview(volumeView)

if let view = volumeView.subviews.first as? UISlider {
    slider = view
}

3) Change volue whenever you need

slider?.value = 0.4
Markus
  • 686
  • 1
  • 11
  • 18
1

Swift Version:

// Outlet added in Storyboard (Add UIView then set class to MPVolumeView)
@IBOutlet weak var mpVolumeView: MPVolumeView!

    // Get volume slider within MPVolumeView
    for subview in self.mpVolumeView.subviews {
        if (subview as UIView).description.rangeOfString("MPVolumeSlider") != nil {
            // Set volume
            let volumeSlider = subview as UISlider
            volumeSlider.value = 1

            // Works with or without the following line:
            // volumeSlider.sendActionsForControlEvents(UIControlEvents.TouchUpInside)
            break
        }
    }
Oak
  • 165
  • 2
  • 14
0

This solution makes a bit nervous and I think its a bit odd an official API doesn't exist but here is my Swift solution built off of ambientlight's post

var _volumeView = MPVolumeView()
var _volumeSlider : UISlider? = nil



self.view.addSubview(_volumeView)
_volumeView.hidden = true

var i = 0
while i < _volumeView.subviews.count {
   if let _r = _volumeView.subviews[i] as? UISlider {
      _volumeSlider = _r
      break
   }
   ++i
}
Krtko
  • 1,055
  • 18
  • 24
0
let masterVolumeSlider  : MPVolumeView = MPVolumeView()

        if let view             = masterVolumeSlider.subviews.first as? UISlider{

        view.value              = fVolume!

        view.sendActionsForControlEvents(UIControlEvents.TouchUpInside)

        }
Vicky
  • 1,095
  • 16
  • 15