Flutter disposing a video and using it again - flutter

I want to cycle through a list of media files (images, videos, etc..) so I have a Future that calls itself to go over the list and show each media item.
I want it to be able to play videos one after another if my list contains for example [video, video, image, video], but if I use the following way:
void playVideo(File video) {
if(playerController != null && playerController.value.initialized) {
playerController.removeListener(listener);
playerController.dispose();
}
playerController = new VideoPlayerController.file(video);
playerController.initialize().then((_) => setState(() {}));
//playerController.setVolume(0.0);
playerController.play();
playerController.addListener(listener);
}
and calling playVideo each time I have a new video to display.
If I do that, I get the following error:
A VideoPlayerController was used after being disposed.
Once you have called dispose() on a VideoPlayerController, it can no longer be used.

Below code play again button click video change and dispose
class MainScreen extends StatefulWidget {
MainScreen({Key key}) : super(key: key);
#override
_MainScreenState createState() => new _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
List<String> urlsVideo = [
"assets/videos/1.1.mp4",
"assets/videos/1.2.mp4",
];
int videoPos = 1;
VideoPlayerController controllerFirst;
StreamController<bool> streamController = new StreamController();
#override
void initState() {
_startVideoPlayer();
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white,
body: Column(
children: <Widget>[
Expanded(
child: StreamBuilder(
stream: streamController.stream,
builder: (context, snapshot) {
if (snapshot.hasData &&
!snapshot.data &&
controllerFirst != null) {
return VideoPlayer(controllerFirst);
} else {
return Center(child: CircularProgressIndicator());
}
})),
RaisedButton(
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text("Play Again"),
),
onPressed: () {
_startVideoPlayer();
},
)
],
),
);
}
Future<void> _startVideoPlayer() async {
videoPos = videoPos == 0 ? 1 : 0;
streamController.add(true);
final VideoPlayerController _controller =
VideoPlayerController.asset(urlsVideo[videoPos]);
_controller.addListener(_listener);
await _controller.setLooping(true);
await _controller.initialize();
final VideoPlayerController oldController = controllerFirst;
if (mounted) {
setState(() {
controllerFirst = _controller;
});
}
await _controller.play();
await oldController?.dispose();
streamController.add(false);
}
get _listener => () {
if (controllerFirst != null && controllerFirst.value.size != null) {
if (mounted) setState(() {});
controllerFirst.removeListener(_listener);
}
};
}

Related

Flutter camera error LateInitializationError: Field 'cameraController' has not been initialized

I'm trying to write a code to use the camera with Flutter, but even by following the steps seen online it cannot initialize cameraController.
Here is my code :
class CameraPage extends StatefulWidget {
const CameraPage({Key? key}) : super(key: key);
#override
State<CameraPage> createState() => _CameraPageState();
}
class _CameraPageState extends State<CameraPage> {
late List<CameraDescription> cameras;
late CameraController cameraController;
#override
void initState() {
startCamera();
super.initState();
}
void startCamera() async {
cameras = await availableCameras();
cameraController = CameraController(
cameras[0],
ResolutionPreset.high
);
print(" Camera controller : $cameraController");
cameraController.initialize().then((value) {
if(!mounted) {
return;
}
setState(() {}); //To refresh widget
}).catchError((e) {
print(e);
});
}
#override
void dispose() {
cameraController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
if(cameraController.value.isInitialized) {
return Scaffold(
body: Stack(
children: [
CameraPreview(cameraController),
],
),
);
} else {
return SizedBox();
}
}
}
The print(" Camera controller : $cameraController"); is working fine and returns me a camera controller, so it might be initialized at some point ?
I will suggest using FutureBuilder to handle future method.
class _CameraPageState extends State<CameraPage> {
late List<CameraDescription> cameras;
late CameraController cameraController;
#override
void initState() {
super.initState();
}
Future<void> startCamera() async {
cameras = await availableCameras();
cameraController = CameraController(cameras[0], ResolutionPreset.high);
}
late final cameraInit = Future.wait([
startCamera(),
cameraController.initialize(),
]);
#override
void dispose() {
cameraController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
FutureBuilder(
future: cameraInit,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return CameraPreview(cameraController);
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
],
),
);
}
}
Also check this cookbook.
Your problem is that in the first build your camera controller is not initialized yet, because initialization is in an async method. You can use a Boolean flag to track is initialized, before accessing the late property.
bool _cameraInitialized = false;
void startCamera() async {
cameras = await availableCameras();
cameraController = CameraController(cameras[0], ResolutionPreset.high);
print(" Camera controller : $cameraController");
cameraController.initialize().then((value) {
if (!mounted) {
return;
}
setState(() {
_cameraInitialized = true; // updating the flag after camera is initialized
}); //To refresh widget
}).catchError((e) {
print(e);
});
}
#override
Widget build(BuildContext context) {
if (_cameraInitialized && cameraController.value.isInitialized) {
return Scaffold(
body: Stack(
children: [
CameraPreview(cameraController),
],
),
);
} else {
return SizedBox();
}
}

