4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Linkbal(リンクバル)Advent Calendar 2023

Day 22

Agoraで話すときに動くシンプルな音波を作成する

Posted at

Agoraについて

Agoraは、ビデオ、音声、対話的な放送ソリューションのAPIを提供するリアルタイムコミュニケーションプラットフォームです。

image.png

準備

1. 新しいプロジェクトを作成

flutter create agora_sample

2. Agora SDKをプロジェクトに追加

flutter pub add agora_rtc_engine
flutter pub add permission_handler

3. シンプルな音波を表示するためのパッケージを追加

flutter pub add loading_indicator

参考:https://pub.dev/packages/loading_indicator

4. Agora.io で開発者アカウントにサインアップする

準備は完了です。はじめましょう!

実装

Agoraプロジェクトを設定

Agora.ioのプロジェクト管理画面でプロジェクトを選択して、設定画面に移動します。

image.png

Generate temp RTC Tokenリンクをクリックしましょう。

image.png

ここで、チャネル名を入力して、トークンを生成しましょう。このトークンは後でチャネルに参加するために使用されます。

音波Widgetを作成

sound_wave.dart
class SoundWave extends StatelessWidget {
  const SoundWave({super.key, required this.hasVoiceCome});

  final bool hasVoiceCome;

  @override
  Widget build(BuildContext context) {
    List<Color> soundWaveColors = const [
      Color(0xFFFF5500),
      Color(0xFFFF6600),
      Color(0xFFFF7700),
      Color(0xFFFF8800),
      Color(0xFFFF9900),
      Color(0xFFFAA400),
      Color(0xFFFBAD00),
      Color(0xFFFCB400),
      Color(0xFFFDBA00),
      Color(0xFFFECC00),
    ];

    return Row(
      children: List.generate(
        2,
        (index) {
          return Padding(
            padding: const EdgeInsets.only(right: 8.0),
            child: LoadingIndicator(
              indicatorType: Indicator.lineScalePulseOutRapid,
              colors: soundWaveColors.sublist(index * 5, (index + 1) * 5),
              pause: !hasVoiceCome,
            ),
          );
        },
      ),
    );
  }
}

通話画面を作成

call_screen.dart
class CallScreen extends StatefulWidget {
  const CallScreen({super.key});

  @override
  State<CallScreen> createState() => _CallScreenState();
}

class _CallScreenState extends State<CallScreen> {
  late RtcEngine _agoraEngine;

  bool _muted = false;
  bool _speakerOff = false;
  bool _hasVoiceCome = false;
  int? _remoteUid;
  bool _isJoined = false;

  final appId = '';        // コピーしたアプリID
  final uid = 0;           // 参加するユーザーID
  final channelId = '';    // 設定したチャネルID(test)
  final token = '';        // 生成されたトークン

  @override
  void initState() {
    super.initState();
    setupVoiceSDKEngine().onError((error, stackTrace) => {
          debugPrint(error.toString()),
          debugPrint(stackTrace.toString()),
        });
  }

  @override
  void dispose() {
    super.dispose();
    _agoraEngine.leaveChannel();
  }

  void _onToggleMute() {
    setState(() {
      _muted = !_muted;
    });
    _agoraEngine.muteLocalAudioStream(_muted);
  }

  void _onToggerSpeaker() {
    setState(() {
      _speakerOff = !_speakerOff;
    });
    _agoraEngine.setEnableSpeakerphone(!_speakerOff);
  }


  void _onJoin() async {
    ChannelMediaOptions options = const ChannelMediaOptions(
      clientRoleType: ClientRoleType.clientRoleBroadcaster,
      channelProfile: ChannelProfileType.channelProfileCommunication,
    );

    try {
      await _agoraEngine.joinChannel(
        token: token,
        channelId: channelId,
        options: options,
        uid: uid,
      );
    } catch (e) {
      debugPrint(e.toString());
    }
  }
  
  void _onLeave() {
    _agoraEngine.leaveChannel();
  }

  Future<void> setupVoiceSDKEngine() async {
    try {
      // Retrieve or request microphone permission
      await [Permission.microphone].request();

      // Create an instance of the Agora engine
      _agoraEngine = createAgoraRtcEngine();
      await _agoraEngine.initialize(RtcEngineContext(appId: appId));

      // Enables the audioVolumeIndication
      await _agoraEngine.enableAudioVolumeIndication(
          interval: 250, smooth: 8, reportVad: true);

      // Register the event handler
      _agoraEngine.registerEventHandler(
        RtcEngineEventHandler(
          onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
            setState(() {
              _isJoined = true;
            });
          },
          onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
            setState(() {
              _remoteUid = remoteUid;
            });
          },
          onUserOffline: (RtcConnection connection, int remoteUid,
              UserOfflineReasonType reason) {
            setState(() {
              _remoteUid = null;
            });
          },
          onAudioVolumeIndication: (
            RtcConnection connection,
            List<AudioVolumeInfo> speakers,
            int speakerNumber,
            int totalVolume,
          ) {
            setState(() {
              _hasVoiceCome = speakers.any((speaker) => speaker.vad == 1);
            });
          },
          onError: (err, msg) => {
            debugPrint(err.toString()),
            debugPrint(msg.toString()),
          },
        ),
      );
    } catch (err) {
      debugPrint(err.toString());
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Get started with Voice Calling'),
      ),
      body: SafeArea(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
          children: [
            SizedBox(height: 40, child: Center(child: _status())),
            Column(
              children: [
                SizedBox(
                  height: 60.0,
                  child: SoundWave(hasVoiceCome: _hasVoiceCome),
                ),
                const SizedBox(height: 40.0),
                BottomActionBar(
                  muted: _muted,
                  speakerOff: _speakerOff,
                  onToggleMute: _onToggleMute,
                  onToggleSpeaker: _onToggerSpeaker,
                  onJoin: _onJoin,
                  onLeave: _onLeave,
                ),
              ],
            )
          ],
        ),
      ),
    );
  }

  Widget _status() {
    String statusText;

    if (!_isJoined) {
      statusText = 'Join a channel';
    } else if (_remoteUid == null) {
      statusText = 'Waiting for a remote user to join...';
    } else {
      statusText = 'Connected to remote user, uid:$_remoteUid';
    }

    return Text(
      statusText,
    );
  }
}

  • enableAudioVolumeIndication(interval:, smooth:, reportVad:): ユーザーの音量インジケーターを報告するようにするために使用されます。
    • smoothはオーディオボリュームインジケーターの感度です。 値の範囲は 0 ~ 10 です。
    • reportVad: ローカルユーザーの音声アクティビティの検出を有効にします。 有効にすると、onAudioVolumeIndicationコールバックのvadパラメータがローカルユーザーの音声アクティビティ ステータスを報告します。
  • onAudioVolumeIndication: _hasVoiceCome = speakers.any((speaker) => speaker.vad == 1): 少なくとも1人の話者が話しているかどうかを確認します。

実装が完了しました。試してみましょう!

結果

会話に参加するには、上記のサイトにアクセスし、以前に設定したチャネルに関する情報を入力します。

そして結果を体験しましょう!:raised_hands:

終わり。

4
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?