使用 Agora Flutter SDK 构建速配应用

约会软件在根据用户的兴趣、爱好、品味等进行匹配方面做得十分出色。但要保持用户的参与度,就不仅仅是为他们找到一个匹配对象这样简单了。

互动是让用户参与的关键。如果 “少即是多”,那么更少的文字意味着更多的互动。为了了解某人,你需要的不仅仅是发短信和看他们的照片(假设他们事实上使用的是自己的照片)。

在本教程中,我将向您介绍一个案例,在约会软件中增加视频通话的功能,以提高用户参与度—从而帮助用户找到一个理想的对象。

先决条件

如果您是 Flutter 新手,请从Flutter文档安装 Flutter SDK 。

构建用户认证页面

首先,我创建了一个简单的登录/注册表单,它需要输入以下两个部分:邮箱 ID 和密码。您可以根据需要自定义此界面。

2
登录/注册页面

在这里,我使用 Firebase 进行用户身份验证并保存用户资料。用户资料包括:

  • 邮箱ID
  • 用户账号
  • 兴趣列表

根据用户的兴趣进行匹配

举个小例子,其中有六个类别,人们可以从中选择完成他们的个人资料。然后,这些兴趣会与数据库中的所有用户进行匹配。如果两个用户有共同的兴趣,他们会被添加到同一个列表中。

3
选择您的兴趣

为了给一个用户找到另一个合适的用户,我将他们的兴趣与其他用户的兴趣进行映射。兴趣重叠的用户被匹配并添加到一个matchedUsers 列表中,其中包括他们的用户账号和可用性状态。

void matchUserInterests() async {
  int count = 0;
  var user = FirebaseAuth.instance.currentUser;
  var db = await FirebaseFirestore.instance.collection('user').get();
  int index = db.docs.indexWhere((element) => element.data()['email'] == user.email);
  await FirebaseFirestore.instance.collection('user').get().then((val) {
    for (var i = 0; i < val.docs.length; i++) {
      for (var j = 0; j < val.docs[i].data()['interests'].length; j++) {
        if (i != index) {
          if (val.docs[i].data()['interests'].contains(val.docs[index].data()['interests'][j])) {
            count++;
          }
        }
      }
      if (count >= 1) {
        value = value.copyWith(matchedUsers: [...value.matchedUsers, AgoraUser(uid: val.docs[i].data()['uid'], isAvailable: true)]);
      }
      count = 0;
    }
  });
}

摄像头和麦克风测试

在与匹配用户进行视频通话之前,我已经添加了一个屏幕来测试您的设备摄像头和麦克风。
4
摄像头和麦克风测试

为了测试相机,我们先初始化 Agora RtcEngine,然后使用startPreview() 模型。


void startVideoPreview() async {
  await value.engine.enableVideo();
  await value.engine.startPreview();
}

为了测试麦克风,我使用了另一个插件mic_stream ,它返回麦克风接收到的音频电平。利用它,我们制作音频条来表示麦克风的状态。


void volumeListener() async {
  // Init a new Stream
  Stream<List<int>> stream = await MicStream.microphone(sampleRate: 44100);
  
  // Start listening to the stream
  listener = stream.listen((samples) {
    double tempVolume1 = 0;
    double tempVolume2 = 0;
    double tempVolume3 = 0;
    setState(() {
      tempVolume1 = volumeHeight1;
      volumeHeight1 = samples[0].toDouble();
      tempVolume2 = volumeHeight2;
      volumeHeight2 = tempVolume1;
      tempVolume3 = volumeHeight3;
      volumeHeight3 = tempVolume2;
      volumeHeight4 = tempVolume3;
    });
  });
}

呼叫页面

对于呼叫页面,我采用了类似于Tinder的用户界面,用户可以根据他们是否喜欢屏幕上的人,向右或向左滑动。

所以我们将首先为本地和远程用户设置视频视图。在这里,我们采用了一个简单的用户界面,本地用户的屏幕被堆叠在远程用户的视图之上。

