From b21857f364059b9122e87e334199668a08ad62b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E9=94=8B?= <2535831261@qq.com> Date: Wed, 8 Jul 2026 09:37:38 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20apivideo=5Flive=5Fstream?= =?UTF-8?q?=20=E4=BE=9D=E8=B5=96=EF=BC=9B=20=E4=BF=AE=E6=94=B9=20Auth=20?= =?UTF-8?q?=E9=A1=B5=E9=9D=A2=E6=96=87=E6=9C=AC=E4=B8=BA=E2=80=9C=E8=BE=93?= =?UTF-8?q?=E5=85=A5=E6=89=A7=E8=A3=81=E5=8F=A3=E4=BB=A4=E2=80=9D=EF=BC=9B?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E5=85=AC=E7=94=A8=20AppBar=20=E7=BB=84?= =?UTF-8?q?=E4=BB=B6=20=E6=A8=A1=E6=8B=9F=E6=A0=B8=E5=BF=83=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E4=BA=A4=E4=BA=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/features/auth/pages/page_auth.dart | 5 +- .../scan_qrcode/pages/page_push_test.dart | 112 ++++++++++++++++++ .../scan_qrcode/pages/page_scan_qrcode.dart | 23 ++-- lib/shared/widgets/app_bar.dart | 31 +++++ pubspec.yaml | 1 + 5 files changed, 152 insertions(+), 20 deletions(-) create mode 100644 lib/features/scan_qrcode/pages/page_push_test.dart create mode 100644 lib/shared/widgets/app_bar.dart diff --git a/lib/features/auth/pages/page_auth.dart b/lib/features/auth/pages/page_auth.dart index 63e3dac..c135f88 100644 --- a/lib/features/auth/pages/page_auth.dart +++ b/lib/features/auth/pages/page_auth.dart @@ -20,10 +20,7 @@ class _AuthPageWidgetState extends State { SizedBox(height: 180.h), Text('裁判工作台', style: TextStyle(fontSize: 50, color: Colors.black)), SizedBox(height: 20.h), - Text( - 'Auth Page', - style: TextStyle(fontSize: 30, color: Colors.black), - ), + Text('输入执裁口令', style: TextStyle(fontSize: 30, color: Colors.black)), SizedBox(height: 20.h), SizedBox( diff --git a/lib/features/scan_qrcode/pages/page_push_test.dart b/lib/features/scan_qrcode/pages/page_push_test.dart new file mode 100644 index 0000000..317fbaf --- /dev/null +++ b/lib/features/scan_qrcode/pages/page_push_test.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; +import 'package:apivideo_live_stream/apivideo_live_stream.dart'; +import 'package:recording_tool/shared/widgets/app_bar.dart'; + +class PushSteamTestWidget extends StatefulWidget { + const PushSteamTestWidget({super.key}); + + final String rtmpUrl = 'rtmp://192.168.1.245:19090/live/2026-07-07'; + + @override + State createState() => _PushSteamTestWidgetState(); +} + +class _PushSteamTestWidgetState extends State + with WidgetsBindingObserver { + late final ApiVideoLiveStreamController _controller; + bool _ready = false; + bool _isStreaming = false; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + + _controller = ApiVideoLiveStreamController( + initialAudioConfig: AudioConfig(bitrate: 128000), + initialVideoConfig: VideoConfig.withDefaultBitrate( + resolution: Resolution.RESOLUTION_1080, + fps: 30, + ), + onConnectionSuccess: () => { + debugPrint('推流成功'), + + setState(() => _isStreaming = true), + }, + onConnectionFailed: (reason) { + setState(() => _isStreaming = false); + debugPrint('推流失败: $reason'); + }, + onDisconnection: () => setState(() => _isStreaming = false), + ); + WidgetsBinding.instance.addPostFrameCallback((_) => _initialize()); + } + + Future _initialize() async { + try { + await _controller.initialize(); + await _controller.startPreview(); + + setState(() => _ready = true); + } catch (e) { + debugPrint('初始化失败: $e'); + } + } + + // 把服务端下发的完整 rtmp:// 地址拆成 "服务器地址" + "streamKey" 两部分 + Future _startPush() async { + final uri = Uri.parse(widget.rtmpUrl); + final streamKey = uri.pathSegments.last; + final baseUrl = widget.rtmpUrl.substring( + 0, + widget.rtmpUrl.lastIndexOf(streamKey), + ); + await _controller.startStreaming(streamKey: streamKey, url: baseUrl); + } + + Future _stopPush() => _controller.stopStreaming(); + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.inactive) { + _controller.stop(); + } else if (state == AppLifecycleState.resumed) { + _controller.startPreview(); + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: myAppBar(context: context), + body: Stack( + children: [ + if (_ready) + ApiVideoCameraPreview(controller: _controller, fit: BoxFit.cover), + Positioned( + bottom: 32, + left: 0, + right: 0, + child: Center( + child: FloatingActionButton( + backgroundColor: _isStreaming ? Colors.red : Colors.green, + onPressed: _ready + ? (_isStreaming ? _stopPush : _startPush) + : null, + child: Icon(_isStreaming ? Icons.stop : Icons.circle), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/features/scan_qrcode/pages/page_scan_qrcode.dart b/lib/features/scan_qrcode/pages/page_scan_qrcode.dart index 192bca9..e9d8d1a 100644 --- a/lib/features/scan_qrcode/pages/page_scan_qrcode.dart +++ b/lib/features/scan_qrcode/pages/page_scan_qrcode.dart @@ -1,9 +1,10 @@ import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:recording_tool/app/router/app_navigator.dart'; +import 'package:recording_tool/features/scan_qrcode/pages/page_push_test.dart'; +import 'package:recording_tool/shared/widgets/app_bar.dart'; import 'package:recording_tool/shared/widgets/app_button.dart'; import 'package:recording_tool/shared/widgets/app_qr_scanner_dialog.dart'; -import 'package:recording_tool/shared/widgets/app_webview.dart'; class ScanQrCodePage extends StatefulWidget { const ScanQrCodePage({super.key}); @@ -16,18 +17,7 @@ class _AuthPageWidgetState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () { - Navigator.of(context).maybePop(); - }, - ), - centerTitle: true, - elevation: 0, - backgroundColor: Colors.transparent, - foregroundColor: Colors.black, - ), + appBar: myAppBar(context: context, title: '扫码'), body: Center( child: Column( children: [ @@ -49,9 +39,10 @@ class _AuthPageWidgetState extends State { final result = await AppQrScannerDialog.show(context); if (result == null || result.isEmpty) return; debugPrint('扫码结果: $result'); - AppNavigator.push( - const WebviewPage(url: 'https://www.dronex.cc/'), - ); + // AppNavigator.push( + // const WebviewPage(url: 'https://www.dronex.cc/'), + // ); + AppNavigator.push(const PushSteamTestWidget()); }, variant: AppButtonVariant.secondary, ), diff --git a/lib/shared/widgets/app_bar.dart b/lib/shared/widgets/app_bar.dart new file mode 100644 index 0000000..3e7904a --- /dev/null +++ b/lib/shared/widgets/app_bar.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +AppBar myAppBar({ + required BuildContext context, + String title = '', + Widget? titleWidget, +}) => AppBar( + leadingWidth: 56.w, + leading: IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: () { + Navigator.of(context).maybePop(); + }, + ), + title: + titleWidget ?? + Text( + title, + textAlign: TextAlign.center, + style: TextStyle( + color: Colors.white, + fontSize: 20.sp, + fontFamily: 'PingFang SC', + fontWeight: FontWeight.w400, + ), + ), + centerTitle: true, + backgroundColor: Colors.transparent, + elevation: 0, +); diff --git a/pubspec.yaml b/pubspec.yaml index 1cc1d6d..4669b82 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,6 +51,7 @@ dependencies: webview_flutter_android: ^4.13.0 webview_flutter_wkwebview: ^3.26.0 webview_flutter: ^4.14.0 + apivideo_live_stream: ^1.2.0 dev_dependencies: flutter_test: