I have an app in which I want to have a single audio player, and the ability to switch out what audio clips are in the player. Currently using AKAudioPlayer and replace(file: audioFile).
I have the following class that gets created on the view controller loading:
class AudioFilePlayer {
var songFile = Bundle.main
var player: AKAudioPlayer!
func play(file: String, type: String) {
var audioFile: AKAudioFile!
let song = songFile.path(forResource: file, ofType: type)
do {
let url = URL(string: song!)
audioFile = try AKAudioFile(forReading: url!)
} catch {
AKLog(error)
}
do {
player = try AKAudioPlayer(file: audioFile)
} catch {
AKLog(error)
}
}
func rePlay(file: String, type: String) {
var audioFile: AKAudioFile!
let song = songFile.path(forResource: file, ofType: type)
do {
let url = URL(string: song!)
audioFile = try AKAudioFile(forReading: url!)
} catch {
AKLog(error)
}
do {
try player.replace(file: audioFile)
} catch {
AKLog(error)
}
}
func pause(){
player.pause()
}
}
Once the app starts, I have the following code to set up the AK signal chain and create a player with an audio file, and I immediately pause it:
audioFilePlayer.play(file: "Breathing_01", type: ".mp3")
audioFilePlayer.player.looping = false
AudioKit.output = audioFilePlayer.player
do {
try AudioKit.start()
} catch {
AKLog("AudioKit did not start!")
}
audioFilePlayer.player.play()
audioFilePlayer.pause()
Elsewhere in the app, I have the following code to replace the audio file used in the player:
self.audioFilePlayer.player.pause()
self.audioFilePlayer.rePlay(file: "Breathing_01", type: "mp3")
self.audioFilePlayer.player.play()
When I run the app and initiate the process of trying to replace the file, I see this log:
2020-04-05 17:32:13.674413-0700 Mindful[24081:4439478] [general] AKAudioPlayer.swift:replace(file:):397:AKAudioPlayer -> File with "Breathing_01.mp3" Reloaded (AKAudioPlayer.swift:replace(file:):397)
2020-04-05 17:32:13.686119-0700 Mindful[24081:4439478] [general] AKAudioPlayer.swift:startTime:171:AKAudioPlayer.startTime = 0.0, startingFrame: 0 (AKAudioPlayer.swift:startTime:171)
2020-04-05 17:32:14.282632-0700 Mindful[24081:4439478] [general] AKAudioPlayer.swift:updatePCMBuffer():570:read 13359773 frames into buffer (AKAudioPlayer.swift:updatePCMBuffer():570)
But no audio output at all. When setting breakpoints, I can confirm that my player is playing, but have no audio.
Any help appreciated!
I had the same issue. Doing the following fixed it:
try player.replace(file: audioFile)
DispatchQueue.main.async {
self.player.play(from: 0)
}
Related
I am having a weird situation and have no clue how to handle this , I am downloading the videos from firestorage and caching into device for future use , meanwhile the background thread is already doing its job , I am passing a video url to the function to play the video. The issue is that sometimes avplayer is playing the right video and sometimes taking some other video url from the cache.
you can find the code in below :
func cacheVideo(for exercise: Exercise) {
print(exercise.imageFileName)
guard let filePath = filePathURL(for: exercise.imageFileName) else { return }
if fileManager.fileExists(atPath: filePath.path) {
// print("already exists")
} else {
exercise.loadRealURL { (url) in
print(url)
self.getFileWith(with: url, saveTo: filePath)
}
}
}
writing file here
func getFileWith(with url: URL, saveTo saveFilePathURL: URL) {
DispatchQueue.global(qos: .background).async {
print(saveFilePathURL.path)
if let videoData = NSData(contentsOf: url) {
videoData.write(to: saveFilePathURL, atomically: true)
DispatchQueue.main.async {
// print("downloaded")
}
} else {
DispatchQueue.main.async {
let error = NSError(domain: "SomeErrorDomain", code: -2001 /* some error code */, userInfo: ["description": "Can't download video"])
print(error.debugDescription)
}
}
}
}
now playing the video using this
func startPlayingVideoOnDemand(url : URL) {
activityIndicatorView.startAnimating()
activityIndicatorView.isHidden = false
print(url)
let cachingPlayerItem = CachingPlayerItem(url: url)
cachingPlayerItem.delegate = self
cachingPlayerItem.download()
// cachingPlayerItem.preferredPeakBitRate = 0
let avasset = AVAsset(url: url)
let playerItem = AVPlayerItem(asset: avasset)
let player = AVPlayer(playerItem: playerItem)
player.automaticallyWaitsToMinimizeStalling = false
initializeVideoLayer(for: player)
}
any suggestions would be highly appreciated.
this was solved because the data model which i was using to download bunch of videos files was accessed in background thread and meanwhile i was trying to assign the url to the same data model class in order to fetch the video and play in avplayer. Hence this was the issue and resolved by simply adding a new attribute into data model for assigning the url to play right away.
I'm trying to play a file I downloaded from S3, after locating the file and passing the URL to the audio player, the audio file won't play/is missing.
This is the complete fileURL: file:///var/mobile/Containers/Data/Application/61F2FC20-4C62-4263-B147-0010805BC0FA/Documents/Dump%20Trucks.mp3
Here is my code:
func playAudio(){
var soundClip: AVAudioPlayer?
if let directory = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true).first{
let path = NSURL(fileURLWithPath: directory).appendingPathComponent("Dump Trucks.mp3")
print("The path is \(String(describing: path))")
do {
soundClip = try AVAudioPlayer(contentsOf: path!)
soundClip?.play()
} catch {
print("Error: Audio File missing.")
}
}
}
The problem is that your player is a local variable. Thus your method comes to an end and the player is destroyed before it ever has a chance to start playing! Declare it as an instance property instead.
So
func playAudio(){
var soundClip: AVAudioPlayer?
Becomes
var soundClip: AVAudioPlayer?
func playAudio(){
This might not solve all your problems but if you don’t do it you certainly will never hear sound.
import Foundation
import AVFoundation
final class MediaPlayer {
static var player = AVAudioPlayer()
class func play() {
do {
let file = Bundle.main.url(forResource: "file_name", withExtension: "mp3")!
player = try AVAudioPlayer(contentsOf: file)
player.numberOfLoops = 0 // loop count, set -1 for infinite
player.volume = 1
player.prepareToPlay()
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [])
try AVAudioSession.sharedInstance().setActive(true)
player.play()
} catch _ {
print("catch")
}
}
}
I used the function slowPlay to create an action that will play background music. I tried to do the reverse in stop to stop the music. But the music does not stop.
I just want to have a button that plays the music and stops the music. I'm using the code:
#IBAction func play(_ sender: Any) {
slowPlay
}
#IBAction func stop(_ sender: Any) {
slowStop
}
func slowPlay() {
do {
let ap = Bundle.main.path(forResource: "slowslow", ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: ap!) as URL)
} catch {
////
}
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayback)
} catch {
}
player?.play()
}
func slowStop() {
do {
let ap = Bundle.main.path(forResource: "slowslow", ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: ap!) as URL)
} catch {
////
}
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayback)
} catch {
}
player?.stop()
}
If you look at your code example you are creating a new instance of the player for both play and stop.
You need to create a class variable for your player, something like:
var player: AVAudioPlayer?
This will allow you to stop like this:
func slowStop() {
player?.stop()
}
Create a single instance for player like blow
var player: AVAudioPlayer?
func slowPlay() {
do {
let ap = Bundle.main.path(forResource: "slowslow", ofType: "mp3")
self.player = try AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: ap!) as URL)
} catch {
////
}
let session = AVAudioSession.sharedInstance()
do {
try session.setCategory(AVAudioSessionCategoryPlayback)
} catch {
//catch error here
}
self.player?.play()
}
func slowStop() {
self.player?.stop()
}
You can add validation to check whether player is valid or not before performing any action.
I try to convert the code which i use to fetch image asset to fetch mp3 file on cloudkit. However, i can't figure the data part. I'm using this library to play audio. It only has one class called "AudioPlayer" so if i want to play music on local folder, it is enough to declare it.
https://github.com/tbaranes/AudioPlayerSwift
func loadSong(completion:#escaping (_ song: AudioPlayer?) -> ()) {
// 1
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
var song: AudioPlayer!
defer {
completion(song)
}
// 2
guard let asset = self.record["Song_File"] as? CKAsset else {
return
}
let songData: AudioPlayer
do {
songData = try Data(contentsOf: asset.fileURL)
} catch {
return
}
song = AudioPlayer(contentsOf: <#T##URL#>)
}
}
Actually, i can't do clearly what i want. (I wanted to stream a song from CloudKit, this one downloads the ckasset). Below code gets the URL of CkAsset so that i can put it in AVPlayer and play it.
func loadSongURL(completion:#escaping (_ url: URL?) -> ()) {
// 1
DispatchQueue.global(qos: DispatchQoS.QoSClass.background).async {
var song_url: URL!
defer {
completion(song_url)
}
// 2
guard let asset = self.record["Song_File"] as? CKAsset else {
return
}
let songURL: URL
do {
print(asset.fileURL)
songURL = asset.fileURL
} catch {
return
}
song_url = songURL
Playing a sound is so verbose in code and would like to create an extension of AVAudioSession if possible. The way I'm doing it is assigning an object to a variable, but need help/advise on how to set up this function so it's reusable and optimized.
Here's what I have:
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
I think I have to create the audioPlayer variable outside of this function, otherwise I had a hard time playing anything. Maybe it can be self contained? I'm hoping to use it something like this:
AVAudioSession.sharedInstance().play("bebop")
Taking the code straight from your example I see two options:
1) Is there any particular reason why you want to make it as an extension of AVAudioSession? If not, just make your own service!
class AudioPlayerService {
static let sharedInstance = AudioPlayerService()
var audioPlayer: AVAudioPlayer?
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
}
2) If you do need to make it as an extension of AVAudioSession, then take a look at associated objects
extension AVAudioSession {
private struct AssociatedKeys {
static var AudioPlayerTag = "AudioPlayerTag"
}
var audioPlayer: AVAudioPlayer? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.AudioPlayerTag) as? AVAudioPlayer
}
set {
if let newValue = newValue {
objc_setAssociatedObject(
self,
&AssociatedKeys.AudioPlayerTag,
newValue as AVAudioPlayer?,
.OBJC_ASSOCIATION_RETAIN_NONATOMIC
)
}
}
}
func playSound(name: String, extension: String = "mp3") {
let sound = NSBundle.mainBundle().URLForResource(name, withExtension: extension)
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
audioPlayer = try AVAudioPlayer(contentsOfURL: sound!)
audioPlayer?.prepareToPlay()
audioPlayer?.play()
} catch { }
}
}
This is the best way I have found to add sound
Filename is shootMissile.wav
func shootMissileSound() {
if let soundURL = NSBundle.mainBundle().URLForResource("shootMissile", withExtension: "wav") {
var mySound: SystemSoundID = 0
AudioServicesCreateSystemSoundID(soundURL, &mySound)
AudioServicesPlaySystemSound(mySound);
}
}