feat: 添加 WebSocket、WebRTC 与 Protobuf 支持

This commit is contained in:
TongTongStudio
2026-07-24 12:06:56 +08:00
parent f550ba6c24
commit 7123039b87
15 changed files with 609 additions and 1 deletions

111
lib/generated/user.pb.dart Normal file
View File

@@ -0,0 +1,111 @@
// This is a generated file - do not edit.
//
// Generated from user.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
class UserInfo extends $pb.GeneratedMessage {
factory UserInfo({
$core.int? id,
$core.String? name,
$core.String? email,
$core.bool? isAdmin,
}) {
final result = create();
if (id != null) result.id = id;
if (name != null) result.name = name;
if (email != null) result.email = email;
if (isAdmin != null) result.isAdmin = isAdmin;
return result;
}
UserInfo._();
factory UserInfo.fromBuffer($core.List<$core.int> data,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromBuffer(data, registry);
factory UserInfo.fromJson($core.String json,
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
create()..mergeFromJson(json, registry);
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
_omitMessageNames ? '' : 'UserInfo',
package: const $pb.PackageName(_omitMessageNames ? '' : 'my_project'),
createEmptyInstance: create)
..aI(1, _omitFieldNames ? '' : 'id')
..aOS(2, _omitFieldNames ? '' : 'name')
..aOS(3, _omitFieldNames ? '' : 'email')
..aOB(4, _omitFieldNames ? '' : 'isAdmin')
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
UserInfo clone() => deepCopy();
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
UserInfo copyWith(void Function(UserInfo) updates) =>
super.copyWith((message) => updates(message as UserInfo)) as UserInfo;
@$core.override
$pb.BuilderInfo get info_ => _i;
@$core.pragma('dart2js:noInline')
static UserInfo create() => UserInfo._();
@$core.override
UserInfo createEmptyInstance() => create();
@$core.pragma('dart2js:noInline')
static UserInfo getDefault() =>
_defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<UserInfo>(create);
static UserInfo? _defaultInstance;
@$pb.TagNumber(1)
$core.int get id => $_getIZ(0);
@$pb.TagNumber(1)
set id($core.int value) => $_setSignedInt32(0, value);
@$pb.TagNumber(1)
$core.bool hasId() => $_has(0);
@$pb.TagNumber(1)
void clearId() => $_clearField(1);
@$pb.TagNumber(2)
$core.String get name => $_getSZ(1);
@$pb.TagNumber(2)
set name($core.String value) => $_setString(1, value);
@$pb.TagNumber(2)
$core.bool hasName() => $_has(1);
@$pb.TagNumber(2)
void clearName() => $_clearField(2);
@$pb.TagNumber(3)
$core.String get email => $_getSZ(2);
@$pb.TagNumber(3)
set email($core.String value) => $_setString(2, value);
@$pb.TagNumber(3)
$core.bool hasEmail() => $_has(2);
@$pb.TagNumber(3)
void clearEmail() => $_clearField(3);
@$pb.TagNumber(4)
$core.bool get isAdmin => $_getBF(3);
@$pb.TagNumber(4)
set isAdmin($core.bool value) => $_setBool(3, value);
@$pb.TagNumber(4)
$core.bool hasIsAdmin() => $_has(3);
@$pb.TagNumber(4)
void clearIsAdmin() => $_clearField(4);
}
const $core.bool _omitFieldNames =
$core.bool.fromEnvironment('protobuf.omit_field_names');
const $core.bool _omitMessageNames =
$core.bool.fromEnvironment('protobuf.omit_message_names');

View File

@@ -0,0 +1,11 @@
// This is a generated file - do not edit.
//
// Generated from user.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports

View File

@@ -0,0 +1,32 @@
// This is a generated file - do not edit.
//
// Generated from user.proto.
// @dart = 3.3
// ignore_for_file: annotate_overrides, camel_case_types, comment_references
// ignore_for_file: constant_identifier_names
// ignore_for_file: curly_braces_in_flow_control_structures
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_relative_imports
// ignore_for_file: unused_import
import 'dart:convert' as $convert;
import 'dart:core' as $core;
import 'dart:typed_data' as $typed_data;
@$core.Deprecated('Use userInfoDescriptor instead')
const UserInfo$json = {
'1': 'UserInfo',
'2': [
{'1': 'id', '3': 1, '4': 1, '5': 5, '10': 'id'},
{'1': 'name', '3': 2, '4': 1, '5': 9, '10': 'name'},
{'1': 'email', '3': 3, '4': 1, '5': 9, '10': 'email'},
{'1': 'is_admin', '3': 4, '4': 1, '5': 8, '10': 'isAdmin'},
],
};
/// Descriptor for `UserInfo`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List userInfoDescriptor = $convert.base64Decode(
'CghVc2VySW5mbxIOCgJpZBgBIAEoBVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRIUCgVlbWFpbB'
'gDIAEoCVIFZW1haWwSGQoIaXNfYWRtaW4YBCABKAhSB2lzQWRtaW4=');

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'test_page.dart';
void main() {
runApp(const MyApp());
@@ -109,6 +110,16 @@ class _MyHomePageState extends State<MyHomePage> {
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 20),
ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const TestPage()),
);
},
child: const Text('Go to WebSocket & WebRTC Test'),
),
],
),
),

131
lib/test_page.dart Normal file
View File

@@ -0,0 +1,131 @@
import 'package:flutter/material.dart';
import 'package:web_socket_channel/web_socket_channel.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart';
class TestPage extends StatefulWidget {
const TestPage({super.key});
@override
State<TestPage> createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> {
final TextEditingController _controller = TextEditingController();
WebSocketChannel? _channel;
final List<String> _messages = [];
final RTCVideoRenderer _localRenderer = RTCVideoRenderer();
@override
void initState() {
super.initState();
initRenderers();
}
Future<void> initRenderers() async {
await _localRenderer.initialize();
}
@override
void dispose() {
_channel?.sink.close();
_localRenderer.dispose();
super.dispose();
}
void _connectWebSocket() {
final url = _controller.text.trim();
if (url.isNotEmpty) {
setState(() {
_channel = WebSocketChannel.connect(Uri.parse(url));
_messages.clear();
});
}
}
void _sendMessage() {
if (_channel != null) {
_channel!.sink.add('Hello from Flutter!');
}
}
Future<void> _startWebRTC() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'facingMode': 'user',
},
};
try {
MediaStream stream = await navigator.mediaDevices.getUserMedia(mediaConstraints);
_localRenderer.srcObject = stream;
setState(() {});
} catch (e) {
print('Error getting user media: $e');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('WebSocket & WebRTC Test'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: [
TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: 'WebSocket URL (e.g. ws://echo.websocket.org)',
),
),
Row(
children: [
ElevatedButton(
onPressed: _connectWebSocket,
child: const Text('Connect'),
),
const SizedBox(width: 10),
ElevatedButton(
onPressed: _sendMessage,
child: const Text('Send Message'),
),
],
),
Expanded(
child: StreamBuilder(
stream: _channel?.stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
_messages.add(snapshot.data.toString());
}
return ListView.builder(
itemCount: _messages.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(_messages[index]),
);
},
);
},
),
),
const Divider(),
const Text('WebRTC Test:'),
ElevatedButton(
onPressed: _startWebRTC,
child: const Text('Start Camera'),
),
SizedBox(
height: 200,
child: RTCVideoView(_localRenderer),
),
],
),
),
);
}
}