diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..99b26a1 --- /dev/null +++ b/.fvmrc @@ -0,0 +1,4 @@ +{ + "flutter": "3.19.3", + "flavors": {} +} diff --git a/android/build.gradle b/android/build.gradle index 6165453..d8b8718 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,19 +1,3 @@ -buildscript { - - ext.kotlin_version = '1.5.32' - ext.exo_player_version = '2.18.5' - - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - group 'me.sithiramunasinghe.flutter.flutter_radio_player' version '1.0-SNAPSHOT' @@ -29,7 +13,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 31 + namespace 'me.sithiramunasinghe.flutter.flutter_radio_player' + compileSdkVersion 36 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -52,14 +37,17 @@ android { dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation "org.greenrobot:eventbus:3.3.1" - implementation 'com.google.code.gson:gson:2.8.9' + implementation 'com.google.code.gson:gson:2.10.1' implementation "androidx.multidex:multidex:2.0.1" - implementation "com.google.android.exoplayer:exoplayer:$exo_player_version" + ext.exo_player_version = '2.18.5' + implementation "com.google.android.exoplayer:exoplayer-core:$exo_player_version" + implementation "com.google.android.exoplayer:exoplayer-ui:$exo_player_version" + implementation "com.google.android.exoplayer:exoplayer-hls:$exo_player_version" implementation "com.google.android.exoplayer:extension-mediasession:$exo_player_version" -} \ No newline at end of file +} diff --git a/android/gradle.properties b/android/gradle.properties index 38c8d45..94adc3a 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,3 @@ org.gradle.jvmargs=-Xmx1536M -android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index f3d0e8a..134eb66 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Thu Oct 15 12:19:13 EET 2020 +#Sun Feb 15 16:54:53 CET 2026 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 95ed40c..99772b7 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1,27 @@ +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" // apply true + id "com.android.application" version "8.12.3" apply false + id "org.jetbrains.kotlin.android" version "2.3.10" apply false +} + +include ":app" + rootProject.name = 'flutter_radio_player' diff --git a/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/FlutterRadioPlayerPlugin.kt b/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/FlutterRadioPlayerPlugin.kt index dfdbbeb..fdfcd6a 100644 --- a/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/FlutterRadioPlayerPlugin.kt +++ b/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/FlutterRadioPlayerPlugin.kt @@ -33,8 +33,6 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met private val GSON = Gson() } - var serviceIntent: Intent? = null - private var isBound: Boolean = false private var context: Context? = null private var pluginActivity: Activity? = null @@ -56,7 +54,6 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met frpChannel?.setMethodCallHandler(null) frpChannel = null eventSink = null - serviceIntent = null } override fun onAttachedToActivity(binding: ActivityPluginBinding) { @@ -75,17 +72,13 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met } override fun onDetachedFromActivity() { - if (isBound) { - frpRadioPlayerService.onDestroy() - } EventBus.getDefault().unregister(this) } private fun onAttachedToEngine(context: Context, binaryMessenger: BinaryMessenger) { - Log.i(TAG, "::: Attaching to FRP to FlutterEngine :::") + Log.i(TAG, "::: Attaching FRP to FlutterEngine :::") this.context = context frpChannel = MethodChannel(binaryMessenger, METHOD_CHANNEL_NAME) - frpChannel?.setMethodCallHandler(this) val eventChannel = EventChannel(binaryMessenger, EVENT_CHANNEL_NAME) eventChannel.setStreamHandler(object : EventChannel.StreamHandler { @@ -100,7 +93,7 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met }) // service intent - serviceIntent = Intent(context, FRPCoreService::class.java) + val serviceIntent = Intent(context, FRPCoreService::class.java) // start the background service. pluginActivity?.startService(serviceIntent) @@ -121,8 +114,10 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met isBound = false } } + Log.i(TAG, "Binding service...") + flutterPluginBinding?.applicationContext?.bindService(serviceIntent, serviceConnection!!, Context.BIND_AUTO_CREATE) - context.bindService(serviceIntent, serviceConnection!!, Context.BIND_AUTO_CREATE) + frpChannel?.setMethodCallHandler(this) } @Subscribe(threadMode = ThreadMode.MAIN) @@ -130,6 +125,7 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met if (eventSink != null) { Log.d(TAG, "FRP Event data = $event") if (event.playbackStatus != null) { + // TODO reconsider unbinding service on stop because it might be too cumbersome to rebind it when resuming if (event.playbackStatus == FRP_STOPPED) { Log.i(TAG, "Service unbind....") isBound = false @@ -152,6 +148,7 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met } override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { + Log.i(TAG, ":::: received method call: ${call.method} ::::") when (call.method) { "init_service" -> { if (isBound) { diff --git a/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/services/FRPCoreService.kt b/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/services/FRPCoreService.kt index b85f523..f27abd6 100644 --- a/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/services/FRPCoreService.kt +++ b/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/services/FRPCoreService.kt @@ -5,12 +5,21 @@ import android.app.Service import android.content.Context import android.content.Intent import android.net.Uri -import android.os.* +import android.os.Binder +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.session.MediaSessionCompat -import com.google.android.exoplayer2.* +import android.support.v4.media.session.PlaybackStateCompat +import com.google.android.exoplayer2.C +import com.google.android.exoplayer2.DefaultLivePlaybackSpeedControl +import com.google.android.exoplayer2.ExoPlayer +import com.google.android.exoplayer2.MediaItem +import com.google.android.exoplayer2.MediaMetadata import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector -import com.google.android.exoplayer2.metadata.MetadataOutput import com.google.android.exoplayer2.source.MediaSource import com.google.android.exoplayer2.source.ProgressiveMediaSource import com.google.android.exoplayer2.source.hls.HlsMediaSource @@ -47,76 +56,66 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener private val mediaSessionId = "flutter_radio_player_media_session_id" private val playbackChannelId = "flutter_radio_player_pb_channel_id" - // private var audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager - - var playbackStatus = FRPPlaybackStatus.STOPPED + var playbackStatus = FRPPlaybackStatus.LOADING var currentMetaData: MediaMetadata? = null var mediaSourceList: List = emptyList() var useICYData: Boolean = false private var currentPlayingItem: FRPCurrentSource? = null - - private var handler: Handler = Handler(Looper.getMainLooper()) - private val binder = LocalBinder() private var exoPlayer: ExoPlayer? = null + private var playerListener: FRPPlayerListener? = null private val eventBus: EventBus = EventBus.getDefault() - private var exoPlayerBuilder = ExoPlayer.Builder(context) private var mediaSessionConnector: MediaSessionConnector? = null private var playerNotificationManager: PlayerNotificationManager? = null - private lateinit var mediaSession: MediaSessionCompat + private var mediaSession: MediaSessionCompat? = null override fun onCreate() { - Log.i(TAG, "FlutterRadioPlayerService::onCreate") - // build exoplayer - exoPlayer = exoPlayerBuilder - .setLooper(handler.looper) - .setAudioAttributes( - AudioAttributes.Builder() - .setUsage(C.USAGE_MEDIA) - .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) - .build(), true - ) - .setLivePlaybackSpeedControl( - DefaultLivePlaybackSpeedControl.Builder() - .setFallbackMaxPlaybackSpeed(1.04f) - .build() - ) - .setHandleAudioBecomingNoisy(true) - .build() - - // exoplayer configuration - exoPlayer?.let { - it.addListener(FRPPlayerListener(this, exoPlayer, playerNotificationManager, eventBus)) - it.playWhenReady = false - } - - exoPlayer?.addAnalyticsListener(EventLogger()) - - Log.i(TAG, "::::: END FlutterRadioPlayerService::onCreate ::::") + // media session + setupMediaSession() } override fun onDestroy() { Log.i(TAG, "::: onDestroy :::") + releaseResources() + super.onDestroy() + } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mediaSession.release() - } - - if (exoPlayer != null) { - exoPlayer?.release() + private fun releaseResources() { + Log.i(TAG, ":::: Releasing resources ::::") + if (playerListener != null) { + exoPlayer?.removeListener(playerListener!!) } + mediaSession?.isActive = false + mediaSession?.release() + mediaSessionConnector?.setClearMediaItemsOnStop(false) mediaSessionConnector?.setPlayer(null) + playerNotificationManager?.setPlayer(null) - super.onDestroy() + exoPlayer?.release() + exoPlayer = null + + mediaSessionConnector = null + } + + override fun onTaskRemoved(rootIntent: Intent?) { + Log.i(TAG, ":::: FlutterRadioPlayerService.onTaskRemoved ::::") + releaseResources() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + stopForeground(STOP_FOREGROUND_REMOVE) + } else { + stopForeground(false) + } + stopSelf() + super.onTaskRemoved(rootIntent) } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { Log.i(TAG, ":::: FlutterRadioPlayerService.onStartCommand ::::") @@ -129,20 +128,80 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener .setNotificationListener(FRPPlayerNotificationListener(this)) .build() - // media session - mediaSession = MediaSessionCompat(this, mediaSessionId) - mediaSession.isActive = true + val handler = Handler(Looper.getMainLooper()) + + // build exoplayer + exoPlayer = ExoPlayer.Builder(context) + .setLooper(handler.looper) + .setAudioAttributes( + AudioAttributes.Builder() + .setUsage(C.USAGE_MEDIA) + .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) + .build(), true + ) + .setLivePlaybackSpeedControl( + DefaultLivePlaybackSpeedControl.Builder() + .setFallbackMaxPlaybackSpeed(1.04f) + .build() + ) + .setHandleAudioBecomingNoisy(true) + .build() - // set connector and player - mediaSessionConnector = MediaSessionConnector(mediaSession) - mediaSessionConnector?.setPlayer(exoPlayer) + val listener = FRPPlayerListener(this, exoPlayer, playerNotificationManager, eventBus) + playerListener = listener + // exoplayer configuration + exoPlayer?.let { + it.addListener(listener) + it.playWhenReady = false + } + + exoPlayer?.addAnalyticsListener(EventLogger()) + + if (mediaSession == null) { + setupMediaSession() + } + + if (mediaSession != null) { + val session: MediaSessionCompat = mediaSession!! + // set connector and player + mediaSessionConnector = MediaSessionConnector(session) + mediaSessionConnector?.setPlayer(exoPlayer) + mediaSessionConnector?.setMediaMetadataProvider { player -> + val builder = MediaMetadataCompat.Builder() + + // Guard against null playback state (the actual crash cause) + val playbackState = mediaSession?.controller?.playbackState + ?: return@setMediaMetadataProvider builder.build() + + val mediaItem = player.currentMediaItem + ?: return@setMediaMetadataProvider builder.build() + + builder + .putString( + MediaMetadataCompat.METADATA_KEY_TITLE, + mediaItem.mediaMetadata.title?.toString() ?: "" + ) + .putString( + MediaMetadataCompat.METADATA_KEY_ARTIST, + mediaItem.mediaMetadata.artist?.toString() ?: "" + ) + .putLong( + MediaMetadataCompat.METADATA_KEY_DURATION, + if (player.duration > 0) player.duration else -1L + ) + .build() + } + } playerNotificationManager?.apply { Log.i(TAG, "Applying configurations...") // default buttons - setUseStopAction(true) + // TODO allow developer to choose actions on player init instead of hardcoding them here + // ie. move true/false to Flutter side of the plugin and apply here + // TODO fix stopping (not pausing) and resuming player before enabling stop button + setUseStopAction(false) setUsePlayPauseActions(true) // next and prev buttons @@ -158,14 +217,32 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener setUseRewindActionInCompactView(false) setPlayer(exoPlayer) - setMediaSessionToken(mediaSession.sessionToken) + if (mediaSession != null) { + setMediaSessionToken(mediaSession!!.sessionToken) + } } - Log.i(TAG, ":::: END OF SERVICE ::::") + Log.i(TAG, ":::: END OF onStartCommand IN SERVICE ::::") return START_REDELIVER_INTENT } + private fun setupMediaSession() { + mediaSession = MediaSessionCompat(this, mediaSessionId) + mediaSession?.setPlaybackState( + PlaybackStateCompat.Builder() + .setState(PlaybackStateCompat.STATE_NONE, 0L, 1.0f) + .setActions( + PlaybackStateCompat.ACTION_PLAY or + PlaybackStateCompat.ACTION_PAUSE or + PlaybackStateCompat.ACTION_PLAY_PAUSE or + PlaybackStateCompat.ACTION_STOP + ) + .build() + ) + mediaSession?.isActive = true + } + fun setMediaSources(sourceList: List, playDefault: Boolean = false) { if (sourceList.isEmpty()) { @@ -179,58 +256,56 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener } Log.i(TAG, "Current PlaybackStatus $playbackStatus") + Log.i(TAG, "Current Player state ${exoPlayer?.playbackState}") val defaultSource = this.mediaSourceList.firstOrNull { frp -> frp.isPrimary } - if (playbackStatus == FRPPlaybackStatus.PAUSED || playbackStatus == FRPPlaybackStatus.STOPPED) { + if (defaultSource != null) { + Log.i(TAG, "Default media item added to exoplayer...") + + val mediaUrl = Uri.parse(defaultSource.url) + + val mediaBuilder = + MediaItem.Builder().setUri(mediaUrl).setLiveConfiguration( + MediaItem.LiveConfiguration.Builder() + .setMaxPlaybackSpeed(1.02f) + .build() + ) + + if (defaultSource.isAcc == true) { + mediaBuilder.setMimeType(MimeTypes.AUDIO_AAC) + Log.d(TAG, "is an AAC media source") + } + + exoPlayer?.addMediaSource(0, buildMediaSource(mediaUrl, mediaBuilder.build())) + updateCurrentPlaying(defaultSource) + } - if (defaultSource != null) { - Log.i(TAG, "Default media item added to exoplayer...") + mediaSourceList.filter { source -> !source.isPrimary }.forEach { frp -> + run { + Log.i(TAG, "Added media source ${frp.title} with url ${frp.url}") - val mediaUrl = Uri.parse(defaultSource.url) + val mediaUrl = Uri.parse(frp.url) - val mediaBuilder = - MediaItem.Builder().setUri(mediaUrl).setLiveConfiguration( + val mediaBuilder = MediaItem.Builder() + .setUri(mediaUrl) + .setLiveConfiguration( MediaItem.LiveConfiguration.Builder() .setMaxPlaybackSpeed(1.02f) .build() ) - if (defaultSource.isAcc!!) { + if (frp.isAcc!!) { mediaBuilder.setMimeType(MimeTypes.AUDIO_AAC) - Log.d(TAG, "is an AAC media source") } - exoPlayer?.addMediaSource(0, buildMediaSource(mediaUrl, mediaBuilder.build())) - updateCurrentPlaying(defaultSource) - } - - mediaSourceList.filter { source -> !source.isPrimary }.forEach { frp -> - run { - Log.i(TAG, "Added media source ${frp.title} with url ${frp.url}") - - val mediaUrl = Uri.parse(frp.url) - - val mediaBuilder = MediaItem.Builder() - .setUri(mediaUrl) - .setLiveConfiguration( - MediaItem.LiveConfiguration.Builder() - .setMaxPlaybackSpeed(1.02f) - .build() - ) - - if (frp.isAcc!!) { - mediaBuilder.setMimeType(MimeTypes.AUDIO_AAC) - } - - exoPlayer?.addMediaSource(buildMediaSource(mediaUrl, mediaBuilder.build())) - } + exoPlayer?.addMediaSource(buildMediaSource(mediaUrl, mediaBuilder.build())) } - - Log.i(TAG, "Preparing player...") - exoPlayer?.prepare() } + Log.i(TAG, "Preparing player...") + exoPlayer?.prepare() + if (playDefault) { Log.i(TAG, "addMediaSources with default play") exoPlayer?.playWhenReady = true @@ -287,10 +362,15 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener } fun seekToMediaItem(index: Int, playIfReady: Boolean) { + Log.i(TAG, "Seeking to media item, pos: $index...") + + Log.d(TAG, "playbackState ${exoPlayer?.playbackState}") + Log.d(TAG, "playbackLooper ${exoPlayer?.playbackLooper}") exoPlayer?.seekToDefaultPosition(index) exoPlayer?.apply { playWhenReady = playIfReady } + exoPlayer?.prepare() val currentMedia = mediaSourceList[exoPlayer?.currentMediaItemIndex!!] eventBus.post(FRPPlayerEvent(currentSource = updateCurrentPlaying(currentMedia))) } @@ -329,9 +409,12 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener } return when (val type = Util.inferContentType(mediaUrl)) { - C.CONTENT_TYPE_HLS -> HlsMediaSource.Factory(defaultDataSource).createMediaSource(mediaItem) + C.CONTENT_TYPE_HLS -> HlsMediaSource.Factory(defaultDataSource) + .createMediaSource(mediaItem) + C.CONTENT_TYPE_OTHER -> ProgressiveMediaSource.Factory(defaultDataSource) .createMediaSource(mediaItem) + else -> { throw FRPException("Unsupported type: $type") } diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 908e6f8..cef9897 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -6,11 +12,6 @@ if (localPropertiesFile.exists()) { } } -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - def flutterVersionCode = localProperties.getProperty('flutter.versionCode') if (flutterVersionCode == null) { flutterVersionCode = '1' @@ -21,12 +22,9 @@ if (flutterVersionName == null) { flutterVersionName = '1.0' } -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - android { - compileSdkVersion flutter.compileSdkVersion + namespace 'me.sithiramunasinghe.flutter_radio_player_example' + compileSdkVersion 36 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -66,5 +64,4 @@ flutter { dependencies { implementation 'androidx.multidex:multidex:2.0.1' - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/example/android/build.gradle b/example/android/build.gradle index a9ddbd9..bc157bd 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.5.32' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.1.3' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -26,6 +13,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 595fb86..8e5bb06 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Fri Jun 23 08:50:38 CEST 2017 +#Fri Aug 29 13:04:46 CEST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 44e62bc..19d2288 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" // apply true + id "com.android.application" version '8.12.3' apply false + id "org.jetbrains.kotlin.android" version "2.3.10" apply false +} + +include ":app" \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 6dcc55c..98822d1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -36,9 +36,17 @@ class _MyAppState extends State { ], ); + AppLifecycleListener? appLifecycleListener; + @override void initState() { super.initState(); + appLifecycleListener = AppLifecycleListener( + onDetach: () { + debugPrint("App detached"); + _flutterRadioPlayer.stop(); + }, + ); _flutterRadioPlayer.initPlayer(); _flutterRadioPlayer.addMediaSources(frpSource); } diff --git a/example/pubspec.lock b/example/pubspec.lock index 2ca2bce..fb74061 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" boolean_selector: dependency: transitive description: @@ -21,42 +21,42 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.4.0" clock: dependency: transitive description: name: clock - sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.1.2" collection: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: name: cupertino_icons - sha256: "1989d917fbe8e6b39806207df5a3fdd3d816cbd090fac2ce26fb45e9a71476e5" + sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" fake_async: dependency: transitive description: name: fake_async - sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" flutter: dependency: "direct main" description: flutter @@ -76,20 +76,36 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "3.0.3" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" - js: + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + url: "https://pub.dev" + source: hosted + version: "11.0.2" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + url: "https://pub.dev" + source: hosted + version: "3.0.10" + leak_tracker_testing: dependency: transitive description: - name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + name: leak_tracker_testing + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "3.0.2" lints: dependency: transitive description: @@ -102,63 +118,63 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5 + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -179,18 +195,26 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.7.6" vector_math: dependency: transitive description: name: vector_math - sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" + vm_service: + dependency: transitive + description: + name: vm_service + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 + url: "https://pub.dev" + source: hosted + version: "13.0.0" sdks: - dart: ">=2.18.0 <3.0.0" - flutter: ">=2.5.0" + dart: ">=3.8.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c7026d1..38599ed 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -6,7 +6,7 @@ description: Demonstrates how to use the flutter_radio_player plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.15.1 <3.0.0" + sdk: ">=3.0.0 <4.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/ios/Classes/core/services/support/FRPPlayerEventHandler.swift b/ios/Classes/core/services/support/FRPPlayerEventHandler.swift index af1ec0d..683f87b 100644 --- a/ios/Classes/core/services/support/FRPPlayerEventHandler.swift +++ b/ios/Classes/core/services/support/FRPPlayerEventHandler.swift @@ -15,9 +15,9 @@ class FRPPlayerEventHandler: NSObject { print("::::: EVENT HANDLER INIT ::::") } - static func handleMetaDataChanges(metaDetails: Array) { + static func handleMetaDataChanges(metaGroup: Array) { if (FRPCoreService.shared.useIcyData) { - metaDetails + metaGroup.first?.items .compactMap({ $0 as AVMetadataItem }) .forEach({ meta in print("Meta details \(meta)") diff --git a/lib/flutter_radio_player.dart b/lib/flutter_radio_player.dart index dc2ece8..8e58011 100644 --- a/lib/flutter_radio_player.dart +++ b/lib/flutter_radio_player.dart @@ -37,6 +37,7 @@ class FlutterRadioPlayer { } _eventStream ??= _eventChannel.receiveBroadcastStream().map((event) => event); + _methodChannel.invokeMethod("init_service"); if (kDebugMode) { print("Initialized Event Channels: Completed"); } diff --git a/pubspec.yaml b/pubspec.yaml index 3723896..ed721a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: flutter_radio_player description: Online Radio Player for Flutter which enable to play streaming URL. Supports Android and iOS as well as WearOs and watchOs -version: 2.0.2 +version: 3.0.3 homepage: "https://github.com/Sithira/FlutterRadioPlayer" environment: - sdk: ">=2.15.1 <3.0.0" + sdk: ">=3.0.0 <4.0.0" flutter: ">=2.5.0" dependencies: