Skip to content

Instantly share code, notes, and snippets.

@CalvinGonsalves
Last active September 23, 2022 23:05
Show Gist options
  • Save CalvinGonsalves/3d01cf88a6660ca87068f17f1c99a5dd to your computer and use it in GitHub Desktop.
Save CalvinGonsalves/3d01cf88a6660ca87068f17f1c99a5dd to your computer and use it in GitHub Desktop.
open and close video stream from web cam in flutter web
import 'package:flutter/material.dart';
import 'dart:html' as html;
import 'dart:ui' as ui;
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Container(),
),
floatingActionButton: FloatingActionButton(
onPressed: () async {
await showDialog(
context: context,
builder: (BuildContext context) {
return FractionallySizedBox(
heightFactor: 0.5,
widthFactor: 0.5,
child: WebCam(),
);
});
},
// tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class WebCam extends StatefulWidget {
@override
_WebCamState createState() => _WebCamState();
}
class _WebCamState extends State<WebCam> {
static html.VideoElement _webcamVideoElement = html.VideoElement();
@override
void initState() {
super.initState();
// Register a webcam
// ignore: undefined_prefixed_name
ui.platformViewRegistry.registerViewFactory('webcamVideoElement',
(int viewId) {
getMedia();
return _webcamVideoElement;
});
}
getMedia() {
html.window.navigator.mediaDevices
.getUserMedia({"video": true}).then((streamHandle) {
_webcamVideoElement
..srcObject = streamHandle
..autoplay = true;
}).catchError((onError) {
print(onError);
});
}
switchCameraOff() {
if (_webcamVideoElement.srcObject != null &&
_webcamVideoElement.srcObject.active) {
var tracks = _webcamVideoElement.srcObject.getTracks();
//stopping tracks and setting srcObject to null to switch camera off
_webcamVideoElement.srcObject = null;
tracks.forEach((track) {
track.stop();
});
}
}
@override
void dispose() {
switchCameraOff();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Container(
child: new HtmlElementView(
key: UniqueKey(),
viewType: 'webcamVideoElement',
)),
Container(
child: Column(
children: [
RaisedButton(
child: Text('Play/Pause'),
onPressed: () async {
if (_webcamVideoElement.paused) {
_webcamVideoElement.play();
} else {
_webcamVideoElement.pause();
}
},
),
RaisedButton(
child: Text('Switch off'),
onPressed: () {
switchCameraOff();
},
),
RaisedButton(
child: Text('Switch on'),
onPressed: () {
if (_webcamVideoElement.srcObject == null) getMedia();
},
),
],
),
),
],
),
);
}
}
@Thithip
Copy link

Thithip commented Mar 23, 2021

Thanks for the Gist, very helpful! ✌️

@JayParikh20
Copy link

JayParikh20 commented Apr 22, 2021

Thanks for the Gist!
Put registerViewFactory in postFrameCallback

like this
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => ui.platformViewRegistry.registerViewFactory('webcamVideoElement', (int viewId) => _webcamVideoElement));

Add getMedia(), if required. Now it also works without it.

This also avoids having flickering issues when restarting the camera or camera from not stoping.

@CalvinGonsalves
Copy link
Author

Thanks for the Gist!

@JayParikh20 Im glad it helped 😃

Put registerViewFactory in postFrameCallback

like this
WidgetsBinding.instance.addPostFrameCallback((timeStamp) => ui.platformViewRegistry.registerViewFactory('webcamVideoElement', (int viewId) => _webcamVideoElement));

Registering it after the first frame will cause an error when rendering HtmlElementView since viewType of "webcamVideoElement" does not exist yet. Also, I haven't experienced flickering issue when testing on chrome browser.

Made a small change and Im using getUserMedia on the mediaDevices instead of navigator because it is not recommended

@JayParikh20
Copy link

Im using getUserMedia on the mediaDevices instead of navigator because it is not recommended

Oh got it, thanks for the info!

Registering it after the first frame will cause an error when rendering HtmlElementView since viewType of "webcamVideoElement" does not exist yet

Yes, true. I forgot to mention one more change that I did, made it return SizedBox when the webcamVideoElement viewType was null (with the help of some variables). The only reason I wanted to add postFramCallback was to remove getMedia() in the initState (to prevent the camera from opening when the page loads). But I guess the gist is perfect the way it is. thanks again! Other people can always look at the comments.

Did you find any way to record it?

@Thithip
Copy link

Thithip commented Apr 27, 2021

For information, I have to add 3 attributes to the HTML element VideoElement to make it works with Safari browser:

    // init the VideoElement
    _webcamVideoElement = html.VideoElement()
      // this 3 following attributes are mandatory for iOS Safari compatibility, thx to https://leemartin.dev/hello-webrtc-on-safari-11-e8bcb5335295
      ..setAttribute('autoplay', '')
      ..setAttribute('muted', '')
      ..setAttribute('playsinline', '');

@CalvinGonsalves
Copy link
Author

CalvinGonsalves commented May 1, 2021

Thanks @Thithip

@JayParikh20 I found a way to record the MediaStream. Got some help from link and MediaStream Recording API .

you can add this when you get the MediaStream from getUserMedia.

    import 'dart:js' as js;
    ...
    //_mediaRecorder is html.MediaRecorder type variable declared
   //streamHandle is the MediaStream
    _mediaRecorder =
          new html.MediaRecorder(streamHandle, {'mimeType': 'video/webm'});

      _mediaRecorder.addEventListener('dataavailable',
          (html.Event event) async {
        final chunks = <html.Blob>[];
        Completer<String> _completer = Completer<String>();
        final html.Blob blob = js.JsObject.fromBrowserObject(event)['data'];
      
        if (blob.size > 0) {
          chunks.add(blob);
        }

        if (_mediaRecorder.state == 'inactive') {

          final blob = html.Blob(chunks, 'video/webm');
          _completer.complete(html.Url.createObjectUrlFromBlob(blob));

          String pathFile = await _completer.future;

          print('url of video :: ${pathFile}');

          html.window.open(pathFile, '');
        }
   
      });

and you can create two additional button to start the recording _mediaRecorder.start() and _mediaRecorder.stop().

@JayParikh20
Copy link

@CalvinGonsalves Awesome! thank you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment