Fade out AVAudioEngine AVAudioPlayerNode - avaudioengine

I´m trying to fade out an mp3 file using AVAudioEngine and AVAudioPlayerNode. I cannot figure it out. Can anybody please help in Swift?
I tried some things already. Everything works quite good but not as smooth as I would like it to be.
var fadeOutPlayer: Float = playerVolume
var fadeOutQuinte: Float = playerQuinteVolume
while fadeOutPlayer > 0.0 {
player.volume = fadeOutPlayer
playerQuinte.volume = fadeOutQuinte
fadeOutPlayer -= 0.001
fadeOutQuinte -= 0.001
usleep(100)
}
I also tried a timer (which doesn't do a fade):
if !isTimerFadeOutRunning {
player.volume = 0
playerQuinte.volume = 0
counter = 0
timerFadeOut = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(runTimerFadeOut), userInfo: nil, repeats: true)
isTimerFadeOutRunning = true
}
}
#objc func runTimerFadeOut() {
counter += 0.1
if counter == 0.2 {
player.pause()
playerQuinte.pause()
}
}

I've attached an AVAudioUnitEQ to my flow before mainMixerNode and then controlled the globalGain. The following code shows the idea:
private func manageFadeOut(volume: Double, fadeOutSecs: Double) {
let timeSecs = 0.001
DispatchQueue.main.asyncAfter(deadline: .now() + timeSecs) {
if !self.isFadingOut { return }
for fx in self.fadeOutEffects { fx.globalGain = Float(-96 * (1.0 - volume)) }
if volume <= 0 {
for player in self.players { player.stop() }
self.isFadingOut = false
return
}
let fade = timeSecs / fadeOutSecs
self.manageFadeOut(volume: volume - fade, fadeOutSecs: fadeOutSecs)
}
}

Related

Fill the CALayer with different color when timer reaches to 5 seconds swift ios

