{"id":616,"date":"2022-02-26T20:31:47","date_gmt":"2022-02-26T20:31:47","guid":{"rendered":"https:\/\/erdbeerbeet.com\/?p=616"},"modified":"2022-02-26T20:31:49","modified_gmt":"2022-02-26T20:31:49","slug":"my-flutter-flight-add-some-sound","status":"publish","type":"post","link":"https:\/\/erdbeerbeet.com\/de\/my-flutter-flight-add-some-sound","title":{"rendered":"My Flutter Flight &#8211; Add some sound"},"content":{"rendered":"\n<p>Even if I am a person who mutes all sounds my devices make, I want sounds and background music for my flutter game. After gathering the files, it was half a day of work to make this puzzle sing to my ears.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Sound Files<\/h2>\n\n\n\n<p>For sound effects, I looked for free sound samples. I ended up using some from <a href=\"https:\/\/mixkit.co\/free-sound-effects\/game\">mixkit.co<\/a>.<\/p>\n\n\n\n<p>As a looping background music, my husband used <a href=\"https:\/\/sonic-pi.net\/\">Sonic Pi<\/a> to create something for me. No link to that, sorry, but credits to him.<\/p>\n\n\n\n<p>The audio files are stored as .wav in my assets\/audio\/ directory and added to pubspec.yaml.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">AudioPlayers<\/h2>\n\n\n\n<p>I ended up with AudioCache from the <a href=\"https:\/\/pub.dev\/packages\/audioplayers\">AudioPlayers Plugin<\/a>. And to centralize my sounds, I created a SoundModule class (although I&#8217;m not happy with the name and open for suggestions). Additionally I want to save the chosen volume settings in Shared Preferences.<\/p>\n\n\n\n<p>First, here is the <strong>SoundModule<\/strong> code:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>import 'package:audioplayers\/audioplayers.dart';\nimport 'package:ruby_robbery\/helper\/preferences.dart';\n\nclass SoundModule {\n\n  static final SoundModule _soundModule = SoundModule._internal();\n\n  Preferences preferences = Preferences();\n  bool mute = false;\n\n  late AudioCache effectCache;\n  late AudioCache backgroundCache;\n  late AudioPlayer backgroundPlayer;\n\n  String LEVEL_UNLOCK_SOUND = \"audio\/sound1.wav\";\n  String LEVEL_COMPLETE_SOUND = \"audio\/sound2.wav\";\n  String TILE_MOVED_SOUND = \"audio\/sound3.wav\";\n\n  String BACKGROUND_MUSIC = \"audio\/background_music.wav\";\n\n  factory SoundModule() {\n    return _soundModule;\n  }\n\n  SoundModule._internal() {\n    effectCache = AudioCache();\n    backgroundCache = AudioCache();\n    mute = preferences.isMute();\n  }\n\n  void startBackgroundMusic() async {\n    backgroundPlayer = await backgroundCache.loop(BACKGROUND_MUSIC, volume: preferences.getBackgroundVolume());\n  }\n\n  void muteBackground() async {\n    await backgroundPlayer.setVolume(0.0);\n    preferences.setBackgroundVolume(0.0);\n  }\n\n  void playSound(String sound) async {\n    if (mute) return;\n    try {\n      await effectCache.play( sound, volume: preferences.getEffectVolume() );\n    } catch (err) {\n      print(err);\n    }\n  }\n\n  void updateBackgroundMusic() async {\n    backgroundPlayer.setVolume(preferences.getBackgroundVolume());\n  }\n\n  void muteSound() {\n    mute = true;\n    backgroundPlayer.setVolume(0.0);\n  }\n}<\/code><\/pre>\n\n\n\n<p>The code is used when a tile is dragged, a level is completed or unlocked. And the background music starts when the app is opened. A call looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void playEffectSound() {\n    SoundModule soundModule = SoundModule();\n    soundModule.playSound(soundModule.TILE_MOVED_SOUND);\n  }<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Settings<\/h2>\n\n\n\n<p>The volume must be variable by the users choice, so I expanded my <strong>SettingPage<\/strong> with the first real setting. As you can see I used some fancy <a href=\"https:\/\/api.flutter.dev\/flutter\/material\/Slider-class.html\">Sliders<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full is-resized wp-duotone-duotone-2\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2022\/02\/Bildschirmfoto-2022-02-26-um-20.41.20.png\" alt=\"\" class=\"wp-image-617\" width=\"421\" height=\"156\" srcset=\"https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2022\/02\/Bildschirmfoto-2022-02-26-um-20.41.20.png 662w, https:\/\/erdbeerbeet.com\/wp-content\/uploads\/2022\/02\/Bildschirmfoto-2022-02-26-um-20.41.20-300x111.png 300w\" sizes=\"auto, (max-width: 421px) 100vw, 421px\" \/><figcaption>Volume Settings Sliders<\/figcaption><\/figure>\n\n\n\n<pre class=\"wp-block-code\"><code>Widget audio() {\n    double backgroundVolume = preferences.getBackgroundVolume()*10;\n    double effectVolume = preferences.getBackgroundVolume()*10;\n\n    return Column(\n      children: &#91;\n        Text(context.l10n.volume),\n        Row(\n          children: &#91;\n            Text(context.l10n.volumeEffect),\n            Slider(\n              max: 10.0,\n              divisions: 10,\n              value: effectVolume,\n              onChanged: (double value) => setState(() {\n                effectVolume = value;\n                preferences.setEffectVolume(value\/10.0);\n                playEffectSound();\n              })\n            )\n          ],\n        ),\n        Row(\n          children: &#91;\n            Text(context.l10n.volumeBackground),\n            Slider(\n              max: 10.0,\n              divisions: 10,\n              value: backgroundVolume,\n              onChanged: (double value) => setState(() {\n                backgroundVolume = value;\n                preferences.setBackgroundVolume(value\/10.0);\n              })\n            )\n          ],\n        )\n      ],\n    );\n  }<\/code><\/pre>\n\n\n\n<p>Changing the effect volume will play a sound to give feedback to the user, whereas the background volume change affects the current player:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>void setBackgroundVolume(double volume) {\n    backgroundVolume = volume;\n    sharedPreferences.setDouble('background_volume', backgroundVolume);\n    SoundModule soundModule = SoundModule();\n    soundModule.updateBackgroundMusic();\n  }<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Autoplay on the web<\/h2>\n\n\n\n<p>Then I stumbled into the problem that the Google Chrome blocks autoplay of sound (and video) as described <a href=\"https:\/\/developer.chrome.com\/blog\/autoplay\/\">here<\/a>.<\/p>\n\n\n\n<p>So I wrapped the start of background music in a condition to exclude web:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>@override\n  void initState() {\n    super.initState();\n    if (!kIsWeb) {\n      SoundModule soundModule = SoundModule();\n      soundModule.startBackgroundMusic();\n    }\n  }<\/code><\/pre>\n\n\n\n<p>&#8230; and I introduced a variable that is checked whenever a button on the main page is pressed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>bool startedBackgroundMusic = false;\n...\nvoid checkBackgroundMusic() {\n    if (!startedBackgroundMusic) {\n      startedBackgroundMusic = true;\n      SoundModule soundModule = SoundModule();\n      soundModule.startBackgroundMusic();\n    }\n  }<\/code><\/pre>\n\n\n\n<p>I will insert some dialogs in the future to warn the user of upcoming background music&#8230;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Mute Button<\/h2>\n\n\n\n<p>As part of web accessibility guidelines and as long as I haven&#8217;t insert my warning dialogs, there has to be a present mute button to stop any sound immediately. Again, as someone who mutes all sounds, I&#8217;m totally behind this.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Widget muteButton(BuildContext context) {\n    checkBackgroundMusic();\n    bool volumeOff = prefs.isMute();\n    return Positioned(\n      right: 30.0,\n      bottom: 0,\n      child: IconButton(\n        icon: volumeOff ? const Icon(Icons.volume_up) : const Icon(Icons.volume_off),\n        color: PuzzleColors.primary0,\n        onPressed: () => prefs.setMute(volumeOff),\n      ),\n    );\n  }<\/code><\/pre>\n\n\n\n<p>Yes, I already see all the places where optimization is possible, but one step after the other.<br>Right now I&#8217;m super happy that I created my very first applications that makes some noise.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Good games feature good sounds and background music. So here is how I managed to insert them into my flutter game.<\/p>\n","protected":false},"author":1,"featured_media":384,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[62,42,13,12,36],"tags":[14,63,64,66],"class_list":["post-616","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-ruby-robbery","category-app-programming","category-flutter","category-programming","category-web-programming","tag-flutter","tag-game","tag-hackathon","tag-sound"],"translation":{"provider":"WPGlobus","version":"3.0.0","language":"de","enabled_languages":["en","de"],"languages":{"en":{"title":true,"content":true,"excerpt":true},"de":{"title":false,"content":false,"excerpt":false}}},"_links":{"self":[{"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/posts\/616","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/comments?post=616"}],"version-history":[{"count":1,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/posts\/616\/revisions"}],"predecessor-version":[{"id":618,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/posts\/616\/revisions\/618"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/media\/384"}],"wp:attachment":[{"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/media?parent=616"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/categories?post=616"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/erdbeerbeet.com\/de\/wp-json\/wp\/v2\/tags?post=616"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}