Flutter video caching for 10 seconds on next 4 videos - flutter

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

Related

Flutter - Can't update state when minimizing app screen

I'm working with Flutter and I have two cards in the home, the first one, I have to show the most recent call that comes to me, and the second card, it should show me the class that is happening according to the current time, however, I want that when my application is minimized or switched, it updates these two cards, where the first card is CardAviso(),
and the second is CardDiarioClasse()
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> with WidgetsBindingObserver {
var loading = false;
final List<HorariosTurma> _list = [];
final List<HorariosTurma> _listCardDiario = [];
String professorNome = "";
_HomeState() {
getProfessorApi();
}
redirectComunicados() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => Comunicados(),
),
);
}
getProfessorApi() {
loading = true;
_list.clear();
API.getApiProfessor().then((response) {
if (response.statusCode == 200) {
Map<String, dynamic> professor = jsonDecode(response.body);
List<dynamic> horariosTurma = professor["horariosturma"];
horariosTurma.forEach((value) {
var horarioTurma = HorariosTurma.fromJson(value);
if (horarioTurma.turma != null || horarioTurma.sala != null) {
_list.add(horarioTurma);
}
});
DateTime now = DateTime(DateTime.now().year, DateTime.now().month,
DateTime.now().day, DateTime.now().hour - 3, DateTime.now().minute);
_list.removeWhere(
(element) => (element.sala == null || element.turma == null));
_list.forEach((e) {
DateTime hourInitList = DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
int.parse(e.horaInicio.toString().split(':')[0]),
int.parse(e.horaInicio.toString().split(':')[1]));
DateTime hourFinalList = DateTime(
DateTime.now().year,
DateTime.now().month,
DateTime.now().day,
int.parse(e.horaFinal.toString().split(':')[0]),
int.parse(e.horaFinal.toString().split(':')[1]));
if (now.isAfter(hourInitList) && now.isBefore(hourFinalList)) {
_listCardDiario.add(e);
}
});
}
loading = false;
});
}
#override
build(BuildContext context) {
return Scaffold(
backgroundColor: const Color(0xffDFE8EC),
drawer: const Navigation(),
appBar: AppBar(
title: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.network("https://eemandev.wd7.com.br/img/logo-branco.png",
height: 50)
]),
backgroundColor: const Color(0xff006f99),
toolbarHeight: 70,
actions: [
Padding(
padding: EdgeInsets.only(right: 16),
child: InkWell(
onTap: () => redirectComunicados(),
child: Icon(Icons.mail),
),
)
]),
body: Padding(
padding: const EdgeInsets.all(26.0),
child: ListView(children: const [
CardAviso(),
CardAction(),
CardDiarioClasse(),
Calendario(),
])));
}
}
I've already made several attempts, including in one of the cards I put the life cycle with the codes below, but it doesn't work:
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
getHorarioDiaAtual();
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.inactive) {
setState(() {});
}
super.didChangeAppLifecycleState(state);
}

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 pull down to refresh seems to work but it does not refresh

I am a beginner in Flutter and I'm struggling with this problem for quite some time now.
I have made a Flutter app which fetches posts from a wordpress website. Everything works fine, except the pull to refresh. It seems to work (at least the indicator is working), but it doesn't update in the background and I have no clue where the problem could be.
Please take a look to my my posts_list.dart file:
import 'package:wordpress_flutter/widgets/refresh_widget.dart';
import '../model/post_entity.dart';
import '../network/wp_api.dart';
import '../widgets/post_list_item.dart';
import '../widgets/refresh_widget.dart';
class PostsList extends StatefulWidget {
int category = 0;
PostsList({this.category = 0});
#override
_PostsListState createState() => _PostsListState();
}
class _PostsListState extends State<PostsList> {
final keyRefresh = GlobalKey<RefreshIndicatorState>();
List<PostEntity> posts = [];
int page = 0;
ScrollController _scrollController = new ScrollController();
bool isLoading = false;
#override
void initState() {
super.initState();
getData();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
getData();
}
});
}
Future getData() async {
keyRefresh.currentState?.show();
if (!isLoading) {
setState(() {
page++;
isLoading = true;
});
WpApi.getPostsList(category: widget.category, page: page).then((_posts) {
setState(() {
isLoading = false;
posts.addAll(_posts);
});
});
}
}
#override
void dispose() {
_scrollController.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) => SizedBox(
height: 300,
child: buildList(),
);
Widget buildList() => posts.isEmpty
? Center(child: CircularProgressIndicator())
: RefreshWidget(
keyRefresh: keyRefresh,
onRefresh: getData,
child: ListView.builder(
itemCount: posts.length + 1,
shrinkWrap: true,
primary: false,
controller: _scrollController,
itemBuilder: (context, index) {
if (index == posts.length) {
return _buildProgressIndicator();
} else {
return PostListItem(posts[index]);
}
}));
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: Visibility(
visible: isLoading,
child: CircularProgressIndicator(
color: Colors.blue,
),
),
),
);
}
}
Here's the refresh_widget.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class RefreshWidget extends StatefulWidget {
final GlobalKey<RefreshIndicatorState> keyRefresh;
final Widget child;
final Future Function() onRefresh;
const RefreshWidget({
Key key,
this.keyRefresh,
#required this.onRefresh,
#required this.child,
}) : super(key: key);
#override
_RefreshWidgetState createState() => _RefreshWidgetState();
}
class _RefreshWidgetState extends State<RefreshWidget> {
#override
Widget build(BuildContext context) =>
Platform.isAndroid ? buildAndroidList() : buildIOSList();
Widget buildAndroidList() => RefreshIndicator(
key: widget.keyRefresh,
onRefresh: widget.onRefresh,
child: widget.child,
);
Widget buildIOSList() => CustomScrollView(
physics: BouncingScrollPhysics(),
slivers: [
CupertinoSliverRefreshControl(onRefresh: widget.onRefresh),
SliverToBoxAdapter(child: widget.child),
],
);
}
You should definitely check out the package called pull_to_refresh. It helps out a lot.
From my observation, your issue could be that you add all your posts that you get to the posts list. Instead, try replacing the list by the incoming list. Your way would only work for the first time, since initially the posts list is empty. However, the next time, it just adds another list of posts to all the posts you got from initState.
Future getData() async {
keyRefresh.currentState?.show();
if (!isLoading) {
setState(() {
page++;
isLoading = true;
});
WpApi.getPostsList(category: widget.category, page: page).then((_posts) {
setState(() {
isLoading = false;
posts = _posts
});
});
}
}