In my app i am using a third party library which does circular animation just like in the appstore app download animation. i am using the external file which i have placed in my project. it works but when the timer reaches to 5 seconds the fill color should be red. Currently the whole layer red color applies, i want to only apply the portion it has progressed. i am attaching the video and the third party file. Please can anyone help me with this as what changes should i make to the library file or is there better solution
same video link in case google drive link doesnt work
External Library link which i am using
video link
external third file file
My code snippet that i have tried:
func startGamePlayTimer(color: UIColor)
{
if !isCardLiftedUp {
self.circleTimerView.isHidden = false
self.circleTimerView.timerFillColor = color
Constants.totalGamePlayTime = 30
self.circleTimerView.startTimer(duration: CFTimeInterval(Constants.totalGamePlayTime))
}
if self.gamePlayTimer == nil
{
self.gamePlayTimer?.invalidate()
let timer = Timer.scheduledTimer(timeInterval: 1.0,
target: self,
selector: #selector(updateGamePlayTime),
userInfo: nil,
repeats: true)
timer.tolerance = 0.05
RunLoop.current.add(timer, forMode: .common)
self.gamePlayTimer = timer
}
}
#objc func updateGamePlayTime()
{
if Constants.totalGamePlayTime > 0
{
Constants.totalGamePlayTime -= 1
if Constants.totalGamePlayTime < 5
{
SoundService.playSound(sound: .turn_timeout)
self.circleTimerView.timerFillColor = UIColor.red.withAlphaComponent(0.7)
}
}
else
{
self.stopGamePlayTimer()
}
}
where circleTimerView is the view which animates as the time progresses
You can achieve that with CAKeyframeAnimation on the StrokePath.
I've Update the code in CircleTimer View as below. You can modified fill color and time as per your need.
#objc func updateGamePlayTime()
{
if Constants.totalGamePlayTime > 0
{
Constants.totalGamePlayTime -= 1
if Constants.totalGamePlayTime < 5
{
SoundService.playSound(sound: .turn_timeout)
// self.circleTimerView.timerFillColor = UIColor.red.withAlphaComponent(0.7) // <- Comment this line
}
}
else
{
self.stopGamePlayTimer()
}
}
open func drawFilled(duration: CFTimeInterval = 5.0) {
clear()
if filledLayer == nil {
let parentLayer = self.layer
let circleLayer = CAShapeLayer()
circleLayer.bounds = parentLayer.bounds
circleLayer.position = CGPoint(x: parentLayer.bounds.midX, y: parentLayer.bounds.midY)
let circleRadius = timerFillDiameter * 0.5
let circleBounds = CGRect(x: parentLayer.bounds.midX - circleRadius, y: parentLayer.bounds.midY - circleRadius, width: timerFillDiameter, height: timerFillDiameter)
circleLayer.fillColor = timerFillColor.cgColor
circleLayer.path = UIBezierPath(ovalIn: circleBounds).cgPath
// CAKeyframeAnimation: Changing Fill Color on when timer reaches 80% of total time
let strokeAnimation = CAKeyframeAnimation(keyPath: "fillColor")
strokeAnimation.keyTimes = [0, 0.25, 0.75, 0.80]
strokeAnimation.values = [timerFillColor.cgColor, timerFillColor.cgColor, timerFillColor.cgColor, UIColor.red.withAlphaComponent(0.7).cgColor]
strokeAnimation.duration = duration;
circleLayer.add(strokeAnimation, forKey: "fillColor")
parentLayer.addSublayer(circleLayer)
filledLayer = circleLayer
}
}
open func startTimer(duration: CFTimeInterval) {
drawFilled(duration: duration) // <- Need to pass duration here
if useMask {
runMaskAnimation(duration: duration)
} else {
runDrawAnimation(duration: duration)
}
}
UPDATE:
CAKeyframeAnimation:
You specify the keyframe values using the values and keyTimes properties.
For example:
let colorKeyframeAnimation = CAKeyframeAnimation(keyPath: "backgroundColor")
colorKeyframeAnimation.values = [UIColor.red.cgColor,
UIColor.green.cgColor,
UIColor.blue.cgColor]
colorKeyframeAnimation.keyTimes = [0, 0.5, 1]
colorKeyframeAnimation.duration = 2
This animation will run for the 2.0 duration.
We have 3 values and 3 keyTimes in this example.
0 is the initial point and 1 be the last point.
It shows which associated values will reflect at particular interval [keyTimes] during the animation.
i.e:
KeyTime 0.5 -> (2 * 0.5) -> At 1 sec during animation -> UIColor.green.cgColor will be shown.
Now to answer your question from the comments, Suppose we have timer of 25 seconds and we want to show some value for last 10 seconds, we can do:
strokeAnimation.keyTimes = [0, 0.25, 0.50, 0.60]
strokeAnimation.values = [timerFillColor.cgColor,timerFillColor.cgColor, timerFillColor.cgColor, timerFillColor.cgColor, UIColor.red.withAlphaComponent(0.7).cgColor]
strokeAnimation.duration = 25.0 // Let's assume duration is 25.0

AVPlayer fadeIn

