Groovy と PS3 Media Server プラグイン

主に PS3 向けの DNLA サーバアプリ PS3 Media Server のプラグインを、言語の勉強がてら Groovy でかいてみました。 🙂

また、pms のプラグインのつくりかたはあまりインターネット上にドキュメントがないみたいなのでメモも兼ねまして。 まぁこんなことしようする人はソースみれば分かる内容ですが(笑)。。。

PS3 Media Server は Java でできていますので、Groovy の jar をクラスパスにいれてあげれば Groovy でプラグインをかくことができます。 Groovy 言語はプリコンパイルでただの .class にできますので Java との相互運用性は非常に高く、こういうときにとても便利です。

とりあえず、pms3 の trunk を落としてきて lib/groovy-all-1.7.10.jar を入れて、ant かけて pms3.jar の本体を差し替え。 あとはプラグインかくだけ。

以下ソースから追ってつくっていますので、間違えがありましたらご容赦を。

まず作成するプラグインを以下のパッケージに配置。

package net.pms.external

 

画面にルートフォルダを一つ追加する場合は、AdditionalFolderAtRoot を implements してあげたクラスを作成。 で、extends VirtualFolder なクラスをつくって addChild して構築したものを getChild() から戻してあげれば初期設定完了です。

class SamplePlugin implements AdditionalFolderAtRoot {
    
    def root
    
    def SamplePlugin() {
        root = new VirtualFolder("Sample", "")
        root.addChild(new SampleDirectory("Child"))
    }
    
    @Override
    public DLNAResource getChild() {
        return root
    }
    
    @Override
    public String name() {
        return "SamplePlugin"
    }
    
    @Override
    public void shutdown() {
        // nothing to do
    }
    
    @Override
    public JComponent config() {
        // no gui
        return new JPanel()
    }
}

 

extends VirtualFolder な discoverChildren() ではそれが持つ子供(さらにフォルダとかファイルとかストリームとか)を addChild(DLNAResiyrce) で追加していきます。このメソッドはクリックされたときに動きます。

class SampleDirectory extends VirtualFolder {
    
    def SampleDirectory(name) {
        super(name, "")
    }
    
    @Override
    public void discoverChildren() {
        addChild(new SampleDirectory("Child"))
        addChild(new WebAudioStream("Station", "http://127.0.0.1/", ""))
        addChild(new RealFile(new File("/tmp/hoge.mp3"))
    }
}

 

簡単あるね。 上のソースは今そらで書いたものなので動かなかったらすいません。。イメージということで。。

最後に plugin というテキストファイルに起動クラス名(この場合は net.pms.external.SamplePlugin)を記述した上で jar にして pms3 の plugins フォルダに格納してサーバ起動すれば動作するはずです。

 icecast_02

trunk 版の起動シェルはプラグインディレクトリにクラスパス通さなくなっている修正がかかっていますのでそちらも併せて。起動メッセージに Found Plugin がでれば成功。 (ちなみに newInstance() しているのは net.pms.external.ExternalFactory.java です。 うまくプラグインが読まれない場合は確認を)

icecast_01

てなわけで、こちらは動作する例ということで Icecast サーバの実装が出力する yp.xml を解析して PS3 上に表示して再生するプラグインです。 ジャンルでサマってフォルダ分けしています。

package net.pms.external
 
import javax.swing.JPanel
import javax.swing.JComponent
 
import net.pms.dlna.DLNAResource
import net.pms.dlna.WebAudioStream
import net.pms.dlna.virtual.VirtualFolder
 
class IcecastPlugin implements AdditionalFolderAtRoot {
    
    def root
    def icecast
    
    def IcecastPlugin() {
        icecast = new Icecast("http://www.example.com/yp.xml")
        
        root = new IcecastDirectory("Icecast Radio", "", {})
        root.addChild(new IcecastDirectory("Genre", "", { c1 ->
            icecast.getGenre().each {
                c1.addChild(new IcecastDirectory(it, it, { c2 ->
                    icecast.getStationByGenre(c2.query).each {
                        c2.addChild(new WebAudioStream(it.value, it.key, ""))
                    }
                }))
            }
        }))
    }
    
    @Override
    public DLNAResource getChild() {
        return root
    }
    
    @Override
    public String name() {
        return "IcecastPlugin"
    }
    
    @Override
    public void shutdown() {
        // nothing to do
    }
    
    @Override
    public JComponent config() {
        // no gui
        return new JPanel()
    }
}
 
class IcecastDirectory extends VirtualFolder {
    
    def explorer
    def query
    
    def IcecastDirectory(name, query, explorer) {
        super(name, "")
        this.query = query
        this.explorer = explorer
    }
    
    @Override
    public void discoverChildren() {
        explorer(this)
    }
}
 
class Icecast {
    
    def yp
    def ypxml
    
    def Icecast(yp) {
        this.yp = yp
    }
    
    def getStationByGenre(genre) {
        parse()
        def stations = [:]
        ypxml.entry.each {
            if(it.genre.text() == genre) {
                stations[it.listen_url.text()] =
                    "${it.server_name.text()} (${it.bitrate.text()}K)"
            }
        }
        return stations
    }
    
    def getGenre() {
        parse()
        def genres = [:]
        ypxml.entry.each {
            genres[it.genre.text()] = ""
        }
        return genres*.key.sort()
    }
    
    def parse() {
        if(ypxml != null && ypxml.entry.size() != 0) return
        ypxml = new XmlParser().parse(yp)
    }
}

 

Groovy の勉強を兼ねていましたので中途半端に遊んでるソースですいません。 木構造でクロージャ渡しの遅延評価を使って一度にツリーを構築しています。 あとは  XML の解析部分が Groovy っぽい部分でしょうか。 ぼくが分からないだけでもっと良い方法はあることでしょう!

さて、こんな感じのものを Java でかいていたらさすがにぼくでもいらっとしそうですが、Groovy なら気楽に短時間でかけますね。

これは動かして調整して、、たぶん…。

このブログ書く方が時間かかっている!

完。

このエントリーをはてなブックマークに追加

コメントを残す

メールアドレスが公開されることはありません。