iOS Sound not playing in Swift - ios

I'm building a tableview in which each cell represents a sound that is played when the user taps that particular cell.
In objective-C it worked fine, but now that Apple released Swift I decided to move over instantly, but for some reason the sound does not play. My code when the user taps the cell:
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!) {
var currentItem = self.items[indexPath.row]
var audioPath = NSString(string: NSBundle.mainBundle().pathForResource(currentItem.soundID, ofType: "mp3"))
println(audioPath)
var audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: nil)
audioPlayer.play()
}
currentItem is the object in the array that has to be called. Each sound I can play is put in a custom object, together with a title and an image. that object is put in an instance of currentItem.
This is what the printNL outputs when I tapp one of my cells:
/private/var/mobile/Containers/Bundle/Application/ADF0CAFC-4C9E-475E-B3F0-CD85A1873CA5/Juichen.app/StupidQuestion.mp3
it does not give an error. I already tried moving the sound file to other folders, but that does not solve the problem either. therefore, I assume that this problem occurs because I am calling the audioPlayer incorrect?
Any help would be highly appreciated!

Let say you have a class myTable:
class myTable : UITableViewController
{
var audioPlayer:AVAudioPlayer? = nil
...
}
And to initialize audioPlayer:
func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!)
{
var currentItem = self.items[indexPath.row]
var audioPath = NSString(string: NSBundle.mainBundle().pathForResource(currentItem.soundID, ofType: "mp3"))
println(audioPath)
var error : NSError? = nil
self.audioPlayer = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: &error)
if (self.audioPlayer == nil)
{
if let playerError = error as? NSError
{
let des : String? = playerError.localizedDescription
println("Error: \(des)")
}
}
else
{
self.audioPlayer.play()
}
}
Hope this helps.

Related

Swift How can I show a video without interruption on reloadData

I am creating a social network app in order to learn swift (using Swift 4) and complete a project . I have a TableView that shows videos and I have added the functionality of 'liking' videos like any social network. My issue is that when you like a video the TableView gets reloaded to show the '+ 1 like' and the Video starts all over again. How can I make it so that the video doesn't restart every time you like a video . This is my code here
1st You get the user clicking the Like Action which sends a call to the database and insert the like and add '+1 to the like field'
#IBAction func LikeAction(_ sender: UIButton) {
DontReload = sender.tag
let url:URL = URL(string:ConnectionString+"insert_like")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let parameter = "parameters"
request.httpBody = parameter.data(using: String.Encoding.utf8)
URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil
{
print("error=\(String(describing: error))")
return
}
DispatchQueue.main.async {
self.reloadTable()
}
}.resume()
}
Then I query the database and return the new data showing the +1 like and other data in Json Format.
func reloadTable() {
var url = URL(string:ConnectionString+"streams")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let parameter = "Parameters"
request.httpBody = parameter.data(using: String.Encoding.utf8)
session.dataTask(with:request, completionHandler: {(data, response, error) in
if error != nil {
} else {
do {
let parsedData = try JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String:Any]
if let Streams = parsedData["Results"] as? [AnyObject]? {
// check for misspelled words
if streamsModel.Locations.count >= 0 {
// Set My Arrays
}
for Stream in Streams! {
// Gets Json Values
}
TableSource.reloadData()
}
}
else {
DispatchQueue.main.async {
streamsModel.Locations.removeAll()
TableSource.reloadData()
}
} catch let error as NSError {
print(error)
}
}
}).resume()
}
This is my TableViewCell and this is obviously called to show the new updated data, however if the user is watching a video and likes it while it is playing then the video restarts... any suggestions on solving this would be great.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "HomeTVC", for: indexPath) as! HomeTVC
// Starts Video Plays
cell.videoView = cell.VideoViewArray[indexPath.row]
cell.videoView.tag = indexPath.row
let movieURL = URL(string: cell.stream_image_string[indexPath.row])
cell.videoCapHeight.constant = CGFloat(Float(cell.pic_height!))
cell.playerView = AVPlayer(url: movieURL!)
cell.MyAVPlayer.player = cell.playerView
cell.MyAVPlayer.videoGravity = AVLayerVideoGravity.resizeAspectFill.rawValue
cell.MyAVPlayer.showsPlaybackControls = false
cell.MyAVPlayer.view.frame = cell.videoView.bounds
cell.videoView.addSubview(cell.MyAVPlayer.view)
controller.addChildViewController(cell.MyAVPlayer)
cell.playerView?.isMuted = false
cell.MyAVPlayer.player?.play()
// Ends Video play
return cell
}
Again my code works the only issue is that my videos restart on Table Reloads . I want to create some type of condition or flag that If a Table View Cell gets 'liked' and there is a video then I want that video to not get reloaded . Any suggestions would be great . Based on suggestions below I will stop using ReloadTable and attempt to grab a reference for that UIButton perhaps something like this
let indexPath = NSIndexPath()
let cell = self.TableSource.dequeueReusableCell(withIdentifier: "HomeTVC", for: indexPath as IndexPath) as! HomeTVC
cell.votes.setTitle("result from server",for: UIControlState.normal)
As per your requirement is seems you don't need to reload whole table view in case of just update like, Once you receive API response of "insert_like" You can update your array and directly get reference of your video running cell and update data source of it.