How to place a key in ListView to maintain state on page change

I am trying to build a steam of posts (think twitter or instagram type posts) that a user is able to scroll through. As they are scrolling, they can click one of the posts and navigate to a new page. When they go navigate back from that page, I want them to remain at the same position on the position that they were previously on within the ListView.
PROBLEM
I can not currently keep the stream widget from rebuilding and returning to the scroll position. I know that one of the solutions to this is to include a key; however, I have tried to including the key in the ListView.builder, but it has not worked.
QUESTION
Where should I include the key? Am I using the right type of key?
class Stream extends StatefulWidget {
Stream({Key key, this.user}) : super(key: key);
final User user;
#override
_StreamState createState() => new _StreamState(
user: user
);
}
class _StreamState extends State<Stream> {
_StreamState({this.user});
final User user;
Firestore _firestore = Firestore.instance;
List<DocumentSnapshot> _posts = [];
bool _loadingPosts = true;
int _per_page = 30;
DocumentSnapshot _lastPosts;
ScrollController _scrollController = ScrollController();
bool _gettingMorePosts = false;
bool _morePostsAvailable = true;
_getPosts() async {
Query q = _firestore
.collection('posts')
.document(user.user_id)
.collection('posts')
.orderBy("timePosted", descending: true)
.limit(_per_page);
setState(() {
_loadingPosts = true;
});
QuerySnapshot querySnapshot = await q.getDocuments();
_posts = querySnapshot.documents;
if (_posts.length == 0) {
setState(() {
_loadingPosts = false;
});
}
else {
_lastPosts = querySnapshot.documents[querySnapshot.documents.length - 1];
setState(() {
_loadingPosts = false;
});
}
}
_getMorePosts() async {
if (_morePostsAvailable == false) {
return;
}
if (_gettingMorePosts == true) {
return;
}
if (_posts.length == 0) {
return;
}
_gettingMorePosts = true;
Query q = _firestore
.collection('posts')
.document(user.user_id)
.collection('posts')
.orderBy("timePosted", descending: true)
.startAfter([_lastPosts.data['timePosted']]).limit(_per_page);
QuerySnapshot querySnapshot = await q.getDocuments();
if (querySnapshot.documents.length == 0) {
_morePostsAvailable = false;
}
if(querySnapshot.documents.length > 0) {
_lastPosts = querySnapshot.documents[querySnapshot.documents.length - 1];
}
_posts.addAll(querySnapshot.documents);
setState(() {});
_gettingMorePosts = false;
}
#override
void initState() {
super.initState();
_getPosts();
_scrollController.addListener(() {
double maxScroll = _scrollController.position.maxScrollExtent;
double currentScroll = _scrollController.position.pixels;
double delta = MediaQuery.of(context).size.height * 0.25;
if (maxScroll - currentScroll < delta) {
_getMorePosts();
}
});
}
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
new Expanded(
child: _loadingPosts == true
? Container(
child: Center(
child: Text(" "),
),
)
: Container(
child: Center(
child: _posts.length == 0
? Center(
child: Text("Follow friends", style: TextStyle(fontSize: 15),),
)
: ListView.builder(
key: widget.key,
controller: _scrollController,
itemCount: _posts.length,
itemBuilder: (BuildContext ctx, int index) {
return new Widget(
//paramenters to build the post widget here
);
}),
),
),
),
],
);
}
One thing to note, since I don't want to return all pages (due to Firestore expenses calling so many posts), the build logic is created such that more posts are loaded upon scroll. I realize this may impact it.
Short answer:
You need to provide key to your ListView.builder like this:
ListView.builder(
key: PageStorageKey("any_text_here"),
// ...
)
Long answer:
You can see that when you come back from screen 2 to screen 1, the item 30 remains on the top.
Sorry it was difficult to reproduce your code due to limited availability to the variables you're using. I created a simple example to demonstrate what you're looking for.
Full code:
void main() => runApp(MaterialApp(home: HomePage()));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView.builder(
key: PageStorageKey("any_text_here"), // this is the key you need
itemCount: 50,
itemBuilder: (_, i) {
return ListTile(
title: Text("Item ${i}"),
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => DetailPage(index: i))),
);
},
),
);
}
}
class DetailPage extends StatefulWidget {
final int index;
const DetailPage({Key key, this.index}) : super(key: key);
#override
_DetailPageState createState() => _DetailPageState();
}
class _DetailPageState extends State<DetailPage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text(
"You clicked ${widget.index}",
style: Theme.of(context).textTheme.headline,
),
),
);
}
}
This kind of key works:
key: PageStorageKey('Your Key Name'),

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