I have been fighting with this to try an implement a fadeIn method for the AVPlayer. I even tried making an extension, but I don't know how to attach it to the AVPlayer.
I don't know why this doesn't work. I can see print statements that the audioPlayer volume is being adjusted up from zero to full volume, but I do not hear any volume until the function exits and then it is at full volume, with no fadeIn.
I'm working in a playground:
import Cocoa
import AVFoundation
let url = URL(fileURLWithPath: "Sample.mp3")
func fadeIn(player: AVPlayer, fadeIn: Float) {
let volumeInc = Float(1.0 / 10.0)
let fadeInc = Float(fadeIn / 10.0)
let sleepTime = useconds_t(fadeInc * 1000000)
var seconds = Float(0.0)
while seconds < fadeIn {
usleep (sleepTime)
print("gainInc (\(volumeInc)) + player.volume (\(player.volume)) = \(volumeInc + player.volume)")
if (volumeInc + player.volume) > 1.0 {
print("Breaking out of the while loop.")
break
} else {
print("Incrementing the player.volume (\(player.volume)) by gainInc (\(volumeInc))")
player.volume += volumeInc
seconds += fadeInc
}
}
}
let player = AVPlayer(url: url)
player.seek(to: CMTimeMakeWithSeconds(45, preferredTimescale: 60000))
player.volume = 0
player.play()
fadeIn(player: player, fadeIn: 2)
It appears AVPlayer is being blocked because even if I forego the method call and just put in
let player = AVPlayer(url: url)
player.seek(to: CMTimeMakeWithSeconds(45, preferredTimescale: 60000))
player.volume = 0
player.play()
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
usleep(200000)
player.volume += 0.1
The player is still being blocked. I then went a step further and tried
let player = AVPlayer(url: url)
player.seek(to: CMTimeMakeWithSeconds(45, preferredTimescale: 60000))
player.volume = 1
player.play()
usleep(2000000)
And the player is still blocked. I haven't read anything that requires AVPlayer to be in it's own thread, so is this behavior correct? Do I need crawl out of this rabbit hole to find another to go down?
EDIT: So, I implemented the answer and I thought I had that viola moment in that I could then monitor any key presses with a play timer and using nested timers to fade in or fade out based upon key presses while keeping track of the play time. However, trying to implement the answer even further, I am once again experiencing a blocking action when trying to timer to fadeItIn within the playTimer timer.
I'm basically wanting to create an app that acts like a radio station scanner to step through my media library and fade a song in over a 2 second period and play for 10 seconds (probably 10, but I use 25 for testing), and if I like it and want to hear more I can click the "Continue playing to end" button. However, if I don't click that button, or pause/stop/next track buttons, the player ends with a 2 second fade out and goes on to the next track in my media library.
Updated code:
import Cocoa
import AVFoundation
import PlaygroundSupport
let url = URL(fileURLWithPath: "Sample.mp3")
let startTime = 45
let fadeIn = 2
let fadeOut = 2
let playTime = 25
let player = AVPlayer(url: url)
let maxVolume:Float = 1.00
var toEnd = false
var pausePlayer = false
var nextTrack = false
var stopPlayer = false
var timePlayed = 0
var playerVolume:Float = 0.00
player.seek(to: CMTimeMakeWithSeconds(Float64(startTime), preferredTimescale: 60000))
player.volume = 0
player.play()
print("player maxVolume = \(maxVolume)")
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { playTimer in
let currentTime = Int(player.currentTime().seconds-Double(startTime))
if currentTime > timePlayed {
timePlayed += 1
print("Current play time = \(currentTime)")
}
if currentTime == 0 {
Timer.scheduledTimer(withTimeInterval: Double(fadeIn/10), repeats: true) { fadeItIn in
print("player.volume before fadeIn adjustment = \(player.volume)")
if playerVolume < maxVolume {
// Doing this to prevent volume from exceeding 1.00
playerVolume += 0.05
player.volume = playerVolume
print("player.volume after fadeIn adjustment = \(player.volume)")
}
if playerVolume >= maxVolume {
print("fadeIn volume has reached maximum volume at \(player.volume), invalidating faderItIn")
fadeItIn.invalidate()
}
}
}
if currentTime >= playTime-fadeOut {
playerVolume = player.volume
Timer.scheduledTimer(withTimeInterval: Double(fadeOut/10), repeats: true) { fadeItOut in
print("player.volume before fadeOut adjustment= \(player.volume)")
if playerVolume > 0.00 {
// Doing this to prevent the volume from going negative
playerVolume -= 0.05
player.volume = playerVolume
print("player.volume after fadeOut adjustment = \(player.volume)")
}
if player.volume <= 0 {
print("fadeOut volume has reached minimum volume at \(player.volume), invalidating fadeItOut")
fadeItOut.invalidate()
}
}
print("Pausing player")
player.pause()
print("Setting player item to nil")
player.replaceCurrentItem(with: nil)
print("Invalidating playTimer")
playTimer.invalidate()
} else if pausePlayer && player.timeControlStatus.rawValue == 1 {
player.pause()
} else if toEnd || nextTrack || stopPlayer && player.timeControlStatus.rawValue == 1 {
playTimer.invalidate()
if stopPlayer || nextTrack {
player.replaceCurrentItem(with: nil)
}
}
}
if toEnd {
Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { playTimer in
let currentTime = Int(player.currentTime().seconds-Double(startTime))
if currentTime > timePlayed {
print("Play time: \(currentTime)")
timePlayed += 1
}
if pausePlayer && player.rate > 0 {
player.pause()
} else if !pausePlayer && player.rate == 0 {
player.play()
}
if stopPlayer || nextTrack {
player.pause()
player.replaceCurrentItem(with: nil)
playTimer.invalidate()
}
}
}
AVPlayer is UI component that works on the main thread so with usleep you block the thread and that can cause crashes in real applications.
To implement your feature you should change the player's volume asynchronous for instance with Timer on the current run loop:
Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in
player.volume += 0.1
if player.volume >= 1 {
timer.invalidate()
}
}

