I am using audioplayers plugin on iOS.
When I have the audio playing and lock the screen, the audio continues to play in the background and the iOS Lock Screen display is showing.
However when I drag the player slider forward on the iOS Lock Screen,it jumps back and resets to the beginning of the audio track, it does not seek to the dragged point.
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers/notifications.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hello_now/models/activity.dart';
import 'package:hello_now/services/user_service.dart';
class PlayerWidget extends StatefulWidget {
final String url;
final PlayerMode mode;
final bool isChecked;
final Function checkBoxCallback;
final VoidCallback? onPlayerFinished;
final String name;
final String title;
final int? fileDuration;
const PlayerWidget(
{Key? key,
required this.url,
required this.title,
this.mode = PlayerMode.MEDIA_PLAYER,
required this.name,
required this.isChecked,
required this.checkBoxCallback,
required this.onPlayerFinished,
this.fileDuration})
: super(key: key);
@override
State<StatefulWidget> createState() {
return _PlayerWidgetState(url, mode, isChecked);
}
}
class _PlayerWidgetState extends State<PlayerWidget> {
String url;
PlayerMode mode;
bool isFinished;
bool showProgress = false;
late final Function callback;
late AudioPlayer _audioPlayer;
PlayerState? _audioPlayerState;
Duration? _duration;
Duration? _position;
bool _isSeeking = false;
Color SliderColor = Color(0xFFEDAF99);
bool shouldShowFirst = true;
PlayerState _playerState = PlayerState.STOPPED;
PlayingRoute _playingRouteState = PlayingRoute.SPEAKERS;
StreamSubscription? _durationSubscription;
StreamSubscription? _positionSubscription;
StreamSubscription? _playerCompleteSubscription;
StreamSubscription? _playerErrorSubscription;
StreamSubscription? _playerStateSubscription;
StreamSubscription<PlayerControlCommand>? _playerControlCommandSubscription;
StreamSubscription? _playerSeekSubscription;
bool get _isPlaying => _playerState == PlayerState.PLAYING;
bool get _isPaused => _playerState == PlayerState.PAUSED;
String get _durationText {
return _duration?.toString().split('.').first ?? '';
}
String get _positionText => _position?.toString().split('.').first ?? '';
bool get _isPlayingThroughEarpiece =>
_playingRouteState == PlayingRoute.EARPIECE;
void callbackHandler(state) {
print('State change $state');
}
_PlayerWidgetState(this.url, this.mode, this.isFinished);
@override
void initState() {
super.initState();
_initAudioPlayer();
}
@override
void dispose() {
saveDuration();
_audioPlayer.dispose();
_audioPlayer.notificationService.clearNotification();
_durationSubscription?.cancel();
_positionSubscription?.cancel();
_playerCompleteSubscription?.cancel();
_playerErrorSubscription?.cancel();
_playerStateSubscription?.cancel();
_playerControlCommandSubscription?.cancel();
_playerSeekSubscription?.cancel();
super.dispose();
}
Future<void> saveDuration() async {
Activity activity = new Activity();
activity.durationCompleted = await _audioPlayer.getCurrentPosition();
activity.uid = FirebaseAuth.instance.currentUser!.email;
DateTime currentDate = DateTime.now(); //DateTime
Timestamp myTimeStamp = Timestamp.fromDate(currentDate);
activity.dateTime = myTimeStamp;
activity.practiceType = widget.title;
var x = activity.durationCompleted! / 1000;
var seconds = x % 60;
UserService userService = new UserService();
userService.addActivity(activity);
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.all(12.0),
child: Stack(
children: [
SliderTheme(
data: SliderTheme.of(context).copyWith(
activeTrackColor: SliderColor,
inactiveTrackColor: SliderColor.withOpacity(0.5),
trackShape: RectangularSliderTrackShape(),
trackHeight: 4.0,
thumbColor: SliderColor,
thumbShape:
RoundSliderThumbShape(enabledThumbRadius: 12.0),
overlayColor: SliderColor.withOpacity(0.5),
overlayShape:
RoundSliderOverlayShape(overlayRadius: 15.0),
),
child: Slider(
SliderColor.withOpacity(0.5),
onChanged: (v) {
print("onChanged");
final duration = _duration;
if (duration == null) {
return;
}
final Position = v * duration.inSeconds;
setState(() {
_audioPlayer
.seek(Duration(seconds: Position.round()));
});
},
onChangeEnd: (v) {
print("onChangeEnd");
final duration = _duration;
if (duration == null) {
return;
}
final Position = v * duration.inSeconds;
_audioPlayer.seek(Duration(seconds: Position.round()));
},
value: (_position != null &&
_duration != null &&
_position!.inMilliseconds > 0 &&
_position!.inMilliseconds <
_duration!.inMilliseconds)
?
_position!.inSeconds / _duration!.inSeconds
: 0.0,
),
),
],
),
),
Text(
_position != null
? '${_printDuration(_position!)} / ${_printDuration(_duration!)}'
: _duration != null
? _durationText
: '',
style: const TextStyle(fontSize: 18.0),
),
Row(
mainAxisSize: MainAxisSize.min,
children: [
AnimatedCrossFade(
crossFadeState: shouldShowFirst
? CrossFadeState.showFirst
: CrossFadeState.showSecond,
duration: const Duration(milliseconds: 200),
firstChild: IconButton(
splashColor: Colors.transparent,
key: const Key('play_button'),
onPressed: _isPlaying ? null : _play,
iconSize: 64.0,
icon: const Icon(Icons.play_arrow),
color: SliderColor,
),
secondChild: IconButton(
splashColor: Colors.transparent,
key: const Key('pause_button'),
onPressed: _isPlaying ? _pause : null,
iconSize: 64.0,
icon: const Icon(Icons.pause),
color: SliderColor,
),
),
],
),
],
),
],
);
}
void _initAudioPlayer() {
_audioPlayer = AudioPlayer(mode: mode);
_durationSubscription = _audioPlayer.onDurationChanged.listen((duration) {
setState(() => _duration = duration);
if (Theme.of(context).platform == TargetPlatform.iOS) {
_audioPlayer.notificationService.startHeadlessService();
_audioPlayer.notificationService.setNotification(
title: widget.title,
artist: widget.name,
albumTitle: 'Hello Now',
imageUrl:
'https://static.wixstatic.com/media/c59891_e7c14f57cc2d42059734f4b44dc8426c~mv2.png/v1/fill/w_260,h_170,al_c,usm_0.66_1.00_0.01/HelloNow-Logo-Stacked-Black%20(1).png',
forwardSkipInterval: const Duration(seconds: 30), // default is 30s
backwardSkipInterval: const Duration(seconds: 30), // default is 30s
duration: duration,
enableNextTrackButton: true,
enablePreviousTrackButton: true,
);
}
});
_playerSeekSubscription = _audioPlayer.onSeekComplete.listen((finished) {
_audioPlayer.onAudioPositionChanged.listen((p) => setState(() {
_position = p;
}));
});
_positionSubscription =
_audioPlayer.onAudioPositionChanged.listen((p) => setState(() {
_position = p;
}));
_playerCompleteSubscription =
_audioPlayer.onPlayerCompletion.listen((event) {
_onComplete();
widget.onPlayerFinished!();
setState(() {
_position = _duration;
shouldShowFirst = !shouldShowFirst;
});
});
_playerErrorSubscription = _audioPlayer.onPlayerError.listen((msg) {
print('audioPlayer error : $msg');
setState(() {
_playerState = PlayerState.STOPPED;
_duration = const Duration();
_position = const Duration();
});
});
_playerControlCommandSubscription =
_audioPlayer.notificationService.onPlayerCommand.listen((command) {
_audioPlayer.onAudioPositionChanged.listen((p) => setState(() {
_position = p;
}));
print('command: $command');
});
_audioPlayer.onPlayerStateChanged.listen((state) {
if (mounted) {
setState(() {
_audioPlayerState = state;
});
}
});
_audioPlayer.onNotificationPlayerStateChanged.listen((state) {
if (mounted) {
setState(() => _audioPlayerState = state);
}
});
_playingRouteState = PlayingRoute.SPEAKERS;
}
Future<int> _play() async {
final playPosition = (_position != null &&
_duration != null &&
_position!.inMilliseconds > 0 &&
_position!.inMilliseconds < _duration!.inMilliseconds)
? _position
: null;
int result = await _audioPlayer.play(url, position: playPosition);
if (result == 1) {
setState(() {
_playerState = PlayerState.PLAYING;
shouldShowFirst = !shouldShowFirst;
});
}
return result;
}
Future<int> _pause() async {
final result = await _audioPlayer.pause();
if (result == 1) {
setState(() {
_playerState = PlayerState.PAUSED;
shouldShowFirst = !shouldShowFirst;
});
}
return result;
}
Future<int> _earpieceOrSpeakersToggle() async {
final result = await _audioPlayer.earpieceOrSpeakersToggle();
if (result == 1) {
setState(() => _playingRouteState = _playingRouteState.toggle());
}
return result;
}
Future<int> _stop() async {
final result = await _audioPlayer.stop();
if (result == 1) {
setState(() {
_playerState = PlayerState.STOPPED;
_position = const Duration();
});
}
return result;
}
void _onComplete() {
setState(() => _playerState = PlayerState.STOPPED);
}
}
String _printDuration(Duration duration) {
String twoDigits(int n) => n.toString().padLeft(2, "0");
String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60));
String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60));
return "$twoDigitMinutes:$twoDigitSeconds";
}
