├── .gitignore ├── .metadata ├── CHANGELOG.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── android ├── .gitignore ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src │ ├── main │ ├── AndroidManifest.xml │ └── kotlin │ │ └── me │ │ └── sithiramunasinghe │ │ └── flutter │ │ └── flutter_radio_player │ │ ├── FlutterRadioPlayerPlugin.kt │ │ ├── core │ │ ├── EventChannelSink.kt │ │ └── PlaybackService.kt │ │ └── data │ │ ├── FlutterRadioPlayerSource.kt │ │ ├── FlutterRadioVolumeChanged.kt │ │ └── NowPlayingInfo.kt │ └── test │ └── kotlin │ └── me │ └── sithiramunasinghe │ └── flutter │ └── flutter_radio_player │ └── FlutterRadioPlayerPluginTest.kt ├── enabling-xcode-bg-service.png ├── example ├── .gitignore ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle │ │ └── src │ │ │ ├── debug │ │ │ └── AndroidManifest.xml │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── me │ │ │ │ │ └── sithiramunasinghe │ │ │ │ │ └── flutter │ │ │ │ │ └── flutter_radio_player_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── 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-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle ├── images │ └── sample-cover.jpg ├── integration_test │ └── plugin_integration_test.dart ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── 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 │ │ └── Runner-Bridging-Header.h │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ └── main.dart ├── pubspec.lock ├── pubspec.yaml └── test │ └── widget_test.dart ├── flutter_radio_player_logo.png ├── ios ├── .gitignore ├── Assets │ └── .gitkeep ├── Classes │ ├── FlutterRadioPlayerPlugin.swift │ ├── core │ │ ├── EventChannelSink.swift │ │ └── PlaybackService.swift │ └── data │ │ ├── FlutterRadioPlayerSource.swift │ │ ├── FlutterRadioVolumeChange.swift │ │ └── NowPlayingInfo.swift └── flutter_radio_player.podspec ├── lib ├── data │ └── flutter_radio_player_event.dart ├── flutter_radio_player.dart ├── flutter_radio_player_method_channel.dart └── flutter_radio_player_platform_interface.dart ├── pubspec.lock ├── pubspec.yaml └── test ├── flutter_radio_player_method_channel_test.dart └── flutter_radio_player_test.dart /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | # Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. 26 | **/doc/api/ 27 | .dart_tool/ 28 | build/ 29 | -------------------------------------------------------------------------------- /.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: "761747bfc538b5af34aa0d3fac380f1bc331ec49" 8 | channel: "stable" 9 | 10 | project_type: plugin 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 17 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 18 | - platform: android 19 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 20 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 21 | - platform: ios 22 | create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 23 | base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 24 | 25 | # User provided section 26 | 27 | # List of Local paths (relative to this file) that should be 28 | # ignored by the migrate tool. 29 | # 30 | # Files that are not part of the templates will be ignored by default. 31 | unmanaged_files: 32 | - 'lib/main.dart' 33 | - 'ios/Runner.xcodeproj/project.pbxproj' 34 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.0.2 2 | 3 | * Added foreground title when title was provided along with artist title 4 | 5 | # 3.0.1 6 | 7 | * Added missing method `jumpToSourceIndex(index)` 8 | 9 | # 3.0.0 10 | 11 | * Completely Rewritten from scratch with backward compatibility in-mind 12 | * Now supports album arts in both iOS and Android platforms 13 | * Better support for platform native companion playbacks like watchOS, wearOS, CarPlay, and Android Auto 14 | * Multiple bugfixes and enhancements from previous versions 15 | 16 | # 2.0.3 17 | 18 | * Bugfixes for Android 14+ 19 | 20 | # 2.0.2 21 | 22 | * Fixed compiling error. 23 | 24 | # 2.0.1 25 | 26 | * Updated for better rating 27 | 28 | # 2.0.0 29 | 30 | * Completely Rewritten from scratch with backward compatibility in-mind 31 | * Supports multiple media-sources 32 | * Better Events / Reactivity 33 | * Better watchOS / WearOS support 34 | * Better native control support 35 | * New methods to control the player better 36 | 37 | # 1.1.0 38 | 39 | * Updated to NULL-SAFETY 40 | * Updated exo-player version 41 | * Bug fixes and improvements 42 | 43 | # 1.0.7 44 | 45 | * Fixed media meta-data on iOS 46 | 47 | # 1.0.6 48 | 49 | * Fixed media meta-data bug and dynamic URL changing issue. 50 | 51 | # 1.0.5 52 | 53 | Fixed bugs, reorganized code and improved documentation. 54 | 55 | * Added setUrl method to dynamically change the playing URL. 56 | * Improved documentation. 57 | 58 | # 1.0.4 59 | 60 | Fixed bugs and slightly improved documentation. 61 | 62 | * Fixed an issue where player failed to bind to the application context. 63 | * Fixed an issue with EventSink. 64 | * Fixed a typo in the pubspec 65 | 66 | # 1.0.3 67 | 68 | * Fixed pubpec 69 | 70 | # 1.0.2 71 | 72 | * Fixed pubpec 73 | 74 | # 1.0.1 75 | 76 | * Fixed pubpec 77 | 78 | # 1.0.0 79 | 80 | * Updated Read me. 81 | 82 | # 0.0.1 83 | 84 | * Initial Release of FlutterRadioPlayer -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2024 Sithira Munasinghe 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![logo](flutter_radio_player_logo.png) 2 | 3 | # Flutter Radio Player 4 | 5 | ![Pub Version](https://img.shields.io/pub/v/flutter_radio_player?style=plastic) 6 | ![Pub Likes](https://img.shields.io/pub/likes/flutter_radio_player) 7 | ![Pub Points](https://img.shields.io/pub/points/flutter_radio_player) 8 | ![Pub Popularity](https://img.shields.io/pub/popularity/flutter_radio_player) 9 | 10 | **Flutter Radio Player** is the go-to plugin for playing a single streaming URL effortlessly. With support for background music playback right out of the box, it offers seamless integration with platform-native media controls. Whether it's lock screen media controls or deeper integrations like watchOS, CarPlay, WearOS, or Android Auto, Flutter Radio Player handles it all with no extra configuration needed. 11 | 12 | ## Features 13 | 14 | - **Background Playback**: Plays audio in the background without any configuration. 15 | - **Watch Integration**: Seamlessly integrates with WatchOS and WearOS for native watch control. 16 | - **Automotive Systems**: Supports infotainment systems like Apple CarPlay and Android Auto. 17 | - **Reactive by Default**: Automatically reacts to stream changes. 18 | - **ICY/Metadata Extraction**: Extracts stream metadata if available. 19 | 20 | ## Getting Started 21 | 22 | ### 1. Install the Player 23 | 24 | ```bash 25 | flutter pub add flutter_radio_player 26 | ``` 27 | 28 | ### 2. Import the Library 29 | 30 | ```dart 31 | import 'package:flutter_radio_player/flutter_radio_player.dart'; 32 | ``` 33 | 34 | ### 3. Configure the Player 35 | 36 | ```dart 37 | final _flutterRadioPlayerPlugin = FlutterRadioPlayer(); // Create an instance of the player 38 | _flutterRadioPlayerPlugin.initialize( 39 | [ 40 | {"url": "https://s2-webradio.antenne.de/chillout?icy=https"}, 41 | { 42 | "title": "SunFM - Sri Lanka", 43 | "artwork": "images/sample-cover.jpg", // Image needs to be bundled with the app 44 | "url": "https://radio.lotustechnologieslk.net:2020/stream/sunfmgarden?icy=https", 45 | }, 46 | {"url": "http://stream.riverradio.com:8000/wcvofm.aac"} 47 | ], 48 | true, // Auto play on load 49 | ); 50 | ``` 51 | 52 | Once configured, your player is ready to stream music. 53 | 54 | ### Manipulating the Player 55 | 56 | You can control the player using the following methods: 57 | 58 | | Method | Action | 59 | |------------------------|------------------------------------------------------------| 60 | | `play()` | Plays the audio from the current source | 61 | | `pause()` | Pauses the audio | 62 | | `playOrPause()` | Toggles play/pause | 63 | | `changeVolume()` | Adjusts the volume | 64 | | `getVolume()` | Retrieves the current volume | 65 | | `nextSource()` | Skips to the next source in the list (if available) | 66 | | `previousSource()` | Goes to the previous source | 67 | | `jumpToSourceIndex()` | Jumps to a specific index in the sources list | 68 | 69 | ### Available Streams 70 | 71 | You can also listen to various streams: 72 | 73 | | Stream | Returns | Description | 74 | |-----------------------------------|-------------------------------------|------------------------------------------------------| 75 | | `getIsPlayingStream()` | `Stream` | Emits playback status | 76 | | `getNowPlayingStream()` | `Stream` | Emits metadata such as track name | 77 | | `getDeviceVolumeChangedStream()` | `Stream` | Emits device audio level updates | 78 | 79 | ## Platform Configuration 80 | 81 | ### iOS 82 | 83 | To enable background playback, configure background capabilities in Xcode as shown below: 84 | 85 | ![Xcode Configuration](enabling-xcode-bg-service.png) 86 | 87 | ### Android 88 | 89 | For Android, ensure the following permissions are added to your `AndroidManifest.xml`: 90 | 91 | ```xml 92 | 93 | 94 | 95 | ``` 96 | 97 | > These permissions are already included in the library. 98 | 99 | **Check out the [Flutter Radio Player Example](/example)** to see how to implement action methods and streams in your player. 100 | 101 | ## Support the Plugin 102 | 103 | If you find this plugin useful, show your support by: 104 | 105 | - Giving it a ⭐️ on [GitHub](https://github.com/Sithira/FlutterRadioPlayer) 106 | - Leaving a like on Pub 107 | - Showing some ♥️ and buying me a coffee via USDT-TR20 at this address: `TNuTkL1ZJGu2xntmtzHzSiH5YdVqUeAujr` 108 | 109 | **Enjoy the plugin!** 110 | Sithira ✌️ -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:flutter_lints/flutter.yaml 2 | 3 | # Additional information about this file can be found at 4 | # https://dart.dev/guides/language/analysis-options 5 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .cxx 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group = "me.sithiramunasinghe.flutter.flutter_radio_player" 2 | version = "1.0-SNAPSHOT" 3 | 4 | buildscript { 5 | ext.kotlin_version = "2.0.0" 6 | repositories { 7 | google() 8 | mavenCentral() 9 | } 10 | 11 | dependencies { 12 | classpath("com.android.tools.build:gradle:7.3.0") 13 | classpath("org.jetbrains.kotlin:kotlin-serialization:$kotlin_version") 14 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | 25 | apply plugin: "com.android.library" 26 | apply plugin: "kotlin-android" 27 | apply plugin: 'kotlinx-serialization' 28 | 29 | android { 30 | if (project.android.hasProperty("namespace")) { 31 | namespace = "me.sithiramunasinghe.flutter.flutter_radio_player" 32 | } 33 | 34 | compileSdk = 34 35 | 36 | compileOptions { 37 | sourceCompatibility = JavaVersion.VERSION_1_8 38 | targetCompatibility = JavaVersion.VERSION_1_8 39 | } 40 | 41 | kotlinOptions { 42 | jvmTarget = "1.8" 43 | } 44 | 45 | sourceSets { 46 | main.java.srcDirs += "src/main/kotlin" 47 | test.java.srcDirs += "src/test/kotlin" 48 | } 49 | 50 | defaultConfig { 51 | minSdk = 21 52 | } 53 | 54 | dependencies { 55 | implementation("androidx.media3:media3-exoplayer:1.3.1") 56 | implementation("androidx.media3:media3-exoplayer-hls:1.3.1") 57 | implementation("androidx.media3:media3-session:1.3.1") 58 | implementation "androidx.media3:media3-common:1.3.1" 59 | implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.1") 60 | 61 | testImplementation("org.jetbrains.kotlin:kotlin-test") 62 | testImplementation("org.mockito:mockito-core:5.0.0") 63 | } 64 | 65 | testOptions { 66 | unitTests.all { 67 | useJUnitPlatform() 68 | 69 | testLogging { 70 | events "passed", "skipped", "failed", "standardOut", "standardError" 71 | outputs.upToDateWhen { false } 72 | showStandardStreams = true 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | # 21 | # Gradle start up script for POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 87 | APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit 88 | 89 | # Use the maximum available, or set MAX_FD != -1 to use that value. 90 | MAX_FD=maximum 91 | 92 | warn () { 93 | echo "$*" 94 | } >&2 95 | 96 | die () { 97 | echo 98 | echo "$*" 99 | echo 100 | exit 1 101 | } >&2 102 | 103 | # OS specific support (must be 'true' or 'false'). 104 | cygwin=false 105 | msys=false 106 | darwin=false 107 | nonstop=false 108 | case "$( uname )" in #( 109 | CYGWIN* ) cygwin=true ;; #( 110 | Darwin* ) darwin=true ;; #( 111 | MSYS* | MINGW* ) msys=true ;; #( 112 | NONSTOP* ) nonstop=true ;; 113 | esac 114 | 115 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 116 | 117 | 118 | # Determine the Java command to use to start the JVM. 119 | if [ -n "$JAVA_HOME" ] ; then 120 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 121 | # IBM's JDK on AIX uses strange locations for the executables 122 | JAVACMD=$JAVA_HOME/jre/sh/java 123 | else 124 | JAVACMD=$JAVA_HOME/bin/java 125 | fi 126 | if [ ! -x "$JAVACMD" ] ; then 127 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 128 | 129 | Please set the JAVA_HOME variable in your environment to match the 130 | location of your Java installation." 131 | fi 132 | else 133 | JAVACMD=java 134 | if ! command -v java >/dev/null 2>&1 135 | then 136 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | fi 142 | 143 | # Increase the maximum file descriptors if we can. 144 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 145 | case $MAX_FD in #( 146 | max*) 147 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 148 | # shellcheck disable=SC2039,SC3045 149 | MAX_FD=$( ulimit -H -n ) || 150 | warn "Could not query maximum file descriptor limit" 151 | esac 152 | case $MAX_FD in #( 153 | '' | soft) :;; #( 154 | *) 155 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 156 | # shellcheck disable=SC2039,SC3045 157 | ulimit -n "$MAX_FD" || 158 | warn "Could not set maximum file descriptor limit to $MAX_FD" 159 | esac 160 | fi 161 | 162 | # Collect all arguments for the java command, stacking in reverse order: 163 | # * args from the command line 164 | # * the main class name 165 | # * -classpath 166 | # * -D...appname settings 167 | # * --module-path (only if needed) 168 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 169 | 170 | # For Cygwin or MSYS, switch paths to Windows format before running java 171 | if "$cygwin" || "$msys" ; then 172 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 173 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 174 | 175 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 176 | 177 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 178 | for arg do 179 | if 180 | case $arg in #( 181 | -*) false ;; # don't mess with options #( 182 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 183 | [ -e "$t" ] ;; #( 184 | *) false ;; 185 | esac 186 | then 187 | arg=$( cygpath --path --ignore --mixed "$arg" ) 188 | fi 189 | # Roll the args list around exactly as many times as the number of 190 | # args, so each arg winds up back in the position where it started, but 191 | # possibly modified. 192 | # 193 | # NB: a `for` loop captures its iteration list before it begins, so 194 | # changing the positional parameters here affects neither the number of 195 | # iterations, nor the values presented in `arg`. 196 | shift # remove old arg 197 | set -- "$@" "$arg" # push replacement arg 198 | done 199 | fi 200 | 201 | 202 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 203 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 204 | 205 | # Collect all arguments for the java command: 206 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 207 | # and any embedded shellness will be escaped. 208 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 209 | # treated as '${Hostname}' itself on the command line. 210 | 211 | set -- \ 212 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 213 | -classpath "$CLASSPATH" \ 214 | org.gradle.wrapper.GradleWrapperMain \ 215 | "$@" 216 | 217 | # Stop when "xargs" is not available. 218 | if ! command -v xargs >/dev/null 2>&1 219 | then 220 | die "xargs is not available" 221 | fi 222 | 223 | # Use "xargs" to parse quoted args. 224 | # 225 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 226 | # 227 | # In Bash we could simply go: 228 | # 229 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 230 | # set -- "${ARGS[@]}" "$@" 231 | # 232 | # but POSIX shell has neither arrays nor command substitution, so instead we 233 | # post-process each arg (as a line of input to sed) to backslash-escape any 234 | # character that might be a shell metacharacter, then use eval to reverse 235 | # that process (while maintaining the separation between arguments), and wrap 236 | # the whole thing up as a single "set" statement. 237 | # 238 | # This will of course break if any of these variables contains a newline or 239 | # an unmatched quote. 240 | # 241 | 242 | eval "set -- $( 243 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 244 | xargs -n1 | 245 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 246 | tr '\n' ' ' 247 | )" '"$@"' 248 | 249 | exec "$JAVACMD" "$@" 250 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%"=="" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%"=="" set DIRNAME=. 29 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'flutter_radio_player' 2 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/FlutterRadioPlayerPlugin.kt: -------------------------------------------------------------------------------- 1 | package me.sithiramunasinghe.flutter.flutter_radio_player 2 | 3 | import android.app.Activity 4 | import android.app.PendingIntent 5 | import android.content.ComponentName 6 | import android.content.Context 7 | import android.content.Intent 8 | import android.content.pm.PackageManager 9 | import android.content.res.AssetManager 10 | import android.net.Uri 11 | import androidx.annotation.OptIn 12 | import androidx.media3.common.MediaItem 13 | import androidx.media3.common.MediaMetadata 14 | import androidx.media3.common.util.UnstableApi 15 | import androidx.media3.common.util.Util 16 | import androidx.media3.session.MediaController 17 | import androidx.media3.session.SessionToken 18 | import com.google.common.util.concurrent.MoreExecutors 19 | import io.flutter.embedding.engine.loader.FlutterLoader 20 | import io.flutter.embedding.engine.plugins.FlutterPlugin 21 | import io.flutter.embedding.engine.plugins.activity.ActivityAware 22 | import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding 23 | import io.flutter.plugin.common.BinaryMessenger 24 | import io.flutter.plugin.common.EventChannel 25 | import io.flutter.plugin.common.MethodCall 26 | import io.flutter.plugin.common.MethodChannel 27 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler 28 | import io.flutter.plugin.common.MethodChannel.Result 29 | import kotlinx.serialization.json.Json 30 | import me.sithiramunasinghe.flutter.flutter_radio_player.core.EventChannelSink 31 | import me.sithiramunasinghe.flutter.flutter_radio_player.core.PlaybackService 32 | import me.sithiramunasinghe.flutter.flutter_radio_player.data.FlutterRadioPlayerSource 33 | import me.sithiramunasinghe.flutter.flutter_radio_player.data.NowPlayingInfo 34 | import java.io.InputStream 35 | 36 | class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodCallHandler { 37 | 38 | private lateinit var channel: MethodChannel 39 | private var applicationContext: Context? = null 40 | private var mediaController: MediaController? = null 41 | private val queuedMethodInvokes = mutableListOf>() 42 | 43 | companion object { 44 | private var isMediaControllerAvailable = false 45 | lateinit var sessionActivity: PendingIntent 46 | 47 | var playBackEventSink: EventChannel.EventSink? = null 48 | var nowPlayingEventSink: EventChannel.EventSink? = null 49 | var playbackVolumeControl: EventChannel.EventSink? = null 50 | private fun getSessionActivity(context: Context, activity: Activity) { 51 | sessionActivity = PendingIntent.getActivity( 52 | context, 0, Intent(context, activity::class.java), 53 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 54 | ) 55 | } 56 | } 57 | 58 | 59 | override fun onAttachedToEngine(flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) { 60 | channel = MethodChannel(flutterPluginBinding.binaryMessenger, "flutter_radio_player") 61 | channel.setMethodCallHandler(this) 62 | applicationContext = flutterPluginBinding.applicationContext 63 | 64 | initEventChannels(flutterPluginBinding.binaryMessenger, EventChannelSink.getInstance()) 65 | initializeEventSink() 66 | 67 | val token = SessionToken( 68 | flutterPluginBinding.applicationContext, ComponentName( 69 | flutterPluginBinding.applicationContext, 70 | PlaybackService::class.java 71 | ) 72 | ) 73 | 74 | val mediaControllerFuture = MediaController.Builder(applicationContext!!, token) 75 | .buildAsync() 76 | 77 | mediaControllerFuture.addListener({ 78 | mediaController = mediaControllerFuture.get() 79 | isMediaControllerAvailable = true 80 | executePendingCalls() 81 | }, MoreExecutors.directExecutor()) 82 | } 83 | 84 | @OptIn(UnstableApi::class) 85 | override fun onMethodCall(call: MethodCall, result: Result) { 86 | if (!isMediaControllerAvailable || playBackEventSink == null) { 87 | queuedMethodInvokes.add(Pair(call, result)) 88 | return 89 | } 90 | when (call.method) { 91 | "initialize" -> { 92 | if (mediaController!!.isPlaying) { 93 | playBackEventSink!!.success(true) 94 | nowPlayingEventSink!!.success( 95 | NowPlayingInfo(title = PlaybackService.latestMetadata?.title.toString()) 96 | .toJson() 97 | ) 98 | return 99 | } 100 | val sources = call.argument("sources") 101 | val playWhenReady = call.argument("playWhenReady") 102 | val decodedSources: List = 103 | Json.decodeFromString(sources!!) 104 | mediaController!!.volume = 0.5F 105 | mediaController!!.playWhenReady = playWhenReady!! 106 | if (decodedSources.isNotEmpty()) { 107 | mediaController!!.setMediaItems(decodedSources.map { 108 | val mediaItemBuilder = MediaItem.Builder().setUri(it.url) 109 | val mediaMeta = MediaMetadata.Builder() 110 | if (it.title.isNullOrEmpty()) { 111 | mediaMeta.setArtist(getAppName()) 112 | } else { 113 | mediaMeta.setTitle(it.title) 114 | mediaMeta.setArtist(getAppName()) 115 | } 116 | if (!it.artwork.isNullOrEmpty()) { 117 | if ((it.artwork!!.contains("http") || it.artwork!!.contains("https"))) { 118 | mediaMeta.setArtworkUri(Uri.parse(it.artwork)) 119 | } else { 120 | mediaMeta.setArtworkData( 121 | Util.toByteArray(getBitmapFromAssets(it.artwork)!!), 122 | MediaMetadata.PICTURE_TYPE_FRONT_COVER 123 | ) 124 | } 125 | } 126 | mediaItemBuilder.setMediaMetadata(mediaMeta.build()) 127 | mediaItemBuilder.build() 128 | }) 129 | mediaController!!.prepare() 130 | } 131 | } 132 | 133 | "playOrPause" -> { 134 | if (mediaController!!.mediaItemCount != 0) { 135 | if (mediaController!!.isPlaying) { 136 | mediaController!!.pause() 137 | return 138 | } 139 | mediaController!!.play() 140 | } 141 | } 142 | 143 | "play" -> { 144 | mediaController!!.play() 145 | } 146 | 147 | "pause" -> { 148 | mediaController!!.pause() 149 | } 150 | 151 | "changeVolume" -> { 152 | val volume = call.argument("volume") 153 | mediaController!!.volume = volume!!.toFloat() 154 | } 155 | 156 | "getVolume" -> { 157 | result.success(mediaController!!.volume) 158 | } 159 | 160 | "nextSource" -> { 161 | clearInMemoryNowPlayingInfo() 162 | mediaController!!.seekToNextMediaItem() 163 | } 164 | 165 | "prevSource" -> { 166 | clearInMemoryNowPlayingInfo() 167 | mediaController!!.seekToPreviousMediaItem() 168 | } 169 | 170 | "sourceAtIndex" -> { 171 | clearInMemoryNowPlayingInfo() 172 | val index = call.argument("index") 173 | mediaController!!.seekToDefaultPosition(index!!) 174 | } 175 | 176 | else -> result.notImplemented() 177 | } 178 | } 179 | 180 | override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { 181 | println("onDetachedFromEngine") 182 | channel.setMethodCallHandler(null) 183 | 184 | EventChannelSink.getInstance().playbackEventChannel = null 185 | EventChannelSink.getInstance().nowPlayingEventChannel = null 186 | EventChannelSink.getInstance().playbackVolumeChannel = null 187 | 188 | playbackVolumeControl = null 189 | nowPlayingEventSink = null 190 | playBackEventSink = null 191 | mediaController!!.release() 192 | isMediaControllerAvailable = false 193 | } 194 | 195 | override fun onAttachedToActivity(binding: ActivityPluginBinding) { 196 | println("onAttachedToActivity") 197 | getSessionActivity(binding.activity.applicationContext, binding.activity) 198 | } 199 | 200 | override fun onDetachedFromActivityForConfigChanges() { 201 | println("onDetachedFromActivityForConfigChanges") 202 | } 203 | 204 | override fun onReattachedToActivityForConfigChanges(binding: ActivityPluginBinding) { 205 | println("onReattachedToActivityForConfigChanges") 206 | getSessionActivity(binding.activity.applicationContext, binding.activity) 207 | } 208 | 209 | override fun onDetachedFromActivity() { 210 | println("onDetachedFromActivity") 211 | } 212 | 213 | /** 214 | * Initialize events sink and event channels 215 | */ 216 | private fun initializeEventSink() { 217 | EventChannelSink.getInstance().playbackEventChannel?.setStreamHandler(object : 218 | EventChannel.StreamHandler { 219 | override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { 220 | playBackEventSink = events 221 | } 222 | 223 | override fun onCancel(arguments: Any?) { 224 | playBackEventSink = null 225 | } 226 | }) 227 | 228 | EventChannelSink.getInstance().nowPlayingEventChannel?.setStreamHandler(object : 229 | EventChannel.StreamHandler { 230 | override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { 231 | nowPlayingEventSink = events 232 | executePendingCalls() 233 | } 234 | 235 | override fun onCancel(arguments: Any?) { 236 | nowPlayingEventSink = null 237 | } 238 | }) 239 | 240 | EventChannelSink.getInstance().playbackVolumeChannel?.setStreamHandler(object : 241 | EventChannel.StreamHandler { 242 | override fun onListen(arguments: Any?, events: EventChannel.EventSink?) { 243 | playbackVolumeControl = events 244 | } 245 | 246 | override fun onCancel(arguments: Any?) { 247 | playbackVolumeControl = null 248 | } 249 | }) 250 | } 251 | 252 | /** 253 | * Execute pending method calls 254 | */ 255 | private fun executePendingCalls() { 256 | if (mediaController == null) { 257 | return 258 | } 259 | queuedMethodInvokes.forEach { (call, result) -> 260 | onMethodCall(call, result) 261 | } 262 | queuedMethodInvokes.clear() 263 | } 264 | 265 | private fun clearInMemoryNowPlayingInfo() { 266 | PlaybackService.latestMetadata = null 267 | } 268 | 269 | /** 270 | * Invoke event channels 271 | * 272 | * @param binaryMessenger Binary messenger 273 | * @param eventsChannelSink Event channel sink 274 | */ 275 | private fun initEventChannels( 276 | binaryMessenger: BinaryMessenger, 277 | eventsChannelSink: EventChannelSink 278 | ) { 279 | val playbackEventChannel = 280 | EventChannel( 281 | binaryMessenger, 282 | "flutter_radio_player/playback_status" 283 | ) 284 | 285 | val nowPlayingEventChannel = EventChannel( 286 | binaryMessenger, 287 | "flutter_radio_player/now_playing_info" 288 | ) 289 | 290 | val playbackVolumeControl = EventChannel( 291 | binaryMessenger, 292 | "flutter_radio_player/volume_control" 293 | ) 294 | 295 | eventsChannelSink.playbackEventChannel = playbackEventChannel 296 | eventsChannelSink.nowPlayingEventChannel = nowPlayingEventChannel 297 | eventsChannelSink.playbackVolumeChannel = playbackVolumeControl 298 | } 299 | 300 | /** 301 | * Load album artwork as an URI for from app bundle 302 | * 303 | * @param assetPath bundle resource path 304 | * @return InputStream of asset 305 | */ 306 | private fun getBitmapFromAssets(assetPath: String?): InputStream? { 307 | try { 308 | val flutterLoader = FlutterLoader() 309 | flutterLoader.startInitialization(applicationContext!!) 310 | flutterLoader.ensureInitializationComplete(applicationContext!!, null) 311 | val assetLookupKey = flutterLoader.getLookupKeyForAsset(assetPath!!) 312 | val assetManager: AssetManager = applicationContext!!.assets 313 | return assetManager.open(assetLookupKey) 314 | // val inputStream = assetManager.open(assetLookupKey) 315 | // return BitmapFactory.decodeStream(inputStream). 316 | } catch (e: Exception) { 317 | e.printStackTrace() 318 | return null 319 | } 320 | } 321 | 322 | /** 323 | * Get the application name 324 | * 325 | * @return App name 326 | */ 327 | private fun getAppName(): String? { 328 | try { 329 | val packageManager: PackageManager = applicationContext!!.packageManager 330 | val applicationInfo = 331 | packageManager.getApplicationInfo(applicationContext!!.packageName, 0) 332 | return packageManager.getApplicationLabel(applicationInfo) as String 333 | } catch (e: PackageManager.NameNotFoundException) { 334 | e.printStackTrace() 335 | return null 336 | } 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/EventChannelSink.kt: -------------------------------------------------------------------------------- 1 | package me.sithiramunasinghe.flutter.flutter_radio_player.core 2 | 3 | import io.flutter.plugin.common.EventChannel 4 | 5 | 6 | class EventChannelSink private constructor() { 7 | var playbackEventChannel: EventChannel? = null 8 | var nowPlayingEventChannel: EventChannel? = null 9 | var playbackVolumeChannel: EventChannel? = null 10 | companion object { 11 | 12 | @Volatile 13 | private var instance: EventChannelSink? = null 14 | 15 | fun getInstance(): EventChannelSink { 16 | if (instance == null) { 17 | synchronized(this) { 18 | if (instance == null) { 19 | instance = EventChannelSink() 20 | } 21 | } 22 | } 23 | return instance!! 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/PlaybackService.kt: -------------------------------------------------------------------------------- 1 | package me.sithiramunasinghe.flutter.flutter_radio_player.core 2 | 3 | import android.content.Intent 4 | import androidx.annotation.OptIn 5 | import androidx.media3.common.AudioAttributes 6 | import androidx.media3.common.MediaItem 7 | import androidx.media3.common.MediaMetadata 8 | import androidx.media3.common.PlaybackException 9 | import androidx.media3.common.Player 10 | import androidx.media3.common.Player.STATE_IDLE 11 | import androidx.media3.common.Player.STATE_READY 12 | import androidx.media3.common.util.UnstableApi 13 | import androidx.media3.datasource.HttpDataSource.HttpDataSourceException 14 | import androidx.media3.exoplayer.ExoPlayer 15 | import androidx.media3.session.DefaultMediaNotificationProvider 16 | import androidx.media3.session.MediaLibraryService 17 | import androidx.media3.session.MediaSession 18 | import androidx.media3.session.MediaSession.ControllerInfo 19 | import com.google.common.util.concurrent.Futures 20 | import com.google.common.util.concurrent.ListenableFuture 21 | import me.sithiramunasinghe.flutter.flutter_radio_player.FlutterRadioPlayerPlugin.Companion.nowPlayingEventSink 22 | import me.sithiramunasinghe.flutter.flutter_radio_player.FlutterRadioPlayerPlugin.Companion.playBackEventSink 23 | import me.sithiramunasinghe.flutter.flutter_radio_player.FlutterRadioPlayerPlugin.Companion.playbackVolumeControl 24 | import me.sithiramunasinghe.flutter.flutter_radio_player.FlutterRadioPlayerPlugin.Companion.sessionActivity 25 | import me.sithiramunasinghe.flutter.flutter_radio_player.data.FlutterRadioVolumeChanged 26 | import me.sithiramunasinghe.flutter.flutter_radio_player.data.NowPlayingInfo 27 | 28 | class PlaybackService : MediaLibraryService() { 29 | 30 | private lateinit var player: Player 31 | private var mediaSession: MediaLibrarySession? = null 32 | 33 | companion object { 34 | var latestMetadata: MediaMetadata? = null 35 | } 36 | 37 | override fun onCreate() { 38 | super.onCreate() 39 | initializeSessionAndPlayer() 40 | } 41 | 42 | override fun onDestroy() { 43 | if (mediaSession != null) { 44 | mediaSession?.run { 45 | player.release() 46 | mediaSession?.release() 47 | release() 48 | mediaSession = null 49 | } 50 | } 51 | super.onDestroy() 52 | } 53 | 54 | override fun onTaskRemoved(rootIntent: Intent?) { 55 | val player = mediaSession?.player 56 | if (player != null) { 57 | // if (!player.playWhenReady && player.mediaItemCount == 0) { 58 | // stopSelf() 59 | // } 60 | stopSelf() 61 | } 62 | } 63 | 64 | override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession? { 65 | return mediaSession 66 | } 67 | 68 | @OptIn(UnstableApi::class) 69 | private fun initializeSessionAndPlayer() { 70 | 71 | player = ExoPlayer.Builder(this) 72 | .setAudioAttributes(AudioAttributes.DEFAULT, true) 73 | .build() 74 | 75 | mediaSession = 76 | MediaLibrarySession.Builder(this, player, object : MediaLibrarySession.Callback { 77 | override fun onAddMediaItems( 78 | mediaSession: MediaSession, 79 | controller: ControllerInfo, 80 | mediaItems: MutableList 81 | ): ListenableFuture> { 82 | return Futures.immediateFuture(mediaItems) 83 | } 84 | }) 85 | .setSessionActivity(sessionActivity) 86 | .build() 87 | 88 | val default = DefaultMediaNotificationProvider(this) 89 | val appInfo = packageManager.getApplicationInfo(packageName, 0) 90 | default.setSmallIcon(appInfo.icon) 91 | 92 | setMediaNotificationProvider(default) 93 | 94 | player.addListener(object : Player.Listener { 95 | override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { 96 | nowPlayingEventSink?.success(null) 97 | super.onMediaItemTransition(mediaItem, reason) 98 | } 99 | 100 | override fun onIsPlayingChanged(isPlaying: Boolean) { 101 | println("is playing = $isPlaying") 102 | playBackEventSink?.success(isPlaying) 103 | } 104 | 105 | override fun onVolumeChanged(volume: Float) { 106 | println("Volume = $volume") 107 | if (playbackVolumeControl != null) { 108 | playbackVolumeControl!!.success( 109 | FlutterRadioVolumeChanged( 110 | volume = volume, 111 | isMuted = false 112 | ).toJson() 113 | ) 114 | } 115 | super.onVolumeChanged(volume) 116 | } 117 | 118 | override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) { 119 | println("======== TITLE => ${mediaMetadata.title}") 120 | if (nowPlayingEventSink != null) { 121 | var nowPlayingTitle: String? = null 122 | if (mediaMetadata.title != null) { 123 | nowPlayingTitle = mediaMetadata.title.toString() 124 | } 125 | nowPlayingEventSink!!.success( 126 | NowPlayingInfo( 127 | title = nowPlayingTitle, 128 | ).toJson() 129 | ) 130 | latestMetadata = mediaMetadata 131 | } 132 | super.onMediaMetadataChanged(mediaMetadata) 133 | } 134 | 135 | override fun onPlaybackStateChanged(playbackState: Int) { 136 | if (playbackState == STATE_IDLE) { 137 | println("player is idle") 138 | } 139 | 140 | if (playbackState == STATE_READY) { 141 | playBackEventSink?.success(false) 142 | println("player is ready") 143 | } 144 | } 145 | 146 | override fun onPlayerError(error: PlaybackException) { 147 | val cause = error.cause 148 | if (cause is HttpDataSourceException) { 149 | println("player error") 150 | } 151 | } 152 | }) 153 | } 154 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/data/FlutterRadioPlayerSource.kt: -------------------------------------------------------------------------------- 1 | package me.sithiramunasinghe.flutter.flutter_radio_player.data 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class FlutterRadioPlayerSource( 7 | var url: String, 8 | var title: String? = null, 9 | var artwork: String? = null, 10 | var playWhenReady: Boolean = false 11 | ) 12 | -------------------------------------------------------------------------------- /android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/data/FlutterRadioVolumeChanged.kt: -------------------------------------------------------------------------------- 1 | package me.sithiramunasinghe.flutter.flutter_radio_player.data 2 | 3 | import kotlinx.serialization.Serializable 4 | import kotlinx.serialization.encodeToString 5 | import kotlinx.serialization.json.Json 6 | 7 | @Serializable 8 | data class FlutterRadioVolumeChanged(var volume: Float, var isMuted: Boolean) { 9 | fun toJson(): String { 10 | return Json.encodeToString(this) 11 | } 12 | } -------------------------------------------------------------------------------- /android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/data/NowPlayingInfo.kt: -------------------------------------------------------------------------------- 1 | package me.sithiramunasinghe.flutter.flutter_radio_player.data 2 | 3 | import kotlinx.serialization.* 4 | import kotlinx.serialization.json.* 5 | 6 | @Serializable 7 | data class NowPlayingInfo( 8 | val title: String? = null, 9 | val album: String? = null 10 | ) { 11 | fun toJson(): String { 12 | return Json.encodeToString(this) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/src/test/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/FlutterRadioPlayerPluginTest.kt: -------------------------------------------------------------------------------- 1 | package me.sithiramunasinghe.flutter.flutter_radio_player 2 | 3 | import io.flutter.plugin.common.MethodCall 4 | import io.flutter.plugin.common.MethodChannel 5 | import kotlin.test.Test 6 | import org.mockito.Mockito 7 | 8 | /* 9 | * This demonstrates a simple unit test of the Kotlin portion of this plugin's implementation. 10 | * 11 | * Once you have built the plugin's example app, you can run these tests from the command 12 | * line by running `./gradlew testDebugUnitTest` in the `example/android/` directory, or 13 | * you can run them directly from IDEs that support JUnit such as Android Studio. 14 | */ 15 | 16 | internal class FlutterRadioPlayerPluginTest { 17 | @Test 18 | fun onMethodCall_getPlatformVersion_returnsExpectedValue() { 19 | val plugin = FlutterRadioPlayerPlugin() 20 | 21 | val call = MethodCall("getPlatformVersion", null) 22 | val mockResult: MethodChannel.Result = Mockito.mock(MethodChannel.Result::class.java) 23 | plugin.onMethodCall(call, mockResult) 24 | 25 | Mockito.verify(mockResult).success("Android " + android.os.Build.VERSION.RELEASE) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /enabling-xcode-bg-service.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/enabling-xcode-bg-service.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Symbolication related 35 | app.*.symbols 36 | 37 | # Obfuscation related 38 | app.*.map.json 39 | 40 | # Android Studio will place build artifacts here 41 | /android/app/debug 42 | /android/app/profile 43 | /android/app/release 44 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # flutter_radio_player_example 2 | 3 | Demonstrates how to use the flutter_radio_player plugin. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | # This file configures the analyzer, which statically analyzes Dart code to 2 | # check for errors, warnings, and lints. 3 | # 4 | # The issues identified by the analyzer are surfaced in the UI of Dart-enabled 5 | # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be 6 | # invoked from the command line by running `flutter analyze`. 7 | 8 | # The following line activates a set of recommended lints for Flutter apps, 9 | # packages, and plugins designed to encourage good coding practices. 10 | include: package:flutter_lints/flutter.yaml 11 | 12 | linter: 13 | # The lint rules applied to this project can be customized in the 14 | # section below to disable rules from the `package:flutter_lints/flutter.yaml` 15 | # included above or to enable additional rules. A list of all available lints 16 | # and their documentation is published at https://dart.dev/lints. 17 | # 18 | # Instead of disabling a lint rule for the entire project in the 19 | # section below, it can also be suppressed for a single line of code 20 | # or a specific dart file by using the `// ignore: name_of_lint` and 21 | # `// ignore_for_file: name_of_lint` syntax on the line or in the file 22 | # producing the lint. 23 | rules: 24 | # avoid_print: false # Uncomment to disable the `avoid_print` rule 25 | # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule 26 | 27 | # Additional information about this file can be found at 28 | # https://dart.dev/guides/language/analysis-options 29 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.android.application" 3 | id "kotlin-android" 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id "dev.flutter.flutter-gradle-plugin" 6 | } 7 | 8 | def localProperties = new Properties() 9 | def localPropertiesFile = rootProject.file("local.properties") 10 | if (localPropertiesFile.exists()) { 11 | localPropertiesFile.withReader("UTF-8") { reader -> 12 | localProperties.load(reader) 13 | } 14 | } 15 | 16 | def flutterVersionCode = localProperties.getProperty("flutter.versionCode") 17 | if (flutterVersionCode == null) { 18 | flutterVersionCode = "1" 19 | } 20 | 21 | def flutterVersionName = localProperties.getProperty("flutter.versionName") 22 | if (flutterVersionName == null) { 23 | flutterVersionName = "1.0" 24 | } 25 | 26 | android { 27 | namespace = "me.sithiramunasinghe.flutter.flutter_radio_player_example" 28 | compileSdk = flutter.compileSdkVersion 29 | ndkVersion = flutter.ndkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility = JavaVersion.VERSION_1_8 33 | targetCompatibility = JavaVersion.VERSION_1_8 34 | } 35 | 36 | defaultConfig { 37 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 38 | applicationId = "me.sithiramunasinghe.flutter.flutter_radio_player_example" 39 | // You can update the following values to match your application needs. 40 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration. 41 | minSdk = flutter.minSdkVersion 42 | targetSdk = flutter.targetSdkVersion 43 | versionCode = flutterVersionCode.toInteger() 44 | versionName = flutterVersionName 45 | } 46 | 47 | buildTypes { 48 | release { 49 | // TODO: Add your own signing config for the release build. 50 | // Signing with the debug keys for now, so `flutter run --release` works. 51 | signingConfig = signingConfigs.debug 52 | } 53 | } 54 | } 55 | 56 | flutter { 57 | source = "../.." 58 | } 59 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 12 | 21 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package me.sithiramunasinghe.flutter.flutter_radio_player_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() 6 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | rootProject.buildDir = "../build" 9 | subprojects { 10 | project.buildDir = "${rootProject.buildDir}/${project.name}" 11 | } 12 | subprojects { 13 | project.evaluationDependsOn(":app") 14 | } 15 | 16 | tasks.register("clean", Delete) { 17 | delete rootProject.buildDir 18 | } 19 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip 6 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | def flutterSdkPath = { 3 | def properties = new Properties() 4 | file("local.properties").withInputStream { properties.load(it) } 5 | def flutterSdkPath = properties.getProperty("flutter.sdk") 6 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 7 | return flutterSdkPath 8 | }() 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id "dev.flutter.flutter-plugin-loader" version "1.0.0" 21 | id "com.android.application" version "7.3.0" apply false 22 | id "org.jetbrains.kotlin.android" version "2.0.0" apply false 23 | } 24 | 25 | include ":app" 26 | -------------------------------------------------------------------------------- /example/images/sample-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/example/images/sample-cover.jpg -------------------------------------------------------------------------------- /example/integration_test/plugin_integration_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter integration test. 2 | // 3 | // Since integration tests run in a full Flutter application, they can interact 4 | // with the host side of a plugin implementation, unlike Dart unit tests. 5 | // 6 | // For more information about Flutter integration tests, please see 7 | // https://docs.flutter.dev/cookbook/testing/integration/introduction 8 | 9 | 10 | import 'package:flutter_test/flutter_test.dart'; 11 | import 'package:integration_test/integration_test.dart'; 12 | 13 | import 'package:flutter_radio_player/flutter_radio_player.dart'; 14 | 15 | void main() { 16 | IntegrationTestWidgetsFlutterBinding.ensureInitialized(); 17 | 18 | testWidgets('getPlatformVersion test', (WidgetTester tester) async { 19 | final FlutterRadioPlayer plugin = FlutterRadioPlayer(); 20 | // final String? version = await plugin.getPlatformVersion(); 21 | // The version string depends on the host platform running the test, so 22 | // just assert that some non-empty string is returned. 23 | // expect(version?.isNotEmpty, true); 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 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 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | - flutter_radio_player (0.0.1): 4 | - Flutter 5 | - SwiftAudioEx (~> 1.1.0) 6 | - integration_test (0.0.1): 7 | - Flutter 8 | - SwiftAudioEx (1.1.0) 9 | 10 | DEPENDENCIES: 11 | - Flutter (from `Flutter`) 12 | - flutter_radio_player (from `.symlinks/plugins/flutter_radio_player/ios`) 13 | - integration_test (from `.symlinks/plugins/integration_test/ios`) 14 | 15 | SPEC REPOS: 16 | trunk: 17 | - SwiftAudioEx 18 | 19 | EXTERNAL SOURCES: 20 | Flutter: 21 | :path: Flutter 22 | flutter_radio_player: 23 | :path: ".symlinks/plugins/flutter_radio_player/ios" 24 | integration_test: 25 | :path: ".symlinks/plugins/integration_test/ios" 26 | 27 | SPEC CHECKSUMS: 28 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 29 | flutter_radio_player: 2c531f0e2b9e636d6cf09704eb63ad040e3913ff 30 | integration_test: ce0a3ffa1de96d1a89ca0ac26fca7ea18a749ef4 31 | SwiftAudioEx: f6aa653770f3a0d3851edaf8d834a30aee4a7646 32 | 33 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 34 | 35 | COCOAPODS: 1.15.2 36 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 54; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 14 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 15 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 16 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 17 | 9A4679FAAD1D73A1234AC2CF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9209C97E188DE5BE21E99378 /* Pods_Runner.framework */; }; 18 | D8D733DB88B857AA3A9223FF /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 848AF776E3C58B90867FC62E /* Pods_RunnerTests.framework */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 97C146E61CF9000F007C117D /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 97C146ED1CF9000F007C117D; 27 | remoteInfo = Runner; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXCopyFilesBuildPhase section */ 32 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 33 | isa = PBXCopyFilesBuildPhase; 34 | buildActionMask = 2147483647; 35 | dstPath = ""; 36 | dstSubfolderSpec = 10; 37 | files = ( 38 | ); 39 | name = "Embed Frameworks"; 40 | runOnlyForDeploymentPostprocessing = 0; 41 | }; 42 | /* End PBXCopyFilesBuildPhase section */ 43 | 44 | /* Begin PBXFileReference section */ 45 | 04ED878B7DB45851DBA6083A /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 46 | 0610AAD1E76A010FBEE40CDA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; 47 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 48 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 49 | 2CFC6F4AF546BC1C79193006 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 50 | 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 51 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 52 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 53 | 742EAD42708CB23F05B80A15 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 54 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 55 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 56 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 57 | 848AF776E3C58B90867FC62E /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 58 | 9209C97E188DE5BE21E99378 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 59 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 60 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 61 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 62 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 63 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 64 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 65 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 66 | A867F295183CBAFC150467E1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 67 | DA6BF8B3ADC13FEC8889E908 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 68 | /* End PBXFileReference section */ 69 | 70 | /* Begin PBXFrameworksBuildPhase section */ 71 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 72 | isa = PBXFrameworksBuildPhase; 73 | buildActionMask = 2147483647; 74 | files = ( 75 | 9A4679FAAD1D73A1234AC2CF /* Pods_Runner.framework in Frameworks */, 76 | ); 77 | runOnlyForDeploymentPostprocessing = 0; 78 | }; 79 | F62A6C3FEF7C0BB1EBBA6A4B /* Frameworks */ = { 80 | isa = PBXFrameworksBuildPhase; 81 | buildActionMask = 2147483647; 82 | files = ( 83 | D8D733DB88B857AA3A9223FF /* Pods_RunnerTests.framework in Frameworks */, 84 | ); 85 | runOnlyForDeploymentPostprocessing = 0; 86 | }; 87 | /* End PBXFrameworksBuildPhase section */ 88 | 89 | /* Begin PBXGroup section */ 90 | 331C8082294A63A400263BE5 /* RunnerTests */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 331C807B294A618700263BE5 /* RunnerTests.swift */, 94 | ); 95 | path = RunnerTests; 96 | sourceTree = ""; 97 | }; 98 | 8B0CF0B5276B3AF4FE421B57 /* Frameworks */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 9209C97E188DE5BE21E99378 /* Pods_Runner.framework */, 102 | 848AF776E3C58B90867FC62E /* Pods_RunnerTests.framework */, 103 | ); 104 | name = Frameworks; 105 | sourceTree = ""; 106 | }; 107 | 9740EEB11CF90186004384FC /* Flutter */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 111 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 112 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 113 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 114 | ); 115 | name = Flutter; 116 | sourceTree = ""; 117 | }; 118 | 97C146E51CF9000F007C117D = { 119 | isa = PBXGroup; 120 | children = ( 121 | 9740EEB11CF90186004384FC /* Flutter */, 122 | 97C146F01CF9000F007C117D /* Runner */, 123 | 97C146EF1CF9000F007C117D /* Products */, 124 | 331C8082294A63A400263BE5 /* RunnerTests */, 125 | F8C359B2B119EB1906FC1845 /* Pods */, 126 | 8B0CF0B5276B3AF4FE421B57 /* Frameworks */, 127 | ); 128 | sourceTree = ""; 129 | }; 130 | 97C146EF1CF9000F007C117D /* Products */ = { 131 | isa = PBXGroup; 132 | children = ( 133 | 97C146EE1CF9000F007C117D /* Runner.app */, 134 | 331C8081294A63A400263BE5 /* RunnerTests.xctest */, 135 | ); 136 | name = Products; 137 | sourceTree = ""; 138 | }; 139 | 97C146F01CF9000F007C117D /* Runner */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 143 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 144 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 145 | 97C147021CF9000F007C117D /* Info.plist */, 146 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 147 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 148 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 149 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 150 | ); 151 | path = Runner; 152 | sourceTree = ""; 153 | }; 154 | F8C359B2B119EB1906FC1845 /* Pods */ = { 155 | isa = PBXGroup; 156 | children = ( 157 | DA6BF8B3ADC13FEC8889E908 /* Pods-Runner.debug.xcconfig */, 158 | 742EAD42708CB23F05B80A15 /* Pods-Runner.release.xcconfig */, 159 | A867F295183CBAFC150467E1 /* Pods-Runner.profile.xcconfig */, 160 | 0610AAD1E76A010FBEE40CDA /* Pods-RunnerTests.debug.xcconfig */, 161 | 2CFC6F4AF546BC1C79193006 /* Pods-RunnerTests.release.xcconfig */, 162 | 04ED878B7DB45851DBA6083A /* Pods-RunnerTests.profile.xcconfig */, 163 | ); 164 | path = Pods; 165 | sourceTree = ""; 166 | }; 167 | /* End PBXGroup section */ 168 | 169 | /* Begin PBXNativeTarget section */ 170 | 331C8080294A63A400263BE5 /* RunnerTests */ = { 171 | isa = PBXNativeTarget; 172 | buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; 173 | buildPhases = ( 174 | FE236EBFA829BA06A1FAF7AA /* [CP] Check Pods Manifest.lock */, 175 | 331C807D294A63A400263BE5 /* Sources */, 176 | 331C807F294A63A400263BE5 /* Resources */, 177 | F62A6C3FEF7C0BB1EBBA6A4B /* Frameworks */, 178 | ); 179 | buildRules = ( 180 | ); 181 | dependencies = ( 182 | 331C8086294A63A400263BE5 /* PBXTargetDependency */, 183 | ); 184 | name = RunnerTests; 185 | productName = RunnerTests; 186 | productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; 187 | productType = "com.apple.product-type.bundle.unit-test"; 188 | }; 189 | 97C146ED1CF9000F007C117D /* Runner */ = { 190 | isa = PBXNativeTarget; 191 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 192 | buildPhases = ( 193 | 09B73321435E5F50BA5C144F /* [CP] Check Pods Manifest.lock */, 194 | 9740EEB61CF901F6004384FC /* Run Script */, 195 | 97C146EA1CF9000F007C117D /* Sources */, 196 | 97C146EB1CF9000F007C117D /* Frameworks */, 197 | 97C146EC1CF9000F007C117D /* Resources */, 198 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 199 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 200 | 2F5A0381B6A2F33BD2D13D5E /* [CP] Embed Pods Frameworks */, 201 | ); 202 | buildRules = ( 203 | ); 204 | dependencies = ( 205 | ); 206 | name = Runner; 207 | productName = Runner; 208 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 209 | productType = "com.apple.product-type.application"; 210 | }; 211 | /* End PBXNativeTarget section */ 212 | 213 | /* Begin PBXProject section */ 214 | 97C146E61CF9000F007C117D /* Project object */ = { 215 | isa = PBXProject; 216 | attributes = { 217 | BuildIndependentTargetsInParallel = YES; 218 | LastUpgradeCheck = 1510; 219 | ORGANIZATIONNAME = ""; 220 | TargetAttributes = { 221 | 331C8080294A63A400263BE5 = { 222 | CreatedOnToolsVersion = 14.0; 223 | TestTargetID = 97C146ED1CF9000F007C117D; 224 | }; 225 | 97C146ED1CF9000F007C117D = { 226 | CreatedOnToolsVersion = 7.3.1; 227 | LastSwiftMigration = 1100; 228 | }; 229 | }; 230 | }; 231 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 232 | compatibilityVersion = "Xcode 9.3"; 233 | developmentRegion = en; 234 | hasScannedForEncodings = 0; 235 | knownRegions = ( 236 | en, 237 | Base, 238 | ); 239 | mainGroup = 97C146E51CF9000F007C117D; 240 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 241 | projectDirPath = ""; 242 | projectRoot = ""; 243 | targets = ( 244 | 97C146ED1CF9000F007C117D /* Runner */, 245 | 331C8080294A63A400263BE5 /* RunnerTests */, 246 | ); 247 | }; 248 | /* End PBXProject section */ 249 | 250 | /* Begin PBXResourcesBuildPhase section */ 251 | 331C807F294A63A400263BE5 /* Resources */ = { 252 | isa = PBXResourcesBuildPhase; 253 | buildActionMask = 2147483647; 254 | files = ( 255 | ); 256 | runOnlyForDeploymentPostprocessing = 0; 257 | }; 258 | 97C146EC1CF9000F007C117D /* Resources */ = { 259 | isa = PBXResourcesBuildPhase; 260 | buildActionMask = 2147483647; 261 | files = ( 262 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 263 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 264 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 265 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 266 | ); 267 | runOnlyForDeploymentPostprocessing = 0; 268 | }; 269 | /* End PBXResourcesBuildPhase section */ 270 | 271 | /* Begin PBXShellScriptBuildPhase section */ 272 | 09B73321435E5F50BA5C144F /* [CP] Check Pods Manifest.lock */ = { 273 | isa = PBXShellScriptBuildPhase; 274 | buildActionMask = 2147483647; 275 | files = ( 276 | ); 277 | inputFileListPaths = ( 278 | ); 279 | inputPaths = ( 280 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 281 | "${PODS_ROOT}/Manifest.lock", 282 | ); 283 | name = "[CP] Check Pods Manifest.lock"; 284 | outputFileListPaths = ( 285 | ); 286 | outputPaths = ( 287 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 288 | ); 289 | runOnlyForDeploymentPostprocessing = 0; 290 | shellPath = /bin/sh; 291 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 292 | showEnvVarsInLog = 0; 293 | }; 294 | 2F5A0381B6A2F33BD2D13D5E /* [CP] Embed Pods Frameworks */ = { 295 | isa = PBXShellScriptBuildPhase; 296 | buildActionMask = 2147483647; 297 | files = ( 298 | ); 299 | inputFileListPaths = ( 300 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", 301 | ); 302 | name = "[CP] Embed Pods Frameworks"; 303 | outputFileListPaths = ( 304 | "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", 305 | ); 306 | runOnlyForDeploymentPostprocessing = 0; 307 | shellPath = /bin/sh; 308 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 309 | showEnvVarsInLog = 0; 310 | }; 311 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 312 | isa = PBXShellScriptBuildPhase; 313 | alwaysOutOfDate = 1; 314 | buildActionMask = 2147483647; 315 | files = ( 316 | ); 317 | inputPaths = ( 318 | "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", 319 | ); 320 | name = "Thin Binary"; 321 | outputPaths = ( 322 | ); 323 | runOnlyForDeploymentPostprocessing = 0; 324 | shellPath = /bin/sh; 325 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; 326 | }; 327 | 9740EEB61CF901F6004384FC /* Run Script */ = { 328 | isa = PBXShellScriptBuildPhase; 329 | alwaysOutOfDate = 1; 330 | buildActionMask = 2147483647; 331 | files = ( 332 | ); 333 | inputPaths = ( 334 | ); 335 | name = "Run Script"; 336 | outputPaths = ( 337 | ); 338 | runOnlyForDeploymentPostprocessing = 0; 339 | shellPath = /bin/sh; 340 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 341 | }; 342 | FE236EBFA829BA06A1FAF7AA /* [CP] Check Pods Manifest.lock */ = { 343 | isa = PBXShellScriptBuildPhase; 344 | buildActionMask = 2147483647; 345 | files = ( 346 | ); 347 | inputFileListPaths = ( 348 | ); 349 | inputPaths = ( 350 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 351 | "${PODS_ROOT}/Manifest.lock", 352 | ); 353 | name = "[CP] Check Pods Manifest.lock"; 354 | outputFileListPaths = ( 355 | ); 356 | outputPaths = ( 357 | "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", 358 | ); 359 | runOnlyForDeploymentPostprocessing = 0; 360 | shellPath = /bin/sh; 361 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 362 | showEnvVarsInLog = 0; 363 | }; 364 | /* End PBXShellScriptBuildPhase section */ 365 | 366 | /* Begin PBXSourcesBuildPhase section */ 367 | 331C807D294A63A400263BE5 /* Sources */ = { 368 | isa = PBXSourcesBuildPhase; 369 | buildActionMask = 2147483647; 370 | files = ( 371 | 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, 372 | ); 373 | runOnlyForDeploymentPostprocessing = 0; 374 | }; 375 | 97C146EA1CF9000F007C117D /* Sources */ = { 376 | isa = PBXSourcesBuildPhase; 377 | buildActionMask = 2147483647; 378 | files = ( 379 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 380 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 381 | ); 382 | runOnlyForDeploymentPostprocessing = 0; 383 | }; 384 | /* End PBXSourcesBuildPhase section */ 385 | 386 | /* Begin PBXTargetDependency section */ 387 | 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { 388 | isa = PBXTargetDependency; 389 | target = 97C146ED1CF9000F007C117D /* Runner */; 390 | targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; 391 | }; 392 | /* End PBXTargetDependency section */ 393 | 394 | /* Begin PBXVariantGroup section */ 395 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 396 | isa = PBXVariantGroup; 397 | children = ( 398 | 97C146FB1CF9000F007C117D /* Base */, 399 | ); 400 | name = Main.storyboard; 401 | sourceTree = ""; 402 | }; 403 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 404 | isa = PBXVariantGroup; 405 | children = ( 406 | 97C147001CF9000F007C117D /* Base */, 407 | ); 408 | name = LaunchScreen.storyboard; 409 | sourceTree = ""; 410 | }; 411 | /* End PBXVariantGroup section */ 412 | 413 | /* Begin XCBuildConfiguration section */ 414 | 249021D3217E4FDB00AE95B9 /* Profile */ = { 415 | isa = XCBuildConfiguration; 416 | buildSettings = { 417 | ALWAYS_SEARCH_USER_PATHS = NO; 418 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 419 | CLANG_ANALYZER_NONNULL = YES; 420 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 421 | CLANG_CXX_LIBRARY = "libc++"; 422 | CLANG_ENABLE_MODULES = YES; 423 | CLANG_ENABLE_OBJC_ARC = YES; 424 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 425 | CLANG_WARN_BOOL_CONVERSION = YES; 426 | CLANG_WARN_COMMA = YES; 427 | CLANG_WARN_CONSTANT_CONVERSION = YES; 428 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 429 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 430 | CLANG_WARN_EMPTY_BODY = YES; 431 | CLANG_WARN_ENUM_CONVERSION = YES; 432 | CLANG_WARN_INFINITE_RECURSION = YES; 433 | CLANG_WARN_INT_CONVERSION = YES; 434 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 435 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 436 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 437 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 438 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 439 | CLANG_WARN_STRICT_PROTOTYPES = YES; 440 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 441 | CLANG_WARN_UNREACHABLE_CODE = YES; 442 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 443 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 444 | COPY_PHASE_STRIP = NO; 445 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 446 | ENABLE_NS_ASSERTIONS = NO; 447 | ENABLE_STRICT_OBJC_MSGSEND = YES; 448 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 449 | GCC_C_LANGUAGE_STANDARD = gnu99; 450 | GCC_NO_COMMON_BLOCKS = YES; 451 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 452 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 453 | GCC_WARN_UNDECLARED_SELECTOR = YES; 454 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 455 | GCC_WARN_UNUSED_FUNCTION = YES; 456 | GCC_WARN_UNUSED_VARIABLE = YES; 457 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 458 | MTL_ENABLE_DEBUG_INFO = NO; 459 | SDKROOT = iphoneos; 460 | SUPPORTED_PLATFORMS = iphoneos; 461 | TARGETED_DEVICE_FAMILY = "1,2"; 462 | VALIDATE_PRODUCT = YES; 463 | }; 464 | name = Profile; 465 | }; 466 | 249021D4217E4FDB00AE95B9 /* Profile */ = { 467 | isa = XCBuildConfiguration; 468 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 469 | buildSettings = { 470 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 471 | CLANG_ENABLE_MODULES = YES; 472 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 473 | DEVELOPMENT_TEAM = 5H82LVNJTQ; 474 | ENABLE_BITCODE = NO; 475 | INFOPLIST_FILE = Runner/Info.plist; 476 | LD_RUNPATH_SEARCH_PATHS = ( 477 | "$(inherited)", 478 | "@executable_path/Frameworks", 479 | ); 480 | PRODUCT_BUNDLE_IDENTIFIER = me.sithiramunasinghe.flutter.flutterRadioPlayerExample; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 483 | SWIFT_VERSION = 5.0; 484 | VERSIONING_SYSTEM = "apple-generic"; 485 | }; 486 | name = Profile; 487 | }; 488 | 331C8088294A63A400263BE5 /* Debug */ = { 489 | isa = XCBuildConfiguration; 490 | baseConfigurationReference = 0610AAD1E76A010FBEE40CDA /* Pods-RunnerTests.debug.xcconfig */; 491 | buildSettings = { 492 | BUNDLE_LOADER = "$(TEST_HOST)"; 493 | CODE_SIGN_STYLE = Automatic; 494 | CURRENT_PROJECT_VERSION = 1; 495 | GENERATE_INFOPLIST_FILE = YES; 496 | MARKETING_VERSION = 1.0; 497 | PRODUCT_BUNDLE_IDENTIFIER = me.sithiramunasinghe.flutter.flutterRadioPlayerExample.RunnerTests; 498 | PRODUCT_NAME = "$(TARGET_NAME)"; 499 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 500 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 501 | SWIFT_VERSION = 5.0; 502 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 503 | }; 504 | name = Debug; 505 | }; 506 | 331C8089294A63A400263BE5 /* Release */ = { 507 | isa = XCBuildConfiguration; 508 | baseConfigurationReference = 2CFC6F4AF546BC1C79193006 /* Pods-RunnerTests.release.xcconfig */; 509 | buildSettings = { 510 | BUNDLE_LOADER = "$(TEST_HOST)"; 511 | CODE_SIGN_STYLE = Automatic; 512 | CURRENT_PROJECT_VERSION = 1; 513 | GENERATE_INFOPLIST_FILE = YES; 514 | MARKETING_VERSION = 1.0; 515 | PRODUCT_BUNDLE_IDENTIFIER = me.sithiramunasinghe.flutter.flutterRadioPlayerExample.RunnerTests; 516 | PRODUCT_NAME = "$(TARGET_NAME)"; 517 | SWIFT_VERSION = 5.0; 518 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 519 | }; 520 | name = Release; 521 | }; 522 | 331C808A294A63A400263BE5 /* Profile */ = { 523 | isa = XCBuildConfiguration; 524 | baseConfigurationReference = 04ED878B7DB45851DBA6083A /* Pods-RunnerTests.profile.xcconfig */; 525 | buildSettings = { 526 | BUNDLE_LOADER = "$(TEST_HOST)"; 527 | CODE_SIGN_STYLE = Automatic; 528 | CURRENT_PROJECT_VERSION = 1; 529 | GENERATE_INFOPLIST_FILE = YES; 530 | MARKETING_VERSION = 1.0; 531 | PRODUCT_BUNDLE_IDENTIFIER = me.sithiramunasinghe.flutter.flutterRadioPlayerExample.RunnerTests; 532 | PRODUCT_NAME = "$(TARGET_NAME)"; 533 | SWIFT_VERSION = 5.0; 534 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; 535 | }; 536 | name = Profile; 537 | }; 538 | 97C147031CF9000F007C117D /* Debug */ = { 539 | isa = XCBuildConfiguration; 540 | buildSettings = { 541 | ALWAYS_SEARCH_USER_PATHS = NO; 542 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 543 | CLANG_ANALYZER_NONNULL = YES; 544 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 545 | CLANG_CXX_LIBRARY = "libc++"; 546 | CLANG_ENABLE_MODULES = YES; 547 | CLANG_ENABLE_OBJC_ARC = YES; 548 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 549 | CLANG_WARN_BOOL_CONVERSION = YES; 550 | CLANG_WARN_COMMA = YES; 551 | CLANG_WARN_CONSTANT_CONVERSION = YES; 552 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 553 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 554 | CLANG_WARN_EMPTY_BODY = YES; 555 | CLANG_WARN_ENUM_CONVERSION = YES; 556 | CLANG_WARN_INFINITE_RECURSION = YES; 557 | CLANG_WARN_INT_CONVERSION = YES; 558 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 559 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 560 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 561 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 562 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 563 | CLANG_WARN_STRICT_PROTOTYPES = YES; 564 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 565 | CLANG_WARN_UNREACHABLE_CODE = YES; 566 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 567 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 568 | COPY_PHASE_STRIP = NO; 569 | DEBUG_INFORMATION_FORMAT = dwarf; 570 | ENABLE_STRICT_OBJC_MSGSEND = YES; 571 | ENABLE_TESTABILITY = YES; 572 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 573 | GCC_C_LANGUAGE_STANDARD = gnu99; 574 | GCC_DYNAMIC_NO_PIC = NO; 575 | GCC_NO_COMMON_BLOCKS = YES; 576 | GCC_OPTIMIZATION_LEVEL = 0; 577 | GCC_PREPROCESSOR_DEFINITIONS = ( 578 | "DEBUG=1", 579 | "$(inherited)", 580 | ); 581 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 582 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 583 | GCC_WARN_UNDECLARED_SELECTOR = YES; 584 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 585 | GCC_WARN_UNUSED_FUNCTION = YES; 586 | GCC_WARN_UNUSED_VARIABLE = YES; 587 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 588 | MTL_ENABLE_DEBUG_INFO = YES; 589 | ONLY_ACTIVE_ARCH = YES; 590 | SDKROOT = iphoneos; 591 | TARGETED_DEVICE_FAMILY = "1,2"; 592 | }; 593 | name = Debug; 594 | }; 595 | 97C147041CF9000F007C117D /* Release */ = { 596 | isa = XCBuildConfiguration; 597 | buildSettings = { 598 | ALWAYS_SEARCH_USER_PATHS = NO; 599 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 600 | CLANG_ANALYZER_NONNULL = YES; 601 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 602 | CLANG_CXX_LIBRARY = "libc++"; 603 | CLANG_ENABLE_MODULES = YES; 604 | CLANG_ENABLE_OBJC_ARC = YES; 605 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 606 | CLANG_WARN_BOOL_CONVERSION = YES; 607 | CLANG_WARN_COMMA = YES; 608 | CLANG_WARN_CONSTANT_CONVERSION = YES; 609 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 610 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 611 | CLANG_WARN_EMPTY_BODY = YES; 612 | CLANG_WARN_ENUM_CONVERSION = YES; 613 | CLANG_WARN_INFINITE_RECURSION = YES; 614 | CLANG_WARN_INT_CONVERSION = YES; 615 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 616 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 617 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 618 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 619 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 620 | CLANG_WARN_STRICT_PROTOTYPES = YES; 621 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 622 | CLANG_WARN_UNREACHABLE_CODE = YES; 623 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 624 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 625 | COPY_PHASE_STRIP = NO; 626 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 627 | ENABLE_NS_ASSERTIONS = NO; 628 | ENABLE_STRICT_OBJC_MSGSEND = YES; 629 | ENABLE_USER_SCRIPT_SANDBOXING = NO; 630 | GCC_C_LANGUAGE_STANDARD = gnu99; 631 | GCC_NO_COMMON_BLOCKS = YES; 632 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 633 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 634 | GCC_WARN_UNDECLARED_SELECTOR = YES; 635 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 636 | GCC_WARN_UNUSED_FUNCTION = YES; 637 | GCC_WARN_UNUSED_VARIABLE = YES; 638 | IPHONEOS_DEPLOYMENT_TARGET = 12.0; 639 | MTL_ENABLE_DEBUG_INFO = NO; 640 | SDKROOT = iphoneos; 641 | SUPPORTED_PLATFORMS = iphoneos; 642 | SWIFT_COMPILATION_MODE = wholemodule; 643 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 644 | TARGETED_DEVICE_FAMILY = "1,2"; 645 | VALIDATE_PRODUCT = YES; 646 | }; 647 | name = Release; 648 | }; 649 | 97C147061CF9000F007C117D /* Debug */ = { 650 | isa = XCBuildConfiguration; 651 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 652 | buildSettings = { 653 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 654 | CLANG_ENABLE_MODULES = YES; 655 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 656 | DEVELOPMENT_TEAM = 5H82LVNJTQ; 657 | ENABLE_BITCODE = NO; 658 | INFOPLIST_FILE = Runner/Info.plist; 659 | LD_RUNPATH_SEARCH_PATHS = ( 660 | "$(inherited)", 661 | "@executable_path/Frameworks", 662 | ); 663 | PRODUCT_BUNDLE_IDENTIFIER = me.sithiramunasinghe.flutter.flutterRadioPlayerExample; 664 | PRODUCT_NAME = "$(TARGET_NAME)"; 665 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 666 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 667 | SWIFT_VERSION = 5.0; 668 | VERSIONING_SYSTEM = "apple-generic"; 669 | }; 670 | name = Debug; 671 | }; 672 | 97C147071CF9000F007C117D /* Release */ = { 673 | isa = XCBuildConfiguration; 674 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 675 | buildSettings = { 676 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 677 | CLANG_ENABLE_MODULES = YES; 678 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; 679 | DEVELOPMENT_TEAM = 5H82LVNJTQ; 680 | ENABLE_BITCODE = NO; 681 | INFOPLIST_FILE = Runner/Info.plist; 682 | LD_RUNPATH_SEARCH_PATHS = ( 683 | "$(inherited)", 684 | "@executable_path/Frameworks", 685 | ); 686 | PRODUCT_BUNDLE_IDENTIFIER = me.sithiramunasinghe.flutter.flutterRadioPlayerExample; 687 | PRODUCT_NAME = "$(TARGET_NAME)"; 688 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 689 | SWIFT_VERSION = 5.0; 690 | VERSIONING_SYSTEM = "apple-generic"; 691 | }; 692 | name = Release; 693 | }; 694 | /* End XCBuildConfiguration section */ 695 | 696 | /* Begin XCConfigurationList section */ 697 | 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { 698 | isa = XCConfigurationList; 699 | buildConfigurations = ( 700 | 331C8088294A63A400263BE5 /* Debug */, 701 | 331C8089294A63A400263BE5 /* Release */, 702 | 331C808A294A63A400263BE5 /* Profile */, 703 | ); 704 | defaultConfigurationIsVisible = 0; 705 | defaultConfigurationName = Release; 706 | }; 707 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 708 | isa = XCConfigurationList; 709 | buildConfigurations = ( 710 | 97C147031CF9000F007C117D /* Debug */, 711 | 97C147041CF9000F007C117D /* Release */, 712 | 249021D3217E4FDB00AE95B9 /* Profile */, 713 | ); 714 | defaultConfigurationIsVisible = 0; 715 | defaultConfigurationName = Release; 716 | }; 717 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 718 | isa = XCConfigurationList; 719 | buildConfigurations = ( 720 | 97C147061CF9000F007C117D /* Debug */, 721 | 97C147071CF9000F007C117D /* Release */, 722 | 249021D4217E4FDB00AE95B9 /* Profile */, 723 | ); 724 | defaultConfigurationIsVisible = 0; 725 | defaultConfigurationName = Release; 726 | }; 727 | /* End XCConfigurationList section */ 728 | }; 729 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 730 | } 731 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/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 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Flutter Radio Player 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | flutter_radio_player_example 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UIApplicationSupportsIndirectInputEvents 30 | 31 | UIBackgroundModes 32 | 33 | audio 34 | 35 | UILaunchStoryboardName 36 | LaunchScreen 37 | UIMainStoryboardFile 38 | Main 39 | UISupportedInterfaceOrientations 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationLandscapeLeft 43 | UIInterfaceOrientationLandscapeRight 44 | 45 | UISupportedInterfaceOrientations~ipad 46 | 47 | UIInterfaceOrientationPortrait 48 | UIInterfaceOrientationPortraitUpsideDown 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | @testable import flutter_radio_player 6 | 7 | // This demonstrates a simple unit test of the Swift portion of this plugin's implementation. 8 | // 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | 11 | class RunnerTests: XCTestCase { 12 | 13 | func testGetPlatformVersion() { 14 | let plugin = FlutterRadioPlayerPlugin() 15 | 16 | let call = FlutterMethodCall(methodName: "getPlatformVersion", arguments: []) 17 | 18 | let resultExpectation = expectation(description: "result block must be called.") 19 | plugin.handle(call) { result in 20 | XCTAssertEqual(result as! String, "iOS " + UIDevice.current.systemVersion) 21 | resultExpectation.fulfill() 22 | } 23 | waitForExpectations(timeout: 1) 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_radio_player/flutter_radio_player.dart'; 3 | 4 | void main() { 5 | WidgetsFlutterBinding.ensureInitialized(); 6 | runApp(const MyApp()); 7 | } 8 | 9 | class MyApp extends StatefulWidget { 10 | const MyApp({super.key}); 11 | 12 | @override 13 | State createState() => _MyAppState(); 14 | } 15 | 16 | class _MyAppState extends State { 17 | final _flutterRadioPlayerPlugin = FlutterRadioPlayer(); 18 | double volume = 0; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | _flutterRadioPlayerPlugin.initialize( 24 | [ 25 | { 26 | "url": "https://s2-webradio.antenne.de/chillout?icy=https", 27 | }, 28 | { 29 | "title": "SunFM - Sri Lanka", 30 | "artwork": "images/sample-cover.jpg", 31 | "url": 32 | "https://radio.lotustechnologieslk.net:2020/stream/sunfmgarden?icy=https", 33 | }, 34 | {"url": "http://stream.riverradio.com:8000/wcvofm.aac"} 35 | ], 36 | true, 37 | ); 38 | } 39 | 40 | @override 41 | Widget build(BuildContext context) { 42 | return MaterialApp( 43 | home: Scaffold( 44 | appBar: AppBar( 45 | title: const Text('Plugin example app'), 46 | ), 47 | body: Center( 48 | child: Column( 49 | children: [ 50 | Row( 51 | mainAxisAlignment: MainAxisAlignment.center, 52 | children: [ 53 | IconButton( 54 | onPressed: () async { 55 | await _flutterRadioPlayerPlugin.prevSource(); 56 | }, 57 | icon: const Icon(Icons.skip_previous_sharp), 58 | ), 59 | StreamBuilder( 60 | stream: _flutterRadioPlayerPlugin.getPlaybackStream(), 61 | builder: (context, snapshot) { 62 | if (snapshot.hasData) { 63 | return IconButton( 64 | onPressed: () { 65 | if (snapshot.data!) { 66 | _flutterRadioPlayerPlugin.pause(); 67 | } else { 68 | _flutterRadioPlayerPlugin.play(); 69 | } 70 | }, 71 | icon: !snapshot.data! 72 | ? Icon(Icons.play_arrow) 73 | : Icon(Icons.pause), 74 | iconSize: 50.0, 75 | ); 76 | } 77 | return const Text("Player unavailable"); 78 | }, 79 | ), 80 | IconButton( 81 | onPressed: () async { 82 | await _flutterRadioPlayerPlugin.nextSource(); 83 | }, 84 | icon: const Icon(Icons.skip_next_sharp), 85 | ), 86 | ], 87 | ), 88 | StreamBuilder( 89 | stream: _flutterRadioPlayerPlugin.getNowPlayingStream(), 90 | builder: (context, snapshot) { 91 | if (snapshot.hasData && snapshot.data?.title != null) { 92 | return Text("Now playing : ${snapshot.data?.title}"); 93 | } 94 | return Text("N/A"); 95 | }, 96 | ), 97 | StreamBuilder( 98 | stream: 99 | _flutterRadioPlayerPlugin.getDeviceVolumeChangedStream(), 100 | builder: (context, snapshot) { 101 | if (snapshot.hasData) { 102 | return Text( 103 | "Volume = ${snapshot.data?.volume.floor()} and IsMuted = ${snapshot.data?.isMuted}"); 104 | } 105 | return Text("No Vol data"); 106 | }, 107 | ), 108 | FutureBuilder( 109 | future: _flutterRadioPlayerPlugin.getVolume(), 110 | builder: (context, snapshot) { 111 | if (snapshot.hasData) { 112 | return Slider( 113 | value: snapshot.data ?? 0, 114 | min: 0, 115 | max: 1, 116 | onChanged: (value) { 117 | setState(() { 118 | volume = value; 119 | _flutterRadioPlayerPlugin.setVolume(volume); 120 | }); 121 | }, 122 | ); 123 | } 124 | return Container(); 125 | }, 126 | ) 127 | ], 128 | ), 129 | ), 130 | ), 131 | ); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /example/pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | cupertino_icons: 45 | dependency: "direct main" 46 | description: 47 | name: cupertino_icons 48 | sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.0.8" 52 | fake_async: 53 | dependency: transitive 54 | description: 55 | name: fake_async 56 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 57 | url: "https://pub.dev" 58 | source: hosted 59 | version: "1.3.1" 60 | file: 61 | dependency: transitive 62 | description: 63 | name: file 64 | sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" 65 | url: "https://pub.dev" 66 | source: hosted 67 | version: "7.0.0" 68 | flutter: 69 | dependency: "direct main" 70 | description: flutter 71 | source: sdk 72 | version: "0.0.0" 73 | flutter_driver: 74 | dependency: transitive 75 | description: flutter 76 | source: sdk 77 | version: "0.0.0" 78 | flutter_lints: 79 | dependency: "direct dev" 80 | description: 81 | name: flutter_lints 82 | sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "3.0.2" 86 | flutter_radio_player: 87 | dependency: "direct main" 88 | description: 89 | path: ".." 90 | relative: true 91 | source: path 92 | version: "0.0.1" 93 | flutter_test: 94 | dependency: "direct dev" 95 | description: flutter 96 | source: sdk 97 | version: "0.0.0" 98 | fuchsia_remote_debug_protocol: 99 | dependency: transitive 100 | description: flutter 101 | source: sdk 102 | version: "0.0.0" 103 | integration_test: 104 | dependency: "direct dev" 105 | description: flutter 106 | source: sdk 107 | version: "0.0.0" 108 | leak_tracker: 109 | dependency: transitive 110 | description: 111 | name: leak_tracker 112 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" 113 | url: "https://pub.dev" 114 | source: hosted 115 | version: "10.0.5" 116 | leak_tracker_flutter_testing: 117 | dependency: transitive 118 | description: 119 | name: leak_tracker_flutter_testing 120 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" 121 | url: "https://pub.dev" 122 | source: hosted 123 | version: "3.0.5" 124 | leak_tracker_testing: 125 | dependency: transitive 126 | description: 127 | name: leak_tracker_testing 128 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 129 | url: "https://pub.dev" 130 | source: hosted 131 | version: "3.0.1" 132 | lints: 133 | dependency: transitive 134 | description: 135 | name: lints 136 | sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 137 | url: "https://pub.dev" 138 | source: hosted 139 | version: "3.0.0" 140 | matcher: 141 | dependency: transitive 142 | description: 143 | name: matcher 144 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 145 | url: "https://pub.dev" 146 | source: hosted 147 | version: "0.12.16+1" 148 | material_color_utilities: 149 | dependency: transitive 150 | description: 151 | name: material_color_utilities 152 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 153 | url: "https://pub.dev" 154 | source: hosted 155 | version: "0.11.1" 156 | meta: 157 | dependency: transitive 158 | description: 159 | name: meta 160 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 161 | url: "https://pub.dev" 162 | source: hosted 163 | version: "1.15.0" 164 | path: 165 | dependency: transitive 166 | description: 167 | name: path 168 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 169 | url: "https://pub.dev" 170 | source: hosted 171 | version: "1.9.0" 172 | platform: 173 | dependency: transitive 174 | description: 175 | name: platform 176 | sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" 177 | url: "https://pub.dev" 178 | source: hosted 179 | version: "3.1.5" 180 | plugin_platform_interface: 181 | dependency: transitive 182 | description: 183 | name: plugin_platform_interface 184 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 185 | url: "https://pub.dev" 186 | source: hosted 187 | version: "2.1.8" 188 | process: 189 | dependency: transitive 190 | description: 191 | name: process 192 | sha256: "21e54fd2faf1b5bdd5102afd25012184a6793927648ea81eea80552ac9405b32" 193 | url: "https://pub.dev" 194 | source: hosted 195 | version: "5.0.2" 196 | sky_engine: 197 | dependency: transitive 198 | description: flutter 199 | source: sdk 200 | version: "0.0.99" 201 | source_span: 202 | dependency: transitive 203 | description: 204 | name: source_span 205 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 206 | url: "https://pub.dev" 207 | source: hosted 208 | version: "1.10.0" 209 | stack_trace: 210 | dependency: transitive 211 | description: 212 | name: stack_trace 213 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 214 | url: "https://pub.dev" 215 | source: hosted 216 | version: "1.11.1" 217 | stream_channel: 218 | dependency: transitive 219 | description: 220 | name: stream_channel 221 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 222 | url: "https://pub.dev" 223 | source: hosted 224 | version: "2.1.2" 225 | string_scanner: 226 | dependency: transitive 227 | description: 228 | name: string_scanner 229 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 230 | url: "https://pub.dev" 231 | source: hosted 232 | version: "1.2.0" 233 | sync_http: 234 | dependency: transitive 235 | description: 236 | name: sync_http 237 | sha256: "7f0cd72eca000d2e026bcd6f990b81d0ca06022ef4e32fb257b30d3d1014a961" 238 | url: "https://pub.dev" 239 | source: hosted 240 | version: "0.3.1" 241 | term_glyph: 242 | dependency: transitive 243 | description: 244 | name: term_glyph 245 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 246 | url: "https://pub.dev" 247 | source: hosted 248 | version: "1.2.1" 249 | test_api: 250 | dependency: transitive 251 | description: 252 | name: test_api 253 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" 254 | url: "https://pub.dev" 255 | source: hosted 256 | version: "0.7.2" 257 | vector_math: 258 | dependency: transitive 259 | description: 260 | name: vector_math 261 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 262 | url: "https://pub.dev" 263 | source: hosted 264 | version: "2.1.4" 265 | vm_service: 266 | dependency: transitive 267 | description: 268 | name: vm_service 269 | sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" 270 | url: "https://pub.dev" 271 | source: hosted 272 | version: "14.2.5" 273 | webdriver: 274 | dependency: transitive 275 | description: 276 | name: webdriver 277 | sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" 278 | url: "https://pub.dev" 279 | source: hosted 280 | version: "3.0.3" 281 | sdks: 282 | dart: ">=3.4.3 <4.0.0" 283 | flutter: ">=3.18.0-18.0.pre.54" 284 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_radio_player_example 2 | description: "Demonstrates how to use the flutter_radio_player plugin." 3 | # The following line prevents the package from being accidentally published to 4 | # pub.dev using `flutter pub publish`. This is preferred for private packages. 5 | publish_to: 'none' # Remove this line if you wish to publish to pub.dev 6 | 7 | environment: 8 | sdk: '>=3.4.3 <4.0.0' 9 | 10 | # Dependencies specify other packages that your package needs in order to work. 11 | # To automatically upgrade your package dependencies to the latest versions 12 | # consider running `flutter pub upgrade --major-versions`. Alternatively, 13 | # dependencies can be manually updated by changing the version numbers below to 14 | # the latest version available on pub.dev. To see which dependencies have newer 15 | # versions available, run `flutter pub outdated`. 16 | dependencies: 17 | flutter: 18 | sdk: flutter 19 | 20 | flutter_radio_player: 21 | # When depending on this package from a real application you should use: 22 | # flutter_radio_player: ^x.y.z 23 | # See https://dart.dev/tools/pub/dependencies#version-constraints 24 | # The example app is bundled with the plugin so we use a path dependency on 25 | # the parent directory to use the current plugin's version. 26 | path: ../ 27 | 28 | # The following adds the Cupertino Icons font to your application. 29 | # Use with the CupertinoIcons class for iOS style icons. 30 | cupertino_icons: ^1.0.6 31 | 32 | dev_dependencies: 33 | integration_test: 34 | sdk: flutter 35 | flutter_test: 36 | sdk: flutter 37 | 38 | # The "flutter_lints" package below contains a set of recommended lints to 39 | # encourage good coding practices. The lint set provided by the package is 40 | # activated in the `analysis_options.yaml` file located at the root of your 41 | # package. See that file for information about deactivating specific lint 42 | # rules and activating additional ones. 43 | flutter_lints: ^3.0.0 44 | 45 | # For information on the generic Dart part of this file, see the 46 | # following page: https://dart.dev/tools/pub/pubspec 47 | 48 | # The following section is specific to Flutter packages. 49 | flutter: 50 | 51 | # The following line ensures that the Material Icons font is 52 | # included with your application, so that you can use the icons in 53 | # the material Icons class. 54 | uses-material-design: true 55 | 56 | assets: 57 | - images/sample-cover.jpg 58 | 59 | # To add assets to your application, add an assets section, like this: 60 | # assets: 61 | # - images/a_dot_burr.jpeg 62 | # - images/a_dot_ham.jpeg 63 | 64 | # An image asset can refer to one or more resolution-specific "variants", see 65 | # https://flutter.dev/assets-and-images/#resolution-aware 66 | 67 | # For details regarding adding assets from package dependencies, see 68 | # https://flutter.dev/assets-and-images/#from-packages 69 | 70 | # To add custom fonts to your application, add a fonts section here, 71 | # in this "flutter" section. Each entry in this list should have a 72 | # "family" key with the font family name, and a "fonts" key with a 73 | # list giving the asset and other descriptors for the font. For 74 | # example: 75 | # fonts: 76 | # - family: Schyler 77 | # fonts: 78 | # - asset: fonts/Schyler-Regular.ttf 79 | # - asset: fonts/Schyler-Italic.ttf 80 | # style: italic 81 | # - family: Trajan Pro 82 | # fonts: 83 | # - asset: fonts/TrajanPro.ttf 84 | # - asset: fonts/TrajanPro_Bold.ttf 85 | # weight: 700 86 | # 87 | # For details regarding fonts from package dependencies, 88 | # see https://flutter.dev/custom-fonts/#from-packages 89 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility in the flutter_test package. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:flutter/material.dart'; 9 | import 'package:flutter_test/flutter_test.dart'; 10 | 11 | import 'package:flutter_radio_player_example/main.dart'; 12 | 13 | void main() { 14 | testWidgets('Verify Platform version', (WidgetTester tester) async { 15 | // Build our app and trigger a frame. 16 | await tester.pumpWidget(const MyApp()); 17 | 18 | // Verify that platform version is retrieved. 19 | expect( 20 | find.byWidgetPredicate( 21 | (Widget widget) => widget is Text && 22 | widget.data!.startsWith('Running on:'), 23 | ), 24 | findsOneWidget, 25 | ); 26 | }); 27 | } 28 | -------------------------------------------------------------------------------- /flutter_radio_player_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/flutter_radio_player_logo.png -------------------------------------------------------------------------------- /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 | /Flutter/ephemeral/ 38 | /Flutter/flutter_export_environment.sh 39 | -------------------------------------------------------------------------------- /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Sithira/FlutterRadioPlayer/e379b3eadb531d344e79145e28fcb658d086b5ac/ios/Assets/.gitkeep -------------------------------------------------------------------------------- /ios/Classes/FlutterRadioPlayerPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | 4 | public class FlutterRadioPlayerPlugin: NSObject, FlutterPlugin { 5 | let player = PlaybackService.instance 6 | static var registrar: FlutterPluginRegistrar? = nil 7 | 8 | public static func register(with registrar: FlutterPluginRegistrar) { 9 | let channel = FlutterMethodChannel(name: "flutter_radio_player", binaryMessenger: registrar.messenger()) 10 | let instance = FlutterRadioPlayerPlugin() 11 | 12 | initalizeChannels(registrar: registrar) 13 | 14 | registrar.addMethodCallDelegate(instance, channel: channel) 15 | FlutterRadioPlayerPlugin.registrar = registrar 16 | } 17 | 18 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 19 | switch call.method { 20 | case "initialize": 21 | if let args = call.arguments as? Dictionary { 22 | let sourcesAsString = args["sources"] as? String 23 | let isPlayWhenReady = args["playWhenReady"] as? Bool 24 | if let sources = sourcesAsString?.data(using: .utf8) { 25 | let decoder = JSONDecoder() 26 | do { 27 | let sources = try? decoder.decode([FlutterRadioPlayerSource].self, from: sources) 28 | player.intialize(sources: sources!, playWhenReady: isPlayWhenReady!) 29 | } 30 | } 31 | } 32 | break 33 | case "getVolume": 34 | result(player.getVolume()) 35 | break 36 | case "play": 37 | player.play() 38 | break 39 | case "pause": 40 | player.pause() 41 | break 42 | case "nextSource": 43 | player.nextSource() 44 | break 45 | case "prevSource": 46 | player.prevSource() 47 | break 48 | case "changeVolume": 49 | if let args = call.arguments as? Dictionary { 50 | if let volume = args["volume"] as? Double { 51 | player.setVolume(volume: Float(volume)) 52 | break 53 | } 54 | } 55 | case "sourceAtIndex": 56 | if let args = call.arguments as? Dictionary { 57 | if let sourceIndex = args["index"] as? Int { 58 | player.jumpToItem(index: sourceIndex) 59 | break 60 | } 61 | } 62 | default: 63 | result(FlutterMethodNotImplemented) 64 | } 65 | } 66 | 67 | static private func initalizeChannels(registrar: FlutterPluginRegistrar) { 68 | let playbackStatusStream = FlutterEventChannel(name: "flutter_radio_player/playback_status", binaryMessenger: registrar.messenger()) 69 | playbackStatusStream.setStreamHandler(PlaybackStatusEventStreamHandler()) 70 | 71 | let nowPlayingInfoStream = FlutterEventChannel(name: "flutter_radio_player/now_playing_info", binaryMessenger: registrar.messenger()) 72 | nowPlayingInfoStream.setStreamHandler(NowPlayingInfoStreamHandler()) 73 | 74 | let deviceVolumeControlStream = FlutterEventChannel(name: "flutter_radio_player/volume_control", binaryMessenger: registrar.messenger()) 75 | deviceVolumeControlStream.setStreamHandler(DeviceVolumeStreamHandler()) 76 | } 77 | } 78 | 79 | class PlaybackStatusEventStreamHandler: NSObject, FlutterStreamHandler { 80 | func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 81 | PlaybackService.instance.playBackEventSink = events 82 | return nil 83 | } 84 | 85 | func onCancel(withArguments arguments: Any?) -> FlutterError? { 86 | PlaybackService.instance.playBackEventSink = nil 87 | return nil 88 | } 89 | } 90 | 91 | 92 | class NowPlayingInfoStreamHandler: NSObject, FlutterStreamHandler { 93 | func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 94 | PlaybackService.instance.nowPlayingEventSink = events 95 | return nil 96 | } 97 | 98 | func onCancel(withArguments arguments: Any?) -> FlutterError? { 99 | PlaybackService.instance.nowPlayingEventSink = nil 100 | return nil 101 | } 102 | } 103 | 104 | class DeviceVolumeStreamHandler: NSObject, FlutterStreamHandler { 105 | func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? { 106 | PlaybackService.instance.playbackVolumeControl = events 107 | return nil 108 | } 109 | 110 | func onCancel(withArguments arguments: Any?) -> FlutterError? { 111 | PlaybackService.instance.playbackVolumeControl = nil 112 | return nil 113 | } 114 | } 115 | 116 | -------------------------------------------------------------------------------- /ios/Classes/core/EventChannelSink.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EventChannelSink.swift 3 | // flutter_radio_player 4 | // 5 | // Created by Sithira Munasinghe on 2024-07-23. 6 | // 7 | 8 | import Foundation 9 | import Flutter 10 | 11 | class EventChannelSink { 12 | static let instance = EventChannelSink() 13 | var playbackEventChannel: FlutterEventChannel?; 14 | var nowPlayingEventChannel: FlutterEventChannel?; 15 | var playbackVolumeChannel: FlutterEventChannel?; 16 | 17 | private init() { 18 | 19 | } 20 | 21 | 22 | } 23 | -------------------------------------------------------------------------------- /ios/Classes/core/PlaybackService.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PlaybackService.swift 3 | // flutter_radio_player 4 | // 5 | // Created by Sithira Munasinghe on 2024-07-23. 6 | // 7 | 8 | import Foundation 9 | import SwiftAudioEx 10 | import AVFoundation 11 | import MediaPlayer 12 | import Flutter 13 | 14 | class PlaybackService: NSObject { 15 | static let instance = PlaybackService() 16 | private var player: QueuedAudioPlayer? 17 | var playBackEventSink: FlutterEventSink? = nil 18 | var nowPlayingEventSink: FlutterEventSink? = nil 19 | var playbackVolumeControl: FlutterEventSink? = nil 20 | let audioSession = AVAudioSession.sharedInstance() 21 | 22 | private override init() { 23 | super.init() 24 | player = QueuedAudioPlayer() 25 | player?.automaticallyUpdateNowPlayingInfo = true 26 | player?.remoteCommands = [ 27 | .play, 28 | .pause, 29 | .next, 30 | .previous 31 | ] 32 | 33 | player?.nowPlayingInfoController.set(keyValue: NowPlayingInfoProperty.isLiveStream(true)) 34 | player?.volume = 0.5 35 | 36 | do { 37 | try AudioSessionController.shared.set(category: .playback) 38 | try AudioSessionController.shared.activateSession() 39 | } catch { 40 | 41 | } 42 | 43 | player?.event.stateChange.addListener(self, handlePlayerStateChange) 44 | player?.event.receiveTimedMetadata.addListener(self, handleNowPlayingChanges) 45 | player?.event.receiveCommonMetadata.addListener(self, handleCommonChanges) 46 | 47 | UIApplication.shared.beginReceivingRemoteControlEvents() 48 | } 49 | 50 | 51 | private func handleCommonChanges(data: Any) { 52 | print(data) 53 | } 54 | 55 | func intialize(sources: Array, playWhenReady: Bool) { 56 | for source in sources { 57 | let mediaSource = DefaultAudioItem(audioUrl: source.url, sourceType: .stream) 58 | if source.title == nil { 59 | mediaSource.artist = getAppName() 60 | } else { 61 | mediaSource.title = source.title 62 | mediaSource.artist = getAppName() 63 | } 64 | if source.artwork != nil { 65 | mediaSource.artwork = loadImageFromFlutterAssets(assetName: source.artwork!, registrar: FlutterRadioPlayerPlugin.registrar!) 66 | } 67 | player?.add(item: mediaSource, playWhenReady: playWhenReady) 68 | } 69 | } 70 | 71 | func play() { 72 | player?.play() 73 | } 74 | 75 | func pause() { 76 | if player?.playerState.rawValue == "playing" { 77 | player?.pause() 78 | } 79 | } 80 | 81 | func nextSource() { 82 | if player?.items != nil { 83 | player?.next() 84 | player?.nowPlayingInfoController.set(keyValue: MediaItemProperty.artist(player?.currentItem?.getArtist())) 85 | self.nowPlayingEventSink?(nil) 86 | } 87 | } 88 | 89 | func prevSource() { 90 | if player?.items != nil { 91 | player?.previous() 92 | self.nowPlayingEventSink?(nil) 93 | } 94 | } 95 | 96 | func getVolume() -> Float { 97 | return (player?.volume)! 98 | } 99 | 100 | func setVolume(volume: Float) { 101 | player?.volume = volume 102 | DispatchQueue.main.async { 103 | let preparedEvent = FlutterRadioVolumeChanged(volume: volume) 104 | if let data = try? JSONEncoder().encode(preparedEvent) { 105 | self.playbackVolumeControl?(String(data: data, encoding: .utf8)) 106 | } 107 | } 108 | self.playbackVolumeControl?(volume) 109 | } 110 | 111 | func jumpToItem(index: Int) { 112 | try? player?.jumpToItem(atIndex: index, playWhenReady: player?.playWhenReady) 113 | } 114 | 115 | private func handleNowPlayingChanges(data: Array) { 116 | let nowPlayingData = data.first?.items.first 117 | if let nowPlayingAVMetaTitle = nowPlayingData?.value { 118 | player?.nowPlayingInfoController.set(keyValue: MediaItemProperty.title(nowPlayingAVMetaTitle as? String)) 119 | let nowPlaying = NowPlayingInfo(title: nowPlayingAVMetaTitle as? String) 120 | if let encodedData = try? JSONEncoder().encode(nowPlaying) { 121 | DispatchQueue.main.async { 122 | self.nowPlayingEventSink?(String(data: encodedData, encoding: .utf8)!) 123 | } 124 | } 125 | } 126 | } 127 | 128 | private func handlePlayerStateChange(state: AVPlayerWrapperState) { 129 | if state.rawValue == "playing" { 130 | DispatchQueue.main.async { 131 | self.playBackEventSink?(true) 132 | } 133 | } 134 | if state.rawValue == "paused" { 135 | DispatchQueue.main.async { 136 | self.playBackEventSink?(false) 137 | } 138 | } 139 | } 140 | 141 | private func getAppName() -> String? { 142 | if let appName = Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String ?? Bundle.main.infoDictionary?["CFBundleName"] as? String { 143 | return appName 144 | } else { 145 | return nil 146 | } 147 | } 148 | 149 | private func loadImageFromFlutterAssets(assetName: String, registrar: FlutterPluginRegistrar) -> UIImage? { 150 | let assetKey = registrar.lookupKey(forAsset: assetName) 151 | guard let assetPath = Bundle.main.path(forResource: assetKey, ofType: nil), 152 | let image = UIImage(contentsOfFile: assetPath) else { 153 | return nil 154 | } 155 | return image 156 | } 157 | 158 | } 159 | -------------------------------------------------------------------------------- /ios/Classes/data/FlutterRadioPlayerSource.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterRadioPlayerSource.swift 3 | // flutter_radio_player 4 | // 5 | // Created by Sithira Munasinghe on 2024-07-24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FlutterRadioPlayerSource: Decodable { 11 | var url: String 12 | var title: String? 13 | var artwork: String? 14 | } 15 | -------------------------------------------------------------------------------- /ios/Classes/data/FlutterRadioVolumeChange.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FlutterRadioVolumeChange.swift 3 | // flutter_radio_player 4 | // 5 | // Created by Sithira Munasinghe on 2024-07-24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct FlutterRadioVolumeChanged: Codable { 11 | var volume: Float 12 | var isMuted: Bool = false 13 | } 14 | -------------------------------------------------------------------------------- /ios/Classes/data/NowPlayingInfo.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NowPlayingInfo.swift 3 | // flutter_radio_player 4 | // 5 | // Created by Sithira Munasinghe on 2024-07-24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct NowPlayingInfo: Codable { 11 | var title: String? = nil 12 | } 13 | -------------------------------------------------------------------------------- /ios/flutter_radio_player.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. 3 | # Run `pod lib lint flutter_radio_player.podspec` to validate before publishing. 4 | # 5 | Pod::Spec.new do |s| 6 | s.name = 'flutter_radio_player' 7 | s.version = '0.0.1' 8 | s.summary = 'Online Radio Player for Flutter which enable to play streaming URL. Supports Android and iOS as well as WearOs and watchOs' 9 | s.description = <<-DESC 10 | Online Radio Player for Flutter which enable to play streaming URL. Supports Android and iOS as well as WearOs and watchOs 11 | DESC 12 | s.homepage = 'http://example.com' 13 | s.license = { :file => '../LICENSE' } 14 | s.author = { 'Your Company' => 'email@example.com' } 15 | s.source = { :path => '.' } 16 | s.source_files = 'Classes/**/*' 17 | s.dependency 'Flutter' 18 | s.dependency 'SwiftAudioEx', '~> 1.1.0' 19 | s.platform = :ios, '12.0' 20 | 21 | # Flutter.framework does not contain a i386 slice. 22 | s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' } 23 | s.swift_version = '5.0' 24 | end 25 | 26 | -------------------------------------------------------------------------------- /lib/data/flutter_radio_player_event.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | class NowPlayingDataChanged { 4 | final String? title; 5 | 6 | NowPlayingDataChanged({ 7 | required this.title, 8 | }); 9 | 10 | factory NowPlayingDataChanged.fromJson(String data) { 11 | var json = jsonDecode(data); 12 | return NowPlayingDataChanged( 13 | title: json['title'], 14 | ); 15 | } 16 | } 17 | 18 | class DeviceVolumeDataChanged { 19 | final double volume; 20 | final bool? isMuted; 21 | 22 | DeviceVolumeDataChanged({required this.volume, required this.isMuted}); 23 | 24 | factory DeviceVolumeDataChanged.fromEvent(String data) { 25 | var json = jsonDecode(data); 26 | return DeviceVolumeDataChanged( 27 | volume: json['volume'], isMuted: json['isMuted']); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/flutter_radio_player.dart: -------------------------------------------------------------------------------- 1 | import 'data/flutter_radio_player_event.dart'; 2 | import 'flutter_radio_player_platform_interface.dart'; 3 | 4 | class FlutterRadioPlayer { 5 | Future initialize( 6 | List> sources, bool playWhenReady) async { 7 | FlutterRadioPlayerPlatform.instance.initialize(sources, playWhenReady); 8 | } 9 | 10 | Future play() { 11 | return FlutterRadioPlayerPlatform.instance.play(); 12 | } 13 | 14 | Future pause() { 15 | return FlutterRadioPlayerPlatform.instance.pause(); 16 | } 17 | 18 | Future setVolume(double volume) { 19 | return FlutterRadioPlayerPlatform.instance.changeVolume(volume); 20 | } 21 | 22 | Future getVolume() { 23 | return FlutterRadioPlayerPlatform.instance.getVolume(); 24 | } 25 | 26 | Future nextSource() { 27 | return FlutterRadioPlayerPlatform.instance.nextSource(); 28 | } 29 | 30 | Future prevSource() { 31 | return FlutterRadioPlayerPlatform.instance.previousSource(); 32 | } 33 | 34 | Future jumpToSourceIndex(int index) { 35 | return FlutterRadioPlayerPlatform.instance.jumpToSourceIndex(index); 36 | } 37 | 38 | Stream getPlaybackStream() => 39 | FlutterRadioPlayerPlatform.instance.getIsPlayingStream(); 40 | 41 | Stream getNowPlayingStream() => 42 | FlutterRadioPlayerPlatform.instance.getNowPlayingStream(); 43 | 44 | Stream getDeviceVolumeChangedStream() => 45 | FlutterRadioPlayerPlatform.instance.getDeviceVolumeChangedStream(); 46 | } 47 | -------------------------------------------------------------------------------- /lib/flutter_radio_player_method_channel.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/services.dart'; 5 | import 'package:flutter_radio_player/data/flutter_radio_player_event.dart'; 6 | 7 | import 'flutter_radio_player_platform_interface.dart'; 8 | 9 | /// An implementation of [FlutterRadioPlayerPlatform] that uses method channels. 10 | class MethodChannelFlutterRadioPlayer extends FlutterRadioPlayerPlatform { 11 | @visibleForTesting 12 | final methodChannel = const MethodChannel('flutter_radio_player'); 13 | 14 | static const playbackStatusEventChannel = 15 | EventChannel("flutter_radio_player/playback_status"); 16 | 17 | static const nowPlayingInfoEventChannel = 18 | EventChannel("flutter_radio_player/now_playing_info"); 19 | 20 | static const deviceVolumeChangedEventChannel = 21 | EventChannel("flutter_radio_player/volume_control"); 22 | 23 | Stream? _playbackStream; 24 | 25 | Stream? _nowPlayingInfo; 26 | 27 | Stream? _deviceVolumeChangedStream; 28 | 29 | @override 30 | Future initialize( 31 | List> sources, bool playWhenReady) async { 32 | await methodChannel.invokeMethod('initialize', { 33 | "sources": jsonEncode(sources), 34 | "playWhenReady": playWhenReady, 35 | }); 36 | } 37 | 38 | @override 39 | Future play() async { 40 | await methodChannel.invokeMethod("play"); 41 | } 42 | 43 | @override 44 | Future pause() async { 45 | await methodChannel.invokeMethod("pause"); 46 | } 47 | 48 | @override 49 | Future playOrPause() async { 50 | await methodChannel.invokeMethod("playOrPause"); 51 | } 52 | 53 | @override 54 | Future changeVolume(double volume) async { 55 | await methodChannel.invokeMethod("changeVolume", {"volume": volume}); 56 | } 57 | 58 | @override 59 | Future getVolume() async { 60 | return await methodChannel.invokeMethod("getVolume"); 61 | } 62 | 63 | @override 64 | Future nextSource() async { 65 | await methodChannel.invokeMethod("nextSource"); 66 | } 67 | 68 | @override 69 | Future previousSource() async { 70 | await methodChannel.invokeMethod("prevSource"); 71 | } 72 | 73 | @override 74 | Future jumpToSourceIndex(int index) async { 75 | await methodChannel.invokeMethod("sourceAtIndex", {"index": index}); 76 | } 77 | 78 | @override 79 | Stream getIsPlayingStream() { 80 | if (_playbackStream != null) { 81 | return _playbackStream!; 82 | } 83 | 84 | var playbackStream = playbackStatusEventChannel 85 | .receiveBroadcastStream() 86 | .asBroadcastStream(onCancel: (sub) { 87 | sub.cancel(); 88 | _playbackStream = null; 89 | }); 90 | 91 | return playbackStream.map( 92 | (dynamic element) { 93 | return element as bool; 94 | }, 95 | ); 96 | } 97 | 98 | @override 99 | Stream getNowPlayingStream() { 100 | if (_nowPlayingInfo != null) { 101 | return _nowPlayingInfo!; 102 | } 103 | 104 | var playerReadyStream = nowPlayingInfoEventChannel 105 | .receiveBroadcastStream() 106 | .asBroadcastStream(onCancel: (sub) { 107 | sub.cancel(); 108 | _nowPlayingInfo = null; 109 | }); 110 | 111 | return playerReadyStream.map((dynamic event) { 112 | return NowPlayingDataChanged.fromJson(event as String); 113 | }); 114 | } 115 | 116 | @override 117 | Stream getDeviceVolumeChangedStream() { 118 | if (_deviceVolumeChangedStream != null) { 119 | return _deviceVolumeChangedStream!; 120 | } 121 | var deviceVolumeChangedStream = deviceVolumeChangedEventChannel 122 | .receiveBroadcastStream() 123 | .asBroadcastStream(onCancel: (sub) { 124 | sub.cancel(); 125 | _deviceVolumeChangedStream = null; 126 | }); 127 | 128 | return deviceVolumeChangedStream.map((dynamic event) { 129 | return DeviceVolumeDataChanged.fromEvent(event as String); 130 | }); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /lib/flutter_radio_player_platform_interface.dart: -------------------------------------------------------------------------------- 1 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 2 | 3 | import 'data/flutter_radio_player_event.dart'; 4 | import 'flutter_radio_player_method_channel.dart'; 5 | 6 | abstract class FlutterRadioPlayerPlatform extends PlatformInterface { 7 | /// Constructs a FlutterRadioPlayerPlatform. 8 | FlutterRadioPlayerPlatform() : super(token: _token); 9 | 10 | static final Object _token = Object(); 11 | 12 | static FlutterRadioPlayerPlatform _instance = 13 | MethodChannelFlutterRadioPlayer(); 14 | 15 | /// The default instance of [FlutterRadioPlayerPlatform] to use. 16 | /// 17 | /// Defaults to [MethodChannelFlutterRadioPlayer]. 18 | static FlutterRadioPlayerPlatform get instance => _instance; 19 | 20 | /// Platform-specific implementations should set this with their own 21 | /// platform-specific class that extends [FlutterRadioPlayerPlatform] when 22 | /// they register themselves. 23 | static set instance(FlutterRadioPlayerPlatform instance) { 24 | PlatformInterface.verifyToken(instance, _token); 25 | _instance = instance; 26 | } 27 | 28 | /// Initialize flutter radio player 29 | Future initialize( 30 | List> sources, bool playWhenReady) { 31 | throw UnimplementedError('initialize() has not been implemented.'); 32 | } 33 | 34 | /// Play the media source 35 | Future play() { 36 | throw UnimplementedError('play() has not been implemented.'); 37 | } 38 | 39 | /// Pause the media source 40 | Future pause() { 41 | throw UnimplementedError('pause() has not been implemented.'); 42 | } 43 | 44 | /// Either play or pause depending on the play state 45 | Future playOrPause() { 46 | throw UnimplementedError('playOrPause() has not been implemented.'); 47 | } 48 | 49 | /// Change the player volume 50 | Future changeVolume(double volume); 51 | 52 | /// Change the next source in the sources index 53 | Future nextSource(); 54 | 55 | /// Change the previous source in the sources index 56 | Future previousSource(); 57 | 58 | /// Jump to source at a index 59 | Future jumpToSourceIndex(int index); 60 | 61 | /// Get the current volume of the player. Defaults to 0.5 (low: 0, max: 1) 62 | Future getVolume(); 63 | 64 | /// Playback stream 65 | Stream getIsPlayingStream(); 66 | 67 | /// Now playing stream of icy / meta info 68 | Stream getNowPlayingStream(); 69 | 70 | /// Stream of player volume changes 71 | Stream getDeviceVolumeChangedStream(); 72 | } 73 | -------------------------------------------------------------------------------- /pubspec.lock: -------------------------------------------------------------------------------- 1 | # Generated by pub 2 | # See https://dart.dev/tools/pub/glossary#lockfile 3 | packages: 4 | async: 5 | dependency: transitive 6 | description: 7 | name: async 8 | sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" 9 | url: "https://pub.dev" 10 | source: hosted 11 | version: "2.11.0" 12 | boolean_selector: 13 | dependency: transitive 14 | description: 15 | name: boolean_selector 16 | sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" 17 | url: "https://pub.dev" 18 | source: hosted 19 | version: "2.1.1" 20 | characters: 21 | dependency: transitive 22 | description: 23 | name: characters 24 | sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" 25 | url: "https://pub.dev" 26 | source: hosted 27 | version: "1.3.0" 28 | clock: 29 | dependency: transitive 30 | description: 31 | name: clock 32 | sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf 33 | url: "https://pub.dev" 34 | source: hosted 35 | version: "1.1.1" 36 | collection: 37 | dependency: transitive 38 | description: 39 | name: collection 40 | sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a 41 | url: "https://pub.dev" 42 | source: hosted 43 | version: "1.18.0" 44 | fake_async: 45 | dependency: transitive 46 | description: 47 | name: fake_async 48 | sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" 49 | url: "https://pub.dev" 50 | source: hosted 51 | version: "1.3.1" 52 | flutter: 53 | dependency: "direct main" 54 | description: flutter 55 | source: sdk 56 | version: "0.0.0" 57 | flutter_lints: 58 | dependency: "direct dev" 59 | description: 60 | name: flutter_lints 61 | sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1" 62 | url: "https://pub.dev" 63 | source: hosted 64 | version: "3.0.2" 65 | flutter_test: 66 | dependency: "direct dev" 67 | description: flutter 68 | source: sdk 69 | version: "0.0.0" 70 | leak_tracker: 71 | dependency: transitive 72 | description: 73 | name: leak_tracker 74 | sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" 75 | url: "https://pub.dev" 76 | source: hosted 77 | version: "10.0.5" 78 | leak_tracker_flutter_testing: 79 | dependency: transitive 80 | description: 81 | name: leak_tracker_flutter_testing 82 | sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" 83 | url: "https://pub.dev" 84 | source: hosted 85 | version: "3.0.5" 86 | leak_tracker_testing: 87 | dependency: transitive 88 | description: 89 | name: leak_tracker_testing 90 | sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" 91 | url: "https://pub.dev" 92 | source: hosted 93 | version: "3.0.1" 94 | lints: 95 | dependency: transitive 96 | description: 97 | name: lints 98 | sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 99 | url: "https://pub.dev" 100 | source: hosted 101 | version: "3.0.0" 102 | matcher: 103 | dependency: transitive 104 | description: 105 | name: matcher 106 | sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb 107 | url: "https://pub.dev" 108 | source: hosted 109 | version: "0.12.16+1" 110 | material_color_utilities: 111 | dependency: transitive 112 | description: 113 | name: material_color_utilities 114 | sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec 115 | url: "https://pub.dev" 116 | source: hosted 117 | version: "0.11.1" 118 | meta: 119 | dependency: transitive 120 | description: 121 | name: meta 122 | sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 123 | url: "https://pub.dev" 124 | source: hosted 125 | version: "1.15.0" 126 | path: 127 | dependency: transitive 128 | description: 129 | name: path 130 | sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" 131 | url: "https://pub.dev" 132 | source: hosted 133 | version: "1.9.0" 134 | plugin_platform_interface: 135 | dependency: "direct main" 136 | description: 137 | name: plugin_platform_interface 138 | sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" 139 | url: "https://pub.dev" 140 | source: hosted 141 | version: "2.1.8" 142 | sky_engine: 143 | dependency: transitive 144 | description: flutter 145 | source: sdk 146 | version: "0.0.99" 147 | source_span: 148 | dependency: transitive 149 | description: 150 | name: source_span 151 | sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" 152 | url: "https://pub.dev" 153 | source: hosted 154 | version: "1.10.0" 155 | stack_trace: 156 | dependency: transitive 157 | description: 158 | name: stack_trace 159 | sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" 160 | url: "https://pub.dev" 161 | source: hosted 162 | version: "1.11.1" 163 | stream_channel: 164 | dependency: transitive 165 | description: 166 | name: stream_channel 167 | sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 168 | url: "https://pub.dev" 169 | source: hosted 170 | version: "2.1.2" 171 | string_scanner: 172 | dependency: transitive 173 | description: 174 | name: string_scanner 175 | sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" 176 | url: "https://pub.dev" 177 | source: hosted 178 | version: "1.2.0" 179 | term_glyph: 180 | dependency: transitive 181 | description: 182 | name: term_glyph 183 | sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 184 | url: "https://pub.dev" 185 | source: hosted 186 | version: "1.2.1" 187 | test_api: 188 | dependency: transitive 189 | description: 190 | name: test_api 191 | sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" 192 | url: "https://pub.dev" 193 | source: hosted 194 | version: "0.7.2" 195 | vector_math: 196 | dependency: transitive 197 | description: 198 | name: vector_math 199 | sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" 200 | url: "https://pub.dev" 201 | source: hosted 202 | version: "2.1.4" 203 | vm_service: 204 | dependency: transitive 205 | description: 206 | name: vm_service 207 | sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" 208 | url: "https://pub.dev" 209 | source: hosted 210 | version: "14.2.5" 211 | sdks: 212 | dart: ">=3.4.3 <4.0.0" 213 | flutter: ">=3.18.0-18.0.pre.54" 214 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_radio_player 2 | description: "Online Radio Player for Flutter which enable to play streaming URL. Supports Android and iOS as well as WearOs and watchOs" 3 | version: 3.0.2 4 | homepage: https://github.com/Sithira/FlutterRadioPlayer 5 | 6 | environment: 7 | sdk: '>=3.4.3 <4.0.0' 8 | flutter: '>=3.3.0' 9 | 10 | dependencies: 11 | flutter: 12 | sdk: flutter 13 | plugin_platform_interface: ^2.0.2 14 | 15 | dev_dependencies: 16 | flutter_test: 17 | sdk: flutter 18 | flutter_lints: ^3.0.0 19 | 20 | # For information on the generic Dart part of this file, see the 21 | # following page: https://dart.dev/tools/pub/pubspec 22 | 23 | # The following section is specific to Flutter packages. 24 | flutter: 25 | # This section identifies this Flutter project as a plugin project. 26 | # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) 27 | # which should be registered in the plugin registry. This is required for 28 | # using method channels. 29 | # The Android 'package' specifies package in which the registered class is. 30 | # This is required for using method channels on Android. 31 | # The 'ffiPlugin' specifies that native code should be built and bundled. 32 | # This is required for using `dart:ffi`. 33 | # All these are used by the tooling to maintain consistency when 34 | # adding or updating assets for this project. 35 | plugin: 36 | platforms: 37 | android: 38 | package: me.sithiramunasinghe.flutter.flutter_radio_player 39 | pluginClass: FlutterRadioPlayerPlugin 40 | ios: 41 | pluginClass: FlutterRadioPlayerPlugin 42 | 43 | # To add assets to your plugin package, add an assets section, like this: 44 | # assets: 45 | # - images/a_dot_burr.jpeg 46 | # - images/a_dot_ham.jpeg 47 | # 48 | # For details regarding assets in packages, see 49 | # https://flutter.dev/assets-and-images/#from-packages 50 | # 51 | # An image asset can refer to one or more resolution-specific "variants", see 52 | # https://flutter.dev/assets-and-images/#resolution-aware 53 | 54 | # To add custom fonts to your plugin package, add a fonts section here, 55 | # in this "flutter" section. Each entry in this list should have a 56 | # "family" key with the font family name, and a "fonts" key with a 57 | # list giving the asset and other descriptors for the font. For 58 | # example: 59 | # fonts: 60 | # - family: Schyler 61 | # fonts: 62 | # - asset: fonts/Schyler-Regular.ttf 63 | # - asset: fonts/Schyler-Italic.ttf 64 | # style: italic 65 | # - family: Trajan Pro 66 | # fonts: 67 | # - asset: fonts/TrajanPro.ttf 68 | # - asset: fonts/TrajanPro_Bold.ttf 69 | # weight: 700 70 | # 71 | # For details regarding fonts in packages, see 72 | # https://flutter.dev/custom-fonts/#from-packages 73 | -------------------------------------------------------------------------------- /test/flutter_radio_player_method_channel_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:flutter_radio_player/flutter_radio_player_method_channel.dart'; 4 | 5 | void main() { 6 | TestWidgetsFlutterBinding.ensureInitialized(); 7 | 8 | MethodChannelFlutterRadioPlayer platform = MethodChannelFlutterRadioPlayer(); 9 | const MethodChannel channel = MethodChannel('flutter_radio_player'); 10 | 11 | setUp(() { 12 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 13 | .setMockMethodCallHandler( 14 | channel, 15 | (MethodCall methodCall) async { 16 | return '42'; 17 | }, 18 | ); 19 | }); 20 | 21 | tearDown(() { 22 | TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger 23 | .setMockMethodCallHandler(channel, null); 24 | }); 25 | 26 | test('getPlatformVersion', () async { 27 | expect('42', '42'); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /test/flutter_radio_player_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter_radio_player/data/flutter_radio_player_event.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:flutter_radio_player/flutter_radio_player.dart'; 4 | import 'package:flutter_radio_player/flutter_radio_player_platform_interface.dart'; 5 | import 'package:flutter_radio_player/flutter_radio_player_method_channel.dart'; 6 | import 'package:plugin_platform_interface/plugin_platform_interface.dart'; 7 | 8 | class MockFlutterRadioPlayerPlatform 9 | with MockPlatformInterfaceMixin 10 | implements FlutterRadioPlayerPlatform { 11 | @override 12 | Future getPlatformVersion() => Future.value('42'); 13 | 14 | @override 15 | Future play() { 16 | // TODO: implement play 17 | throw UnimplementedError(); 18 | } 19 | 20 | @override 21 | Future changeVolume(double volume) { 22 | // TODO: implement changeVolume 23 | throw UnimplementedError(); 24 | } 25 | 26 | @override 27 | Stream getDeviceVolumeChangedStream() { 28 | // TODO: implement getDeviceVolumeChangedStream 29 | throw UnimplementedError(); 30 | } 31 | 32 | @override 33 | Stream getIsPlayingStream() { 34 | // TODO: implement getIsPlayingStream 35 | throw UnimplementedError(); 36 | } 37 | 38 | @override 39 | Stream getNowPlayingStream() { 40 | // TODO: implement getNowPlayingStream 41 | throw UnimplementedError(); 42 | } 43 | 44 | @override 45 | Future getVolume() { 46 | // TODO: implement getVolume 47 | throw UnimplementedError(); 48 | } 49 | 50 | @override 51 | Future initialize( 52 | List> sources, bool playWhenReady) { 53 | // TODO: implement initialize 54 | throw UnimplementedError(); 55 | } 56 | 57 | @override 58 | Future jumpToSourceIndex(int index) { 59 | // TODO: implement jumpToSourceIndex 60 | throw UnimplementedError(); 61 | } 62 | 63 | @override 64 | Future nextSource() { 65 | // TODO: implement nextSource 66 | throw UnimplementedError(); 67 | } 68 | 69 | @override 70 | Future pause() { 71 | // TODO: implement pause 72 | throw UnimplementedError(); 73 | } 74 | 75 | @override 76 | Future playOrPause() { 77 | // TODO: implement playOrPause 78 | throw UnimplementedError(); 79 | } 80 | 81 | @override 82 | Future previousSource() { 83 | // TODO: implement previousSource 84 | throw UnimplementedError(); 85 | } 86 | } 87 | 88 | void main() { 89 | final FlutterRadioPlayerPlatform initialPlatform = 90 | FlutterRadioPlayerPlatform.instance; 91 | 92 | test('$MethodChannelFlutterRadioPlayer is the default instance', () { 93 | expect(initialPlatform, isInstanceOf()); 94 | }); 95 | 96 | test('getPlatformVersion', () async { 97 | FlutterRadioPlayer flutterRadioPlayerPlugin = FlutterRadioPlayer(); 98 | MockFlutterRadioPlayerPlatform fakePlatform = 99 | MockFlutterRadioPlayerPlatform(); 100 | FlutterRadioPlayerPlatform.instance = fakePlatform; 101 | 102 | expect('42', '42'); 103 | }); 104 | } 105 | --------------------------------------------------------------------------------