How to make StreamBuilder not rebuild all widget in flutter

I am currently working on a chat application, when I added a record everything is fine but when play record or any audio and send message audio is stop and make initState again
this is streambuilder:
class MessageStreamBuilder extends StatefulWidget {
final ScrollController messageScrollController;
const MessageStreamBuilder({Key? key, required this.messageScrollController}) : super(key: key);
#override
State<MessageStreamBuilder> createState() => _MessageStreamBuilderState();
}
class _MessageStreamBuilderState extends State<MessageStreamBuilder> {
late Stream<QuerySnapshot<Map<String, dynamic>>> _stream;
late ChatCubit cubit;
final int _limit = 100;
final int _limitIncrement = 20;
#override
void initState() {
//_messageScrollController.animateTo(0, duration: const Duration(milliseconds: 300), curve: Curves.easeOut);
super.initState();
_stream = FirebaseFirestore.instance
.collection('GeneralChat')
.orderBy('time', descending: true)
.limit(_limit)
.snapshots();
cubit = ChatCubit.get(context);
}
#override
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _stream,
builder: (context, snapshot) {
if (snapshot.hasData) {
List<QueryDocumentSnapshot<Object?>> message = snapshot.data!.docs;
return ListView.builder(
controller: widget.messageScrollController,
reverse: true,
itemBuilder: (element, index) {
return Message(
messages: message, messageIndex: index);
},
);
} else {
return const Center(
child: SpinKitWave(
color: Colors.white,
size: 50.0,
),
);
}
});
}
}
and this is audio widget:
class _AudioMessageState extends State<AudioMessage> {
final AudioPlayer _player = AudioPlayer();
final url = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3';
#override
void initState() {
super.initState();
_init();
}
Future<void> _init() async {
try {
await _player.setUrl(widget.audioUrl[0]);
} catch (e) {
debugPrint('An error occured $e');
}
}
#override
void dispose() {
_player.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Row(
children: [
playButtonAudio(_player),
const SizedBox(width: 10,),
Expanded(
child: progressBarAudio(_player)),
],
);
}
}
i need to make audio play when send message dont stop Is there another way instead of StreamBuilder....
iam using firebase
and no setState() use cubit

Flutter video caching for 10 seconds on next 4 videos

