Skip to content

Commit

Permalink
feat: Chat send/receive implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudwebrtc committed Oct 9, 2024
1 parent 9278aa0 commit 2e4804c
Show file tree
Hide file tree
Showing 28 changed files with 706 additions and 469 deletions.
3 changes: 2 additions & 1 deletion example/lib/src/prejoin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class Prejoin extends StatelessWidget {

String _name = '';

String _roomName = '';
String _roomName = 'livekit-room';

void onTextNameChanged(String name) async {
_name = name;
Expand Down Expand Up @@ -61,6 +61,7 @@ class Prejoin extends StatelessWidget {
child: TextInput(
onTextChanged: onTextRoomNameChanged,
hintText: 'Enter room name',
text: _roomName,
),
),
),
Expand Down
10 changes: 8 additions & 2 deletions example/lib/src/ui/text_input.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import 'package:flutter/material.dart';

class TextInput extends StatelessWidget {
TextInput({super.key, required this.onTextChanged, required this.hintText});
final TextEditingController _textController = TextEditingController();
TextInput(
{super.key,
required this.onTextChanged,
required this.hintText,
String? text})
: _textController = TextEditingController(text: text ?? '');

final TextEditingController _textController;

final Function(String) onTextChanged;

Expand Down
88 changes: 88 additions & 0 deletions lib/src/context/chat.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import 'dart:convert';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_components/src/ui/debug/logger.dart';

// topic: lk-chat-topic
class ChatMessage {
final String message;
final int timestamp;
final String id;
final bool sender;

final Participant? participant;

ChatMessage({
required this.message,
required this.timestamp,
required this.id,
this.participant,
required this.sender,
});

Map<String, dynamic> toMap() {
return {
'message': message,
'timestamp': timestamp,
'id': id,
};
}

String toJson() => const JsonEncoder().convert(toMap());

factory ChatMessage.fromJsonString(String source, Participant? participant) =>
ChatMessage.fromMap(const JsonDecoder().convert(source), participant);

factory ChatMessage.fromMap(
Map<String, dynamic> map, Participant? participant) {
return ChatMessage(
message: map['message'],
timestamp: map['timestamp'],
id: map['id'],
participant: participant,
sender: false,
);
}
}

mixin ChatContextMixin on ChangeNotifier {
final List<ChatMessage> _messages = [];

List<ChatMessage> get messages => _messages;

LocalParticipant? _localParticipant;

void chatContextSetup(
EventsListener<RoomEvent> listener, LocalParticipant localParticipant) {
listener.on<DataReceivedEvent>((event) {
var str = utf8.decode(Uint8List.fromList(event.data));
Debug.log('DataReceivedEvent $str');
addMessageFromMap(str, event.participant);
});
_localParticipant = localParticipant;
}

void sendMessage(String message) {
final msg = ChatMessage(
message: message,
timestamp: DateTime.now().millisecondsSinceEpoch,
id: DateTime.now().millisecondsSinceEpoch.toString(),
sender: true,
);
addMessage(msg);
_localParticipant?.publishData(const Utf8Encoder().convert(msg.toJson()),
topic: 'lk-chat-topic');
}

void addMessage(ChatMessage message) {
_messages.add(message);
notifyListeners();
}

void addMessageFromMap(String buf, Participant? participant) {
final message = ChatMessage.fromJsonString(buf, participant);
addMessage(message);
}
}
66 changes: 19 additions & 47 deletions lib/src/context/media_device.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,24 @@ import 'dart:async';
import 'package:flutter/foundation.dart';

import 'package:livekit_client/livekit_client.dart';
import 'package:livekit_components/livekit_components.dart';

class MediaDevicesContext extends ChangeNotifier {
MediaDevicesContext({required this.roomContext})
: _audioInputs = [],
_audioOutputs = [],
_videoInputs = [] {
_subscription = Hardware.instance.onDeviceChange.stream
.listen((List<MediaDevice> devices) {
_loadDevices(devices);
});
Hardware.instance.enumerateDevices().then(_loadDevices);
}

RoomContext roomContext;

mixin MediaDeviceContextMixin on ChangeNotifier {
CameraPosition position = CameraPosition.front;

List<MediaDevice>? _audioInputs;
List<MediaDevice>? _audioOutputs;
List<MediaDevice>? _videoInputs;
StreamSubscription? _subscription;

List<MediaDevice>? get audioInputs => _audioInputs;

List<MediaDevice>? get audioOutputs => _audioOutputs;

List<MediaDevice>? get videoInputs => _videoInputs;

String? selectedVideoInputDeviceId;

String? selectedAudioInputDeviceId;

@override
void dispose() {
_subscription?.cancel();
}

void _loadDevices(List<MediaDevice> devices) async {
Future<void> loadDevices() async {
final devices = await Hardware.instance.enumerateDevices();
_audioInputs = devices.where((d) => d.kind == 'audioinput').toList();
_audioOutputs = devices.where((d) => d.kind == 'audiooutput').toList();
_videoInputs = devices.where((d) => d.kind == 'videoinput').toList();
Expand Down Expand Up @@ -96,34 +75,27 @@ class MediaDevicesContext extends ChangeNotifier {

void selectAudioInput(MediaDevice device) async {
selectedAudioInputDeviceId = device.deviceId;
if (roomContext.connected) {
await Hardware.instance.selectAudioInput(device);
} else {
await _localAudioTrack?.dispose();
notifyListeners();
_localAudioTrack = await LocalAudioTrack.create(
AudioCaptureOptions(
deviceId: device.deviceId,
),
);
}
await _localAudioTrack?.dispose();
notifyListeners();
_localAudioTrack = await LocalAudioTrack.create(
AudioCaptureOptions(
deviceId: device.deviceId,
),
);

notifyListeners();
}

Future<void> selectVideoInput(MediaDevice device) async {
selectedVideoInputDeviceId = device.deviceId;
if (roomContext.connected) {
await _localVideoTrack?.switchCamera(device.deviceId);
} else {
await _localVideoTrack?.dispose();
notifyListeners();
_localVideoTrack = await LocalVideoTrack.createCameraTrack(
CameraCaptureOptions(
cameraPosition: position,
deviceId: device.deviceId,
),
);
}
await _localVideoTrack?.dispose();
notifyListeners();
_localVideoTrack = await LocalVideoTrack.createCameraTrack(
CameraCaptureOptions(
cameraPosition: position,
deviceId: device.deviceId,
),
);
notifyListeners();
}
}
4 changes: 3 additions & 1 deletion lib/src/context/participant.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import 'package:flutter/foundation.dart';

import 'package:livekit_client/livekit_client.dart';

import '../ui/debug/logger.dart';

class ParticipantContext extends ChangeNotifier {
ParticipantContext(this._participant)
: _listener = _participant.createListener() {
Expand Down Expand Up @@ -45,7 +47,7 @@ class ParticipantContext extends ChangeNotifier {
})
..on<TranscriptionEvent>((e) {
for (var seg in e.segments) {
print('Transcription: ${seg.text} ${seg.isFinal}');
Debug.log('Transcription: ${seg.text} ${seg.isFinal}');
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion lib/src/context/permission.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import 'package:flutter/foundation.dart';

class PermissionContext extends ChangeNotifier {
mixin PermissionContext on ChangeNotifier {
bool _isGranted = false;
bool get isGranted => _isGranted;

Expand Down
Loading

0 comments on commit 2e4804c

Please sign in to comment.