Upgrade to Pro — share decks privately, control downloads, hide ads and more …

ReactNativeとKotlinで叶える夢のリアルタイム音声配信

 ReactNativeとKotlinで叶える夢のリアルタイム音声配信

yu mitsuhori

October 02, 2021
Tweet

More Decks by yu mitsuhori

Other Decks in Technology

Transcript

  1. ReactNativeとKotlinで叶える
    夢のリアルタイム音声配信
    株式会社stand.fm エンジニア 三堀 裕
    2021/10/02 ReactNativeMatsuri

    View Slide

  2. 三堀 裕

    stand.fmのソフトウェアエンジニア

    モバイル開発がメイン。前職ではAndroid、Flutterを
    やってました

    1013Youmeee

    2

    View Slide

  3. 発表のテーマ、ゴール感
    - テーマ
    - ReactNative×Kotlin(Android)
    - 音声収録・配信アプリのクライアント実装イメージについて
    - ゴール感
    - ReactNativeにおけるAndroidのNativeModuleの実装方法がわかる
    - Kotlinの魅力が伝わっている、導入方法がわかる
    - 音声収録・配信アプリの配信クライアントの実装イメージが伝わっている
    音声収録・配信アプリの実装を例に上げながら
    ReactNative×Kotlinの魅力を伝えたい!!

    3

    View Slide

  4. 本発表で話さないこと

    - iOS、サーバーサイドの実装方法について

    - 音声配信アプリの受信クライアントの実装方法(配信音声を聞く側)

    - 使用ライブラリの優劣については議論しません

    - サンプルで使用する例が必ずしもベストプラクティスではないです。あくまで一例という前提でご覧
    いただければと思います 

    4

    View Slide

  5. アジェンダ
    - ReactNativeのNativeModuleとは
    - ReactNativeでのKotlinの導入方法
    - Kotlinの基本文法やJavaとの比較
    - Kotlinを用いた音声収録・配信機能の実装
    - 収録
    - 配信(RTMP)
    - まとめ
    5

    View Slide

  6. ReactNativeの
    NativeModuleとは

    6

    View Slide

  7. NativeModuleとは

    - ReactNativeでネイティブのコードを実行する仕組み

    - Android: Java, Kotlin 

    - iOS: Swift, Objective-C 

    - https://reactnative.dev/docs/native-modules-intro

    引用:
    https://medium.com/hackernoon/first-experiences-with-react-native-bridging-an-andr
    oid-native-module-for-app-authentication-501fec247b2b 

    7

    View Slide

  8. NativeModuleの実装を知っておくと良いこと

    - Expoでの開発をしていたがネイティブのモジュールを使わざるを得なくなったとき

    - プラットフォームごとのSDKやAPIを用いないと実現できない場合がある

    (今回取り上げる音声の録音、配信などもこれに該当する)

    - react-native関連のOSSライブラリがあるが、自分たちが望んでいる挙動でない場合や、クラッ
    シュなどの問題がある場合に対応できるようにしておく必要がある

    8

    View Slide

  9. AndroidのNativeModuleを実装する準備

    1. AndroidStudioをインストール

    a. https://developer.android.com/studio 

    2. AndroidStudioでReactNativeプロジェクトの

    androidディレクトリを開く(→のようになる)

    9
    ↑ReactNativeプロジェクトをAndroidStudioで開く

    2つのJavaファイルが
    生成される


    View Slide

  10. 10
    NativeModuleによるJSとNative間のやり取りの種類

    1. JS→Nativeを呼ぶ場合

    2. Native→JSにイベントを通知する場合


    View Slide

  11. 11
    NativeModuleの主な実装ステップ

    1. Java側

    a. Moduleクラスを作る(JS側に公開するインターフェース) 

    b. Packageクラスを作る 

    c. MainApplication.javaでbのpackageをわたす 

    2. JS側

    a. Javaで定義したNativeModuleのインターフェースを実装する 


    View Slide

  12. 12
    NativeModule導入サンプル〜Androidのトースト表示〜

    - トースト:Androidにおける簡易的なメッセージ表示をする

    コンポーネント

    - トーストを表示するサンプルを作ってみます

    トースト


    View Slide

  13. NativeModuleの例(Java側)〜Androidのトースト表示〜

    a. ToastModuleクラスを作る 

    13
    public class ToastModule extends ReactContextBaseJavaModule {
    public ToastModule(ReactApplicationContext context) {
    super(context);
    }
    @NonNull
    @Override
    public String getName() {
    return "ToastModule";
    }
    @ReactMethod
    public void showToast(String text) {
    final Toast toast = Toast.makeText(getReactApplicationContext(), text, Toast.LENGTH_LONG);
    toast.show();
    }
    }
    @ReactMethodアノテーションをつけたメソッドがJS側から実行できる 

    JSに公開するNativeModuleの名前を定義 

    ReactContextBaseJavaModule 

    を継承


    View Slide

  14. NativeModuleの例(Java側)〜Androidのトースト表示〜

    b. Packageクラスを作る 

    14
    public class ToastPackage implements ReactPackage {
    @NonNull
    @Override
    public List createNativeModules(@NonNull ReactApplicationContext reactContext) {
    List modules = new ArrayList<>();
    modules.add(new ToastModule(reactContext));
    return modules;
    }
    @NonNull
    @Override
    public List createViewManagers(@NonNull ReactApplicationContext reactContext) {
    return Collections.emptyList();
    }
    }
    ReactPackageインターフェースを実装 

    ToastModuleインスタンスを初期化し、追加 


    View Slide

  15. NativeModuleの例(Java側)〜Androidのトースト表示〜

    c. MainApplication.javaにToastPackageのインスタンスを追加する 

    15
    public class MainApplication extends Application implements ReactApplication {
    private final ReactNativeHost mReactNativeHost =
    new ReactNativeHost(this) {
    @Override
    protected List getPackages() {
    @SuppressWarnings("UnnecessaryLocalVariable")
    List packages = new PackageList(this).getPackages();
    packages.add(new ToastPackage());
    return packages;
    }
    };
    }
    Packageインスタンスを初期化し、追加 


    View Slide

  16. NativeModuleの例(JS側)〜Androidのトースト表示〜

    a. NativeModuleのインターフェースを定義(toast.js) 

    16
    // @flow
    import { NativeModules } from 'react-native'
    const { ToastModule } = NativeModules
    const showToast = React.useCallback(() => {
    ToastModule.showToast('Tapped!')
    }, [showToast])
    b. タップ時showToastメソッドをコールする→ 

    return (


    SHOW TOAST!


    );
    先程定義したToastModuleをimport 

    ToastModuleのメソッドを呼び出す 


    View Slide

  17. 17
    NativeModule(Native→JSにイベントを通知する場合)

    - ネイティブ側からのEventをjs側に伝えるための手段

    - NativeEventEmitterというものを使う

    WritableMap params = Arguments.createMap();
    params.putString("message", "Something is occured.");
    context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
    .emit("SomethingEvent", params);
    import { NativeModules, NativeEventEmitter } from 'react-native'
    const eventEmitter = new NativeEventEmitter(NativeModules.SomethingModule)
    eventEmitter.addListener('SomethingEvent', (event) => {
    console.log(event.message) // output: Something is occured
    })
    イベントパラメータの作成 

    イベント名とパラメータを指定してemit()を呼ぶ 

    ネイティブで指定したイベント名でイベントをListenする 

    指定したModuleのNativeEventEmitterを定義 


    View Slide

  18. ReactNativeでの
    Kotlinの導入
    18

    View Slide

  19. - 一言で言えばJavaの上位互換(Java好きの方怒らないで下さい...🙏)

    - JetBrains製のJVM言語の一つ

    - Androidネイティブ開発におけるファースト言語とされている

    - 完全Java互換

    - JavaからKotlinをコードを呼べるし、KotlinからJavaのコードを呼べる 

    - NullSafety, SealedClassなど様々な言語機能を備えている

    - 実装編で取り上げます

    Kotlinとは

    19

    View Slide

  20. 20
    Kotlinの基本文法

    val immutableValue = ""
    immutableValue = "mutable" // error
    var mutableValue = ""
    mutableValue = "mutable" // ok
    val text1: String? = null // ok
    val text2: String? = "hoge" // ok
    val text3: String = null // error
    val text4: String = "hoge" // ok
    ・変数定義(val, var)
 ・Nullable(?), NonNull

    ・関数定義はfun

    fun something(): String {
    return "something"
    }
    ・クラス定義

    class Car(val name: String)
    interface Vehicle {
    fun start()
    }
    class Car(val name: String): Vehicle {
    override fun start() {
    }
    }
    ・インターフェースの継承(実装)

    ・ラムダ(jsのアロー関数のようなイメージ)

    val callback: (String) -> Unit = { data: String ->
    Log.d("Log", data)
    }

    View Slide

  21. ReactNativeライブラリはAndroid側はJavaで書かれている事が多い

    21
    「Kotlinで書きたい!!!」

    Androidエンジニアとしては... 

    と思うわけです。。

    - 以下のようなライブラリをforkして修正することがあるが、これらはJavaで書かれている

    - 例1: react-native-share

    - https://github.com/react-native-share/react-native-share

    - 例2: react-native-track-player

    - https://github.com/DoubleSymmetry/react-native-track-player

    View Slide

  22. ReactNativeでKotlinを使う方法

    1. AndroidStudioでKotlinのプラグインをインストール

    a. Preferences > Plugins > 「Kotlin」を検索し、Installをする 

    2. build.gradleでKotlinの依存を含める

    a. build.gradleとapp/build.gradleの2つにKotlinを利用するための宣言を書く 

    22

    View Slide

  23. 23
    build.gradleでKotlinの依存を含める

    build.gradle

    buildscript {
    ext {
    kotlin_version = '1.4.10' // add
    }
    dependencies {
    classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // add
    }
    }
    apply plugin: "com.android.application"
    apply plugin: "kotlin-android" // add
    apply plugin: "kotlin-android-extensions" // add

    dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" // add
    }
    app/build.gradle

    SyncNowを押下し、依存モジュールをインストール 


    View Slide

  24. 24
    既存コードをKotlinに変換する

    - AndroidStudioでは「Convert Java File to Kotlin File」

    というコマンドがある

    - これを実行すると自動的にJavaをKotlinに変換してくれる

    - 変換されたコードはKotlinらしい書き方になっていない場合

    があるので気になる方は直すと良い

    View Slide

  25. 変換前:ToastModule.java

    25
    public class ToastModule extends ReactContextBaseJavaModule {
    public ToastModule(ReactApplicationContext context) {
    super(context);
    }
    @NonNull
    @Override
    public String getName() {
    return "ToastModule";
    }
    @ReactMethod
    public void showToast(String text) {
    final Toast toast = Toast.makeText(getReactApplicationContext(), text, Toast.LENGTH_LONG);
    toast.show();
    }
    }

    View Slide

  26. 変換後:ToastModule.kt

    26
    class ToastModule(context: ReactApplicationContext?) : ReactContextBaseJavaModule(context) {
    override fun getName(): String = "ToastModule"
    @ReactMethod
    fun showToast(text: String?) {
    val toast = Toast.makeText(reactApplicationContext, text, Toast.LENGTH_LONG)
    toast.show()
    }
    }

    View Slide

  27. 27
    Kotlinに変換するメリット

    - とにかく記述量が少なく済む

    - 冗長性のあるコードを削減できる

    - classはデフォルトでpublicになる 

    - 変数定義(val)はデフォルトでfinal扱いになる 

    - 安全性のあるコードが書ける

    - Nullsafetyのサポートなど 

    とにかく開発体験が良いので

    音声収録・配信機能を実装しながら見ていきましょう


    View Slide

  28. Kotlinを用いた
    音声収録・配信機能の実装

    28

    View Slide

  29. stand.fmは誰でもかんたんに

    アプリで収録・LIVE配信ができる音声
    プラットフォーム


    29
    の 超簡易版 を作ります

    View Slide

  30. 今回作る機能ってどんなもの?

    30
    1. 音声を録音してファイルとして出力する機能

    2. 録音した音声をストリーミングする機能


    View Slide

  31. Kotlinを用いた
    音声収録・配信機能の実装
    〜音声の収録〜
    31

    View Slide

  32. 音声の録音機能の全体イメージ

    32
    App
    配信者
    サンプリング
    PCM AAC
    エンコード
    音声ファイル
    (.m4a)
    ファイル出力

    View Slide

  33. 音声の録音機能の実装イメージ

    33
    システムの録音権限の要求
    音声ファイルの準備
    録音停止
    録音開始
    録音ファイルの出力

    View Slide

  34. 34
    音声の録音機能の実装〜システムの録音権限の要求〜

    1. android/app/src/AndroidManifest.xmlにRECORD_AUDIO権限を追加

    ...
    package="com.rnmatsurisampleapp">


    2. ユーザーにパーミッションを明示的に要求

    export const requestPermission = async (): Promise => {
    const granted = await PermissionsAndroid.request(
    PermissionsAndroid.RECORD_AUDIO,
    {
    title: "録音の開始にはシステム権限の許可が必要です",
    message: "音声の録音に使用します。許可しますか",
    }
    )
    return granted === PermissionsAndroid.RESULTS.GRANTED
    }
    RECORD_AUDIO権限の追加

    PermissionsAndroidモジュールでユーザーにマイク権限をリクエスト

    このような権限要求ダイアログが表示される

    View Slide

  35. 音声の録音機能の実装〜音声ファイルの準備〜

    35
    - RecorderModuleを定義

    - startRecordingのReactMethodを定義する

    - 録音した音声の出力先ファイルを作成しておく

    class RecorderModule(context: ReactApplicationContext?) : ReactContextBaseJavaModule(context) {
    private var recordingFilePath: String? = null
    @ReactMethod
    fun startRecording(promise: Promise) {
    val fileName = System.currentTimeMillis().toString()
    recordingFilePath = "${this.reactApplicationContext.filesDir.absolutePath}/${fileName}.m4a"
    }
    } アプリ内領域のストレージの絶対パスを取得 


    View Slide

  36. 音声の録音機能の実装〜録音開始〜

    36
    - 音声の録音にはMediaRecorderというAndroidのAPIを使う

    - https://developer.android.com/guide/topics/media/mediarecorder?hl=ja

    - 録音に使用されるサンプリングレートや出力フォーマットなどの設定をする

    - prepare()を呼んだあとstart()を呼ぶことで録音が開始される

    private var recorder: MediaRecorder? = null
    @ReactMethod
    fun startRecording(promise: Promise) {
    recorder = MediaRecorder().apply {
    setAudioSource(MediaRecorder.AudioSource.MIC)
    setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
    setOutputFile(recordingFilePath)
    setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
    setAudioSamplingRate(44100)
    try {
    prepare()
    start()
    } catch (e: IOException) {
    promise.reject(e)
    }
    }
    promise.resolve(null)
    }
    ・入力ソース:マイク

    ・出力ファイルフォーマット:MP4(.m4a)

    ・エンコーダーの指定:AAC

    ・サンプリングレート:44.1kHz

    録音の開始

    以下の項目を設定 


    View Slide

  37. 音声の録音機能の実装〜録音停止〜

    37
    - MediaRecorderのstop()とrelease()を実行し、録音を停止する

    - 録音したファイルパスをjs側に返す

    @ReactMethod
    fun stopRecording(promise: Promise) {
    recorder?.apply {
    stop()
    release()
    }
    recorder = null
    recordingFilePath = null
    promise.resolve(recordingFilePath)
    }
    録音された音声ファイルのパスを返す

    録音を停止する


    View Slide

  38. PromiseとAndroidのNativeModule

    38
    - Promiseの結果としてNativeModuleでの結果を返したい場合は、Promiseオブジェクトを
    ReactMethodの引数に渡す

    - resolveする場合はpromise.resolveを呼び、返り値を引数に渡す

    - rejectする場合はpromise.rejectを呼ぶ

    - 引数にはErrorCode, Message, Throwableを渡すことができ、js側のreject時のオブジェクトにまとめ
    て渡される

    @ReactMethod
    fun startRecording(promise: Promise) {

    try {
    promise.resolve(null)
    } catch (e: Exception) {
    promise.reject(e)
    }
    }
    try {
    const result = await RecorderModule.startRecording()
    } catch (err) {
    console.error(err)
    }
    Promiseの結果を渡す


    View Slide

  39. 音声の録音機能の実装〜js側の呼び出し〜

    39
    - 録音開始時と終了時にNativeModuleを呼び出すuseCallbackを定義

    const onStartRecordingPressed = useCallback(async () => {
    try {
    await RecorderModule.startRecording()
    } catch (err) {
    // エラー処理
    }
    }, [])
    const onStopRecordingPressed = useCallback(async () => {
    try {
    const filePath = await RecorderModule.stopRecording()
    } catch (err) {
    // エラー処理
    }
    }, [])
    録音の開始

    録音の停止


    View Slide

  40. - あるオブジェクトに対してスコープを新たに作ることで、冗長な表現を削減できる

    val recorder = MediaRecorder().apply {
    setAudioSource(MediaRecorder.AudioSource.MIC)
    setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
    setOutputFile(recordingFilePath)
    setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
    setAudioSamplingRate(44100)
    }
    40
    Kotlin 小話1: スコープ関数〜apply〜

    MediaRecorder recorder = new MediaRecorder();
    recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
    recorder.setOutputFormat(MediaRecorder.OutputFormat.MPEG_4);
    recorder.setOutputFile(recordingFilePath);
    recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
    recorder.setAudioSamplingRate(44100);
    ↑Javaのrecorder. を省略できる

    apply: このスコープの中はMediaRecorderがthisに
    なる


    View Slide

  41. 41
    Kotlin 小話1: 5つのスコープ関数

    recorder?.let {
    it.prepare()
    it.start()
    }
    ・let(Nullableをアンラップする)

    val recorder: MediaRecorder = MediaRecorder().apply {
    setOutputFile(recordingFilePath)
    setAudioSamplingRate(44100)
    }
    ・apply

    (繰り返しインスタンスのメソッドを呼び出す)

    val recorder: MediaRecorder = MediaRecorder().also {
    Log.d("Log", it.hashCode().toString())
    }
    ・also

    (初期化ついでにそのインスタンスを使いたい

    val result: String = with(recorder) {
    this?.start()
    "ok"
    } // result: "ok"
    ・with(runの代替手段)

    val result: String = recorder?.run {
    this.start()
    "ok"
    } // result: "ok"
    ・run(ラムダ内で結果を返したい)

    View Slide

  42. Kotlinを用いた
    音声収録・配信機能の実装
    〜音声の配信〜
    42

    View Slide

  43. 音声の配信機能の全体イメージ

    43
    App (配信クライアント )
    MediaServer
    RTMP
    配信者
    録音
    PCM AAC
    エンコード
    App (受信クライアント )
    CDN
    HLS

    View Slide

  44. 音声の配信機能の全体イメージ(今日話す部分)

    44
    App (配信クライアント )
    MediaServer
    RTMP
    配信者
    録音
    PCM AAC
    エンコード
    App (受信クライアント )
    CDN
    HLS

    View Slide

  45. 45
    RTMP(Real-Time Messaging Protocol)とは

    - Adobe社が開発したビデオや音声をリアルタイムに転送するためのストリーミングプロトコル

    - TCPベースのプロトコルで低遅延接続を維持できるように設計されている

    - 対応音声コーデック:

    - AAC、AAC-LC、MP3、Speexなど 

    - 関連フォーマット:

    - RTMPS (SSL経由で暗号化) 

    - RTMFP (TCPの代わりにUDP経由でレイヤー化) 


    View Slide

  46. 音声の配信機能の実装イメージ

    46
    RTMPコネクションの開始
    音声キャプチャとエンコーダの開始
    キャプチャ音声のAACエンコーディング
    AAC音声をRTMPで逐次アップロード
    RTMPコネクションの停止
    音声キャプチャとエンコーダの停止
    ストリーミングの開始 
 ストリーミングの停止 

    ストリーミング中


    View Slide

  47. RTMPストリーミング用クライアントライブラリの導入

    - RTMPストリーミングにはrtmp-rtsp-stream-client-javaというネイティブ用ライブラリを使います

    - https://github.com/pedroSG94/rtmp-rtsp-stream-client-java 

    - 音声のキャプチャ(録音)、エンコードも一括でやってくれる 

    - 導入にはapp/build.gradleにdependenciesを定義して、Sync Nowを押下

    47
    dependencies {
    ...
    implementation "com.github.pedroSG94.rtmp-rtsp-stream-client-java:rtplibrary:2.1.1"
    }

    View Slide

  48. RTMPコネクションと録音の開始〜コネクション開始〜

    48
    class StreamingModule(context: ReactApplicationContext?) : ReactContextBaseJavaModule(context) {
    override fun getName(): String = "StreamingModule"
    private var rtmpClient: RtmpOnlyAudio? = null
    @ReactMethod
    fun startStreaming(promise: Promise) {
    val rtmpUrl = "rtmp://exmaple.com:1935/rnmatsurisampleapp/abcdefg"
    rtmpClient = RtmpOnlyAudio(connectCheckerRtmp).apply {
    setAuthorization("username", "password")
    prepareAudio(64 * 1024, 44100, false)
    startStream(rtmpUrl)
    }
    }
    }
    - StreamingModuleというNativeModuleを定義

    - startStreamingメソッドを実装

    - RTMPクライアント(RtmpOnlyAudio)の初期化と認証パラメータの設定 

    - 録音音声のエンコーダのパラメータ設定 

    - startStreamを呼び出してRTMPコネクションを開始要求を発行 

    RTMPクライアントのインスタンス

    RTMPの認証パラメータのセット

    音声関連の設定(bitrate, sampleRate, isStereo)

    マイク、エンコーダーの初期化をする

    RTMPコネクションを開始

    View Slide

  49. RTMPコネクションと録音の開始〜RTMPイベントのコールバック設定〜

    - RtmpOnlyAudioの初期化にはConnectCheckerRtmpというコールバック受け取り用のオブジェクトを渡す
    必要がある

    49
    private val connectCheckerRtmp = object: ConnectCheckerRtmp {
    override fun onAuthErrorRtmp() {
    // RTMPの認証時にエラーが起きた場合
    }
    override fun onAuthSuccessRtmp() {
    // RTMPの認証に成功した場合
    }
    override fun onConnectionFailedRtmp(reason: String) {
    // RTMPコネクションの接続が失敗した場合
    }
    override fun onConnectionStartedRtmp(rtmpUrl: String) {
    // RTMPコネクションの接続が開始されたとき
    }
    override fun onConnectionSuccessRtmp() {
    // RTMPコネクションの接続が成功したとき
    }
    override fun onDisconnectRtmp() {
    // RTMPコネクションのSocketが切れたとき
    }
    ...
    }

    View Slide

  50. RTMPコネクションと録音の停止

    - stopStreamというメソッドを呼びストリーミング停止要求を行う

    - RTMPコネクション、録音、エンコーダの停止処理を同時にやってくれる 

    50
    class StreamingModule(context: ReactApplicationContext?) : ReactContextBaseJavaModule(context) {
    ...
    @ReactMethod
    fun stopStreaming() {
    rtmpClient?.stopStream()
    }
    }

    View Slide

  51. 51
    ストリーミング開始の成功と失敗のハンドリング

    - startStreamとstopStreamは非同期で実行される

    - それらの結果を利用したい場合は、ConnectCheckerRtmpのコールバック結果を元にしてハンド
    リングをする

    - 今回はNativeEventEmitterで成功と失敗のコールバックをjs側に伝えるようにする

    fun sendEvent(context: ReactApplicationContext, event: StreamingEvent) {
    val body = event.toBodyMap()
    context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
    .emit(event.name, body)
    }
    private val connectCheckerRtmp = object: ConnectCheckerRtmp {
    override fun onConnectionFailedRtmp(reason: String) {
    sendEvent(context, StreamingEvent.OnError(StreamingErrorCode.RTMP_CONNECTION_FAILED_OR_CLOSED))
    }
    override fun onConnectionSuccessRtmp() {
    sendEvent(context, StreamingEvent.OnStartedStream)
    }
    }
    KotlinのNativeEventEmitterの実装

    RTMPコネクションの失敗イベントをemit

    RTMPコネクションの成功イベントをemit

    View Slide

  52. 52
    Kotlin小話2: SealedClass

    - SealedClassを使うと、通常のenumではできないクラスインスタンスなどをenumの要素の一つの
    ように扱えるようになる

    sealed class StreamingEvent(val name: String) {
    object OnSuccessStream: StreamingEvent("onSuccessStream")
    class OnError(val code: StreamingErrorCode): StreamingEvent("onError")
    }
    ↑

    ・別のイベントだが同じ型として扱え、共通のプロパティも定義で
    きる

    ・onErrorにしかないプロパティも定義できる

    (型推論も効く)

    ↑whenとis演算子で比較でき、 

    eventタイプごとにEmitパラメータのス
    キーマを定義することができる 

    2つの型を定義


    View Slide

  53. 53
    音声の配信〜js側の呼び出し〜

    - startStreamingとstopStreamingを呼び出すcallbackを定義

    - NativeのEventListenerを登録し、イベントをハンドリングする

    import { NativeModules, NativeEventEmitter } from
    'react-native'
    const { StreamingModule } = NativeModules
    const eventEmitter = new NativeEventEmitter(StreamingModule)
    const onStartStreamingPressed = useCallback(async () => {
    await StreamingModule.startStreaming()
    }, [])
    const onStopStreamingPressed = useCallback(async () => {
    await StreamingModule.stopStreaming()
    }, [])
    useEffect(() => {
    const listener = eventEmitter.addListener(
    'onSuccessStream', (event) => {
    showToast('ストリーミングの開始に成功しました ')
    })
    return () => { listener.remove() }
    }, [showToast])
    useEffect(() => {
    const listener = eventEmitter.addListener(
    'onError', (event) => {
    showToast('ストリーミングに失敗しました ')
    })
    return () => { listener.remove() }
    }, [showToast])

    View Slide

  54. stand.fmは誰でもかんたんに

    アプリで収録・LIVE配信ができる音声
    プラットフォーム


    54
    の 超簡易版 を例に Kotlin を用いた
    NativeModuleの実装についてお伝えしました。
    ということで、今回は

    View Slide

  55. まとめ

    55

    View Slide

  56. まとめ

    - ReactNativeでAndroidのNativeModuleを書くときはKotlinで
    🐤

    - Androidにおける音声録音・配信アプリの実装について

    - 音声を録音し、ファイルに保存するには MediaRecorder を使う

    - 音声の配信には RTMP などのストリーミングプロトコルとライブラリを使う 

    - ライブラリには、 rtmp-rtsp-stream-client-java が使える

    - 実際の音声配信アプリは、配信クライアントだけではなくサーバー側や受信クライアント側も適
    切に実装する必要があるので広範な知識が必要

    56

    View Slide

  57. We are hiring!
    エンジニア積極的に募集中です
    https://corp.stand.fm/recruit
    詳細はこちら ● CTO候補
    ● VPoE候補
    ● クライアントエンジニア
    ● バックエンドエンジニア
    ● 機械学習エンジニア
    ● 配信基盤エンジニア
    ● QAエンジニア
    ● エンジニアリングマネージャー
    ● UI/UXデザイナー
    積極募集しているプロダクト開発メンバー
    57

    View Slide

  58. 58

    View Slide