How to make StreamBuilder not rebuild all widget in flutter - 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

Related

Flutter appending fetched data to a listview inside a future builder

I'd like to ask when using the FutureBuilder to display fetched data from a remote server in a ListView. I check if the bottom of the ListView was reached using ScrollController. Everything is working well until I try to load new data and append them to the existing ListView I fetch the data add them to my Array and the in setState((){}) I update the list for the FutureBuilder this is obviously the wrong approach since then the whole FutureBuilder is rebuilt and so is the ListView. The changes however do appear all the new items are in the list as intended however it slows performance not significantly since ListView is not keeping tiles out of view active but it has a small impact on performance, but the main issue is that since ListView gets rebuilt, I'm thrown as a user to the start of this list that's because the ListView got rebuilt. Now what I would like to achieve is that the ListView doesn't get rebuilt every time I get new data. Here is the code of the whole StateFulWidget
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import '../widgets/rss_card.dart';
import '../extensions/colors.dart';
import '../extensions/rss.dart';
import '../main.dart';
import '../models/rss.dart';
class RssListView extends StatefulWidget {
final String? channel;
const RssListView.fromChannel(this.channel, {Key? key}) : super(key: key);
#override
State<RssListView> createState() => _RssListViewState();
}
class _RssListViewState extends State<RssListView>
with AutomaticKeepAliveClientMixin {
late RssListModel _rssListModel;
double _offset = 0.0;
final double _limit = 5.0;
Future<List<RssItemModel>?>? _rssFuture;
final ScrollController _scrollController = ScrollController();
Map<String, Object> _args({double? newOffset}) => {
'offset': newOffset ?? _offset,
'limit': _limit,
};
Future<bool> isConnected() async {
var conn = await Connectivity().checkConnectivity();
return (conn == ConnectivityResult.mobile ||
conn == ConnectivityResult.wifi ||
conn == ConnectivityResult.ethernet)
? true
: false;
}
Future<void> _pullRefresh() async {
_rssListModel.refresh(_args(
newOffset: 0,
));
List<RssItemModel>? refreshedRssItems = await _rssListModel.fetchData();
setState(() {
_rssFuture = Future.value(refreshedRssItems);
});
}
Future<List<RssItemModel>?> get initialize async {
await _rssListModel.initializationDone;
return _rssListModel.Items;
}
void _loadMore() async {
List<RssItemModel>? moreItems = await _rssListModel
.loadMoreWithArgs(_args(newOffset: _offset += _limit));
setState(() {
_rssFuture = Future.value(moreItems);
});
}
void _showSnackBarWithDelay({int? milliseconds}) {
Future.delayed(
Duration(milliseconds: milliseconds ?? 200),
() {
ScaffoldMessenger.of(context).showSnackBar(getDefaultSnackBar(
message: 'No Internet Connection',
));
},
);
}
void _addScrollControllerListener() {
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
(_scrollController.position.maxScrollExtent)) _loadMore();
});
}
#override
bool get wantKeepAlive => true;
#override
void initState() {
super.initState();
_rssListModel = RssListModel.fromChannel(widget.channel, _args());
isConnected().then((internet) {
if (!internet) {
_showSnackBarWithDelay();
} else {
_addScrollControllerListener();
setState(() {
_rssFuture = initialize;
});
}
});
}
#override
Widget build(BuildContext context) {
super.build(context);
return Container(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
),
color: Colors.white,
child: FutureBuilder<List<RssItemModel?>?>(
future: _rssFuture,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.active:
break;
case ConnectionState.waiting:
return getLoadingWidget();
case ConnectionState.done:
{
if (!snapshot.hasData || snapshot.data!.isEmpty)
return _noDataView('No data to display');
if (snapshot.hasError)
return _noDataView("There was an error while fetching data");
return _refreshIndicator(snapshot);
}
}
return _noDataView('Unable to fetch data from server');
},
),
);
}
/// Returns a `RefreshIndicator` wrapping our `ListView`
Widget _refreshIndicator(AsyncSnapshot snapshot) => RefreshIndicator(
backgroundColor: const Color.fromARGB(255, 255, 255, 255),
triggerMode: RefreshIndicatorTriggerMode.anywhere,
color: MyColors.Red,
onRefresh: _pullRefresh,
child: _listView(snapshot),
);
/// Returns a `ListView` builder from an `AsyncSnapshot`
Widget _listView(AsyncSnapshot snapshot) => ListView.builder(
controller: _scrollController,
clipBehavior: Clip.none,
itemCount: snapshot.data!.length,
physics: const BouncingScrollPhysics(),
itemBuilder: (context, index) => RssCard(snapshot.data![index]),
);
/// Returns a `Widget` informing of "No Data Fetched"
Widget _noDataView(String message) => Center(
child: Text(
message,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w800,
),
),
);
}
What you need is to hold onto the state in some Listenable, such as ValueNotifier and use ValueListenableBuilder to build your ListView. I put together this demo to show you what I mean:
import 'package:flutter/material.dart';
import 'package:uuid/uuid.dart';
void main() {
runApp(MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
));
}
#immutable
class Person {
final String id;
Person() : id = const Uuid().v4();
}
class DataController extends ValueNotifier<Iterable<Person>> {
DataController() : super([]) {
addMoreValues();
}
void addMoreValues() {
value = value.followedBy(
Iterable.generate(
30,
(_) => Person(),
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late final ScrollController _controller;
final _generator = DataController();
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(() {
if (_controller.position.atEdge && _controller.position.pixels != 0.0) {
_generator.addMoreValues();
}
});
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Home Page'),
),
body: ValueListenableBuilder(
valueListenable: _generator,
builder: (context, value, child) {
final persons = value as Iterable<Person>;
return ListView.builder(
controller: _controller,
itemCount: persons.length,
itemBuilder: (context, index) {
final person = persons.elementAt(index);
return ListTile(
title: Text(person.id),
);
},
);
},
),
);
}
}

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(),
);
}

