添加设备代码信息到 Android 和 iOS 平台信息;更新 Auth 页面以使用新的 TextEditingController 管理输入;重构推流测试页面以支持动态 RTMP URL 解析;删除不再需要的测试文件。

This commit is contained in:
2026-07-09 12:26:41 +08:00
parent 98a3381716
commit a1f6ea14cf
29 changed files with 267 additions and 1797 deletions
@@ -1,11 +1,18 @@
import 'package:apivideo_live_stream/apivideo_live_stream.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:recording_tool/features/scan_qrcode/utils/rtmp_stream_target.dart';
import 'package:recording_tool/shared/widgets/app_bar.dart';
import 'package:recording_tool/shared/widgets/app_toast.dart';
class PushSteamTestWidget extends StatefulWidget {
const PushSteamTestWidget({super.key});
final String steamKey = '20260708';
final String rtmpUrl = 'rtmp://192.168.1.180:19090/live/';
const PushSteamTestWidget({
super.key,
this.rtmpUrl =
'rtmp://192.168.1.245:19090/蔡依婷vs夏志豪_空中格斗赛_高中组/蔡依婷vs夏志豪_空中格斗赛_高中组',
});
final String rtmpUrl;
@override
State<PushSteamTestWidget> createState() => _PushSteamTestWidgetState();
@@ -57,19 +64,24 @@ class _PushSteamTestWidgetState extends State<PushSteamTestWidget>
}
}
// 把服务端下发的完整 rtmp:// 地址拆成 "服务器地址" + "streamKey" 两部分
Future<void> _startPush() async {
// final uri = Uri.parse(widget.rtmpUrl);
final streamKey = widget.steamKey;
// final streamKey =
// '${DateTime.timestamp()}-${Random(1000).nextInt(1000).toString()}';
// debugPrint('streamKey- $streamKey ');
// final baseUrl = widget.rtmpUrl.substring(
// 0,
// widget.rtmpUrl.lastIndexOf(streamKey),
// );
await _controller.startStreaming(streamKey: streamKey, url: widget.rtmpUrl);
try {
final target = RtmpStreamTarget.parse(widget.rtmpUrl);
await _controller.startStreaming(
streamKey: target.streamKey,
url: target.url,
);
} on FormatException catch (e) {
debugPrint('推流地址错误: ${e.message}');
AppToast.show(e.message);
} on PlatformException catch (e) {
final message = e.message ?? e.code;
debugPrint('推流失败: ${e.code} $message');
AppToast.show('推流失败: $message');
} catch (e) {
debugPrint('推流失败: $e');
AppToast.showError(e);
}
}
Future<void> _stopPush() => _controller.stopStreaming();
@@ -40,7 +40,7 @@ class _AuthPageWidgetState extends State<ScanQrCodePage> {
if (result == null || result.isEmpty) return;
debugPrint('扫码结果: $result');
Future.delayed(const Duration(milliseconds: 1)).then((_) {
AppNavigator.push(PushSteamTestWidget());
AppNavigator.push(PushSteamTestWidget(rtmpUrl: result));
// AppNavigator.push(
// const WebviewPage(url: 'https://www.dronex.cc/'),
// );
@@ -0,0 +1,33 @@
class RtmpStreamTarget {
const RtmpStreamTarget({required this.url, required this.streamKey});
final String url;
final String streamKey;
factory RtmpStreamTarget.parse(String value) {
final source = value.trim();
final uri = Uri.tryParse(source);
if (uri == null ||
(uri.scheme != 'rtmp' && uri.scheme != 'rtmps') ||
uri.host.isEmpty) {
throw const FormatException('推流地址必须是 rtmp:// 或 rtmps:// 开头的完整地址');
}
final pathSegments = uri.pathSegments
.where((segment) => segment.trim().isNotEmpty)
.toList(growable: false);
if (pathSegments.length < 2) {
throw const FormatException('推流地址路径必须包含 app 和 streamKey,例如 /app/xxxx');
}
final appPath = pathSegments.take(pathSegments.length - 1).join('/');
final streamKey = pathSegments.last;
final baseUrl = uri
.replace(path: '/$appPath', query: null, fragment: null)
.toString();
return RtmpStreamTarget(url: baseUrl, streamKey: streamKey);
}
String get fullUrl => '$url/$streamKey';
}