質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.33%

  • Swift

    7688questions

    Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Swift AVAudioPlayerを導入するとページスクロールでクラッシュする

解決済

回答 1

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 269

torkia

score 14

mp3データの音の再生にAVAudioPlayerを使用しているのですが、ページをめくって画面遷移していると途中でクラッシュしてしまいます。

具体的には、初期画面のテーブルビューのセルから画面遷移をし、遷移したページからページをいくらかめくったところでクラッシュしてしまいます。

audioPlayer関連のコードを消せば、ページめくりをしてもエラーは出ません。
mp3のデータを各ページ少なくするとエラーはでません。
約260コ分ぐらいののデータをページスクロールし続けたらクラッシュします。

テーブルビューにその都度戻って各ページに移動するにはエラーはでません。
上記のことから、mp3の音データはちゃんと揃ってあります。

エラーの原因や解決方法などを教えて頂けたら大変助かります。
宜しくお願い致します。

 発生している問題・エラーメッセージ

該当コード内で
audioPlayers.append(audioPlayer)のところで[Thread1: EXC BAD INSTRUCTION]

デバックエリアの出力では
Error The operation couldn\U2019t be completed. (NSOSStatusErrorDomain error -42.)
fatal error: unexpectedly found nil while unwrapping an Optional value

 該当のソースコード

[使用バージョン Swift3]
import UIKit
import AVFoundation

class PageContentViewController: UIViewController, UIScrollViewDelegate, AVAudioPlayerDelegate {

    let soundNameArray = [
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 1
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 2
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 3
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 4
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 5
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 6
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 7
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 8
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 9
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 10
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 11
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 12
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 13
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 14
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 15
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 16
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 17
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 18
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"],  // 19
        ["1","2","1","2","1","2","1","2","1","2","1","2","1","2","1","2"]   // 20
        ]
        // 1つの配列に16コずつだと16ページ目でクラッシュ(17ページ目を呼ぶのにエラーがでている)

    var pageIndex:Int = 0           // 各ページのindex
    let indexPageLabel = UILabel()  // index確認用ラベル

    // sound関係
    var soundArray:[String] = []   // ファイル名のデータを格納する
    var audioUrls:[URL] = []       // soundArrayの要素からパスとurlを作成し格納する
    var audioPlayer:AVAudioPlayer!  // urlから音声データにして格納する
    var audioPlayers:[AVAudioPlayer] = []  // 音声データを格納する
    var soundButtons = [UIButton]()  // 音声を鳴らすボタンを格納する


    override func viewDidLoad() {
        super.viewDidLoad()

        // indexPageラベル
        indexPageLabel.frame = CGRect(x: 20, y: 10, width: 240, height: 20)
        indexPageLabel.text = "pageIndex: \(pageIndex)   ページNo: \((pageIndex) + 1)"
        indexPageLabel.backgroundColor = .yellow
        self.view.addSubview(indexPageLabel)


        soundArray = soundNameArray[pageIndex]

        // soundArrayの要素すべてを取り出してaudioPlayers配列に格納する
        for i in soundArray {
            let audioPath = Bundle.main.path(forResource: "\(i)", ofType:"mp3")!
            let audioUrl = URL(fileURLWithPath: audioPath)
            do {
                audioPlayer = try AVAudioPlayer(contentsOf: audioUrl)
                audioPlayers.append(audioPlayer)
            } catch {
                print("エラー")
            }
        }

        audioPlayer.delegate = self
        audioPlayer.prepareToPlay()


        // サウンドボタンを量産   button
        for index in 0..<soundArray.count {
            let soundButton = UIButton()
            soundButton.frame = CGRect(x: 20, y: 40 + (index * 30), width: 40, height: 30)
            soundButton.backgroundColor = UIColor.lightGray
            soundButton.titleLabel?.font = UIFont.systemFont(ofSize: 10)
            soundButton.setTitle("▶", for: .normal)
            soundButton.setTitleColor(UIColor.darkGray, for: .normal)
            soundButton.tag = index // ボタン識別用ID
            soundButton.addTarget(self, action: #selector(buttonEventSound(sender:)), for: .touchUpInside)
            soundButtons.append(soundButton)
            self.view.addSubview(soundButton)
        }

    }  //viewDidLoadを閉じる


    // ボタンイベント音再生
    func buttonEventSound(sender: UIButton) {
        let index = sender.tag
        if (audioPlayers[index].isPlaying){
            audioPlayers[index].stop()
            audioPlayers[index].currentTime = 0
        }
        else{
            for i in audioPlayers {
                if (i.isPlaying) {
                    i.stop()
                    i.currentTime = 0
                }
            }
            audioPlayers[index].play()
        }
    }

     // 音楽再生が成功した時に呼ばれるメソッド
     func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
        print("音終了")
     }

