Skip to content

Commit

Permalink
Initial version is 1.0.0.
Browse files Browse the repository at this point in the history
  • Loading branch information
cloudwebrtc committed Oct 25, 2024
1 parent c338c98 commit 18c1a67
Show file tree
Hide file tree
Showing 14 changed files with 249 additions and 133 deletions.
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
## 0.0.1
# Changelog

* TODO: Describe initial release.
## 1.0.0 (2024-10-27)

* Initial release
33 changes: 15 additions & 18 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,18 @@ class MyHomePage extends StatelessWidget {
(deviceScreenType == DeviceScreenType.mobile &&
roomCtx.isChatEnabled)
? Expanded(
child: Padding(
padding: const EdgeInsets.only(top: 50),
child: ChatBuilder(
builder:
(context, enabled, chatCtx, messages) {
return ChatWidget(
messages: messages,
onSend: (message) =>
chatCtx.sendMessage(message),
onClose: () {
chatCtx.disableChat();
},
);
},
),
child: ChatBuilder(
builder:
(context, enabled, chatCtx, messages) {
return ChatWidget(
messages: messages,
onSend: (message) =>
chatCtx.sendMessage(message),
onClose: () {
chatCtx.disableChat();
},
);
},
),
)
: Expanded(
Expand All @@ -119,9 +116,9 @@ class MyHomePage extends StatelessWidget {

/// layout builder
layoutBuilder:
roomCtx.focusedTrackSid != null
? const CarouselLayoutBuilder()
: const GridLayoutBuilder(),
roomCtx.isPinnedTracksEmpty
? const GridLayoutBuilder()
: const CarouselLayoutBuilder(),

/// participant builder
participantBuilder: (context) {
Expand Down
45 changes: 25 additions & 20 deletions lib/livekit_components.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,19 @@ export 'src/debug/logger.dart';
export 'src/ui/builder/camera_preview.dart';
export 'src/ui/builder/room/chat_toggle.dart';
export 'src/ui/builder/room/chat.dart';
export 'src/ui/builder/track/connection_quality_indicator.dart';
export 'src/ui/builder/room/disconnect_button.dart';
export 'src/ui/builder/track/e2e_encryption_indicator.dart';
export 'src/ui/builder/track/is_speaking_indicator.dart';
export 'src/ui/builder/room/join_button.dart';
export 'src/ui/builder/room/media_device_select_button.dart';
export 'src/ui/builder/room/media_device.dart';
export 'src/ui/builder/room/room_connection_state.dart';
export 'src/ui/builder/room/room_metadata.dart';
export 'src/ui/builder/room/room_name.dart';
export 'src/ui/builder/room/room_participants.dart';
export 'src/ui/builder/room/room.dart';
export 'src/ui/builder/room/screenshare_toggle.dart';
export 'src/ui/builder/room/room_active_recording_indicator.dart';

export 'src/ui/builder/participant/participant_track.dart';
export 'src/ui/builder/participant/participant_attributes.dart';
export 'src/ui/builder/participant/participant_loop.dart';
export 'src/ui/builder/participant/participant_metadata.dart';
Expand All @@ -25,33 +31,32 @@ export 'src/ui/builder/participant/participant_name.dart';
export 'src/ui/builder/participant/participant_kind.dart';
export 'src/ui/builder/participant/participant_permissions.dart';
export 'src/ui/builder/participant/participant_transcription.dart';
export 'src/ui/builder/room/room_active_recording_indicator.dart';
export 'src/ui/builder/room/room_connection_state.dart';
export 'src/ui/builder/room/room_metadata.dart';
export 'src/ui/builder/room/room_name.dart';
export 'src/ui/builder/room/room_participants.dart';
export 'src/ui/builder/room/room.dart';
export 'src/ui/widgets/toast.dart';
export 'src/ui/builder/room/screenshare_toggle.dart';

export 'src/ui/widgets/camera_preview.dart';
export 'src/ui/builder/track/connection_quality_indicator.dart';
export 'src/ui/builder/track/e2e_encryption_indicator.dart';
export 'src/ui/builder/track/is_speaking_indicator.dart';

export 'src/ui/widgets/participant/connection_quality_indicator.dart';
export 'src/ui/widgets/participant/participant_status_bar.dart';
export 'src/ui/widgets/participant/participant_tile_widget.dart';
export 'src/ui/widgets/participant/is_speaking_indicator.dart';

export 'src/ui/widgets/room/camera_select_button.dart';
export 'src/ui/widgets/room/chat_toggle.dart';
export 'src/ui/widgets/room/chat_widget.dart';
export 'src/ui/widgets/participant/connection_quality_indicator.dart';
export 'src/ui/widgets/room/media_device_select_button.dart';
export 'src/ui/widgets/room/microphone_select_button.dart';
export 'src/ui/widgets/room/control_bar.dart';
export 'src/ui/widgets/room/disconnect_button.dart';
export 'src/ui/widgets/room/screenshare_toggle.dart';

export 'src/ui/widgets/track/focus_toggle.dart';
export 'src/ui/widgets/participant/is_speaking_indicator.dart';
export 'src/ui/widgets/room/media_device_select_button.dart';
export 'src/ui/widgets/room/microphone_select_button.dart';
export 'src/ui/widgets/track/no_track_widget.dart';
export 'src/ui/widgets/participant/participant_status_bar.dart';
export 'src/ui/widgets/participant/participant_tile_widget.dart';
export 'src/ui/widgets/track/video_track_widget.dart';
export 'src/ui/widgets/room/screenshare_toggle.dart';
export 'src/ui/widgets/theme.dart';
export 'src/ui/widgets/track/track_stats_widget.dart';
export 'src/ui/widgets/theme.dart';
export 'src/ui/widgets/toast.dart';
export 'src/ui/widgets/camera_preview.dart';

export 'src/ui/layout/layouts.dart';
export 'src/ui/layout/grid_layout.dart';
Expand Down
38 changes: 26 additions & 12 deletions lib/src/context/room_context.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class RoomContext extends ChangeNotifier with ChatContextMixin {
_roomMetadata = event.room.metadata;
_activeRecording = event.room.isRecording;
_roomName = event.room.name;
_sortParticipants();
_buildParticipants();
notifyListeners();
})
..on<RoomDisconnectedEvent>((event) {
Expand Down Expand Up @@ -86,7 +86,7 @@ class RoomContext extends ChangeNotifier with ChatContextMixin {
..on<ParticipantConnectedEvent>((event) {
Debug.event(
'RoomContext: ParticipantConnectedEvent participant = ${event.participant.identity}');
_sortParticipants();
_buildParticipants();
})
..on<ParticipantDisconnectedEvent>((event) {
Debug.event(
Expand All @@ -97,21 +97,21 @@ class RoomContext extends ChangeNotifier with ChatContextMixin {
})
..on<TrackPublishedEvent>((event) {
Debug.event('ParticipantContext: TrackPublishedEvent');
_sortParticipants();
_buildParticipants();
})
..on<TrackUnpublishedEvent>((event) {
Debug.event('ParticipantContext: TrackUnpublishedEvent');
_sortParticipants();
_buildParticipants();
})
..on<LocalTrackPublishedEvent>((event) {
Debug.event(
'RoomContext: LocalTrackPublishedEvent track = ${event.publication.sid}');
_sortParticipants();
_buildParticipants();
})
..on<LocalTrackUnpublishedEvent>((event) {
Debug.event(
'RoomContext: LocalTrackUnpublishedEvent track = ${event.publication.sid}');
_sortParticipants();
_buildParticipants();
});

if (connect && url != null && token != null) {
Expand Down Expand Up @@ -209,7 +209,7 @@ class RoomContext extends ChangeNotifier with ChatContextMixin {
/// Get the list of participants.
List<Participant> get participants => _participants;

void _sortParticipants() {
void _buildParticipants() {
_participants.clear();

if (!connected) {
Expand All @@ -224,14 +224,28 @@ class RoomContext extends ChangeNotifier with ChatContextMixin {
notifyListeners();
}

void setFocusedTrack(String? sid) {
_focusedTrackSid = sid;
Debug.log('Focused track: $sid');
void pinningTrack(String sid) {
_pinnedTrackSid.remove(sid);
_pinnedTrackSid.insert(0, sid);
Debug.event('Pinning track: $sid');
notifyListeners();
}

String? _focusedTrackSid;
String? get focusedTrackSid => _focusedTrackSid;
void unpinningTrack(String sid) {
_pinnedTrackSid.remove(sid);
Debug.event('Unpinning track: $sid');
notifyListeners();
}

void clearPinnedTracks() {
_pinnedTrackSid.clear();
notifyListeners();
}

final List<String> _pinnedTrackSid = [];
List<String> get pinnedTracks => _pinnedTrackSid;

bool get isPinnedTracksEmpty => _pinnedTrackSid.isEmpty;

LocalVideoTrack? _localVideoTrack;

Expand Down
127 changes: 75 additions & 52 deletions lib/src/ui/builder/participant/participant_loop.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,63 @@ import 'package:flutter/material.dart';
import 'package:livekit_client/livekit_client.dart';
import 'package:provider/provider.dart';

import '../../../context/participant_context.dart';
import '../../../context/room_context.dart';
import '../../../context/track_reference_context.dart';
import '../../../debug/logger.dart';
import '../../layout/grid_layout.dart';
import '../../layout/layouts.dart';
import 'participant_track.dart';

class ParticipantLoop extends StatelessWidget {
const ParticipantLoop({
super.key,
required this.participantBuilder,
this.layoutBuilder = const GridLayoutBuilder(),
this.sorting,
this.showAudioTracks = false,
this.showVideoTracks = true,
});

final WidgetBuilder participantBuilder;
final Map<Participant, TrackPublication?> Function(
Map<Participant, TrackPublication?> tracks)? sorting;
final ParticipantLayoutBuilder layoutBuilder;

final bool showAudioTracks;
final bool showVideoTracks;

Map<Participant, TrackPublication?> buildTracksMap(
bool audio, bool video, List<Participant> participants) {
final Map<Participant, TrackPublication?> trackMap = {};
int index = 0;
for (Participant participant in participants) {
Debug.log('=> participant ${participant.identity}, index: $index');
index++;
var tracks = participant.trackPublications.values;
for (var track in tracks) {
if (track.kind == TrackType.AUDIO && !audio) {
continue;
}
if (track.kind == TrackType.VIDEO && !video) {
continue;
}
trackMap[participant] = track;

Debug.log(
'=> ${track.source.toString()} track ${track.sid} for ${participant.identity}');
}

if (!audio && !tracks.any((t) => t.kind == TrackType.VIDEO) ||
!video && tracks.any((t) => t.kind == TrackType.AUDIO) ||
tracks.isEmpty) {
trackMap[participant] = null;

Debug.log('=> no tracks for ${participant.identity}');
}
}

return trackMap;
}

@override
Widget build(BuildContext context) {
return Consumer<RoomContext>(
Expand All @@ -35,65 +70,53 @@ class ParticipantLoop extends StatelessWidget {
shouldRebuild: (previous, next) => previous.length != next.length,
builder: (context, participants, child) {
var roomCtx = Provider.of<RoomContext>(context);
final Map<String, Widget> trackWidgets = {};
int index = 0;
for (Participant participant in participants) {
Debug.log(
'=> participant ${participant.identity}, index: $index');
index++;
var tracks = participant.trackPublications.values;
for (var track in tracks) {
if (track.kind == TrackType.AUDIO && !showAudioTracks) {
continue;
}
if (track.kind == TrackType.VIDEO && !showVideoTracks) {
continue;
}
trackWidgets[track.sid] = MultiProvider(
providers: [
ChangeNotifierProvider(
key: ValueKey(track.sid),
create: (_) => ParticipantContext(participant),
),
ChangeNotifierProvider(
key: ValueKey(track.sid),
create: (_) =>
TrackReferenceContext(participant, pub: track),
),
],
child: participantBuilder(context),
);

Debug.log(
'=> ${track.source.toString()} track ${track.sid} for ${participant.identity}');
}
Map<String, Widget> trackWidgets = {};

if (!showAudioTracks &&
!tracks.any((t) => t.kind == TrackType.VIDEO) ||
!showVideoTracks &&
tracks.any((t) => t.kind == TrackType.AUDIO) ||
tracks.isEmpty) {
trackWidgets[participant.identity] = ChangeNotifierProvider(
key: ValueKey(participant.identity),
create: (_) => ParticipantContext(participant),
child: participantBuilder(context),
);
var trackMap = buildTracksMap(
showAudioTracks, showVideoTracks, participants);

if (sorting != null) {
trackMap = sorting!(trackMap);
}

Debug.log('=> no tracks for ${participant.identity}');
for (var item in trackMap.entries) {
var participant = item.key;
var track = item.value;
if (track == null) {
trackWidgets[participant.identity] = ParticipantTrack(
participant: participant,
builder: (context) => participantBuilder(context),
);
} else {
trackWidgets[track.sid] = ParticipantTrack(
participant: participant,
track: track,
builder: (context) => participantBuilder(context),
);
}
}

var children = trackWidgets.values.toList();
List<Widget> pinned = [];

/// Move focused track to the front
var focused = trackWidgets.entries
.where((e) => e.key == roomCtx.focusedTrackSid)
.firstOrNull;
/// Move focused tracks to the pinned list
var focused = roomCtx.pinnedTracks
.map((e) {
if (trackWidgets.containsKey(e)) {
return trackWidgets[e]!;
}
return null;
})
.whereType<Widget>()
.toList();

if (focused != null) {
children.remove(focused.value);
children.insert(0, focused.value);
if (focused.isNotEmpty) {
children.removeWhere((e) => focused.contains(e));
pinned.addAll(focused);
}
return layoutBuilder.build(context, children);

return layoutBuilder.build(context, children, pinned);
});
},
);
Expand Down
Loading

0 comments on commit 18c1a67

Please sign in to comment.