添加 apivideo_live_stream 依赖;
修改 Auth 页面文本为“输入执裁口令”; 添加公用 AppBar 组件 模拟核心流程交互
This commit is contained in:
@@ -20,10 +20,7 @@ class _AuthPageWidgetState extends State<AuthPageWidget> {
|
|||||||
SizedBox(height: 180.h),
|
SizedBox(height: 180.h),
|
||||||
Text('裁判工作台', style: TextStyle(fontSize: 50, color: Colors.black)),
|
Text('裁判工作台', style: TextStyle(fontSize: 50, color: Colors.black)),
|
||||||
SizedBox(height: 20.h),
|
SizedBox(height: 20.h),
|
||||||
Text(
|
Text('输入执裁口令', style: TextStyle(fontSize: 30, color: Colors.black)),
|
||||||
'Auth Page',
|
|
||||||
style: TextStyle(fontSize: 30, color: Colors.black),
|
|
||||||
),
|
|
||||||
SizedBox(height: 20.h),
|
SizedBox(height: 20.h),
|
||||||
|
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
|||||||
@@ -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<PushSteamTestWidget> createState() => _PushSteamTestWidgetState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PushSteamTestWidgetState extends State<PushSteamTestWidget>
|
||||||
|
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<void> _initialize() async {
|
||||||
|
try {
|
||||||
|
await _controller.initialize();
|
||||||
|
await _controller.startPreview();
|
||||||
|
|
||||||
|
setState(() => _ready = true);
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('初始化失败: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 把服务端下发的完整 rtmp:// 地址拆成 "服务器地址" + "streamKey" 两部分
|
||||||
|
Future<void> _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<void> _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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||||
import 'package:recording_tool/app/router/app_navigator.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_button.dart';
|
||||||
import 'package:recording_tool/shared/widgets/app_qr_scanner_dialog.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 {
|
class ScanQrCodePage extends StatefulWidget {
|
||||||
const ScanQrCodePage({super.key});
|
const ScanQrCodePage({super.key});
|
||||||
@@ -16,18 +17,7 @@ class _AuthPageWidgetState extends State<ScanQrCodePage> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: myAppBar(context: context, title: '扫码'),
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.of(context).maybePop();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
centerTitle: true,
|
|
||||||
elevation: 0,
|
|
||||||
backgroundColor: Colors.transparent,
|
|
||||||
foregroundColor: Colors.black,
|
|
||||||
),
|
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
@@ -49,9 +39,10 @@ class _AuthPageWidgetState extends State<ScanQrCodePage> {
|
|||||||
final result = await AppQrScannerDialog.show(context);
|
final result = await AppQrScannerDialog.show(context);
|
||||||
if (result == null || result.isEmpty) return;
|
if (result == null || result.isEmpty) return;
|
||||||
debugPrint('扫码结果: $result');
|
debugPrint('扫码结果: $result');
|
||||||
AppNavigator.push(
|
// AppNavigator.push(
|
||||||
const WebviewPage(url: 'https://www.dronex.cc/'),
|
// const WebviewPage(url: 'https://www.dronex.cc/'),
|
||||||
);
|
// );
|
||||||
|
AppNavigator.push(const PushSteamTestWidget());
|
||||||
},
|
},
|
||||||
variant: AppButtonVariant.secondary,
|
variant: AppButtonVariant.secondary,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
);
|
||||||
@@ -51,6 +51,7 @@ dependencies:
|
|||||||
webview_flutter_android: ^4.13.0
|
webview_flutter_android: ^4.13.0
|
||||||
webview_flutter_wkwebview: ^3.26.0
|
webview_flutter_wkwebview: ^3.26.0
|
||||||
webview_flutter: ^4.14.0
|
webview_flutter: ^4.14.0
|
||||||
|
apivideo_live_stream: ^1.2.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user