How to fix cannot access video stream (NotAllowedError) in flutter webview to use html5+webRTC camera api?

5,199

Solution 1

I have done something like this

on my index.html page or the file that you have created in your angular side

I have added one button :

<button type="button" onclick="displayMsg()" class="btn btn-default btn-login">Scan</button>

and handled its click event as below and we will post message as 'scan'

<script type="text/javascript">
            function displayMsg(){
                Print.postMessage("scan");
            }
        </script>

added this package in my pubspec.yaml file

flutter_barcode_scanner: ^0.1.5+1

do run flutter pub get to update dependencies

then on my main.dart file

I have imported

import 'dart:async';
import 'package:flutter_barcode_scanner/flutter_barcode_scanner.dart';

in main.dart file added below code to handle the event triggered on displayMsg() function - 'scan' message

  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);   }

This is how my complete main.dart file looks now

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'))
          ],
        ),
      ),
    );
  }
}

check and test and let me know if that works for you or not, This is working fine at my end. Print will generate error in browser you need to test this in android or ios. Hope this helps.

Solution 2

You get the Cannot access video stream (NotAllowedError) error because you need to grant the right permissions to the webview.

For Android, in the AndroidManifest.xml, you need to add these permissions:

<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" />

But this is not enough! To request permissions about the camera and microphone, you can use the permission_handler plugin.

So, for the webview, you can use my plugin flutter_inappwebview and use the androidOnPermissionRequest event for Android, that is an event fired when the WebView is requesting permission to access the specified resources (that is the Android native WebChromeClient.onPermissionRequest event).

An example of using WebRTC that works on Android:

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);
                      }
                  ),
                ),
              ),
            ]))
    );
  }
}

This example uses the room 158489234 on https://appr.tc/, that is a video chat demo app based on WebRTC (https://github.com/webrtc/apprtc). To get it work, you need to set the option mediaPlaybackRequiresUserGesture to false and implement (for Android) the onPermissionRequest event.

Share:
5,199
Vishal Bondre
Author by

Vishal Bondre

Updated on December 11, 2022

Comments

  • Vishal Bondre
    Vishal Bondre over 1 year

    I am using instascan.min js library to scan QR Codes in my web angular js application. but in my flutter web view, I got an error - Cannot access video stream (NotAllowedError), And I am unable to fix it. I don't have too much knowledge of flutter.

    I have tried to give permission to camera access but it's not working.

    //# My Flutter Code

    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(),
      ),
    );
    }
    }
    

    //# this is my scanner code in angular js controller

    $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();
    }
    

    I am expecting a camera view in a flutter web view.

    • Mac
      Mac over 4 years
      did you get a solution for this?
    • Vishal Bondre
      Vishal Bondre over 4 years
      no sir, Not Yet
    • Mac
      Mac over 4 years
      We have to use flutter_barcode_scanner plugin to make the camera work. Please try out once will post answer today evening after testing it properly.
  • Mac
    Mac over 4 years
    Great! please accept the answer if that works for you.
  • fideldonson
    fideldonson over 4 years
    When implementing the flutter_webview_plugin i dont get the javascriptChannels option. Are you working in another version?