Androidのアプリはサイズが50MB以下じゃないといけないというルールがあったのだけど、それが緩和されて2GBx2の拡張ファイルというものをアップロードできて、データとして利用できる。そこでその中に動画データを入れて再生する方法を調べた。

基本的にはAPK Expansion Files | Android Developersとかほかのサイトで説明されているとおりだけど、いろいろつまづいたのでメモっておく。

拡張ファイルの形式

拡張ファイルobbはjobbというツールで作る方法と、zipファイルを使う方法がある。だけど動画を再生しようとした時にjobbで作ったファイルだとマウントに失敗してしまって動かなかったので、zip形式を使うことにする。動画ファイルは-nで再圧縮せずにアーカイブする(そうしないとストリーミング再生できない)。

$ zip -n .mp4 main.1.com.example.appname.obb moviefile.mp4

zipファイルでも、作成するファイル名は規則に従い、.obbにする。

必要なライブラリの追加

Eclipse のメニューの Window > Android SDK Manager から

  • Extras/Google Play APK Expansion Library
  • Extras/Google Play Licensing Library

をインストールする。インストールしたら、Eclipseのワークスペースに

  • $(ADT)/sdk/extras/google/play_apk_expansion/downloader_library
  • $(ADT)/sdk/extras/google/play_apk_expansion/zip_file
  • $(ADT)/sdk/extras/google/play_licensing/library

をインポートする。それぞれ、プロジェクトのProperties > Android > Library > Is Library にチェックを入れる。

downloader_library は Properties > Java Build Path > Projects にlibrary(play_licensingのもの) を追加する。

したら自分のプロジェクトで、Properties > Android > Library > Addで追加する。さらにProperties > Java Build Path の Projects > Add で追加して、Order and Export でチェックを入れる。

あとのソースの修正は、APK Expansion Files | Android Developersやサンプルなどを見てゴリゴリ書く。

zip_fileライブラリソースの修正

対象とするAndroidのバージョンがAndroid3.1(SDK 12)以上ならいいんだけど、それ以前でも扱いたい場合にはzip_fileライブラリ中のAPEZProviderを修正する必要がある。単純なことなんだけど、android.os.Bundle#getString(key, default)がSDK 12以降で、それ以前の機種で使おうとするとNoSuchMethodErrorが出てしまう。

デフォルトを与えないメソッド(失敗した場合にはnullが返る)はあるので、それを使うように修正する:

public abstract class APEZProvider extends ContentProvider { 
  ...
  private boolean initIfNecessary() {
    ...
      String mainFileName = pi.metaData.getString("mainFilename");
      if (mainFileName == null) {
        mainFileName = NO_FILE;
      } else {
        ...
        // patchFileNameも同様にする

Zip中のファイルをUriで指定できるようにする

Uriで"content://com.example.appname/moviename.mp4"としたときにZipの拡張ファイルからサーブできるようにする:

package com.example.appname;

import java.io.File;
import android.net.Uri;
import com.android.vending.expansion.zipfile.APEZProvider;

public class ZipContentProvider extends APEZProvider {
  private static final String AUTHORITY = "com.example.appname";  // これはこのクラスが属するパスに合わせる必要がある?

  public static Uri buildUri(String pathIntoApk) {
    StringBuilder contentPath = new StringBuilder("content://");
    contentPath.append(AUTHORITY);
    contentPath.append(File.separator);
    contentPath.append(pathIntoApk);
    return Uri.parse(contentPath.toString());
  }

  @Override
  public String getAuthority() {
    return AUTHORITY;
  }
}

AndroidManifest.xmlの<application>中にも追加する:

        <provider android:name=".ZipContentProvider"
                  android:authorities="com.example.appname">
            <meta-data android:name="mainVersion" android:value="1" />
            <meta-data android:name="patchVersion" android:value="0" />
        </provider>

この定義の中にも拡張ファイルのバージョン番号を指定する必要がある。

動画の再生

VideoViewのUriで"content://com.example.appname.obb/〜"で指定する:

    VideoView vv = (VideoView) findViewById(R.id.videoPlayer);
    vv.setMediaController(null);
    vv.setVideoURI(ZipContentProvider.buildUri("moviename.mp4"));
    vv.start();
    vv.requestFocus();

テスト時

テスト用のAndroidデバイスをUSBでつないで、拡張ファイルを指定の場所にコピーすれば、わざわざサーバーからダウンロードせずに動作確認ができる。

$ adb shell mkdir /mnt/sdcard/Android/obb/com.example.appname
$ adb push main.1.com.example.appname.obb /mnt/sdcard/Android/obb/com.example.appname/

Google Play Developer Consoleでテストする

アルファ版としてアップして、APKのアップロードの2回目以降、アップロード後に出てくるメニューに拡張ファイルをアップロードする項目が現れるので、そこから更にアップロードする。

とにかく面倒臭すぎるし、罠が多い。なぜこんなことになってしまうのか。