WebRTC signaling heartbeat

This commit is contained in:
2025-10-19 18:58:54 +08:00
parent 43d767bc03
commit f4b28c3fa2
7 changed files with 42 additions and 23 deletions

View File

@@ -152,6 +152,7 @@ sealed class CallParticipant with _$CallParticipant {
required String accountId, required String accountId,
@Default(null) SnAccount? account, @Default(null) SnAccount? account,
required DateTime joinedAt, required DateTime joinedAt,
@Default(false) bool isLocal,
}) = _CallParticipant; }) = _CallParticipant;
factory CallParticipant.fromJson(Map<String, dynamic> json) => factory CallParticipant.fromJson(Map<String, dynamic> json) =>

View File

@@ -2241,7 +2241,7 @@ as List<CallParticipant>,
/// @nodoc /// @nodoc
mixin _$CallParticipant { mixin _$CallParticipant {
String get identity; String get name; String get accountId; SnAccount? get account; DateTime get joinedAt; String get identity; String get name; String get accountId; SnAccount? get account; DateTime get joinedAt; bool get isLocal;
/// Create a copy of CallParticipant /// Create a copy of CallParticipant
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@@ -2254,16 +2254,16 @@ $CallParticipantCopyWith<CallParticipant> get copyWith => _$CallParticipantCopyW
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isLocal, isLocal) || other.isLocal == isLocal));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,identity,name,accountId,account,joinedAt); int get hashCode => Object.hash(runtimeType,identity,name,accountId,account,joinedAt,isLocal);
@override @override
String toString() { String toString() {
return 'CallParticipant(identity: $identity, name: $name, accountId: $accountId, account: $account, joinedAt: $joinedAt)'; return 'CallParticipant(identity: $identity, name: $name, accountId: $accountId, account: $account, joinedAt: $joinedAt, isLocal: $isLocal)';
} }
@@ -2274,7 +2274,7 @@ abstract mixin class $CallParticipantCopyWith<$Res> {
factory $CallParticipantCopyWith(CallParticipant value, $Res Function(CallParticipant) _then) = _$CallParticipantCopyWithImpl; factory $CallParticipantCopyWith(CallParticipant value, $Res Function(CallParticipant) _then) = _$CallParticipantCopyWithImpl;
@useResult @useResult
$Res call({ $Res call({
String identity, String name, String accountId, SnAccount? account, DateTime joinedAt String identity, String name, String accountId, SnAccount? account, DateTime joinedAt, bool isLocal
}); });
@@ -2291,14 +2291,15 @@ class _$CallParticipantCopyWithImpl<$Res>
/// Create a copy of CallParticipant /// Create a copy of CallParticipant
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? accountId = null,Object? account = freezed,Object? joinedAt = null,}) { @pragma('vm:prefer-inline') @override $Res call({Object? identity = null,Object? name = null,Object? accountId = null,Object? account = freezed,Object? joinedAt = null,Object? isLocal = null,}) {
return _then(_self.copyWith( return _then(_self.copyWith(
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount?,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable as SnAccount?,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
as DateTime, as DateTime,isLocal: null == isLocal ? _self.isLocal : isLocal // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }
/// Create a copy of CallParticipant /// Create a copy of CallParticipant
@@ -2392,10 +2393,10 @@ return $default(_that);case _:
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String identity, String name, String accountId, SnAccount? account, DateTime joinedAt)? $default,{required TResult orElse(),}) {final _that = this; @optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String identity, String name, String accountId, SnAccount? account, DateTime joinedAt, bool isLocal)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) { switch (_that) {
case _CallParticipant() when $default != null: case _CallParticipant() when $default != null:
return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.joinedAt);case _: return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.joinedAt,_that.isLocal);case _:
return orElse(); return orElse();
} }
@@ -2413,10 +2414,10 @@ return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.jo
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String identity, String name, String accountId, SnAccount? account, DateTime joinedAt) $default,) {final _that = this; @optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String identity, String name, String accountId, SnAccount? account, DateTime joinedAt, bool isLocal) $default,) {final _that = this;
switch (_that) { switch (_that) {
case _CallParticipant(): case _CallParticipant():
return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.joinedAt);} return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.joinedAt,_that.isLocal);}
} }
/// A variant of `when` that fallback to returning `null` /// A variant of `when` that fallback to returning `null`
/// ///
@@ -2430,10 +2431,10 @@ return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.jo
/// } /// }
/// ``` /// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String identity, String name, String accountId, SnAccount? account, DateTime joinedAt)? $default,) {final _that = this; @optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String identity, String name, String accountId, SnAccount? account, DateTime joinedAt, bool isLocal)? $default,) {final _that = this;
switch (_that) { switch (_that) {
case _CallParticipant() when $default != null: case _CallParticipant() when $default != null:
return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.joinedAt);case _: return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.joinedAt,_that.isLocal);case _:
return null; return null;
} }
@@ -2445,7 +2446,7 @@ return $default(_that.identity,_that.name,_that.accountId,_that.account,_that.jo
@JsonSerializable() @JsonSerializable()
class _CallParticipant implements CallParticipant { class _CallParticipant implements CallParticipant {
const _CallParticipant({required this.identity, required this.name, required this.accountId, this.account = null, required this.joinedAt}); const _CallParticipant({required this.identity, required this.name, required this.accountId, this.account = null, required this.joinedAt, this.isLocal = false});
factory _CallParticipant.fromJson(Map<String, dynamic> json) => _$CallParticipantFromJson(json); factory _CallParticipant.fromJson(Map<String, dynamic> json) => _$CallParticipantFromJson(json);
@override final String identity; @override final String identity;
@@ -2453,6 +2454,7 @@ class _CallParticipant implements CallParticipant {
@override final String accountId; @override final String accountId;
@override@JsonKey() final SnAccount? account; @override@JsonKey() final SnAccount? account;
@override final DateTime joinedAt; @override final DateTime joinedAt;
@override@JsonKey() final bool isLocal;
/// Create a copy of CallParticipant /// Create a copy of CallParticipant
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@@ -2467,16 +2469,16 @@ Map<String, dynamic> toJson() {
@override @override
bool operator ==(Object other) { bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)); return identical(this, other) || (other.runtimeType == runtimeType&&other is _CallParticipant&&(identical(other.identity, identity) || other.identity == identity)&&(identical(other.name, name) || other.name == name)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.account, account) || other.account == account)&&(identical(other.joinedAt, joinedAt) || other.joinedAt == joinedAt)&&(identical(other.isLocal, isLocal) || other.isLocal == isLocal));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(includeFromJson: false, includeToJson: false)
@override @override
int get hashCode => Object.hash(runtimeType,identity,name,accountId,account,joinedAt); int get hashCode => Object.hash(runtimeType,identity,name,accountId,account,joinedAt,isLocal);
@override @override
String toString() { String toString() {
return 'CallParticipant(identity: $identity, name: $name, accountId: $accountId, account: $account, joinedAt: $joinedAt)'; return 'CallParticipant(identity: $identity, name: $name, accountId: $accountId, account: $account, joinedAt: $joinedAt, isLocal: $isLocal)';
} }
@@ -2487,7 +2489,7 @@ abstract mixin class _$CallParticipantCopyWith<$Res> implements $CallParticipant
factory _$CallParticipantCopyWith(_CallParticipant value, $Res Function(_CallParticipant) _then) = __$CallParticipantCopyWithImpl; factory _$CallParticipantCopyWith(_CallParticipant value, $Res Function(_CallParticipant) _then) = __$CallParticipantCopyWithImpl;
@override @useResult @override @useResult
$Res call({ $Res call({
String identity, String name, String accountId, SnAccount? account, DateTime joinedAt String identity, String name, String accountId, SnAccount? account, DateTime joinedAt, bool isLocal
}); });
@@ -2504,14 +2506,15 @@ class __$CallParticipantCopyWithImpl<$Res>
/// Create a copy of CallParticipant /// Create a copy of CallParticipant
/// with the given fields replaced by the non-null parameter values. /// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? accountId = null,Object? account = freezed,Object? joinedAt = null,}) { @override @pragma('vm:prefer-inline') $Res call({Object? identity = null,Object? name = null,Object? accountId = null,Object? account = freezed,Object? joinedAt = null,Object? isLocal = null,}) {
return _then(_CallParticipant( return _then(_CallParticipant(
identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable identity: null == identity ? _self.identity : identity // ignore: cast_nullable_to_non_nullable
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable as String,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable as String,account: freezed == account ? _self.account : account // ignore: cast_nullable_to_non_nullable
as SnAccount?,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable as SnAccount?,joinedAt: null == joinedAt ? _self.joinedAt : joinedAt // ignore: cast_nullable_to_non_nullable
as DateTime, as DateTime,isLocal: null == isLocal ? _self.isLocal : isLocal // ignore: cast_nullable_to_non_nullable
as bool,
)); ));
} }

