Motivation
AppleTV をお使いの皆様にはお馴染の AirPlay という機能、私もよくお世話になっております。 操作は iPad 、画面は別ディスプレイで広く使う。よい時代になったものです。
そんな中、 Airplay の Ruby Binding を使って作成された Tomohiro/airplayer というものを最近愛用しています。 「Command-line AirPlay video client for Apple TV」の通り
$ airplayer play http://heinlein.local/Movies/AKIRA.m4v
$ airplayer play '~/Movies/Trailers/007 SKYFALL.mp4'
とするだけで AppleTV 側のディスプレイで動画が見れるわけです。 「ターミナルから出たくなかったから作った」と作者が言うだけのことはあります。便利便利。
しかし僕は考えました。
世の中にはターミナルよりも Emacs から出たくない人の方が多いのではないか?
そんな貴方の為に gongo/airplay-el を作りました。
Required
でしか動作確認していないので、それ以外の環境の方で動いた!とか動かない!みたいなお話まってます。
Installation
MELPA に登録されています。
M-x package-install RET airplay
でいけると思うます。まだ設定していない方は
を見ればいけると思います。
ちなみに依存パッケージは
です。これらがまだ入ってない方も package-install airplay で一緒にインストールされると思います。
Demo
※ 今のバージョンよりも少し前のやつなので、動画に映ってるコードそのままでは動かないと思います。まあ雰囲気だけでも。
airplay.el (alpha ver) from gongo on Vimeo.
Usage
※ 最新は README となりますので、気になる方は以下よりも README をご覧下さい。
Photo
ローカルな画像ファイルを指定すると、AppleTV に表示されます。
(airplay/image:view "appletv.jpg")
また、画像表示時に動きも指定できます。
(airplay/image:view "appletv.jpg" :none) ;; 引数無しと同じ (airplay/image:view "appletv.jpg" :slide_left) (airplay/image:view "appletv.jpg" :slide_right) (airplay/image:view "appletv.jpg" :dissolve)
例えば :slide_right
と指定すると、画像が左から右にスライドしてきます。
後述しますが、airplay.el は deferred でリクエストを送っているので
(lexical-let ( (image "appletv.jpg") (wait 2000)) (deferred:$ (deferred:nextc (airplay/image:view image :slide_left) (lambda (x) (deferred:wait wait))) (deferred:nextc it (lambda (x) (airplay/image:view image :slide_right))) (deferred:nextc it (lambda (x) (deferred:wait wait))) (deferred:nextc it (lambda (x) (airplay:stop)))))
みたいなこともできます。画像が左からスライド→2秒静止→右からスライド→2秒静止→終了みたいな感じです
Video
動画 URL を指定すると、AppleTV で再生が始まります。
;; 上述したデモ動画です。 (airplay/video:play "https://dl.dropbox.com/u/2532139/IMG_0381XXX.m4v")
また、ローカルにある動画ファイルも指定可能です。
(airplay/video:play "~/Dropbox/Public/IMG_0381XXX.m4v")
Control
画像の描画や動画の再生を止めたい場合は stop コマンドを実行します
(airplay:stop) ;; もしくは M-x airplay:stop
Development
中について少しだけ。レビューお願いします。
ローカルな動画ファイル再生の為に HTTP Server 起動
AppleTV に動画を再生してもらう為には、 HTTP Response として動画のデータを配信してくれる URL を渡す必要がある *1 ので、そのままローカルのファイルパスを渡すだけでは駄目です。
また、AppleTV が動画配信サーバに対して Range Request *2 を要求するので、その応対が可能な HTTP Server が必要となります。
結果
- ローカルファイルが指定された場合は simple-httpd を使って動画配信用サーバを起動
- airplay.el の
(defun airplay/server:boot (video))
- airplay.el の
- simple-httpd は Request Range 及び Partial Responses に対応していないため、自分で書いた
最初は airplay.el に全部記述していたんですが、例えば100MB以上のファイルを配信するとなると Emacs が余裕で固まってしまい、再生中は Emacs 使えないという悲しい事態になったので、別の emacs daemon を立ち上げてそこを配信用にしています。 再生終了後は daemon を kill-emacs してサーバ毎停止しています。
ローカルにサーバを起動するというアイディアは airplayer から頂きました。
AppleTV の IP アドレス / ポートを取得する
Ruby の airplay では AppleTV (というかAirServer デバイス) の IPアドレス及びポート番号は tenderlove/dnssd を利用しています。 どうにか pure emacs lisp でいけないかなといろいろ調べた結果、 LAN 内の AppleTV の IP アドレスを取得する Emacs Lisp - Thanks Driven Life みたいなことをやりました。
※ 最新版は airplay.el の airplay/device:browse
になります。
リンク先でも述べてますが、 dns.el の存在を知らずに、自力で DNS パケットの pack/unpack を調べてたのはよい思い出です。
deferred
airplay-el で初めてがっつりと deferred.el の機能を使ってみましたが、難しい。けど楽しい!
ここら辺は AppleTV へのリクエストorレスポンスを任せている request.el が deferred をラップしていたのでとても助かりました。
一番大変だったのが、 airplay/video:play
の部分です。
Airplay API で描画、再生したコンテンツは、AppleTV に対して 30 秒 (Keep-Alive の値?) リクエストを行わないと終了していまいます。なので一定時間毎(今回は 1 秒)に airplay/video:scrub
を実行して再生をストップさせないようにしつつ、再生時間のチェック(停止時間一秒以内であれば stop する)を行っています。
そこら辺を実現する為に deferred:timeout だったり wait だったり、いろいろ混ざってわかりませんでした。 なんとなく動いてる気はするんですが、どこかおかしい点があれば指摘お願いします。
Conclusion
この先としては、
- 動画ファイルを D&D したら再生開始
- ボタンやスライダー(?)を利用して再生中の動画の操作 (pause, resume, seek)
- dired を拡張してプレイリストもどき
とかとかできるように eshell + airplayer という簡潔な手段を取らずがんばってみたんですが、 ここまでくるともう iTunes とかでいい気もしますね!
そんなわけで、皆様も良きアニメライフを