読者です 読者をやめる 読者になる 読者になる

Thanks Driven Life

日々是感謝

Emacs と AppleTVの架け橋となる airplay-el

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 が必要となります。

結果

  1. ローカルファイルが指定された場合は simple-httpd を使って動画配信用サーバを起動
    • airplay.el(defun airplay/server:boot (video))
  2. 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 とかでいい気もしますね!

そんなわけで、皆様も良きアニメライフを