Skip to content

Instantly share code, notes, and snippets.

@alexeyismirnov
Created June 20, 2019 03:18
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save alexeyismirnov/ff71b4ddfd29b650b20b20dc5249619a to your computer and use it in GitHub Desktop.
Save alexeyismirnov/ff71b4ddfd29b650b20b20dc5249619a to your computer and use it in GitHub Desktop.
MJPEG player written entirely in Flutter/Dart
import 'package:flutter/material.dart';
import 'mjpeg_view.dart';
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: 'MJPEG demo'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final video_url =
'http://harriscam.ce.rit.edu/mjpg/video.mjpg?resolution=352x288';
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(child: MjpegView(url: video_url)));
}
}
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:typed_data';
import 'dart:async';
class MjpegView extends StatefulWidget {
MjpegView({this.url, this.fps = 2});
String url;
int fps;
@override
MjpegViewState createState() => MjpegViewState();
}
class MjpegViewState extends State<MjpegView> {
Image mjpeg = null;
var imgBuf;
Stopwatch timer = Stopwatch();
http.Client client = http.Client();
StreamSubscription videoStream;
@override
void initState() {
super.initState();
var request = http.Request("GET", Uri.parse(widget.url));
client.send(request).then((response) {
var startIndex = -1;
var endIndex = -1;
List<int> buf = List<int>();
Duration ts = null;
timer.start();
videoStream = response.stream.listen((List<int> data) {
for (var i = 0; i < data.length - 1; i++) {
if (data[i] == 0xff && data[i + 1] == 0xd8) {
startIndex = buf.length + i;
}
if (data[i] == 0xff && data[i + 1] == 0xd9) {
endIndex = buf.length + i;
}
}
buf.addAll(data);
if (startIndex != -1 && endIndex != -1) {
// print('start $startIndex, end $endIndex');
timer.stop();
ts = timer.elapsed;
if (ts.inMilliseconds > 1000 / widget.fps) {
// print('duration ${ts.inMilliseconds / 1000}');
imgBuf = List<int>.from(buf.getRange(startIndex, endIndex + 2));
mjpeg = Image.memory(Uint8List.fromList(imgBuf));
precacheImage(mjpeg.image, context);
Future.delayed(const Duration(milliseconds: 100)).then((_) {
if (mounted) setState(() {});
});
timer.reset();
}
startIndex = endIndex = -1;
buf = List<int>();
timer.start();
}
});
});
}
@override
void deactivate() {
timer?.stop();
videoStream?.cancel();
client?.close();
super.deactivate();
}
@override
Widget build(BuildContext context) {
return mjpeg != null ? mjpeg : Center(child: CircularProgressIndicator());
}
}
@alexeyismirnov
Copy link
Author

That's great. Thanks.

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