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

fix: onPrepared event to wait until player is ready / finished loading the source #1469

Merged
merged 94 commits into from
May 8, 2023
Merged
Show file tree
Hide file tree
Changes from 87 commits
Commits
Show all changes
94 commits
Select commit Hold shift + click to select a range
ce22e53
fix: onPrepared event to fix immediate playing
Gustl22 Apr 10, 2023
b67a556
attempt to fix tests
Gustl22 Apr 10, 2023
df43b19
fix tests
Gustl22 Apr 11, 2023
c2ea6d1
potential fix for: #1118, #1384
Gustl22 Apr 11, 2023
320e308
prepare android
Gustl22 Apr 11, 2023
7babf9b
isPrepared param
Gustl22 Apr 11, 2023
f9668e0
remove print
Gustl22 Apr 11, 2023
7869904
prepeared for web
Gustl22 Apr 11, 2023
ac8a95f
feat(darwin): delayed onPrepared
Gustl22 Apr 11, 2023
a634c4a
test: emit fake prepared event
Gustl22 Apr 11, 2023
a73271b
test: fix tests
Gustl22 Apr 11, 2023
ec4846e
make preparedCompleter nullable
Gustl22 Apr 12, 2023
0042c46
windows: add loading callback
Gustl22 Apr 12, 2023
91e324d
add tests
Gustl22 Apr 12, 2023
7cda792
fix(web): onLoaded source
Gustl22 Apr 13, 2023
c8dee6b
avoid need of playing file for error
Gustl22 Apr 13, 2023
7eca74d
pump before play
Gustl22 Apr 14, 2023
0baac1b
dart format
Gustl22 Apr 14, 2023
46c4153
flutter analyze
Gustl22 Apr 16, 2023
0e1bdea
Merge branch 'main' into gustl22/prepare-event
Gustl22 Apr 18, 2023
133bc59
feat(web): error stream
Gustl22 Apr 19, 2023
8e3fc03
fix(android): run on ui/main thread
Gustl22 Apr 19, 2023
39ff23c
feat(web): better precision for web
Gustl22 Apr 19, 2023
262f895
update precision and update interval for platforms
Gustl22 Apr 19, 2023
208f130
increase deviation for web
Gustl22 Apr 19, 2023
a2bcae2
fix(windows): avoid freezing UI on loading source (closes #1118)
Gustl22 Apr 19, 2023
252fa44
test: for calling method after dispose
Gustl22 Apr 19, 2023
cb3b876
PlayerState.dispose
Gustl22 Apr 19, 2023
83530c1
Merge branch 'main' into gustl22/prepare-event
Gustl22 Apr 20, 2023
45bd314
dispose for audio pool
Gustl22 Apr 20, 2023
139e364
throw PlatformException, if player is not created or disposed
Gustl22 Apr 20, 2023
e9c1a60
lib test improvement
Gustl22 Apr 24, 2023
e709637
dispose in tests
Gustl22 Apr 24, 2023
888263c
refactor st to stackTrace
Gustl22 Apr 24, 2023
ea2fc3c
refactor st to stackTrace
Gustl22 Apr 24, 2023
81da1a4
linux: decrease update interval
Gustl22 Apr 24, 2023
e106abe
decrease test initialization timeout
Gustl22 Apr 24, 2023
2b56b95
use prepared completer in setting source only
Gustl22 Apr 24, 2023
ba8faf2
some linux improvements
Gustl22 Apr 24, 2023
819ac2a
some linux improvements
Gustl22 Apr 25, 2023
f303d13
Revert "decrease test initialization timeout"
Gustl22 Apr 25, 2023
1cf979f
dart format
Gustl22 Apr 25, 2023
8bb5ab3
test macos 13
Gustl22 Apr 25, 2023
a367eef
improve build and test workflows (fixes #1405)
Gustl22 Apr 25, 2023
2804c34
Merge branch 'main' into gustl22/macos-13
Gustl22 Apr 25, 2023
37966c3
grep iphone 14
Gustl22 Apr 25, 2023
34ab6bd
WIP: use run command instead of test for linux tests
Gustl22 Apr 25, 2023
24fe499
grep first line only
Gustl22 Apr 25, 2023
f9a2d23
use test command again
Gustl22 Apr 25, 2023
3da8964
Merge branch 'gustl22/macos-13' into gustl22/prepare-event
Gustl22 Apr 25, 2023
d0d083d
linux: attempt to fix playbin
Gustl22 Apr 26, 2023
5633e7d
linux: deinit gst
Gustl22 Apr 26, 2023
a2ffaac
linux: better dispose
Gustl22 Apr 26, 2023
4c27fef
linux: better comparison for src
Gustl22 Apr 26, 2023
ddede30
linux: dispose method and event channel
Gustl22 Apr 26, 2023
75e3a88
Merge remote-tracking branch 'upstream/gustl22/1475-dispose' into gus…
Gustl22 Apr 26, 2023
062f49a
linux: use search for erasing player
Gustl22 Apr 26, 2023
b0dcb1a
linux: fix dispose
Gustl22 Apr 26, 2023
121a870
Merge branch 'main' into gustl22/1475-dispose
Gustl22 Apr 27, 2023
81805cb
dispose in tests, fix linux dispose,various fixes
Gustl22 Apr 27, 2023
ec592b6
linux: better exceptions
Gustl22 Apr 27, 2023
60ab981
linux: fix crash
Gustl22 Apr 27, 2023
3a53b4e
linux: finally fixing all errors
Gustl22 Apr 27, 2023
9cf5038
dart format & analyze
Gustl22 Apr 27, 2023
9d3cb04
fix(web): test for disposed player
Gustl22 Apr 27, 2023
e59e1af
run error tests as in platform interface scope
Gustl22 Apr 29, 2023
dba0149
use run command for ios / macos
Gustl22 May 1, 2023
fb51d48
fix(darwin): avoid force unwrapping of weak reference
Gustl22 May 1, 2023
653056c
use run command for linux and android
Gustl22 May 1, 2023
6f6423a
fix(android): only call cancel, if not yet disposed
Gustl22 May 1, 2023
8af9c67
fix(darwin): release AVPlayerItem on failure
Gustl22 May 3, 2023
9434a0b
Test: pump and settle after cancel
Gustl22 May 3, 2023
5401e30
ci: use flutter run command for ios and macos
Gustl22 May 3, 2023
329af0c
test: avoid cancelling the global event stream
Gustl22 May 3, 2023
548dee7
ci: use test command for android
Gustl22 May 3, 2023
9cda452
test: debug for linux
Gustl22 May 3, 2023
a006759
attempt: init gstreamer elsewhere
Gustl22 May 3, 2023
160a576
attempt 2: init gstreamer elsewhere
Gustl22 May 3, 2023
2a0b74d
Revert "test: debug for linux"
Gustl22 May 3, 2023
6fe6ce1
Adapt comment for linux
Gustl22 May 3, 2023
476f1e1
Revert to test command
Gustl22 May 3, 2023
a59b95d
docs: remove FIXME on linux
Gustl22 May 3, 2023
cab2472
Merge branch 'gustl22/1475-dispose' into gustl22/prepare-event
Gustl22 May 3, 2023
1712632
update docs
Gustl22 May 3, 2023
45eb81b
Merge branch 'main' into gustl22/prepare-event
Gustl22 May 3, 2023
1fd8a3a
dart format
Gustl22 May 3, 2023
eff6c4c
flutter analyze
Gustl22 May 3, 2023
b0d86f0
attempt to fix linux error
Gustl22 May 5, 2023
fbc658d
test: resuse same platform channel event id
Gustl22 May 6, 2023
f2cb634
fix: disposing event channel on linux
Gustl22 May 7, 2023
87d21bd
Revert "fix: disposing event channel on linux"
Gustl22 May 7, 2023
a5f65dd
fix: disposing event channel on linux
Gustl22 May 7, 2023
c14161c
Merge remote-tracking branch 'upstream/main' into gustl22/prepare-event
Gustl22 May 7, 2023
0a2f3e6
make tests run on Linux
Gustl22 May 7, 2023
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
81 changes: 53 additions & 28 deletions packages/audioplayers/example/integration_test/lib_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';

import 'mock_html.dart' if (dart.library.html) 'dart:html' show DomException;
import 'platform_features.dart';
import 'source_test_data.dart';
import 'test_utils.dart';
Expand All @@ -19,12 +18,12 @@ void main() {

IntegrationTestWidgetsFlutterBinding.ensureInitialized();

final wavUrl1TestData = LibSourceTestData(
source: UrlSource(wavUrl1),
duration: const Duration(milliseconds: 451),
);
final audioTestDataList = [
if (features.hasUrlSource)
LibSourceTestData(
source: UrlSource(wavUrl1),
duration: const Duration(milliseconds: 451),
),
if (features.hasUrlSource) wavUrl1TestData,
if (features.hasUrlSource)
LibSourceTestData(
source: UrlSource(wavUrl2),
Expand Down Expand Up @@ -73,6 +72,7 @@ void main() {

// Start all players simultaneously
final iterator = List<int>.generate(audioTestDataList.length, (i) => i);
await tester.pump();
await Future.wait<void>(
iterator.map((i) => players[i].play(audioTestDataList[i].source)),
);
Expand Down Expand Up @@ -103,6 +103,7 @@ void main() {

for (var i = 0; i < audioTestDataList.length; i++) {
final td = audioTestDataList[i];
await tester.pump();
await player.play(td.source);
await tester.pumpAndSettle();
// Sources take some time to get initialized
Expand Down Expand Up @@ -130,7 +131,7 @@ void main() {
final player = AudioPlayer();
await player.setReleaseMode(ReleaseMode.stop);

final td = audioTestDataList[0];
final td = wavUrl1TestData;

var audioContext = AudioContextConfig(
//ignore: avoid_redundant_argument_values
Expand All @@ -141,6 +142,7 @@ void main() {
await AudioPlayer.global.setAudioContext(audioContext);
await player.setAudioContext(audioContext);

await tester.pump();
await player.play(td.source);
await tester.pumpAndSettle();
await tester.pump(td.duration + const Duration(seconds: 8));
Expand Down Expand Up @@ -173,7 +175,7 @@ void main() {
await player.setReleaseMode(ReleaseMode.stop);
player.setPlayerMode(PlayerMode.lowLatency);

final td = audioTestDataList[0];
final td = wavUrl1TestData;

var audioContext = AudioContextConfig(
//ignore: avoid_redundant_argument_values
Expand All @@ -184,6 +186,7 @@ void main() {
await AudioPlayer.global.setAudioContext(audioContext);
await player.setAudioContext(audioContext);

await tester.pump();
await player.setSource(td.source);
await player.resume();
await tester.pumpAndSettle();
Expand Down Expand Up @@ -249,51 +252,37 @@ void main() {

group('Errors', () {
testWidgets(
'Throw PlatformException, when playing invalid file',
'Throw PlatformException, when loading invalid file',
(tester) async {
final player = AudioPlayer();
try {
// Throws PlatformException via MethodChannel:
await tester.pump();
await player.setSource(AssetSource(invalidAsset));
await player.resume();
fail('PlatformException not thrown');
// ignore: avoid_catches_without_on_clauses
} catch (e) {
if (kIsWeb) {
expect(e, isInstanceOf<DomException>());
expect((e as DomException).name, 'NotSupportedError');
} else {
expect(e, isInstanceOf<PlatformException>());
}
expect(e, isInstanceOf<PlatformException>());
}
await player.dispose();
},
// Linux provides errors only asynchronously.
skip: !kIsWeb && Platform.isLinux,
);

testWidgets(
'Throw PlatformException, when playing non existent file',
'Throw PlatformException, when loading non existent file',
(tester) async {
final player = AudioPlayer();
try {
// Throws PlatformException via MethodChannel:
await tester.pump();
await player.setSource(UrlSource('non_existent.txt'));
await player.resume();
fail('PlatformException not thrown');
// ignore: avoid_catches_without_on_clauses
} catch (e) {
if (kIsWeb) {
expect(e, isInstanceOf<DomException>());
expect((e as DomException).name, 'NotSupportedError');
} else {
expect(e, isInstanceOf<PlatformException>());
}
expect(e, isInstanceOf<PlatformException>());
}
await player.dispose();
},
// Linux provides errors only asynchronously.
skip: !kIsWeb && Platform.isLinux,
);
});

Expand All @@ -318,6 +307,42 @@ void main() {
);
}
});

testWidgets('#setSource #getPosition and #getDuration', (tester) async {
final platform = AudioplayersPlatformInterface.instance;

const playerId = 'somePlayerId';
await platform.create(playerId);

final preparedCompleter = Completer<void>();
final eventStream = platform.getEventStream(playerId);
final onPreparedSub = eventStream
.where((event) => event.eventType == AudioEventType.prepared)
.map((event) => event.isPrepared!)
.listen(
(isPrepared) {
if (isPrepared) {
preparedCompleter.complete();
}
},
onError: preparedCompleter.completeError,
);
await tester.pump();
await platform.setSourceUrl(
playerId,
(wavUrl1TestData.source as UrlSource).url,
);
await preparedCompleter.future.timeout(const Duration(seconds: 30));

expect(await platform.getCurrentPosition(playerId), 0);
expect(
await platform.getDuration(playerId),
wavUrl1TestData.duration.inMilliseconds,
);

await onPreparedSub.cancel();
await platform.dispose(playerId);
});
});

group('Platform event channel', () {
Expand Down
6 changes: 0 additions & 6 deletions packages/audioplayers/example/integration_test/mock_html.dart

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,19 @@ Future<void> testStreamsTab(
}

extension StreamWidgetTester on WidgetTester {
// Precision for duration & position:
// Android: two tenth of a second
// Precision for position & duration:
// Android: millisecond
// Windows: millisecond
// Linux: second
// Web: second
// Linux: millisecond
// Web: millisecond
// Darwin: millisecond

// Update interval for duration & position:
// Android: two tenth of a second
// Update interval for position:
// Android: ~200ms
// Windows: ~250ms
// Linux: second
// Web: second
// Linux: ~250ms
// Web: ~250ms
// Darwin: ?

Future<void> stopStream() async {
final st = StackTrace.current.toString();
Expand All @@ -144,7 +146,11 @@ extension StreamWidgetTester on WidgetTester {
await waitFor(
() async => expectWidgetHasDuration(
const Key('onDurationText'),
matcher: (Duration? actual) => durationRangeMatcher(actual, duration),
matcher: (Duration? actual) => durationRangeMatcher(
actual,
duration,
deviation: const Duration(milliseconds: 500),
),
),
timeout: timeout,
stackTrace: st,
Expand Down
47 changes: 39 additions & 8 deletions packages/audioplayers/lib/src/audioplayer.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,10 @@ class AudioPlayer {
Stream<void> get onSeekComplete => eventStream
.where((event) => event.eventType == AudioEventType.seekComplete);

Stream<bool> get _onPrepared => eventStream
.where((event) => event.eventType == AudioEventType.prepared)
.map((event) => event.isPrepared!);

/// Stream of log events.
Stream<String> get onLog => eventStream
.where((event) => event.eventType == AudioEventType.log)
Expand Down Expand Up @@ -150,8 +154,8 @@ class AudioPlayer {
onError: _eventStreamController.addError,
);
creatingCompleter.complete();
} on Exception catch (e, st) {
creatingCompleter.completeError(e, st);
} on Exception catch (e, stackTrace) {
creatingCompleter.completeError(e, stackTrace);
}
}

Expand Down Expand Up @@ -279,8 +283,27 @@ class AudioPlayer {
/// This will delegate to one of the specific methods below depending on
/// the source type.
Future<void> setSource(Source source) async {
await creatingCompleter.future;
return source.setOnPlayer(this);
// Implementations of setOnPlayer also call `creatingCompleter.future`
await source.setOnPlayer(this);
}

Future<void> _completePrepared(Future<void> Function() fun) async {
final preparedCompleter = Completer<void>();
final onPreparedSubscription = _onPrepared.listen(
(isPrepared) {
if (isPrepared) {
preparedCompleter.complete();
}
},
onError: (Object e, [StackTrace? stackTrace]) {
if (preparedCompleter.isCompleted == false) {
preparedCompleter.completeError(e, stackTrace);
}
},
);
await fun();
await preparedCompleter.future.timeout(const Duration(seconds: 30));
onPreparedSubscription.cancel();
}

/// Sets the URL to a remote link.
Expand All @@ -290,7 +313,9 @@ class AudioPlayer {
Future<void> setSourceUrl(String url) async {
_source = UrlSource(url);
await creatingCompleter.future;
return _platform.setSourceUrl(playerId, url, isLocal: false);
await _completePrepared(
() => _platform.setSourceUrl(playerId, url, isLocal: false),
);
}

/// Sets the URL to a file in the users device.
Expand All @@ -300,7 +325,9 @@ class AudioPlayer {
Future<void> setSourceDeviceFile(String path) async {
_source = DeviceFileSource(path);
await creatingCompleter.future;
return _platform.setSourceUrl(playerId, path, isLocal: true);
await _completePrepared(
() => _platform.setSourceUrl(playerId, path, isLocal: true),
);
}

/// Sets the URL to an asset in your Flutter application.
Expand All @@ -312,13 +339,17 @@ class AudioPlayer {
_source = AssetSource(path);
final url = await audioCache.load(path);
await creatingCompleter.future;
return _platform.setSourceUrl(playerId, url.path, isLocal: true);
await _completePrepared(
() => _platform.setSourceUrl(playerId, url.path, isLocal: true),
);
}

Future<void> setSourceBytes(Uint8List bytes) async {
_source = BytesSource(bytes);
await creatingCompleter.future;
return _platform.setSourceBytes(playerId, bytes);
await _completePrepared(
() => _platform.setSourceBytes(playerId, bytes),
);
}

/// Get audio duration after setting url.
Expand Down
6 changes: 6 additions & 0 deletions packages/audioplayers/test/fake_audioplayers_platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ class FakeAudioplayersPlatform extends AudioplayersPlatformInterface {
@override
Future<void> setSourceBytes(String playerId, Uint8List bytes) async {
calls.add(FakeCall(id: playerId, method: 'setSourceBytes', value: bytes));
eventStreamControllers[playerId]?.add(
const AudioEvent(eventType: AudioEventType.prepared, isPrepared: true),
);
}

@override
Expand All @@ -132,6 +135,9 @@ class FakeAudioplayersPlatform extends AudioplayersPlatformInterface {
bool? isLocal,
}) async {
calls.add(FakeCall(id: playerId, method: 'setSourceUrl', value: url));
eventStreamControllers[playerId]?.add(
const AudioEvent(eventType: AudioEventType.prepared, isPrepared: true),
);
}

@override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ class MediaPlayerPlayer(
}

override fun prepare() {
mediaPlayer.prepare()
mediaPlayer.prepareAsync()
}

override fun reset() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,15 @@ class WrappedPlayer internal constructor(
}

var released = true
var prepared = false

var prepared: Boolean = false
set(value) {
if (field != value) {
field = value
ref.handlePrepared(this, value)
}
}

var playing = false
var shouldSeekTo = -1

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,11 @@ public class SwiftAudioplayersDarwinPlugin: NSObject, FlutterPlugin {
}

player.setSourceUrl(url: url!, isLocal: isLocal, completer: {
result(1)
player.eventHandler.onPrepared(isPrepared: true)
}, completerError: {
result(FlutterError(code: "DarwinAudioError", message: "AVPlayerItem.Status.failed on setSourceUrl", details: nil))
player.eventHandler.onError(code: "DarwinAudioError", message: "AVPlayerItem.Status.failed on setSourceUrl", details: nil)
})
result(1)
return
} else if method == "setSourceBytes" {
result(FlutterError(code: "DarwinAudioError", message: "setSourceBytes is not currently implemented on iOS", details: nil))
Expand Down Expand Up @@ -342,6 +343,12 @@ class AudioPlayersStreamHandler: NSObject, FlutterStreamHandler {
}
}

func onPrepared(isPrepared: Bool) {
if let eventSink = self.sink {
eventSink(["event": "audio.onPrepared", "value": isPrepared])
}
}

func onLog(message: String) {
if let eventSink = self.sink {
eventSink(["event": "audio.onLog", "value": message])
Expand Down
Loading