nana開発者ブログ

音楽SNSアプリ"nana"開発チームのブログです。

nanaの新機能、音声ライブ配信を支える技術 - Android編

nana musicでAndroidアプリ開発を担当している市原です。

前回の荒井の記事でもありましたように、10月にリリースした新機能nana LIVEのAndroidにおける開発を担当しましたので、 今回はライブのデータ同期をどうやってAndroidで実現したのかをご紹介したいと思います。

tech.nana-music.com

ライブ配信機能のデータ同期方法について

nana LIVEはリアルタイムで視聴ユーザー人数や投稿されたコメントを表示させたり、ライブが終了した場合に全てのユーザーを退出させたりします。 それらを実現するために使用したのがFirebase Realtime Database(以下、Realtime Database)です。

firebase.google.com

Realtime DatabaseはFirebaseのデータベースで、リアルタイムにクライアント間での同期が必要な場面に利用します。 Androidの他にiOSやUnityなど複数のプラットフォームに対応しており、シンプルに実装可能です。
nanaパーティーの実装でも利用しており実績があるので採用しました。

ライブ配信で同期するデータについて

まずはnana LIVEではどのようなデータを同期しているかをご紹介します。

①メタデータ

ライブ名、現在の視聴者数などライブに関する基本的な情報です。

②承認待ちユーザー

登壇に立候補している各ユーザー名、アイコン画像などの情報です。

③登壇者

各登壇者のユーザー名、アイコン画像などの情報です。

④コメント

ユーザーが投稿したコメントやライブ終了1分前などの通知コメントです。

⑤セットリスト

ホストやゲストがライブ内で再生するために追加した楽曲一覧です。
セットリスト内のどの曲が再生されているかも含まれています。

⑥ライブ内通知

ホストがライブが終了した、ホストの通信状況によりライブが終了した、などの情報です。

f:id:nanamusic-tech:20220210135329p:plainf:id:nanamusic-tech:20220210143023p:plain

更新のシーケンス

以下は、コメント投稿した場合などのユーザー起因のイベント時のシーケンスです。

f:id:nanamusic-tech:20220209101155p:plain
nana_live_comment

以下は、ライブが終了した場合などのサーバーで管理しているイベント時のシーケンスです。

f:id:nanamusic-tech:20220209101219p:plain
nana_live_notification

nana LIVEではユーザー端末から直接Realtime Databaseの更新を行わず、nanaのサーバーを経由しています。
セットリスト情報をはじめとしたデータ管理が複雑なのでサーバー側で一元管理してRealtime Databaseの更新を行なっています。
ユーザー端末(Android/iOSなど)からも直接Realtime Databaseの更新が可能なので、実装する機能ごとに設計の検討が必要です。

実装方法

Gradleの設定

まずはRealtime Databaseを取り込むため、build.gradle(:app)でGradleの設定を行います。

dependencies {

    implementation platform("com.google.firebase:firebase-bom:{任意のバージョン}")
    // Javaは-ktxが不要
    implementation "com.google.firebase:firebase-database-ktx"

}

同期データの定義

次に同期したいデータを定義します。

data class MetaResponse(
    val id: Int = 0,
    val name: String = ""
    val createdAt: String = ""
)

リファレンスインスタンスの作成

同期するデータがRealtime Database上に以下のようにあったとします。

f:id:nanamusic-tech:20220208104901p:plain
ライブメタデータ

階層に合わせたインスタンスを作成します。

// 今回の例のroomIdは1234
val reference = FirebaseDatabase.getInstance().getReference("live/$roomId/meta")

リスナー追加(全取得)

作成したインスタンスにリスナーを追加することでデータを取得します。

private val valueEventListener = object : ValueEventListener {

    override fun onDataChange(snapshot: DataSnapshot) {
        // 取得したデータ
        val meta = snapshot.getValue(MetaResponse::class.java)
    }

    override fun onCancelled(error: DatabaseError) {
        // エラー処理
    }
}

reference.addValueEventListener(valueEventListener)

上記のリスナーはデータの変更があった場合に呼び出されます。
データの中で変更があったプロパティーだけでなく、変更されていないプロパティーも含めて全てが返却されます。

リスナー追加(差分取得)

nana LIVEではコメントに関しては追加されたコメント部分だけを取得し
その差分を追加表示させています。

private val childEventListener = object : ChildEventListener {

    override fun onChildAdded(snapshot: DataSnapshot, previousChildName: String?) {
        // 追加時
        val item = snapshot.getValue<LiveCommentResponse>(CommentResponse::class.java)
    }

    override fun onChildChanged(snapshot: DataSnapshot, previousChildName: String?) {
        // 変更時
    }

    override fun onChildRemoved(snapshot: DataSnapshot) {
        // 削除時
    }

    override fun onChildMoved(snapshot: DataSnapshot, previousChildName: String?) {
        // 移動時(データ内のリストの順序変更など)
    }

    override fun onCancelled(error: DatabaseError) {
        // エラー処理
    }
}

リスナーの削除

最後にライブの終了時などデータ取得が不要になったらリスナーを削除します。

reference.removeEventListener(valueEventLiistener)

以上でデータ同期の一連の流れが実装できました。

最後に

nana musicではユーザーに新しい体験を提供できるエンジニアを募集しております。 カジュアル面談なども実施しておりますので、興味のある方は下記よりお気軽にお問い合わせください!

www.wantedly.com