我不熟悉Flutter,最近开始从事我的第一个Flutter项目。
其想法是,当用户触摸按钮/图像时,它应该播放声音。
我正在使用android studio创建该应用。我已经导入了一个名为Audioplayers以帮助播放音频。
[当我运行代码时,会构建该应用程序并显示图像,但是当我点击图像时,它给了我一个我无法解决的错误:
Unhandled Exception: NoSuchMethodError: The getter 'position' was called on null
这是代码:
MaterialApp(
home: Scaffold(
backgroundColor: Colors.black,
body: Center(
child: GestureDetector(
onTap: () {
AudioPlayer audioPlayer = AudioPlayer();
audioPlayer.setVolume(1.0);
playLocal() async {
int result = await audioPlayer.play(
'C:/Users/JaganathPrathap/AndroidStudioProjects/bruh/assetsproj/audio/movie_1.mp3',
isLocal: true);
}
},
child: Image(
image: AssetImage('assetsproj/images/PngItem_4931119.png'),
),
),
),
),
),
这是错误:
E/flutter (11532): [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The getter 'position' was called on null.
E/flutter (11532): Receiver: null
E/flutter (11532): Tried calling: position
E/flutter (11532): #0 Object.noSuchMethod (dart:core-patch/object_patch.dart:53:5)
E/flutter (11532): #1 TapGestureRecognizer.handleTapDown (package:flutter/src/gestures/tap.dart:456:28)
E/flutter (11532): #2 BaseTapGestureRecognizer._checkDown (package:flutter/src/gestures/tap.dart:256:5)
E/flutter (11532): #3 BaseTapGestureRecognizer.didExceedDeadline (package:flutter/src/gestures/tap.dart:227:5)
E/flutter (11532): #4 PrimaryPointerGestureRecognizer.didExceedDeadlineWithEvent (package:flutter/src/gestures/recognizer.dart:493:5)
E/flutter (11532): #5 PrimaryPointerGestureRecognizer.addAllowedPointer.<anonymous closure> (package:flutter/src/gestures/recognizer.dart:446:40)
E/flutter (11532): #6 _rootRun (dart:async/zone.dart:1122:38)
E/flutter (11532): #7 _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (11532): #8 _CustomZone.runGuarded (dart:async/zone.dart:925:7)
E/flutter (11532): #9 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:965:23)
E/flutter (11532): #10 _rootRun (dart:async/zone.dart:1126:13)
E/flutter (11532): #11 _CustomZone.run (dart:async/zone.dart:1023:19)
E/flutter (11532): #12 _CustomZone.bindCallback.<anonymous closure> (dart:async/zone.dart:949:23)
E/flutter (11532): #13 Timer._createTimer.<anonymous closure> (dart:async-patch/timer_patch.dart:23:15)
E/flutter (11532): #14 _Timer._runTimers (dart:isolate-patch/timer_impl.dart:384:19)
E/flutter (11532): #15 _Timer._handleMessage (dart:isolate-patch/timer_impl.dart:418:5)
E/flutter (11532): #16 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:174:12)
E/flutter (11532):
我不确定为什么会收到此错误。
我不确定为什么会收到此错误。
您的代码缺少Flutter应用正常运行所需的一切,因此可能是原因。
您需要了解的三件事:
没有牢牢掌握这些概念,您就无法编写Flutter应用程序。
[对不起,我听起来像个万事通,但当您显示的代码有很多缺失的片段时,这个问题无法回答,表明您对其中的任何一个都不了解上述三点。
如果您阅读了软件包的Readme File,则会看到以下一行:
此示例捆绑了一个Player Widget,可以将其用作非常简单的音频播放器界面。
看看这个,并在上面的句子中标记“非常简单”部分。记住,这只是一个小部件(应用程序的一个组件)的一个示例,该小部件可以包含在您自己的代码中,用于管理小部件,状态和其他所有内容(代码中完全缺少的内容)。
这里是非常简单示例小部件的代码:
// player_widget.dart
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
enum PlayerState { stopped, playing, paused }
enum PlayingRouteState { speakers, earpiece }
class PlayerWidget extends StatefulWidget {
final String url;
final PlayerMode mode;
PlayerWidget({@required this.url, this.mode = PlayerMode.MEDIA_PLAYER});
@override
State<StatefulWidget> createState() {
return _PlayerWidgetState(url, mode);
}
}
class _PlayerWidgetState extends State<PlayerWidget> {
String url;
PlayerMode mode;
AudioPlayer _audioPlayer;
AudioPlayerState _audioPlayerState;
Duration _duration;
Duration _position;
PlayerState _playerState = PlayerState.stopped;
PlayingRouteState _playingRouteState = PlayingRouteState.speakers;
StreamSubscription _durationSubscription;
StreamSubscription _positionSubscription;
StreamSubscription _playerCompleteSubscription;
StreamSubscription _playerErrorSubscription;
StreamSubscription _playerStateSubscription;
get _isPlaying => _playerState == PlayerState.playing;
get _isPaused => _playerState == PlayerState.paused;
get _durationText => _duration?.toString()?.split('.')?.first ?? '';
get _positionText => _position?.toString()?.split('.')?.first ?? '';
get _isPlayingThroughEarpiece =>
_playingRouteState == PlayingRouteState.earpiece;
_PlayerWidgetState(this.url, this.mode);
@override
void initState() {
super.initState();
_initAudioPlayer();
}
@override
void dispose() {
_audioPlayer.stop();
_durationSubscription?.cancel();
_positionSubscription?.cancel();
_playerCompleteSubscription?.cancel();
_playerErrorSubscription?.cancel();
_playerStateSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: _isPlaying ? null : () => _play(),
iconSize: 64.0,
icon: Icon(Icons.play_arrow),
color: Colors.cyan),
IconButton(
onPressed: _isPlaying ? () => _pause() : null,
iconSize: 64.0,
icon: Icon(Icons.pause),
color: Colors.cyan),
IconButton(
onPressed: _isPlaying || _isPaused ? () => _stop() : null,
iconSize: 64.0,
icon: Icon(Icons.stop),
color: Colors.cyan),
IconButton(
onPressed: _earpieceOrSpeakersToggle,
iconSize: 64.0,
icon: _isPlayingThroughEarpiece
? Icon(Icons.volume_up)
: Icon(Icons.hearing)
,
color: Colors.cyan),
],
),
Column(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: EdgeInsets.all(12.0),
child: Stack(
children: [
Slider(
onChanged: (v) {
final Position = v * _duration.inMilliseconds;
_audioPlayer
.seek(Duration(milliseconds: Position.round()));
},
value: (_position != null &&
_duration != null &&
_position.inMilliseconds > 0 &&
_position.inMilliseconds < _duration.inMilliseconds)
? _position.inMilliseconds / _duration.inMilliseconds
: 0.0,
),
],
),
),
Text(
_position != null
? '${_positionText ?? ''} / ${_durationText ?? ''}'
: _duration != null ? _durationText : '',
style: TextStyle(fontSize: 24.0),
),
],
),
Text("State: $_audioPlayerState")
],
);
}
void _initAudioPlayer() {
_audioPlayer = AudioPlayer(mode: mode);
_durationSubscription = _audioPlayer.onDurationChanged.listen((duration) {
setState(() => _duration = duration);
// TODO implemented for iOS, waiting for android impl
if (Theme.of(context).platform == TargetPlatform.iOS) {
// (Optional) listen for notification updates in the background
_audioPlayer.startHeadlessService();
// set at least title to see the notification bar on ios.
_audioPlayer.setNotification(
title: 'App Name',
artist: 'Artist or blank',
albumTitle: 'Name or blank',
imageUrl: 'url or blank',
forwardSkipInterval: const Duration(seconds: 30), // default is 30s
backwardSkipInterval: const Duration(seconds: 30), // default is 30s
duration: duration,
elapsedTime: Duration(seconds: 0));
}
});
_positionSubscription =
_audioPlayer.onAudioPositionChanged.listen((p) => setState(() {
_position = p;
}));
_playerCompleteSubscription =
_audioPlayer.onPlayerCompletion.listen((event) {
_onComplete();
setState(() {
_position = _duration;
});
});
_playerErrorSubscription = _audioPlayer.onPlayerError.listen((msg) {
print('audioPlayer error : $msg');
setState(() {
_playerState = PlayerState.stopped;
_duration = Duration(seconds: 0);
_position = Duration(seconds: 0);
});
});
_audioPlayer.onPlayerStateChanged.listen((state) {
if (!mounted) return;
setState(() {
_audioPlayerState = state;
});
});
_audioPlayer.onNotificationPlayerStateChanged.listen((state) {
if (!mounted) return;
setState(() => _audioPlayerState = state);
});
_playingRouteState = PlayingRouteState.speakers;
}
Future<int> _play() async {
final playPosition = (_position != null &&
_duration != null &&
_position.inMilliseconds > 0 &&
_position.inMilliseconds < _duration.inMilliseconds)
? _position
: null;
final result = await _audioPlayer.play(url, position: playPosition);
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);
return result;
}
Future<int> _pause() async {
final result = await _audioPlayer.pause();
if (result == 1) setState(() => _playerState = PlayerState.paused);
return result;
}
Future<int> _earpieceOrSpeakersToggle() async {
final result = await _audioPlayer.earpieceOrSpeakersToggle();
if (result == 1)
setState(() => _playingRouteState =
_playingRouteState == PlayingRouteState.speakers
? PlayingRouteState.earpiece
: PlayingRouteState.speakers);
return result;
}
Future<int> _stop() async {
final result = await _audioPlayer.stop();
if (result == 1) {
setState(() {
_playerState = PlayerState.stopped;
_position = Duration();
});
}
return result;
}
void _onComplete() {
setState(() => _playerState = PlayerState.stopped);
}
}
现在,所缺少的只是组成Flutter应用程序的其余部分。
[如果您浏览此main.dart文件中的代码,并将其与代码中包含的内容进行比较,您可能会发现一些缺少的部分:
// main.dart
import 'dart:async';
import 'dart:io';
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart';
import 'package:flutter/src/foundation/constants.dart';
import 'player_widget.dart';
typedef void OnError(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';
const kUrl3 = 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio1xtra_mf_p';
void main() {
runApp(MaterialApp(home: ExampleApp()));
}
class ExampleApp extends StatefulWidget {
@override
_ExampleAppState createState() => _ExampleAppState();
}
class _ExampleAppState extends State<ExampleApp> {
AudioCache audioCache = AudioCache();
AudioPlayer advancedPlayer = AudioPlayer();
String localFilePath;
@override
void initState() {
super.initState();
if (kIsWeb) {
// Calls to Platform.isIOS fails on web
return;
}
if (Platform.isIOS) {
if (audioCache.fixedPlayer != null) {
audioCache.fixedPlayer.startHeadlessService();
}
advancedPlayer.startHeadlessService();
}
}
Future _loadFile() async {
final bytes = await readBytes(kUrl1);
final dir = await getApplicationDocumentsDirectory();
final file = File('${dir.path}/audio.mp3');
await file.writeAsBytes(bytes);
if (await file.exists()) {
setState(() {
localFilePath = file.path;
});
}
}
Widget remoteUrl() {
return SingleChildScrollView(
child: _Tab(children: [
Text(
'Sample 1 ($kUrl1)',
style: TextStyle(fontWeight: FontWeight.bold),
),
PlayerWidget(url: kUrl1),
Text(
'Sample 2 ($kUrl2)',
style: TextStyle(fontWeight: FontWeight.bold),
),
PlayerWidget(url: kUrl2),
Text(
'Sample 3 ($kUrl3)',
style: TextStyle(fontWeight: FontWeight.bold),
),
PlayerWidget(url: kUrl3),
Text(
'Sample 4 (Low Latency mode) ($kUrl1)',
style: TextStyle(fontWeight: FontWeight.bold),
),
PlayerWidget(url: kUrl1, mode: PlayerMode.LOW_LATENCY),
]),
);
}
Widget localFile() {
return _Tab(children: [
Text('File: $kUrl1'),
_Btn(txt: 'Download File to your Device', onPressed: () => _loadFile()),
Text('Current local file path: $localFilePath'),
localFilePath == null
? Container()
: PlayerWidget(
url: localFilePath,
),
]);
}
Widget localAsset() {
return SingleChildScrollView(
child: _Tab(children: [
Text('Play Local Asset \'audio.mp3\':'),
_Btn(txt: 'Play', onPressed: () => audioCache.play('audio.mp3')),
Text('Loop Local Asset \'audio.mp3\':'),
_Btn(txt: 'Loop', onPressed: () => audioCache.loop('audio.mp3')),
Text('Play Local Asset \'audio2.mp3\':'),
_Btn(txt: 'Play', onPressed: () => audioCache.play('audio2.mp3')),
Text('Play Local Asset In Low Latency \'audio.mp3\':'),
_Btn(
txt: 'Play',
onPressed: () =>
audioCache.play('audio.mp3', mode: PlayerMode.LOW_LATENCY)),
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\':'),
_Btn(
txt: 'Play',
onPressed: () =>
audioCache.play('audio2.mp3', mode: PlayerMode.LOW_LATENCY)),
getLocalFileDuration(),
]),
);
}
Future<int> _getDuration() async {
File audiofile = await audioCache.load('audio2.mp3');
await advancedPlayer.setUrl(
audiofile.path,
);
int duration = await Future.delayed(
Duration(seconds: 2), () => advancedPlayer.getDuration());
return duration;
}
getLocalFileDuration() {
return FutureBuilder<int>(
future: _getDuration(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return Text('No Connection...');
case ConnectionState.active:
case ConnectionState.waiting:
return Text('Awaiting result...');
case ConnectionState.done:
if (snapshot.hasError) return Text('Error: ${snapshot.error}');
return Text(
'audio2.mp3 duration is: ${Duration(milliseconds: snapshot.data)}');
}
return null; // unreachable
},
);
}
Widget notification() {
return _Tab(children: [
Text('Play notification sound: \'messenger.mp3\':'),
_Btn(
txt: 'Play',
onPressed: () =>
audioCache.play('messenger.mp3', isNotification: true)),
]);
}
@override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<Duration>.value(
initialData: Duration(),
value: advancedPlayer.onAudioPositionChanged),
],
child: DefaultTabController(
length: 5,
child: Scaffold(
appBar: AppBar(
bottom: TabBar(
tabs: [
Tab(text: 'Remote Url'),
Tab(text: 'Local File'),
Tab(text: 'Local Asset'),
Tab(text: 'Notification'),
Tab(text: 'Advanced'),
],
),
title: Text('audioplayers Example'),
),
body: TabBarView(
children: [
remoteUrl(),
localFile(),
localAsset(),
notification(),
Advanced(
advancedPlayer: advancedPlayer,
)
],
),
),
),
);
}
}
class Advanced extends StatefulWidget {
final AudioPlayer advancedPlayer;
const Advanced({Key key, this.advancedPlayer}) : super(key: key);
@override
_AdvancedState createState() => _AdvancedState();
}
class _AdvancedState extends State<Advanced> {
bool seekDone;
@override
void initState() {
widget.advancedPlayer.seekCompleteHandler =
(finished) => setState(() => seekDone = finished);
super.initState();
}
@override
Widget build(BuildContext context) {
final audioPosition = Provider.of<Duration>(context);
return SingleChildScrollView(
child: _Tab(
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),
]),
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),
]),
Column(children: [
Text('Volume'),
Row(children: [
_Btn(
txt: '0.0',
onPressed: () => widget.advancedPlayer.setVolume(0.0)),
_Btn(
txt: '0.5',
onPressed: () => widget.advancedPlayer.setVolume(0.5)),
_Btn(
txt: '1.0',
onPressed: () => widget.advancedPlayer.setVolume(1.0)),
_Btn(
txt: '2.0',
onPressed: () => widget.advancedPlayer.setVolume(2.0)),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
Column(children: [
Text('Control'),
Row(children: [
_Btn(
txt: 'resume',
onPressed: () => widget.advancedPlayer.resume()),
_Btn(
txt: 'pause', onPressed: () => widget.advancedPlayer.pause()),
_Btn(txt: 'stop', onPressed: () => widget.advancedPlayer.stop()),
_Btn(
txt: 'release',
onPressed: () => widget.advancedPlayer.release()),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
Column(children: [
Text('Seek in milliseconds'),
Row(children: [
_Btn(
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);
}),
_Btn(
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);
}),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
Column(children: [
Text('Rate'),
Row(children: [
_Btn(
txt: '0.5',
onPressed: () =>
widget.advancedPlayer.setPlaybackRate(playbackRate: 0.5)),
_Btn(
txt: '1.0',
onPressed: () =>
widget.advancedPlayer.setPlaybackRate(playbackRate: 1.0)),
_Btn(
txt: '1.5',
onPressed: () =>
widget.advancedPlayer.setPlaybackRate(playbackRate: 1.5)),
_Btn(
txt: '2.0',
onPressed: () =>
widget.advancedPlayer.setPlaybackRate(playbackRate: 2.0)),
], mainAxisAlignment: MainAxisAlignment.spaceEvenly),
]),
Text('Audio Position: ${audioPosition}'),
seekDone == null
? SizedBox(
width: 0,
height: 0,
)
: Text(seekDone ? "Seek Done" : "Seeking..."),
],
),
);
}
}
class _Tab extends StatelessWidget {
final List<Widget> children;
const _Tab({Key key, this.children}) : super(key: key);
@override
Widget build(BuildContext context) {
return Center(
child: Container(
alignment: Alignment.topCenter,
padding: EdgeInsets.all(16.0),
child: SingleChildScrollView(
child: Column(
children: children
.map((w) => Container(child: w, padding: EdgeInsets.all(6.0)))
.toList(),
),
),
),
);
}
}
class _Btn extends StatelessWidget {
final String txt;
final VoidCallback onPressed;
const _Btn({Key key, this.txt, this.onPressed}) : super(key: key);
@override
Widget build(BuildContext context) {
return ButtonTheme(
minWidth: 48.0,
child: RaisedButton(child: Text(txt), onPressed: onPressed));
}
}