Merge pull request #6641 from Bnyro/master

feat: audio player support for no internet activity
This commit is contained in:
Bnyro 2024-10-20 16:25:32 +02:00 committed by GitHub
commit a50b2f75e4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 64 additions and 24 deletions

View File

@ -43,6 +43,7 @@
<activity <activity
android:name=".ui.activities.NoInternetActivity" android:name=".ui.activities.NoInternetActivity"
android:label="@string/noInternet" android:label="@string/noInternet"
android:launchMode="singleTop"
android:screenOrientation="locked" /> android:screenOrientation="locked" />
<activity <activity
@ -114,6 +115,7 @@
<activity <activity
android:name=".ui.activities.OfflinePlayerActivity" android:name=".ui.activities.OfflinePlayerActivity"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"
android:launchMode="singleTop"
android:label="@string/player" android:label="@string/player"
android:supportsPictureInPicture="true" /> android:supportsPictureInPicture="true" />
@ -410,7 +412,8 @@
android:exported="false" android:exported="false"
android:foregroundServiceType="mediaPlayback" /> android:foregroundServiceType="mediaPlayback" />
<service android:name=".services.OnClearFromRecentService" <service
android:name=".services.OnClearFromRecentService"
android:enabled="true" android:enabled="true"
android:exported="false" /> android:exported="false" />

View File

@ -51,4 +51,5 @@ object IntentData {
const val offlinePlayer = "offlinePlayer" const val offlinePlayer = "offlinePlayer"
const val downloadTab = "downloadTab" const val downloadTab = "downloadTab"
const val shuffle = "shuffle" const val shuffle = "shuffle"
const val noInternet = "noInternet"
} }

View File

