iOS 7: MPMusicPlayerController volume deprecated. How to change device volume now?
Asked Answered
B

8

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.

Basra answered 7/10, 2013 at 6:58 Comment(5)
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.Vogler
There is AVAudioPlayer: #5222964. The question for me is whether it can play a song from the iPod library.Koran
Agreed, it is very weird that Apple have deprecated these methods without providing a proper replacement.Omentum
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?Photina
@Photina please create separate question for this issueBasra
D
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)

Disputant answered 28/7, 2014 at 10:15 Comment(18)
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?Inquisitor
@Inquisitor 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 - bignerdranch.com/blog/javascriptcore-exampleDisputant
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.Inquisitor
@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.Disputant
Ok, thanks so far! I will try to learn this, too. Learning, learning, learning ;-)Inquisitor
@5Ke, that is the description of MPVolumeView's subview, not MPVolumeView instance itselfDisputant
This code, as written, will introduce an issue: Potential leak of an object stored into 'volumeView'Homecoming
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]){Homecoming
Great this works! but could my app be rejected for using this method?Diverse
@Diverse As long as you don't use private API calls, it won't.Disputant
will this work on other devices? i mean about its compatibilityTownspeople
what devices are you referring to?Disputant
earlier versions of iOs.Townspeople
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.Disputant
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 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 volumeLasala
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.Lasala
@Lasala 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.Disputant
@Disputant 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! :)Lasala
V
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.
Vogler answered 23/10, 2013 at 7:26 Comment(2)
Only the second option should be used >= iOS 7.0. Thanks! :)Lasala
as of iOS 10, you can still use the volume property!Camelliacamelopard
B
9

Just do this:

let masterVolumeSlider: MPVolumeView = MPVolumeView()

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

    view.value = 1.0

}
Burnard answered 2/7, 2015 at 22:2 Comment(3)
An 'import MediaPlayer' is also neededWaldman
masterVolumeSlider.alpha = 0.01; self.view.addSubview(masterVolumeSlider) also allowed me to reduce it without showing the OS volume meterDecennium
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.Vaudevillian
L
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
  }
}
Loci answered 15/2, 2015 at 1:14 Comment(0)
L
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
Lavonna answered 22/8, 2017 at 17:49 Comment(0)
F
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
        }
    }
Frightened answered 16/2, 2015 at 20:16 Comment(0)
T
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
}
Truant answered 17/9, 2015 at 1:48 Comment(0)
C
0
let masterVolumeSlider  : MPVolumeView = MPVolumeView()

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

        view.value              = fVolume!

        view.sendActionsForControlEvents(UIControlEvents.TouchUpInside)

        }
Covell answered 20/1, 2016 at 8:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.