/// Helper function to get list of native views
  List<Widget> _getRenderViews() {
    final List<StatefulWidget> list = [];
    list.add(rtc_local_view.SurfaceView());
    widget.controller.value.matchedUsers.forEach((AgoraUser agoraUser) {
      if (agoraUser.isAvailable) {
        list.add(rtc_remote_view.SurfaceView(uid: agoraUser.uid));
        agoraUser.copyWith(isAvailable: false);
      }
    });
    return list;
  }

  /// Video view wrapper
  Widget _videoView(view) {
    return Expanded(child: Container(child: view));
  }

  Widget _localVideoView(view) {
    return Container(
      height: 150,
      width: 120,
      child: view,
    );
  }

视频查看

两个用户之间的视频通话类似 Tinder UI 的效果。因此,只要有人向右或向左滑动另一个用户,他们就有可能加入与另一个匹配用户的通话。对于类似 Tinder 的用户界面,我们使用了一个名为tcard的插件。


List<Widget> tinderCards() {
  final List<Widget> tcards = List.generate(widget.controller.value.matchedUsers.length, (index) {
  final views = _getRenderViews();
  return Container(
    child: Stack(
      children: <Widget>[
        _videoView(rtc_remote_view.SurfaceView(uid: widget.controller.value.matchedUsers[index].uid)),
        Align(alignment: Alignment(0.95, -0.95), child: _localVideoView(views[0])),
      ],
    ));
  });
  return tcards;
}

@override
  Widget build(BuildContext context) {
    return Scaffold(
      body: TCard(
        controller: _controller,
        lockYAxis: true,
        cards: tinderCards(),
        onBack: (index, info) async {
          await widget.controller.leaveVideoChannel(index);
        },
        onForward: (index, info) async {
          info.direction == SwipDirection.Left
            ? await widget.controller.leaveVideoChannel(index)
            : await widget.controller.onForwardAction(index);
        },
      )
    );
  }

Agora视频通话卡

当用户向右或向左滑动时会发生以下情况:

  • 向左滑动:用户离开当前对话,然后加入一个新的对话,与另一个匹配的人互动。

Future<void> leaveVideoChannel(int index) async {
  await value.engine.leaveChannel();
  int remoteUserIndex = value.allUsers.indexWhere((element) => element.uid == value.matchedUsers[0].uid);
  
  List<AgoraUser> tempList = value.allUsers;
  tempList[remoteUserIndex] = tempList[remoteUserIndex].copyWith(isAvailable: true);
  value = value.copyWith(allUsers: tempList);

  List<AgoraUser> tempList2 = value.matchedUsers;
  tempList2.removeAt(0);
  value = value.copyWith(matchedUsers: tempList2);
  
  joinVideoChannel(0);
}

向左滑动

  • 向右滑动: 用户离开当前频道,并将其用户账号保存在喜欢的用户列表中。用户账号列表的存储是为了让两个互相喜欢的人在划走这个用户后仍然可以互相发送信息。然后,用户加入一个随机的对话,与另一个匹配的人一起。

Future<void> onForwardAction(int index) async {
    await value.engine.leaveChannel();

    int remoteUid = value.matchedUsers[0].uid;
    value = value.copyWith(likedUsers: [...value.likedUsers, remoteUid]);
    value.likedUsers.forEach((element) {
      print('Liked User: $element');
    });

    List<AgoraUser> tempList = value.matchedUsers;
    tempList.removeAt(0);
    value = value.copyWith(matchedUsers: tempList);

    joinVideoChannel(0);
  }

向右滑动

消息页面

对于所有被向右滑动的用户,我们维护一个 likedUsers 列表,该列表有这些用户的账号。用户账号帮助我们与匹配成功的用户加入一个RTM频道,这样我们就可以在通话结束后向他们发送消息。

要设置 RTM SDK,请执行以下步骤:

  • 初始化SDK。在这一步,我们创建 RtmChannelRtmClient 对象,然后用App ID、令牌和通道名称来初始化它们。
void createClient() async {
    String username;
    await FirebaseFirestore.instance.collection('user').get().then((value) {
      username = value.docs[3].data()['email'];
    });
    value = value.copyWith(client: await AgoraRtmClient.createInstance(appId));
    value.client.onMessageReceived = (AgoraRtmMessage message, String peerId) {
      _logPeer(message.text);
    };
    value.client.onConnectionStateChanged = (int state, int reason) {
      print('Connection state changed: ' + state.toString() + ', reason: ' + reason.toString());
      if (state == 5) {
        value.client.logout();
      }
    };
    toggleLogin(username);
  }

