我有一个 AVAudioPlayer 播放一些音频(呃!)
当用户按下按钮时启动音频。当他们释放它时,我希望音频淡出。
我正在使用界面生成器...所以我试图在“内部修饰”上连接一个功能,该功能使音频在 1 秒内淡出然后停止。
有任何想法吗?
谢谢
我有一个 AVAudioPlayer 播放一些音频(呃!)
当用户按下按钮时启动音频。当他们释放它时,我希望音频淡出。
我正在使用界面生成器...所以我试图在“内部修饰”上连接一个功能,该功能使音频在 1 秒内淡出然后停止。
有任何想法吗?
谢谢
这是我的做法:
-(void)doVolumeFade
{
if (self.player.volume > 0.1) {
self.player.volume = self.player.volume - 0.1;
[self performSelector:@selector(doVolumeFade) withObject:nil afterDelay:0.1];
} else {
// Stop and get the sound ready for playing again
[self.player stop];
self.player.currentTime = 0;
[self.player prepareToPlay];
self.player.volume = 1.0;
}
}
.
11 年后:请注意,setVolume#fadeDuration
现在存在!
Swift 有一个AVAudioPlayer
方法可以用来淡出,它包含在以下代码中iOS 10.0
:
var audioPlayer = AVAudioPlayer()
...
audioPlayer.setVolume(0, fadeDuration: 3)
我使用 NSOperation 子类解决了这个问题,因此音量衰减不会阻塞主线程。它还允许淡入淡出排队和忘记。这对于播放带有淡入和淡出效果的单发声音特别有用,因为它们在最后一个淡入淡出完成后被解除分配。
// Example of MXAudioPlayerFadeOperation in NSOperationQueue
NSOperationQueue *audioFaderQueue = [[NSOperationQueue alloc] init];
[audioFaderQueue setMaxConcurrentOperationCount:1]; // Execute fades serially.
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"bg" ofType:@"mp3"]; // path to bg.mp3
AVAudioPlayer *player = [[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:filePath] error:NULL] autorelease];
[player setNumberOfLoops:-1];
[player setVolume:0.0];
// Note that delay is delay after last fade due to the Operation Queue working serially.
MXAudioPlayerFadeOperation *fadeIn = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:1.0 overDuration:3.0];
[fadeIn setDelay:2.0];
MXAudioPlayerFadeOperation *fadeDown = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:0.1 overDuration:3.0];
[fadeDown setDelay:0.0];
MXAudioPlayerFadeOperation *fadeUp = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:1.0 overDuration:4.0];
[fadeUp setDelay:0.0];
MXAudioPlayerFadeOperation *fadeOut = [[MXAudioPlayerFadeOperation alloc] initFadeWithAudioPlayer:player toVolume:0.0 overDuration:3.0];
[fadeOut setDelay:2.0];
[audioFaderQueue addOperation:fadeIn]; // 2.0s - 5.0s
[audioFaderQueue addOperation:fadeDown]; // 5.0s - 8.0s
[audioFaderQueue addOperation:fadeUp]; // 8.0s - 12.0s
[audioFaderQueue addOperation:fadeOut]; // 14.0s - 17.0s
[fadeIn release];
[fadeDown release];
[fadeUp release];
[fadeOut release];
有关 MXAudioPlayerFadeOperation 类代码,请参见这篇文章。
我最终将一些答案组合在一起并将其转换为 Swift,最终以这种方法结束:
func fadeVolumeAndPause(){
if self.player?.volume > 0.1 {
self.player?.volume = self.player!.volume - 0.1
var dispatchTime: dispatch_time_t = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(dispatchTime, dispatch_get_main_queue(), {
self.fadeVolumeAndPause()
})
} else {
self.player?.pause()
self.player?.volume = 1.0
}
}
这些都是很好的答案,但是它们不涉及指定衰减率(或将对数曲线应用于衰减,这有时是可取的),或指定从您衰减到的单位的 dB 减少数。
这是我的一个应用程序中的一个例外,删除了一些与这个问题无关的“花里胡哨”。
请享用!
#define linearToDecibels(linear) (MIN(10,MAX(-100,20.0 * log10(linear))))
#define decibelsToLinear(decibels) (pow (10, (0.05 * decibels)))
#define fadeInfoId(n) [fadeInfo objectForKey:@#n]
#define fadeInfoObject(NSObject,n) ((NSObject*) fadeInfoId(n))
#define fadeInfoFloat(n) [fadeInfoId(n) floatValue]
#define useFadeInfoObject(n) * n = fadeInfoId(n)
#define useFadeInfoFloat(n) n = fadeInfoFloat(n)
#define setFadeInfoId(n,x) [fadeInfo setObject:x forKey:@#n]
#define setFadeInfoFloat(n,x) setFadeInfoId(n,[NSNumber numberWithFloat:x])
#define setFadeInfoFlag(n) setFadeInfoId(n,[NSNumber numberWithBool:YES])
#define saveFadeInfoId(n) setFadeInfoId(n,n)
#define saveFadeInfoFloat(n) setFadeInfoFloat(n,n)
#define fadeAVAudioPlayer_default nil
#define fadeAVAudioPlayer_linearFade @"linearFade"
#define fadeAVAudioPlayer_fadeToStop @"fadeToStop"
#define fadeAVAudioPlayer_linearFadeToStop @"linearFadeToStop"
-(void) fadeAVAudioPlayerTimerEvent:(NSTimer *) timer {
NSMutableDictionary *fadeInfo = timer.userInfo;
NSTimeInterval elapsed = 0 - [fadeInfoObject(NSDate,startTime) timeIntervalSinceNow];
NSTimeInterval useFadeInfoFloat(fadeTime);
float useFadeInfoFloat(fadeToLevel);
AVAudioPlayer useFadeInfoObject(player);
double linear;
if (elapsed>fadeTime) {
if (fadeInfoId(stopPlaybackAtFadeTime)) {
[player stop];
linear = fadeInfoFloat(fadeFromLevel);
} else {
linear = fadeToLevel;
}
[timer invalidate];
[fadeInfo release];
} else {
if (fadeInfoId(linearCurve)) {
float useFadeInfoFloat(fadeFromLevel);
float fadeDelta = fadeToLevel-fadeFromLevel;
linear = fadeFromLevel + (fadeDelta * (elapsed/fadeTime));
} else {
float useFadeInfoFloat(fadeToDB);
float useFadeInfoFloat(fadeFromDB);
float fadeDelta = fadeToDB-fadeFromDB;
float decibels = fadeFromDB + (fadeDelta * (elapsed/fadeTime));
linear = decibelsToLinear(decibels);
}
}
[player setVolume: linear];
//[self displayFaderLevelForMedia:player];
//[self updateMediaVolumeLabel:player];
}
-(void) fadeAVAudioPlayerLinear:(AVAudioPlayer *)player over:(NSTimeInterval) fadeTime fadeToLevel:(float) fadeToLevel fadeMode:(NSString*)fadeMode {
NSMutableDictionary *fadeInfo = [[NSMutableDictionary alloc ]init];
saveFadeInfoId(player);
float fadeFromLevel = player.volume;// to optimize macros put value in var, so we don't call method 3 times.
float fadeFromDB = linearToDecibels(fadeFromLevel);
float fadeToDB = linearToDecibels(fadeToLevel);
saveFadeInfoFloat(fadeFromLevel);
saveFadeInfoFloat(fadeToLevel);
saveFadeInfoFloat(fadeToDB);
saveFadeInfoFloat(fadeFromDB);
saveFadeInfoFloat(fadeTime);
setFadeInfoId(startTime,[NSDate date]);
if([fadeMode isEqualToString:fadeAVAudioPlayer_fadeToStop]||[fadeMode isEqualToString:fadeAVAudioPlayer_linearFadeToStop]){
setFadeInfoFlag(stopPlaybackAtFadeTime);
}
if([fadeMode isEqualToString:fadeAVAudioPlayer_linearFade]||[fadeMode isEqualToString:fadeAVAudioPlayer_linearFadeToStop]){
setFadeInfoFlag(linearCurve);
}
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(fadeAVAudioPlayerTimerEvent:) userInfo:fadeInfo repeats:YES];
}
-(void) fadeAVAudioPlayer:(AVAudioPlayer *)player over:(NSTimeInterval) fadeTime fadeToDB:(float) fadeToDB fadeMode:(NSString*)fadeMode {
[self fadeAVAudioPlayerLinear:player over:fadeTime fadeToLevel:decibelsToLinear(fadeToDB) fadeMode:fadeMode ];
}
-(void) fadeoutAVAudioPlayer:(AVAudioPlayer *)player {
[self fadeAVAudioPlayerLinear:player over:5.0 fadeToLevel:0 fadeMode:fadeAVAudioPlayer_default];
}
-(void) fadeinAVAudioPlayer:(AVAudioPlayer *)player {
[self fadeAVAudioPlayerLinear:player over:5.0 fadeToLevel:0 fadeMode:fadeAVAudioPlayer_default];
}
swift 3的扩展灵感来自投票最多的答案。对于那些喜欢复制粘贴的人:)
extension AVAudioPlayer {
func fadeOut() {
if volume > 0.1 {
// Fade
volume -= 0.1
perform(#selector(fadeOut), with: nil, afterDelay: 0.1)
} else {
// Stop and get the sound ready for playing again
stop()
prepareToPlay()
volume = 1
}
}
}
我用 Swift 写了一个辅助类来淡入淡出AvAudioPlayer
。您可以使用对数音量功能以获得更渐进的褪色效果。
let player = AVAudioPlayer(contentsOfURL: soundURL, error: nil)
let fader = iiFaderForAvAudioPlayer(player: player)
fader.fadeIn()
fader.fadeOut()
我喜欢 Ambroise Collon 的答案,所以我投了赞成票,但 Swift 是静态类型的,所以performSelector
: 方法将被淘汰,也许替代方案可以调度异步(在这个版本中,我还添加了目标卷作为参数)
func dispatchDelay(delay:Double, closure:@escaping ()->()) {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delay, execute: closure)
}
extension AVAudioPlayer {
func fadeOut(vol:Float) {
if volume > vol {
//print("vol is : \(vol) and volume is: \(volume)")
dispatchDelay(delay: 0.1, closure: {
[weak self] in
guard let strongSelf = self else { return }
strongSelf.volume -= 0.01
strongSelf.fadeOut(vol: vol)
})
} else {
volume = vol
}
}
func fadeIn(vol:Float) {
if volume < vol {
dispatchDelay(delay: 0.1, closure: {
[weak self] in
guard let strongSelf = self else { return }
strongSelf.volume += 0.01
strongSelf.fadeIn(vol: vol)
})
} else {
volume = vol
}
}
}
在我看来,这似乎是 NSOperationQueue 的下降使用。
因此,这是我的解决方案:
-(void) fadeIn
{
if (self.currentPlayer.volume >= 1.0f) return;
else {
self.currentPlayer.volume+=0.10;
__weak typeof (self) weakSelf = self;
[NSThread sleepForTimeInterval:0.2f];
[self.fadingQueue addOperationWithBlock:^{
NSLog(@"fading in %.2f", self.currentPlayer.volume);
[weakSelf fadeIn];
}];
}
}
-(void) fadeOut
{
if (self.currentPlayer.volume <= 0.0f) return;
else {
self.currentPlayer.volume -=0.1;
__weak typeof (self) weakSelf = self;
[NSThread sleepForTimeInterval:0.2f];
[self.fadingQueue addOperationWithBlock:^{
NSLog(@"fading out %.2f", self.currentPlayer.volume);
[weakSelf fadeOut];
}];
}
}
这个怎么样:(如果传入的时间是负数,则淡出声音,否则淡入)
- (void) fadeInOutVolumeOverTime: (NSNumber *)time
{
#define fade_out_steps 0.1
float theVolume = player.volume;
NSTimeInterval theTime = [time doubleValue];
int sign = (theTime >= 0) ? 1 : -1;
// before we call this, if we are fading out, we save the volume
// so that we can restore back to that level in the fade in
if ((sign == 1) &&
((theVolume >= savedVolume) ||
(theTime == 0))) {
player.volume = savedVolume;
}
else if ((sign == -1) && (theVolume <= 0)) {
NSLog(@"fading");
[player pause];
[self performSelector:@selector(fadeInOutVolumeOverTime:) withObject:[NSNumber numberWithDouble:0] afterDelay:1.0];
}
else {
theTime *= fade_out_steps;
player.volume = theVolume + fade_out_steps * sign;
[self performSelector:@selector(fadeInOutVolumeOverTime:) withObject:time afterDelay:fabs(theTime)];
}
}
在 Objective-C 中试试这个:
NSURL *trackURL = [[NSURL alloc]initFileURLWithPath:@"path to audio track"];
// fade duration in seconds
NSTimeInterval fadeDuration = 0.3;
// duration of the audio track in seconds
NSTimeInterval audioTrackDuration = 5.0;
AVAudioPlayer *audioPlayer = [[AVAudioPlayer alloc] initWithContentsOfURL:trackURL error:nil];
[audioPlayer play];
// first we set the volume to 1 - highest
[audioPlayer setVolume:1.0];
// then to 0 - lowest
[musicAudioPlayer setVolume:0 fadeDuration:audioTrackDuration - fadeDuration];
快速解决方案:
此处评分最高的答案很棒,但由于 0.1 的音量步长太大,因此会产生口吃效果。使用 0.01 可以获得更平滑的淡入淡出效果。
使用此代码,您可以指定希望淡入淡出过渡持续多长时间。
let fadeVolumeStep: Float = 0.01
let fadeTime = 0.5 // Fade time in seconds
var fadeVolumeStepTime: Double {
return fadeTime / Double(1.0 / fadeVolumeStep)
}
func fadeOut() {
guard let player = self.player else {
return
}
if !player.playing { return }
func fadeOutPlayer() {
if player.volume > fadeVolumeStep {
player.volume -= fadeVolumeStep
delay(time: fadeVolumeStepTime, closure: {
fadeOutPlayer()
})
} else {
player.stop()
player.currentTime = 0
player.prepareToPlay()
}
}
fadeOutPlayer()
}
func fadeIn() {
guard let player = self.player else {
return
}
if player.playing { return }
player.volume = 0
player.play()
func fadeInPlayer() {
if player.volume <= 1 - fadeVolumeStep {
player.volume += fadeVolumeStep
delay(time: fadeVolumeStepTime, closure: {
fadeInPlayer()
})
} else {
player.volume = 1
}
}
fadeInPlayer()
}
func delay(time delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(), closure)
}
您可以使用fadeTime
常数调整时间。
objc.io 的 Nick Lockwood 提供了一个非常好的解决方案。它利用核心动画来计时音量变化,如果需要将 UI 动画与音量变化(包括交互式动画)同步,它会派上用场。