Skip to content

Commit

Permalink
feat: improve example (#1267)
Browse files Browse the repository at this point in the history
* no need to stop on playerComplete event
* reset position to zero on playerComplete event
* keys for TextWidgets of values in `Tgl`
* keys for TextWidgets in toasts
* make some methods private
* format displayed numbers to be consistent
* setSource via file picker
* button to get current player state
* set release mode to stop by default
* handle `onPlayerComplete` and onSeekComplete` globally to be able to display toasts across tabs
  • Loading branch information
Gustl22 authored Sep 2, 2022
1 parent 56df6ed commit a8154da
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,9 @@ class _PlayerWidgetState extends State<PlayerWidget> {
);

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

Expand Down
25 changes: 18 additions & 7 deletions packages/audioplayers/example/lib/components/tgl.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';

class Tgl extends StatelessWidget {
final List<String> options;
final Map<String, String> options;
final int selected;
final void Function(int) onChange;

Expand All @@ -15,15 +16,24 @@ class Tgl extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ToggleButtons(
isSelected: options.asMap().keys.map((it) => it == selected).toList(),
isSelected: options.entries
.mapIndexed((index, element) => index == selected)
.toList(),
onPressed: onChange,
children: options.map((it) => Text(it)).toList(),
children: options.entries
.map(
(entry) => Text(
entry.value,
key: Key(entry.key),
),
)
.toList(),
);
}
}

class EnumTgl<T extends Enum> extends StatelessWidget {
final List<T> options;
final Map<String, T> options;
final T selected;
final void Function(T) onChange;

Expand All @@ -36,10 +46,11 @@ class EnumTgl<T extends Enum> extends StatelessWidget {

@override
Widget build(BuildContext context) {
final optionValues = options.values.toList();
return Tgl(
options: options.map((e) => e.name).toList(),
selected: options.indexOf(selected),
onChange: (it) => onChange(options.firstWhere((e) => e == options[it])),
options: options.map((key, value) => MapEntry(key, value.name)),
selected: optionValues.indexOf(selected),
onChange: (it) => onChange(optionValues[it]),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
// ignore_for_file: depend_on_referenced_packages

import 'package:audioplayers_web/audioplayers_web.dart';
import 'package:file_picker/_internal/file_picker_web.dart';

import 'package:flutter_web_plugins/flutter_web_plugins.dart';

// ignore: public_member_api_docs
void registerPlugins(Registrar registrar) {
AudioplayersPlugin.registerWith(registrar);
FilePickerWeb.registerWith(registrar);
registrar.registerMessageHandler();
}
40 changes: 38 additions & 2 deletions packages/audioplayers/example/lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:async';

import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers_example/components/tabs.dart';
import 'package:audioplayers_example/components/tgl.dart';
Expand All @@ -6,6 +8,7 @@ import 'package:audioplayers_example/tabs/controls.dart';
import 'package:audioplayers_example/tabs/logger.dart';
import 'package:audioplayers_example/tabs/sources.dart';
import 'package:audioplayers_example/tabs/streams.dart';
import 'package:audioplayers_example/utils.dart';
import 'package:flutter/material.dart';

typedef OnError = void Function(Exception exception);
Expand All @@ -22,10 +25,41 @@ class ExampleApp extends StatefulWidget {
}

class _ExampleAppState extends State<ExampleApp> {
List<AudioPlayer> players = List.generate(4, (_) => AudioPlayer());
List<AudioPlayer> players =
List.generate(4, (_) => AudioPlayer()..setReleaseMode(ReleaseMode.stop));
int selectedPlayerIdx = 0;

AudioPlayer get selectedPlayer => players[selectedPlayerIdx];
List<StreamSubscription> streams = [];

@override
void initState() {
super.initState();
players.asMap().forEach((index, player) {
streams.add(
player.onPlayerComplete.listen(
(it) => toast(
'Player complete!',
textKey: Key('toast-player-complete-$index'),
),
),
);
streams.add(
player.onSeekComplete.listen(
(it) => toast(
'Seek complete!',
textKey: Key('toast-seek-complete-$index'),
),
),
);
});
}

@override
void dispose() {
streams.forEach((it) => it.cancel());
super.dispose();
}

@override
Widget build(BuildContext context) {
Expand All @@ -39,7 +73,9 @@ class _ExampleAppState extends State<ExampleApp> {
padding: const EdgeInsets.all(8.0),
child: Center(
child: Tgl(
options: const ['P1', 'P2', 'P3', 'P4'],
options: ['P1', 'P2', 'P3', 'P4']
.asMap()
.map((key, value) => MapEntry('player-$key', value)),
selected: selectedPlayerIdx,
onChange: (v) => setState(() => selectedPlayerIdx = v),
),
Expand Down
55 changes: 36 additions & 19 deletions packages/audioplayers/example/lib/tabs/controls.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,27 @@ class _ControlsTabState extends State<ControlsTab>
with AutomaticKeepAliveClientMixin<ControlsTab> {
String modalInputSeek = '';

Future<void> update(Future<void> Function() fn) async {
Future<void> _update(Future<void> Function() fn) async {
await fn();
// update everyone who listens to "player"
setState(() {});
}

Future<void> seekPercent(double percent) async {
Future<void> _seekPercent(double percent) async {
final duration = await widget.player.getDuration();
if (duration == null) {
toast('Failed to get duration for proportional seek.');
toast(
'Failed to get duration for proportional seek.',
textKey: const Key('toast-proportional-seek-duration-null'),
);
return;
}
final position = duration * percent;
seekDuration(position);
_seekDuration(position);
}

Future<void> seekDuration(Duration duration) async {
update(() => widget.player.seek(duration));
Future<void> _seekDuration(Duration duration) async {
await _update(() => widget.player.seek(duration));
}

@override
Expand Down Expand Up @@ -74,8 +77,10 @@ class _ControlsTabState extends State<ControlsTab>
children: [
const Text('Volume'),
...[0.0, 0.5, 1.0, 2.0].map((it) {
final formattedVal = it.toStringAsFixed(1);
return Btn(
txt: it.toString(),
key: Key('control-volume-$formattedVal'),
txt: formattedVal,
onPressed: () => widget.player.setVolume(it),
);
}),
Expand All @@ -86,8 +91,10 @@ class _ControlsTabState extends State<ControlsTab>
children: [
const Text('Rate'),
...[0.0, 0.5, 1.0, 2.0].map((it) {
final formattedVal = it.toStringAsFixed(1);
return Btn(
txt: it.toString(),
key: Key('control-rate-$formattedVal'),
txt: formattedVal,
onPressed: () => widget.player.setPlaybackRate(it),
);
}),
Expand All @@ -98,10 +105,14 @@ class _ControlsTabState extends State<ControlsTab>
children: [
const Text('Player Mode'),
EnumTgl<PlayerMode>(
options: PlayerMode.values,
key: const Key('control-player-mode'),
options: {
for (var e in PlayerMode.values)
'control-player-mode-${e.name}': e
},
selected: widget.player.mode,
onChange: (playerMode) {
update(() => widget.player.setPlayerMode(playerMode));
onChange: (playerMode) async {
await _update(() => widget.player.setPlayerMode(playerMode));
},
),
],
Expand All @@ -111,10 +122,14 @@ class _ControlsTabState extends State<ControlsTab>
children: [
const Text('Release Mode'),
EnumTgl<ReleaseMode>(
options: ReleaseMode.values,
key: const Key('control-release-mode'),
options: {
for (var e in ReleaseMode.values)
'control-release-mode-${e.name}': e
},
selected: widget.player.releaseMode,
onChange: (releaseMode) {
update(() => widget.player.setReleaseMode(releaseMode));
onChange: (releaseMode) async {
await _update(() => widget.player.setReleaseMode(releaseMode));
},
),
],
Expand All @@ -124,9 +139,11 @@ class _ControlsTabState extends State<ControlsTab>
children: [
const Text('Seek'),
...[0.0, 0.5, 1.0].map((it) {
final formattedVal = it.toStringAsFixed(1);
return Btn(
txt: it.toString(),
onPressed: () => seekPercent(it),
key: Key('control-seek-$formattedVal'),
txt: formattedVal,
onPressed: () => _seekPercent(it),
);
}),
Btn(
Expand All @@ -145,7 +162,7 @@ class _ControlsTabState extends State<ControlsTab>
txt: 'millis',
onPressed: () {
Navigator.of(context).pop();
seekDuration(
_seekDuration(
Duration(
milliseconds: int.parse(modalInputSeek),
),
Expand All @@ -156,7 +173,7 @@ class _ControlsTabState extends State<ControlsTab>
txt: 'seconds',
onPressed: () {
Navigator.of(context).pop();
seekDuration(
_seekDuration(
Duration(
seconds: int.parse(modalInputSeek),
),
Expand All @@ -167,7 +184,7 @@ class _ControlsTabState extends State<ControlsTab>
txt: '%',
onPressed: () {
Navigator.of(context).pop();
seekPercent(double.parse(modalInputSeek));
_seekPercent(double.parse(modalInputSeek));
},
),
Btn(
Expand Down
13 changes: 12 additions & 1 deletion packages/audioplayers/example/lib/tabs/sources.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:audioplayers/audioplayers.dart';
import 'package:audioplayers_example/components/btn.dart';
import 'package:audioplayers_example/components/tab_wrapper.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';

Expand Down Expand Up @@ -99,7 +100,17 @@ class _SourcesTabState extends State<SourcesTab>
setSource(BytesSource(bytes));
},
),
// TODO(luan): Add local files via file picker
Btn(
key: const Key('setSource-url-local'),
txt: 'Pick local file',
onPressed: () async {
final result = await FilePicker.platform.pickFiles();
final path = result?.files.single.path;
if (path != null) {
setSource(DeviceFileSource(path));
}
},
),
],
);
}
Expand Down
28 changes: 22 additions & 6 deletions packages/audioplayers/example/lib/tabs/streams.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import 'package:audioplayers_example/components/btn.dart';
import 'package:audioplayers_example/components/pad.dart';
import 'package:audioplayers_example/components/player_widget.dart';
import 'package:audioplayers_example/components/tab_wrapper.dart';
import 'package:audioplayers_example/utils.dart';
import 'package:flutter/material.dart';

class StreamsTab extends StatefulWidget {
Expand All @@ -20,10 +19,11 @@ class StreamsTab extends StatefulWidget {
class _StreamsTabState extends State<StreamsTab>
with AutomaticKeepAliveClientMixin<StreamsTab> {
Duration? position, duration;
PlayerState? state;
late List<StreamSubscription> streams;

Duration? streamDuration, streamPosition;
PlayerState? state;
PlayerState? streamState;

@override
void initState() {
Expand All @@ -32,11 +32,9 @@ class _StreamsTabState extends State<StreamsTab>
widget.player.onDurationChanged
.listen((it) => setState(() => streamDuration = it)),
widget.player.onPlayerStateChanged
.listen((it) => setState(() => state = it)),
.listen((it) => setState(() => streamState = it)),
widget.player.onPositionChanged
.listen((it) => setState(() => streamPosition = it)),
widget.player.onPlayerComplete.listen((it) => toast('Player complete!')),
widget.player.onSeekComplete.listen((it) => toast('Seek complete!')),
];
}

Expand All @@ -56,6 +54,10 @@ class _StreamsTabState extends State<StreamsTab>
setState(() => this.duration = duration);
}

Future<void> getPlayerState() async {
setState(() => state = widget.player.state);
}

@override
Widget build(BuildContext context) {
super.build(context);
Expand Down Expand Up @@ -89,6 +91,20 @@ class _StreamsTabState extends State<StreamsTab>
),
],
),
Row(
children: [
Btn(
key: const Key('getPlayerState'),
txt: 'Get State',
onPressed: getPlayerState,
),
const Pad(width: 8.0),
Text(
state?.toString() ?? '-',
key: const Key('playerStateText'),
),
],
),
const Divider(color: Colors.black),
const Text('Streams'),
Text(
Expand All @@ -100,7 +116,7 @@ class _StreamsTabState extends State<StreamsTab>
key: const Key('onPositionText'),
),
Text(
'Stream State: $state',
'Stream State: $streamState',
key: const Key('onStateText'),
),
const Divider(color: Colors.black),
Expand Down
5 changes: 3 additions & 2 deletions packages/audioplayers/example/lib/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import 'package:audioplayers_example/components/dlg.dart';
import 'package:flutter/material.dart';

extension StateExt<T extends StatefulWidget> on State<T> {
void toast(String message) {
void toast(String message, {Key? textKey}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
content: Text(message, key: textKey),
duration: const Duration(milliseconds: 500),
),
);
}
Expand Down
Loading

0 comments on commit a8154da

Please sign in to comment.