feat: 添加媒体权限请求处理;更新 H5ShellPage 以支持音视频权限管理
This commit is contained in:
135
lib/main.dart
135
lib/main.dart
@@ -3,6 +3,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
|
||||
@@ -119,12 +120,21 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
||||
}
|
||||
|
||||
WebViewController _buildController(int lineIndex) {
|
||||
return WebViewController()
|
||||
return WebViewController(
|
||||
onPermissionRequest: (request) {
|
||||
unawaited(_handleWebViewPermissionRequest(request));
|
||||
},
|
||||
)
|
||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||
..addJavaScriptChannel(
|
||||
'OpenIMShell',
|
||||
onMessageReceived: (message) => _handleShellMessage(lineIndex, message),
|
||||
)
|
||||
..addJavaScriptChannel(
|
||||
'OpenIMFlutterShell',
|
||||
onMessageReceived: (message) =>
|
||||
_handleFlutterShellMessage(lineIndex, message),
|
||||
)
|
||||
..setBackgroundColor(_shellBackground)
|
||||
..setNavigationDelegate(
|
||||
NavigationDelegate(
|
||||
@@ -211,6 +221,35 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleFlutterShellMessage(int lineIndex, JavaScriptMessage message) {
|
||||
try {
|
||||
final decoded = jsonDecode(message.message);
|
||||
if (decoded is! Map) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (decoded['type']) {
|
||||
case 'requestMediaPermissions':
|
||||
final requestId = decoded['requestId']?.toString();
|
||||
if (requestId == null || requestId.isEmpty) {
|
||||
return;
|
||||
}
|
||||
unawaited(
|
||||
_handleShellMediaPermissionRequest(
|
||||
lineIndex: lineIndex,
|
||||
requestId: requestId,
|
||||
audio: decoded['audio'] == true,
|
||||
video: decoded['video'] == true,
|
||||
),
|
||||
);
|
||||
case 'openAppSettings':
|
||||
unawaited(openAppSettings());
|
||||
}
|
||||
} catch (_) {
|
||||
// Ignore malformed shell messages from web content.
|
||||
}
|
||||
}
|
||||
|
||||
void _scheduleShellCoverFallback(int lineIndex) {
|
||||
final slot = _lineSlots[lineIndex];
|
||||
slot.shellCoverFallbackTimer?.cancel();
|
||||
@@ -309,6 +348,88 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
||||
return _runJavaScriptSafely(lineIndex, script);
|
||||
}
|
||||
|
||||
Future<void> _handleShellMediaPermissionRequest({
|
||||
required int lineIndex,
|
||||
required String requestId,
|
||||
required bool audio,
|
||||
required bool video,
|
||||
}) async {
|
||||
final result = await _requestNativeMediaPermissions(
|
||||
audio: audio,
|
||||
video: video,
|
||||
);
|
||||
final payload = jsonEncode({
|
||||
'requestId': requestId,
|
||||
'granted': result.granted,
|
||||
'permanentlyDenied': result.permanentlyDenied,
|
||||
'restricted': result.restricted,
|
||||
});
|
||||
final script = '''
|
||||
(() => {
|
||||
try {
|
||||
window.dispatchEvent(new CustomEvent('openim-shell-media-permission-result', { detail: $payload }));
|
||||
} catch (_) {}
|
||||
})();
|
||||
''';
|
||||
await _runJavaScriptSafely(lineIndex, script);
|
||||
}
|
||||
|
||||
Future<void> _handleWebViewPermissionRequest(
|
||||
WebViewPermissionRequest request,
|
||||
) async {
|
||||
final requestsOnlySupportedMediaTypes = request.types.every(
|
||||
(type) =>
|
||||
type == WebViewPermissionResourceType.camera ||
|
||||
type == WebViewPermissionResourceType.microphone,
|
||||
);
|
||||
if (!requestsOnlySupportedMediaTypes) {
|
||||
await request.deny();
|
||||
return;
|
||||
}
|
||||
|
||||
final result = await _requestNativeMediaPermissions(
|
||||
audio: request.types.contains(WebViewPermissionResourceType.microphone),
|
||||
video: request.types.contains(WebViewPermissionResourceType.camera),
|
||||
);
|
||||
|
||||
if (result.granted) {
|
||||
await request.grant();
|
||||
} else {
|
||||
await request.deny();
|
||||
}
|
||||
}
|
||||
|
||||
Future<_NativeMediaPermissionResult> _requestNativeMediaPermissions({
|
||||
required bool audio,
|
||||
required bool video,
|
||||
}) async {
|
||||
final permissions = <Permission>[
|
||||
if (audio) Permission.microphone,
|
||||
if (video) Permission.camera,
|
||||
];
|
||||
|
||||
if (permissions.isEmpty) {
|
||||
return const _NativeMediaPermissionResult(granted: true);
|
||||
}
|
||||
|
||||
var granted = true;
|
||||
var permanentlyDenied = false;
|
||||
var restricted = false;
|
||||
|
||||
for (final permission in permissions) {
|
||||
final status = await permission.request();
|
||||
granted = granted && status.isGranted;
|
||||
permanentlyDenied = permanentlyDenied || status.isPermanentlyDenied;
|
||||
restricted = restricted || status.isRestricted;
|
||||
}
|
||||
|
||||
return _NativeMediaPermissionResult(
|
||||
granted: granted,
|
||||
permanentlyDenied: permanentlyDenied,
|
||||
restricted: restricted,
|
||||
);
|
||||
}
|
||||
|
||||
void _updateSlotUrl(int lineIndex, String? url) {
|
||||
if (url == null || lineIndex < 0 || lineIndex >= _lineSlots.length) {
|
||||
return;
|
||||
@@ -499,6 +620,18 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
||||
}
|
||||
}
|
||||
|
||||
class _NativeMediaPermissionResult {
|
||||
const _NativeMediaPermissionResult({
|
||||
required this.granted,
|
||||
this.permanentlyDenied = false,
|
||||
this.restricted = false,
|
||||
});
|
||||
|
||||
final bool granted;
|
||||
final bool permanentlyDenied;
|
||||
final bool restricted;
|
||||
}
|
||||
|
||||
class _H5LineWebViewSlot {
|
||||
_H5LineWebViewSlot({
|
||||
required this.line,
|
||||
|
||||
Reference in New Issue
Block a user