how to make this lazyload scrolling working with provider

it take about 7 days trying make a working example for lazyload listview with provider in flutter with real world example and it's still not working because i think something is missing
As a note : the first load , works good and when i scroll it's print (scroll) but nothing happened it's still in the same page
if i try to return _todolist variable in the _onScrollUpdated it not change page correctly and after three times i see this error
E/flutter ( 7713): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)]
Unhandled Exception: type 'String' is not a subtype of type
'List' E/flutter ( 7713): #0 TodoService.fetchTodos
(package:flutter_todo_provider/services/todo_service.dart:32:21)
json example
https://jsonformatter.org/52c83e
todos_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';
import 'package:flutter_todo_provider/helpers/http_exception.dart';
import 'package:provider/provider.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/services/todo_service.dart';
class TodosScreen extends StatefulWidget {
#override
_TodosScreenState createState() => _TodosScreenState();
}
class _TodosScreenState extends State<TodosScreen> {
ScrollController _controller;
List<dynamic> _todoList;
bool _isLoading ;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_onScrollUpdated);
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Configuration.AppName),
),
body: FutureBuilder(
future: _fetchListItems(),
builder: (context, snapshot){
if(snapshot.hasData){
return _listItems(snapshot.data);
}
return _buildProgressIndicator();
}
),
);
}
_fetchListItems() async {
try {
await Provider.of<TodoService>(context, listen: false).loadNextPage();
_todoList = Provider.of<TodoService>(context, listen: false).items;
} on HttpException catch (e) {
EasyLoading.showError(e.message);
}
return _todoList ;
}
Widget _listItems(data){
_isLoading = Provider.of<TodoService>(context, listen: false).isLoading ;
return ListView.builder(
controller: _controller,
itemCount: data.length ,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].title),
subtitle:Text(data[index].content),
trailing: Icon(Icons.print),
);
},
);
}
Future<void> _onScrollUpdated() async {
print("Scroll11");
var maxScroll = _controller.position.maxScrollExtent;
var currentPosition = _controller.position.pixels;
if (currentPosition == maxScroll ) {
try {
await Provider.of<TodoService>(context, listen: false).loadNextPage();
_todoList = Provider.of<TodoService>(context, listen: false).items;
// return _todoList ; if use this line i see the error
} on HttpException catch (e) {
EasyLoading.showError(e.message);
}
}
}
Widget _buildProgressIndicator() {
_isLoading = Provider.of<TodoService>(context, listen: false).isLoading ;
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: _isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
}
todo_service.dart
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_todo_provider/.env.dart';
import 'package:flutter_todo_provider/models/todo.dart';
class TodoService with ChangeNotifier {
bool isLoading = false;
bool isFetching = false;
int currentPage = 1;
int totalRows = 10;
List<Todo> items = [];
loadNextPage() async {
await fetchTodos(currentPage);
currentPage++;
notifyListeners();
}
Future fetchTodos(int currentPage) async {
try {
//404
var options = Options(headers: {
HttpHeaders.authorizationHeader: 'Basic ${Configuration.authToken}'
});
Map<String, dynamic> qParams = {
'current_page': currentPage,
};
Response response = await Dio().get('${Configuration.ApiUrl}/todos/my_todos', options: options, queryParameters: qParams);
List<dynamic> responseBode = response.data["data"];
responseBode.forEach(( dynamic json) {
items.add(Todo.fromJson(json));
});
notifyListeners();
} on DioError catch (e) {
print("Error Message" + e.response.statusMessage);
return items=[];
}
}
}
Here is the code:
class TodoScreen extends StatefulWidget {
// Your service goes here
// (the class extending ChangeNotifier)
#override
_TodoScreenState createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final TodoService todoService = TodoService();
ScrollController _controller;
#override
void initState() {
super.initState();
_controller = ScrollController();
_controller.addListener(_onScrollUpdated);
loadNextPage();
}
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Configuration.AppName'),
),
body: ChangeNotifierProvider.value(
value: todoService,
child: Consumer<TodoService>(builder: (_, ctl, __) {
if (todoService.isLoading) {
return _buildProgressIndicator();
} else {
return _listItems(todoService.items);
}
}),
),
);
}
Widget _listItems(data) {
return ListView.builder(
controller: _controller,
itemCount: data.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(data[index].title),
subtitle: Text(data[index].content),
trailing: Icon(Icons.print),
);
},
);
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: CircularProgressIndicator(),
),
);
}
Future<void> _onScrollUpdated() async {
var maxScroll = _controller.position.maxScrollExtent;
var currentPosition = _controller.position.pixels;
if (currentPosition == maxScroll) {
todoService.loadNextPage();
}
}
}
Note that i didn't make changes to your service. The notifyListeners will do all the job for us.
When you are using Provider, the idea is to keep all your data inside the controller or service (the class that extends ChangeNitifier) and just use the variables with notifyListeners to change the behavior of your screen.
The screen needs to be listening for changes, for this we use the pair ChangeNotifierProvider.value with Consumer(builder: (_, ctl, __) {}).
Use ChangeNotifierProvider in some upper level of the widget tree and use Consumer only where you need the widget to be updated. You can even use more than one Consumer, they all just need to be under ChangeNotifierProvider.

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.

Flutter disposing a video and using it again

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);
}
};
}