50f56c5e1e
* Respect `RTCIceTransportPolicy` enum and organize * Simplify syntax where possible etc. * Combine `VideoPreset` and `VideoPresets` * Default values for `ConnectOptions` * Build URI instead of String manipulation * Slight modifications to Exception * `LiveKitTheme` for example * `VideoEncoding` class * Organize imports * First simulcast implementation * Remove unnecessary try-catches * Update Android settings * Remember uri and token * example improvements * `fit` parameter for VideoTrackRenderer * Simulcast option for example * Pass `defaultPublishOptions` * Show only `VideoQuality` * Pass tests * Better buildUri logic * Named parameter to positional * Explicit imports * `VideoParameter` instead of `VideoPreset` * Use `mediaTrack.getSettings` when possible * Safer dispose logic * Safer `PCTransport` Update transport.dart * Synchronized events for `SignalClient` * Use logger instead of print * Make example compile for iOS * First screen share implementation * Make example work with screen share * Example improvement * Code optimization * Don't depend on web_socket_channel * Fix: Unpublish track bug * Show participant mute state & identity * Update protos * Remote mute/unmute * iOS Background mode * Separate `createCameraTrack` and `createScreenTrack` * Clean up * PB fix * format * Fix analyzer warning * Android clean up * Update README.md * Clean up
92 lines
2.5 KiB
Dart
92 lines
2.5 KiB
Dart
import 'package:collection/collection.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter_webrtc/flutter_webrtc.dart';
|
|
import 'package:livekit_client/livekit_client.dart';
|
|
|
|
import 'no_video.dart';
|
|
import 'participant_info.dart';
|
|
|
|
class ParticipantWidget extends StatefulWidget {
|
|
//
|
|
final Participant participant;
|
|
final VideoQuality quality;
|
|
|
|
const ParticipantWidget(
|
|
this.participant, {
|
|
this.quality = VideoQuality.MEDIUM,
|
|
Key? key,
|
|
}) : super(key: key);
|
|
|
|
@override
|
|
State<StatefulWidget> createState() => _ParticipantWidgetState();
|
|
}
|
|
|
|
class _ParticipantWidgetState extends State<ParticipantWidget> with ParticipantDelegate {
|
|
//
|
|
TrackPublication? videoPub;
|
|
TrackPublication? audioPub;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
widget.participant.addListener(_onParticipantChanged);
|
|
_onParticipantChanged();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
widget.participant.removeListener(_onParticipantChanged);
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
void didUpdateWidget(covariant ParticipantWidget oldWidget) {
|
|
oldWidget.participant.removeListener(_onParticipantChanged);
|
|
widget.participant.addListener(_onParticipantChanged);
|
|
_onParticipantChanged();
|
|
super.didUpdateWidget(oldWidget);
|
|
}
|
|
|
|
// 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;
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext ctx) => Container(
|
|
color: Theme.of(ctx).cardColor,
|
|
child: Stack(
|
|
children: [
|
|
// Video
|
|
if (videoPub != null)
|
|
VideoTrackRenderer(
|
|
videoPub!.track as VideoTrack,
|
|
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover,
|
|
)
|
|
else
|
|
const NoVideoWidget(),
|
|
|
|
Align(
|
|
alignment: Alignment.bottomCenter,
|
|
child: ParticipantInfoWidget(
|
|
title: widget.participant.identity,
|
|
muted: audioPub == null,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|