はじめに

ドットインストール はひとつの動画短くて移動時間とか見るのに便利そうなのですが、オフラインで見る方法が用意されてないので、レッスンの動画をまとめてキャッシュしてオフラインでも見られるiPadアプリを書きました。(AppStoreとかに出すことはないので、各自ビルド)

gif

機能

  • WebViewでドットインストールを開く機能
  • レッスン一覧ページで右上のボタンを押すと、そのレッスンの動画をまとめてキャッシュする(一瞬固まったようにみえるのは手抜きのため)
  • キャッシュした動画をiOSのプレイヤーで開く機能

ぐらいの機能しかありません。

バグ

Youtubeの動画本体のURLを取得するのにhellozimi/HCYoutubeParserを利用しているのですが、
何故かこれ経由で取得した動画が縦半分に別れて二重に表示されたりすることがあります。(ストリーミングだと大丈夫な気がするので、その辺の問題なのかな?)

プレイヤーのせいというよりは動画自体に何か問題がでてる感じがするので、心当たりがある人は教えて下さい。

追記

との事で、最新のもの(:head)を使うようにしたら問題なくなりました。

後、一部そのままだとビルドできない感じがしたので修正しました。

仕組み

JavaScript

azu/DotChairsPlayer はiOSアプリなのでObjective-Cで書かれていますが、
レッスン一覧の動画URL(実際は動画のID)を取得する部分はWebViewでJavaScriptを実行して取得しています。
(ドットインストールにAPIがあるわけじゃないので)

Objective-C <ー> JavaScript のやり取りにはよくあるブリッジにはWebViewJavascriptBridgeを使っています。

Bridge

レッスン一覧画面にJavaScriptをインジェクションして、各レッスン画面へXHRして動画のIDを取ってくる感じです。

インジェクションするJavaScriptは以下に置いてあります。

こういうブックマークレット的なJavaScriptは適当に書いてしまう事が多いですが、
browserifyを使うことでnodeモジュールの利用や、モジュール管理もしやすくなるので、browserifyを使ってビルドしたものを使っています。

最近はPromisesについて色々調べてるので、Bluebirdを使ってpromiseでラップしたXHRで書かれています。

レッスン個別ページにXHRを発行する必要があるので、数十のXHRを叩きますがそれをpromiseでラップすると、それぞれの通信(+加工)が一つのpromiseオブジェクトに落とせます。

全てのpromiseオブジェクトが解決したら(全ての通信が終わったら)というのは promise.all を使うことで簡単に書けるようになります。

 // promiseオブジェクトの配列
var promises = urlObject.map(function (object) {
   return xhr(object["url"]).then(getYoutubeID);
});
Promise.all(promises).then(function (objects) {
    // 全てのpromiseが解決(通信が終わった)
});

iOSアプリ

storyboard

タブUIのアプリにしちゃったので、Storyboardのファイルはタブごとに分割しています。

Storyboardの各タブのインスタンス(NavigationController)は、Pilky.me – Using Storyboardsで書かれているようなfactory経由でインスタンス化して使っています。

AppEntryFactoryは単にViewControllerのインスタンスを作るだけのクラスで、
それを管理するCEO Object(アプリではAppFactoryAdmin)があります。

storyboardを分割している場合、self.storyboard から別のタブにある画面を参照することが出来ませんが、ViewControllerへのアクセスを AppFactoryAdmin を経由させることであまり意識しないで別のタブの画面を使うことなどができるような感じです。

別タブの画面等コンテキストが違うものを参照したいViewControllerはCEO Objectへの参照を持つようになっています。

また、AppDelegateが結構スッキリさせているので、アプリケーションテスト時に柔軟にいじれたりできるんじゃないかなと期待しています。

まだこのパターンを掴みきってないのでそこまでいいのかはわかってないですが、
とりあえずタブUIの時はタブごとにstoryboardを分割するとInterface Builderがそこまで重くならなかったり、マージもし易いのでオススメです。

factoryで画面のインスタンスを作るパターンを適応すると、xibの画面とstoryboardが混ざったような(レガシーから移行中の)アプリとかにも結構柔軟に画面を追加できたりするのでそういう所でも役だつかもしれません。

その他

3時間ぐらいでガッと作ったアプリなので、storyboardの管理とか結構勘でやってる部分が多くあんまり参考になるかは怪しいですがそんな感じです。

動画が半分に分裂するやつだけどうにかしたいです…(原因がわからない…)

ソースコード