Closed estevez-dev closed 3 years ago
Hello :)
This package doesn't pool jpg images every second, it read an mjpeg stream, maybe that's why you have this error. Because the url you give (http://cam2.sigulda.lv/trase.jpg) is just an image not a real stream.
For example this is a real mjpeg stream http://64.74.184.131:8080/mjpg/video.mjpg
Ah sorry, indeed mjpeg stream is formed on my server side. So the server is pooling this jpg every second and creates a stream. I can't give you a link because it needs authorization. Any way I tried to use your stream url and it works fine. So I'm assuming there is something wrong with my streams =( Will check, thanks.
There is multiple implementation of mjpeg streams, but to have it working with this package it needs this https://github.com/mylisabox/flutter_mjpeg/blob/master/lib/src/mjpeg.dart#L91
So each frame should be separated with trigger
, start with soi
and end with eoi
values. So nothing too complex. Give it a try. maybe the problem come from the authorization too.
Let me know how it goes :) good luck
Sorry to bother you with issues but I am getting the same type of error, a little bit different output. You can review it in below.
I followed the stack and it ends with mjpeg.dart, this line. So I commented the _httpClient.close();
line and error disappears. I just wonder what happens when I comment that line and would it cause any issues to my program?
By the way, I can not give a link to my stream however I use this service on my streaming server. I do not use a bare link such as [http://localhost:8000]() but instead I use [http://localhost:8000/stream/0]() on my app to watch the stream. It works fine when I do that.
Here is an example stream that I found on the Internet in case you want to review:
https://appointed-crab-2830.dataplicity.io/stream/0
E/flutter (15164): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: type '(HttpException) => Null' is not a subtype of type '(dynamic) => dynamic'
E/flutter (15164): #0 _invokeErrorHandler (dart:async/async_error.dart:18:23)
E/flutter (15164): #1 _HandleErrorStream._handleError (dart:async/stream_pipe.dart:288:9)
E/flutter (15164): #2 _ForwardingStreamSubscription._handleError (dart:async/stream_pipe.dart:170:13)
E/flutter (15164): #3 _rootRunBinary (dart:async/zone.dart:1146:38)
E/flutter (15164): #4 _CustomZone.runBinary (dart:async/zone.dart:1039:19)
E/flutter (15164): #5 _CustomZone.runBinaryGuarded (dart:async/zone.dart:941:7)
E/flutter (15164): #6 _BufferingStreamSubscription._sendError.sendError (dart:async/stream_impl.dart:357:15)
E/flutter (15164): #7 _BufferingStreamSubscription._sendError (dart:async/stream_impl.dart:375:16)
E/flutter (15164): #8 _BufferingStreamSubscription._addError (dart:async/stream_impl.dart:274:7)
E/flutter (15164): #9 _ForwardingStreamSubscription._addError (dart:async/stream_pipe.dart:139:11)
E/flutter (15164): #10 _addErrorWithReplacement (dart:async/stream_pipe.dart:190:8)
E/flutter (15164): #11 _HandleErrorStream._handleError (dart:async/stream_pipe.dart:293:11)
E/flutter (15164): #12 _ForwardingStreamSubscription._handleError (dart:async/stream_pipe.dart:170:13)
E/flutter (15164): #13 _rootRunBinary (dart:async/zone.dart:1146:38)
E/flutter (15164): #14 _CustomZone.runBinary (dart:async/zone.dart:1039:19)
E/flutter (15164): #15 _CustomZone.runBinaryGuarded (dart:async/zone.dart:941:7)
E/flutter (15164): #16 _BufferingStreamSubscription._sendError.sendError (dart:async/stream_impl.dart:357:15)
E/flutter (15164): #17 _BufferingStreamSubscription._sendError (dart:async/stream_impl.dart:375:16)
E/flutter (15164): #18 _BufferingStreamSubscription._addError (dart:async/stream_impl.dart:274:7)
E/flutter (15164): #19 _SyncStreamControllerDispatch._sendError (dart:async/stream_controller.dart:770:19)
E/flutter (15164): #20 _StreamController._addError (dart:async/stream_controller.dart:650:7)
E/flutter (15164): #21 _StreamController.addError (dart:async/stream_controller.dart:602:5)
E/flutter (15164): #22 _HttpParser._onDone (dart:_http/http_parser.dart:824:25)
E/flutter (15164): #23 _rootRun (dart:async/zone.dart:1122:38)
E/flutter (15164): #24 _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (15164): #25 _CustomZone.runGuarded (dart:async/zone.dart:925:7)
E/flutter (15164): #26 _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:391:13)
E/flutter (15164): #27 _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:401:15)
E/flutter (15164): #28 _BufferingStreamSubscription._close (dart:async/stream_impl.dart:285:7)
E/flutter (15164): #29 _SyncStreamControllerDispatch._sendDone (dart:async/stream_controller.dart:774:19)
E/flutter (15164): #30 _StreamController._closeUnchecked (dart:async/stream_controller.dart:631:7)
E/flutter (15164): #31 _StreamController.close (dart:async/stream_controller.dart:624:5)
E/flutter (15164): #32 _Socket._onDone (dart:io-patch/socket_patch.dart:1866:19)
E/flutter (15164): #33 _rootRun (dart:async/zone.dart:1122:38)
E/flutter (15164): #34 _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (15164): #35 _CustomZone.runGuarded (dart:async/zone.dart:925:7)
E/flutter (15164): #36 _BufferingStreamSubscription._sendDone.sendDone (dart:async/stream_impl.dart:391:13)
E/flutter (15164): #37 _BufferingStreamSubscription._sendDone (dart:async/stream_impl.dart:401:15)
E/flutter (15164): #38 _BufferingStreamSubscription._close (dart:async/stream_impl.dart:285:7)
E/flutter (15164): #39 _SyncStreamControllerDispatch._sendDone (dart:async/stream_controller.dart:774:19)
E/flutter (15164): #40 _StreamController._closeUnchecked (dart:async/stream_controller.dart:631:7)
E/flutter (15164): #41 _StreamController.close (dart:async/stream_controller.dart:624:5)
E/flutter (15164): #42 _RawSecureSocket._close (dart:io/secure_socket.dart:650:17)
E/flutter (15164): #43 _RawSecureSocket.shutdown (dart:io/secure_socket.dart:672:9)
E/flutter (15164): #44 _RawSecureSocket.close (dart:io/secure_socket.dart:625:5)
E/flutter (15164): #45 _Socket._closeRawSocket (dart:io-patch/socket_patch.dart:1822:9)
E/flutter (15164): #46 _Socket.destroy (dart:io-patch/socket_patch.dart:1754:5)
E/flutter (15164): #47 _HttpClientConnection.destroy (dart:_http/http_impl.dart:1870:13)
E/flutter (15164): #48 _ConnectionTarget.close (dart:_http/http_impl.dart:2033:11)
E/flutter (15164): #49 _HttpClient._closeConnections (dart:_http/http_impl.dart:2379:24)
E/flutter (15164): #50 _HttpClient.close (dart:_http/http_impl.dart:2227:5)
E/flutter (15164): #51 IOClient.close (package:http/src/io_client.dart:73:14)
E/flutter (15164): #52 _StreamManager.dispose (package:flutter_mjpeg/src/mjpeg.dart:130:17)
E/flutter (15164): #53
If you're not doing _httpClient.close();
it means the http stream is never closed, so you're leaking an http socket that will stay open forever. And next time you'll create another stream that will stay open too and at some point you're app will be slow or crash cause of memory.
with https://appointed-crab-2830.dataplicity.io/stream/0
you have the problem ? when exactly ?
I route to my LiveStream page, Mjpeg works fine with https://appointed-crab-2830.dataplicity.io/stream/0
url (in my case I have another url with exactly the same structure, this is just an example)
I can watch it live. When I pop back to my home screen, I get this error.
I understand that I need to close the http stream but when I uncomment the line that I mentioned, I get the error. What could be the cause? Or should I just ignore it?
It looks like the problem is in the dispose()
function.
Note: In both cases trying to establish a new connection after calling [close] will throw an exception.
The _StreamManager.dispose()
is a future.
The first argument of
useEffect
needs to return a function as a clean function. https://github.com/mylisabox/flutter_mjpeg/blob/01c3cd4c83544ee07feb4b37a188d00740e4267a/lib/src/mjpeg.dart#L70In definition of
useEffect
. When cleaning is needed, it is just called as a normal function. It does not wait for future execution to complete.Go back to
_StreamManager.dispose()
. It probably hasn't closed yet. But at the same time, the following code is ready to create a new HTTP connection. So the program went wrong. https://github.com/mylisabox/flutter_mjpeg/blob/01c3cd4c83544ee07feb4b37a188d00740e4267a/lib/src/mjpeg.dart#L64
In short, it‘s the problem that asynchrony and state are not well managed.
Interesting! To me useEffect callback is called when widget is disposed, so it mean the StreamManager will not be called anymore anyway.
useMemoized should then give me another instance of StreamManager so I should in fact be good.
that was my asumpsions but I'll double check that once I have time. I also happy to review and merged PR if you can reproduce and fix it for sure. On my side last time I checked I wasn't able to reproduce it
In my opinion, when any of the stream
, isLive
, visible
or timeout
was been changed, the manager
will be re-instantiated, resulting in the callback in useEffect
wall be called again.
Yes sorry I tried to type quickly because I was on my phone, but that's exactly it, meaning that useEffet is always disposing an old one, so we don't care if that one take time to be disposed as it's not used anymore. We basically always deal with a new one when a param is changing, so taking time to dispose shouldn't be a problem (at least in my mind :D). But again when I got time I'll dig more into this !
I didn't reproduce this error log. But I've proved that it does create a new HTTP connection before closing the previous one.
Add
final manager = useMemoized(() { print(">>>>>> [${DateTime.now()}] Create a new manager instance."); return _StreamManager(stream, isLive && visible.visible, headers, timeout); }, [stream, isLive, visible.visible]);
useEffect(() { print(">>>>>> [${DateTime.now()}] call useEffect."); errorState.value = null; manager.updateStream(context, image, errorState); return manager.dispose; }, [manager]);
Future<void> dispose() async { print(">>>>>> [${DateTime.now()}] dispose begin."); if (_subscription != null) { await _subscription!.cancel(); _subscription = null; } _httpClient.close(); print(">>>>>> [${DateTime.now()}] dispose end."); }
try { print(">>>>>> [${DateTime.now()}] Create a new HTTP connection."); final request = Request("GET", Uri.parse(stream));
Running example application, and tap the
Toggle
button. And then get the following log:flutter: >>>>>> [2021-06-10 10:44:21.484232] Create a new manager instance. flutter: >>>>>> [2021-06-10 10:44:21.489948] call useEffect. flutter: >>>>>> [2021-06-10 10:44:21.493157] Create a new HTTP connection. flutter: >>>>>> [2021-06-10 10:44:33.749914] Create a new manager instance. flutter: >>>>>> [2021-06-10 10:44:33.750066] call useEffect. flutter: >>>>>> [2021-06-10 10:44:33.750125] Create a new HTTP connection. flutter: >>>>>> [2021-06-10 10:44:33.751426] dispose begin. flutter: >>>>>> [2021-06-10 10:44:33.765322] dispose end. flutter: >>>>>> [2021-06-10 10:44:42.786749] dispose begin. flutter: >>>>>> [2021-06-10 10:44:42.787803] dispose end.
Your logs are correct, but they don't show the instance they are called on.
This is what I expect:
flutter: >>>>>> [2021-06-10 10:44:21.484232] Create a new manager instance. Instance 1
flutter: >>>>>> [2021-06-10 10:44:21.489948] call useEffect. Instance 1
flutter: >>>>>> [2021-06-10 10:44:21.493157] Create a new HTTP connection. Instance 1
flutter: >>>>>> [2021-06-10 10:44:33.749914] Create a new manager instance. Instance 2
flutter: >>>>>> [2021-06-10 10:44:33.750066] call useEffect. Instance 2
flutter: >>>>>> [2021-06-10 10:44:33.750125] Create a new HTTP connection. Instance 2
flutter: >>>>>> [2021-06-10 10:44:33.751426] dispose begin. Instance 1
flutter: >>>>>> [2021-06-10 10:44:33.765322] dispose end. Instance 1
flutter: >>>>>> [2021-06-10 10:44:42.786749] dispose begin. Instance 2
flutter: >>>>>> [2021-06-10 10:44:42.787803] dispose end. Instance 2
Basically it's totally fine that a new connection is created before the dispose if it's not on the same instance. Because each instance have his own HTTP client. So the disposed httpClient is actually not used when it's disposed.
I've done some research and found this: https://github.com/dart-lang/http/issues/418 Look like it was an issue on dart side that is now fixed. So I suggest just to close this one.
Agree?
Hi @jaumard, You're right. Sorry, I misunderstood it.
No problem happy to be pushed on that ^^ because I could have been wrong :D Thanks for that!
First of all thanks for you work on this plugin.
Trying to use version 1.2.5 on Android.
After first frame received and displayed I'm getting the error:
I have two different MJPEG streams and getting the same error for both of them. Here is one of them: http://cam2.sigulda.lv/trase.jpg