View File

@@ -281,6 +281,7 @@ _CallParticipant _$CallParticipantFromJson(Map<String, dynamic> json) =>
? null ? null
: SnAccount.fromJson(json['account'] as Map<String, dynamic>), : SnAccount.fromJson(json['account'] as Map<String, dynamic>),
joinedAt: DateTime.parse(json['joined_at'] as String), joinedAt: DateTime.parse(json['joined_at'] as String),
isLocal: json['is_local'] as bool? ?? false,
); );
Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) => Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
@@ -290,6 +291,7 @@ Map<String, dynamic> _$CallParticipantToJson(_CallParticipant instance) =>
'account_id': instance.accountId, 'account_id': instance.accountId,
'account': instance.account?.toJson(), 'account': instance.account?.toJson(),
'joined_at': instance.joinedAt.toIso8601String(), 'joined_at': instance.joinedAt.toIso8601String(),
'is_local': instance.isLocal,
}; };
_SnRealtimeCall _$SnRealtimeCallFromJson(Map<String, dynamic> json) => _SnRealtimeCall _$SnRealtimeCallFromJson(Map<String, dynamic> json) =>

View File

@@ -178,6 +178,7 @@ class CallNotifier extends _$CallNotifier {
accountId: p.userinfo.id, accountId: p.userinfo.id,
account: p.userinfo, account: p.userinfo,
joinedAt: DateTime.now(), joinedAt: DateTime.now(),
isLocal: true,
); );
return CallParticipantLive( return CallParticipantLive(
participant: participantInfo, participant: participantInfo,

View File

@@ -6,7 +6,7 @@ part of 'call.dart';
// RiverpodGenerator // RiverpodGenerator
// ************************************************************************** // **************************************************************************
String _$callNotifierHash() => r'91e546c8711d1b46740ad592cbe481173b227e7b'; String _$callNotifierHash() => r'6db330370d473eaea313dd9f9439d261c355095f';
/// See also [CallNotifier]. /// See also [CallNotifier].
@ProviderFor(CallNotifier) @ProviderFor(CallNotifier)

View File

@@ -57,6 +57,7 @@ class WebRTCSignaling {
final StreamController<WebRTCWelcomeMessage> _welcomeController = final StreamController<WebRTCWelcomeMessage> _welcomeController =
StreamController<WebRTCWelcomeMessage>.broadcast(); StreamController<WebRTCWelcomeMessage>.broadcast();
WebSocketChannel? _channel; WebSocketChannel? _channel;
Timer? _heartbeatTimer;
Stream<SignalingMessage> get messages => _messageController.stream; Stream<SignalingMessage> get messages => _messageController.stream;
Stream<WebRTCWelcomeMessage> get welcomeMessages => _welcomeController.stream; Stream<WebRTCWelcomeMessage> get welcomeMessages => _welcomeController.stream;
@@ -88,6 +89,9 @@ class WebRTCSignaling {
} }
await _channel!.ready; await _channel!.ready;
// Start heartbeat timer
_heartbeatTimer = Timer.periodic(const Duration(seconds: 30), (timer) => _sendHeartbeat());
_channel!.stream.listen( _channel!.stream.listen(
(data) { (data) {
final dataStr = final dataStr =
@@ -196,7 +200,15 @@ class WebRTCSignaling {
); );
} }
void _sendHeartbeat() {
if (_channel == null) return;
talker.info('[WebRTC Signaling] Sending heartbeat');
final packet = WebSocketPacket(type: 'heartbeat', data: null);
_channel!.sink.add(jsonEncode(packet.toJson()));
}
void disconnect() { void disconnect() {
_heartbeatTimer?.cancel();
_channel?.sink.close(); _channel?.sink.close();
_messageController.close(); _messageController.close();
_welcomeController.close(); _welcomeController.close();

View File

@@ -1,6 +1,6 @@
; ================================================== ; ==================================================
#define AppVersion "3.2.0" #define AppVersion "3.3.0"
#define BuildNumber "134" #define BuildNumber "136"
; ================================================== ; ==================================================
#define FullVersion AppVersion + "." + BuildNumber #define FullVersion AppVersion + "." + BuildNumber