Flutter: Video player not initialized - flutter

I have a video preview widget that takes either a string url or a video file. If the parameter is a String, it downloads the file from online/the cache. With this is mind, my implementation is as follows:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hero/helpers/cache_manager/cache_manager.dart';
import 'package:video_player/video_player.dart';
import 'package:chewie/chewie.dart';
class WaveVideoPreview extends StatefulWidget {
final File? videoFile;
final String? videoUrl;
WaveVideoPreview({this.videoFile, this.videoUrl});
#override
_WaveVideoPreviewState createState() => _WaveVideoPreviewState();
}
class _WaveVideoPreviewState extends State<WaveVideoPreview> {
late VideoPlayerController? _controller;
late ChewieController _chewieController;
void initState() {
super.initState();
_initAsync();
}
void _initAsync() async {
File? _videoFile = widget.videoFile;
if (_videoFile == null) {
_videoFile = await getVideo(_videoFile);
}
_controller = VideoPlayerController.file(_videoFile!)
..initialize().then((_) {
setState(() {
_chewieController = ChewieController(
videoPlayerController: _controller!,
aspectRatio: _controller!.value.aspectRatio,
autoPlay: false,
looping: true,
allowFullScreen: false,
);
});
});
}
Future<File?> getVideo(File? _videoFile) async {
_videoFile = await MyCache.getVideo(widget.videoUrl!);
return _videoFile;
}
#override
Widget build(BuildContext context) {
return Container(
height: 200.0,
child: (_controller?.value.isInitialized ?? false)
? Chewie(
controller: _chewieController,
)
: SizedBox.shrink(),
);
}
#override
void dispose() {
_controller!.dispose();
_chewieController.dispose();
super.dispose();
}
}
with
static Future<File?> getVideo(String url) async {
DefaultCacheManager _cacheManager = DefaultCacheManager();
File? file = await _cacheManager.getSingleFile(url);
return file;
}
Which is throwing the error:
════════ Exception caught by widgets library ═══════════════════════════════════
The following LateError was thrown building WaveVideoPreview(dirty, state: _WaveVideoPreviewState#659ef):
LateInitializationError: Field '_controller#1875314998' has not been initialized.
The relevant error-causing widget was
WaveVideoPreview
lib/…/widget/wave_tile.dart:84
When the exception was thrown, this was the stack
#0 _WaveVideoPreviewState._controller (package:hero/screens/home/home_screens/views/waves/widget/video/wave_video_preview.dart)
package:hero/…/video/wave_video_preview.dart:1
#1 _WaveVideoPreviewState.build
package:hero/…/video/wave_video_preview.dart:57
Anyone know whats going on? I've tried changing the video player between nullable and non nullable, but still to no avail. Also as you can see i have a null check, but still nothing.

_initAsync is using async and it will take some frame to initialize the controllers.
It would be better to use FutureBuilder for this. or make those nullable and the do a null check while using it.
VideoPlayerController? _controller;
ChewieController? _chewieController;

Related

Flutter LateError on controller has not been initialized

When I try to call my widget it's showing an error on the controller that _controller is not initialized I try to set it in initstate.
class CameraApp extends StatefulWidget {
final dynamic loadingWidget;
CameraApp(this.loadingWidget);
_CameraAppState createState() => _CameraAppState();
}
class _CameraAppState extends State<CameraApp> with WidgetsBindingObserver {
late List<CameraDescription> _cameras;
late CameraController _controller;
int _selected = 0;
#override
void initState() {
CameraController _controller;
super.initState();
setupCamera();
WidgetsBinding.instance!.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance!.addObserver(this);
_controller.dispose();
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
if (_controller == null || !_controller.value.isInitialized) {
return;
}
if (state == AppLifecycleState.inactive) {
_controller.dispose();
} else if (state == AppLifecycleState.resumed) {
setupCamera();
}
}
#override
Widget build(BuildContext context) {
if (_controller == null) {
if (widget.loadingWidget != null) {
return widget.loadingWidget;
} else {
return Container(
color: Colors.black,
);
}
} else {
return CameraPreview(_controller);
}
}
Future<void> setupCamera() async {
await [
Permission.camera,
].request();
_cameras = await availableCameras();
var controller = await selectCamera();
setState(() => _controller = controller);
}
selectCamera() async {
var controller =
CameraController(_cameras[_selected], ResolutionPreset.max);
await controller.initialize();
return controller;
}
toggleCamera() async {
int newSelected = (_selected + 1) % _cameras.length;
_selected = newSelected;
var controller = await selectCamera();
setState(() => _controller = controller);
}
}
I am showing this camera on some widgets but don't figure out how to solve this issue. Maybe because of late it's causing an issue. Showing every time when its load i also try to add contoller.initialize(); in initstate but not working
LateError means a variable declared using the late keyword has not been initialized by the time you try to use it, as a general rule, I try to never use the late keyword unless there is no better way to achieve what I want because it tends to cause hard to find errors.
So you have two late variables, _controller and _cameras.
both initialize on the setupCamera method, which is asynchronous and gets called on initState, but the problem I believe is that initState does not wait for them to finish initializing before running build, where you try to read _controller and, because you have yet to assign it, you get a LateError.
If my assertion is correct, it should be a relatively simple fix:
from:
late List<CameraDescription> _cameras;
late CameraController _controller;
to:
List<CameraDescription> _cameras = []; // could also be null I guess.
CameraController _controller = null;
You already have null checks everywhere in case _controller is null, I believe you should take advantage of that so that if build runs before _controller has a value assigned, you get the loading widget.
CameraController _controller = null;
It can't take null value.

Error: Bad state: Cannot add new events after calling close

I am using '''youtube_player_iframe: ^2.1.0''' package for displaying YouTube video in Flutter web app. The video is playing absolutely fine but it gives this error whenever i tap on full screen to play video in full screen also it do not make the video to come on full screen and return to its previous size
ERROR
Error: Bad state: Cannot add new events after calling close
at Object.throw_ [as throw] (http://localhost:42339/dart_sdk.js:5041:11)
at _AsyncBroadcastStreamController.new.add (http://localhost:42339/dart_sdk.js:31586:44)
at controller.YoutubePlayerController.new.add (http://localhost:42339/packages/youtube_player_iframe/src/helpers/youtube_value_builder.dart.lib.js:894:32)
at http://localhost:42339/packages/youtube_player_iframe/src/helpers/youtube_value_builder.dart.lib.js:469:29
at Object._checkAndCall (http://localhost:42339/dart_sdk.js:5246:16)
at Object.dcall (http://localhost:42339/dart_sdk.js:5251:17)
at http://localhost:42339/dart_sdk.js:100646:100
Error: Bad state: Cannot add new events after calling close
at Object.throw_ [as throw] (http://localhost:42339/dart_sdk.js:5041:11)
at _AsyncBroadcastStreamController.new.add (http://localhost:42339/dart_sdk.js:31586:44)
at controller.YoutubePlayerController.new.add (http://localhost:42339/packages/youtube_player_iframe/src/helpers/youtube_value_builder.dart.lib.js:894:32)
at http://localhost:42339/packages/youtube_player_iframe/src/helpers/youtube_value_builder.dart.lib.js:469:29
at Object._checkAndCall (http://localhost:42339/dart_sdk.js:5246:16)
at Object.dcall (http://localhost:42339/dart_sdk.js:5251:17)
at http://localhost:42339/dart_sdk.js:100646:100
CODE :
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:youtube_player_iframe/youtube_player_iframe.dart';
class YoutubePlayerWeb extends StatefulWidget {
final String url;
const YoutubePlayerWeb({Key? key, required this.url}) : super(key: key);
#override
_YoutubePlayerState createState() => _YoutubePlayerState();
}
class _YoutubePlayerState extends State<YoutubePlayerWeb> {
late YoutubePlayerController _controller;
void runYoutubePlay()
{
_controller = YoutubePlayerController(
initialVideoId: YoutubePlayerController.convertUrlToId(widget.url).toString(),
params: const YoutubePlayerParams(
showControls: true,
desktopMode: true,
showFullscreenButton: true,
privacyEnhanced: true,
showVideoAnnotations: true ,
autoPlay: false,
enableCaption: true,
color: 'red',
)
);
}
void youtubePlayerFullScreen()
{
_controller.onEnterFullscreen = ()
{
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
]);
print("ENTERED FULLSCREEN");
};
_controller.onExitFullscreen = ()
{
print("EXITED FULLSCREEN");
};
}
#override
void initState() {
runYoutubePlay();
youtubePlayerFullScreen();
super.initState();
}
#override
void dispose() {
_controller.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
const player = YoutubePlayerIFrame();
return YoutubePlayerControllerProvider(controller: _controller, child: player);
}
}
Please Help me and please tell where am i going wrong ?
The cause of the issue is that an event is being tried to be used on YoutubePlayerController after it has been closed on dispose(). A checker can be used to see if _controller is still open.
if (!_controller.isClosed){
// Add events
}

Flutter web - Geolocator not working when uploaded to server

everyone.
I'm trying to develop a PWA with flutter 2.2.1 that shows a map using Mapbox_gl and displays the user current location using Geolocator.
So far everything works as expected while debuging the app, but when I run:
flutter build
or
flutter build --release
and then run
firebase deploy
the site gets uploaded, the map shows as intended and it asks for permissions but the user's location is never shown and Google Chrome's Console throws this error:
Uncaught TypeError: m.gfR is not a function
at Object.avh (main.dart.js:20405)
at main.dart.js:65755
at aiD.a (main.dart.js:5853)
at aiD.$2 (main.dart.js:34394)
at ahm.$1 (main.dart.js:34386)
at Rx.o1 (main.dart.js:35356)
at adi.$0 (main.dart.js:34770)
at Object.tQ (main.dart.js:5975)
at a5.mn (main.dart.js:34687)
at ada.$0 (main.dart.js:34731)
Here's the code I'm using on flutter:
mapbox.dart
import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:geolocator/geolocator.dart';
import 'package:kkc/main.dart';
import 'package:mapbox_gl/mapbox_gl.dart';
import 'package:kkc/services/location_service.dart';
class Mapbox extends StatefulWidget {
const Mapbox();
#override
State createState() => MapboxState();
}
class MapboxState extends State<Mapbox> {
final Random _rnd = new Random();
Position? _currentLocation;
LatLng _currentCoordinates = new LatLng(0,0);
final List<_PositionItem> _positionItems = <_PositionItem>[];
StreamSubscription<Position>? _positionStreamSubscription;
late MapboxMapController _mapController;
List<Marker> _markers = [];
List<_MarkerState> _markerStates = [];
CameraPosition _kInitialPosition = CameraPosition(
target: LatLng(19.4274418, -99.1682147),
zoom: 18.0,
tilt: 70,
);
void _addMarkerStates(_MarkerState markerState) {
_markerStates.add(markerState);
}
void _onMapCreated(MapboxMapController controller) {
_mapController = controller;
controller.addListener(() {
if (controller.isCameraMoving) {
_updateMarkerPosition();
}
});
}
void _onStyleLoadedCallback() {
_updateMarkerPosition();
}
void _onCameraIdleCallback() {
_updateMarkerPosition();
}
void _updateMarkerPosition() {
final coordinates = <LatLng>[];
for (final markerState in _markerStates) {
coordinates.add(markerState.getCoordinate());
}
_mapController.toScreenLocationBatch(coordinates).then((points) {
_markerStates.asMap().forEach((i, value) {
_markerStates[i].updatePosition(points[i]);
});
});
}
void _addMarker(Point<double> point, LatLng coordinates) {
setState(() {
_markers.add(Marker(_rnd.nextInt(100000).toString(), coordinates, point, _addMarkerStates));
});
}
#override
void initState() {
super.initState();
_getCurrentLocation();
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
body: Stack(children: [
MapboxMap(
accessToken: Kukulcan.MAPBOX_ACCESS_TOKEN,
trackCameraPosition: true,
onMapCreated: _onMapCreated,
onCameraIdle: _onCameraIdleCallback,
onStyleLoadedCallback: _onStyleLoadedCallback,
initialCameraPosition: _kInitialPosition,
),
IgnorePointer(
ignoring: true,
child: Stack(
children: _markers,
))
]),
);
}
void _getCurrentLocation() async {
_currentLocation = await LocationService.startLocationService();
_currentCoordinates = new LatLng(_currentLocation!.latitude,_currentLocation!.longitude);
await _mapController.animateCamera(CameraUpdate.newLatLng(_currentCoordinates));
_addMarker(new Point(1, 1), _currentCoordinates);
if (_positionStreamSubscription == null) {
final positionStream = Geolocator.getPositionStream();
_positionStreamSubscription = positionStream.handleError((error) {
_positionStreamSubscription?.cancel();
_positionStreamSubscription = null;
}).listen((position) => setState(() => _positionItems.add(
_PositionItem(_PositionItemType.position, position.toString()))));
_positionStreamSubscription?.pause();
}
}
}
class Marker extends StatefulWidget {
final Point _initialPosition;
LatLng _coordinate;
final void Function(_MarkerState) _addMarkerState;
Marker(
String key, this._coordinate, this._initialPosition, this._addMarkerState)
: super(key: Key(key));
#override
State<StatefulWidget> createState() {
final state = _MarkerState(_initialPosition);
_addMarkerState(state);
return state;
}
}
class _MarkerState extends State with TickerProviderStateMixin {
final _iconSize = 80.0;
Point _position;
_MarkerState(this._position);
#override
Widget build(BuildContext context) {
var ratio = 1.0;
//web does not support Platform._operatingSystem
if (!kIsWeb) {
// iOS returns logical pixel while Android returns screen pixel
ratio = Platform.isIOS ? 1.0 : MediaQuery.of(context).devicePixelRatio;
}
return Positioned(
left: _position.x / ratio - _iconSize / 2,
top: _position.y / ratio - _iconSize / 2,
child: Image.asset('assets/img/pin.png', height: _iconSize));
}
void updatePosition(Point<num> point) {
setState(() {
_position = point;
});
}
LatLng getCoordinate() {
return (widget as Marker)._coordinate;
}
}
enum _PositionItemType {
permission,
position,
}
class _PositionItem {
_PositionItem(this.type, this.displayValue);
final _PositionItemType type;
final String displayValue;
}
Does anyone have an idea on what's the problem?
Cheers!
Anyway the solution i found is to use --no-sound-null-safety argument as stated by geolocat documentation
I quote:
NOTE: due to a bug in the dart:html library the web version of the Geolocator plugin does not work with sound null safety enabled and compiled in release mode. Running the App in release mode with sound null safety enabled results in a Uncaught TypeError (see issue #693). The current workaround would be to build your App with sound null safety disabled in release mode:

How to wait for camera to initialize?

I kept getting an error from the camera.dart that "name" was being called on a null object.
After some time, I realized that the problem was the that the build method is called before the async code in my initstate finished (I'm actually slightly proud that I understood the problem at least :))
I tried many different ways to initialize my camera properly, but I could not.
This is the last iteration of my code.
What's the idiomatic way of handling this future?
class _PicturePreviewState extends State<PicturePreview> {
List<CameraDescription> cameras;
CameraDescription camera;
CameraController cameraController;
Future<void> initializeController;
Future<void> getCameras() async {
try {
cameras = await availableCameras();
} catch(e) {print(e);}
camera = cameras.last;
print(camera);
}
#override
void initState() {
super.initState();
// getCameras();
availableCameras().then((availableCameras) {
cameras = availableCameras;
camera = cameras.first;
cameraController = CameraController(
camera,
ResolutionPreset.low,
);
initializeController = cameraController.initialize();
print(cameraController.value.isInitialized);
});
// cameraController = CameraController(
// camera,
// ResolutionPreset.low,
// );
// initializeController = cameraController.initialize();
// print(cameraController.value.isInitialized);
}
#override
void dispose() {
cameraController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<void>(
future: initializeController,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return CameraPreview(cameraController);
}
else {
// Otherwise, display a loading indicator.
print(snapshot.connectionState);
return Center(child: CircularProgressIndicator());
}
},
),
I have been relying on this page to use the camera package, but I could not use it verbatim because I can't keep passing down the camera object down my widget tree.
I fixed it.
I put the initializing of the camera object in the in the parent of the widget.
class _TakeReceiptPictureState extends State<TakeReceiptPicture> {
List<CameraDescription> cameras;
CameraDescription camera;
#override
void initState() {
super.initState();
availableCameras().then((availableCameras) {
cameras = availableCameras;
camera = cameras.first;
});
}
Then made the widget that takes the picture have a parameter of type CameraDescription.
class PicturePreview extends StatefulWidget {
final CameraDescription camera;
const PicturePreview(this.camera, {Key key}) : super(key: key);
#override
_PicturePreviewState createState() => _PicturePreviewState();
}
Then passed the camera initialized in the parent to picture widget
onTap: () {
Navigator.of(context).push(
PageTransition(
type: PageTransitionType.transferRight,
child: PicturePreview(camera)),
);
}),
by the time the child widget's build method runs, the camera object is already initialized and ready to go.
Now the state of the child have only two variables, the camera controller and the initialize controller future.
CameraController cameraController;
Future<void> initializeController;
#override
void initState() {
super.initState();
cameraController = CameraController(
widget.camera,
ResolutionPreset.low,
);
initializeController = cameraController.initialize();
}
TLDR: let the initialization of the camera object be the responsibility of the parent of the widget.

setState() or markNeedsBuild() called during build Exception using Chewie Controller

I have a video playing on the screen using the code below. When I hit the fullscreen icon on the controllers, I get the exception setState() or markNeedsBuild() called during build. The screen should go to fullscreen in the landscape when I hit the icon. It goes but then comes to portrait again (i.e. device orientation).
The following assertion was thrown while dispatching notifications for VideoPlayerController:
setState() or markNeedsBuild() called during build.
Debug console says:
This MaterialControls widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: MaterialControls
dependencies: [_InheritedTheme, _LocalizationsScope-[GlobalKey#8836f], _ChewieControllerProvider]
state: _MaterialControlsState#84529
The widget which was currently being built when the offending call was made was: VideoApp
dirty
state: VideoAppState#8eaa6
When the exception was thrown, this was the stack
Element.markNeedsBuild.<anonymous closure>
Element.markNeedsBuild
State.setState
_MaterialControlsState._updateState
ChangeNotifier.notifyListeners
...
The VideoPlayerController sending notification was: VideoPlayerController#cc7a6(VideoPlayerValue(duration: 0:11:43.069000, size: Size(640.0, 360.0), position: 0:00:03.609000, buffered: [DurationRange(start: 0:00:00.000000, end: 0:00:10.543000)], isPlaying: true, isLooping: false, isBuffering: falsevolume: 1.0, errorDescription: null))
Here is the code which I am using currently.
import 'package:flutter/material.dart';
import 'package:chewie/chewie.dart';
import 'package:flutter/services.dart';
import 'package:video_player/video_player.dart';
typedef void EndOfVideo();
class VideoApp extends StatefulWidget {
final VideoAppState _videoAppState = VideoAppState();
final String videoUrl;
final EndOfVideo endOfVideo;
final bool autoPlay;
VideoApp({
this.videoUrl,
this.endOfVideo,
this.autoPlay
});
#override
State<StatefulWidget> createState() => _videoAppState;
}
class VideoAppState extends State<VideoApp> {
bool _eovReached = false;
// bool wasLandscape = false;
// bool leaveFullscreen = false;
VideoPlayerController _videoPlayerController;
ChewieController _chewieController;
VoidCallback listener;
VideoAppState() {
listener = () {
if(_videoPlayerController.value.initialized) {
Duration duration = _videoPlayerController.value.duration;
Duration position = _videoPlayerController.value.position;
if (duration.inSeconds - position.inSeconds < 3) {
if(!_eovReached) {
_eovReached = true;
widget.endOfVideo();
}
}
}
};
}
initialize(){
if(_videoPlayerController != null && _videoPlayerController.value.isPlaying) {
_videoPlayerController.pause();
}
_videoPlayerController = VideoPlayerController.network(
widget.videoUrl
);
if(_chewieController != null) {
_chewieController.dispose();
}
_chewieController = ChewieController(
allowedScreenSleep: false,
allowFullScreen: true,
// uncomment line below to make video fullscreen when play button is hit
// fullScreenByDefault : true,
deviceOrientationsAfterFullScreen: [
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeLeft,
DeviceOrientation.landscapeRight,
],
videoPlayerController: _videoPlayerController,
aspectRatio: 16 / 9,
autoPlay: false,
looping: false,
autoInitialize: false,
);
_videoPlayerController.addListener(listener);
_videoPlayerController.initialize();
}
#override
void initState() {
super.initState();
try {
this.initialize();
}catch(e){}
}
#override
void didUpdateWidget(VideoApp oldWidget) {
super.didUpdateWidget(oldWidget);
if (this.mounted){
if(oldWidget.videoUrl != widget.videoUrl) {
try {
this.initialize();
}catch(e){
}
}
}
}
#override
void dispose() {
_videoPlayerController.dispose();
_chewieController.dispose();
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);
super.dispose();
}
#override
Widget build(BuildContext context) {
if(widget.autoPlay) {
_videoPlayerController.play();
}
return new Container(
child: new Center(
child: new Chewie(
controller: _chewieController,
)
),
);
}
}
My question is what could be causing this error and how to fix this. I also want to make the video player go fullscreen when the orientation of the device is landscapeleft or landscaperight.
Let me know if I should add anything else in here.
Thank you.
Edit:
I have fixed the exception. Thanks to Ibrahim Karahan! I need help with making the video player go full screen when the device is turned landscape. Thanks again.