├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── settings.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── danielr2001 │ │ └── audioplayer │ │ ├── AudioPlayerPlugin.java │ │ ├── audioplayers │ │ ├── BackgroundAudioPlayer.java │ │ └── ForegroundAudioPlayer.java │ │ ├── enums │ │ ├── NotificationActionCallbackMode.java │ │ ├── NotificationActionName.java │ │ ├── NotificationCustomActions.java │ │ ├── NotificationDefaultActions.java │ │ ├── PlayerMode.java │ │ └── PlayerState.java │ │ ├── interfaces │ │ ├── AsyncResponse.java │ │ └── AudioPlayer.java │ │ ├── models │ │ └── AudioObject.java │ │ └── notifications │ │ ├── LoadImageFromUrl.java │ │ └── MediaNotificationManager.java │ └── res │ └── drawable │ ├── ic_next.xml │ ├── ic_pause.xml │ ├── ic_play.xml │ └── ic_previous.xml ├── example ├── .flutter-plugins-dependencies ├── .gitignore ├── .metadata ├── README.md ├── android │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ │ └── danielr2001 │ │ │ │ │ └── exoplayer_example │ │ │ │ │ └── MainActivity.java │ │ │ └── res │ │ │ │ ├── drawable │ │ │ │ ├── ic_custom1.xml │ │ │ │ ├── ic_custom2.xml │ │ │ │ ├── ic_launcher.png │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── images │ ├── Screenshot_1.png │ ├── Screenshot_2.png │ ├── Screenshot_3.png │ └── Screenshot_4.png ├── ios │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ └── contents.xcworkspacedata │ └── Runner │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Assets.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ ├── Icon-App-20x20@1x.png │ │ │ ├── Icon-App-20x20@2x.png │ │ │ ├── Icon-App-20x20@3x.png │ │ │ ├── Icon-App-29x29@1x.png │ │ │ ├── Icon-App-29x29@2x.png │ │ │ ├── Icon-App-29x29@3x.png │ │ │ ├── Icon-App-40x40@1x.png │ │ │ ├── Icon-App-40x40@2x.png │ │ │ ├── Icon-App-40x40@3x.png │ │ │ ├── Icon-App-60x60@2x.png │ │ │ ├── Icon-App-60x60@3x.png │ │ │ ├── Icon-App-76x76@1x.png │ │ │ ├── Icon-App-76x76@2x.png │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ └── LaunchImage.imageset │ │ │ ├── Contents.json │ │ │ ├── LaunchImage.png │ │ │ ├── LaunchImage@2x.png │ │ │ ├── LaunchImage@3x.png │ │ │ └── README.md │ │ ├── Base.lproj │ │ ├── LaunchScreen.storyboard │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── main.m ├── lib │ ├── main.dart │ └── player_widget.dart └── pubspec.yaml ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── AudioPlayerPlugin.h │ └── AudioPlayerPlugin.m └── flutter_exoplayer.podspec ├── lib ├── audio_notification.dart └── audioplayer.dart ├── pubspec.yaml └── test └── audioplayer_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .DS_Store 3 | .atom/ 4 | .idea 5 | .packages 6 | .dart_tool/ 7 | .pub/ 8 | build/ 9 | ios/.generated/ 10 | packages 11 | .classpath 12 | .project 13 | .settings 14 | .vscode 15 | testing 16 | pubspec.lock 17 | android/local.properties 18 | example/ios/Flutter/flutter_export_environment.sh 19 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: b712a172f9694745f50505c93340883493b505e5 8 | channel: stable 9 | 10 | project_type: plugin 11 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.6.1 4 | 5 | - **[Update]** Updated example app. 6 | - **[Update]** Added AndroidX properties and pre 1.12 Android to example app (thanks, @Yeikel200). 7 | 8 | ## 0.6.0 9 | 10 | - **[Add]** Added Support to Null Safety. 11 | 12 | ## 0.5.5 13 | 14 | - **[Add]** + **[Fix]** Added start from certain position (In Dart side was already implemented but not in Java side). 15 | 16 | ## 0.5.4 17 | 18 | - **[Feature]** control of playback speed. 19 | 20 | ## 0.5.3 21 | 22 | - **[Feature]** updating Notification metadata dynamically. 23 | 24 | ## 0.5.2 25 | 26 | - **[Fix]** minor bugs (thanks, @btanarola). 27 | 28 | ## 0.5.1 29 | 30 | - **[Fix]** hiding notification when player stopped. 31 | - **[Update]** notification managment code efficiency. 32 | 33 | ## 0.5.0 34 | 35 | - **[Add]** two customizable actions with callback in addition to the default ones. 36 | - **[Change]** NotificationActionMode enum to NotificationDefaultActions. 37 | - **[Update]** example app. 38 | 39 | ## 0.4.0 40 | 41 | - **[Add]** protection from notification errors. 42 | - **[Fix]** seek position when paused (it would set the state to playing). 43 | - **[Fix]** getCurrentPosition. 44 | 45 | ## 0.3.5 46 | 47 | - **[Feature]** getVolume. 48 | - **[Fix]** some bugs. 49 | 50 | ## 0.3.4 51 | 52 | - **[Change]** error handling to act as dispose method. 53 | - **[Fix]** some bugs. 54 | 55 | ## 0.3.3 56 | 57 | - **[Fix]** getCurrentPlayingAudioIndex. 58 | 59 | ## 0.3.2 60 | 61 | - **[Fix]** next and previous actions not working when paused. 62 | 63 | ## 0.3.1 64 | 65 | - **[Fix]** seekPosition and seekIndex errors in foregroundPlayer. 66 | 67 | ## 0.3.0 68 | 69 | - **[Change]** Renamed seek to seekPosition. 70 | - **[Feature]** seekIndex that lets you seek to a specific index in playlist (available only when playing playlist). 71 | - **[Add]** index parameter to playAll, that indicates from what index to start playing. 72 | 73 | ## 0.2.1 74 | 75 | - **[Update]** code efficiency. 76 | - **[Fix]** some minor bugs. 77 | 78 | ## 0.2.0 79 | 80 | - **[Fix]** player state handling completly. 81 | - **[Change]** the behavior of the `COMPLETED` state to act similarly as `PAUSED` state. 82 | 83 | ## 0.1.0 84 | 85 | - **[Fix]** player state handling. 86 | - **[Feature]** SetRepeatMode. 87 | 88 | ## 0.0.2 89 | 90 | - **[Change]** class name, Exoplayer => Audioplayer. 91 | - **[Feature]** custom notification callback via stream (Dart side) in addition to the default (only Java side). 92 | 93 | ## 0.0.1 94 | 95 | - Initial Open Source release. 96 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT license 2 | 3 | Copyright (c) 2019 Daniel Rachlin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flutter_exoplayer 2 | 3 | A Flutter plugin that let's you play multiple audio files simultaneously with an option to choose if to play in background or as a forground service, for now works only for Android. 4 | 5 | ![](example/images/Screenshot_1.png) ![](example/images/Screenshot_2.png) ![](example/images/Screenshot_3.png) ![](example/images/Screenshot_4.png) 6 | 7 | ## Why pick us 8 | 9 | Flutter_exoplayer uses the Java ExoPlayer library, which unlike Android's MediaPlayer offers fast audio buffering, especially when using playlists. 10 | All thanks to the ExoPlayer's `ConcatenatingMediaSource` that let's you use an audio list that allways buffers the next audios. This feature 11 | of the ExoPlayer let's you play playlists very smoothly. 12 | 13 | Moreover Flutter_exoplayer offers many features such as: 14 | 15 | * Providing realtime player states (PLAYING, PAUSED, STOPPED, RELEASED etc'). 16 | * Run unlimited count of audios simultaneously. 17 | * Providing audio Session ID for visualizers. 18 | * It has 2 options for audio playing: 19 | * Foreground - plays the audio in foreground service so Android won't kill the service when app is in background. 20 | * Background - plays the audio in background (Android can easily kill the service when app is in background), the main use of this option is when app is in foreground. 21 | * Providing streams such as: current player position, player duration, current player index, player state etc`. 22 | 23 | In addition this library is only in it's first steps, any new feature suggestions or bug reports are allways welcome (just submit an issue/PR in my [repository](https://github.com/danielR2001/flutter_exoplayer)), only in this way we can make this library better! 24 | 25 | 26 | ## Install 27 | 28 | just add this dependency in your pubsec.yaml file: 29 | 30 | ```yaml 31 | dependencies: 32 | flutter_exoplayer: ^0.6.1 33 | ``` 34 | 35 | ## Support us 36 | 37 | If you find a bug or a feature you want to be added to this library go to the github repository [Github](https://github.com/danielR2001/flutter_exoplayer), there you can add a new issue and tell us about the bug/feature, and if you think you can fix/add by yourself I would love to get a PR from you. 38 | 39 | ## Usage 40 | 41 | An `AudioPlayer` instance can play a single audio at a time. To create it, simply call the constructor: 42 | 43 | ```dart 44 | AudioPlayer audioPlayer = AudioPlayer(); 45 | ``` 46 | 47 | You can create multiple instances to play audio simultaneously, but only if you choose `playerMode: PlayerMode.BACKGROUND`, because android can't run two similar services. 48 | 49 | For all methods that return a `Future`: that's the status of the operation (Result is an enum which contains 3 options: SUCCESS, FAIL and ERROR). If `SUCCESS`, the operation was successful, If `FAIL`, you tried to call audio conrolling methods on released audio player (this status is never returned when calling `play` or `playAll`). Otherwise it's the platform native ERROR code. 50 | 51 | Logs are disable by default! To debug, run: 52 | 53 | ```dart 54 | AudioPlayer.logEnabled = true; 55 | ``` 56 | 57 | ### Playing Audio 58 | 59 | To play audio you have two options: 60 | 1. play single audio. 61 | 2. play playlist. 62 | 63 | * play single audio. 64 | 65 | ```dart 66 | String url = "URL"; 67 | audioPlayer.play(url); 68 | ``` 69 | 70 | * play playlist. 71 | 72 | ```dart 73 | List urls = ["URL1","URL2","URL3"]; 74 | audioPlayer.playAll(urls); 75 | ``` 76 | 77 | The url you pass can be either local direction or network url. 78 | 79 | By default the player is set to play in background (Android system can easily kill the Audio player when app is in background), if Player mode is set to FOREGROUND then you need to also pass `audioObject` instance for the foreground notification, respectAudioFocus is set to false (if your app is respectiong audio focus it will pause when other app get's audio focus and duck if other app getting temporary access of audio focus), repeatMode is also set by default to false (every audio source will play only once), by default the volume is set to max (1.0), the index of the audio that you you want to start with by default is set to 0. To change one or more of this parameters you need to just pass them to play method. 80 | 81 | ```dart 82 | final Result result = await audioPlayer.play(url, 83 | repeatMode: true, 84 | respectAudioFocus: true, 85 | playerMode: PlayerMode.FOREGROUND, 86 | audioObject: audioObject); 87 | if (result == Result.ERROR) { 88 | print("something went wrong in play method :("); 89 | } 90 | ``` 91 | 92 | ```dart 93 | final Result result = await audioPlayer.playAll(urls, 94 | repeatMode: true, 95 | respectAudioFocus: true, 96 | playerMode: PlayerMode.FOREGROUND, 97 | audioObjects: audioObjects); 98 | if (result == Result.ERROR) { 99 | print("something went wrong in playAll method :("); 100 | } 101 | ``` 102 | 103 | ### Controlling 104 | 105 | 106 | After you call play you can control you audio with pause, resume, stop, release, next, previous and seek methods. 107 | 108 | * Pause: Will pause your audio and keep the position. 109 | 110 | ```dart 111 | final Result result = await audioPlayer.pause(); 112 | if (result == Result.FAIL) { 113 | print( 114 | "you tried to call audio conrolling methods on released audio player :("); 115 | } else if (result == Result.ERROR) { 116 | print("something went wrong in pause :("); 117 | } 118 | ``` 119 | 120 | * Resume: Will resume your audio from the exact position it was paused on. 121 | 122 | ```dart 123 | final Result result = await audioPlayer.resume(); 124 | if (result == Result.FAIL) { 125 | print( 126 | "you tried to call audio conrolling methods on released audio player :("); 127 | } else if (result == Result.ERROR) { 128 | print("something went wrong in resume :("); 129 | } 130 | ``` 131 | 132 | * Stop: Will stop your audio and restart it position. 133 | 134 | ```dart 135 | final Result result = await audioPlayer.stop(); 136 | if (result == Result.FAIL) { 137 | print( 138 | "you tried to call audio conrolling methods on released audio player :("); 139 | } else if (result == Result.ERROR) { 140 | print("something went wrong in stop :("); 141 | } 142 | ``` 143 | 144 | * Release: Will release your audio source from the player (you need to call play again). 145 | 146 | ```dart 147 | final Result result = await audioPlayer.release(); 148 | if (result == Result.FAIL) { 149 | print( 150 | "you tried to call audio conrolling methods on released audio player :("); 151 | } else if (result == Result.ERROR) { 152 | print("something went wrong in release :("); 153 | } 154 | ``` 155 | 156 | * Next: Will play the next song in the playlist or if playing single audio it will restart the current. 157 | 158 | ```dart 159 | final Result result = await audioPlayer.next(); 160 | if (result == Result.FAIL) { 161 | print( 162 | "you tried to call audio conrolling methods on released audio player :("); 163 | } else if (result == Result.ERROR) { 164 | print("something went wrong in next :("); 165 | } 166 | ``` 167 | 168 | * Previous: Will play the previous song in the playlist or if playing single audio it will restart the current. 169 | 170 | ```dart 171 | final Result result = await audioPlayer.previous(); 172 | if (result == Result.FAIL) { 173 | print( 174 | "you tried to call audio conrolling methods on released audio player :("); 175 | } else if (result == Result.ERROR) { 176 | print("something went wrong in previous :("); 177 | } 178 | ``` 179 | 180 | * seekPosition: Will seek to the position you set. 181 | 182 | ```dart 183 | final Result result = await audioPlayer.seekPosition(_duration)); 184 | if (result == Result.FAIL) { 185 | print( 186 | "you tried to call audio conrolling methods on released audio player :("); 187 | } else if (result == Result.ERROR) { 188 | print("something went wrong in seek :("); 189 | } 190 | ``` 191 | 192 | * seekIndex: Will seek to the index in the playlist you set. 193 | 194 | ```dart 195 | final Result result = await audioPlayer.seekIndex(index)); 196 | if (result == Result.FAIL) { 197 | print( 198 | "you tried to call audio conrolling methods on released audio player :("); 199 | } else if (result == Result.ERROR) { 200 | print("something went wrong in seekIndex :("); 201 | } 202 | ``` 203 | 204 | ### Notification Customization 205 | 206 | When playing in `PlayerMode.FOREGROUND` then the player will show foreground notification, You can customize it in the `AudioObject` thing like priority/ background color / what actions to show and etc'. 207 | 208 | `NotificationDefaultActions` represents the actions you want to show with your notification (previous, play/pause, next), you have the option to choose between: NONE - only play/pause, PREVIOUS - previous and play/pause, NEXT - next and play/pause, and ALL - that include all actions. 209 | 210 | `NotificationCustomActions` represents the custom actions you want to show with your notification (like, download etc'), you have the option to choose between: DISABLED - show no custom icons, ONE - show one custom icon, TWO - show two custom icons. The callback of this actions is returned via `onNotificationActionCallback` (CUSTOM1 is the left action, as CUSTOM2 is the right action). If you chose ONE\TWO you have to provide the resource for 211 | the icon inside APP_NAME\android\app\src\main\res\drawable, the resource needs to be vector image. The names of the files need to be: "ic_custom1" for 212 | the left custom action and "ic_custom2" for the right action. 213 | 214 | Attention! If you choose to show the custom actions you have to follow the instructions above! the file names needs to be as the instructions say and their location too. 215 | 216 | `NotificationActionCallbackMode` is a mode that lets you choose between two options: DEFAULT or CUSTOM, 217 | this parameter decides if you will recieve action callback (`CUSTOM`) or not (`DEFAULT`) when user taps on the action via `onNotificationActionCallback` stream, and then you can make custom action for your taste. If set to 218 | `DEFAULT` then the action will do only as the action name says (PLAY -> play, PREVIOUS -> play previous etc`). 219 | 220 | Attention! You need to place your app icon or the icon you want to show in the APP_NAME\android\app\src\main\res\drawable folder (you can drop multiple icons there), if you won`t do so your app will crash because android require a small icon for notification. 221 | 222 | ```dart 223 | AudioObject audioObject = AudioObject( 224 | smallIconFileName: "your icon file name", 225 | title: "title", 226 | subTitle: "artist", 227 | largeIconUrl: "local or network image url", 228 | isLocal: false, 229 | notificationDefaultActions: NotificationDefaultActions.ALL, 230 | notificationCustomActions: NotificationCustomActions.TWO, 231 | ); 232 | ``` 233 | 234 | ### Streams 235 | 236 | The AudioPlayer supports subscribing to events like so: 237 | 238 | #### Duration Event 239 | 240 | This event returns the duration of the file, when it's available (it might take a while because it's being downloaded or buffered). 241 | 242 | ```dart 243 | audioPlayer.onDurationChanged.listen((Duration d) { 244 | print('Max duration: $d'); 245 | setState(() => duration = d); 246 | }); 247 | ``` 248 | 249 | #### Position Event 250 | 251 | This Event updates the current position of the audio. You can use it to make a progress bar, for instance. 252 | 253 | ```dart 254 | audioPlayer.onAudioPositionChanged.listen((Duration p) { 255 | print('Current position: $p'); 256 | setState(() => position = p); 257 | }); 258 | ``` 259 | 260 | #### State Event 261 | 262 | This Event returns the current player state. You can use it to show if player playing, or stopped, or paused. 263 | 264 | ```dart 265 | audioPlayer.onPlayerStateChanged.listen((PlayerState s) { 266 | print('Current player state: $s'); 267 | setState(() => plaVyerState = s); 268 | }); 269 | ``` 270 | 271 | #### Completion Event 272 | 273 | This Event is called when the audio finishes playing (in playAll mode it fires only when all playlist finishes playing). 274 | If `repeatMode` is set yo true then this event is never fired. It does not fire when you interrupt the audio with pause or stop. 275 | `COMPLETED` state acts just like `PAUSED` state with seek to the first audio and position 0, and can restart the audio player 276 | with resume/play/playAll. 277 | 278 | ```dart 279 | audioPlayer.onPlayerCompletion.listen((event) { 280 | print('Current player is completed'); 281 | }); 282 | ``` 283 | 284 | #### Audio Session ID Event 285 | 286 | This Event is called when audio session id is changed. 287 | 288 | ```dart 289 | audioPlayer.onAudioSessionIdChange.listen((audioSessionId) { 290 | print("audio Session Id: $audioSessionId"); 291 | }); 292 | ``` 293 | 294 | #### Notification Action Event 295 | 296 | This Event is called when the user taps on one of the notification actions, then the stream will 297 | return the action name of the action that the user has clicked on. 298 | 299 | ```dart 300 | audioPlayer.onNotificationActionCallback.listen((notificationActionName) { 301 | //do something 302 | }); 303 | ``` 304 | 305 | #### Audio Index Event 306 | 307 | This Event is called when the current player audio index is changed (new audio is being played). 308 | 309 | ```dart 310 | audioPlayer.onCurrentAudioIndexChanged.listen((index) { 311 | setState(() { 312 | currentIndex = index; 313 | }); 314 | }); 315 | ``` 316 | 317 | #### Error Event 318 | 319 | This is called when an unexpected ERROR is thrown in the native code. 320 | 321 | ```dart 322 | audioPlayer.onPlayerError.listen((msg) { 323 | print('audioPlayer ERROR : $msg'); 324 | setState(() { 325 | playerState = PlayerState.stopped; 326 | duration = Duration(seconds: 0); 327 | position = Duration(seconds: 0); 328 | }); 329 | }); 330 | ``` 331 | 332 | ## Supported Formats 333 | 334 | You can check a list of supported formats below: 335 | 336 | - [Android](https://exoplayer.dev/supported-formats.html) 337 | 338 | ## IOS implementation 339 | 340 | If you have the time and want to implement this library on IOS, i would love to get PR, and hopefully add your PR to my library. 341 | 342 | ## Credits 343 | 344 | This project was originally a fork of [luanpotter's audioplayers](https://github.com/luanpotter/audioplayers) that was also originally a fork of [rxlabz's audioplayer](https://github.com/rxlabz/audioplayer), but since we have diverged and added more features. 345 | 346 | Thanks for @rxlabz and @luanpotter for the amazing work! 347 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | local.properties 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'danielr2001.flutter_exoplayer' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:4.1.2' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 28 26 | 27 | defaultConfig { 28 | minSdkVersion 16 29 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 30 | } 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | 35 | compileOptions { 36 | targetCompatibility JavaVersion.VERSION_1_8 37 | } 38 | } 39 | dependencies { 40 | implementation 'androidx.appcompat:appcompat:1.2.0' 41 | implementation 'com.google.android.exoplayer:exoplayer:2.11.1' 42 | implementation 'androidx.core:core:1.3.2' 43 | implementation 'androidx.media:media:1.2.1' 44 | } -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | android.enableJetifier=true 2 | android.useAndroidX=true 3 | org.gradle.jvmargs=-Xmx1536M -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'audioplayer' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/AudioPlayerPlugin.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer; 2 | 3 | import danielr2001.audioplayer.audioplayers.ForegroundAudioPlayer; 4 | import danielr2001.audioplayer.audioplayers.BackgroundAudioPlayer; 5 | import danielr2001.audioplayer.interfaces.AudioPlayer; 6 | import danielr2001.audioplayer.models.AudioObject; 7 | import danielr2001.audioplayer.enums.NotificationDefaultActions; 8 | import danielr2001.audioplayer.enums.NotificationCustomActions; 9 | import danielr2001.audioplayer.enums.NotificationActionName; 10 | import danielr2001.audioplayer.enums.NotificationActionCallbackMode; 11 | import danielr2001.audioplayer.enums.PlayerState; 12 | import danielr2001.audioplayer.enums.PlayerMode; 13 | 14 | import android.app.Activity; 15 | import android.app.ActivityManager; 16 | import android.os.Handler; 17 | import android.os.Build; 18 | import android.os.IBinder; 19 | import android.content.ServiceConnection; 20 | import android.content.Context; 21 | import android.content.Intent; 22 | import android.content.ComponentName; 23 | import android.util.Log; 24 | 25 | import androidx.core.content.ContextCompat; 26 | 27 | import java.lang.ref.WeakReference; 28 | import java.util.ArrayList; 29 | import java.util.HashMap; 30 | import java.util.Map; 31 | import java.util.logging.Level; 32 | import java.util.logging.Logger; 33 | 34 | import io.flutter.plugin.common.MethodCall; 35 | import io.flutter.plugin.common.MethodChannel; 36 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 37 | import io.flutter.plugin.common.PluginRegistry.Registrar; 38 | 39 | public class AudioPlayerPlugin implements MethodCallHandler { 40 | 41 | private static final Logger LOGGER = Logger.getLogger(AudioPlayerPlugin.class.getCanonicalName()); 42 | 43 | private final MethodChannel channel; 44 | private final Handler handler = new Handler(); 45 | private Runnable positionUpdates; 46 | 47 | private final Map audioPlayers = new HashMap<>(); 48 | private Context context; 49 | private Activity activity; 50 | 51 | private PlayerMode playerMode; 52 | private AudioObject audioObject; 53 | private final ArrayList audioObjects = new ArrayList<>(); 54 | 55 | // temp variables for foreground player 56 | private AudioPlayer tempPlayer; 57 | private String tempPlayerId; 58 | private boolean tempRepeatMode; 59 | private boolean tempRespectAudioFocus; 60 | private AudioPlayerPlugin tempAudioPlayerPlugin; 61 | private int tempIndex; 62 | private int tempPos; 63 | 64 | private ServiceConnection connection = new ServiceConnection() { 65 | 66 | @SuppressWarnings("unchecked") 67 | @Override 68 | public void onServiceConnected(ComponentName className, IBinder service) { 69 | ForegroundAudioPlayer.LocalBinder binder = (ForegroundAudioPlayer.LocalBinder) service; 70 | tempPlayer = binder.getService(); // just like tempPlayer = ForegroundAudioPlayer(); 71 | tempPlayer.initAudioPlayer(tempAudioPlayerPlugin, tempAudioPlayerPlugin.activity, tempPlayerId); 72 | tempPlayer.setPlayerAttributes(tempRepeatMode, tempRespectAudioFocus, playerMode); 73 | if (playerMode == PlayerMode.PLAYLIST) { 74 | tempPlayer.playAll((ArrayList) audioObjects.clone(), tempIndex, tempPos); 75 | } else { 76 | tempPlayer.play(audioObject, tempPos); 77 | } 78 | audioPlayers.put(tempPlayerId, tempPlayer); 79 | } 80 | 81 | @Override 82 | public void onServiceDisconnected(ComponentName arg0) { 83 | } 84 | }; 85 | 86 | public static void registerWith(final Registrar registrar) { 87 | final MethodChannel channel = new MethodChannel(registrar.messenger(), "danielr2001/audioplayer"); 88 | channel.setMethodCallHandler(new AudioPlayerPlugin(channel, registrar.activity())); 89 | } 90 | 91 | private AudioPlayerPlugin(final MethodChannel channel, Activity activity) { 92 | this.channel = channel; 93 | this.activity = activity; 94 | this.context = activity.getApplicationContext(); 95 | this.channel.setMethodCallHandler(this); 96 | } 97 | 98 | @Override 99 | public void onMethodCall(final MethodCall call, final MethodChannel.Result response) { 100 | try { 101 | handleMethodCall(call, response); 102 | } catch (Exception e) { 103 | dispose(); 104 | LOGGER.log(Level.SEVERE, "Unexpected error!", e); 105 | response.success(0); // error 106 | } 107 | } 108 | 109 | @SuppressWarnings("unchecked") 110 | private void handleMethodCall(final MethodCall call, final MethodChannel.Result response) { 111 | final String playerId = call.argument("playerId"); 112 | AudioPlayer player = null; 113 | this.audioObjects.clear(); 114 | this.audioObject = null; 115 | if (audioPlayers.containsKey(playerId)) { 116 | player = getPlayer(playerId); 117 | } 118 | if (call.method.equals("play") || call.method.equals("playAll") || player != null) { // check if player is released then do nothing 119 | switch (call.method) { 120 | case "play": { 121 | final String url = call.argument("url"); 122 | final boolean repeatMode = call.argument("repeatMode"); 123 | final boolean respectAudioFocus = call.argument("respectAudioFocus"); 124 | final boolean isBackground = call.argument("isBackground"); 125 | final int position = call.argument("position"); 126 | 127 | this.playerMode = PlayerMode.SINGLE; 128 | if (isBackground) { 129 | // init player as BackgroundAudioPlayer instance 130 | this.audioObject = new AudioObject(url); 131 | if (player != null && !player.isPlayerReleased()) { 132 | player.play(this.audioObject, position); 133 | } else { 134 | player = new BackgroundAudioPlayer(); 135 | player.initAudioPlayer(this, this.activity, playerId); 136 | player.setPlayerAttributes(repeatMode, respectAudioFocus, this.playerMode); 137 | player.play(this.audioObject, position); 138 | 139 | audioPlayers.put(playerId, player); 140 | } 141 | 142 | } else { 143 | final String smallIconFileName = call.argument("smallIconFileName"); 144 | final String title = call.argument("title"); 145 | final String subTitle = call.argument("subTitle"); 146 | final String largeIconUrl = call.argument("largeIconUrl"); 147 | final boolean isLocal = call.argument("isLocal"); 148 | final int notificationDefaultActionsInt = call.argument("notificationDefaultActions"); 149 | final int notificationActionCallbackModeInt = call.argument("notificationActionCallbackMode"); 150 | final int notificationCustomActionsInt = call.argument("notificationCustomActions"); 151 | 152 | this.tempPlayer = player; 153 | this.tempPlayerId = playerId; 154 | this.tempRepeatMode = repeatMode; 155 | this.tempRespectAudioFocus = respectAudioFocus; 156 | this.tempAudioPlayerPlugin = this; 157 | this.tempPos = position; 158 | 159 | NotificationDefaultActions notificationDefaultActions; 160 | NotificationActionCallbackMode notificationActionCallbackMode; 161 | NotificationCustomActions notificationCustomActions; 162 | if (notificationDefaultActionsInt == 0) { 163 | notificationDefaultActions = NotificationDefaultActions.NONE; 164 | } else if (notificationDefaultActionsInt == 1) { 165 | notificationDefaultActions = NotificationDefaultActions.NEXT; 166 | } else if (notificationDefaultActionsInt == 2) { 167 | notificationDefaultActions = NotificationDefaultActions.PREVIOUS; 168 | } else { 169 | notificationDefaultActions = NotificationDefaultActions.ALL; 170 | } 171 | 172 | if (notificationCustomActionsInt == 1) { 173 | notificationCustomActions = NotificationCustomActions.ONE; 174 | } else if (notificationCustomActionsInt == 2) { 175 | notificationCustomActions = NotificationCustomActions.TWO; 176 | } else { 177 | notificationCustomActions = NotificationCustomActions.DISABLED; 178 | } 179 | 180 | if (notificationActionCallbackModeInt == 0) { 181 | notificationActionCallbackMode = NotificationActionCallbackMode.DEFAULT; 182 | } else { 183 | notificationActionCallbackMode = NotificationActionCallbackMode.CUSTOM; 184 | } 185 | 186 | this.audioObject = new AudioObject(url, smallIconFileName, title, subTitle, largeIconUrl, isLocal, 187 | notificationDefaultActions, notificationActionCallbackMode, notificationCustomActions); 188 | // init player as ForegroundAudioPlayer service 189 | if (player != null && !player.isPlayerReleased()) { 190 | player.play(this.audioObject, position); 191 | } else { 192 | startForegroundPlayer(); 193 | } 194 | } 195 | break; 196 | } 197 | case "playAll": { 198 | final ArrayList urls = call.argument("urls"); 199 | final boolean repeatMode = call.argument("repeatMode"); 200 | final boolean isBackground = call.argument("isBackground"); 201 | final boolean respectAudioFocus = call.argument("respectAudioFocus"); 202 | final int index = call.argument("index"); 203 | final int position = call.argument("position"); 204 | 205 | this.playerMode = PlayerMode.PLAYLIST; 206 | if (isBackground) { 207 | // init player as BackgroundAudioPlayer instance 208 | for (String url : urls) { 209 | this.audioObjects.add(new AudioObject(url)); 210 | } 211 | if (player != null && !player.isPlayerReleased()) { 212 | player.playAll((ArrayList) this.audioObjects.clone(), index, position); 213 | } else { 214 | player = new BackgroundAudioPlayer(); 215 | player.initAudioPlayer(this, this.activity, playerId); 216 | player.setPlayerAttributes(repeatMode, respectAudioFocus, this.playerMode); 217 | player.playAll((ArrayList) this.audioObjects.clone(), index, position); 218 | 219 | audioPlayers.put(playerId, player); 220 | } 221 | } else { 222 | final ArrayList smallIconFileNames = call.argument("smallIconFileNames"); 223 | final ArrayList titles = call.argument("titles"); 224 | final ArrayList subTitles = call.argument("subTitles"); 225 | final ArrayList largeIconUrls = call.argument("largeIconUrls"); 226 | final ArrayList isLocals = call.argument("isLocals"); 227 | final ArrayList notificationDefaultActionsInts = call.argument("notificationDefaultActionsList"); 228 | final ArrayList notificationActionCallbackModeInts = call.argument("notificationActionCallbackModes"); 229 | final ArrayList notificationCustomActionsInts = call.argument("notificationCustomActionsList"); 230 | 231 | this.tempPlayer = player; 232 | this.tempPlayerId = playerId; 233 | this.tempRepeatMode = repeatMode; 234 | this.tempRespectAudioFocus = respectAudioFocus; 235 | this.tempAudioPlayerPlugin = this; 236 | this.tempIndex = index; 237 | this.tempPos = position; 238 | 239 | for (int i = 0; i < urls.size(); i++) { 240 | NotificationDefaultActions notificationDefaultActions; 241 | NotificationActionCallbackMode notificationActionCallbackMode; 242 | NotificationCustomActions notificationCustomActions; 243 | if (notificationDefaultActionsInts.get(i) == 0) { 244 | notificationDefaultActions = NotificationDefaultActions.NONE; 245 | } else if (notificationDefaultActionsInts.get(i) == 1) { 246 | notificationDefaultActions = NotificationDefaultActions.NEXT; 247 | } else if (notificationDefaultActionsInts.get(i) == 2) { 248 | notificationDefaultActions = NotificationDefaultActions.PREVIOUS; 249 | } else { 250 | notificationDefaultActions = NotificationDefaultActions.ALL; 251 | } 252 | 253 | if (notificationCustomActionsInts.get(i) == 1) { 254 | notificationCustomActions = NotificationCustomActions.ONE; 255 | } else if (notificationCustomActionsInts.get(i) == 2) { 256 | notificationCustomActions = NotificationCustomActions.TWO; 257 | } else { 258 | notificationCustomActions = NotificationCustomActions.DISABLED; 259 | } 260 | 261 | if (notificationActionCallbackModeInts.get(i) == 0) { 262 | notificationActionCallbackMode = NotificationActionCallbackMode.DEFAULT; 263 | } else { 264 | notificationActionCallbackMode = NotificationActionCallbackMode.CUSTOM; 265 | } 266 | 267 | this.audioObjects.add(new AudioObject(urls.get(i), smallIconFileNames.get(i), titles.get(i), 268 | subTitles.get(i), largeIconUrls.get(i), isLocals.get(i), notificationDefaultActions, 269 | notificationActionCallbackMode, notificationCustomActions)); 270 | } 271 | // init player as ForegroundAudioPlayer service 272 | if (player != null && !player.isPlayerReleased()) { 273 | player.playAll((ArrayList) this.audioObjects.clone(), index, position); 274 | } else { 275 | startForegroundPlayer(); 276 | } 277 | } 278 | break; 279 | } 280 | case "next": { 281 | player.next(); 282 | break; 283 | } 284 | case "previous": { 285 | player.previous(); 286 | break; 287 | } 288 | case "resume": { 289 | player.resume(); 290 | break; 291 | } 292 | case "pause": { 293 | player.pause(); 294 | break; 295 | } 296 | case "stop": { 297 | player.stop(); 298 | break; 299 | } 300 | case "release": { 301 | if (!player.isBackground() && !player.isPlayerReleased()) { 302 | this.context.unbindService(connection); 303 | } 304 | audioPlayers.remove(player.getPlayerId()); 305 | player.release(); 306 | break; 307 | } 308 | case "seekPosition": { 309 | final int position = call.argument("position"); 310 | player.seekPosition(position); 311 | break; 312 | } 313 | case "seekIndex": { 314 | final int index = call.argument("index"); 315 | player.seekIndex(index); 316 | break; 317 | } 318 | case "setVolume": { 319 | final double vol = call.argument("volume"); 320 | final float volume = (float) vol; 321 | player.setVolume(volume); 322 | break; 323 | } 324 | case "setRepeatMode": { 325 | final boolean repeatMode = call.argument("repeatMode"); 326 | player.setRepeatMode(repeatMode); 327 | break; 328 | } 329 | case "setPlaybackSpeed": { 330 | final double spd = call.argument("speed"); 331 | final float speed = (float) spd; 332 | player.setPlaybackSpeed(speed); 333 | break; 334 | } 335 | case "setAudioObject": { 336 | final String smallIconFileName = call.argument("smallIconFileName"); 337 | final String title = call.argument("title"); 338 | final String subTitle = call.argument("subTitle"); 339 | final String largeIconUrl = call.argument("largeIconUrl"); 340 | final int notificationDefaultActionsInt = call.argument("notificationDefaultActions"); 341 | final int notificationActionCallbackModeInt = call.argument("notificationActionCallbackMode"); 342 | final int notificationCustomActionsInt = call.argument("notificationCustomActions"); 343 | 344 | NotificationDefaultActions notificationDefaultActions; 345 | NotificationActionCallbackMode notificationActionCallbackMode; 346 | NotificationCustomActions notificationCustomActions; 347 | if (notificationDefaultActionsInt == 0) { 348 | notificationDefaultActions = NotificationDefaultActions.NONE; 349 | } else if (notificationDefaultActionsInt == 1) { 350 | notificationDefaultActions = NotificationDefaultActions.NEXT; 351 | } else if (notificationDefaultActionsInt == 2) { 352 | notificationDefaultActions = NotificationDefaultActions.PREVIOUS; 353 | } else { 354 | notificationDefaultActions = NotificationDefaultActions.ALL; 355 | } 356 | 357 | if (notificationCustomActionsInt == 1) { 358 | notificationCustomActions = NotificationCustomActions.ONE; 359 | } else if (notificationCustomActionsInt == 2) { 360 | notificationCustomActions = NotificationCustomActions.TWO; 361 | } else { 362 | notificationCustomActions = NotificationCustomActions.DISABLED; 363 | } 364 | 365 | if (notificationActionCallbackModeInt == 0) { 366 | notificationActionCallbackMode = NotificationActionCallbackMode.DEFAULT; 367 | } else { 368 | notificationActionCallbackMode = NotificationActionCallbackMode.CUSTOM; 369 | } 370 | 371 | this.audioObject = new AudioObject(smallIconFileName, title, subTitle, largeIconUrl, notificationDefaultActions, 372 | notificationActionCallbackMode, notificationCustomActions); 373 | 374 | player.setAudioObject(this.audioObject); 375 | return; 376 | } 377 | case "setAudioObjects": { 378 | final ArrayList smallIconFileNames = call.argument("smallIconFileNames"); 379 | final ArrayList titles = call.argument("titles"); 380 | final ArrayList subTitles = call.argument("subTitles"); 381 | final ArrayList largeIconUrls = call.argument("largeIconUrls"); 382 | final ArrayList notificationDefaultActionsInts = call.argument("notificationDefaultActionsList"); 383 | final ArrayList notificationActionCallbackModeInts = call.argument("notificationActionCallbackModes"); 384 | final ArrayList notificationCustomActionsInts = call.argument("notificationCustomActionsList"); 385 | 386 | for (int i = 0; i < smallIconFileNames.size(); i++) { 387 | NotificationDefaultActions notificationDefaultActions; 388 | NotificationActionCallbackMode notificationActionCallbackMode; 389 | NotificationCustomActions notificationCustomActions; 390 | if (notificationDefaultActionsInts.get(i) == 0) { 391 | notificationDefaultActions = NotificationDefaultActions.NONE; 392 | } else if (notificationDefaultActionsInts.get(i) == 1) { 393 | notificationDefaultActions = NotificationDefaultActions.NEXT; 394 | } else if (notificationDefaultActionsInts.get(i) == 2) { 395 | notificationDefaultActions = NotificationDefaultActions.PREVIOUS; 396 | } else { 397 | notificationDefaultActions = NotificationDefaultActions.ALL; 398 | } 399 | 400 | if (notificationCustomActionsInts.get(i) == 1) { 401 | notificationCustomActions = NotificationCustomActions.ONE; 402 | } else if (notificationCustomActionsInts.get(i) == 2) { 403 | notificationCustomActions = NotificationCustomActions.TWO; 404 | } else { 405 | notificationCustomActions = NotificationCustomActions.DISABLED; 406 | } 407 | 408 | if (notificationActionCallbackModeInts.get(i) == 0) { 409 | notificationActionCallbackMode = NotificationActionCallbackMode.DEFAULT; 410 | } else { 411 | notificationActionCallbackMode = NotificationActionCallbackMode.CUSTOM; 412 | } 413 | 414 | this.audioObjects 415 | .add(new AudioObject(smallIconFileNames.get(i), titles.get(i), subTitles.get(i), largeIconUrls.get(i), 416 | notificationDefaultActions, notificationActionCallbackMode, notificationCustomActions)); 417 | } 418 | 419 | player.setAudioObjects(this.audioObjects); 420 | return; 421 | } 422 | case "setSpecificAudioNotification": { 423 | final String smallIconFileName = call.argument("smallIconFileName"); 424 | final String title = call.argument("title"); 425 | final String subTitle = call.argument("subTitle"); 426 | final String largeIconUrl = call.argument("largeIconUrl"); 427 | final int notificationDefaultActionsInt = call.argument("notificationDefaultActions"); 428 | final int notificationActionCallbackModeInt = call.argument("notificationActionCallbackMode"); 429 | final int notificationCustomActionsInt = call.argument("notificationCustomActions"); 430 | final int index = call.argument("index"); 431 | 432 | NotificationDefaultActions notificationDefaultActions; 433 | NotificationActionCallbackMode notificationActionCallbackMode; 434 | NotificationCustomActions notificationCustomActions; 435 | if (notificationDefaultActionsInt == 0) { 436 | notificationDefaultActions = NotificationDefaultActions.NONE; 437 | } else if (notificationDefaultActionsInt == 1) { 438 | notificationDefaultActions = NotificationDefaultActions.NEXT; 439 | } else if (notificationDefaultActionsInt == 2) { 440 | notificationDefaultActions = NotificationDefaultActions.PREVIOUS; 441 | } else { 442 | notificationDefaultActions = NotificationDefaultActions.ALL; 443 | } 444 | 445 | if (notificationCustomActionsInt == 1) { 446 | notificationCustomActions = NotificationCustomActions.ONE; 447 | } else if (notificationCustomActionsInt == 2) { 448 | notificationCustomActions = NotificationCustomActions.TWO; 449 | } else { 450 | notificationCustomActions = NotificationCustomActions.DISABLED; 451 | } 452 | 453 | if (notificationActionCallbackModeInt == 0) { 454 | notificationActionCallbackMode = NotificationActionCallbackMode.DEFAULT; 455 | } else { 456 | notificationActionCallbackMode = NotificationActionCallbackMode.CUSTOM; 457 | } 458 | 459 | this.audioObject = new AudioObject(smallIconFileName, title, subTitle, largeIconUrl, notificationDefaultActions, 460 | notificationActionCallbackMode, notificationCustomActions); 461 | 462 | player.setSpecificAudioObject(this.audioObject, index); 463 | return; 464 | } 465 | case "getVolume": { 466 | response.success(player.getVolume()); 467 | return; 468 | } 469 | case "getDuration": { 470 | response.success(player.getDuration()); 471 | return; 472 | } 473 | case "getCurrentPosition": { 474 | response.success(player.getCurrentPosition()); 475 | return; 476 | } 477 | case "getCurrentPlayingAudioIndex": { 478 | response.success(player.getCurrentPlayingAudioIndex()); 479 | return; 480 | } 481 | case "getPlaybackSpeed": { 482 | response.success(player.getPlaybackSpeed()); 483 | return; 484 | } 485 | case "dispose": { 486 | dispose(); 487 | return; 488 | } 489 | default: { 490 | response.notImplemented(); 491 | return; 492 | } 493 | } 494 | response.success(2); // success 495 | } else { 496 | response.success(1); // fail 497 | } 498 | } 499 | 500 | public void handleNotificationActionCallback(AudioPlayer audioplayer, NotificationActionName notificationActionName) { 501 | switch (notificationActionName) { 502 | case PREVIOUS: 503 | channel.invokeMethod("audio.onNotificationActionCallback", buildArguments(audioplayer.getPlayerId(), 0)); 504 | break; 505 | case NEXT: 506 | channel.invokeMethod("audio.onNotificationActionCallback", buildArguments(audioplayer.getPlayerId(), 1)); 507 | break; 508 | case PLAY: 509 | channel.invokeMethod("audio.onNotificationActionCallback", buildArguments(audioplayer.getPlayerId(), 2)); 510 | break; 511 | case PAUSE: 512 | channel.invokeMethod("audio.onNotificationActionCallback", buildArguments(audioplayer.getPlayerId(), 3)); 513 | break; 514 | case CUSTOM1: 515 | channel.invokeMethod("audio.onNotificationActionCallback", buildArguments(audioplayer.getPlayerId(), 4)); 516 | break; 517 | case CUSTOM2: 518 | channel.invokeMethod("audio.onNotificationActionCallback", buildArguments(audioplayer.getPlayerId(), 5)); 519 | break; 520 | } 521 | } 522 | 523 | public void handleAudioSessionIdChange(AudioPlayer audioplayer, int audioSessionId) { 524 | channel.invokeMethod("audio.onAudioSessionIdChange", buildArguments(audioplayer.getPlayerId(), audioSessionId)); 525 | } 526 | 527 | public void handlePlayerIndex(AudioPlayer audioplayer) { 528 | channel.invokeMethod("audio.onCurrentPlayingAudioIndexChange", 529 | buildArguments(audioplayer.getPlayerId(), audioplayer.getCurrentPlayingAudioIndex())); 530 | } 531 | 532 | public void handleStateChange(AudioPlayer audioplayer, PlayerState playerState) { 533 | switch (playerState) { 534 | case RELEASED: { // -1 535 | channel.invokeMethod("audio.onStateChanged", buildArguments(audioplayer.getPlayerId(), -1)); 536 | break; 537 | } 538 | case STOPPED: { // 0 539 | channel.invokeMethod("audio.onStateChanged", buildArguments(audioplayer.getPlayerId(), 0)); 540 | break; 541 | } 542 | case BUFFERING: { // 1 543 | channel.invokeMethod("audio.onStateChanged", buildArguments(audioplayer.getPlayerId(), 1)); 544 | break; 545 | } 546 | case PLAYING: { // 2 547 | channel.invokeMethod("audio.onStateChanged", buildArguments(audioplayer.getPlayerId(), 2)); 548 | break; 549 | } 550 | case PAUSED: { // 3 551 | channel.invokeMethod("audio.onStateChanged", buildArguments(audioplayer.getPlayerId(), 3)); 552 | break; 553 | } 554 | case COMPLETED: { // 4 555 | channel.invokeMethod("audio.onStateChanged", buildArguments(audioplayer.getPlayerId(), 4)); 556 | break; 557 | } 558 | } 559 | } 560 | 561 | public void handlePositionUpdates() { 562 | startPositionUpdates(); 563 | } 564 | 565 | private AudioPlayer getPlayer(String playerId) { 566 | return audioPlayers.get(playerId); 567 | } 568 | 569 | private void startForegroundPlayer() { 570 | if (!isMyServiceRunning(ForegroundAudioPlayer.class)) { 571 | ContextCompat.startForegroundService(this.context, new Intent(this.context, ForegroundAudioPlayer.class)); 572 | this.context.bindService(new Intent(this.context, ForegroundAudioPlayer.class), connection, 573 | Context.BIND_AUTO_CREATE); 574 | } else { 575 | Log.e("AudioPlayerPlugin", "Can't start more than 1 service at a time, to stop service call release method"); 576 | } 577 | } 578 | 579 | private void startPositionUpdates() { 580 | if (positionUpdates != null) { 581 | return; 582 | } 583 | positionUpdates = new UpdateCallback(audioPlayers, channel, handler, this); 584 | handler.post(positionUpdates); 585 | } 586 | 587 | private void stopPositionUpdates() { 588 | positionUpdates = null; 589 | handler.removeCallbacksAndMessages(null); 590 | } 591 | 592 | private static Map buildArguments(String playerId, Object value) { 593 | Map result = new HashMap<>(); 594 | result.put("playerId", playerId); 595 | result.put("value", value); 596 | return result; 597 | } 598 | 599 | private void dispose() { 600 | for (AudioPlayer player : audioPlayers.values()) { 601 | if (player.isPlayerInitialized()) { 602 | if (!player.isBackground() && !player.isPlayerReleased()) { 603 | this.context.unbindService(connection); 604 | } 605 | player.release(); 606 | } 607 | } 608 | audioPlayers.clear(); 609 | } 610 | 611 | @SuppressWarnings("deprecation") 612 | private boolean isMyServiceRunning(Class serviceClass) { 613 | ActivityManager manager = (ActivityManager) this.context.getSystemService(Context.ACTIVITY_SERVICE); 614 | for (ActivityManager.RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) { 615 | if (serviceClass.getName().equals(service.service.getClassName())) { 616 | return true; 617 | } 618 | } 619 | return false; 620 | } 621 | 622 | private static final class UpdateCallback implements Runnable { 623 | 624 | private final WeakReference> audioPlayers; 625 | private final WeakReference channel; 626 | private final WeakReference handler; 627 | private final WeakReference audioPlayerPlugin; 628 | 629 | private UpdateCallback(final Map audioPlayers, final MethodChannel channel, 630 | final Handler handler, final AudioPlayerPlugin audioPlayerPlugin) { 631 | this.audioPlayers = new WeakReference<>(audioPlayers); 632 | this.channel = new WeakReference<>(channel); 633 | this.handler = new WeakReference<>(handler); 634 | this.audioPlayerPlugin = new WeakReference<>(audioPlayerPlugin); 635 | } 636 | 637 | @Override 638 | public void run() { 639 | final Map audioPlayers = this.audioPlayers.get(); 640 | final MethodChannel channel = this.channel.get(); 641 | final Handler handler = this.handler.get(); 642 | final AudioPlayerPlugin audioPlayerPlugin = this.audioPlayerPlugin.get(); 643 | 644 | if (audioPlayers == null || channel == null || handler == null || audioPlayerPlugin == null) { 645 | if (audioPlayerPlugin != null) { 646 | audioPlayerPlugin.stopPositionUpdates(); 647 | } 648 | return; 649 | } 650 | 651 | boolean nonePlaying = true; 652 | for (AudioPlayer player : audioPlayers.values()) { 653 | if (!player.isPlaying()) { 654 | if (player.isPlayerCompleted()) { 655 | channel.invokeMethod("audio.onDurationChanged", buildArguments(player.getPlayerId(), player.getDuration())); 656 | } 657 | continue; 658 | } 659 | try { 660 | nonePlaying = false; 661 | channel.invokeMethod("audio.onDurationChanged", buildArguments(player.getPlayerId(), player.getDuration())); 662 | channel.invokeMethod("audio.onCurrentPositionChanged", 663 | buildArguments(player.getPlayerId(), player.getCurrentPosition())); 664 | } catch (UnsupportedOperationException e) { 665 | Log.e("AudioPlayerPlugin", "Error when updating position and duration"); 666 | } 667 | } 668 | 669 | if (nonePlaying) { 670 | audioPlayerPlugin.stopPositionUpdates(); 671 | } else { 672 | handler.postDelayed(this, 200); 673 | } 674 | } 675 | } 676 | } 677 | -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/audioplayers/BackgroundAudioPlayer.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.audioplayers; 2 | 3 | import danielr2001.audioplayer.interfaces.AudioPlayer; 4 | import danielr2001.audioplayer.notifications.MediaNotificationManager; 5 | import danielr2001.audioplayer.AudioPlayerPlugin; 6 | import danielr2001.audioplayer.models.AudioObject; 7 | import danielr2001.audioplayer.enums.PlayerState; 8 | import danielr2001.audioplayer.enums.PlayerMode; 9 | 10 | import android.app.Activity; 11 | import android.content.Intent; 12 | import android.content.Context; 13 | import android.net.Uri; 14 | import android.os.Binder; 15 | import android.os.IBinder; 16 | import android.util.Log; 17 | 18 | import androidx.annotation.Nullable; 19 | 20 | import com.google.android.exoplayer2.C; 21 | import com.google.android.exoplayer2.ExoPlaybackException; 22 | import com.google.android.exoplayer2.ExoPlayerFactory; 23 | import com.google.android.exoplayer2.Player; 24 | import com.google.android.exoplayer2.SimpleExoPlayer; 25 | import com.google.android.exoplayer2.Timeline; 26 | import com.google.android.exoplayer2.audio.AudioAttributes; 27 | import com.google.android.exoplayer2.analytics.AnalyticsListener; 28 | import com.google.android.exoplayer2.source.ConcatenatingMediaSource; 29 | import com.google.android.exoplayer2.source.MediaSource; 30 | import com.google.android.exoplayer2.source.ProgressiveMediaSource; 31 | import com.google.android.exoplayer2.source.TrackGroupArray; 32 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 33 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; 34 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; 35 | import com.google.android.exoplayer2.util.Util; 36 | import com.google.android.exoplayer2.PlaybackParameters; 37 | 38 | import java.util.ArrayList; 39 | 40 | public class BackgroundAudioPlayer implements AudioPlayer { 41 | 42 | private Context context; 43 | private AudioPlayerPlugin ref; 44 | private BackgroundAudioPlayer backgroundAudioPlayer; 45 | private String playerId; 46 | 47 | // player attributes 48 | private float speed = 1f; 49 | private float volume = 1; 50 | private boolean repeatMode; 51 | private boolean respectAudioFocus; 52 | private PlayerMode playerMode; 53 | 54 | // player states 55 | private boolean initialized = false; 56 | private boolean buffering = false; 57 | private boolean playing = false; 58 | private boolean stopped = false; 59 | private boolean released = true; 60 | private boolean completed = false; 61 | 62 | // ExoPlayer 63 | private SimpleExoPlayer player; 64 | 65 | private ArrayList audioObjects; 66 | private AudioObject audioObject; 67 | 68 | private static final String TAG = "BackgroundAudioPlayer"; 69 | 70 | @Override 71 | public void setAudioObjects(ArrayList audioObjects) { 72 | } 73 | 74 | @Override 75 | public void setAudioObject(AudioObject audioObject) { 76 | } 77 | 78 | @Override 79 | public void setSpecificAudioObject(AudioObject audioObject, int index) { 80 | } 81 | 82 | @Override 83 | public void initAudioPlayer(AudioPlayerPlugin ref, Activity activity, String playerId) { 84 | this.initialized = true; 85 | 86 | this.ref = ref; 87 | this.context = activity.getApplicationContext(); 88 | this.playerId = playerId; 89 | this.backgroundAudioPlayer = this; 90 | } 91 | 92 | @Override 93 | public void initExoPlayer(int index, int position) { 94 | player = new SimpleExoPlayer.Builder(context).setTrackSelector(new DefaultTrackSelector(context)).build(); 95 | DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(this.context, 96 | Util.getUserAgent(this.context, "exoPlayerLibrary")); 97 | // playlist/single audio load 98 | if (playerMode == PlayerMode.PLAYLIST) { 99 | ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(); 100 | for (AudioObject audioObject : audioObjects) { 101 | MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory) 102 | .createMediaSource(Uri.parse(audioObject.getUrl())); 103 | concatenatingMediaSource.addMediaSource(mediaSource); 104 | } 105 | player.prepare(concatenatingMediaSource); 106 | if (index != 0) { 107 | player.seekTo(index, position); 108 | } 109 | } else { 110 | MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory) 111 | .createMediaSource(Uri.parse(audioObject.getUrl())); 112 | player.prepare(mediaSource); 113 | 114 | player.seekTo(0, position); 115 | } 116 | // handle audio focus 117 | if (this.respectAudioFocus) { 118 | AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA) 119 | .setContentType(C.CONTENT_TYPE_MUSIC).build(); 120 | player.setAudioAttributes(audioAttributes, true); 121 | } 122 | // set repeat mode 123 | if (repeatMode) { 124 | player.setRepeatMode(player.REPEAT_MODE_ALL); 125 | } 126 | } 127 | 128 | @Override 129 | public void play(AudioObject audioObject, int position) { 130 | if (this.completed || this.stopped) { 131 | this.resume(); 132 | } else { 133 | if(this.playing){ 134 | this.stop(); 135 | } 136 | this.stopped = false; 137 | this.released = false; 138 | 139 | this.audioObject = audioObject; 140 | this.initExoPlayer(0, position); 141 | initEventListeners(); 142 | player.setPlayWhenReady(true); 143 | } 144 | } 145 | 146 | @Override 147 | public void playAll(ArrayList audioObjects, int index, int position) { 148 | if (this.completed || this.stopped) { 149 | this.resume(); 150 | } else { 151 | if(this.playing){ 152 | this.stop(); 153 | } 154 | this.stopped = false; 155 | this.released = false; 156 | 157 | this.audioObjects = audioObjects; 158 | this.initExoPlayer(index, position); 159 | initEventListeners(); 160 | player.setPlayWhenReady(true); 161 | } 162 | } 163 | 164 | @Override 165 | public void next() { 166 | if (!this.released) { 167 | player.next(); 168 | resume(); 169 | } 170 | } 171 | 172 | @Override 173 | public void previous() { 174 | if (!this.released) { 175 | player.previous(); 176 | resume(); 177 | } 178 | } 179 | 180 | @Override 181 | public void pause() { 182 | if (!this.released && this.playing) { 183 | player.setPlayWhenReady(false); 184 | } 185 | } 186 | 187 | @Override 188 | public void resume() { 189 | if (!this.released && !this.playing) { 190 | if (!this.stopped) { 191 | this.completed = false; 192 | player.setPlayWhenReady(true); 193 | } else { 194 | this.stopped = false; 195 | this.initExoPlayer(0 , 0); 196 | initEventListeners(); 197 | player.setPlayWhenReady(true); 198 | } 199 | } 200 | } 201 | 202 | @Override 203 | public void stop() { 204 | if (!this.released) { 205 | player.stop(true); 206 | } 207 | } 208 | 209 | @Override 210 | public void release() { 211 | if (!this.released) { 212 | this.initialized = false; 213 | this.buffering = false; 214 | this.playing = false; 215 | this.stopped = false; 216 | this.released = true; 217 | this.completed = false; 218 | 219 | this.audioObject = null; 220 | this.audioObjects = null; 221 | player.release(); 222 | player = null; 223 | ref.handleStateChange(this, PlayerState.RELEASED); 224 | } 225 | } 226 | 227 | @Override 228 | public void seekPosition(int position) { 229 | if (!this.released) { 230 | player.seekTo(player.getCurrentWindowIndex(), position); 231 | } 232 | } 233 | 234 | @Override 235 | public void seekIndex(int index) { 236 | if (!this.released && playerMode == PlayerMode.PLAYLIST) { 237 | player.seekTo(index, 0); 238 | } 239 | } 240 | 241 | @Override 242 | public boolean isPlaying() { 243 | return this.playing; 244 | } 245 | 246 | @Override 247 | public boolean isBackground() { 248 | return true; 249 | } 250 | 251 | @Override 252 | public boolean isPlayerInitialized() { 253 | return this.initialized; 254 | } 255 | 256 | @Override 257 | public boolean isPlayerReleased() { 258 | return this.released; 259 | } 260 | 261 | @Override 262 | public boolean isPlayerCompleted() { 263 | return this.completed; 264 | } 265 | 266 | @Override 267 | public String getPlayerId() { 268 | return this.playerId; 269 | } 270 | 271 | @Override 272 | public long getDuration() { 273 | if (!this.released) { 274 | return player.getDuration(); 275 | } else { 276 | return -1; 277 | } 278 | } 279 | 280 | @Override 281 | public long getCurrentPosition() { 282 | if (!this.released) { 283 | return player.getCurrentPosition(); 284 | } else { 285 | return -1; 286 | } 287 | } 288 | 289 | @Override 290 | public int getCurrentPlayingAudioIndex() { 291 | return player.getCurrentWindowIndex(); 292 | } 293 | 294 | @Override 295 | public float getVolume() { 296 | return player.getVolume(); 297 | } 298 | 299 | @Override 300 | public float getPlaybackSpeed() { 301 | return this.speed; 302 | } 303 | 304 | @Override 305 | public void setPlayerAttributes(boolean repeatMode, boolean respectAudioFocus, PlayerMode playerMode) { 306 | this.repeatMode = repeatMode; 307 | this.respectAudioFocus = respectAudioFocus; 308 | this.playerMode = playerMode; 309 | } 310 | 311 | @Override 312 | public void setVolume(float volume) { 313 | if (!this.released && this.volume != volume) { 314 | this.volume = volume; 315 | player.setVolume(volume); 316 | } 317 | } 318 | 319 | @Override 320 | public void setRepeatMode(boolean repeatMode) { 321 | if (!this.released && this.repeatMode != repeatMode) { 322 | this.repeatMode = repeatMode; 323 | if (this.repeatMode) { 324 | player.setRepeatMode(player.REPEAT_MODE_ALL); 325 | } else { 326 | player.setRepeatMode(player.REPEAT_MODE_OFF); 327 | } 328 | } 329 | } 330 | 331 | @Override 332 | public void setPlaybackSpeed(float speed) { 333 | if (!this.released && this.speed != speed) { 334 | this.speed = speed; 335 | PlaybackParameters param = new PlaybackParameters(speed); 336 | player.setPlaybackParameters(param); 337 | } 338 | } 339 | 340 | private void initEventListeners() { 341 | player.addAnalyticsListener(new AnalyticsListener() { 342 | @Override 343 | public void onAudioSessionId(EventTime eventTime, int audioSessionId) { 344 | ref.handleAudioSessionIdChange(backgroundAudioPlayer, audioSessionId); 345 | } 346 | }); 347 | player.addListener(new Player.EventListener() { 348 | 349 | @Override 350 | public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { 351 | ref.handlePlayerIndex(backgroundAudioPlayer); 352 | } 353 | 354 | @Override 355 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { 356 | switch (playbackState) { 357 | case Player.STATE_BUFFERING: { 358 | // buffering 359 | buffering = true; 360 | ref.handleStateChange(backgroundAudioPlayer, PlayerState.BUFFERING); 361 | break; 362 | } 363 | case Player.STATE_READY: { 364 | if (completed) { 365 | buffering = false; 366 | ref.handleStateChange(backgroundAudioPlayer, PlayerState.COMPLETED); 367 | } else if (buffering) { 368 | // playing 369 | buffering = false; 370 | if (playWhenReady) { 371 | playing = true; 372 | ref.handlePositionUpdates(); 373 | ref.handleStateChange(backgroundAudioPlayer, PlayerState.PLAYING); 374 | } else { 375 | ref.handleStateChange(backgroundAudioPlayer, PlayerState.PAUSED); 376 | } 377 | } else if (playWhenReady) { 378 | // resumed 379 | playing = true; 380 | ref.handlePositionUpdates(); 381 | ref.handleStateChange(backgroundAudioPlayer, PlayerState.PLAYING); 382 | } else if (!playWhenReady) { 383 | // paused 384 | playing = false; 385 | ref.handleStateChange(backgroundAudioPlayer, PlayerState.PAUSED); 386 | } 387 | break; 388 | } 389 | case Player.STATE_ENDED: { 390 | // completed 391 | playing = false; 392 | completed = true; 393 | player.setPlayWhenReady(false); 394 | player.seekTo(0, 0); 395 | break; 396 | } 397 | case Player.STATE_IDLE: { 398 | // stopped 399 | playing = false; 400 | stopped = true; 401 | completed = false; 402 | buffering = false; 403 | ref.handleStateChange(backgroundAudioPlayer, PlayerState.STOPPED); 404 | break; 405 | } 406 | // handle of released is in release method! 407 | } 408 | } 409 | 410 | @Override 411 | public void onPlayerError(ExoPlaybackException error) { 412 | Log.e(TAG, error.getMessage()); 413 | } 414 | }); 415 | } 416 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/audioplayers/ForegroundAudioPlayer.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.audioplayers; 2 | 3 | import danielr2001.audioplayer.interfaces.AudioPlayer; 4 | import danielr2001.audioplayer.notifications.MediaNotificationManager; 5 | import danielr2001.audioplayer.AudioPlayerPlugin; 6 | import danielr2001.audioplayer.models.AudioObject; 7 | import danielr2001.audioplayer.enums.PlayerState; 8 | import danielr2001.audioplayer.enums.NotificationActionName; 9 | import danielr2001.audioplayer.enums.NotificationActionCallbackMode; 10 | import danielr2001.audioplayer.enums.PlayerMode; 11 | 12 | import android.app.Activity; 13 | import android.app.Service; 14 | import android.content.Intent; 15 | import android.content.Context; 16 | import android.net.Uri; 17 | import android.os.Binder; 18 | import android.os.IBinder; 19 | 20 | import android.support.v4.media.session.MediaSessionCompat; 21 | import android.util.Log; 22 | 23 | import androidx.annotation.Nullable; 24 | import androidx.media.session.MediaButtonReceiver; 25 | 26 | import com.google.android.exoplayer2.C; 27 | import com.google.android.exoplayer2.ExoPlaybackException; 28 | import com.google.android.exoplayer2.ExoPlayerFactory; 29 | import com.google.android.exoplayer2.Player; 30 | import com.google.android.exoplayer2.SimpleExoPlayer; 31 | import com.google.android.exoplayer2.Timeline; 32 | import com.google.android.exoplayer2.audio.AudioAttributes; 33 | import com.google.android.exoplayer2.analytics.AnalyticsListener; 34 | import com.google.android.exoplayer2.source.ConcatenatingMediaSource; 35 | import com.google.android.exoplayer2.source.MediaSource; 36 | import com.google.android.exoplayer2.source.ProgressiveMediaSource; 37 | import com.google.android.exoplayer2.source.TrackGroupArray; 38 | import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 39 | import com.google.android.exoplayer2.trackselection.DefaultTrackSelector; 40 | import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; 41 | import com.google.android.exoplayer2.util.Util; 42 | import com.google.android.exoplayer2.PlaybackParameters; 43 | 44 | import java.util.ArrayList; 45 | 46 | public class ForegroundAudioPlayer extends Service implements AudioPlayer { 47 | private final IBinder binder = new LocalBinder(); 48 | 49 | public class LocalBinder extends Binder { 50 | public ForegroundAudioPlayer getService() { 51 | return ForegroundAudioPlayer.this; 52 | } 53 | } 54 | 55 | private ForegroundAudioPlayer foregroundAudioPlayer; 56 | private MediaNotificationManager mediaNotificationManager; 57 | private Context context; 58 | private AudioPlayerPlugin ref; 59 | private MediaSessionCompat mediaSession; 60 | private String playerId; 61 | 62 | // player attributes 63 | private float speed = 1f; 64 | private float volume = 1; 65 | private boolean repeatMode; 66 | private boolean respectAudioFocus; 67 | private PlayerMode playerMode; 68 | 69 | // player states 70 | private boolean initialized = false; 71 | private boolean buffering = false; 72 | private boolean playing = false; 73 | private boolean stopped = false; 74 | private boolean released = true; 75 | private boolean completed = false; 76 | 77 | // ExoPlayer 78 | private SimpleExoPlayer player; 79 | 80 | private ArrayList audioObjects; 81 | private AudioObject audioObject; 82 | 83 | private static final String TAG = "ForegroundAudioPlayer"; 84 | 85 | @Nullable 86 | @Override 87 | public IBinder onBind(Intent intent) { 88 | return binder; 89 | } 90 | 91 | @Override 92 | public int onStartCommand(Intent intent, int flags, int startId) { 93 | this.context = getApplicationContext(); 94 | mediaSession = new MediaSessionCompat(this.context, "playback"); 95 | // ! TODO handle MediaButtonReceiver's callbacks 96 | // MediaButtonReceiver.handleIntent(mediaSession, intent); 97 | // mediaSession.setCallback(mediaSessionCallback); 98 | if (intent.getAction() != null) { 99 | AudioObject currentAudioObject; 100 | if (this.playerMode == PlayerMode.PLAYLIST) { 101 | currentAudioObject = this.audioObjects.get(player.getCurrentWindowIndex()); 102 | } else { 103 | currentAudioObject = this.audioObject; 104 | } 105 | if (intent.getAction().equals(MediaNotificationManager.PREVIOUS_ACTION)) { 106 | if (currentAudioObject.getNotificationActionCallbackMode() == NotificationActionCallbackMode.DEFAULT) { 107 | previous(); 108 | } else { 109 | ref.handleNotificationActionCallback(this.foregroundAudioPlayer, NotificationActionName.PREVIOUS); 110 | } 111 | } else if (intent.getAction().equals(MediaNotificationManager.PLAY_ACTION)) { 112 | if (currentAudioObject.getNotificationActionCallbackMode() == NotificationActionCallbackMode.DEFAULT) { 113 | if (!stopped) { 114 | resume(); 115 | } else { 116 | if (playerMode == PlayerMode.PLAYLIST) { 117 | playAll(audioObjects, 0, 0); 118 | } else { 119 | play(audioObject, 0); 120 | } 121 | } 122 | } else { 123 | ref.handleNotificationActionCallback(this.foregroundAudioPlayer, NotificationActionName.PLAY); 124 | } 125 | } else if (intent.getAction().equals(MediaNotificationManager.PAUSE_ACTION)) { 126 | if (currentAudioObject.getNotificationActionCallbackMode() == NotificationActionCallbackMode.DEFAULT) { 127 | pause(); 128 | } else { 129 | ref.handleNotificationActionCallback(this.foregroundAudioPlayer, NotificationActionName.PAUSE); 130 | } 131 | } else if (intent.getAction().equals(MediaNotificationManager.NEXT_ACTION)) { 132 | if (currentAudioObject.getNotificationActionCallbackMode() == NotificationActionCallbackMode.DEFAULT) { 133 | next(); 134 | } else { 135 | ref.handleNotificationActionCallback(this.foregroundAudioPlayer, NotificationActionName.NEXT); 136 | } 137 | } else if (intent.getAction().equals(MediaNotificationManager.CUSTOM1_ACTION)) { 138 | ref.handleNotificationActionCallback(this.foregroundAudioPlayer, NotificationActionName.CUSTOM1); 139 | } else if (intent.getAction().equals(MediaNotificationManager.CUSTOM2_ACTION)) { 140 | ref.handleNotificationActionCallback(this.foregroundAudioPlayer, NotificationActionName.CUSTOM2); 141 | } 142 | } 143 | return START_STICKY; 144 | } 145 | 146 | @Override 147 | public void onTaskRemoved(Intent rootIntent) { 148 | super.onTaskRemoved(rootIntent); 149 | this.release(); 150 | } 151 | 152 | @SuppressWarnings("unchecked") 153 | @Override 154 | public void setAudioObjects(ArrayList audioObjects) { 155 | if (this.audioObjects != null) { 156 | for (int i = 0; i < this.audioObjects.size(); i++) { 157 | audioObjects.get(i).setUrl(this.audioObjects.get(i).getUrl()); 158 | audioObjects.get(i).setIsLocal(this.audioObjects.get(i).getIsLocal()); 159 | } 160 | this.audioObjects = (ArrayList) audioObjects.clone(); 161 | mediaNotificationManager.makeNotification(this.audioObjects.get(player.getCurrentWindowIndex()), playing); 162 | } 163 | } 164 | 165 | @SuppressWarnings("unchecked") 166 | @Override 167 | public void setAudioObject(AudioObject audioObject) { 168 | if (this.audioObject != null) { 169 | audioObject.setUrl(this.audioObject.getUrl()); 170 | audioObject.setIsLocal(this.audioObject.getIsLocal()); 171 | this.audioObject = audioObject; 172 | mediaNotificationManager.makeNotification(this.audioObject, playing); 173 | } 174 | } 175 | 176 | @Override 177 | public void setSpecificAudioObject(AudioObject audioObject, int index) { 178 | if (this.audioObjects != null) { 179 | audioObject.setUrl(this.audioObjects.get(index).getUrl()); 180 | audioObject.setIsLocal(this.audioObjects.get(index).getIsLocal()); 181 | 182 | this.audioObjects.set(index, audioObject); 183 | if (getCurrentPlayingAudioIndex() == index) 184 | mediaNotificationManager.makeNotification(this.audioObjects.get(player.getCurrentWindowIndex()), 185 | playing); 186 | } 187 | } 188 | 189 | @Override 190 | public void initAudioPlayer(AudioPlayerPlugin ref, Activity activity, String playerId) { 191 | this.initialized = true; 192 | 193 | this.playerId = playerId; 194 | this.ref = ref; 195 | this.mediaNotificationManager = new MediaNotificationManager(this, this.context, this.mediaSession, activity); 196 | this.foregroundAudioPlayer = this; 197 | } 198 | 199 | @Override 200 | public void initExoPlayer(int index, int position) { 201 | player = new SimpleExoPlayer.Builder(context).setTrackSelector(new DefaultTrackSelector(context)).build(); 202 | DefaultDataSourceFactory dataSourceFactory = new DefaultDataSourceFactory(this.context, 203 | Util.getUserAgent(this.context, "exoPlayerLibrary")); 204 | player.setForegroundMode(true); 205 | // playlist/single audio load 206 | if (this.playerMode == PlayerMode.PLAYLIST) { 207 | ConcatenatingMediaSource concatenatingMediaSource = new ConcatenatingMediaSource(); 208 | for (AudioObject audioObject : audioObjects) { 209 | MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory) 210 | .createMediaSource(Uri.parse(audioObject.getUrl())); 211 | concatenatingMediaSource.addMediaSource(mediaSource); 212 | } 213 | player.prepare(concatenatingMediaSource); 214 | if (index != 0) { 215 | player.seekTo(index, position); 216 | } 217 | } else { 218 | MediaSource mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory) 219 | .createMediaSource(Uri.parse(this.audioObject.getUrl())); 220 | player.prepare(mediaSource); 221 | 222 | player.seekTo(0, position); 223 | } 224 | // handle audio focus 225 | if (this.respectAudioFocus) { // ! TODO catch duck pause! 226 | AudioAttributes audioAttributes = new AudioAttributes.Builder().setUsage(C.USAGE_MEDIA) 227 | .setContentType(C.CONTENT_TYPE_MUSIC).build(); 228 | player.setAudioAttributes(audioAttributes, true); 229 | } 230 | // set repeat mode 231 | if (repeatMode) { 232 | player.setRepeatMode(player.REPEAT_MODE_ALL); 233 | } 234 | } 235 | 236 | @Override 237 | public void play(AudioObject audioObject, int position) { 238 | if (this.completed) { 239 | this.resume(); 240 | } else { 241 | this.released = false; 242 | 243 | this.audioObject = audioObject; 244 | this.initExoPlayer(0, position); 245 | initEventListeners(); 246 | player.setPlayWhenReady(true); 247 | } 248 | } 249 | 250 | @Override 251 | public void playAll(ArrayList audioObjects, int index, int position) { 252 | if (this.completed || this.stopped) { 253 | this.resume(); 254 | } else { 255 | this.released = false; 256 | 257 | this.audioObjects = audioObjects; 258 | this.initExoPlayer(index, position); 259 | initEventListeners(); 260 | player.setPlayWhenReady(true); 261 | } 262 | } 263 | 264 | @Override 265 | public void next() { 266 | if (!this.released) { 267 | player.next(); 268 | resume(); 269 | } 270 | } 271 | 272 | @Override 273 | public void previous() { 274 | if (!this.released) { 275 | player.previous(); 276 | resume(); 277 | } 278 | } 279 | 280 | @Override 281 | public void pause() { 282 | if (!this.released && this.playing) { 283 | stopForeground(false); 284 | player.setPlayWhenReady(false); 285 | } 286 | } 287 | 288 | @Override 289 | public void resume() { 290 | if (!this.released && !this.playing) { 291 | if (!this.stopped) { 292 | this.completed = false; 293 | player.setPlayWhenReady(true); 294 | } else { 295 | this.stopped = false; 296 | this.initExoPlayer(0, 0); 297 | initEventListeners(); 298 | player.setPlayWhenReady(true); 299 | } 300 | } 301 | } 302 | 303 | @Override 304 | public void stop() { 305 | if (!this.released) { 306 | mediaNotificationManager.setIsShowing(false); 307 | stopForeground(true); 308 | player.stop(true); 309 | } 310 | } 311 | 312 | @Override 313 | public void release() { 314 | if (!this.released) { 315 | if (this.playing) { 316 | stopForeground(true); 317 | mediaNotificationManager.setIsShowing(false); 318 | } 319 | this.initialized = false; 320 | this.buffering = false; 321 | this.playing = false; 322 | this.stopped = false; 323 | this.released = true; 324 | this.completed = false; 325 | 326 | this.audioObject = null; 327 | this.audioObjects = null; 328 | player.release(); 329 | player = null; 330 | ref.handleStateChange(this, PlayerState.RELEASED); 331 | stopSelf(); 332 | } 333 | } 334 | 335 | @Override 336 | public void seekPosition(int position) { 337 | if (!this.released) { 338 | player.seekTo(player.getCurrentWindowIndex(), position); 339 | } 340 | } 341 | 342 | @Override 343 | public void seekIndex(int index) { 344 | if (!this.released) { 345 | player.seekTo(index, 0); 346 | } 347 | } 348 | 349 | @Override 350 | public boolean isPlaying() { 351 | return this.playing; 352 | } 353 | 354 | @Override 355 | public boolean isBackground() { 356 | return false; 357 | } 358 | 359 | @Override 360 | public boolean isPlayerInitialized() { 361 | return this.initialized; 362 | } 363 | 364 | @Override 365 | public boolean isPlayerReleased() { 366 | return this.released; 367 | } 368 | 369 | @Override 370 | public boolean isPlayerCompleted() { 371 | return this.completed; 372 | } 373 | 374 | @Override 375 | public String getPlayerId() { 376 | return this.playerId; 377 | } 378 | 379 | @Override 380 | public long getDuration() { 381 | if (!this.released) { 382 | return player.getDuration(); 383 | } else { 384 | return -1; 385 | } 386 | } 387 | 388 | @Override 389 | public long getCurrentPosition() { 390 | if (!this.released) { 391 | return player.getCurrentPosition(); 392 | } else { 393 | return -1; 394 | } 395 | } 396 | 397 | @Override 398 | public int getCurrentPlayingAudioIndex() { 399 | return player.getCurrentWindowIndex(); 400 | } 401 | 402 | @Override 403 | public float getVolume() { 404 | return player.getVolume(); 405 | } 406 | 407 | @Override 408 | public float getPlaybackSpeed() { 409 | return this.speed; 410 | } 411 | 412 | @Override 413 | public void setPlayerAttributes(boolean repeatMode, boolean respectAudioFocus, PlayerMode playerMode) { 414 | this.repeatMode = repeatMode; 415 | this.respectAudioFocus = respectAudioFocus; 416 | this.playerMode = playerMode; 417 | } 418 | 419 | @Override 420 | public void setVolume(float volume) { 421 | if (!this.released && this.volume != volume) { 422 | this.volume = volume; 423 | player.setVolume(volume); 424 | } 425 | } 426 | 427 | @Override 428 | public void setRepeatMode(boolean repeatMode) { 429 | if (!this.released && this.repeatMode != repeatMode) { 430 | this.repeatMode = repeatMode; 431 | if (this.repeatMode) { 432 | player.setRepeatMode(player.REPEAT_MODE_ALL); 433 | } else { 434 | player.setRepeatMode(player.REPEAT_MODE_OFF); 435 | } 436 | } 437 | } 438 | 439 | @Override 440 | public void setPlaybackSpeed(float speed) { 441 | if (!this.released && this.speed != speed) { 442 | this.speed = speed; 443 | PlaybackParameters param = new PlaybackParameters(speed); 444 | player.setPlaybackParameters(param); 445 | } 446 | } 447 | 448 | private void initEventListeners() { 449 | player.addAnalyticsListener(new AnalyticsListener() { 450 | @Override 451 | public void onAudioSessionId(EventTime eventTime, int audioSessionId) { 452 | ref.handleAudioSessionIdChange(foregroundAudioPlayer, audioSessionId); 453 | } 454 | }); 455 | player.addListener(new Player.EventListener() { 456 | 457 | @Override 458 | public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) { 459 | if (mediaNotificationManager.isShowing() || !mediaNotificationManager.isInitialized()) { 460 | if (playerMode == PlayerMode.PLAYLIST) { 461 | mediaNotificationManager.makeNotification(audioObjects.get(player.getCurrentWindowIndex()), 462 | true); 463 | } else { 464 | mediaNotificationManager.makeNotification(audioObject, true); 465 | } 466 | } else { 467 | mediaNotificationManager.setIsInitialized(false); // the player was stopped. 468 | } 469 | ref.handlePlayerIndex(foregroundAudioPlayer); 470 | } 471 | 472 | @Override 473 | public void onPlayerStateChanged(boolean playWhenReady, int playbackState) { 474 | switch (playbackState) { 475 | case Player.STATE_BUFFERING: { 476 | // buffering 477 | buffering = true; 478 | ref.handleStateChange(foregroundAudioPlayer, PlayerState.BUFFERING); 479 | break; 480 | } 481 | case Player.STATE_READY: { 482 | if (completed) { 483 | buffering = false; 484 | if (mediaNotificationManager.isShowing()) { 485 | mediaNotificationManager.makeNotification(false); 486 | } 487 | ref.handleStateChange(foregroundAudioPlayer, PlayerState.COMPLETED); 488 | } else if (buffering) { 489 | // playing 490 | buffering = false; 491 | if (playWhenReady) { 492 | playing = true; 493 | if (mediaNotificationManager.isShowing()) { 494 | mediaNotificationManager.makeNotification(true); 495 | } 496 | ref.handleStateChange(foregroundAudioPlayer, PlayerState.PLAYING); 497 | ref.handlePositionUpdates(); 498 | } else { 499 | ref.handleStateChange(foregroundAudioPlayer, PlayerState.PAUSED); 500 | } 501 | } else if (playWhenReady) { 502 | // resumed 503 | playing = true; 504 | if (mediaNotificationManager.isShowing()) { 505 | mediaNotificationManager.makeNotification(true); 506 | } 507 | ref.handlePositionUpdates(); 508 | ref.handleStateChange(foregroundAudioPlayer, PlayerState.PLAYING); 509 | } else if (!playWhenReady) { 510 | // paused 511 | playing = false; 512 | if (mediaNotificationManager.isShowing()) { 513 | mediaNotificationManager.makeNotification(false); 514 | } 515 | ref.handleStateChange(foregroundAudioPlayer, PlayerState.PAUSED); 516 | } 517 | 518 | break; 519 | } 520 | case Player.STATE_ENDED: { 521 | // completed 522 | playing = false; 523 | completed = true; 524 | 525 | stopForeground(false); 526 | player.setPlayWhenReady(false); 527 | player.seekTo(0, 0); 528 | break; 529 | } 530 | case Player.STATE_IDLE: { 531 | // stopped 532 | playing = false; 533 | stopped = true; 534 | completed = false; 535 | buffering = false; 536 | ref.handleStateChange(foregroundAudioPlayer, PlayerState.STOPPED); 537 | 538 | break; 539 | } // handle of released is in release method! 540 | } 541 | } 542 | 543 | @Override 544 | public void onPlayerError(ExoPlaybackException error) { 545 | Log.e(TAG, error.getMessage()); 546 | } 547 | }); 548 | } 549 | 550 | //// private MediaSessionCompat.Callback mediaSessionCallback = new 551 | //// MediaSessionCompat.Callback() { 552 | //// @Override 553 | //// public void onPlay() { 554 | //// Log.d("hii","play!"); 555 | //// super.onPlay(); 556 | //// } 557 | 558 | //// @Override 559 | //// public void onPause() { 560 | //// Log.d("hii","pause!"); 561 | //// super.onPause(); 562 | //// } 563 | //// }; 564 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/enums/NotificationActionCallbackMode.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.enums; 2 | 3 | public enum NotificationActionCallbackMode { 4 | CUSTOM, 5 | DEFAULT, 6 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/enums/NotificationActionName.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.enums; 2 | 3 | public enum NotificationActionName { 4 | PREVIOUS, 5 | NEXT, 6 | PLAY, 7 | PAUSE, 8 | CUSTOM1, 9 | CUSTOM2, 10 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/enums/NotificationCustomActions.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.enums; 2 | 3 | public enum NotificationCustomActions { 4 | DISABLED, 5 | ONE, 6 | TWO, 7 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/enums/NotificationDefaultActions.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.enums; 2 | 3 | public enum NotificationDefaultActions { 4 | NONE, 5 | NEXT, 6 | PREVIOUS, 7 | ALL, 8 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/enums/PlayerMode.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.enums; 2 | 3 | public enum PlayerMode { 4 | PLAYLIST, 5 | SINGLE, 6 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/enums/PlayerState.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.enums; 2 | 3 | public enum PlayerState { 4 | PLAYING, 5 | PAUSED, 6 | COMPLETED, 7 | STOPPED, 8 | RELEASED, 9 | BUFFERING, 10 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/interfaces/AsyncResponse.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.interfaces; 2 | 3 | import android.graphics.Bitmap; 4 | 5 | import java.util.Map; 6 | 7 | public interface AsyncResponse { 8 | void processFinish(Map bitmapMap); 9 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/interfaces/AudioPlayer.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.interfaces; 2 | 3 | import danielr2001.audioplayer.AudioPlayerPlugin; 4 | import danielr2001.audioplayer.models.AudioObject; 5 | import danielr2001.audioplayer.enums.PlayerMode; 6 | 7 | import android.app.Activity; 8 | import android.content.Context; 9 | 10 | import java.util.ArrayList; 11 | 12 | public interface AudioPlayer { 13 | 14 | //initializers 15 | void initAudioPlayer(AudioPlayerPlugin ref, Activity activity, String playerId); 16 | 17 | void initExoPlayer(int index, int position); 18 | 19 | //player contols 20 | void play(AudioObject audioObject, int position); 21 | 22 | void playAll(ArrayList audioObjects, int index, int position); 23 | 24 | void next(); 25 | 26 | void previous(); 27 | 28 | void pause(); 29 | 30 | void resume(); 31 | 32 | void stop(); 33 | 34 | void release(); 35 | 36 | void seekPosition(int position); 37 | 38 | void seekIndex(int index); 39 | 40 | //state check 41 | boolean isPlaying(); 42 | 43 | boolean isBackground(); 44 | 45 | boolean isPlayerInitialized(); 46 | 47 | boolean isPlayerReleased(); 48 | 49 | boolean isPlayerCompleted(); 50 | 51 | //getters 52 | String getPlayerId(); 53 | 54 | long getDuration(); 55 | 56 | long getCurrentPosition(); 57 | 58 | int getCurrentPlayingAudioIndex(); 59 | 60 | float getVolume(); 61 | 62 | float getPlaybackSpeed(); 63 | 64 | //setters 65 | void setPlayerAttributes(boolean repeatMode, boolean respectAudioFocus, PlayerMode playerMode); 66 | 67 | void setVolume(float volume); 68 | 69 | void setRepeatMode(boolean repeatMode); 70 | 71 | void setAudioObjects(ArrayList audioObjects); 72 | 73 | void setAudioObject(AudioObject audioObject); 74 | 75 | void setSpecificAudioObject(AudioObject audioObject, int index); 76 | 77 | void setPlaybackSpeed(float speed); 78 | } 79 | -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/models/AudioObject.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.models; 2 | 3 | import danielr2001.audioplayer.enums.NotificationDefaultActions; 4 | import danielr2001.audioplayer.enums.NotificationCustomActions; 5 | import danielr2001.audioplayer.enums.NotificationActionCallbackMode; 6 | 7 | import android.graphics.Bitmap; 8 | 9 | public class AudioObject { 10 | private String url; 11 | private String smallIconFileName; 12 | private String title; 13 | private String subTitle; 14 | private String largeIconUrl; 15 | private boolean isLocal; 16 | private NotificationDefaultActions notificationDefaultActions; 17 | private NotificationActionCallbackMode notificationActionCallbackMode; 18 | private NotificationCustomActions notificationCustomActions; 19 | 20 | private Bitmap largeIcon; 21 | 22 | // clone 23 | public AudioObject(AudioObject audioObject) { 24 | this.url = audioObject.url; 25 | this.smallIconFileName = audioObject.smallIconFileName; 26 | this.title = audioObject.title; 27 | this.subTitle = audioObject.subTitle; 28 | this.largeIconUrl = audioObject.largeIconUrl; 29 | this.isLocal = audioObject.isLocal; 30 | this.notificationDefaultActions = audioObject.notificationDefaultActions; 31 | this.notificationActionCallbackMode = audioObject.notificationActionCallbackMode; 32 | this.notificationCustomActions = audioObject.notificationCustomActions; 33 | } 34 | 35 | // for foreground player 36 | public AudioObject(String url, String smallIconFileName, String title, String subTitle, String largeIconUrl, 37 | boolean isLocal, NotificationDefaultActions notificationDefaultActions, 38 | NotificationActionCallbackMode notificationActionCallbackMode, 39 | NotificationCustomActions notificationCustomActions) { 40 | this.url = url; 41 | this.smallIconFileName = smallIconFileName; 42 | this.title = title; 43 | this.subTitle = subTitle; 44 | this.largeIconUrl = largeIconUrl; 45 | this.isLocal = isLocal; 46 | this.notificationDefaultActions = notificationDefaultActions; 47 | this.notificationActionCallbackMode = notificationActionCallbackMode; 48 | this.notificationCustomActions = notificationCustomActions; 49 | } 50 | 51 | // for background player 52 | public AudioObject(String url) { 53 | this.url = url; 54 | } 55 | 56 | // for notification change 57 | public AudioObject(String smallIconFileName, String title, String subTitle, String largeIconUrl, 58 | NotificationDefaultActions notificationDefaultActions, 59 | NotificationActionCallbackMode notificationActionCallbackMode, 60 | NotificationCustomActions notificationCustomActions) { 61 | this.smallIconFileName = smallIconFileName; 62 | this.title = title; 63 | this.subTitle = subTitle; 64 | this.largeIconUrl = largeIconUrl; 65 | this.notificationDefaultActions = notificationDefaultActions; 66 | this.notificationActionCallbackMode = notificationActionCallbackMode; 67 | this.notificationCustomActions = notificationCustomActions; 68 | } 69 | 70 | public String getSmallIconFileName() { 71 | return smallIconFileName; 72 | } 73 | 74 | public String getTitle() { 75 | return title; 76 | } 77 | 78 | public String getSubTitle() { 79 | return subTitle; 80 | } 81 | 82 | public String getLargeIconUrl() { 83 | return largeIconUrl; 84 | } 85 | 86 | public Bitmap getLargeIcon() { 87 | return largeIcon; 88 | } 89 | 90 | public String getUrl() { 91 | return url; 92 | } 93 | 94 | public boolean getIsLocal() { 95 | return isLocal; 96 | } 97 | 98 | public NotificationDefaultActions getNotificationActionMode() { 99 | return notificationDefaultActions; 100 | } 101 | 102 | public NotificationActionCallbackMode getNotificationActionCallbackMode() { 103 | return notificationActionCallbackMode; 104 | } 105 | 106 | public NotificationCustomActions getNotificationCustomActions() { 107 | return notificationCustomActions; 108 | } 109 | 110 | public void setLargeIcon(Bitmap bitmap) { 111 | this.largeIcon = bitmap; 112 | } 113 | 114 | public void setUrl(String url) { 115 | this.url = url; 116 | } 117 | 118 | public void setIsLocal(boolean isLocal) { 119 | this.isLocal = isLocal; 120 | } 121 | } -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/notifications/LoadImageFromUrl.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.notifications; 2 | 3 | import danielr2001.audioplayer.interfaces.AsyncResponse; 4 | 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.os.AsyncTask; 8 | import android.util.Log; 9 | 10 | import java.io.IOException; 11 | import java.io.InputStream; 12 | import java.io.File; 13 | import java.net.HttpURLConnection; 14 | import java.net.MalformedURLException; 15 | import java.net.URL; 16 | import java.util.HashMap; 17 | import java.util.Map; 18 | 19 | public class LoadImageFromUrl extends AsyncTask> { 20 | 21 | private String imageUrl; 22 | private boolean isLocal; 23 | private AsyncResponse delegate = null; 24 | 25 | public LoadImageFromUrl(String imageUrl, boolean isLocal, AsyncResponse asyncResponse) { 26 | super(); 27 | this.imageUrl = imageUrl; 28 | this.isLocal = isLocal; 29 | this.delegate = asyncResponse; 30 | } 31 | 32 | @Override 33 | protected Map doInBackground(String... strings) { 34 | Map bitmapMap = new HashMap(); 35 | if (isLocal) { 36 | if (new File(this.imageUrl).exists()) { 37 | Bitmap temp = BitmapFactory.decodeFile(this.imageUrl); 38 | if (temp != null) { 39 | bitmapMap.put(this.imageUrl, temp); 40 | return bitmapMap; 41 | } 42 | } else { 43 | Log.e("ExoPlayerPlugin", "Local image doesn`t exist!"); 44 | } 45 | } else { 46 | Bitmap temp = getImageBitmapFromNetworkUrl(); 47 | if (temp != null) { 48 | bitmapMap.put(this.imageUrl, temp); 49 | return bitmapMap; 50 | } 51 | } 52 | return null; 53 | } 54 | 55 | private Bitmap getImageBitmapFromNetworkUrl() { 56 | try { 57 | URL url = new URL(this.imageUrl); 58 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 59 | connection.setDoInput(true); 60 | connection.connect(); 61 | InputStream in = connection.getInputStream(); 62 | return BitmapFactory.decodeStream(in); 63 | } catch (Exception e) { 64 | Log.e("ExoPlayerPlugin", "Failed loading image!"); 65 | return null; 66 | } 67 | } 68 | 69 | @Override 70 | protected void onPostExecute(Map bitmapMap) { 71 | super.onPostExecute(bitmapMap); 72 | delegate.processFinish(bitmapMap); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /android/src/main/java/danielr2001/audioplayer/notifications/MediaNotificationManager.java: -------------------------------------------------------------------------------- 1 | package danielr2001.audioplayer.notifications; 2 | 3 | import danielr2001.audioplayer.audioplayers.ForegroundAudioPlayer; 4 | import danielr2001.audioplayer.enums.NotificationDefaultActions; 5 | import danielr2001.audioplayer.enums.NotificationCustomActions; 6 | import danielr2001.audioplayer.interfaces.AsyncResponse; 7 | import danielr2001.audioplayer.R; 8 | import danielr2001.audioplayer.models.AudioObject; 9 | 10 | import android.app.Activity; 11 | import android.app.Notification; 12 | import android.app.NotificationChannel; 13 | import android.app.NotificationManager; 14 | import android.app.PendingIntent; 15 | import android.content.Context; 16 | import android.content.Intent; 17 | import android.content.res.Resources; 18 | import android.graphics.Bitmap; 19 | import android.os.Build; 20 | import android.support.v4.media.session.MediaSessionCompat; 21 | import android.util.Log; 22 | 23 | import androidx.core.app.NotificationCompat; 24 | 25 | import java.util.Map; 26 | 27 | public class MediaNotificationManager { 28 | public static final String PLAY_ACTION = "com.daniel.exoPlayer.action.play"; 29 | public static final String PAUSE_ACTION = "com.daniel.exoPlayer.action.pause"; 30 | public static final String PREVIOUS_ACTION = "com.daniel.exoPlayer.action.previous"; 31 | public static final String NEXT_ACTION = "com.daniel.exoPlayer.action.next"; 32 | public static final String CUSTOM1_ACTION = "com.daniel.exoPlayer.action.custom1"; 33 | public static final String CUSTOM2_ACTION = "com.daniel.exoPlayer.action.custom2"; 34 | private static final int NOTIFICATION_ID = 1; 35 | private static final String CHANNEL_ID = "Playback"; 36 | 37 | private ForegroundAudioPlayer foregroundExoPlayer; 38 | private Context context; 39 | private Activity activity; 40 | 41 | private NotificationManager notificationManager; 42 | private MediaSessionCompat mediaSession; 43 | 44 | private Intent playIntent; 45 | private Intent pauseIntent; 46 | private Intent prevIntent; 47 | private Intent nextIntent; 48 | private Intent notificationIntent; 49 | private Intent customIntent1; 50 | private Intent customIntent2; 51 | 52 | private PendingIntent ppPlayIntent; 53 | private PendingIntent pPauseIntent; 54 | private PendingIntent pPrevIntent; 55 | private PendingIntent pNextIntent; 56 | private PendingIntent pNotificatioIntent; 57 | private PendingIntent pCustomIntent1; 58 | private PendingIntent pCustomIntent2; 59 | 60 | private AudioObject audioObject; 61 | private boolean isPlaying; 62 | private boolean isShowing; 63 | private boolean isInitialized; 64 | 65 | public void setIsShowing(boolean isShowing) { 66 | this.isShowing = isShowing; 67 | } 68 | 69 | public boolean isShowing() { 70 | return isShowing; 71 | } 72 | 73 | public void setIsInitialized(boolean isInitialized) { 74 | this.isInitialized = isInitialized; 75 | } 76 | 77 | public boolean isInitialized() { 78 | return isInitialized; 79 | } 80 | 81 | public MediaNotificationManager(ForegroundAudioPlayer foregroundExoPlayer, Context context, MediaSessionCompat mediaSession, Activity activity) { 82 | this.context = context; 83 | this.foregroundExoPlayer = foregroundExoPlayer; 84 | this.mediaSession = mediaSession; 85 | this.activity = activity; 86 | isInitialized = false; 87 | 88 | initIntents(); 89 | } 90 | 91 | private void initIntents() { 92 | notificationIntent = new Intent(this.context, activity.getClass()); 93 | pNotificatioIntent = PendingIntent.getActivity(this.context, 0, notificationIntent, 0); 94 | 95 | playIntent = new Intent(this.context, ForegroundAudioPlayer.class); 96 | playIntent.setAction(PLAY_ACTION); 97 | ppPlayIntent = PendingIntent.getService(this.context, 1, playIntent, 0); 98 | 99 | pauseIntent = new Intent(this.context, ForegroundAudioPlayer.class); 100 | pauseIntent.setAction(PAUSE_ACTION); 101 | pPauseIntent = PendingIntent.getService(this.context, 1, pauseIntent, 0); 102 | 103 | prevIntent = new Intent(this.context, ForegroundAudioPlayer.class); 104 | prevIntent.setAction(PREVIOUS_ACTION); 105 | pPrevIntent = PendingIntent.getService(this.context, 1, prevIntent, 0); 106 | 107 | nextIntent = new Intent(this.context, ForegroundAudioPlayer.class); 108 | nextIntent.setAction(NEXT_ACTION); 109 | pNextIntent = PendingIntent.getService(this.context, 1, nextIntent, 0); 110 | 111 | customIntent1 = new Intent(this.context, ForegroundAudioPlayer.class); 112 | customIntent1.setAction(CUSTOM1_ACTION); 113 | pCustomIntent1 = PendingIntent.getService(this.context, 1, customIntent1, 0); 114 | 115 | customIntent2 = new Intent(this.context, ForegroundAudioPlayer.class); 116 | customIntent2.setAction(CUSTOM2_ACTION); 117 | pCustomIntent2 = PendingIntent.getService(this.context, 1, customIntent2, 0); 118 | 119 | } 120 | 121 | // make new notification 122 | public void makeNotification(AudioObject audioObject, boolean isPlaying) { 123 | isInitialized = true; 124 | isShowing = true; 125 | this.audioObject = audioObject; 126 | this.isPlaying = isPlaying; 127 | if (audioObject.getLargeIconUrl() != null) { 128 | loadImageFromUrl(audioObject.getLargeIconUrl(), audioObject.getIsLocal()); 129 | } else { 130 | showNotification(); 131 | } 132 | } 133 | 134 | // update current notification 135 | public void makeNotification(boolean isPlaying) { 136 | this.isPlaying = isPlaying; 137 | showNotification(); 138 | 139 | } 140 | 141 | private void showNotification() { 142 | Notification notification; 143 | int icon = this.context.getResources().getIdentifier(audioObject.getSmallIconFileName(), "drawable", this.context.getPackageName()); 144 | 145 | notificationManager = initNotificationManager(); 146 | NotificationCompat.Builder builder = new NotificationCompat.Builder(this.context, CHANNEL_ID) 147 | .setSmallIcon(icon) 148 | .setWhen(System.currentTimeMillis()) 149 | .setShowWhen(false) 150 | .setColorized(true) 151 | .setSound(null) 152 | .setContentIntent(pNotificatioIntent); 153 | 154 | if (audioObject.getTitle() != null) { 155 | builder.setContentTitle(audioObject.getTitle()); 156 | } 157 | if (audioObject.getSubTitle() != null) { 158 | builder.setContentText(audioObject.getSubTitle()); 159 | } 160 | if (audioObject.getLargeIcon() != null) { 161 | builder.setLargeIcon(audioObject.getLargeIcon()); 162 | } 163 | if (!this.isPlaying) { 164 | builder.setTimeoutAfter(900000); 165 | } 166 | builder = initNotificationActions(builder); 167 | 168 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 169 | builder = initNotificationStyle(builder); 170 | } 171 | 172 | notification = builder.build(); 173 | 174 | notificationManager.notify(NOTIFICATION_ID, notification); 175 | if (this.isPlaying) { 176 | foregroundExoPlayer.startForeground(NOTIFICATION_ID, notification); 177 | } else { 178 | foregroundExoPlayer.stopForeground(false); 179 | } 180 | } 181 | 182 | private NotificationManager initNotificationManager() { 183 | NotificationManager notificationManager; 184 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { 185 | NotificationChannel notificationChannel = new NotificationChannel(CHANNEL_ID, "Playback", android.app.NotificationManager.IMPORTANCE_DEFAULT); 186 | notificationChannel.setSound(null, null); 187 | notificationChannel.setShowBadge(false); 188 | 189 | notificationManager = (android.app.NotificationManager) this.context 190 | .getSystemService(Context.NOTIFICATION_SERVICE); 191 | notificationManager.createNotificationChannel(notificationChannel); 192 | } else { 193 | notificationManager = (android.app.NotificationManager) this.context 194 | .getSystemService(Context.NOTIFICATION_SERVICE); 195 | } 196 | return notificationManager; 197 | } 198 | 199 | private NotificationCompat.Builder initNotificationActions(NotificationCompat.Builder builder) { 200 | int customIcon1 = this.context.getResources().getIdentifier("ic_custom1", "drawable", // ! TODO maybe change to custom file name 201 | this.context.getPackageName()); 202 | int customIcon2 = this.context.getResources().getIdentifier("ic_custom2", "drawable", 203 | this.context.getPackageName()); 204 | 205 | if (audioObject.getNotificationCustomActions() == NotificationCustomActions.ONE 206 | || audioObject.getNotificationCustomActions() == NotificationCustomActions.TWO) { 207 | builder.addAction(customIcon1, "Custom1", pCustomIntent1); 208 | } 209 | if (audioObject.getNotificationActionMode() == NotificationDefaultActions.PREVIOUS 210 | || audioObject.getNotificationActionMode() == NotificationDefaultActions.ALL) { 211 | builder.addAction(R.drawable.ic_previous, "Previous", pPrevIntent); 212 | } 213 | 214 | if (this.isPlaying) { 215 | builder.addAction(R.drawable.ic_pause, "Pause", pPauseIntent); 216 | } else { 217 | builder.addAction(R.drawable.ic_play, "Play", ppPlayIntent); 218 | } 219 | 220 | if (audioObject.getNotificationActionMode() == NotificationDefaultActions.NEXT 221 | || audioObject.getNotificationActionMode() == NotificationDefaultActions.ALL) { 222 | builder.addAction(R.drawable.ic_next, "Next", pNextIntent); 223 | } 224 | if (audioObject.getNotificationCustomActions() == NotificationCustomActions.TWO) { 225 | builder.addAction(customIcon2, "Custom2", pCustomIntent2); 226 | } 227 | return builder; 228 | } 229 | 230 | private NotificationCompat.Builder initNotificationStyle(NotificationCompat.Builder builder) { 231 | if (audioObject.getNotificationActionMode() == NotificationDefaultActions.NEXT 232 | || audioObject.getNotificationActionMode() == NotificationDefaultActions.PREVIOUS) { 233 | builder.setStyle(new androidx.media.app.NotificationCompat.DecoratedMediaCustomViewStyle() 234 | .setShowActionsInCompactView(0, 1).setMediaSession(mediaSession.getSessionToken())); 235 | } else if (audioObject.getNotificationActionMode() == NotificationDefaultActions.ALL) { 236 | builder.setStyle(new androidx.media.app.NotificationCompat.DecoratedMediaCustomViewStyle() 237 | .setShowActionsInCompactView(0, 1, 2).setMediaSession(mediaSession.getSessionToken())); 238 | } else { 239 | builder.setStyle(new androidx.media.app.NotificationCompat.DecoratedMediaCustomViewStyle() 240 | .setShowActionsInCompactView(0).setMediaSession(mediaSession.getSessionToken())); 241 | } 242 | return builder; 243 | } 244 | 245 | private void loadImageFromUrl(String imageUrl, boolean isLocal) { 246 | try { 247 | new LoadImageFromUrl(imageUrl, isLocal, new AsyncResponse() { 248 | @Override 249 | public void processFinish(Map bitmapMap) { 250 | if (bitmapMap != null) { 251 | if (bitmapMap.get(audioObject.getLargeIconUrl()) != null) { 252 | audioObject.setLargeIcon(bitmapMap.get(audioObject.getLargeIconUrl())); 253 | showNotification(); 254 | } else { 255 | Log.e("ExoPlayerPlugin", "canceled showing notification!"); 256 | } 257 | } else { 258 | showNotification(); 259 | Log.e("ExoPlayerPlugin", "Failed loading image!"); 260 | } 261 | } 262 | }).execute(); 263 | } catch (Exception e) { 264 | Log.e("ExoPlayerPlugin", "Failed loading image!"); 265 | } 266 | } 267 | } 268 | -------------------------------------------------------------------------------- /android/src/main/res/drawable/ic_next.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /android/src/main/res/drawable/ic_pause.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /android/src/main/res/drawable/ic_play.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /android/src/main/res/drawable/ic_previous.xml: -------------------------------------------------------------------------------- 1 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /example/.flutter-plugins-dependencies: -------------------------------------------------------------------------------- 1 | {"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"flutter_exoplayer","path":"C:\\\\Users\\\\danie\\\\VisualStudioProjects\\\\flutter_exoplayer\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\danie\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]}],"android":[{"name":"flutter_exoplayer","path":"C:\\\\Users\\\\danie\\\\VisualStudioProjects\\\\flutter_exoplayer\\\\","dependencies":[]},{"name":"path_provider","path":"C:\\\\Users\\\\danie\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider-2.0.1\\\\","dependencies":[]}],"macos":[{"name":"path_provider_macos","path":"C:\\\\Users\\\\danie\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_macos-2.0.0\\\\","dependencies":[]}],"linux":[{"name":"path_provider_linux","path":"C:\\\\Users\\\\danie\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_linux-2.0.0\\\\","dependencies":[]}],"windows":[{"name":"path_provider_windows","path":"C:\\\\Users\\\\danie\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\path_provider_windows-2.0.0\\\\","dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"flutter_exoplayer","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos","path_provider_linux","path_provider_windows"]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_macos","dependencies":[]},{"name":"path_provider_windows","dependencies":[]}],"date_created":"2021-04-01 22:40:58.605886","version":"2.0.1"} -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .packages 28 | .pub-cache/ 29 | .pub/ 30 | /build/ 31 | pubspec.lock 32 | 33 | # Android related 34 | **/android/**/gradle-wrapper.jar 35 | **/android/.gradle 36 | **/android/captures/ 37 | **/android/gradlew 38 | **/android/gradlew.bat 39 | **/android/local.properties 40 | **/android/**/GeneratedPluginRegistrant.java 41 | 42 | # iOS/XCode related 43 | **/ios/**/*.mode1v3 44 | **/ios/**/*.mode2v3 45 | **/ios/**/*.moved-aside 46 | **/ios/**/*.pbxuser 47 | **/ios/**/*.perspectivev3 48 | **/ios/**/*sync/ 49 | **/ios/**/.sconsign.dblite 50 | **/ios/**/.tags* 51 | **/ios/**/.vagrant/ 52 | **/ios/**/DerivedData/ 53 | **/ios/**/Icon? 54 | **/ios/**/Pods/ 55 | **/ios/**/.symlinks/ 56 | **/ios/**/profile 57 | **/ios/**/xcuserdata 58 | **/ios/.generated/ 59 | **/ios/Flutter/App.framework 60 | **/ios/Flutter/Flutter.framework 61 | **/ios/Flutter/Generated.xcconfig 62 | **/ios/Flutter/app.flx 63 | **/ios/Flutter/app.zip 64 | **/ios/Flutter/flutter_assets/ 65 | **/ios/ServiceDefinitions.json 66 | **/ios/Runner/GeneratedPluginRegistrant.* 67 | 68 | # Exceptions to above rules. 69 | !**/ios/**/default.mode1v3 70 | !**/ios/**/default.mode2v3 71 | !**/ios/**/default.pbxuser 72 | !**/ios/**/default.perspectivev3 73 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages 74 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: b712a172f9694745f50505c93340883493b505e5 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_exoplayer example 2 | 3 | This is an example usage of flutter_exoplayer plugin. 4 | 5 | It's a simple app with three tabs. 6 | 7 | - Remote Url: Plays audio from a remote url from the Internet. 8 | - Local File: Downloads a file to your device in order to play it from your device. 9 | - Remote Url and Local File: plays downloaded and not downloaded audio mix. 10 | 11 | This example bundles a `PlayerWidget` that could be used as a very simple audio player interface. 12 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | android { 18 | compileSdkVersion 28 19 | 20 | lintOptions { 21 | disable 'InvalidPackage' 22 | } 23 | 24 | defaultConfig { 25 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 26 | applicationId "danielr2001.exoplayer_example" 27 | minSdkVersion 16 28 | targetSdkVersion 28 29 | versionCode 1 30 | versionName "1.0" 31 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 32 | } 33 | 34 | buildTypes { 35 | release { 36 | signingConfig signingConfigs.debug 37 | } 38 | } 39 | } 40 | 41 | flutter { 42 | source '../..' 43 | } 44 | 45 | dependencies { 46 | androidTestImplementation 'androidx.annotation:annotation:1.1.0' 47 | androidTestImplementation 'androidx.test:runner:1.2.0' 48 | androidTestImplementation 'androidx.test:rules:1.2.0' 49 | } 50 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 12 | 19 | 23 | 24 | 25 | 26 | 29 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/danielr2001/exoplayer_example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package danielr2001.exoplayer_example; 2 | 3 | import io.flutter.embedding.android.FlutterActivity; 4 | 5 | public class MainActivity extends FlutterActivity {} 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/ic_custom1.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/ic_custom2.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/android/app/src/main/res/drawable/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 14 | 17 | 18 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.3' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | android.enableR8=true 5 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jan 04 11:04:28 IST 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/images/Screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/images/Screenshot_1.png -------------------------------------------------------------------------------- /example/images/Screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/images/Screenshot_2.png -------------------------------------------------------------------------------- /example/images/Screenshot_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/images/Screenshot_3.png -------------------------------------------------------------------------------- /example/images/Screenshot_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/images/Screenshot_4.png -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Generated.xcconfig" 2 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 12 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 13 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 14 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 15 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 16 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 17 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 18 | 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 19 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 20 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 21 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXCopyFilesBuildPhase section */ 25 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 26 | isa = PBXCopyFilesBuildPhase; 27 | buildActionMask = 2147483647; 28 | dstPath = ""; 29 | dstSubfolderSpec = 10; 30 | files = ( 31 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 32 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 33 | ); 34 | name = "Embed Frameworks"; 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXCopyFilesBuildPhase section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 41 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 42 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 43 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 44 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 45 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 46 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 47 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 48 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 49 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 50 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 51 | 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 52 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 53 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 54 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 55 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 56 | /* End PBXFileReference section */ 57 | 58 | /* Begin PBXFrameworksBuildPhase section */ 59 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 60 | isa = PBXFrameworksBuildPhase; 61 | buildActionMask = 2147483647; 62 | files = ( 63 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 64 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 65 | ); 66 | runOnlyForDeploymentPostprocessing = 0; 67 | }; 68 | /* End PBXFrameworksBuildPhase section */ 69 | 70 | /* Begin PBXGroup section */ 71 | 9740EEB11CF90186004384FC /* Flutter */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 3B80C3931E831B6300D905FE /* App.framework */, 75 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 76 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 77 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 78 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 79 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 80 | ); 81 | name = Flutter; 82 | sourceTree = ""; 83 | }; 84 | 97C146E51CF9000F007C117D = { 85 | isa = PBXGroup; 86 | children = ( 87 | 9740EEB11CF90186004384FC /* Flutter */, 88 | 97C146F01CF9000F007C117D /* Runner */, 89 | 97C146EF1CF9000F007C117D /* Products */, 90 | CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 97C146EF1CF9000F007C117D /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 97C146EE1CF9000F007C117D /* Runner.app */, 98 | ); 99 | name = Products; 100 | sourceTree = ""; 101 | }; 102 | 97C146F01CF9000F007C117D /* Runner */ = { 103 | isa = PBXGroup; 104 | children = ( 105 | 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 106 | 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, 107 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 108 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 109 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 110 | 97C147021CF9000F007C117D /* Info.plist */, 111 | 97C146F11CF9000F007C117D /* Supporting Files */, 112 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 113 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 114 | ); 115 | path = Runner; 116 | sourceTree = ""; 117 | }; 118 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 97C146F21CF9000F007C117D /* main.m */, 122 | ); 123 | name = "Supporting Files"; 124 | sourceTree = ""; 125 | }; 126 | /* End PBXGroup section */ 127 | 128 | /* Begin PBXNativeTarget section */ 129 | 97C146ED1CF9000F007C117D /* Runner */ = { 130 | isa = PBXNativeTarget; 131 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 132 | buildPhases = ( 133 | 9740EEB61CF901F6004384FC /* Run Script */, 134 | 97C146EA1CF9000F007C117D /* Sources */, 135 | 97C146EB1CF9000F007C117D /* Frameworks */, 136 | 97C146EC1CF9000F007C117D /* Resources */, 137 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 138 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 139 | ); 140 | buildRules = ( 141 | ); 142 | dependencies = ( 143 | ); 144 | name = Runner; 145 | productName = Runner; 146 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 147 | productType = "com.apple.product-type.application"; 148 | }; 149 | /* End PBXNativeTarget section */ 150 | 151 | /* Begin PBXProject section */ 152 | 97C146E61CF9000F007C117D /* Project object */ = { 153 | isa = PBXProject; 154 | attributes = { 155 | LastUpgradeCheck = 1020; 156 | ORGANIZATIONNAME = "The Chromium Authors"; 157 | TargetAttributes = { 158 | 97C146ED1CF9000F007C117D = { 159 | CreatedOnToolsVersion = 7.3.1; 160 | }; 161 | }; 162 | }; 163 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 164 | compatibilityVersion = "Xcode 3.2"; 165 | developmentRegion = en; 166 | hasScannedForEncodings = 0; 167 | knownRegions = ( 168 | en, 169 | Base, 170 | ); 171 | mainGroup = 97C146E51CF9000F007C117D; 172 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 173 | projectDirPath = ""; 174 | projectRoot = ""; 175 | targets = ( 176 | 97C146ED1CF9000F007C117D /* Runner */, 177 | ); 178 | }; 179 | /* End PBXProject section */ 180 | 181 | /* Begin PBXResourcesBuildPhase section */ 182 | 97C146EC1CF9000F007C117D /* Resources */ = { 183 | isa = PBXResourcesBuildPhase; 184 | buildActionMask = 2147483647; 185 | files = ( 186 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 187 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 188 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 189 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 190 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 191 | ); 192 | runOnlyForDeploymentPostprocessing = 0; 193 | }; 194 | /* End PBXResourcesBuildPhase section */ 195 | 196 | /* Begin PBXShellScriptBuildPhase section */ 197 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 198 | isa = PBXShellScriptBuildPhase; 199 | buildActionMask = 2147483647; 200 | files = ( 201 | ); 202 | inputPaths = ( 203 | ); 204 | name = "Thin Binary"; 205 | outputPaths = ( 206 | ); 207 | runOnlyForDeploymentPostprocessing = 0; 208 | shellPath = /bin/sh; 209 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 210 | }; 211 | 9740EEB61CF901F6004384FC /* Run Script */ = { 212 | isa = PBXShellScriptBuildPhase; 213 | buildActionMask = 2147483647; 214 | files = ( 215 | ); 216 | inputPaths = ( 217 | ); 218 | name = "Run Script"; 219 | outputPaths = ( 220 | ); 221 | runOnlyForDeploymentPostprocessing = 0; 222 | shellPath = /bin/sh; 223 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 224 | }; 225 | /* End PBXShellScriptBuildPhase section */ 226 | 227 | /* Begin PBXSourcesBuildPhase section */ 228 | 97C146EA1CF9000F007C117D /* Sources */ = { 229 | isa = PBXSourcesBuildPhase; 230 | buildActionMask = 2147483647; 231 | files = ( 232 | 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 233 | 97C146F31CF9000F007C117D /* main.m in Sources */, 234 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | }; 238 | /* End PBXSourcesBuildPhase section */ 239 | 240 | /* Begin PBXVariantGroup section */ 241 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 242 | isa = PBXVariantGroup; 243 | children = ( 244 | 97C146FB1CF9000F007C117D /* Base */, 245 | ); 246 | name = Main.storyboard; 247 | sourceTree = ""; 248 | }; 249 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 250 | isa = PBXVariantGroup; 251 | children = ( 252 | 97C147001CF9000F007C117D /* Base */, 253 | ); 254 | name = LaunchScreen.storyboard; 255 | sourceTree = ""; 256 | }; 257 | /* End PBXVariantGroup section */ 258 | 259 | /* Begin XCBuildConfiguration section */ 260 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 261 | isa = XCBuildConfiguration; 262 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 263 | buildSettings = { 264 | ALWAYS_SEARCH_USER_PATHS = NO; 265 | CLANG_ANALYZER_NONNULL = YES; 266 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 267 | CLANG_CXX_LIBRARY = "libc++"; 268 | CLANG_ENABLE_MODULES = YES; 269 | CLANG_ENABLE_OBJC_ARC = YES; 270 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 271 | CLANG_WARN_BOOL_CONVERSION = YES; 272 | CLANG_WARN_COMMA = YES; 273 | CLANG_WARN_CONSTANT_CONVERSION = YES; 274 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 275 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 276 | CLANG_WARN_EMPTY_BODY = YES; 277 | CLANG_WARN_ENUM_CONVERSION = YES; 278 | CLANG_WARN_INFINITE_RECURSION = YES; 279 | CLANG_WARN_INT_CONVERSION = YES; 280 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 281 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 282 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 283 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 284 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 285 | CLANG_WARN_STRICT_PROTOTYPES = YES; 286 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 287 | CLANG_WARN_UNREACHABLE_CODE = YES; 288 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 289 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 290 | COPY_PHASE_STRIP = NO; 291 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 292 | ENABLE_NS_ASSERTIONS = NO; 293 | ENABLE_STRICT_OBJC_MSGSEND = YES; 294 | GCC_C_LANGUAGE_STANDARD = gnu99; 295 | GCC_NO_COMMON_BLOCKS = YES; 296 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 297 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 298 | GCC_WARN_UNDECLARED_SELECTOR = YES; 299 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 300 | GCC_WARN_UNUSED_FUNCTION = YES; 301 | GCC_WARN_UNUSED_VARIABLE = YES; 302 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 303 | MTL_ENABLE_DEBUG_INFO = NO; 304 | SDKROOT = iphoneos; 305 | TARGETED_DEVICE_FAMILY = "1,2"; 306 | VALIDATE_PRODUCT = YES; 307 | }; 308 | name = Profile; 309 | }; 310 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 311 | isa = XCBuildConfiguration; 312 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 313 | buildSettings = { 314 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 315 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 316 | DEVELOPMENT_TEAM = S8QB4VV633; 317 | ENABLE_BITCODE = NO; 318 | FRAMEWORK_SEARCH_PATHS = ( 319 | "$(inherited)", 320 | "$(PROJECT_DIR)/Flutter", 321 | ); 322 | INFOPLIST_FILE = Runner/Info.plist; 323 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 324 | LIBRARY_SEARCH_PATHS = ( 325 | "$(inherited)", 326 | "$(PROJECT_DIR)/Flutter", 327 | ); 328 | PRODUCT_BUNDLE_IDENTIFIER = dr.library.exoplayerExample; 329 | PRODUCT_NAME = "$(TARGET_NAME)"; 330 | VERSIONING_SYSTEM = "apple-generic"; 331 | }; 332 | name = Profile; 333 | }; 334 | 97C147031CF9000F007C117D /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 337 | buildSettings = { 338 | ALWAYS_SEARCH_USER_PATHS = NO; 339 | CLANG_ANALYZER_NONNULL = YES; 340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 341 | CLANG_CXX_LIBRARY = "libc++"; 342 | CLANG_ENABLE_MODULES = YES; 343 | CLANG_ENABLE_OBJC_ARC = YES; 344 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 345 | CLANG_WARN_BOOL_CONVERSION = YES; 346 | CLANG_WARN_COMMA = YES; 347 | CLANG_WARN_CONSTANT_CONVERSION = YES; 348 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 349 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 350 | CLANG_WARN_EMPTY_BODY = YES; 351 | CLANG_WARN_ENUM_CONVERSION = YES; 352 | CLANG_WARN_INFINITE_RECURSION = YES; 353 | CLANG_WARN_INT_CONVERSION = YES; 354 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 355 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 356 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 357 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 358 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 359 | CLANG_WARN_STRICT_PROTOTYPES = YES; 360 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 361 | CLANG_WARN_UNREACHABLE_CODE = YES; 362 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 363 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 364 | COPY_PHASE_STRIP = NO; 365 | DEBUG_INFORMATION_FORMAT = dwarf; 366 | ENABLE_STRICT_OBJC_MSGSEND = YES; 367 | ENABLE_TESTABILITY = YES; 368 | GCC_C_LANGUAGE_STANDARD = gnu99; 369 | GCC_DYNAMIC_NO_PIC = NO; 370 | GCC_NO_COMMON_BLOCKS = YES; 371 | GCC_OPTIMIZATION_LEVEL = 0; 372 | GCC_PREPROCESSOR_DEFINITIONS = ( 373 | "DEBUG=1", 374 | "$(inherited)", 375 | ); 376 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 377 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 378 | GCC_WARN_UNDECLARED_SELECTOR = YES; 379 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 380 | GCC_WARN_UNUSED_FUNCTION = YES; 381 | GCC_WARN_UNUSED_VARIABLE = YES; 382 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 383 | MTL_ENABLE_DEBUG_INFO = YES; 384 | ONLY_ACTIVE_ARCH = YES; 385 | SDKROOT = iphoneos; 386 | TARGETED_DEVICE_FAMILY = "1,2"; 387 | }; 388 | name = Debug; 389 | }; 390 | 97C147041CF9000F007C117D /* Release */ = { 391 | isa = XCBuildConfiguration; 392 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 393 | buildSettings = { 394 | ALWAYS_SEARCH_USER_PATHS = NO; 395 | CLANG_ANALYZER_NONNULL = YES; 396 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 397 | CLANG_CXX_LIBRARY = "libc++"; 398 | CLANG_ENABLE_MODULES = YES; 399 | CLANG_ENABLE_OBJC_ARC = YES; 400 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 401 | CLANG_WARN_BOOL_CONVERSION = YES; 402 | CLANG_WARN_COMMA = YES; 403 | CLANG_WARN_CONSTANT_CONVERSION = YES; 404 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 405 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 406 | CLANG_WARN_EMPTY_BODY = YES; 407 | CLANG_WARN_ENUM_CONVERSION = YES; 408 | CLANG_WARN_INFINITE_RECURSION = YES; 409 | CLANG_WARN_INT_CONVERSION = YES; 410 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 411 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 412 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 413 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 414 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 415 | CLANG_WARN_STRICT_PROTOTYPES = YES; 416 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 417 | CLANG_WARN_UNREACHABLE_CODE = YES; 418 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 419 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 420 | COPY_PHASE_STRIP = NO; 421 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 422 | ENABLE_NS_ASSERTIONS = NO; 423 | ENABLE_STRICT_OBJC_MSGSEND = YES; 424 | GCC_C_LANGUAGE_STANDARD = gnu99; 425 | GCC_NO_COMMON_BLOCKS = YES; 426 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 427 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 428 | GCC_WARN_UNDECLARED_SELECTOR = YES; 429 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 430 | GCC_WARN_UNUSED_FUNCTION = YES; 431 | GCC_WARN_UNUSED_VARIABLE = YES; 432 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 433 | MTL_ENABLE_DEBUG_INFO = NO; 434 | SDKROOT = iphoneos; 435 | TARGETED_DEVICE_FAMILY = "1,2"; 436 | VALIDATE_PRODUCT = YES; 437 | }; 438 | name = Release; 439 | }; 440 | 97C147061CF9000F007C117D /* Debug */ = { 441 | isa = XCBuildConfiguration; 442 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 443 | buildSettings = { 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 446 | ENABLE_BITCODE = NO; 447 | FRAMEWORK_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "$(PROJECT_DIR)/Flutter", 450 | ); 451 | INFOPLIST_FILE = Runner/Info.plist; 452 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 453 | LIBRARY_SEARCH_PATHS = ( 454 | "$(inherited)", 455 | "$(PROJECT_DIR)/Flutter", 456 | ); 457 | PRODUCT_BUNDLE_IDENTIFIER = dr.library.exoplayerExample; 458 | PRODUCT_NAME = "$(TARGET_NAME)"; 459 | VERSIONING_SYSTEM = "apple-generic"; 460 | }; 461 | name = Debug; 462 | }; 463 | 97C147071CF9000F007C117D /* Release */ = { 464 | isa = XCBuildConfiguration; 465 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 466 | buildSettings = { 467 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 468 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 469 | ENABLE_BITCODE = NO; 470 | FRAMEWORK_SEARCH_PATHS = ( 471 | "$(inherited)", 472 | "$(PROJECT_DIR)/Flutter", 473 | ); 474 | INFOPLIST_FILE = Runner/Info.plist; 475 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 476 | LIBRARY_SEARCH_PATHS = ( 477 | "$(inherited)", 478 | "$(PROJECT_DIR)/Flutter", 479 | ); 480 | PRODUCT_BUNDLE_IDENTIFIER = dr.library.exoplayerExample; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | VERSIONING_SYSTEM = "apple-generic"; 483 | }; 484 | name = Release; 485 | }; 486 | /* End XCBuildConfiguration section */ 487 | 488 | /* Begin XCConfigurationList section */ 489 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 490 | isa = XCConfigurationList; 491 | buildConfigurations = ( 492 | 97C147031CF9000F007C117D /* Debug */, 493 | 97C147041CF9000F007C117D /* Release */, 494 | 249021D3217E4FDB00AE95B9 /* Profile */, 495 | ); 496 | defaultConfigurationIsVisible = 0; 497 | defaultConfigurationName = Release; 498 | }; 499 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 500 | isa = XCConfigurationList; 501 | buildConfigurations = ( 502 | 97C147061CF9000F007C117D /* Debug */, 503 | 97C147071CF9000F007C117D /* Release */, 504 | 249021D4217E4FDB00AE95B9 /* Profile */, 505 | ); 506 | defaultConfigurationIsVisible = 0; 507 | defaultConfigurationName = Release; 508 | }; 509 | /* End XCConfigurationList section */ 510 | }; 511 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 512 | } 513 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #include "AppDelegate.h" 2 | #include "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | exoplayer_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UISupportedInterfaceOrientations 30 | 31 | UIInterfaceOrientationPortrait 32 | UIInterfaceOrientationLandscapeLeft 33 | UIInterfaceOrientationLandscapeRight 34 | 35 | UISupportedInterfaceOrientations~ipad 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationPortraitUpsideDown 39 | UIInterfaceOrientationLandscapeLeft 40 | UIInterfaceOrientationLandscapeRight 41 | 42 | UIViewControllerBasedStatusBarAppearance 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:io'; 3 | 4 | import 'package:flutter/material.dart'; 5 | import 'package:path_provider/path_provider.dart'; 6 | import 'player_widget.dart'; 7 | import 'package:http/http.dart'; 8 | 9 | const kUrl1 = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3'; 10 | const kUrl2 = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3'; 11 | const kUrl3 = 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3'; 12 | 13 | //const kUrl3 = 'http://bbcmedia.ic.llnwd.net/stream/bbcmedia_radio1xtra_mf_p'; 14 | final List urls = [kUrl1, kUrl2, kUrl3]; 15 | 16 | void main() { 17 | runApp(MaterialApp(home: ExampleApp())); 18 | } 19 | 20 | class ExampleApp extends StatefulWidget { 21 | @override 22 | _ExampleAppState createState() => _ExampleAppState(); 23 | } 24 | 25 | class _ExampleAppState extends State { 26 | String localFilePath = ""; 27 | 28 | Widget _tab(List children) { 29 | return Center( 30 | child: Container( 31 | padding: EdgeInsets.all(16.0), 32 | child: Column( 33 | children: children 34 | .map((w) => Container(child: w, padding: EdgeInsets.all(6.0))) 35 | .toList(), 36 | ), 37 | ), 38 | ); 39 | } 40 | 41 | Widget remoteUrl() { 42 | return SingleChildScrollView( 43 | child: _tab([ 44 | Text( 45 | 'Sample 1: ($kUrl1)', 46 | style: TextStyle(fontWeight: FontWeight.bold), 47 | ), 48 | PlayerWidget(url: kUrl1), 49 | Text( 50 | 'Sample 2: ($kUrl2)', 51 | style: TextStyle(fontWeight: FontWeight.bold), 52 | ), 53 | PlayerWidget(url: kUrl2), 54 | Text( 55 | 'Sample 3: ($kUrl3)', 56 | style: TextStyle(fontWeight: FontWeight.bold), 57 | ), 58 | PlayerWidget(url: kUrl3), 59 | Text( 60 | 'Sample 4: playlist of (samples 1-3)', 61 | style: TextStyle(fontWeight: FontWeight.bold), 62 | ), 63 | PlayerWidget(urls: urls), 64 | Text( 65 | "Credit to www.bensound.com for the music and images", 66 | style: TextStyle( 67 | color: Colors.grey, 68 | fontSize: 12, 69 | ), 70 | ), 71 | ]), 72 | ); 73 | } 74 | 75 | Widget localFile() { 76 | return _tab([ 77 | Text('File: $kUrl1'), 78 | _btn('Download File to your Device', () => _loadFile()), 79 | Text('Current local file path: $localFilePath'), 80 | PlayerWidget(url: localFilePath), 81 | Text( 82 | "Credit to www.bensound.com for the music and images", 83 | style: TextStyle( 84 | color: Colors.grey, 85 | fontSize: 12, 86 | ), 87 | ), 88 | ]); 89 | } 90 | 91 | Widget localAndRemote() { 92 | return _tab([ 93 | Text('File: $kUrl1'), 94 | _btn('Download File to your Device', () => _loadFile()), 95 | PlayerWidget(urls: [localFilePath, kUrl2, kUrl3]), 96 | Text( 97 | "Credit to www.bensound.com for the music and images", 98 | style: TextStyle( 99 | color: Colors.grey, 100 | fontSize: 12, 101 | ), 102 | ), 103 | ]); 104 | } 105 | 106 | Widget _btn(String txt, VoidCallback onPressed) { 107 | return ButtonTheme( 108 | minWidth: 48.0, 109 | child: TextButton(child: Text(txt), onPressed: onPressed), 110 | buttonColor: Colors.pink, 111 | ); 112 | } 113 | 114 | Future _loadFile() async { 115 | final bytes = await readBytes(Uri(path: kUrl1)); 116 | final dir = await getApplicationDocumentsDirectory(); 117 | final file = File('${dir.path}/audio.mp3'); 118 | 119 | await file.writeAsBytes(bytes); 120 | if (await file.exists()) { 121 | setState(() { 122 | localFilePath = file.path; 123 | }); 124 | } 125 | } 126 | 127 | @override 128 | Widget build(BuildContext context) { 129 | return DefaultTabController( 130 | length: 3, 131 | child: Scaffold( 132 | appBar: AppBar( 133 | backgroundColor: Colors.pink, 134 | bottomOpacity: 1.0, 135 | bottom: TabBar( 136 | indicatorColor: Colors.pink, 137 | tabs: [ 138 | Tab(text: 'Remote Url'), 139 | Tab(text: 'Local File'), 140 | Tab(text: 'Local & Remote Mix'), 141 | ], 142 | ), 143 | title: Text('flutter_exoplayer Example'), 144 | ), 145 | body: TabBarView( 146 | children: [ 147 | remoteUrl(), 148 | localFile(), 149 | localAndRemote(), 150 | ], 151 | ), 152 | ), 153 | ); 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /example/lib/player_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter_exoplayer/audio_notification.dart'; 4 | import 'package:flutter_exoplayer/audioplayer.dart'; 5 | import 'package:flutter_exoplayer_example/main.dart'; 6 | import 'package:flutter/material.dart'; 7 | 8 | const imageUrl1 = "https://www.bensound.com/bensound-img/buddy.jpg"; 9 | const imageUrl2 = "https://www.bensound.com/bensound-img/epic.jpg"; 10 | const imageUrl3 = "https://www.bensound.com/bensound-img/onceagain.jpg"; 11 | 12 | class PlayerWidget extends StatefulWidget { 13 | final String? url; 14 | final List? urls; 15 | 16 | PlayerWidget({this.url, this.urls}); 17 | 18 | @override 19 | State createState() { 20 | return _PlayerWidgetState(url: url, urls: urls); 21 | } 22 | } 23 | 24 | class _PlayerWidgetState extends State { 25 | final String? url; 26 | final List? urls; 27 | 28 | late AudioPlayer _audioPlayer; 29 | Duration? _duration; 30 | Duration? _position; 31 | int _currentIndex = 0; 32 | 33 | PlayerState _playerState = PlayerState.RELEASED; 34 | StreamSubscription? _durationSubscription; 35 | StreamSubscription? _positionSubscription; 36 | StreamSubscription? _playerCompleteSubscription; 37 | StreamSubscription? _playerErrorSubscription; 38 | StreamSubscription? _playerStateSubscription; 39 | StreamSubscription? _playerIndexSubscription; 40 | StreamSubscription? _playerAudioSessionIdSubscription; 41 | StreamSubscription? _notificationActionCallbackSubscription; 42 | 43 | final List audioNotifications = [ 44 | AudioNotification( 45 | smallIconFileName: "ic_launcher", 46 | title: "title1", 47 | subTitle: "artist1", 48 | largeIconUrl: imageUrl1, 49 | isLocal: false, 50 | notificationDefaultActions: NotificationDefaultActions.ALL, 51 | notificationCustomActions: NotificationCustomActions.TWO, 52 | ), 53 | AudioNotification( 54 | smallIconFileName: "ic_launcher", 55 | title: "title2", 56 | subTitle: "artist2", 57 | largeIconUrl: imageUrl2, 58 | isLocal: false, 59 | notificationDefaultActions: NotificationDefaultActions.ALL), 60 | AudioNotification( 61 | smallIconFileName: "ic_launcher", 62 | title: "title3", 63 | subTitle: "artist3", 64 | largeIconUrl: imageUrl3, 65 | isLocal: false, 66 | notificationDefaultActions: NotificationDefaultActions.ALL), 67 | ]; 68 | 69 | get _isPlaying => _playerState == PlayerState.PLAYING; 70 | get _durationText => _duration?.toString().split('.').first ?? ''; 71 | get _positionText => _position?.toString().split('.').first ?? ''; 72 | 73 | _PlayerWidgetState({this.url, this.urls}); 74 | 75 | @override 76 | void initState() { 77 | super.initState(); 78 | _initAudioPlayer(); 79 | } 80 | 81 | @override 82 | void dispose() { 83 | _audioPlayer.dispose(); 84 | _durationSubscription?.cancel(); 85 | _positionSubscription?.cancel(); 86 | _playerCompleteSubscription?.cancel(); 87 | _playerErrorSubscription?.cancel(); 88 | _playerStateSubscription?.cancel(); 89 | _playerIndexSubscription?.cancel(); 90 | _playerAudioSessionIdSubscription?.cancel(); 91 | _notificationActionCallbackSubscription?.cancel(); 92 | super.dispose(); 93 | } 94 | 95 | @override 96 | Widget build(BuildContext context) { 97 | return Column( 98 | children: [ 99 | Padding( 100 | padding: const EdgeInsets.only(top: 20), 101 | child: Row( 102 | children: [ 103 | Expanded( 104 | child: Padding( 105 | padding: const EdgeInsets.symmetric(horizontal: 10), 106 | child: GestureDetector( 107 | child: Container( 108 | width: 70, 109 | height: 45, 110 | decoration: BoxDecoration( 111 | color: Colors.pink, 112 | borderRadius: BorderRadius.circular(5)), 113 | child: Center( 114 | child: Text( 115 | "Play", 116 | style: TextStyle( 117 | color: Colors.white, 118 | fontSize: 18, 119 | fontWeight: FontWeight.bold, 120 | ), 121 | ), 122 | ), 123 | ), 124 | onTap: () => _play(), 125 | ), 126 | ), 127 | ), 128 | Expanded( 129 | child: Padding( 130 | padding: const EdgeInsets.symmetric(horizontal: 10), 131 | child: GestureDetector( 132 | child: Container( 133 | width: 70, 134 | height: 45, 135 | decoration: BoxDecoration( 136 | color: Colors.pink, 137 | borderRadius: BorderRadius.circular(5)), 138 | child: Center( 139 | child: Text( 140 | "Stop", 141 | style: TextStyle( 142 | color: Colors.white, 143 | fontSize: 18, 144 | fontWeight: FontWeight.bold, 145 | ), 146 | ), 147 | ), 148 | ), 149 | onTap: () => _stop(), 150 | ), 151 | ), 152 | ), 153 | Expanded( 154 | child: Padding( 155 | padding: const EdgeInsets.symmetric(horizontal: 10), 156 | child: GestureDetector( 157 | child: Container( 158 | width: 70, 159 | height: 45, 160 | decoration: BoxDecoration( 161 | color: Colors.pink, 162 | borderRadius: BorderRadius.circular(5)), 163 | child: Center( 164 | child: Text( 165 | "Release", 166 | style: TextStyle( 167 | color: Colors.white, 168 | fontSize: 18, 169 | fontWeight: FontWeight.bold, 170 | ), 171 | ), 172 | ), 173 | ), 174 | onTap: () => _release(), 175 | ), 176 | ), 177 | ), 178 | ], 179 | ), 180 | ), 181 | Padding( 182 | padding: const EdgeInsets.only(top: 20), 183 | child: Row( 184 | mainAxisSize: MainAxisSize.min, 185 | children: [ 186 | IconButton( 187 | onPressed: () => _previous(), 188 | iconSize: 45.0, 189 | icon: Icon(Icons.skip_previous), 190 | color: Colors.pink), 191 | IconButton( 192 | onPressed: _isPlaying ? () => _pause() : () => _resume(), 193 | iconSize: 45.0, 194 | icon: _isPlaying ? Icon(Icons.pause) : Icon(Icons.play_arrow), 195 | color: Colors.pink), 196 | IconButton( 197 | onPressed: () => _next(), 198 | iconSize: 45.0, 199 | icon: Icon(Icons.skip_next), 200 | color: Colors.pink), 201 | ], 202 | ), 203 | ), 204 | SizedBox( 205 | width: 400, 206 | height: 30, 207 | child: SliderTheme( 208 | data: SliderThemeData( 209 | thumbShape: RoundSliderThumbShape(enabledThumbRadius: 5), 210 | trackHeight: 3, 211 | thumbColor: Colors.pink, 212 | inactiveTrackColor: Colors.grey, 213 | activeTrackColor: Colors.pink, 214 | overlayColor: Colors.transparent, 215 | ), 216 | child: Slider( 217 | value: _position?.inMilliseconds.toDouble() ?? 0.0, 218 | min: 0.0, 219 | max: _duration?.inMilliseconds.toDouble() ?? 0.0, 220 | onChanged: (double value) async { 221 | final Result result = await _audioPlayer 222 | .seekPosition(Duration(milliseconds: value.toInt())); 223 | if (result == Result.FAIL) { 224 | print( 225 | "you tried to call audio conrolling methods on released audio player :("); 226 | } else if (result == Result.ERROR) { 227 | print("something went wrong in seek :("); 228 | } 229 | _position = Duration(milliseconds: value.toInt()); 230 | }, 231 | ), 232 | ), 233 | ), 234 | Row( 235 | mainAxisSize: MainAxisSize.min, 236 | children: [ 237 | Text( 238 | _position != null 239 | ? '${_positionText ?? ''} / ${_durationText ?? ''}' 240 | : _duration != null 241 | ? _durationText 242 | : '', 243 | style: TextStyle(fontSize: 24.0), 244 | ), 245 | ], 246 | ), 247 | Text("State: $_playerState"), 248 | Text("index: $_currentIndex"), 249 | Padding( 250 | padding: const EdgeInsets.symmetric(vertical: 15), 251 | child: Container( 252 | height: 2, 253 | //width: 350, 254 | decoration: BoxDecoration( 255 | border: Border( 256 | bottom: BorderSide( 257 | color: Colors.pink, 258 | ), 259 | ), 260 | ), 261 | ), 262 | ), 263 | ], 264 | ); 265 | } 266 | 267 | void _initAudioPlayer() { 268 | _audioPlayer = AudioPlayer(); 269 | _durationSubscription = _audioPlayer.onDurationChanged.listen((duration) { 270 | setState(() { 271 | _duration = duration; 272 | }); 273 | }); 274 | _positionSubscription = _audioPlayer.onAudioPositionChanged.listen((pos) { 275 | setState(() { 276 | _position = pos; 277 | }); 278 | }); 279 | _playerStateSubscription = 280 | _audioPlayer.onPlayerStateChanged.listen((playerState) { 281 | setState(() { 282 | _playerState = playerState; 283 | print(_playerState); 284 | }); 285 | }); 286 | _playerIndexSubscription = 287 | _audioPlayer.onCurrentAudioIndexChanged.listen((index) { 288 | setState(() { 289 | _position = Duration(milliseconds: 0); 290 | _currentIndex = index; 291 | }); 292 | }); 293 | _playerAudioSessionIdSubscription = 294 | _audioPlayer.onAudioSessionIdChange.listen((audioSessionId) { 295 | print("audio Session Id: $audioSessionId"); 296 | }); 297 | _notificationActionCallbackSubscription = _audioPlayer 298 | .onNotificationActionCallback 299 | .listen((notificationActionName) { 300 | //do something 301 | }); 302 | _playerCompleteSubscription = _audioPlayer.onPlayerCompletion.listen((a) { 303 | _position = Duration(milliseconds: 0); 304 | }); 305 | } 306 | 307 | Future _play() async { 308 | if (url != null) { 309 | final Result result = await _audioPlayer.play( 310 | url!, 311 | repeatMode: true, 312 | respectAudioFocus: false, 313 | playerMode: PlayerMode.FOREGROUND, 314 | audioNotification: audioNotifications[0], 315 | ); 316 | if (result == Result.ERROR) { 317 | print("something went wrong in play method :("); 318 | } 319 | } else if (urls != null) { 320 | final Result result = await _audioPlayer.playAll( 321 | urls!, 322 | repeatMode: false, 323 | respectAudioFocus: true, 324 | playerMode: PlayerMode.FOREGROUND, 325 | audioNotifications: audioNotifications, 326 | ); 327 | if (result == Result.ERROR) { 328 | print("something went wrong in playAll method :("); 329 | } 330 | } 331 | } 332 | 333 | Future _resume() async { 334 | final Result result = await _audioPlayer.resume(); 335 | if (result == Result.FAIL) { 336 | print( 337 | "you tried to call audio conrolling methods on released audio player :("); 338 | } else if (result == Result.ERROR) { 339 | print("something went wrong in resume :("); 340 | } 341 | } 342 | 343 | Future _pause() async { 344 | final Result result = await _audioPlayer.pause(); 345 | if (result == Result.FAIL) { 346 | print( 347 | "you tried to call audio conrolling methods on released audio player :("); 348 | } else if (result == Result.ERROR) { 349 | print("something went wrong in pause :("); 350 | } 351 | } 352 | 353 | Future _stop() async { 354 | final Result result = await _audioPlayer.stop(); 355 | if (result == Result.FAIL) { 356 | print( 357 | "you tried to call audio conrolling methods on released audio player :("); 358 | } else if (result == Result.ERROR) { 359 | print("something went wrong in stop :("); 360 | } 361 | } 362 | 363 | Future _release() async { 364 | final Result result = await _audioPlayer.release(); 365 | if (result == Result.FAIL) { 366 | print( 367 | "you tried to call audio conrolling methods on released audio player :("); 368 | } else if (result == Result.ERROR) { 369 | print("something went wrong in release :("); 370 | } 371 | } 372 | 373 | Future _next() async { 374 | final Result result = await _audioPlayer.next(); 375 | if (result == Result.FAIL) { 376 | print( 377 | "you tried to call audio conrolling methods on released audio player :("); 378 | } else if (result == Result.ERROR) { 379 | print("something went wrong in next :("); 380 | } 381 | } 382 | 383 | Future _previous() async { 384 | final Result result = await _audioPlayer.previous(); 385 | if (result == Result.FAIL) { 386 | print( 387 | "you tried to call audio conrolling methods on released audio player :("); 388 | } else if (result == Result.ERROR) { 389 | print("something went wrong in previous :("); 390 | } 391 | } 392 | 393 | String getUrlMatchingImage() { 394 | if (url == kUrl1) { 395 | return imageUrl1; 396 | } else if (url == kUrl2) { 397 | return imageUrl2; 398 | } else if (url == kUrl2) { 399 | return imageUrl3; 400 | } else { 401 | return imageUrl1; 402 | } 403 | } 404 | } 405 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_exoplayer_example 2 | description: Demonstrates how to use the flutter_exoplayer plugin. 3 | 4 | environment: 5 | sdk: ">=2.12.0 <3.0.0" 6 | 7 | 8 | dependencies: 9 | flutter: 10 | sdk: flutter 11 | http: ^0.13.1 12 | path_provider: ^2.0.1 13 | flutter_exoplayer: 14 | path: ../ 15 | 16 | flutter: 17 | uses-material-design: true 18 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/danielR2001/flutter_exoplayer/44c26c5dd500f116f910c6e6871ee882b11c62c5/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/AudioPlayerPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface AudioPlayerPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /ios/Classes/AudioPlayerPlugin.m: -------------------------------------------------------------------------------- 1 | #import "AudioPlayerPlugin.h" 2 | 3 | @implementation AudioPlayerPlugin 4 | + (void)registerWithRegistrar:(NSObject*)registrar { 5 | FlutterMethodChannel* channel = [FlutterMethodChannel 6 | methodChannelWithName:@"exoplayer" 7 | binaryMessenger:[registrar messenger]]; 8 | AudioPlayerPlugin* instance = [[AudioPlayerPlugin alloc] init]; 9 | [registrar addMethodCallDelegate:instance channel:channel]; 10 | } 11 | 12 | - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { 13 | if ([@"getPlatformVersion" isEqualToString:call.method]) { 14 | result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]); 15 | } else { 16 | result(FlutterMethodNotImplemented); 17 | } 18 | } 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ios/flutter_exoplayer.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'flutter_exoplayer' 6 | s.version = '0.6.1' 7 | s.summary = 'A flutter plugin to play audio files using the Java ExoPlayer library.' 8 | s.description = <<-DESC 9 | A flutter plugin to play audio files using the Java ExoPlayer library.' 10 | DESC 11 | s.homepage = 'https://github.com/danielR2001/flutter_exoplayer' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Daniel Rachlin' => 'daniel.rachlin@gmail.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | 19 | s.ios.deployment_target = '8.0' 20 | end 21 | 22 | -------------------------------------------------------------------------------- /lib/audio_notification.dart: -------------------------------------------------------------------------------- 1 | enum NotificationDefaultActions { 2 | NONE, 3 | NEXT, 4 | PREVIOUS, 5 | ALL, 6 | } 7 | 8 | enum NotificationActionName { 9 | PREVIOUS, 10 | NEXT, 11 | PLAY, 12 | PAUSE, 13 | CUSTOM1, 14 | CUSTOM2, 15 | } 16 | 17 | enum NotificationActionCallbackMode { 18 | CUSTOM, 19 | DEFAULT, 20 | } 21 | 22 | enum NotificationCustomActions { 23 | DISABLED, 24 | ONE, 25 | TWO, 26 | } 27 | 28 | class AudioNotification { 29 | final String smallIconFileName; 30 | final String? title; 31 | final String? subTitle; 32 | final String? largeIconUrl; 33 | final bool isLocal; 34 | final NotificationDefaultActions notificationDefaultActions; 35 | final NotificationActionCallbackMode notificationActionCallbackMode; 36 | final NotificationCustomActions notificationCustomActions; 37 | // TODO add background color! 38 | // TODO notification importance! 39 | // TODO set timeout! 40 | 41 | AudioNotification({ 42 | required this.smallIconFileName, 43 | this.title, 44 | this.subTitle = "", 45 | this.largeIconUrl = "", 46 | this.isLocal = false, 47 | this.notificationDefaultActions = NotificationDefaultActions.ALL, 48 | this.notificationActionCallbackMode = 49 | NotificationActionCallbackMode.DEFAULT, 50 | this.notificationCustomActions = NotificationCustomActions.DISABLED, 51 | }); 52 | } 53 | -------------------------------------------------------------------------------- /lib/audioplayer.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'package:flutter_exoplayer/audio_notification.dart'; 3 | import 'package:flutter/services.dart'; 4 | import 'package:uuid/uuid.dart'; 5 | 6 | enum PlayerState { 7 | RELEASED, 8 | STOPPED, 9 | BUFFERING, 10 | PLAYING, 11 | PAUSED, 12 | COMPLETED, 13 | } 14 | 15 | enum PlayerMode { 16 | FOREGROUND, 17 | BACKGROUND, 18 | } 19 | 20 | enum Result { 21 | SUCCESS, 22 | FAIL, 23 | ERROR, 24 | } 25 | 26 | const PlayerStateMap = { 27 | -1: PlayerState.RELEASED, 28 | 0: PlayerState.STOPPED, 29 | 1: PlayerState.BUFFERING, 30 | 2: PlayerState.PLAYING, 31 | 3: PlayerState.PAUSED, 32 | 4: PlayerState.COMPLETED, 33 | }; 34 | 35 | const ResultMap = { 36 | 0: Result.ERROR, 37 | 1: Result.FAIL, 38 | 2: Result.SUCCESS, 39 | }; 40 | 41 | const NotificationDefaultActionsMap = { 42 | NotificationDefaultActions.NONE: 0, 43 | NotificationDefaultActions.NEXT: 1, 44 | NotificationDefaultActions.PREVIOUS: 2, 45 | NotificationDefaultActions.ALL: 3, 46 | }; 47 | 48 | const NotificationCustomActionsMap = { 49 | NotificationCustomActions.DISABLED: 0, 50 | NotificationCustomActions.ONE: 1, 51 | NotificationCustomActions.TWO: 2, 52 | }; 53 | 54 | const NotificationActionNameMap = { 55 | 0: NotificationActionName.PREVIOUS, 56 | 1: NotificationActionName.NEXT, 57 | 2: NotificationActionName.PLAY, 58 | 3: NotificationActionName.PAUSE, 59 | 4: NotificationActionName.CUSTOM1, 60 | 5: NotificationActionName.CUSTOM2, 61 | }; 62 | 63 | const NotificationActionCallbackModeMap = { 64 | NotificationActionCallbackMode.DEFAULT: 0, 65 | NotificationActionCallbackMode.CUSTOM: 1, 66 | }; 67 | 68 | class AudioPlayer { 69 | static MethodChannel _channel = const MethodChannel('danielr2001/audioplayer') 70 | ..setMethodCallHandler(platformCallHandler); 71 | 72 | static final _uuid = Uuid(); 73 | static bool logEnabled = false; 74 | static final players = Map(); 75 | 76 | late String _playerId; 77 | late PlayerState _playerState; 78 | 79 | String get playerId => _playerId; 80 | 81 | PlayerState get playerState => _playerState; 82 | 83 | final StreamController _playerStateController = 84 | StreamController.broadcast(); 85 | 86 | final StreamController _positionController = 87 | StreamController.broadcast(); 88 | 89 | final StreamController _durationController = 90 | StreamController.broadcast(); 91 | 92 | final StreamController _completionController = 93 | StreamController.broadcast(); 94 | 95 | final StreamController _errorController = 96 | StreamController.broadcast(); 97 | 98 | final StreamController _currentPlayingIndexController = 99 | StreamController.broadcast(); 100 | 101 | final StreamController _audioSessionIdController = 102 | StreamController.broadcast(); 103 | 104 | final StreamController _notificationActionController = 105 | StreamController.broadcast(); 106 | 107 | /// Stream of changes on player playerState. 108 | /// 109 | /// Events are sent every time the state of the audioplayer is changed 110 | Stream get onPlayerStateChanged => _playerStateController.stream; 111 | 112 | /// Stream of changes on audio position. 113 | /// 114 | /// Roughly fires every 200 milliseconds. Will continuously update the 115 | /// position of the playback if the status is [AudioPlayerState.PLAYING]. 116 | /// 117 | /// You can use it on a progress bar, for instance. 118 | Stream get onAudioPositionChanged => _positionController.stream; 119 | 120 | /// Stream of changes on audio duration. 121 | /// 122 | /// An event is going to be sent as soon as the audio duration is available 123 | /// (it might take a while to download or buffer it). 124 | Stream get onDurationChanged => _durationController.stream; 125 | 126 | /// Stream of player completions. 127 | /// 128 | /// Events are sent every time an audio is finished, therefore no event is 129 | /// sent when an audio is paused or stopped. 130 | /// 131 | /// [ReleaseMode.LOOP] also sends events to this stream. 132 | Stream get onPlayerCompletion => _completionController.stream; 133 | 134 | /// Stream of player audio session ID. 135 | /// 136 | /// Events are sent every time the audio session id is changed. 137 | Stream get onAudioSessionIdChange => _audioSessionIdController.stream; 138 | 139 | /// Stream of player errors. 140 | /// 141 | /// Events are sent when an unexpected error is thrown in the native code. 142 | Stream get onPlayerError => _errorController.stream; 143 | 144 | /// Stream of notification actions callback. 145 | /// 146 | /// Events are sent every time the user taps on one of the notification`s 147 | /// actions, if `NotificationActionCallbackMode.CUSTOM` is passed to `AudioNotification`. 148 | Stream get onNotificationActionCallback => 149 | _notificationActionController.stream; 150 | 151 | /// Stream of current playing index. 152 | /// 153 | /// Events are sent when current index of a player is being changed. 154 | Stream get onCurrentAudioIndexChanged => 155 | _currentPlayingIndexController.stream; 156 | 157 | // PlayerState _audioPlayerState; 158 | 159 | // PlayerState get state => _audioPlayerState; 160 | 161 | /// Initializes AudioPlayer 162 | /// 163 | AudioPlayer() { 164 | _playerState = PlayerState.RELEASED; 165 | _playerId = _uuid.v4(); 166 | players[playerId] = this; 167 | } 168 | 169 | /// Plays an audio. 170 | /// 171 | /// If [PlayerMode] is set to [PlayerMode.FOREGROUND], then you also need to pass: 172 | /// [audioNotification] for providing the foreground notification. 173 | Future play( 174 | String url, { 175 | bool repeatMode = false, 176 | bool respectAudioFocus = false, 177 | Duration position = const Duration(milliseconds: 0), 178 | PlayerMode playerMode = PlayerMode.BACKGROUND, 179 | AudioNotification? audioNotification, 180 | }) async { 181 | if (playerMode == PlayerMode.FOREGROUND && audioNotification == null) 182 | return Result.ERROR; 183 | 184 | return ResultMap[await _invokeMethod('play', { 185 | 'url': url, 186 | 'repeatMode': repeatMode, 187 | 'isBackground': playerMode == PlayerMode.BACKGROUND, 188 | 'respectAudioFocus': respectAudioFocus, 189 | 'position': position.inMilliseconds, 190 | // audio notification object 191 | 'smallIconFileName': audioNotification?.smallIconFileName, 192 | 'title': audioNotification?.title, 193 | 'subTitle': audioNotification?.subTitle, 194 | 'largeIconUrl': audioNotification?.largeIconUrl, 195 | 'isLocal': audioNotification?.isLocal, 196 | 'notificationDefaultActions': NotificationDefaultActionsMap[ 197 | audioNotification?.notificationDefaultActions], 198 | 'notificationActionCallbackMode': NotificationActionCallbackModeMap[ 199 | audioNotification?.notificationActionCallbackMode], 200 | 'notificationCustomActions': NotificationCustomActionsMap[ 201 | audioNotification?.notificationCustomActions], 202 | }) as int] ?? 203 | Result.ERROR; 204 | } 205 | 206 | /// Plays your playlist. 207 | /// 208 | /// If [PlayerMode] is set to [PlayerMode.FOREGROUND], then you also need to pass: 209 | /// [audioNotifications] for providing the foreground notification. 210 | Future playAll( 211 | List urls, { 212 | int index = 0, 213 | bool repeatMode = false, 214 | bool respectAudioFocus = false, 215 | Duration position = const Duration(milliseconds: 0), 216 | PlayerMode playerMode = PlayerMode.BACKGROUND, 217 | List audioNotifications = const [], 218 | }) async { 219 | if (audioNotifications.length != urls.length && 220 | playerMode == PlayerMode.FOREGROUND) return Result.ERROR; 221 | 222 | return ResultMap[await _invokeMethod('playAll', { 223 | 'urls': urls, 224 | 'repeatMode': repeatMode, 225 | 'isBackground': playerMode == PlayerMode.BACKGROUND, 226 | 'respectAudioFocus': respectAudioFocus, 227 | 'position': position.inMilliseconds, 228 | 'index': index, 229 | // audio notification objects 230 | 'smallIconFileNames': 231 | audioNotifications.map((e) => e.smallIconFileName).toList(), 232 | 'titles': audioNotifications.map((e) => e.title).toList(), 233 | 'subTitles': audioNotifications.map((e) => e.subTitle).toList(), 234 | 'largeIconUrls': 235 | audioNotifications.map((e) => e.largeIconUrl).toList(), 236 | 'isLocals': audioNotifications.map((e) => e.isLocal).toList(), 237 | 'notificationDefaultActionsList': audioNotifications 238 | .map((e) => 239 | NotificationDefaultActionsMap[e.notificationDefaultActions]) 240 | .toList(), 241 | 'notificationActionCallbackModes': audioNotifications 242 | .map((e) => NotificationActionCallbackModeMap[ 243 | e.notificationActionCallbackMode]) 244 | .toList(), 245 | 'notificationCustomActionsList': audioNotifications 246 | .map((e) => 247 | NotificationCustomActionsMap[e.notificationCustomActions]) 248 | .toList(), 249 | }) as int] ?? 250 | Result.ERROR; 251 | } 252 | 253 | /// Pauses the audio that is currently playing. 254 | /// 255 | /// If you call [resume] later, the audio will resume from the point that it 256 | /// has been paused. 257 | Future pause() async { 258 | return ResultMap[await _invokeMethod('pause') as int] ?? Result.ERROR; 259 | } 260 | 261 | /// Plays the next song. 262 | /// 263 | /// If playing only single audio it will restart the current. 264 | Future next() async { 265 | return ResultMap[await _invokeMethod('next') as int] ?? Result.ERROR; 266 | } 267 | 268 | /// Plays the previous song. 269 | /// 270 | /// If playing only single audio it will restart the current. 271 | Future previous() async { 272 | return ResultMap[await _invokeMethod('previous') as int] ?? Result.ERROR; 273 | } 274 | 275 | /// Stops the audio that is currently playing. 276 | /// 277 | /// The position is going to be reset and you will no longer be able to resume 278 | /// from the last point. 279 | Future stop() async { 280 | return ResultMap[await _invokeMethod('stop') as int] ?? Result.ERROR; 281 | } 282 | 283 | /// Resumes the audio that has been paused. 284 | Future resume() async { 285 | return ResultMap[await _invokeMethod('resume') as int] ?? Result.ERROR; 286 | } 287 | 288 | /// Releases the resources associated with this audio player. 289 | /// 290 | Future release() async { 291 | return ResultMap[await _invokeMethod('release') as int] ?? Result.ERROR; 292 | } 293 | 294 | /// Moves the cursor to the desired position. 295 | Future seekPosition(Duration position) async { 296 | return ResultMap[await _invokeMethod( 297 | 'seekPosition', {'position': position.inMilliseconds}) as int] ?? 298 | Result.ERROR; 299 | } 300 | 301 | /// Switches to the desired index in playlist. 302 | Future seekIndex(int index) async { 303 | return ResultMap[ 304 | await _invokeMethod('seekIndex', {'index': index}) as int] ?? 305 | Result.ERROR; 306 | } 307 | 308 | /// Gets audio duration after setting url. 309 | /// 310 | /// It will be available as soon as the audio duration is available 311 | /// (it might take a while to download or buffer it if file is not local). 312 | Future getDuration() async { 313 | return Duration(milliseconds: await _invokeMethod('getDuration') as int); 314 | } 315 | 316 | /// Gets audio volume 317 | /// 318 | /// Volume range is from 0-1. 319 | Future getVolume() async { 320 | return await _invokeMethod('getVolume') as double; 321 | } 322 | 323 | /// Gets the Playback speed 324 | /// 325 | /// speed range is from 1-8. 326 | Future getPlaybackSpeed() async { 327 | return await _invokeMethod('getPlaybackSpeed') as double; 328 | } 329 | 330 | /// Gets audio current playing position 331 | /// 332 | /// the position starts from 0. 333 | Future getCurrentPosition() async { 334 | int milliseconds = await _invokeMethod('getCurrentPosition') as int; 335 | return Duration(milliseconds: milliseconds); 336 | } 337 | 338 | /// Gets current playing audio index 339 | Future getCurrentPlayingAudioIndex() async { 340 | return await _invokeMethod('getCurrentPlayingAudioIndex') as int; 341 | } 342 | 343 | /// Sets the volume (amplitude). 344 | /// 345 | /// Volume range is from 0-1. 346 | /// 0 is mute and 1 is the max volume. The values between 0 and 1 are linearly 347 | /// interpolated. 348 | Future setVolume(double volume) async { 349 | return ResultMap[ 350 | await _invokeMethod('setVolume', {'volume': volume}) as int] ?? 351 | Result.ERROR; 352 | } 353 | 354 | // Sets the repeat mode. 355 | Future setRepeatMode(bool repeatMode) async { 356 | return ResultMap[ 357 | await _invokeMethod('setRepeatMode', {'repeatMode': repeatMode}) 358 | as int] ?? 359 | Result.ERROR; 360 | } 361 | 362 | // Sets the playback speed. 363 | /// 364 | /// speed range is from 1-8. 365 | /// 1 is regular speed. 366 | Future setPlaybackSpeed(double speed) async { 367 | return ResultMap[ 368 | await _invokeMethod('setPlaybackSpeed', {'speed': speed}) as int] ?? 369 | Result.ERROR; 370 | } 371 | 372 | /// Sets the [AudioNotification] for the single player, if you want to change specific 373 | /// notification in [AudioNotification]s list use [setSpecificAudioNotification]. 374 | Future setAudioNotification( 375 | AudioNotification audioNotification) async { 376 | return ResultMap[await _invokeMethod('setAudioObject', { 377 | 'smallIconFileName': audioNotification.smallIconFileName, 378 | 'title': audioNotification.title, 379 | 'subTitle': audioNotification.subTitle, 380 | 'largeIconUrl': audioNotification.largeIconUrl, 381 | 'notificationDefaultActions': NotificationDefaultActionsMap[ 382 | audioNotification.notificationDefaultActions], 383 | 'notificationActionCallbackMode': NotificationActionCallbackModeMap[ 384 | audioNotification.notificationActionCallbackMode], 385 | 'notificationCustomActions': NotificationCustomActionsMap[ 386 | audioNotification.notificationCustomActions], 387 | }) as int] ?? 388 | Result.ERROR; 389 | } 390 | 391 | /// Sets the [AudioNotification]s for the playlist player. 392 | Future setAudioNotifications( 393 | List audioNotifications) async { 394 | return ResultMap[await _invokeMethod('setAudioObjects', { 395 | 'smallIconFileNames': 396 | audioNotifications.map((e) => e.smallIconFileName).toList(), 397 | 'titles': audioNotifications.map((e) => e.title).toList(), 398 | 'subTitles': audioNotifications.map((e) => e.subTitle).toList(), 399 | 'largeIconUrls': 400 | audioNotifications.map((e) => e.largeIconUrl).toList(), 401 | 'notificationDefaultActionsList': audioNotifications 402 | .map((e) => 403 | NotificationDefaultActionsMap[e.notificationDefaultActions]) 404 | .toList(), 405 | 'notificationActionCallbackModes': audioNotifications 406 | .map((e) => NotificationActionCallbackModeMap[ 407 | e.notificationActionCallbackMode]) 408 | .toList(), 409 | 'notificationCustomActionsList': audioNotifications 410 | .map((e) => 411 | NotificationCustomActionsMap[e.notificationCustomActions]) 412 | .toList(), 413 | }) as int] ?? 414 | Result.ERROR; 415 | } 416 | 417 | /// Sets a sepcific [AudioNotification] in the [AudioNotification]s for the playlist player. 418 | Future setSpecificAudioNotification( 419 | AudioNotification audioNotification, int index) async { 420 | return ResultMap[await _invokeMethod('setSpecificAudioNotification', { 421 | 'smallIconFileName': audioNotification.smallIconFileName, 422 | 'title': audioNotification.title, 423 | 'subTitle': audioNotification.subTitle, 424 | 'largeIconUrl': audioNotification.largeIconUrl, 425 | 'notificationDefaultActions': 426 | audioNotification.notificationDefaultActions, 427 | 'notificationActionCallbackMode': 428 | audioNotification.notificationActionCallbackMode, 429 | 'notificationCustomActions': 430 | audioNotification.notificationCustomActions, 431 | 'index': index, 432 | }) as int] ?? 433 | Result.ERROR; 434 | } 435 | 436 | static Future platformCallHandler(MethodCall call) async { 437 | try { 438 | _doHandlePlatformCall(call); 439 | } catch (ex) { 440 | _log('Unexpected error: $ex'); 441 | } 442 | } 443 | 444 | Future _invokeMethod( 445 | String method, [ 446 | Map arguments = const {}, 447 | ]) { 448 | final Map withPlayerId = Map.of(arguments) 449 | ..['playerId'] = playerId; 450 | 451 | return _channel 452 | .invokeMethod(method, withPlayerId) 453 | .then((result) => (result)); 454 | } 455 | 456 | static Future _doHandlePlatformCall(MethodCall call) async { 457 | final Map callArgs = call.arguments as Map; 458 | _log('_platformCallHandler call ${call.method} $callArgs'); 459 | 460 | final playerId = callArgs['playerId'] as String; 461 | final AudioPlayer player = players[playerId]!; 462 | final value = callArgs['value']; 463 | 464 | switch (call.method) { 465 | case 'audio.onDurationChanged': 466 | Duration newDuration = Duration(milliseconds: value); 467 | player._durationController.add(newDuration); 468 | break; 469 | case 'audio.onCurrentPositionChanged': 470 | Duration newDuration = Duration(milliseconds: value); 471 | player._positionController.add(newDuration); 472 | break; 473 | case 'audio.onStateChanged': 474 | player._playerState = PlayerStateMap[value]!; 475 | player._playerStateController.add(player._playerState); 476 | break; 477 | case 'audio.onCurrentPlayingAudioIndexChange': 478 | player._currentPlayingIndexController.add(value); 479 | print("track changed to: $value"); 480 | break; 481 | case 'audio.onAudioSessionIdChange': 482 | player._audioSessionIdController.add(value); 483 | break; 484 | case 'audio.onNotificationActionCallback': 485 | player._notificationActionController 486 | .add(NotificationActionNameMap[value]!); 487 | break; 488 | case 'audio.onError': 489 | player._playerState = PlayerState.RELEASED; 490 | player._errorController.add(value); 491 | break; 492 | default: 493 | _log('Unknown method ${call.method} '); 494 | } 495 | } 496 | 497 | static void _log(String param) { 498 | if (logEnabled) print(param); 499 | } 500 | 501 | Future dispose() async { 502 | List futures = []; 503 | await _invokeMethod('dispose'); 504 | if (!_playerStateController.isClosed) 505 | futures.add(_playerStateController.close()); 506 | if (!_positionController.isClosed) futures.add(_positionController.close()); 507 | if (!_durationController.isClosed) futures.add(_durationController.close()); 508 | if (!_completionController.isClosed) 509 | futures.add(_completionController.close()); 510 | if (!_errorController.isClosed) futures.add(_errorController.close()); 511 | if (!_currentPlayingIndexController.isClosed) 512 | futures.add(_currentPlayingIndexController.close()); 513 | if (!_audioSessionIdController.isClosed) 514 | futures.add(_audioSessionIdController.close()); 515 | if (!_notificationActionController.isClosed) 516 | futures.add(_notificationActionController.close()); 517 | await Future.wait(futures); 518 | } 519 | } 520 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_exoplayer 2 | description: A flutter plugin to play audio files using the Java ExoPlayer library. 3 | version: 0.6.1 4 | homepage: https://github.com/danielR2001/flutter_exoplayer 5 | 6 | environment: 7 | sdk: ">=2.12.0 <3.0.0" 8 | 9 | dependencies: 10 | uuid: ^3.0.3 11 | flutter: 12 | sdk: flutter 13 | 14 | dev_dependencies: 15 | test: ^1.16.8 16 | flutter: 17 | plugin: 18 | androidPackage: danielr2001.audioplayer 19 | pluginClass: AudioPlayerPlugin 20 | -------------------------------------------------------------------------------- /test/audioplayer_test.dart: -------------------------------------------------------------------------------- 1 | void main() {} 2 | --------------------------------------------------------------------------------