foresthillのブログ

ホームページ・CMS制作からアプリ開発、ゲーム製作となんでもござれ。

【iPhoneアプリ】【Swift】SolPlayer開発記(4)iPhone内にあるファイルを読み込む方法

すべての音源をソルフェジオに変える音楽プレイヤー、
「SolPlayer」の開発記、第4弾。(ここまでコピペ)

前回の記事では、AVAudioEngineを使って曲を再生する方法を紹介しました。

前回の記事↓
foresthill.hatenablog.com

今回は、iPhone内にあるファイルを読み込む」方法を紹介します。

実装方法

私がアホなせいかもしれませんが、ここは意外と苦労しました。
redmineのチケット記録を観ると、予定工数が1に対し、実工数は6。(時間単位)

まぁこれを読んでくれているあなたは工数1以内で行けると思います^^b

さて、それでは実際の実装方法を紹介します。

メディアピッカー(MediaPicker)を使う

iPhone内にあるファイルを読み込む方法は、
ズバリ「メディアピッカー(MediaPicker)」を使うこと。

他にもいくつか方法はあるみたいですが、
ひと通り試した結果、自分にはこの方法が一番やりやすかったです。

このメディアピッカーを使うことで、読み込み処理を
自分で実装する必要がなくなります。

あとこちらでやるべきことは、
このメディアピッカーを呼び出す処理と、
そこから情報を取得する処理です。

さて、それでは例のごとく、他力本願でやっていきます。

nackpan.net

上記記事を参考に、SolPlayerに実装したコードが以下となります。

必要な実装としては、細かく言えば以下のようになります。

  1. クラスの定義に「MPMediaPickerControllerDelegate」を付け加える
  2. 以下の3つの処理を追加
    1. メディアピッカー(MediaPicker)を呼び出す
    2. メディアピッカー(MediaPicker)でファイルが選択された後
    3. メディアピッカー(MediaPicker)で「キャンセル」が押されたとき
1.クラス定義
import UIKit
import MediaPlayer
import AVFoundation
 
class ViewController: UIViewController, MPMediaPickerControllerDelegate {
2-1.メディアピッカーを呼び出す
    @IBAction func addSong(sender: UIButton) {
        //MPMediaPickerControllerのインスタンス作成
        let picker = MPMediaPickerController()
        //pickerのデリゲートを設定
        picker.delegate = self
        //複数選択を可にする(true/falseで設定)
        picker.allowsPickingMultipleItems = true
        //AssetURLが読み込めない音源は表示しない
        picker.showsItemsWithProtectedAssets = false
        //CloudItemsもAssetURLが読み込めないので表示しない
        picker.showsCloudItems = false
        //ピッカーを表示する
        presentViewController(picker, animated:true, completion: nil)
    }
    
2-2.メディアピッカーで選択された情報を取り出す
    //メディアアイテムピッカーでアイテムを選択完了した時に呼び出される(必須)
    func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {

        //playlistにmediaItemを追加
        mediaItemCollection.items.forEach { (mediaItem) in
            playlist?.append(mediaItem)
        }

        //ピッカーを閉じ、破棄する
        self.dismissViewControllerAnimated(true, completion: nil)
    }

mediaItemCollectionに選択されたファイルが、[MediaItem](MediaItemの配列)という形で入っています。
それを自作したプレイリスト(playlist)という配列型のデータに追加しています。

2-3.メディアピッカーで「キャンセル」された時
    //選択がキャンセルされた場合に呼ばれる
    func mediaPickerDidCancel(mediaPicker: MPMediaPickerController) {
        // ピッカーを閉じ、破棄する
        dismissViewControllerAnimated(true, completion: nil)
    }

補足

ただ、このメディアピッカーを使う際に、
いくつか補足や注意点があります。

ファイル読み込みに関する注意点

複数選択可にする

参考サイトでは複数選択を不可(false)にしていましたが、
使ってみた感じ、複数選択できたほうが何かと便利なので、
「true(可)」にしています。

//複数選択を可にする(true/falseで設定)
picker.allowsPickingMultipleItems = true
AssetURLを読み込めない音源を非表示にする

AssetURLを読み込めない音源ファイルは、そもそもAVAudioPlayerNodeで再生できません。
なので、ファイル自体を選択できないように、メディアピッカー上でも非表示にしちゃいます。

(ちなみに参考元のサイトでは、1つだけ音源を読み込み、その音源のAssetURLがnilの場合
再生しないという挙動にしているため、エラーは起きないようになっています)

//AssetURLが読み込めない音源は表示しない
picker.showsItemsWithProtectedAssets = false

//CloudItemsもAssetURLが読み込めないので表示しない
picker.showsCloudItems = false

MediaItemに入っている情報

メディアピッカーでファイルを選択すると
MediaItemというクラスで取得できます。

この、MediaItemに入っている情報と、
入っていない情報があるので、

取得できる情報
  • assetURL(音源のURL)※一番重要
  • persistentID(ファイルを一意に特定するID)
  • title(タイトル)
  • artist(アーティスト名)
  • albumTitle(アルバム名)
  • artwork(アートワーク:画像)
  • その他もろもろ
取得できない情報
  • 再生時間

「再生時間こそ欲しいんだよ!」ってところで
イライラしたつまづいたので、ここにその実装方法を記しておきます。

呼び出しメソッド

    func getDuration() -> Double {
        
        if self.assetURL != nil {
            do {
                let audioFile = try AVAudioFile(forReading: assetURL!)
                
                //サンプルレートの取得
                let sampleRate = audioFile.fileFormat.sampleRate
                
                //再生時間
                return Double(audioFile.length) / sampleRate
                
            } catch {
               //
            }
        }
        
        return 0.0
        
    }

これを、再生時間を入れたい変数(フィールド)に入れます。

(実装例)

self.duration = getDuration()


以上、「iPhone内にあるファイルを読み込む方法」でした。


おまけ

ちなみに、実際にSolPlayerに実装した内容は、
もう少しカスタマイズしています。

    //メディアアイテムピッカーでアイテムを選択完了した時に呼び出される(必須)
    func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {

        //AppDelegateのインスタンスを取得しplayListを格納
        let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate

        //playlistにmediaItemを追加
        mediaItemCollection.items.forEach { (mediaItem) in
            playlist?.append(Song(mediaItem: mediaItem))
        }

        appDelegate.playlist = playlist

        //print("playlist=\(playlist)")

        //ピッカーを閉じ、破棄する
        self.dismissViewControllerAnimated(true, completion: nil)

        //tableviewの更新
        tableView.reloadData()

    }

付け加えた処理としては、

  1. playlistをAppDelegate上で持つようにした → 画面をまたいでplaylistが使える
  2. mediaItemをSongという自作クラスで詰め替えた → 機能を追加する際にもろもろ便利
  3. tableViewを更新 → playlistの内容がtableViewに表示されるようにしているため

こちらに関しては、もし気が向いたら詳細を解説したいと思います。
興味がある方はコメントください。

以上です。