🐛 Bug fixes of webrtc
This commit is contained in:
@@ -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 {
|
||||
|
@@ -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();
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user