I'm using audioplayers
library in flutter and I'm trying to save and restore the player position to except playing from the first position, like with caching player seek bar position, in this code, I tried to save and restore that with SharedPreference
but my implemented is unsuccessful
class ApplicationSettings {
ApplicationSettings(StreamingSharedPreferences preferences)
: showIntro = preferences.getBool('showIntro', defaultValue: false),
pageViewIndex = preferences.getInt('pageViewIndex', defaultValue: 0),
audioPosition = preferences.getString('audioPosition', defaultValue: "{}")
;
final Preference<bool> showIntro;
final Preference<int> pageViewIndex;
final Preference<String> audioPosition;
}
AudioInformation
class:
part'audio_information.g.dart';
@JsonSerializable(nullable: true)
class AudioInformation {
final String productName;
final int audioPosition;
AudioInformation(this.productName, this.audioPosition);
factory AudioInformation.fromJson(Map<String, dynamic> json) => _$AudioInformationFromJson(json);
Map<String, dynamic> toJson() => _$AudioInformationToJson(this);
}
PlayerWidget
class:
enum PlayerState { stopped, playing, paused }
enum PlayingRouteState { speakers, earpiece }
class PlayerWidget extends StatefulWidget {
final String url;
final PlayerMode mode;
final String productName;
final String imageUrl;
PlayerWidget({Key key, @required this.url, this.mode = PlayerMode.MEDIA_PLAYER, @required this.productName, @required this.imageUrl}) : super(key: key);
@override
State<StatefulWidget> createState() {
return _PlayerWidgetState(url, mode);
}
}
class _PlayerWidgetState extends State<PlayerWidget> {
_PlayerWidgetState(this.url, this.mode);
String url;
PlayerMode mode;
AudioPlayer _audioPlayer;
Duration _duration;
Duration _position;
PlayerState _playerState = PlayerState.stopped;
PlayingRouteState _playingRouteState = PlayingRouteState.speakers;
StreamSubscription _durationSubscription;
StreamSubscription _positionSubscription;
StreamSubscription _playerCompleteSubscription;
StreamSubscription _playerErrorSubscription;
StreamSubscription _playerStateSubscription;
StreamSubscription<PlayerControlCommand> _playerControlCommandSubscription;
get _isPlaying => _playerState == PlayerState.playing;
get _isPaused => _playerState == PlayerState.paused;
get _durationText => _duration?.toString()?.split('.')?.first ?? '';
get _positionText => _position?.toString()?.split('.')?.first ?? '';
Preference<String> _audioPosition;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_audioPosition = Provider.of<ApplicationSettings>(context).audioPosition;
}
@override
void initState() {
super.initState();
_initAudioPlayer();
_play();
}
@override
void dispose() {
_audioPlayer.dispose();
_durationSubscription?.cancel();
_positionSubscription?.cancel();
_playerCompleteSubscription?.cancel();
_playerErrorSubscription?.cancel();
_playerStateSubscription?.cancel();
_playerControlCommandSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return PreferenceBuilder(
preference: _audioPosition,
builder: (context, String audioDetail) {
/* SAVE position*/
AudioInformation _audio = AudioInformation('${widget.productName}', _duration?.inMilliseconds?.round()??0);
_audioPosition.setValue(_audio.toJson().toString());
return Scaffold(
appBar: AppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(Icons.audiotrack_outlined),
Expanded(
child: Text(
' - ${widget.productName}',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
),
],
),
),
body: Stack(
children: [
Positioned.fill(
child: CachedNetworkImage(
imageUrl: widget.imageUrl,
fit: BoxFit.cover,
)),
Container(
width: double.infinity,
height: double.infinity,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 2.0, sigmaY: 2.0),
child: Container(
color: Colors.white.withOpacity(0.7),
),
),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.all(16.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(11.0),
boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.2), offset: Offset(0.0, 0.0), spreadRadius: 1.0)],
border: Border.all(color: Colors.black)),
child: ClipRRect(
borderRadius: BorderRadius.circular(10.0),
child: CachedNetworkImage(
imageUrl: '${widget.imageUrl}',
fit: BoxFit.cover,
width: 150.0,
),
),
),
Container(
margin: EdgeInsets.all(8.0),
padding: EdgeInsets.all(5.0),
decoration: BoxDecoration(color: Colors.white.withOpacity(0.5), border: Border.all(color: Colors.black), borderRadius: BorderRadius.circular(5.0)),
child: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
IconButton(
key: Key('play_button'),
onPressed: _isPlaying ? null : () => _play(),
iconSize: 64.0,
icon: Icon(MdiIcons.playCircle),
color: Colors.black,
),
IconButton(
key: Key('pause_button'),
onPressed: _isPlaying ? () => _pause() : null,
iconSize: 64.0,
icon: Icon(MdiIcons.pauseCircle),
color: Colors.green[900],
),
IconButton(
key: Key('stop_button'),
onPressed: _isPlaying || _isPaused ? () => _stop() : null,
iconSize: 64.0,
icon: Icon(MdiIcons.stopCircle),
color: Colors.indigo[700],
),
],
),
Slider(
onChanged: (v) {
final position = v * _duration.inMilliseconds;
_audioPlayer.seek(Duration(milliseconds: position.round()));
/* SAVE position*/
AudioInformation _audio = AudioInformation('${widget.productName}', position.round());
_audioPosition.setValue(_audio.toJson().toString());
},
value: (_position != null && _duration != null && _position.inMilliseconds > 0 && _position.inMilliseconds < _duration.inMilliseconds)
? _position.inMilliseconds / _duration.inMilliseconds
: 0.0,
),
],
),
),
_durationText != null && _durationText.toString().isNotEmpty
? Container(
height: 43.0,
padding: EdgeInsets.all(8.0),
decoration: BoxDecoration(color: Colors.white.withOpacity(0.5), border: Border.all(color: Colors.black), borderRadius: BorderRadius.circular(5.0)),
child: Text(
_position != null
? '${_positionText ?? ''} / ${_durationText ?? ''}'
: _duration != null
? _durationText
: ' --- ',
style: TextStyle(fontSize: 24.0),
),
)
: Container(
height: 43.0,
),
],
),
],
),
);
});
}
void _initAudioPlayer() {
_audioPlayer = AudioPlayer(mode: mode);
_durationSubscription = _audioPlayer.onDurationChanged.listen((duration) {
setState(() => _duration = duration);
});
_positionSubscription = _audioPlayer.onAudioPositionChanged.listen((p) => setState(() {
_position = p;
}));
_playerCompleteSubscription = _audioPlayer.onPlayerCompletion.listen((event) {
_onComplete();
setState(() {
_position = _duration;
});
});
_playerErrorSubscription = _audioPlayer.onPlayerError.listen((msg) {
print('audioPlayer error : $msg');
setState(() {
_playerState = PlayerState.stopped;
_duration = Duration(seconds: 0);
_position = Duration(seconds: 0);
});
});
_playerControlCommandSubscription = _audioPlayer.onPlayerCommand.listen((command) {
print('command');
});
_audioPlayer.onPlayerStateChanged.listen((state) {
if (!mounted) return;
});
_audioPlayer.onNotificationPlayerStateChanged.listen((state) {
if (!mounted) return;
//setState(() => _audioPlayerState = state);
});
_playingRouteState = PlayingRouteState.speakers;
}
Future<int> _play() async {
final playPosition = (_position != null && _duration != null && _position.inMilliseconds > 0 && _position.inMilliseconds < _duration.inMilliseconds) ? _position : null;
final result = await _audioPlayer.play(url, position: playPosition);
if (result == 1) setState(() => _playerState = PlayerState.playing);
_audioPlayer.setPlaybackRate(playbackRate: 1.0);
/* RESTORE position*/
if (_audioPosition?.getValue() != null) {
final _res = jsonDecode(_audioPosition.getValue());
int pos = _audioPosition.getValue() == '{}' ? 0 : _res['audioPosition'];
_audioPlayer.seek(Duration(milliseconds: pos));
}
return result;
}
Future<int> _pause() async {
final result = await _audioPlayer.pause();
if (result == 1) setState(() => _playerState = PlayerState.paused);
return result;
}
Future<int> _stop() async {
final result = await _audioPlayer.stop();
if (result == 1) {
setState(() {
_playerState = PlayerState.stopped;
_position = Duration();
});
}
return result;
}
void _onComplete() {
setState(() => _playerState = PlayerState.stopped);
}
}
In the PreferenceBuilder widget, you should remove the first "Save Position" code, those 2 lines code before the scaffold return overrides the value of the saved audio position with a zero value when the widget builds for the first time, this is because the duration is null when the widget builds for the first time. A better place to save the audio position in sharedpreference is in the
_audioplayer.onAudioPositionChanged()
function in the
_initAudioPlayer()
function. You can check the snippet below to get an idea of what I am talking about
AudioInformation _audio = AudioInformation('${widget.productName}', p?.inMilliseconds?.round() ?? 0);
_audioPosition.setValue(_audio.toJson().toString());
setState(() {
_position = p;
});
});
This should solve the issue you are facing