如何修复在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?的主要内容,如果未能解决你的问题,请参考以下文章

如何修复颤振的 AndroidX 不兼容问题?

如何在颤动中修复“net::ERR_CLEARTEXT_NOT_PERMITTED”

如何在flutter中通过webview下载/创建pdf

如何使用 webview_flutter 包运行自定义 Javascript?

Flutter - 无法从 Webview 下载文件

webview_flutter3.0.4无法加载http图片