如何修复在flutter webview中无法访问视频流(NotAllowedError)以使用html5 + webRTC相机api?
Posted
技术标签:
【中文标题】如何修复在flutter webview中无法访问视频流(NotAllowedError)以使用html5 + webRTC相机api?【英文标题】:How to fix cannot access video stream (NotAllowedError) in flutter webview to use html5+webRTC camera api? 【发布时间】:2019-10-25 21:58:45 【问题描述】:我正在使用 instascan.min js 库来扫描我的 web angular js 应用程序中的二维码。但在我颤动的网页视图中,我收到一个错误 - 无法访问视频流 (NotAllowedError),我无法修复它。我对flutter了解不多。
我已尝试授予相机访问权限,但它不起作用。
//#我的 Flutter 代码
import 'package:flutter/material.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
void main () => runApp(MyApp());
class MyApp extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'FOTOFACE WALLET',
debugShowCheckedModeBanner: false,
home: Home(),
);
class Home extends StatefulWidget
@override
_HomeState createState() => _HomeState();
class _HomeState extends State<Home>
@override
Widget build(BuildContext context)
return WebviewScaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(0),
child: AppBar(
automaticallyImplyLeading: true, // hides leading widget
backgroundColor: new Color(0xFF404E67),
)
),
url: "https://fotofacewallet.com",
initialChild: Center(
child: CircularProgressIndicator(),
),
);
//# 这是我在 Angular js 控制器中的扫描仪代码
$scope.scan = () =>
var overlay = $('.overlay'),
close = $('<div class="close" id="closescanbtn">close</div>');
overlay.append(close);
let scanner = new Instascan.Scanner(
video: document.getElementById('preview')
);
scanner.addListener('scan', function (content)
scanner.stop();
$('.overlay').fadeOut();
$('.overlay').hide();
$scope.scanpayProcess(content);
);
Instascan.Camera.getCameras().then(function (cameras)
if (cameras.length > 0)
if(cameras[1])
scanner.start(cameras[1]);
else
scanner.start(cameras[0]);
else
alert('No cameras found.');
).catch(function (e)
alert(e);
);
$('.overlay').show();
我希望在颤动的网络视图中看到相机视图。
【问题讨论】:
你找到解决方案了吗? 不,先生,还没有 我们必须使用flutter_barcode_scanner插件来使相机工作。请尝试一次,今天晚上在正确测试后发布答案。 【参考方案1】:我做过类似的事情
在我的 index.html 页面或您在 Angular 侧创建的文件上
我添加了一个按钮:
<button type="button" onclick="displayMsg()" class="btn btn-default btn-login">Scan</button>
并按如下方式处理其点击事件,我们将发布消息为“扫描”
<script type="text/javascript">
function displayMsg()
Print.postMessage("scan");
</script>
在我的 pubspec.yaml 文件中添加了这个包
flutter_barcode_scanner: ^0.1.5+1
运行flutter pub get
来更新依赖项
然后在我的 main.dart 文件中
我已经导入
import 'dart:async';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
在 main.dart 文件中添加下面的代码来处理在 displayMsg() 函数上触发的事件 - 'scan' 消息
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Print',
onMessageReceived: (JavascriptMessage message)
if(message.message == 'scan')
print(message.message);
sBarcode(MyApp());
),
].toSet();
sBarcode(someVal) async
String bCode = await FlutterBarcodeScanner.scanBarcode("#ff6666", "Cancel", true); print(bCode);
someVal.enterBarcode(bCode); // to get the scanned barcode
return;
enterBarcode(barc)
flutterWebViewPlugin.evalJavascript("document.getElementById('yourtextboxid').value="
+ barc);
这就是我完整的 main.dart 文件现在的样子
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';
import 'package:flutter_webview_plugin/flutter_webview_plugin.dart';
const kandroidUserAgent =
'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/62.0.3202.94 Mobile Safari/537.36';
String selectedUrl = 'add_your_url_here';
// ignore: prefer_collection_literals
final Set<JavascriptChannel> jsChannels = [
JavascriptChannel(
name: 'Print',
onMessageReceived: (JavascriptMessage message)
if(message.message == 'scan')
//MyApp.startBarcode();
print(message.message);
sBarcode(MyApp());
),
].toSet();
sBarcode(someVal) async
String bCode = await FlutterBarcodeScanner.scanBarcode("#ff6666", "Cancel", true);
print(bCode);
someVal.enterBarcode(bCode);
return;
void main() => runApp(MyApp());
class MyApp extends StatelessWidget
final flutterWebViewPlugin = FlutterWebviewPlugin();
enterBarcode(barc)
flutterWebViewPlugin.evalJavascript("document.getElementById('barcodenumber').value=" + barc);
@override
Widget build(BuildContext context)
return MaterialApp(
title: 'Flutter WebView Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
routes:
// '/': (_) => const MyHomePage(title: 'Flutter WebView Demo'),
'/': (_)
return WebviewScaffold(
url: selectedUrl,
javascriptChannels: jsChannels,
withZoom: true,
withLocalStorage: true,
withJavascript: true,
hidden: true,
initialChild: Container(
color: Colors.white,
child: const Center(
child: Text('Loading...'),
),
),
);
,
,
);
class MyHomePage extends StatefulWidget
const MyHomePage(Key key, this.title) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
class _MyHomePageState extends State<MyHomePage>
// Instance of WebView plugin
final flutterWebViewPlugin = FlutterWebviewPlugin();
// On destroy stream
StreamSubscription _onDestroy;
// On urlChanged stream
StreamSubscription<String> _onUrlChanged;
// On urlChanged stream
StreamSubscription<WebViewStateChanged> _onStateChanged;
StreamSubscription<WebViewHttpError> _onHttpError;
StreamSubscription<double> _onProgressChanged;
StreamSubscription<double> _onScrollYChanged;
StreamSubscription<double> _onScrollXChanged;
final _urlCtrl = TextEditingController(text: selectedUrl);
final _codeCtrl = TextEditingController(text: 'window.navigator.userAgent');
final _scaffoldKey = GlobalKey<ScaffoldState>();
final _history = [];
@override
void initState()
super.initState();
flutterWebViewPlugin.close();
_urlCtrl.addListener(()
selectedUrl = _urlCtrl.text;
);
// Add a listener to on destroy WebView, so you can make came actions.
_onDestroy = flutterWebViewPlugin.onDestroy.listen((_)
if (mounted)
// Actions like show a info toast.
_scaffoldKey.currentState.showSnackBar(
const SnackBar(content: const Text('Webview Destroyed')));
);
// Add a listener to on url changed
_onUrlChanged = flutterWebViewPlugin.onUrlChanged.listen((String url)
if (mounted)
setState(()
_history.add('onUrlChanged: $url');
);
);
_onProgressChanged =
flutterWebViewPlugin.onProgressChanged.listen((double progress)
if (mounted)
setState(()
_history.add('onProgressChanged: $progress');
);
);
_onScrollYChanged =
flutterWebViewPlugin.onScrollYChanged.listen((double y)
if (mounted)
setState(()
_history.add('Scroll in Y Direction: $y');
);
);
_onScrollXChanged =
flutterWebViewPlugin.onScrollXChanged.listen((double x)
if (mounted)
setState(()
_history.add('Scroll in X Direction: $x');
);
);
_onStateChanged =
flutterWebViewPlugin.onStateChanged.listen((WebViewStateChanged state)
print(state.type);
if (mounted)
setState(()
_history.add('onStateChanged: $state.type $state.url');
);
);
_onHttpError =
flutterWebViewPlugin.onHttpError.listen((WebViewHttpError error)
if (mounted)
setState(()
_history.add('onHttpError: $error.code $error.url');
);
);
@override
void dispose()
// Every listener should be canceled, the same should be done with this stream.
_onDestroy.cancel();
_onUrlChanged.cancel();
_onStateChanged.cancel();
_onHttpError.cancel();
_onProgressChanged.cancel();
_onScrollXChanged.cancel();
_onScrollYChanged.cancel();
flutterWebViewPlugin.dispose();
super.dispose();
@override
Widget build(BuildContext context)
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(
title: const Text('Plugin example app'),
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
padding: const EdgeInsets.all(24.0),
child: TextField(controller: _urlCtrl),
),
RaisedButton(
onPressed: ()
flutterWebViewPlugin.launch(
selectedUrl,
rect: Rect.fromLTWH(
0.0, 0.0, MediaQuery.of(context).size.width, 300.0),
userAgent: kAndroidUserAgent,
invalidUrlRegex:
r'^(https).+(twitter)', // prevent redirecting to twitter when user click on its icon in flutter website
);
,
child: const Text('Open Webview (rect)'),
),
RaisedButton(
onPressed: ()
flutterWebViewPlugin.launch(selectedUrl, hidden: true);
,
child: const Text('Open "hidden" Webview'),
),
RaisedButton(
onPressed: ()
flutterWebViewPlugin.launch(selectedUrl);
,
child: const Text('Open Fullscreen Webview'),
),
RaisedButton(
onPressed: ()
Navigator.of(context).pushNamed('/widget');
,
child: const Text('Open widget webview'),
),
Container(
padding: const EdgeInsets.all(24.0),
child: TextField(controller: _codeCtrl),
),
RaisedButton(
onPressed: ()
final future =
flutterWebViewPlugin.evalJavascript(_codeCtrl.text);
future.then((String result)
setState(()
_history.add('eval: $result');
);
);
,
child: const Text('Eval some javascript'),
),
RaisedButton(
onPressed: ()
setState(()
_history.clear();
);
flutterWebViewPlugin.close();
,
child: const Text('Close'),
),
RaisedButton(
onPressed: ()
flutterWebViewPlugin.getCookies().then((m)
setState(()
_history.add('cookies: $m');
);
);
,
child: const Text('Cookies'),
),
Text(_history.join('\n'))
],
),
),
);
检查和测试,让我知道这是否适合你,这在我的最后工作正常。 打印会在浏览器中生成错误,您需要在 android 或 ios 中进行测试。 希望这会有所帮助。
【讨论】:
太棒了!如果对您有用,请接受答案。 在实现 flutter_webview_plugin 时,我没有得到 javascriptChannels 选项。您正在使用其他版本吗?【参考方案2】:您会收到 Cannot access video stream (NotAllowedError)
错误,因为您需要向 webview 授予正确的权限。
对于Android,在AndroidManifest.xml
,你需要添加这些权限:
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.VIDEO_CAPTURE" />
<uses-permission android:name="android.permission.AUDIO_CAPTURE" />
但这还不够!要请求有关摄像头和麦克风的权限,您可以使用permission_handler 插件。
所以,对于 webview,你可以使用我的插件 flutter_inappwebview 并使用 Android 的 androidOnPermissionRequest
事件,这是当 WebView 请求访问指定资源的权限时触发的事件(即 Android 原生 @ 987654323@)。
在 Android 上使用 WebRTC 的示例:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:permission_handler/permission_handler.dart';
Future main() async
WidgetsFlutterBinding.ensureInitialized();
await Permission.camera.request();
await Permission.microphone.request();
runApp(MyApp());
class MyApp extends StatefulWidget
@override
_MyAppState createState() => new _MyAppState();
class _MyAppState extends State<MyApp>
@override
Widget build(BuildContext context)
return MaterialApp(
home: InAppWebViewPage()
);
class InAppWebViewPage extends StatefulWidget
@override
_InAppWebViewPageState createState() => new _InAppWebViewPageState();
class _InAppWebViewPageState extends State<InAppWebViewPage>
InAppWebViewController _webViewController;
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text("InAppWebView")
),
body: Container(
child: Column(children: <Widget>[
Expanded(
child: Container(
child: InAppWebView(
initialUrl: "https://appr.tc/r/158489234",
initialOptions: InAppWebViewGroupOptions(
crossPlatform: InAppWebViewOptions(
mediaPlaybackRequiresUserGesture: false,
debuggingEnabled: true,
),
),
onWebViewCreated: (InAppWebViewController controller)
_webViewController = controller;
,
androidOnPermissionRequest: (InAppWebViewController controller, String origin, List<String> resources) async
return PermissionRequestResponse(resources: resources, action: PermissionRequestResponseAction.GRANT);
),
),
),
]))
);
此示例使用https://appr.tc/ 上的房间158489234
,这是一个基于WebRTC (https://github.com/webrtc/apprtc) 的视频聊天演示应用。
要使其工作,您需要将选项 mediaPlaybackRequiresUserGesture
设置为 false
并实现(对于 Android)onPermissionRequest
事件。
【讨论】:
以上是关于如何修复在flutter webview中无法访问视频流(NotAllowedError)以使用html5 + webRTC相机api?的主要内容,如果未能解决你的问题,请参考以下文章
如何在颤动中修复“net::ERR_CLEARTEXT_NOT_PERMITTED”