Swift - How to pause a playing audio by clicking the same button?

So I have a list of audio files and when a cell is pressed it plays the audio. This works fine but I am not able to pause it by clicking the same cell again.
My code for the table view:
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath)
{
let a = indexPath.section;
let b = indexPath.row;
var path = NSBundle.mainBundle().pathForResource(musicArray[b], ofType: "mp3")
var error:NSError?
do{
audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: path!))
}
catch
{
print("Something bad happened")
}
if (audioPlayer.playing)
{
audioPlayer.pause();
}
else
{
audioPlayer.play();
}
}
musicArray has the titles of the audio files
You're creating a new AVAudioPlayer every time you select the cell. You need to keep a reference to the previous one and tell that audio player to pause.
You can create a singleton object.
import AVFoundation
class SomeAudioManager: NSObject, AVAudioPlayerDelegate
{
class var sharedInstance: SomeAudioManager {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: SomeAudioManager? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = SomeAudioManager()
}
return Static.instance!
}
func audioView(songname: String,format: String) {
let audioPlayer: AVAudioPlayer
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(songname, ofType:format)!), fileTypeHint: AVFileTypeMPEGLayer3)
audioPlayer.delegate = self;
audioPlayer.play()
} catch {
// error
}
}
}
It will be alive through the whole app.
How to implement AVAudioPlayer Inside Singleton Method?
You can just create a singleton of the player so that you will always know if it's playing or not.

Casting URL from MPMediaItem

I am trying to make it so that when someone touches a cell in this UITableView, that a song will begin to play. Each cell has an MPMediaItem associated with it. I want to play the songs using an AVAudioPlayer. I am trying to get the url of the MPMediaItem and use it to get the song for the AVAudioPlayer.
var songsList: [MPMediaItem] = MPMediaQuery.songsQuery().items!
var player = AVPlayer()
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let song = songsList[indexPath.section]
if let url = song.valueForProperty(MPMediaItemPropertyAssetURL) as! NSURL? {
let newPlayerItem = AVPlayerItem(URL: url)
player.replaceCurrentItemWithPlayerItem(newPlayerItem)
player.play()
}
else {
print("Failed to cast to URL")
}
}
The variable url fails to cast as NSURL and I cannot figure out why.
Thanks for any help
After many tests, I found the problem is you can't cast a non-optional value to an optional value. So delete ? after NSURL in your origin code. May it helps.

AVAudioPlayer wont play sound when called in other swift file

