Compare commits
13 Commits
master
...
release-0.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a233314652 | ||
|
|
ec467db7ed | ||
|
|
3a49573c64 | ||
|
|
5346ee6c37 | ||
|
|
2df9c4c0e3 | ||
|
|
429b6601d6 | ||
|
|
4ebc3e1f50 | ||
|
|
9a41127a4d | ||
|
|
4010c2e839 | ||
|
|
d8af891489 | ||
|
|
b4753ebeae | ||
|
|
e300dd4187 | ||
|
|
f7dee09fd3 |
@@ -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?) {
|
||||
|
||||
@@ -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
|
||||
})
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -55,6 +55,7 @@ class LegacyAccountMigration(
|
||||
address = serverAddress ?: "",
|
||||
loginDisclaimer = null,
|
||||
version = serverVersion,
|
||||
lastRefreshed = 0,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -20,6 +20,7 @@ class ServerButtonView @JvmOverloads constructor(
|
||||
|
||||
init {
|
||||
isFocusable = true
|
||||
isClickable = true
|
||||
descendantFocusability = ViewGroup.FOCUS_BLOCK_DESCENDANTS
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user