From 98a33817160d29d5bc19dc4bf58d580794e1755a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E9=94=8B?= <2535831261@qq.com> Date: Thu, 9 Jul 2026 10:10:53 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20AndroidManifest.xml=20?= =?UTF-8?q?=E4=BB=A5=E5=90=AF=E7=94=A8=E6=98=8E=E6=96=87=E6=B5=81=E9=87=8F?= =?UTF-8?q?=EF=BC=9B=E5=9C=A8=20index.html=20=E4=B8=AD=E6=9B=BF=E6=8D=A2?= =?UTF-8?q?=20token=20=E5=80=BC=20=E5=A2=9E=E5=BC=BA=20WebviewPage=20?= =?UTF-8?q?=E7=9A=84=E6=B8=85=E7=90=86=E5=92=8C=E5=AF=BC=E8=88=AA=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/src/main/AndroidManifest.xml | 3 +- assets/html/index.html | 3 +- lib/features/auth/pages/page_auth.dart | 14 +- .../scan_qrcode/pages/page_push_test.dart | 28 ++- .../scan_qrcode/pages/page_scan_qrcode.dart | 10 +- lib/shared/widgets/app_webview.dart | 183 +++++++++++------- 6 files changed, 154 insertions(+), 87 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 9d726ae..efaaff5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -21,7 +21,8 @@ + android:icon="@mipmap/ic_launcher" + android:usesCleartextTraffic="true"> { child: AppButton( label: '确定', onPressed: () { - // AppNavigator.push(const ScanQrCodePage()); - AppNavigator.push( - const WebviewPage(assetPath: 'assets/html/index.html'), - ); + AppNavigator.push(const ScanQrCodePage()); + // return; + // AppNavigator.push( + // const WebviewPage( + // url: + // 'http://192.168.1.64:5173/#/pages/h5/score-entry?eventId=10237&scheduleId=113716750536806400&playerId=74300708945531007', + // ), + // ); }, variant: AppButtonVariant.secondary, ), diff --git a/lib/features/scan_qrcode/pages/page_push_test.dart b/lib/features/scan_qrcode/pages/page_push_test.dart index 5cc30aa..5b8bd1b 100644 --- a/lib/features/scan_qrcode/pages/page_push_test.dart +++ b/lib/features/scan_qrcode/pages/page_push_test.dart @@ -4,8 +4,8 @@ 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'; + final String steamKey = '20260708'; + final String rtmpUrl = 'rtmp://192.168.1.180:19090/live/'; @override State createState() => _PushSteamTestWidgetState(); @@ -37,7 +37,11 @@ class _PushSteamTestWidgetState extends State setState(() => _isStreaming = false); debugPrint('推流失败: $reason'); }, - onDisconnection: () => setState(() => _isStreaming = false), + onDisconnection: () => { + debugPrint('推流断开'), + + setState(() => _isStreaming = false), + }, ); WidgetsBinding.instance.addPostFrameCallback((_) => _initialize()); } @@ -55,13 +59,17 @@ class _PushSteamTestWidgetState extends State // 把服务端下发的完整 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); + // 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); } Future _stopPush() => _controller.stopStreaming(); diff --git a/lib/features/scan_qrcode/pages/page_scan_qrcode.dart b/lib/features/scan_qrcode/pages/page_scan_qrcode.dart index e9d8d1a..eb2c30b 100644 --- a/lib/features/scan_qrcode/pages/page_scan_qrcode.dart +++ b/lib/features/scan_qrcode/pages/page_scan_qrcode.dart @@ -39,10 +39,12 @@ 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 PushSteamTestWidget()); + Future.delayed(const Duration(milliseconds: 1)).then((_) { + AppNavigator.push(PushSteamTestWidget()); + // AppNavigator.push( + // const WebviewPage(url: 'https://www.dronex.cc/'), + // ); + }); }, variant: AppButtonVariant.secondary, ), diff --git a/lib/shared/widgets/app_webview.dart b/lib/shared/widgets/app_webview.dart index 92e2f2a..140112d 100644 --- a/lib/shared/widgets/app_webview.dart +++ b/lib/shared/widgets/app_webview.dart @@ -1,5 +1,9 @@ +import 'dart:async'; +import 'dart:convert'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:recording_tool/app/router/app_navigator.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter_android/webview_flutter_android.dart'; import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart'; @@ -8,8 +12,15 @@ class WebviewPage extends StatefulWidget { final String? url; final String? title; final String? assetPath; + final bool? canPop; - const WebviewPage({super.key, this.url, this.title, this.assetPath}); + const WebviewPage({ + super.key, + this.url, + this.title, + this.assetPath, + this.canPop, + }); @override State createState() => WebviewPageState(); @@ -28,6 +39,16 @@ class WebviewPageState extends State { _initController(); } + @override + void dispose() { + final controller = _controller; + _controller = null; + if (controller != null) { + unawaited(_disposeWebViewController(controller)); + } + super.dispose(); + } + Future _initController() async { if ((widget.url?.isEmpty ?? true) && widget.assetPath == null) { return; @@ -52,7 +73,8 @@ class WebviewPageState extends State { }, onPageFinished: (String url) async { debugPrint('webview - page finished: $url'); - await _injectPostMessageBridge(controller); + // await _injectPostMessageBridge(controller); + await _injectH5NeedData(controller); if (!mounted) return; setState(() => _isLoading = false); }, @@ -107,43 +129,72 @@ class WebviewPageState extends State { return; } - if (!mounted) return; + if (!mounted) { + unawaited(_disposeWebViewController(controller)); + return; + } setState(() => _controller = controller); } + Future _disposeWebViewController(WebViewController controller) async { + await _runWebViewCleanupStep( + 'reset navigation delegate', + () => controller.setNavigationDelegate(NavigationDelegate()), + ); + await _runWebViewCleanupStep( + 'remove javascript channel', + () => controller.removeJavaScriptChannel(_javaScriptChannelName), + ); + await _runWebViewCleanupStep( + 'load blank page', + () => controller.loadRequest(Uri.parse('about:blank')), + ); + await _runWebViewCleanupStep('clear cache', controller.clearCache); + await _runWebViewCleanupStep( + 'clear local storage', + controller.clearLocalStorage, + ); + await _runWebViewCleanupStep( + 'clear cookies', + () => WebViewCookieManager().clearCookies(), + ); + } + + Future _runWebViewCleanupStep( + String step, + Future Function() cleanup, + ) async { + try { + debugPrint('webview - dispose cleanup at $step '); + + await cleanup(); + } catch (e) { + debugPrint('webview - dispose cleanup failed at $step: $e'); + } + } + void _onMessageReceived(JavaScriptMessage message) { debugPrint('收到来自 WebView 的消息: ${message.message}'); + final jsonMap = json.decode(message.message); + if (jsonMap['type'] != null && jsonMap['type'] == 'navigator_pop') { + Navigator.of(context).maybePop(); + } } /// 脚本注入传输给h5 数据 Future _injectH5NeedData(WebViewController controller) async { + final token = + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjgxNDI4NTczNTY2NjY4ODAwLCJkYXRhIjp7fSwic3ViIjoiODE0Mjg1NzM1NjY2Njg4MDAiLCJleHAiOjE3ODM5MzM1ODQsIm5iZiI6MTc4MzMyODc4NCwiaWF0IjoxNzgzMzI4Nzg0fQ.r1wYfvpV-5HIgwUuvq1Or3jPgjc3AhfJmoV1PNP23-Y'; try { await controller.runJavaScript(''' - (function () { - if (window.__appBridgePostMessageBridgeInstalled) { + (function () { + if (window.__appInstallData) { return; - } + } + window.__appInstallData = ${true}; - window.__appBridgePostMessageBridgeInstalled = true; - window.addEventListener('message', function (event) { - if (!window.AppBridge || !window.AppBridge.postMessage) { - return; - } + window.AppBridge.token = '$token'; - var payload = event.data; - if (typeof payload !== 'string') { - try { - payload = JSON.stringify(payload); - if (typeof payload === 'undefined') { - payload = String(event.data); - } - } catch (error) { - payload = String(payload); - } - } - - window.AppBridge.postMessage(payload); - }); })(); '''); } catch (e) { @@ -151,41 +202,6 @@ class WebviewPageState extends State { } } - Future _injectPostMessageBridge(WebViewController controller) async { - // try { - // await controller.runJavaScript(''' - // (function () { - // if (window.__appBridgePostMessageBridgeInstalled) { - // return; - // } - - // window.__appBridgePostMessageBridgeInstalled = true; - // window.addEventListener('message', function (event) { - // if (!window.AppBridge || !window.AppBridge.postMessage) { - // return; - // } - - // var payload = event.data; - // if (typeof payload !== 'string') { - // try { - // payload = JSON.stringify(payload); - // if (typeof payload === 'undefined') { - // payload = String(event.data); - // } - // } catch (error) { - // payload = String(payload); - // } - // } - - // window.AppBridge.postMessage(payload); - // }); - // })(); - // '''); - // } catch (e) { - // debugPrint('webview - inject postMessage bridge failed: $e'); - // } - } - PlatformWebViewControllerCreationParams _createPlatformParams() { if (WebViewPlatform.instance is WebKitWebViewPlatform) { return WebKitWebViewControllerCreationParams( @@ -226,11 +242,48 @@ class WebviewPageState extends State { return const Center(child: CircularProgressIndicator()); } - return Stack( - children: [ - Positioned.fill(child: WebViewWidget(controller: controller)), - if (_isLoading) const Center(child: CircularProgressIndicator()), - ], + return PopScope( + canPop: widget.canPop ?? true, + onPopInvokedWithResult: (didPop, result) async { + if (didPop) return; + + final ok = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: const Text("提示"), + content: const Text("是否返回上一页?"), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: const Text("取消"), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: const Text("确定"), + ), + ], + ), + ); + if (ok == true) { + AppNavigator.pop(); + } + }, + child: Scaffold( + body: Column( + children: [ + SizedBox(height: 48), + Expanded( + child: Stack( + children: [ + Positioned.fill(child: WebViewWidget(controller: controller)), + if (_isLoading) + const Center(child: CircularProgressIndicator()), + ], + ), + ), + ], + ), + ), ); } }