Flutter 保存并恢复 AudioPlayer 位置

Flutter save and restore AudioPlayer position

我在 flutter 中使用 audioplayers 库,我试图保存和恢复玩家位置,除了从第一个位置播放,就像缓存玩家搜索栏位置一样,在这段代码中,我试图用 SharedPreference 保存和恢复它,但我的实现不成功

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

在 PreferenceBuilder 小部件中,您应该删除第一个“保存位置”代码,脚手架之前的那两行代码 return 在小部件构建时用零值覆盖已保存的音频位置第一次,这是因为第一次构建小部件时,持续时间为空。在 sharedpreference 中保存音频位置的更好位置是

_audioplayer.onAudioPositionChanged()

中发挥作用

_initAudioPlayer()

函数。您可以查看下面的代码片段以了解我在说什么

     AudioInformation _audio = AudioInformation('${widget.productName}', p?.inMilliseconds?.round() ?? 0);
     _audioPosition.setValue(_audio.toJson().toString());
     setState(() {
       _position = p;
     });
   });

这应该可以解决您面临的问题