2023/06/20 DMM Android勉強会にて発表
DMM TVのSDカードダウンロード機能を実装した話#dmm_android勉強会
View Slide
自己紹介HN: マヤミト本名: 富山 雄太GitHub: https://github.com/yt8492趣味: Kotlin, Twitter, ウマ娘22新卒動画配信事業部AndroidエンジニアTwitter: yt8492
DMM TVの開発をしています
SDカード対応をしたのでその話をします
DMM TVのダウンロード仕様について● 配信形式○ MPEG-DASH● コンテンツ保護○ Widevine→ MP4などの動画ファイルを単純にダウンロードして保存して終わり!ではない
ExoPlayerのダウンロード関連の主要な登場人物● Cache○ ExoPlayerのCache。ダウンロードの保存先の情報をもつ。● Download○ ダウンロードしたデータ。● DownloadIndex○ Downloadを保存・取得する。● DownloadManager○ Downloadを管理する。CacheとDownloadIndexをもつ。Downloadを取得したり、Download中のものをキャンセルしたりする。● DownloadRequest○ ダウンロードするために必要な情報。ダウンロードしたい URIやライセンスキーなどを持つ。● DownloadService○ DownloadをするService。
ExoPlayerでダウンロードして再生するまで1. DownloadRequest を組み立てる2. DownloadService.sendAddDownload を呼び出しダウンロードする3. DownloadManager から DownloadIndex を取得し、ダウンロードしたデータを表す Download のインスタンスを取得する4. Download のインスタンスから MediaItem を生成する5. CacheDataSource.Factory を MediaSource.Factory (DMM TVの場合はDashMediaSource.Factory)に渡す6. MediaSource.Factory の createMediaSource メソッドに4の MediaItem を渡し、MediaSource を取得する7. MediaSource をExoPlayer で再生する
MediaItemの設定● uriは動画のURL● ここで使っているUtilはExoPlayer側で用意されているもの
DownloadHelperの設定● DownloadHelper.forMediaItemで先程のmediaItemを渡し、DownloadHelperを取得する
DownloadRequestの取得● 先程の downloadHelper の getDownloadRequest を呼び出し、DownloadRequest を取得する● 引数にuniqueなidを渡す(今回は動画のid)
DownloadService.sendAddDownload を呼び出す● requestは先程の DownloadRequest● DownloadManager を返すメソッドを実装した DownloadService のclassオブジェクトを渡す
Downloadのインスタンスを取得する● DownloadRequest の取得の際に設定したidをもとに Download を取得する
MediaSource を取得する● CacheDataSource.Factory を設定する● DashMediaSource.Factory を設定する● createMediaSource で生成する● あとはExoPlayerに渡して再生する
ExoPlayerのダウンロード関連の主要な登場人物(復習)● Cache○ ExoPlayerのCache。ダウンロードの保存先の情報をもつ。● Download○ ダウンロードしたデータ。● DownloadIndex○ Downloadを保存・取得する。● DownloadManager○ Downloadを管理する。CacheとDownloadIndexをもつ。Downloadを取得したり、Download中のものをキャンセルしたりする。● DownloadRequest○ ダウンロードするために必要な情報。ダウンロードしたい URIやライセンスキーなどを持つ。● DownloadService○ DownloadをするService。
ここまでが事前知識
DMM TVではダウンロードをどのように実装していたか● もともとはSDカードに対応しておらず、内部ストレージへのみのダウンロード機能だった● Cache, DownloadManagerをDIコンテナ(Koin)でシングルトンで管理し、使うところにDIしていた
SDカード対応でどうなったか● もともとはSDカードに対応しておらず、内部ストレージへのみのダウンロード機能だった● Cache, DownloadManagerをDIコンテナ(Koin)でシングルトンで管理し、使うところにDIしていた→ CacheとDownloadManagerは複数の保存先に対応していない😇SDカードに対応させるには、内部ストレージ用とSDカード用でCacheとDownloadManagerを別々に用意し、必要に応じて出し分ける必要がある😇SDカードの状態も考えると直接DIするとまずいので結構直さなきゃいけない😇
やったこと1. 保存先の設定の項目をアプリに追加する2. 必要に応じて出し分けるProviderクラスを用意3. Cache, DownloadManagerを直接DIするのをやめ、ProviderをDIする4. 保存時は保存先の設定を見てどちらのDownloadManagerを使うか決める5. 再生時は両方のDownloadManagerから取得を試み、取得できたほうを使う
必要に応じてCacheを出し分けるProviderの実装1. 保存先の設定をもとに保存先のディレクトリを取得する処理の実装2. 保存先の設定を受け取り、Cacheのインスタンスを生成するFactoryの実装3. Cacheのインスタンスを管理するProviderの実装
保存先のディレクトリを取得する処理の実装
Cacheのインスタンスを生成するFactoryの実装● SDカードが挿入されていない場合、downloadDirectoryがnullになる
Cacheのインスタンスを管理するProviderの実装● 同じ保存先ディレクトリのCacheは2つ以上同時に保持するとエラーになるため、synchronizedで対策
DownloadManagerを出し分けるProviderの実装1. DownloadIndexインスタンスを内部ストレージとSDカードで分ける2. 保存先の設定を受け取り、DownloadManagerインスタンスを生成するFactoryの実装3. DownloadManagerのインスタンスを管理するProviderの実装
DownloadIndexを内部ストレージとSDカードで分ける● 内部ストレージとSDカードどちらに保存しているかの判断のために分ける
DownloadManagerを生成するFactoryの実装
DownloadManagerを管理するProviderの実装
DownloadService.sendAddDownload呼び出し修正前● DownloadManagerを返すDownloadServiceのメソッドで直接DIコンテナから取得している● DownloadService.sendAddDownloadに内部ストレージのDownloadServiceのclassオブジェクトを固定で渡している
DownloadService.sendAddDownload呼び出し修正● DownloadManagerを返すDownloadServiceのメソッドでProviderから取得している● DownloadService.sendAddDownloadで渡すclassオブジェクトを設定から出し分ける
Downloadインスタンス取得の修正前● 直接DIした内部ストレージのDownloadManagerから単純に取得する
Downloadインスタンス取得の修正● 内部ストレージとSDカード両方から取得を試み、どちらに保存されているか判断
MediaSource取得の修正前● 直接DIした内部ストレージのCacheをDataSourceに使う
MediaSource取得の修正● 前述の処理で判定した保存先をもとに、cacheを取得するように修正
これでSDカードでも保存・再生できるように🎉
まとめ● ExoPlayerのCacheやDownloadManagerは、複数の保存先を管理するような仕組みがない● そのため、内部ストレージとSDカードでCacheとDownloadManagerを分ける必要がある● CacheとDownloadManagerを管理するProviderを実装すると便利● もっといいやり方あれば教えてください
ありがとうございました!