Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bug] ParticipantWidget.widgetFor Repeated display will cause image lag #644

Open
lurongshuang opened this issue Nov 24, 2024 · 5 comments

Comments

@lurongshuang
Copy link

Describe the bug
In the floating window function, local or remote images will be repeatedly obtained and given to different parent widgets. When obtaining multiple times, the view will freeze.

[✓] Flutter (Channel stable, 3.24.3, on macOS 15.1.1 24B91 darwin-arm64, locale zh-Hans-CN)
[!] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
Android Studio (version 2024.1)
Xcode - develop for iOS and macOS (Xcode 16.0)

@lurongshuang
Copy link
Author

  • thread Dynacast #62, queue = 'DecodingQueue', stop reason = EXC_RESOURCE (RESOURCE_TYPE_MEMORY: high watermark memory limit exceeded) (limit=1850 MB)
    frame #0: 0x00000001056fef60 WebRTC___lldb_unnamed_symbol3992 + 180 WebRTC___lldb_unnamed_symbol3992:
    -> 0x1056fef60 <+180>: stp q0, q1, [x2]
    0x1056fef64 <+184>: add x10, x2, x3
    0x1056fef68 <+188>: stp q2, q3, [x10]
    0x1056fef6c <+192>: add x2, x10, x3
    Target 0: (Runner) stopped.

@lurongshuang
Copy link
Author

ios 16.7.10
Iphone X

@lurongshuang
Copy link
Author

