🐛 Bug fixes of webrtc

This commit is contained in:
2025-10-19 18:22:03 +08:00
parent e96b1fd9d4
commit 0910be88ef
3 changed files with 136 additions and 22 deletions

View File

@@ -120,14 +120,16 @@ class CallNotifier extends _$CallNotifier {
// Add local participant immediately when WebRTC is initialized
final userinfo = ref.watch(userInfoProvider);
if (userinfo.value != null) {
_addLocalParticipant(userinfo.value!);
}
}
void _addLocalParticipant(SnAccount userinfo) {
if (_webrtcManager == null) return;
// Remove any existing local participant first
_participants.removeWhere((p) => p.participant.name == 'You');
_participants.removeWhere((p) => p.participant.identity == userinfo.id);
// Add local participant (current user)
final localParticipant = CallParticipantLive(
@@ -154,12 +156,16 @@ class CallNotifier extends _$CallNotifier {
final webrtcParticipants = _webrtcManager!.participants;
// Get the local participant (should be the first one)
final localParticipant =
_participants.isNotEmpty && _participants[0].participant.name == 'You'
// Always ensure local participant exists
final existingLocalParticipant =
_participants.isNotEmpty &&
_participants[0].remoteParticipant.id == _webrtcManager!.roomId
? _participants[0]
: null;
final localParticipant =
existingLocalParticipant ?? _createLocalParticipant();
// Add remote participants
final remoteParticipants =
webrtcParticipants.map((p) {
@@ -179,14 +185,63 @@ class CallNotifier extends _$CallNotifier {
}).toList();
// Combine local participant with remote participants
_participants =
localParticipant != null
? [localParticipant, ...remoteParticipants]
: remoteParticipants;
_participants = [localParticipant, ...remoteParticipants];
state = state.copyWith();
}
CallParticipantLive _createLocalParticipant() {
return CallParticipantLive(
participant: CallParticipant(
identity: _webrtcManager!.roomId, // Use roomId as local identity
name: 'You',
accountId: '',
account: null,
joinedAt: DateTime.now(),
),
remoteParticipant: WebRTCParticipant(
id: _webrtcManager!.roomId,
name: 'You',
userinfo: SnAccount(
id: '',
name: '',
nick: '',
language: '',
isSuperuser: false,
automatedId: null,
profile: SnAccountProfile(
id: '',
firstName: '',
middleName: '',
lastName: '',
bio: '',
gender: '',
pronouns: '',
location: '',
timeZone: '',
links: [],
experience: 0,
level: 0,
socialCredits: 0,
socialCreditsLevel: 0,
levelingProgress: 0,
picture: null,
background: null,
verification: null,
usernameColor: null,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
deletedAt: null,
),
perkSubscription: null,
createdAt: DateTime.now(),
updatedAt: DateTime.now(),
deletedAt: null,
),
)..remoteStream = _webrtcManager!.localStream, // Access local stream
);
}
Future<void> joinRoom(String roomId) async {
if (_roomId == roomId && _webrtcManager != null) {
talker.info('[Call] Call skipped. Already connected to this room');
@@ -258,12 +313,24 @@ class CallNotifier extends _$CallNotifier {
final target = !state.isMicrophoneEnabled;
state = state.copyWith(isMicrophoneEnabled: target);
await _webrtcManager?.toggleMicrophone(target);
// Update local participant's audio state
if (_participants.isNotEmpty) {
_participants[0].remoteParticipant.isAudioEnabled = target;
state = state.copyWith(); // Trigger UI update
}
}
Future<void> toggleCamera() async {
final target = !state.isCameraEnabled;
state = state.copyWith(isCameraEnabled: target);
await _webrtcManager?.toggleCamera(target);
// Update local participant's video state
if (_participants.isNotEmpty) {
_participants[0].remoteParticipant.isVideoEnabled = target;
state = state.copyWith(); // Trigger UI update
}
}
Future<void> toggleScreenShare(BuildContext context) async {

View File

@@ -55,7 +55,7 @@ class WebRTCManager {
try {
_localStream = await navigator.mediaDevices.getUserMedia({
'audio': true,
'video': false,
'video': true,
});
talker.info('[WebRTC] Local stream initialized');
} catch (e) {
@@ -263,6 +263,12 @@ class WebRTCManager {
track.enabled = enabled;
}
}
// Update audio enabled state for all participants (they share the same local stream)
for (final participant in _participants.values) {
participant.isAudioEnabled = enabled;
_participantController.add(participant);
}
}
Future<void> toggleCamera(bool enabled) async {
@@ -271,6 +277,12 @@ class WebRTCManager {
track.enabled = enabled;
});
}
// Update video enabled state for all participants (they share the same local stream)
for (final participant in _participants.values) {
participant.isVideoEnabled = enabled;
_participantController.add(participant);
}
}
List<WebRTCParticipant> get participants => _participants.values.toList();

View File

@@ -81,30 +81,65 @@ class SpeakingRippleAvatar extends HookConsumerWidget {
}
}
class CallParticipantTile extends HookConsumerWidget {
class CallParticipantTile extends StatefulWidget {
final CallParticipantLive live;
const CallParticipantTile({super.key, required this.live});
@override
Widget build(BuildContext context, WidgetRef ref) {
if (live.hasVideo && live.remoteParticipant.remoteStream != null) {
State<CallParticipantTile> createState() => _CallParticipantTileState();
}
class _CallParticipantTileState extends State<CallParticipantTile> {
RTCVideoRenderer? _renderer;
@override
void initState() {
super.initState();
_initRenderer();
}
@override
void didUpdateWidget(CallParticipantTile oldWidget) {
super.didUpdateWidget(oldWidget);
// Update renderer source when the stream changes
if (_renderer != null &&
widget.live.remoteParticipant.remoteStream !=
oldWidget.live.remoteParticipant.remoteStream) {
_renderer!.srcObject = widget.live.remoteParticipant.remoteStream;
}
}
Future<void> _initRenderer() async {
_renderer = RTCVideoRenderer();
await _renderer!.initialize();
_renderer!.srcObject = widget.live.remoteParticipant.remoteStream;
if (mounted) {
setState(() {});
}
}
@override
void dispose() {
_renderer?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
if (widget.live.hasVideo &&
widget.live.remoteParticipant.remoteStream != null &&
_renderer != null) {
return Stack(
fit: StackFit.loose,
children: [
AspectRatio(
aspectRatio: 16 / 9,
child: RTCVideoView(
RTCVideoRenderer()
..srcObject = live.remoteParticipant.remoteStream,
),
),
AspectRatio(aspectRatio: 16 / 9, child: RTCVideoView(_renderer!)),
Positioned(
left: 8,
right: 8,
bottom: 8,
child: Text(
'@${live.participant.name}',
'@${widget.live.participant.name}',
textAlign: TextAlign.center,
style: const TextStyle(
fontSize: 14,
@@ -123,7 +158,7 @@ class CallParticipantTile extends HookConsumerWidget {
],
);
} else {
return SpeakingRippleAvatar(size: 84, live: live);
return SpeakingRippleAvatar(size: 84, live: widget.live);
}
}
}