-
Notifications
You must be signed in to change notification settings - Fork 823
/
audioplayer.dart
297 lines (251 loc) · 9.18 KB
/
audioplayer.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
import 'dart:async';
// TODO(gustl22): remove when upgrading min Flutter version to >=3.3.0
// ignore: unnecessary_import
import 'dart:typed_data';
import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers_platform_interface/audioplayers_platform_interface.dart';
import 'package:flutter/services.dart';
import 'package:uuid/uuid.dart';
const _uuid = Uuid();
/// This represents a single AudioPlayer, which can play one audio at a time.
/// To play several audios at the same time, you must create several instances
/// of this class.
///
/// It holds methods to play, loop, pause, stop, seek the audio, and some useful
/// hooks for handlers and callbacks.
class AudioPlayer {
static final global = GlobalPlatformInterface.instance;
static final _platform = AudioplayersPlatform.instance;
/// This is the [AudioCache] instance used by this player.
/// Unless you want to control multiple caches separately, you don't need to
/// change anything as the global instance will be used by default.
AudioCache audioCache = AudioCache.instance;
PlayerState _playerState = PlayerState.stopped;
PlayerState get state => _playerState;
Source? _source;
Source? get source => _source;
set state(PlayerState state) {
if (!_playerStateController.isClosed) {
_playerStateController.add(state);
}
_playerState = state;
}
late StreamSubscription _onPlayerCompleteStreamSubscription;
final StreamController<PlayerState> _playerStateController =
StreamController<PlayerState>.broadcast();
/// Stream of changes on player state.
Stream<PlayerState> get onPlayerStateChanged => _playerStateController.stream;
/// Stream of changes on audio position.
///
/// Roughly fires every 200 milliseconds. Will continuously update the
/// position of the playback if the status is [PlayerState.playing].
///
/// You can use it on a progress bar, for instance.
Stream<Duration> get onPositionChanged =>
_platform.positionStream.filter(playerId);
/// Stream of changes on audio duration.
///
/// An event is going to be sent as soon as the audio duration is available
/// (it might take a while to download or buffer it).
Stream<Duration> get onDurationChanged =>
_platform.durationStream.filter(playerId);
/// Stream of player completions.
///
/// Events are sent every time an audio is finished, therefore no event is
/// sent when an audio is paused or stopped.
///
/// [ReleaseMode.loop] also sends events to this stream.
Stream<void> get onPlayerComplete =>
_platform.completeStream.filter(playerId);
/// Stream of seek completions.
///
/// An event is going to be sent as soon as the audio seek is finished.
Stream<void> get onSeekComplete =>
_platform.seekCompleteStream.filter(playerId);
/// An unique ID generated for this instance of [AudioPlayer].
///
/// This is used to properly exchange messages with the [MethodChannel].
final String playerId;
/// Current mode of the audio player. Can be updated at any time, but is going
/// to take effect only at the next time you play the audio.
PlayerMode _mode = PlayerMode.mediaPlayer;
PlayerMode get mode => _mode;
ReleaseMode _releaseMode = ReleaseMode.release;
ReleaseMode get releaseMode => _releaseMode;
/// Creates a new instance and assigns an unique id to it.
AudioPlayer({String? playerId}) : playerId = playerId ?? _uuid.v4() {
_onPlayerCompleteStreamSubscription = onPlayerComplete.listen((_) {
state = PlayerState.completed;
if (releaseMode == ReleaseMode.release) {
_source = null;
}
});
}
Future<void> play(
Source source, {
double? volume,
double? balance,
AudioContext? ctx,
Duration? position,
PlayerMode? mode,
}) async {
if (mode != null) {
await setPlayerMode(mode);
}
if (volume != null) {
await setVolume(volume);
}
if (balance != null) {
await setBalance(balance);
}
if (ctx != null) {
await setAudioContext(ctx);
}
if (position != null) {
await seek(position);
}
await setSource(source);
return resume();
}
Future<void> setAudioContext(AudioContext ctx) {
return _platform.setAudioContext(playerId, ctx);
}
Future<void> setPlayerMode(PlayerMode mode) {
_mode = mode;
return _platform.setPlayerMode(playerId, mode);
}
/// Pauses the audio that is currently playing.
///
/// If you call [resume] later, the audio will resume from the point that it
/// has been paused.
Future<void> pause() async {
await _platform.pause(playerId);
state = PlayerState.paused;
}
/// Stops the audio that is currently playing.
///
/// The position is going to be reset and you will no longer be able to resume
/// from the last point.
Future<void> stop() async {
await _platform.stop(playerId);
state = PlayerState.stopped;
}
/// Resumes the audio that has been paused or stopped.
Future<void> resume() async {
await _platform.resume(playerId);
state = PlayerState.playing;
}
/// Releases the resources associated with this media player.
///
/// The resources are going to be fetched or buffered again as soon as you
/// call [resume] or change the source.
Future<void> release() async {
await _platform.release(playerId);
state = PlayerState.stopped;
_source = null;
}
/// Moves the cursor to the desired position.
Future<void> seek(Duration position) {
return _platform.seek(playerId, position);
}
/// Sets the stereo balance.
///
/// -1 - The left channel is at full volume; the right channel is silent.
/// 1 - The right channel is at full volume; the left channel is silent.
/// 0 - Both channels are at the same volume.
Future<void> setBalance(double balance) {
return _platform.setBalance(playerId, balance);
}
/// Sets the volume (amplitude).
///
/// 0 is mute and 1 is the max volume. The values between 0 and 1 are linearly
/// interpolated.
Future<void> setVolume(double volume) {
return _platform.setVolume(playerId, volume);
}
/// Sets the release mode.
///
/// Check [ReleaseMode]'s doc to understand the difference between the modes.
Future<void> setReleaseMode(ReleaseMode releaseMode) {
_releaseMode = releaseMode;
return _platform.setReleaseMode(playerId, releaseMode);
}
/// Sets the playback rate - call this after first calling play() or resume().
///
/// iOS and macOS have limits between 0.5 and 2x
/// Android SDK version should be 23 or higher
Future<void> setPlaybackRate(double playbackRate) {
return _platform.setPlaybackRate(playerId, playbackRate);
}
/// Sets the audio source for this player.
///
/// This will delegate to one of the specific methods below depending on
/// the source type.
Future<void> setSource(Source source) {
return source.setOnPlayer(this);
}
/// Sets the URL to a remote link.
///
/// The resources will start being fetched or buffered as soon as you call
/// this method.
Future<void> setSourceUrl(String url) {
_source = UrlSource(url);
return _platform.setSourceUrl(playerId, url, isLocal: false);
}
/// Sets the URL to a file in the users device.
///
/// The resources will start being fetched or buffered as soon as you call
/// this method.
Future<void> setSourceDeviceFile(String path) {
_source = DeviceFileSource(path);
return _platform.setSourceUrl(playerId, path, isLocal: true);
}
/// Sets the URL to an asset in your Flutter application.
/// The global instance of AudioCache will be used by default.
///
/// The resources will start being fetched or buffered as soon as you call
/// this method.
Future<void> setSourceAsset(String path) async {
_source = AssetSource(path);
final url = await audioCache.load(path);
return _platform.setSourceUrl(playerId, url.path, isLocal: true);
}
Future<void> setSourceBytes(Uint8List bytes) {
_source = BytesSource(bytes);
return _platform.setSourceBytes(playerId, bytes);
}
/// Get audio duration after setting url.
/// Use it in conjunction with setUrl.
///
/// It will be available as soon as the audio duration is available
/// (it might take a while to download or buffer it if file is not local).
Future<Duration?> getDuration() async {
final milliseconds = await _platform.getDuration(playerId);
if (milliseconds == null) {
return null;
}
return Duration(milliseconds: milliseconds);
}
// Gets audio current playing position
Future<Duration?> getCurrentPosition() async {
final milliseconds = await _platform.getCurrentPosition(playerId);
if (milliseconds == null) {
return null;
}
return Duration(milliseconds: milliseconds);
}
/// Closes all [StreamController]s.
///
/// You must call this method when your [AudioPlayer] instance is not going to
/// be used anymore. If you try to use it after this you will get errors.
Future<void> dispose() async {
// First stop and release all native resources.
await release();
final futures = <Future>[
if (!_playerStateController.isClosed) _playerStateController.close(),
_onPlayerCompleteStreamSubscription.cancel()
];
_source = null;
await Future.wait<dynamic>(futures);
}
}