I have two swift files - my ViewController:UIViewController and AudioPlayer:AVAudioPlayer.
My AudioPlayer file has this function
func seaGullSound() {
var tmp = AVAudioPlayer()
var seaGullSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("Gulls", ofType: "mp3")!)
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
tmp = AVAudioPlayer(contentsOfURL: seaGullSound, error: &error)
tmp.prepareToPlay()
tmp.play()
println("This function got called!")
}
I'm trying to call that function in my ViewController thru tapping a button, using this code:
#IBAction func playSound(sender: AnyObject) {
var audio = AudioPlayer()
audio.seaGullSound()
}
The sound is not played when I click the button. However, the print statement works. I can get the audio to play if I move seaGullSound() to the ViewController file, so I know the mp3 does work. I haven't moved the audio to ViewController because I want to develop the habit of not crowding all my code into one file. Thanks in advance for the help.
edit:
class HighScore: UIViewController {
var audioInitializer = AudioPlayer()
func updateHighScore(score:Int) -> String {
NSUserDefaults.standardUserDefaults().integerForKey("highscore")
//Check if score is higher than NSUserDefaults stored value and change NSUserDefaults stored value if it's true
if score > NSUserDefaults.standardUserDefaults().integerForKey("highscore") {
//call applause sound
audioInitializer.applauseSound()
//set score
NSUserDefaults.standardUserDefaults().setInteger(score, forKey: "highscore")
NSUserDefaults.standardUserDefaults().synchronize()
}
NSUserDefaults.standardUserDefaults().integerForKey("highscore")
//use below line to reset high score for testing
//NSUserDefaults.standardUserDefaults().removeObjectForKey("highscore")
return String(NSUserDefaults.standardUserDefaults().integerForKey("highscore"))
}}
here is the file with the sounds:
class AudioPlayer: AVAudioPlayer {
var soundMaster = AVAudioPlayer()
func tappingSound() {
var tapSoundURL = NSBundle.mainBundle().URLForResource("tapSound", withExtension: "mp3")
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
soundMaster = AVAudioPlayer(contentsOfURL: tapSoundURL, error: &error)
soundMaster.prepareToPlay()
soundMaster.play()
}
//need to call in highscore.swift
func applauseSound() {
var tapSoundURL = NSBundle.mainBundle().URLForResource("applause", withExtension: "mp3")
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
soundMaster = AVAudioPlayer(contentsOfURL: tapSoundURL, error: &error)
soundMaster.prepareToPlay()
soundMaster.play()
println("did this get called?")
}}
You just have move the declaration of your tmp AVAudioPlayer out of your method. Declare it as class variable.
You should also use URLForResource instead of pathForResource:
let seaGullSoundURL = NSBundle.mainBundle().URLForResource("Gulls", withExtension: "mp3")!
Try like this:
import UIKit
import AVFoundation
class HighScore: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func updateHighScore(score:Int) -> String {
//Check if score is higher than NSUserDefaults stored value and change NSUserDefaults stored value if it's true
if score > NSUserDefaults().integerForKey("highscore") {
//call applause sound
playAudio("applause")
//set score
NSUserDefaults().setInteger(score, forKey: "highscore")
}
//use below line to reset high score for testing
//NSUserDefaults.standardUserDefaults().removeObjectForKey("highscore")
return NSUserDefaults().integerForKey("highscore").description
}
func playAudio(audioName: String ) {
var error:NSError?
if let audioURL = NSBundle.mainBundle().URLForResource(audioName, withExtension: "mp3") {
audioPlayer = AVAudioPlayer(contentsOfURL: audioURL, error: &error)
audioPlayer.prepareToPlay()
audioPlayer.play()
} else if let error = error {
println(error.description)
}
}
#IBAction func playSound(sender: UIButton) {
playAudio("Gulls")
}
}

AVAudioPlayer not playing audio in Swift

