guru_sdk/guru_app/plugins/soundpool/lib/soundpool.dart

429 lines
14 KiB
Dart

import 'dart:async';
import 'dart:typed_data';
import 'package:soundpool_platform_interface/soundpool_platform_interface.dart';
import 'src/platform_options.dart';
export 'src/platform_options.dart';
class Soundpool {
static const _DEFAULT_SOUND_PRIORITY = 1;
final int _maxStreams;
final StreamType _streamType;
final Map<String, dynamic> _platformOptions;
final Completer<int> _soundpoolId = Completer();
SoundpoolPlatform get _platformInstance => SoundpoolPlatform.instance;
bool _disposed = false;
Soundpool._(this._platformOptions, [StreamType type = StreamType.music, int maxStreams = 1])
: assert(maxStreams > 0),
_streamType = type,
_maxStreams = maxStreams;
@override
String toString() {
return 'Soundpool($_streamType:$hashCode)';
}
/// Creates the Soundpool instance with stream type setting set.
/// Soundpool can play up to [maxStreams] of simultaneous streams
///
/// *Note:* Optional [streamType] parameter has effect on Android only.
@Deprecated('Use `fromOptions` instead')
factory Soundpool({StreamType streamType = StreamType.music, int maxStreams = 1}) =>
Soundpool.fromOptions(
options: SoundpoolOptions(
streamType: streamType,
maxStreams: maxStreams,
),
);
/// Creates the Soundpool instance with stream type setting set.
factory Soundpool.fromOptions({SoundpoolOptions options = SoundpoolOptions.kDefault}) {
return Soundpool._(
options._platformOptions,
options.streamType,
options.maxStreams,
).._connect();
}
/// Prepares sound for playing
///
/// ```
/// load(await rootBundle.load("sounds/dices.m4a")); // loads file from assets
/// ```
///
/// Returns soundId for future use in [play] (soundId > -1) or `-1` when sound file failed to load
///
/// ## web
/// [priority] is ignored.
Future<int> load(ByteData rawSound, {int priority = _DEFAULT_SOUND_PRIORITY}) =>
loadUint8List(rawSound.buffer.asUint8List(), priority: priority);
/// Prepares sound for playing
///
/// Loads sound data and buffers it for future playing.
///
/// Returns soundId for future use in [play] (soundId > -1) or `-1` when sound data failed to load
///
/// ## web
/// [priority] is ignored.
Future<int> loadUint8List(Uint8List rawSound, {int priority = _DEFAULT_SOUND_PRIORITY}) async {
assert(!_disposed, "Soundpool instance was already disposed");
int poolId = await _soundpoolId.future;
int soundId = await _platformInstance.loadUint8List(poolId, rawSound, priority);
return soundId;
}
/// Prepares sound for playing
///
/// Loads sound data from file pointed by [uri]
/// Returns soundId for future use in [play] (soundId > -1) or `-1` when sound file failed to load
///
/// ## web
/// [priority] is ignored.
Future<int> loadUri(String uri, {int priority = _DEFAULT_SOUND_PRIORITY}) async {
assert(!_disposed, "Soundpool instance was already disposed");
int poolId = await _soundpoolId.future;
int soundId = await _platformInstance.loadUri(poolId, uri, priority);
return soundId;
}
/// Prepares sound for playing and plays immediately when loaded
///
/// ```
/// loadAndPlay(await rootBundle.load("sounds/dices.m4a")); // loads file from assets
/// ```
///
/// Returns soundId for future use in [play] (soundId > -1) or `-1` when sound file failed to load
///
/// See also:
///
/// * [load], which allows for precaching the sound data
///
/// ## web
/// [priority] and [repeat] are ignored. The sound is played only once.
Future<int> loadAndPlay(ByteData rawSound,
{int priority = _DEFAULT_SOUND_PRIORITY, int repeat = 0, double rate = 1.0}) async {
int soundId = await load(rawSound, priority: priority);
if (soundId > -1) {
play(soundId, repeat: repeat, rate: rate);
}
return soundId;
}
/// Prepares sound for playing and plays immediately after loading
///
/// Loads sound data, buffers it for future playing and starts playing immediately
/// when loaded.
///
/// Returns soundId for future use in [play] (soundId > -1) or `-1` when sound file failed to load
///
/// See also:
///
/// * [loadUint8List], which allows for precaching the sound data
///
/// ## web
/// [priority] and [repeat] are ignored. The sound is played only once.
Future<int> loadAndPlayUint8List(Uint8List rawSound,
{int priority = _DEFAULT_SOUND_PRIORITY, int repeat = 0, double rate = 1.0}) async {
assert(!_disposed, "Soundpool instance was already disposed");
int soundId = await loadUint8List(rawSound, priority: priority);
if (soundId > -1) {
play(soundId, repeat: repeat, rate: rate);
}
return soundId;
}
/// Prepares sound for playing and plays immediately after loading
///
/// Loads sound data from file pointed by [uri]
///
/// Returns soundId for future use in [play] (soundId > -1) or `-1` when sound file failed to load
///
/// See also:
///
/// * [loadUri], which allows for precaching the sound data
///
/// ## web
/// [priority] and [repeat] are ignored. The sound is played only once.
Future<int> loadAndPlayUri(String uri,
{int priority = _DEFAULT_SOUND_PRIORITY, int repeat = 0, double rate = 1.0}) async {
assert(!_disposed, "Soundpool instance was already disposed");
int soundId = await loadUri(uri, priority: priority);
if (soundId > -1) {
play(soundId, repeat: repeat, rate: rate);
}
return soundId;
}
/// Plays sound identified by [soundId]
///
/// Returns streamId to further control playback or 0 if playing failed to
/// start
///
/// ## web
/// [repeat] is ignored. The sound is played only once.
Future<int> play(int soundId, {int repeat = 0, double rate = 1.0}) async {
assert(!_disposed, "Soundpool instance was already disposed");
assert(
rate >= 0.5 && rate <= 2.0,
"'rate' has to be value in (0.5 - 2.0) range",
);
assert(soundId > -1, "Invalid 'soundId' parameter. Only values greater than -1 are valid.");
int poolId = await _soundpoolId.future;
return await _platformInstance.play(poolId, soundId, repeat, rate);
}
/// Starts playing the sound identified by [soundId].
///
/// Returns instance to control playback
///
/// ## web
/// [repeat] is ignored. The sound is played only once.
Future<AudioStreamControl> playWithControls(int soundId,
{int repeat = 0, double rate = 1.0}) async {
final streamId = await play(soundId, repeat: repeat, rate: rate);
return AudioStreamControl._(this, streamId);
}
/// Stops playing sound identified by [streamId]
///
///
Future<void> stop(int streamId) async {
assert(!_disposed, "Soundpool instance was already disposed");
assert(streamId > 0, "Invalid 'streamId' parameter. Only values greater than 0 are valid.");
int poolId = await _soundpoolId.future;
await _platformInstance.stop(poolId, streamId);
}
/// Pauses playing sound identified by [streamId]
///
/// ## web
/// *DOES NOT WORK!*.
Future<void> pause(int streamId) async {
assert(!_disposed, "Soundpool instance was already disposed");
assert(streamId > 0, "Invalid 'streamId' parameter. Only values greater than 0 are valid.");
int poolId = await _soundpoolId.future;
await _platformInstance.pause(poolId, streamId);
}
/// Resumes playing sound identified by [streamId]
///
/// ## web
/// *DOES NOT WORK!*.
Future<void> resume(int streamId) async {
assert(!_disposed, "Soundpool instance was already disposed");
assert(streamId > 0, "Invalid 'streamId' parameter. Only values greater than 0 are valid.");
int poolId = await _soundpoolId.future;
await _platformInstance.resume(poolId, streamId);
}
/// Sets volume for playing sound identified by [soundId] or [streamId]
///
/// At least [volume] or both [volumeLeft] and [volumeRight] have to be passed
///
/// ## web
/// [volumeLeft] and [volumeRight] pair has no effect.
Future<void> setVolume(
{int? soundId,
int? streamId,
double? volume,
double? volumeLeft,
double? volumeRight}) async {
assert(!_disposed, "Soundpool instance was already disposed");
assert(
soundId != null || streamId != null,
"Either 'soundId' or 'streamI"
"d' has to be passed");
assert(
volume != null || (volumeLeft != null && volumeRight != null),
"Ei"
"ther 'volume' or both 'volumeLeft' and 'volumeRight' has to be "
"passed");
assert(streamId == null || streamId > 0,
"Invalid 'streamId' parameter. Only values greater than 0 are valid.");
assert(soundId == null || soundId > -1,
"Invalid 'soundId' parameter. Only values greater than -1 are valid.");
if (volume != null && volumeLeft == null) {
volumeLeft = volume;
}
if (volume != null && volumeRight == null) {
volumeRight = volume;
}
await _soundpoolId.future.then((poolId) =>
_platformInstance.setVolume(poolId, soundId, streamId, volumeLeft, volumeRight));
}
/// Sets playback rate. A value of 1.0 means normal speed, 0.5 - half speed, 2.0 - double speed.
///
/// Available value range: (0.5 - 2.0)
Future<void> setRate({required int streamId, required double playbackRate}) async {
assert(!_disposed, "Soundpool instance was already disposed");
assert(streamId > 0, "Invalid 'streamId' parameter. Only values greater than 0 are valid.");
assert(
playbackRate >= 0.5 && playbackRate <= 2.0,
"'playbackRate' has to be value in (0.5 - 2.0) range",
);
await _soundpoolId.future
.then((poolId) => _platformInstance.setRate(poolId, streamId, playbackRate));
}
/// Releases loaded sounds
///
/// Should be called to clear buffered sounds
Future<void> release() async {
assert(!_disposed, "Soundpool instance was already disposed");
await _soundpoolId.future.then((poolId) => _platformInstance.release(poolId));
}
/// Disposes soundpool
///
/// The Soundpool instance is not usable anymore
void dispose() {
_soundpoolId.future.then((poolId) => _platformInstance.dispose(poolId), onError: (_) {});
_disposed = true;
}
StreamType get streamType => _streamType;
/// Connects to native Soundpool instance
_connect() async {
final int id = await _platformInstance.init(_streamType.index, _maxStreams, _platformOptions);
if (id >= 0) {
_soundpoolId.complete(id);
} else {
_soundpoolId.completeError("Soundpool initialization failed");
}
}
}
/// The type of the audio stream. Different streams may have distinct audio
/// settings (e.g. volume level) within the system
///
/// All sounds for particular Soundpool would be played using the selected stream
enum StreamType {
/// Audio stream for the phone ring
ring,
/// Audio stream for alarms
alarm,
/// Audio stream for music playbacks
music,
/// Audio stream for notifications
notification
}
/// Controls for played sound
///
/// Utility class that wraps the stream id with a easy-to-use API
class AudioStreamControl {
final Soundpool _pool;
bool _playing = true;
bool _stopped = false;
/// Id of the stream that is controlled by this object
final int stream;
/// Returns true if stream is not paused
///
/// This does not reflect actual player state. The sound may have been finished
/// any moment before by reaching the end
bool get playing => _playing;
/// Returns true if stream has been stopped and cannot be resumed
///
/// This does not reflect actual player state. The sound may have been finished
/// any moment before by reaching the end
bool get stopped => _stopped;
AudioStreamControl._(this._pool, this.stream);
/// Stops playing the stream associated with this object
Future<void> stop() async {
await _pool.stop(stream);
_stopped = true;
_playing = false;
}
/// Pauses playing the stream associated with this object
Future<void> pause() async {
if (!_stopped && _playing) {
await _pool.pause(stream);
_playing = false;
}
}
/// Resumes paused stream associated with this object
Future<void> resume() async {
if (!_stopped && _playing) {
await _pool.pause(stream);
_playing = true;
}
}
/// Sets volume for playing sound identified by [soundId] or [streamId]
///
/// At least [volume] or both [volumeLeft] and [volumeRight] have to be passed
Future setVolume({double? volume, double? volumeLeft, double? volumeRight}) {
return _pool.setVolume(
streamId: stream, volume: volume, volumeLeft: volumeLeft, volumeRight: volumeRight);
}
/// Sets playback rate. A value of 1.0 means normal speed, 0.5 - half speed, 2.0 - double speed.
///
/// Available value range: (0.5 - 2.0)
Future setRate({required double playbackRate}) {
return _pool.setRate(
streamId: stream,
playbackRate: playbackRate,
);
}
}
class SoundpoolOptions {
/// The type of stream used by the pool
final StreamType streamType;
/// Maximum number of pararell sounds being played
final int maxStreams;
/// Android specific options
final SoundpoolOptionsAndroid androidOptions;
/// iOS specific options
final SoundpoolOptionsIos iosOptions;
/// Web specific options
final SoundpoolOptionsWeb webOptions;
/// MacOS specific options
final SoundpoolOptionsMacos macosOptions;
const SoundpoolOptions({
this.streamType = StreamType.music,
this.maxStreams = 1,
this.androidOptions = SoundpoolOptionsAndroid.kDefault,
this.iosOptions = SoundpoolOptionsIos.kDefault,
this.webOptions = SoundpoolOptionsWeb.kDefault,
this.macosOptions = SoundpoolOptionsMacos.kDefault,
});
static const kDefault = SoundpoolOptions();
Map<String, dynamic> get _platformOptions => Map.fromEntries([
...androidOptions.toOptionsMap().entries.map((e) => MapEntry('android_${e.key}', e.value)),
...iosOptions.toOptionsMap().entries.map((e) => MapEntry('ios_${e.key}', e.value)),
...webOptions.toOptionsMap().entries.map((e) => MapEntry('web_${e.key}', e.value)),
...macosOptions.toOptionsMap().entries.map((e) => MapEntry('macos_${e.key}', e.value)),
]);
}