Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

普段はUnity C#でスマホゲームを作っていますが、今回やりたい事がその範囲で対応できなかったので、iOS/AndroidでPluginを書くことにしました。

やりたかったこと

スマホ内の曲を一覧表示し、選択した曲を再生する。
作成にあたり、他記事、Githubを大分参考にさせていただきました。
Swift,Javaは未経験なので、突っ込み所あれば突っ込んで頂けるとありがたいです。

pluginを呼び出す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の下に、以下のコードを置いてください。

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

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)
    }
}
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

後日

免責

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした