livekit_service.dart 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:dart_jsonwebtoken/dart_jsonwebtoken.dart';
  4. import 'package:flutter/foundation.dart';
  5. import 'package:livekit_client/livekit_client.dart';
  6. import 'package:asr_client/models/websocket_message.dart';
  7. /// Wraps LiveKit Room connection and relays transcription data messages.
  8. abstract interface class LiveKitService {
  9. Stream<ServerMessage> get messageStream;
  10. bool get isConnected;
  11. Future<void> connect({required String url, required String room, required String identity});
  12. Future<void> disconnect();
  13. }
  14. final class LiveKitServiceImpl implements LiveKitService {
  15. LiveKitServiceImpl({Room? room}) : _room = room ?? Room();
  16. final Room _room;
  17. final _messageController = StreamController<ServerMessage>.broadcast();
  18. var _connected = false;
  19. EventsListener<RoomEvent>? _listener;
  20. @override
  21. Stream<ServerMessage> get messageStream => _messageController.stream;
  22. @override
  23. bool get isConnected => _connected;
  24. @override
  25. Future<void> connect({
  26. required String url,
  27. required String room,
  28. required String identity,
  29. }) async {
  30. if (_connected) return;
  31. final token = _makeToken(room, identity);
  32. debugPrint('[lk] connecting to $url room=$room');
  33. // Listen for room events (including connection failures)
  34. _listener = _room.createListener()
  35. ..on<DataReceivedEvent>((e) {
  36. debugPrint('[lk] data received topic=${e.topic} bytes=${e.data.length}');
  37. if (e.topic == 'transcription') {
  38. try {
  39. final json = jsonDecode(utf8.decode(e.data)) as Map<String, dynamic>;
  40. final message = ServerMessage.fromJson(json);
  41. _messageController.add(message);
  42. } catch (ex) {
  43. debugPrint('[lk] bad data: $ex');
  44. }
  45. }
  46. })
  47. ..on<RoomDisconnectedEvent>((e) {
  48. debugPrint('[lk] disconnected: ${e.reason}');
  49. _connected = false;
  50. })
  51. ..on<RoomReconnectingEvent>((e) {
  52. debugPrint('[lk] reconnecting');
  53. })
  54. ..on<RoomReconnectedEvent>((e) {
  55. debugPrint('[lk] reconnected');
  56. });
  57. try {
  58. await _room.connect(
  59. '$url',
  60. token,
  61. );
  62. } catch (e, st) {
  63. debugPrint('[lk] connect failed: $e');
  64. debugPrint('[lk] stack: $st');
  65. rethrow;
  66. }
  67. debugPrint('[lk] connected, publishing mic...');
  68. try {
  69. await _room.localParticipant!.setMicrophoneEnabled(true);
  70. debugPrint('[lk] mic published');
  71. } catch (e, st) {
  72. debugPrint('[lk] mic FAILED: $e\n$st');
  73. }
  74. _connected = true;
  75. _messageController.add(const ConnectedMessage(model: {}));
  76. }
  77. @override
  78. Future<void> disconnect() async {
  79. if (!_connected) return;
  80. _connected = false;
  81. _listener?.dispose();
  82. _listener = null;
  83. await _room.disconnect();
  84. }
  85. String _makeToken(String room, String identity) {
  86. // Dev token: for localhost LiveKit Server with key "devkey"
  87. // In production, tokens should be generated server-side.
  88. const key = 'devkey';
  89. const secret = 'secretsecretsecretsecretsecret12';
  90. final jwt = JWT(
  91. {
  92. 'iss': key,
  93. 'sub': identity,
  94. 'name': identity,
  95. 'nbf': DateTime.now()
  96. .subtract(const Duration(minutes: 1))
  97. .millisecondsSinceEpoch ~/
  98. 1000,
  99. 'exp': DateTime.now()
  100. .add(const Duration(hours: 6))
  101. .millisecondsSinceEpoch ~/
  102. 1000,
  103. 'video': {
  104. 'roomJoin': true,
  105. 'room': room,
  106. 'canPublish': true,
  107. 'canSubscribe': true,
  108. 'canPublishData': true,
  109. },
  110. },
  111. );
  112. return jwt.sign(SecretKey(secret));
  113. }
  114. }