Compare commits

...

13 Commits

Author SHA1 Message Date
Jellifi007
a233314652 H264 52 Level available on NVIDIA Shield but not detected (#1237)
(cherry picked from commit faf76efdbd)
2021-11-04 15:11:18 +01:00
Niels van Velzen
ec467db7ed Fix touch support in home/auth screens (#1230)
* Fix touch support for "switch user" button

* Fix touch support for server button

(cherry picked from commit a6f17e8903)
2021-11-04 15:10:53 +01:00
Niels van Velzen
3a49573c64 Refresh server infor after migration
(cherry picked from commit 3ea6bf1293)
2021-11-04 15:10:44 +01:00
Cameron
5346ee6c37 fixed playback controls and seeking causing erroneous timestamp updates (#1219)
* removed calls to skip when dpad left/right is called

the new player scrubs seekbar when these dpad buttons are called. Continuing to call seek appears to cause issues with the timestamps

* update playback controller position with seek pos

* dpad no longer calls skip. seek updates current pos

the new player scrubs seekbar when these dpad buttons are called. Continuing to call seek appears to cause issues with the timestamps

update playback controller position with seek pos

* mCurrentPosition is now updated on seeking in direct play

* added changes suggested by nielsvanvelzen

(cherry picked from commit 3025ed115f)
2021-11-04 15:10:13 +01:00
Niels van Velzen
2df9c4c0e3 Fix "screen saver" not showing in AudioNowPlayingActivity
(cherry picked from commit 3cb5f2068d)
2021-11-04 15:10:01 +01:00
Niels van Velzen
429b6601d6 Don't wait for bitrate detection on launch
Also catch errors

(cherry picked from commit 7fc63f56d8)
2021-10-21 18:45:54 +02:00
Niels van Velzen
4ebc3e1f50 Change default video player to ExoPlayer instead of auto detect
(cherry picked from commit 65f4b75673)
2021-10-21 18:40:43 +02:00
Cameron
9a41127a4d "select" button on controller can now pause/unpause without extra navigation (#1207)
* fixed issue #1101

overrides PlaybackTransportRowPresenter's onProgressBarClicked to use CustomPlaybackTransportControlGlue's playPauseAction. Perhaps the lowercase 'p' is to blame for the default implementation not working.

* Update app/src/main/java/org/jellyfin/androidtv/ui/playback/overlay/CustomPlaybackTransportControlGlue.java

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>

Co-authored-by: Niels van Velzen <nielsvanvelzen@users.noreply.github.com>
(cherry picked from commit b59543c05e)
2021-10-21 18:40:32 +02:00
Niels van Velzen
4010c2e839 Fix trailing slashes causing connection issues in apiclient
(cherry picked from commit cb43e9024b)
2021-10-21 18:40:11 +02:00
Niels van Velzen
d8af891489 Act when setActiveSession returns false during login
(cherry picked from commit 283f75f5c7)
2021-10-21 18:40:01 +02:00
Niels van Velzen
b4753ebeae Fix skipBackwardLength instead of skipBackLength (#1182)
(cherry picked from commit 379e3e7c2c)
2021-10-07 18:51:42 +02:00
Niels van Velzen
e300dd4187 Check nullability for mFragment in PlaybackController
(cherry picked from commit f9fa23953f)
2021-10-07 18:51:28 +02:00
Niels van Velzen
f7dee09fd3 Remove build number from internal version reference
This fixes the "server version not supported" toast from showing the build number. 10.7.7.0 now shows as 10.7.7

(cherry picked from commit 9e48578366)
2021-10-07 18:51:19 +02:00
17 changed files with 97 additions and 93 deletions

View File

@@ -6,8 +6,10 @@ import androidx.lifecycle.LifecycleObserver
import androidx.lifecycle.OnLifecycleEvent
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.work.*
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.acra.config.dialog
import org.acra.config.httpSender
import org.acra.config.limiter
@@ -87,6 +89,7 @@ class JellyfinApplication : TvApp(), LifecycleObserver {
/**
* Called from the StartupActivity when the user session is started.
*/
@DelicateCoroutinesApi
suspend fun onSessionStart() {
val workManager by inject<WorkManager>()
val autoBitrate by inject<AutoBitrate>()
@@ -104,9 +107,7 @@ class JellyfinApplication : TvApp(), LifecycleObserver {
).await()
// Detect auto bitrate
withContext(Dispatchers.IO) {
autoBitrate.detect()
}
GlobalScope.launch(Dispatchers.IO) { autoBitrate.detect() }
}
override fun attachBaseContext(base: Context?) {

View File

@@ -51,7 +51,7 @@ class ApiBinder(
api.EnableAutomaticNetworking(ServerInfo().apply {
id = session.serverId.toString()
name = server.name
address = server.address
address = server.address.removeSuffix("/")
userId = session.userId.toString()
accessToken = session.accessToken
})

View File

@@ -219,7 +219,8 @@ class AuthenticationRepositoryImpl(
)
authenticationStore.putUser(server.id, userId, updatedUser)
accountManagerHelper.putAccount(AccountManagerAccount(userId, server.id, updatedUser.name, result.accessToken))
val accountManagerAccount = AccountManagerAccount(userId, server.id, updatedUser.name, result.accessToken)
accountManagerHelper.putAccount(accountManagerAccount)
val user = PrivateUser(
id = userId,
@@ -230,8 +231,15 @@ class AuthenticationRepositoryImpl(
imageTag = userInfo.primaryImageTag,
lastUsed = Date().time,
)
setActiveSession(user, server)
emit(AuthenticatedState)
// We just added the account so it should activate properly although in rare cases it doesn't.
// this is often caused by issues in the platforms account manager
if (setActiveSession(user, server)) emit(AuthenticatedState)
else {
// Try to remove the account and ask for sign in
accountManagerHelper.removeAccount(accountManagerAccount)
emit(RequireSignInState)
}
}
override fun logout(user: User) {

View File

@@ -55,6 +55,7 @@ class LegacyAccountMigration(
address = serverAddress ?: "",
loginDisclaimer = null,
version = serverVersion,
lastRefreshed = 0,
)
)
}

View File

@@ -31,7 +31,7 @@ interface ServerRepository {
suspend fun refreshServerInfo(server: Server): Boolean
companion object {
val minimumServerVersion = Jellyfin.minimumVersion
val minimumServerVersion = Jellyfin.minimumVersion.copy(build = null)
}
}

View File

@@ -4,14 +4,7 @@ import android.content.Context
import android.view.KeyEvent
import androidx.preference.PreferenceManager
import org.acra.ACRA
import org.jellyfin.androidtv.preference.constant.AppTheme
import org.jellyfin.androidtv.preference.constant.AudioBehavior
import org.jellyfin.androidtv.preference.constant.ClockBehavior
import org.jellyfin.androidtv.preference.constant.NextUpBehavior
import org.jellyfin.androidtv.preference.constant.PreferredVideoPlayer
import org.jellyfin.androidtv.preference.constant.RatingType
import org.jellyfin.androidtv.preference.constant.WatchedIndicatorBehavior
import org.jellyfin.androidtv.preference.constant.defaultAudioBehavior
import org.jellyfin.androidtv.preference.constant.*
import org.jellyfin.androidtv.util.DeviceUtils
/**
@@ -87,7 +80,7 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
/**
* Preferred video player.
*/
var videoPlayer = Preference.enum("video_player", PreferredVideoPlayer.AUTO)
var videoPlayer = Preference.enum("video_player", PreferredVideoPlayer.EXOPLAYER)
/**
* Enable refresh rate switching when device supports it

View File

@@ -20,6 +20,7 @@ class ServerButtonView @JvmOverloads constructor(
init {
isFocusable = true
isClickable = true
descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
}

View File

@@ -1,17 +1,18 @@
package org.jellyfin.androidtv.ui.home
import android.content.Intent
import android.graphics.PorterDuff
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomViewTarget
import com.bumptech.glide.request.transition.Transition
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.TvApp
import org.jellyfin.androidtv.auth.SessionRepository
import org.jellyfin.androidtv.databinding.FragmentToolbarHomeBinding
@@ -40,7 +41,7 @@ class HomeToolbarFragment : Fragment() {
activity?.startActivity(settingsIntent)
}
binding.switchUsersContainer.setOnClickListener {
binding.switchUsers.setOnClickListener {
switchUser()
}
@@ -55,25 +56,23 @@ class HomeToolbarFragment : Fragment() {
private fun setUserImage(image: String?) {
Glide.with(requireContext())
.load(image)
.placeholder(R.drawable.ic_switch_users)
.centerInside()
.circleCrop()
.into(object : CustomViewTarget<ImageButton, Drawable>(binding.switchUsersImage) {
.into(object : CustomViewTarget<ImageButton, Drawable>(binding.switchUsers) {
override fun onLoadFailed(errorDrawable: Drawable?) {
binding.switchUsersImage.isVisible = false
binding.switchUsersIcon.isVisible = true
binding.switchUsersImage.setImageDrawable(null)
binding.switchUsers.imageTintMode = PorterDuff.Mode.SRC_IN
binding.switchUsers.setImageDrawable(errorDrawable)
}
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
binding.switchUsersImage.isVisible = true
binding.switchUsersIcon.isVisible = false
binding.switchUsersImage.setImageDrawable(resource)
binding.switchUsers.imageTintMode = null
binding.switchUsers.setImageDrawable(resource)
}
override fun onResourceCleared(placeholder: Drawable?) {
binding.switchUsersImage.isVisible = false
binding.switchUsersIcon.isVisible = true
binding.switchUsersImage.setImageDrawable(null)
binding.switchUsers.imageTintMode = PorterDuff.Mode.SRC_IN
binding.switchUsers.setImageDrawable(placeholder)
}
})
}

View File

@@ -91,7 +91,6 @@ public class AudioNowPlayingActivity extends BaseActivity {
private BaseItemDto mBaseItem;
private ListRow mQueueRow;
private boolean mApplyAlpha = true;
private long lastUserInteraction;
private boolean ssActive;
@@ -238,7 +237,6 @@ public class AudioNowPlayingActivity extends BaseActivity {
protected void onResume() {
super.onResume();
loadItem();
if (mBaseItem != null && (mBaseItem.getBackdropCount() > 1 || (mBaseItem.getParentBackdropImageTags() != null && mBaseItem.getParentBackdropImageTags().size() > 1)))
rotateBackdrops();
//link events
mediaManager.getValue().addAudioEventListener(audioEventListener);
@@ -476,11 +474,14 @@ public class AudioNowPlayingActivity extends BaseActivity {
mBackdropLoop = new Runnable() {
@Override
public void run() {
if (mBaseItem != null && (mBaseItem.getBackdropCount() > 1 || (mBaseItem.getParentBackdropImageTags() != null && mBaseItem.getParentBackdropImageTags().size() > 1)))
backgroundService.getValue().setBackground(mBaseItem);
//manage our "screen saver" too
if (mediaManager.getValue().isPlayingAudio() && !ssActive && System.currentTimeMillis() - lastUserInteraction > 60000) {
startScreenSaver();
}
mLoopHandler.postDelayed(this, BACKDROP_ROTATION_INTERVAL);
}
};
@@ -498,7 +499,6 @@ public class AudioNowPlayingActivity extends BaseActivity {
mArtistName.setAlpha(.3f);
mGenreRow.setVisibility(View.INVISIBLE);
mClock.setAlpha(.3f);
mApplyAlpha = false;
ObjectAnimator fadeOut = ObjectAnimator.ofFloat(mScrollView, "alpha", 1f, 0f);
fadeOut.setDuration(1000);
fadeOut.start();
@@ -511,7 +511,6 @@ public class AudioNowPlayingActivity extends BaseActivity {
}
protected void stopScreenSaver() {
mApplyAlpha = true;
mLogoImage.setVisibility(View.GONE);
mSSArea.setAlpha(0f);
mArtistName.setAlpha(1f);

View File

@@ -573,14 +573,12 @@ public class CustomPlaybackOverlayFragment extends Fragment implements IPlayback
if (!mIsVisible) {
if (!DeviceUtils.isFireTv() && !mPlaybackController.isLiveTv()) {
if (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
mPlaybackController.skip(30000);
mIsVisible = true;
setFadingEnabled(true);
return true;
}
if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
mPlaybackController.skip(-11000);
mIsVisible = true;
setFadingEnabled(true);
return true;

View File

@@ -79,6 +79,7 @@ public class PlaybackController {
private StreamInfo mCurrentStreamInfo;
private List<SubtitleStreamInfo> mSubtitleStreams;
@Nullable
private IPlaybackOverlayFragment mFragment;
private Boolean spinnerOff = false;
@@ -227,7 +228,8 @@ public class PlaybackController {
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getString(R.string.too_many_errors));
mPlaybackState = PlaybackState.ERROR;
stop();
mFragment.finish();
if (mFragment != null) mFragment.finish();
}
}
@@ -631,7 +633,7 @@ public class PlaybackController {
// get subtitle info
mSubtitleStreams = response.GetSubtitleProfiles(false, apiClient.getValue().getApiUrl(), apiClient.getValue().getAccessToken());
mFragment.updateDisplay();
if (mFragment != null) mFragment.updateDisplay();
// when using VLC if source is stereo or we're on the Fire platform with AC3 - use most compatible output
if (!mVideoManager.isNativeMode() &&
@@ -711,7 +713,7 @@ public class PlaybackController {
play(mCurrentPosition);
burningSubs = false;
} else {
mFragment.addManualSubtitles(null);
if (mFragment != null) mFragment.addManualSubtitles(null);
mVideoManager.disableSubs();
}
return;
@@ -741,7 +743,7 @@ public class PlaybackController {
break;
case Embed:
if (!mVideoManager.isNativeMode()) {
mFragment.addManualSubtitles(null); // in case these were on
if (mFragment != null) mFragment.addManualSubtitles(null); // in case these were on
if (!mVideoManager.setSubtitleTrack(index, getCurrentlyPlayingItem().getMediaStreams())) {
// error selecting internal subs
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getResources().getString(R.string.msg_unable_load_subs));
@@ -750,9 +752,9 @@ public class PlaybackController {
}
// not using vlc - fall through to external handling
case External:
mFragment.addManualSubtitles(null);
if (mFragment != null) mFragment.addManualSubtitles(null);
mVideoManager.disableSubs();
mFragment.showSubLoadingMsg(true);
if (mFragment != null) mFragment.showSubLoadingMsg(true);
stream.setDeliveryMethod(SubtitleDeliveryMethod.External);
stream.setDeliveryUrl(String.format("%1$s/Videos/%2$s/%3$s/Subtitles/%4$s/0/Stream.JSON", apiClient.getValue().getApiUrl(), mCurrentStreamInfo.getItemId(), mCurrentStreamInfo.getMediaSourceId(), String.valueOf(stream.getIndex())));
apiClient.getValue().getSubtitles(stream.getDeliveryUrl(), new Response<SubtitleTrackInfo>() {
@@ -762,11 +764,11 @@ public class PlaybackController {
if (info != null) {
Timber.d("Adding json subtitle track to player");
mFragment.addManualSubtitles(info);
if (mFragment != null) mFragment.addManualSubtitles(info);
} else {
Timber.e("Empty subtitle result");
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getResources().getString(R.string.msg_unable_load_subs));
mFragment.showSubLoadingMsg(false);
if (mFragment != null) mFragment.showSubLoadingMsg(false);
}
}
@@ -774,7 +776,7 @@ public class PlaybackController {
public void onError(Exception ex) {
Timber.e(ex, "Error downloading subtitles");
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getResources().getString(R.string.msg_unable_load_subs));
mFragment.showSubLoadingMsg(false);
if (mFragment != null) mFragment.showSubLoadingMsg(false);
}
});
@@ -865,8 +867,8 @@ public class PlaybackController {
DisplayPreferences cachedPrefs = TvApp.getApplication() != null ?
TvApp.getApplication().getCachedDisplayPrefs("usersettings", "emby") : null;
int skipMS = cachedPrefs != null && cachedPrefs.getCustomPrefs().get("skipBackwardLength") != null ?
Integer.parseInt(cachedPrefs.getCustomPrefs().get("skipBackwardLength")) : 10000;
int skipMS = cachedPrefs != null && cachedPrefs.getCustomPrefs().get("skipBackLength") != null ?
Integer.parseInt(cachedPrefs.getCustomPrefs().get("skipBackLength")) : 10000;
skip(-skipMS);
}
@@ -877,6 +879,8 @@ public class PlaybackController {
if (mPlaybackMethod == PlayMethod.Transcode && ContainerTypes.MKV.equals(mCurrentStreamInfo.getContainer())) {
//mkv transcodes require re-start of stream for seek
mVideoManager.stopPlayback();
// update mCurrentPosition because when play() is called after seeking it uses its value
mCurrentPosition = pos;
playbackManager.getValue().changeVideoStream(mCurrentStreamInfo, apiClient.getValue().getServerInfo().getId(), mCurrentOptions, pos * 10000, apiClient.getValue(), new Response<StreamInfo>() {
@Override
public void onResponse(StreamInfo response) {
@@ -895,13 +899,16 @@ public class PlaybackController {
if (mVideoManager.isNativeMode() && !isLiveTv && ContainerTypes.TS.equals(mCurrentStreamInfo.getContainer())) {
//Exo does not support seeking in .ts
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getString(R.string.seek_error));
} else if (mVideoManager.seekTo(pos) >= 0) {
} else {
long oldposition = mCurrentPosition;
mCurrentPosition = pos;
if (mVideoManager.seekTo(pos) < 0) {
mCurrentPosition = oldposition;
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getString(R.string.seek_error));
}
}
}
}
private long currentSkipPos = 0;
@@ -926,7 +933,7 @@ public class PlaybackController {
if (currentSkipPos < 0) currentSkipPos = 0;
Timber.d("Duration reported as: %s current pos: %s",mVideoManager.getDuration(), mVideoManager.getCurrentPosition());
if (currentSkipPos > mVideoManager.getDuration()) currentSkipPos = mVideoManager.getDuration() - 1000;
mFragment.setCurrentTime(currentSkipPos);
if (mFragment != null) mFragment.setCurrentTime(currentSkipPos);
if (getPlaybackMethod().equals(PlayMethod.DirectPlay)) {
seek(currentSkipPos);
currentSkipPos = 0;
@@ -954,7 +961,7 @@ public class PlaybackController {
channel.setCurrentProgram(program);
mCurrentProgramEndTime = channel.getEndDate() != null ? TimeUtils.convertToLocalDate(channel.getEndDate()).getTime() : 0;
mCurrentProgramStartTime = channel.getPremiereDate() != null ? TimeUtils.convertToLocalDate(channel.getPremiereDate()).getTime() : 0;
mFragment.updateDisplay();
if (mFragment != null) mFragment.updateDisplay();
}
}
});
@@ -1006,7 +1013,7 @@ public class PlaybackController {
}
long currentTime = isLiveTv ? getTimeShiftedProgress() : mVideoManager.getCurrentPosition();
if (isLiveTv && !directStreamLiveTv) {
if (isLiveTv && !directStreamLiveTv && mFragment != null) {
mFragment.setSecondaryTime(getRealTimeProgress());
}
@@ -1082,7 +1089,7 @@ public class PlaybackController {
// Show "Next Up" fragment
spinnerOff = false;
mediaManager.getValue().setCurrentVideoQueue(mItems);
mFragment.showNextUp(nextItem.getId());
if (mFragment != null) mFragment.showNextUp(nextItem.getId());
} else {
mCurrentIndex++;
play(0);
@@ -1090,7 +1097,7 @@ public class PlaybackController {
} else {
// exit activity
Timber.d("Last item completed. Finishing activity.");
mFragment.finish();
if (mFragment != null) mFragment.finish();
}
}
@@ -1106,7 +1113,7 @@ public class PlaybackController {
Utils.showToast(TvApp.getApplication(), TvApp.getApplication().getString(R.string.msg_error_live_stream));
directStreamLiveTv = false;
PlaybackHelper.retrieveAndPlay(getCurrentlyPlayingItem().getId(), false, TvApp.getApplication());
mFragment.finish();
if (mFragment != null) mFragment.finish();
} else {
String msg = TvApp.getApplication().getString(R.string.video_error_unknown_error);
Timber.e("Playback error - %s", msg);
@@ -1188,10 +1195,10 @@ public class PlaybackController {
updateTvProgramInfo();
}
final Long currentTime = isLiveTv && mCurrentProgramStartTime > 0 ? getRealTimeProgress() : mVideoManager.getCurrentPosition();
mFragment.setCurrentTime(currentTime);
if (mFragment != null) mFragment.setCurrentTime(currentTime);
//if (isLiveTv && !directStreamLiveTv) mFragment.setSecondaryTime(getRealTimeProgress());
mCurrentPosition = currentTime;
mFragment.updateSubtitles(currentTime);
if (mFragment != null) mFragment.updateSubtitles(currentTime);
}
updateProgress = continueUpdate;

View File

@@ -144,6 +144,12 @@ public class CustomPlaybackTransportControlGlue extends PlaybackTransportControl
return vh;
}
@Override
protected void onProgressBarClicked(PlaybackTransportRowPresenter.ViewHolder vh) {
CustomPlaybackTransportControlGlue controlglue = CustomPlaybackTransportControlGlue.this;
controlglue.onActionClicked(controlglue.playPauseAction);
}
@Override
protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
super.onBindRowViewHolder(vh, item);

View File

@@ -1,6 +1,7 @@
package org.jellyfin.androidtv.util
import org.jellyfin.sdk.api.client.KtorClient
import org.jellyfin.sdk.api.client.exception.ApiClientException
import org.jellyfin.sdk.api.client.extensions.detectBitrate
import org.jellyfin.sdk.api.operations.MediaInfoApi
import timber.log.Timber
@@ -17,9 +18,12 @@ class AutoBitrate(
private set
suspend fun detect() {
try {
val measurement = mediaInfoApi.detectBitrate()
bitrate = measurement.bitrate
Timber.i("Auto bitrate set to: %d", bitrate)
} catch (err: ApiClientException) {
Timber.e(err, "Failed to detect bitrate")
}
}
}

View File

@@ -22,6 +22,8 @@ public class DeviceUtils {
private static final String FIRE_TV_MODEL_GEN_1 = "AFTB";
private static final String FIRE_TV_MODEL_GEN_2 = "AFTS";
private static final String FIRE_TV_MODEL_GEN_3 = "AFTN";
// Nvidia Shield TV Model
private static final String SHIELD_TV_MODEL = "SHIELD Android TV";
public static boolean isChromecastWithGoogleTV() {
return Build.MODEL.equals(CHROMECAST_GOOGLE_TV);
@@ -39,6 +41,10 @@ public class DeviceUtils {
return Build.MODEL.equals(FIRE_STICK_4K_MODEL);
}
public static boolean isShieldTv() {
return Build.MODEL.equals(SHIELD_TV_MODEL);
}
public static boolean has4kVideoSupport() {
return !Arrays.asList(
// These devices only support a max video resolution of 1080p

View File

@@ -74,6 +74,7 @@ object ProfileHelper {
// https://developer.amazon.com/docs/fire-tv/device-specifications.html
DeviceUtils.isFireTvStick4k() -> H264_LEVEL_5_2
DeviceUtils.isFireTv() -> H264_LEVEL_4_1
DeviceUtils.isShieldTv() -> H264_LEVEL_5_2
else -> H264_LEVEL_5_1
}
)

View File

@@ -36,33 +36,12 @@
android:layout_width="8dp"
android:layout_height="0dp" />
<!-- Only one of the next 2 buttons should be shown at a time -->
<FrameLayout
android:id="@+id/switch_users_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:descendantFocusability="blocksDescendants"
android:focusable="true">
<ImageButton
android:id="@+id/switch_users_icon"
android:id="@+id/switch_users"
style="@style/Button.Icon"
android:layout_width="41dp"
android:layout_height="41dp"
android:contentDescription="@string/lbl_switch_user"
android:duplicateParentState="true"
android:src="@drawable/ic_switch_users" />
<ImageButton
android:id="@+id/switch_users_image"
style="@style/Button.Icon"
android:layout_width="41dp"
android:layout_height="41dp"
android:contentDescription="@string/lbl_switch_user"
android:duplicateParentState="true"
android:tint="@null"
android:visibility="gone" />
</FrameLayout>
</LinearLayout>
</org.jellyfin.androidtv.ui.shared.ToolbarView>

View File

@@ -3,6 +3,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="@style/Button.Default"
android:clickable="false"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:duplicateParentState="true"