Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions example/lib/pages/room.dart
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,7 @@ class _RoomPageState extends State<RoomPage> {
// Create video track
final localVideo = await LocalVideoTrack.createCameraTrack();
// Try to publish the video
await widget.room.localParticipant.publishVideoTrack(
localVideo,
// options: TrackPublishOptions(
// // simulcast: true,
// videoEncoding: VideoParameters.presetQVGA169.encoding,
// ),
);
await widget.room.localParticipant.publishVideoTrack(localVideo);

// Create mic track
final localAudio = await LocalAudioTrack.create();
Expand Down
116 changes: 98 additions & 18 deletions example/lib/widgets/participant.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:eva_icons_flutter/eva_icons_flutter.dart';
import 'package:flutter/material.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
import 'package:livekit_client/livekit_client.dart';
Expand All @@ -23,8 +24,8 @@ class ParticipantWidget extends StatefulWidget {

class _ParticipantWidgetState extends State<ParticipantWidget> {
//
TrackPublication? videoPub;
TrackPublication? audioPub;
TrackPublication? firstVideoPub;
TrackPublication? firstAudioPub;

@override
void initState() {
Expand All @@ -50,18 +51,14 @@ class _ParticipantWidgetState extends State<ParticipantWidget> {
// register for change so Flutter will re-build the widget upon change
void _onParticipantChanged() {
//
final firstAudio = widget.participant.audioTracks
.firstWhereOrNull((pub) => pub.subscribed);
final firstVideo = widget.participant.videoTracks
.firstWhereOrNull((pub) => !pub.isScreenShare && pub.subscribed);

if (firstVideo is RemoteTrackPublication) {
firstVideo.videoQuality = widget.quality;
}

setState(() {
audioPub = !(firstAudio?.muted ?? true) ? firstAudio : null;
videoPub = !(firstVideo?.muted ?? true) ? firstVideo : null;
// For simplification, We are assuming here
// there is only 1 video / audio tracks.
firstAudioPub = widget.participant.audioTracks.firstOrNull;
firstVideoPub = widget.participant.videoTracks.firstOrNull;
if (firstVideoPub is RemoteTrackPublication) {
(firstVideoPub as RemoteTrackPublication).videoQuality = widget.quality;
}
});
}

Expand All @@ -71,22 +68,105 @@ class _ParticipantWidgetState extends State<ParticipantWidget> {
child: Stack(
children: [
// Video
if (videoPub != null)
if (firstVideoPub?.subscribed == true &&
firstVideoPub?.muted == false)
VideoTrackRenderer(
videoPub!.track as VideoTrack,
firstVideoPub!.track as VideoTrack,
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
)
else
const NoVideoWidget(),

Align(
alignment: Alignment.bottomCenter,
child: ParticipantInfoWidget(
title: widget.participant.identity,
muted: audioPub == null,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisSize: MainAxisSize.min,
children: [
//
// Menu for Video RemoteTrackPublication
//
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.end,
children: [
if (firstVideoPub is RemoteTrackPublication)
RemoteTrackPublicationMenuWidget(
pub: firstVideoPub as RemoteTrackPublication,
icon: EvaIcons.videoOutline,
),
//
// Menu for Audio RemoteTrackPublication
//
if (firstAudioPub is RemoteTrackPublication)
RemoteTrackPublicationMenuWidget(
pub: firstAudioPub as RemoteTrackPublication,
icon: EvaIcons.volumeUpOutline,
),
],
),

ParticipantInfoWidget(
title: widget.participant.identity,
audioAvailable: firstAudioPub?.muted == false &&
firstAudioPub?.subscribed == true,
),
],
),
),
],
),
);
}

class RemoteTrackPublicationMenuWidget extends StatelessWidget {
final IconData icon;
final RemoteTrackPublication pub;
const RemoteTrackPublicationMenuWidget({
required this.pub,
required this.icon,
Key? key,
}) : super(key: key);

@override
Widget build(BuildContext context) => Material(
// type: MaterialType.card,
color: Colors.black.withOpacity(0.3),
// shape: CircleBorder(),
child: PopupMenuButton<Function>(
// shape: CircleBorder(),
icon: Icon(icon),
onSelected: (value) => value(),
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<Function>>[
//
// Mute/Unmute
//
if (pub.muted == false)
PopupMenuItem(
child: const Text('Mute'),
value: () => pub.muted = true,
),
if (pub.muted == true)
PopupMenuItem(
child: const Text('Un-mute'),
value: () => pub.muted = false,
),
//
// Subscribe/Unsubscribe
//
if (pub.subscribed == false)
PopupMenuItem(
child: const Text('Subscribe'),
value: () => pub.subscribed = true,
),
if (pub.subscribed == true)
PopupMenuItem(
child: const Text('Un-subscribe'),
value: () => pub.subscribed = false,
),
];
},
),
);
}
8 changes: 4 additions & 4 deletions example/lib/widgets/participant_info.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import 'package:flutter/material.dart';
class ParticipantInfoWidget extends StatelessWidget {
//
final String? title;
final bool muted;
final bool audioAvailable;

const ParticipantInfoWidget({
this.title,
this.muted = true,
this.audioAvailable = true,
Key? key,
}) : super(key: key);

Expand All @@ -33,8 +33,8 @@ class ParticipantInfoWidget extends StatelessWidget {
Padding(
padding: const EdgeInsets.only(left: 5),
child: Icon(
!muted ? EvaIcons.mic : EvaIcons.micOff,
color: !muted ? Colors.white : Colors.red,
audioAvailable ? EvaIcons.mic : EvaIcons.micOff,
color: audioAvailable ? Colors.white : Colors.red,
size: 16,
),
),
Expand Down