From b0273153db12f62ffc41f7147cd16eb9a8760c8f Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Tue, 23 May 2023 14:42:44 +0200 Subject: [PATCH 01/81] fix: resume after release --- .../audioplayers_web/lib/wrapped_player.dart | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/audioplayers_web/lib/wrapped_player.dart b/packages/audioplayers_web/lib/wrapped_player.dart index dc91edfc1..01e2deccc 100644 --- a/packages/audioplayers_web/lib/wrapped_player.dart +++ b/packages/audioplayers_web/lib/wrapped_player.dart @@ -34,7 +34,8 @@ class WrappedPlayer { } _currentUrl = url; - stop(); + release(); + _pausedAt = 0; recreateNode(); if (_isPlaying) { await resume(); @@ -132,8 +133,7 @@ class WrappedPlayer { ); _playerEndedSubscription = p.onEnded.listen( (_) { - _pausedAt = 0; - p.currentTime = 0; + stop(); eventStreamController.add( const AudioEvent(eventType: AudioEventType.complete), ); @@ -205,9 +205,12 @@ class WrappedPlayer { } void stop() { - _cancel(); + pause(); _pausedAt = 0; player?.currentTime = 0; + if (_currentReleaseMode == ReleaseMode.release) { + release(); + } } void seek(int position) { @@ -219,14 +222,6 @@ class WrappedPlayer { } } - void _cancel() { - _isPlaying = false; - player?.pause(); - if (_currentReleaseMode == ReleaseMode.release) { - player = null; - } - } - void log(String message) { eventStreamController.add( AudioEvent(eventType: AudioEventType.log, logMessage: message), From 090e1ab41a5dd38353db56dcfe968a71923a6236 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Tue, 1 Aug 2023 17:12:45 +0200 Subject: [PATCH 02/81] tests: test audio sources in lib --- .../app/app_source_test_data.dart | 16 ++++++++-------- .../lib/lib_source_test_data.dart | 13 ++++++++++++- .../example/integration_test/lib_test.dart | 6 ++++++ 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/packages/audioplayers/example/integration_test/app/app_source_test_data.dart b/packages/audioplayers/example/integration_test/app/app_source_test_data.dart index 8e9a9faa7..b70eb36e1 100644 --- a/packages/audioplayers/example/integration_test/app/app_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/app/app_source_test_data.dart @@ -29,39 +29,39 @@ final audioTestDataList = [ sourceKey: 'url-remote-wav-1', duration: const Duration(milliseconds: 451), ), - if (_features.hasUrlSource) + /*if (_features.hasUrlSource) AppSourceTestData( sourceKey: 'url-remote-wav-2', duration: const Duration(seconds: 1, milliseconds: 068), - ), + ),*/ if (_features.hasUrlSource) AppSourceTestData( sourceKey: 'url-remote-mp3-1', duration: const Duration(minutes: 3, seconds: 30, milliseconds: 77), ), - if (_features.hasUrlSource) + /*if (_features.hasUrlSource) AppSourceTestData( sourceKey: 'url-remote-mp3-2', duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), - ), + ),*/ if (_features.hasUrlSource && _features.hasPlaylistSourceType) AppSourceTestData( sourceKey: 'url-remote-m3u8', duration: Duration.zero, isLiveStream: true, ), - if (_features.hasUrlSource) + /*if (_features.hasUrlSource) AppSourceTestData( sourceKey: 'url-remote-mpga', duration: Duration.zero, isLiveStream: true, - ), + ),*/ if (_features.hasAssetSource) AppSourceTestData( sourceKey: 'asset-wav', duration: const Duration(seconds: 1, milliseconds: 068), ), - if (_features.hasAssetSource) + /*if (_features.hasAssetSource) AppSourceTestData( sourceKey: 'asset-mp3', duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), @@ -75,5 +75,5 @@ final audioTestDataList = [ AppSourceTestData( sourceKey: 'bytes-remote', duration: const Duration(minutes: 3, seconds: 30, milliseconds: 76), - ), + ),*/ ]; diff --git a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart index 77423e788..15bd3f84e 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart @@ -1,5 +1,6 @@ import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers_example/tabs/sources.dart'; +import 'package:http/http.dart'; import '../platform_features.dart'; import '../source_test_data.dart'; @@ -36,7 +37,7 @@ final mp3Url1TestData = LibSourceTestData( duration: const Duration(minutes: 3, seconds: 30, milliseconds: 77), ); -final audioTestDataList = [ +Future> getAudioTestDataList() async => [ if (_features.hasUrlSource) wavUrl1TestData, if (_features.hasUrlSource) LibSourceTestData( @@ -71,4 +72,14 @@ final audioTestDataList = [ source: AssetSource(mp3Asset), duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), ), + if (_features.hasBytesSource) + LibSourceTestData( + source: BytesSource(await AudioCache.instance.loadAsBytes(wavAsset)), + duration: const Duration(seconds: 1, milliseconds: 068), + ), + if (_features.hasBytesSource) + LibSourceTestData( + source: BytesSource(await readBytes(Uri.parse(mp3Url1))), + duration: const Duration(minutes: 3, seconds: 30, milliseconds: 76), + ), ]; diff --git a/packages/audioplayers/example/integration_test/lib_test.dart b/packages/audioplayers/example/integration_test/lib_test.dart index cbf0102fa..bb4a9cb1f 100644 --- a/packages/audioplayers/example/integration_test/lib_test.dart +++ b/packages/audioplayers/example/integration_test/lib_test.dart @@ -18,6 +18,12 @@ void main() { final isAndroid = !kIsWeb && Platform.isAndroid; group('play multiple sources', () { + late List audioTestDataList; + + setUp(() async { + audioTestDataList = await getAudioTestDataList(); + }); + testWidgets( 'play multiple sources simultaneously', (WidgetTester tester) async { From 8dadebbb683eb50edfbd203e2eb1e574aceecc21 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 09:37:16 +0200 Subject: [PATCH 03/81] run with every source --- .../example/integration_test/lib_test.dart | 11 +-- .../integration_test/platform_test.dart | 75 ++++++++++--------- 2 files changed, 42 insertions(+), 44 deletions(-) diff --git a/packages/audioplayers/example/integration_test/lib_test.dart b/packages/audioplayers/example/integration_test/lib_test.dart index bb4a9cb1f..ac2dc28fa 100644 --- a/packages/audioplayers/example/integration_test/lib_test.dart +++ b/packages/audioplayers/example/integration_test/lib_test.dart @@ -17,12 +17,8 @@ void main() { final isAndroid = !kIsWeb && Platform.isAndroid; - group('play multiple sources', () { - late List audioTestDataList; - - setUp(() async { - audioTestDataList = await getAudioTestDataList(); - }); + group('play multiple sources', () async { + final audioTestDataList = await getAudioTestDataList(); testWidgets( 'play multiple sources simultaneously', @@ -62,8 +58,7 @@ void main() { (WidgetTester tester) async { final player = AudioPlayer(); - for (var i = 0; i < audioTestDataList.length; i++) { - final td = audioTestDataList[i]; + for (final td in audioTestDataList) { await tester.pumpLinux(); await player.play(td.source); await tester.pumpAndSettle(); diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 6b2ba71b9..0368a1c32 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -89,9 +89,10 @@ void main() { ); }); - group('Platform method channel', () { + group('Platform method channel', () async { late AudioplayersPlatformInterface platform; late String playerId; + final audioTestDataList = await getAudioTestDataList(); setUp(() async { platform = AudioplayersPlatformInterface.instance; @@ -124,43 +125,45 @@ void main() { await tester.pumpLinux(); }); - testWidgets('#setSource #getPosition and #getDuration', (tester) async { - await tester.prepareSource( - playerId: playerId, - platform: platform, - testData: wavUrl1TestData, - ); - expect(await platform.getCurrentPosition(playerId), 0); - expect( - await platform.getDuration(playerId), - wavUrl1TestData.duration.inMilliseconds, - ); - await tester.pumpLinux(); - }); + for (final td in audioTestDataList) { + testWidgets('#setSource #getPosition and #getDuration', (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + expect(await platform.getCurrentPosition(playerId), 0); + expect( + await platform.getDuration(playerId), + td.duration.inMilliseconds, + ); + await tester.pumpLinux(); + }); - testWidgets('#seek with millisecond precision', (tester) async { - await tester.prepareSource( - playerId: playerId, - platform: platform, - testData: mp3Url1TestData, - ); + testWidgets('#seek with millisecond precision', (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); - final eventStream = platform.getEventStream(playerId); - final seekCompleter = Completer(); - final onSeekSub = eventStream - .where((event) => event.eventType == AudioEventType.seekComplete) - .listen( - (_) { - seekCompleter.complete(); - }, - onError: seekCompleter.completeError, - ); - await platform.seek(playerId, const Duration(milliseconds: 21)); - await seekCompleter.future.timeout(const Duration(seconds: 30)); - await onSeekSub.cancel(); - expect(await platform.getCurrentPosition(playerId), 21); - await tester.pumpLinux(); - }); + final eventStream = platform.getEventStream(playerId); + final seekCompleter = Completer(); + final onSeekSub = eventStream + .where((event) => event.eventType == AudioEventType.seekComplete) + .listen( + (_) { + seekCompleter.complete(); + }, + onError: seekCompleter.completeError, + ); + await platform.seek(playerId, const Duration(milliseconds: 21)); + await seekCompleter.future.timeout(const Duration(seconds: 30)); + await onSeekSub.cancel(); + expect(await platform.getCurrentPosition(playerId), 21); + await tester.pumpLinux(); + }); + } testWidgets('Set same source twice (#1520)', (tester) async { for (var i = 0; i < 2; i++) { From 36c3979dc2aba88dc720150151242180d7649bc6 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 09:43:06 +0200 Subject: [PATCH 04/81] remove redundant resources --- .../lib/lib_source_test_data.dart | 96 ++++++++++--------- 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart index 15bd3f84e..95f9ecfc6 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart @@ -1,6 +1,5 @@ import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers_example/tabs/sources.dart'; -import 'package:http/http.dart'; import '../platform_features.dart'; import '../source_test_data.dart'; @@ -37,49 +36,52 @@ final mp3Url1TestData = LibSourceTestData( duration: const Duration(minutes: 3, seconds: 30, milliseconds: 77), ); -Future> getAudioTestDataList() async => [ - if (_features.hasUrlSource) wavUrl1TestData, - if (_features.hasUrlSource) - LibSourceTestData( - source: UrlSource(wavUrl2), - duration: const Duration(seconds: 1, milliseconds: 068), - ), - if (_features.hasUrlSource) mp3Url1TestData, - if (_features.hasUrlSource) - LibSourceTestData( - source: UrlSource(mp3Url2), - duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), - ), - if (_features.hasUrlSource && _features.hasPlaylistSourceType) - LibSourceTestData( - source: UrlSource(m3u8StreamUrl), - duration: Duration.zero, - isLiveStream: true, - ), - if (_features.hasUrlSource) - LibSourceTestData( - source: UrlSource(mpgaStreamUrl), - duration: Duration.zero, - isLiveStream: true, - ), - if (_features.hasAssetSource) - LibSourceTestData( - source: AssetSource(wavAsset), - duration: const Duration(seconds: 1, milliseconds: 068), - ), - if (_features.hasAssetSource) - LibSourceTestData( - source: AssetSource(mp3Asset), - duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), - ), - if (_features.hasBytesSource) - LibSourceTestData( - source: BytesSource(await AudioCache.instance.loadAsBytes(wavAsset)), - duration: const Duration(seconds: 1, milliseconds: 068), - ), - if (_features.hasBytesSource) - LibSourceTestData( - source: BytesSource(await readBytes(Uri.parse(mp3Url1))), - duration: const Duration(minutes: 3, seconds: 30, milliseconds: 76), - ), -]; +// Some sources are commented which are considered redundant +Future> getAudioTestDataList() async { + return [ + if (_features.hasUrlSource) wavUrl1TestData, + /*if (_features.hasUrlSource) + LibSourceTestData( + source: UrlSource(wavUrl2), + duration: const Duration(seconds: 1, milliseconds: 068), + ),*/ + if (_features.hasUrlSource) mp3Url1TestData, + /*if (_features.hasUrlSource) + LibSourceTestData( + source: UrlSource(mp3Url2), + duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), + ),*/ + if (_features.hasUrlSource && _features.hasPlaylistSourceType) + LibSourceTestData( + source: UrlSource(m3u8StreamUrl), + duration: Duration.zero, + isLiveStream: true, + ), + if (_features.hasUrlSource) + LibSourceTestData( + source: UrlSource(mpgaStreamUrl), + duration: Duration.zero, + isLiveStream: true, + ), + if (_features.hasAssetSource) + LibSourceTestData( + source: AssetSource(wavAsset), + duration: const Duration(seconds: 1, milliseconds: 068), + ), + /*if (_features.hasAssetSource) + LibSourceTestData( + source: AssetSource(mp3Asset), + duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), + ),*/ + if (_features.hasBytesSource) + LibSourceTestData( + source: BytesSource(await AudioCache.instance.loadAsBytes(wavAsset)), + duration: const Duration(seconds: 1, milliseconds: 068), + ), + /*if (_features.hasBytesSource) + LibSourceTestData( + source: BytesSource(await readBytes(Uri.parse(mp3Url1))), + duration: const Duration(minutes: 3, seconds: 30, milliseconds: 76), + ),*/ + ]; +} From 85600057f978ca9f3d1e2bd3b2444a141f602697 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 10:16:07 +0200 Subject: [PATCH 05/81] move to platform tests --- .../app/tabs/controls_tab.dart | 5 +- .../lib/lib_source_test_data.dart | 2 +- .../integration_test/platform_test.dart | 143 +++++++++++++++--- 3 files changed, 122 insertions(+), 28 deletions(-) diff --git a/packages/audioplayers/example/integration_test/app/tabs/controls_tab.dart b/packages/audioplayers/example/integration_test/app/tabs/controls_tab.dart index 065f49374..426c1418d 100644 --- a/packages/audioplayers/example/integration_test/app/tabs/controls_tab.dart +++ b/packages/audioplayers/example/integration_test/app/tabs/controls_tab.dart @@ -66,6 +66,7 @@ Future testControlsTab( await tester.stop(); } + // Test all features in low latency mode: final isBytesSource = audioSourceTestData.sourceKey.contains('bytes'); if (features.hasLowLatency && !audioSourceTestData.isLiveStream && @@ -150,7 +151,6 @@ extension ControlsWidgetTester on WidgetTester { printWithTimeOnFailure('Test Volume: $volume'); await scrollToAndTap(Key('control-volume-$volume')); await resume(); - // TODO(Gustl22): get volume from native implementation await pump(timeout); await stop(); } @@ -162,7 +162,6 @@ extension ControlsWidgetTester on WidgetTester { printWithTimeOnFailure('Test Balance: $balance'); await scrollToAndTap(Key('control-balance-$balance')); await resume(); - // TODO(novikov): get balance from native implementation await pump(timeout); await stop(); } @@ -174,7 +173,6 @@ extension ControlsWidgetTester on WidgetTester { printWithTimeOnFailure('Test Rate: $rate'); await scrollToAndTap(Key('control-rate-$rate')); await resume(); - // TODO(Gustl22): get rate from native implementation await pump(timeout); await stop(); } @@ -224,6 +222,5 @@ extension ControlsWidgetTester on WidgetTester { if (isResume) { await resume(); } - // TODO(Gustl22): get release mode from native implementation } } diff --git a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart index 95f9ecfc6..5f8c5c5be 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart @@ -16,7 +16,7 @@ class LibSourceTestData extends SourceTestData { @override String toString() { - return 'RawSourceTestData(' + return 'LibSourceTestData(' 'source: $source, ' 'duration: $duration, ' 'isLiveStream: $isLiveStream' diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 0368a1c32..7ef0c092a 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -9,9 +9,11 @@ import 'package:integration_test/integration_test.dart'; import 'lib/lib_source_test_data.dart'; import 'lib/lib_test_utils.dart'; +import 'platform_features.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + final features = PlatformFeatures.instance(); group('Logging', () { testWidgets('Emit platform log', (tester) async { @@ -126,7 +128,8 @@ void main() { }); for (final td in audioTestDataList) { - testWidgets('#setSource #getPosition and #getDuration', (tester) async { + testWidgets('#setSource #getPosition and #getDuration ${td.source}', + (tester) async { await tester.prepareSource( playerId: playerId, platform: platform, @@ -140,29 +143,123 @@ void main() { await tester.pumpLinux(); }); - testWidgets('#seek with millisecond precision', (tester) async { - await tester.prepareSource( - playerId: playerId, - platform: platform, - testData: td, - ); + if (features.hasVolume) { + testWidgets('#volume ${td.source}', (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + for (final volume in [0.0, 0.5, 1.0]) { + await platform.setVolume(playerId, volume); + await platform.resume(playerId); + await tester.pump(const Duration(seconds: 1)); + await platform.stop(playerId); + } + // May check native volume here + await tester.pumpLinux(); + }); + } - final eventStream = platform.getEventStream(playerId); - final seekCompleter = Completer(); - final onSeekSub = eventStream - .where((event) => event.eventType == AudioEventType.seekComplete) - .listen( - (_) { - seekCompleter.complete(); - }, - onError: seekCompleter.completeError, - ); - await platform.seek(playerId, const Duration(milliseconds: 21)); - await seekCompleter.future.timeout(const Duration(seconds: 30)); - await onSeekSub.cancel(); - expect(await platform.getCurrentPosition(playerId), 21); - await tester.pumpLinux(); - }); + if (features.hasBalance) { + testWidgets('#balance ${td.source}', (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + for (final balance in [-1.0, 0.0, 1.0]) { + await platform.setBalance(playerId, balance); + await platform.resume(playerId); + await tester.pump(const Duration(seconds: 1)); + await platform.stop(playerId); + } + // May check native balance here + await tester.pumpLinux(); + }); + } + + if (features.hasPlaybackRate && !td.isLiveStream) { + testWidgets('#playbackRate ${td.source}', (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + for (final playbackRate in [0.5, 1.0, 2.0]) { + await platform.setPlaybackRate(playerId, playbackRate); + await platform.resume(playerId); + await tester.pump(const Duration(seconds: 1)); + await platform.stop(playerId); + } + // May check native playback rate here + await tester.pumpLinux(); + }); + } + + if (features.hasSeek && !td.isLiveStream) { + testWidgets('#seek with millisecond precision ${td.source}', + (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + + final eventStream = platform.getEventStream(playerId); + final seekCompleter = Completer(); + final onSeekSub = eventStream + .where((event) => event.eventType == AudioEventType.seekComplete) + .listen( + (_) { + seekCompleter.complete(); + }, + onError: seekCompleter.completeError, + ); + await platform.seek(playerId, const Duration(milliseconds: 21)); + await seekCompleter.future.timeout(const Duration(seconds: 30)); + await onSeekSub.cancel(); + expect(await platform.getCurrentPosition(playerId), 21); + await tester.pumpLinux(); + }); + } + + if (features.hasReleaseModeLoop && + !td.isLiveStream && + td.duration < const Duration(seconds: 2)) { + testWidgets('#ReleaseMode.loop ${td.source}', (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + await platform.setReleaseMode(playerId, ReleaseMode.loop); + await platform.resume(playerId); + await tester.pump(const Duration(seconds: 3)); + await platform.stop(playerId); + + // May check number of loops here + await tester.pumpLinux(); + }); + + testWidgets('#ReleaseMode.release ${td.source}', (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + await platform.setReleaseMode(playerId, ReleaseMode.release); + await platform.resume(playerId); + await tester.pump(const Duration(seconds: 3)); + // No need to call stop, as it should be released by now + // TODO(Gustl22): test if source was released + + // May check number of loops here + await tester.pumpLinux(); + + // TODO(Gustl22): test 'platform.release()' + }); + } } testWidgets('Set same source twice (#1520)', (tester) async { From f4008de3a766af77a268a4b842dd0d63dfe5b1d9 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 10:42:31 +0200 Subject: [PATCH 06/81] move to platform tests --- .../example/integration_test/lib_test.dart | 11 ++++------- .../integration_test/platform_test.dart | 19 ++++++++++++------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/audioplayers/example/integration_test/lib_test.dart b/packages/audioplayers/example/integration_test/lib_test.dart index ac2dc28fa..5e02b91ac 100644 --- a/packages/audioplayers/example/integration_test/lib_test.dart +++ b/packages/audioplayers/example/integration_test/lib_test.dart @@ -10,16 +10,13 @@ import 'lib/lib_test_utils.dart'; import 'platform_features.dart'; import 'test_utils.dart'; -void main() { - final features = PlatformFeatures.instance(); - +void main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - + final features = PlatformFeatures.instance(); final isAndroid = !kIsWeb && Platform.isAndroid; + final audioTestDataList = await getAudioTestDataList(); - group('play multiple sources', () async { - final audioTestDataList = await getAudioTestDataList(); - + group('play multiple sources', () { testWidgets( 'play multiple sources simultaneously', (WidgetTester tester) async { diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 7ef0c092a..9ce38ead1 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -11,9 +11,10 @@ import 'lib/lib_source_test_data.dart'; import 'lib/lib_test_utils.dart'; import 'platform_features.dart'; -void main() { +void main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final features = PlatformFeatures.instance(); + final audioTestDataList = await getAudioTestDataList(); group('Logging', () { testWidgets('Emit platform log', (tester) async { @@ -91,10 +92,9 @@ void main() { ); }); - group('Platform method channel', () async { + group('Platform method channel', () { late AudioplayersPlatformInterface platform; late String playerId; - final audioTestDataList = await getAudioTestDataList(); setUp(() async { platform = AudioplayersPlatformInterface.instance; @@ -363,10 +363,15 @@ extension on WidgetTester { onError: preparedCompleter.completeError, ); await pumpLinux(); - await platform.setSourceUrl( - playerId, - (testData.source as UrlSource).url, - ); + final source = testData.source; + if (source is UrlSource) { + await platform.setSourceUrl(playerId, source.url); + } else if (source is AssetSource) { + final url = await AudioCache.instance.load(source.path); + await platform.setSourceUrl(playerId, url.path, isLocal: true); + } else if (source is BytesSource) { + await platform.setSourceBytes(playerId, source.bytes); + } await preparedCompleter.future.timeout(const Duration(seconds: 30)); await onPreparedSub.cancel(); } From 73466b76ebe60ba7a6990d6a78c42e61e6e99d19 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 10:51:52 +0200 Subject: [PATCH 07/81] move to platform tests --- .../app/app_source_test_data.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/audioplayers/example/integration_test/app/app_source_test_data.dart b/packages/audioplayers/example/integration_test/app/app_source_test_data.dart index b70eb36e1..08e1ad726 100644 --- a/packages/audioplayers/example/integration_test/app/app_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/app/app_source_test_data.dart @@ -23,6 +23,8 @@ class AppSourceTestData extends SourceTestData { final _features = PlatformFeatures.instance(); +// All sources are tested again in lib or platform tests, +// therefore comment most of them to save testing time final audioTestDataList = [ if (_features.hasUrlSource) AppSourceTestData( @@ -34,11 +36,11 @@ final audioTestDataList = [ sourceKey: 'url-remote-wav-2', duration: const Duration(seconds: 1, milliseconds: 068), ),*/ - if (_features.hasUrlSource) + /*if (_features.hasUrlSource) AppSourceTestData( sourceKey: 'url-remote-mp3-1', duration: const Duration(minutes: 3, seconds: 30, milliseconds: 77), - ), + ),*/ /*if (_features.hasUrlSource) AppSourceTestData( sourceKey: 'url-remote-mp3-2', @@ -56,22 +58,22 @@ final audioTestDataList = [ duration: Duration.zero, isLiveStream: true, ),*/ - if (_features.hasAssetSource) + /*if (_features.hasAssetSource) AppSourceTestData( sourceKey: 'asset-wav', duration: const Duration(seconds: 1, milliseconds: 068), - ), + ),*/ /*if (_features.hasAssetSource) AppSourceTestData( sourceKey: 'asset-mp3', duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), - ), - if (_features.hasBytesSource) + ),*/ + /*if (_features.hasBytesSource) AppSourceTestData( sourceKey: 'bytes-local', duration: const Duration(seconds: 1, milliseconds: 068), - ), - if (_features.hasBytesSource) + ),*/ + /*if (_features.hasBytesSource) AppSourceTestData( sourceKey: 'bytes-remote', duration: const Duration(minutes: 3, seconds: 30, milliseconds: 76), From fd9a8f2d9a6c402d01162a81ca1982d294e38fdc Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 15:57:56 +0200 Subject: [PATCH 08/81] fix platform tests --- .../integration_test/app/app_test_utils.dart | 14 -------------- .../example/integration_test/platform_test.dart | 10 ++++++++-- .../example/integration_test/test_utils.dart | 14 ++++++++++++++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/audioplayers/example/integration_test/app/app_test_utils.dart b/packages/audioplayers/example/integration_test/app/app_test_utils.dart index 3cd8f6306..7c35c61b2 100644 --- a/packages/audioplayers/example/integration_test/app/app_test_utils.dart +++ b/packages/audioplayers/example/integration_test/app/app_test_utils.dart @@ -125,20 +125,6 @@ $lastFailureMsg''', } await pumpAndSettle(); } - - bool durationRangeMatcher( - Duration? actual, - Duration? expected, { - Duration deviation = const Duration(seconds: 1), - }) { - if (actual == null && expected == null) { - return true; - } - if (actual == null || expected == null) { - return false; - } - return actual >= (expected - deviation) && actual <= (expected + deviation); - } } void expectWidgetHasText( diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 9ce38ead1..df55e04b6 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -10,6 +10,7 @@ import 'package:integration_test/integration_test.dart'; import 'lib/lib_source_test_data.dart'; import 'lib/lib_test_utils.dart'; import 'platform_features.dart'; +import 'test_utils.dart'; void main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -136,9 +137,14 @@ void main() async { testData: td, ); expect(await platform.getCurrentPosition(playerId), 0); + final durationMs = await platform.getDuration(playerId); expect( - await platform.getDuration(playerId), - td.duration.inMilliseconds, + durationMs != null ? Duration(milliseconds: durationMs) : null, + (Duration? actual) => durationRangeMatcher( + actual, + td.duration, + deviation: const Duration(milliseconds: 1), + ), ); await tester.pumpLinux(); }); diff --git a/packages/audioplayers/example/integration_test/test_utils.dart b/packages/audioplayers/example/integration_test/test_utils.dart index 24a09943e..2e05c5ea2 100644 --- a/packages/audioplayers/example/integration_test/test_utils.dart +++ b/packages/audioplayers/example/integration_test/test_utils.dart @@ -3,3 +3,17 @@ import 'package:flutter_test/flutter_test.dart'; void printWithTimeOnFailure(String message) { printOnFailure('${DateTime.now()}: $message'); } + +bool durationRangeMatcher( + Duration? actual, + Duration? expected, { + Duration deviation = const Duration(seconds: 1), +}) { + if (actual == null && expected == null) { + return true; + } + if (actual == null || expected == null) { + return false; + } + return actual >= (expected - deviation) && actual <= (expected + deviation); +} From 19ea8ce057b644c5b320e06f7caaffcecdf10f34 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 16:06:24 +0200 Subject: [PATCH 09/81] test order --- .../example/integration_test/platform_test.dart | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index df55e04b6..862142de6 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -148,8 +148,10 @@ void main() async { ); await tester.pumpLinux(); }); + } - if (features.hasVolume) { + if (features.hasVolume) { + for (final td in audioTestDataList) { testWidgets('#volume ${td.source}', (tester) async { await tester.prepareSource( playerId: playerId, @@ -166,8 +168,10 @@ void main() async { await tester.pumpLinux(); }); } + } - if (features.hasBalance) { + if (features.hasBalance) { + for (final td in audioTestDataList) { testWidgets('#balance ${td.source}', (tester) async { await tester.prepareSource( playerId: playerId, @@ -184,7 +188,9 @@ void main() async { await tester.pumpLinux(); }); } + } + for (final td in audioTestDataList) { if (features.hasPlaybackRate && !td.isLiveStream) { testWidgets('#playbackRate ${td.source}', (tester) async { await tester.prepareSource( @@ -202,7 +208,9 @@ void main() async { await tester.pumpLinux(); }); } + } + for (final td in audioTestDataList) { if (features.hasSeek && !td.isLiveStream) { testWidgets('#seek with millisecond precision ${td.source}', (tester) async { @@ -229,7 +237,9 @@ void main() async { await tester.pumpLinux(); }); } + } + for (final td in audioTestDataList) { if (features.hasReleaseModeLoop && !td.isLiveStream && td.duration < const Duration(seconds: 2)) { @@ -247,7 +257,9 @@ void main() async { // May check number of loops here await tester.pumpLinux(); }); + } + for (final td in audioTestDataList) { testWidgets('#ReleaseMode.release ${td.source}', (tester) async { await tester.prepareSource( playerId: playerId, From 45f8ca99078d5c7ae5fc4c1e65b3b5321f56f310 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 16:13:21 +0200 Subject: [PATCH 10/81] release mode release --- .../audioplayers/example/integration_test/platform_test.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 862142de6..67d051643 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -258,8 +258,10 @@ void main() async { await tester.pumpLinux(); }); } + } - for (final td in audioTestDataList) { + for (final td in audioTestDataList) { + if (features.hasReleaseModeRelease && !td.isLiveStream) { testWidgets('#ReleaseMode.release ${td.source}', (tester) async { await tester.prepareSource( playerId: playerId, From 80a5d5fb202a6b37cfca3c038c919d4004df19e1 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 2 Aug 2023 16:45:02 +0200 Subject: [PATCH 11/81] positionEvent --- .../integration_test/platform_test.dart | 36 ++++++++++++++++--- 1 file changed, 31 insertions(+), 5 deletions(-) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 67d051643..e30f0cee2 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -225,11 +225,9 @@ void main() async { final onSeekSub = eventStream .where((event) => event.eventType == AudioEventType.seekComplete) .listen( - (_) { - seekCompleter.complete(); - }, - onError: seekCompleter.completeError, - ); + (_) => seekCompleter.complete(), + onError: seekCompleter.completeError, + ); await platform.seek(playerId, const Duration(milliseconds: 21)); await seekCompleter.future.timeout(const Duration(seconds: 30)); await onSeekSub.cancel(); @@ -308,6 +306,34 @@ void main() async { await platform.dispose(playerId); }); + for (final td in audioTestDataList) { + if (features.hasPositionEvent && + (td.isLiveStream || td.duration > const Duration(seconds: 2))) { + 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) => event.eventType == AudioEventType.position) + .listen( + (event) => position = event.position, + ); + + await platform.resume(playerId); + await tester.pumpAndSettle(const Duration(seconds: 1)); + expect(position, greaterThan(Duration.zero)); + await platform.stop(playerId); + await onPositionSub.cancel(); + await tester.pumpLinux(); + }); + } + } + testWidgets('Listen and cancel twice', (tester) async { final eventStream = platform.getEventStream(playerId); for (var i = 0; i < 2; i++) { From e70529e006338ad48e8aecda45d2f4835d75c942 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Thu, 3 Aug 2023 11:48:38 +0200 Subject: [PATCH 12/81] durationEvent --- .../integration_test/platform_test.dart | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index e30f0cee2..8d989984c 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -306,6 +306,43 @@ void main() async { await platform.dispose(playerId); }); + for (final td in audioTestDataList) { + if (features.hasDurationEvent && !td.isLiveStream) { + testWidgets('#durationEvent ${td.source}', (tester) async { + final eventStream = platform.getEventStream(playerId); + final durationCompleter = Completer(); + final onDurationSub = eventStream + .where((event) => event.eventType == AudioEventType.duration) + .listen( + (event) => durationCompleter.complete(event.duration), + onError: durationCompleter.completeError, + ); + + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + // TODO(Gustl22): Test duration event during preparation instead of + // during playing. + + await platform.resume(playerId); + expect( + await durationCompleter.future.timeout(const Duration(seconds: 30)), + (Duration? actual) => durationRangeMatcher( + actual, + td.duration, + deviation: const Duration(milliseconds: 1), + ), + ); + await platform.stop(playerId); + + await onDurationSub.cancel(); + await tester.pumpLinux(); + }); + } + } + for (final td in audioTestDataList) { if (features.hasPositionEvent && (td.isLiveStream || td.duration > const Duration(seconds: 2))) { From 47eb51d5809db2bf7eb642ed30f0876fa9739e58 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Thu, 3 Aug 2023 13:50:32 +0200 Subject: [PATCH 13/81] fix: disposing event channel --- .../lib/src/audioplayers_platform.dart | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/packages/audioplayers_platform_interface/lib/src/audioplayers_platform.dart b/packages/audioplayers_platform_interface/lib/src/audioplayers_platform.dart index d8fe5d432..9562af85e 100644 --- a/packages/audioplayers_platform_interface/lib/src/audioplayers_platform.dart +++ b/packages/audioplayers_platform_interface/lib/src/audioplayers_platform.dart @@ -12,6 +12,18 @@ import 'package:flutter/services.dart'; class AudioplayersPlatform extends AudioplayersPlatformInterface with MethodChannelAudioplayersPlatform, EventChannelAudioplayersPlatform { AudioplayersPlatform(); + + @override + Future create(String playerId) async { + await super.create(playerId); + createEventStream(playerId); + } + + @override + Future dispose(String playerId) async { + await super.dispose(playerId); + disposeEventStream(playerId); + } } mixin MethodChannelAudioplayersPlatform @@ -212,12 +224,12 @@ mixin MethodChannelAudioplayersPlatform mixin EventChannelAudioplayersPlatform implements EventChannelAudioplayersPlatformInterface { - @override - Stream getEventStream(String playerId) { - // Only can be used after have created the event channel on the native side. - final eventChannel = EventChannel('xyz.luan/audioplayers/events/$playerId'); + final Map> streams = {}; - return eventChannel.receiveBroadcastStream().map( + // Only can be used after have created the event channel on the native side. + void createEventStream(String playerId) { + final eventChannel = EventChannel('xyz.luan/audioplayers/events/$playerId'); + streams[playerId] = eventChannel.receiveBroadcastStream().map( (dynamic event) { final map = event as Map; final eventType = map.getString('event'); @@ -258,4 +270,15 @@ mixin EventChannelAudioplayersPlatform }, ); } + + void disposeEventStream(String playerId) { + if (streams.containsKey(playerId)) { + streams.remove(playerId); + } + } + + @override + Stream getEventStream(String playerId) { + return streams[playerId]!; + } } From c474d557b6bf476aacfb87a679353a7f89b33d3b Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Thu, 3 Aug 2023 13:51:37 +0200 Subject: [PATCH 14/81] fix: duration tests --- .../audioplayers/example/integration_test/platform_test.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 8d989984c..a09815ebb 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -323,10 +323,7 @@ void main() async { platform: platform, testData: td, ); - // TODO(Gustl22): Test duration event during preparation instead of - // during playing. - await platform.resume(playerId); expect( await durationCompleter.future.timeout(const Duration(seconds: 30)), (Duration? actual) => durationRangeMatcher( @@ -335,8 +332,6 @@ void main() async { deviation: const Duration(milliseconds: 1), ), ); - await platform.stop(playerId); - await onDurationSub.cancel(); await tester.pumpLinux(); }); From b86ce4571857dcf2501918e912f0b173bdab025d Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Thu, 3 Aug 2023 13:52:38 +0200 Subject: [PATCH 15/81] [WIP]: remove pump for linux --- .../example/integration_test/lib/lib_test_utils.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/audioplayers/example/integration_test/lib/lib_test_utils.dart b/packages/audioplayers/example/integration_test/lib/lib_test_utils.dart index 60767cd29..e128dc0b3 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_test_utils.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_test_utils.dart @@ -5,9 +5,9 @@ import 'package:flutter_test/flutter_test.dart'; extension LibWidgetTester on WidgetTester { Future pumpLinux() async { - if (!kIsWeb && Platform.isLinux) { - // FIXME(gustl22): Linux needs additional pump (#1556) - await pump(); - } + // if (!kIsWeb && Platform.isLinux) { + // // FIXME(gustl22): Linux needs additional pump (#1556) + // await pump(); + // } } } From 2008f1033e4f2de724af87282ac8463a473f051b Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Mon, 7 Aug 2023 10:57:01 +0200 Subject: [PATCH 16/81] Allow 1 ms discrepancy for position --- .../example/integration_test/platform_test.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index a09815ebb..9e3fe33b2 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -228,10 +228,18 @@ void main() async { (_) => seekCompleter.complete(), onError: seekCompleter.completeError, ); - await platform.seek(playerId, const Duration(milliseconds: 21)); + await platform.seek(playerId, const Duration(milliseconds: 22)); await seekCompleter.future.timeout(const Duration(seconds: 30)); await onSeekSub.cancel(); - expect(await platform.getCurrentPosition(playerId), 21); + final positionMs = await platform.getCurrentPosition(playerId); + expect( + positionMs != null ? Duration(milliseconds: positionMs) : null, + (Duration? actual) => durationRangeMatcher( + actual, + const Duration(milliseconds: 22), + deviation: const Duration(milliseconds: 1), + ), + ); await tester.pumpLinux(); }); } From 07ef77de381559e262800324b52fe580562f093c Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Mon, 7 Aug 2023 16:25:09 +0200 Subject: [PATCH 17/81] skip sources with variable bitrate --- .../app/app_source_test_data.dart | 2 + .../integration_test/app/tabs/stream_tab.dart | 5 +-- .../lib/lib_source_test_data.dart | 2 + .../integration_test/platform_test.dart | 45 +++++++++++-------- .../integration_test/source_test_data.dart | 4 ++ .../example/lib/tabs/sources.dart | 2 +- .../audioplayers_linux/linux/audio_player.cc | 2 + 7 files changed, 38 insertions(+), 24 deletions(-) diff --git a/packages/audioplayers/example/integration_test/app/app_source_test_data.dart b/packages/audioplayers/example/integration_test/app/app_source_test_data.dart index 08e1ad726..753e230c7 100644 --- a/packages/audioplayers/example/integration_test/app/app_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/app/app_source_test_data.dart @@ -8,6 +8,7 @@ class AppSourceTestData extends SourceTestData { AppSourceTestData({ required this.sourceKey, required super.duration, + super.isVBR, super.isLiveStream, }); @@ -39,6 +40,7 @@ final audioTestDataList = [ /*if (_features.hasUrlSource) AppSourceTestData( sourceKey: 'url-remote-mp3-1', + isVBR: true, duration: const Duration(minutes: 3, seconds: 30, milliseconds: 77), ),*/ /*if (_features.hasUrlSource) diff --git a/packages/audioplayers/example/integration_test/app/tabs/stream_tab.dart b/packages/audioplayers/example/integration_test/app/tabs/stream_tab.dart index d7aa558b5..78a301f69 100644 --- a/packages/audioplayers/example/integration_test/app/tabs/stream_tab.dart +++ b/packages/audioplayers/example/integration_test/app/tabs/stream_tab.dart @@ -23,10 +23,7 @@ Future testStreamsTab( await tester.testPosition(Duration.zero); } - final isImmediateDurationSupported = - features.hasMp3Duration || !audioSourceTestData.sourceKey.contains('mp3'); - - if (features.hasDurationEvent && isImmediateDurationSupported) { + if (features.hasDurationEvent && !audioSourceTestData.isVBR) { // Display duration before playing await tester.testDuration(audioSourceTestData.duration); } diff --git a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart index 5f8c5c5be..eb630b845 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart @@ -11,6 +11,7 @@ class LibSourceTestData extends SourceTestData { LibSourceTestData({ required this.source, required super.duration, + super.isVBR, super.isLiveStream, }); @@ -34,6 +35,7 @@ final wavUrl1TestData = LibSourceTestData( final mp3Url1TestData = LibSourceTestData( source: UrlSource(mp3Url1), duration: const Duration(minutes: 3, seconds: 30, milliseconds: 77), + isVBR: true, ); // Some sources are commented which are considered redundant diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 9e3fe33b2..499c66e3d 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'dart:io'; import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers_example/tabs/sources.dart'; import 'package:audioplayers_platform_interface/audioplayers_platform_interface.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -15,6 +17,7 @@ import 'test_utils.dart'; void main() async { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); final features = PlatformFeatures.instance(); + final isLinux = !kIsWeb && Platform.isLinux; final audioTestDataList = await getAudioTestDataList(); group('Logging', () { @@ -129,25 +132,29 @@ void main() async { }); for (final td in audioTestDataList) { - testWidgets('#setSource #getPosition and #getDuration ${td.source}', - (tester) async { - await tester.prepareSource( - playerId: playerId, - platform: platform, - testData: td, - ); - expect(await platform.getCurrentPosition(playerId), 0); - final durationMs = await platform.getDuration(playerId); - expect( - durationMs != null ? Duration(milliseconds: durationMs) : null, - (Duration? actual) => durationRangeMatcher( - actual, - td.duration, - deviation: const Duration(milliseconds: 1), - ), - ); - await tester.pumpLinux(); - }); + testWidgets( + '#setSource #getPosition and #getDuration ${td.source}', + (tester) async { + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + expect(await platform.getCurrentPosition(playerId), 0); + final durationMs = await platform.getDuration(playerId); + expect( + durationMs != null ? Duration(milliseconds: durationMs) : null, + (Duration? actual) => durationRangeMatcher( + actual, + td.duration, + deviation: const Duration(milliseconds: 1), + ), + ); + await tester.pumpLinux(); + }, + skip: isLinux && + td.isVBR, // TODO(gustl22): cannot determine duration for VBR on Linux + ); } if (features.hasVolume) { diff --git a/packages/audioplayers/example/integration_test/source_test_data.dart b/packages/audioplayers/example/integration_test/source_test_data.dart index a9f17e717..760aab237 100644 --- a/packages/audioplayers/example/integration_test/source_test_data.dart +++ b/packages/audioplayers/example/integration_test/source_test_data.dart @@ -3,10 +3,14 @@ abstract class SourceTestData { Duration duration; bool isLiveStream; + + /// Whether this source has variable bitrate + bool isVBR; SourceTestData({ required this.duration, this.isLiveStream = false, + this.isVBR = false, }); @override diff --git a/packages/audioplayers/example/lib/tabs/sources.dart b/packages/audioplayers/example/lib/tabs/sources.dart index 61f46006f..cf0b4e051 100644 --- a/packages/audioplayers/example/lib/tabs/sources.dart +++ b/packages/audioplayers/example/lib/tabs/sources.dart @@ -120,7 +120,7 @@ class _SourcesTabState extends State ), _createSourceTile( setSourceKey: const Key('setSource-url-remote-mp3-1'), - title: 'Remote URL MP3 1', + title: 'Remote URL MP3 1 (VBR)', subtitle: 'ambient_c_motion.mp3', source: UrlSource(mp3Url1), ), diff --git a/packages/audioplayers_linux/linux/audio_player.cc b/packages/audioplayers_linux/linux/audio_player.cc index 0c8c2f6b2..65e6ad9af 100644 --- a/packages/audioplayers_linux/linux/audio_player.cc +++ b/packages/audioplayers_linux/linux/audio_player.cc @@ -364,6 +364,8 @@ int64_t AudioPlayer::GetPosition() { int64_t AudioPlayer::GetDuration() { gint64 duration = 0; if (!gst_element_query_duration(playbin, GST_FORMAT_TIME, &duration)) { + // FIXME: Get duration for MP3 with variable bit rate with gst-discoverer: + // https://gstreamer.freedesktop.org/documentation/pbutils/gstdiscoverer.html?gi-language=c#gst_discoverer_info_get_duration this->OnLog("Could not query current duration."); return 0; } From a9a52e35e5cce18ca0ee02240027d23073037031 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Mon, 7 Aug 2023 16:32:54 +0200 Subject: [PATCH 18/81] skip sources with variable bitrate --- .../integration_test/platform_test.dart | 64 ++++++++++--------- 1 file changed, 35 insertions(+), 29 deletions(-) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 499c66e3d..6c9d4a381 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -152,8 +152,8 @@ void main() async { ); await tester.pumpLinux(); }, - skip: isLinux && - td.isVBR, // TODO(gustl22): cannot determine duration for VBR on Linux + // TODO(gustl22): cannot determine duration for VBR on Linux + skip: isLinux && td.isVBR, ); } @@ -323,33 +323,39 @@ void main() async { for (final td in audioTestDataList) { if (features.hasDurationEvent && !td.isLiveStream) { - testWidgets('#durationEvent ${td.source}', (tester) async { - final eventStream = platform.getEventStream(playerId); - final durationCompleter = Completer(); - final onDurationSub = eventStream - .where((event) => event.eventType == AudioEventType.duration) - .listen( - (event) => durationCompleter.complete(event.duration), - onError: durationCompleter.completeError, - ); - - await tester.prepareSource( - playerId: playerId, - platform: platform, - testData: td, - ); - - expect( - await durationCompleter.future.timeout(const Duration(seconds: 30)), - (Duration? actual) => durationRangeMatcher( - actual, - td.duration, - deviation: const Duration(milliseconds: 1), - ), - ); - await onDurationSub.cancel(); - await tester.pumpLinux(); - }); + testWidgets( + '#durationEvent ${td.source}', + (tester) async { + final eventStream = platform.getEventStream(playerId); + final durationCompleter = Completer(); + final onDurationSub = eventStream + .where((event) => event.eventType == AudioEventType.duration) + .listen( + (event) => durationCompleter.complete(event.duration), + onError: durationCompleter.completeError, + ); + + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: td, + ); + + expect( + await durationCompleter.future + .timeout(const Duration(seconds: 30)), + (Duration? actual) => durationRangeMatcher( + actual, + td.duration, + deviation: const Duration(milliseconds: 1), + ), + ); + await onDurationSub.cancel(); + await tester.pumpLinux(); + }, + // TODO(gustl22): cannot determine duration for VBR on Linux + skip: isLinux && td.isVBR, + ); } } From 2a516cca65ee877ccc2c90ab709bdd4020db7edc Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Mon, 7 Aug 2023 17:03:32 +0200 Subject: [PATCH 19/81] exclude some tests --- .../integration_test/lib/lib_source_test_data.dart | 12 +++++++----- .../example/integration_test/platform_test.dart | 6 ++++-- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart index eb630b845..c68ffc79a 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart @@ -38,6 +38,12 @@ final mp3Url1TestData = LibSourceTestData( isVBR: true, ); +final m3u8UrltestData = LibSourceTestData( + source: UrlSource(m3u8StreamUrl), + duration: Duration.zero, + isLiveStream: true, +); + // Some sources are commented which are considered redundant Future> getAudioTestDataList() async { return [ @@ -54,11 +60,7 @@ Future> getAudioTestDataList() async { duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), ),*/ if (_features.hasUrlSource && _features.hasPlaylistSourceType) - LibSourceTestData( - source: UrlSource(m3u8StreamUrl), - duration: Duration.zero, - isLiveStream: true, - ), + m3u8UrltestData, if (_features.hasUrlSource) LibSourceTestData( source: UrlSource(mpgaStreamUrl), diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 6c9d4a381..1502776bd 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -152,8 +152,10 @@ void main() async { ); await tester.pumpLinux(); }, - // TODO(gustl22): cannot determine duration for VBR on Linux - skip: isLinux && td.isVBR, + // FIXME(gustl22): cannot determine initial duration for VBR on Linux + // FIXME(gustl22): determines wrong initial position for m3u8 on Linux + skip: isLinux && td.isVBR || + isLinux && td.source == m3u8UrltestData.source, ); } From df4accc1db493b5e4c6302a95bf5d996a41b42e3 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Tue, 8 Aug 2023 11:08:52 +0200 Subject: [PATCH 20/81] remove linux pumps, where not necessary --- .../integration_test/lib/lib_test_utils.dart | 8 +++---- .../integration_test/platform_test.dart | 21 ------------------- 2 files changed, 4 insertions(+), 25 deletions(-) diff --git a/packages/audioplayers/example/integration_test/lib/lib_test_utils.dart b/packages/audioplayers/example/integration_test/lib/lib_test_utils.dart index e128dc0b3..60767cd29 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_test_utils.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_test_utils.dart @@ -5,9 +5,9 @@ import 'package:flutter_test/flutter_test.dart'; extension LibWidgetTester on WidgetTester { Future pumpLinux() async { - // if (!kIsWeb && Platform.isLinux) { - // // FIXME(gustl22): Linux needs additional pump (#1556) - // await pump(); - // } + if (!kIsWeb && Platform.isLinux) { + // FIXME(gustl22): Linux needs additional pump (#1556) + await pump(); + } } } diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 1502776bd..35deb4796 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -38,7 +38,6 @@ void main() async { final log = await logCompleter.future; expect(log, 'SomeLog'); await onLogSub.cancel(); - await tester.pumpLinux(); await player.dispose(); }); @@ -64,7 +63,6 @@ void main() async { (tester) async { final player = AudioPlayer(); try { - await tester.pumpLinux(); // Throws PlatformException via MethodChannel: await player.setSource(AssetSource(invalidAsset)); fail('PlatformException not thrown'); @@ -72,7 +70,6 @@ void main() async { } catch (e) { expect(e, isInstanceOf()); } - await tester.pumpLinux(); await player.dispose(); }, ); @@ -82,7 +79,6 @@ void main() async { (tester) async { final player = AudioPlayer(); try { - await tester.pumpLinux(); // Throws PlatformException via MethodChannel: await player.setSource(UrlSource('non_existent.txt')); fail('PlatformException not thrown'); @@ -90,7 +86,6 @@ void main() async { } catch (e) { expect(e, isInstanceOf()); } - await tester.pumpLinux(); await player.dispose(); }, ); @@ -128,7 +123,6 @@ void main() async { // Create player again, so it can be disposed in tearDown await platform.create(playerId); - await tester.pumpLinux(); }); for (final td in audioTestDataList) { @@ -150,7 +144,6 @@ void main() async { deviation: const Duration(milliseconds: 1), ), ); - await tester.pumpLinux(); }, // FIXME(gustl22): cannot determine initial duration for VBR on Linux // FIXME(gustl22): determines wrong initial position for m3u8 on Linux @@ -174,7 +167,6 @@ void main() async { await platform.stop(playerId); } // May check native volume here - await tester.pumpLinux(); }); } } @@ -194,7 +186,6 @@ void main() async { await platform.stop(playerId); } // May check native balance here - await tester.pumpLinux(); }); } } @@ -214,7 +205,6 @@ void main() async { await platform.stop(playerId); } // May check native playback rate here - await tester.pumpLinux(); }); } } @@ -249,7 +239,6 @@ void main() async { deviation: const Duration(milliseconds: 1), ), ); - await tester.pumpLinux(); }); } } @@ -270,7 +259,6 @@ void main() async { await platform.stop(playerId); // May check number of loops here - await tester.pumpLinux(); }); } } @@ -288,10 +276,6 @@ void main() async { await tester.pump(const Duration(seconds: 3)); // No need to call stop, as it should be released by now // TODO(Gustl22): test if source was released - - // May check number of loops here - await tester.pumpLinux(); - // TODO(Gustl22): test 'platform.release()' }); } @@ -305,7 +289,6 @@ void main() async { testData: wavUrl1TestData, ); } - await tester.pumpLinux(); }); }); @@ -353,7 +336,6 @@ void main() async { ), ); await onDurationSub.cancel(); - await tester.pumpLinux(); }, // TODO(gustl22): cannot determine duration for VBR on Linux skip: isLinux && td.isVBR, @@ -384,7 +366,6 @@ void main() async { expect(position, greaterThan(Duration.zero)); await platform.stop(playerId); await onPositionSub.cancel(); - await tester.pumpLinux(); }); } } @@ -395,7 +376,6 @@ void main() async { final eventSub = eventStream.listen(null); await eventSub.cancel(); } - await tester.pumpLinux(); }); testWidgets('Emit platform error', (tester) async { @@ -416,7 +396,6 @@ void main() async { expect(platformException.code, 'SomeErrorCode'); expect(platformException.message, 'SomeErrorMessage'); await eventStreamSub.cancel(); - await tester.pumpLinux(); }); testWidgets('Emit global platform error', (tester) async { From 04e60b685a9154b3b030c9acec441a683708681b Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Tue, 8 Aug 2023 11:24:03 +0200 Subject: [PATCH 21/81] add one more pump --- .../app/app_source_test_data.dart | 1 + .../lib/lib_source_test_data.dart | 16 +++++++++------- .../example/integration_test/platform_test.dart | 6 +++++- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/audioplayers/example/integration_test/app/app_source_test_data.dart b/packages/audioplayers/example/integration_test/app/app_source_test_data.dart index 753e230c7..e49937ea3 100644 --- a/packages/audioplayers/example/integration_test/app/app_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/app/app_source_test_data.dart @@ -17,6 +17,7 @@ class AppSourceTestData extends SourceTestData { return 'UiSourceTestData(' 'sourceKey: $sourceKey, ' 'duration: $duration, ' + 'isVBR: $isVBR, ' 'isLiveStream: $isLiveStream' ')'; } diff --git a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart index c68ffc79a..ddf735736 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart @@ -20,6 +20,7 @@ class LibSourceTestData extends SourceTestData { return 'LibSourceTestData(' 'source: $source, ' 'duration: $duration, ' + 'isVBR: $isVBR, ' 'isLiveStream: $isLiveStream' ')'; } @@ -38,12 +39,17 @@ final mp3Url1TestData = LibSourceTestData( isVBR: true, ); -final m3u8UrltestData = LibSourceTestData( +final m3u8UrlTestData = LibSourceTestData( source: UrlSource(m3u8StreamUrl), duration: Duration.zero, isLiveStream: true, ); +final wavAssetTestData = LibSourceTestData( + source: AssetSource(wavAsset), + duration: const Duration(seconds: 1, milliseconds: 068), +); + // Some sources are commented which are considered redundant Future> getAudioTestDataList() async { return [ @@ -60,18 +66,14 @@ Future> getAudioTestDataList() async { duration: const Duration(minutes: 1, seconds: 34, milliseconds: 119), ),*/ if (_features.hasUrlSource && _features.hasPlaylistSourceType) - m3u8UrltestData, + m3u8UrlTestData, if (_features.hasUrlSource) LibSourceTestData( source: UrlSource(mpgaStreamUrl), duration: Duration.zero, isLiveStream: true, ), - if (_features.hasAssetSource) - LibSourceTestData( - source: AssetSource(wavAsset), - duration: const Duration(seconds: 1, milliseconds: 068), - ), + if (_features.hasAssetSource) wavAssetTestData, /*if (_features.hasAssetSource) LibSourceTestData( source: AssetSource(mp3Asset), diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 35deb4796..6e6f002c8 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -148,7 +148,7 @@ void main() async { // FIXME(gustl22): cannot determine initial duration for VBR on Linux // FIXME(gustl22): determines wrong initial position for m3u8 on Linux skip: isLinux && td.isVBR || - isLinux && td.source == m3u8UrltestData.source, + isLinux && td.source == m3u8UrlTestData.source, ); } @@ -326,6 +326,10 @@ void main() async { testData: td, ); + if (td.source == wavAssetTestData.source) { + await tester.pumpLinux(); + } + expect( await durationCompleter.future .timeout(const Duration(seconds: 30)), From d6ca59a1046ae98b010e40b333ec308bccf9ca89 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Tue, 8 Aug 2023 12:29:59 +0200 Subject: [PATCH 22/81] convert logging and error tests to platform --- .../lib/lib_source_test_data.dart | 10 ++ .../integration_test/platform_test.dart | 109 +++++++++--------- 2 files changed, 64 insertions(+), 55 deletions(-) diff --git a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart index ddf735736..044079ef7 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart @@ -50,6 +50,16 @@ final wavAssetTestData = LibSourceTestData( duration: const Duration(seconds: 1, milliseconds: 068), ); +final invalidAssetTestData = LibSourceTestData( + source: AssetSource(invalidAsset), + duration: Duration.zero, +); + +final nonExistentUrlTestData = LibSourceTestData( + source: UrlSource('non_existent.txt'), + duration: Duration.zero, +); + // Some sources are commented which are considered redundant Future> getAudioTestDataList() async { return [ diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 6e6f002c8..f4c86930b 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:io'; import 'package:audioplayers/audioplayers.dart'; -import 'package:audioplayers_example/tabs/sources.dart'; import 'package:audioplayers_platform_interface/audioplayers_platform_interface.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; @@ -20,90 +19,55 @@ void main() async { final isLinux = !kIsWeb && Platform.isLinux; final audioTestDataList = await getAudioTestDataList(); - group('Logging', () { - testWidgets('Emit platform log', (tester) async { - final logCompleter = Completer(); - - const playerId = 'somePlayerId'; - final player = AudioPlayer(playerId: playerId); - final onLogSub = player.onLog.listen( - logCompleter.complete, - onError: logCompleter.completeError, - ); - - await player.creatingCompleter.future; - final platform = AudioplayersPlatformInterface.instance; - await platform.emitLog(playerId, 'SomeLog'); + group('Platform method channel', () { + late AudioplayersPlatformInterface platform; + late String playerId; - final log = await logCompleter.future; - expect(log, 'SomeLog'); - await onLogSub.cancel(); - await player.dispose(); + setUp(() async { + platform = AudioplayersPlatformInterface.instance; + playerId = 'somePlayerId'; + await platform.create(playerId); }); - testWidgets('Emit global platform log', (tester) async { - final completer = Completer(); - final eventStreamSub = AudioPlayer.global.onLog.listen( - completer.complete, - onError: completer.completeError, - ); - - final global = GlobalAudioplayersPlatformInterface.instance; - await global.emitGlobalLog('SomeGlobalLog'); - - final log = await completer.future; - expect(log, 'SomeGlobalLog'); - await eventStreamSub.cancel(); + tearDown(() async { + await platform.dispose(playerId); }); - }); - group('Errors', () { testWidgets( 'Throw PlatformException, when loading invalid file', (tester) async { - final player = AudioPlayer(); try { // Throws PlatformException via MethodChannel: - await player.setSource(AssetSource(invalidAsset)); + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: invalidAssetTestData, + ); fail('PlatformException not thrown'); // ignore: avoid_catches_without_on_clauses } catch (e) { expect(e, isInstanceOf()); } - await player.dispose(); }, ); testWidgets( 'Throw PlatformException, when loading non existent file', (tester) async { - final player = AudioPlayer(); try { // Throws PlatformException via MethodChannel: - await player.setSource(UrlSource('non_existent.txt')); + await tester.prepareSource( + playerId: playerId, + platform: platform, + testData: nonExistentUrlTestData, + ); fail('PlatformException not thrown'); // ignore: avoid_catches_without_on_clauses } catch (e) { expect(e, isInstanceOf()); } - await player.dispose(); }, ); - }); - - group('Platform method channel', () { - late AudioplayersPlatformInterface platform; - late String playerId; - - setUp(() async { - platform = AudioplayersPlatformInterface.instance; - playerId = 'somePlayerId'; - await platform.create(playerId); - }); - - tearDown(() async { - await platform.dispose(playerId); - }); testWidgets('#create and #dispose', (tester) async { await tester.pumpAndSettle(); @@ -382,6 +346,41 @@ void main() async { } }); + testWidgets('Emit platform log', (tester) async { + final logCompleter = Completer(); + final logSub = platform + .getEventStream(playerId) + .where((event) => event.eventType == AudioEventType.log) + .map((event) => event.logMessage) + .listen(logCompleter.complete, onError: logCompleter.completeError); + + await platform.emitLog(playerId, 'SomeLog'); + + final log = await logCompleter.future; + expect(log, 'SomeLog'); + await logSub.cancel(); + }); + + testWidgets('Emit global platform log', (tester) async { + final global = GlobalAudioplayersPlatformInterface.instance; + final logCompleter = Completer(); + + /* final eventStreamSub = */ + global + .getGlobalEventStream() + .where((event) => event.eventType == AudioEventType.log) + .map((event) => event.logMessage) + .listen(logCompleter.complete, onError: logCompleter.completeError); + + await global.emitGlobalLog('SomeGlobalLog'); + + final log = await logCompleter.future; + expect(log, 'SomeGlobalLog'); + // FIXME: cancelling the global event stream leads to + // MissingPluginException on Android, if dispose app afterwards + // await eventStreamSub.cancel(); + }); + testWidgets('Emit platform error', (tester) async { final errorCompleter = Completer(); final eventStreamSub = platform From 6227a4c2d9d465852819db2b108721bbe4a4a27d Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Tue, 8 Aug 2023 13:38:20 +0200 Subject: [PATCH 23/81] fix: completing preparedCompleter twice --- .../example/integration_test/platform_test.dart | 6 +++++- packages/audioplayers/lib/src/audioplayer.dart | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index f4c86930b..f97e65cab 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -443,7 +443,11 @@ extension on WidgetTester { preparedCompleter.complete(); } }, - onError: preparedCompleter.completeError, + onError: (Object e, [StackTrace? st]) { + if (!preparedCompleter.isCompleted) { + preparedCompleter.completeError(e, st); + } + }, ); await pumpLinux(); final source = testData.source; diff --git a/packages/audioplayers/lib/src/audioplayer.dart b/packages/audioplayers/lib/src/audioplayer.dart index 73ab50f0a..c676e2a1d 100644 --- a/packages/audioplayers/lib/src/audioplayer.dart +++ b/packages/audioplayers/lib/src/audioplayer.dart @@ -308,7 +308,7 @@ class AudioPlayer { } }, onError: (Object e, [StackTrace? stackTrace]) { - if (preparedCompleter.isCompleted == false) { + if (!preparedCompleter.isCompleted) { preparedCompleter.completeError(e, stackTrace); } }, From 719e596e6fcfa9c3f3bf60c4746e67369c249184 Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Tue, 8 Aug 2023 13:44:07 +0200 Subject: [PATCH 24/81] add pumpLinux again --- .../integration_test/platform_test.dart | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index f97e65cab..6903ef4c4 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -48,6 +48,7 @@ void main() async { } catch (e) { expect(e, isInstanceOf()); } + await tester.pumpLinux(); }, ); @@ -66,6 +67,7 @@ void main() async { } catch (e) { expect(e, isInstanceOf()); } + await tester.pumpLinux(); }, ); @@ -87,6 +89,7 @@ void main() async { // Create player again, so it can be disposed in tearDown await platform.create(playerId); + await tester.pumpLinux(); }); for (final td in audioTestDataList) { @@ -108,6 +111,7 @@ void main() async { deviation: const Duration(milliseconds: 1), ), ); + await tester.pumpLinux(); }, // FIXME(gustl22): cannot determine initial duration for VBR on Linux // FIXME(gustl22): determines wrong initial position for m3u8 on Linux @@ -131,6 +135,7 @@ void main() async { await platform.stop(playerId); } // May check native volume here + await tester.pumpLinux(); }); } } @@ -150,6 +155,7 @@ void main() async { await platform.stop(playerId); } // May check native balance here + await tester.pumpLinux(); }); } } @@ -169,6 +175,7 @@ void main() async { await platform.stop(playerId); } // May check native playback rate here + await tester.pumpLinux(); }); } } @@ -203,6 +210,7 @@ void main() async { deviation: const Duration(milliseconds: 1), ), ); + await tester.pumpLinux(); }); } } @@ -223,6 +231,7 @@ void main() async { await platform.stop(playerId); // May check number of loops here + await tester.pumpLinux(); }); } } @@ -241,6 +250,7 @@ void main() async { // No need to call stop, as it should be released by now // TODO(Gustl22): test if source was released // TODO(Gustl22): test 'platform.release()' + await tester.pumpLinux(); }); } } @@ -253,6 +263,7 @@ void main() async { testData: wavUrl1TestData, ); } + await tester.pumpLinux(); }); }); @@ -304,6 +315,7 @@ void main() async { ), ); await onDurationSub.cancel(); + await tester.pumpLinux(); }, // TODO(gustl22): cannot determine duration for VBR on Linux skip: isLinux && td.isVBR, @@ -334,6 +346,7 @@ void main() async { expect(position, greaterThan(Duration.zero)); await platform.stop(playerId); await onPositionSub.cancel(); + await tester.pumpLinux(); }); } } @@ -343,6 +356,7 @@ void main() async { for (var i = 0; i < 2; i++) { final eventSub = eventStream.listen(null); await eventSub.cancel(); + await tester.pumpLinux(); } }); @@ -359,6 +373,7 @@ void main() async { final log = await logCompleter.future; expect(log, 'SomeLog'); await logSub.cancel(); + await tester.pumpLinux(); }); testWidgets('Emit global platform log', (tester) async { @@ -379,6 +394,7 @@ void main() async { // FIXME: cancelling the global event stream leads to // MissingPluginException on Android, if dispose app afterwards // await eventStreamSub.cancel(); + await tester.pumpLinux(); }); testWidgets('Emit platform error', (tester) async { @@ -399,6 +415,7 @@ void main() async { expect(platformException.code, 'SomeErrorCode'); expect(platformException.message, 'SomeErrorMessage'); await eventStreamSub.cancel(); + await tester.pumpLinux(); }); testWidgets('Emit global platform error', (tester) async { @@ -422,6 +439,7 @@ void main() async { // FIXME: cancelling the global event stream leads to // MissingPluginException on Android, if dispose app afterwards // await eventStreamSub.cancel(); + await tester.pumpLinux(); }); }); } From 77892eab22c8123cab592da4f0307c48e344a96d Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Wed, 9 Aug 2023 11:25:05 +0200 Subject: [PATCH 25/81] test: add cooldown for mpga on web --- .../integration_test/lib/lib_source_test_data.dart | 13 +++++++------ .../example/integration_test/platform_test.dart | 10 ++++++++++ 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart index 044079ef7..c64c8f147 100644 --- a/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart +++ b/packages/audioplayers/example/integration_test/lib/lib_source_test_data.dart @@ -45,6 +45,12 @@ final m3u8UrlTestData = LibSourceTestData( isLiveStream: true, ); +final mpgaUrlTestData = LibSourceTestData( + source: UrlSource(mpgaStreamUrl), + duration: Duration.zero, + isLiveStream: true, +); + final wavAssetTestData = LibSourceTestData( source: AssetSource(wavAsset), duration: const Duration(seconds: 1, milliseconds: 068), @@ -77,12 +83,7 @@ Future> getAudioTestDataList() async { ),*/ if (_features.hasUrlSource && _features.hasPlaylistSourceType) m3u8UrlTestData, - if (_features.hasUrlSource) - LibSourceTestData( - source: UrlSource(mpgaStreamUrl), - duration: Duration.zero, - isLiveStream: true, - ), + if (_features.hasUrlSource) mpgaUrlTestData, if (_features.hasAssetSource) wavAssetTestData, /*if (_features.hasAssetSource) LibSourceTestData( diff --git a/packages/audioplayers/example/integration_test/platform_test.dart b/packages/audioplayers/example/integration_test/platform_test.dart index 6903ef4c4..49b0e34a1 100644 --- a/packages/audioplayers/example/integration_test/platform_test.dart +++ b/packages/audioplayers/example/integration_test/platform_test.dart @@ -130,6 +130,7 @@ void main() async { ); for (final volume in [0.0, 0.5, 1.0]) { await platform.setVolume(playerId, volume); + await tester._mpgaWebCooldown(td); await platform.resume(playerId); await tester.pump(const Duration(seconds: 1)); await platform.stop(playerId); @@ -150,6 +151,7 @@ void main() async { ); for (final balance in [-1.0, 0.0, 1.0]) { await platform.setBalance(playerId, balance); + await tester._mpgaWebCooldown(td); await platform.resume(playerId); await tester.pump(const Duration(seconds: 1)); await platform.stop(playerId); @@ -170,6 +172,7 @@ void main() async { ); for (final playbackRate in [0.5, 1.0, 2.0]) { await platform.setPlaybackRate(playerId, playbackRate); + await tester._mpgaWebCooldown(td); await platform.resume(playerId); await tester.pump(const Duration(seconds: 1)); await platform.stop(playerId); @@ -445,6 +448,13 @@ void main() async { } extension on WidgetTester { + Future _mpgaWebCooldown(LibSourceTestData td) async { + if (kIsWeb && td.source == mpgaUrlTestData.source) { + // FIXME: web needs cooldown, to be able to resume mpga source + await pump(const Duration(seconds: 1)); + } + } + Future prepareSource({ required String playerId, required AudioplayersPlatformInterface platform, From 13c0577acc2911862730e3161fb1775706c9c0ac Mon Sep 17 00:00:00 2001 From: Gustl22 Date: Thu, 10 Aug 2023 22:50:01 +0200 Subject: [PATCH 26/81] [WIP] server: stream wav --- .../example/lib/tabs/sources.dart | 4 +- .../example/server/bin/server.dart | 10 ++-- .../example/server/bin/stream_route.dart | 60 +++++++++++++++++++ .../example/server/public/index.html | 1 + .../audioplayers/example/server/pubspec.yaml | 1 + 5 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 packages/audioplayers/example/server/bin/stream_route.dart diff --git a/packages/audioplayers/example/lib/tabs/sources.dart b/packages/audioplayers/example/lib/tabs/sources.dart index cf0b4e051..e5b097dba 100644 --- a/packages/audioplayers/example/lib/tabs/sources.dart +++ b/packages/audioplayers/example/lib/tabs/sources.dart @@ -20,7 +20,9 @@ final mp3Url2 = '$host/files/audio/nasa_on_a_mission.mp3'; final m3u8StreamUrl = useLocalServer ? '$host/files/live_streams/nasa_power_of_the_rovers.m3u8' : 'https://a.files.bbci.co.uk/media/live/manifesto/audio/simulcast/hls/nonuk/sbr_low/ak/bbc_radio_one.m3u8'; -const mpgaStreamUrl = 'https://timesradio.wireless.radio/stream'; +final mpgaStreamUrl = useLocalServer + ? '$host/stream/wav' + : 'https://timesradio.wireless.radio/stream'; const wavAsset = 'laser.wav'; const mp3Asset = 'nasa_on_a_mission.mp3'; diff --git a/packages/audioplayers/example/server/bin/server.dart b/packages/audioplayers/example/server/bin/server.dart index 80a69c89b..d544a84b1 100644 --- a/packages/audioplayers/example/server/bin/server.dart +++ b/packages/audioplayers/example/server/bin/server.dart @@ -3,8 +3,11 @@ import 'dart:io'; import 'package:shelf/shelf.dart'; import 'package:shelf/shelf_io.dart' as shelf_io; +import 'package:shelf_router/shelf_router.dart' as shelf_router; import 'package:shelf_static/shelf_static.dart' as shelf_static; +import 'stream_route.dart'; + Future main() async { final port = int.parse(Platform.environment['PORT'] ?? '8080'); final requestTimeoutMillis = @@ -12,7 +15,7 @@ Future main() async { final isLogRequests = (Platform.environment['LOG_REQUESTS'] ?? 'false') == 'true'; - final cascade = Cascade().add(_staticHandler); + final cascade = Cascade().add(_staticHandler).add(_router); var pipeline = const Pipeline(); if (isLogRequests) { @@ -36,9 +39,6 @@ Future main() async { port, ); - // TODO(Gustl22): provide an audio streaming endpoint: - // Inspiration: https://github.com/daspinola/video-stream-sample/blob/master/server.js - print( 'Serving at http://${server.address.host}:${server.port} with latency of $requestTimeoutMillis ms', ); @@ -49,3 +49,5 @@ final _staticHandler = shelf_static.createStaticHandler( defaultDocument: 'index.html', serveFilesOutsidePath: true, ); + +final _router = shelf_router.Router()..mount('/stream', StreamRoute().pipeline); diff --git a/packages/audioplayers/example/server/bin/stream_route.dart b/packages/audioplayers/example/server/bin/stream_route.dart new file mode 100644 index 000000000..1acc7451e --- /dev/null +++ b/packages/audioplayers/example/server/bin/stream_route.dart @@ -0,0 +1,60 @@ +import 'dart:io'; + +import 'package:async/async.dart'; +import 'package:shelf/shelf.dart'; +import 'package:shelf_router/shelf_router.dart'; + +class StreamRoute { + Router get router { + final router = Router(); + router.get('/wav', (Request request) async { + final range = request.headers['range']; + const contentType = {'Content-Type': 'audio/wav'}; + if (range != null) { + final file = File('public/files/audio/laser.wav').openRead(); + final fileSize = await file.length; + + final parts = range.replaceFirst('bytes=', '').split('-'); + final start = int.parse(parts[0]); + final end = int.tryParse(parts[1]) ?? fileSize - 1; + + if (start >= fileSize) { + return Response( + 416, + body: 'Requested range not satisfiable\n$start >= $fileSize', + ); + } + + final streamReader = ChunkedStreamReader(file); + final head = { + 'Content-Range': 'bytes $start-$end/$fileSize', + 'Accept-Ranges': 'bytes', + 'Content-Length': '${(end - start) + 1}', + ...contentType, + }; + final res = Response.ok( + await streamReader.readChunk(2), + headers: head, + ); + return res; + } else { + final bytes = await File('public/files/audio/laser.wav').readAsBytes(); + final fileSize = bytes.length; + final head = { + 'Content-Length': '$fileSize', + ...contentType, + }; + final res = Response.ok( + bytes.toList(), + headers: head, + ); + return res; + } + }); + return router; + } + + Handler get pipeline { + return const Pipeline().addHandler(router); + } +} diff --git a/packages/audioplayers/example/server/public/index.html b/packages/audioplayers/example/server/public/index.html index 93e08ddc1..728d1f66b 100644 --- a/packages/audioplayers/example/server/public/index.html +++ b/packages/audioplayers/example/server/public/index.html @@ -75,6 +75,7 @@

Audioplayers Test Server

  • /files/live_streams/nasa_power_of_the_rovers.m3u8
  • +
  • /stream/wav