     // デコード中にエラーが起きた時に呼ばれるメソッド
     func audioPlayerDecodeErrorDidOccur(_ player: AVAudioPlayer, error: Error?) {
        print("デコードエラー")
     }


    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

}

 その他のソースコード

import UIKit

class PageViewController: UIPageViewController, UIPageViewControllerDataSource {

    var selectedIndex: Int = 0    // タップされたセルのindex
    var pageIndex:Int = 0  // 各ページに割り当てたindex
    var contentVCs = [UIViewController]()   // ページングするviewControllerを格納する配列

    override func viewDidLoad() {
        super.viewDidLoad()

        // ナビゲーションバーの透過を無効にする。
        self.navigationController!.navigationBar.isTranslucent = false

        dataSource = self

        for index in 0..<20 {
            let contentVC = storyboard?.instantiateViewController(withIdentifier: "PageContentViewController") as! PageContentViewController
            contentVC.pageIndex = index
            contentVCs.append(contentVC)
        }

        self.setViewControllers([contentVCs[selectedIndex]], direction: .forward, animated: true, completion: nil)

    }  // viewDidLoad()を閉じる


    // MARK: - UIPageViewControllerDataSource
    // スワイプでページを戻る(Before)
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? {
        guard let index = contentVCs.index(of: viewController as! PageContentViewController), index > 0 else {
            return nil
        }
        let previousVC = contentVCs[index - 1]
        return previousVC
    }

    // スワイプでページを進む(After)
    func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? {
        guard let index = contentVCs.index(of: viewController as! PageContentViewController), index < contentVCs.count - 1 else {
            return nil
        }
        let nextVC = contentVCs[index + 1]
        return nextVC
    }
} 
import UIKit

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

    let sectionTitle = ["Title"]   //セクションに表示するデータ
    let section0 = Array(1...20)   //セルに表示するデータ

    @IBOutlet weak var tableView: UITableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        tableView.delegate = self
        tableView.dataSource = self
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func numberOfSections(in tableView: UITableView) -> Int {
        return sectionTitle.count
    }

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return section0.count
    }

    func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
        return sectionTitle[section]
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 30
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = "\(section0[indexPath.row])"
        return cell
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "ToPageViewController" {
            if let indexPath = self.tableView.indexPathForSelectedRow {
                tableView.deselectRow(at: indexPath, animated: false)
                if let pageViewController = segue.destination as? PageViewController {
                    pageViewController.selectedIndex = indexPath.row
                }
            }
        }
    }

} 

 試したこと

クラッシュするページでurlまでは取得できているのですが、audioPlayerのところからnilになり(エラーチェックでエラーがあるならnilが入るようにしている)、デバックエリアに [Error The operation couldn\U2019t be completed. (NSOSStatusErrorDomain error -42.)]  と出力されます。
エラーチェックでnilが入るコード関連を消すと、1ページ分多くめくったところで、AppDelegateのクラス宣言ところに [Thread1: signal SIGABLT] と表示され、デバックエリアには [libc++abi.dylib: terminating with uncaught exception of type NSException] と出力されています。

 補足情報(FW/ツールのバージョンなど)

Swift3

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

checkベストアンサー

+1

しばらくSwiftから離れているのでお力になれるかわかりませんが。

また、エラーを実際に試していないので確定的な回答ができないのが申し訳ないですが、何も解答がつかないよりは参考になるのかなと思いとりあえず回答させていただきます。

