ドットインストールをオフラインで見るiPadアプリを作った
はじめに
ドットインストール はひとつの動画短くて移動時間とか見るのに便利そうなのですが、オフラインで見る方法が用意されてないので、レッスンの動画をまとめてキャッシュしてオフラインでも見られるiPadアプリを書きました。(AppStoreとかに出すことはないので、各自ビルド)
機能
- WebViewでドットインストールを開く機能
- レッスン一覧ページで右上のボタンを押すと、そのレッスンの動画をまとめてキャッシュする(一瞬固まったようにみえるのは手抜きのため)
- キャッシュした動画をiOSのプレイヤーで開く機能
ぐらいの機能しかありません。
バグ
Youtubeの動画本体のURLを取得するのにhellozimi/HCYoutubeParserを利用しているのですが、
何故かこれ経由で取得した動画が縦半分に別れて二重に表示されたりすることがあります。(ストリーミングだと大丈夫な気がするので、その辺の問題なのかな?)
Youtubeの動画MPMoviePlayerViewControllerで再生すると何か2重になるんだけどどういう事? pic.twitter.com/uFq9pQPI0j
— azu (@azu_re) February 16, 2014
プレイヤーのせいというよりは動画自体に何か問題がでてる感じがするので、心当たりがある人は教えて下さい。
追記
@azu_re 気になったので調べてみたんですけど、たぶんHCYoutubeParserの問題で。解析時に一旦正常な動画URL取得したのを分割されちゃう不明なURLに上書きしてる。直そうかと思ってフォークしたら最新版では修正されていたのでアップデートすればうまくいくかもしれません
— laiso(レイソー) (@laiso) February 18, 2014
との事で、最新のもの(:head
)を使うようにしたら問題なくなりました。
後、一部そのままだとビルドできない感じがしたので修正しました。
仕組み
JavaScript
azu/DotChairsPlayer はiOSアプリなのでObjective-Cで書かれていますが、
レッスン一覧の動画URL(実際は動画のID)を取得する部分はWebViewでJavaScriptを実行して取得しています。
(ドットインストールにAPIがあるわけじゃないので)
Objective-C <ー> JavaScript のやり取りにはよくあるブリッジにはWebViewJavascriptBridgeを使っています。
レッスン一覧画面に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の管理とか結構勘でやってる部分が多くあんまり参考になるかは怪しいですがそんな感じです。
動画が半分に分裂するやつだけどうにかしたいです…(原因がわからない…)
ソースコード
お知らせ欄
JavaScript Primerの書籍版がAmazonで購入できます。
JavaScriptに関する最新情報は週一でJSer.infoを更新しています。
GitHub Sponsorsでの支援を募集しています。