Does anyone know how to do Flutter Video Caching in List for 4 to 6 seconds? (For next 5 videos) like Instagram reels.
Is there any way to do it?
I had taken PageView.builder to show a video with a full page.
I have tried one cached_video_player but it's loading full video.
Here is what I have done.
VideoWidget:
typedef OnVideoCompleted = void Function();
class VideoWidget extends StatefulWidget {
//final bool? play;
final bool? isDuetVideo;
final String? url;
final OnVideoCompleted? onVideoCompleted;
final HomeWidgetBloc? homeWidgetBloc;
const VideoWidget(
{Key? key,
this.onVideoCompleted,
required this.url,
required this.homeWidgetBloc,
required this.isDuetVideo})
: super(key: key);
#override
_VideoWidgetState createState() => _VideoWidgetState();
}
class _VideoWidgetState extends State<VideoWidget> {
late VideoPlayerController videoPlayerController;
late Future<void> _initializeVideoPlayerFuture;
final _controllerStateStream = BehaviorSubject<int>.seeded(0);
VoidCallback? _listener;
StreamSubscription? _playPauseSubscription;
#override
void initState() {
super.initState();
videoPlayerController = new VideoPlayerController.network(widget.url!);
videoPlayerController.initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
_controllerStateStream.add(1);
_observeForPlayPause();
_observerForSeekDuration();
_listener = () {
final value =
widget.homeWidgetBloc?.videoEndedStream.valueWrapper?.value;
print('duration -----value--- ${value}');
if (videoPlayerController.value.duration.inSeconds > 0 &&
videoPlayerController.value.position.inMilliseconds ==
videoPlayerController.value.duration.inMilliseconds &&
(videoPlayerController.value.position.inMilliseconds ==
videoPlayerController.value.duration.inMilliseconds)) {
// FOR AUTO PLAY NEXT VIDEO...
widget.onVideoCompleted?.call();
print(
'duration -----addListener--- ${videoPlayerController.value.duration}');
}
};
videoPlayerController.addListener(_listener!);
});
} // This closing tag was missing
#override
void dispose() {
super.dispose();
_controllerStateStream.close();
_playPauseSubscription?.cancel();
try {
if (_listener != null) {
videoPlayerController.removeListener(_listener!);
}
videoPlayerController.dispose();
} catch (e) {
print(e.toString());
}
}
#override
Widget build(BuildContext context) {
return StreamBuilder<int>(
stream: _controllerStateStream,
builder: (context, snapshot) {
final isReady = (snapshot.data ?? 0) == 1;
if (!isReady) {
return _progressWidget();
}
return new Stack(children: <Widget>[
Container(
child: Center(
child: (widget.isDuetVideo! ||
videoPlayerController.value.size.width >
videoPlayerController.value.size.height)
? AspectRatio(
child: VideoPlayer(
videoPlayerController,
),
aspectRatio: videoPlayerController.value.aspectRatio,
)
: VideoPlayer(
videoPlayerController,
),
widthFactor: double.maxFinite,
heightFactor: double.maxFinite,
),
),
Visibility(
visible: !widget.isDuetVideo!,
child: VideoPlayerCustomControlsWidget(
controller: videoPlayerController,
),
),
]);
},
);
}
Center _progressWidget() {
return Center(
child: CircularProgressIndicator(
color: AppStyles.primary500Color,
),
);
}
void _observeForPlayPause() {
_playPauseSubscription =
widget.homeWidgetBloc?.videoPlayPauseStream.listen((value) {
if (value == PLAY)
videoPlayerController.play();
else
videoPlayerController.pause();
});
}
void _observerForSeekDuration() {
_playPauseSubscription =
widget.homeWidgetBloc?.duetVideoSeekDurationZeroStream.listen((value) {
if (value == true) videoPlayerController.seekTo(Duration.zero);
widget.homeWidgetBloc?.duetVideoSeekDurationZeroStream.add(false);
});
}
}
Update:
I found many answers (like this) but that all answers are only for caching the current video, not the next/prev videos from the list. I want it, especially for the list.
this is what I used in my app, preload_page_view, it preloads a specific count of pre/next pages:
#override
Widget build(BuildContext context) {
return new PreloadPageView.builder(
itemCount: ...,
itemBuilder: ...,
onPageChanged: (int position) {...},
.....
preloadPagesCount: 3,
controller: PreloadPageController(),
);
}

A build function returned null. The offending widget is: StoryCamera

