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

feat!: FramePositionUpdater & TimerPositionUpdater #1664

Merged
merged 5 commits into from
Oct 22, 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
12 changes: 12 additions & 0 deletions getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -333,3 +333,15 @@ If you want to specify the playerId, you can do so when creating the playing:
```

Two players with the same id will point to the same media player on the native side.

### PositionUpdater

By default, the position stream is updated on every new frame. You can change this behavior to e.g. update on a certain
interval with the `TimerPositionUpdater` or implement your own `PositionUpdater`:

```dart
player.positionUpdater = TimerPositionUpdater(
interval: const Duration(milliseconds: 100),
getPosition: player.getCurrentPosition,
);
```
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ $lastFailureMsg''',
scrollable: find.byType(Scrollable).first,
);
}
await pumpAndSettle();
await pump();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,15 +148,15 @@ Future<void> testControlsTab(
extension ControlsWidgetTester on WidgetTester {
Future<void> resume() async {
await scrollToAndTap(const Key('control-resume'));
await pumpAndSettle();
await pump();
}

Future<void> stop() async {
final st = StackTrace.current.toString();

await scrollToAndTap(const Key('control-stop'));
await waitOneshot(const Key('toast-player-stopped-0'), stackTrace: st);
await pumpAndSettle();
await pump();
}

Future<void> testVolume(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Future<void> testStreamsTab(
await tester.pumpAndSettle();

// Stream position is tracked as soon as source is loaded
if (features.hasPositionEvent && !audioSourceTestData.isLiveStream) {
if (!audioSourceTestData.isLiveStream) {
// Display position before playing
await tester.testPosition(Duration.zero);
}
Expand All @@ -33,7 +33,7 @@ Future<void> testStreamsTab(

await tester.pumpAndSettle();
await tester.scrollToAndTap(const Key('play_button'));
await tester.pumpAndSettle();
await tester.pump();

// Cannot test more precisely as it is dependent on pollInterval
// and updateInterval of native implementation.
Expand All @@ -47,18 +47,16 @@ Future<void> testStreamsTab(
}

// Test if onPositionText is set.
if (features.hasPositionEvent) {
await tester.testPosition(
Duration.zero,
matcher: (Duration? position) => greaterThan(position ?? Duration.zero),
timeout: timeout,
);
await tester.testOnPosition(
Duration.zero,
matcher: greaterThan,
timeout: timeout,
);
}
await tester.testPosition(
Duration.zero,
matcher: (Duration? position) => greaterThan(position ?? Duration.zero),
timeout: timeout,
);
await tester.testOnPosition(
Duration.zero,
matcher: greaterThan,
timeout: timeout,
);
}

if (features.hasDurationEvent && !audioSourceTestData.isLiveStream) {
Expand Down Expand Up @@ -110,7 +108,7 @@ Future<void> testStreamsTab(
);
}
}
if (features.hasPositionEvent && !audioSourceTestData.isLiveStream) {
if (!audioSourceTestData.isLiveStream) {
await tester.testPosition(Duration.zero);
}
}
Expand All @@ -123,13 +121,6 @@ extension StreamWidgetTester on WidgetTester {
// Web: millisecond
// Darwin: millisecond

// Update interval for position:
// Android: ~200ms
// Windows: ~250ms
// Linux: ~250ms
// Web: ~250ms
// Darwin: ~200ms

Future<void> stopStream() async {
final st = StackTrace.current.toString();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,19 @@ extension LibWidgetTester on WidgetTester {
await pump();
}
}

/// See [pumpFrames].
Future<void> pumpGlobalFrames(
Duration maxDuration, [
Duration interval = const Duration(milliseconds: 16, microseconds: 683),
]) {
var elapsed = Duration.zero;
return TestAsyncUtils.guard<void>(() async {
binding.scheduleFrame();
while (elapsed < maxDuration) {
await binding.pump(interval);
elapsed += interval;
}
});
}
}
85 changes: 75 additions & 10 deletions packages/audioplayers/example/integration_test/lib_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ void main() async {

await tester.pumpLinux();
await player.play(specialCharAssetTestData.source);
await tester.pumpAndSettle();
// Sources take some time to get initialized
await tester.pump(const Duration(seconds: 8));
await player.stop();
Expand All @@ -41,7 +40,6 @@ void main() async {
final path = await player.audioCache.loadPath(specialCharAsset);
expect(path, isNot(contains('%'))); // Ensure path is not URL encoded
await player.play(DeviceFileSource(path));
await tester.pumpAndSettle();
// Sources take some time to get initialized
await tester.pump(const Duration(seconds: 8));
await player.stop();
Expand All @@ -57,7 +55,6 @@ void main() async {

await tester.pumpLinux();
await player.play(specialCharUrlTestData.source);
await tester.pumpAndSettle();
// Sources take some time to get initialized
await tester.pump(const Duration(seconds: 8));
await player.stop();
Expand All @@ -73,7 +70,6 @@ void main() async {

await tester.pumpLinux();
await player.play(noExtensionAssetTestData.source);
await tester.pumpAndSettle();
// Sources take some time to get initialized
await tester.pump(const Duration(seconds: 8));
await player.stop();
Expand All @@ -86,6 +82,81 @@ void main() async {
skip: isIOS || isMacOS,
);

group('AP events', () {
late AudioPlayer player;

setUp(() async {
player = AudioPlayer(
playerId: 'somePlayerId',
);
});

void testPositionUpdater(
LibSourceTestData td, {
bool useTimerPositionUpdater = false,
}) {
final positionUpdaterName = useTimerPositionUpdater
? 'TimerPositionUpdater'
: 'FramePositionUpdater';
testWidgets(
'#positionEvent with $positionUpdaterName: ${td.source}',
(tester) async {
await tester.pumpLinux();

if (useTimerPositionUpdater) {
player.positionUpdater = TimerPositionUpdater(
getPosition: player.getCurrentPosition,
interval: const Duration(milliseconds: 100),
);
}
final futurePositions = player.onPositionChanged.toList();

await player.setReleaseMode(ReleaseMode.stop);
await player.setSource(td.source);
await player.resume();
await tester.pumpGlobalFrames(const Duration(seconds: 5));

if (!td.isLiveStream && td.duration! < const Duration(seconds: 2)) {
expect(player.state, PlayerState.completed);
} else {
if (td.isLiveStream || td.duration! > const Duration(seconds: 10)) {
expect(player.state, PlayerState.playing);
} else {
// Don't know for sure, if has yet completed or is still playing
}
await player.stop();
expect(player.state, PlayerState.stopped);
}
await tester.pumpLinux();
await player.dispose();
final positions = await futurePositions;
printOnFailure('Positions: $positions');
expect(positions, isNot(contains(null)));
expect(positions, contains(greaterThan(Duration.zero)));
if (td.isLiveStream) {
// TODO(gustl22): Live streams may have zero or null as initial
// position. This should be consistent across all platforms.
} else {
expect(positions.first, Duration.zero);
expect(positions.last, Duration.zero);
}
},
// FIXME(gustl22): Android provides no position for samples shorter
// than 0.5 seconds.
skip: isAndroid &&
!td.isLiveStream &&
td.duration! < const Duration(seconds: 1),
);
}

/// Test at least one source with [TimerPositionUpdater].
testPositionUpdater(mp3Url1TestData, useTimerPositionUpdater: true);

for (final td in audioTestDataList) {
testPositionUpdater(td);
}
});

group('play multiple sources', () {
testWidgets(
'play multiple sources simultaneously',
Expand All @@ -99,7 +170,6 @@ void main() async {
await Future.wait<void>(
iterator.map((i) => players[i].play(audioTestDataList[i].source)),
);
await tester.pumpAndSettle();
// Sources take some time to get initialized
await tester.pump(const Duration(seconds: 8));
for (var i = 0; i < audioTestDataList.length; i++) {
Expand Down Expand Up @@ -128,7 +198,6 @@ void main() async {
for (final td in audioTestDataList) {
await tester.pumpLinux();
await player.play(td.source);
await tester.pumpAndSettle();
// Sources take some time to get initialized
await tester.pump(const Duration(seconds: 8));
if (td.isLiveStream || td.duration! > const Duration(seconds: 10)) {
Expand Down Expand Up @@ -168,7 +237,6 @@ void main() async {

await tester.pumpLinux();
await player.play(td.source);
await tester.pumpAndSettle();
await tester
.pump((td.duration ?? Duration.zero) + const Duration(seconds: 8));
expect(player.state, PlayerState.completed);
Expand All @@ -182,7 +250,6 @@ void main() async {
await player.setAudioContext(audioContext);

await player.resume();
await tester.pumpAndSettle();
await tester
.pump((td.duration ?? Duration.zero) + const Duration(seconds: 8));
expect(player.state, PlayerState.completed);
Expand Down Expand Up @@ -217,7 +284,6 @@ void main() async {
await tester.pumpLinux();
await player.setSource(td.source);
await player.resume();
await tester.pumpAndSettle();
await tester
.pump((td.duration ?? Duration.zero) + const Duration(seconds: 8));
expect(player.state, PlayerState.playing);
Expand All @@ -233,7 +299,6 @@ void main() async {
await player.setAudioContext(audioContext);

await player.resume();
await tester.pumpAndSettle();
await tester
.pump((td.duration ?? Duration.zero) + const Duration(seconds: 8));
expect(player.state, PlayerState.playing);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@ class PlatformFeatures {
final bool hasPlayingRoute; // Not yet tested

final bool hasDurationEvent;
final bool hasPositionEvent;
final bool hasPlayerStateEvent;
final bool hasErrorEvent; // Not yet tested

Expand All @@ -116,7 +115,6 @@ class PlatformFeatures {
this.hasRecordingActive = true,
this.hasPlayingRoute = true,
this.hasDurationEvent = true,
this.hasPositionEvent = true,
this.hasPlayerStateEvent = true,
this.hasErrorEvent = true,
});
Expand Down
41 changes: 0 additions & 41 deletions packages/audioplayers/example/integration_test/platform_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ void main() async {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
final features = PlatformFeatures.instance();
final isLinux = !kIsWeb && defaultTargetPlatform == TargetPlatform.linux;
final isAndroid = !kIsWeb && defaultTargetPlatform == TargetPlatform.android;
final audioTestDataList = await getAudioTestDataList();

group('Platform method channel', () {
Expand Down Expand Up @@ -362,46 +361,6 @@ void main() async {
}
}

for (final td in audioTestDataList) {
if (features.hasPositionEvent) {
testWidgets(
'#positionEvent ${td.source}',
(tester) async {
await tester.prepareSource(
playerId: playerId,
platform: platform,
testData: td,
);

final eventStream = platform.getEventStream(playerId);
Duration? position;
final onPositionSub = eventStream.where(
(event) {
return event.eventType == AudioEventType.position &&
event.position != null &&
event.position! > Duration.zero;
},
).listen(
(event) => position = event.position,
);

await platform.resume(playerId);
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(position, isNotNull);
expect(position, greaterThan(Duration.zero));
await platform.stop(playerId);
await onPositionSub.cancel();
await tester.pumpLinux();
},
// FIXME(gustl22): Android provides no position for samples shorter
// than 0.5 seconds.
skip: isAndroid &&
!td.isLiveStream &&
td.duration! < const Duration(seconds: 1),
);
}
}

for (final td in audioTestDataList) {
if (!td.isLiveStream && td.duration! < const Duration(seconds: 2)) {
testWidgets('#completeEvent ${td.source}', (tester) async {
Expand Down
1 change: 1 addition & 0 deletions packages/audioplayers/lib/audioplayers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ export 'src/audio_logger.dart';
export 'src/audio_pool.dart';
export 'src/audioplayer.dart';
export 'src/global_audio_scope.dart';
export 'src/position_updater.dart';
export 'src/source.dart';
Loading