初始化 RTM SDK

  • 用一个用户名登录。这里我们使用存储在Firebase中的电子邮件作为用户名。因为它是我们模型中的一个主键,这将确保每个用户的用户名都是唯一的。
void toggleLogin(String username) async {
    try {
      await value.client.login(null, username);
      print('Login success: ' + username);
    } catch (errorCode) {
      print('Login error: ' + errorCode.toString());
    }
  }

RTM登录

  • 加入一个对话。一旦我们登录了RTM,我们就可以加入一个对话。这需要一个独特的名称,该名称由对话中的两个用户使用。在这个例子中,我使用本地和远程用户的账号来创建一个唯一的对话名称。

Future<void> toggleJoinChannel(int remoteChannelName) async {
   String channelName;
   if (remoteChannelName < value.localUid) {
     channelName = value.localUid.toString() + remoteChannelName.toString();
   } else {
     channelName = remoteChannelName.toString() + value.localUid.toString();
   }
   try {
     value = value.copyWith(channel: await _createChannel(channelName));
     await value.channel.join();
     print('Join channel success.');
   } catch (errorCode) {
     print('Join channel error: ' + errorCode.toString());
   }
}

Future<AgoraRtmChannel> _createChannel(String name) async {
   AgoraRtmChannel channel = await value.client.createChannel(name);
   channel.onMemberJoined = (AgoraRtmMember member) {
     print("Member joined: " + member.userId + ', channel: ' + member.channelId);
   };
   channel.onMemberLeft = (AgoraRtmMember member) {
     print("Member left: " + member.userId + ', channel: ' + member.channelId);
   };
   channel.onMessageReceived = (AgoraRtmMessage message, AgoraRtmMember member) {
     print('Chanel Message Received : ' + message.text);
     _logPeer(message.text);
   };
   return channel;
 }

加入 RTM 对话

  • 发送一个消息。一旦你加入了一个对话,你就可以调用sendMessage 方法来向一个对话发送消息。
void toggleSendChannelMessage(String text) async {
  if (text.isEmpty) {
    print('Please input text to send.');
    return;
  }
  try {
    await value.channel.sendMessage(AgoraRtmMessage.fromText(text));
    _log(text);
  } catch (errorCode) {
    print('Send channel message error: ' + errorCode.toString());
  }
}

发送 RTM 消息

  • 离开当前对话。当用户退出聊天页面时,离开当前对话很重要,这样用户可以根据需要加入任何其他对话。

void leaveRtmChannel() async {
  await value.channel.leave();
  value = value.copyWith(messages: []);
}

离开 RTM 对话

测试

在建立应用程序之前,请确保您已执行以下操作:

  • 您添加了您的App Id和token来初始化您的Agora SDK
  • 您将您的应用与 firebase 相关联以注册所有用户
  • 构建应用程序后,您应该会看到如下内容:
    88

结论

RTC和RTM的结合可以创造很多可能性。在本教程中,我们看到了一个最大的参与应用:快速约会,两个随机的人根据他们的兴趣被匹配,并被添加到一个RTC对话。 你可以在这里找到这个应用程序的完整代码。

其他资源

如需了解有关 Agora Flutter SDK 和其他案例的更多信息,请参阅此处的开发人员指南。

您还可以在声网Agora Flutter API参考上述功能的完整文档以及更多内容。

我邀请您加入Agora.io 开发者 Slack 社区
原文作者:Meherdeep Thakur
原文链接:https://www.agora.io/en/blog/build-a-speed-dating-app-using-the-agora-flutter-sdk/

推荐阅读
作者信息
AgoraTechnicalTeam
TA 暂未填写个人简介
文章
175
相关专栏
SDK 教程
62 文章
本专栏仅用于分享音视频相关的技术文章,与其他开发者和 Agora 研发团队交流、分享行业前沿技术、资讯。发帖前,请参考「社区发帖指南」,方便您更好的展示所发表的文章和内容。