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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:webview_flutter/webview_flutter.dart';
|
import 'package:webview_flutter/webview_flutter.dart';
|
||||||
|
|
||||||
@@ -119,12 +120,21 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
WebViewController _buildController(int lineIndex) {
|
WebViewController _buildController(int lineIndex) {
|
||||||
return WebViewController()
|
return WebViewController(
|
||||||
|
onPermissionRequest: (request) {
|
||||||
|
unawaited(_handleWebViewPermissionRequest(request));
|
||||||
|
},
|
||||||
|
)
|
||||||
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
..setJavaScriptMode(JavaScriptMode.unrestricted)
|
||||||
..addJavaScriptChannel(
|
..addJavaScriptChannel(
|
||||||
'OpenIMShell',
|
'OpenIMShell',
|
||||||
onMessageReceived: (message) => _handleShellMessage(lineIndex, message),
|
onMessageReceived: (message) => _handleShellMessage(lineIndex, message),
|
||||||
)
|
)
|
||||||
|
..addJavaScriptChannel(
|
||||||
|
'OpenIMFlutterShell',
|
||||||
|
onMessageReceived: (message) =>
|
||||||
|
_handleFlutterShellMessage(lineIndex, message),
|
||||||
|
)
|
||||||
..setBackgroundColor(_shellBackground)
|
..setBackgroundColor(_shellBackground)
|
||||||
..setNavigationDelegate(
|
..setNavigationDelegate(
|
||||||
NavigationDelegate(
|
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) {
|
void _scheduleShellCoverFallback(int lineIndex) {
|
||||||
final slot = _lineSlots[lineIndex];
|
final slot = _lineSlots[lineIndex];
|
||||||
slot.shellCoverFallbackTimer?.cancel();
|
slot.shellCoverFallbackTimer?.cancel();
|
||||||
@@ -309,6 +348,88 @@ class _H5ShellPageState extends State<H5ShellPage> {
|
|||||||
return _runJavaScriptSafely(lineIndex, script);
|
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) {
|
void _updateSlotUrl(int lineIndex, String? url) {
|
||||||
if (url == null || lineIndex < 0 || lineIndex >= _lineSlots.length) {
|
if (url == null || lineIndex < 0 || lineIndex >= _lineSlots.length) {
|
||||||
return;
|
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 {
|
class _H5LineWebViewSlot {
|
||||||
_H5LineWebViewSlot({
|
_H5LineWebViewSlot({
|
||||||
required this.line,
|
required this.line,
|
||||||
|
|||||||
Reference in New Issue
Block a user