一週間もの試行錯誤の結果、、、なんとか成功
いや〜本当に苦労しました。WEBのChromeで音が出るのに、Androidでは音が出ず。😭
ただのBEEP音でも良いのに、flutter_beepは開発が止まっていて。ブログの古い記事に翻弄されました。
最終的にflutterのaudioplayersのみ開発が継続されていることに気づきました。
鉄則:ブログ記事を鵜呑みにせず、公式パッケージのExampleソースを読もう!!
何が駄目だったのか、どのようにして解決したのか
当初、flutterの開発版を使っていたのですが、日替わりでガンガン仕様が変わり(開発版だから当たり前)
困惑しっぱなしでした。まだ初心者なんだから、素直にStable版を使うことにしました。
(flutter のinstall記事も書き直しました )
僕はUbuntuからクリーンインストールしました。(心配性なので)
必要なのはBEEP音だったので、短すぎるMp3を使っていました。最低0.3秒くらいは無いと駄目でした。
実験するときは、ちゃんと再生できるMp3ファイルを準備しないと泥沼です!!
/android/app/build.gradle.kts に、ndkバージョン指定が必要でした。(コンパイル中にエラーが表示されます。)
......
android {
namespace = "com.example.audioapp"
compileSdk = flutter.compileSdkVersion
// ndkVersion = flutter.ndkVersion
ndkVersion = "27.0.12077973"
.......
サンプルソース(最低限の音出し)
下記のソースは、公式で紹介しているソースをちょっと改良したものです。
assets/sounds/pi2.mp3 というファイルが入っていることを前提にしています。
pubspeck.yaml のassetの記載も忘れずに。
flutter:
# ......
assets:
- assets/images/
- assets/sounds/
#スペースによる段つけにも全て意味があります!! 上記のようにフォルダ名のみ記載しましょう。- assetsで始まるのも重要
ソース内では、'sounds/pi2.mp3'のように記載します。
main.dart 赤色部分がaudioplayersに関連しているところです。
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'playerwidget.dart';
void main() {
runApp(const MaterialApp(home: _SimpleExampleApp()));
}
class _SimpleExampleApp extends StatefulWidget {
const _SimpleExampleApp();
@override
_SimpleExampleAppState createState() => _SimpleExampleAppState();
}
class _SimpleExampleAppState extends State<_SimpleExampleApp> {
late AudioPlayer player = AudioPlayer(); // 空の入れ物を宣言している
@override
void initState() { // Widget初期化処理
super.initState();
// Create the audio player.
player = AudioPlayer();
// Set the release mode to keep the source after playback has completed.
player.setReleaseMode(ReleaseMode.stop);
// Start the player as soon as the app is displayed.
WidgetsBinding.instance.addPostFrameCallback((_) async {
await player.setSource(AssetSource('sounds/pi2.mp3'));
await player.resume();// いきなり音がでます。最初音を出したくなければ、この行は消しましょう。
});
}
@override
void dispose() { // ゴミ掃除
// Release all sources and dispose the player.
player.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Simple Player'),
),
body: PlayerWidget(player: player), // 見通しが良いように、別ファイルにしてみました。
);
}
}
playerwidget.dart 以下は音を再生するICONボタン付きのWidgetです。パッケージを使うためのノウハウが殆ど包含されています。
//playerwidget.dart
import 'dart:async';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
class PlayerWidget extends StatefulWidget {
final AudioPlayer player;
const PlayerWidget({
required this.player,
super.key,
});
@override
State<StatefulWidget> createState() {
return _PlayerWidgetState();
}
}
class _PlayerWidgetState extends State<PlayerWidget> {
PlayerState? _playerState;
Duration? _duration;
Duration? _position;
StreamSubscription? _durationSubscription;
StreamSubscription? _positionSubscription;
StreamSubscription? _playerCompleteSubscription;
StreamSubscription? _playerStateChangeSubscription;
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 ?? '';
AudioPlayer get player => widget.player;
@override
void initState() {
super.initState();
// Use initial values from player
_playerState = player.state;
player.getDuration().then(
(value) => setState(() {
_duration = value;
}),
);
player.getCurrentPosition().then(
(value) => setState(() {
_position = value;
}),
);
_initStreams();
}
@override
void setState(VoidCallback fn) {
// Subscriptions only can be closed asynchronously,
// therefore events can occur after widget has been disposed.
if (mounted) {
super.setState(fn);
}
}
@override
void dispose() {
_durationSubscription?.cancel();
_positionSubscription?.cancel();
_playerCompleteSubscription?.cancel();
_playerStateChangeSubscription?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
final color = Theme.of(context).primaryColor;
return Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
key: const Key('play_button'),
onPressed: _isPlaying ? null : _play,
iconSize: 48.0,
icon: const Icon(Icons.play_arrow),
color: color,
),
IconButton(
key: const Key('pause_button'),
onPressed: _isPlaying ? _pause : null,
iconSize: 48.0,
icon: const Icon(Icons.pause),
color: color,
),
IconButton(
key: const Key('stop_button'),
onPressed: _isPlaying || _isPaused ? _stop : null,
iconSize: 48.0,
icon: const Icon(Icons.stop),
color: color,
),
],
),
Slider( // スライダーをセットした位置から再生する
onChanged: (value) {
final duration = _duration;
if (duration == null) {
return;
}
final position = value * duration.inMilliseconds;
player.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: const TextStyle(fontSize: 16.0),
),
],
);
}
void _initStreams() {
_durationSubscription = player.onDurationChanged.listen((duration) {
setState(() => _duration = duration);
});
_positionSubscription = player.onPositionChanged.listen(
(p) => setState(() => _position = p),
);
_playerCompleteSubscription = player.onPlayerComplete.listen((event) {
setState(() {
_playerState = PlayerState.stopped;
_position = Duration.zero;
});
});
_playerStateChangeSubscription =
player.onPlayerStateChanged.listen((state) {
setState(() {
_playerState = state;
});
});
}
Future<void> _play() async {
await player.resume();
setState(() => _playerState = PlayerState.playing);
}
Future<void> _pause() async {
await player.pause();
setState(() => _playerState = PlayerState.paused);
}
Future<void> _stop() async {
await player.stop();
setState(() {
_playerState = PlayerState.stopped;
_position = Duration.zero;
});
}
}
なお、一定間隔で音を出す例は、日数計算アプリのdripsound.dartを見てください。
(Dart のTimer.periodicを使いましたが、それが良いのかはわからない。再生中に何度も再生コマンドを叩くと落ちるので、ひと工夫必要です)