I’m developing the flutter app which can play audio. Audio is fetching from firebase and playing perfectly but the problem is that when begin to play audio and exit from that screen, again, revisiting that screen, it won’t update its state(seekbar, play/pause button, time passed).
usually what happens that when I have implemented local state when audio is been load then duration of the music is updated and I’ started playing then it is updating slider(seekbar) and current position of the song. But after I have used Provider it is not updating completely.
below is the widget code
import 'dart:ui';
import 'package:app/model/PlayerState.dart';
import 'package:app/stories/get_stories.dart';
import 'package:audio_service/audio_service.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:app/globals.dart' as globals;
import 'package:just_audio/just_audio.dart';
import 'package:provider/provider.dart';
import '../widgets/error_alert.dart';
import 'package:audio_session/audio_session.dart';
class PlayingStory extends StatefulWidget {
final String story;
final String episode;
final String playLink;
final String poster;
const PlayingStory(
{Key? key,
required this.story,
required this.episode,
required this.playLink,
required this.poster})
: super(key: key);
@override
State<StatefulWidget> createState() => PlayingStoryState();
}
class PlayingStoryState extends State<PlayingStory> with WidgetsBindingObserver {
double _value = 0.0;
final audioPlayer = AudioPlayer();
late bool isPlaying;
late AppLifecycleState state;
late String episode = widget.episode;
// void _showChoices(BuildContext context) {
//
// // GetStories().updateStoryList(widget.story,context);
// var nextEpisode = GetStories().fetchNextEpisodes(widget.story);
//
// OverlayState overlayState = Overlay.of(context);
// OverlayEntry? choice;
//
// final Size size = MediaQuery.of(context).size;
// final height = size.height;
// final width = size.width;
//
// choice = OverlayEntry(builder: (context) {
// print("_-_-_-_-_-_-_-_-_-_-_ OVERLAY CALLED _-_-_-_-_-_-_-_-_-_-_");
// return Scaffold(
// backgroundColor: Colors.transparent,
// body: Center(
// child: Padding(
// padding: EdgeInsets.symmetric(horizontal: width * 0.035, vertical: height * 0.06),
// child: ColoredBox(
// color: const Color(0xff838080),
// child: BackdropFilter(
// filter: ImageFilter.blur(
// sigmaY: 8.0,
// sigmaX: 8.0,
// ),
// child: Center(
// child: ListView(
// children: [
//
// FutureBuilder<Map<dynamic, dynamic>>(
// future: nextEpisode,
// builder: (context, snapshot) {
// if (!snapshot.hasData) {
// return const Center(
// child: CircularProgressIndicator(),
// );
// }
//
// final nxteps = snapshot.data!;
//
// return ListView.builder(
// physics: const NeverScrollableScrollPhysics(),
// shrinkWrap: true,
// itemCount: nxteps.length,
// itemBuilder: (context, index) {
// return ListTile(
// onTap: () async {
// var eps = await GetStories().playNextEpisode(widget.story, nxteps.keys.toList()[index]);
// setState(() {
// print(eps);
// audioInit(eps["episode_audio"]);
// episode = eps["episode_id"];
// choice!.remove();
// });
// },
// shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(5),
// ),
// tileColor: Colors.red,
// title: Text(
// nxteps.values.toList()[index],
// style: const TextStyle(color: Colors.white),
// ),
// subtitle: Text(
// nxteps.keys.toList()[index],
// style: const TextStyle(color: Colors.white),
// ),
// leading: IconButton(
// onPressed: () {
// choice!.remove();
// },
// color: Colors.white,
// iconSize: height * 0.04,
// icon: const Icon(
// Icons.play_arrow_rounded,
// ),
// ),
// );
// },
// );
// },
// )
//
// ],
// ),
// ),
// ),
// ),
// ),
// ),
// );
// });
//
// overlayState.insert(choice);
// }
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
audioInit(widget.playLink);
isPlaying = false;
}
Future<void> audioInit(audio) async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.speech());
audioPlayer.positionStream.listen((position) {
if (mounted == true) {
// setState(() {
_value = position.inSeconds.toDouble();
Duration duration = Duration(seconds: _value.toInt());
Provider.of<PlayerStateModel>(context, listen: false).setCurrentPosition(duration);
// });
}
});
audioPlayer.durationStream.listen((event) {
Provider.of<PlayerStateModel>(context, listen: false).setDuration(event!);
});
audioPlayer.playerStateStream.listen((event) {
if(event.processingState == ProcessingState.completed) {
if(mounted) {
// setState(() {
isPlaying = false;
showAlert(context, "SOng is Completed!");
// _showChoices(context);
// });
}
if (kDebugMode) {
print("_-_-_-_-_-_ SONG COMPLETED _-_-_-_-_-_");
}
} else {
if (kDebugMode) {
print("_-_-_-_-_-_ SONG NOT COMPLETED _-_-_-_-_-_");
}
}
});
audioPlayer.playbackEventStream.listen((event) {
}, onError: (Object e, StackTrace stackTrace) {
if (kDebugMode) {
print('A stream error occurred: $e');
}
});
// Try to load audio from a source and catch any errors.
try {
// AAC example: https://dl.espressif.com/dl/audio/ff-16b-2c-44100hz.aac
await audioPlayer.setAudioSource(AudioSource.uri(
Uri.parse(audio),
tag: MediaItem(
// Specify a unique ID for each media item:
id: 'StoryFy',
// Metadata to display in the notification:
album: widget.story,
title: episode.split("=")[0],
artUri: Uri.parse(widget.poster),
),
));
final duration = audioPlayer.duration;
Provider.of<PlayerStateModel>(context, listen: false).setCurrentPosition(duration!);
// audioPlayer.play();
// setState(() {
// isPlaying = true;
// });
} catch (e) {
if (kDebugMode) {
print("Error loading audio source: $e");
}
}
}
// @override
// void dispose() {
// audioPlayer.dispose();
// WidgetsBinding.instance.removeObserver(this);
// super.dispose();
// }
// @override
// void didChangeAppLifecycleState(AppLifecycleState state) {
// super.didChangeAppLifecycleState(state);
// if (state == AppLifecycleState.resumed) {
// // Resume audio playback if necessary
// // if (isPlaying) {
// // audioPlayer.play();
// // }
// state = state;
// } else if (state == AppLifecycleState.paused) {
// // Pause audio playback if necessary
// // if (isPlaying) {
// // audioPlayer.pause();
// // }
// }
// }
String formatDuration(Duration duration) {
final hours = duration.inHours.remainder(60).toString().padLeft(2, '0');
final minutes = duration.inMinutes.remainder(60).toString().padLeft(2, '0');
final seconds = duration.inSeconds.remainder(60).toString().padLeft(2, '0');
return '$hours:$minutes:$seconds';
}
@override
Widget build(BuildContext context) {
final Size screenSize = MediaQuery.of(context).size;
final double width = screenSize.width;
final double height = screenSize.height;
final playerStateModel = Provider.of<PlayerStateModel>(context, listen: false);
return Scaffold(
appBar: AppBar(),
body: ChangeNotifierProvider(
create: (_) => PlayerStateModel(),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: width * 0.03),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Center(
child: Card(
elevation: 10,
child: ClipRRect(
borderRadius: BorderRadius.circular(10),
child: Image.network(
widget.poster,
height: height * 0.5,
width: width,
fit: BoxFit.fill,
),
),
),
),
SizedBox(
height: height * 0.05,
),
Center(
child: Text(
episode.split("=")[0],
style: TextStyle(
fontSize: height * 0.03,
),
),
),
SizedBox(
height: height * 0.01,
),
Center(
child: Text(
widget.story,
style: TextStyle(
fontSize: height * 0.02,
),
),
),
SizedBox(
height: height * 0.02,
),
Consumer<PlayerStateModel>(
builder: (context, playerState, child) {
_value = playerState.currentPosition.inSeconds.toDouble();
return Slider(
value: _value,
min: 0.0,
max: playerState.duration.inSeconds.toDouble(),
inactiveColor: globals.shadowColor,
onChanged: (newValue) {
final clampedValue = newValue.clamp(0.0, playerState.duration.inSeconds.toDouble());
audioPlayer.seek(Duration(seconds: clampedValue.toInt()));
setState(() {
_value = clampedValue;
});
},
);
},
),
// StreamBuilder<Duration?>(
// stream: audioPlayer.durationStream,
// builder: (context, snapshot) {
// final duration = snapshot.data ?? Duration.zero;
// return Slider(
// value: _value,
// min: 0.0,
// max: duration.inSeconds.toDouble(),
// inactiveColor: globals.shadowColor,
// onChanged: (newValue) {
// final clampedValue = newValue.clamp(0.0, duration.inSeconds.toDouble());
// audioPlayer.seek(Duration(seconds: clampedValue.toInt()));
// },
// );
// },
// ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
// StreamBuilder<Duration>(
// stream: audioPlayer.positionStream,
// builder: (context, snapshot) {
// final position = snapshot.data ?? Duration.zero;
// return Text(formatDuration(position));
// },
// ),
Consumer<PlayerStateModel>(
builder: (context, playerState, child) {
return Text(formatDuration(playerState.currentPosition));
},
),
// StreamBuilder<Duration?>(
// stream: audioPlayer.durationStream,
// builder: (context, snapshot) {
// final duration = snapshot.data ?? Duration.zero;
// return Text(formatDuration(duration));
// },
// ),
Consumer<PlayerStateModel>(
builder: (context, playerState, child) {
return Text(formatDuration(playerState.duration));
},
),
],
),
SizedBox(
height: height * 0.02,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton(
onPressed: () {
showAlert(context, "data.values.toList()[index]");
audioPlayer.seekToPrevious();
},
iconSize: height * 0.05,
color: Colors.storyfytheme,
icon: const Icon(
Icons.skip_previous_rounded,
),
),
IconButton(
onPressed: () async {
final currentPosition = audioPlayer.position;
final duration = audioPlayer.duration;
if (currentPosition != null && duration != null) {
final newPosition = currentPosition - const Duration(seconds: 10);
final clampedPosition = newPosition.isNegative ? Duration.zero : newPosition;
audioPlayer.seek(clampedPosition);
}
},
iconSize: height * 0.05,
color: Colors.storyfytheme,
icon: const Icon(
Icons.replay_10_rounded,
),
),
IconButton(
onPressed: () {
if (mounted == true) {
playerStateModel.setIsPlaying(!playerStateModel.isPlaying);
(isPlaying) ? audioPlayer.pause() : audioPlayer.play();
setState(() {
isPlaying = !isPlaying;
});
}
},
iconSize: height * 0.05,
color: Colors.storyfytheme,
icon: isPlaying
? const Icon(
Icons.pause,
)
: const Icon(
Icons.play_arrow_rounded,
),
),
IconButton(
onPressed: () async {
final currentPosition = audioPlayer.position;
final duration = audioPlayer.duration;
if (currentPosition != null && duration != null) {
final newPosition = currentPosition + const Duration(seconds: 10);
final clampedPosition = newPosition.compareTo(duration) > 0 ? duration : newPosition;
audioPlayer.seek(clampedPosition);
}
// await audioPlayer.seek(Duration(
// seconds: audioPlayer.position.inSeconds + 10));
},
iconSize: height * 0.05,
color: Colors.storyfytheme,
icon: const Icon(
Icons.forward_10_rounded,
),
),
IconButton(
onPressed: () {
showAlert(context, "data.values.toList()[index]");
audioPlayer.seekToNext();
},
iconSize: height * 0.05,
color: Colors.storyfytheme,
icon: const Icon(
Icons.skip_next_rounded,
),
)
],
)
],
),
),
),
);
}
}
and below is the state class
import 'package:flutter/widgets.dart';
import 'package:provider/provider.dart';
class PlayerStateModel extends ChangeNotifier {
bool _isPlaying = true;
Duration _currentPosition = Duration.zero;
Duration _duration = Duration.zero;
bool get isPlaying => _isPlaying;
Duration get duration => _duration;
Duration get currentPosition => _currentPosition;
void setIsPlaying(bool value) {
_isPlaying = value;
notifyListeners();
}
void setCurrentPosition(Duration value) {
_currentPosition = value;
notifyListeners();
}
void setDuration(Duration value) {
_duration = value;
notifyListeners();
}
}
2
Answers
UPDATE
So, below is the updated code that works well when we are in current screen. but let us consider a situation that you need to some how leaving the app and revisiting this screen then it will not get updated values. also I wan to implement such on tap event on audio notification that auto navigates
PlayingStory
screen.Because this is a lot of code, there might be other errors, but on a first quick look:
why are you inserting two
ChangeNotifierProvider
?Your inner
Consumer
widgets will use thePlayerStateModel
of the inner provider, but your modifications from youraudioInit
access and modify thePlayerStateModel
of the outer provider instead for which the consumers will get no updates.Also as a side note, you sometimes still use the local
isPlaying
of the state object sometimes withsetState
and sometimes not and sometimes you use thePlayerStateModel.isPlaying
instead. Is this intended?