From c0313818bc725f990cb10120215f491856c19ead Mon Sep 17 00:00:00 2001 From: lstonussi Date: Thu, 9 May 2024 11:48:00 -0300 Subject: [PATCH 01/14] wip --- packages/player/android/build.gradle | 13 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../android/src/main/AndroidManifest.xml | 20 +- .../player/FavoriteModeActionProvider.kt | 56 +- .../player/MediaButtonEventHandler.kt | 195 +- .../player/MediaControlBroadcastReceiver.kt | 2 +- .../br/com/suamusica/player/MediaService.kt | 1037 +++++---- .../player/MusicPlayerPlaybackPreparer.kt | 238 +- .../suamusica/player/NextActionProvider.kt | 38 +- .../suamusica/player/NotificationBuilder.kt | 51 +- .../com/suamusica/player/PackageValidator.kt | 2 +- .../player/PreviousActionProvider.kt | 38 +- .../media/parser/CustomHlsMasterPlaylist.java | 644 +++--- .../media/parser/CustomHlsPlaylistParser.java | 1960 ++++++++--------- .../parser/SMHlsPlaylistParserFactory.java | 90 +- .../example/.flutter-plugins-dependencies | 2 +- .../player/example/android/app/build.gradle | 23 +- .../android/app/src/main/AndroidManifest.xml | 7 +- packages/player/example/android/build.gradle | 26 +- .../player/example/android/gradle.properties | 6 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- 21 files changed, 2344 insertions(+), 2108 deletions(-) diff --git a/packages/player/android/build.gradle b/packages/player/android/build.gradle index 7505b818..04d7c692 100644 --- a/packages/player/android/build.gradle +++ b/packages/player/android/build.gradle @@ -11,7 +11,7 @@ buildscript { } 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,23 @@ 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.media3:media3-exoplayer:1.2.1" implementation "androidx.media:media:1.7.0" implementation "org.jetbrains.kotlin:kotlin-reflect" + // Glide dependencies implementation "com.github.bumptech.glide:glide:4.12.0" -// implementation files('/Users/alantrope/flutter/bin/cache/artifacts/engine/android-x64/flutter.jar') + implementation 'androidx.media3:media3-exoplayer-hls:1.2.1' + implementation 'androidx.media3:media3-session:1.2.1' + implementation "androidx.media3:media3-common:1.2.1" +// implementation files('/Users/suamusica/flutter/bin/cache/artifacts/engine/android-x64/flutter.jar') kapt "com.github.bumptech.glide:compiler:4.12.0" } 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..21ace208 100644 --- a/packages/player/android/src/main/AndroidManifest.xml +++ b/packages/player/android/src/main/AndroidManifest.xml @@ -1,22 +1,24 @@ + 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 index 31bfcd74..1c4b42ba 100644 --- 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 @@ -1,28 +1,28 @@ -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 +//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 androidx.media3.common.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/MediaButtonEventHandler.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaButtonEventHandler.kt index 5c43182e..98c3366c 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,18 +1,140 @@ package br.com.suamusica.player import android.content.Intent +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.util.UnstableApi +import androidx.media3.session.MediaSession +import androidx.media3.session.SessionCommand +import androidx.media3.session.SessionResult +import com.google.common.util.concurrent.Futures +import com.google.common.util.concurrent.ListenableFuture -class MediaButtonEventHandler : MediaSessionConnector.MediaButtonEventHandler { +@UnstableApi +class MediaButtonEventHandler( + private val mediaService: MediaService, +) : MediaSession.Callback { + override fun onConnect( + session: MediaSession, + controller: MediaSession.ControllerInfo + ): MediaSession.ConnectionResult { + Log.d("Player", "onConnect") + val sessionCommands = MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() + .add(SessionCommand("NEXT", Bundle.EMPTY)) + .add(SessionCommand("seek", session.token.extras)) + .add(SessionCommand("PREVIOUS", Bundle.EMPTY)) + .add(SessionCommand("Favoritar", Bundle.EMPTY)) + .add(SessionCommand("prepare", session.token.extras)) + .add(SessionCommand("play", Bundle.EMPTY)) + .add(SessionCommand("send_notification", session.token.extras)) + .build() + return MediaSession.ConnectionResult.AcceptedResultBuilder(session) + .setAvailableSessionCommands(sessionCommands) + .build() + } + + override fun onAddMediaItems( + mediaSession: MediaSession, + controller: MediaSession.ControllerInfo, + mediaItems: MutableList + ): ListenableFuture> { + Log.d("Player", "onAddMediaItems") + return super.onAddMediaItems(mediaSession, controller, mediaItems) + } + + override fun onCustomCommand( + session: MediaSession, + controller: MediaSession.ControllerInfo, + customCommand: SessionCommand, + args: Bundle + ): ListenableFuture { + if (customCommand.customAction == "send_notification") { + Log.d("Player", "send_notification2") + args.let { + val name = it.getString(PlayerPlugin.NAME_ARGUMENT)!! + val author = it.getString(PlayerPlugin.AUTHOR_ARGUMENT)!! + val url = it.getString(PlayerPlugin.URL_ARGUMENT)!! + val coverUrl = it.getString(PlayerPlugin.COVER_URL_ARGUMENT)!! + val isPlaying = it.getBoolean(PlayerPlugin.IS_PLAYING_ARGUMENT) + val isFavorite = it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT) + val bigCoverUrl = it.getString(PlayerPlugin.BIG_COVER_URL_ARGUMENT) + mediaService.sendNotification( + Media( + name, + author, + url, + coverUrl, + bigCoverUrl, + isFavorite, + ), + isPlaying, + ) + } + } + if (customCommand.customAction == "play") { + mediaService.play() + } + if (customCommand.customAction == "NEXT") { + PlayerSingleton.next() + } + if (customCommand.customAction == "seek") { + mediaService.seek(args.getLong("position"), args.getBoolean("playWhenReady")) + } + if (customCommand.customAction == "Favoritar") { + // Do custom logic here +// saveToFavorites(session.player.currentMediaItem) + PlayerSingleton.favorite( + session.player.currentMediaItem?.mediaMetadata?.extras?.getBoolean( + FAVORITE, + false + ) ?: false + ) - override fun onMediaButtonEvent(player: Player, intent: Intent): Boolean { +// mediaService.setFavorite(it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT)) + + return Futures.immediateFuture( + SessionResult(SessionResult.RESULT_SUCCESS) + ) + } + if (customCommand.customAction == "prepare") { + Log.d("Player", "prepare2") + args.let { + val cookie = it.getString("cookie")!! + val name = it.getString("name")!! + val author = it.getString("author")!! + val url = it.getString("url")!! + val coverUrl = it.getString("coverUrl")!! + val bigCoverUrl = it.getString("bigCoverUrl")!! + var isFavorite: Boolean? = null; + if (it.containsKey(PlayerPlugin.IS_FAVORITE_ARGUMENT)) { + isFavorite = it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT) + } + mediaService.prepare( + cookie, + Media(name, author, url, coverUrl, bigCoverUrl, isFavorite) + ) + } + } + return Futures.immediateFuture( + SessionResult(SessionResult.RESULT_SUCCESS) + ) + } + + + @UnstableApi + override fun onMediaButtonEvent( + session: MediaSession, + controllerInfo: MediaSession.ControllerInfo, + intent: Intent + ): Boolean { onMediaButtonEventHandler(intent) return true } + @UnstableApi fun onMediaButtonEventHandler(intent: Intent?) { if (intent == null) { @@ -20,43 +142,44 @@ class MediaButtonEventHandler : MediaSessionConnector.MediaButtonEventHandler { } if (Intent.ACTION_MEDIA_BUTTON == intent.action) { - mediaButtonHandler(intent) - } else if (intent.hasExtra(FAVORITE)) { - PlayerSingleton.favorite(intent.getBooleanExtra(FAVORITE, false)) - } + val ke = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) + Log.d("Player", "Key: $ke") - } + if (ke!!.action == KeyEvent.ACTION_UP) { + return + } - private fun mediaButtonHandler(intent: Intent) { - val ke = intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT) - Log.d("Player", "Key: $ke") + when (ke.keyCode) { + KeyEvent.KEYCODE_MEDIA_PLAY -> { + mediaService.play() + } - if (ke!!.action == KeyEvent.ACTION_UP) { - return - } + KeyEvent.KEYCODE_MEDIA_PAUSE -> { + mediaService.pause() + } - when (ke.keyCode) { - KeyEvent.KEYCODE_MEDIA_PLAY -> { - PlayerSingleton.play() - } - KeyEvent.KEYCODE_MEDIA_PAUSE -> { - PlayerSingleton.pause() - } - KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { - Log.d("Player", "Player: Key Code : PlayPause") - PlayerSingleton.togglePlayPause() - } - KeyEvent.KEYCODE_MEDIA_NEXT -> { - Log.d("Player", "Player: Key Code : Next") - PlayerSingleton.next() - } - KeyEvent.KEYCODE_MEDIA_PREVIOUS -> { - Log.d("Player", "Player: Key Code : Previous") - PlayerSingleton.previous() - } - KeyEvent.KEYCODE_MEDIA_STOP -> { - PlayerSingleton.stop() + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE -> { + Log.d("Player", "Player: Key Code : PlayPause") + mediaService.togglePlayPause() + } + + KeyEvent.KEYCODE_MEDIA_NEXT -> { + Log.d("Player", "Player: Key Code : Next") + PlayerSingleton.next() + } + + KeyEvent.KEYCODE_MEDIA_PREVIOUS -> { + Log.d("Player", "Player: Key Code : Previous") + PlayerSingleton.previous() + } + + KeyEvent.KEYCODE_MEDIA_STOP -> { + PlayerSingleton.stop() + } } + } else if (intent.hasExtra(FAVORITE)) { + PlayerSingleton.favorite(intent.getBooleanExtra(FAVORITE, false)) } + } } \ No newline at end of file diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaControlBroadcastReceiver.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaControlBroadcastReceiver.kt index 3a95336e..5ff206e5 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaControlBroadcastReceiver.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaControlBroadcastReceiver.kt @@ -6,6 +6,6 @@ import android.content.Intent class MediaControlBroadcastReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { - MediaButtonEventHandler().onMediaButtonEventHandler(intent) + // MediaButtonEventHandler().onMediaButtonEventHandler(intent) } } \ No newline at end of file diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt index abb3a818..fbc2b8a9 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt @@ -22,32 +22,41 @@ import android.support.v4.media.session.MediaControllerCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat import android.util.Log +import androidx.annotation.OptIn import androidx.core.app.NotificationManagerCompat import androidx.core.content.ContextCompat import androidx.media.session.MediaButtonReceiver -import br.com.suamusica.player.media.parser.SMHlsPlaylistParserFactory +import androidx.media3.common.C +import androidx.media3.common.MediaItem +import androidx.media3.common.PlaybackException +import androidx.media3.common.PlaybackParameters +import androidx.media3.common.Player +import androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK +import androidx.media3.common.Timeline +import androidx.media3.common.Tracks +import androidx.media3.common.AudioAttributes +import androidx.media3.common.MediaMetadata +import androidx.media3.common.MediaMetadata.PICTURE_TYPE_FRONT_COVER +import androidx.media3.common.util.UnstableApi +import androidx.media3.common.util.Util +import androidx.media3.datasource.DataSource +import androidx.media3.datasource.DefaultHttpDataSource +import androidx.media3.datasource.FileDataSource +import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.exoplayer.hls.HlsMediaSource +import androidx.media3.exoplayer.source.ProgressiveMediaSource +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaController +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaSessionService +import androidx.media3.session.SessionCommand +//import br.com.suamusica.player.media.parser.SMHlsPlaylistParserFactory import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.FutureTarget import com.bumptech.glide.request.RequestOptions -import com.google.android.exoplayer2.C -import com.google.android.exoplayer2.ExoPlayer -import com.google.android.exoplayer2.MediaItem -import com.google.android.exoplayer2.PlaybackException -import com.google.android.exoplayer2.PlaybackParameters -import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.Player.DISCONTINUITY_REASON_SEEK -import com.google.android.exoplayer2.Timeline -import com.google.android.exoplayer2.Tracks -import com.google.android.exoplayer2.audio.AudioAttributes -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector -import com.google.android.exoplayer2.ext.mediasession.TimelineQueueNavigator -import com.google.android.exoplayer2.source.ProgressiveMediaSource -import com.google.android.exoplayer2.source.hls.HlsMediaSource -import com.google.android.exoplayer2.upstream.DataSource -import com.google.android.exoplayer2.upstream.DefaultHttpDataSource -import com.google.android.exoplayer2.upstream.FileDataSource -import com.google.android.exoplayer2.util.Util +import com.google.common.collect.ImmutableList +import com.google.common.util.concurrent.ListenableFuture import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -57,23 +66,20 @@ import java.io.File import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean -class MediaService : androidx.media.MediaBrowserServiceCompat() { +@UnstableApi +class MediaService : MediaSessionService() { private val TAG = "MediaService" private val userAgent = "SuaMusica/player (Linux; Android ${Build.VERSION.SDK_INT}; ${Build.BRAND}/${Build.MODEL})" private var packageValidator: PackageValidator? = null - - private var mediaSession: MediaSessionCompat? = null - private var mediaController: MediaControllerCompat? = null - private var mediaSessionConnector: MediaSessionConnector? = null - private var media: Media? = null - private var notificationBuilder: NotificationBuilder? = null private var notificationManager: NotificationManagerCompat? = null private var isForegroundService = false private var wifiLock: WifiManager.WifiLock? = null private var wakeLock: PowerManager.WakeLock? = null + private var mediaSession: MediaSession? = null + private var mediaController: ListenableFuture? = null private val uAmpAudioAttributes = AudioAttributes.Builder() .setContentType(C.AUDIO_CONTENT_TYPE_MUSIC) @@ -88,6 +94,7 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { private val BROWSABLE_ROOT = "/" private val EMPTY_ROOT = "@empty@" + companion object { private val glideOptions = RequestOptions() .fallback(R.drawable.default_art) @@ -97,7 +104,7 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { private const val NOTIFICATION_LARGE_ICON_SIZE = 500 // px private const val LOCAL_COVER_PNG = "../app_flutter/covers/0.png" // px - @OptIn(DelicateCoroutinesApi::class) + @kotlin.OptIn(DelicateCoroutinesApi::class) fun getArts(context: Context, artUri: String?, callback: (Bitmap?) -> Unit) { GlobalScope.launch(Dispatchers.IO) { Log.i("getArts", " artUri: $artUri") @@ -135,11 +142,11 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { } } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { - Log.d(TAG, "onStartCommand") - return Service.START_STICKY - - } +// override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { +// Log.d(TAG, "onStartCommand") +// return Service.START_STICKY +// +// } override fun onCreate() { super.onCreate() @@ -156,20 +163,20 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { wifiLock?.setReferenceCounted(false) wakeLock?.setReferenceCounted(false) - val sessionActivityPendingIntent = - this.packageManager?.getLaunchIntentForPackage(this.packageName)?.let { sessionIntent -> - PendingIntent.getActivity(this, 0, sessionIntent, PendingIntent.FLAG_IMMUTABLE) - } - - val mediaButtonReceiver = ComponentName(this, MediaButtonReceiver::class.java) - mediaSession = mediaSession?.let { it } - ?: MediaSessionCompat(this, TAG, mediaButtonReceiver, null) - .apply { - setSessionActivity(sessionActivityPendingIntent) - isActive = true - } - - mediaSession?.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) +// val sessionActivityPendingIntent = +// this.packageManager?.getLaunchIntentForPackage(this.packageName)?.let { sessionIntent -> +// PendingIntent.getActivity(this, 0, sessionIntent, PendingIntent.FLAG_IMMUTABLE) +// } +// +// val mediaButtonReceiver = ComponentName(this, MediaButtonReceiver::class.java) +// mediaSession = mediaSession?.let { it } +// ?: MediaSessionCompat(this, TAG, mediaButtonReceiver, null) +// .apply { +// setSessionActivity(sessionActivityPendingIntent) +// isActive = true +// } + +// mediaSession?.setFlags(MediaSessionCompat.FLAG_HANDLES_QUEUE_COMMANDS) player = ExoPlayer.Builder(this).build().apply { setAudioAttributes(uAmpAudioAttributes, true) @@ -177,46 +184,77 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { // setWakeMode(C.WAKE_MODE_NETWORK) setHandleAudioBecomingNoisy(true) } - mediaSession?.let { mediaSession -> - val sessionToken = mediaSession.sessionToken - // we must connect the service to the media session - this.sessionToken = sessionToken - - val mediaControllerCallback = MediaControllerCallback() - - mediaController = MediaControllerCompat(this, sessionToken).also { mediaController -> - mediaController.registerCallback(mediaControllerCallback) - - mediaSessionConnector = MediaSessionConnector(mediaSession).also { connector -> - connector.setPlayer(player) - connector.setPlaybackPreparer(MusicPlayerPlaybackPreparer(this)) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (Build.MANUFACTURER.equals("samsung", ignoreCase = true)) { - connector.setCustomActionProviders( - FavoriteModeActionProvider(applicationContext), - NextActionProvider(), - PreviousActionProvider(), - ) - } else { - connector.setCustomActionProviders( - FavoriteModeActionProvider(applicationContext), - PreviousActionProvider(), - NextActionProvider(), - ) - } - } - connector.setMediaButtonEventHandler(MediaButtonEventHandler()) - connector.setEnabledPlaybackActions( - PlaybackStateCompat.ACTION_PLAY - or PlaybackStateCompat.ACTION_PAUSE - or PlaybackStateCompat.ACTION_REWIND - or PlaybackStateCompat.ACTION_FAST_FORWARD - or PlaybackStateCompat.ACTION_SEEK_TO - ) - } - } - } - } + + + + player?.let { + mediaSession = MediaSession.Builder(this, it) +// .setCustomLayout( +// ImmutableList.of( +// CommandButton.Builder() +// .setDisplayName("Save to favorites") +// .setIconResId(R.drawable.ic_favorite_notification_player) +// .setSessionCommand(SessionCommand("Favoritar", Bundle())) +// .build(), +// CommandButton.Builder() +// .setDisplayName("PREVIOUS") +// .setIconResId(R.drawable.ic_prev_notification_player) +// .setSessionCommand(SessionCommand("NEXT", Bundle())) +// .build(), +// CommandButton.Builder() +// .setDisplayName("NEXT") +// .setIconResId(R.drawable.ic_next_notification_player) +// .setSessionCommand(SessionCommand("NEXT", Bundle())) +// .build() +// ) +// ) + .setCallback(MediaButtonEventHandler(this)).build() + } + +// mediaSession?.let { mediaSession -> +// val sessionToken = mediaSession.sessionToken +// // we must connect the service to the media session +// this.sessionToken = sessionToken +// +// val mediaControllerCallback = MediaControllerCallback() +// +// mediaController = MediaControllerCompat(this, sessionToken).also { mediaController -> +// mediaController.registerCallback(mediaControllerCallback) +// +// mediaSessionConnector = MediaSessionConnector(mediaSession).also { connector -> +// connector.setPlayer(player) +// connector.setPlaybackPreparer(MusicPlayerPlaybackPreparer(this)) +// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { +// if (Build.MANUFACTURER.equals("samsung", ignoreCase = true)) { +// connector.setCustomActionProviders( +// FavoriteModeActionProvider(applicationContext), +// NextActionProvider(), +// PreviousActionProvider(), +// ) +// } else { +// connector.setCustomActionProviders( +// FavoriteModeActionProvider(applicationContext), +// PreviousActionProvider(), +// NextActionProvider(), +// ) +// } +// } +// connector.setMediaButtonEventHandler(MediaButtonEventHandler()) +// connector.setEnabledPlaybackActions( +// PlaybackStateCompat.ACTION_PLAY +// or PlaybackStateCompat.ACTION_PAUSE +// or PlaybackStateCompat.ACTION_REWIND +// or PlaybackStateCompat.ACTION_FAST_FORWARD +// or PlaybackStateCompat.ACTION_SEEK_TO +// ) +// } +// } +// } + } + + override fun onGetSession( + controllerInfo: MediaSession.ControllerInfo + ): MediaSession? = mediaSession override fun onTaskRemoved(rootIntent: Intent) { Log.d(TAG, "onTaskRemoved") @@ -238,14 +276,14 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { Log.d(TAG, "onDestroy") // mediaController?.unregisterCallback(mediaControllerCallback) releaseLock() - mediaSessionConnector?.setPlayer(null) +// mediaSessionConnector?.setPlayer(null) player?.release() stopSelf() mediaSession?.run { - isActive = false + player.release() release() - Log.d("MusicService", "onDestroy(isActive: $isActive)") + mediaSession = null } releasePossibleLeaks() @@ -260,7 +298,7 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { packageValidator = null mediaSession = null mediaController = null - mediaSessionConnector = null +// mediaSessionConnector = null wifiLock = null wakeLock = null @@ -280,26 +318,26 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { } } - override fun onGetRoot( - clientPackageName: String, - clientUid: Int, - rootHints: Bundle? - ): BrowserRoot? { - val isKnowCaller = packageValidator?.isKnownCaller(clientPackageName, clientUid) ?: false - - return if (isKnowCaller) { - BrowserRoot(BROWSABLE_ROOT, null) - } else { - BrowserRoot(EMPTY_ROOT, null) - } - } - - override fun onLoadChildren( - parentId: String, - result: Result> - ) { - result.sendResult(mutableListOf()) - } +// override fun onGetRoot( +// clientPackageName: String, +// clientUid: Int, +// rootHints: Bundle? +// ): BrowserRoot? { +// val isKnowCaller = packageValidator?.isKnownCaller(clientPackageName, clientUid) ?: false +// +// return if (isKnowCaller) { +// BrowserRoot(BROWSABLE_ROOT, null) +// } else { +// BrowserRoot(EMPTY_ROOT, null) +// } +// } +// +// override fun onLoadChildren( +// parentId: String, +// result: Result> +// ) { +// result.sendResult(mutableListOf()) +// } fun prepare(cookie: String, media: Media) { this.media = media @@ -312,481 +350,510 @@ class MediaService : androidx.media.MediaBrowserServiceCompat() { dataSourceFactory.setDefaultRequestProperties(mapOf("Cookie" to cookie)) // Metadata Build - val metadataBuilder = MediaMetadataCompat.Builder() + val metadataBuilder = MediaMetadata.Builder() val art = null +// metadataBuilder.apply { +// album = media.author +// albumArt = art +// title = media.name +// displayTitle = media.name +// putString(MediaMetadataCompat.METADATA_KEY_ARTIST, media.author) +// putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, media.name) +// putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art) +// putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, art) +// } metadataBuilder.apply { - album = media.author - albumArt = art - title = media.name - displayTitle = media.name - putString(MediaMetadataCompat.METADATA_KEY_ARTIST, media.author) - putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, media.name) - putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art) - putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, art) + setAlbumTitle(media.name) + setArtist(media.author) +// setArtworkData(art,PICTURE_TYPE_FRONT_COVER) + setArtist(media.author) + setTitle(media.name) + setDisplayTitle(media.name) +// putString(MediaMetadataCompat.METADATA_KEY_ARTIST, media.author) +// putString(MediaMetadataCompat.METADATA_KEY_DISPLAY_SUBTITLE, media.name) +// putBitmap(MediaMetadataCompat.METADATA_KEY_ART, art) +// putBitmap(MediaMetadataCompat.METADATA_KEY_DISPLAY_ICON, art) +// val metadata = metadataBuilder.build() +// mediaSession?.setMetadata(metadata) +// mediaSessionConnector?.setMediaMetadataProvider { +// return@setMediaMetadataProvider metadata +// } +// if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { +// val timelineQueueNavigator = object : TimelineQueueNavigator(mediaSession!!) { +// override fun getSupportedQueueNavigatorActions(player: Player): Long { +// return PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or +// PlaybackStateCompat.ACTION_SKIP_TO_NEXT or +// PlaybackStateCompat.ACTION_SEEK_TO +// } +// +// override fun getMediaDescription( +// player: Player, +// windowIndex: Int +// ): MediaDescriptionCompat { +// player.let { +// return MediaDescriptionCompat.Builder().apply { +// setTitle(media.author) +// setSubtitle(media.name) +// setIconUri(Uri.parse(media.coverUrl)) +// }.build() +// } +// } +// } +// mediaSessionConnector?.setQueueNavigator(timelineQueueNavigator) } val metadata = metadataBuilder.build() - mediaSession?.setMetadata(metadata) - mediaSessionConnector?.setMediaMetadataProvider { - return@setMediaMetadataProvider metadata - } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - val timelineQueueNavigator = object : TimelineQueueNavigator(mediaSession!!) { - override fun getSupportedQueueNavigatorActions(player: Player): Long { - return PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS or - PlaybackStateCompat.ACTION_SKIP_TO_NEXT or - PlaybackStateCompat.ACTION_SEEK_TO + val url = media.url + Log.i(TAG, "Player: URL: $url") + + val uri = if (url.startsWith("/")) Uri.fromFile(File(url)) else Uri.parse(url) + val mediaItem = MediaItem.Builder() + .setUri(uri) + .setMediaMetadata(metadata) + .build() + @C.ContentType val type = Util.inferContentType(uri) + Log.i(TAG, "Player: Type: $type HLS: ${C.TYPE_HLS}") + val source = when (type) { +// C.CONTENT_TYPE_HLS -> HlsMediaSource.Factory(dataSourceFactory) +// .setPlaylistParserFactory(SMHlsPlaylistParserFactory()) +// .setAllowChunklessPreparation(true) +// .createMediaSource(MediaItem.fromUri(uri)) + + C.CONTENT_TYPE_OTHER -> { + Log.i(TAG, "Player: URI: $uri") + val factory: DataSource.Factory = + if (uri.scheme != null && uri.scheme?.startsWith("http") == true) { + dataSourceFactory + } else { + FileDataSource.Factory() + } + + ProgressiveMediaSource.Factory(factory) + .createMediaSource(MediaItem.fromUri(uri)) } - override fun getMediaDescription( - player: Player, - windowIndex: Int - ): MediaDescriptionCompat { - player.let { - return MediaDescriptionCompat.Builder().apply { - setTitle(media.author) - setSubtitle(media.name) - setIconUri(Uri.parse(media.coverUrl)) - }.build() - } + else -> { + throw IllegalStateException("Unsupported type: $type") } } - mediaSessionConnector?.setQueueNavigator(timelineQueueNavigator) + player?.pause() + player?.prepare(source) } - val url = media.url - Log.i(TAG, "Player: URL: $url") - - val uri = if (url.startsWith("/")) Uri.fromFile(File(url)) else Uri.parse(url) - - @C.ContentType val type = Util.inferContentType(uri) - Log.i(TAG, "Player: Type: $type HLS: ${C.TYPE_HLS}") - val source = when (type) { - C.TYPE_HLS -> HlsMediaSource.Factory(dataSourceFactory) - .setPlaylistParserFactory(SMHlsPlaylistParserFactory()) - .setAllowChunklessPreparation(true) - .createMediaSource(MediaItem.fromUri(uri)) - C.TYPE_OTHER -> { - Log.i(TAG, "Player: URI: $uri") - val factory: DataSource.Factory = - if (uri.scheme != null && uri.scheme?.startsWith("http") == true) { - dataSourceFactory - } else { - FileDataSource.Factory() - } - ProgressiveMediaSource.Factory(factory).createMediaSource(MediaItem.fromUri(uri)) - } - else -> { - throw IllegalStateException("Unsupported type: $type") +// } + fun play() { + performAndEnableTracking { + player?.play() } } - player?.pause() - player?.prepare(source) - } - fun play() { - performAndEnableTracking { - player?.play() - } - } - fun adsPlaying() { - getArts(applicationContext,null) { bitmap -> - this.media = Media("Propaganda", "", "", "",null,null ) - val notification = buildNotification(PlaybackStateCompat.STATE_PLAYING, true, bitmap) - notification?.let { - notificationManager?.notify(NOW_PLAYING_NOTIFICATION, it) - shouldStartService(it) + fun adsPlaying() { + getArts(applicationContext, null) { bitmap -> + this.media = Media("Propaganda", "", "", "", null, null) + val notification = + buildNotification(PlaybackStateCompat.STATE_PLAYING, true, bitmap) + notification?.let { + notificationManager?.notify(NOW_PLAYING_NOTIFICATION, it) + shouldStartService(it) + } } } - } - fun sendCommand(type: String) { - val extra = Bundle() - extra.putString("type", type) - mediaSession?.setExtras(extra) - } + fun sendCommand(type: String) { + val extra = Bundle() + extra.putString("type", type) + mediaSession?.setSessionExtras(extra) + } - fun setFavorite(favorite: Boolean?) { - media?.let { - this.media = Media(it.name, it.author, it.url, it.coverUrl, it.bigCoverUrl, favorite) - sendNotification(this.media!!, null) + fun setFavorite(favorite: Boolean?) { + media?.let { + this.media = + Media(it.name, it.author, it.url, it.coverUrl, it.bigCoverUrl, favorite) + sendNotification(this.media!!, null) + } } - } - fun sendNotification(media: Media, isPlayingExternal: Boolean?) { - getArts(applicationContext, media.bigCoverUrl ?: media.coverUrl) { bitmap -> - mediaSession?.let { - val onGoing: Boolean = if (isPlayingExternal == null) { - val state = player?.playbackState ?: PlaybackStateCompat.STATE_NONE - state == PlaybackStateCompat.STATE_PLAYING || state == PlaybackStateCompat.STATE_BUFFERING - } else { - isPlayingExternal - } - this.media = media - val notification = notificationBuilder?.buildNotification( - it, - media, - onGoing, - isPlayingExternal, - media.isFavorite, - player?.duration, bitmap - ) - notification?.let { - notificationManager?.notify(NOW_PLAYING_NOTIFICATION, notification) + fun sendNotification(media: Media, isPlayingExternal: Boolean?) { + getArts(applicationContext, media.bigCoverUrl ?: media.coverUrl) { bitmap -> + mediaSession?.let { + val onGoing: Boolean = if (isPlayingExternal == null) { + val state = player?.playbackState ?: PlaybackStateCompat.STATE_NONE + state == PlaybackStateCompat.STATE_PLAYING || state == PlaybackStateCompat.STATE_BUFFERING + } else { + isPlayingExternal + } + this.media = media + val notification = notificationBuilder?.buildNotification( + it, + media, + onGoing, + isPlayingExternal, + media.isFavorite, + player?.duration, bitmap + ) + notification?.let { + notificationManager?.notify(NOW_PLAYING_NOTIFICATION, notification) + } } } } - } - fun removeNotification() { - removeNowPlayingNotification(); - } + fun removeNotification() { + removeNowPlayingNotification(); + } - fun seek(position: Long, playWhenReady: Boolean) { - player?.seekTo(position) - player?.playWhenReady = playWhenReady - } + fun seek(position: Long, playWhenReady: Boolean) { + player?.seekTo(position) + player?.playWhenReady = playWhenReady + } - fun pause() { - performAndDisableTracking { - player?.pause() + fun pause() { + performAndDisableTracking { + player?.pause() + } } - } - fun stop() { - performAndDisableTracking { - player?.stop() + fun stop() { + performAndDisableTracking { + player?.stop() + } } - } - fun togglePlayPause() { - performAndDisableTracking { - if (player?.isPlaying == true) { - player?.pause() - } else { - player?.play() + fun togglePlayPause() { + performAndDisableTracking { + if (player?.isPlaying == true) { + player?.pause() + } else { + player?.play() + } } } - } - fun release() { - performAndDisableTracking { - player?.stop() + fun release() { + performAndDisableTracking { + player?.stop() + } } - } - private fun removeNowPlayingNotification() { - Log.d(TAG, "removeNowPlayingNotification") - Thread(Runnable { - notificationManager?.cancel(NOW_PLAYING_NOTIFICATION) - }).start() + private fun removeNowPlayingNotification() { + Log.d(TAG, "removeNowPlayingNotification") + Thread(Runnable { + notificationManager?.cancel(NOW_PLAYING_NOTIFICATION) + }).start() - } + } - private fun notifyPositionChange() { - var position = player?.currentPosition ?: 0L - val duration = player?.duration ?: 0L - position = if (position > duration) duration else position + private fun notifyPositionChange() { + var position = player?.currentPosition ?: 0L + val duration = player?.duration ?: 0L + position = if (position > duration) duration else position - if (duration > 0) { - val extra = Bundle() - extra.putString("type", "position") - extra.putLong("position", position) - extra.putLong("duration", duration) - mediaSession?.setExtras(extra) + if (duration > 0) { + val extra = Bundle() + extra.putString("type", "position") + extra.putLong("position", position) + extra.putLong("duration", duration) + mediaSession?.setSessionExtras(extra) + } } - } - private fun startTrackingProgress() { - if (progressTracker != null) { - return + private fun startTrackingProgress() { + if (progressTracker != null) { + return + } + this.progressTracker = ProgressTracker(Handler()) } - this.progressTracker = ProgressTracker(Handler()) - } - private fun stopTrackingProgress() { - progressTracker?.stopTracking() - progressTracker = null - } - - private fun stopTrackingProgressAndPerformTask(callable: () -> Unit) { - if (progressTracker != null) { - progressTracker!!.stopTracking(callable) - } else { - callable() + private fun stopTrackingProgress() { + progressTracker?.stopTracking() + progressTracker = null } - progressTracker = null - } - private fun performAndEnableTracking(callable: () -> Unit) { - callable() - startTrackingProgress() - } + private fun stopTrackingProgressAndPerformTask(callable: () -> Unit) { + if (progressTracker != null) { + progressTracker!!.stopTracking(callable) + } else { + callable() + } + progressTracker = null + } - private fun performAndDisableTracking(callable: () -> Unit) { - callable() - stopTrackingProgress() - } + private fun performAndEnableTracking(callable: () -> Unit) { + callable() + startTrackingProgress() + } - private fun buildNotification( - updatedState: Int, - onGoing: Boolean, - art: Bitmap? - ): Notification? { - return if (updatedState != PlaybackStateCompat.STATE_NONE) { - mediaSession?.let { - notificationBuilder?.buildNotification( - it, - media, - onGoing, - null, - media?.isFavorite, - player?.duration, - art - ) + private fun performAndDisableTracking(callable: () -> Unit) { + callable() + stopTrackingProgress() + } + + private fun buildNotification( + updatedState: Int, + onGoing: Boolean, + art: Bitmap? + ): Notification? { + return if (updatedState != PlaybackStateCompat.STATE_NONE) { + mediaSession?.let { + notificationBuilder?.buildNotification( + it, + media, + onGoing, + null, + media?.isFavorite, + player?.duration, + art + ) + } + } else { + null } - } else { - null } - } - private fun playerEventListener(): Player.Listener { - return object : Player.Listener { - override fun onTimelineChanged(timeline: Timeline, reason: Int) { - Log.i(TAG, "onTimelineChanged: timeline: $timeline reason: $reason") - } + private fun playerEventListener(): Player.Listener { + return object : Player.Listener { + override fun onTimelineChanged(timeline: Timeline, reason: Int) { + Log.i(TAG, "onTimelineChanged: timeline: $timeline reason: $reason") + } - override fun onTracksChanged(tracks: Tracks) { - Log.i(TAG, "onTracksChanged: ") - } + override fun onTracksChanged(tracks: Tracks) { + Log.i(TAG, "onTracksChanged: ") + } - override fun onLoadingChanged(isLoading: Boolean) { - Log.i(TAG, "onLoadingChanged: isLoading: $isLoading") - } + override fun onLoadingChanged(isLoading: Boolean) { + Log.i(TAG, "onLoadingChanged: isLoading: $isLoading") + } - override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { - Log.i( - TAG, - "onPlayerStateChanged: playWhenReady: $playWhenReady playbackState: $playbackState currentPlaybackState: ${player?.playbackState}" - ) - if (playWhenReady) { - val duration = player?.duration ?: 0L - acquireLock( - if (duration > 1L) duration + TimeUnit.MINUTES.toMillis(2) else TimeUnit.MINUTES.toMillis( - 3 - ) + override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) { + Log.i( + TAG, + "onPlayerStateChanged: playWhenReady: $playWhenReady playbackState: $playbackState currentPlaybackState: ${player?.playbackState}" ) - } else - releaseLock() + if (playWhenReady) { + val duration = player?.duration ?: 0L + acquireLock( + if (duration > 1L) duration + TimeUnit.MINUTES.toMillis(2) else TimeUnit.MINUTES.toMillis( + 3 + ) + ) + } else + releaseLock() - if (playWhenReady && playbackState == ExoPlayer.STATE_READY) { - // - } else { - if (player?.playerError != null) { + if (playWhenReady && playbackState == ExoPlayer.STATE_READY) { // } else { - when (playbackState) { - ExoPlayer.STATE_IDLE -> { // 1 - // - } - ExoPlayer.STATE_BUFFERING -> { // 2 - // - } - ExoPlayer.STATE_READY -> { // 3 - val status = - if (playWhenReady) PlayerState.PLAYING else PlayerState.PAUSED - if (previousState == -1) { - // when we define that the track shall not "playWhenReady" - // no position info is sent - // therefore, we need to "emulate" the first position notification - // by sending it directly - notifyPositionChange() - } else { - if (status == PlayerState.PAUSED) { - stopTrackingProgressAndPerformTask { + if (player?.playerError != null) { + // + } else { + when (playbackState) { + ExoPlayer.STATE_IDLE -> { // 1 + // + } + + ExoPlayer.STATE_BUFFERING -> { // 2 + // + } + + ExoPlayer.STATE_READY -> { // 3 + val status = + if (playWhenReady) PlayerState.PLAYING else PlayerState.PAUSED + if (previousState == -1) { + // when we define that the track shall not "playWhenReady" + // no position info is sent + // therefore, we need to "emulate" the first position notification + // by sending it directly + notifyPositionChange() + } else { + if (status == PlayerState.PAUSED) { + stopTrackingProgressAndPerformTask { + // + } + } else { // } - } else { - // - } + } } - } - ExoPlayer.STATE_ENDED -> { // 4 - stopTrackingProgressAndPerformTask { - // + + ExoPlayer.STATE_ENDED -> { // 4 + stopTrackingProgressAndPerformTask { + // + } } } } } + previousState = playbackState } - previousState = playbackState - } - - override fun onRepeatModeChanged(repeatMode: Int) { - Log.i(TAG, "onRepeatModeChanged: $repeatMode") - } - override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) { - Log.i(TAG, "onShuffleModeEnabledChanged: $shuffleModeEnabled") - } + override fun onRepeatModeChanged(repeatMode: Int) { + Log.i(TAG, "onRepeatModeChanged: $repeatMode") + } - override fun onPlayerError(error: PlaybackException) { - Log.e(TAG, "onPLayerError: ${error.message}", error) - val bundle = Bundle() - bundle.putString("type", "error") - bundle.putString( - "error", - if (error.cause.toString() - .contains("Permission denied") - ) "Permission denied" else error.message - ) - mediaSession?.setExtras(bundle) - } + override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) { + Log.i(TAG, "onShuffleModeEnabledChanged: $shuffleModeEnabled") + } - override fun onPositionDiscontinuity(reason: Int) { - Log.i(TAG, "onPositionDiscontinuity: $reason") - if (reason == DISCONTINUITY_REASON_SEEK) { + override fun onPlayerError(error: PlaybackException) { + Log.e(TAG, "onPLayerError: ${error.message}", error) val bundle = Bundle() - bundle.putString("type", "seek-end") - mediaSession?.setExtras(bundle) + bundle.putString("type", "error") + bundle.putString( + "error", + if (error.cause.toString() + .contains("Permission denied") + ) "Permission denied" else error.message + ) + mediaSession?.setSessionExtras(bundle) } - } - - override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) { - Log.i(TAG, "onPlaybackParametersChanged: $playbackParameters") - } + override fun onPositionDiscontinuity(reason: Int) { + Log.i(TAG, "onPositionDiscontinuity: $reason") + if (reason == DISCONTINUITY_REASON_SEEK) { + val bundle = Bundle() + bundle.putString("type", "seek-end") + mediaSession?.setSessionExtras(bundle) + } - } - } + } - private inner class ProgressTracker(val handler: Handler) : Runnable { - private val shutdownRequest = AtomicBoolean(false) - private var shutdownTask: (() -> Unit)? = null + override fun onPlaybackParametersChanged(playbackParameters: PlaybackParameters) { + Log.i(TAG, "onPlaybackParametersChanged: $playbackParameters") + } - init { - handler.post(this) + } } - override fun run() { - notifyPositionChange() + private inner class ProgressTracker(val handler: Handler) : Runnable { + private val shutdownRequest = AtomicBoolean(false) + private var shutdownTask: (() -> Unit)? = null - if (!shutdownRequest.get()) { - handler.postDelayed(this, 800 /* ms */) - } else { - shutdownTask?.let { - it() - } + init { + handler.post(this) } - } - fun stopTracking() { - shutdownRequest.set(true) - } + override fun run() { + notifyPositionChange() - fun stopTracking(callable: () -> Unit) { - shutdownTask = callable - stopTracking() - } - } + if (!shutdownRequest.get()) { + handler.postDelayed(this, 800 /* ms */) + } else { + shutdownTask?.let { + it() + } + } + } - fun shouldStartService(notification: Notification) { - if (!isForegroundService) { - Log.i(TAG, "Starting Service") - try { - ContextCompat.startForegroundService( - applicationContext, - Intent(applicationContext, this@MediaService.javaClass) - ) - startForeground(NOW_PLAYING_NOTIFICATION, notification) - } catch (e: Exception) { - startForeground(NOW_PLAYING_NOTIFICATION, notification) - ContextCompat.startForegroundService( - applicationContext, - Intent(applicationContext, this@MediaService.javaClass) - ) + fun stopTracking() { + shutdownRequest.set(true) } - isForegroundService = true + fun stopTracking(callable: () -> Unit) { + shutdownTask = callable + stopTracking() + } } - } - fun stopService() { - if (isForegroundService) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - stopForeground(STOP_FOREGROUND_DETACH) - } else { - stopForeground(false) + fun shouldStartService(notification: Notification) { + if (!isForegroundService) { + Log.i(TAG, "Starting Service") + try { + ContextCompat.startForegroundService( + applicationContext, + Intent(applicationContext, this@MediaService.javaClass) + ) + startForeground(NOW_PLAYING_NOTIFICATION, notification) + } catch (e: Exception) { + startForeground(NOW_PLAYING_NOTIFICATION, notification) + ContextCompat.startForegroundService( + applicationContext, + Intent(applicationContext, this@MediaService.javaClass) + ) + } + isForegroundService = true + } - isForegroundService = false - stopSelf() - Log.i(TAG, "Stopping Service") } - } - private inner class MediaControllerCallback : MediaControllerCompat.Callback() { - override fun onMetadataChanged(metadata: MediaMetadataCompat?) { - Log.d( - TAG, - "onMetadataChanged: title: ${metadata?.title} duration: ${metadata?.duration}" - ) + fun stopService() { + if (isForegroundService) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + stopForeground(STOP_FOREGROUND_DETACH) + } else { + stopForeground(false) + } + isForegroundService = false + stopSelf() + Log.i(TAG, "Stopping Service") + } } - override fun onPlaybackStateChanged(state: PlaybackStateCompat?) { - Log.d(TAG, "onPlaybackStateChanged state: $state") - updateNotification(state!!) - } + private inner class MediaControllerCallback : MediaControllerCompat.Callback() { + override fun onMetadataChanged(metadata: MediaMetadataCompat?) { + Log.d( + TAG, + "onMetadataChanged: title: ${metadata?.title} duration: ${metadata?.duration}" + ) + } - override fun onQueueChanged(queue: MutableList?) { - Log.d(TAG, "onQueueChanged queue: $queue") - } + override fun onPlaybackStateChanged(state: PlaybackStateCompat?) { + Log.d(TAG, "onPlaybackStateChanged state: $state") + updateNotification(state!!) + } - @SuppressLint("WakelockTimeout") - private fun updateNotification(state: PlaybackStateCompat) { - if (mediaController?.metadata == null || mediaSession == null) { - return + override fun onQueueChanged(queue: MutableList?) { + Log.d(TAG, "onQueueChanged queue: $queue") } - getArts(applicationContext,media?.bigCoverUrl ?: media?.coverUrl) { bitmap -> - val updatedState = state.state - val onGoing = - updatedState == PlaybackStateCompat.STATE_PLAYING || updatedState == PlaybackStateCompat.STATE_BUFFERING - // Skip building a notification when state is "none". - val notification = if (updatedState != PlaybackStateCompat.STATE_NONE) { - buildNotification(updatedState, onGoing, bitmap) - } else { - null + + @SuppressLint("WakelockTimeout") + private fun updateNotification(state: PlaybackStateCompat) { + if (mediaSession == null) { + return } - Log.d(TAG, "!!! updateNotification state: $updatedState $onGoing") - - when (updatedState) { - PlaybackStateCompat.STATE_BUFFERING, - PlaybackStateCompat.STATE_PLAYING -> { - Log.i(TAG, "updateNotification: STATE_BUFFERING or STATE_PLAYING") - /** - * This may look strange, but the documentation for [Service.startForeground] - * notes that "calling this method does *not* put the service in the started - * state itself, even though the name sounds like it." - */ - if (notification != null) { - notificationManager?.notify(NOW_PLAYING_NOTIFICATION, notification) - shouldStartService(notification) - } + getArts(applicationContext, media?.bigCoverUrl ?: media?.coverUrl) { bitmap -> + val updatedState = state.state + val onGoing = + updatedState == PlaybackStateCompat.STATE_PLAYING || updatedState == PlaybackStateCompat.STATE_BUFFERING + // Skip building a notification when state is "none". + val notification = if (updatedState != PlaybackStateCompat.STATE_NONE) { + buildNotification(updatedState, onGoing, bitmap) + } else { + null } - else -> { - if (isForegroundService) { - // If playback has ended, also stop the service. - if (updatedState == PlaybackStateCompat.STATE_NONE && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - stopService() - } + Log.d(TAG, "!!! updateNotification state: $updatedState $onGoing") + + when (updatedState) { + PlaybackStateCompat.STATE_BUFFERING, + PlaybackStateCompat.STATE_PLAYING -> { + Log.i(TAG, "updateNotification: STATE_BUFFERING or STATE_PLAYING") + /** + * This may look strange, but the documentation for [Service.startForeground] + * notes that "calling this method does *not* put the service in the started + * state itself, even though the name sounds like it." + */ if (notification != null) { notificationManager?.notify(NOW_PLAYING_NOTIFICATION, notification) - } else - removeNowPlayingNotification() + shouldStartService(notification) + } + } + + else -> { + if (isForegroundService) { + // If playback has ended, also stop the service. + if (updatedState == PlaybackStateCompat.STATE_NONE && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + stopService() + } + if (notification != null) { + notificationManager?.notify( + NOW_PLAYING_NOTIFICATION, + notification + ) + } else + removeNowPlayingNotification() + } } } } } } } -} diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MusicPlayerPlaybackPreparer.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MusicPlayerPlaybackPreparer.kt index abb82b12..d69a2c18 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MusicPlayerPlaybackPreparer.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MusicPlayerPlaybackPreparer.kt @@ -1,133 +1,133 @@ -package br.com.suamusica.player +// package br.com.suamusica.player -import android.net.Uri -import android.os.Bundle -import android.os.ResultReceiver -import android.util.Log -import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector +// import android.net.Uri +// import android.os.Bundle +// import android.os.ResultReceiver +// import android.util.Log +// import androidx.media3.common.Player +// import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector -class MusicPlayerPlaybackPreparer( - private val mediaService: MediaService, - ) : MediaSessionConnector.PlaybackPreparer { - val TAG = "Player" +// class MusicPlayerPlaybackPreparer( +// private val mediaService: MediaService, +// ) : MediaSessionConnector.PlaybackPreparer { +// val TAG = "Player" - override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) { - Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromMediaId : START") +// override fun onPrepareFromMediaId(mediaId: String, playWhenReady: Boolean, extras: Bundle?) { +// Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromMediaId : START") - Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromMediaId : END") - } +// Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromMediaId : END") +// } - override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) { - Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromSearch : START") +// override fun onPrepareFromSearch(query: String, playWhenReady: Boolean, extras: Bundle?) { +// Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromSearch : START") - Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromSearch : END") - } +// Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromSearch : END") +// } - override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) { - Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromUri : START") +// override fun onPrepareFromUri(uri: Uri, playWhenReady: Boolean, extras: Bundle?) { +// Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromUri : START") - Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromUri : END") - } +// Log.i(TAG, "MusicPlayerPlaybackPreparer.onPrepareFromUri : END") +// } - override fun onCommand(player: Player, - command: String, extras: Bundle?, cb: ResultReceiver?): Boolean { - try { - Log.i(TAG, "MusicPlayerPlaybackPreparer.onCommand : START") +// override fun onCommand(player: Player, +// command: String, extras: Bundle?, cb: ResultReceiver?): Boolean { +// try { +// Log.i(TAG, "MusicPlayerPlaybackPreparer.onCommand : START") - return when (command) { - "prepare" -> { - return extras?.let { - val cookie = it.getString("cookie")!! - val name = it.getString("name")!! - val author = it.getString("author")!! - val url = it.getString("url")!! - val coverUrl = it.getString("coverUrl")!! - val bigCoverUrl = it.getString("bigCoverUrl") +// return when (command) { +// "prepare" -> { +// return extras?.let { +// val cookie = it.getString("cookie")!! +// val name = it.getString("name")!! +// val author = it.getString("author")!! +// val url = it.getString("url")!! +// val coverUrl = it.getString("coverUrl")!! +// val bigCoverUrl = it.getString("bigCoverUrl") - var isFavorite:Boolean? = null; - if(it.containsKey(PlayerPlugin.IS_FAVORITE_ARGUMENT)){ - isFavorite = it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT) - } - mediaService.prepare(cookie, Media(name, author, url, coverUrl, bigCoverUrl,isFavorite)) - return@let true - } ?: false - } - "play" -> { - mediaService.play() - true - } - "pause" -> { - mediaService.pause() - true - } - "stop" -> { - mediaService.stop() - true - } - "togglePlayPause" -> { - mediaService.togglePlayPause() - true - } - "release" -> { - mediaService.release() - true - } - "seek" -> { - return extras?.let { - val position = it.getLong("position") - val playWhenReady = it.getBoolean("playWhenReady") - mediaService.seek(position, playWhenReady) - return@let true - } ?: false - } - "remove_notification" -> { - mediaService.removeNotification() - return true - } - "send_notification" -> { - return extras?.let { - val name = it.getString("name")!! - val author = it.getString("author")!! - val url = it.getString("url")!! - val coverUrl = it.getString("coverUrl")!! - val bigCoverUrl = it.getString("bigCoverUrl") - var isPlaying:Boolean? = null; - var isFavorite:Boolean? = null; - if(it.containsKey(PlayerPlugin.IS_PLAYING_ARGUMENT)){ - isPlaying = it.getBoolean(PlayerPlugin.IS_PLAYING_ARGUMENT) - } - if(it.containsKey(PlayerPlugin.IS_FAVORITE_ARGUMENT)){ - isFavorite = it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT) - } - mediaService.sendNotification(Media(name, author, url, coverUrl, bigCoverUrl, isFavorite),isPlaying) - return true - } ?: false - } - "ads_playing" -> { - mediaService.adsPlaying() - return true - } - FAVORITE -> { - return extras?.let { - if(it.containsKey(PlayerPlugin.IS_FAVORITE_ARGUMENT)){ - mediaService.setFavorite(it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT)) - } - return@let true - } ?: false - } - else -> false - } - } finally { - Log.i(TAG, "MusicPlayerPlaybackPreparer.onCommand : END") - } - } +// var isFavorite:Boolean? = null; +// if(it.containsKey(PlayerPlugin.IS_FAVORITE_ARGUMENT)){ +// isFavorite = it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT) +// } +// mediaService.prepare(cookie, Media(name, author, url, coverUrl, bigCoverUrl,isFavorite)) +// return@let true +// } ?: false +// } +// "play" -> { +// mediaService.play() +// true +// } +// "pause" -> { +// mediaService.pause() +// true +// } +// "stop" -> { +// mediaService.stop() +// true +// } +// "togglePlayPause" -> { +// mediaService.togglePlayPause() +// true +// } +// "release" -> { +// mediaService.release() +// true +// } +// "seek" -> { +// return extras?.let { +// val position = it.getLong("position") +// val playWhenReady = it.getBoolean("playWhenReady") +// mediaService.seek(position, playWhenReady) +// return@let true +// } ?: false +// } +// "remove_notification" -> { +// mediaService.removeNotification() +// return true +// } +// "send_notification" -> { +// return extras?.let { +// val name = it.getString("name")!! +// val author = it.getString("author")!! +// val url = it.getString("url")!! +// val coverUrl = it.getString("coverUrl")!! +// val bigCoverUrl = it.getString("bigCoverUrl") +// var isPlaying:Boolean? = null; +// var isFavorite:Boolean? = null; +// if(it.containsKey(PlayerPlugin.IS_PLAYING_ARGUMENT)){ +// isPlaying = it.getBoolean(PlayerPlugin.IS_PLAYING_ARGUMENT) +// } +// if(it.containsKey(PlayerPlugin.IS_FAVORITE_ARGUMENT)){ +// isFavorite = it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT) +// } +// mediaService.sendNotification(Media(name, author, url, coverUrl, bigCoverUrl, isFavorite),isPlaying) +// return true +// } ?: false +// } +// "ads_playing" -> { +// mediaService.adsPlaying() +// return true +// } +// FAVORITE -> { +// return extras?.let { +// if(it.containsKey(PlayerPlugin.IS_FAVORITE_ARGUMENT)){ +// mediaService.setFavorite(it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT)) +// } +// return@let true +// } ?: false +// } +// else -> false +// } +// } finally { +// Log.i(TAG, "MusicPlayerPlaybackPreparer.onCommand : END") +// } +// } - override fun getSupportedPrepareActions(): Long { - return 0L - } +// override fun getSupportedPrepareActions(): Long { +// return 0L +// } - override fun onPrepare(playWhenReady: Boolean) { +// override fun onPrepare(playWhenReady: Boolean) { - } -} \ No newline at end of file +// } +// } \ No newline at end of file diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/NextActionProvider.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/NextActionProvider.kt index 097b75ca..4d6d0721 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/NextActionProvider.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/NextActionProvider.kt @@ -1,23 +1,23 @@ -package br.com.suamusica.player +// package br.com.suamusica.player -import android.os.Bundle -import android.support.v4.media.session.PlaybackStateCompat -import android.util.Log -import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector +// import android.os.Bundle +// import android.support.v4.media.session.PlaybackStateCompat +// import android.util.Log +// import androidx.media3.common.Player +// import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector -class NextActionProvider : - MediaSessionConnector.CustomActionProvider { +// class NextActionProvider : +// MediaSessionConnector.CustomActionProvider { - override fun onCustomAction(player: Player, action: String, extras: Bundle?) { - PlayerSingleton.next() - } +// override fun onCustomAction(player: Player, action: String, extras: Bundle?) { +// PlayerSingleton.next() +// } - override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? { - return PlaybackStateCompat.CustomAction.Builder( - "Ir a próxima música", - "Ir a próxima música", - R.drawable.ic_next_notification_player, - ).build() - } -} \ No newline at end of file +// override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? { +// return PlaybackStateCompat.CustomAction.Builder( +// "Ir a próxima música", +// "Ir a próxima música", +// R.drawable.ic_next_notification_player, +// ).build() +// } +// } \ No newline at end of file diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/NotificationBuilder.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/NotificationBuilder.kt index 2b46e36a..39f09aca 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/NotificationBuilder.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/NotificationBuilder.kt @@ -9,6 +9,7 @@ import android.content.Intent import android.graphics.Bitmap import android.media.MediaMetadata import android.os.Build +import android.os.Bundle import android.support.v4.media.MediaMetadataCompat import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat.* @@ -17,8 +18,14 @@ import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.media.app.NotificationCompat.MediaStyle import androidx.media.session.MediaButtonReceiver +import androidx.media3.common.util.UnstableApi +import androidx.media3.session.CommandButton +import androidx.media3.session.MediaSession +import androidx.media3.session.MediaStyleNotificationHelper +import androidx.media3.session.SessionCommand import com.bumptech.glide.load.engine.DiskCacheStrategy import com.bumptech.glide.request.RequestOptions +import com.google.common.collect.ImmutableList import kotlinx.coroutines.* import java.util.* @@ -26,7 +33,7 @@ const val NOW_PLAYING_CHANNEL: String = "br.com.suamusica.media.NOW_PLAYING" const val NOW_PLAYING_NOTIFICATION: Int = 0xb339 const val FAVORITE: String = "favorite" -/** +@UnstableApi /** * Helper class to encapsulate code for building notifications. */ class NotificationBuilder(private val context: Context) { @@ -84,7 +91,7 @@ class NotificationBuilder(private val context: Context) { fun buildNotification( - mediaSession: MediaSessionCompat, + mediaSession: MediaSession, media: Media?, onGoing: Boolean, isPlayingExternal: Boolean?, @@ -95,7 +102,8 @@ class NotificationBuilder(private val context: Context) { if (shouldCreateNowPlayingChannel()) { createNowPlayingChannel() } - val playbackState = mediaSession.controller.playbackState + Log.i("NotificationBuilder", "buildNotification") + val playbackState = mediaSession.player.duration val builder = NotificationCompat.Builder(context, NOW_PLAYING_CHANNEL) val actions = if (isFavorite == null) mutableListOf(0, 1, 2) else mutableListOf( 0, @@ -104,7 +112,7 @@ class NotificationBuilder(private val context: Context) { ) // favorite,play/pause,next val duration = mediaDuration ?: 0L val currentDuration = - mediaSession.controller.metadata.getLong(MediaMetadata.METADATA_KEY_DURATION) + mediaSession.player.currentPosition val shouldUseMetadata = Build.VERSION.SDK_INT >= Build.VERSION_CODES.R isFavorite?.let { @@ -121,12 +129,14 @@ class NotificationBuilder(private val context: Context) { } } - playbackState.isPlaying -> { + //TODO: adicionar a variavel correta antes era um getter + mediaSession.player.isPlaying -> { Log.i("NotificationBuilder", "Player is playing... onGoing: $onGoing") builder.addAction(pauseAction) } - playbackState.isPlayEnabled -> { + //TODO: adicionar a variavel correta antes era um getter + mediaSession.player.isPlaying -> { Log.i("NotificationBuilder", "Player is NOT playing... onGoing: $onGoing") builder.addAction(playAction) } @@ -139,24 +149,23 @@ class NotificationBuilder(private val context: Context) { builder.addAction(skipToNextAction) - val mediaStyle = MediaStyle() + val mediaStyle = MediaStyleNotificationHelper.MediaStyle(mediaSession) .setCancelButtonIntent(stopPendingIntent) .setShowActionsInCompactView(*actions.toIntArray()) .setShowCancelButton(true) - .setMediaSession(mediaSession.sessionToken) - - if (shouldUseMetadata && currentDuration != duration) { - mediaSession.setMetadata( - MediaMetadataCompat.Builder() - .putString(MediaMetadata.METADATA_KEY_TITLE, media?.name ?: "Propaganda") - .putString(MediaMetadata.METADATA_KEY_ARTIST, media?.author ?: "") - .putBitmap( - MediaMetadata.METADATA_KEY_ALBUM_ART, art - ) - .putLong(MediaMetadata.METADATA_KEY_DURATION, duration) // 4 - .build() - ) - } + +// if (shouldUseMetadata && currentDuration != duration) { +// mediaSession.setMetadata( +// MediaMetadataCompat.Builder() +// .putString(MediaMetadata.METADATA_KEY_TITLE, media?.name ?: "Propaganda") +// .putString(MediaMetadata.METADATA_KEY_ARTIST, media?.author ?: "") +// .putBitmap( +// MediaMetadata.METADATA_KEY_ALBUM_ART, art +// ) +// .putLong(MediaMetadata.METADATA_KEY_DURATION, duration) // 4 +// .build() +// ) +// } val notifyIntent = Intent("SUA_MUSICA_FLUTTER_NOTIFICATION_CLICK").apply { diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/PackageValidator.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/PackageValidator.kt index 995aa543..4348534a 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/PackageValidator.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/PackageValidator.kt @@ -13,7 +13,7 @@ import android.util.Base64 import android.util.Log import androidx.annotation.XmlRes import androidx.media.MediaBrowserServiceCompat -import com.google.android.exoplayer2.BuildConfig +import androidx.media3.common.BuildConfig import org.xmlpull.v1.XmlPullParserException import java.io.IOException import java.security.MessageDigest diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/PreviousActionProvider.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/PreviousActionProvider.kt index dc5a58a5..b504a4c4 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/PreviousActionProvider.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/PreviousActionProvider.kt @@ -1,23 +1,23 @@ -package br.com.suamusica.player +// package br.com.suamusica.player -import android.os.Bundle -import android.support.v4.media.session.PlaybackStateCompat -import android.util.Log -import com.google.android.exoplayer2.Player -import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector +// import android.os.Bundle +// import android.support.v4.media.session.PlaybackStateCompat +// import android.util.Log +// import androidx.media3.common.Player +// import com.google.android.exoplayer2.ext.mediasession.MediaSessionConnector -class PreviousActionProvider : - MediaSessionConnector.CustomActionProvider { +// class PreviousActionProvider : +// MediaSessionConnector.CustomActionProvider { - override fun onCustomAction(player: Player, action: String, extras: Bundle?) { - PlayerSingleton.previous() - } +// override fun onCustomAction(player: Player, action: String, extras: Bundle?) { +// PlayerSingleton.previous() +// } - override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? { - return PlaybackStateCompat.CustomAction.Builder( - "Voltar a música anterior", - "Voltar a música anterior", - R.drawable.ic_prev_notification_player, - ).build() - } -} \ No newline at end of file +// override fun getCustomAction(player: Player): PlaybackStateCompat.CustomAction? { +// return PlaybackStateCompat.CustomAction.Builder( +// "Voltar a música anterior", +// "Voltar a música anterior", +// R.drawable.ic_prev_notification_player, +// ).build() +// } +// } \ No newline at end of file diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/CustomHlsMasterPlaylist.java b/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/CustomHlsMasterPlaylist.java index d37ad30c..600c3162 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/CustomHlsMasterPlaylist.java +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/CustomHlsMasterPlaylist.java @@ -1,322 +1,322 @@ -package br.com.suamusica.player.media.parser; - -import android.net.Uri; - -import androidx.annotation.Nullable; - -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.drm.DrmInitData; -import com.google.android.exoplayer2.offline.StreamKey; -import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; -import com.google.android.exoplayer2.util.MimeTypes; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public final class CustomHlsMasterPlaylist extends HlsPlaylist { - - /** Represents an empty master playlist, from which no attributes can be inherited. */ - public static final CustomHlsMasterPlaylist EMPTY = - new CustomHlsMasterPlaylist( - /* baseUri= */ "", - /* tags= */ new ArrayList(), - /* variants= */ new ArrayList(), - /* videos= */ new ArrayList(), - /* audios= */ new ArrayList(), - /* subtitles= */ new ArrayList(), - /* closedCaptions= */ new ArrayList(), - /* muxedAudioFormat= */ null, - /* muxedCaptionFormats= */ new ArrayList(), - /* hasIndependentSegments= */ false, - /* variableDefinitions= */ new HashMap(), - /* sessionKeyDrmInitData= */ new ArrayList()); - - // These constants must not be changed because they are persisted in offline stream keys. - public static final int GROUP_INDEX_VARIANT = 0; - public static final int GROUP_INDEX_AUDIO = 1; - public static final int GROUP_INDEX_SUBTITLE = 2; - - /** A variant (i.e. an #EXT-X-STREAM-INF tag) in a master playlist. */ - public static final class Variant { - - /** The variant's url. */ - public final Uri url; - - /** Format information associated with this variant. */ - public final Format format; - - /** The video rendition group referenced by this variant, or {@code null}. */ - @Nullable - public final String videoGroupId; - - /** The audio rendition group referenced by this variant, or {@code null}. */ - @Nullable public final String audioGroupId; - - /** The subtitle rendition group referenced by this variant, or {@code null}. */ - @Nullable public final String subtitleGroupId; - - /** The caption rendition group referenced by this variant, or {@code null}. */ - @Nullable public final String captionGroupId; - - /** - * @param url See {@link #url}. - * @param format See {@link #format}. - * @param videoGroupId See {@link #videoGroupId}. - * @param audioGroupId See {@link #audioGroupId}. - * @param subtitleGroupId See {@link #subtitleGroupId}. - * @param captionGroupId See {@link #captionGroupId}. - */ - public Variant( - Uri url, - Format format, - @Nullable String videoGroupId, - @Nullable String audioGroupId, - @Nullable String subtitleGroupId, - @Nullable String captionGroupId) { - this.url = url; - this.format = format; - this.videoGroupId = videoGroupId; - this.audioGroupId = audioGroupId; - this.subtitleGroupId = subtitleGroupId; - this.captionGroupId = captionGroupId; - } - - /** - * Creates a variant for a given media playlist url. - * - * @param url The media playlist url. - * @return The variant instance. - */ - public static CustomHlsMasterPlaylist.Variant createMediaPlaylistVariantUrl(Uri url) { - Format format = - new Format.Builder() - .setId("0") - .setLabel(null) - .setLanguage(null) - .setSelectionFlags(0) - .setRoleFlags(0) - .setAverageBitrate(Format.NO_VALUE) - .setPeakBitrate(Format.NO_VALUE) - .setCodecs(null) - .setContainerMimeType(MimeTypes.APPLICATION_M3U8) - .setSampleMimeType(null) - .build(); - return new CustomHlsMasterPlaylist.Variant( - url, - format, - /* videoGroupId= */ null, - /* audioGroupId= */ null, - /* subtitleGroupId= */ null, - /* captionGroupId= */ null); - } - - /** Returns a copy of this instance with the given {@link Format}. */ - public CustomHlsMasterPlaylist.Variant copyWithFormat(Format format) { - return new CustomHlsMasterPlaylist.Variant(url, format, videoGroupId, audioGroupId, subtitleGroupId, captionGroupId); - } - } - - /** A rendition (i.e. an #EXT-X-MEDIA tag) in a master playlist. */ - public static final class Rendition { - - /** The rendition's url, or null if the tag does not have a URI attribute. */ - @Nullable public final Uri url; - - /** Format information associated with this rendition. */ - public final Format format; - - /** The group to which this rendition belongs. */ - public final String groupId; - - /** The name of the rendition. */ - public final String name; - - /** - * @param url See {@link #url}. - * @param format See {@link #format}. - * @param groupId See {@link #groupId}. - * @param name See {@link #name}. - */ - public Rendition(@Nullable Uri url, Format format, String groupId, String name) { - this.url = url; - this.format = format; - this.groupId = groupId; - this.name = name; - } - - } - - /** All of the media playlist URLs referenced by the playlist. */ - public final List mediaPlaylistUrls; - /** The variants declared by the playlist. */ - public final List variants; - /** The video renditions declared by the playlist. */ - public final List videos; - /** The audio renditions declared by the playlist. */ - public final List audios; - /** The subtitle renditions declared by the playlist. */ - public final List subtitles; - /** The closed caption renditions declared by the playlist. */ - public final List closedCaptions; - - /** - * The format of the audio muxed in the variants. May be null if the playlist does not declare any - * muxed audio. - */ - public final Format muxedAudioFormat; - /** - * The format of the closed captions declared by the playlist. May be empty if the playlist - * explicitly declares no captions are available, or null if the playlist does not declare any - * captions information. - */ - public final List muxedCaptionFormats; - /** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */ - public final Map variableDefinitions; - /** DRM initialization data derived from #EXT-X-SESSION-KEY tags. */ - public final List sessionKeyDrmInitData; - - /** - * @param baseUri See {@link #baseUri}. - * @param tags See {@link #tags}. - * @param variants See {@link #variants}. - * @param videos See {@link #videos}. - * @param audios See {@link #audios}. - * @param subtitles See {@link #subtitles}. - * @param closedCaptions See {@link #closedCaptions}. - * @param muxedAudioFormat See {@link #muxedAudioFormat}. - * @param muxedCaptionFormats See {@link #muxedCaptionFormats}. - * @param hasIndependentSegments See {@link #hasIndependentSegments}. - * @param variableDefinitions See {@link #variableDefinitions}. - * @param sessionKeyDrmInitData See {@link #sessionKeyDrmInitData}. - */ - public CustomHlsMasterPlaylist( - String baseUri, - List tags, - List variants, - List videos, - List audios, - List subtitles, - List closedCaptions, - Format muxedAudioFormat, - List muxedCaptionFormats, - boolean hasIndependentSegments, - Map variableDefinitions, - List sessionKeyDrmInitData) { - super(baseUri, tags, hasIndependentSegments); - this.mediaPlaylistUrls = - Collections.unmodifiableList( - getMediaPlaylistUrls(variants, videos, audios, subtitles, closedCaptions)); - this.variants = Collections.unmodifiableList(variants); - this.videos = Collections.unmodifiableList(videos); - this.audios = Collections.unmodifiableList(audios); - this.subtitles = Collections.unmodifiableList(subtitles); - this.closedCaptions = Collections.unmodifiableList(closedCaptions); - this.muxedAudioFormat = muxedAudioFormat; - this.muxedCaptionFormats = muxedCaptionFormats != null - ? Collections.unmodifiableList(muxedCaptionFormats) : null; - this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions); - this.sessionKeyDrmInitData = Collections.unmodifiableList(sessionKeyDrmInitData); - } - - @Override - public CustomHlsMasterPlaylist copy(List streamKeys) { - return new CustomHlsMasterPlaylist( - baseUri, - tags, - copyStreams(variants, GROUP_INDEX_VARIANT, streamKeys), - // TODO: Allow stream keys to specify video renditions to be retained. - /* videos= */ new ArrayList(), - copyStreams(audios, GROUP_INDEX_AUDIO, streamKeys), - copyStreams(subtitles, GROUP_INDEX_SUBTITLE, streamKeys), - // TODO: Update to retain all closed captions. - /* closedCaptions= */ new ArrayList(), - muxedAudioFormat, - muxedCaptionFormats, - hasIndependentSegments, - variableDefinitions, - sessionKeyDrmInitData); - } - - /** - * Creates a playlist with a single variant. - * - * @param variantUrl The url of the single variant. - * @return A master playlist with a single variant for the provided url. - */ - public static CustomHlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUrl) { - List variant = - Collections.singletonList(CustomHlsMasterPlaylist.Variant.createMediaPlaylistVariantUrl(Uri.parse(variantUrl))); - return new CustomHlsMasterPlaylist( - /* baseUri= */ null, - /* tags= */ new ArrayList(), - variant, - /* videos= */ new ArrayList(), - /* audios= */ new ArrayList(), - /* subtitles= */ new ArrayList(), - /* closedCaptions= */ new ArrayList(), - /* muxedAudioFormat= */ null, - /* muxedCaptionFormats= */ null, - /* hasIndependentSegments= */ false, - /* variableDefinitions= */ new HashMap(), - /* sessionKeyDrmInitData= */ new ArrayList()); - } - - private static List getMediaPlaylistUrls( - List variants, - List videos, - List audios, - List subtitles, - List closedCaptions) { - ArrayList mediaPlaylistUrls = new ArrayList<>(); - for (int i = 0; i < variants.size(); i++) { - Uri uri = variants.get(i).url; - if (!mediaPlaylistUrls.contains(uri)) { - mediaPlaylistUrls.add(uri); - } - } - addMediaPlaylistUrls(videos, mediaPlaylistUrls); - addMediaPlaylistUrls(audios, mediaPlaylistUrls); - addMediaPlaylistUrls(subtitles, mediaPlaylistUrls); - addMediaPlaylistUrls(closedCaptions, mediaPlaylistUrls); - return mediaPlaylistUrls; - } - - private static void addMediaPlaylistUrls(List renditions, List out) { - for (int i = 0; i < renditions.size(); i++) { - Uri uri = renditions.get(i).url; - if (uri != null && !out.contains(uri)) { - out.add(uri); - } - } - } - - private static List copyStreams( - List streams, int groupIndex, List streamKeys) { - List copiedStreams = new ArrayList<>(streamKeys.size()); - // TODO: - // 1. When variants with the same URL are not de-duplicated, duplicates must not increment - // trackIndex so as to avoid breaking stream keys that have been persisted for offline. All - // duplicates should be copied if the first variant is copied, or discarded otherwise. - // 2. When renditions with null URLs are permitted, they must not increment trackIndex so as to - // avoid breaking stream keys that have been persisted for offline. All renitions with null - // URLs should be copied. They may become unreachable if all variants that reference them are - // removed, but this is OK. - // 3. Renditions with URLs matching copied variants should always themselves be copied, even if - // the corresponding stream key is omitted. Else we're throwing away information for no gain. - for (int i = 0; i < streams.size(); i++) { - T stream = streams.get(i); - for (int j = 0; j < streamKeys.size(); j++) { - StreamKey streamKey = streamKeys.get(j); - if (streamKey.groupIndex == groupIndex && streamKey.streamIndex == i) { - copiedStreams.add(stream); - break; - } - } - } - return copiedStreams; - } - -} \ No newline at end of file +//package br.com.suamusica.player.media.parser; +// +//import android.net.Uri; +// +//import androidx.annotation.Nullable; +// +//import androidx.media3.common.Format; +//import androidx.media3.common.DrmInitData; +//import androidx.media3.common.StreamKey; +//import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; +//import androidx.media3.common.MimeTypes; +// +//import java.util.ArrayList; +//import java.util.Collections; +//import java.util.HashMap; +//import java.util.List; +//import java.util.Map; +// +//public final class CustomHlsMasterPlaylist extends HlsPlaylist { +// +// /** Represents an empty master playlist, from which no attributes can be inherited. */ +// public static final CustomHlsMasterPlaylist EMPTY = +// new CustomHlsMasterPlaylist( +// /* baseUri= */ "", +// /* tags= */ new ArrayList(), +// /* variants= */ new ArrayList(), +// /* videos= */ new ArrayList(), +// /* audios= */ new ArrayList(), +// /* subtitles= */ new ArrayList(), +// /* closedCaptions= */ new ArrayList(), +// /* muxedAudioFormat= */ null, +// /* muxedCaptionFormats= */ new ArrayList(), +// /* hasIndependentSegments= */ false, +// /* variableDefinitions= */ new HashMap(), +// /* sessionKeyDrmInitData= */ new ArrayList()); +// +// // These constants must not be changed because they are persisted in offline stream keys. +// public static final int GROUP_INDEX_VARIANT = 0; +// public static final int GROUP_INDEX_AUDIO = 1; +// public static final int GROUP_INDEX_SUBTITLE = 2; +// +// /** A variant (i.e. an #EXT-X-STREAM-INF tag) in a master playlist. */ +// public static final class Variant { +// +// /** The variant's url. */ +// public final Uri url; +// +// /** Format information associated with this variant. */ +// public final Format format; +// +// /** The video rendition group referenced by this variant, or {@code null}. */ +// @Nullable +// public final String videoGroupId; +// +// /** The audio rendition group referenced by this variant, or {@code null}. */ +// @Nullable public final String audioGroupId; +// +// /** The subtitle rendition group referenced by this variant, or {@code null}. */ +// @Nullable public final String subtitleGroupId; +// +// /** The caption rendition group referenced by this variant, or {@code null}. */ +// @Nullable public final String captionGroupId; +// +// /** +// * @param url See {@link #url}. +// * @param format See {@link #format}. +// * @param videoGroupId See {@link #videoGroupId}. +// * @param audioGroupId See {@link #audioGroupId}. +// * @param subtitleGroupId See {@link #subtitleGroupId}. +// * @param captionGroupId See {@link #captionGroupId}. +// */ +// public Variant( +// Uri url, +// Format format, +// @Nullable String videoGroupId, +// @Nullable String audioGroupId, +// @Nullable String subtitleGroupId, +// @Nullable String captionGroupId) { +// this.url = url; +// this.format = format; +// this.videoGroupId = videoGroupId; +// this.audioGroupId = audioGroupId; +// this.subtitleGroupId = subtitleGroupId; +// this.captionGroupId = captionGroupId; +// } +// +// /** +// * Creates a variant for a given media playlist url. +// * +// * @param url The media playlist url. +// * @return The variant instance. +// */ +// public static CustomHlsMasterPlaylist.Variant createMediaPlaylistVariantUrl(Uri url) { +// Format format = +// new Format.Builder() +// .setId("0") +// .setLabel(null) +// .setLanguage(null) +// .setSelectionFlags(0) +// .setRoleFlags(0) +// .setAverageBitrate(Format.NO_VALUE) +// .setPeakBitrate(Format.NO_VALUE) +// .setCodecs(null) +// .setContainerMimeType(MimeTypes.APPLICATION_M3U8) +// .setSampleMimeType(null) +// .build(); +// return new CustomHlsMasterPlaylist.Variant( +// url, +// format, +// /* videoGroupId= */ null, +// /* audioGroupId= */ null, +// /* subtitleGroupId= */ null, +// /* captionGroupId= */ null); +// } +// +// /** Returns a copy of this instance with the given {@link Format}. */ +// public CustomHlsMasterPlaylist.Variant copyWithFormat(Format format) { +// return new CustomHlsMasterPlaylist.Variant(url, format, videoGroupId, audioGroupId, subtitleGroupId, captionGroupId); +// } +// } +// +// /** A rendition (i.e. an #EXT-X-MEDIA tag) in a master playlist. */ +// public static final class Rendition { +// +// /** The rendition's url, or null if the tag does not have a URI attribute. */ +// @Nullable public final Uri url; +// +// /** Format information associated with this rendition. */ +// public final Format format; +// +// /** The group to which this rendition belongs. */ +// public final String groupId; +// +// /** The name of the rendition. */ +// public final String name; +// +// /** +// * @param url See {@link #url}. +// * @param format See {@link #format}. +// * @param groupId See {@link #groupId}. +// * @param name See {@link #name}. +// */ +// public Rendition(@Nullable Uri url, Format format, String groupId, String name) { +// this.url = url; +// this.format = format; +// this.groupId = groupId; +// this.name = name; +// } +// +// } +// +// /** All of the media playlist URLs referenced by the playlist. */ +// public final List mediaPlaylistUrls; +// /** The variants declared by the playlist. */ +// public final List variants; +// /** The video renditions declared by the playlist. */ +// public final List videos; +// /** The audio renditions declared by the playlist. */ +// public final List audios; +// /** The subtitle renditions declared by the playlist. */ +// public final List subtitles; +// /** The closed caption renditions declared by the playlist. */ +// public final List closedCaptions; +// +// /** +// * The format of the audio muxed in the variants. May be null if the playlist does not declare any +// * muxed audio. +// */ +// public final Format muxedAudioFormat; +// /** +// * The format of the closed captions declared by the playlist. May be empty if the playlist +// * explicitly declares no captions are available, or null if the playlist does not declare any +// * captions information. +// */ +// public final List muxedCaptionFormats; +// /** Contains variable definitions, as defined by the #EXT-X-DEFINE tag. */ +// public final Map variableDefinitions; +// /** DRM initialization data derived from #EXT-X-SESSION-KEY tags. */ +// public final List sessionKeyDrmInitData; +// +// /** +// * @param baseUri See {@link #baseUri}. +// * @param tags See {@link #tags}. +// * @param variants See {@link #variants}. +// * @param videos See {@link #videos}. +// * @param audios See {@link #audios}. +// * @param subtitles See {@link #subtitles}. +// * @param closedCaptions See {@link #closedCaptions}. +// * @param muxedAudioFormat See {@link #muxedAudioFormat}. +// * @param muxedCaptionFormats See {@link #muxedCaptionFormats}. +// * @param hasIndependentSegments See {@link #hasIndependentSegments}. +// * @param variableDefinitions See {@link #variableDefinitions}. +// * @param sessionKeyDrmInitData See {@link #sessionKeyDrmInitData}. +// */ +// public CustomHlsMasterPlaylist( +// String baseUri, +// List tags, +// List variants, +// List videos, +// List audios, +// List subtitles, +// List closedCaptions, +// Format muxedAudioFormat, +// List muxedCaptionFormats, +// boolean hasIndependentSegments, +// Map variableDefinitions, +// List sessionKeyDrmInitData) { +// super(baseUri, tags, hasIndependentSegments); +// this.mediaPlaylistUrls = +// Collections.unmodifiableList( +// getMediaPlaylistUrls(variants, videos, audios, subtitles, closedCaptions)); +// this.variants = Collections.unmodifiableList(variants); +// this.videos = Collections.unmodifiableList(videos); +// this.audios = Collections.unmodifiableList(audios); +// this.subtitles = Collections.unmodifiableList(subtitles); +// this.closedCaptions = Collections.unmodifiableList(closedCaptions); +// this.muxedAudioFormat = muxedAudioFormat; +// this.muxedCaptionFormats = muxedCaptionFormats != null +// ? Collections.unmodifiableList(muxedCaptionFormats) : null; +// this.variableDefinitions = Collections.unmodifiableMap(variableDefinitions); +// this.sessionKeyDrmInitData = Collections.unmodifiableList(sessionKeyDrmInitData); +// } +// +// @Override +// public CustomHlsMasterPlaylist copy(List streamKeys) { +// return new CustomHlsMasterPlaylist( +// baseUri, +// tags, +// copyStreams(variants, GROUP_INDEX_VARIANT, streamKeys), +// // TODO: Allow stream keys to specify video renditions to be retained. +// /* videos= */ new ArrayList(), +// copyStreams(audios, GROUP_INDEX_AUDIO, streamKeys), +// copyStreams(subtitles, GROUP_INDEX_SUBTITLE, streamKeys), +// // TODO: Update to retain all closed captions. +// /* closedCaptions= */ new ArrayList(), +// muxedAudioFormat, +// muxedCaptionFormats, +// hasIndependentSegments, +// variableDefinitions, +// sessionKeyDrmInitData); +// } +// +// /** +// * Creates a playlist with a single variant. +// * +// * @param variantUrl The url of the single variant. +// * @return A master playlist with a single variant for the provided url. +// */ +// public static CustomHlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUrl) { +// List variant = +// Collections.singletonList(CustomHlsMasterPlaylist.Variant.createMediaPlaylistVariantUrl(Uri.parse(variantUrl))); +// return new CustomHlsMasterPlaylist( +// /* baseUri= */ null, +// /* tags= */ new ArrayList(), +// variant, +// /* videos= */ new ArrayList(), +// /* audios= */ new ArrayList(), +// /* subtitles= */ new ArrayList(), +// /* closedCaptions= */ new ArrayList(), +// /* muxedAudioFormat= */ null, +// /* muxedCaptionFormats= */ null, +// /* hasIndependentSegments= */ false, +// /* variableDefinitions= */ new HashMap(), +// /* sessionKeyDrmInitData= */ new ArrayList()); +// } +// +// private static List getMediaPlaylistUrls( +// List variants, +// List videos, +// List audios, +// List subtitles, +// List closedCaptions) { +// ArrayList mediaPlaylistUrls = new ArrayList<>(); +// for (int i = 0; i < variants.size(); i++) { +// Uri uri = variants.get(i).url; +// if (!mediaPlaylistUrls.contains(uri)) { +// mediaPlaylistUrls.add(uri); +// } +// } +// addMediaPlaylistUrls(videos, mediaPlaylistUrls); +// addMediaPlaylistUrls(audios, mediaPlaylistUrls); +// addMediaPlaylistUrls(subtitles, mediaPlaylistUrls); +// addMediaPlaylistUrls(closedCaptions, mediaPlaylistUrls); +// return mediaPlaylistUrls; +// } +// +// private static void addMediaPlaylistUrls(List renditions, List out) { +// for (int i = 0; i < renditions.size(); i++) { +// Uri uri = renditions.get(i).url; +// if (uri != null && !out.contains(uri)) { +// out.add(uri); +// } +// } +// } +// +// private static List copyStreams( +// List streams, int groupIndex, List streamKeys) { +// List copiedStreams = new ArrayList<>(streamKeys.size()); +// // TODO: +// // 1. When variants with the same URL are not de-duplicated, duplicates must not increment +// // trackIndex so as to avoid breaking stream keys that have been persisted for offline. All +// // duplicates should be copied if the first variant is copied, or discarded otherwise. +// // 2. When renditions with null URLs are permitted, they must not increment trackIndex so as to +// // avoid breaking stream keys that have been persisted for offline. All renitions with null +// // URLs should be copied. They may become unreachable if all variants that reference them are +// // removed, but this is OK. +// // 3. Renditions with URLs matching copied variants should always themselves be copied, even if +// // the corresponding stream key is omitted. Else we're throwing away information for no gain. +// for (int i = 0; i < streams.size(); i++) { +// T stream = streams.get(i); +// for (int j = 0; j < streamKeys.size(); j++) { +// StreamKey streamKey = streamKeys.get(j); +// if (streamKey.groupIndex == groupIndex && streamKey.streamIndex == i) { +// copiedStreams.add(stream); +// break; +// } +// } +// } +// return copiedStreams; +// } +// +//} \ No newline at end of file diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/CustomHlsPlaylistParser.java b/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/CustomHlsPlaylistParser.java index 531b73e4..f352b06e 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/CustomHlsPlaylistParser.java +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/CustomHlsPlaylistParser.java @@ -1,980 +1,980 @@ -package br.com.suamusica.player.media.parser; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.URLEncoder; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Queue; -import java.util.TreeMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.Format; -import com.google.android.exoplayer2.ParserException; -import com.google.android.exoplayer2.drm.DrmInitData; -import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; -import com.google.android.exoplayer2.metadata.Metadata; -import com.google.android.exoplayer2.source.UnrecognizedInputFormatException; -import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry; -import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; -import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; -import com.google.android.exoplayer2.upstream.ParsingLoadable; -import com.google.android.exoplayer2.util.Assertions; -import com.google.android.exoplayer2.util.MimeTypes; -import com.google.android.exoplayer2.util.UriUtil; -import com.google.android.exoplayer2.util.Util; - -import android.util.Log; - -import android.net.Uri; -import android.text.TextUtils; -import android.util.Base64; - -import androidx.annotation.Nullable; - -public class CustomHlsPlaylistParser implements ParsingLoadable.Parser { - - private static final String PLAYLIST_HEADER = "#EXTM3U"; - - private static final String TAG_PREFIX = "#EXT"; - - private static final String TAG_VERSION = "#EXT-X-VERSION"; - private static final String TAG_PLAYLIST_TYPE = "#EXT-X-PLAYLIST-TYPE"; - private static final String TAG_DEFINE = "#EXT-X-DEFINE"; - private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF"; - private static final String TAG_MEDIA = "#EXT-X-MEDIA"; - private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION"; - private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; - private static final String TAG_DISCONTINUITY_SEQUENCE = "#EXT-X-DISCONTINUITY-SEQUENCE"; - private static final String TAG_PROGRAM_DATE_TIME = "#EXT-X-PROGRAM-DATE-TIME"; - private static final String TAG_INIT_SEGMENT = "#EXT-X-MAP"; - private static final String TAG_INDEPENDENT_SEGMENTS = "#EXT-X-INDEPENDENT-SEGMENTS"; - private static final String TAG_MEDIA_DURATION = "#EXTINF"; - private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE"; - private static final String TAG_START = "#EXT-X-START"; - private static final String TAG_ENDLIST = "#EXT-X-ENDLIST"; - private static final String TAG_KEY = "#EXT-X-KEY"; - private static final String TAG_SESSION_KEY = "#EXT-X-SESSION-KEY"; - private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE"; - private static final String TAG_GAP = "#EXT-X-GAP"; - - private static final String TYPE_AUDIO = "AUDIO"; - private static final String TYPE_VIDEO = "VIDEO"; - private static final String TYPE_SUBTITLES = "SUBTITLES"; - private static final String TYPE_CLOSED_CAPTIONS = "CLOSED-CAPTIONS"; - - private static final String METHOD_NONE = "NONE"; - private static final String METHOD_AES_128 = "AES-128"; - private static final String METHOD_SAMPLE_AES = "SAMPLE-AES"; - // Replaced by METHOD_SAMPLE_AES_CTR. Keep for backward compatibility. - private static final String METHOD_SAMPLE_AES_CENC = "SAMPLE-AES-CENC"; - private static final String METHOD_SAMPLE_AES_CTR = "SAMPLE-AES-CTR"; - private static final String KEYFORMAT_PLAYREADY = "com.microsoft.playready"; - private static final String KEYFORMAT_IDENTITY = "identity"; - private static final String KEYFORMAT_WIDEVINE_PSSH_BINARY = - "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; - private static final String KEYFORMAT_WIDEVINE_PSSH_JSON = "com.widevine"; - - private static final String BOOLEAN_TRUE = "YES"; - private static final String BOOLEAN_FALSE = "NO"; - - private static final String ATTR_CLOSED_CAPTIONS_NONE = "CLOSED-CAPTIONS=NONE"; - - private static final Pattern REGEX_AVERAGE_BANDWIDTH = - Pattern.compile("AVERAGE-BANDWIDTH=(\\d+)\\b"); - private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\""); - private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\""); - private static final Pattern REGEX_SUBTITLES = Pattern.compile("SUBTITLES=\"(.+?)\""); - private static final Pattern REGEX_CLOSED_CAPTIONS = Pattern.compile("CLOSED-CAPTIONS=\"(.+?)\""); - private static final Pattern REGEX_BANDWIDTH = Pattern.compile("[^-]BANDWIDTH=(\\d+)\\b"); - private static final Pattern REGEX_CHANNELS = Pattern.compile("CHANNELS=\"(.+?)\""); - private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\""); - private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)"); - private static final Pattern REGEX_FRAME_RATE = Pattern.compile("FRAME-RATE=([\\d\\.]+)\\b"); - private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION - + ":(\\d+)\\b"); - private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b"); - private static final Pattern REGEX_PLAYLIST_TYPE = Pattern.compile(TAG_PLAYLIST_TYPE - + ":(.+)\\b"); - private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE - + ":(\\d+)\\b"); - private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION - + ":([\\d\\.]+)\\b"); - private static final Pattern REGEX_MEDIA_TITLE = - Pattern.compile(TAG_MEDIA_DURATION + ":[\\d\\.]+\\b,(.+)"); - private static final Pattern REGEX_TIME_OFFSET = Pattern.compile("TIME-OFFSET=(-?[\\d\\.]+)\\b"); - private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE - + ":(\\d+(?:@\\d+)?)\\b"); - private static final Pattern REGEX_ATTR_BYTERANGE = - Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\""); - private static final Pattern REGEX_METHOD = - Pattern.compile( - "METHOD=(" - + METHOD_NONE - + "|" - + METHOD_AES_128 - + "|" - + METHOD_SAMPLE_AES - + "|" - + METHOD_SAMPLE_AES_CENC - + "|" - + METHOD_SAMPLE_AES_CTR - + ")" - + "\\s*(?:,|$)"); - private static final Pattern REGEX_KEYFORMAT = Pattern.compile("KEYFORMAT=\"(.+?)\""); - private static final Pattern REGEX_KEYFORMATVERSIONS = - Pattern.compile("KEYFORMATVERSIONS=\"(.+?)\""); - private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\""); - private static final Pattern REGEX_IV = Pattern.compile("IV=([^,.*]+)"); - private static final Pattern REGEX_TYPE = Pattern.compile("TYPE=(" + TYPE_AUDIO + "|" + TYPE_VIDEO - + "|" + TYPE_SUBTITLES + "|" + TYPE_CLOSED_CAPTIONS + ")"); - private static final Pattern REGEX_LANGUAGE = Pattern.compile("LANGUAGE=\"(.+?)\""); - private static final Pattern REGEX_NAME = Pattern.compile("NAME=\"(.+?)\""); - private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\""); - private static final Pattern REGEX_CHARACTERISTICS = Pattern.compile("CHARACTERISTICS=\"(.+?)\""); - private static final Pattern REGEX_INSTREAM_ID = - Pattern.compile("INSTREAM-ID=\"((?:CC|SERVICE)\\d+)\""); - private static final Pattern REGEX_AUTOSELECT = compileBooleanAttrPattern("AUTOSELECT"); - private static final Pattern REGEX_DEFAULT = compileBooleanAttrPattern("DEFAULT"); - private static final Pattern REGEX_FORCED = compileBooleanAttrPattern("FORCED"); - private static final Pattern REGEX_VALUE = Pattern.compile("VALUE=\"(.+?)\""); - private static final Pattern REGEX_IMPORT = Pattern.compile("IMPORT=\"(.+?)\""); - private static final Pattern REGEX_VARIABLE_REFERENCE = - Pattern.compile("\\{\\$([a-zA-Z0-9\\-_]+)\\}"); - - private final CustomHlsMasterPlaylist masterPlaylist; - - /** - * Creates an instance where media playlists are parsed without inheriting attributes from a - * master playlist. - */ - public CustomHlsPlaylistParser() { - this(CustomHlsMasterPlaylist.EMPTY); - } - - /** - * Creates an instance where parsed media playlists inherit attributes from the given master - * playlist. - * - * @param masterPlaylist The master playlist from which media playlists will inherit attributes. - */ - public CustomHlsPlaylistParser(CustomHlsMasterPlaylist masterPlaylist) { - this.masterPlaylist = masterPlaylist; - } - - @Override - public HlsPlaylist parse(Uri uri, InputStream inputStream) throws IOException { - Log.i("MusicService", "Player : Parser..."); - BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); - Queue extraLines = new ArrayDeque<>(); - String line; - try { - if (!checkPlaylistHeader(reader)) { - throw new UnrecognizedInputFormatException("Input does not start with the #EXTM3U header.", - uri); - } - while ((line = reader.readLine()) != null) { - line = line.trim(); - Log.i("MusicService", "Player : Parser : Line 0: " + line); - if (line.isEmpty()) { - // Do nothing. - } else if (line.startsWith(TAG_STREAM_INF)) { - extraLines.add(line); - return parseMasterPlaylist(new CustomHlsPlaylistParser.LineIterator(extraLines, reader), uri.toString()); - } else if (line.startsWith(TAG_TARGET_DURATION) - || line.startsWith(TAG_MEDIA_SEQUENCE) - || line.startsWith(TAG_MEDIA_DURATION) - || line.startsWith(TAG_KEY) - || line.startsWith(TAG_BYTERANGE) - || line.equals(TAG_DISCONTINUITY) - || line.equals(TAG_DISCONTINUITY_SEQUENCE) - || line.equals(TAG_ENDLIST)) { - extraLines.add(line); - return parseMediaPlaylist( - masterPlaylist, new CustomHlsPlaylistParser.LineIterator(extraLines, reader), uri.toString()); - } else { - extraLines.add(line); - } - } - } finally { - Util.closeQuietly(reader); - } - throw ParserException.createForUnsupportedContainerFeature("Failed to parse the playlist, could not identify any tags."); - } - - private static boolean checkPlaylistHeader(BufferedReader reader) throws IOException { - int last = reader.read(); - if (last == 0xEF) { - if (reader.read() != 0xBB || reader.read() != 0xBF) { - return false; - } - // The playlist contains a Byte Order Mark, which gets discarded. - last = reader.read(); - } - last = skipIgnorableWhitespace(reader, true, last); - int playlistHeaderLength = PLAYLIST_HEADER.length(); - for (int i = 0; i < playlistHeaderLength; i++) { - if (last != PLAYLIST_HEADER.charAt(i)) { - return false; - } - last = reader.read(); - } - last = skipIgnorableWhitespace(reader, false, last); - return Util.isLinebreak(last); - } - - private static int skipIgnorableWhitespace(BufferedReader reader, boolean skipLinebreaks, int c) - throws IOException { - while (c != -1 && Character.isWhitespace(c) && (skipLinebreaks || !Util.isLinebreak(c))) { - c = reader.read(); - } - return c; - } - - private static CustomHlsMasterPlaylist parseMasterPlaylist(CustomHlsPlaylistParser.LineIterator iterator, String baseUri) - throws IOException { - HashMap> urlToVariantInfos = new HashMap<>(); - HashMap variableDefinitions = new HashMap<>(); - ArrayList variants = new ArrayList<>(); - ArrayList videos = new ArrayList<>(); - ArrayList audios = new ArrayList<>(); - ArrayList subtitles = new ArrayList<>(); - ArrayList closedCaptions = new ArrayList<>(); - ArrayList mediaTags = new ArrayList<>(); - ArrayList sessionKeyDrmInitData = new ArrayList<>(); - ArrayList tags = new ArrayList<>(); - Format muxedAudioFormat = null; - List muxedCaptionFormats = null; - boolean noClosedCaptions = false; - boolean hasIndependentSegmentsTag = false; - - String line; - while (iterator.hasNext()) { - line = iterator.next(); - - Log.i("MusicService", "Player : Parser : Line 1 : " + line); - - if (line.startsWith(TAG_PREFIX)) { - // We expose all tags through the playlist. - tags.add(line); - } - - if (line.startsWith(TAG_DEFINE)) { - variableDefinitions.put( - /* key= */ parseStringAttr(line, REGEX_NAME, variableDefinitions), - /* value= */ parseStringAttr(line, REGEX_VALUE, variableDefinitions)); - } else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) { - hasIndependentSegmentsTag = true; - } else if (line.startsWith(TAG_MEDIA)) { - // Media tags are parsed at the end to include codec information from #EXT-X-STREAM-INF - // tags. - mediaTags.add(line); - } else if (line.startsWith(TAG_SESSION_KEY)) { - String keyFormat = - parseOptionalStringAttr(line, REGEX_KEYFORMAT, KEYFORMAT_IDENTITY, variableDefinitions); - DrmInitData.SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions); - if (schemeData != null) { - String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions); - String scheme = parseEncryptionScheme(method); - sessionKeyDrmInitData.add(new DrmInitData(scheme, schemeData)); - } - } else if (line.startsWith(TAG_STREAM_INF)) { - noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE); - int bitrate = parseIntAttr(line, REGEX_BANDWIDTH); - String averageBandwidthString = - parseOptionalStringAttr(line, REGEX_AVERAGE_BANDWIDTH, variableDefinitions); - if (averageBandwidthString != null) { - // If available, the average bandwidth attribute is used as the variant's bitrate. - bitrate = Integer.parseInt(averageBandwidthString); - } - String codecs = parseOptionalStringAttr(line, REGEX_CODECS, variableDefinitions); - String resolutionString = - parseOptionalStringAttr(line, REGEX_RESOLUTION, variableDefinitions); - int width; - int height; - if (resolutionString != null) { - String[] widthAndHeight = resolutionString.split("x"); - width = Integer.parseInt(widthAndHeight[0]); - height = Integer.parseInt(widthAndHeight[1]); - if (width <= 0 || height <= 0) { - // Resolution string is invalid. - width = Format.NO_VALUE; - height = Format.NO_VALUE; - } - } else { - width = Format.NO_VALUE; - height = Format.NO_VALUE; - } - float frameRate = Format.NO_VALUE; - String frameRateString = - parseOptionalStringAttr(line, REGEX_FRAME_RATE, variableDefinitions); - if (frameRateString != null) { - frameRate = Float.parseFloat(frameRateString); - } - String videoGroupId = parseOptionalStringAttr(line, REGEX_VIDEO, variableDefinitions); - String audioGroupId = parseOptionalStringAttr(line, REGEX_AUDIO, variableDefinitions); - String subtitlesGroupId = - parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions); - String closedCaptionsGroupId = - parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions); - line = - replaceVariableReferences( - iterator.next(), variableDefinitions); // #EXT-X-STREAM-INF's URI. - Uri uri = UriUtil.resolveToUri(baseUri, line); - Format format = - new Format.Builder() - .setId(Integer.toString(variants.size())) - .setLabel(null) - .setSelectionFlags(0) - .setRoleFlags(0) - .setAverageBitrate(bitrate) - .setPeakBitrate(bitrate) - .setCodecs(codecs) - .setMetadata(null) - .setContainerMimeType(MimeTypes.APPLICATION_M3U8) - .setSampleMimeType(null) - .setInitializationData(null) - .setWidth(width) - .setHeight(height) - .setFrameRate(frameRate) - .build(); - CustomHlsMasterPlaylist.Variant variant = - new CustomHlsMasterPlaylist.Variant( - uri, format, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId); - variants.add(variant); - ArrayList variantInfosForUrl = urlToVariantInfos.get(uri); - if (variantInfosForUrl == null) { - variantInfosForUrl = new ArrayList<>(); - urlToVariantInfos.put(uri, variantInfosForUrl); - } - variantInfosForUrl.add(new HlsTrackMetadataEntry.VariantInfo(bitrate, bitrate, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId)); - } - } - - // TODO: Don't deduplicate variants by URL. - ArrayList deduplicatedVariants = new ArrayList<>(); - HashSet urlsInDeduplicatedVariants = new HashSet<>(); - for (int i = 0; i < variants.size(); i++) { - CustomHlsMasterPlaylist.Variant variant = variants.get(i); - if (urlsInDeduplicatedVariants.add(variant.url)) { - Assertions.checkState(variant.format.metadata == null); - HlsTrackMetadataEntry hlsMetadataEntry = - new HlsTrackMetadataEntry( - /* groupId= */ null, /* name= */ null, urlToVariantInfos.get(variant.url)); - deduplicatedVariants.add( - variant.copyWithFormat( - variant.format.buildUpon().setMetadata((new Metadata(hlsMetadataEntry))).build())); - } - } - - for (int i = 0; i < mediaTags.size(); i++) { - line = mediaTags.get(i); - String groupId = parseStringAttr(line, REGEX_GROUP_ID, variableDefinitions); - String name = parseStringAttr(line, REGEX_NAME, variableDefinitions); - String referenceUri = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions); - Uri uri = referenceUri == null ? null : UriUtil.resolveToUri(baseUri, referenceUri); - String language = parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions); - @C.SelectionFlags int selectionFlags = parseSelectionFlags(line); - @C.RoleFlags int roleFlags = parseRoleFlags(line, variableDefinitions); - String formatId = groupId + ":" + name; - Format format; - Metadata metadata = - new Metadata(new HlsTrackMetadataEntry(groupId, name, new ArrayList())); - switch (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) { - case TYPE_VIDEO: - CustomHlsMasterPlaylist.Variant variant = getVariantWithVideoGroup(variants, groupId); - String codecs = null; - int width = Format.NO_VALUE; - int height = Format.NO_VALUE; - float frameRate = Format.NO_VALUE; - if (variant != null) { - Format variantFormat = variant.format; - codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO); - width = variantFormat.width; - height = variantFormat.height; - frameRate = variantFormat.frameRate; - } - String sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null; - format = - new Format.Builder() - .setId(formatId) - .setLabel(name) - .setSelectionFlags(selectionFlags) - .setRoleFlags(roleFlags) - .setAverageBitrate(Format.NO_VALUE) - .setPeakBitrate(Format.NO_VALUE) - .setCodecs(codecs) - .setMetadata(metadata) - .setContainerMimeType(MimeTypes.APPLICATION_M3U8) - .setSampleMimeType(sampleMimeType) - .setInitializationData(null) - .setWidth(width) - .setHeight(height) - .setFrameRate(frameRate) - .build(); - //.copyWithMetadata(metadata); - if (uri == null) { - // TODO: Remove this case and add a Rendition with a null uri to videos. - } else { - videos.add(new CustomHlsMasterPlaylist.Rendition(uri, format, groupId, name)); - } - break; - case TYPE_AUDIO: - variant = getVariantWithAudioGroup(variants, groupId); - codecs = - variant != null - ? Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_AUDIO) - : null; - sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null; - int channelCount = parseChannelsAttribute(line, variableDefinitions); - format = new Format.Builder() - .setId(formatId) - .setLabel(name) - .setLanguage(language) - .setSelectionFlags(selectionFlags) - .setRoleFlags(roleFlags) - .setAverageBitrate(Format.NO_VALUE) - .setPeakBitrate(Format.NO_VALUE) - .setCodecs(codecs) - .setMetadata(metadata) - .setContainerMimeType(MimeTypes.APPLICATION_M3U8) - .setSampleMimeType(sampleMimeType) - .setInitializationData(null) - .setChannelCount(channelCount) - .setSampleRate(Format.NO_VALUE) - .build(); - if (uri == null) { - // TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios. - muxedAudioFormat = format; - } else { - audios.add(new CustomHlsMasterPlaylist.Rendition(uri, format.buildUpon().setMetadata(metadata).build(), groupId, name)); - } - break; - case TYPE_SUBTITLES: - format = - new Format.Builder() - .setId(formatId) - .setLabel(name) - .setLanguage(language) - .setSelectionFlags(selectionFlags) - .setRoleFlags(roleFlags) - .setAverageBitrate(Format.NO_VALUE) - .setPeakBitrate(Format.NO_VALUE) - .setCodecs(null) - .setContainerMimeType(MimeTypes.APPLICATION_M3U8) - .setSampleMimeType(MimeTypes.TEXT_VTT) - .setMetadata(metadata) - .build(); - subtitles.add(new CustomHlsMasterPlaylist.Rendition(uri, format, groupId, name)); - break; - case TYPE_CLOSED_CAPTIONS: - String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID, variableDefinitions); - String mimeType; - int accessibilityChannel; - if (instreamId.startsWith("CC")) { - mimeType = MimeTypes.APPLICATION_CEA608; - accessibilityChannel = Integer.parseInt(instreamId.substring(2)); - } else /* starts with SERVICE */ { - mimeType = MimeTypes.APPLICATION_CEA708; - accessibilityChannel = Integer.parseInt(instreamId.substring(7)); - } - if (muxedCaptionFormats == null) { - muxedCaptionFormats = new ArrayList<>(); - } - muxedCaptionFormats.add( - new Format.Builder() - .setId(formatId) - .setLabel(name) - .setLanguage(language) - .setSelectionFlags(selectionFlags) - .setRoleFlags(roleFlags) - .setAverageBitrate(Format.NO_VALUE) - .setPeakBitrate(Format.NO_VALUE) - .setCodecs(null) - .setContainerMimeType(null) - .setSampleMimeType(mimeType) - .setAccessibilityChannel(accessibilityChannel) - .build()); - - // TODO: Remove muxedCaptionFormats and add a Rendition with a null uri to closedCaptions. - break; - default: - // Do nothing. - break; - } - } - - if (noClosedCaptions) { - muxedCaptionFormats = Collections.emptyList(); - } - - return new CustomHlsMasterPlaylist( - URLEncoder.encode(baseUri, "utf-8"), - tags, - deduplicatedVariants, - videos, - audios, - subtitles, - closedCaptions, - muxedAudioFormat, - muxedCaptionFormats, - hasIndependentSegmentsTag, - variableDefinitions, - sessionKeyDrmInitData); - } - - private static CustomHlsMasterPlaylist.Variant getVariantWithAudioGroup(ArrayList variants, String groupId) { - for (int i = 0; i < variants.size(); i++) { - CustomHlsMasterPlaylist.Variant variant = variants.get(i); - if (groupId.equals(variant.audioGroupId)) { - return variant; - } - } - return null; - } - - private static CustomHlsMasterPlaylist.Variant getVariantWithVideoGroup(ArrayList variants, String groupId) { - for (int i = 0; i < variants.size(); i++) { - CustomHlsMasterPlaylist.Variant variant = variants.get(i); - if (groupId.equals(variant.videoGroupId)) { - return variant; - } - } - return null; - } - - private static HlsMediaPlaylist parseMediaPlaylist( - CustomHlsMasterPlaylist masterPlaylist, CustomHlsPlaylistParser.LineIterator iterator, String baseUri) throws IOException { - @HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN; - long startOffsetUs = C.TIME_UNSET; - long mediaSequence = 0; - int version = 1; // Default version == 1. - long targetDurationUs = C.TIME_UNSET; - boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments; - boolean hasEndTag = false; - HlsMediaPlaylist.Segment initializationSegment = null; - HashMap variableDefinitions = new HashMap<>(); - List segments = new ArrayList<>(); - List tags = new ArrayList<>(); - - long segmentDurationUs = 0; - String segmentTitle = ""; - boolean hasDiscontinuitySequence = false; - int playlistDiscontinuitySequence = 0; - int relativeDiscontinuitySequence = 0; - long playlistStartTimeUs = 0; - long segmentStartTimeUs = 0; - long segmentByteRangeOffset = 0; - long segmentByteRangeLength = C.LENGTH_UNSET; - long segmentMediaSequence = 0; - boolean hasGapTag = false; - - DrmInitData playlistProtectionSchemes = null; - String fullSegmentEncryptionKeyUri = null; - String fullSegmentEncryptionIV = null; - TreeMap currentSchemeDatas = new TreeMap<>(); - String encryptionScheme = null; - DrmInitData cachedDrmInitData = null; - List trailingParts = new ArrayList<>(); - Map renditionReports = new HashMap<>(); - HlsMediaPlaylist.ServerControl serverControl = new HlsMediaPlaylist.ServerControl(0,false,0,0,false); - List updatedParts = new ArrayList<>(); - - String line; - while (iterator.hasNext()) { - line = iterator.next(); - - Log.i("MusicService", "Player : Parser : Line 2: " + line); - - if (line.startsWith(TAG_PREFIX)) { - // We expose all tags through the playlist. - tags.add(line); - } - - if (line.startsWith(TAG_PLAYLIST_TYPE)) { - String playlistTypeString = parseStringAttr(line, REGEX_PLAYLIST_TYPE, variableDefinitions); - if ("VOD".equals(playlistTypeString)) { - playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_VOD; - } else if ("EVENT".equals(playlistTypeString)) { - playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_EVENT; - } - } else if (line.startsWith(TAG_START)) { - startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND); - } else if (line.startsWith(TAG_INIT_SEGMENT)) { - String uri = parseStringAttr(line, REGEX_URI, variableDefinitions); - String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE, variableDefinitions); - if (byteRange != null) { - String[] splitByteRange = byteRange.split("@"); - segmentByteRangeLength = Long.parseLong(splitByteRange[0]); - if (splitByteRange.length > 1) { - segmentByteRangeOffset = Long.parseLong(splitByteRange[1]); - } - } - if (fullSegmentEncryptionKeyUri != null && fullSegmentEncryptionIV == null) { - // See RFC 8216, Section 4.3.2.5. - throw ParserException.createForUnsupportedContainerFeature( - "The encryption IV attribute must be present when an initialization segment is " - + "encrypted with METHOD=AES-128."); - } - initializationSegment = - new HlsMediaPlaylist.Segment( - uri, - segmentByteRangeOffset, - segmentByteRangeLength, - fullSegmentEncryptionKeyUri, - fullSegmentEncryptionIV); - segmentByteRangeOffset = 0; - segmentByteRangeLength = C.LENGTH_UNSET; - } else if (line.startsWith(TAG_TARGET_DURATION)) { - targetDurationUs = parseIntAttr(line, REGEX_TARGET_DURATION) * C.MICROS_PER_SECOND; - } else if (line.startsWith(TAG_MEDIA_SEQUENCE)) { - mediaSequence = parseLongAttr(line, REGEX_MEDIA_SEQUENCE); - segmentMediaSequence = mediaSequence; - } else if (line.startsWith(TAG_VERSION)) { - version = parseIntAttr(line, REGEX_VERSION); - } else if (line.startsWith(TAG_DEFINE)) { - String importName = parseOptionalStringAttr(line, REGEX_IMPORT, variableDefinitions); - if (importName != null) { - String value = masterPlaylist.variableDefinitions.get(importName); - if (value != null) { - variableDefinitions.put(importName, value); - } else { - // The master playlist does not declare the imported variable. Ignore. - } - } else { - variableDefinitions.put( - parseStringAttr(line, REGEX_NAME, variableDefinitions), - parseStringAttr(line, REGEX_VALUE, variableDefinitions)); - } - } else if (line.startsWith(TAG_MEDIA_DURATION)) { - segmentDurationUs = - (long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND); - segmentTitle = parseOptionalStringAttr(line, REGEX_MEDIA_TITLE, "", variableDefinitions); - } else if (line.startsWith(TAG_KEY)) { - String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions); - String keyFormat = - parseOptionalStringAttr(line, REGEX_KEYFORMAT, KEYFORMAT_IDENTITY, variableDefinitions); - fullSegmentEncryptionKeyUri = null; - fullSegmentEncryptionIV = null; - if (METHOD_NONE.equals(method)) { - currentSchemeDatas.clear(); - cachedDrmInitData = null; - } else /* !METHOD_NONE.equals(method) */ { - fullSegmentEncryptionIV = parseOptionalStringAttr(line, REGEX_IV, variableDefinitions); - if (KEYFORMAT_IDENTITY.equals(keyFormat)) { - if (METHOD_AES_128.equals(method)) { - // The segment is fully encrypted using an identity key. - fullSegmentEncryptionKeyUri = parseStringAttr(line, REGEX_URI, variableDefinitions); - } else { - // Do nothing. Samples are encrypted using an identity key, but this is not supported. - // Hopefully, a traditional DRM alternative is also provided. - } - } else { - if (encryptionScheme == null) { - encryptionScheme = parseEncryptionScheme(method); - } - DrmInitData.SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions); - if (schemeData != null) { - cachedDrmInitData = null; - currentSchemeDatas.put(keyFormat, schemeData); - } - } - } - } else if (line.startsWith(TAG_BYTERANGE)) { - String byteRange = parseStringAttr(line, REGEX_BYTERANGE, variableDefinitions); - String[] splitByteRange = byteRange.split("@"); - segmentByteRangeLength = Long.parseLong(splitByteRange[0]); - if (splitByteRange.length > 1) { - segmentByteRangeOffset = Long.parseLong(splitByteRange[1]); - } - } else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) { - hasDiscontinuitySequence = true; - playlistDiscontinuitySequence = Integer.parseInt(line.substring(line.indexOf(':') + 1)); - } else if (line.equals(TAG_DISCONTINUITY)) { - relativeDiscontinuitySequence++; - } else if (line.startsWith(TAG_PROGRAM_DATE_TIME)) { - if (playlistStartTimeUs == 0) { - long programDatetimeUs = - Util.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1))); - playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs; - } - } else if (line.equals(TAG_GAP)) { - hasGapTag = true; - } else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) { - hasIndependentSegmentsTag = true; - } else if (line.equals(TAG_ENDLIST)) { - hasEndTag = true; - } else if (!line.startsWith("#")) { - String segmentEncryptionIV; - if (fullSegmentEncryptionKeyUri == null) { - segmentEncryptionIV = null; - } else if (fullSegmentEncryptionIV != null) { - segmentEncryptionIV = fullSegmentEncryptionIV; - } else { - segmentEncryptionIV = Long.toHexString(segmentMediaSequence); - } - - segmentMediaSequence++; - if (segmentByteRangeLength == C.LENGTH_UNSET) { - segmentByteRangeOffset = 0; - } - - if (cachedDrmInitData == null && !currentSchemeDatas.isEmpty()) { - DrmInitData.SchemeData[] schemeDatas = currentSchemeDatas.values().toArray(new DrmInitData.SchemeData[0]); - cachedDrmInitData = new DrmInitData(encryptionScheme, schemeDatas); - if (playlistProtectionSchemes == null) { - DrmInitData.SchemeData[] playlistSchemeDatas = new DrmInitData.SchemeData[schemeDatas.length]; - for (int i = 0; i < schemeDatas.length; i++) { - playlistSchemeDatas[i] = schemeDatas[i].copyWithData(null); - } - playlistProtectionSchemes = new DrmInitData(encryptionScheme, playlistSchemeDatas); - } - } - - line = URLEncoder.encode(line, "utf-8"); - - Log.i("MediaService", "Player : Parser : Line 3: " + replaceVariableReferences(line, variableDefinitions)); - - segments.add( - new HlsMediaPlaylist.Segment( - replaceVariableReferences(line, variableDefinitions), - initializationSegment, - segmentTitle, - segmentDurationUs, - relativeDiscontinuitySequence, - segmentStartTimeUs, - cachedDrmInitData, - fullSegmentEncryptionKeyUri, - segmentEncryptionIV, - segmentByteRangeOffset, - segmentByteRangeLength, - hasGapTag, - updatedParts)); - segmentStartTimeUs += segmentDurationUs; - segmentDurationUs = 0; - segmentTitle = ""; - if (segmentByteRangeLength != C.LENGTH_UNSET) { - segmentByteRangeOffset += segmentByteRangeLength; - } - segmentByteRangeLength = C.LENGTH_UNSET; - hasGapTag = false; - } - } - - return new HlsMediaPlaylist( - playlistType, - baseUri, - tags, - startOffsetUs, - false, - playlistStartTimeUs, - hasDiscontinuitySequence, - playlistDiscontinuitySequence, - mediaSequence, - version, - targetDurationUs, - targetDurationUs, - hasIndependentSegmentsTag, - hasEndTag, - /* hasProgramDateTime= */ playlistStartTimeUs != 0, - playlistProtectionSchemes, - segments, trailingParts, serverControl, renditionReports); - } - - @C.SelectionFlags - private static int parseSelectionFlags(String line) { - int flags = 0; - if (parseOptionalBooleanAttribute(line, REGEX_DEFAULT, false)) { - flags |= C.SELECTION_FLAG_DEFAULT; - } - if (parseOptionalBooleanAttribute(line, REGEX_FORCED, false)) { - flags |= C.SELECTION_FLAG_FORCED; - } - if (parseOptionalBooleanAttribute(line, REGEX_AUTOSELECT, false)) { - flags |= C.SELECTION_FLAG_AUTOSELECT; - } - return flags; - } - - @C.RoleFlags - private static int parseRoleFlags(String line, Map variableDefinitions) { - String concatenatedCharacteristics = - parseOptionalStringAttr(line, REGEX_CHARACTERISTICS, variableDefinitions); - if (TextUtils.isEmpty(concatenatedCharacteristics)) { - return 0; - } - String[] characteristics = Util.split(concatenatedCharacteristics, ","); - @C.RoleFlags int roleFlags = 0; - if (Util.contains(characteristics, "public.accessibility.describes-video")) { - roleFlags |= C.ROLE_FLAG_DESCRIBES_VIDEO; - } - if (Util.contains(characteristics, "public.accessibility.transcribes-spoken-dialog")) { - roleFlags |= C.ROLE_FLAG_TRANSCRIBES_DIALOG; - } - if (Util.contains(characteristics, "public.accessibility.describes-music-and-sound")) { - roleFlags |= C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND; - } - if (Util.contains(characteristics, "public.easy-to-read")) { - roleFlags |= C.ROLE_FLAG_EASY_TO_READ; - } - return roleFlags; - } - - private static int parseChannelsAttribute(String line, Map variableDefinitions) { - String channelsString = parseOptionalStringAttr(line, REGEX_CHANNELS, variableDefinitions); - return channelsString != null - ? Integer.parseInt(Util.splitAtFirst(channelsString, "/")[0]) - : Format.NO_VALUE; - } - - @Nullable - private static DrmInitData.SchemeData parseDrmSchemeData( - String line, String keyFormat, Map variableDefinitions) - throws ParserException { - String keyFormatVersions = - parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions); - if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) { - String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions); - return new DrmInitData.SchemeData( - C.WIDEVINE_UUID, - MimeTypes.VIDEO_MP4, - Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT)); - } else if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) { - return new DrmInitData.SchemeData(C.WIDEVINE_UUID, "hls", Util.getUtf8Bytes(line)); - } else if (KEYFORMAT_PLAYREADY.equals(keyFormat) && "1".equals(keyFormatVersions)) { - String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions); - byte[] data = Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT); - byte[] psshData = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, data); - return new DrmInitData.SchemeData(C.PLAYREADY_UUID, MimeTypes.VIDEO_MP4, psshData); - } - return null; - } - - private static String parseEncryptionScheme(String method) { - return METHOD_SAMPLE_AES_CENC.equals(method) || METHOD_SAMPLE_AES_CTR.equals(method) - ? C.CENC_TYPE_cenc - : C.CENC_TYPE_cbcs; - } - - private static int parseIntAttr(String line, Pattern pattern) throws ParserException { - return Integer.parseInt(parseStringAttr(line, pattern, new HashMap())); - } - - private static long parseLongAttr(String line, Pattern pattern) throws ParserException { - return Long.parseLong(parseStringAttr(line, pattern, new HashMap())); - } - - private static double parseDoubleAttr(String line, Pattern pattern) throws ParserException { - return Double.parseDouble(parseStringAttr(line, pattern, new HashMap())); - } - - private static String parseStringAttr( - String line, Pattern pattern, Map variableDefinitions) - throws ParserException { - String value = parseOptionalStringAttr(line, pattern, variableDefinitions); - if (value != null) { - return value; - } else { - throw ParserException.createForUnsupportedContainerFeature("Couldn't match " + pattern.pattern() + " in " + line); - } - } - - private static @Nullable - String parseOptionalStringAttr( - String line, Pattern pattern, Map variableDefinitions) { - return parseOptionalStringAttr(line, pattern, null, variableDefinitions); - } - - private static String parseOptionalStringAttr( - String line, - Pattern pattern, - String defaultValue, - Map variableDefinitions) { - Matcher matcher = pattern.matcher(line); - String value = matcher.find() ? matcher.group(1) : defaultValue; - return variableDefinitions.isEmpty() || value == null - ? value - : replaceVariableReferences(value, variableDefinitions); - } - - private static String replaceVariableReferences( - String string, Map variableDefinitions) { - Matcher matcher = REGEX_VARIABLE_REFERENCE.matcher(string); - // TODO: Replace StringBuffer with StringBuilder once Java 9 is available. - StringBuffer stringWithReplacements = new StringBuffer(); - while (matcher.find()) { - String groupName = matcher.group(1); - if (variableDefinitions.containsKey(groupName)) { - matcher.appendReplacement( - stringWithReplacements, Matcher.quoteReplacement(variableDefinitions.get(groupName))); - } else { - // The variable is not defined. The value is ignored. - } - } - matcher.appendTail(stringWithReplacements); - return stringWithReplacements.toString(); - } - - private static boolean parseOptionalBooleanAttribute( - String line, Pattern pattern, boolean defaultValue) { - Matcher matcher = pattern.matcher(line); - if (matcher.find()) { - return matcher.group(1).equals(BOOLEAN_TRUE); - } - return defaultValue; - } - - private static Pattern compileBooleanAttrPattern(String attribute) { - return Pattern.compile(attribute + "=(" + BOOLEAN_FALSE + "|" + BOOLEAN_TRUE + ")"); - } - - private static class LineIterator { - - private final BufferedReader reader; - private final Queue extraLines; - - private String next; - - public LineIterator(Queue extraLines, BufferedReader reader) { - this.extraLines = extraLines; - this.reader = reader; - } - - public boolean hasNext() throws IOException { - if (next != null) { - return true; - } - if (!extraLines.isEmpty()) { - next = extraLines.poll(); - return true; - } - while ((next = reader.readLine()) != null) { - next = next.trim(); - if (!next.isEmpty()) { - return true; - } - } - return false; - } - - public String next() throws IOException { - String result = null; - if (hasNext()) { - result = next; - next = null; - } - return result; - } - - } - -} \ No newline at end of file +//package br.com.suamusica.player.media.parser; +// +//import java.io.BufferedReader; +//import java.io.IOException; +//import java.io.InputStream; +//import java.io.InputStreamReader; +//import java.net.URLEncoder; +//import java.util.ArrayDeque; +//import java.util.ArrayList; +//import java.util.Collections; +//import java.util.HashMap; +//import java.util.HashSet; +//import java.util.List; +//import java.util.Map; +//import java.util.Queue; +//import java.util.TreeMap; +//import java.util.regex.Matcher; +//import java.util.regex.Pattern; +// +//import androidx.media3.common.C; +//import androidx.media3.common.Format; +//import androidx.media3.common.ParserException; +//import androidx.media3.common.DrmInitData; +//import com.google.android.exoplayer2.extractor.mp4.PsshAtomUtil; +//import androidx.media3.common.Metadata; +//import com.google.android.exoplayer2.source.UnrecognizedInputFormatException; +//import com.google.android.exoplayer2.source.hls.HlsTrackMetadataEntry; +//import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; +//import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; +//import com.google.android.exoplayer2.upstream.ParsingLoadable; +//import com.google.android.exoplayer2.util.Assertions; +//import androidx.media3.common.MimeTypes; +//import com.google.android.exoplayer2.util.UriUtil; +//import com.google.android.exoplayer2.util.Util; +// +//import android.util.Log; +// +//import android.net.Uri; +//import android.text.TextUtils; +//import android.util.Base64; +// +//import androidx.annotation.Nullable; +// +//public class CustomHlsPlaylistParser implements ParsingLoadable.Parser { +// +// private static final String PLAYLIST_HEADER = "#EXTM3U"; +// +// private static final String TAG_PREFIX = "#EXT"; +// +// private static final String TAG_VERSION = "#EXT-X-VERSION"; +// private static final String TAG_PLAYLIST_TYPE = "#EXT-X-PLAYLIST-TYPE"; +// private static final String TAG_DEFINE = "#EXT-X-DEFINE"; +// private static final String TAG_STREAM_INF = "#EXT-X-STREAM-INF"; +// private static final String TAG_MEDIA = "#EXT-X-MEDIA"; +// private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION"; +// private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; +// private static final String TAG_DISCONTINUITY_SEQUENCE = "#EXT-X-DISCONTINUITY-SEQUENCE"; +// private static final String TAG_PROGRAM_DATE_TIME = "#EXT-X-PROGRAM-DATE-TIME"; +// private static final String TAG_INIT_SEGMENT = "#EXT-X-MAP"; +// private static final String TAG_INDEPENDENT_SEGMENTS = "#EXT-X-INDEPENDENT-SEGMENTS"; +// private static final String TAG_MEDIA_DURATION = "#EXTINF"; +// private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE"; +// private static final String TAG_START = "#EXT-X-START"; +// private static final String TAG_ENDLIST = "#EXT-X-ENDLIST"; +// private static final String TAG_KEY = "#EXT-X-KEY"; +// private static final String TAG_SESSION_KEY = "#EXT-X-SESSION-KEY"; +// private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE"; +// private static final String TAG_GAP = "#EXT-X-GAP"; +// +// private static final String TYPE_AUDIO = "AUDIO"; +// private static final String TYPE_VIDEO = "VIDEO"; +// private static final String TYPE_SUBTITLES = "SUBTITLES"; +// private static final String TYPE_CLOSED_CAPTIONS = "CLOSED-CAPTIONS"; +// +// private static final String METHOD_NONE = "NONE"; +// private static final String METHOD_AES_128 = "AES-128"; +// private static final String METHOD_SAMPLE_AES = "SAMPLE-AES"; +// // Replaced by METHOD_SAMPLE_AES_CTR. Keep for backward compatibility. +// private static final String METHOD_SAMPLE_AES_CENC = "SAMPLE-AES-CENC"; +// private static final String METHOD_SAMPLE_AES_CTR = "SAMPLE-AES-CTR"; +// private static final String KEYFORMAT_PLAYREADY = "com.microsoft.playready"; +// private static final String KEYFORMAT_IDENTITY = "identity"; +// private static final String KEYFORMAT_WIDEVINE_PSSH_BINARY = +// "urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"; +// private static final String KEYFORMAT_WIDEVINE_PSSH_JSON = "com.widevine"; +// +// private static final String BOOLEAN_TRUE = "YES"; +// private static final String BOOLEAN_FALSE = "NO"; +// +// private static final String ATTR_CLOSED_CAPTIONS_NONE = "CLOSED-CAPTIONS=NONE"; +// +// private static final Pattern REGEX_AVERAGE_BANDWIDTH = +// Pattern.compile("AVERAGE-BANDWIDTH=(\\d+)\\b"); +// private static final Pattern REGEX_VIDEO = Pattern.compile("VIDEO=\"(.+?)\""); +// private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\""); +// private static final Pattern REGEX_SUBTITLES = Pattern.compile("SUBTITLES=\"(.+?)\""); +// private static final Pattern REGEX_CLOSED_CAPTIONS = Pattern.compile("CLOSED-CAPTIONS=\"(.+?)\""); +// private static final Pattern REGEX_BANDWIDTH = Pattern.compile("[^-]BANDWIDTH=(\\d+)\\b"); +// private static final Pattern REGEX_CHANNELS = Pattern.compile("CHANNELS=\"(.+?)\""); +// private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\""); +// private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)"); +// private static final Pattern REGEX_FRAME_RATE = Pattern.compile("FRAME-RATE=([\\d\\.]+)\\b"); +// private static final Pattern REGEX_TARGET_DURATION = Pattern.compile(TAG_TARGET_DURATION +// + ":(\\d+)\\b"); +// private static final Pattern REGEX_VERSION = Pattern.compile(TAG_VERSION + ":(\\d+)\\b"); +// private static final Pattern REGEX_PLAYLIST_TYPE = Pattern.compile(TAG_PLAYLIST_TYPE +// + ":(.+)\\b"); +// private static final Pattern REGEX_MEDIA_SEQUENCE = Pattern.compile(TAG_MEDIA_SEQUENCE +// + ":(\\d+)\\b"); +// private static final Pattern REGEX_MEDIA_DURATION = Pattern.compile(TAG_MEDIA_DURATION +// + ":([\\d\\.]+)\\b"); +// private static final Pattern REGEX_MEDIA_TITLE = +// Pattern.compile(TAG_MEDIA_DURATION + ":[\\d\\.]+\\b,(.+)"); +// private static final Pattern REGEX_TIME_OFFSET = Pattern.compile("TIME-OFFSET=(-?[\\d\\.]+)\\b"); +// private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE +// + ":(\\d+(?:@\\d+)?)\\b"); +// private static final Pattern REGEX_ATTR_BYTERANGE = +// Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\""); +// private static final Pattern REGEX_METHOD = +// Pattern.compile( +// "METHOD=(" +// + METHOD_NONE +// + "|" +// + METHOD_AES_128 +// + "|" +// + METHOD_SAMPLE_AES +// + "|" +// + METHOD_SAMPLE_AES_CENC +// + "|" +// + METHOD_SAMPLE_AES_CTR +// + ")" +// + "\\s*(?:,|$)"); +// private static final Pattern REGEX_KEYFORMAT = Pattern.compile("KEYFORMAT=\"(.+?)\""); +// private static final Pattern REGEX_KEYFORMATVERSIONS = +// Pattern.compile("KEYFORMATVERSIONS=\"(.+?)\""); +// private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\""); +// private static final Pattern REGEX_IV = Pattern.compile("IV=([^,.*]+)"); +// private static final Pattern REGEX_TYPE = Pattern.compile("TYPE=(" + TYPE_AUDIO + "|" + TYPE_VIDEO +// + "|" + TYPE_SUBTITLES + "|" + TYPE_CLOSED_CAPTIONS + ")"); +// private static final Pattern REGEX_LANGUAGE = Pattern.compile("LANGUAGE=\"(.+?)\""); +// private static final Pattern REGEX_NAME = Pattern.compile("NAME=\"(.+?)\""); +// private static final Pattern REGEX_GROUP_ID = Pattern.compile("GROUP-ID=\"(.+?)\""); +// private static final Pattern REGEX_CHARACTERISTICS = Pattern.compile("CHARACTERISTICS=\"(.+?)\""); +// private static final Pattern REGEX_INSTREAM_ID = +// Pattern.compile("INSTREAM-ID=\"((?:CC|SERVICE)\\d+)\""); +// private static final Pattern REGEX_AUTOSELECT = compileBooleanAttrPattern("AUTOSELECT"); +// private static final Pattern REGEX_DEFAULT = compileBooleanAttrPattern("DEFAULT"); +// private static final Pattern REGEX_FORCED = compileBooleanAttrPattern("FORCED"); +// private static final Pattern REGEX_VALUE = Pattern.compile("VALUE=\"(.+?)\""); +// private static final Pattern REGEX_IMPORT = Pattern.compile("IMPORT=\"(.+?)\""); +// private static final Pattern REGEX_VARIABLE_REFERENCE = +// Pattern.compile("\\{\\$([a-zA-Z0-9\\-_]+)\\}"); +// +// private final CustomHlsMasterPlaylist masterPlaylist; +// +// /** +// * Creates an instance where media playlists are parsed without inheriting attributes from a +// * master playlist. +// */ +// public CustomHlsPlaylistParser() { +// this(CustomHlsMasterPlaylist.EMPTY); +// } +// +// /** +// * Creates an instance where parsed media playlists inherit attributes from the given master +// * playlist. +// * +// * @param masterPlaylist The master playlist from which media playlists will inherit attributes. +// */ +// public CustomHlsPlaylistParser(CustomHlsMasterPlaylist masterPlaylist) { +// this.masterPlaylist = masterPlaylist; +// } +// +// @Override +// public HlsPlaylist parse(Uri uri, InputStream inputStream) throws IOException { +// Log.i("MusicService", "Player : Parser..."); +// BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)); +// Queue extraLines = new ArrayDeque<>(); +// String line; +// try { +// if (!checkPlaylistHeader(reader)) { +// throw new UnrecognizedInputFormatException("Input does not start with the #EXTM3U header.", +// uri); +// } +// while ((line = reader.readLine()) != null) { +// line = line.trim(); +// Log.i("MusicService", "Player : Parser : Line 0: " + line); +// if (line.isEmpty()) { +// // Do nothing. +// } else if (line.startsWith(TAG_STREAM_INF)) { +// extraLines.add(line); +// return parseMasterPlaylist(new CustomHlsPlaylistParser.LineIterator(extraLines, reader), uri.toString()); +// } else if (line.startsWith(TAG_TARGET_DURATION) +// || line.startsWith(TAG_MEDIA_SEQUENCE) +// || line.startsWith(TAG_MEDIA_DURATION) +// || line.startsWith(TAG_KEY) +// || line.startsWith(TAG_BYTERANGE) +// || line.equals(TAG_DISCONTINUITY) +// || line.equals(TAG_DISCONTINUITY_SEQUENCE) +// || line.equals(TAG_ENDLIST)) { +// extraLines.add(line); +// return parseMediaPlaylist( +// masterPlaylist, new CustomHlsPlaylistParser.LineIterator(extraLines, reader), uri.toString()); +// } else { +// extraLines.add(line); +// } +// } +// } finally { +// Util.closeQuietly(reader); +// } +// throw ParserException.createForUnsupportedContainerFeature("Failed to parse the playlist, could not identify any tags."); +// } +// +// private static boolean checkPlaylistHeader(BufferedReader reader) throws IOException { +// int last = reader.read(); +// if (last == 0xEF) { +// if (reader.read() != 0xBB || reader.read() != 0xBF) { +// return false; +// } +// // The playlist contains a Byte Order Mark, which gets discarded. +// last = reader.read(); +// } +// last = skipIgnorableWhitespace(reader, true, last); +// int playlistHeaderLength = PLAYLIST_HEADER.length(); +// for (int i = 0; i < playlistHeaderLength; i++) { +// if (last != PLAYLIST_HEADER.charAt(i)) { +// return false; +// } +// last = reader.read(); +// } +// last = skipIgnorableWhitespace(reader, false, last); +// return Util.isLinebreak(last); +// } +// +// private static int skipIgnorableWhitespace(BufferedReader reader, boolean skipLinebreaks, int c) +// throws IOException { +// while (c != -1 && Character.isWhitespace(c) && (skipLinebreaks || !Util.isLinebreak(c))) { +// c = reader.read(); +// } +// return c; +// } +// +// private static CustomHlsMasterPlaylist parseMasterPlaylist(CustomHlsPlaylistParser.LineIterator iterator, String baseUri) +// throws IOException { +// HashMap> urlToVariantInfos = new HashMap<>(); +// HashMap variableDefinitions = new HashMap<>(); +// ArrayList variants = new ArrayList<>(); +// ArrayList videos = new ArrayList<>(); +// ArrayList audios = new ArrayList<>(); +// ArrayList subtitles = new ArrayList<>(); +// ArrayList closedCaptions = new ArrayList<>(); +// ArrayList mediaTags = new ArrayList<>(); +// ArrayList sessionKeyDrmInitData = new ArrayList<>(); +// ArrayList tags = new ArrayList<>(); +// Format muxedAudioFormat = null; +// List muxedCaptionFormats = null; +// boolean noClosedCaptions = false; +// boolean hasIndependentSegmentsTag = false; +// +// String line; +// while (iterator.hasNext()) { +// line = iterator.next(); +// +// Log.i("MusicService", "Player : Parser : Line 1 : " + line); +// +// if (line.startsWith(TAG_PREFIX)) { +// // We expose all tags through the playlist. +// tags.add(line); +// } +// +// if (line.startsWith(TAG_DEFINE)) { +// variableDefinitions.put( +// /* key= */ parseStringAttr(line, REGEX_NAME, variableDefinitions), +// /* value= */ parseStringAttr(line, REGEX_VALUE, variableDefinitions)); +// } else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) { +// hasIndependentSegmentsTag = true; +// } else if (line.startsWith(TAG_MEDIA)) { +// // Media tags are parsed at the end to include codec information from #EXT-X-STREAM-INF +// // tags. +// mediaTags.add(line); +// } else if (line.startsWith(TAG_SESSION_KEY)) { +// String keyFormat = +// parseOptionalStringAttr(line, REGEX_KEYFORMAT, KEYFORMAT_IDENTITY, variableDefinitions); +// DrmInitData.SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions); +// if (schemeData != null) { +// String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions); +// String scheme = parseEncryptionScheme(method); +// sessionKeyDrmInitData.add(new DrmInitData(scheme, schemeData)); +// } +// } else if (line.startsWith(TAG_STREAM_INF)) { +// noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE); +// int bitrate = parseIntAttr(line, REGEX_BANDWIDTH); +// String averageBandwidthString = +// parseOptionalStringAttr(line, REGEX_AVERAGE_BANDWIDTH, variableDefinitions); +// if (averageBandwidthString != null) { +// // If available, the average bandwidth attribute is used as the variant's bitrate. +// bitrate = Integer.parseInt(averageBandwidthString); +// } +// String codecs = parseOptionalStringAttr(line, REGEX_CODECS, variableDefinitions); +// String resolutionString = +// parseOptionalStringAttr(line, REGEX_RESOLUTION, variableDefinitions); +// int width; +// int height; +// if (resolutionString != null) { +// String[] widthAndHeight = resolutionString.split("x"); +// width = Integer.parseInt(widthAndHeight[0]); +// height = Integer.parseInt(widthAndHeight[1]); +// if (width <= 0 || height <= 0) { +// // Resolution string is invalid. +// width = Format.NO_VALUE; +// height = Format.NO_VALUE; +// } +// } else { +// width = Format.NO_VALUE; +// height = Format.NO_VALUE; +// } +// float frameRate = Format.NO_VALUE; +// String frameRateString = +// parseOptionalStringAttr(line, REGEX_FRAME_RATE, variableDefinitions); +// if (frameRateString != null) { +// frameRate = Float.parseFloat(frameRateString); +// } +// String videoGroupId = parseOptionalStringAttr(line, REGEX_VIDEO, variableDefinitions); +// String audioGroupId = parseOptionalStringAttr(line, REGEX_AUDIO, variableDefinitions); +// String subtitlesGroupId = +// parseOptionalStringAttr(line, REGEX_SUBTITLES, variableDefinitions); +// String closedCaptionsGroupId = +// parseOptionalStringAttr(line, REGEX_CLOSED_CAPTIONS, variableDefinitions); +// line = +// replaceVariableReferences( +// iterator.next(), variableDefinitions); // #EXT-X-STREAM-INF's URI. +// Uri uri = UriUtil.resolveToUri(baseUri, line); +// Format format = +// new Format.Builder() +// .setId(Integer.toString(variants.size())) +// .setLabel(null) +// .setSelectionFlags(0) +// .setRoleFlags(0) +// .setAverageBitrate(bitrate) +// .setPeakBitrate(bitrate) +// .setCodecs(codecs) +// .setMetadata(null) +// .setContainerMimeType(MimeTypes.APPLICATION_M3U8) +// .setSampleMimeType(null) +// .setInitializationData(null) +// .setWidth(width) +// .setHeight(height) +// .setFrameRate(frameRate) +// .build(); +// CustomHlsMasterPlaylist.Variant variant = +// new CustomHlsMasterPlaylist.Variant( +// uri, format, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId); +// variants.add(variant); +// ArrayList variantInfosForUrl = urlToVariantInfos.get(uri); +// if (variantInfosForUrl == null) { +// variantInfosForUrl = new ArrayList<>(); +// urlToVariantInfos.put(uri, variantInfosForUrl); +// } +// variantInfosForUrl.add(new HlsTrackMetadataEntry.VariantInfo(bitrate, bitrate, videoGroupId, audioGroupId, subtitlesGroupId, closedCaptionsGroupId)); +// } +// } +// +// // TODO: Don't deduplicate variants by URL. +// ArrayList deduplicatedVariants = new ArrayList<>(); +// HashSet urlsInDeduplicatedVariants = new HashSet<>(); +// for (int i = 0; i < variants.size(); i++) { +// CustomHlsMasterPlaylist.Variant variant = variants.get(i); +// if (urlsInDeduplicatedVariants.add(variant.url)) { +// Assertions.checkState(variant.format.metadata == null); +// HlsTrackMetadataEntry hlsMetadataEntry = +// new HlsTrackMetadataEntry( +// /* groupId= */ null, /* name= */ null, urlToVariantInfos.get(variant.url)); +// deduplicatedVariants.add( +// variant.copyWithFormat( +// variant.format.buildUpon().setMetadata((new Metadata(hlsMetadataEntry))).build())); +// } +// } +// +// for (int i = 0; i < mediaTags.size(); i++) { +// line = mediaTags.get(i); +// String groupId = parseStringAttr(line, REGEX_GROUP_ID, variableDefinitions); +// String name = parseStringAttr(line, REGEX_NAME, variableDefinitions); +// String referenceUri = parseOptionalStringAttr(line, REGEX_URI, variableDefinitions); +// Uri uri = referenceUri == null ? null : UriUtil.resolveToUri(baseUri, referenceUri); +// String language = parseOptionalStringAttr(line, REGEX_LANGUAGE, variableDefinitions); +// @C.SelectionFlags int selectionFlags = parseSelectionFlags(line); +// @C.RoleFlags int roleFlags = parseRoleFlags(line, variableDefinitions); +// String formatId = groupId + ":" + name; +// Format format; +// Metadata metadata = +// new Metadata(new HlsTrackMetadataEntry(groupId, name, new ArrayList())); +// switch (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) { +// case TYPE_VIDEO: +// CustomHlsMasterPlaylist.Variant variant = getVariantWithVideoGroup(variants, groupId); +// String codecs = null; +// int width = Format.NO_VALUE; +// int height = Format.NO_VALUE; +// float frameRate = Format.NO_VALUE; +// if (variant != null) { +// Format variantFormat = variant.format; +// codecs = Util.getCodecsOfType(variantFormat.codecs, C.TRACK_TYPE_VIDEO); +// width = variantFormat.width; +// height = variantFormat.height; +// frameRate = variantFormat.frameRate; +// } +// String sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null; +// format = +// new Format.Builder() +// .setId(formatId) +// .setLabel(name) +// .setSelectionFlags(selectionFlags) +// .setRoleFlags(roleFlags) +// .setAverageBitrate(Format.NO_VALUE) +// .setPeakBitrate(Format.NO_VALUE) +// .setCodecs(codecs) +// .setMetadata(metadata) +// .setContainerMimeType(MimeTypes.APPLICATION_M3U8) +// .setSampleMimeType(sampleMimeType) +// .setInitializationData(null) +// .setWidth(width) +// .setHeight(height) +// .setFrameRate(frameRate) +// .build(); +// //.copyWithMetadata(metadata); +// if (uri == null) { +// // TODO: Remove this case and add a Rendition with a null uri to videos. +// } else { +// videos.add(new CustomHlsMasterPlaylist.Rendition(uri, format, groupId, name)); +// } +// break; +// case TYPE_AUDIO: +// variant = getVariantWithAudioGroup(variants, groupId); +// codecs = +// variant != null +// ? Util.getCodecsOfType(variant.format.codecs, C.TRACK_TYPE_AUDIO) +// : null; +// sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null; +// int channelCount = parseChannelsAttribute(line, variableDefinitions); +// format = new Format.Builder() +// .setId(formatId) +// .setLabel(name) +// .setLanguage(language) +// .setSelectionFlags(selectionFlags) +// .setRoleFlags(roleFlags) +// .setAverageBitrate(Format.NO_VALUE) +// .setPeakBitrate(Format.NO_VALUE) +// .setCodecs(codecs) +// .setMetadata(metadata) +// .setContainerMimeType(MimeTypes.APPLICATION_M3U8) +// .setSampleMimeType(sampleMimeType) +// .setInitializationData(null) +// .setChannelCount(channelCount) +// .setSampleRate(Format.NO_VALUE) +// .build(); +// if (uri == null) { +// // TODO: Remove muxedAudioFormat and add a Rendition with a null uri to audios. +// muxedAudioFormat = format; +// } else { +// audios.add(new CustomHlsMasterPlaylist.Rendition(uri, format.buildUpon().setMetadata(metadata).build(), groupId, name)); +// } +// break; +// case TYPE_SUBTITLES: +// format = +// new Format.Builder() +// .setId(formatId) +// .setLabel(name) +// .setLanguage(language) +// .setSelectionFlags(selectionFlags) +// .setRoleFlags(roleFlags) +// .setAverageBitrate(Format.NO_VALUE) +// .setPeakBitrate(Format.NO_VALUE) +// .setCodecs(null) +// .setContainerMimeType(MimeTypes.APPLICATION_M3U8) +// .setSampleMimeType(MimeTypes.TEXT_VTT) +// .setMetadata(metadata) +// .build(); +// subtitles.add(new CustomHlsMasterPlaylist.Rendition(uri, format, groupId, name)); +// break; +// case TYPE_CLOSED_CAPTIONS: +// String instreamId = parseStringAttr(line, REGEX_INSTREAM_ID, variableDefinitions); +// String mimeType; +// int accessibilityChannel; +// if (instreamId.startsWith("CC")) { +// mimeType = MimeTypes.APPLICATION_CEA608; +// accessibilityChannel = Integer.parseInt(instreamId.substring(2)); +// } else /* starts with SERVICE */ { +// mimeType = MimeTypes.APPLICATION_CEA708; +// accessibilityChannel = Integer.parseInt(instreamId.substring(7)); +// } +// if (muxedCaptionFormats == null) { +// muxedCaptionFormats = new ArrayList<>(); +// } +// muxedCaptionFormats.add( +// new Format.Builder() +// .setId(formatId) +// .setLabel(name) +// .setLanguage(language) +// .setSelectionFlags(selectionFlags) +// .setRoleFlags(roleFlags) +// .setAverageBitrate(Format.NO_VALUE) +// .setPeakBitrate(Format.NO_VALUE) +// .setCodecs(null) +// .setContainerMimeType(null) +// .setSampleMimeType(mimeType) +// .setAccessibilityChannel(accessibilityChannel) +// .build()); +// +// // TODO: Remove muxedCaptionFormats and add a Rendition with a null uri to closedCaptions. +// break; +// default: +// // Do nothing. +// break; +// } +// } +// +// if (noClosedCaptions) { +// muxedCaptionFormats = Collections.emptyList(); +// } +// +// return new CustomHlsMasterPlaylist( +// URLEncoder.encode(baseUri, "utf-8"), +// tags, +// deduplicatedVariants, +// videos, +// audios, +// subtitles, +// closedCaptions, +// muxedAudioFormat, +// muxedCaptionFormats, +// hasIndependentSegmentsTag, +// variableDefinitions, +// sessionKeyDrmInitData); +// } +// +// private static CustomHlsMasterPlaylist.Variant getVariantWithAudioGroup(ArrayList variants, String groupId) { +// for (int i = 0; i < variants.size(); i++) { +// CustomHlsMasterPlaylist.Variant variant = variants.get(i); +// if (groupId.equals(variant.audioGroupId)) { +// return variant; +// } +// } +// return null; +// } +// +// private static CustomHlsMasterPlaylist.Variant getVariantWithVideoGroup(ArrayList variants, String groupId) { +// for (int i = 0; i < variants.size(); i++) { +// CustomHlsMasterPlaylist.Variant variant = variants.get(i); +// if (groupId.equals(variant.videoGroupId)) { +// return variant; +// } +// } +// return null; +// } +// +// private static HlsMediaPlaylist parseMediaPlaylist( +// CustomHlsMasterPlaylist masterPlaylist, CustomHlsPlaylistParser.LineIterator iterator, String baseUri) throws IOException { +// @HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN; +// long startOffsetUs = C.TIME_UNSET; +// long mediaSequence = 0; +// int version = 1; // Default version == 1. +// long targetDurationUs = C.TIME_UNSET; +// boolean hasIndependentSegmentsTag = masterPlaylist.hasIndependentSegments; +// boolean hasEndTag = false; +// HlsMediaPlaylist.Segment initializationSegment = null; +// HashMap variableDefinitions = new HashMap<>(); +// List segments = new ArrayList<>(); +// List tags = new ArrayList<>(); +// +// long segmentDurationUs = 0; +// String segmentTitle = ""; +// boolean hasDiscontinuitySequence = false; +// int playlistDiscontinuitySequence = 0; +// int relativeDiscontinuitySequence = 0; +// long playlistStartTimeUs = 0; +// long segmentStartTimeUs = 0; +// long segmentByteRangeOffset = 0; +// long segmentByteRangeLength = C.LENGTH_UNSET; +// long segmentMediaSequence = 0; +// boolean hasGapTag = false; +// +// DrmInitData playlistProtectionSchemes = null; +// String fullSegmentEncryptionKeyUri = null; +// String fullSegmentEncryptionIV = null; +// TreeMap currentSchemeDatas = new TreeMap<>(); +// String encryptionScheme = null; +// DrmInitData cachedDrmInitData = null; +// List trailingParts = new ArrayList<>(); +// Map renditionReports = new HashMap<>(); +// HlsMediaPlaylist.ServerControl serverControl = new HlsMediaPlaylist.ServerControl(0,false,0,0,false); +// List updatedParts = new ArrayList<>(); +// +// String line; +// while (iterator.hasNext()) { +// line = iterator.next(); +// +// Log.i("MusicService", "Player : Parser : Line 2: " + line); +// +// if (line.startsWith(TAG_PREFIX)) { +// // We expose all tags through the playlist. +// tags.add(line); +// } +// +// if (line.startsWith(TAG_PLAYLIST_TYPE)) { +// String playlistTypeString = parseStringAttr(line, REGEX_PLAYLIST_TYPE, variableDefinitions); +// if ("VOD".equals(playlistTypeString)) { +// playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_VOD; +// } else if ("EVENT".equals(playlistTypeString)) { +// playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_EVENT; +// } +// } else if (line.startsWith(TAG_START)) { +// startOffsetUs = (long) (parseDoubleAttr(line, REGEX_TIME_OFFSET) * C.MICROS_PER_SECOND); +// } else if (line.startsWith(TAG_INIT_SEGMENT)) { +// String uri = parseStringAttr(line, REGEX_URI, variableDefinitions); +// String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE, variableDefinitions); +// if (byteRange != null) { +// String[] splitByteRange = byteRange.split("@"); +// segmentByteRangeLength = Long.parseLong(splitByteRange[0]); +// if (splitByteRange.length > 1) { +// segmentByteRangeOffset = Long.parseLong(splitByteRange[1]); +// } +// } +// if (fullSegmentEncryptionKeyUri != null && fullSegmentEncryptionIV == null) { +// // See RFC 8216, Section 4.3.2.5. +// throw ParserException.createForUnsupportedContainerFeature( +// "The encryption IV attribute must be present when an initialization segment is " +// + "encrypted with METHOD=AES-128."); +// } +// initializationSegment = +// new HlsMediaPlaylist.Segment( +// uri, +// segmentByteRangeOffset, +// segmentByteRangeLength, +// fullSegmentEncryptionKeyUri, +// fullSegmentEncryptionIV); +// segmentByteRangeOffset = 0; +// segmentByteRangeLength = C.LENGTH_UNSET; +// } else if (line.startsWith(TAG_TARGET_DURATION)) { +// targetDurationUs = parseIntAttr(line, REGEX_TARGET_DURATION) * C.MICROS_PER_SECOND; +// } else if (line.startsWith(TAG_MEDIA_SEQUENCE)) { +// mediaSequence = parseLongAttr(line, REGEX_MEDIA_SEQUENCE); +// segmentMediaSequence = mediaSequence; +// } else if (line.startsWith(TAG_VERSION)) { +// version = parseIntAttr(line, REGEX_VERSION); +// } else if (line.startsWith(TAG_DEFINE)) { +// String importName = parseOptionalStringAttr(line, REGEX_IMPORT, variableDefinitions); +// if (importName != null) { +// String value = masterPlaylist.variableDefinitions.get(importName); +// if (value != null) { +// variableDefinitions.put(importName, value); +// } else { +// // The master playlist does not declare the imported variable. Ignore. +// } +// } else { +// variableDefinitions.put( +// parseStringAttr(line, REGEX_NAME, variableDefinitions), +// parseStringAttr(line, REGEX_VALUE, variableDefinitions)); +// } +// } else if (line.startsWith(TAG_MEDIA_DURATION)) { +// segmentDurationUs = +// (long) (parseDoubleAttr(line, REGEX_MEDIA_DURATION) * C.MICROS_PER_SECOND); +// segmentTitle = parseOptionalStringAttr(line, REGEX_MEDIA_TITLE, "", variableDefinitions); +// } else if (line.startsWith(TAG_KEY)) { +// String method = parseStringAttr(line, REGEX_METHOD, variableDefinitions); +// String keyFormat = +// parseOptionalStringAttr(line, REGEX_KEYFORMAT, KEYFORMAT_IDENTITY, variableDefinitions); +// fullSegmentEncryptionKeyUri = null; +// fullSegmentEncryptionIV = null; +// if (METHOD_NONE.equals(method)) { +// currentSchemeDatas.clear(); +// cachedDrmInitData = null; +// } else /* !METHOD_NONE.equals(method) */ { +// fullSegmentEncryptionIV = parseOptionalStringAttr(line, REGEX_IV, variableDefinitions); +// if (KEYFORMAT_IDENTITY.equals(keyFormat)) { +// if (METHOD_AES_128.equals(method)) { +// // The segment is fully encrypted using an identity key. +// fullSegmentEncryptionKeyUri = parseStringAttr(line, REGEX_URI, variableDefinitions); +// } else { +// // Do nothing. Samples are encrypted using an identity key, but this is not supported. +// // Hopefully, a traditional DRM alternative is also provided. +// } +// } else { +// if (encryptionScheme == null) { +// encryptionScheme = parseEncryptionScheme(method); +// } +// DrmInitData.SchemeData schemeData = parseDrmSchemeData(line, keyFormat, variableDefinitions); +// if (schemeData != null) { +// cachedDrmInitData = null; +// currentSchemeDatas.put(keyFormat, schemeData); +// } +// } +// } +// } else if (line.startsWith(TAG_BYTERANGE)) { +// String byteRange = parseStringAttr(line, REGEX_BYTERANGE, variableDefinitions); +// String[] splitByteRange = byteRange.split("@"); +// segmentByteRangeLength = Long.parseLong(splitByteRange[0]); +// if (splitByteRange.length > 1) { +// segmentByteRangeOffset = Long.parseLong(splitByteRange[1]); +// } +// } else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) { +// hasDiscontinuitySequence = true; +// playlistDiscontinuitySequence = Integer.parseInt(line.substring(line.indexOf(':') + 1)); +// } else if (line.equals(TAG_DISCONTINUITY)) { +// relativeDiscontinuitySequence++; +// } else if (line.startsWith(TAG_PROGRAM_DATE_TIME)) { +// if (playlistStartTimeUs == 0) { +// long programDatetimeUs = +// Util.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1))); +// playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs; +// } +// } else if (line.equals(TAG_GAP)) { +// hasGapTag = true; +// } else if (line.equals(TAG_INDEPENDENT_SEGMENTS)) { +// hasIndependentSegmentsTag = true; +// } else if (line.equals(TAG_ENDLIST)) { +// hasEndTag = true; +// } else if (!line.startsWith("#")) { +// String segmentEncryptionIV; +// if (fullSegmentEncryptionKeyUri == null) { +// segmentEncryptionIV = null; +// } else if (fullSegmentEncryptionIV != null) { +// segmentEncryptionIV = fullSegmentEncryptionIV; +// } else { +// segmentEncryptionIV = Long.toHexString(segmentMediaSequence); +// } +// +// segmentMediaSequence++; +// if (segmentByteRangeLength == C.LENGTH_UNSET) { +// segmentByteRangeOffset = 0; +// } +// +// if (cachedDrmInitData == null && !currentSchemeDatas.isEmpty()) { +// DrmInitData.SchemeData[] schemeDatas = currentSchemeDatas.values().toArray(new DrmInitData.SchemeData[0]); +// cachedDrmInitData = new DrmInitData(encryptionScheme, schemeDatas); +// if (playlistProtectionSchemes == null) { +// DrmInitData.SchemeData[] playlistSchemeDatas = new DrmInitData.SchemeData[schemeDatas.length]; +// for (int i = 0; i < schemeDatas.length; i++) { +// playlistSchemeDatas[i] = schemeDatas[i].copyWithData(null); +// } +// playlistProtectionSchemes = new DrmInitData(encryptionScheme, playlistSchemeDatas); +// } +// } +// +// line = URLEncoder.encode(line, "utf-8"); +// +// Log.i("MediaService", "Player : Parser : Line 3: " + replaceVariableReferences(line, variableDefinitions)); +// +// segments.add( +// new HlsMediaPlaylist.Segment( +// replaceVariableReferences(line, variableDefinitions), +// initializationSegment, +// segmentTitle, +// segmentDurationUs, +// relativeDiscontinuitySequence, +// segmentStartTimeUs, +// cachedDrmInitData, +// fullSegmentEncryptionKeyUri, +// segmentEncryptionIV, +// segmentByteRangeOffset, +// segmentByteRangeLength, +// hasGapTag, +// updatedParts)); +// segmentStartTimeUs += segmentDurationUs; +// segmentDurationUs = 0; +// segmentTitle = ""; +// if (segmentByteRangeLength != C.LENGTH_UNSET) { +// segmentByteRangeOffset += segmentByteRangeLength; +// } +// segmentByteRangeLength = C.LENGTH_UNSET; +// hasGapTag = false; +// } +// } +// +// return new HlsMediaPlaylist( +// playlistType, +// baseUri, +// tags, +// startOffsetUs, +// false, +// playlistStartTimeUs, +// hasDiscontinuitySequence, +// playlistDiscontinuitySequence, +// mediaSequence, +// version, +// targetDurationUs, +// targetDurationUs, +// hasIndependentSegmentsTag, +// hasEndTag, +// /* hasProgramDateTime= */ playlistStartTimeUs != 0, +// playlistProtectionSchemes, +// segments, trailingParts, serverControl, renditionReports); +// } +// +// @C.SelectionFlags +// private static int parseSelectionFlags(String line) { +// int flags = 0; +// if (parseOptionalBooleanAttribute(line, REGEX_DEFAULT, false)) { +// flags |= C.SELECTION_FLAG_DEFAULT; +// } +// if (parseOptionalBooleanAttribute(line, REGEX_FORCED, false)) { +// flags |= C.SELECTION_FLAG_FORCED; +// } +// if (parseOptionalBooleanAttribute(line, REGEX_AUTOSELECT, false)) { +// flags |= C.SELECTION_FLAG_AUTOSELECT; +// } +// return flags; +// } +// +// @C.RoleFlags +// private static int parseRoleFlags(String line, Map variableDefinitions) { +// String concatenatedCharacteristics = +// parseOptionalStringAttr(line, REGEX_CHARACTERISTICS, variableDefinitions); +// if (TextUtils.isEmpty(concatenatedCharacteristics)) { +// return 0; +// } +// String[] characteristics = Util.split(concatenatedCharacteristics, ","); +// @C.RoleFlags int roleFlags = 0; +// if (Util.contains(characteristics, "public.accessibility.describes-video")) { +// roleFlags |= C.ROLE_FLAG_DESCRIBES_VIDEO; +// } +// if (Util.contains(characteristics, "public.accessibility.transcribes-spoken-dialog")) { +// roleFlags |= C.ROLE_FLAG_TRANSCRIBES_DIALOG; +// } +// if (Util.contains(characteristics, "public.accessibility.describes-music-and-sound")) { +// roleFlags |= C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND; +// } +// if (Util.contains(characteristics, "public.easy-to-read")) { +// roleFlags |= C.ROLE_FLAG_EASY_TO_READ; +// } +// return roleFlags; +// } +// +// private static int parseChannelsAttribute(String line, Map variableDefinitions) { +// String channelsString = parseOptionalStringAttr(line, REGEX_CHANNELS, variableDefinitions); +// return channelsString != null +// ? Integer.parseInt(Util.splitAtFirst(channelsString, "/")[0]) +// : Format.NO_VALUE; +// } +// +// @Nullable +// private static DrmInitData.SchemeData parseDrmSchemeData( +// String line, String keyFormat, Map variableDefinitions) +// throws ParserException { +// String keyFormatVersions = +// parseOptionalStringAttr(line, REGEX_KEYFORMATVERSIONS, "1", variableDefinitions); +// if (KEYFORMAT_WIDEVINE_PSSH_BINARY.equals(keyFormat)) { +// String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions); +// return new DrmInitData.SchemeData( +// C.WIDEVINE_UUID, +// MimeTypes.VIDEO_MP4, +// Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT)); +// } else if (KEYFORMAT_WIDEVINE_PSSH_JSON.equals(keyFormat)) { +// return new DrmInitData.SchemeData(C.WIDEVINE_UUID, "hls", Util.getUtf8Bytes(line)); +// } else if (KEYFORMAT_PLAYREADY.equals(keyFormat) && "1".equals(keyFormatVersions)) { +// String uriString = parseStringAttr(line, REGEX_URI, variableDefinitions); +// byte[] data = Base64.decode(uriString.substring(uriString.indexOf(',')), Base64.DEFAULT); +// byte[] psshData = PsshAtomUtil.buildPsshAtom(C.PLAYREADY_UUID, data); +// return new DrmInitData.SchemeData(C.PLAYREADY_UUID, MimeTypes.VIDEO_MP4, psshData); +// } +// return null; +// } +// +// private static String parseEncryptionScheme(String method) { +// return METHOD_SAMPLE_AES_CENC.equals(method) || METHOD_SAMPLE_AES_CTR.equals(method) +// ? C.CENC_TYPE_cenc +// : C.CENC_TYPE_cbcs; +// } +// +// private static int parseIntAttr(String line, Pattern pattern) throws ParserException { +// return Integer.parseInt(parseStringAttr(line, pattern, new HashMap())); +// } +// +// private static long parseLongAttr(String line, Pattern pattern) throws ParserException { +// return Long.parseLong(parseStringAttr(line, pattern, new HashMap())); +// } +// +// private static double parseDoubleAttr(String line, Pattern pattern) throws ParserException { +// return Double.parseDouble(parseStringAttr(line, pattern, new HashMap())); +// } +// +// private static String parseStringAttr( +// String line, Pattern pattern, Map variableDefinitions) +// throws ParserException { +// String value = parseOptionalStringAttr(line, pattern, variableDefinitions); +// if (value != null) { +// return value; +// } else { +// throw ParserException.createForUnsupportedContainerFeature("Couldn't match " + pattern.pattern() + " in " + line); +// } +// } +// +// private static @Nullable +// String parseOptionalStringAttr( +// String line, Pattern pattern, Map variableDefinitions) { +// return parseOptionalStringAttr(line, pattern, null, variableDefinitions); +// } +// +// private static String parseOptionalStringAttr( +// String line, +// Pattern pattern, +// String defaultValue, +// Map variableDefinitions) { +// Matcher matcher = pattern.matcher(line); +// String value = matcher.find() ? matcher.group(1) : defaultValue; +// return variableDefinitions.isEmpty() || value == null +// ? value +// : replaceVariableReferences(value, variableDefinitions); +// } +// +// private static String replaceVariableReferences( +// String string, Map variableDefinitions) { +// Matcher matcher = REGEX_VARIABLE_REFERENCE.matcher(string); +// // TODO: Replace StringBuffer with StringBuilder once Java 9 is available. +// StringBuffer stringWithReplacements = new StringBuffer(); +// while (matcher.find()) { +// String groupName = matcher.group(1); +// if (variableDefinitions.containsKey(groupName)) { +// matcher.appendReplacement( +// stringWithReplacements, Matcher.quoteReplacement(variableDefinitions.get(groupName))); +// } else { +// // The variable is not defined. The value is ignored. +// } +// } +// matcher.appendTail(stringWithReplacements); +// return stringWithReplacements.toString(); +// } +// +// private static boolean parseOptionalBooleanAttribute( +// String line, Pattern pattern, boolean defaultValue) { +// Matcher matcher = pattern.matcher(line); +// if (matcher.find()) { +// return matcher.group(1).equals(BOOLEAN_TRUE); +// } +// return defaultValue; +// } +// +// private static Pattern compileBooleanAttrPattern(String attribute) { +// return Pattern.compile(attribute + "=(" + BOOLEAN_FALSE + "|" + BOOLEAN_TRUE + ")"); +// } +// +// private static class LineIterator { +// +// private final BufferedReader reader; +// private final Queue extraLines; +// +// private String next; +// +// public LineIterator(Queue extraLines, BufferedReader reader) { +// this.extraLines = extraLines; +// this.reader = reader; +// } +// +// public boolean hasNext() throws IOException { +// if (next != null) { +// return true; +// } +// if (!extraLines.isEmpty()) { +// next = extraLines.poll(); +// return true; +// } +// while ((next = reader.readLine()) != null) { +// next = next.trim(); +// if (!next.isEmpty()) { +// return true; +// } +// } +// return false; +// } +// +// public String next() throws IOException { +// String result = null; +// if (hasNext()) { +// result = next; +// next = null; +// } +// return result; +// } +// +// } +// +//} \ No newline at end of file diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/SMHlsPlaylistParserFactory.java b/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/SMHlsPlaylistParserFactory.java index 91b425aa..f0277cc0 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/SMHlsPlaylistParserFactory.java +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/media/parser/SMHlsPlaylistParserFactory.java @@ -1,45 +1,45 @@ -package br.com.suamusica.player.media.parser; - -import androidx.annotation.Nullable; - -import java.util.Collections; -import java.util.List; - -import com.google.android.exoplayer2.offline.FilteringManifestParser; -import com.google.android.exoplayer2.offline.StreamKey; -import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist; -import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist; -import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; -import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParserFactory; -import com.google.android.exoplayer2.upstream.ParsingLoadable; - -public final class SMHlsPlaylistParserFactory implements HlsPlaylistParserFactory { - - private final List streamKeys; - - /** Creates an instance that does not filter any parsing results. */ - public SMHlsPlaylistParserFactory() { - this(Collections.emptyList()); - } - - /** - * Creates an instance that filters the parsing results using the given {@code streamKeys}. - * - * @param streamKeys See {@link - * FilteringManifestParser#FilteringManifestParser(ParsingLoadable.Parser, List)}. - */ - public SMHlsPlaylistParserFactory(List streamKeys) { - this.streamKeys = streamKeys; - } - - @Override - public ParsingLoadable.Parser createPlaylistParser() { - return new FilteringManifestParser<>(new CustomHlsPlaylistParser(), streamKeys); - } - - @Override - public ParsingLoadable.Parser createPlaylistParser(HlsMultivariantPlaylist multivariantPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) { - return new FilteringManifestParser<>(new CustomHlsPlaylistParser(), streamKeys); - } - -} +//package br.com.suamusica.player.media.parser; +// +//import androidx.annotation.Nullable; +// +//import java.util.Collections; +//import java.util.List; +// +//import com.google.android.exoplayer2.offline.FilteringManifestParser; +//import androidx.media3.common.StreamKey; +//import com.google.media3.common.source.hls.playlist.HlsMediaPlaylist; +//import com.google.android.exoplayer2.source.hls.playlist.HlsMultivariantPlaylist; +//import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylist; +//import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParserFactory; +//import com.google.android.exoplayer2.upstream.ParsingLoadable; +// +//public final class SMHlsPlaylistParserFactory implements HlsPlaylistParserFactory { +// +// private final List streamKeys; +// +// /** Creates an instance that does not filter any parsing results. */ +// public SMHlsPlaylistParserFactory() { +// this(Collections.emptyList()); +// } +// +// /** +// * Creates an instance that filters the parsing results using the given {@code streamKeys}. +// * +// * @param streamKeys See {@link +// * FilteringManifestParser#FilteringManifestParser(ParsingLoadable.Parser, List)}. +// */ +// public SMHlsPlaylistParserFactory(List streamKeys) { +// this.streamKeys = streamKeys; +// } +// +// @Override +// public ParsingLoadable.Parser createPlaylistParser() { +// return new FilteringManifestParser<>(new CustomHlsPlaylistParser(), streamKeys); +// } +// +// @Override +// public ParsingLoadable.Parser createPlaylistParser(HlsMultivariantPlaylist multivariantPlaylist, @Nullable HlsMediaPlaylist previousMediaPlaylist) { +// return new FilteringManifestParser<>(new CustomHlsPlaylistParser(), streamKeys); +// } +// +//} diff --git a/packages/player/example/.flutter-plugins-dependencies b/packages/player/example/.flutter-plugins-dependencies index de8a34aa..83be2397 100644 --- a/packages/player/example/.flutter-plugins-dependencies +++ b/packages/player/example/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"smplayer","path":"/Users/suamusica/projects/suamusica/flutter_plugins/packages/player/","native_build":true,"dependencies":["isar_flutter_libs"]}],"android":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/","native_build":true,"dependencies":[]},{"name":"smplayer","path":"/Users/suamusica/projects/suamusica/flutter_plugins/packages/player/","native_build":true,"dependencies":["isar_flutter_libs"]}],"macos":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"isar_flutter_libs","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"smplayer","dependencies":["isar_flutter_libs","path_provider"]}],"date_created":"2024-02-05 17:03:28.400968","version":"3.19.0-0.3.pre"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"smplayer","path":"/Users/suamusica/Documents/Dev/SM/flutter_plugins/packages/player/","native_build":true,"dependencies":["isar_flutter_libs"]}],"android":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_android","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_android-2.2.4/","native_build":true,"dependencies":[]},{"name":"smplayer","path":"/Users/suamusica/Documents/Dev/SM/flutter_plugins/packages/player/","native_build":true,"dependencies":["isar_flutter_libs"]}],"macos":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_foundation","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]}],"windows":[{"name":"isar_flutter_libs","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/isar_flutter_libs-3.1.0/","native_build":true,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/suamusica/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]}],"web":[]},"dependencyGraph":[{"name":"isar_flutter_libs","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"smplayer","dependencies":["isar_flutter_libs","path_provider"]}],"date_created":"2024-05-09 11:03:41.030919","version":"3.19.1"} \ No newline at end of file diff --git a/packages/player/example/android/app/build.gradle b/packages/player/example/android/app/build.gradle index 2ed9ff85..9d68c793 100644 --- a/packages/player/example/android/app/build.gradle +++ b/packages/player/example/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion 28 + compileSdkVersion 34 sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -39,8 +39,8 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "br.com.suamusica.suamusica_player_example" - minSdkVersion 16 - targetSdkVersion 30 + minSdkVersion 21 + targetSdkVersion 34 versionCode flutterVersionCode.toInteger() versionName flutterVersionName testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" @@ -54,9 +54,16 @@ android { } } - compileOptions { + + compileOptions { + coreLibraryDesugaringEnabled true + sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + kotlinOptions { + jvmTarget = '1.8' + } + namespace 'br.com.suamusica.suamusica_player_example' } flutter { @@ -65,7 +72,15 @@ flutter { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'com.android.support:multidex:1.0.3' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' + testImplementation 'junit:junit:4.13.2' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' + api ('com.google.ads.interactivemedia.v3:interactivemedia:'){ + version { + strictly '3.33.0' + } + } } diff --git a/packages/player/example/android/app/src/main/AndroidManifest.xml b/packages/player/example/android/app/src/main/AndroidManifest.xml index b23c5249..c9991d80 100644 --- a/packages/player/example/android/app/src/main/AndroidManifest.xml +++ b/packages/player/example/android/app/src/main/AndroidManifest.xml @@ -1,5 +1,5 @@ + xmlns:tools="http://schemas.android.com/tools"> @@ -17,7 +17,8 @@ android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:exported="true"> - - - - \ No newline at end of file diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaControlBroadcastReceiver.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaControlBroadcastReceiver.kt deleted file mode 100644 index cb980bbf..00000000 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaControlBroadcastReceiver.kt +++ /dev/null @@ -1,13 +0,0 @@ -package br.com.suamusica.player - -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import androidx.media3.common.util.UnstableApi - -class MediaControlBroadcastReceiver : BroadcastReceiver() { - @UnstableApi - override fun onReceive(context: Context?, intent: Intent?) { - MediaButtonEventHandler(null).onMediaButtonEventHandler(intent) - } -} \ No newline at end of file From 968c43cb006c04e1976bf71f97e1d20421000e8a Mon Sep 17 00:00:00 2001 From: lstonussi Date: Thu, 22 Aug 2024 17:40:16 -0300 Subject: [PATCH 12/14] remove comments and fix release --- .../player/MediaButtonEventHandler.kt | 1 - .../br/com/suamusica/player/MediaService.kt | 101 +----------------- 2 files changed, 4 insertions(+), 98 deletions(-) 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 54c4bb5f..f50bc7c4 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 @@ -72,7 +72,6 @@ class MediaButtonEventHandler( customCommand: SessionCommand, args: Bundle ): ListenableFuture { - Log.d("Player", "TESTE1 CUSTOM_COMMAND: ${customCommand.customAction} | $args") if (customCommand.customAction == "favoritar" || customCommand.customAction == "desfavoritar") { val isFavorite = customCommand.customAction == "favoritar" buildIcons(isFavorite) diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt index a89896f3..7ab48791 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt @@ -11,9 +11,6 @@ import android.os.Build import android.os.Bundle import android.os.Handler import android.os.PowerManager -import android.support.v4.media.MediaMetadataCompat -import android.support.v4.media.session.MediaControllerCompat -import android.support.v4.media.session.MediaSessionCompat import android.support.v4.media.session.PlaybackStateCompat import android.util.Log import androidx.media3.common.AudioAttributes @@ -183,17 +180,15 @@ class MediaService : MediaSessionService() { } override fun onDestroy() { + removeNotification() Log.d(TAG, "onDestroy") -// mediaController?.unregisterCallback(mediaControllerCallback) releaseLock() - removeNotification() player?.release() stopSelf() mediaSession?.run { - player.release() - release() - mediaSession = null + releaseAndPerformAndDisableTracking() + Log.d("MusicService", "onDestroy") } releasePossibleLeaks() @@ -233,7 +228,6 @@ class MediaService : MediaSessionService() { dataSourceFactory.setUserAgent(userAgent) dataSourceFactory.setAllowCrossProtocolRedirects(true) dataSourceFactory.setDefaultRequestProperties(mapOf("Cookie" to cookie)) - // Metadata Build val metadata = buildMetaData(media) val url = media.url Log.i(TAG, "Player: URL: $url") @@ -341,7 +335,7 @@ class MediaService : MediaSessionService() { } } - fun release() { + fun releaseAndPerformAndDisableTracking() { performAndDisableTracking { player?.stop() } @@ -513,25 +507,6 @@ class MediaService : MediaSessionService() { } } -// fun shouldStartService() { -// if (!isForegroundService) { -// Log.i(TAG, "Starting Service") -// try { -// ContextCompat.startForegroundService( -// applicationContext, Intent(applicationContext, this@MediaService.javaClass) -// ) -// startForeground(NOW_PLAYING_NOTIFICATION, notification) -// } catch (e: Exception) { -// startForeground(NOW_PLAYING_NOTIFICATION, notification) -// ContextCompat.startForegroundService( -// applicationContext, Intent(applicationContext, this@MediaService.javaClass) -// ) -// } -// isForegroundService = true -// -// } -// } - private fun stopService() { if (isForegroundService) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { @@ -544,72 +519,4 @@ class MediaService : MediaSessionService() { Log.i(TAG, "Stopping Service") } } - - private inner class MediaControllerCallback : MediaControllerCompat.Callback() { - override fun onMetadataChanged(metadata: MediaMetadataCompat?) { -// Log.d( -// TAG, "onMetadataChanged: title: ${metadata?.title} duration: ${metadata?.duration}" -// ) - } - - override fun onPlaybackStateChanged(state: PlaybackStateCompat?) { - Log.d(TAG, "onPlaybackStateChanged1 state: $state") -// updateNotification(state!!) - } - - override fun onQueueChanged(queue: MutableList?) { - Log.d(TAG, "onQueueChanged queue: $queue") - } - -// @SuppressLint("WakelockTimeout") -// private fun updateNotification(state: PlaybackStateCompat) { -// Log.d(TAG, "TESTE1 updateNotification") -// if (mediaSession == null) { -// return -// } -// getArts(applicationContext, media?.bigCoverUrl ?: media?.coverUrl) { bitmap -> -// val updatedState = state.state -// val onGoing = -// updatedState == PlaybackStateCompat.STATE_PLAYING || updatedState == PlaybackStateCompat.STATE_BUFFERING -// // Skip building a notification when state is "none". -//// val notification = if (updatedState != PlaybackStateCompat.STATE_NONE) { -//// buildNotification(updatedState, onGoing, bitmap) -//// } else { -//// null -//// } -// Log.d(TAG, "!!! updateNotification state: $updatedState $onGoing") -// -// when (updatedState) { -// PlaybackStateCompat.STATE_BUFFERING, PlaybackStateCompat.STATE_PLAYING -> { -// Log.i(TAG, "updateNotification: STATE_BUFFERING or STATE_PLAYING") -// /** -// * This may look strange, but the documentation for [Service.startForeground] -// * notes that "calling this method does *not* put the service in the started -// * state itself, even though the name sounds like it." -// */ -//// if (notification != null) { -//// notificationManager?.notify(NOW_PLAYING_NOTIFICATION, notification) -//// shouldStartService(notification) -//// } -// } -// -// else -> { -// if (isForegroundService) { -// // If playback has ended, also stop the service. -// if (updatedState == PlaybackStateCompat.STATE_NONE && Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { -// stopService() -// } -//// if (notification != null) { -//// notificationManager?.notify( -//// NOW_PLAYING_NOTIFICATION, -//// notification -//// ) -//// } else -//// removeNowPlayingNotification() -// } -// } -// } -// } -// } - } } From 35491ba0aa961ba441eb7e030fafae13d787901b Mon Sep 17 00:00:00 2001 From: lstonussi Date: Mon, 26 Aug 2024 09:44:01 -0300 Subject: [PATCH 13/14] fix remove notification --- .../player/MediaButtonEventHandler.kt | 72 +++---- .../br/com/suamusica/player/MediaService.kt | 184 +++++++++++------- .../com/suamusica/player/PlayerSingleton.kt | 1 + 3 files changed, 152 insertions(+), 105 deletions(-) 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 f50bc7c4..43dc15b1 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 @@ -5,12 +5,15 @@ import android.os.Build import android.os.Bundle import android.util.Log import android.view.KeyEvent +import androidx.media3.common.MediaItem +import androidx.media3.common.Player 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 @@ -32,20 +35,23 @@ class MediaButtonEventHandler( ): MediaSession.ConnectionResult { Log.d("Player", "onConnect") val sessionCommands = - MediaSession.ConnectionResult.DEFAULT_SESSION_COMMANDS.buildUpon() - .add(SessionCommand("next", Bundle.EMPTY)) - .add(SessionCommand("seek", session.token.extras)) - .add(SessionCommand("previous", Bundle.EMPTY)) - .add(SessionCommand("pause", Bundle.EMPTY)) - .add(SessionCommand("favoritar", Bundle.EMPTY)) - .add(SessionCommand("desfavoritar", Bundle.EMPTY)) - .add(SessionCommand("prepare", 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)) - .build() + 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("prepare", 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)) + }.build() val playerCommands = MediaSession.ConnectionResult.DEFAULT_PLAYER_COMMANDS.buildUpon() @@ -61,19 +67,15 @@ class MediaButtonEventHandler( .build() } - override fun onPostConnect(session: MediaSession, controller: MediaSession.ControllerInfo) { - super.onPostConnect(session, controller) - Log.d("Player", "onPostConnect") - } - override fun onCustomCommand( session: MediaSession, controller: MediaSession.ControllerInfo, customCommand: SessionCommand, args: Bundle ): ListenableFuture { - if (customCommand.customAction == "favoritar" || customCommand.customAction == "desfavoritar") { - val isFavorite = customCommand.customAction == "favoritar" + Log.d("Player", "#MEDIA3# - onCustomCommand ${customCommand.customAction}") + if (customCommand.customAction == "notification_favoritar" || customCommand.customAction == "notification_desfavoritar") { + val isFavorite = customCommand.customAction == "notification_favoritar" buildIcons(isFavorite) PlayerSingleton.favorite(isFavorite) } @@ -86,6 +88,10 @@ class MediaButtonEventHandler( mediaService?.togglePlayPause() } + if (customCommand.customAction == "stop") { + mediaService?.stop() + } + if (customCommand.customAction == "send_notification") { args.let { val isFavorite = it.getBoolean(PlayerPlugin.IS_FAVORITE_ARGUMENT) @@ -96,17 +102,17 @@ class MediaButtonEventHandler( if (customCommand.customAction == "play") { mediaService?.play() } - if (customCommand.customAction == "previous") { + if (customCommand.customAction == "notification_previous") { PlayerSingleton.previous() } - if (customCommand.customAction == "next") { + if (customCommand.customAction == "notification_next") { PlayerSingleton.next() } if (customCommand.customAction == "pause") { mediaService?.pause() } - if (customCommand.customAction == "remove_notification" || customCommand.customAction == "ads_playing" ) { - mediaService?.removeNotification(); + if (customCommand.customAction == "ads_playing" || customCommand.customAction == "remove_notification") { + mediaService?.removeNotification() } if (customCommand.customAction == "prepare") { @@ -140,29 +146,29 @@ class MediaButtonEventHandler( .setIconResId(if (isFavorite) drawable.media3_icon_heart_filled else drawable.media3_icon_heart_unfilled) .setSessionCommand( SessionCommand( - if (isFavorite) "desfavoritar" else "favoritar", + if (isFavorite) "notification_desfavoritar" else "notification_favoritar", Bundle() ) ) .setEnabled(true) .build(), CommandButton.Builder() - .setDisplayName("next") + .setDisplayName("notification_next") .setIconResId(drawable.media3_icon_next) - .setSessionCommand(SessionCommand("next", Bundle.EMPTY)) + .setSessionCommand(SessionCommand("notification_next", Bundle.EMPTY)) .setEnabled(true) .build(), ) return mediaService?.mediaSession?.setCustomLayout( - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { list.plus( CommandButton.Builder() - .setDisplayName("previous") + .setDisplayName("notification_previous") .setIconResId(drawable.media3_icon_previous) - .setSessionCommand(SessionCommand("previous", Bundle.EMPTY)) + .setSessionCommand(SessionCommand("notification_previous", Bundle.EMPTY)) .build() ) - } else{ + } else { list } ) @@ -202,7 +208,7 @@ class MediaButtonEventHandler( } Log.d("Player", "Key: $ke") - + Log.d("Player", "#MEDIA3# - Key: $ke") when (ke.keyCode) { KeyEvent.KEYCODE_MEDIA_PLAY -> { PlayerSingleton.play() diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt index 7ab48791..95c79256 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt @@ -1,5 +1,6 @@ package br.com.suamusica.player +import android.app.PendingIntent import android.app.Service import android.content.Context import android.content.Intent @@ -15,6 +16,7 @@ import android.support.v4.media.session.PlaybackStateCompat import android.util.Log import androidx.media3.common.AudioAttributes import androidx.media3.common.C +import androidx.media3.common.ForwardingPlayer import androidx.media3.common.MediaItem import androidx.media3.common.MediaMetadata import androidx.media3.common.MediaMetadata.PICTURE_TYPE_FRONT_COVER @@ -34,20 +36,15 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.hls.HlsMediaSource import androidx.media3.exoplayer.source.ProgressiveMediaSource import androidx.media3.session.CacheBitmapLoader +import androidx.media3.session.CommandButton +import androidx.media3.session.DefaultMediaNotificationProvider import androidx.media3.session.MediaController +import androidx.media3.session.MediaNotification import androidx.media3.session.MediaSession import androidx.media3.session.MediaSessionService import br.com.suamusica.player.media.parser.SMHlsPlaylistParserFactory -import com.bumptech.glide.Glide -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.request.FutureTarget -import com.bumptech.glide.request.RequestOptions +import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.ListenableFuture -import kotlinx.coroutines.DelicateCoroutinesApi -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.io.ByteArrayOutputStream import java.io.File import java.util.concurrent.TimeUnit @@ -77,49 +74,50 @@ class MediaService : MediaSessionService() { private var previousState: Int = -1 private lateinit var dataSourceBitmapLoader: DataSourceBitmapLoader + private lateinit var mediaButtonEventHandler: MediaButtonEventHandler - companion object { - private val glideOptions = RequestOptions().fallback(R.drawable.default_art) - .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).timeout(5000) - - private const val NOTIFICATION_LARGE_ICON_SIZE = 500 // px - private const val LOCAL_COVER_PNG = "../app_flutter/covers/0.png" // px - - @OptIn(DelicateCoroutinesApi::class) - fun getArts(context: Context, artUri: String?, callback: (Bitmap) -> Unit) { - GlobalScope.launch(Dispatchers.IO) { - Log.i("getArts", " artUri: $artUri") - val glider = Glide.with(context).applyDefaultRequestOptions(glideOptions).asBitmap() - val file = File(context.filesDir, LOCAL_COVER_PNG) - lateinit var bitmap: Bitmap - val futureTarget: FutureTarget? = when { - !artUri.isNullOrBlank() -> glider.load(artUri) - .submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE) - - file.exists() -> glider.load(Uri.fromFile(file)) - .submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE) - - else -> null - } - - if (futureTarget != null) { - bitmap = try { - futureTarget.get() - } catch (e: Exception) { - Log.i("getArts", "ART EXCP: $e") - if (file.exists()) { - BitmapFactory.decodeFile(file.absolutePath) - } else { - BitmapFactory.decodeResource(context.resources, R.drawable.default_art) - } - } - } - withContext(Dispatchers.Main) { - callback(bitmap) - } - } - } - } +// companion object { +// private val glideOptions = RequestOptions().fallback(R.drawable.default_art) +// .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).timeout(5000) +// +// private const val NOTIFICATION_LARGE_ICON_SIZE = 500 // px +// private const val LOCAL_COVER_PNG = "../app_flutter/covers/0.png" // px + +// @OptIn(DelicateCoroutinesApi::class) +// fun getArts(context: Context, artUri: String?, callback: (Bitmap) -> Unit) { +// GlobalScope.launch(Dispatchers.IO) { +// Log.i("getArts", " artUri: $artUri") +// val glider = Glide.with(context).applyDefaultRequestOptions(glideOptions).asBitmap() +// val file = File(context.filesDir, LOCAL_COVER_PNG) +// lateinit var bitmap: Bitmap +// val futureTarget: FutureTarget? = when { +// !artUri.isNullOrBlank() -> glider.load(artUri) +// .submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE) +// +// file.exists() -> glider.load(Uri.fromFile(file)) +// .submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE) +// +// else -> null +// } +// +// if (futureTarget != null) { +// bitmap = try { +// futureTarget.get() +// } catch (e: Exception) { +// Log.i("getArts", "ART EXCP: $e") +// if (file.exists()) { +// BitmapFactory.decodeFile(file.absolutePath) +// } else { +// BitmapFactory.decodeResource(context.resources, R.drawable.default_art) +// } +// } +// } +// withContext(Dispatchers.Main) { +// callback(bitmap) +// } +// } +// } +// } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.d(TAG, "onStartCommand") @@ -130,6 +128,7 @@ class MediaService : MediaSessionService() { override fun onCreate() { super.onCreate() + mediaButtonEventHandler = MediaButtonEventHandler(this) packageValidator = PackageValidator(applicationContext, R.xml.allowed_media_browser_callers) wifiLock = (applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager).createWifiLock( @@ -152,11 +151,63 @@ class MediaService : MediaSessionService() { dataSourceBitmapLoader = DataSourceBitmapLoader(applicationContext) + Log.d(TAG, "MEDIA3 - handleCustomCommand ${Build.VERSION_CODES.TIRAMISU}") player?.let { mediaSession = MediaSession.Builder(this, it) .setBitmapLoader(CacheBitmapLoader(dataSourceBitmapLoader)) - .setCallback(MediaButtonEventHandler(this)) + .setCallback(mediaButtonEventHandler) .build() + + this@MediaService.setMediaNotificationProvider(object : MediaNotification.Provider { + override fun createNotification( + mediaSession: MediaSession, + customLayout: ImmutableList, + actionFactory: MediaNotification.ActionFactory, + onNotificationChangedCallback: MediaNotification.Provider.Callback + ): MediaNotification { + val defaultMediaNotificationProvider = + DefaultMediaNotificationProvider(applicationContext) + .apply { + setSmallIcon(R.drawable.ic_notification) + } + + val customMedia3Notification = + defaultMediaNotificationProvider.createNotification( + mediaSession, + mediaSession.customLayout, + actionFactory, + onNotificationChangedCallback, + ) + val notifyIntent = Intent("SUA_MUSICA_FLUTTER_NOTIFICATION_CLICK").apply { + addCategory(Intent.CATEGORY_DEFAULT) + flags = + Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP + } + val NOW_PLAYING_CHANNEL: String = "br.com.suamusica.media.NOW_PLAYING" + val NOW_PLAYING_NOTIFICATION = 0xb339 + val notifyPendingIntent = PendingIntent.getActivity( + applicationContext, + 0, + notifyIntent, + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT else PendingIntent.FLAG_UPDATE_CURRENT + ) + return MediaNotification( + NOW_PLAYING_NOTIFICATION, + customMedia3Notification.notification.apply { + contentIntent = notifyPendingIntent +// channelId = NOW_PLAYING_NOTIFICATION + }) + } + + override fun handleCustomCommand( + session: MediaSession, + action: String, + extras: Bundle + ): Boolean { + Log.d(TAG, "#MEDIA3# - handleCustomCommand $action") + return false + } + }) } } @@ -276,12 +327,7 @@ class MediaService : MediaSessionService() { dataSourceBitmapLoader.loadBitmap(Uri.parse(media.bigCoverUrl!!)) .get(5000, TimeUnit.MILLISECONDS) } catch (e: Exception) { - Log.d("Player", "TESTE1 catch") - var bitmapFallback: Bitmap? = null - getArts(applicationContext, media.bigCoverUrl) { bitmap -> - bitmapFallback = bitmap - } - bitmapFallback + BitmapFactory.decodeResource(resources, R.drawable.default_art) } art?.compress(Bitmap.CompressFormat.PNG, 95, stream) @@ -297,15 +343,18 @@ class MediaService : MediaSessionService() { return metadata } - // } fun play() { performAndEnableTracking { player?.play() } } - + fun removeNotification() { - player?.removeMediaItem(0); + object: ForwardingPlayer(player!!) { + override fun getDuration(): Long { + return C.TIME_UNSET + } + } } fun seek(position: Long, playWhenReady: Boolean) { @@ -335,20 +384,12 @@ class MediaService : MediaSessionService() { } } - fun releaseAndPerformAndDisableTracking() { + private fun releaseAndPerformAndDisableTracking() { performAndDisableTracking { player?.stop() } } -// private fun removeNowPlayingNotification() { -// Log.d(TAG, "removeNowPlayingNotification") -// Thread(Runnable { -// notificationManager?.cancel(NOW_PLAYING_NOTIFICATION) -// }).start() -// -// } - private fun notifyPositionChange() { var position = player?.currentPosition ?: 0L @@ -443,8 +484,7 @@ class MediaService : MediaSessionService() { } else { stopTrackingProgressAndPerformTask {} } - } - else if(playbackState == Player.STATE_ENDED) { + } else if (playbackState == Player.STATE_ENDED) { stopTrackingProgressAndPerformTask {} } previousState = playbackState diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/PlayerSingleton.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/PlayerSingleton.kt index 9cf70b81..8170ecdc 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/PlayerSingleton.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/PlayerSingleton.kt @@ -48,6 +48,7 @@ object PlayerSingleton { } fun next() { + Log.d("Player", "#MEDIA3# - commandCenter NEXT") channel?.invokeMethod("commandCenter.onNext", emptyMap()) } From e3c7c874af9a0ba89ff7f071d0e5790e71f9327c Mon Sep 17 00:00:00 2001 From: lstonussi Date: Mon, 26 Aug 2024 11:58:05 -0300 Subject: [PATCH 14/14] remove unused code --- .../br/com/suamusica/player/MediaService.kt | 46 +------------------ 1 file changed, 1 insertion(+), 45 deletions(-) diff --git a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt index 95c79256..1ade6b97 100644 --- a/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt +++ b/packages/player/android/src/main/kotlin/br/com/suamusica/player/MediaService.kt @@ -76,49 +76,6 @@ class MediaService : MediaSessionService() { private lateinit var dataSourceBitmapLoader: DataSourceBitmapLoader private lateinit var mediaButtonEventHandler: MediaButtonEventHandler -// companion object { -// private val glideOptions = RequestOptions().fallback(R.drawable.default_art) -// .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).timeout(5000) -// -// private const val NOTIFICATION_LARGE_ICON_SIZE = 500 // px -// private const val LOCAL_COVER_PNG = "../app_flutter/covers/0.png" // px - -// @OptIn(DelicateCoroutinesApi::class) -// fun getArts(context: Context, artUri: String?, callback: (Bitmap) -> Unit) { -// GlobalScope.launch(Dispatchers.IO) { -// Log.i("getArts", " artUri: $artUri") -// val glider = Glide.with(context).applyDefaultRequestOptions(glideOptions).asBitmap() -// val file = File(context.filesDir, LOCAL_COVER_PNG) -// lateinit var bitmap: Bitmap -// val futureTarget: FutureTarget? = when { -// !artUri.isNullOrBlank() -> glider.load(artUri) -// .submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE) -// -// file.exists() -> glider.load(Uri.fromFile(file)) -// .submit(NOTIFICATION_LARGE_ICON_SIZE, NOTIFICATION_LARGE_ICON_SIZE) -// -// else -> null -// } -// -// if (futureTarget != null) { -// bitmap = try { -// futureTarget.get() -// } catch (e: Exception) { -// Log.i("getArts", "ART EXCP: $e") -// if (file.exists()) { -// BitmapFactory.decodeFile(file.absolutePath) -// } else { -// BitmapFactory.decodeResource(context.resources, R.drawable.default_art) -// } -// } -// } -// withContext(Dispatchers.Main) { -// callback(bitmap) -// } -// } -// } -// } - override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.d(TAG, "onStartCommand") super.onStartCommand(intent, flags, startId) @@ -178,12 +135,12 @@ class MediaService : MediaSessionService() { actionFactory, onNotificationChangedCallback, ) + val notifyIntent = Intent("SUA_MUSICA_FLUTTER_NOTIFICATION_CLICK").apply { addCategory(Intent.CATEGORY_DEFAULT) flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP } - val NOW_PLAYING_CHANNEL: String = "br.com.suamusica.media.NOW_PLAYING" val NOW_PLAYING_NOTIFICATION = 0xb339 val notifyPendingIntent = PendingIntent.getActivity( applicationContext, @@ -195,7 +152,6 @@ class MediaService : MediaSessionService() { NOW_PLAYING_NOTIFICATION, customMedia3Notification.notification.apply { contentIntent = notifyPendingIntent -// channelId = NOW_PLAYING_NOTIFICATION }) }