Swift - Animate Image sequence

I have a list of images that I want to animate in Swift. I have tried to find the best possible way to do that - but I still have some problems.
The way I animate my image-list right now is by:
var animatedImage = UIImageView()
var velkomstImgList = [UIImage]()
override func viewDidAppear(_ animated: Bool) {
animatedImage.frame = CGRect(x: 0, y: 0, width: self.scrollView.frame.width, height: 200)
animatedImage.contentMode = .scaleAspectFit
scrollView.addSubview(animatedImage)
animateVelkomst()
}
func animateVelkomst() {
for i in 0...150 {
velkomstImgList.append(UIImage(named:"Velkomst_\(i).png")!)
if i == 150 {
self.animatedImage.animationImages = self.velkomstImgList
self.animatedImage.animationDuration = 5.0
self.animatedImage.startAnimating()
}
}
}
It works and the animation appears as expected. But the for loop in the beginning takes pretty long time and I dont think this is the right way to show the animation. Any suggestions on how I should animate the image sequence?
loading is slow because you are attempt to load 150 images same time. you can use timer and load image when you need it.
let imageCount = 150
var frameIndex = 0
var timer: Timer?
func startAnimation() {
frameIndex = 0
let deltaTime = 5.0 / Double(imageCount)
timer = Timer(timeInterval: deltaTime, target: self, selector: #selector(updateFrame), userInfo: nil, repeats: true)
}
func stopAnimation() {
timer?.invalidate()
timer = nil
}
#objc func updateFrame(_ timer: Timer) {
self.animatedImage.image = UIImage(named:"Velkomst_\(frameIndex).png")
frameIndex += 1
if frameIndex > imageCount {
frameIndex = 0
}
}

Swift Countdown Function