まずはじめのfatal error: unexpectedly found nil while unwrapping an Optional valueに関しては、audioPlayer.delegateが実行されるときにaudioPlayernilになってしまっているというまあ文面そのままのエラーなので、audioPlayer.delegate = selfaudioPlayer.prepareToPlay()if audioPlayer != nilなどで囲ってあげる必要がありますね(でないと、audioPlayerがセットされなかったときにエラーでクラッシュしてしまう)

もっというと、delegateの宣言とprepareToPlay()は再生ボタンを押すタイミングでセットすべきかなと。複数のmp3ファイルを読み込んでいるので、viewDidLoad()で読んでも配列の最後の音声だけが流れることになります。なので、

   // ボタンイベント音再生
    func buttonEventSound(sender: UIButton) {

        let index = sender.tag

        if (audioPlayers[index]?.isPlaying)!{
            audioPlayers[index]?.stop()
            audioPlayers[index]?.currentTime = 0
        }
        else{
            for i in audioPlayers {
                if (i?.isPlaying)! {
                    i?.stop()
                    i?.currentTime = 0
                }
            }
            if let player = audioPlayers[index] {
                player.delegate = self
                //その他音声設定
                player.prepareToPlay()
                player.play()
            }
        }
    }

と。

で、本題のエラーなんですが、参照できるものが少なく僕もよくわかっていないです。

OSStatus

こちらのサイトで error -42を調べるとcoreAudio(AVFoundationはこれ)のエラーでkAudio_TooManyFilesOpenErrorとあるので、csvから取得したURLでAVAudioPlayerをセットしすぎている(配列にたくさん入れすぎている)か何かかなと思います。

こちらもボタンを押すたびにAVAudioPlayerを生成するようにするか、なにか設定で上限を変更するかで回避できるかもしれません。デバイスごとに挙動が変わる可能性があるので、前者のほうがいいでしょうね。。。

追記
仕様がわかりにくいので見当違いの回答なのかなと思ってはいます
TableViewでセル選択では表示されるということなので、ページめくりのときに値が渡されていないということでしょうか……
より詳しい仕様・またはプロジェクトそのものがあると回答も集まりやすいかと思います。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2018/07/11 12:59 編集

    ご回答ありがとうございます。

    なかなか回答がつかず、回答が得られるようにプロジェクトの仕様を詳しくシンプルにまとめるのに時間がかかっておりました。
    そんな折に回答がついたので、大変ありがたく、丁寧な説明もありがとうございました。参考になりました。
    最初に載せていたコードとは少し違ってしまいましたが、コードを編集したものをアップしました。
    sample用に、csvファイルからの呼び出しをやめて、mp3の配列を作成し呼び出すようにしました。

    ボタンメソッド内でdelegateとplay()をセットするようにしてもクラッシュするので、
    再生するaudioPlayerの作成もボタンメソッド内でやるようにしてみました。

    func buttonEventSound(sender: UIButton) {
    let index = sender.tag
    let audioPath = Bundle.main.path(forResource: "\(soundArray[index])", ofType:"mp3")!
    let audioUrl = URL(fileURLWithPath: audioPath)
    do {
    audioPlayer = try AVAudioPlayer(contentsOf: audioUrl)
    audioPlayer.delegate = self
    audioPlayer.prepareToPlay()
    audioPlayer.play()
    } catch {
    print("エラー")
    }
    }

    これでページングでクラッシュすることはなくなりました。
    ですが、他の問題もでてきてしまったので、また別の項目で質問させて頂こうかと思っています。
    (同じボタンで再生中の音を停止させたり、ページ移動で再生中の音を停止させる)

    ■追記■
    別の方法で、viewWillDisappearで配列audioPlayersをremoveAll()して、viewWillAppearで再生するaudioPlayerと配列audioPlayersの作成をするようにするとクラッシュしなくなりました。
    どの方法がいいのかは分からないですが・・・。

    キャンセル

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 90.33%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる

同じタグがついた質問を見る

  • Swift

    7688questions

    Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています