主に 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 フォルダに格納してサーバ起動すれば動作するはずです。
trunk 版の起動シェルはプラグインディレクトリにクラスパス通さなくなっている修正がかかっていますのでそちらも併せて。起動メッセージに Found Plugin がでれば成功。 (ちなみに newInstance() しているのは net.pms.external.ExternalFactory.java です。 うまくプラグインが読まれない場合は確認を)
てなわけで、こちらは動作する例ということで 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 なら気楽に短時間でかけますね。
これは動かして調整して、、たぶん…。
[tegaki]このブログ書く方が時間かかっている![/tegaki]
完。