I have this code in a very simple, single view Swift application in my ViewController:
var audioPlayer = AVAudioPlayer()
#IBAction func playMyFile(sender: AnyObject) {
let fileString = NSBundle.mainBundle().pathForResource("audioFile", ofType: "m4a")
let url = NSURL(fileURLWithPath: fileString)
var error : NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: url, error: &error)
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
if (audioPlayer.isEqual(nil)) {
println("There was an error: (er)")
} else {
audioPlayer.play()
NSLog("working")
}
I have added import AVFoundation and audioPlayer is a global variable. When I execute the code, it does print "working", so it makes it through without errors but no sound is played. The device is not in silent.
There's so much wrong with your code that Socratic method breaks down; it will probably be easiest just to throw it out and show you:
var player : AVAudioPlayer! = nil // will be Optional, must supply initializer
#IBAction func playMyFile(sender: AnyObject?) {
let path = NSBundle.mainBundle().pathForResource("audioFile", ofType:"m4a")
let fileURL = NSURL(fileURLWithPath: path)
player = AVAudioPlayer(contentsOfURL: fileURL, error: nil)
player.prepareToPlay()
player.delegate = self
player.play()
}
I have not bothered to do any error checking, but the upside is you'll crash if there's a problem.
One final point, which may or may not be relevant: not every m4a file is playable. A highly compressed file, for example, can fail silently (pun intended).
Important that AvPlayer is class member and not in the given function, else it goes out of scope... :)
I had to declare a global player variable
var player: AVAudioPlayer!
and set it in viewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
player = AVAudioPlayer()
}
Then I could play the audio file wherever like this:
func playAudioFile(){
do {
if audioFileUrl == nil{
return
}
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
/* The following line is required for the player to work on iOS 11. Change the file type accordingly*/
player = try AVAudioPlayer(contentsOf: audioFileUrl, fileTypeHint: AVFileType.m4a.rawValue)
/* iOS 10 and earlier require the following line:
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3) */
guard let player = player else { return }
player.play()
print("PLAYING::::: \(audioFileUrl)")
}
catch let error {
print(error.localizedDescription)
}
}
}
Here is a working snippet from my swift project. Replace "audiofile" by your file name.
var audioPlayer = AVAudioPlayer()
let audioPath = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("audiofile", ofType: "mp3"))
audioPlayer = AVAudioPlayer(contentsOfURL: audioPath, error: nil)
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
audioPlayer.play()
You can download fully functional Swift Audio Player application source code from here https://github.com/bpolat/Music-Player
for some reason (probably a bug) Xcode can't play certain music files in the .m4a and the .mp3 format I would recommend changing them all to .wav files to get it to play
//top of your class
var audioPlayer = AVAudioPlayer
//where you want to play your sound
let Sound = NSURL(fileURLWithPath: Bundle.main.path(forResource: "sound", ofType: "wav")!)
do {
audioPlayer = try AVAudioPlayer(contentsOf: Sound as URL)
audioPlayer.prepareToPlay()
} catch {
print("Problem in getting File")
}
audioPlayer.play()
var audioPlayer = AVAudioPlayer()
var alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("KiepRongBuon", ofType: "mp3")!)
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.prepareToPlay()
audioPlayer.play()
I used the below code in my app and it works. Hope that is helpful.
var audioPlayer: AVAudioPlayer!
if var filePath = NSBundle.mainBundle().pathForResource("audioFile", ofType:"mp3"){
var filePathUrl = NSURL.fileURLWithPath(filePath)
audioPlayer = AVAudioPlayer(contentsOfURL: filePathUrl, error: nil)
audioPlayer.play()
}else {
println("Path for audio file not found")
}
In Swift Coding using Try catch, this issues will solve and play audio for me and my code below,
var playerVal = AVAudioPlayer()
#IBAction func btnPlayAction(sender: AnyObject) {
let fileURL: NSURL = NSURL(string: url)!
let soundData = NSData(contentsOfURL: fileURL)
do {
playerVal = try AVAudioPlayer(data: soundData!)
}
catch {
print("Something bad happened. Try catching specific errors to narrow things down",error)
}
playerVal.delegate = self
playerVal.prepareToPlay()
playerVal.play()
}
Based on #matt answer but little bit detailed 'cause original answer did not completely satisfied me.
import AVFoundation
class YourController: UIViewController {
private var player : AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
prepareAudioPlayer()
}
#IBAction func playAudio() {
player?.play()
}
}
extension YourController: AVAudioPlayerDelegate {}
private extension YourController {
func prepareAudioPlayer() {
guard let path = Bundle.main.path(forResource: "you-audio", ofType:"mp3") else {
return
}
let fileURL = URL(fileURLWithPath: path)
do {
player = try AVAudioPlayer(contentsOf: fileURL)
} catch let ex {
print(ex.localizedDescription)
}
player?.prepareToPlay()
player?.delegate = self
}
}
swift 3.0:
import UIKit
import AVFoundation
class ViewController: UIViewController
{
var audioplayer = AVAudioPlayer()
#IBAction func Play(_ sender: Any)
{
audioplayer.play()
}
#IBAction func Pause(_ sender: Any)
{
if audioplayer.isPlaying
{
audioplayer.pause()
}
else
{
}
}
#IBAction func Restart(_ sender: Any)
{
if audioplayer.isPlaying
{
audioplayer.currentTime = 0
audioplayer.play()
}
else
{
audioplayer.play()
}
}
override func viewDidLoad()
{
super.viewDidLoad()
do
{
audioplayer = try AVAudioPlayer(contentsOf:URL.init(fileURLWithPath:Bundle.main.path(forResource:"bahubali", ofType: "mp3")!))
audioplayer.prepareToPlay()
var audioSession = AVAudioSession.sharedInstance()
do
{
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
catch
{
}
}
catch
{
print (error)
}
}
}

Resources