diff --git a/assets_audio_player_web/.gitignore b/assets_audio_player_web/.gitignore
new file mode 100644
index 00000000..e9dc58d3
--- /dev/null
+++ b/assets_audio_player_web/.gitignore
@@ -0,0 +1,7 @@
+.DS_Store
+.dart_tool/
+
+.packages
+.pub/
+
+build/
diff --git a/assets_audio_player_web/.metadata b/assets_audio_player_web/.metadata
new file mode 100644
index 00000000..ec5d4108
--- /dev/null
+++ b/assets_audio_player_web/.metadata
@@ -0,0 +1,10 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: b8bd09db210d2c6299555643af8db4b8ff3e8d92
+ channel: master
+
+project_type: plugin
diff --git a/assets_audio_player_web/CHANGELOG.md b/assets_audio_player_web/CHANGELOG.md
new file mode 100644
index 00000000..083446c8
--- /dev/null
+++ b/assets_audio_player_web/CHANGELOG.md
@@ -0,0 +1,436 @@
+## 3.1.2
+
+- Migrates to `package:web` to support WASM
+
+## 3.1.1
+
+- fix startup crash issue for some Android devices.
+
+## 3.1.0
+
+- revert back changes
+
+## 3.0.9
+
+- fix notification package naming and build issue
+
+## 3.0.8
+
+- fix warnings
+
+## 3.0.7
+
+- Update dependencies and fix notification issue
+
+## 3.0.6
+
+- Update kotling version and fix minor issues
+
+## 3.0.5
+
+- Breaking change, with Flutter 3.0 removed null aware for WidgetsBinding
+
+## 3.0.4+5
+
+- Fixed null aware
+
+## 3.0.4+4
+
+- Fix issues and update exoplayer
+
+## 3.0.4+3
+
+- Fix warnings and abstract issue
+
+## 3.0.4+2
+
+- Fix flutter 3.0 issues
+
+## 3.0.4+1
+
+- Fix andorid 12 issues and update exoplayer and gradles
+
+## 3.0.4
+
+- Fix web open player issue
+- update example app Android 12 compatable
+
+## 3.0.3+9
+
+- Fix mimType issue mp3 files from urls without extension #630
+- Fix web Null issue
+
+## 3.0.3+8
+
+- Fix android 12 / api 31 issue.
+- Fix macOs build issue
+- Fix web assets issue for Web
+
+## 3.0.3+7
+
+- Fix android 12 / api 31 issue.
+- Fix macOs build issue
+- Fix web assets issue for Web
+
+## 3.0.3+6
+
+- Added DRM supports
+- Fix playSpeed for WEB.
+
+## 3.0.3+5
+
+- Added pitch controller
+
+## 3.0.3+4
+
+- Updated dependencies
+- Fix duplicate class issue
+- Fix: assetsAudioPlayer.open playSpeed is not work
+
+## 3.0.3+3
+
+- fix duration issue
+
+## 3.0.3+2
+
+- update build number
+
+## 3.0.3+1
+
+- fixed no function for stopForeground
+
+## 3.0.3
+
+- Fix notification issue
+
+## 3.0.2
+
+- Fix version issue
+
+## 3.0.1
+
+- Fix web player
+
+## 3.0.0
+
+- Fix some issues
+- Migrate to null safety
+
+## 2.0.15
+
+- update android 30 and fixed local assets issue
+- should fix android alarm manager issue
+
+## 2.0.14
+
+- update packages
+
+## 2.0.13+9
+
+- fix opening multiple audio player.
+
+## 2.0.13+8
+
+- fix opening multiple audio player.
+
+## 2.0.13+7
+
+- fix version conflicts
+
+## 2.0.13+6
+
+- fix android crash issue
+
+## 2.0.13+5
+
+- fix opened multiple instance for android problem.
+
+## 2.0.13+2
+
+- fixed some issues on ios
+- fix crash issue on android
+
+## 2.0.13+1
+
+- fixed some innues on macos/ios
+
+## 2.0.12
+
+- Fixed AudioType.network networkHeaders
+- Improve documentation
+- CustomPrevIcon fixed
+
+## 2.0.9+2
+
+- Renamed PhoneCallStrategy to AudioFocusStrategy
+- Allow on android to resume native players after focus lost
+
+## 2.0.8+5
+
+- Added Android HeadPhoneStrategy
+- Fix local path file uri (android)
+- Added open multiple calls protection
+- Open uri content on androids
+
+## 2.0.6+7
+
+- Cache now use `http` instead of `dio`
+- Added live tag on notification for LiveStream play (ios)
+- Added audio session id (android only)
+
+## 2.0.5+7
+
+- Added custom error handling (beta)
+- Dispose is now a future
+- Fixed playlist insert / replace
+
+## 2.0.5
+
+- Added Cache management (beta), with Audio.network(url, cached: true)
+
+## 2.0.4+2
+
+- Added HLS, Dash, SmoothStream support on Android
+- Added `laylist.replaceAt` method
+
+## 2.0.3+6
+
+- ExoPlayer network now set `allowCrossProtocolRedirect=true` by default
+- Fixed notification hide on livestream pause (android)
+- Added custom icons for android from drawable names
+- Fixed notification texts on Samsung devices
+
+## 2.0.3+1
+
+- Added custom notification icons for Android (in AndroidManifest.xml)
+- Fixed `seek` and `seekBy` not working on the web
+- `PlayList.startIndex` is now mutable
+- Stop player then call `play` reopen it at `playlist.startIndex`
+- Increased buffer size on android/exoplayer
+- Added keepLoopMode on prev/next
+
+## 2.0.2
+
+- Breaking change : `loop` boolean now enumerate 3 values : `none`, `single` and `playlist`
+
+## 2.0.1+9
+
+- Added `.showNotification = true/false` to hide dynamically displayed notification
+- Added custom action on notif click(android)
+- Added `isBuffering` to `RealtimePlayingInfos`
+- Added `AssetsAudioPlayerGroup` (beta)
+- Added Headers in `Audio.network` & `Audio.liveStream`
+
+## 2.0.1
+
+- Added `.playerState` (play/pause/stop)
+- Stop now ping finish listeners
+
+## 2.0.0+6
+
+- Added MacOS support
+- Fixed gapeless loop (single audio)
+- Fixed audio file notification
+
+## 1.7.0
+
+- Fixed bluetooth on android on some devices
+- Fallback to android native MediaPlayer if exoplayer can't read the file
+- Added `audio.updateMetas` to update notification content after creation
+- Android Seekbar notification is now optional
+- Android usable notification Seekbar
+- Added stop custom notification action
+
+## 1.6.3
+
+- Custom notification icon (android)
+- Custom notification actions
+- Fixed notification close on android
+- Fixed android auto-focus
+- Added playInBackground mode
+- Added shuffle
+
+## 1.6.1
+
+- Playlist is now mutable, we can add audios after creation
+- renamed `ReadingPlaylist get playlist` to `ReadingPlaylist get readingPlaylist`
+- added `Playlist get playlist`
+
+## 1.6.0+4
+
+- Fixed playlist issue on android
+- Fixed issue on bluetooth android play/pause
+- Fixed PlayerBuilder currentPosition
+- Added extra map into audio
+
+## 1.6.0
+
+- Added some checks on swift code
+- Fixed totalDuration or liveStream
+- Fixed ios notifications
+- Added bluetooth headset actions (play/pause/next/prev/stop)
+
+## 1.5.0
+
+- Added `Audio.liveStream(url)`
+- Fixed notification image from assets on android
+- Fixed android notification actions on playlist
+- Added `AudioWidget`
+
+## 1.4.7
+
+- added `package` on assets audios (& notif images)
+- all methods return Future
+- open can throw an exception if the url is not found
+
+## 1.4.6+1
+
+- fixed android notifications actions
+- refactored package, added `src/` and `package` keyword
+- added player_builders
+
+## 1.4.5
+
+- fixed implementation of local file play on iOS
+
+## 1.4.4
+
+- Added notifications on android
+
+## 1.4+3+6
+
+- Beta fix for audio focus
+
+## 1.4+3+5
+
+- Beta implementation of local file play on iOS
+
+## 1.4.3+4
+
+- Moved to last flutter version `>=1.12.13+hotfix.6`
+- Implemented new android `FlutterPlugin`
+- Stop all players while getting a phone call
+- Added `playspeed` as optional parameter on on open()
+
+## 1.4.2+1
+
+- Moved to android ExoPlayer
+- Added `playSpeed` (beta)
+- Added `forwardRewind` (beta)
+- Added `seekBy`
+
+## 1.4.0+1
+
+- Bump gradle versions : `wrapper`=(5.4.1-all) `build:gradle`=(3.5.3)
+
+## 1.4.0
+
+- Added `respectSilentMode` as open optional argument
+- Added `showNotification` on iOS to map with MPNowPlayingInfoCenter (default: false)
+- Added `metas` on audios (title, artist, ...) for notifications
+- Use new plugin build format for iOS
+
+## 1.3.9
+
+- Empty constructor now create a new player
+- Added factory AssetsAudioPlayer.withId()
+- Added `playAndForget` witch create, open, play & dispose the player on finish
+- Added AssetsAudioPlayer.allPlayers() witch returns a map of all players
+- Reworked the android player
+
+## 1.3.8+1
+
+- Added `seek` as optional parameter on `open` method
+
+## 1.3.8
+
+- Fully rebased the web support on html.AudioElement (instead of howler)
+- Fully rebases the ios support on AvPlayer (instead of AvAudioPlayer)
+- Added support for network audios with `.open(Audio.network(url))` on Android/ios/web
+
+## 1.3.7+1
+
+- Added `RealtimePlayingInfos` stream
+
+## 1.3.6+1
+
+- Added volume as optional parameter on open()
+
+## 1.3.6
+
+- Extracted web support to assets_audio_player_web: 1.3.6
+
+## 1.3.5+1
+
+- Volume does not reset anymore on looping audios
+
+## 1.3.4
+
+- Fixed player on Android
+
+## 1.3.3
+
+- Fixed build on Android & iOS
+
+## 1.3.2
+
+- Rewritten the web support, using now https://github.com/florent37/flutter_web_howl
+
+## 1.3.1+2
+
+- Upgraded RxDart dependency
+- fixed lint issues
+- lowerCamelCase AssetsAudioPlayer volumes consts
+
+## 1.3.1
+
+- Fixed build on iOS
+
+## 1.3.0
+
+- Added web support, works only on debug mode
+
+## 1.2.8
+
+- Added constructors
+
+* AssetsAudioPlayer.newPlayer
+* AssetsAudioPlayer(id: "PLAYER_ID")
+
+to create new players and play multiples songs in parallel
+
+the default constructor AssetsAudioPlayer() still works as usual
+
+## 1.2.7
+
+- Added "volume" property (listen/set)
+
+## 1.2.6
+
+- Added an "autoPlay" optional attribute to open methods
+
+## 1.2.5
+
+- Compatible with Swift 5
+
+## 1.2.4
+
+- Added playlist
+
+## 1.2.3
+
+- Added playlist (beta)
+
+## 1.2.1
+
+- Added looping setter/getter
+
+## 1.2.0
+
+- Upgraded RxDart to 0.23.1
+- Fixed assets playing on iOS
+- Fixed playing location on Android
+
+## 0.0.1
+
+- initial release.
diff --git a/assets_audio_player_web/LICENSE b/assets_audio_player_web/LICENSE
new file mode 100644
index 00000000..d9e3d01d
--- /dev/null
+++ b/assets_audio_player_web/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright 2019 Florent37
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/assets_audio_player_web/README.md b/assets_audio_player_web/README.md
new file mode 100644
index 00000000..a55c6f83
--- /dev/null
+++ b/assets_audio_player_web/README.md
@@ -0,0 +1,1022 @@
+# ๐ง assets_audio_player ๐
+
+[](
+https://pub.dartlang.org/packages/assets_audio_player)
+
+
+
+
+
+
+[](https://codemagic.io/apps/5ed8002fe1907b001c67db52/5ed8002fe1907b001c67db51/latest_build)
+[](https://www.codefactor.io/repository/github/florent37/flutter-assetsaudioplayer)
+
+Play music/audio stored in assets files (simultaneously) directly from Flutter (android / ios / web / macos).
+
+You can also use play audio files from **network** using their url, **radios/livestream** and **local files**
+
+**Notification can be displayed on Android & iOS, and bluetooth actions are handled**
+
+```yaml
+flutter:
+ assets:
+ - assets/audios/
+```
+
+```Dart
+AssetsAudioPlayer.newPlayer().open(
+ Audio("assets/audios/song1.mp3"),
+ autoPlay: true,
+ showNotification: true,
+);
+```
+
+[](https://github.com/florent37/Flutter-AssetsAudioPlayer)
+[](https://github.com/florent37/Flutter-AssetsAudioPlayer)
+
+# ๐ฅ Import
+
+```yaml
+dependencies:
+ assets_audio_player: ^2.0.13
+
+or
+
+assets_audio_player:
+git:
+url: https://github.com/florent37/Flutter-AssetsAudioPlayer.git
+ref: master
+
+ref can be latest commit id.
+```
+
+**Works with `flutter: ">=1.12.13+hotfix.6 <2.0.0"`, be sure to upgrade your sdk**
+
+You like the package ? buy me a kofi :)
+
+
+
+
+
+
+ Audio Source |
+ Android |
+ iOS |
+ Web |
+ MacOS |
+
+
+
+
+ ๐๏ธ Asset file (asset path) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ Network file (url) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ Local file (path) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ป Network LiveStream / radio (url) (Default, HLS, Dash, SmoothStream) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+
+
+
+
+
+ Feature |
+ Android |
+ iOS |
+ Web |
+ MacOS |
+
+
+
+
+ ๐ถ Multiple players |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฝ Open Playlist |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฌSystem notification |
+ โ
|
+ โ
|
+ ๐ซ |
+ ๐ซ |
+
+
+ ๐ง Bluetooth actions |
+ โ
|
+ โ
|
+ ๐ซ |
+ ๐ซ |
+
+
+ ๐ Respect System silent mode |
+ โ
|
+ โ
|
+ ๐ซ |
+ ๐ซ |
+
+
+ ๐ Pause on phone call |
+ โ
|
+ โ
|
+ ๐ซ |
+ ๐ซ |
+
+
+
+
+
+
+
+ Commands |
+ Android |
+ iOS |
+ Web |
+ MacOS |
+
+
+
+
+ โถ Play |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โธ Pause |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โน Stop |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โฉ Seek(position) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โชโฉ SeekBy(position) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โฉ Forward(speed) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โช Rewind(speed) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โญ Next |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โฎ Prev |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+
+
+
+
+
+ Widgets |
+ Android |
+ iOS |
+ Web |
+ MacOS |
+
+
+
+
+ ๐ฆ Audio Widget |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฆ Widget Builders |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฆ AudioPlayer Builders Extension |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+
+
+
+
+
+ Properties |
+ Android |
+ iOS |
+ Web |
+ MacOS |
+
+
+
+
+ ๐ Loop |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ Shuffle |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ get/set Volume |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ โฉ get/set Play Speed |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+
+
+
+
+
+ Listeners |
+ Android |
+ iOS |
+ Web |
+ MacOS |
+
+
+
+
+ ๐ฆป Listener onReady(completeDuration) |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฆป Listener currentPosition |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฆป Listener finished |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฆป Listener buffering |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฆป Listener volume |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+ ๐ฆปListener Play Speed |
+ โ
|
+ โ
|
+ โ
|
+ โ
|
+
+
+
+
+# ๐ Import assets files
+
+No needed to copy songs to a media cache, with assets_audio_player you can open them directly from the assets.
+
+1. Create an audio directory in your assets (not necessary named "audios")
+2. Declare it inside your pubspec.yaml
+
+```yaml
+flutter:
+ assets:
+ - assets/audios/
+```
+
+## ๐ ๏ธ Getting Started
+
+```Dart
+final assetsAudioPlayer = AssetsAudioPlayer();
+
+assetsAudioPlayer.open(
+ Audio("assets/audios/song1.mp3"),
+);
+```
+
+You can also play *network songs* from *url*
+
+```Dart
+final assetsAudioPlayer = AssetsAudioPlayer();
+
+try {
+ await assetsAudioPlayer.open(
+ Audio.network("http://www.mysite.com/myMp3file.mp3"),
+ );
+} catch (t) {
+ //mp3 unreachable
+}
+```
+
+*LiveStream / Radio* from *url*
+
+**The main difference with network, if you pause/play, on livestream it will resume to present duration**
+
+```Dart
+final assetsAudioPlayer = AssetsAudioPlayer();
+
+try {
+ await assetsAudioPlayer.open(
+ Audio.liveStream(MY_LIVESTREAM_URL),
+ );
+} catch (t) {
+ //stream unreachable
+}
+```
+
+And play *songs from file*
+
+```Dart
+//create a new player
+final assetsAudioPlayer = AssetsAudioPlayer();
+
+assetsAudioPlayer.open(
+ Audio.file(FILE_URI),
+);
+```
+
+for file uri, please look at https://pub.dev/packages/path_provider
+
+```Dart
+assetsAudioPlayer.playOrPause();
+assetsAudioPlayer.play();
+assetsAudioPlayer.pause();
+```
+
+```Dart
+assetsAudioPlayer.seek(Duration to);
+assetsAudioPlayer.seekBy(Duration by);
+```
+
+```Dart
+assetsAudioPlayer.forwardRewind(double speed);
+//if positive, forward, if negative, rewind
+```
+
+```Dart
+assetsAudioPlayer.stop();
+```
+
+
+# Notifications
+
+
+[](https://github.com/florent37/Flutter-AssetsAudioPlayer)
+
+[](https://github.com/florent37/Flutter-AssetsAudioPlayer)
+
+on iOS, it will use `MPNowPlayingInfoCenter`
+
+1. Add metas inside your audio
+
+```dart
+final audio = Audio("/assets/audio/country.mp3",
+ metas: Metas(
+ title: "Country",
+ artist: "Florent Champigny",
+ album: "CountryAlbum",
+ image: MetasImage.asset("assets/images/country.jpg"), //can be MetasImage.network
+ ),
+ );
+```
+
+2. open with `showNotification: true`
+
+```dart
+_player.open(audio, showNotification: true)
+```
+
+## Custom notification
+
+Custom icon (android only)
+
+### By ResourceName
+
+Make sur you added those icons inside your `android/res/drawable` **!!! not on flutter assets !!!!**
+
+```dart
+await _assetsAudioPlayer.open(
+ myAudio,
+ showNotification: true,
+ notificationSettings: NotificationSettings(
+ customStopIcon: AndroidResDrawable(name: "ic_stop_custom"),
+ customPauseIcon: AndroidResDrawable(name:"ic_pause_custom"),
+ customPlayIcon: AndroidResDrawable(name:"ic_play_custom"),
+ customPrevIcon: AndroidResDrawable(name:"ic_prev_custom"),
+ customNextIcon: AndroidResDrawable(name:"ic_next_custom"),
+ )
+
+```
+
+And don't forget tell proguard to keep those resources for release mode
+
+(part Keeping Resources)
+
+https://sites.google.com/a/android.com/tools/tech-docs/new-build-system/resource-shrinking
+
+```xml
+
+
+
+```
+
+### By Manifest
+
+1. Add your icon into your android's `res` folder (android/app/src/main/res)
+
+2. Reference this icon into your AndroidManifest (android/app/src/main/AndroidManifest.xml)
+
+```xml
+
+```
+
+You can also change actions icons
+
+```
+
+
+
+
+
+```
+
+## Handle notification click (android)
+
+Add in main
+```dart
+AssetsAudioPlayer.setupNotificationsOpenAction((notification) {
+ //custom action
+ return true; //true : handled, does not notify others listeners
+ //false : enable others listeners to handle it
+});
+```
+
+Then if you want a custom action on widget
+
+```dart
+AssetsAudioPlayer.addNotificationOpenAction((notification) {
+ //custom action
+ return false; //true : handled, does not notify others listeners
+ //false : enable others listeners to handle it
+});
+```
+
+## Custom actions
+
+You can enable/disable a notification action
+
+```dart
+open(AUDIO,
+ showNotification: true,
+ notificationSettings: NotificationSettings(
+ prevEnabled: false, //disable the previous button
+
+ //and have a custom next action (will disable the default action)
+ customNextAction: (player) {
+ print("next");
+ }
+ )
+
+)
+```
+
+## Update audio's metas / notification content
+
+After your audio creation, just call
+
+```dart
+audio.updateMetas(
+ player: _assetsAudioPlayer, //add the player if the audio is actually played
+ title: "My new title",
+ artist: "My new artist",
+ //if I not provide a new album, it keep the old one
+ image: MetasImage.network(
+ //my new image url
+ ),
+);
+```
+
+## Bluetooth Actions
+
+You have to enable notification to make them work
+
+Available remote commands :
+
+- Play / Pause
+- Next
+- Prev
+- Stop
+
+## HeadPhone Strategy
+
+(Only for Android for now)
+
+while opening a song/playlist, add a strategy
+
+```dart
+assetsAudioPlayer.open(
+ ...
+ headPhoneStrategy: HeadPhoneStrategy.pauseOnUnplug,
+ //headPhoneStrategy: HeadPhoneStrategy.none, //default
+ //headPhoneStrategy: HeadPhoneStrategy.pauseOnUnplugPlayOnPlug,
+)
+```
+
+If you want to make it work on bluetooth too, you'll have to add the BLUETOOTH permission inside your AndroidManifest.xml
+
+```xml
+
+```
+
+# โ Play in parallel / simultaneously
+
+You can create new AssetsAudioPlayer using AssetsAudioPlayer.newPlayer(),
+which will play songs in a different native Media Player
+
+This will enable to play two songs simultaneously
+
+You can have as many player as you want !
+
+```dart
+///play 3 songs in parallel
+AssetsAudioPlayer.newPlayer().open(
+ Audio("assets/audios/song1.mp3")
+);
+AssetsAudioPlayer.newPlayer().open(
+ Audio("assets/audios/song2.mp3")
+);
+
+//another way, with create, open, play & dispose the player on finish
+AssetsAudioPlayer.playAndForget(
+ Audio("assets/audios/song3.mp3")
+);
+```
+
+Each player has an unique generated `id`, you can retrieve or create them manually using
+
+```dart
+final player = AssetsAudioPlayer.withId(id: "MY_UNIQUE_ID");
+```
+
+# ๐๏ธ Playlist
+```Dart
+assetsAudioPlayer.open(
+ Playlist(
+ audios: [
+ Audio("assets/audios/song1.mp3"),
+ Audio("assets/audios/song2.mp3")
+ ]
+ ),
+ loopMode: LoopMode.playlist //loop the full playlist
+);
+
+assetsAudioPlayer.next();
+assetsAudioPlayer.prev();
+assetsAudioPlayer.playlistPlayAtIndex(1);
+```
+
+## Audio Widget
+
+If you want a more flutter way to play audio, try the `AudioWidget` !
+
+[](https://github.com/florent37/Flutter-AssetsAudioPlayer)
+
+```dart
+//inside a stateful widget
+
+bool _play = false;
+
+@override
+Widget build(BuildContext context) {
+ return AudioWidget.assets(
+ path: "assets/audios/country.mp3",
+ play: _play,
+ child: RaisedButton(
+ child: Text(
+ _play ? "pause" : "play",
+ ),
+ onPressed: () {
+ setState(() {
+ _play = !_play;
+ });
+ }
+ ),
+ onReadyToPlay: (duration) {
+ //onReadyToPlay
+ },
+ onPositionChanged: (current, duration) {
+ //onPositionChanged
+ },
+ );
+}
+```
+
+How to ๐ stop ๐ the AudioWidget ?
+
+Just remove the Audio from the tree !
+Or simply keep `play: false`
+
+## ๐ง Listeners
+
+All listeners exposes Streams
+Using RxDart, AssetsAudioPlayer exposes some listeners as ValueObservable (Observable that provides synchronous access to the last emitted item);
+
+### ๐ต Current song
+```Dart
+//The current playing audio, filled with the total song duration
+assetsAudioPlayer.current //ValueObservable
+
+//Retrieve directly the current played asset
+final PlayingAudio playing = assetsAudioPlayer.current.value;
+
+//Listen to the current playing song
+assetsAudioPlayer.current.listen((playingAudio){
+ final asset = playingAudio.assetAudio;
+ final songDuration = playingAudio.duration;
+})
+```
+
+### โ Current song duration
+
+```Dart
+//Listen to the current playing song
+final duration = assetsAudioPlayer.current.value.duration;
+```
+
+### โณ Current position (in seconds)
+
+```Dart
+assetsAudioPlayer.currentPosition //ValueObservable
+
+//retrieve directly the current song position
+final Duration position = assetsAudioPlayer.currentPosition.value;
+
+return StreamBuilder(
+ stream: assetsAudioPlayer.currentPosition,
+ builder: (context, asyncSnapshot) {
+ final Duration duration = asyncSnapshot.data;
+ return Text(duration.toString());
+ }),
+```
+
+or use a PlayerBuilder !
+
+```dart
+PlayerBuilder.currentPosition(
+ player: _assetsAudioPlayer,
+ builder: (context, duration) {
+ return Text(duration.toString());
+ }
+)
+```
+
+or Player Builder Extension
+
+```dart
+_assetsAudioPlayer.builderCurrentPosition(
+ builder: (context, duration) {
+ return Text(duration.toString());
+ }
+)
+```
+
+### โถ IsPlaying
+boolean observable representing the current mediaplayer playing state
+```Dart
+assetsAudioPlayer.isPlaying // ValueObservable
+
+//retrieve directly the current player state
+final bool playing = assetsAudioPlayer.isPlaying.value;
+
+//will follow the AssetsAudioPlayer playing state
+return StreamBuilder(
+ stream: assetsAudioPlayer.isPlaying,
+ builder: (context, asyncSnapshot) {
+ final bool isPlaying = asyncSnapshot.data;
+ return Text(isPlaying ? "Pause" : "Play");
+ }),
+```
+
+or use a PlayerBuilder !
+
+```dart
+PlayerBuilder.isPlaying(
+ player: _assetsAudioPlayer,
+ builder: (context, isPlaying) {
+ return Text(isPlaying ? "Pause" : "Play");
+ }
+)
+```
+
+or Player Builder Extension
+
+```dart
+_assetsAudioPlayer.builderIsPlaying(
+ builder: (context, isPlaying) {
+ return Text(isPlaying ? "Pause" : "Play");
+ }
+)
+```
+
+### ๐ Volume
+
+Change the volume (between 0.0 & 1.0)
+```Dart
+assetsAudioPlayer.setVolume(0.5);
+```
+
+The media player can follow the system "volume mode" (vibrate, muted, normal)
+Simply set the `respectSilentMode` optional parameter as `true`
+
+```dart
+_player.open(PLAYABLE, respectSilentMode: true);
+```
+
+https://developer.android.com/reference/android/media/AudioManager.html?hl=fr#getRingerMode()
+
+https://developer.apple.com/documentation/avfoundation/avaudiosessioncategorysoloambient
+
+
+Listen the volume
+
+```dart
+return StreamBuilder(
+ stream: assetsAudioPlayer.volume,
+ builder: (context, asyncSnapshot) {
+ final double volume = asyncSnapshot.data;
+ return Text("volume : $volume");
+ }),
+```
+
+or use a PlayerBuilder !
+
+```dart
+PlayerBuilder.volume(
+ player: _assetsAudioPlayer,
+ builder: (context, volume) {
+ return Text("volume : $volume");
+ }
+)
+```
+
+### โ Finished
+
+Called when the current song has finished to play,
+
+it gives the Playing audio that just finished
+
+```Dart
+assetsAudioPlayer.playlistAudioFinished //ValueObservable
+
+assetsAudioPlayer.playlistAudioFinished.listen((Playing playing){
+
+})
+```
+
+Called when the complete playlist has finished to play
+
+```Dart
+assetsAudioPlayer.playlistFinished //ValueObservable
+
+assetsAudioPlayer.playlistFinished.listen((finished){
+
+})
+```
+
+### ๐ Looping
+
+```Dart
+final LoopMode loopMode = assetsAudioPlayer.loop;
+// possible values
+// LoopMode.none : not looping
+// LoopMode.single : looping a single audio
+// LoopMode.playlist : looping the fyll playlist
+
+assetsAudioPlayer.setLoopMode(LoopMode.single);
+
+assetsAudioPlayer.loopMode.listen((loopMode){
+ //listen to loop
+})
+
+assetsAudioPlayer.toggleLoop(); //toggle the value of looping
+```
+
+
+# Error Handling
+
+By default, on playing error, it stop the audio
+
+BUT you can add a custom behavior
+
+```dart
+_player.onErrorDo = (handler){
+ handler.player.stop();
+};
+```
+
+Open another audio
+
+```dart
+_player.onErrorDo = (handler){
+ handler.player.open(ANOTHER_AUDIO);
+};
+```
+
+Try to open again on same position
+
+```dart
+_player.onErrorDo = (handler){
+ handler.player.open(
+ handler.playlist.copyWith(
+ startIndex: handler.playlistIndex
+ ),
+ seek: handler.currentPosition
+ );
+};
+```
+
+# Network Policies (android/iOS/macOS)
+
+Android only allow HTTPS calls, you will have an error if you're using HTTP,
+don't forget to add INTERNET permission and seet `usesCleartextTraffic="true"` in your **AndroidManifest.xml**
+
+```
+
+
+
+
+ ...
+
+
+```
+
+iOS only allow HTTPS calls, you will have an error if you're using HTTP,
+don't forget to edit your **info.plist** and set `NSAppTransportSecurity` to `NSAllowsArbitraryLoads`
+
+```
+NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
+```
+
+To enable http calls on macOs, you have to add input/output calls capabilities into `info.plist`
+
+```
+NSAppTransportSecurity
+
+ NSAllowsArbitraryLoads
+
+
+UIBackgroundModes
+
+ audio
+ fetch
+
+com.apple.security.network.client
+
+```
+
+and in your
+
+`Runner/DebugProfile.entitlements`
+
+add
+
+```
+com.apple.security.network.client
+
+```
+
+Complete `Runner/DebugProfile.entitlements`
+
+```
+
+
+
+
+ com.apple.security.app-sandbox
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.network.server
+
+ com.apple.security.network.client
+
+
+
+```
+
+# ๐ถ Musics
+
+All musics used in the samples came from https://www.freemusicarchive.org/
diff --git a/assets_audio_player_web/analysis_options.yaml b/assets_audio_player_web/analysis_options.yaml
new file mode 100644
index 00000000..a3be6b82
--- /dev/null
+++ b/assets_audio_player_web/analysis_options.yaml
@@ -0,0 +1 @@
+include: package:flutter_lints/flutter.yaml
\ No newline at end of file
diff --git a/assets_audio_player_web/lib/assets_audio_player_web.dart b/assets_audio_player_web/lib/assets_audio_player_web.dart
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/assets_audio_player_web/lib/assets_audio_player_web.dart
@@ -0,0 +1 @@
+
diff --git a/assets_audio_player_web/lib/generated/i18n.dart b/assets_audio_player_web/lib/generated/i18n.dart
new file mode 100644
index 00000000..6d0e90c8
--- /dev/null
+++ b/assets_audio_player_web/lib/generated/i18n.dart
@@ -0,0 +1,130 @@
+// import 'dart:async';
+
+// import 'package:flutter/foundation.dart';
+// import 'package:flutter/material.dart';
+
+// // ignore_for_file: non_constant_identifier_names
+// // ignore_for_file: camel_case_types
+// // ignore_for_file: prefer_single_quotes
+
+// // This file is automatically generated. DO NOT EDIT, all your changes would be lost.
+// class S implements WidgetsLocalizations {
+// const S();
+
+// static S current;
+
+// static const GeneratedLocalizationsDelegate delegate =
+// GeneratedLocalizationsDelegate();
+
+// static S of(BuildContext context) => Localizations.of(context, S);
+
+// @override
+// TextDirection get textDirection => TextDirection.ltr;
+// }
+
+// class $en extends S {
+// const $en();
+// }
+
+// class GeneratedLocalizationsDelegate extends LocalizationsDelegate {
+// const GeneratedLocalizationsDelegate();
+
+// List get supportedLocales {
+// return const [
+// Locale('en', ''),
+// ];
+// }
+
+// LocaleListResolutionCallback listResolution(
+// {Locale fallback, bool withCountry = true}) {
+// return (List locales, Iterable supported) {
+// if (locales == null || locales.isEmpty) {
+// return fallback ?? supported.first;
+// } else {
+// return _resolve(locales.first, fallback, supported, withCountry);
+// }
+// };
+// }
+
+// LocaleResolutionCallback resolution(
+// {Locale fallback, bool withCountry = true}) {
+// return (Locale locale, Iterable supported) {
+// return _resolve(locale, fallback, supported, withCountry);
+// };
+// }
+
+// @override
+// Future load(Locale locale) {
+// final String lang = getLang(locale);
+// if (lang != null) {
+// switch (lang) {
+// case 'en':
+// S.current = const $en();
+// return SynchronousFuture(S.current);
+// default:
+// // NO-OP.
+// }
+// }
+// S.current = const S();
+// return SynchronousFuture(S.current);
+// }
+
+// @override
+// bool isSupported(Locale locale) => _isSupported(locale, true);
+
+// @override
+// bool shouldReload(GeneratedLocalizationsDelegate old) => false;
+
+// ///
+// /// Internal method to resolve a locale from a list of locales.
+// ///
+// Locale _resolve(Locale locale, Locale fallback, Iterable supported,
+// bool withCountry) {
+// if (locale == null || !_isSupported(locale, withCountry)) {
+// return fallback ?? supported.first;
+// }
+
+// final Locale languageLocale = Locale(locale.languageCode, '');
+// if (supported.contains(locale)) {
+// return locale;
+// } else if (supported.contains(languageLocale)) {
+// return languageLocale;
+// } else {
+// final Locale fallbackLocale = fallback ?? supported.first;
+// return fallbackLocale;
+// }
+// }
+
+// ///
+// /// Returns true if the specified locale is supported, false otherwise.
+// ///
+// bool _isSupported(Locale locale, bool withCountry) {
+// if (locale != null) {
+// for (Locale supportedLocale in supportedLocales) {
+// // Language must always match both locales.
+// if (supportedLocale.languageCode != locale.languageCode) {
+// continue;
+// }
+
+// // If country code matches, return this locale.
+// if (supportedLocale.countryCode == locale.countryCode) {
+// return true;
+// }
+
+// // If no country requirement is requested, check if this locale has no country.
+// if (true != withCountry &&
+// (supportedLocale.countryCode == null ||
+// supportedLocale.countryCode.isEmpty)) {
+// return true;
+// }
+// }
+// }
+// return false;
+// }
+// }
+
+// String getLang(Locale l) => l == null
+// ? null
+// : l.countryCode != null && l.countryCode.isEmpty
+// ? l.languageCode
+// : l.toString();
diff --git a/assets_audio_player_web/lib/web/abstract_web_player.dart b/assets_audio_player_web/lib/web/abstract_web_player.dart
new file mode 100644
index 00000000..5ecc9e5b
--- /dev/null
+++ b/assets_audio_player_web/lib/web/abstract_web_player.dart
@@ -0,0 +1,79 @@
+import 'dart:async';
+
+import 'package:web/web.dart' as web;
+import 'package:flutter/services.dart';
+
+/// Web Player
+abstract class WebPlayer {
+ final MethodChannel channel;
+
+ static const methodPosition = 'player.position';
+ static const methodVolume = 'player.volume';
+ static const methodPlaySpeed = 'player.playSpeed';
+ static const methodFinished = 'player.finished';
+ static const methodIsPlaying = 'player.isPlaying';
+ static const methodIsBuffering = 'player.isBuffering';
+ static const methodCurrent = 'player.current';
+ static const methodForwardRewindSpeed = 'player.forwardRewind';
+
+ WebPlayer({required this.channel});
+
+ num get volume;
+
+ set volume(num volume);
+
+ num get playSpeed;
+
+ set playSpeed(num playSpeed);
+
+ bool get isPlaying;
+
+ set isPlaying(bool value);
+
+ num get currentPosition;
+
+ void play();
+
+ void pause();
+
+ void stop();
+
+ String findAssetPath(String path, String audioType, {String? package}) {
+ if (audioType == 'network' ||
+ audioType == 'liveStream' ||
+ audioType == 'file') {
+ return path;
+ }
+ // in web, assets are packaged in a /assets/ folder
+ // if you want '/asset/3' as described in pubspec
+ // it will be in /assets/asset/3
+
+ /* for release mode, need to change the 'url', remove the /#/ and add /asset before */
+ if (path.startsWith('/')) {
+ path = path.replaceFirst('/', '');
+ }
+ if (package != null) {
+ path = 'packages/$package/$path';
+ }
+
+ path = ('${web.window.location.href.replaceAll('/#/', '')}/assets/$path');
+ return path;
+ }
+
+ Future open({
+ required String path,
+ required String audioType,
+ bool autoStart = false,
+ double volume = 1,
+ double? seek,
+ double? playSpeed,
+ Map? networkHeaders,
+ String? package,
+ });
+
+ void seek({double to});
+
+ void forwardRewind(double speed);
+
+ void loopSingleAudio(bool loop);
+}
diff --git a/assets_audio_player_web/lib/web/assets_audio_player_web.dart b/assets_audio_player_web/lib/web/assets_audio_player_web.dart
new file mode 100644
index 00000000..21a6f68d
--- /dev/null
+++ b/assets_audio_player_web/lib/web/assets_audio_player_web.dart
@@ -0,0 +1,115 @@
+import 'dart:async';
+
+import 'package:assets_audio_player_web/web/web_player_html.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_web_plugins/flutter_web_plugins.dart';
+
+import 'abstract_web_player.dart';
+
+/// Web plugin
+class AssetsAudioPlayerWebPlugin {
+ final Map _players = {};
+ final BinaryMessenger messenger;
+
+ AssetsAudioPlayerWebPlugin({required this.messenger});
+
+ WebPlayer _newPlayer(String id, MethodChannel channel) {
+ return WebPlayerHtml(
+ channel: channel,
+ );
+ }
+
+ WebPlayer _getOrCreate(String id) {
+ if (_players.containsKey(id)) {
+ return _players[id]!;
+ } else {
+ final newPlayer = _newPlayer(
+ id,
+ MethodChannel(
+ 'assets_audio_player/' + id,
+ const StandardMethodCodec(),
+ messenger,
+ ));
+ _players[id] = newPlayer;
+ return newPlayer;
+ }
+ }
+
+ static void registerWith(Registrar registrar) {
+ final channel = MethodChannel(
+ 'assets_audio_player',
+ const StandardMethodCodec(),
+ registrar,
+ );
+ final instance = AssetsAudioPlayerWebPlugin(messenger: registrar);
+ channel.setMethodCallHandler(instance.handleMethodCall);
+ }
+
+ Future handleMethodCall(MethodCall call) async {
+ //print(call.method);
+ switch (call.method) {
+ case 'isPlaying':
+ final String id = call.arguments['id'];
+ return Future.value(_getOrCreate(id).isPlaying);
+ case 'play':
+ final String id = call.arguments['id'];
+ _getOrCreate(id).play();
+ return Future.value(true);
+ case 'pause':
+ final String id = call.arguments['id'];
+ _getOrCreate(id).pause();
+ return Future.value(true);
+ case 'stop':
+ final String id = call.arguments['id'];
+ _getOrCreate(id).stop();
+ return Future.value(true);
+ case 'volume':
+ final String id = call.arguments['id'];
+ final double volume = (call.arguments['volume'] as num).toDouble();
+ _getOrCreate(id).volume = volume;
+ return Future.value(true);
+ case 'playSpeed':
+ final String id = call.arguments['id'];
+ final double playSpeed =
+ (call.arguments['playSpeed'] as num).toDouble();
+ _getOrCreate(id).playSpeed = playSpeed;
+ return Future.value(true);
+ case 'forwardRewind':
+ final String id = call.arguments['id'];
+ final double speed = (call.arguments['speed'] as num).toDouble();
+ _getOrCreate(id).forwardRewind(speed);
+ return Future.value(true);
+ case 'loopSingleAudio':
+ final String id = call.arguments['id'];
+ final bool loop = call.arguments['loop'];
+ _getOrCreate(id).loopSingleAudio(loop);
+ return Future.value(true);
+ case 'seek':
+ final String id = call.arguments['id'];
+ final double to = (call.arguments['to'] as num).toDouble();
+ _getOrCreate(id).seek(to: to);
+ return Future.value(true);
+ case 'open':
+ final String id = call.arguments['id'];
+ final String path = call.arguments['path'];
+ final String audioType = call.arguments['audioType'];
+ final double volume = (call.arguments['volume'] as num).toDouble();
+ final double? seek = (call.arguments['seek'] as num?)?.toDouble();
+ final double playSpeed =
+ (call.arguments['playSpeed'] as num).toDouble();
+ final bool autoStart = call.arguments['autoStart'] ?? false;
+ final Map? networkHeaders = call.arguments['networkHeaders'];
+ final String? package = call.arguments['package'];
+ return _getOrCreate(id).open(
+ path: path,
+ audioType: audioType,
+ volume: volume,
+ seek: seek,
+ playSpeed: playSpeed,
+ autoStart: autoStart,
+ networkHeaders: networkHeaders,
+ package: package,
+ );
+ }
+ }
+}
diff --git a/assets_audio_player_web/lib/web/web_player_html.dart b/assets_audio_player_web/lib/web/web_player_html.dart
new file mode 100644
index 00000000..9d1a313d
--- /dev/null
+++ b/assets_audio_player_web/lib/web/web_player_html.dart
@@ -0,0 +1,235 @@
+import 'dart:async';
+
+import 'package:web/web.dart' as web;
+import 'package:flutter/services.dart';
+
+import 'abstract_web_player.dart';
+
+/// Web Player
+class WebPlayerHtml extends WebPlayer {
+ @override
+ WebPlayerHtml({required MethodChannel channel}) : super(channel: channel);
+
+ StreamSubscription? _onEndListener;
+ StreamSubscription? _onCanPlayListener;
+
+ void _clearListeners() {
+ _onEndListener?.cancel();
+ _onCanPlayListener?.cancel();
+ }
+
+ web.HTMLAudioElement? _audioElement;
+
+ @override
+ num get volume => _audioElement?.volume ?? 1.0;
+
+ @override
+ set volume(num volume) {
+ _audioElement?.volume = volume;
+ channel.invokeMethod(WebPlayer.methodVolume, volume);
+ }
+
+ @override
+ num get playSpeed => _audioElement?.playbackRate ?? 1.0;
+
+ @override
+ set playSpeed(num playSpeed) {
+ _audioElement?.playbackRate = playSpeed;
+ channel.invokeMethod(WebPlayer.methodPlaySpeed, playSpeed);
+ }
+
+ bool _isPlaying = false;
+
+ @override
+ bool get isPlaying => _isPlaying;
+
+ @override
+ set isPlaying(bool value) {
+ _isPlaying = value;
+ channel.invokeMethod(WebPlayer.methodIsPlaying, value);
+ if (value) {
+ _listenPosition();
+ } else {
+ _stopListenPosition();
+ }
+ }
+
+ @override
+ num get currentPosition => _audioElement?.currentTime ?? 0;
+
+ var __listenPosition = false;
+
+ num? _durationMs;
+ num? _position;
+
+ void _listenPosition() async {
+ __listenPosition = true;
+ await Future.doWhile(() {
+ final durationMs = (_audioElement?.duration ?? 0) * 1000;
+ if (durationMs != _durationMs) {
+ _durationMs = durationMs;
+ channel.invokeMethod(
+ WebPlayer.methodCurrent,
+ {'totalDurationMs': durationMs},
+ );
+ }
+
+ if (_position != currentPosition) {
+ _position = currentPosition;
+ final positionMs = currentPosition * 1000;
+ channel.invokeMethod(WebPlayer.methodPosition, positionMs);
+ }
+ return Future.delayed(const Duration(milliseconds: 200)).then((value) {
+ return __listenPosition;
+ });
+ });
+ }
+
+ void _stopListenPosition() {
+ __listenPosition = false;
+ }
+
+ @override
+ void play() {
+ if (_audioElement != null) {
+ isPlaying = true;
+ forwardHandler?.stop();
+ _audioElement?.play();
+ }
+ }
+
+ @override
+ void pause() {
+ if (_audioElement != null) {
+ isPlaying = false;
+ forwardHandler?.stop();
+ _audioElement?.pause();
+ }
+ }
+
+ @override
+ void stop() {
+ forwardHandler?.stop();
+ forwardHandler = null;
+
+ _clearListeners();
+
+ if (_audioElement != null) {
+ isPlaying = false;
+ pause();
+ _audioElement?.currentTime = 0;
+ channel.invokeMethod(WebPlayer.methodPosition, 0);
+ }
+ }
+
+ @override
+ Future open({
+ required String path,
+ required String audioType,
+ String? package,
+ bool autoStart = false,
+ double volume = 1,
+ double? seek,
+ double? playSpeed,
+ Map? networkHeaders,
+ }) async {
+ stop();
+ _durationMs = null;
+ _position = null;
+ _audioElement = web.HTMLAudioElement();
+
+ // it seems html audielement cannot take networkHeaders :'(
+
+ _onEndListener = _audioElement?.onEnded.listen((event) {
+ channel.invokeMethod(WebPlayer.methodFinished, true);
+ });
+
+ _onCanPlayListener = _audioElement?.onCanPlay.listen((event) {
+ if (autoStart) {
+ play();
+ }
+
+ this.volume = volume;
+ final durationMs = (_audioElement?.duration ?? 0) * 1000;
+
+ if (durationMs != _durationMs) {
+ _durationMs = durationMs;
+ channel.invokeMethod(
+ WebPlayer.methodCurrent,
+ {'totalDurationMs': durationMs},
+ );
+ }
+
+ if (seek != null) {
+ this.seek(to: seek);
+ }
+
+ if (playSpeed != null) {
+ this.playSpeed = playSpeed;
+ }
+
+ // single event
+ _onCanPlayListener?.cancel();
+ _onCanPlayListener = null;
+ });
+
+ // The `src` of the _audioElement is the last property that is set, so all
+ // the listeners for the events that the plugin cares about are attached.
+ _audioElement!.src = findAssetPath(
+ path,
+ audioType,
+ package: package,
+ );
+ }
+
+ @override
+ void seek({double? to}) {
+ if (_audioElement != null && to != null) {
+ /// Explainer on the `/1000`
+ /// The value being sent down from the plugin
+ /// is in `milliseconds` and `AudioElement` uses seconds.
+ /// This is to convert it.
+ final toInSeconds = to / 1000;
+ _audioElement?.currentTime = toInSeconds;
+ }
+ }
+
+ @override
+ void loopSingleAudio(bool loop) {
+ _audioElement?.loop = loop;
+ }
+
+ void seekBy({required double by}) {
+ final current = currentPosition;
+ final to = current + by;
+ seek(to: to);
+ }
+
+ ForwardHandler? forwardHandler;
+ @override
+ void forwardRewind(double speed) {
+ pause();
+ channel.invokeMethod(WebPlayer.methodForwardRewindSpeed, speed);
+ forwardHandler?.stop();
+ forwardHandler = ForwardHandler();
+ _listenPosition(); // for this usecase, enable listen position
+ forwardHandler?.start(this, speed);
+ }
+}
+
+class ForwardHandler {
+ bool _isEnabled = false;
+ static const _timelapse = 300;
+
+ void start(WebPlayerHtml player, double speed) async {
+ _isEnabled = true;
+ while (_isEnabled) {
+ player.seekBy(by: speed * _timelapse);
+ await Future.delayed(const Duration(milliseconds: _timelapse));
+ }
+ }
+
+ void stop() {
+ _isEnabled = false;
+ }
+}
diff --git a/assets_audio_player_web/publish.sh b/assets_audio_player_web/publish.sh
new file mode 100755
index 00000000..ddec5068
--- /dev/null
+++ b/assets_audio_player_web/publish.sh
@@ -0,0 +1,2 @@
+flutter format lib/
+pub publish --force
\ No newline at end of file
diff --git a/assets_audio_player_web/pubspec.yaml b/assets_audio_player_web/pubspec.yaml
new file mode 100644
index 00000000..e558b8c8
--- /dev/null
+++ b/assets_audio_player_web/pubspec.yaml
@@ -0,0 +1,36 @@
+name: assets_audio_player_web
+description: Web plugin for assets_audio_player, play music/audio stored in assets files directly from Flutter.
+version: 3.1.2
+#author: Florent Champigny
+homepage: https://github.com/florent37/Flutter-AssetsAudioPlayer
+
+environment:
+ sdk: ">=2.18.0 <4.0.0"
+ flutter: ">=3.3.0"
+
+dependencies:
+ flutter:
+ sdk: flutter
+ flutter_web_plugins:
+ sdk: flutter
+ web: ">=0.5.1 <2.0.0"
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+ flutter_lints: ^2.0.2
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter.
+flutter:
+ # This section identifies this Flutter project as a plugin project.
+ # The 'pluginClass' and Android 'package' identifiers should not ordinarily
+ # be modified. They are used by the tooling to maintain consistency when
+ # adding or updating assets for this project.
+ plugin:
+ platforms:
+ web:
+ pluginClass: AssetsAudioPlayerWebPlugin
+ fileName: web/assets_audio_player_web.dart
diff --git a/assets_audio_player_web/res/values/strings_en.arb b/assets_audio_player_web/res/values/strings_en.arb
new file mode 100644
index 00000000..9e26dfee
--- /dev/null
+++ b/assets_audio_player_web/res/values/strings_en.arb
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/assets_audio_player_web/test/assets_audio_player_web_test.dart b/assets_audio_player_web/test/assets_audio_player_web_test.dart
new file mode 100644
index 00000000..ab73b3a2
--- /dev/null
+++ b/assets_audio_player_web/test/assets_audio_player_web_test.dart
@@ -0,0 +1 @@
+void main() {}
diff --git a/pubspec.yaml b/pubspec.yaml
index df0211da..cdfb65b3 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -17,6 +17,14 @@ dependencies:
http: ">=0.13.0 <2.0.0"
path_provider: ^2.0.8
+ # The design on https://flutter.dev/go/federated-plugins was to leave
+ # this constraint as 'any'. We cannot do it right now as it fails pub publish
+ # validation, so we set a ^ constraint.
+ # TODO(amirh): Revisit this (either update this part in the design or the pub tool).
+ # https://github.com/flutter/flutter/issues/46264
+ # assets_audio_player_web: ^3.0.0-nullsafety.0
+ assets_audio_player_web: ^3.1.1
+
dev_dependencies:
flutter_test:
sdk: flutter
@@ -33,3 +41,5 @@ flutter:
pluginClass: AssetsAudioPlayerPlugin
macos:
pluginClass: AssetsAudioPlayerPlugin
+ web:
+ default_package: assets_audio_player_web
\ No newline at end of file