From 92c197eac4dde0496370eef4ae6312e09689d11f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 26 Jan 2023 00:29:23 +0100 Subject: [PATCH 01/18] delete unused StreamingCore class --- .../flutter/flutter_radio_player/core/StreamingCore.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/StreamingCore.kt diff --git a/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/StreamingCore.kt b/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/StreamingCore.kt new file mode 100644 index 0000000..e69de29 From a1ef5360ae2d1fab34bab19df845b18134e2f320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 26 Jan 2023 00:30:03 +0100 Subject: [PATCH 02/18] update kotlin version --- android/build.gradle | 4 ++-- example/android/build.gradle | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 6165453..4aecbb3 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,6 @@ buildscript { - ext.kotlin_version = '1.5.32' + ext.kotlin_version = '1.7.21' ext.exo_player_version = '2.18.5' repositories { @@ -62,4 +62,4 @@ dependencies { implementation "com.google.android.exoplayer:exoplayer:$exo_player_version" implementation "com.google.android.exoplayer:extension-mediasession:$exo_player_version" -} \ No newline at end of file +} diff --git a/example/android/build.gradle b/example/android/build.gradle index a9ddbd9..717cd8c 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.5.32' + ext.kotlin_version = '1.7.21' repositories { google() mavenCentral() From 5b02211b41904cf2374e1e7c1882d97a6736e07f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 26 Jan 2023 00:53:12 +0100 Subject: [PATCH 03/18] fix check for if default source is acc --- .../flutter_radio_player/core/services/FRPCoreService.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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..6282600 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 @@ -196,7 +196,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener .build() ) - if (defaultSource.isAcc!!) { + if (defaultSource.isAcc == true) { mediaBuilder.setMimeType(MimeTypes.AUDIO_AAC) Log.d(TAG, "is an AAC media source") } From b42411aa3e914452bade9f74476e75237ef393e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Sat, 28 Jan 2023 11:03:28 +0100 Subject: [PATCH 04/18] upgrade example dependencies --- example/android/build.gradle | 2 +- example/android/gradle/wrapper/gradle-wrapper.properties | 2 +- example/pubspec.lock | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/example/android/build.gradle b/example/android/build.gradle index 717cd8c..ae83546 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -6,7 +6,7 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:7.1.3' + classpath 'com.android.tools.build:gradle:7.4.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index 595fb86..6b66533 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip diff --git a/example/pubspec.lock b/example/pubspec.lock index 2ca2bce..3fd9998 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -45,10 +45,10 @@ packages: 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: @@ -76,7 +76,7 @@ packages: path: ".." relative: true source: path - version: "2.0.0" + version: "2.0.2" flutter_test: dependency: "direct dev" description: flutter @@ -192,5 +192,5 @@ packages: source: hosted version: "2.1.4" sdks: - dart: ">=2.18.0 <3.0.0" + dart: ">=2.18.0 <4.0.0" flutter: ">=2.5.0" From 09d8e52724c70c8f172dd0f7f768e968ab0323a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Sat, 28 Jan 2023 11:04:06 +0100 Subject: [PATCH 05/18] fix fetching metadata for iOs build --- ios/Classes/core/services/support/FRPPlayerEventHandler.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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)") From d220bd6394da64fe049fda2efe9b0c0eb4003054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Sat, 28 Jan 2023 11:05:38 +0100 Subject: [PATCH 06/18] upgrade plugin version to v2.0.3 --- example/pubspec.lock | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index 3fd9998..bb69552 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -76,7 +76,7 @@ packages: path: ".." relative: true source: path - version: "2.0.2" + version: "2.0.3" flutter_test: dependency: "direct dev" description: flutter diff --git a/pubspec.yaml b/pubspec.yaml index 3723896..d1cfbec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ 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: 2.0.3 homepage: "https://github.com/Sithira/FlutterRadioPlayer" environment: From a0d8d03e4252039e7438aee96f5af5de7a5b995f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 2 Feb 2023 01:10:01 +0100 Subject: [PATCH 07/18] upgrade Android build gradle, kotlin and dependencies versions --- android/build.gradle | 6 ++---- android/gradle.properties | 1 - android/gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/build.gradle | 4 ++-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 4aecbb3..1517c70 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,6 +1,6 @@ buildscript { - ext.kotlin_version = '1.7.21' + ext.kotlin_version = '1.8.0' ext.exo_player_version = '2.18.5' repositories { @@ -9,7 +9,6 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } @@ -29,7 +28,6 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { - compileSdkVersion 31 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -56,7 +54,7 @@ dependencies { 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" 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..32b25ba 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip diff --git a/example/android/build.gradle b/example/android/build.gradle index ae83546..d4c3e97 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,12 +1,12 @@ buildscript { - ext.kotlin_version = '1.7.21' + ext.kotlin_version = '1.8.0' repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.0' + classpath 'com.android.tools.build:gradle:7.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } From 907f6610a61a6c694575fae8dfd09a740020f3ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 2 Feb 2023 01:10:48 +0100 Subject: [PATCH 08/18] move player initialization before setting media sources, disable stop button until resuming fixed, --- .../FlutterRadioPlayerPlugin.kt | 1 + .../core/services/FRPCoreService.kt | 86 ++++++++++--------- 2 files changed, 48 insertions(+), 39 deletions(-) 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..afcbd2a 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 @@ -130,6 +130,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 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 6282600..96b9ba7 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 @@ -10,7 +10,6 @@ import android.support.v4.media.session.MediaSessionCompat import com.google.android.exoplayer2.* 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,8 +46,6 @@ 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 currentMetaData: MediaMetadata? = null var mediaSourceList: List = emptyList() @@ -66,38 +63,6 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener private lateinit var mediaSession: MediaSessionCompat - 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 ::::") - } - override fun onDestroy() { Log.i(TAG, "::: onDestroy :::") @@ -135,14 +100,16 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener // set connector and player mediaSessionConnector = MediaSessionConnector(mediaSession) - mediaSessionConnector?.setPlayer(exoPlayer) 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 @@ -157,7 +124,6 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener setUseFastForwardActionInCompactView(false) setUseRewindActionInCompactView(false) - setPlayer(exoPlayer) setMediaSessionToken(mediaSession.sessionToken) } @@ -172,6 +138,47 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener throw FRPException("Empty media sources") } + // TODO maybe find a better init location where the player will always be initialized correctly (onCreate wasn't called always) + // build exoplayer + if (exoPlayer == null) { + 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() + ) + .setRenderersFactory( + DefaultRenderersFactory(context) + .forceEnableMediaCodecAsynchronousQueueing() + ) + .setHandleAudioBecomingNoisy(true) + .build() + + // exoplayer configuration + exoPlayer?.let { + it.addListener( + FRPPlayerListener( + this, + exoPlayer, + playerNotificationManager, + eventBus + ) + ) + it.playWhenReady = false + } + + exoPlayer?.addAnalyticsListener(EventLogger()) + mediaSessionConnector?.setPlayer(exoPlayer) + playerNotificationManager?.setPlayer(exoPlayer) + } + this.mediaSourceList = sourceList.sortedByDescending { it.isPrimary } if (this.mediaSourceList.none { frpAudioSource -> frpAudioSource.isPrimary }) { @@ -329,7 +336,8 @@ 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 -> { From 0dac402d54dcd5b236af3d95dc1915e9129c6bf2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 2 Feb 2023 01:11:00 +0100 Subject: [PATCH 09/18] update plugin version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index d1cfbec..e66d0d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ 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.3 +version: 2.1.0 homepage: "https://github.com/Sithira/FlutterRadioPlayer" environment: From 97a3a5180d45327a5e94cf94e04420142a34bd89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 2 Feb 2023 12:24:52 +0100 Subject: [PATCH 10/18] android - allow removing notification when playback paused, remove notification when app terminated --- .../core/services/FRPCoreService.kt | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) 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 96b9ba7..9718451 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 @@ -7,6 +7,7 @@ import android.content.Intent import android.net.Uri import android.os.* import android.support.v4.media.session.MediaSessionCompat +import androidx.core.app.NotificationCompat.ServiceNotificationBehavior import com.google.android.exoplayer2.* import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector @@ -71,10 +72,12 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener mediaSession.release() } - if (exoPlayer != null) { - exoPlayer?.release() + exoPlayer?.release() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + stopForeground(STOP_FOREGROUND_REMOVE) + } else { + stopForeground(false) } - mediaSessionConnector?.setPlayer(null) playerNotificationManager?.setPlayer(null) @@ -260,6 +263,11 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener } fun pause() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + stopForeground(STOP_FOREGROUND_DETACH) + } else { + stopForeground(false) + } exoPlayer?.pause() } From 60d8f837f761e141c1f678b37732ac56b02eca94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 2 Feb 2023 20:54:38 +0100 Subject: [PATCH 11/18] reduce exoplayer dependencies to only needed ones --- android/build.gradle | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/android/build.gradle b/android/build.gradle index 1517c70..09f4c3d 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -58,6 +58,8 @@ dependencies { implementation "androidx.multidex:multidex:2.0.1" - implementation "com.google.android.exoplayer:exoplayer:$exo_player_version" + 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" } From a30726c37d0f8f8588f9930dc88f3cc1c9f19d41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 2 Feb 2023 20:56:09 +0100 Subject: [PATCH 12/18] try to make sure the service and its player are initialized on every new app start and service binding --- .../FlutterRadioPlayerPlugin.kt | 6 +- .../core/services/FRPCoreService.kt | 84 ++++++++++--------- lib/flutter_radio_player.dart | 1 + 3 files changed, 48 insertions(+), 43 deletions(-) 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 afcbd2a..351205e 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 @@ -85,7 +85,6 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met Log.i(TAG, "::: Attaching to 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 { @@ -121,8 +120,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) @@ -153,6 +154,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 9718451..5033e28 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 @@ -7,7 +7,6 @@ import android.content.Intent import android.net.Uri import android.os.* import android.support.v4.media.session.MediaSessionCompat -import androidx.core.app.NotificationCompat.ServiceNotificationBehavior import com.google.android.exoplayer2.* import com.google.android.exoplayer2.audio.AudioAttributes import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector @@ -47,7 +46,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener private val mediaSessionId = "flutter_radio_player_media_session_id" private val playbackChannelId = "flutter_radio_player_pb_channel_id" - var playbackStatus = FRPPlaybackStatus.STOPPED + var playbackStatus = FRPPlaybackStatus.LOADING var currentMetaData: MediaMetadata? = null var mediaSourceList: List = emptyList() var useICYData: Boolean = false @@ -58,7 +57,6 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener private val binder = LocalBinder() private var exoPlayer: ExoPlayer? = null private val eventBus: EventBus = EventBus.getDefault() - private var exoPlayerBuilder = ExoPlayer.Builder(context) private var mediaSessionConnector: MediaSessionConnector? = null private var playerNotificationManager: PlayerNotificationManager? = null @@ -73,6 +71,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener } exoPlayer?.release() + exoPlayer = null if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground(STOP_FOREGROUND_REMOVE) } else { @@ -106,7 +105,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener playerNotificationManager?.apply { - Log.i(TAG, "Applying configurations...") + Log.i(TAG, ":::: Applying configurations... ::::") // default buttons // TODO allow developer to choose actions on player init instead of hardcoding them here @@ -130,7 +129,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener setMediaSessionToken(mediaSession.sessionToken) } - Log.i(TAG, ":::: END OF SERVICE ::::") + Log.i(TAG, ":::: END OF onStartCommand IN SERVICE ::::") return START_REDELIVER_INTENT } @@ -144,7 +143,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener // TODO maybe find a better init location where the player will always be initialized correctly (onCreate wasn't called always) // build exoplayer if (exoPlayer == null) { - exoPlayer = exoPlayerBuilder + exoPlayer = ExoPlayer.Builder(context) .setLooper(handler.looper) .setAudioAttributes( AudioAttributes.Builder() @@ -189,58 +188,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...") - if (defaultSource != null) { - Log.i(TAG, "Default media item added to exoplayer...") + val mediaUrl = Uri.parse(defaultSource.url) - 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) + } + + 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( + val mediaBuilder = MediaItem.Builder() + .setUri(mediaUrl) + .setLiveConfiguration( MediaItem.LiveConfiguration.Builder() .setMaxPlaybackSpeed(1.02f) .build() ) - if (defaultSource.isAcc == true) { + 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 @@ -302,10 +299,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))) } 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"); } From ec26e3775ca602a68e6f5b97b266b50600b0ec55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Thu, 2 Feb 2023 21:05:14 +0100 Subject: [PATCH 13/18] upgrade plugin version to v2.2.0 --- example/pubspec.lock | 56 ++++++++++++++++++++++---------------------- pubspec.yaml | 2 +- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/example/pubspec.lock b/example/pubspec.lock index bb69552..1d977c8 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,10 +21,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" clock: dependency: transitive description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.2" cupertino_icons: dependency: "direct main" description: @@ -76,20 +76,12 @@ packages: path: ".." relative: true source: path - version: "2.0.3" + version: "2.2.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" - js: - dependency: transitive - description: - name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" - url: "https://pub.dev" - source: hosted - version: "0.6.5" lints: dependency: transitive description: @@ -102,34 +94,34 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" path: dependency: transitive description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" sky_engine: dependency: transitive description: flutter @@ -139,10 +131,10 @@ packages: 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: @@ -179,10 +171,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.6.0" vector_math: dependency: transitive description: @@ -191,6 +183,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" sdks: - dart: ">=2.18.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index e66d0d4..481b8ef 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ 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.1.0 +version: 2.2.0 homepage: "https://github.com/Sithira/FlutterRadioPlayer" environment: From f01ca6839d982634cce48d2ec8db1303a07de348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Mon, 15 Jan 2024 11:15:53 +0100 Subject: [PATCH 14/18] Delete android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/StreamingCore.kt --- .../flutter/flutter_radio_player/core/StreamingCore.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/StreamingCore.kt diff --git a/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/StreamingCore.kt b/android/src/main/kotlin/me/sithiramunasinghe/flutter/flutter_radio_player/core/StreamingCore.kt deleted file mode 100644 index e69de29..0000000 From 785d4dd9a7a710373f0f84c4ab574e0f349c0b18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Sun, 31 Aug 2025 13:44:25 +0200 Subject: [PATCH 15/18] upgrade android/gradle version, fix service and player lifecycle, add FVM --- .fvmrc | 4 + android/build.gradle | 20 +-- android/settings.gradle | 26 ++++ .../FlutterRadioPlayerPlugin.kt | 7 +- .../core/services/FRPCoreService.kt | 125 +++++++++--------- example/android/app/build.gradle | 19 ++- example/android/build.gradle | 15 +-- .../gradle/wrapper/gradle-wrapper.properties | 4 +- example/android/settings.gradle | 30 +++-- example/pubspec.lock | 104 +++++++++------ example/pubspec.yaml | 2 +- pubspec.yaml | 4 +- 12 files changed, 200 insertions(+), 160 deletions(-) create mode 100644 .fvmrc 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 09f4c3d..d8b8718 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,18 +1,3 @@ -buildscript { - - ext.kotlin_version = '1.8.0' - ext.exo_player_version = '2.18.5' - - repositories { - google() - mavenCentral() - } - - dependencies { - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - group 'me.sithiramunasinghe.flutter.flutter_radio_player' version '1.0-SNAPSHOT' @@ -28,6 +13,8 @@ apply plugin: 'com.android.library' apply plugin: 'kotlin-android' android { + namespace 'me.sithiramunasinghe.flutter.flutter_radio_player' + compileSdkVersion 36 compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -50,7 +37,7 @@ 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" @@ -58,6 +45,7 @@ dependencies { implementation "androidx.multidex:multidex:2.0.1" + 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" diff --git a/android/settings.gradle b/android/settings.gradle index 95ed40c..be8fa6f 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.2" apply false + id "org.jetbrains.kotlin.android" version "1.8.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 351205e..88a33a0 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) { @@ -82,7 +79,7 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met } 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) @@ -99,7 +96,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) 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 5033e28..aae4305 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,9 +5,17 @@ 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.session.MediaSessionCompat -import com.google.android.exoplayer2.* +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.source.MediaSource @@ -51,9 +59,6 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener 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 val eventBus: EventBus = EventBus.getDefault() @@ -62,28 +67,42 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener private lateinit var mediaSession: MediaSessionCompat + override fun onCreate() { + Log.i(TAG, "FlutterRadioPlayerService::onCreate") + } + override fun onDestroy() { Log.i(TAG, "::: onDestroy :::") - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mediaSession.release() + mediaSessionConnector?.setPlayer(null) + playerNotificationManager?.setPlayer(null) + + if (exoPlayer != null) { + exoPlayer?.release() + exoPlayer == null } - exoPlayer?.release() - exoPlayer = null + if (mediaSessionConnector != null) { + mediaSessionConnector = null + } + mediaSession.setActive(false) + mediaSession.release() + super.onDestroy() + } + + override fun onTaskRemoved(rootIntent: Intent?) { + Log.i(TAG, ":::: FlutterRadioPlayerService.onTaskRemoved ::::") if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { stopForeground(STOP_FOREGROUND_REMOVE) } else { stopForeground(false) } - mediaSessionConnector?.setPlayer(null) - playerNotificationManager?.setPlayer(null) - - super.onDestroy() + 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 ::::") @@ -100,12 +119,41 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener 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() + + // exoplayer configuration + exoPlayer?.let { + it.addListener(FRPPlayerListener(this, exoPlayer, playerNotificationManager, eventBus)) + it.playWhenReady = false + } + + exoPlayer?.addAnalyticsListener(EventLogger()) + + // set connector and player mediaSessionConnector = MediaSessionConnector(mediaSession) + mediaSessionConnector?.setPlayer(exoPlayer) playerNotificationManager?.apply { - Log.i(TAG, ":::: Applying configurations... ::::") + Log.i(TAG, "Applying configurations...") // default buttons // TODO allow developer to choose actions on player init instead of hardcoding them here @@ -126,6 +174,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener setUseFastForwardActionInCompactView(false) setUseRewindActionInCompactView(false) + setPlayer(exoPlayer) setMediaSessionToken(mediaSession.sessionToken) } @@ -140,47 +189,6 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener throw FRPException("Empty media sources") } - // TODO maybe find a better init location where the player will always be initialized correctly (onCreate wasn't called always) - // build exoplayer - if (exoPlayer == null) { - 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() - ) - .setRenderersFactory( - DefaultRenderersFactory(context) - .forceEnableMediaCodecAsynchronousQueueing() - ) - .setHandleAudioBecomingNoisy(true) - .build() - - // exoplayer configuration - exoPlayer?.let { - it.addListener( - FRPPlayerListener( - this, - exoPlayer, - playerNotificationManager, - eventBus - ) - ) - it.playWhenReady = false - } - - exoPlayer?.addAnalyticsListener(EventLogger()) - mediaSessionConnector?.setPlayer(exoPlayer) - playerNotificationManager?.setPlayer(exoPlayer) - } - this.mediaSourceList = sourceList.sortedByDescending { it.isPrimary } if (this.mediaSourceList.none { frpAudioSource -> frpAudioSource.isPrimary }) { @@ -260,11 +268,6 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener } fun pause() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - stopForeground(STOP_FOREGROUND_DETACH) - } else { - stopForeground(false) - } exoPlayer?.pause() } 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 d4c3e97..bc157bd 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,16 +1,3 @@ -buildscript { - ext.kotlin_version = '1.8.0' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:7.4.1' - 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 6b66533..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.5-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 44e62bc..3b75051 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.2' apply false + id "org.jetbrains.kotlin.android" version "1.8.10" apply false +} + +include ":app" \ No newline at end of file diff --git a/example/pubspec.lock b/example/pubspec.lock index 1d977c8..13a3a47 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: async - sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.11.0" + version: "2.13.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" characters: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + 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: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.17.2" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: 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,12 +76,36 @@ packages: path: ".." relative: true source: path - version: "2.2.0" + version: "3.0.0" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + url: "https://pub.dev" + source: hosted + version: "10.0.9" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" lints: dependency: transitive description: @@ -94,87 +118,87 @@ packages: dependency: transitive description: name: matcher - sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.8.3" + 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: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.10.1" 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: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.7.4" vector_math: dependency: transitive description: @@ -183,14 +207,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" - web: + vm_service: dependency: transitive description: - name: web - sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + name: vm_service + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "0.1.4-beta" + version: "15.0.0" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=2.5.0" + dart: ">=3.7.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/pubspec.yaml b/pubspec.yaml index 481b8ef..e672f48 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.2.0 +version: 3.0.0 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: From 7682cc91bf7f02454aa9d38ee955d329e1e95bb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Fri, 13 Feb 2026 17:08:33 +0100 Subject: [PATCH 16/18] fix closing player service --- android/settings.gradle | 2 +- .../FlutterRadioPlayerPlugin.kt | 3 --- .../core/services/FRPCoreService.kt | 20 +++++++++---------- pubspec.yaml | 2 +- 4 files changed, 11 insertions(+), 16 deletions(-) diff --git a/android/settings.gradle b/android/settings.gradle index be8fa6f..df31581 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -19,7 +19,7 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" // apply true id "com.android.application" version "8.12.2" apply false - id "org.jetbrains.kotlin.android" version "1.8.10" apply false + id "org.jetbrains.kotlin.android" version "2.1.0" apply false } include ":app" 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 88a33a0..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 @@ -72,9 +72,6 @@ class FlutterRadioPlayerPlugin : FlutterPlugin, ActivityAware, MethodChannel.Met } override fun onDetachedFromActivity() { - if (isBound) { - frpRadioPlayerService.onDestroy() - } EventBus.getDefault().unregister(this) } 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 aae4305..266133d 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 @@ -69,6 +69,10 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener override fun onCreate() { Log.i(TAG, "FlutterRadioPlayerService::onCreate") + + // media session + mediaSession = MediaSessionCompat(this, mediaSessionId) + mediaSession.isActive = true } override fun onDestroy() { @@ -78,16 +82,14 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener mediaSessionConnector?.setPlayer(null) playerNotificationManager?.setPlayer(null) - if (exoPlayer != null) { - exoPlayer?.release() - exoPlayer == null - } + exoPlayer?.release() + exoPlayer == null + + mediaSessionConnector = null - if (mediaSessionConnector != null) { - mediaSessionConnector = null - } mediaSession.setActive(false) mediaSession.release() + super.onDestroy() } @@ -115,10 +117,6 @@ 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 diff --git a/pubspec.yaml b/pubspec.yaml index e672f48..7ea196a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ 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: 3.0.0 +version: 3.0.1 homepage: "https://github.com/Sithira/FlutterRadioPlayer" environment: From f1a92f8010146a2468948247ee17294eb01f0fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Mon, 16 Feb 2026 15:21:16 +0100 Subject: [PATCH 17/18] try to fix media session null if service's onCreate is not called --- .../gradle/wrapper/gradle-wrapper.properties | 5 +- android/settings.gradle | 4 +- .../core/services/FRPCoreService.kt | 45 +++++++--- example/android/settings.gradle | 4 +- example/lib/main.dart | 8 ++ example/pubspec.lock | 88 +++++++++---------- pubspec.yaml | 2 +- 7 files changed, 93 insertions(+), 63 deletions(-) diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 32b25ba..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-7.5.1-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index df31581..99772b7 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" // apply true - id "com.android.application" version "8.12.2" apply false - id "org.jetbrains.kotlin.android" version "2.1.0" apply false + id "com.android.application" version "8.12.3" apply false + id "org.jetbrains.kotlin.android" version "2.3.10" apply false } include ":app" 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 266133d..855a0ca 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 @@ -61,23 +61,35 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener private var currentPlayingItem: FRPCurrentSource? = null private val binder = LocalBinder() private var exoPlayer: ExoPlayer? = null + private var playerListener: FRPPlayerListener? = null private val eventBus: EventBus = EventBus.getDefault() 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") // media session mediaSession = MediaSessionCompat(this, mediaSessionId) - mediaSession.isActive = true + mediaSession?.isActive = true } override fun onDestroy() { Log.i(TAG, "::: onDestroy :::") + releaseResources() + super.onDestroy() + } + + private fun releaseResources() { + Log.i(TAG, ":::: Releasing resources ::::") + if (playerListener != null) { + exoPlayer?.removeListener(playerListener!!) + } + + mediaSession?.release() mediaSessionConnector?.setPlayer(null) playerNotificationManager?.setPlayer(null) @@ -86,15 +98,11 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener exoPlayer == null mediaSessionConnector = null - - mediaSession.setActive(false) - mediaSession.release() - - super.onDestroy() } 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 { @@ -136,18 +144,27 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener .setHandleAudioBecomingNoisy(true) .build() + val listener = FRPPlayerListener(this, exoPlayer, playerNotificationManager, eventBus) + playerListener = listener // exoplayer configuration exoPlayer?.let { - it.addListener(FRPPlayerListener(this, exoPlayer, playerNotificationManager, eventBus)) + it.addListener(listener) it.playWhenReady = false } exoPlayer?.addAnalyticsListener(EventLogger()) + if (mediaSession == null) { + mediaSession = MediaSessionCompat(this, mediaSessionId) + mediaSession?.isActive = true + } - // set connector and player - mediaSessionConnector = MediaSessionConnector(mediaSession) - mediaSessionConnector?.setPlayer(exoPlayer) + if (mediaSession != null) { + val session: MediaSessionCompat = mediaSession!! + // set connector and player + mediaSessionConnector = MediaSessionConnector(session) + mediaSessionConnector?.setPlayer(exoPlayer) + } playerNotificationManager?.apply { @@ -173,7 +190,9 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener setUseRewindActionInCompactView(false) setPlayer(exoPlayer) - setMediaSessionToken(mediaSession.sessionToken) + if (mediaSession != null) { + setMediaSessionToken(mediaSession!!.sessionToken) + } } Log.i(TAG, ":::: END OF onStartCommand IN SERVICE ::::") @@ -349,8 +368,10 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener return when (val type = Util.inferContentType(mediaUrl)) { 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/settings.gradle b/example/android/settings.gradle index 3b75051..19d2288 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" // apply true - id "com.android.application" version '8.12.2' apply false - id "org.jetbrains.kotlin.android" version "1.8.10" apply false + 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 13a3a47..a547b00 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.11.0" boolean_selector: dependency: transitive description: name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.1" characters: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.3.0" clock: dependency: transitive description: name: clock - sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.1.1" collection: dependency: transitive description: name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a url: "https://pub.dev" source: hosted - version: "1.19.1" + version: "1.18.0" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.1" flutter: dependency: "direct main" description: flutter @@ -76,7 +76,7 @@ packages: path: ".." relative: true source: path - version: "3.0.0" + version: "3.0.1" flutter_test: dependency: "direct dev" description: flutter @@ -86,26 +86,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "10.0.0" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 url: "https://pub.dev" source: hosted - version: "3.0.9" + version: "2.0.1" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "2.0.1" lints: dependency: transitive description: @@ -118,87 +118,87 @@ packages: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.16+1" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.11.0" path: dependency: transitive description: name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.9.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.0" + version: "0.0.99" source_span: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.0" stack_trace: dependency: transitive description: name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" url: "https://pub.dev" source: hosted - version: "1.12.1" + version: "1.11.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.2" string_scanner: dependency: transitive description: name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" url: "https://pub.dev" source: hosted - version: "1.4.1" + version: "1.2.0" term_glyph: dependency: transitive description: name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.1" test_api: dependency: transitive description: name: test_api - sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" url: "https://pub.dev" source: hosted - version: "0.7.4" + version: "0.6.1" vector_math: dependency: transitive description: @@ -211,10 +211,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "13.0.0" sdks: - dart: ">=3.7.0-0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.2.0-0 <4.0.0" + flutter: ">=2.5.0" diff --git a/pubspec.yaml b/pubspec.yaml index 7ea196a..136b98c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ 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: 3.0.1 +version: 3.0.2 homepage: "https://github.com/Sithira/FlutterRadioPlayer" environment: From 9f5127333c27324f26b2dc5f52d9e2019b58ed0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20Filipovi=C4=87?= Date: Sun, 22 Feb 2026 18:04:48 +0100 Subject: [PATCH 18/18] fix onStop being triggered when notification swiped --- .../core/services/FRPCoreService.kt | 55 +++++++++++++-- example/pubspec.lock | 68 +++++++++---------- pubspec.yaml | 2 +- 3 files changed, 84 insertions(+), 41 deletions(-) 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 855a0ca..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 @@ -10,7 +10,9 @@ 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 android.support.v4.media.session.PlaybackStateCompat import com.google.android.exoplayer2.C import com.google.android.exoplayer2.DefaultLivePlaybackSpeedControl import com.google.android.exoplayer2.ExoPlayer @@ -72,8 +74,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener Log.i(TAG, "FlutterRadioPlayerService::onCreate") // media session - mediaSession = MediaSessionCompat(this, mediaSessionId) - mediaSession?.isActive = true + setupMediaSession() } override fun onDestroy() { @@ -89,13 +90,15 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener exoPlayer?.removeListener(playerListener!!) } + mediaSession?.isActive = false mediaSession?.release() - + mediaSessionConnector?.setClearMediaItemsOnStop(false) mediaSessionConnector?.setPlayer(null) + playerNotificationManager?.setPlayer(null) exoPlayer?.release() - exoPlayer == null + exoPlayer = null mediaSessionConnector = null } @@ -155,8 +158,7 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener exoPlayer?.addAnalyticsListener(EventLogger()) if (mediaSession == null) { - mediaSession = MediaSessionCompat(this, mediaSessionId) - mediaSession?.isActive = true + setupMediaSession() } if (mediaSession != null) { @@ -164,6 +166,31 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener // 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 { @@ -200,6 +227,22 @@ class FRPCoreService : Service(), PlayerNotificationManager.NotificationListener 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()) { diff --git a/example/pubspec.lock b/example/pubspec.lock index a547b00..fb74061 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -21,26 +21,26 @@ packages: dependency: transitive description: name: characters - sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 url: "https://pub.dev" source: hosted - version: "1.3.0" + 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: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.1" cupertino_icons: dependency: "direct main" description: @@ -53,10 +53,10 @@ packages: 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,7 +76,7 @@ packages: path: ".." relative: true source: path - version: "3.0.1" + version: "3.0.3" flutter_test: dependency: "direct dev" description: flutter @@ -86,26 +86,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa" + sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" url: "https://pub.dev" source: hosted - version: "10.0.0" + version: "11.0.2" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0 + sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.10" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47 + sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.2" lints: dependency: transitive description: @@ -118,39 +118,39 @@ packages: dependency: transitive description: name: matcher - sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 url: "https://pub.dev" source: hosted - version: "0.12.16+1" + version: "0.12.17" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c url: "https://pub.dev" source: hosted - version: "1.11.0" + version: "1.16.0" path: dependency: transitive description: name: path - sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" url: "https://pub.dev" source: hosted - version: "1.9.0" + 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: @@ -163,18 +163,18 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7 + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.4" string_scanner: dependency: transitive description: @@ -195,18 +195,18 @@ packages: dependency: transitive description: name: test_api - sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b" + sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" url: "https://pub.dev" source: hosted - version: "0.6.1" + 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: @@ -216,5 +216,5 @@ packages: source: hosted version: "13.0.0" sdks: - dart: ">=3.2.0-0 <4.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/pubspec.yaml b/pubspec.yaml index 136b98c..ed721a5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ 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: 3.0.2 +version: 3.0.3 homepage: "https://github.com/Sithira/FlutterRadioPlayer" environment: