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());
}
}
@mytja
Copy link

mytja commented Mar 28, 2021

Thank you so much

@ValJoly
Copy link

ValJoly commented Sep 22, 2022

Hello, thanks for your work and this view but i just have a question. when i try to increase the framerate (number of fps in parameter) the view that stream my ip cam start flickering. Do you have any clue why it's doing that ?

@alexeyismirnov
Copy link
Author

Hello, thanks for your work and this view but i just have a question. when i try to increase the framerate (number of fps in parameter) the view that stream my ip cam start flickering. Do you have any clue why it's doing that ?

I really don't know, have not worked on this project for a few years now.

@elmcrest
Copy link

Hey, just wanted to add this refactored by ChatGPT (GPT-4) version which works for me:

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'dart:typed_data';
import 'dart:async';

class MjpegView extends StatefulWidget {
  final String? url;
  final int fps;

  const MjpegView({super.key, this.url, this.fps = 2});

  @override
  MjpegViewState createState() => MjpegViewState();
}

class MjpegViewState extends State<MjpegView> {
  Image? mjpeg;
  List<int>? imgBuf;
  final Stopwatch timer = Stopwatch();
  final http.Client client = http.Client();
  StreamSubscription? videoStream;

  @override
  void initState() {
    super.initState();

    final request = http.Request("GET", Uri.parse(widget.url!));

    client.send(request).then((response) {
      var startIndex = -1;
      var endIndex = -1;
      var buf = <int>[];

      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) {
          timer.stop();
          final ts = timer.elapsed;

          if (ts.inMilliseconds > 1000 ~/ widget.fps) {
            imgBuf = buf.sublist(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 = <int>[];
          timer.start();
        }
      });
    });
  }

  @override
  void deactivate() {
    timer.stop();
    videoStream?.cancel();
    client.close();
    super.deactivate();
  }

  @override
  Widget build(BuildContext context) {
    return mjpeg ?? const 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