diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 20e62b31d..6628e540e 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -24,7 +24,7 @@ jobs: run: flutter format --dry-run --set-exit-if-changed . - name: Analyze project source - run: flutter analyze --no-fatal-infos --no-fatal-warnings + run: ./scripts/analyze.sh - name: Run tests run: flutter test diff --git a/analysis_options.yaml b/analysis_options.yaml index 357014f28..25dfc68d7 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,11 +1,144 @@ # Source of linter options: # http://dart-lang.github.io/linter/lints/options/options.html +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + + plugins: + - dart_code_metrics + linter: rules: + - always_declare_return_types + - always_put_control_body_on_new_line + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_double_and_int_checks + - avoid_dynamic_calls + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_null_checks_in_equality_operators + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_return_types_on_setters + - avoid_shadowing_type_parameters + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_as_parameter_names + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions - camel_case_types + - cancel_subscriptions + - cast_nullable_to_non_nullable + - close_sinks + - comment_references + # TODO(luan) re-enable this once we migrate + # - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - directives_ordering + - do_not_use_environment + - empty_catches + - empty_constructor_bodies + - empty_statements + - exhaustive_cases + - file_names - hash_and_equals + - implementation_imports + - invariant_booleans - iterable_contains_unrelated_type + - join_return_with_assignment + - library_names + - library_prefixes - list_remove_unrelated_type + - literal_only_boolean_expressions + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_runtimeType_toString + - omit_local_variable_types + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_contains + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_mixin + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_relative_imports + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + - recursive_getters + - slash_for_doc_comments + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_full_hex_values_for_flutter_colors + - use_function_type_syntax_for_parameters + - use_is_even_rather_than_modulo + - use_rethrow_when_possible - unrelated_type_equality_checks - - valid_regexps + - unsafe_html + - void_checks + +dart_code_metrics: + rules: + - prefer-trailing-comma + - prefer-trailing-comma-for-collection + - no-equal-then-else + - no-object-declaration + - potential-null-dereference + metrics-exclude: + - '**' diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 000000000..2f79c8988 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,143 @@ +# Source of linter options: +# http://dart-lang.github.io/linter/lints/options/options.html + +analyzer: + strong-mode: + implicit-casts: false + implicit-dynamic: false + + plugins: + - dart_code_metrics + +linter: + rules: + - always_declare_return_types + - always_put_control_body_on_new_line + - always_require_non_null_named_parameters + - annotate_overrides + - avoid_double_and_int_checks + - avoid_dynamic_calls + - avoid_empty_else + - avoid_equals_and_hash_code_on_mutable_classes + - avoid_escaping_inner_quotes + - avoid_field_initializers_in_const_classes + - avoid_init_to_null + - avoid_js_rounded_ints + - avoid_null_checks_in_equality_operators + - avoid_private_typedef_functions + - avoid_redundant_argument_values + - avoid_relative_lib_imports + - avoid_return_types_on_setters + - avoid_shadowing_type_parameters + - avoid_slow_async_io + - avoid_type_to_string + - avoid_types_as_parameter_names + - avoid_unused_constructor_parameters + - await_only_futures + - camel_case_extensions + - camel_case_types + - cancel_subscriptions + - cast_nullable_to_non_nullable + - close_sinks + - comment_references + - constant_identifier_names + - control_flow_in_finally + - curly_braces_in_flow_control_structures + - directives_ordering + - do_not_use_environment + - empty_catches + - empty_constructor_bodies + - empty_statements + - exhaustive_cases + - file_names + - hash_and_equals + - implementation_imports + - invariant_booleans + - iterable_contains_unrelated_type + - join_return_with_assignment + - library_names + - library_prefixes + - list_remove_unrelated_type + - literal_only_boolean_expressions + - missing_whitespace_between_adjacent_strings + - no_adjacent_strings_in_list + - no_duplicate_case_values + - no_runtimeType_toString + - omit_local_variable_types + - package_api_docs + - package_names + - package_prefixed_library_names + - parameter_assignments + - prefer_adjacent_string_concatenation + - prefer_asserts_in_initializer_lists + - prefer_collection_literals + - prefer_conditional_assignment + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_contains + - prefer_equal_for_default_values + - prefer_final_fields + - prefer_final_in_for_each + - prefer_final_locals + - prefer_for_elements_to_map_fromIterable + - prefer_foreach + - prefer_function_declarations_over_variables + - prefer_generic_function_type_aliases + - prefer_if_elements_to_conditional_expressions + - prefer_if_null_operators + - prefer_initializing_formals + - prefer_inlined_adds + - prefer_interpolation_to_compose_strings + - prefer_is_empty + - prefer_is_not_empty + - prefer_is_not_operator + - prefer_iterable_whereType + - prefer_mixin + - prefer_null_aware_operators + - prefer_single_quotes + - prefer_spread_collections + - prefer_relative_imports + - prefer_typing_uninitialized_variables + - prefer_void_to_null + - provide_deprecation_message + - recursive_getters + - slash_for_doc_comments + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - type_init_formals + - unnecessary_await_in_return + - unnecessary_brace_in_string_interps + - unnecessary_const + - unnecessary_getters_setters + - unnecessary_lambdas + - unnecessary_new + - unnecessary_null_aware_assignments + - unnecessary_null_in_if_null_operators + - unnecessary_overrides + - unnecessary_parenthesis + - unnecessary_raw_strings + - unnecessary_statements + - unnecessary_string_escapes + - unnecessary_string_interpolations + - unnecessary_this + - use_full_hex_values_for_flutter_colors + - use_function_type_syntax_for_parameters + - use_is_even_rather_than_modulo + - use_rethrow_when_possible + - unrelated_type_equality_checks + - unsafe_html + - void_checks + +dart_code_metrics: + rules: + - prefer-trailing-comma + - prefer-trailing-comma-for-collection + - no-equal-then-else + - no-object-declaration + - potential-null-dereference + metrics-exclude: + - '**/**' diff --git a/example/lib/main.dart b/example/lib/main.dart index 19c3df6ec..073a7fe76 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,15 +5,15 @@ import 'package:audioplayers/audio_cache.dart'; import 'package:audioplayers/audioplayers.dart'; import 'package:audioplayers/player_mode.dart'; import 'package:audioplayers/release_mode.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/src/foundation/constants.dart'; import 'package:http/http.dart'; import 'package:path_provider/path_provider.dart'; import 'package:provider/provider.dart'; import 'player_widget.dart'; -typedef void OnError(Exception exception); +typedef OnError = void Function(Exception exception); const kUrl1 = 'https://luan.xyz/files/audio/ambient_c_motion.mp3'; const kUrl2 = 'https://luan.xyz/files/audio/nasa_on_a_mission.mp3'; @@ -54,13 +54,13 @@ class _ExampleAppState extends State { final file = File('${dir.path}/audio.mp3'); await file.writeAsBytes(bytes); - if (await file.exists()) { + if (file.existsSync()) { setState(() => localFilePath = file.path); } } Widget remoteUrl() { - return SingleChildScrollView( + return const SingleChildScrollView( child: _Tab( children: [ Text( @@ -91,21 +91,19 @@ class _ExampleAppState extends State { Widget localFile() { return _Tab(children: [ - Text(' -- manually load bytes (no web!) --'), - Text('File: $kUrl1'), - _Btn(txt: 'Download File to your Device', onPressed: () => _loadFile()), + const Text(' -- manually load bytes (no web!) --'), + const Text('File: $kUrl1'), + _Btn(txt: 'Download File to your Device', onPressed: _loadFile), Text('Current local file path: $localFilePath'), - localFilePath == null ? Container() : PlayerWidget(url: localFilePath!), + if (localFilePath != null) PlayerWidget(url: localFilePath!), Container( - constraints: BoxConstraints.expand(width: 1.0, height: 20.0), + constraints: const BoxConstraints.expand(width: 1.0, height: 20.0), ), - Text(' -- via AudioCache --'), - Text('File: $kUrl2'), - _Btn(txt: 'Download File to your Device', onPressed: () => _loadFileAC()), + const Text(' -- via AudioCache --'), + const Text('File: $kUrl2'), + _Btn(txt: 'Download File to your Device', onPressed: _loadFileAC), Text('Current AC loaded: $localAudioCacheURI'), - localAudioCacheURI == null - ? Container() - : PlayerWidget(url: localAudioCacheURI!), + if (localAudioCacheURI != null) PlayerWidget(url: localAudioCacheURI!), ]); } @@ -118,9 +116,9 @@ class _ExampleAppState extends State { return SingleChildScrollView( child: _Tab( children: [ - Text('Play Local Asset \'audio.mp3\':'), + const Text("Play Local Asset 'audio.mp3':"), _Btn(txt: 'Play', onPressed: () => audioCache.play('audio.mp3')), - Text('Play Local Asset (via byte source) \'audio.mp3\':'), + const Text("Play Local Asset (via byte source) 'audio.mp3':"), _Btn( txt: 'Play', onPressed: () async { @@ -129,9 +127,9 @@ class _ExampleAppState extends State { audioCache.playBytes(bytes); }, ), - Text('Loop Local Asset \'audio.mp3\':'), + const Text("Loop Local Asset 'audio.mp3':"), _Btn(txt: 'Loop', onPressed: () => audioCache.loop('audio.mp3')), - Text('Loop Local Asset (via byte source) \'audio.mp3\':'), + const Text("Loop Local Asset (via byte source) 'audio.mp3':"), _Btn( txt: 'Loop', onPressed: () async { @@ -140,28 +138,37 @@ class _ExampleAppState extends State { audioCache.playBytes(bytes, loop: true); }, ), - Text('Play Local Asset \'audio2.mp3\':'), + const Text("Play Local Asset 'audio2.mp3':"), _Btn(txt: 'Play', onPressed: () => audioCache.play('audio2.mp3')), - Text('Play Local Asset In Low Latency \'audio.mp3\':'), + const Text("Play Local Asset In Low Latency 'audio.mp3':"), _Btn( txt: 'Play', - onPressed: () => - audioCache.play('audio.mp3', mode: PlayerMode.LOW_LATENCY), + onPressed: () { + audioCache.play('audio.mp3', mode: PlayerMode.LOW_LATENCY); + }, + ), + const Text( + "Play Local Asset Concurrently In Low Latency 'audio.mp3':", ), - Text('Play Local Asset Concurrently In Low Latency \'audio.mp3\':'), _Btn( - txt: 'Play', - onPressed: () async { - await audioCache.play('audio.mp3', - mode: PlayerMode.LOW_LATENCY); - await audioCache.play('audio2.mp3', - mode: PlayerMode.LOW_LATENCY); - }), - Text('Play Local Asset In Low Latency \'audio2.mp3\':'), + txt: 'Play', + onPressed: () async { + await audioCache.play( + 'audio.mp3', + mode: PlayerMode.LOW_LATENCY, + ); + await audioCache.play( + 'audio2.mp3', + mode: PlayerMode.LOW_LATENCY, + ); + }, + ), + const Text("Play Local Asset In Low Latency 'audio2.mp3':"), _Btn( txt: 'Play', - onPressed: () => - audioCache.play('audio2.mp3', mode: PlayerMode.LOW_LATENCY), + onPressed: () { + audioCache.play('audio2.mp3', mode: PlayerMode.LOW_LATENCY); + }, ), getLocalFileDuration(), ], @@ -172,25 +179,26 @@ class _ExampleAppState extends State { Future _getDuration() async { final uri = await audioCache.load('audio2.mp3'); await advancedPlayer.setUrl(uri.toString()); - int duration = await Future.delayed( - Duration(seconds: 2), + return Future.delayed( + const Duration(seconds: 2), () => advancedPlayer.getDuration(), ); - return duration; } - getLocalFileDuration() { + FutureBuilder getLocalFileDuration() { return FutureBuilder( future: _getDuration(), builder: (BuildContext context, AsyncSnapshot snapshot) { switch (snapshot.connectionState) { case ConnectionState.none: - return Text('No Connection...'); + return const Text('No Connection...'); case ConnectionState.active: case ConnectionState.waiting: - return Text('Awaiting result...'); + return const Text('Awaiting result...'); case ConnectionState.done: - if (snapshot.hasError) return Text('Error: ${snapshot.error}'); + if (snapshot.hasError) { + return Text('Error: ${snapshot.error}'); + } return Text( 'audio2.mp3 duration is: ${Duration(milliseconds: snapshot.data!)}', ); @@ -202,13 +210,16 @@ class _ExampleAppState extends State { } Widget notification() { - return _Tab(children: [ - Text('Play notification sound: \'messenger.mp3\':'), - _Btn( - txt: 'Play', - onPressed: () => audioCache.play('messenger.mp3', isNotification: true), - ), - ]); + return _Tab( + children: [ + const Text("Play notification sound: 'messenger.mp3':"), + _Btn( + txt: 'Play', + onPressed: () => + audioCache.play('messenger.mp3', isNotification: true), + ), + ], + ); } @override @@ -216,14 +227,15 @@ class _ExampleAppState extends State { return MultiProvider( providers: [ StreamProvider.value( - initialData: Duration(), - value: advancedPlayer.onAudioPositionChanged), + initialData: const Duration(), + value: advancedPlayer.onAudioPositionChanged, + ), ], child: DefaultTabController( length: 5, child: Scaffold( appBar: AppBar( - bottom: TabBar( + bottom: const TabBar( tabs: [ Tab(text: 'Remote Url'), Tab(text: 'Local File'), @@ -232,7 +244,7 @@ class _ExampleAppState extends State { Tab(text: 'Advanced'), ], ), - title: Text('audioplayers Example'), + title: const Text('audioplayers Example'), ), body: TabBarView( children: [ @@ -276,48 +288,54 @@ class _AdvancedState extends State { children: [ Column( children: [ - Text('Source Url'), - Row(children: [ - _Btn( - txt: 'Audio 1', - onPressed: () => widget.advancedPlayer.setUrl(kUrl1), - ), - _Btn( - txt: 'Audio 2', - onPressed: () => widget.advancedPlayer.setUrl(kUrl2), - ), - _Btn( - txt: 'Stream', - onPressed: () => widget.advancedPlayer.setUrl(kUrl3), - ), - ], mainAxisAlignment: MainAxisAlignment.spaceEvenly), + const Text('Source Url'), + Row( + children: [ + _Btn( + txt: 'Audio 1', + onPressed: () => widget.advancedPlayer.setUrl(kUrl1), + ), + _Btn( + txt: 'Audio 2', + onPressed: () => widget.advancedPlayer.setUrl(kUrl2), + ), + _Btn( + txt: 'Stream', + onPressed: () => widget.advancedPlayer.setUrl(kUrl3), + ), + ], + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + ), ], ), Column( children: [ - Text('Release Mode'), - Row(children: [ - _Btn( - txt: 'STOP', - onPressed: () => - widget.advancedPlayer.setReleaseMode(ReleaseMode.STOP), - ), - _Btn( - txt: 'LOOP', - onPressed: () => - widget.advancedPlayer.setReleaseMode(ReleaseMode.LOOP), - ), - _Btn( - txt: 'RELEASE', - onPressed: () => - widget.advancedPlayer.setReleaseMode(ReleaseMode.RELEASE), - ), - ], mainAxisAlignment: MainAxisAlignment.spaceEvenly), + const Text('Release Mode'), + Row( + children: [ + _Btn( + txt: 'STOP', + onPressed: () => + widget.advancedPlayer.setReleaseMode(ReleaseMode.STOP), + ), + _Btn( + txt: 'LOOP', + onPressed: () => + widget.advancedPlayer.setReleaseMode(ReleaseMode.LOOP), + ), + _Btn( + txt: 'RELEASE', + onPressed: () => widget.advancedPlayer + .setReleaseMode(ReleaseMode.RELEASE), + ), + ], + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + ), ], ), Column( children: [ - Text('Volume'), + const Text('Volume'), Row( children: [0.0, 0.3, 0.5, 1.0, 1.1, 2.0].map((e) { return _Btn( @@ -331,7 +349,7 @@ class _AdvancedState extends State { ), Column( children: [ - Text('Control'), + const Text('Control'), Row( children: [ _Btn( @@ -357,47 +375,51 @@ class _AdvancedState extends State { ), Column( children: [ - Text('Seek in milliseconds'), + const Text('Seek in milliseconds'), Row( children: [ _Btn( - txt: '100ms', - onPressed: () { - widget.advancedPlayer.seek( - Duration( - milliseconds: audioPosition.inMilliseconds + 100, - ), - ); - setState(() => seekDone = false); - }), + txt: '100ms', + onPressed: () { + widget.advancedPlayer.seek( + Duration( + milliseconds: audioPosition.inMilliseconds + 100, + ), + ); + setState(() => seekDone = false); + }, + ), _Btn( - txt: '500ms', - onPressed: () { - widget.advancedPlayer.seek( - Duration( - milliseconds: audioPosition.inMilliseconds + 500, - ), - ); - setState(() => seekDone = false); - }), + txt: '500ms', + onPressed: () { + widget.advancedPlayer.seek( + Duration( + milliseconds: audioPosition.inMilliseconds + 500, + ), + ); + setState(() => seekDone = false); + }, + ), _Btn( - txt: '1s', - onPressed: () { - widget.advancedPlayer.seek( - Duration(seconds: audioPosition.inSeconds + 1), - ); - setState(() => seekDone = false); - }), + txt: '1s', + onPressed: () { + widget.advancedPlayer.seek( + Duration(seconds: audioPosition.inSeconds + 1), + ); + setState(() => seekDone = false); + }, + ), _Btn( - txt: '1.5s', - onPressed: () { - widget.advancedPlayer.seek( - Duration( - milliseconds: audioPosition.inMilliseconds + 1500, - ), - ); - setState(() => seekDone = false); - }), + txt: '1.5s', + onPressed: () { + widget.advancedPlayer.seek( + Duration( + milliseconds: audioPosition.inMilliseconds + 1500, + ), + ); + setState(() => seekDone = false); + }, + ), ], mainAxisAlignment: MainAxisAlignment.spaceEvenly, ), @@ -405,7 +427,7 @@ class _AdvancedState extends State { ), Column( children: [ - Text('Rate'), + const Text('Rate'), Row( children: [0.5, 1.0, 1.5, 2.0].map((e) { return _Btn( @@ -419,7 +441,7 @@ class _AdvancedState extends State { ), ], ), - Text('Audio Position: ${audioPosition}'), + Text('Audio Position: $audioPosition'), if (seekDone != null) Text(seekDone! ? 'Seek Done' : 'Seeking...'), ], ), @@ -437,11 +459,16 @@ class _Tab extends StatelessWidget { return Center( child: Container( alignment: Alignment.topCenter, - padding: EdgeInsets.all(16.0), + padding: const EdgeInsets.all(16.0), child: SingleChildScrollView( child: Column( children: children - .map((w) => Container(child: w, padding: EdgeInsets.all(6.0))) + .map( + (w) => Container( + child: w, + padding: const EdgeInsets.all(6.0), + ), + ) .toList(), ), ), diff --git a/example/lib/player_widget.dart b/example/lib/player_widget.dart index 70f2153c0..010f109cf 100644 --- a/example/lib/player_widget.dart +++ b/example/lib/player_widget.dart @@ -12,7 +12,7 @@ class PlayerWidget extends StatefulWidget { final String url; final PlayerMode mode; - PlayerWidget({ + const PlayerWidget({ Key? key, required this.url, this.mode = PlayerMode.MEDIA_PLAYER, @@ -42,12 +42,13 @@ class _PlayerWidgetState extends State { StreamSubscription? _playerStateSubscription; StreamSubscription? _playerControlCommandSubscription; - get _isPlaying => _playerState == PlayerState.PLAYING; - get _isPaused => _playerState == PlayerState.PAUSED; - get _durationText => _duration?.toString().split('.').first ?? ''; - get _positionText => _position?.toString().split('.').first ?? ''; + bool get _isPlaying => _playerState == PlayerState.PLAYING; + bool get _isPaused => _playerState == PlayerState.PAUSED; + String get _durationText => _duration?.toString().split('.').first ?? ''; + String get _positionText => _position?.toString().split('.').first ?? ''; - get _isPlayingThroughEarpiece => _playingRouteState == PlayingRoute.EARPIECE; + bool get _isPlayingThroughEarpiece => + _playingRouteState == PlayingRoute.EARPIECE; _PlayerWidgetState(this.url, this.mode); @@ -78,32 +79,32 @@ class _PlayerWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ IconButton( - key: Key('play_button'), - onPressed: _isPlaying ? null : () => _play(), + key: const Key('play_button'), + onPressed: _isPlaying ? null : _play, iconSize: 64.0, - icon: Icon(Icons.play_arrow), + icon: const Icon(Icons.play_arrow), color: Colors.cyan, ), IconButton( - key: Key('pause_button'), - onPressed: _isPlaying ? () => _pause() : null, + key: const Key('pause_button'), + onPressed: _isPlaying ? _pause : null, iconSize: 64.0, - icon: Icon(Icons.pause), + icon: const Icon(Icons.pause), color: Colors.cyan, ), IconButton( - key: Key('stop_button'), - onPressed: _isPlaying || _isPaused ? () => _stop() : null, + key: const Key('stop_button'), + onPressed: _isPlaying || _isPaused ? _stop : null, iconSize: 64.0, - icon: Icon(Icons.stop), + icon: const Icon(Icons.stop), color: Colors.cyan, ), IconButton( onPressed: _earpieceOrSpeakersToggle, iconSize: 64.0, icon: _isPlayingThroughEarpiece - ? Icon(Icons.volume_up) - : Icon(Icons.hearing), + ? const Icon(Icons.volume_up) + : const Icon(Icons.hearing), color: Colors.cyan, ), ], @@ -112,7 +113,7 @@ class _PlayerWidgetState extends State { mainAxisSize: MainAxisSize.min, children: [ Padding( - padding: EdgeInsets.all(12.0), + padding: const EdgeInsets.all(12.0), child: Stack( children: [ Slider( @@ -134,15 +135,15 @@ class _PlayerWidgetState extends State { ), Text( _position != null - ? '${_positionText ?? ''} / ${_durationText ?? ''}' + ? '$_positionText / $_durationText' : _duration != null ? _durationText : '', - style: TextStyle(fontSize: 24.0), + style: const TextStyle(fontSize: 24.0), ), ], ), - Text('State: $_audioPlayerState') + Text('State: $_audioPlayerState'), ], ); } @@ -166,7 +167,6 @@ class _PlayerWidgetState extends State { forwardSkipInterval: const Duration(seconds: 30), // default is 30s backwardSkipInterval: const Duration(seconds: 30), // default is 30s duration: duration, - elapsedTime: Duration(seconds: 0), enableNextTrackButton: true, enablePreviousTrackButton: true, ); @@ -190,8 +190,8 @@ class _PlayerWidgetState extends State { print('audioPlayer error : $msg'); setState(() { _playerState = PlayerState.STOPPED; - _duration = Duration(seconds: 0); - _position = Duration(seconds: 0); + _duration = const Duration(); + _position = const Duration(); }); }); @@ -201,15 +201,17 @@ class _PlayerWidgetState extends State { }); _audioPlayer.onPlayerStateChanged.listen((state) { - if (!mounted) return; - setState(() { - _audioPlayerState = state; - }); + if (mounted) { + setState(() { + _audioPlayerState = state; + }); + } }); _audioPlayer.onNotificationPlayerStateChanged.listen((state) { - if (!mounted) return; - setState(() => _audioPlayerState = state); + if (mounted) { + setState(() => _audioPlayerState = state); + } }); _playingRouteState = PlayingRoute.SPEAKERS; @@ -223,26 +225,31 @@ class _PlayerWidgetState extends State { ? _position : null; final result = await _audioPlayer.play(url, position: playPosition); - if (result == 1) setState(() => _playerState = PlayerState.PLAYING); + if (result == 1) { + setState(() => _playerState = PlayerState.PLAYING); + } // default playback rate is 1.0 // this should be called after _audioPlayer.play() or _audioPlayer.resume() // this can also be called everytime the user wants to change playback rate in the UI - _audioPlayer.setPlaybackRate(playbackRate: 1.0); + _audioPlayer.setPlaybackRate(); return result; } Future _pause() async { final result = await _audioPlayer.pause(); - if (result == 1) setState(() => _playerState = PlayerState.PAUSED); + if (result == 1) { + setState(() => _playerState = PlayerState.PAUSED); + } return result; } Future _earpieceOrSpeakersToggle() async { final result = await _audioPlayer.earpieceOrSpeakersToggle(); - if (result == 1) + if (result == 1) { setState(() => _playingRouteState = _playingRouteState.toggle()); + } return result; } @@ -251,7 +258,7 @@ class _PlayerWidgetState extends State { if (result == 1) { setState(() { _playerState = PlayerState.STOPPED; - _position = Duration(); + _position = const Duration(); }); } return result; diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 26c467896..7408fb216 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: dev_dependencies: # flutter_driver: # sdk: flutter - test: any + dart_code_metrics: ^3.0.0-nullsafety.0 flutter: uses-material-design: true diff --git a/lib/audio_cache.dart b/lib/audio_cache.dart index 3d485142a..c4ae91bce 100644 --- a/lib/audio_cache.dart +++ b/lib/audio_cache.dart @@ -137,7 +137,7 @@ class AudioCache { } AudioPlayer _player(PlayerMode mode) { - return fixedPlayer ?? new AudioPlayer(mode: mode); + return fixedPlayer ?? AudioPlayer(mode: mode); } /// Plays the given [fileName]. @@ -157,7 +157,7 @@ class AudioCache { bool? duckAudio, }) async { final uri = await load(fileName); - AudioPlayer player = _player(mode); + final player = _player(mode); player.setReleaseMode(ReleaseMode.STOP); await player.play( uri.toString(), @@ -170,7 +170,7 @@ class AudioCache { return player; } - /// Plays the given [fileName] by a byte source. + /// Plays the given [fileBytes] by a byte source. /// /// This is no different than calling this API via AudioPlayer, except it will return (if applicable) the cached AudioPlayer. Future playBytes( @@ -182,7 +182,7 @@ class AudioCache { bool stayAwake = false, bool recordingActive = false, }) async { - AudioPlayer player = _player(mode); + final player = _player(mode); if (loop) { player.setReleaseMode(ReleaseMode.LOOP); diff --git a/lib/audioplayers.dart b/lib/audioplayers.dart index c0b108340..101b47b06 100644 --- a/lib/audioplayers.dart +++ b/lib/audioplayers.dart @@ -23,7 +23,7 @@ class AudioPlayer { const MethodChannel('xyz.luan/audioplayers') ..setMethodCallHandler(platformCallHandler); - static final _uuid = Uuid(); + static const _uuid = Uuid(); final StreamController _playerStateController = StreamController.broadcast(); @@ -55,7 +55,7 @@ class AudioPlayer { /// /// This is used to exchange messages with the [MethodChannel] /// (because there is only one channel for all players). - static final players = Map(); + static final players = {}; /// Enables more verbose logging. /// @@ -139,22 +139,22 @@ class AudioPlayer { /// Creates a new instance and assigns an unique id to it. AudioPlayer({this.mode = PlayerMode.MEDIA_PLAYER, String? playerId}) - : this.playerId = playerId ?? _uuid.v4() { + : playerId = playerId ?? _uuid.v4() { players[this.playerId] = this; notificationService = NotificationService(_invokeMethod); } Future _invokeMethod( String method, [ - Map arguments = const {}, - ]) { - final Map withPlayerId = Map.of(arguments) - ..['playerId'] = playerId - ..['mode'] = mode.toString(); - - return _channel - .invokeMethod(method, withPlayerId) - .then((result) => (result as int)); + Map arguments = const {}, + ]) async { + final enhancedArgs = { + ...arguments, + 'playerId': playerId, + 'mode': mode.toString(), + }; + final result = await _channel.invokeMethod(method, enhancedArgs); + return result ?? 0; // if null, we assume error } /// Plays an audio. @@ -176,16 +176,19 @@ class AudioPlayer { }) async { isLocal ??= isLocalUrl(url); - final int result = await _invokeMethod('play', { - 'url': url, - 'isLocal': isLocal, - 'volume': volume, - 'position': position?.inMilliseconds, - 'respectSilence': respectSilence, - 'stayAwake': stayAwake, - 'duckAudio': duckAudio, - 'recordingActive': recordingActive, - }); + final result = await _invokeMethod( + 'play', + { + 'url': url, + 'isLocal': isLocal, + 'volume': volume, + 'position': position?.inMilliseconds, + 'respectSilence': respectSilence, + 'stayAwake': stayAwake, + 'duckAudio': duckAudio, + 'recordingActive': recordingActive, + }, + ); if (result == 1) { state = PlayerState.PLAYING; @@ -214,15 +217,18 @@ class AudioPlayer { ); } - final int result = await _invokeMethod('playBytes', { - 'bytes': bytes, - 'volume': volume, - 'position': position?.inMilliseconds, - 'respectSilence': respectSilence, - 'stayAwake': stayAwake, - 'duckAudio': duckAudio, - 'recordingActive': recordingActive, - }); + final result = await _invokeMethod( + 'playBytes', + { + 'bytes': bytes, + 'volume': volume, + 'position': position?.inMilliseconds, + 'respectSilence': respectSilence, + 'stayAwake': stayAwake, + 'duckAudio': duckAudio, + 'recordingActive': recordingActive, + }, + ); if (result == 1) { state = PlayerState.PLAYING; @@ -288,7 +294,12 @@ class AudioPlayer { /// Moves the cursor to the desired position. Future seek(Duration position) { _positionController.add(position); - return _invokeMethod('seek', {'position': position.inMilliseconds}); + return _invokeMethod( + 'seek', + { + 'position': position.inMilliseconds, + }, + ); } /// Sets the volume (amplitude). @@ -296,7 +307,12 @@ class AudioPlayer { /// 0 is mute and 1 is the max volume. The values between 0 and 1 are linearly /// interpolated. Future setVolume(double volume) { - return _invokeMethod('setVolume', {'volume': volume}); + return _invokeMethod( + 'setVolume', + { + 'volume': volume, + }, + ); } /// Sets the release mode. @@ -305,7 +321,9 @@ class AudioPlayer { Future setReleaseMode(ReleaseMode releaseMode) { return _invokeMethod( 'setReleaseMode', - {'releaseMode': releaseMode.toString()}, + { + 'releaseMode': releaseMode.toString(), + }, ); } @@ -315,7 +333,12 @@ class AudioPlayer { /// Android SDK version should be 23 or higher. /// not sure if that's changed recently. Future setPlaybackRate({double playbackRate = 1.0}) { - return _invokeMethod('setPlaybackRate', {'playbackRate': playbackRate}); + return _invokeMethod( + 'setPlaybackRate', + { + 'playbackRate': playbackRate, + }, + ); } /// Sets the URL. @@ -328,13 +351,16 @@ class AudioPlayer { /// respectSilence is not implemented on macOS. Future setUrl( String url, { - bool isLocal: false, + bool? isLocal, bool respectSilence = false, }) { - isLocal = isLocalUrl(url); return _invokeMethod( 'setUrl', - {'url': url, 'isLocal': isLocal, 'respectSilence': respectSilence}, + { + 'url': url, + 'isLocal': isLocal ?? isLocalUrl(url), + 'respectSilence': respectSilence, + }, ); } @@ -361,11 +387,11 @@ class AudioPlayer { } static Future _doHandlePlatformCall(MethodCall call) async { - final Map callArgs = call.arguments as Map; + final callArgs = call.arguments as Map; _log('_platformCallHandler call ${call.method} $callArgs'); final playerId = callArgs['playerId'] as String; - final AudioPlayer? player = players[playerId]; + final player = players[playerId]; if (!kReleaseMode && Platform.isAndroid && player == null) { final oldPlayer = AudioPlayer(playerId: playerId); @@ -374,22 +400,24 @@ class AudioPlayer { players.remove(playerId); return; } - if (player == null) return; - - final value = callArgs['value']; + if (player == null) { + return; + } switch (call.method) { case 'audio.onNotificationPlayerStateChanged': - final bool isPlaying = value; + final isPlaying = callArgs['value'] as bool; player.notificationState = isPlaying ? PlayerState.PLAYING : PlayerState.PAUSED; break; case 'audio.onDuration': - Duration newDuration = Duration(milliseconds: value); + final millis = callArgs['value'] as int; + final newDuration = Duration(milliseconds: millis); player._durationController.add(newDuration); break; case 'audio.onCurrentPosition': - Duration newDuration = Duration(milliseconds: value); + final millis = callArgs['value'] as int; + final newDuration = Duration(milliseconds: millis); player._positionController.add(newDuration); break; case 'audio.onComplete': @@ -397,11 +425,13 @@ class AudioPlayer { player._completionController.add(null); break; case 'audio.onSeekComplete': - player._seekCompleteController.add(value); + final complete = callArgs['value'] as bool; + player._seekCompleteController.add(complete); break; case 'audio.onError': + final error = callArgs['value'] as String; player.state = PlayerState.STOPPED; - player._errorController.add(value); + player._errorController.add(error); break; case 'audio.onGotNextTrackCommand': player._commandController.add(PlayerControlCommand.NEXT_TRACK); @@ -426,24 +456,36 @@ class AudioPlayer { /// be used anymore. If you try to use it after this you will get errors. Future dispose() async { // First stop and release all native resources. - await this.release(); + await release(); - List futures = []; + final futures = []; - if (!_playerStateController.isClosed) + if (!_playerStateController.isClosed) { futures.add(_playerStateController.close()); - if (!_notificationPlayerStateController.isClosed) + } + if (!_notificationPlayerStateController.isClosed) { futures.add(_notificationPlayerStateController.close()); - if (!_positionController.isClosed) futures.add(_positionController.close()); - if (!_durationController.isClosed) futures.add(_durationController.close()); - if (!_completionController.isClosed) + } + if (!_positionController.isClosed) { + futures.add(_positionController.close()); + } + if (!_durationController.isClosed) { + futures.add(_durationController.close()); + } + if (!_completionController.isClosed) { futures.add(_completionController.close()); - if (!_seekCompleteController.isClosed) + } + if (!_seekCompleteController.isClosed) { futures.add(_seekCompleteController.close()); - if (!_errorController.isClosed) futures.add(_errorController.close()); - if (!_commandController.isClosed) futures.add(_commandController.close()); + } + if (!_errorController.isClosed) { + futures.add(_errorController.close()); + } + if (!_commandController.isClosed) { + futures.add(_commandController.close()); + } - await Future.wait(futures); + await Future.wait(futures); players.remove(playerId); } @@ -451,7 +493,9 @@ class AudioPlayer { final playingRoute = _playingRouteState.toggle(); final result = await _invokeMethod( 'earpieceOrSpeakersToggle', - {'playingRoute': playingRoute.name()}, + { + 'playingRoute': playingRoute.name(), + }, ); if (result == 1) { @@ -462,8 +506,8 @@ class AudioPlayer { } bool isLocalUrl(String url) { - return url.startsWith("/") || - url.startsWith("file://") || + return url.startsWith('/') || + url.startsWith('file://') || url.substring(1).startsWith(':\\'); } } diff --git a/lib/audioplayers_notifications.dart b/lib/audioplayers_notifications.dart index a4cdfd064..9cfd2df29 100644 --- a/lib/audioplayers_notifications.dart +++ b/lib/audioplayers_notifications.dart @@ -1,6 +1,6 @@ import 'dart:ui'; -import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb; +import 'package:flutter/foundation.dart' show defaultTargetPlatform; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; @@ -70,18 +70,21 @@ class NotificationService { bool enablePreviousTrackButton = false, bool enableNextTrackButton = false, }) async { - return _call('setNotification', { - 'title': title, - 'albumTitle': albumTitle, - 'artist': artist, - 'imageUrl': imageUrl, - 'forwardSkipInterval': forwardSkipInterval.inSeconds, - 'backwardSkipInterval': backwardSkipInterval.inSeconds, - 'duration': duration.inSeconds, - 'elapsedTime': elapsedTime.inSeconds, - 'enablePreviousTrackButton': enablePreviousTrackButton, - 'enableNextTrackButton': enableNextTrackButton, - }); + return _call( + 'setNotification', + { + 'title': title, + 'albumTitle': albumTitle, + 'artist': artist, + 'imageUrl': imageUrl, + 'forwardSkipInterval': forwardSkipInterval.inSeconds, + 'backwardSkipInterval': backwardSkipInterval.inSeconds, + 'duration': duration.inSeconds, + 'elapsedTime': elapsedTime.inSeconds, + 'enablePreviousTrackButton': enablePreviousTrackButton, + 'enableNextTrackButton': enableNextTrackButton, + }, + ); } Future _callWithHandle(String methodName, Function callback) async { @@ -93,7 +96,9 @@ class NotificationService { } await platformChannelInvoke( methodName, - {'handleKey': _getBgHandleKey(callback)}, + { + 'handleKey': _getBgHandleKey(callback), + }, ); } @@ -119,8 +124,7 @@ List _getBgHandleKey(Function callback) { /// MethodChannels to open a persistent communication channel to trigger /// callbacks. void _backgroundCallbackDispatcher() { - const MethodChannel _channel = - MethodChannel('xyz.luan/audioplayers_callback'); + const _channel = MethodChannel('xyz.luan/audioplayers_callback'); // Setup Flutter state needed for MethodChannels. WidgetsFlutterBinding.ensureInitialized(); @@ -132,24 +136,25 @@ void _backgroundCallbackDispatcher() { // native portion of the plugin. Here we message the audio notification data // which we then pass to the provided callback. _channel.setMethodCallHandler((MethodCall call) async { + final args = call.arguments as Map; Function(PlayerState) _performCallbackLookup() { - final CallbackHandle handle = CallbackHandle.fromRawHandle( - call.arguments['updateHandleMonitorKey']); + final handle = CallbackHandle.fromRawHandle( + args['updateHandleMonitorKey'] as int, + ); // PluginUtilities.getCallbackFromHandle performs a lookup based on the // handle we retrieved earlier. - final Function? closure = PluginUtilities.getCallbackFromHandle(handle); + final closure = PluginUtilities.getCallbackFromHandle(handle); if (closure == null) { - print('Fatal Error: Callback lookup failed!'); + throw 'Fatal Error: Callback lookup failed!'; } return closure as Function(PlayerState); } - final Map callArgs = call.arguments as Map; if (call.method == 'audio.onNotificationBackgroundPlayerStateChanged') { onAudioChangeBackgroundEvent ??= _performCallbackLookup(); - final String playerState = callArgs['value']; + final playerState = args['value'] as String; if (playerState == 'playing') { onAudioChangeBackgroundEvent!(PlayerState.PLAYING); } else if (playerState == 'paused') { diff --git a/lib/audioplayers_web.dart b/lib/audioplayers_web.dart index 86a347347..de2c94f53 100644 --- a/lib/audioplayers_web.dart +++ b/lib/audioplayers_web.dart @@ -1,10 +1,11 @@ import 'dart:async'; import 'dart:html'; -import 'package:audioplayers/release_mode.dart'; import 'package:flutter/services.dart'; import 'package:flutter_web_plugins/flutter_web_plugins.dart'; +import 'release_mode.dart'; + class WrappedPlayer { double? pausedAt; double currentVolume = 1.0; @@ -90,13 +91,13 @@ class AudioplayersPlugin { Map players = {}; static void registerWith(Registrar registrar) { - final MethodChannel channel = MethodChannel( + final channel = MethodChannel( 'xyz.luan/audioplayers', const StandardMethodCodec(), registrar, ); - final AudioplayersPlugin instance = AudioplayersPlugin(); + final instance = AudioplayersPlugin(); channel.setMethodCallHandler(instance.handleMethodCall); } @@ -105,7 +106,7 @@ class AudioplayersPlugin { } Future setUrl(String playerId, String url) async { - final WrappedPlayer player = getOrCreatePlayer(playerId); + final player = getOrCreatePlayer(playerId); if (player.currentUrl == url) { return player; @@ -121,22 +122,23 @@ class AudioplayersPlugin { Future handleMethodCall(MethodCall call) async { final method = call.method; - final playerId = call.arguments['playerId']; + final args = call.arguments as Map; + final playerId = args['playerId'] as String; switch (method) { case 'setUrl': { - final String url = call.arguments['url']; + final url = args['url'] as String; await setUrl(playerId, url); return 1; } case 'play': { - final String url = call.arguments['url']; + final url = args['url'] as String; // TODO(luan) think about isLocal (is it needed or not) - double volume = call.arguments['volume'] ?? 1.0; - final double position = call.arguments['position'] ?? 0; + final volume = args['volume'] as double? ?? 1.0; + final position = args['position'] as double? ?? 0; // web does not care for the `stayAwake` argument final player = await setUrl(playerId, url); @@ -162,14 +164,13 @@ class AudioplayersPlugin { } case 'setVolume': { - double volume = call.arguments['volume'] ?? 1.0; + final volume = args['volume'] as double? ?? 1.0; getOrCreatePlayer(playerId).setVolume(volume); return 1; } case 'setReleaseMode': { - ReleaseMode releaseMode = - parseReleaseMode(call.arguments['releaseMode']); + final releaseMode = parseReleaseMode(args['releaseMode'] as String); getOrCreatePlayer(playerId).setReleaseMode(releaseMode); return 1; } diff --git a/lib/player_mode.dart b/lib/player_mode.dart index b70b8eab0..6370a4bdc 100644 --- a/lib/player_mode.dart +++ b/lib/player_mode.dart @@ -1,5 +1,5 @@ -/// This enum is meant to be used as a parameter of the [AudioPlayer]'s -/// constructor. It represents the general mode of the [AudioPlayer]. +/// This enum is meant to be used as a parameter of the AudioPlayer's +/// constructor. It represents the general mode of the AudioPlayer. /// // In iOS and macOS, both modes have the same backend implementation. enum PlayerMode { diff --git a/lib/release_mode.dart b/lib/release_mode.dart index f5ed028c8..72bdebe9b 100644 --- a/lib/release_mode.dart +++ b/lib/release_mode.dart @@ -1,20 +1,20 @@ -/// This enum is meant to be used as a parameter of [setReleaseMode] method. +/// This enum is meant to be used as a parameter of setReleaseMode method. /// -/// It represents the behaviour of [AudioPlayer] when an audio is finished or +/// It represents the behaviour of AudioPlayer when an audio is finished or /// stopped. enum ReleaseMode { - /// Releases all resources, just like calling [release] method. + /// Releases all resources, just like calling release method. /// /// In Android, the media player is quite resource-intensive, and this will /// let it go. Data will be buffered again when needed (if it's a remote file, /// it will be downloaded again). - /// In iOS and macOS, works just like [stop] method. + /// In iOS and macOS, works just like stop method. /// /// This is the default behaviour. RELEASE, /// Keeps buffered data and plays again after completion, creating a loop. - /// Notice that calling [stop] method is not enough to release the resources + /// Notice that calling stop method is not enough to release the resources /// when this mode is being used. LOOP, diff --git a/pubspec.lock b/pubspec.lock index 25932f84b..6b570b7f2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,41 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "20.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.0" + analyzer_plugin: + dependency: transitive + description: + name: analyzer_plugin + url: "https://pub.dartlang.org" + source: hosted + version: "0.5.0" + ansicolor: + dependency: transitive + description: + name: ansicolor + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" async: dependency: transitive description: @@ -29,6 +64,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.0" + cli_util: + dependency: transitive + description: + name: cli_util + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.0" clock: dependency: transitive description: @@ -43,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.15.0" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" crypto: dependency: transitive description: @@ -50,6 +99,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.0.0" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.17.0" + dart_code_metrics: + dependency: "direct dev" + description: + name: dart_code_metrics + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0-nullsafety.1" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + dartdoc: + dependency: "direct dev" + description: + name: dartdoc + url: "https://pub.dartlang.org" + source: hosted + version: "0.42.0" fake_async: dependency: transitive description: @@ -86,6 +163,20 @@ packages: description: flutter source: sdk version: "0.0.0" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.15.0" http: dependency: "direct main" description: @@ -107,6 +198,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.6.3" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + markdown: + dependency: transitive + description: + name: markdown + url: "https://pub.dartlang.org" + source: hosted + version: "4.0.0" matcher: dependency: transitive description: @@ -121,6 +226,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" path: dependency: transitive description: @@ -191,6 +303,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "4.1.0" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" sky_engine: dependency: transitive description: flutter @@ -259,6 +385,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.0" win32: dependency: transitive description: @@ -273,6 +406,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.2.0" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" sdks: dart: ">=2.12.0 <3.0.0" flutter: ">=1.20.0" diff --git a/pubspec.yaml b/pubspec.yaml index 15e552ec2..8cff2052e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,7 +29,8 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter - http: ^0.13.0 + dart_code_metrics: ^3.0.0-nullsafety.0 + dartdoc: ^0.42.0 environment: sdk: ">=2.12.0 <3.0.0" diff --git a/scripts/analyze.sh b/scripts/analyze.sh new file mode 100755 index 000000000..858d6be0a --- /dev/null +++ b/scripts/analyze.sh @@ -0,0 +1,45 @@ +#!/usr/bin/env bash + +function run_analyze() { + if ! grep -q "dart_code_metrics" ./pubspec.yaml; then + echo "Missing dart_code_metrics in pubspec.yaml" + exit 1 + fi + + result=$(flutter pub get 2>&1) # Sadly a pub get can block up our actions as it will retry forever if a package is not found, but this should atleast report everything else. + if [ $? -ne 0 ]; then + echo "flutter pub get failed:" + echo "$result" + exit 1 + fi + + result=$(flutter pub run dart_code_metrics:metrics .) + if [ "$result" != "" ]; then + echo "flutter dart code metrics issues:" + echo "$result" + exit 1 + fi + + result=$(flutter analyze .) + if ! echo "$result" | grep -q "No issues found!"; then + echo "$result" + echo "flutter analyze issue:" + exit 1 + fi +} + +echo "Starting audioplayers Analyzer" +echo "-----------------------" +for file in $(find . -type f -name "pubspec.yaml"); do + dir=$(dirname $file) + cd $dir + echo "Analyzing $dir" + run_analyze + analyze_result=$? + if [ $analyze_result -ne 0 ]; then + exit $analyze_result + fi + cd $(cd -) +done + +exit 0 diff --git a/test/audio_cache_test.dart b/test/audio_cache_test.dart index c580331d3..0fb4af821 100644 --- a/test/audio_cache_test.dart +++ b/test/audio_cache_test.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:io'; import 'package:audioplayers/audio_cache.dart'; import 'package:audioplayers/audioplayers.dart'; @@ -22,16 +21,15 @@ class MyAudioCache extends AudioCache { void main() { TestWidgetsFlutterBinding.ensureInitialized(); - const MethodChannel _channel = - const MethodChannel('plugins.flutter.io/path_provider'); + const _channel = MethodChannel('plugins.flutter.io/path_provider'); _channel.setMockMethodCallHandler((c) async => '/tmp'); - const channel = const MethodChannel('xyz.luan/audioplayers'); + const channel = MethodChannel('xyz.luan/audioplayers'); channel.setMockMethodCallHandler((MethodCall call) async => 1); group('AudioCache', () { test('sets cache', () async { - MyAudioCache player = MyAudioCache(); + final player = MyAudioCache(); await player.load('audio.mp3'); expect(player.loadedFiles['audio.mp3'], isNotNull); expect(player.called, hasLength(1)); @@ -42,16 +40,16 @@ void main() { }); test('fixedPlayer vs non fixedPlayer', () async { - MyAudioCache fixed = MyAudioCache(fixedPlayer: AudioPlayer()); - String fixedId = fixed.fixedPlayer!.playerId; - MyAudioCache regular = MyAudioCache(); + final fixed = MyAudioCache(fixedPlayer: AudioPlayer()); + final fixedId = fixed.fixedPlayer!.playerId; + final regular = MyAudioCache(); - AudioPlayer a1 = await fixed.play('audio.mp3'); + final a1 = await fixed.play('audio.mp3'); expect(a1.playerId, fixedId); - AudioPlayer a2 = await fixed.play('audio.mp3'); + final a2 = await fixed.play('audio.mp3'); expect(a2.playerId, fixedId); - AudioPlayer a3 = await regular.play('audio.mp3'); + final a3 = await regular.play('audio.mp3'); expect(a3.playerId, isNot(fixedId)); }); }); diff --git a/test/audioplayers_test.dart b/test/audioplayers_test.dart index c4b031722..d961b359a 100644 --- a/test/audioplayers_test.dart +++ b/test/audioplayers_test.dart @@ -2,11 +2,19 @@ import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:audioplayers/audioplayers.dart'; +extension _Args on MethodCall { + Map get args => arguments as Map; + + String getString(String key) { + return args[key] as String; + } +} + void main() { TestWidgetsFlutterBinding.ensureInitialized(); - List calls = []; - const channel = const MethodChannel('xyz.luan/audioplayers'); + final calls = []; + const channel = MethodChannel('xyz.luan/audioplayers'); channel.setMockMethodCallHandler((MethodCall call) async { calls.add(call); return 0; @@ -20,37 +28,37 @@ void main() { group('AudioPlayers', () { test('#play', () async { calls.clear(); - AudioPlayer player = AudioPlayer(); + final player = AudioPlayer(); await player.play('internet.com/file.mp3'); - MethodCall call = popCall(); + final call = popCall(); expect(call.method, 'play'); - expect(call.arguments['url'], 'internet.com/file.mp3'); + expect(call.getString('url'), 'internet.com/file.mp3'); }); test('multiple players', () async { calls.clear(); - AudioPlayer player1 = AudioPlayer(); - AudioPlayer player2 = AudioPlayer(); + final player1 = AudioPlayer(); + final player2 = AudioPlayer(); await player1.play('internet.com/file.mp3'); - MethodCall call = popCall(); - String player1Id = call.arguments['playerId']; + final call = popCall(); + final player1Id = call.getString('playerId'); expect(call.method, 'play'); - expect(call.arguments['url'], 'internet.com/file.mp3'); + expect(call.getString('url'), 'internet.com/file.mp3'); await player1.play('internet.com/file.mp3'); - expect(popCall().arguments['playerId'], player1Id); + expect(popCall().getString('playerId'), player1Id); await player2.play('internet.com/file.mp3'); - expect(popCall().arguments['playerId'], isNot(player1Id)); + expect(popCall().getString('playerId'), isNot(player1Id)); await player1.play('internet.com/file.mp3'); - expect(popCall().arguments['playerId'], player1Id); + expect(popCall().getString('playerId'), player1Id); }); test('#resume, #pause and #duration', () async { calls.clear(); - AudioPlayer player = AudioPlayer(); + final player = AudioPlayer(); await player.setUrl('assets/audio.mp3'); expect(popCall().method, 'setUrl');