`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';

abstract class ParticipantWidget extends StatefulWidget {
// Convenience method to return relevant widget for participant
static ParticipantWidget widgetFor(ParticipantTrack participantTrack,
{bool showStatsLayer = false, Widget? noVideoWidget, Key? key}) {
if (participantTrack.participant is LocalParticipant) {
return LocalParticipantWidget(
participantTrack.participant as LocalParticipant,
participantTrack.type,
showStatsLayer,
noVideoWidget: noVideoWidget,
key: key);
} else if (participantTrack.participant is RemoteParticipant) {
return RemoteParticipantWidget(
participantTrack.participant as RemoteParticipant,
participantTrack.type,
showStatsLayer,
noVideoWidget: noVideoWidget,
key: key);
}
throw UnimplementedError('Unknown participant type');
}

// Must be implemented by child class
abstract final Participant participant;
abstract final ParticipantTrackType type;
abstract final bool showStatsLayer;
final VideoQuality quality;
abstract final Widget? noVideoWidget;

const ParticipantWidget({this.quality = VideoQuality.MEDIUM, super.key});
}

class LocalParticipantWidget extends ParticipantWidget {
@OverRide
final LocalParticipant participant;
@OverRide
final ParticipantTrackType type;
@OverRide
final bool showStatsLayer;
@OverRide
final Widget? noVideoWidget;

const LocalParticipantWidget(
this.participant,
this.type,
this.showStatsLayer, {
this.noVideoWidget,
super.key,
});

@OverRide
State createState() => _LocalParticipantWidgetState();
}

class RemoteParticipantWidget extends ParticipantWidget {
@OverRide
final RemoteParticipant participant;
@OverRide
final ParticipantTrackType type;
@OverRide
final bool showStatsLayer;

@OverRide
final Widget? noVideoWidget;

const RemoteParticipantWidget(
this.participant, this.type, this.showStatsLayer,
{this.noVideoWidget, super.key});

@OverRide
State createState() => _RemoteParticipantWidgetState();
}

abstract class _ParticipantWidgetState
extends State {
bool _visible = true;

VideoTrack? get activeVideoTrack;

AudioTrack? get activeAudioTrack;

TrackPublication? get videoPublication;

TrackPublication? get audioPublication;

bool get isScreenShare => widget.type == ParticipantTrackType.kScreenShare;
EventsListener? _listener;

@OverRide
void initState() {
super.initState();
_listener = widget.participant.createListener();
_listener?.on((e) {
for (var seg in e.segments) {
print('Transcription: ${seg.text} ${seg.isFinal}');
}
});

widget.participant.addListener(_onParticipantChanged);
_onParticipantChanged();

}

@OverRide
void dispose() {
widget.participant.removeListener(_onParticipantChanged);
_listener?.dispose();
super.dispose();
}

@OverRide
void didUpdateWidget(covariant T oldWidget) {
oldWidget.participant.removeListener(_onParticipantChanged);
widget.participant.addListener(_onParticipantChanged);
_onParticipantChanged();
super.didUpdateWidget(oldWidget);
}

// Notify Flutter that UI re-build is required, but we don't set anything here
// since the updated values are computed properties.
void _onParticipantChanged() => setState(() {});

// Widgets to show above the info bar
List extraWidgets(bool isScreenShare) => [];

@OverRide
Widget build(BuildContext ctx) {
return activeVideoTrack != null && !activeVideoTrack!.muted
? VideoTrackRenderer(
renderMode: VideoRenderMode.auto,
activeVideoTrack!,
fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitCover)
: widget.noVideoWidget ?? const NoVideoWidget();
}
// Container(
// foregroundDecoration: BoxDecoration(
// border: widget.participant.isSpeaking && !isScreenShare
// ? Border.all(
// width: 5,
// color: TCS.darkBackground,
// )
// : null,
// ),
// decoration: BoxDecoration(
// color: Theme.of(ctx).cardColor,
// ),
// child: Stack(children: [
// Video
// InkWell(
// onTap: () => setState(() => _visible = !_visible),
// child: activeVideoTrack != null && !activeVideoTrack!.muted
// ? VideoTrackRenderer(
// renderMode: VideoRenderMode.auto,
// activeVideoTrack!,
// fit: RTCVideoViewObjectFit.RTCVideoViewObjectFitContain)
// : const NoVideoWidget()),
// Bottom bar
// Align(
// alignment: Alignment.topRight,
// child: Column(
// crossAxisAlignment: CrossAxisAlignment.stretch,
// mainAxisSize: MainAxisSize.min,
// children: [
// ParticipantInfoWidget(
// title: widget.participant.name.isNotEmpty
// ? '${widget.participant.name} (${widget.participant.identity})'
// : widget.participant.identity,
// audioAvailable: audioPublication?.muted == false &&
// audioPublication?.subscribed == true,
// connectionQuality: widget.participant.connectionQuality,
// isScreenShare: isScreenShare,
// enabledE2EE: widget.participant.isEncrypted)
// ])),
// if (widget.showStatsLayer)
// Positioned(
// top: 30.r,
// left: 5.r,
// child: ParticipantStatsWidget(participant: widget.participant)),
// ...extraWidgets(isScreenShare)
// ]));
}

class _LocalParticipantWidgetState
extends _ParticipantWidgetState {
@OverRide
LocalTrackPublication? get videoPublication =>
widget.participant.videoTrackPublications
.where((element) => element.source == widget.type.lkVideoSourceType)
.firstOrNull;

@OverRide
LocalTrackPublication? get audioPublication =>
widget.participant.audioTrackPublications
.where((element) => element.source == widget.type.lkAudioSourceType)
.firstOrNull;

@OverRide
VideoTrack? get activeVideoTrack => videoPublication?.track;

@OverRide
AudioTrack? get activeAudioTrack => audioPublication?.track;
}

class _RemoteParticipantWidgetState
extends _ParticipantWidgetState {
@OverRide
RemoteTrackPublication? get videoPublication =>
widget.participant.videoTrackPublications
.where((element) => element.source == widget.type.lkVideoSourceType)
.firstOrNull;

@OverRide
RemoteTrackPublication? get audioPublication =>
widget.participant.audioTrackPublications
.where((element) => element.source == widget.type.lkAudioSourceType)
.firstOrNull;

@OverRide
VideoTrack? get activeVideoTrack => videoPublication?.track;

@OverRide
AudioTrack? get activeAudioTrack => audioPublication?.track;

@OverRide
List extraWidgets(bool isScreenShare) => [
// Row(
// mainAxisSize: MainAxisSize.max,
// mainAxisAlignment: MainAxisAlignment.end,
// children: [
// Menu for RemoteTrackPublication
// if (audioPublication != null)
// RemoteTrackPublicationMenuWidget(
// pub: audioPublication!,
// icon: Icons.volume_up,
// ),
// // Menu for RemoteTrackPublication
// if (videoPublication != null)
// RemoteTrackPublicationMenuWidget(
// pub: videoPublication!,
// icon: isScreenShare ? Icons.monitor : Icons.videocam,
// ),
// if (videoPublication != null)
// RemoteTrackFPSMenuWidget(
// pub: videoPublication!,
// icon: Icons.menu,
// ),
// if (videoPublication != null)
// RemoteTrackQualityMenuWidget(
// pub: videoPublication!,
// icon: Icons.monitor_outlined,
// ),
// ],
// ),
];
}

class RemoteTrackPublicationMenuWidget extends StatelessWidget {
final IconData icon;
final RemoteTrackPublication pub;

const RemoteTrackPublicationMenuWidget({
required this.pub,
required this.icon,
super.key,
});

@OverRide
Widget build(BuildContext context) => Material(
color: Colors.black.withOpacity(0.3),
child: PopupMenuButton(
tooltip: 'Subscribe menu',
icon: Icon(icon,
color: {
TrackSubscriptionState.notAllowed: Colors.red,
TrackSubscriptionState.unsubscribed: Colors.grey,
TrackSubscriptionState.subscribed: Colors.green,
}[pub.subscriptionState]),
onSelected: (value) => value(),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
// Subscribe/Unsubscribe
if (pub.subscribed == false)
PopupMenuItem(
child: const Text('Subscribe'),
value: () => pub.subscribe(),
)
else if (pub.subscribed == true)
PopupMenuItem(
child: const Text('Un-subscribe'),
value: () => pub.unsubscribe(),
),
],
),
);
}

class RemoteTrackFPSMenuWidget extends StatelessWidget {
final IconData icon;
final RemoteTrackPublication pub;

const RemoteTrackFPSMenuWidget({
required this.pub,
required this.icon,
super.key,
});

@OverRide
Widget build(BuildContext context) => Material(
color: Colors.black.withOpacity(0.3),
child: PopupMenuButton(
tooltip: 'Preferred FPS',
icon: Icon(icon, color: Colors.white),
onSelected: (value) => value(),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
PopupMenuItem(
child: const Text('60'),
value: () async {
pub.setVideoFPS(60);
}),
PopupMenuItem(
child: const Text('30'),
value: () => pub.setVideoFPS(30),
),
PopupMenuItem(
child: const Text('15'),
value: () => pub.setVideoFPS(15),
),
PopupMenuItem(
child: const Text('8'), value: () => pub.setVideoFPS(8))
]));
}

class RemoteTrackQualityMenuWidget extends StatelessWidget {
final IconData icon;
final RemoteTrackPublication pub;

const RemoteTrackQualityMenuWidget({
required this.pub,
required this.icon,
super.key,
});

@OverRide
Widget build(BuildContext context) => Material(
color: Colors.black.withOpacity(0.3),
child: PopupMenuButton(
tooltip: 'Preferred Quality',
icon: Icon(icon, color: Colors.white),
onSelected: (value) => value(),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
PopupMenuItem(
child: const Text('HIGH'),
value: () => pub.setVideoQuality(VideoQuality.HIGH),
),
PopupMenuItem(
child: const Text('MEDIUM'),
value: () => pub.setVideoQuality(VideoQuality.MEDIUM),
),
PopupMenuItem(
child: const Text('LOW'),
value: () => pub.setVideoQuality(VideoQuality.LOW),
),
],
),
);
}
`

@lurongshuang
Copy link
Author

` Widget getLocalFullScreen(bool isLocal, {Widget? noVideoWidget, Key? key}) {
if (isLocal) {
if (_room == null || _room!.localParticipant == null) {
return noVideoWidget ?? const NoVideoWidget();
}
return ParticipantWidget.widgetFor(
ParticipantTrack(participant: _room!.localParticipant!),
noVideoWidget: noVideoWidget,
key: key);
}

if (participantTracks.isEmpty) {
  return noVideoWidget ?? const NoVideoWidget();
}

return ParticipantWidget.widgetFor(participantTracks.first,
    noVideoWidget: noVideoWidget, key: key);

}`

@lurongshuang
Copy link
Author

During the switching process, the frame rate kept decreasing, causing the screen to lag

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant