Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move video player into fragment #203

Merged
merged 2 commits into from
Oct 23, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 4 additions & 8 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<action android:name="android.intent.action.VIEW" />
<data android:mimeType="*/*" />
</intent>

<package android:name="com.mxtech.videoplayer.ad" />
<package android:name="com.mxtech.videoplayer.pro" />
<package android:name="is.xyz.mpv" />
Expand All @@ -39,20 +40,15 @@

<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode">
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboard|keyboardHidden|uiMode"
android:launchMode="singleTask"
android:supportsPictureInPicture="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>

<activity
android:name=".player.PlayerActivity"
android:launchMode="singleTop"
android:screenOrientation="sensorLandscape"
android:supportsPictureInPicture="true"
android:theme="@style/AppTheme.Player" />

<service android:name=".webapp.RemotePlayerService" />

<service
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/org/jellyfin/mobile/ApplicationModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import org.jellyfin.mobile.api.TimberLogger
import org.jellyfin.mobile.fragment.ConnectFragment
import org.jellyfin.mobile.fragment.WebViewFragment
import org.jellyfin.mobile.player.PlayerEvent
import org.jellyfin.mobile.player.PlayerFragment
import org.jellyfin.mobile.utils.Constants
import org.jellyfin.mobile.utils.PermissionRequestHelper
import org.jellyfin.mobile.webapp.RemoteVolumeProvider
Expand Down Expand Up @@ -44,4 +45,5 @@ val applicationModule = module {
// Fragments
fragment { ConnectFragment() }
fragment { WebViewFragment() }
fragment { PlayerFragment() }
}
27 changes: 19 additions & 8 deletions app/src/main/java/org/jellyfin/mobile/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import org.jellyfin.mobile.cast.Chromecast
import org.jellyfin.mobile.cast.IChromecast
import org.jellyfin.mobile.fragment.ConnectFragment
import org.jellyfin.mobile.fragment.WebViewFragment
import org.jellyfin.mobile.utils.*
import org.jellyfin.mobile.player.PlayerFragment
import org.jellyfin.mobile.utils.PermissionRequestHelper
import org.jellyfin.mobile.utils.SmartOrientationListener
import org.jellyfin.mobile.utils.lazyView
import org.jellyfin.mobile.utils.replaceFragment
import org.jellyfin.mobile.webapp.RemotePlayerService
import org.koin.android.ext.android.inject
import org.koin.androidx.fragment.android.setupKoinFragmentFactory
Expand Down Expand Up @@ -48,15 +52,14 @@ class MainActivity : AppCompatActivity() {
// Bind player service
bindService(Intent(this, RemotePlayerService::class.java), serviceConnection, Service.BIND_AUTO_CREATE)

// Handle window insets
setStableLayoutFlags()

// Load UI
appPreferences.instanceUrl?.toHttpUrlOrNull().also { url ->
if (url != null) {
replaceFragment<WebViewFragment>()
} else {
replaceFragment<ConnectFragment>()
with(supportFragmentManager) {
if (url != null) {
replaceFragment<WebViewFragment>()
} else {
replaceFragment<ConnectFragment>()
}
}
}

Expand All @@ -80,6 +83,14 @@ class MainActivity : AppCompatActivity() {
return true
}

override fun onUserLeaveHint() {
for (fragment in supportFragmentManager.fragments) {
if (fragment is PlayerFragment && fragment.isVisible) {
fragment.onUserLeaveHint()
}
}
}

override fun onStop() {
super.onStop()
orientationListener.disable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class NativeInterface(private val fragment: WebViewFragment) : KoinComponent {

@JavascriptInterface
fun openClientSettings() {
fragment.requireActivity().addFragment<SettingsFragment>()
fragment.parentFragmentManager.addFragment<SettingsFragment>()
}

@JavascriptInterface
Expand Down
20 changes: 12 additions & 8 deletions app/src/main/java/org/jellyfin/mobile/bridge/NativePlayer.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
package org.jellyfin.mobile.bridge

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.webkit.JavascriptInterface
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.add
import kotlinx.coroutines.channels.Channel
import org.jellyfin.mobile.AppPreferences
import org.jellyfin.mobile.PLAYER_EVENT_CHANNEL
import org.jellyfin.mobile.R
import org.jellyfin.mobile.player.ExoPlayerFormats
import org.jellyfin.mobile.player.PlayerActivity
import org.jellyfin.mobile.player.PlayerEvent
import org.jellyfin.mobile.player.PlayerFragment
import org.jellyfin.mobile.settings.VideoPlayerType
import org.jellyfin.mobile.utils.Constants
import org.koin.core.KoinComponent
import org.koin.core.inject
import org.koin.core.qualifier.named

class NativePlayer(private val context: Context) : KoinComponent {
class NativePlayer(private val fragmentManager: FragmentManager) : KoinComponent {

private val appPreferences: AppPreferences by inject()
private val playerEventChannel: Channel<PlayerEvent> by inject(named(PLAYER_EVENT_CHANNEL))
Expand All @@ -28,11 +30,13 @@ class NativePlayer(private val context: Context) : KoinComponent {

@JavascriptInterface
fun loadPlayer(args: String) {
val playerIntent = Intent(context, PlayerActivity::class.java).apply {
action = Constants.ACTION_PLAY_MEDIA
putExtra(Constants.EXTRA_MEDIA_SOURCE_ITEM, args)
val fragmentArgs = Bundle().apply {
putString(Constants.EXTRA_MEDIA_SOURCE_ITEM, args)
}
context.startActivity(playerIntent)
fragmentManager.beginTransaction().apply {
add<PlayerFragment>(R.id.fragment_container, args = fragmentArgs)
addToBackStack(null)
}.commit()
}

@JavascriptInterface
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,9 @@ class ConnectFragment : Fragment() {
if (httpUrl != null) {
appPreferences.instanceUrl = httpUrl.toString()
clearServerList()
with(requireActivity()) {
if (supportFragmentManager.backStackEntryCount > 0)
supportFragmentManager.popBackStack()
with(parentFragmentManager) {
if (backStackEntryCount > 0)
popBackStack()
replaceFragment<WebViewFragment>()
}
}
Expand Down
19 changes: 9 additions & 10 deletions app/src/main/java/org/jellyfin/mobile/fragment/WebViewFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ class WebViewFragment : Fragment() {
domStorageEnabled = true
}
addJavascriptInterface(NativeInterface(this@WebViewFragment), "NativeInterface")
addJavascriptInterface(NativePlayer(context), "NativePlayer")
addJavascriptInterface(NativePlayer(parentFragmentManager), "NativePlayer")
addJavascriptInterface(externalPlayer, "ExternalPlayer")

loadUrl(requireNotNull(appPreferences.instanceUrl) { "Server url has not been set!" })
Expand All @@ -190,16 +190,15 @@ class WebViewFragment : Fragment() {
}

fun onSelectServer(error: Boolean = false) {
activity?.run {
if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
if (error) {
val extras = Bundle().apply {
putBoolean(Constants.FRAGMENT_CONNECT_EXTRA_ERROR, true)
}
replaceFragment<ConnectFragment>(extras)
} else {
addFragment<ConnectFragment>()
val activity = activity
if (activity != null && activity.lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
if (error) {
val extras = Bundle().apply {
putBoolean(Constants.FRAGMENT_CONNECT_EXTRA_ERROR, true)
}
parentFragmentManager.replaceFragment<ConnectFragment>(extras)
} else {
parentFragmentManager.addFragment<ConnectFragment>()
}
}
}
Expand Down
44 changes: 24 additions & 20 deletions app/src/main/java/org/jellyfin/mobile/player/PlaybackMenus.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,20 @@ import androidx.core.view.get
import androidx.core.view.isVisible
import androidx.core.view.size
import org.jellyfin.mobile.R
import org.jellyfin.mobile.databinding.ActivityPlayerBinding
import org.jellyfin.mobile.databinding.ExoPlayerControlViewBinding
import org.jellyfin.mobile.databinding.FragmentPlayerBinding
import org.jellyfin.mobile.player.source.ExoPlayerTracksGroup
import org.jellyfin.mobile.player.source.JellyfinMediaSource

/**
* Provides a menu UI for audio, subtitle and video stream selection
*/
class PlaybackMenus(
private val activity: PlayerActivity,
private val playerBinding: ActivityPlayerBinding,
private val fragment: PlayerFragment,
private val playerBinding: FragmentPlayerBinding,
private val playerControlsBinding: ExoPlayerControlViewBinding
) : PopupMenu.OnDismissListener {
private val context = playerBinding.root.context
private val lockScreenButton: View by playerControlsBinding::lockScreenButton
private val audioStreamsButton: View by playerControlsBinding::audioStreamsButton
private val subtitlesButton: View by playerControlsBinding::subtitlesButton
Expand All @@ -33,44 +34,44 @@ class PlaybackMenus(

init {
lockScreenButton.setOnClickListener {
activity.lockScreen()
fragment.lockScreen()
}
audioStreamsButton.setOnClickListener {
activity.suppressControllerAutoHide(true)
fragment.suppressControllerAutoHide(true)
audioStreamsMenu.show()
}
subtitlesButton.setOnClickListener {
activity.suppressControllerAutoHide(true)
fragment.suppressControllerAutoHide(true)
subtitlesMenu.show()
}
infoButton.setOnClickListener {
playbackInfo.isVisible = !playbackInfo.isVisible
}
playbackInfo.setOnClickListener {
playbackInfo.isVisible = false
dismissPlaybackInfo()
}
}

fun onItemChanged(item: JellyfinMediaSource) {
buildMenuItems(subtitlesMenu.menu, SUBTITLES_MENU_GROUP, item.subtitleTracksGroup, true)
buildMenuItems(audioStreamsMenu.menu, AUDIO_MENU_GROUP, item.audioTracksGroup)

val playMethod = activity.getString(R.string.playback_info_play_method, item.playMethod)
val transcodingInfo = activity.getString(R.string.playback_info_transcoding, item.isTranscoding)
val playMethod = context.getString(R.string.playback_info_play_method, item.playMethod)
val transcodingInfo = context.getString(R.string.playback_info_transcoding, item.isTranscoding)
val videoTracksInfo = item.videoTracksGroup.tracks.run {
joinToString(
"\n",
"${activity.getString(R.string.playback_info_video_streams)}:\n",
"${fragment.getString(R.string.playback_info_video_streams)}:\n",
limit = 3,
truncated = activity.getString(R.string.playback_info_and_x_more, size - 3)
truncated = fragment.getString(R.string.playback_info_and_x_more, size - 3)
) { "- ${it.title}" }
}
val audioTracksInfo = item.audioTracksGroup.tracks.run {
joinToString(
"\n",
"${activity.getString(R.string.playback_info_audio_streams)}:\n",
"${fragment.getString(R.string.playback_info_audio_streams)}:\n",
limit = 5,
truncated = activity.getString(R.string.playback_info_and_x_more, size - 3)
truncated = fragment.getString(R.string.playback_info_and_x_more, size - 3)
) { "- ${it.title} (${it.language})" }
}
playbackInfo.text = listOf(
Expand All @@ -81,9 +82,9 @@ class PlaybackMenus(
).joinToString("\n\n")
}

private fun createSubtitlesMenu() = PopupMenu(activity, subtitlesButton).apply {
private fun createSubtitlesMenu() = PopupMenu(context, subtitlesButton).apply {
setOnMenuItemClickListener { clickedItem ->
activity.onSubtitleSelected(clickedItem.itemId).also { success ->
fragment.onSubtitleSelected(clickedItem.itemId).also { success ->
if (success) {
menu.forEach { it.isChecked = false }
clickedItem.isChecked = true
Expand All @@ -93,9 +94,9 @@ class PlaybackMenus(
setOnDismissListener(this@PlaybackMenus)
}

private fun createAudioStreamsMenu() = PopupMenu(activity, audioStreamsButton).apply {
private fun createAudioStreamsMenu() = PopupMenu(context, audioStreamsButton).apply {
setOnMenuItemClickListener { clickedItem: MenuItem ->
activity.onAudioTrackSelected(clickedItem.itemId).also { success ->
fragment.onAudioTrackSelected(clickedItem.itemId).also { success ->
if (success) {
menu.forEach { it.isChecked = false }
clickedItem.isChecked = true
Expand All @@ -107,7 +108,7 @@ class PlaybackMenus(

private fun buildMenuItems(menu: Menu, groupId: Int, tracksGroup: ExoPlayerTracksGroup<*>, showNone: Boolean = false) {
menu.clear()
if (showNone) menu.add(groupId, -1, Menu.NONE, activity.getString(R.string.menu_item_none))
if (showNone) menu.add(groupId, -1, Menu.NONE, fragment.getString(R.string.menu_item_none))
tracksGroup.tracks.forEachIndexed { index, track ->
menu.add(groupId, index, Menu.NONE, track.title)
}
Expand All @@ -127,9 +128,12 @@ class PlaybackMenus(
}
}

fun dismissPlaybackInfo() {
playbackInfo.isVisible = false
}

override fun onDismiss(menu: PopupMenu) {
activity.restoreFullscreenState()
activity.suppressControllerAutoHide(false)
fragment.suppressControllerAutoHide(false)
}

companion object {
Expand Down
Loading