Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: Improve Docs #1710

Merged
merged 10 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
This tutorial should help you get started with the audioplayers library, covering the basics but guiding you all the way through advanced features.
You can also play around with our [official example app](https://bluefireteam.github.io/audioplayers/) and [explore the code](https://github.com/bluefireteam/audioplayers/tree/main/packages/audioplayers/example), that showcases every feature the library has to offer.

In order to install this package, add the [latest version](pub.dev/packages/audioplayers) of `audioplayers` to your `pubspec.yaml` file. This packages uses [the Federated Plugin](https://docs.flutter.dev/development/packages-and-plugins/developing-packages) guidelines to support multiple platforms, so it should just work on all supported platforms your app is built for without any extra configuration. You should not need to add the `audioplayers_*` packages directly.
In order to install this package, add the [latest version](pub.dev/packages/audioplayers) of `audioplayers` to your `pubspec.yaml` file.
This package uses [the Federated Plugin](https://docs.flutter.dev/development/packages-and-plugins/developing-packages) guidelines to support multiple platforms, so it should just work on all supported platforms your app is built for without any extra configuration.
You do not need to add the `audioplayers_*` packages directly.

## Setup Platforms

Expand All @@ -29,8 +31,9 @@ Each AudioPlayer is created empty and has to be configured with an audio source
The source (cf. packages/audioplayers/lib/src/source.dart) is basically what audio you are playing (a song, sound effect, radio stream, etc), and it can have one of 4 types:

1. **UrlSource**: get the audio from a remote URL from the Internet. This can be a direct link to a supported file to be downloaded, or a radio stream.
1. **DeviceFileSource**: access a file in the user's device, probably selected by a file picker
1. **AssetSource**: play an asset bundled with your app, normally within the `assets` directory
1. **DeviceFileSource**: access a file in the user's device, probably selected by a file picker.
1. **AssetSource**: play an asset bundled with your app, by default within the `assets` directory.
To customize the prefix, see [AudioCache](#audiocache).
1. **BytesSource** (only some platforms): pass in the bytes of your audio directly (read it from anywhere).

In order to set the source on your player instance, call `setSource` with the appropriate source object:
Expand Down Expand Up @@ -76,7 +79,7 @@ Changes the current position (note: this does not affect the "playing" status).
Stops the playback but keeps the current position.

```dart
await player.pause();
await player.pause();
```

### stop
Expand Down Expand Up @@ -164,7 +167,11 @@ The Player Mode represents what kind of native SDK is used to playback audio, wh
1. `.mediaPlayer` (default): for long media files or streams.
1. `.lowLatency`: for short audio files, since it reduces the impacts on visuals or UI performance.

**Note**: on low latency mode, the player won't fire any duration or position updates. Also, it is not possible to use the seek method to set the audio a specific position.
**Note**: on low latency mode, these features are NOT available:
- get duration & duration event
- get position & position event
- playback completion event (this means you are responsible for stopping the player)
- seeking & seek completion event

Normally you want to use `.mediaPlayer` unless you care about performance and your audios are short (i.e. for sound effects in games).

Expand All @@ -182,7 +189,7 @@ You can pick one of 3 options:
1. `.error` (default): show only error messages
1. `.none`: show no messages at all (not recommended)

**Note**: before opening any issue, always try changing the log level to `.info` to gather any information that my assist you on solving the problem.
**Note**: before opening any issue, always try changing the log level to `.info` to gather any information that might assist you with solving the problem.

**Note**: despite our best efforts, some native SDK implementations that we use spam a lot of log messages that we currently haven't figured out how to conform to this configuration (specially noticeable on Android). If you would like to contribute with a PR, they are more than welcome!

Expand Down Expand Up @@ -322,6 +329,31 @@ It works as a cache because it keeps track of the copied files so that you can r

If desired, you can change the `AudioCache` per player via the `AudioPlayer().audioCache` property or for all players via `AudioCache.instance`.

#### Local Assets

When playing local assets, by default every instance of AudioPlayers uses a [shared global instance of AudioCache](https://pub.dev/documentation/audioplayers/latest/audioplayers/AudioPlayer/audioCache.html), that will have a [default prefix "/assets"](https://pub.dev/documentation/audioplayers/latest/audioplayers/AudioCache/prefix.html) configured, as per Flutter conventions.
However, you can easily change that by specifying your own instance of AudioCache with any other (or no) prefix.

Default behavior, presuming that your audio is stored in `/assets/audio/my-audio.wav`:
```dart
final player = AudioPlayer();
await player.play(AssetSource('audio/my-audio.wav'));
```

Remove the asset prefix for all players:
```dart
AudioCache.instance = AudioCache(prefix: '')
final player = AudioPlayer();
await player.play(AssetSource('assets/audio/my-audio.wav'));
```

Set a different prefix for only one player (e.g. when using assets from another package):
```dart
final player = AudioPlayer();
player.audioCache = AudioCache(prefix: 'packages/OTHER_PACKAGE/assets/')
await player.play(AssetSource('other-package-audio.wav'));
```

Gustl22 marked this conversation as resolved.
Show resolved Hide resolved
### playerId

By default, each time you initialize a new instance of AudioPlayer, a unique playerId is generated and assigned to it using the [uuid package](https://pub.dev/packages/uuid).
Expand Down
20 changes: 15 additions & 5 deletions packages/audioplayers/example/README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,25 @@
# AudioPlayer Example

This is an example usage of audioplayers plugin.
Check out the live [example app](https://bluefireteam.github.io/audioplayers/) as demonstration.

It's a simple app with three tabs.

- Remote Url: Plays audio from a remote url from the Internet.
- Local File: Downloads a file to your device in order to play it from your device.
- Local Asset: Play one of the assets bundled with this app.
It's a simple app with several tabs:
- **Src**: Manage audio sources.
- Url: Plays audio from a remote Url from the Internet.
- Asset: Play one of the assets bundled with this app.
- Device File: Play a file from your device from the specified path.
- Byte Array: Play from an array of bytes.
- **Ctrl**: Control playback, such as volume, balance and rate.
- **Stream**: Display of stream updates and properties.
- **Ctx**: Customize the audio context for mobile devices.
- **Log**: Display of logs.

This example bundles a `PlayerWidget` that could be used as a very simple audio player interface.

## Setup

In order to successfully run the example locally, you have to [set up](https://github.com/bluefireteam/audioplayers/blob/main/contributing.md#environment-setup) your environment with `melos`.

## Dart Environment Variables

Set the following variables as additional args `--dart-define MY_VAR=xyz`:
Expand Down
243 changes: 243 additions & 0 deletions packages/audioplayers/example/example.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# Simple audio player app example

A complete example showcasing all _audioplayers_ features can be found in our [repository](https://github.com/bluefireteam/audioplayers/tree/main/packages/audioplayers/example).
Also check out our live [web app](https://bluefireteam.github.io/audioplayers/).

```dart
import 'dart:async';

import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';

void main() {
runApp(const MaterialApp(home: _SimpleExampleApp()));
}

class _SimpleExampleApp extends StatefulWidget {
const _SimpleExampleApp();

@override
_SimpleExampleAppState createState() => _SimpleExampleAppState();
}

class _SimpleExampleAppState extends State<_SimpleExampleApp> {
late AudioPlayer player = AudioPlayer();

@override
void initState() {
super.initState();

// Create the audio player.
player = AudioPlayer();

// Set the release mode to keep the source after playback has completed.
player.setReleaseMode(ReleaseMode.stop);

// Start the player as soon as the app is displayed.
WidgetsBinding.instance.addPostFrameCallback((_) async {
await player.setSource(AssetSource('ambient_c_motion.mp3'));
await player.resume();
});
}

@override
void dispose() {
// Release all sources and dispose the player.
player.dispose();

super.dispose();
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Player'),
),
body: PlayerWidget(player: player),
);
}
}

// The PlayerWidget is a copy of "/lib/components/player_widget.dart".
//#region PlayerWidget

class PlayerWidget extends StatefulWidget {
final AudioPlayer player;

const PlayerWidget({
required this.player,
super.key,
});

@override
State<StatefulWidget> createState() {
return _PlayerWidgetState();
}
}

class _PlayerWidgetState extends State<PlayerWidget> {
PlayerState? _playerState;
Duration? _duration;
Duration? _position;

StreamSubscription? _durationSubscription;
StreamSubscription? _positionSubscription;
StreamSubscription? _playerCompleteSubscription;
StreamSubscription? _playerStateChangeSubscription;

bool get _isPlaying => _playerState == PlayerState.playing;

bool get _isPaused => _playerState == PlayerState.paused;

String get _durationText => _duration?.toString().split('.').first ?? '';

String get _positionText => _position?.toString().split('.').first ?? '';

AudioPlayer get player => widget.player;

@override
void initState() {
super.initState();
// Use initial values from player
_playerState = player.state;
player.getDuration().then(
(value) => setState(() {
_duration = value;
}),
);
player.getCurrentPosition().then(
(value) => setState(() {
_position = value;
}),
);
_initStreams();
}

@override
void setState(VoidCallback fn) {
// Subscriptions only can be closed asynchronously,
// therefore events can occur after widget has been disposed.
if (mounted) {
super.setState(fn);
}
}

@override
void dispose() {
_durationSubscription?.cancel();
_positionSubscription?.cancel();
_playerCompleteSubscription?.cancel();
_playerStateChangeSubscription?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
final color = Theme.of(context).primaryColor;
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
key: const Key('play_button'),
onPressed: _isPlaying ? null : _play,
iconSize: 48.0,
icon: const Icon(Icons.play_arrow),
color: color,
),
IconButton(
key: const Key('pause_button'),
onPressed: _isPlaying ? _pause : null,
iconSize: 48.0,
icon: const Icon(Icons.pause),
color: color,
),
IconButton(
key: const Key('stop_button'),
onPressed: _isPlaying || _isPaused ? _stop : null,
iconSize: 48.0,
icon: const Icon(Icons.stop),
color: color,
),
],
),
Slider(
onChanged: (value) {
final duration = _duration;
if (duration == null) {
return;
}
final position = value * duration.inMilliseconds;
player.seek(Duration(milliseconds: position.round()));
},
value: (_position != null &&
_duration != null &&
_position!.inMilliseconds > 0 &&
_position!.inMilliseconds < _duration!.inMilliseconds)
? _position!.inMilliseconds / _duration!.inMilliseconds
: 0.0,
),
Text(
_position != null
? '$_positionText / $_durationText'
: _duration != null
? _durationText
: '',
style: const TextStyle(fontSize: 16.0),
),
],
);
}

void _initStreams() {
_durationSubscription = player.onDurationChanged.listen((duration) {
setState(() => _duration = duration);
});

_positionSubscription = player.onPositionChanged.listen(
(p) => setState(() => _position = p),
);

_playerCompleteSubscription = player.onPlayerComplete.listen((event) {
setState(() {
_playerState = PlayerState.stopped;
_position = Duration.zero;
});
});

_playerStateChangeSubscription =
player.onPlayerStateChanged.listen((state) {
setState(() {
_playerState = state;
});
});
}

Future<void> _play() async {
final position = _position;
if (position != null && position.inMilliseconds > 0) {
await player.seek(position);
}
await player.resume();
setState(() => _playerState = PlayerState.playing);
}

Future<void> _pause() async {
await player.pause();
setState(() => _playerState = PlayerState.paused);
}

Future<void> _stop() async {
await player.stop();
setState(() {
_playerState = PlayerState.stopped;
_position = Duration.zero;
});
}
}

//#endregion
```
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';

// This code is also used in the example.md. Please keep it up to date.
class PlayerWidget extends StatefulWidget {
final AudioPlayer player;

Expand Down Expand Up @@ -106,12 +107,12 @@ class _PlayerWidgetState extends State<PlayerWidget> {
],
),
Slider(
onChanged: (v) {
onChanged: (value) {
final duration = _duration;
if (duration == null) {
return;
}
final position = v * duration.inMilliseconds;
final position = value * duration.inMilliseconds;
player.seek(Duration(milliseconds: position.round()));
},
value: (_position != null &&
Expand All @@ -129,7 +130,6 @@ class _PlayerWidgetState extends State<PlayerWidget> {
: '',
style: const TextStyle(fontSize: 16.0),
),
Text('State: ${_playerState ?? '-'}'),
],
);
}
Expand Down
Loading