iOS upnpx で UPnP デバイスをブラウズ（その2 コンテンツ一覧の取得）

[iOS upnpx で UPnP デバイスをブラウズ（その1 デバイス発見まで）](https://qiita.com/imamurh/items/9f3d035ee697c77431e9) のつづき

# Content Directory Service (CDS)

検出したデバイス (BasicUPnPDevice) のうち、MediaServer (MediaServer1Device) の Content Directory Service (CDS) に対して BrowseDirectChildren を実行する。

```swift
import upnpx

class BrowseViewController: UITableViewController {

    var device: MediaServer1Device! // 対象のデバイス
    var containerObject: MediaServer1ContainerObject? // 現在のコンテナ
    var mediaObjects: [MediaServer1BasicObject] = [] { // コンテナ内のオブジェクト一覧
        didSet {
            DispatchQueue.main.async { [weak self] in
                self?.tableView.reloadData()
            }
        }
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        DispatchQueue.global().async { [weak self] in
            self?.browseDirectChildren()
        }
    }

    func browseDirectChildren() {
        let objectId = containerObject?.objectID ?? "0" // containerObject が nil のときは root(="0")
        let result = NSMutableString()
        let numberReturned = NSMutableString()
        let totalMatches = NSMutableString()
        let updateID = NSMutableString()
        device.contentDirectory.browse(
            withObjectID: objectId,
            browseFlag: "BrowseDirectChildren",
            filter: "*",
            startingIndex: "0",
            requestedCount: "0",
            sortCriteria: "",
            outResult: result,
            outNumberReturned: numberReturned,
            outTotalMatches: totalMatches,
            outUpdateID: updateID
        )
        print("result: ", result)
        print("numberReturned: ", numberReturned)
        print("totalMatches: ", totalMatches)
        print("updateID: ", updateID)

        // Parse result
        let didl = (result as String).data(using: .utf8)
        let mediaObjects = NSMutableArray()
        let parser = MediaServerBasicObjectParser(mediaObjectArray: mediaObjects, itemsOnly: false)
        parser?.parse(from: didl)
        self.mediaObjects = mediaObjects.flatMap { $0 as? MediaServer1BasicObject }
    }
    ...
}
```

取得したオブジェクト (MediaServer1BasicObject) のうち、コンテナは MediaServer1ContainerObject、写真/音楽/動画などのコンテンツは MediaServer1ItemObject になる。MediaServer1ContainerObject の ObjectID を指定して再度 BrowseDirectChildren を実行することでコンテナを掘り進んでいける。

```swift
    // MARK: - Table view data source

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "mediaObject", for: indexPath)
        let mediaObject = mediaObjects[indexPath.row]
        cell.textLabel?.text = mediaObject.title
        if let itemObject = mediaObject as? MediaServer1ItemObject {
            cell.detailTextLabel?.text = itemObject.duration
            cell.accessoryType = .none
        } else {
            cell.detailTextLabel?.text = nil
            cell.accessoryType = .disclosureIndicator
        }
        return cell
    }

    // MARK: - Segue

    override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
        guard let indexPath = tableView.indexPathForSelectedRow else { return false }
        return mediaObjects[indexPath.row] as? MediaServer1ContainerObject != nil
    }

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        guard let indexPath = tableView.indexPathForSelectedRow else { return }
        if let browseViewController = segue.destination as? BrowseViewController,
            let containerObject = mediaObjects[indexPath.row] as? MediaServer1ContainerObject {
            browseViewController.device = device
            browseViewController.containerObject = containerObject
        }
    }
```

# 結果

![UPnPDeviceBrowser.gif](https://qiita-image-store.s3.amazonaws.com/0/121719/e812174b-0bdd-5889-0466-1b7573bdaae1.gif)

# 気づいたこと

* コンテンツが多いコンテナに対して BrowseDirectChildren を実行すると結構待たされる...
* そして SOAP リクエストは並列に処理されないっぽいので時間の掛かるリクエストがあると後続のリクエストが待たされる
* リクエストのキャンセルがない?

# Code

https://github.com/imamurh/UPnPDeviceBrowser

* 最低限のコードなので悪しからず...

*オワリ*