Unityからスマホの曲を取得、一覧表示して再生するPlugin

普段はUnity C#でスマホゲームを作っていますが、今回やりたい事がその範囲で対応できなかったので、iOS/AndroidでPluginを書くことにしました。
# やりたかったこと
 **スマホ内の曲を一覧表示し、選択した曲を再生する。**
作成にあたり、他記事、Githubを大分参考にさせていただきました。
Swift,Javaは未経験なので、突っ込み所あれば突っ込んで頂けるとありがたいです。

# pluginを呼び出すCSharp

```csharp:MediaPicker

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Runtime.InteropServices;

/*
 * 音楽再生.
 * */
public class MusicPlay : MonoBehaviour {

	#if UNITY_IOS
	[DllImport ("__Internal")]
	public static extern void MusicOpen_();

	#elif UNITY_ANDROID
	public musicPicker musicPicker_;
	#endif

	void Awake(){
		#if UNITY_ANDROID
		musicPicker_ = gameObject.AddComponent<musicPicker> ();
		#endif
	}

	public void openMedia(){
	#if UNITY_ANDROID
		musicPicker_.musicList();
	#elif UNITY_IOS
		Debug.Log(">>> MusicOpen_ call");
		MusicOpen_();
	#else
	#endif
	}
}
```

# iOS
Objective-Cは書けそうになかったので、Swiftで書きました。
UnityC# -> Obj-C -> Swift の呼び出しが可能と解り、以下のようなコードになりました。MusicOpen_以外はSwiftです。
NSObject（詳細ははっきりしませんでしたが、必須？のようです）とprotcolを継承したViewBindから、UIViewControllerであるViewControllerを作成しています。
StoryBoardなども使う事無くテーブル一覧を作成できるので素晴らしいです。
UnityのプロジェクトフォルダのAssets/Plugins/iOSの下に、以下のコードを置いてください。


