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

View File

@ -51,4 +51,5 @@ object IntentData {
const val offlinePlayer = "offlinePlayer"
const val downloadTab = "downloadTab"
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.OnlinePlayerService
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.PlayerFragment
@ -80,10 +81,14 @@ object BackgroundHelper {
fun playOnBackgroundOffline(context: Context, videoId: String?, downloadTab: DownloadTab, shuffle: Boolean = false) {
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)
.putExtra(IntentData.videoId, videoId)
.putExtra(IntentData.shuffle, shuffle)
.putExtra(IntentData.downloadTab, downloadTab)
.putExtra(IntentData.noInternet, noInternet)
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.ui.activities.MainActivity
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.PlayerFragment
import com.github.libretube.ui.views.SingleViewTouchableMotionLayout
@ -98,7 +99,7 @@ object NavigationHelper {
* Start the audio player fragment
*/
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 {
val args = bundleOf(
IntentData.minimizeByDefault to minimizeByDefault,

View File

@ -124,6 +124,7 @@ abstract class AbstractPlayerService : LifecycleService() {
}
abstract val isOfflinePlayer: Boolean
abstract val intentActivity: Class<*>
override fun onCreate() {
super.onCreate()
@ -149,8 +150,8 @@ abstract class AbstractPlayerService : LifecycleService() {
lifecycleScope.launch {
if (intent != null) {
createPlayerAndNotification()
onServiceCreated(intent)
createPlayerAndNotification()
startPlaybackAndUpdateNotification()
}
else stopSelf()
@ -181,7 +182,8 @@ abstract class AbstractPlayerService : LifecycleService() {
this,
player!!,
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.helpers.PlayerHelper
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.util.PlayingQueue
import kotlinx.coroutines.Dispatchers
@ -31,6 +33,9 @@ import kotlin.io.path.exists
@UnstableApi
class OfflinePlayerService : AbstractPlayerService() {
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 lateinit var downloadTab: DownloadTab
@ -39,6 +44,7 @@ class OfflinePlayerService : AbstractPlayerService() {
override suspend fun onServiceCreated(intent: Intent) {
downloadTab = intent.serializableExtra(IntentData.downloadTab)!!
shuffle = intent.getBooleanExtra(IntentData.shuffle, false)
noInternetService = intent.getBooleanExtra(IntentData.noInternet, false)
videoId = if (shuffle) {
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.obj.PlayerNotificationData
import com.github.libretube.parcelable.PlayerData
import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.util.PlayingQueue
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -35,6 +36,7 @@ import kotlinx.serialization.encodeToString
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
class OnlinePlayerService : AbstractPlayerService() {
override val isOfflinePlayer: Boolean = false
override val intentActivity: Class<*> = MainActivity::class.java
// PlaylistId/ChannelId for autoplay
private var playlistId: String? = null

View File

@ -6,10 +6,12 @@ import androidx.activity.addCallback
import androidx.fragment.app.commit
import androidx.fragment.app.replace
import com.github.libretube.R
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.ActivityNointernetBinding
import com.github.libretube.helpers.NavigationHelper
import com.github.libretube.helpers.NetworkHelper
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.fragments.AudioPlayerFragment
import com.github.libretube.ui.fragments.DownloadsFragment
import com.google.android.material.snackbar.Snackbar
@ -33,7 +35,7 @@ class NoInternetActivity : BaseActivity() {
binding.downloads.setOnClickListener {
supportFragmentManager.commit {
replace<DownloadsFragment>(R.id.noInternet_container)
replace<DownloadsFragment>(R.id.container)
addToBackStack(null)
}
}
@ -51,4 +53,17 @@ class NoInternetActivity : BaseActivity() {
?: 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(
this,
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.OnlinePlayerService
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.listeners.AudioPlayerThumbnailListener
import com.github.libretube.ui.models.ChaptersViewModel
@ -64,7 +65,8 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
val binding get() = _binding!!
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 chaptersModel: ChaptersViewModel by activityViewModels()
@ -99,7 +101,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
val serviceClass =
if (isOffline) OfflinePlayerService::class.java else OnlinePlayerService::class.java
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 {
val mainMotionLayout = mainActivity.binding.mainMotionLayout
mainMotionLayout.transitionToStart()
mainActivity?.binding?.mainMotionLayout?.transitionToStart()
binding.playerMotionLayout.transitionToEnd()
}
@ -208,7 +209,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
}
binding.miniPlayerClose.setOnClickListener {
activity?.unbindService(connection)
activity.unbindService(connection)
BackgroundHelper.stopBackgroundPlay(requireContext())
killFragment()
}
@ -244,15 +245,17 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
private fun killFragment() {
viewModel.isFullscreen.value = false
binding.playerMotionLayout.transitionToEnd()
mainActivity.supportFragmentManager.commit {
activity.supportFragmentManager.commit {
remove(this@AudioPlayerFragment)
}
}
@SuppressLint("ClickableViewAccessibility")
private fun initializeTransitionLayout() {
mainActivity.binding.container.isVisible = true
val mainMotionLayout = mainActivity.binding.mainMotionLayout
if (mainActivity == null) return
mainActivity!!.binding.container.isVisible = true
val mainMotionLayout = mainActivity!!.binding.mainMotionLayout
mainMotionLayout.progress = 0F
binding.playerMotionLayout.addTransitionListener(object : TransitionAdapter() {
@ -408,7 +411,7 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
// unregister all listeners and the connected [playerService]
playerService?.onStateOrPlayingChanged = null
runCatching {
activity?.unbindService(connection)
activity.unbindService(connection)
}
super.onDestroy()

View File

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

View File

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