更新 AndroidManifest.xml 以启用明文流量;在 index.html 中替换 token 值
增强 WebviewPage 的清理和导航功能。
This commit is contained in:
@@ -21,7 +21,8 @@
|
|||||||
<application
|
<application
|
||||||
android:label="飞行极控录像工作台"
|
android:label="飞行极控录像工作台"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:usesCleartextTraffic="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
|
|||||||
@@ -32,8 +32,7 @@
|
|||||||
|
|
||||||
window.__appInstallData = true;
|
window.__appInstallData = true;
|
||||||
|
|
||||||
window.AppBridge.token = 'xxx';
|
window.AppBridge.token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjgxNDI4NTczNTY2NjY4ODAwLCJkYXRhIjp7fSwic3ViIjoiODE0Mjg1NzM1NjY2Njg4MDAiLCJleHAiOjE3ODM5MzM1ODQsIm5iZiI6MTc4MzMyODc4NCwiaWF0IjoxNzgzMzI4Nzg0fQ.r1wYfvpV-5HIgwUuvq1Or3jPgjc3AhfJmoV1PNP23-Y';
|
||||||
window.AppBridge.playId = 'xxx';
|
|
||||||
|
|
||||||
///TODO 后续业务
|
///TODO 后续业务
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
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/shared/widgets/app_webview.dart';
|
import 'package:recording_tool/features/scan_qrcode/pages/page_scan_qrcode.dart';
|
||||||
import 'package:recording_tool/shared/widgets/widgets.dart';
|
import 'package:recording_tool/shared/widgets/widgets.dart';
|
||||||
|
|
||||||
class AuthPageWidget extends StatefulWidget {
|
class AuthPageWidget extends StatefulWidget {
|
||||||
@@ -35,10 +35,14 @@ class _AuthPageWidgetState extends State<AuthPageWidget> {
|
|||||||
child: AppButton(
|
child: AppButton(
|
||||||
label: '确定',
|
label: '确定',
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
// AppNavigator.push(const ScanQrCodePage());
|
AppNavigator.push(const ScanQrCodePage());
|
||||||
AppNavigator.push(
|
// return;
|
||||||
const WebviewPage(assetPath: 'assets/html/index.html'),
|
// AppNavigator.push(
|
||||||
);
|
// const WebviewPage(
|
||||||
|
// url:
|
||||||
|
// 'http://192.168.1.64:5173/#/pages/h5/score-entry?eventId=10237&scheduleId=113716750536806400&playerId=74300708945531007',
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
},
|
},
|
||||||
variant: AppButtonVariant.secondary,
|
variant: AppButtonVariant.secondary,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import 'package:recording_tool/shared/widgets/app_bar.dart';
|
|||||||
|
|
||||||
class PushSteamTestWidget extends StatefulWidget {
|
class PushSteamTestWidget extends StatefulWidget {
|
||||||
const PushSteamTestWidget({super.key});
|
const PushSteamTestWidget({super.key});
|
||||||
|
final String steamKey = '20260708';
|
||||||
final String rtmpUrl = 'rtmp://192.168.1.245:19090/live/2026-07-07';
|
final String rtmpUrl = 'rtmp://192.168.1.180:19090/live/';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PushSteamTestWidget> createState() => _PushSteamTestWidgetState();
|
State<PushSteamTestWidget> createState() => _PushSteamTestWidgetState();
|
||||||
@@ -37,7 +37,11 @@ class _PushSteamTestWidgetState extends State<PushSteamTestWidget>
|
|||||||
setState(() => _isStreaming = false);
|
setState(() => _isStreaming = false);
|
||||||
debugPrint('推流失败: $reason');
|
debugPrint('推流失败: $reason');
|
||||||
},
|
},
|
||||||
onDisconnection: () => setState(() => _isStreaming = false),
|
onDisconnection: () => {
|
||||||
|
debugPrint('推流断开'),
|
||||||
|
|
||||||
|
setState(() => _isStreaming = false),
|
||||||
|
},
|
||||||
);
|
);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) => _initialize());
|
WidgetsBinding.instance.addPostFrameCallback((_) => _initialize());
|
||||||
}
|
}
|
||||||
@@ -55,13 +59,17 @@ class _PushSteamTestWidgetState extends State<PushSteamTestWidget>
|
|||||||
|
|
||||||
// 把服务端下发的完整 rtmp:// 地址拆成 "服务器地址" + "streamKey" 两部分
|
// 把服务端下发的完整 rtmp:// 地址拆成 "服务器地址" + "streamKey" 两部分
|
||||||
Future<void> _startPush() async {
|
Future<void> _startPush() async {
|
||||||
final uri = Uri.parse(widget.rtmpUrl);
|
// final uri = Uri.parse(widget.rtmpUrl);
|
||||||
final streamKey = uri.pathSegments.last;
|
final streamKey = widget.steamKey;
|
||||||
final baseUrl = widget.rtmpUrl.substring(
|
// final streamKey =
|
||||||
0,
|
// '${DateTime.timestamp()}-${Random(1000).nextInt(1000).toString()}';
|
||||||
widget.rtmpUrl.lastIndexOf(streamKey),
|
|
||||||
);
|
// debugPrint('streamKey- $streamKey ');
|
||||||
await _controller.startStreaming(streamKey: streamKey, url: baseUrl);
|
// final baseUrl = widget.rtmpUrl.substring(
|
||||||
|
// 0,
|
||||||
|
// widget.rtmpUrl.lastIndexOf(streamKey),
|
||||||
|
// );
|
||||||
|
await _controller.startStreaming(streamKey: streamKey, url: widget.rtmpUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _stopPush() => _controller.stopStreaming();
|
Future<void> _stopPush() => _controller.stopStreaming();
|
||||||
|
|||||||
@@ -39,10 +39,12 @@ 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');
|
||||||
|
Future.delayed(const Duration(milliseconds: 1)).then((_) {
|
||||||
|
AppNavigator.push(PushSteamTestWidget());
|
||||||
// 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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.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/webview_flutter.dart';
|
||||||
import 'package:webview_flutter_android/webview_flutter_android.dart';
|
import 'package:webview_flutter_android/webview_flutter_android.dart';
|
||||||
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
|
import 'package:webview_flutter_wkwebview/webview_flutter_wkwebview.dart';
|
||||||
@@ -8,8 +12,15 @@ class WebviewPage extends StatefulWidget {
|
|||||||
final String? url;
|
final String? url;
|
||||||
final String? title;
|
final String? title;
|
||||||
final String? assetPath;
|
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
|
@override
|
||||||
State<WebviewPage> createState() => WebviewPageState();
|
State<WebviewPage> createState() => WebviewPageState();
|
||||||
@@ -28,6 +39,16 @@ class WebviewPageState extends State<WebviewPage> {
|
|||||||
_initController();
|
_initController();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
final controller = _controller;
|
||||||
|
_controller = null;
|
||||||
|
if (controller != null) {
|
||||||
|
unawaited(_disposeWebViewController(controller));
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _initController() async {
|
Future<void> _initController() async {
|
||||||
if ((widget.url?.isEmpty ?? true) && widget.assetPath == null) {
|
if ((widget.url?.isEmpty ?? true) && widget.assetPath == null) {
|
||||||
return;
|
return;
|
||||||
@@ -52,7 +73,8 @@ class WebviewPageState extends State<WebviewPage> {
|
|||||||
},
|
},
|
||||||
onPageFinished: (String url) async {
|
onPageFinished: (String url) async {
|
||||||
debugPrint('webview - page finished: $url');
|
debugPrint('webview - page finished: $url');
|
||||||
await _injectPostMessageBridge(controller);
|
// await _injectPostMessageBridge(controller);
|
||||||
|
await _injectH5NeedData(controller);
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
},
|
},
|
||||||
@@ -107,43 +129,72 @@ class WebviewPageState extends State<WebviewPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!mounted) return;
|
if (!mounted) {
|
||||||
|
unawaited(_disposeWebViewController(controller));
|
||||||
|
return;
|
||||||
|
}
|
||||||
setState(() => _controller = controller);
|
setState(() => _controller = controller);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _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<void> _runWebViewCleanupStep(
|
||||||
|
String step,
|
||||||
|
Future<void> 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) {
|
void _onMessageReceived(JavaScriptMessage message) {
|
||||||
debugPrint('收到来自 WebView 的消息: ${message.message}');
|
debugPrint('收到来自 WebView 的消息: ${message.message}');
|
||||||
|
final jsonMap = json.decode(message.message);
|
||||||
|
if (jsonMap['type'] != null && jsonMap['type'] == 'navigator_pop') {
|
||||||
|
Navigator.of(context).maybePop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 脚本注入传输给h5 数据
|
/// 脚本注入传输给h5 数据
|
||||||
Future<void> _injectH5NeedData(WebViewController controller) async {
|
Future<void> _injectH5NeedData(WebViewController controller) async {
|
||||||
|
final token =
|
||||||
|
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOjgxNDI4NTczNTY2NjY4ODAwLCJkYXRhIjp7fSwic3ViIjoiODE0Mjg1NzM1NjY2Njg4MDAiLCJleHAiOjE3ODM5MzM1ODQsIm5iZiI6MTc4MzMyODc4NCwiaWF0IjoxNzgzMzI4Nzg0fQ.r1wYfvpV-5HIgwUuvq1Or3jPgjc3AhfJmoV1PNP23-Y';
|
||||||
try {
|
try {
|
||||||
await controller.runJavaScript('''
|
await controller.runJavaScript('''
|
||||||
(function () {
|
(function () {
|
||||||
if (window.__appBridgePostMessageBridgeInstalled) {
|
if (window.__appInstallData) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
window.__appInstallData = ${true};
|
||||||
|
|
||||||
window.__appBridgePostMessageBridgeInstalled = true;
|
window.AppBridge.token = '$token';
|
||||||
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) {
|
} catch (e) {
|
||||||
@@ -151,41 +202,6 @@ class WebviewPageState extends State<WebviewPage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _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() {
|
PlatformWebViewControllerCreationParams _createPlatformParams() {
|
||||||
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
|
if (WebViewPlatform.instance is WebKitWebViewPlatform) {
|
||||||
return WebKitWebViewControllerCreationParams(
|
return WebKitWebViewControllerCreationParams(
|
||||||
@@ -226,11 +242,48 @@ class WebviewPageState extends State<WebviewPage> {
|
|||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
|
|
||||||
return Stack(
|
return PopScope(
|
||||||
|
canPop: widget.canPop ?? true,
|
||||||
|
onPopInvokedWithResult: (didPop, result) async {
|
||||||
|
if (didPop) return;
|
||||||
|
|
||||||
|
final ok = await showDialog<bool>(
|
||||||
|
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: [
|
children: [
|
||||||
Positioned.fill(child: WebViewWidget(controller: controller)),
|
Positioned.fill(child: WebViewWidget(controller: controller)),
|
||||||
if (_isLoading) const Center(child: CircularProgressIndicator()),
|
if (_isLoading)
|
||||||
|
const Center(child: CircularProgressIndicator()),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user