Gauche:RemoteSlide
Shiro: Skype中継などを使ってリモートでプレゼンする時に、 会場で表示するスライドを手元から操作するためのスクリプトです。 2006/11のLispセミナー及び 2007/1のKahuaセミナーで実際に使用したものです。 とりあえず本番で使えれば良いってことで、汎用性やら安定性やら 使い勝手やらは全く考慮していません。もし使うならat your own riskで お願いします。上記セミナー資料が同梱されてるのでセミナー資料を ブラウズするという目的にも使えます:-)
リモート講演に使う場合、会場側に必要なのはブラウザ (FireFox) だけです。
ダウンロード
サーバ本体は、nekoieさん作のtcpcgiという Gaucheで書かれたhttpdを改造したものです。tcpcgiは本来、cgiスクリプトを forkして実行するのが主目的なんですが、マルチスレッドで組込みのハンドラを 実行するように変更してあります。リモートスライドのためだけならtcpcgiの 全機能は必要ないのですが、最短工数で動かすためにこんなハックになりました。 改造版のtarballはこちらからダウンロードできます:
また、リモートスライドハンドラ自体はこちらです。セミナー資料も入っています。
必要なもの
- Gauche-0.8.9以降 (0.8.8ではバグのためうまく動きません)。 --enable-threads=pthreadsでconfigureしてあることが必須です。
- FireFox (IEでは動きません。他のブラウザは確かめていません。)
走らせかた
- tcpcgi-0.4.4.2-mtpatch.tgzを展開し、configure + make します。
- remote-slide.tgzを展開し、中にあるファイルを tcpcgi-0.4.4.2-mtpatch/src 以下にコピーします。
- tcpcgi-0.4.4.2-mtpatch/srcに降りて、./remote-slide.scm kahua-seminar.scm などとしてサーバを起動します。"[[0]]>" というプロンプトが出ればOK。
- ブラウザから走らせたホストのポート8888にhttpでアクセスします。
アクセスすると、スライドの右上にこんなナビゲータが出ます:
《←・→》
- "《" : 先頭ページ
- "←" : 前のページ
- "・" : 現在のページをリロード
- "→" : 次のページ
- "》" : 最後のページ
このナビゲータをクリックすると、 このサーバにつないでいる全てのブラウザで同じようにページが切り替わります。 従ってこのURLを手元と講演会場のブラウザで表示させておいて、手元で ページを切り替えれば、講演会場でもページが切り替わるという寸法です。
また、プロンプトからもページの遷移 (リターンで次ページ、"-"で前ページ) が できるほか、"l" でコンテンツファイル (上記の場合は kahua-seminar.scm) を リロードします。スライドを見ながら内容を更新した場合なんかに便利です。
プロンプトに Ctrl-D するか、Ctrl-C で中断すればサーバは落ちます。 この時ブラウザの方には 「コネクションが失われた」というalertが出ます。 サーバを再立ち上げしてブラウザ側でリロードすればまたつながります (ページは 最初に戻るけど)。
サーバのポート番号を変えるには、remote-slide.scmの下の方、 (make <tcpcgi-server> ...) の引数をいじってください。
中身
- remote-slide.scm : リモートスライド機能を実現してるスクリプト。
- kahua-seminar.scm : Kahuaセミナー講演スライド
- lisp-seminar.scm : Lispセミナー講演スライド
原理はとても簡単で、ブラウザ側からはXMLHttpRequestでページを フェッチする毎に直ちに同じURLをタイムスタンプつきでフェッチにゆきます。 サーバ側はリクエストを止めといて、状態変化があった時にリプライを 一斉に返します。今流行りのCometの原理みたいなもんかな?
サーバの核心部分だけ抜き出すとこんな感じ:
(define *current* 0) (define *mutex* (make-mutex)) (define *cond* (make-condition-variable)) (define *timestamp* (time->seconds (current-time))) (define (touch) (mutex-lock! *mutex*) (set! *timestamp* (time->seconds (current-time))) (condition-variable-broadcast! *cond*) (mutex-unlock! *mutex*)) (define (page-cgi) (let* ((params (cgi-parse-parameters)) (pts (cgi-get-parameter "ts" params :default 0 :convert x->number))) (let loop () (mutex-lock! *mutex*) (let1 ts *timestamp* (cond ((> ts pts) (mutex-unlock! *mutex*) (write-tree (list (cgi-header :content-type "text/plain; charset=utf-8" :x-timestamp (x->string ts)) (list-ref *pages* *current* '())))) (else (mutex-unlock! *mutex* *cond*) (loop))))))) (define (next-cgi) (inc! *current*) (when (>= *current* (length *pages*)) (set! *current* (- (length *pages*) 1))) (touch) (write-tree (cgi-header)))
各ブラウザのXMLHttpRequestはそれぞれ独立したスレッドでpage-cgiにより ハンドルされます。これは通常 (mutex-unlock! *mutex* *cond*) のところで condition variable待ちになっています。ナビゲーションリンクを クリックすると next-cgi等が実行されて、これが touch を実行し、 condition variableで待ってるスレッドを起動します。
注意
手元と会場でスクリーンサイズとブラウザのフォントを合わせておかないと 意図通りに表示されません。
議論、コメント
- Shiro(2007/01/20 02:46:45 PST): あ、これは試行錯誤の残りカスですな。 (use wiliki)をコメントアウトしちゃっても動きます。 修正したtarballを上げ直しておきました。