diff --git a/packages/player/.gitignore b/packages/player/.gitignore
index a8a202ba..2292831d 100644
--- a/packages/player/.gitignore
+++ b/packages/player/.gitignore
@@ -6,7 +6,9 @@
build/
-
+example/linux/
+example/macos/
+example/windows/
# Created by https://www.gitignore.io/api/dart,android,flutter,jetbrains,visualstudio,androidstudio,visualstudiocode
# Edit at https://www.gitignore.io/?templates=dart,android,flutter,jetbrains,visualstudio,androidstudio,visualstudiocode
diff --git a/packages/player/android/build.gradle b/packages/player/android/build.gradle
index 7505b818..96ffe242 100644
--- a/packages/player/android/build.gradle
+++ b/packages/player/android/build.gradle
@@ -4,14 +4,14 @@ version '1.0.4'
buildscript {
ext.kotlin_version = '1.9.20'
- ext.exoplayer_version = '2.19.1'
+ ext.media3_version = '1.4.1'
repositories {
google()
mavenCentral()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.2.2'
+ classpath 'com.android.tools.build:gradle:8.1.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -44,20 +44,24 @@ android {
lintOptions {
disable 'InvalidPackage'
}
-
+ namespace = "br.com.suamusica.player"
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3'
- implementation "com.google.android.exoplayer:exoplayer:$exoplayer_version"
- implementation "com.google.android.exoplayer:extension-mediasession:$exoplayer_version"
implementation "androidx.media:media:1.7.0"
implementation "org.jetbrains.kotlin:kotlin-reflect"
+ //MEDIA3 DEPENDENCIES
+ implementation "androidx.media3:media3-exoplayer:$media3_version"
+ implementation "androidx.media3:media3-exoplayer-hls:$media3_version"
+ implementation "androidx.media3:media3-session:$media3_version"
+ implementation "androidx.media3:media3-common:$media3_version"
+ implementation "androidx.media3:media3-ui:$media3_version"
+
+// implementation files('/Users/lucastonussi/flutter/bin/cache/artifacts/engine/android-x64/flutter.jar')
+ implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2'
- // Glide dependencies
- implementation "com.github.bumptech.glide:glide:4.12.0"
-// implementation files('/Users/alantrope/flutter/bin/cache/artifacts/engine/android-x64/flutter.jar')
- kapt "com.github.bumptech.glide:compiler:4.12.0"
+ implementation "com.google.code.gson:gson:2.10.1"
}
diff --git a/packages/player/android/gradle/wrapper/gradle-wrapper.properties b/packages/player/android/gradle/wrapper/gradle-wrapper.properties
index ffed3a25..fae08049 100644
--- a/packages/player/android/gradle/wrapper/gradle-wrapper.properties
+++ b/packages/player/android/gradle/wrapper/gradle-wrapper.properties
@@ -1,5 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/packages/player/android/src/main/AndroidManifest.xml b/packages/player/android/src/main/AndroidManifest.xml
index 0d48337f..9fd545b9 100644
--- a/packages/player/android/src/main/AndroidManifest.xml
+++ b/packages/player/android/src/main/AndroidManifest.xml
@@ -1,22 +1,19 @@
+ xmlns:tools="http://schemas.android.com/tools">
+
+
+
+
-
-
-
-
-
-
-
+
\ No newline at end of file
diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/FavoriteModeActionProvider.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/FavoriteModeActionProvider.kt
deleted file mode 100644
index 31bfcd74..00000000
--- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/FavoriteModeActionProvider.kt
+++ /dev/null
@@ -1,28 +0,0 @@
-package br.com.suamusica.player
-
-import android.app.PendingIntent
-import android.content.Context
-import android.content.Intent
-import android.os.Build
-import android.os.Bundle
-import android.support.v4.media.session.PlaybackStateCompat
-import android.util.Log
-import androidx.core.app.NotificationCompat
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
-import java.util.*
-
-class FavoriteModeActionProvider(private val context: Context) :
- MediaSessionConnector.CustomActionProvider {
-
- override fun onCustomAction(player: Player, action: String, extras: Bundle?) {
- PlayerSingleton.favorite(action == "Favoritar")
- }
-
- override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? {
- if (PlayerSingleton.lastFavorite) {
- return PlaybackStateCompat.CustomAction.Builder("Desfavoritar", "Desfavoritar", R.drawable.ic_unfavorite_notification_player,).build()
- }
- return PlaybackStateCompat.CustomAction.Builder("Favoritar", "Favoritar", R.drawable.ic_favorite_notification_player,).build()
- }
-}
\ No newline at end of file
diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/Media.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/Media.kt
index 3b6b2763..13dfbb5c 100644
--- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/Media.kt
+++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/Media.kt
@@ -1,10 +1,27 @@
package br.com.suamusica.player
-class Media(
- val name: String,
- val author: String,
- val url: String,
- val coverUrl: String,
- val bigCoverUrl: String?,
- val isFavorite: Boolean?
+import com.google.gson.annotations.SerializedName
+
+data class Media(
+ @SerializedName("id") val id: Long,
+ @SerializedName("name") val name: String,
+ @SerializedName("ownerId") val ownerId: Int,
+ @SerializedName("albumId") val albumId: Long,
+ @SerializedName("albumTitle") val albumTitle: String,
+ @SerializedName("author") val author: String,
+ @SerializedName("url") val url: String,
+ @SerializedName("is_local") val isLocal: Boolean,
+ @SerializedName("cover_url") val coverUrl: String,
+ @SerializedName("bigCover") val bigCoverUrl: String,
+ @SerializedName("is_verified") val isVerified: Boolean,
+ @SerializedName("shared_url") val shareUrl: String,
+ @SerializedName("playlist_id") val playlistId: Long,
+ @SerializedName("is_spot") val isSpot: Boolean,
+ @SerializedName("isFavorite") val isFavorite: Boolean?,
+ @SerializedName("fallbackUrl") val fallbackUrl: String,
+ @SerializedName("indexInPlaylist") val indexInPlaylist: Int?,
+ @SerializedName("catid") val categoryId: Int,
+ @SerializedName("playlistTitle") val playlistTitle: String,
+ @SerializedName("playlistCoverUrl") val playlistCoverUrl: String,
+ @SerializedName("playlistOwnerId") val playlistOwnerId: Int
)
\ No newline at end of file
diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaButtonEventHandler.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaButtonEventHandler.kt
index 5c43182e..2a9611f2 100644
--- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaButtonEventHandler.kt
+++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaButtonEventHandler.kt
@@ -1,62 +1,403 @@
package br.com.suamusica.player
import android.content.Intent
+import android.os.Build
+import android.os.Bundle
import android.util.Log
import android.view.KeyEvent
-import com.google.android.exoplayer2.Player
-import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector
+import androidx.media3.common.MediaItem
+import androidx.media3.common.Player
+import androidx.media3.common.Player.COMMAND_GET_TIMELINE
+import androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT
+import androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM
+import androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS
+import androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM
+import androidx.media3.common.util.UnstableApi
+import androidx.media3.session.CommandButton
+import androidx.media3.session.LibraryResult
+import androidx.media3.session.MediaLibraryService
+import androidx.media3.session.MediaSession
+import androidx.media3.session.R.drawable
+import androidx.media3.session.SessionCommand
+import androidx.media3.session.SessionResult
+import br.com.suamusica.player.PlayerPlugin.Companion.DISABLE_REPEAT_MODE
+import br.com.suamusica.player.PlayerPlugin.Companion.ENQUEUE
+import br.com.suamusica.player.PlayerPlugin.Companion.FAVORITE
+import br.com.suamusica.player.PlayerPlugin.Companion.ID_FAVORITE_ARGUMENT
+import br.com.suamusica.player.PlayerPlugin.Companion.ID_URI_ARGUMENT
+import br.com.suamusica.player.PlayerPlugin.Companion.INDEXES_TO_REMOVE
+import br.com.suamusica.player.PlayerPlugin.Companion.IS_FAVORITE_ARGUMENT
+import br.com.suamusica.player.PlayerPlugin.Companion.LOAD_ONLY
+import br.com.suamusica.player.PlayerPlugin.Companion.NEW_URI_ARGUMENT
+import br.com.suamusica.player.PlayerPlugin.Companion.PLAY_FROM_QUEUE_METHOD
+import br.com.suamusica.player.PlayerPlugin.Companion.POSITIONS_LIST
+import br.com.suamusica.player.PlayerPlugin.Companion.POSITION_ARGUMENT
+import br.com.suamusica.player.PlayerPlugin.Companion.REMOVE_ALL
+import br.com.suamusica.player.PlayerPlugin.Companion.REMOVE_IN
+import br.com.suamusica.player.PlayerPlugin.Companion.REORDER
+import br.com.suamusica.player.PlayerPlugin.Companion.REPEAT_MODE
+import br.com.suamusica.player.PlayerPlugin.Companion.SET_REPEAT_MODE
+import br.com.suamusica.player.PlayerPlugin.Companion.TIME_POSITION_ARGUMENT
+import br.com.suamusica.player.PlayerPlugin.Companion.TOGGLE_SHUFFLE
+import br.com.suamusica.player.PlayerPlugin.Companion.UPDATE_FAVORITE
+import br.com.suamusica.player.PlayerPlugin.Companion.UPDATE_IS_PLAYING
+import br.com.suamusica.player.PlayerPlugin.Companion.UPDATE_MEDIA_URI
+import com.google.common.collect.ImmutableList
+import com.google.common.util.concurrent.Futures
+import com.google.common.util.concurrent.ListenableFuture
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
+import com.google.gson.GsonBuilder
+import kotlinx.serialization.decodeFromString
+import kotlinx.serialization.json.Json
-class MediaButtonEventHandler : MediaSessionConnector.MediaButtonEventHandler {
+@UnstableApi
+class MediaButtonEventHandler(
+ private val mediaService: MediaService,
+) : MediaSession.Callback {
+ private val BROWSABLE_ROOT = "/"
+ private val EMPTY_ROOT = "@empty@"
+ override fun onConnect(
+ session: MediaSession,
+ controller: MediaSession.ControllerInfo
+ ): MediaSession.ConnectionResult {
+ Log.d("Player", "onConnect")
+ val sessionCommands =
+ MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon().apply {
+ add(SessionCommand("notification_next", Bundle.EMPTY))
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ add(SessionCommand("notification_previous", Bundle.EMPTY))
+ }
+ add(SessionCommand("notification_favoritar", Bundle.EMPTY))
+ add(SessionCommand("notification_desfavoritar", Bundle.EMPTY))
+ add(SessionCommand("seek", session.token.extras))
+ add(SessionCommand("pause", Bundle.EMPTY))
+ add(SessionCommand("stop", Bundle.EMPTY))
+ add(SessionCommand("next", Bundle.EMPTY))
+ add(SessionCommand("previous", Bundle.EMPTY))
+ add(SessionCommand(UPDATE_FAVORITE, session.token.extras))
+ add(SessionCommand(FAVORITE, session.token.extras))
+ add(SessionCommand(TOGGLE_SHUFFLE, Bundle.EMPTY))
+ add(SessionCommand(REPEAT_MODE, Bundle.EMPTY))
+ add(SessionCommand(DISABLE_REPEAT_MODE, Bundle.EMPTY))
+ add(SessionCommand(ENQUEUE, session.token.extras))
+ add(SessionCommand(REMOVE_ALL, Bundle.EMPTY))
+ add(SessionCommand(REORDER, session.token.extras))
+ add(SessionCommand(REMOVE_IN, session.token.extras))
+ add(SessionCommand(SET_REPEAT_MODE, session.token.extras))
+ add(SessionCommand("prepare", session.token.extras))
+ add(SessionCommand("playFromQueue", session.token.extras))
+ add(SessionCommand("play", Bundle.EMPTY))
+ add(SessionCommand("remove_notification", Bundle.EMPTY))
+ add(SessionCommand("send_notification", session.token.extras))
+ add(SessionCommand("ads_playing", Bundle.EMPTY))
+ add(SessionCommand("onTogglePlayPause", Bundle.EMPTY))
+ add(SessionCommand(UPDATE_MEDIA_URI, session.token.extras))
+ add(SessionCommand(UPDATE_IS_PLAYING, session.token.extras))
+ }.build()
- override fun onMediaButtonEvent(player: Player, intent: Intent): Boolean {
- onMediaButtonEventHandler(intent)
- return true
+ val playerCommands =
+ MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon()
+ .remove(COMMAND_SEEK_TO_PREVIOUS)
+ .remove(COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM)
+ .remove(COMMAND_SEEK_TO_NEXT)
+ .remove(COMMAND_SEEK_TO_NEXT_MEDIA_ITEM)
+ .add(COMMAND_GET_TIMELINE)
+ .build()
+
+ return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
+ .setAvailableSessionCommands(sessionCommands)
+ .setAvailablePlayerCommands(playerCommands)
+ .build()
}
- fun onMediaButtonEventHandler(intent: Intent?) {
+ override fun onCustomCommand(
+ session: MediaSession,
+ controller: MediaSession.ControllerInfo,
+ customCommand: SessionCommand,
+ args: Bundle
+ ): ListenableFuture {
+ Log.d("Player", "#MEDIA3# - onCustomCommand ${customCommand.customAction}")
+ if (customCommand.customAction == "notification_favoritar" || customCommand.customAction == "notification_desfavoritar") {
+ val isFavorite = customCommand.customAction == "notification_favoritar"
+ PlayerSingleton.favorite(isFavorite)
+ buildIcons()
+ }
- if (intent == null) {
- return
+ if(customCommand.customAction == UPDATE_IS_PLAYING){
+ buildIcons()
}
- if (Intent.ACTION_MEDIA_BUTTON == intent.action) {
- mediaButtonHandler(intent)
- } else if (intent.hasExtra(FAVORITE)) {
- PlayerSingleton.favorite(intent.getBooleanExtra(FAVORITE, false))
+ if (customCommand.customAction == "seek") {
+ mediaService.seek(args.getLong("position"), args.getBoolean("playWhenReady"))
+ }
+ if (customCommand.customAction == FAVORITE) {
+ val isFavorite = args.getBoolean(IS_FAVORITE_ARGUMENT)
+ val mediaItem = session.player.currentMediaItem!!
+ updateFavoriteMetadata(
+ session.player,
+ session.player.currentMediaItemIndex,
+ mediaItem,
+ isFavorite,
+ )
+ buildIcons()
+ }
+ if (customCommand.customAction == REMOVE_ALL) {
+ mediaService.removeAll()
+ }
+ if (customCommand.customAction == REMOVE_IN) {
+ mediaService.removeIn(
+ args.getIntegerArrayList(INDEXES_TO_REMOVE) ?: emptyList()
+ )
+ }
+ if (customCommand.customAction == REORDER) {
+ val oldIndex = args.getInt("oldIndex")
+ val newIndex = args.getInt("newIndex")
+ val json = args.getString(POSITIONS_LIST)
+ val gson = GsonBuilder().create()
+ val mediaListType = object : TypeToken>?>() {}.type
+ val positionsList: List