Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ buildscript {

// version numbers
ext.kotlin_version = '1.3.50'
ext.exo_player_version = '2.11.2'
ext.exo_player_version = '2.12.1'

repositories {
google()
Expand Down Expand Up @@ -51,4 +51,6 @@ dependencies {
implementation "com.google.android.exoplayer:exoplayer:$exo_player_version"
implementation "com.google.android.exoplayer:extension-mediasession:$exo_player_version"

compileOnly files('tmplibs/flutter.jar')

}
3 changes: 2 additions & 1 deletion android/gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#Thu Oct 15 12:19:13 EET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
5 changes: 3 additions & 2 deletions android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@
<uses-permission android:name="android.permission.INTERNET" />

<!-- Services for the plugin -->
<application android:usesCleartextTraffic="true">
<service android:name=".core.StreamingCore"/>
<application android:usesCleartextTraffic="true"
android:supportsRtl="true">
<service android:name=".core.StreamingCore" android:stopWithTask="true"/>
</application>
</manifest>

Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package me.sithiramunasinghe.flutter.flutter_radio_player

import android.app.Activity
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.content.*
import android.content.res.AssetFileDescriptor
import android.content.res.AssetManager
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.os.IBinder
import androidx.annotation.NonNull
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.EventChannel.EventSink
Expand All @@ -17,18 +26,23 @@ import io.flutter.plugin.common.PluginRegistry.Registrar
import me.sithiramunasinghe.flutter.flutter_radio_player.core.PlayerItem
import me.sithiramunasinghe.flutter.flutter_radio_player.core.StreamingCore
import me.sithiramunasinghe.flutter.flutter_radio_player.core.enums.PlayerMethods
import java.util.*
import java.util.logging.Logger
import kotlin.concurrent.schedule


/** FlutterRadioPlayerPlugin */
public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
private var logger = Logger.getLogger(FlutterRadioPlayerPlugin::javaClass.name)
public var activity: Activity? = null

private lateinit var methodChannel: MethodChannel

private var mEventSink: EventSink? = null
private var mEventMetaDataSink: EventSink? = null

companion object {

@JvmStatic
fun registerWith(registrar: Registrar) {
val instance = FlutterRadioPlayerPlugin()
Expand All @@ -45,9 +59,11 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
var isBound = false
lateinit var applicationContext: Context
lateinit var coreService: StreamingCore
var bitmap: Bitmap? = null
lateinit var serviceIntent: Intent
}


override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
buildEngine(flutterPluginBinding.applicationContext, flutterPluginBinding.binaryMessenger)
}
Expand All @@ -69,6 +85,11 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
play()
result.success(null)
}
PlayerMethods.NEW_PLAY.value -> {
logger.info("newPlay service invoked")
newPlay()
result.success(null)
}
PlayerMethods.PAUSE.value -> {
logger.info("pause service invoked")
pause()
Expand All @@ -79,6 +100,13 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
stop()
result.success(null)
}
PlayerMethods.SET_TITLE.value -> {
logger.info("setTitle service invoked")
val title = call.argument<String>("title")!!
val subTitle = call.argument<String>("subtitle")!!
coreService.setTitle(title,subTitle)
result.success(null)
}
PlayerMethods.INIT.value -> {
logger.info("start service invoked")
init(call)
Expand All @@ -93,6 +121,12 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
PlayerMethods.SET_URL.value -> {
logger.info("Set url invoked")
val url = call.argument<String>("streamUrl")!!
val coverByteArray = call.argument<ByteArray>("coverImage")
if (coverByteArray != null){
bitmap = BitmapFactory.decodeByteArray(coverByteArray,0 ,coverByteArray.size)
coreService.iconBitmap = bitmap
}

val playWhenReady = call.argument<String>("playWhenReady")!!
setUrl(url, playWhenReady)
}
Expand All @@ -118,7 +152,6 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
applicationContext = context
serviceIntent = Intent(applicationContext, StreamingCore::class.java)


initEventChannelStatus(messenger)
initEventChannelMetaData(messenger)

Expand Down Expand Up @@ -179,14 +212,25 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {

private fun init(methodCall: MethodCall) {
logger.info("Attempting to initialize service...")

val coverByteArray = methodCall.argument<ByteArray>("coverImage")
if (coverByteArray != null){
bitmap = BitmapFactory.decodeByteArray(coverByteArray,0 ,coverByteArray.size)
}

if (!isBound) {
logger.info("Service not bound, binding now....")
serviceIntent = setIntentData(serviceIntent, buildPlayerDetailsMeta(methodCall))
applicationContext.bindService(serviceIntent, serviceConnection, Context.BIND_IMPORTANT)
applicationContext.startService(serviceIntent)
} else {
Timer("SettingUp", false).schedule(500) {
coreService.reEmmitSatus()
}
}
}


private fun isPlaying(): Boolean {
logger.info("Attempting to get playing status....")
val playingStatus = coreService.isPlaying()
Expand All @@ -199,11 +243,17 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
if (isPlaying()) pause() else play()
}


private fun play() {
logger.info("Attempting to play music....")
logger.info("Attempting to Play music....")
coreService.play()
}

private fun newPlay() {
logger.info("Attempting to newPlay music....")
coreService.newPlay()
}

private fun pause() {
logger.info("Attempting to pause music....")
coreService.pause()
Expand Down Expand Up @@ -244,14 +294,20 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
override fun onServiceDisconnected(name: ComponentName?) {
isBound = false
// coreService = null
logger.info("Service Disconnected...")
}

override fun onServiceConnected(name: ComponentName?, binder: IBinder?) {
val localBinder = binder as StreamingCore.LocalBinder
coreService = localBinder.service
coreService.activity = this@FlutterRadioPlayerPlugin.activity
isBound = true
// coreService.reEmmitSatus()
logger.info("Service Connection Established...")
logger.info("Service bounded...")

coreService.iconBitmap = bitmap

}
}

Expand All @@ -260,6 +316,7 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
*/
private var broadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {

if (intent != null) {
val returnStatus = intent.getStringExtra("status")
logger.info("Received status: $returnStatus")
Expand All @@ -281,4 +338,17 @@ public class FlutterRadioPlayerPlugin : FlutterPlugin, MethodCallHandler {
}
}
}

override fun onDetachedFromActivity() {
}

override fun onReattachedToActivityForConfigChanges(p0: ActivityPluginBinding) {
}

override fun onAttachedToActivity(p0: ActivityPluginBinding) {
this.activity = p0.activity
}

override fun onDetachedFromActivityForConfigChanges() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ const val FLUTTER_RADIO_PLAYER_STOPPED = "flutter_radio_stopped"
const val FLUTTER_RADIO_PLAYER_PLAYING = "flutter_radio_playing"
const val FLUTTER_RADIO_PLAYER_PAUSED = "flutter_radio_paused"
const val FLUTTER_RADIO_PLAYER_LOADING = "flutter_radio_loading"
//const val FLUTTER_RADIO_PLAYER_READY = "flutter_radio_ready"

const val FLUTTER_RADIO_PLAYER_ERROR = "flutter_radio_error"
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package me.sithiramunasinghe.flutter.flutter_radio_player.core

import com.google.android.exoplayer2.C
import com.google.android.exoplayer2.ControlDispatcher
import com.google.android.exoplayer2.Player
import com.google.android.exoplayer2.Timeline

/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/** Default [ControlDispatcher]. */
class CustomControlDispatcher @JvmOverloads constructor(
/** Returns the fast forward increment in milliseconds. */
@set:Deprecated("""Create a new instance instead and pass the new instance to the UI component. This
makes sure the UI gets updated and is in sync with the new values.""") var fastForwardIncrementMs: Long = CustomControlDispatcher.Companion.DEFAULT_FAST_FORWARD_MS.toLong(),
/** Returns the rewind increment in milliseconds. */
@set:Deprecated("""Create a new instance instead and pass the new instance to the UI component. This
makes sure the UI gets updated and is in sync with the new values.""") var rewindIncrementMs: Long = CustomControlDispatcher.Companion.DEFAULT_REWIND_MS.toLong()) : ControlDispatcher {
private val window: Timeline.Window

override fun dispatchSetPlayWhenReady(player: Player, playWhenReady: Boolean): Boolean {
if (!playWhenReady)
player.playWhenReady = playWhenReady
else{
player.stop()
player.prepare()
player.playWhenReady = playWhenReady
}
return true
}

override fun dispatchSeekTo(player: Player, windowIndex: Int, positionMs: Long): Boolean {
player.seekTo(windowIndex, positionMs)
return true
}

override fun dispatchPrevious(player: Player): Boolean {
val timeline = player.currentTimeline
if (timeline.isEmpty || player.isPlayingAd) {
return true
}
val windowIndex = player.currentWindowIndex
timeline.getWindow(windowIndex, window)
val previousWindowIndex = player.previousWindowIndex
if (previousWindowIndex != C.INDEX_UNSET
&& (player.currentPosition <= CustomControlDispatcher.Companion.MAX_POSITION_FOR_SEEK_TO_PREVIOUS
|| window.isDynamic && !window.isSeekable)) {
player.seekTo(previousWindowIndex, C.TIME_UNSET)
} else {
player.seekTo(windowIndex, /* positionMs= */0)
}
return true
}

override fun dispatchNext(player: Player): Boolean {
val timeline = player.currentTimeline
if (timeline.isEmpty || player.isPlayingAd) {
return true
}
val windowIndex = player.currentWindowIndex
val nextWindowIndex = player.nextWindowIndex
if (nextWindowIndex != C.INDEX_UNSET) {
player.seekTo(nextWindowIndex, C.TIME_UNSET)
} else if (timeline.getWindow(windowIndex, window).isLive) {
player.seekTo(windowIndex, C.TIME_UNSET)
}
return true
}

override fun dispatchRewind(player: Player): Boolean {
if (isRewindEnabled && player.isCurrentWindowSeekable) {
CustomControlDispatcher.Companion.seekToOffset(player, -rewindIncrementMs)
}
return true
}

override fun dispatchFastForward(player: Player): Boolean {
if (isFastForwardEnabled && player.isCurrentWindowSeekable) {
CustomControlDispatcher.Companion.seekToOffset(player, fastForwardIncrementMs)
}
return true
}

override fun dispatchSetRepeatMode(player: Player, @Player.RepeatMode repeatMode: Int): Boolean {
player.repeatMode = repeatMode
return true
}

override fun dispatchSetShuffleModeEnabled(player: Player, shuffleModeEnabled: Boolean): Boolean {
player.shuffleModeEnabled = shuffleModeEnabled
return true
}

override fun dispatchStop(player: Player, reset: Boolean): Boolean {
player.stop(reset)
return true
}

override fun isRewindEnabled(): Boolean {
return rewindIncrementMs > 0
}

override fun isFastForwardEnabled(): Boolean {
return fastForwardIncrementMs > 0
}

companion object {
/** The default fast forward increment, in milliseconds. */
const val DEFAULT_FAST_FORWARD_MS = 15000

/** The default rewind increment, in milliseconds. */
const val DEFAULT_REWIND_MS = 5000
private const val MAX_POSITION_FOR_SEEK_TO_PREVIOUS = 3000

// Internal methods.
private fun seekToOffset(player: Player, offsetMs: Long) {
var positionMs = player.currentPosition + offsetMs
val durationMs = player.duration
if (durationMs != C.TIME_UNSET) {
positionMs = Math.min(positionMs, durationMs)
}
positionMs = Math.max(positionMs, 0)
player.seekTo(player.currentWindowIndex, positionMs)
}
}
/**
* Creates an instance with the given increments.
*
* @param fastForwardIncrementMs The fast forward increment in milliseconds. A non-positive value
* disables the fast forward operation.
* @param rewindIncrementMs The rewind increment in milliseconds. A non-positive value disables
* the rewind operation.
*/
/** Creates an instance. */
init {
window = Timeline.Window()
}
}
Loading