Shiro: Skype中継などを使ってリモートでプレゼンする時に、 会場で表示するスライドを手元から操作するためのスクリプトです。 2006/11のLispセミナー及び 2007/1のKahuaセミナーで実際に使用したものです。 とりあえず本番で使えれば良いってことで、汎用性やら安定性やら 使い勝手やらは全く考慮していません。もし使うならat your own riskで お願いします。上記セミナー資料が同梱されてるのでセミナー資料を ブラウズするという目的にも使えます:-)
リモート講演に使う場合、会場側に必要なのはブラウザ (FireFox) だけです。
サーバ本体は、nekoieさん作のtcpcgiという Gaucheで書かれたhttpdを改造したものです。tcpcgiは本来、cgiスクリプトを forkして実行するのが主目的なんですが、マルチスレッドで組込みのハンドラを 実行するように変更してあります。リモートスライドのためだけならtcpcgiの 全機能は必要ないのですが、最短工数で動かすためにこんなハックになりました。 改造版のtarballはこちらからダウンロードできます:
また、リモートスライドハンドラ自体はこちらです。セミナー資料も入っています。
アクセスすると、スライドの右上にこんなナビゲータが出ます:
《←・→》
このナビゲータをクリックすると、 このサーバにつないでいる全てのブラウザで同じようにページが切り替わります。 従ってこのURLを手元と講演会場のブラウザで表示させておいて、手元で ページを切り替えれば、講演会場でもページが切り替わるという寸法です。
また、プロンプトからもページの遷移 (リターンで次ページ、"-"で前ページ) が できるほか、"l" でコンテンツファイル (上記の場合は kahua-seminar.scm) を リロードします。スライドを見ながら内容を更新した場合なんかに便利です。
プロンプトに Ctrl-D するか、Ctrl-C で中断すればサーバは落ちます。 この時ブラウザの方には 「コネクションが失われた」というalertが出ます。 サーバを再立ち上げしてブラウザ側でリロードすればまたつながります (ページは 最初に戻るけど)。
サーバのポート番号を変えるには、remote-slide.scmの下の方、 (make <tcpcgi-server> ...) の引数をいじってください。
原理はとても簡単で、ブラウザ側からは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で待ってるスレッドを起動します。
手元と会場でスクリーンサイズとブラウザのフォントを合わせておかないと 意図通りに表示されません。