I've migrated my VoIP application to iOS7 recently, and there's an open bug still remaining which I can't understand.
When in VoIP call, in iOS6 you can push the physical volume button to lower the volume, down to Zero.
Now, in iOS7, I can lower the sound only down to the last step before zero, which means the volume cannot be muted.
I suspect this is something that's on iOS7 side, since I don't have the problem with the same IPA on an iOS6 device.
Anybody knows what's up?
A bit old question, but I also needed a solution for Gui13's question/comment:
We're not using any sort of control in our app, all volume interaction is based on volume buttons.. So I could maybe implement this but I'd need a way to tie it to the volume buttons. Is this possible?
Apple recommends using MPVolumeView, so I came up with this:
MPVolumeView *volumeView = [MPVolumeView new];
[self.view addSubview:volumeView];
and then:
__block UISlider *volumeSlider = nil;
[[volumeView subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[UISlider class]]) {
volumeSlider = obj;
*stop = YES;
}
}];
[volumeSlider addTarget:self action:#selector(handleVolumeChanged:) forControlEvents:UIControlEventValueChanged];
with:
- (void)handleVolumeChanged:(id)sender
{
NSLog(#"%s - %f", __PRETTY_FUNCTION__, ((UISlider *)sender).value);
}
The [MPVolumeView new] code above inits MPVolumeView with no frame so, it's not visible in our self.view where it has been added as a subview, but it is important that it has to be added!
You can also init the MPVolumeView with the code:
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:self.view.frame];
volumeView.showsRouteButton = NO;
volumeView.showsVolumeSlider = NO;
[self.view addSubview:volumeView];
which will also init the empty MPVolumeView (i.e. without RouteButton and VolumeSlider).
What's interesting is that UISlider subview (actually MPVolumeSlider, the subclass of UISlider) will still be found in the enumerateObjectsUsingBlock above.
This approach is also interesting because you can save the reference to volumeSlider and use it later to set volume from code, or your custom control:
Init and add to your view:
MPVolumeView *volumeView = [MPVolumeView new];
volumeView.showsRouteButton = NO;
volumeView.showsVolumeSlider = NO;
[self.view addSubview:volumeView];
Find and save reference to UISlider:
__weak __typeof(self)weakSelf = self;
[[volumeView subviews] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[UISlider class]]) {
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf.volumeSlider = obj;
*stop = YES;
}
}];
Add target action for UIControlEventValueChanged:
[self.volumeSlider addTarget:self action:#selector(handleVolumeChanged:) forControlEvents:UIControlEventValueChanged];
And then update your custom control when the volume has been changed (i.e. by the hardware volume controls):
- (void)handleVolumeChanged:(id)sender
{
NSLog(#"%s - %f", __PRETTY_FUNCTION__, self.volumeSlider.value);
self.myCustomVolumeSliderView.value = self.volumeSlider.value;
}
and also:
- (IBAction)myCustomVolumeSliderViewValueChanged:(id)sender {
NSLog(#"set volume to: %f", self.myCustomVolumeSliderView.value);
self.volumeSlider.value = self.myCustomVolumeSliderView.value;
}
Hope this helps someone (and that Apple doesn't remove MPVolumeSlider from MPVolumeView).
Same problem with my VoIP app...
My Solution:
After everything is set up, set the category to Playback,
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayback error:nil];
Then set it back to PlayAndRecord,
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord error:nil];
This works for me, hope this helps someone who comes across the same problem.
You have to use applicationMusicPlayer instead of iPodMusicPlayer to set the system volume:
#import <MediaPlayer/MediaPlayer.h>
musicPlayer = [MPMusicPlayerController applicationMusicPlayer];
musicPlayer.volume = 1; // max volume
musicPlayer.volume = 0; // min volume (mute)
musicPlayer.volume = 0.0625; // 1 bar on the overlay volume display
Apple is assuming that they know each and every way in which a volume will need to be administered.
There are, in fact, occasions when we want to change how the volume is controlled without forcing the user to have a volume slider on the screen, (alarm clocks and media players come to mind immediately).
Yuou can take reference
https://developer.apple.com/library/ios/documentation/mediaplayer/reference/MPVolumeView_Class/Reference/Reference.html
http://ios-blog.co.uk/tutorials/controlling-system-output-volume-with-the-mpvolumeview-class-part-one/
Related
I'm using MobileVLCKit in form of MobileVLCKit-prod version 2.7.9 from CocoaPods.
I am initializing VLCMediaPlayer this way
VLCMediaPlyer *player;
....
player=[[VLCMediaPlayer alloc] init];
player.delegate=self;
VLCMedia * media;
media=[VLCMedia mediaWithURL:#"UrlOfRemoteAudiFile"]
I don't setup player.drawable because I only need audio and remote file doesn't contain video stream anyway.
Audio file plays correctly. Pause/resume works.
but I cannot change sound volume: player.audio.volume returns 0, attempts to assign player.audio.volume value in interval 0..200 are ignored with no change in volume.
I currently test on iOS9.
Only solution I found so far which works is changing system volume.
only way I find to do that (partially based on code from ttps://stackoverflow.com/questions/33168497/ios-9-how-to-change-volume-programmatically-without-showing-system-sound-bar-po ) is just change system volume.
code like this.
- (void) fakeSystemVolumeSetup:(float)newValue {
MPVolumeView* volumeView = [[MPVolumeView alloc] init];
// Get the Volume Slider
UISlider* volumeViewSlider = nil;
for (UIView *view in [volumeView subviews]){
if ([view.class.description isEqualToString:#"MPVolumeSlider"]){
volumeViewSlider = (UISlider*)view;
break;
}
}
// Fake the volume setting
[volumeViewSlider setValue:newValue animated:YES];
[volumeViewSlider sendActionsForControlEvents:UIControlEventTouchUpInside];
}
It works and it turned out that having system indicators active is even better for this project.
In this code, Audio is a class where all things concerning AVAudioPlayer are handled. Currently there is some music playing. I want to have this fade out completely in 1 second and then play another song. Simultaneously, I want a new scene to appear. So I want the new song to start right as the transition (also lasting 1 second) ends. However, I find myself always waiting for the music to stop, before the scene transition starts. I've tried many ways, with and without selectors, with and without the various sleep functions, but without any luck. I've also tried to use stopMusic instead of transitionMusic and then use playMusic: in the new scene, but I always find myself waiting for stopMusic to finish before the transition starts.
Here's a part of the first scene, which is run if a certain button on the screen is pressed:
Audio *musicPlayer = [[Audio alloc] init];
NewScene *newScene = [NewScene sceneWithSize:self.size];
SKTransition *reveal = [SKTransition pushWithDirection:SKTransitionDirectionLeft duration:1.0];
reveal.pausesOutgoingScene = NO;
reveal.pausesIncomingScene = NO;
[self.view presentScene:newScene transition:reveal];
[musicPlayer transitionMusic:#"NewSong"];
The method transitionMusic: is as follows:
-(void)transitionMusic:(NSString *)fileName {
[self performSelectorOnMainThread:#selector(stopMusic) withObject:nil waitUntilDone:YES];
[self playMusic:fileName];
}
And stopMusic and playMusic: are:
-(void)stopMusic {
while(BGM.volume > 0.05) {
[BGM setVolume:(BGM.volume - 0.05)];
[NSThread sleepForTimeInterval:0.05];
}
[BGM stop];
}
-(void)playMusic:(NSString *)fileName {
NSURL * backgroundMusicURL = [[NSBundle mainBundle] URLForResource:[NSString stringWithFormat:#"%#",fileName] withExtension:#"caf"];
BGM = [[AVAudioPlayer alloc] initWithContentsOfURL:backgroundMusicURL error:nil];
[BGM setNumberOfLoops:-1];
[BGM setVolume:1.0];
[BGM prepareToPlay];
[BGM play];
}
Also, BGM is a static audioplayer declared in Audio.h. Can anyone point out what I'm doing wrong?
Your musicPlayer player object is dependent on your old scene. You need to create a singleton to handle your transition music independently from one scene to another.
BoxesOfBabies.com has a good tutorial on how to set up a singleton and use it to Control Music Across Multiple SKScene.
EDIT:
In your SKScene:
#import "MySingleton"
#implementation MainScene
{
MySingleton *_mySingleton;
}
In your init method:
_mySingleton = [MySingleton sharedInstance];
To start playing audio in any of your scenes:
[_mySingleton startPlayingSong1];
or
[_mySingleton startPlayingSong2];
whatever methods you have in your singleton.
I want to create a simple mobilesubstrate tweak that hides and shows status bar icons like battery or Carrier or wifi signal indecator. I've seen libstatusbar project but i can't find out how to hide iOS's icons. Is there any other way to do this without the use of this library? I just want to hide and show the default icons
Not possible using public API. You can only hide the entire status bar, not only certain elements of it.
For jailbreak, take a look at:
https://github.com/nst/iOS-Runtime-Headers/blob/master/Frameworks/UIKit.framework/UIStatusBarItem.h
In particularly, look at the following methods:
+ (BOOL)itemType:(int)arg1 idiom:(int)arg2 appearsInRegion:(int)arg3;
+ (BOOL)itemType:(int)arg1 idiom:(int)arg2 canBeEnabledForData:(id)arg3 style:(id)arg4;
These methods are consulted whether iterms should appear or not. Return NO here to disable items.
Here is what I use in my tweak:
int itemToHide = 0;
[[objc_getClass("SBStatusBarStateAggregator") sharedInstance] beginCoalescentBlock];
[[objc_getClass("SBStatusBarStateAggregator") sharedInstance] _setItem:itemToHide enabled:NO];
[[objc_getClass("SBStatusBarStateAggregator") sharedInstance] endCoalescentBlock];
Only problem - iOS uses integer values for status bar items and they're different on different iOS versions. You could test every iOS version and store values for each one of them but I found a better way.
I hook SBStatusBarStateAggregator _setItem:(int)arg1 enabled:(BOOL)arg2 method. Then I call one of the SBStatusBarStateAggregator -(void)_update**** methods. For example, let's say I want to find location icon index. I call SBStatusBarStateAggregator -(void)_updateLocationItem method. It then will call hooked SBStatusBarStateAggregator _setItem:(int)arg1 enabled:(BOOL)arg2 where I will store the index.
I also hook SBStatusBarStateAggregator -(void)_notifyItemChanged:(int)arg. This method is called as part of SBStatusBarStateAggregator -(void)_update**** call. When determing status bar icon index I simply ignore calls to it by returning without calling original implementation.
And if you want to permanently hide some of the icons you still need to hook SBStatusBarStateAggregator _setItem:(int)arg1 enabled:(BOOL)arg2 and SBStatusBarStateAggregator -(void)_notifyItemChanged:(int)arg in order to ignore any iOS attempts to show hidden icons. For example, signal level and data/time are reanabled every time they're updated.
That's all for iOS 7. On iOS 5-6 API is different but I use pretty much the same approach. To hide status bar item
int itemToHide = 0;
[[objc_getClass("SBStatusBarDataManager") sharedDataManager] setStatusBarItem:itemToHide enabled:NO];
I hook SBStatusBarDataManager -(void)updateStatusBarItem:(int)item to determine icon index and then call SBStatusBarDataManager -(void)_locationStatusChange in case of location icon.
Ok. Here is solution.
In your plist file add row:
View controller-based status bar appearance : NO
Make a category on UINavigationBar with this content:
#import "UINavigationBar+StatusBar.h"
#import
#implementation UINavigationBar (StatusBar)
+ (void)load
{
[self swizzleOriginalSelectorWithName:#"layoutSubviews" toSelectorWithName:#"my_layoutSubviews"];
}
- (void)my_layoutSubviews
{
[self my_layoutSubviews];
[self setFrame:CGRectMake(0, 0, self.frame.size.width, 64)];
}
+ (void)swizzleOriginalSelectorWithName:(NSString *)origName toSelectorWithName:(NSString *)swizzleName
{
Method origMethod = class_getInstanceMethod([self class], NSSelectorFromString(origName));
Method newMethod = class_getInstanceMethod([self class], NSSelectorFromString(swizzleName));
method_exchangeImplementations(origMethod, newMethod);
}
#end
This will increase navigation bar for 20pt.
Then, make your custom view for status bar.
e.g.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[self makeCustomSatusBar];
// Override point for customization after application launch.
return YES;
}
- (void)makeCustomSatusBar
{
[[UIApplication sharedApplication] setStatusBarHidden:YES];
UIColor *statusBarColor = [UIColor blackColor];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.window.frame.size.width, 20)];
view.layer.zPosition = INT_MAX;
view.backgroundColor = [UIColor clearColor];
// Making time label
NSDateFormatter *formatter = [NSDateFormatter new];
formatter.dateFormat = #"HH:mm";
UILabel *timeLabel = [UILabel new];
timeLabel.text = [formatter stringFromDate:[NSDate date]];
timeLabel.textColor = statusBarColor;
timeLabel.font = [UIFont systemFontOfSize:12];
[timeLabel sizeToFit];
timeLabel.center = CGPointMake(view.frame.size.width/2, view.frame.size.height/2);
[view addSubview:timeLabel];
//
// make other indicators you need...
//...
[self.window addSubview:view];
}
And you will have something like this:
Note, that you need to update values of your custom view every time (i.e. time label, battery, etc..) , so it would be better to make a separate class for your status bar, and make a infinite timer with 1 sec of tick and do your updates in timer's action.
may be you just need this?
[[UIApplication sharedApplication] setStatusBarHidden:YES]
And if you want just empty view on top of 20pt height, then make that and add to UIWindow, and shift down subview of UIWindow for 20 pt
I'm developing a radio streaming app with XCode 4.5 for iOS 6 and mainly using storyboards.
I successfully made it to be able to play in background. So wherever I go from my app, whether to other tabs or even after clicking the Home button on the simulator,it keeps playing.
I'm using Matt Gallagher's audio streamer, which I include in my app delegate .m file below
#pragma mark - audio streaming
- (void)playAudio:(indoRadio *)np withButton:(NSString *)currentButton
{
if ([currentButton isEqual:#"playbutton.png"])
{
[self.nowplayingVC.downloadSourceField resignFirstResponder];
[self createStreamer:np.URL];
[self setButtonImageNamed:#"loadingbutton.png"];
[audioStreamer start];
}
else
{
[audioStreamer stop];
}
}
- (void)createStreamer:(NSString *)url
{
if (audioStreamer)
{
return;
}
[self destroyStreamer];
NSString *escapedValue = (__bridge NSString *)CFURLCreateStringByAddingPercentEscapes(nil,(CFStringRef)url,NULL,NULL,kCFStringEncodingUTF8);
NSURL *streamurl = [NSURL URLWithString:escapedValue];
audioStreamer = [[AudioStreamer alloc] initWithURL:streamurl];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(playbackStateChanged:)
name:ASStatusChangedNotification
object:audioStreamer];
}
- (void)playbackStateChanged:(NSNotification *)aNotification
{
NSLog(#"playback state changed? %d",[audioStreamer isWaiting]);
if ([audioStreamer isWaiting])
{
[self setButtonImageNamed:#"loadingbutton.png"];
}
else if ([audioStreamer isPlaying])
{
[self setButtonImageNamed:#"stopbutton.png"];
}
else if ([audioStreamer isIdle])
{
[self destroyStreamer];
[self setButtonImageNamed:#"playbutton.png"];
}
}
- (void)destroyStreamer
{
if (audioStreamer)
{
[[NSNotificationCenter defaultCenter]
removeObserver:self
name:ASStatusChangedNotification
object:audioStreamer];
[audioStreamer stop];
audioStreamer = nil;
}
}
- (void)setButtonImageNamed:(NSString *)imageName
{
if (!imageName)
{
imageName = #"playButton";
}
self.nowplayingVC.currentImageName = imageName;
UIImage *image = [UIImage imageNamed:imageName];
[self.nowplayingVC.playBtn.layer removeAllAnimations];
[self.nowplayingVC.playBtn setImage:image forState:0];
if ([imageName isEqual:#"loadingbutton.png"])
{
[self.nowplayingVC spinButton];
}
}
The problem I got is that when I click the play button, it starts the audio streamers but doesn't change into either loading button or stop button. This makes me unable to stop the audio since the button image is not changed. And since I put an NSLog inside the playbackStateChanged: method,I know that the playback state did change. It seems like the setButtonImagedNamed: method is not firing. Any thoughts?
Please have look of my answer:Disabled buttons not changing image in
For a button there is normal, selected, highlighted and disabled state.
So in your case you have to handle the normal and selected state.
In the xib, set image to a button for normal and selected state.
Now in the function of playAudio instead of checking the image name check the button state as below:
- (void)playAudio:(indoRadio *)np withButton:(NSString *)currentButton
{
if (playButton.selected)
{
//Stop the streaming
//set selection NO
[playButton setSelected:NO];
}
else
{
//Start the streaming
//set selection YES
[playButton setSelected:YES];
}
}
playButton is the IBOutlet of play button.
As you have set the images in xib, so the image automatically get changed as per the state of the image
In case you have added the playBtn programmatically, check if the button is declared as weak or its getting released somewhere. If it is weak, make it strong.
In case the button is in the nib file, then the problem is in the IBOutlet. Redo the connection properly.
It turned out that the delegate didn't know which nowplayingVC I was calling. #sarp-kaya's question about Calling a UIViewController method from app delegate has inspired me.
I actually have put this code in my view controller viewDidLoad:
myAppDelegate* appDelegate = [UIApplication sharedApplication].delegate;
but I forgot to add this line:
appDelegate.nowplayingVC = self;
So, all I need to do is adding that one line and it works now. okay, so silly of me for not noticing such a basic thing. But thanks for your helps :D
All,
I am attempting to load a set of sounds asynchronously when I load a UIViewController. At about the same time, I am (occasionally) also placing a UIView on the top of my ViewController's hierarchy to present a help overlay. When I do this, the app crashes with a bad exec. If the view is not added, the app does not crash. My ViewController looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
__soundHelper = [[SoundHelper alloc] initWithSounds];
// Other stuff
}
- (void)viewDidAppear:(BOOL)animated
{
// ****** Set up the Help Screen
self.coachMarkView = [[FHSCoachMarkView alloc] initWithImageName:#"help_GradingVC"
coveringView:self.view
withOpacity:0.9
dismissOnTap:YES
withDelegate:self];
[self.coachMarkView showCoachMarkView];
[super viewDidAppear:animated];
}
The main asynchronous loading method of SoundHelper (called from 'initWithSounds') looks like this:
// Helper method that loads sounds as needed
- (void)loadSounds {
// Run this loading code in a separate thread
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *loadSoundsOp = [NSBlockOperation blockOperationWithBlock:^{
// Find all sound files (*.caf) in resource bundles
__soundCache = [[NSMutableDictionary alloc]initWithCapacity:0];
NSString * sndFileName;
NSArray *soundFiles = [[NSBundle mainBundle] pathsForResourcesOfType:STR_SOUND_EXT inDirectory:nil];
// Loop through all of the sounds found
for (NSString * soundFileNamePath in soundFiles) {
// Add the sound file to the dictionary
sndFileName = [[soundFileNamePath lastPathComponent] lowercaseString];
[__soundCache setObject:[self soundPath:soundFileNamePath] forKey:sndFileName];
}
// From: https://stackoverflow.com/questions/7334647/nsoperationqueue-and-uitableview-release-is-crashing-my-app
[self performSelectorOnMainThread:#selector(description) withObject:nil waitUntilDone:NO];
}];
[operationQueue addOperation:loadSoundsOp];
}
The crash seems to occur when the block exits. The init of FHSCoachMarkView looks like this:
- (FHSCoachMarkView *)initWithImageName:(NSString *) imageName
coveringView:(UIView *) view
withOpacity:(CGFloat) opacity
dismissOnTap:(BOOL) dismissOnTap
withDelegate:(id<FHSCoachMarkViewDelegate>) delegateID
{
// Reset Viewed Coach Marks if User Setting is set to show them
[self resetSettings];
__coveringView = view;
self = [super initWithFrame:__coveringView.frame];
if (self) {
// Record the string for later reference
__coachMarkName = [NSString stringWithString:imageName];
self.delegate = delegateID;
UIImage * image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
// ****** Configure the View Hierarchy
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imgView];
[__coveringView.superview insertSubview:self aboveSubview:__coveringView];
// ****** Configure the View Hierarchy with the proper opacity
__coachMarkViewOpacity = opacity;
self.hidden = YES;
self.opaque = NO;
self.alpha = __coachMarkViewOpacity;
imgView.hidden = NO;
imgView.opaque = NO;
imgView.alpha = __coachMarkViewOpacity;
// ****** Configure whether the coachMark can be dismissed when it's body is tapped
__dismissOnTap = dismissOnTap;
// If it is dismissable, set up a gesture recognizer
if (__dismissOnTap) {
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(coachMarkWasTapped:)];
[self addGestureRecognizer:tapGesture];
}
}
return self;
}
I have tried invoking the asynchronous block using both NSBlockOperation and dispatch_async and both have had the same results. Additionally, I've removed the aysnch call altogether and loaded the sounds on the main thread. That works fine. I also tried the solution suggested by #Jason in: NSOperationQueue and UITableView release is crashing my app but the same thing happened there too.
Is this actually an issue with the view being added in FHSCoachMarkView, or is it possibly related to the fact that both access mainBundle? I'm a bit new to asynch coding in iOS, so I'm at a bit of a loss. Any help would be appreciated!
Thanks,
Scott
I figured this out: I had set up a listener on the SoundHelper object (NSUserDefaultsDidChangeNotification) that listened for when NSUserDefaults were changed, and loaded the sounds if the user defaults indicated so. The FHSCoachMarkView was also making changes to NSUserDefaults. In the SoundHelper, I was not properly checking which defaults were being changed, so the asynch sound loading method was being called each time a change was made. So multiple threads were attempting to modify the __soundCache instance variable. it didn't seem to like that.
Question: Is this the correct way to answer your own question? Or should I have just added a comment to the question it self?
Thanks.