I am attempting to create a countdown timer for a game using SpriteKit, but whenever I try to run countDown(), my game freezes. I am pretty sure my logic is correct here. I do not know what is going on.
func countDown(){
let countDownWait = SKAction.wait(forDuration: 1.0)
repeat {
self.run(countDownWait){
self.countDownTime -= 1
}
} while (self.countDownTime > 0)
if self.countDownTime == 0{
self.runGameOver()
}
}
you can do some checking in the update func for time passed or use a SKAction to track time similar to what you were doing in your code
let someLabel = SKLabelNode()
func countdown() {
var offset: Double = 0
for x in (0...10).reversed() {
run(SKAction.wait(forDuration: offset)) {
someLabel.text = "\(x)"
if x == 0 {
//do something when counter hits 0
//self.runGameOver()
}
else {
//maybe play some sound tick file here
}
}
offset += 1.0
}
}
Here's how I solved this problem for my Swift/SpriteKit 'Breakout' application. I wanted a countdown from 5 to 1 onthe main game screen but before the ball started to move. I Added these functions and then a call to countdown(5) at the end of didMoveToView: Notice the ball.physicsBody!.applyImpulse(CGVectorMake(20, 20)) as the last step of endCountdownwhich starts the ball and effectively starts the game.
func countdown(count: Int) {
countdownLabel.horizontalAlignmentMode = .Center
countdownLabel.verticalAlignmentMode = .Baseline
countdownLabel.position = CGPoint(x: size.width/2, y: size.height*(1/3))
countdownLabel.fontColor = SKColor.whiteColor()
countdownLabel.fontSize = size.height / 30
countdownLabel.zPosition = 100
countdownLabel.text = "Launching ball in \(count)..."
addChild(countdownLabel)
let counterDecrement = SKAction.sequence([SKAction.waitForDuration(1.0),
SKAction.runBlock(countdownAction)])
runAction(SKAction.sequence([SKAction.repeatAction(counterDecrement, count: 5),
SKAction.runBlock(endCountdown)]))
}
func countdownAction() {
count--
countdownLabel.text = "Launching ball in \(count)..."
}
func endCountdown() {
countdownLabel.removeFromParent()
ball.physicsBody!.applyImpulse(CGVectorMake(20, 20))
}
Try this solution to run countdown...
var seconds = 7200
var timer = Timer()
override func viewDidLoad() {
super.viewDidLoad()
runTimer()
}
func runTimer() {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(self.updateTimer)), userInfo: nil, repeats: true)
}
#objc func updateTimer() {
if seconds < 1 {
timer.invalidate()
//Send alert to indicate time's up.
} else {
seconds -= 1
timerLabel.text = timeString(time: TimeInterval(seconds))
}
}
func timeString(time:TimeInterval) -> String {
let hours = Int(time) / 3600
let minutes = Int(time) / 60 % 60
let seconds = Int(time) % 60
return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
}
Have you considered using a timer? Here is a good tutorial. It might take you 20 minutes to go through it or so, but it goes into detail about starting, stopping, pausing, and more for timers.

How can we programmatically change the brightness of the iPhone screen?

How can I change the brightness of the screen programmatically using iPhone SDK?
[[UIScreen mainScreen] setBrightness: yourvalue];
Requires iOS 5.0 or later. yourvalue is a float between 0.0 and 1.0.
UPDATE: For Swift 3
UIScreen.main.brightness = YourBrightnessValue
Here's the swift answer to perform this
UIScreen.mainScreen().brightness = YourBrightnessValue
YourBrightnessValue is a float between 0.0 and 1.0
I had some problems with changing the screen brightness in viewDidLoad/viewWillDisappear so I created a singleton class to handle all the action. This is how I do it:
import Foundation
import UIKit
final class ScreenBrightnessHelper {
private var timer: Timer?
private var brightness: CGFloat?
private var isBrighteningScreen = false
private var isDarkeningScreen = false
private init() { }
static let shared = ScreenBrightnessHelper()
func brightenDisplay() {
resetTimer()
isBrighteningScreen = true
if #available(iOS 10.0, *), timer == nil {
brightness = UIScreen.main.brightness
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
UIScreen.main.brightness = UIScreen.main.brightness + 0.01
if UIScreen.main.brightness > 0.99 || !self.isBrighteningScreen {
self.resetTimer()
}
}
}
timer?.fire()
}
func darkenDisplay() {
resetTimer()
isDarkeningScreen = true
guard let brightness = brightness else {
return
}
if #available(iOS 10.0, *), timer == nil {
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
UIScreen.main.brightness = UIScreen.main.brightness - 0.01
if UIScreen.main.brightness < brightness || !self.isDarkeningScreen {
self.resetTimer()
self.brightness = nil
}
}
timer?.fire()
}
}
private func resetTimer() {
timer?.invalidate()
timer = nil
isBrighteningScreen = false
isDarkeningScreen = false
}
}