2012/07/24

video_tagで視聴するredmineプラグインを作る

redmineチケット予約・録画までは出来たが、視聴する部分をどうするか悩んだ結果、
致し方なくじゃなくて、頑張ってredmineプラグインで視聴する部分を作成してみた。

redmineはrailsアプリなので、railsとしてのMVCのVC(View & Controller)を使って簡単に作れるんじゃないかな?と思った。だけどrailsのちゃんとしたプラグインは作ったこと無い。調べながら作って何とかなったので、工程を書き記しておく。

/opt/redmine
ここが自分のredmineのインストール場所。人によって色々だよね。
ここをカレントディレクトリにしてプラグイン生成を行う。

自分のRedmine環境
  Redmine version                          2.0.3.stable
  Ruby version                             1.9.3 (x86_64-linux)
  Rails version                            3.2.6
  Environment                              production
  Database adapter                         Mysql2

プラグイン作成
$ sudo RAILS_ENV=production ruby script/rails generate redmine_plugin redmine_video
RAILS_ENV環境変数を指定しないと
var/lib/gems/1.9.1/gems/bundler-1.1.4/lib/bundler/rubygems_integration.rb:147:in `block in replace_gem': Please install the mysql adapter: `gem install activerecord-mysql-adapter` (mysql is not part of the bundle. Add it to Gemfile.) (LoadError)
とかいうエラーが出る。これを信じるとハマる。

何か昔のRedmineとは作成フレーズが違うのだね。
redmine色が薄れてRails色が濃くなった感じ?
以前はプラグイン名のプレフィックスに勝手に'redmine_'が追加されたような・・・
最新のはそうじゃないみたいだけど、過去の慣習に合わせて'redmine_video'にした。
これも安易な名前だけど、自分用なのでこれでよし。

コントローラ作成
$ sudo RAILS_ENV=production ruby script/rails generate redmine_plugin_controller redmine_video video index
コントローラテンプレートもひとつ作る。アクションは一応'index'だけ作っておく。
追加はコントローラスクリプトに直接書き加えて追加していけばいいか。

/opt/redmine/plugins/redmine_video
プラグインがここに作られた。以前は/opt/redmine/vender/plugins/だったはず。
場所変わった。とにかくここに色々作られるけど、使うのはapp/controllersとapp/viewsだけだろう。

config/routes.rb
これもちゃんと書かないとダメってことになったらしい。勝手には作ってくれないので自分で書き加える。最初は以下の一行だけ書いておく。後からアクションが増えるたびに書き加える。
get 'video/index',:to=>'video#index'
えーと、http://(redmine-url)/video/indexってやったら、videoコントローラのindexアクションがGETメソッドで呼ばれるっていうルート定義。勘違いしていた。アクセスがGETメソッドに限定されるルート定義っていう意味だった。
POST限定なら post、GET/POST両方で使うなら match らしい。

app/controllers/video_controller.rb
この中にvideoコントローラのアクションの内部処理を記述する。
以下のように、indexアクションで録画チケットを検索してViewに渡すようにした。
def index
  @issues =Issue.find(:all, :conditions => {:tracker_id => 3, :status_id => 4}, :order => "start_date DESC")
 end

app/views/video/index.html.erb
コントローラ内でrenderで表示する処理を書かなければ、controller名/アクション名_html.erb
がテンプレートとして表示されるようになる。この中にもrubyスクリプトを埋め込める。試行錯誤の末、出来上がったのがこちら。
<h2>録画ビデオ一覧</h2>

<table class="list">
    <% @issues.each do |issue| %>

    <tr class="<%= cycle('odd','even') %>">
      <td>
        <% thumb = issue.attachments.detect {|a| a.content_type == "image/jpg"} %>
        <%= link_to image_tag("/videos/"+thumb.disk_filename),
            {:action => 'play', :id => issue.id } %>
      </td>
      <td>
        <table>
        <% time = issue.custom_values.detect {|v|
           v.customized_id == issue.id and v.custom_field_id == 1 }
           channel = issue.custom_values.detect {|v|
           v.customized_id == issue.id and v.custom_field_id == 2 } %>
        <tr><th nowrap>タイトル:  <th><b><%= issue.subject %></tr>
        <tr><td nowrap>ID:         <td><%= issue.id %></tr>
        <tr><td nowrap>説明:      <td><%= issue.description %></tr>
        <tr><td nowrap>録画日:    <td><%= issue.start_date %></tr>
        <tr><td nowrap>時間:      <td><%= format("%04d",time.value.to_i) %></tr>
        <tr><td nowrap>チャンネル:<td><%= channel.value %></tr>
        <tr><td nowrap>カテゴリ:  <td><%= issue.category %></tr>
        </table>
      </td>
      <% end %>
</table>
(貼り付けが綺麗にできないので、'<'を"<"にしてます)

<% @issues.each do |issue| %>~<% end %>
コントローラから受け取った@issues分繰り返し。<%と%>で囲ったところがrubyスクリプトとして実行される。何処にでも挿入できる。

<% thumb = issue.attachments.detect {|a| a.content_type == "image/jpg"} %>
サムネイル表示どうしようかと重たけど、チケットにアタッチしてあるやつをこんな風に取得できた。これがそのままattachementクラスなので、thumb.disk_filenameでファイル名が取り出せる。

この辺が外部スクリプトでActiveRecord使ってアクセスしてた時と勝手が違うところだね。

image_tag(filepath)
railsの関数。これ使うと、<img src="filepath" />に展開してくれる。filepathをファイル名だけにすると、"images/filename"に勝手になる。

imagesディレクトリはredmineが使用済みなので、'/videos'でファイルにアクセスできるようにした。

最初は、<img src="/redmine/attachments/<%= thumb.id %>/<%= thumb.filename %>
なんて書いてみたけれど、ものすごく遅いので、/opt/redmine/public/videos/にビデオやサムネイルファイルが保存してある場所へリンクを貼ることにした。
'videos'にしたのは、後で出てくる、video_tagのデフォルトパスになっているから。

link_to
railsの関数。これはアンカータグに変換してくれる。
{:action => 'play', :id => issue.id }
同じコントローラ内のplayアクションにid付きのコマンドへリンクするよってこと。

平たく言うと、サムネイルをクリックすると、video/play/#チケット番号 へ飛ぶリンクが作られるようにってことだ。結果は簡単だけど知らないところからやると結構悩むよ。むずいよ。

開始時間とかのカスタムフィールドもattachmentsと同じ要領で取得できたけど、何かカッコ悪い感じがする。もっと簡素に書けるんじゃないかなぁ。でもRails初心者なのでここまでが精一杯だ。

(redmine-url)/video/index で一覧表示!
録画チケットを、よくあるサムネイル付きの一覧表示させる事ができました。

playアクション
一覧表示のリンクから呼ばれる再生用のビューを作成します。

routes.rbに、get 'video/play/:id',:to=>'video#play' を追加する。
video/play/#id でアクセスしたら、videoコントローラのplayアクションが呼ばれる。
#idは、コントローラスクリプト内でparams[:id]で参照できる。なので、
def play
 @issue = Issue.find(params[:id])
end
っていう簡単な処理をvideoコントローラに追加した。@issueに目的のチケットが格納される。

app/views/video/play.html.erb
index.html.erbと同じ所にplay.html.erbを作る。generateで作らなかったので自分で作る。
<h2>録画ビデオ一覧</h2>
<h2><%= @issue.subject %></h2>

<% video = @issue.attachments.detect {|a| a.content_type == "video/mp4"} %>
<% thumb = @issue.attachments.detect {|a| a.content_type == "image/jpg"} %>

<%= link_to video_tag video.disk_filename,
    :poster => image_path("/videos/"+thumb.disk_filename),
:autoplay => true, :autobuffer => true, :controls => true, :size => "960x540" %>
(こっちも、'<'を"<"にしてます)

video_tag
肝心の関数です。video_tagというrails関数を使うと、
video_tag video.filename しか書いてないけど、これで、<video src="/video/filename"> に変換される。アトリビュートもオプション設定で幾つかできるので、適当に設定してみた。

フルスクリーン設定ができない
大体のブラウザでフルスクリーンにできるから大した問題ではないけど、出来ないやつもあるんで、一応大きめのサイズを設定しておく。この辺は別の手段で色々やればなんか出来たりするんじゃないかと思うけど、分からんです。
試しに、:size="100%x100%"とかやってみたけど、やっぱりダメだった・・・

MacやPCだとまあまあ良い感じ
一覧からサムネイルクリックでビデオ再生ページになって自動再生されるように出来た。デスクトップ環境からなら私はこれで十分OKだ。ぜんぜん使える!

だけど、Firefoxだと再生しないぞ?なんででしょう?
何でだ?じゃなくて、Firefoxはogg(theora+vorbis)じゃないと再生できないんだね。最近知りましたよ。そういえばffmpegのコンフィグでそんなのがあったな。これも今後の課題かな。

iPad/Androidなどのモバイル端末だとなぁ
前にも書いた気がするけど、autoplayが効かないのはそのままなので、再生ページで自分で小さな再生ボタンをタッチしなければならない。で、そのページ内で再生されるわけじゃなくビデオプレーヤが起動してその中で再生されるという、まだるっこしい感じになる。

一覧にvideoタグが付いていればいいんじゃね?
と思ってやってみた。一覧のサムネイルにビデオ再生ボタンが付いた感じになった。
心なしかこっちの方が再生ボタンが大きい気がする。モバイル端末ならば、勝手にフルスクリーンで再生されるから、この方が自然な感じがする。
逆に、デスクトップのブラウザだと小さいサムネイルサイズのまま再生されるので、再生ページに飛ばしたほうが自然だ。

うーむ。どっちもどっちだ。ということで、両方ありにした。
視聴する環境で、どっちがいいか自分で選択すればいいじゃないかと。
ということで、選択しやすいメニューを追加することにした。

listアクションとlist.html.erb
コントローラアクション'list'を、indexと同じ処理で追加。
routes.rbへ、get 'video/list',:to=>'video#list' を追加。
index.html.erbのlink_toの部分を以下のように変更しただけのビューを追加。
<%= video_tag video.disk_filename,
       :poster => image_path("/videos/"+thumb.disk_filename),
       :controls => true, :size => "320x180" %>

分かりにくいけど、video#indexがPCに適したやつ。video#listがモバイルに適したやつ。アクションビュー2種類にしたので、この2つを選択するためのメニューを追加する。

アプリケーションメニュー
redmineプラグインメニューはいろんな所に出現させることが出きるようだけど、一番簡単そうでタッチしやすい(モバイル端末だとリンクが小さいと押しにくいのだ)、アプリケーションメニューに追加することにした。

app/init.rb
この中で何処にメニューを出すかとか、パーミッションとかグループとか色々やるらしいけど、アプリケーションメニュー設定は簡単だ。

menu( :application_menu,
      :videos_index, { :controller => 'video', :action => 'index' }, :caption => "VideoIndex" )
menu( :application_menu,
      :videos_view, { :controller => 'video', :action => 'list' }, :caption => "VideoList" )

をdo/endの中に追加すればいいだけ。カッコは省略するほうがRubyOnRailsらしいそうだけど、自分は癖で書いてしまった。

ruby/rails/RailsGuidesをゆっくり和訳してみたよ
プラグイン開発ではこちらのサイトを大いに活用させていただきました。

予想していたよりも遥かに簡素に書けることが分かった。が、そのシンプルな結果を得るまでに苦労する。便利フレームワークは便利になるまでが苦しい。

完成にはまだ至らないけれど、番組データ取得・予約・録画・視聴までの一連のフローは実現できたかな。オリンピックにぎりぎり間に合ってよかった~。

色々実用レベルでの問題を残しっぱなしなので次回からは、そうした細々したところを1つ1つ解決していく感じになるかな。

因みに、不完全ながらもオリンピック番組を録画するために、クエリー使った自動予約機能を使う。重複処理ができてないので、1チャンネルだけにしてかつ、ライブじゃなくて録画番組の方が予約されるように設定した。うまく処理されるかなぁ?


Redmine/Railsプラグイン初心者が言うのもなんですが、開発中とか更新したスクリプトを反映させるには頻繁にRedmineの再起動が必要。だけどwebrick再起動とかPassenger経由ならApache再起動とかリロードとかやりたくない。redmineだけをリロードさせたい。そんなときのTipsを一つ。
(redmine-url)/tmp/restart.txt っていうファイルを更新すると、次のRedmineへのアクセスで自動的にリロードが行われる。自分の場合は
$ sudo touch /opt/redmine/tmp/restart.txt
ってやると、次のアクセスでリロードが走る。プラグイン更新とこれの繰り返し。
メンテや何かのプラグイン追加したときにも使えるんで便利です。
開発中のプラグインだけリロードとかあればもっと便利だけど。

0 件のコメント:

コメントを投稿