nana music Tech Blog

株式会社nana music 開発チームのブログです。

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

初めまして!nana musicでiOSエンジニアを担当しております、荒井と申します。

iOSアプリ開発歴は10年程で、Objective-CにARC(AutomaticReferenceCounting)がない時代に、手作業でretainしたりreleaseしていたりするのがとても懐かしいです。

さて前回の中島の記事でもありましたように、10月にリリースした新機能nana LIVEのiOSにおける開発を担当しましたので、今回はどうやってiOSで実現したのかをご紹介したいと思います。

tech.nana-music.com

Agoraによるライブ配信機能の構築

nana LIVEは最大6人で同時に話したり、音楽を流したりできる機能です。 同様の機能に2019年にリリースしたnanaパーティというものがありますが、こちらはWowzaというライブストリーミングのサーバーを立て、 RTMPやHLSで通信することによって機能を実現していました。

しかし、Wowzaによる配信は

  • Wowzaサーバーの管理、監視
  • トラフィックに応じたWowzaサーバーのスケールアップ

などの対応が必要であり、そのコストも無視できなくなっておりました。

そこで登場したのがAgoraです。

Clubhouseによる導入

Agoraが日本で注目され出したのは、音声SNSの「Clubhouse」が配信技術としてAgoraを使っていると認識され始めたのがきっかけでしょう。

Agoraは音声や映像のストリーミングを行うAPIを提供するサービスです。 Agoraは利用した分だけ料金を支払えば良いので、利用ユーザーが増えてもスケールは自動で行ってくれます。

今回は音声のストリーミング機能を作るにあたり、必要な手順をご紹介します。

開発リソース

リソーストップ

www.agora.io

iOS SDK

github.com

開発の手順

1. Agoraのアカウント、プロジェクト作成

まずはアカウントを作成しましょう。

Agora.io Signup

次にプロジェクトを作成します。

Dashboard

「Create」ボタンをクリックします。

f:id:nanamusic-tech:20211226110600p:plain

Authentication Mechanism は2種類ありますが、開発時においては下の Testing Mode を選んでおけば大丈夫です。

こちらを選択すれば、サーバーサイドでの認証は不要で開発することができます。

開発完了して、QA、リリースする際には上の Secured Mode にする必要があります。

サーバーサイドでのtokenの生成についてはこちらを参照ください。

Authenticate Your Users with Tokens

f:id:nanamusic-tech:20211226111937p:plain

次に、作成したプロジェクトのApp IDと書かれている部分をクリックしApp IDをコピーします。 このApp IDをこのあと使うため、メモしておきましょう。

2. SDKの導入

SDKは直接ダウンロードすることもできますが、 CocoaPodsやSwift Package Managerもサポートされているので、 今回はSwift Package Managerで導入します。

Downloads

下記のURLをSwift Package Managerで登録します。

https://github.com/AgoraIO/AgoraRtcEngine_iOS

3. 認証

まずViewControllerの中でAgora SDKを初期化します。

AppID は先ほどAgoraプロジェクト作成時に取得したApp IDを指定します。

    var agoraKit: AgoraRtcEngineKit?

    func initializeAgoraEngine() {
        agoraKit = AgoraRtcEngineKit.sharedEngine(withAppId: AppID, delegate: nil)
    }

Testing Modeでの認証はこれだけです。

4. 部屋の作成、入室

3で初期化したAgoraのインスタンスを使って、部屋を作成します。

今回は認証モードを Testing Mode にしているため、 tokenは nil を指定します。

    func createChannel() {
        agoraKit.joinChannel(byToken: nil, channelId: "testchannel", info:nil, uid:0) { [weak self] (sid, uid, elapsed) -> Void in
            self.agoraKit.setEnableSpeakerphone(true)
            UIApplication.shared.isIdleTimerDisabled = true
        }
    }

部屋を作成したユーザー以外が入室する場合は、 同じ channelId を指定し、同じように joinChannel メソッドを呼ぶことで同じ部屋に入ることができます。

5. マイクの制御

録音ボリュームを調整するには、 adjustRecordingSignalVolume メソッドを使います。

指定する引数はデフォルトのボリュームが 100 になり、任意の値を指定可能です。

    agoraKit.adjustRecordingSignalVolume(50)

再生ボリュームを調整する場合は、 adjustPlaybackSignalVolume を使って同じように調整できます。

    agoraKit.adjustPlaybackSignalVolume(50)

6. オーディオmix

音声データに任意の音源をミックスして配信することも可能です。 また以下のファイルタイプがサポートされています。

  • MP3
  • AAC
  • M4A
  • MP4
  • WAV
  • 3GP

再生を開始するには startAudioMixing メソッドを呼びます。

    let filePath = "file path"
    let loopback = false
    let replace = false
    let cycle = 1
    let startPos = 0

    agoraKit.startAudioMixing(filePath, loopback: loopback, replace: replace, cycle: cycle, startPos: startPos)

filePath はローカル、リモートどちらのファイルでも指定することができます。

リモートのパスを指定した場合は、ダウンロード処理もAgoraが行ってくれます。

再生を停止する場合は stopAudioMixing メソッドを呼びます。

    agoraKit.stopAudioMixing()

7. 退室

退室する際は、 leaveChannel メソッドを呼びます。 部屋を作成したユーザーが退室しても、その他のユーザーは強制的に退室させられるわけではないので、別途退室処理を行ってあげる必要があります。

    func leaveChannel() {
        agoraKit.leaveChannel(nil)
        UIApplication.shared.isIdleTimerDisabled = false
    }

以上の手順で、一通りの音声配信をすることが可能です。

Agoraを使う際の注意点

Agora SDKを使った際にハマった箇所が2点あったため、ご紹介します。

ローカル端末(自分)だとAgoraからuserIdが0で返ってくる

AgoraRtcEngineDelegate を指定することで、様々な情報をcallbackで取得することができます。

例えばnana LIVEでは、話しているユーザーのアイコンが白く光るような演出が出るようになっています。

f:id:nanamusic-tech:20211226121523p:plain

これは reportAudioVolumeIndicationOfSpeakers で返ってくる情報で実現しているのですが、自分(ローカル端末)の場合は userId0 で返ってくるという仕様がわからずに、悩んでしまいました。 userId0 で返ってきた場合は手元の端末だという風に実装してあげることで実現することができました。

    func rtcEngine(_ engine: AgoraRtcEngineKit, reportAudioVolumeIndicationOfSpeakers speakers: [AgoraRtcAudioVolumeInfo], totalVolume: Int) {
    }

音源によってはストリーミング再生できないことがある

nana LIVEでは当初リモートの音源のパスを指定していましたが、音源によってはAgoraからエラーが返ってくることがありました。 対応方法としては、自前で音源をローカルにダウンロードした上で、ローカルのパスを指定してあげることで再生できるようになりました。

次回予告

次回はnana LIVEのAndroid版開発を担当した市原が、Firebase Realtime Databaseの事例を交えて、 Android版nana LIVEの実装についてご紹介する予定です。 お楽しみに。

最後に

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

www.wantedly.com