```objc:MusicOpen.mm
# import <Foundation/Foundation.h>
# import <XXX-Swift.h> // XCodeが作成するヘッダ。扱いについては別の良記事が参考に
extern "C" {
    void MusicOpen_()
    {
        NSLog(@">>> MusicOpen_");
        [ViewBind initTableView] ;
    }
}
```
```swift:ViewBind.swift
import Foundation
import MediaPlayer
import UIKit

open class ViewBind : NSObject, audiosend{

    static var audioPlayer: AVAudioPlayer! = nil
    static var view: ViewController! = nil

	static func initTableView(){
		//UnityのViewControllerの取得.
		let unityViewController = UnityGetGLViewController()
		
		view = ViewController()
 		
		let nav = UINavigationController(rootViewController: view)
		
		// 画面遷移.
        //使ってませんが、無名関数呼び出しのメモのために置いておく
        //unityViewController?.present(nav, animated: true, completion: (() -> Void) { SendMe() })
        unityViewController?.present(nav, animated: true, completion: nil)
	}
    func SendMe( ){
    }

    //protocolで呼び出される
    //C#のdelegateとは大分違います
    //swiftのdelegateについてはqiitaにも記事があって参考になりました
    func audioSendMethod( audio: AVAudioPlayer)
    {
        ViewBind.audioPlayer = audio
    }
}

```
```swift:ViewController.swift
import UIKit
import MediaPlayer
import AVFoundation

//protcol
//ViewBind と ViewControllerを audiosend を介して、delegate呼び出し可能にします
@objc protocol audiosend {
    func audioSendMethod( audio: AVAudioPlayer)
}

class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    var myTableView: UITableView!
    var albums: [SongInfo] = []
    var songQuery: SongQuery = SongQuery()
    var audio: AVAudioPlayer! = nil
    
    weak var delegate: audiosend?
   
    override func viewDidLoad() {
        
        super.viewDidLoad()

        albums = songQuery.get()
        if(albums.count == 0){
            self.dismiss(animated: true, completion: nil)
            return;
        }

        let barHeight: CGFloat = UIApplication.shared.statusBarFrame.size.height
        let displayWidth: CGFloat = self.view.frame.width
        let displayHeight: CGFloat = self.view.frame.height
        
        myTableView = UITableView(frame: CGRect(x: 0, y: barHeight, width: displayWidth, height: displayHeight))

        self.title = "Songs"
        
        self.myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "MyCell")
        self.myTableView.delegate = self
        self.myTableView.dataSource = self
        self.view.addSubview(self.myTableView)
        print(">>> viewDidLoad")
   }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
     // sectionの数を返す
    func numberOfSectionsInTableView( in tableView: UITableView! ) -> Int {
        return albums.count
    }
    
    // 各sectionのitem数を返す
    func tableView( _ tableView: UITableView, numberOfRowsInSection section: Int ) -> Int  {
        return albums.count
    }
    
    func tableView( _ tableView: UITableView, cellForRowAt indexPath : IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "MyCell", for: indexPath as IndexPath)
        cell.textLabel!.text = albums[indexPath.row].songTitle
        return cell;
    }
    
    // sectionのタイトル
    func tableView( _ tableView: UITableView, titleForHeaderInSection section: Int ) -> String? {
        return ""
    }
    
    // 選択した音楽を再生
    func tableView( _ tableView: UITableView, didSelectRowAt indexPath:IndexPath ) {
        
        // soundIdからMediaItemを取得
        let songId: NSNumber = albums[indexPath.row].songId
        let item: MPMediaItem = songQuery.getItem( songId )
        
        let url: URL = item.value( forProperty: MPMediaItemPropertyAssetURL ) as! URL
        
        self.title = albums[indexPath.row].songTitle
        
        do{
            audio = try AVAudioPlayer(contentsOf: url)
            audio.numberOfLoops = -1
            audio.prepareToPlay()
            audio.play()
        }catch{
        }
        //Viewが終了しても、選択して再生したaudioが消滅しないように、苦肉の策
        //ViewBindのstaticに渡してしまう
        self.delegate?.audioSendMethod(audio: audio)
        self.dismiss(animated: true, completion: nil)
    }
}
```
```swift:SongQuery,swift
import Foundation
import MediaPlayer

// 曲情報
struct SongInfo {
    
    var albumTitle: String
    var artistName: String
    var songTitle:  String
    var songId   :  NSNumber
}

// アルバム情報
struct AlbumInfo {
    var albumTitle: String
    var songs: [SongInfo]
}

class SongQuery {
    
    // iPhoneに入ってる曲を全部返す
    func get() -> [SongInfo] {
        
        // アルバム情報から曲を取り出す
        let songsQuery: MPMediaQuery = MPMediaQuery.songs()
        songsQuery.addFilterPredicate(MPMediaPropertyPredicate(value: false, forProperty: MPMediaItemPropertyIsCloudItem))
        if #available(iOS 9.2, *) {
            songsQuery.addFilterPredicate(MPMediaPropertyPredicate(value: false, forProperty: MPMediaItemPropertyHasProtectedAsset))
        } else {
            // Fallback on earlier versions
        }
        
        if(songsQuery.collections?.isEmpty == true){
            print(">>> songsQuery.collections empty")
        }else{
            print(">>> songsQuery.collections \(String(describing: songsQuery.collections?.count))")
        }
        let songsItems: [MPMediaItem] = songsQuery.items! as [MPMediaItem]
        var songs: [SongInfo] = []
        for song in songsItems {
                let songInfo: SongInfo = SongInfo(
                    albumTitle: song.value( forProperty: MPMediaItemPropertyAlbumTitle ) as! String,
                    artistName: song.value( forProperty: MPMediaItemPropertyArtist ) as! String,
                    songTitle:  song.value( forProperty: MPMediaItemPropertyTitle ) as! String,
                    songId:     song.value( forProperty: MPMediaItemPropertyPersistentID ) as! NSNumber
                )
                print(">>> \(songInfo.songTitle)")
                songs.append( songInfo )
        }
        return songs
    }
    
    // songIdからMediaItemを取り出す
    func getItem( _ songId: NSNumber ) -> MPMediaItem {
    
        let property: MPMediaPropertyPredicate = MPMediaPropertyPredicate( value: songId, forProperty: MPMediaItemPropertyPersistentID )
        
        let query: MPMediaQuery = MPMediaQuery()
        query.addFilterPredicate( property )
        
        var items: [MPMediaItem] = query.items!
        
        return items[items.count - 1]
    }
}
```

# Andeoid
後日

# 免責
上記コードを使用する事は可能ですが、発生したあらゆる問題等の責任は負いません