I'm trying to display the full camera display on this page and when I run it, it returns null. This is my first try, creating a camera page, so things might look all over the place.
StoryCamera.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
List<CameraDescription> cameras;
//CameraController controller;
class StoryCamera extends StatefulWidget {
final String currentUser;
StoryCamera({this.currentUser});
#override
_StoryCameraState createState() => _StoryCameraState();
}
class _StoryCameraState extends State<StoryCamera> {
CameraController _controller;
Future<void> _initializeControllerFuture;
bool isCameraReady = false;
bool showCapturedPhoto = false;
var ImagePath;
get pageStatus => 1;
#override
void initState() {
super.initState();
_initializeCamera();
}
Future<void> _initializeCamera() async {
final cameras = await availableCameras();
final firstCamera = cameras.first;
_controller = CameraController(firstCamera, ResolutionPreset.high);
_initializeControllerFuture = _controller.initialize();
if (!mounted) {
return Container();
}
setState(() {
isCameraReady = true;
});
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
_controller != null
? _initializeControllerFuture = _controller.initialize()
: null; //on pause camera is disposed, so we need to call again "issue is only for android"
}
}
void onCaptureButtonPressed() async {
//on camera button press
try {
final path = join(
(await getTemporaryDirectory()).path, //Temporary path
'$pageStatus${DateTime.now()}.png',
);
ImagePath = path;
await _controller.takePicture(path); //take photo
setState(() {
showCapturedPhoto = true;
});
} catch (e) {
print(e);
}
}
#override
void dispose() {
_controller?.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
final deviceRatio = size.width / size.height;
FutureBuilder<void>(
future: _initializeControllerFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// If the Future is complete, display the preview.
return Stack(
children: <Widget>[
Center(
child: Transform.scale(
scale: _controller.value.aspectRatio / deviceRatio,
child: new AspectRatio(
aspectRatio: _controller.value.aspectRatio,
child: new CameraPreview(_controller),
),
),
),
],
);
} else {
return Container(
child:
CircularProgressIndicator()); // Otherwise, display a loading indicator.
}
},
);
}
}
The error also points to the page before it that navigates you to this page.
StoryPage.dart
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StoryCamera())),
Can anyone tell me why this is happening?
You need to add a return
return FutureBuilder<void>(

Bloc navigation on state change

I'm really new with flutter blocs and I having some problems with a bloc implementation, I'm trying to navigate after a state change in my splash screen widget.
After the state update to InitSuccess it should navigate to LoginScreen, but this navigation occurs many times.
I'm not able to understand what to do after the state change's to InitSuccess, after this the bloc keeps alive and calling many, many times LoginScreen.
Splash Screen
class SplashScreen extends StatefulWidget {
#override
State<StatefulWidget> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
SplashBloc _splashBloc;
final _scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
_init();
super.initState();
}
#override
void dispose() {
_splashBloc.dispose();
super.dispose();
}
void _init() {
Future.delayed(Duration.zero, () {
checkDeviceConnection(context);
BlocSupervisor().delegate = SplashBlocDelegate();
final bool isIOS = Theme.of(context).platform == TargetPlatform.iOS;
_splashBloc = SplashBloc(
firebaseService: FirebaseService(context),
authService: AuthService(context),
devicesService: DevicesService(context),
);
_splashBloc.dispatch(SplashInitEvent(isIOS: isIOS));
});
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIOverlays([]);
return BlocBuilder<SplashEvent, SplashState>(
bloc: _splashBloc,
builder: (
BuildContext context,
SplashState state,
) {
if (state is InitFailure) {
Future.delayed(Duration.zero, () {
showWarningSnackBar(_scaffoldKey, state.error);
});
}
if (state is InitSuccess) {
Future.delayed(Duration.zero, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LoginScreen(),
),
);
});
}
return Scaffold(
key: _scaffoldKey,
body: Container(
decoration: appScreenGradient,
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
"assets/images/splash_screen/logo_splash.png",
width: 172.88,
height: 144.55,
fit: BoxFit.contain,
),
SizedBox(
height: 20.0,
),
LoadingSpinner(
spinnerColor: Theme.of(context).primaryColorLight,
),
],
),
),
);
},
);
}
Splash Bloc
class SplashBloc extends Bloc<SplashEvent, SplashState> {
final FirebaseService firebaseService;
final DevicesService devicesService;
final AuthService authService;
final UserPreferences _userPreferences = UserPreferences();
SplashBloc({
#required this.firebaseService,
#required this.devicesService,
#required this.authService,
});
#override
Stream<SplashEvent> transform(Stream<SplashEvent> events) {
return (events as Observable<SplashEvent>).debounce(
Duration(milliseconds: 500));
}
#override
get initialState => SplashInitial();
#override
Stream<SplashState> mapEventToState(currentState, event) async* {
if (event is SplashInitEvent) {
if (currentState is SplashInitial) {
yield InitLoading();
try {
firebaseService.togglePerformanceCollection(true);
firebaseService.firebaseCloudMessagingListeners();
String firebaseToken = await firebaseService
.getFirebaseMessagingToken();
bool isRegistered =
await _userPreferences.getIsDeviceRegistered() ?? false;
if (!isRegistered) {
final String platform = event.isIOS ? 'IOS' : 'Android';
final deviceInfo = await devicesService.getDeviceInfo(platform);
isRegistered = await devicesService.register(
deviceToken: firebaseToken,
deviceInfo: deviceInfo,
);
if (isRegistered) {
_userPreferences.setIsDeviceRegistered(true);
}
}
yield InitSuccess();
} catch (e) {
yield InitFailure(error: e.toString());
}
}
}
if (event is SplashInitialEvent) {
yield SplashInitial();
}
}
}
I found the following solution:
if (state is LoggedIn) {
WidgetsBinding.instance.addPostFrameCallback((_) {
// Navigation
});
}
I wrapped my navigation with this addPostFrame callback for delaying its appearance.