Androidで動画広告用のライブラリーを作ってみたい(動画ダウンロード編)

こんにちは、ブログ初投稿の本間です。
うどんよりそばが好きです。
話は変わりますが、業務でまったくAndroidアプリの開発に携わっていないので、
久しぶりにAndroid関連の開発をしたい。。。と思ったので作ることにしました。

そこで今回は、

最近巷で動画広告が流行っているようなので動画広告用のライブラリーを作ってみようと思います。

s
※ 画像はイメージです

動画広告を実現するためには何においても動画の再生は必須機能ですね。
動画を再生する方法は2つ考えられます。

  • ストリーミング再生
  • 予めダウンロードした動画を再生

ストリーミング再生ではネット環境によってはスムーズに再生できず、
ユーザを(#^ω^)イラッとさせてしまう可能性があります。
そこでこのライブラリーでは起動時に非同期で動画をダウンロードしてスムーズに再生できるようにしようと思います。
ざっくりとですがライブラリの概要を紹介します。

  1. 動画のダウンロード <- 今回はこれについて
  2. 動画の一覧表示
  3. 動画の再生

今回はこの中の「動画のダウンロード」について書いていきます。

動画のダウンロード

一言で「動画のダウンロード」と言ってもなにをすれば。。。
というわけで「動画のダウンロード」を3つの工程に分割してみましょう。

  1. HTTPで動画のデータストリームを取得
  2. データストリームからファイルの作成
  3. アプリのデータ領域にファイルを保存

非同期処理のクラス定義

上記の1, 2, 3の処理は非同期で行う必要があるので、非同期処理を行うクラスを定義します。

これはあくまでライブラリなので導入したアプリのメイン処理を邪魔せずに非同期で動画取得した方が良さそうです。
そもそも、Android 3.0(API Level 11)以降ではメインスレッドでHTTPのリクエストをすると「NetworkOnMainThreadException」が発生するので非同期での動画取得は必須ですね。
Android 1.5(API Level 3)から使用できる非同期処理用のクラスAsyncTaskを使ってみます。

※Android 3.0(API Level 11)から使用できる非同期処理用のクラスAsyncTaskLoaderもあったのですがカスタマイズしづらかったのでAsyncTaskを使うことにしました。
ただ、API Level 11の前と後でAsyncTask#executeの挙動が異なるそうなので今後調査したいと思います。

public class DownloadMovieAsyncTask extends AsyncTask<String, String, String> {

@Override
protected String doInBackground(String... urls) {
// 非同期処理内容
// ここに動画をダウンロードする処理を書きます。
}

@Override
protected void onProgressUpdate(String... progress) {
// 処理の進捗状況を都度受け取る
// 今回は使いません。
}

@Override
protected void onPostExecute(String result) {
// 非同期処理終了後にresultを受け取る
// ここで呼び出しもとにコールバックします。
}
}

これで、非同期処理のためのクラスができました。
今回は、doInBackgroundに1, 2, 3の処理を書いていきます。

1. HTTPで動画のデータストリームを取得

次は動画のデータストリームを取得してみましょう。
動画のデータストリームを取得する際にはHTTPを使います。
AndroidでHTTP通信をするときはApache HTTPClientHttpUrlConnectionのどちらかで実装できたのですが、Android 6.0(API Level 23)でApache HTTPClientが削除されたので「HttpUrlConnection」を使用することにします。

  private void download(URL url) throws IOException {
HttpURLConnection urlConnection = null;
try {
// URLに対して接続します。
urlConnection = (HttpURLConnection)url.openConnection();
// 接続先からデータストリームを取得します。
InputStream inputStream = urlConnection.getInputStream();
} finally {
if (urlConnection != null) urlConnection.disconnect();
}
}

ここまでで、動画のデータを非同期で取得することが出来ました。しかし、このままではファイルとして書き込むことが出来ないのでデータストリームからファイルを作成する必要があります。

2. データストリームからファイルの作成

次は、データストリームからファイルを作ります。
ブラウザを使ってファイルをダウンロードするときはブラウザがデータストリームをいい感じにファイルにして保存してくれますが、今回はブラウザを使わないので自分でやらないといけません。

  private void writeFile(InputStream inputStream, File outputFile)
throws IOException
{
byte[] bytes = new byte[BUFFER_SIZE];
BufferedInputStream bis = new BufferedInputStream(inputStream, BUFFER_SIZE);
FileOutputStream fos = new FileOutputStream(outputFile);
try {
int length = 0;
while ((length = bis.read(bytes, 0, BUFFER_SIZE)) > 0) {
fos.write(bytes, 0, length);
}
} finally {
fos.flush();
fos.close();
bis.close();
}
}

地道にデータストリームからbyteで読み込んでFileにストリームで書き込んでいきます。
これで動画をファイルに書き込むことが出来る様になりました。後はどこに保存するか決めるだけですね。

3. アプリのデータ領域にファイルを保存

最後に動画を保存します。
Androidにファイルを保存する場所は大きく分けて2つあります。

  • 外部ストレージ領域
  • アプリ内のデータ領域

外部ストレージ領域にファイルを保存する場合には「WRITE_EXTERNAL_STORAGE」の権限が必要になり、ライブラリの導入のハードルが上がってしまいます。
アプリ内のデータ領域にファイルを保存する場合は特に権限は必要ないのでアプリ内のデータ領域にファイルを保存することにします。

アプリ内のデータ領域にも2種類があります。

  • キャッシュ領域
  • データ領域

キャッシュ領域はデバイスのストレージ容量が少なくなったときにシステムによって削除されてしまいます。また推奨されるキャッシュ領域の容量は1MBなので動画を保存するには足りないです。(640 x 360で1分のmp4の容量が約5MB)
今回は特に制限のなさそうなデータ領域にファイルを保存することにします。アプリのデータ領域なのでメインアプリが保存しているデータとバッティングしないように、ライブラリ用のディクトリを作成しておきます。

  Fiel appRootDir = context.getFilesDir();
File libRootDir = new File( appRootDir, "com.adways.movie.ad" ).mkdirs();

これで保存するディレクトリの用意もできました。

まとめ

今回実装した、

  1. HTTPで動画のデータストリームを取得
  2. データストリームからファイルの作成
  3. アプリのデータ領域にファイルを保存

この3つの処理をまとめるとこのような感じになります。

  public class DownloadMovieAsyncTask extends AsyncTask<String, String, String> {

private String mURL;
private File mOutputFile;
public DownloadVideoAsyncTask(String url, File outputFile) {
this.mURL = url;
this.mOutputFile = outputFile;
}

@Override
protected String doInBackground(String... unused) {
// 非同期処理内容
URL movieURL = new URL(mURL);
download(movieURL, mOutputFile);
}

@Override
protected void onPostExecute(String result) {
// 非同期処理終了後にresultを受け取る
// ここで呼び出しもとにコールバックします。
}

private void download(URL url, File outputFile) throws IOException {
HttpURLConnection urlConnection = null;
try {
// URLに対して接続します。
urlConnection = (HttpURLConnection)url.openConnection();
// 接続先からデータストリームを取得します。
InputStream inputStream = urlConnection.getInputStream();
// ファイルの保存
writeFile(inputStream, outputFile);
} finally {
if (urlConnection != null) urlConnection.disconnect();
}
}

private void writeFile(InputStream inputStream, File outputFile)
throws IOException
{
byte[] bytes = new byte[BUFFER_SIZE];
BufferedInputStream bis = new BufferedInputStream(inputStream, BUFFER_SIZE);
FileOutputStream fos = new FileOutputStream(outputFile);
try {
int length = 0;
while ((length = bis.read(bytes, 0, BUFFER_SIZE)) > 0) {
fos.write(bytes, 0, length);
}
} finally {
fos.flush();
fos.close();
bis.close();
}
}
}

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
・・・
Fiel appRootDir = context.getFilesDir();
File libRootDir = new File( appRootDir, "com.adways.movie.ad" ).mkdirs();
File outputFile = new File( libRootDir, "movie_ad.mp4" );

new DownloadMovieAsyncTask("動画のURL", outputFile)
.execute();
}
}

これで動画のダウンロードができるようになりました。
今後は「動画の一覧表示」と「動画の再生」についても書けたらいいな。。。

  1. 動画のダウンロード <- OK!!
  2. 動画の一覧表示 <- Coming soon!
  3. 動画の再生

それでは今夜もそばをすすってまいります。では。