From 64a243a312773f6934933855be4c24e2f5cfbf6f Mon Sep 17 00:00:00 2001 From: Hiroshi Horie <548776+hiroshihorie@users.noreply.github.com> Date: Thu, 2 Dec 2021 14:21:41 +0700 Subject: [PATCH] update example (no casting) --- example/lib/pages/room.dart | 5 +- example/lib/widgets/participant.dart | 163 +++++++++++++++++++-------- 2 files changed, 121 insertions(+), 47 deletions(-) diff --git a/example/lib/pages/room.dart b/example/lib/pages/room.dart index 6903bce..6627af1 100644 --- a/example/lib/pages/room.dart +++ b/example/lib/pages/room.dart @@ -133,7 +133,7 @@ class _RoomPageState extends State { children: [ Expanded( child: participants.isNotEmpty - ? ParticipantWidget(participants.first) + ? ParticipantWidget.widgetFor(participants.first) : Container()), SizedBox( height: 100, @@ -143,8 +143,7 @@ class _RoomPageState extends State { itemBuilder: (BuildContext context, int index) => SizedBox( width: 100, height: 100, - child: ParticipantWidget(participants[index + 1], - quality: VideoQuality.LOW), + child: ParticipantWidget.widgetFor(participants[index + 1]), ), ), ), diff --git a/example/lib/widgets/participant.dart b/example/lib/widgets/participant.dart index e44694e..64433ae 100644 --- a/example/lib/widgets/participant.dart +++ b/example/lib/widgets/participant.dart @@ -8,26 +8,60 @@ import 'package:livekit_example/theme.dart'; import 'no_video.dart'; import 'participant_info.dart'; -class ParticipantWidget extends StatefulWidget { - // - final Participant participant; +abstract class ParticipantWidget extends StatefulWidget { + // Convenience method to return relevant widget for participant + static ParticipantWidget widgetFor(Participant participant) { + if (participant is LocalParticipant) { + return LocalParticipantWidget(participant); + } else if (participant is RemoteParticipant) { + return RemoteParticipantWidget(participant); + } + throw UnimplementedError('Unknown participant type'); + } + + // Must be implemented by child class + abstract final Participant participant; final VideoQuality quality; - const ParticipantWidget( - this.participant, { + const ParticipantWidget({ this.quality = VideoQuality.MEDIUM, Key? key, }) : super(key: key); +} + +class LocalParticipantWidget extends ParticipantWidget { + @override + final LocalParticipant participant; + + const LocalParticipantWidget( + this.participant, { + Key? key, + }) : super(key: key); @override - State createState() => _ParticipantWidgetState(); + State createState() => _LocalParticipantWidgetState(); } -class _ParticipantWidgetState extends State { +class RemoteParticipantWidget extends ParticipantWidget { + @override + final RemoteParticipant participant; + + const RemoteParticipantWidget( + this.participant, { + Key? key, + }) : super(key: key); + + @override + State createState() => _RemoteParticipantWidgetState(); +} + +abstract class _ParticipantWidgetState + extends State { // - TrackPublication? firstVideoPub; - TrackPublication? firstAudioPub; bool _visible = true; + VideoTrack? get activeVideoTrack; + TrackPublication? get firstVideoPublication; + TrackPublication? get firstAudioPublication; @override void initState() { @@ -43,7 +77,7 @@ class _ParticipantWidgetState extends State { } @override - void didUpdateWidget(covariant ParticipantWidget oldWidget) { + void didUpdateWidget(covariant T oldWidget) { oldWidget.participant.removeListener(_onParticipantChanged); widget.participant.addListener(_onParticipantChanged); _onParticipantChanged(); @@ -56,14 +90,17 @@ class _ParticipantWidgetState extends State { setState(() { // 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; - } + // firstAudioPub = widget.participant.audioTracks.firstOrNull; + // firstVideoPub = widget.participant.videoTracks.firstOrNull; + // if (firstVideoPub is RemoteTrackPublication) { + // (firstVideoPub as RemoteTrackPublication).videoQuality = widget.quality; + // } }); } + // Widgets to stack above + List above() => []; + @override Widget build(BuildContext ctx) => Container( foregroundDecoration: BoxDecoration( @@ -82,49 +119,26 @@ class _ParticipantWidgetState extends State { // Video InkWell( onTap: () => setState(() => _visible = !_visible), - child: (firstVideoPub?.subscribed == true && - firstVideoPub?.muted == false && - _visible) + child: activeVideoTrack != null ? VideoTrackRenderer( - firstVideoPub!.track as VideoTrack, + activeVideoTrack!, fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover, ) : const NoVideoWidget(), ), + // Bottom bar Align( alignment: Alignment.bottomCenter, 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, - ), - ], - ), - + ...above(), ParticipantInfoWidget( title: widget.participant.identity, - audioAvailable: firstAudioPub?.muted == false && - firstAudioPub?.subscribed == true, + audioAvailable: firstAudioPublication?.muted == false && + firstAudioPublication?.subscribed == true, connectionQuality: widget.participant.connectionQuality, ), ], @@ -135,6 +149,67 @@ class _ParticipantWidgetState extends State { ); } +class _LocalParticipantWidgetState + extends _ParticipantWidgetState { + @override + LocalTrackPublication? get firstVideoPublication => + widget.participant.videoTracks.firstOrNull; + + @override + LocalTrackPublication? get firstAudioPublication => + widget.participant.audioTracks.firstOrNull; + + @override + VideoTrack? get activeVideoTrack { + if (firstVideoPublication?.subscribed == true && + firstVideoPublication?.muted == false && + _visible) { + return firstVideoPublication?.track; + } + } +} + +class _RemoteParticipantWidgetState + extends _ParticipantWidgetState { + @override + RemoteTrackPublication? get firstVideoPublication => + widget.participant.videoTracks.firstOrNull; + + @override + RemoteTrackPublication? get firstAudioPublication => + widget.participant.audioTracks.firstOrNull; + + @override + VideoTrack? get activeVideoTrack { + if (firstVideoPublication?.subscribed == true && + firstVideoPublication?.muted == false && + _visible) { + return firstVideoPublication?.track; + } + } + + @override + List above() => [ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + if (firstVideoPublication != null) + RemoteTrackPublicationMenuWidget( + pub: firstVideoPublication!, + icon: EvaIcons.videoOutline, + ), + // Menu for Audio RemoteTrackPublication + if (firstAudioPublication != null) + RemoteTrackPublicationMenuWidget( + pub: firstAudioPublication!, + icon: EvaIcons.volumeUpOutline, + ), + ], + ), + ]; +} + class RemoteTrackPublicationMenuWidget extends StatelessWidget { final IconData icon; final RemoteTrackPublication pub;