@ -11,6 +11,7 @@ import com.github.libretube.parcelable.PlayerData
import com.github.libretube.services.OfflinePlayerService import com.github.libretube.services.OfflinePlayerService
import com.github.libretube.services.OnlinePlayerService import com.github.libretube.services.OnlinePlayerService
import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.activities.NoInternetActivity
import com.github.libretube.ui.fragments.DownloadTab import com.github.libretube.ui.fragments.DownloadTab
import com.github.libretube.ui.fragments.PlayerFragment import com.github.libretube.ui.fragments.PlayerFragment
@ -80,10 +81,14 @@ object BackgroundHelper {
fun playOnBackgroundOffline(context: Context, videoId: String?, downloadTab: DownloadTab, shuffle: Boolean = false) { fun playOnBackgroundOffline(context: Context, videoId: String?, downloadTab: DownloadTab, shuffle: Boolean = false) {
stopBackgroundPlay(context) stopBackgroundPlay(context)
// whether the service is started from the MainActivity or NoInternetActivity
val noInternet = ContextHelper.tryUnwrapActivity<NoInternetActivity>(context) != null
val playerIntent = Intent(context, OfflinePlayerService::class.java) val playerIntent = Intent(context, OfflinePlayerService::class.java)
.putExtra(IntentData.videoId, videoId) .putExtra(IntentData.videoId, videoId)
.putExtra(IntentData.shuffle, shuffle) .putExtra(IntentData.shuffle, shuffle)
.putExtra(IntentData.downloadTab, downloadTab) .putExtra(IntentData.downloadTab, downloadTab)
.putExtra(IntentData.noInternet, noInternet)
ContextCompat.startForegroundService(context, playerIntent) ContextCompat.startForegroundService(context, playerIntent)
} }

View File

@ -20,6 +20,7 @@ import com.github.libretube.extensions.toID
import com.github.libretube.parcelable.PlayerData import com.github.libretube.parcelable.PlayerData
import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.activities.ZoomableImageActivity import com.github.libretube.ui.activities.ZoomableImageActivity
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.fragments.AudioPlayerFragment import com.github.libretube.ui.fragments.AudioPlayerFragment
import com.github.libretube.ui.fragments.PlayerFragment import com.github.libretube.ui.fragments.PlayerFragment
import com.github.libretube.ui.views.SingleViewTouchableMotionLayout import com.github.libretube.ui.views.SingleViewTouchableMotionLayout
@ -98,7 +99,7 @@ object NavigationHelper {
* Start the audio player fragment * Start the audio player fragment
*/ */
fun startAudioPlayer(context: Context, offlinePlayer: Boolean = false, minimizeByDefault: Boolean = false) { fun startAudioPlayer(context: Context, offlinePlayer: Boolean = false, minimizeByDefault: Boolean = false) {
val activity = ContextHelper.unwrapActivity<MainActivity>(context) val activity = ContextHelper.unwrapActivity<BaseActivity>(context)
activity.supportFragmentManager.commitNow { activity.supportFragmentManager.commitNow {
val args = bundleOf( val args = bundleOf(
IntentData.minimizeByDefault to minimizeByDefault, IntentData.minimizeByDefault to minimizeByDefault,

View File

@ -124,6 +124,7 @@ abstract class AbstractPlayerService : LifecycleService() {
} }
abstract val isOfflinePlayer: Boolean abstract val isOfflinePlayer: Boolean
abstract val intentActivity: Class<*>
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
@ -149,8 +150,8 @@ abstract class AbstractPlayerService : LifecycleService() {
lifecycleScope.launch { lifecycleScope.launch {
if (intent != null) { if (intent != null) {
createPlayerAndNotification()
onServiceCreated(intent) onServiceCreated(intent)
createPlayerAndNotification()
startPlaybackAndUpdateNotification() startPlaybackAndUpdateNotification()
} }
else stopSelf() else stopSelf()
@ -181,7 +182,8 @@ abstract class AbstractPlayerService : LifecycleService() {
this, this,
player!!, player!!,
backgroundOnly = true, backgroundOnly = true,
offlinePlayer = isOfflinePlayer offlinePlayer = isOfflinePlayer,
intentActivity = intentActivity
) )
} }

View File

@ -17,6 +17,8 @@ import com.github.libretube.extensions.toAndroidUri
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.helpers.PlayerHelper import com.github.libretube.helpers.PlayerHelper
import com.github.libretube.obj.PlayerNotificationData import com.github.libretube.obj.PlayerNotificationData
import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.activities.NoInternetActivity
import com.github.libretube.ui.fragments.DownloadTab import com.github.libretube.ui.fragments.DownloadTab
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -31,6 +33,9 @@ import kotlin.io.path.exists
@UnstableApi @UnstableApi
class OfflinePlayerService : AbstractPlayerService() { class OfflinePlayerService : AbstractPlayerService() {
override val isOfflinePlayer: Boolean = true override val isOfflinePlayer: Boolean = true
private var noInternetService: Boolean = false
override val intentActivity: Class<*>
get() = if (noInternetService) NoInternetActivity::class.java else MainActivity::class.java
private var downloadWithItems: DownloadWithItems? = null private var downloadWithItems: DownloadWithItems? = null
private lateinit var downloadTab: DownloadTab private lateinit var downloadTab: DownloadTab
@ -39,6 +44,7 @@ class OfflinePlayerService : AbstractPlayerService() {
override suspend fun onServiceCreated(intent: Intent) { override suspend fun onServiceCreated(intent: Intent) {
downloadTab = intent.serializableExtra(IntentData.downloadTab)!! downloadTab = intent.serializableExtra(IntentData.downloadTab)!!
shuffle = intent.getBooleanExtra(IntentData.shuffle, false) shuffle = intent.getBooleanExtra(IntentData.shuffle, false)
noInternetService = intent.getBooleanExtra(IntentData.noInternet, false)
videoId = if (shuffle) { videoId = if (shuffle) {
runBlocking(Dispatchers.IO) { runBlocking(Dispatchers.IO) {

View File

@ -23,6 +23,7 @@ import com.github.libretube.helpers.PlayerHelper.checkForSegments
import com.github.libretube.helpers.ProxyHelper import com.github.libretube.helpers.ProxyHelper
import com.github.libretube.obj.PlayerNotificationData import com.github.libretube.obj.PlayerNotificationData
import com.github.libretube.parcelable.PlayerData import com.github.libretube.parcelable.PlayerData
import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.util.PlayingQueue import com.github.libretube.util.PlayingQueue
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -35,6 +36,7 @@ import kotlinx.serialization.encodeToString
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) @androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
class OnlinePlayerService : AbstractPlayerService() { class OnlinePlayerService : AbstractPlayerService() {
override val isOfflinePlayer: Boolean = false override val isOfflinePlayer: Boolean = false
override val intentActivity: Class<*> = MainActivity::class.java
// PlaylistId/ChannelId for autoplay // PlaylistId/ChannelId for autoplay
private var playlistId: String? = null private var playlistId: String? = null

View File

@ -6,10 +6,12 @@ import androidx.activity.addCallback
import androidx.fragment.app.commit import androidx.fragment.app.commit
import androidx.fragment.app.replace import androidx.fragment.app.replace
import com.github.libretube.R import com.github.libretube.R
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.ActivityNointernetBinding import com.github.libretube.databinding.ActivityNointernetBinding
import com.github.libretube.helpers.NavigationHelper import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.helpers.NetworkHelper import com.github.libretube.helpers.NetworkHelper
import com.github.libretube.ui.base.BaseActivity import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.fragments.AudioPlayerFragment
import com.github.libretube.ui.fragments.DownloadsFragment import com.github.libretube.ui.fragments.DownloadsFragment
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
@ -33,7 +35,7 @@ class NoInternetActivity : BaseActivity() {
binding.downloads.setOnClickListener { binding.downloads.setOnClickListener {
supportFragmentManager.commit { supportFragmentManager.commit {
replace<DownloadsFragment>(R.id.noInternet_container) replace<DownloadsFragment>(R.id.container)
addToBackStack(null) addToBackStack(null)
} }
} }
@ -51,4 +53,17 @@ class NoInternetActivity : BaseActivity() {
?: finishAffinity() ?: finishAffinity()
} }
} }
override fun onNewIntent(intent: Intent) {
super.onNewIntent(intent)
if (intent.getBooleanExtra(IntentData.openAudioPlayer, false)) {
// attempt to recycle already existing audio player fragment first before creating new one
supportFragmentManager.fragments.filterIsInstance<AudioPlayerFragment>().firstOrNull()?.let {
it.binding.playerMotionLayout.transitionToStart()
return
}
NavigationHelper.startAudioPlayer(this)
}
}
} }

View File

@ -220,7 +220,8 @@ class OfflinePlayerActivity : BaseActivity() {
nowPlayingNotification = NowPlayingNotification( nowPlayingNotification = NowPlayingNotification(
this, this,
viewModel.player, viewModel.player,
offlinePlayer = true offlinePlayer = true,
intentActivity = OfflinePlayerActivity::class.java
) )
} }

View File

@ -44,6 +44,7 @@ import com.github.libretube.services.AbstractPlayerService
import com.github.libretube.services.OfflinePlayerService import com.github.libretube.services.OfflinePlayerService
import com.github.libretube.services.OnlinePlayerService import com.github.libretube.services.OnlinePlayerService
import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.interfaces.AudioPlayerOptions import com.github.libretube.ui.interfaces.AudioPlayerOptions
import com.github.libretube.ui.listeners.AudioPlayerThumbnailListener import com.github.libretube.ui.listeners.AudioPlayerThumbnailListener
import com.github.libretube.ui.models.ChaptersViewModel import com.github.libretube.ui.models.ChaptersViewModel
@ -64,7 +65,8 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
val binding get() = _binding!! val binding get() = _binding!!
private lateinit var audioHelper: AudioHelper private lateinit var audioHelper: AudioHelper
private val mainActivity get() = context as MainActivity private val activity get() = context as BaseActivity
private val mainActivity get() = activity as? MainActivity
private val viewModel: CommonPlayerViewModel by activityViewModels() private val viewModel: CommonPlayerViewModel by activityViewModels()
private val chaptersModel: ChaptersViewModel by activityViewModels() private val chaptersModel: ChaptersViewModel by activityViewModels()
@ -99,7 +101,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
val serviceClass = val serviceClass =
if (isOffline) OfflinePlayerService::class.java else OnlinePlayerService::class.java if (isOffline) OfflinePlayerService::class.java else OnlinePlayerService::class.java
Intent(activity, serviceClass).also { intent -> Intent(activity, serviceClass).also { intent ->
activity?.bindService(intent, connection, 0) activity.bindService(intent, connection, 0)
} }
} }
@ -132,8 +134,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
} }
binding.minimizePlayer.setOnClickListener { binding.minimizePlayer.setOnClickListener {
val mainMotionLayout = mainActivity.binding.mainMotionLayout mainActivity?.binding?.mainMotionLayout?.transitionToStart()
mainMotionLayout.transitionToStart()
binding.playerMotionLayout.transitionToEnd() binding.playerMotionLayout.transitionToEnd()
} }
@ -208,7 +209,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
} }
binding.miniPlayerClose.setOnClickListener { binding.miniPlayerClose.setOnClickListener {
activity?.unbindService(connection) activity.unbindService(connection)
BackgroundHelper.stopBackgroundPlay(requireContext()) BackgroundHelper.stopBackgroundPlay(requireContext())
killFragment() killFragment()
} }
@ -244,15 +245,17 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
private fun killFragment() { private fun killFragment() {
viewModel.isFullscreen.value = false viewModel.isFullscreen.value = false
binding.playerMotionLayout.transitionToEnd() binding.playerMotionLayout.transitionToEnd()
mainActivity.supportFragmentManager.commit { activity.supportFragmentManager.commit {
remove(this@AudioPlayerFragment) remove(this@AudioPlayerFragment)
} }
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
private fun initializeTransitionLayout() { private fun initializeTransitionLayout() {
mainActivity.binding.container.isVisible = true if (mainActivity == null) return
val mainMotionLayout = mainActivity.binding.mainMotionLayout
mainActivity!!.binding.container.isVisible = true
val mainMotionLayout = mainActivity!!.binding.mainMotionLayout
mainMotionLayout.progress = 0F mainMotionLayout.progress = 0F
binding.playerMotionLayout.addTransitionListener(object : TransitionAdapter() { binding.playerMotionLayout.addTransitionListener(object : TransitionAdapter() {
@ -408,7 +411,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
// unregister all listeners and the connected [playerService] // unregister all listeners and the connected [playerService]
playerService?.onStateOrPlayingChanged = null playerService?.onStateOrPlayingChanged = null
runCatching { runCatching {
activity?.unbindService(connection) activity.unbindService(connection)
} }
super.onDestroy() super.onDestroy()

View File

@ -35,7 +35,8 @@ class NowPlayingNotification(
private val context: Context, private val context: Context,
private val player: ExoPlayer, private val player: ExoPlayer,
private val backgroundOnly: Boolean = false, private val backgroundOnly: Boolean = false,
private val offlinePlayer: Boolean = false private val offlinePlayer: Boolean = false,
private val intentActivity: Class<*> = MainActivity::class.java
) { ) {
private var videoId: String? = null private var videoId: String? = null
private val nManager = context.getSystemService<NotificationManager>()!! private val nManager = context.getSystemService<NotificationManager>()!!
@ -74,13 +75,13 @@ class NowPlayingNotification(
// starts a new MainActivity Intent when the player notification is clicked // starts a new MainActivity Intent when the player notification is clicked
// it doesn't start a completely new MainActivity because the MainActivity's launchMode // it doesn't start a completely new MainActivity because the MainActivity's launchMode
// is set to "singleTop" in the AndroidManifest (important!!!) // is set to "singleTop" in the AndroidManifest (important!!!)
// that's the only way to launch back into the previous activity (e.g. the player view // that's the only way to launch back into the previous activity (e.g. the player view)
if (!backgroundOnly) return null val intent = Intent(context, intentActivity).apply {
if (backgroundOnly) {
val intent = Intent(context, MainActivity::class.java).apply { putExtra(IntentData.openAudioPlayer, true)
putExtra(IntentData.openAudioPlayer, true) putExtra(IntentData.offlinePlayer, offlinePlayer)
putExtra(IntentData.offlinePlayer, offlinePlayer) addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) }
} }

View File

@ -60,7 +60,7 @@
</LinearLayout> </LinearLayout>
<FrameLayout <FrameLayout
android:id="@+id/noInternet_container" android:id="@+id/container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:elevation="20dp" android:elevation="20dp"