Compare commits

...

18 Commits

Author SHA1 Message Date
Cameron
9bbbfa4e26 fix player track selection - offset MediaStream indexes to account for external tracks (#1732)
* offset mediastream indexes used for player track selection to account for external tracks

* use imports for libVLC classes

(cherry picked from commit 5e767ad7d1)
2022-05-28 13:59:49 +02:00
Niels van Velzen
050110922c Fix favorite button in 0.13.z 2022-05-09 16:37:52 +02:00
Niels van Velzen
f97db4643e Use JF_BOT_TOKEN in app-publish workflow
(cherry picked from commit 876e6bc5e9)
2022-05-09 16:35:15 +02:00
dependabot[bot]
1715dc02b7 Bump github/codeql-action from 1 to 2
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 1 to 2.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v1...v2)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 57e814844c)
2022-05-05 22:34:04 +02:00
dependabot[bot]
23c8b42e79 Bump actions/setup-java from 2 to 3
Bumps [actions/setup-java](https://github.com/actions/setup-java) from 2 to 3.
- [Release notes](https://github.com/actions/setup-java/releases)
- [Commits](https://github.com/actions/setup-java/compare/v2...v3)

---
updated-dependencies:
- dependency-name: actions/setup-java
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
(cherry picked from commit 7b1b678fcd)
2022-05-05 22:33:56 +02:00
naveensrinivasan
3ddcf4d284 Set permissions for GitHub actions
- Included permissions for the action. https://github.com/ossf/scorecard/blob/main/docs/checks.md#token-permissions

https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#permissions

https://docs.github.com/en/actions/using-jobs/assigning-permissions-to-jobs

[Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests](https://securitylab.github.com/research/github-actions-preventing-pwn-requests/)

 Restrict the GitHub token permissions only to the required ones; this way, even if the attackers will succeed in compromising your workflow, they won’t be able to do much.

Signed-off-by: naveensrinivasan <172697+naveensrinivasan@users.noreply.github.com>
(cherry picked from commit f76ad3765e)
2022-05-05 22:33:41 +02:00
cameron
6a97e9b05b revert subtitle alignment back to left align, fix srt newline parsing
(cherry picked from commit 408f6073d0)
2022-05-05 19:37:45 +02:00
Cameron
6af1df5122 Use enum for refresh rate switching setting - allow choosing between scaling on device or on tv (#1647)
* use enum for refresh rate switching preference

* fixed enum value comments

* renamed preference key

* log every considered mode, restored accidentally deleted string

* added missing brackets

* updated comment for migration, use original depends for preference screen

* renamed string

* offset default resolutionDifference by 1 to fix SCALE_ON_DEVICE ranking

(cherry picked from commit 6dafe0a9d0)
2022-05-05 19:37:37 +02:00
cameron
68f0edca71 initialize video manager when falling back to vlc
(cherry picked from commit 04967b5811)
2022-05-05 19:36:26 +02:00
Niels van Velzen
f5fda8c7de Fix button remapping not saving the new button
(cherry picked from commit 9feaf107b2)
2022-05-05 19:36:03 +02:00
gradle-update-robot
9ea2b81b28 Update Gradle Wrapper from 7.4.1 to 7.4.2.
Signed-off-by: gradle-update-robot <gradle-update-robot@regolo.cc>
(cherry picked from commit 65ebd3dac8)
2022-04-09 11:26:17 +02:00
Cameron
c568862ee1 fix getSubtitleStreamIndex returning the wrong index (#1594)
* dont use mDefaultSubIndex in getSubtitleStreamIndex

* only set mCurrentOptions SubtitleStreamIndex if subtitles load without error

* clear mDefaultSubIndex in switchSubtitleStream

(cherry picked from commit 5fd89ed8c9)
2022-04-09 11:15:58 +02:00
Niels van Velzen
42b767f619 Fix ASPECT_RATIO_BANNER again
(cherry picked from commit 7d1a653c10)
2022-04-09 11:15:48 +02:00
Niels van Velzen
ebf7fc4244 Add appCategory attribute to manifest
(cherry picked from commit e2cdab31ea)
2022-04-09 11:15:35 +02:00
Niels van Velzen
102642d58f Fix ExpandableTextView focusable when text is not ellipsized
(cherry picked from commit db3af65226)
2022-04-09 11:15:27 +02:00
Cameron
5115e1181e fixed subtitles not clearing properly when switching (again) (#1571)
* fixed subtitles not clearing properly when switching (again)

* edited a couple comments so they're clearer

(cherry picked from commit df015146e2)
2022-04-09 11:15:17 +02:00
David Fairbrother
f70ac357b6 Switch to using audio time stretching by default
This matches libVLC's normal default. Looking back in the project
history this was set to no stretching from the very start @commit
9528b9a3ee

Since libVLC use it by default and this wasn't a bug fix I propose we
can opt in the audio stretching by default. This should only trigger when
a user selects a playback speed that isn't 1x anyway.

(cherry picked from commit cfbadba4e3)
2022-04-09 11:15:03 +02:00
cameron
7136341c3c center align subtitles
(cherry picked from commit 079d004f2a)
2022-04-09 11:14:51 +02:00
18 changed files with 322 additions and 247 deletions

View File

@@ -6,6 +6,9 @@ on:
- master
pull_request:
permissions:
contents: read
jobs:
build:
runs-on: ubuntu-20.04
@@ -13,7 +16,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: adopt
java-version: 11

View File

@@ -7,14 +7,20 @@ on:
- release-*
pull_request:
permissions:
contents: read
jobs:
lint:
permissions:
contents: read # for actions/checkout to fetch code
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
runs-on: ubuntu-20.04
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: adopt
java-version: 11
@@ -22,7 +28,7 @@ jobs:
- name: Run detekt and lint task
run: ./gradlew --build-cache --no-daemon --info detekt lint
- name: Upload SARIF files
uses: github/codeql-action/upload-sarif@v1
uses: github/codeql-action/upload-sarif@v2
if: ${{ always() }}
with:
sarif_file: .

View File

@@ -13,7 +13,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: adopt
java-version: 11
@@ -50,7 +50,7 @@ jobs:
- name: Upload release archive to GitHub release
uses: alexellis/upload-assets@0.3.0
env:
GITHUB_TOKEN: ${{ github.token }}
GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
with:
asset_paths: '["build/jellyfin-publish/*"]'
- name: Upload release archive to repo.jellyfin.org

View File

@@ -7,6 +7,9 @@ on:
- release-*
pull_request:
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-20.04
@@ -14,7 +17,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v3
- name: Setup Java
uses: actions/setup-java@v2
uses: actions/setup-java@v3
with:
distribution: adopt
java-version: 11

View File

@@ -8,6 +8,9 @@ on:
paths:
- '**/gradlе-wrapper.jar'
permissions:
contents: read
jobs:
validate:
runs-on: ubuntu-20.04

View File

@@ -40,6 +40,7 @@
<application
android:name=".JellyfinApplication"
android:allowBackup="true"
android:appCategory="video"
android:banner="@drawable/app_banner"
android:dataExtractionRules="@xml/backup_rules"
android:fullBackupContent="@xml/backup_content"

View File

@@ -10,6 +10,7 @@ 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.RefreshRateSwitchingBehavior
import org.jellyfin.androidtv.preference.constant.WatchedIndicatorBehavior
import org.jellyfin.androidtv.preference.constant.defaultAudioBehavior
import org.jellyfin.androidtv.util.DeviceUtils
@@ -90,9 +91,9 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
var videoPlayer = Preference.enum("video_player", PreferredVideoPlayer.EXOPLAYER)
/**
* Enable refresh rate switching when device supports it
* Change refresh rate to match media when device supports it
*/
var refreshRateSwitchingEnabled = Preference.boolean("pref_refresh_switching", false)
var refreshRateSwitchingBehavior = Preference.enum("refresh_rate_switching_behavior", RefreshRateSwitchingBehavior.DISABLED)
/**
* Send a path instead to the external player
@@ -230,6 +231,15 @@ class UserPreferences(context: Context) : SharedPreferenceStore(
// Disable AC3 (Dolby Digital) on Fire Stick Gen 1 devices
if (DeviceUtils.isFireTvStickGen1()) putBoolean("pref_bitstream_ac3", false)
}
// v0.13.3 to v0.13.4
migration(toVersion = 6) {
putEnum("refresh_rate_switching_behavior",
when {
it.getBoolean("pref_refresh_switching", false) -> RefreshRateSwitchingBehavior.SCALE_ON_TV
else -> RefreshRateSwitchingBehavior.DISABLED
})
}
}
}
}

View File

@@ -0,0 +1,22 @@
package org.jellyfin.androidtv.preference.constant
import org.jellyfin.androidtv.R
import org.jellyfin.androidtv.ui.preference.dsl.EnumDisplayOptions
enum class RefreshRateSwitchingBehavior {
@EnumDisplayOptions(R.string.state_disabled)
DISABLED,
/**
* When comparing modes, use difference in resolution to rank modes.
*/
@EnumDisplayOptions(R.string.pref_refresh_rate_scale_on_tv)
SCALE_ON_TV,
/**
* When comparing modes, rank native resolution modes highest.
* Otherwise use difference in resolution to rank modes.
*/
@EnumDisplayOptions(R.string.pref_refresh_rate_scale_on_device)
SCALE_ON_DEVICE,
}

View File

@@ -44,10 +44,12 @@ class ExpandableTextView(context: Context, attrs: AttributeSet?) : AppCompatText
}
}
override fun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int) {
super.onTextChanged(text, start, lengthBefore, lengthAfter)
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
isFocusable = !text.isNullOrBlank()
isClickable = !text.isNullOrBlank()
// Only make focusable when text is ellipsized.
val isEllipsized = !text.isNullOrBlank() && lineCount > 0 && layout.getEllipsisCount(lineCount - 1) > 0
isFocusable = isEllipsized
isClickable = isEllipsized
}
}

View File

@@ -15,6 +15,7 @@ import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupMenu;
@@ -622,7 +623,7 @@ public class ItemListActivity extends FragmentActivity {
@Override
public void onResponse(UserItemDataDto response) {
mBaseItem.setUserData(response);
((TextUnderButton)v).setImageResource(response.getIsFavorite() ? R.drawable.ic_heart_red : R.drawable.ic_heart);
((ImageButton)v).setImageResource(response.getIsFavorite() ? R.drawable.ic_heart_red : R.drawable.ic_heart);
dataRefreshService.getValue().setLastFavoriteUpdate(System.currentTimeMillis());
}
});

View File

@@ -1483,6 +1483,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements LiveTvGui
// Encode whitespace as html entities
final String htmlText = text
.replaceAll("\\r\\n", "<br>")
.replaceAll("\\n", "<br>")
.replaceAll("\\\\h", "&ensp;");
final SpannableString span = new SpannableString(TextUtilsKt.toHtmlSpanned(htmlText));

View File

@@ -25,6 +25,7 @@ import org.jellyfin.androidtv.preference.UserPreferences;
import org.jellyfin.androidtv.preference.UserSettingPreferences;
import org.jellyfin.androidtv.preference.constant.NextUpBehavior;
import org.jellyfin.androidtv.preference.constant.PreferredVideoPlayer;
import org.jellyfin.androidtv.preference.constant.RefreshRateSwitchingBehavior;
import org.jellyfin.androidtv.ui.livetv.TvManager;
import org.jellyfin.androidtv.util.DeviceUtils;
import org.jellyfin.androidtv.util.TimeUtils;
@@ -115,15 +116,18 @@ public class PlaybackController {
private long lastPlaybackError = 0;
private Display.Mode[] mDisplayModes;
private boolean refreshRateSwitchingEnabled;
private RefreshRateSwitchingBehavior refreshRateSwitchingBehavior = RefreshRateSwitchingBehavior.DISABLED;
public PlaybackController(List<BaseItemDto> items, CustomPlaybackOverlayFragment fragment) {
mItems = items;
mFragment = fragment;
mHandler = new Handler();
refreshRateSwitchingEnabled = DeviceUtils.is60() && userPreferences.getValue().get(UserPreferences.Companion.getRefreshRateSwitchingEnabled());
if (refreshRateSwitchingEnabled) getDisplayModes();
if (DeviceUtils.is60()) {
refreshRateSwitchingBehavior = userPreferences.getValue().get(UserPreferences.Companion.getRefreshRateSwitchingBehavior());
if (refreshRateSwitchingBehavior != RefreshRateSwitchingBehavior.DISABLED)
getDisplayModes();
}
// Set default value for useVlc field
// when set to auto the default will be exoplayer
@@ -214,7 +218,7 @@ public class PlaybackController {
}
public int getSubtitleStreamIndex() {
return (mCurrentOptions != null && mCurrentOptions.getSubtitleStreamIndex() != null) ? mCurrentOptions.getSubtitleStreamIndex() : mDefaultSubIndex;
return (mCurrentOptions != null && mCurrentOptions.getSubtitleStreamIndex() != null) ? mCurrentOptions.getSubtitleStreamIndex() : -1;
}
public List<SubtitleStreamInfo> getSubtitleStreams() {
@@ -296,7 +300,7 @@ public class PlaybackController {
mDisplayModes = display.getSupportedModes();
Timber.i("** Available display refresh rates:");
for (Display.Mode mDisplayMode : mDisplayModes) {
Timber.i("%f", mDisplayMode.getRefreshRate());
Timber.d("display mode %s - %dx%d@%f", mDisplayMode.getModeId(), mDisplayMode.getPhysicalWidth(), mDisplayMode.getPhysicalHeight(), mDisplayMode.getRefreshRate());
}
}
@@ -311,8 +315,10 @@ public class PlaybackController {
Display.Mode defaultMode = mFragment.requireActivity().getWindowManager().getDefaultDisplay().getMode();
Timber.d("trying to find display mode for video: %sx%s @%sfps", videoStream.getWidth(), videoStream.getHeight(), videoStream.getRealFrameRate());
Timber.d("trying to find display mode for video: %dx%d@%f", videoStream.getWidth(), videoStream.getHeight(), videoStream.getRealFrameRate());
for (Display.Mode mode : mDisplayModes) {
Timber.d("considering display mode: %s - %dx%d@%f", mode.getModeId(), mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
// Skip unwanted display modes
if (mode.getPhysicalWidth() < 1280 || mode.getPhysicalHeight() < 720) // Skip non-HD
continue;
@@ -324,18 +330,28 @@ public class PlaybackController {
if (rate != sourceRate && rate != sourceRate * 2 && rate != Math.round(sourceRate * 2.5)) // Skip inappropriate rates
continue;
Timber.d("qualifying display mode: %sx%s @%sfps", mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
Timber.d("qualifying display mode: %s - %dx%d@%f", mode.getModeId(), mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
int resolutionDifference = 0;
if (!(mode.getPhysicalWidth() == defaultMode.getPhysicalWidth() && mode.getPhysicalHeight() == defaultMode.getPhysicalHeight()))
resolutionDifference = mode.getPhysicalWidth() - videoStream.getWidth();
// if scaling on-device, keep native resolution modes at diff 0 (best score)
// for other resolutions when scaling on device, or if scaling on tv, score based on distance from media resolution
// use -1 as the default so, with SCALE_ON_DEVICE, a mode at native resolution will rank higher than
// a mode with equal refresh rate and the same resolution as the media
int resolutionDifference = -1;
if ((refreshRateSwitchingBehavior == RefreshRateSwitchingBehavior.SCALE_ON_DEVICE &&
!(mode.getPhysicalWidth() == defaultMode.getPhysicalWidth() && mode.getPhysicalHeight() == defaultMode.getPhysicalHeight())) ||
refreshRateSwitchingBehavior == RefreshRateSwitchingBehavior.SCALE_ON_TV) {
resolutionDifference = Math.abs(mode.getPhysicalWidth() - videoStream.getWidth());
}
int refreshRateDifference = rate - sourceRate;
// use 100,000 to account for refresh rates 120Hz+ (at 120Hz rate == 12,000)
int weight = 100000 - refreshRateDifference + 100000 - resolutionDifference;
if (weight > curWeight) {
Timber.d("preferring mode: %sx%s @%sfps", mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
Timber.d("preferring mode: %s - %dx%d@%f", mode.getModeId(), mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
curWeight = weight;
bestMode = mode;
}
@@ -357,7 +373,8 @@ public class PlaybackController {
Timber.i("*** Best refresh mode is: %s - %dx%d/%f",
best.getModeId(), best.getPhysicalWidth(), best.getPhysicalHeight(), best.getRefreshRate());
if (current.getModeId() != best.getModeId()) {
Timber.i("*** Attempting to change refresh rate from %s/%s", current.getModeId(), current.getRefreshRate());
Timber.i("*** Attempting to change refresh rate from: %s - %dx%d@%f", current.getModeId(), current.getPhysicalWidth(),
current.getPhysicalHeight(),current.getRefreshRate());
WindowManager.LayoutParams params = mFragment.requireActivity().getWindow().getAttributes();
params.preferredDisplayModeId = best.getModeId();
mFragment.requireActivity().getWindow().setAttributes(params);
@@ -365,7 +382,7 @@ public class PlaybackController {
Timber.i("Display is already in best mode");
}
} else {
Timber.i("*** Unable to find display mode for refresh rate: %s", videoStream.getRealFrameRate());
Timber.i("*** Unable to find display mode for refresh rate: %f", videoStream.getRealFrameRate());
}
}
@@ -404,8 +421,8 @@ public class PlaybackController {
play(position, null);
}
private void play(long position, @Nullable Integer transcodedSubtitle) {
Timber.d("Play called from state: %s with pos: %d and sub index: %d", mPlaybackState, position, transcodedSubtitle);
private void play(long position, @Nullable Integer forcedSubtitleIndex) {
Timber.d("Play called from state: %s with pos: %d and sub index: %d", mPlaybackState, position, forcedSubtitleIndex);
if (position < 0) {
Timber.i("Negative start requested - adjusting to zero");
@@ -514,8 +531,8 @@ public class PlaybackController {
vlcOptions.setEnableDirectStream(false);
vlcOptions.setEnableDirectPlay(false);
}
vlcOptions.setSubtitleStreamIndex(transcodedSubtitle);
vlcOptions.setMediaSourceId(transcodedSubtitle != null ? getCurrentMediaSource().getId() : null);
vlcOptions.setSubtitleStreamIndex(forcedSubtitleIndex);
vlcOptions.setMediaSourceId(forcedSubtitleIndex != null ? getCurrentMediaSource().getId() : null);
DeviceProfile vlcProfile = new LibVlcProfile(isLiveTv);
vlcOptions.setProfile(vlcProfile);
@@ -526,8 +543,8 @@ public class PlaybackController {
if (exoErrorEncountered || (isLiveTv && !directStreamLiveTv))
internalOptions.setEnableDirectStream(false);
internalOptions.setMaxAudioChannels(Utils.downMixAudio() ? 2 : null); //have to downmix at server
internalOptions.setSubtitleStreamIndex(transcodedSubtitle);
internalOptions.setMediaSourceId(transcodedSubtitle != null ? getCurrentMediaSource().getId() : null);
internalOptions.setSubtitleStreamIndex(forcedSubtitleIndex);
internalOptions.setMediaSourceId(forcedSubtitleIndex != null ? getCurrentMediaSource().getId() : null);
DeviceProfile internalProfile = new BaseProfile();
if (DeviceUtils.is60() || userPreferences.getValue().get(UserPreferences.Companion.getAc3Enabled())) {
boolean hlsSupported = false;
@@ -672,15 +689,22 @@ public class PlaybackController {
if (mVideoManager == null)
return;
mVideoManager.init(getBufferAmount(), useDeinterlacing);
Timber.d("server default: %s inferred first track: %s", internalResponse.getMediaSource().getDefaultAudioStreamIndex(), bestGuessAudioTrack(internalResponse.getMediaSource()));
mCurrentOptions = useVlc ? vlcOptions : internalOptions;
startItem(item, position, useVlc ? vlcResponse : internalResponse);
}
@Override
public void onError(Exception exception) {
Timber.e(exception, "Unable to get internal stream info");
Timber.e(exception, "Unable to get stream info for internal player - falling back to libVLC");
if (mVideoManager == null)
return;
boolean useDeinterlacing = vlcResponse.getMediaSource().getVideoStream() != null &&
vlcResponse.getMediaSource().getVideoStream().getIsInterlaced() &&
(vlcResponse.getMediaSource().getVideoStream().getWidth() == null ||
vlcResponse.getMediaSource().getVideoStream().getWidth() > 1200);
mVideoManager.init(getBufferAmount(), useDeinterlacing);
mCurrentOptions = vlcOptions;
startItem(item, position, vlcResponse);
}
@@ -734,15 +758,14 @@ public class PlaybackController {
mCurrentStreamInfo = response;
// set the audio index so that it is preserved if the stream is rebuilt when seeking
mCurrentOptions.setAudioStreamIndex(response.getMediaSource().getDefaultAudioStreamIndex());
// set media source Id in case we need to switch to transcoding
mCurrentOptions.setMediaSourceId(response.getMediaSource().getId());
setDefaultAudioIndex(response);
// get subtitle info
mSubtitleStreams = response.GetSubtitleProfiles(false, apiClient.getValue().getApiUrl(), apiClient.getValue().getAccessToken());
mDefaultSubIndex = response.getMediaSource().getDefaultSubtitleStreamIndex() != null ? response.getMediaSource().getDefaultSubtitleStreamIndex() : mDefaultSubIndex;
setDefaultAudioIndex(response);
Timber.d("default audio index set to %s remote default %s", mDefaultAudioIndex, response.getMediaSource().getDefaultAudioStreamIndex());
Timber.d("default sub index set to %s remote default %s", mDefaultSubIndex, response.getMediaSource().getDefaultSubtitleStreamIndex());
// if burning in, set the subtitle index and the burningSubs flag so that onPrepared and switchSubtitleStream will know that we already have subtitles enabled
@@ -752,6 +775,8 @@ public class PlaybackController {
mCurrentOptions.setSubtitleStreamIndex(mDefaultSubIndex);
Timber.d("stream started with burnt in subs");
burningSubs = true;
} else {
mCurrentOptions.setSubtitleStreamIndex(null);
}
Long mbPos = position * 10000;
@@ -770,7 +795,7 @@ public class PlaybackController {
}
// set refresh rate
if (refreshRateSwitchingEnabled) {
if (refreshRateSwitchingBehavior != RefreshRateSwitchingBehavior.DISABLED) {
setRefreshRate(response.getMediaSource().getVideoStream());
}
@@ -829,17 +854,21 @@ public class PlaybackController {
}
public int getAudioStreamIndex() {
int currIndex = mDefaultAudioIndex;
int currIndex = -1;
// if not transcoding, libVLC & exoplayer are able to switch tracks and report their selected track
if (hasInitializedVideoManager() && !isTranscoding() && mVideoManager.getVLCAudioTrack() > -1) {
currIndex = mVideoManager.getVLCAudioTrack();
} else if (hasInitializedVideoManager() && !isTranscoding() && isNativeMode() && mVideoManager.getExoPlayerTrack(MediaStreamType.Audio, getCurrentlyPlayingItem().getMediaStreams()) > -1) {
currIndex = mVideoManager.getExoPlayerTrack(MediaStreamType.Audio, getCurrentlyPlayingItem().getMediaStreams());
} else if (!isTranscoding() && isNativeMode() && mCurrentOptions.getAudioStreamIndex() != null) {
// Use stream index from mCurrentOptions if it's set.
// This should be null until the player has been queried at least once after playback starts
//
// Use DefaultAudioStreamIndex for transcoding since they are encoded with only one stream
//
// Otherwise, query the players
if (mCurrentOptions.getAudioStreamIndex() != null) {
currIndex = mCurrentOptions.getAudioStreamIndex();
} else if (getCurrentMediaSource().getDefaultAudioStreamIndex() != null) {
} else if (isTranscoding() && getCurrentMediaSource().getDefaultAudioStreamIndex() != null) {
currIndex = getCurrentMediaSource().getDefaultAudioStreamIndex();
} else if (hasInitializedVideoManager() && !isTranscoding()) {
currIndex = isNativeMode() ? mVideoManager.getExoPlayerTrack(MediaStreamType.Audio, getCurrentlyPlayingItem().getMediaStreams()) :
mVideoManager.getVLCAudioTrack(getCurrentlyPlayingItem().getMediaStreams());
}
return currIndex;
}
@@ -864,8 +893,13 @@ public class PlaybackController {
if (mDefaultAudioIndex != -1)
return;
if (bestGuessAudioTrack(info.getMediaSource()) != null)
mDefaultAudioIndex = bestGuessAudioTrack(info.getMediaSource());
Integer remoteDefault = info.getMediaSource().getDefaultAudioStreamIndex();
Integer bestGuess = bestGuessAudioTrack(info.getMediaSource());
if (remoteDefault != null)
mDefaultAudioIndex = remoteDefault;
else if (bestGuess != null)
mDefaultAudioIndex = bestGuess;
Timber.d("default audio index set to %s", mDefaultAudioIndex);
}
@@ -877,6 +911,10 @@ public class PlaybackController {
Timber.d("trying to switch audio stream from %s to %s", currAudioIndex, index);
if (currAudioIndex == index) {
Timber.d("skipping setting audio stream, already set to requested index %s", index);
if (mCurrentOptions.getAudioStreamIndex() == null || mCurrentOptions.getAudioStreamIndex() != index) {
Timber.d("setting mCurrentOptions audio stream index from %s to %s", mCurrentOptions.getAudioStreamIndex(), index);
mCurrentOptions.setAudioStreamIndex(index);
}
return;
}
@@ -886,7 +924,7 @@ public class PlaybackController {
if (isNativeMode() && !isTranscoding() && mVideoManager.setExoPlayerTrack(index, MediaStreamType.Audio, getCurrentlyPlayingItem().getMediaStreams())) {
mCurrentOptions.setMediaSourceId(getCurrentMediaSource().getId());
mCurrentOptions.setAudioStreamIndex(index);
} else if (!isNativeMode() && !isTranscoding() && mVideoManager.setVLCAudioTrack(index) == index) {
} else if (!isNativeMode() && !isTranscoding() && mVideoManager.setVLCAudioTrack(index, getCurrentlyPlayingItem().getMediaStreams()) == index) {
// if setAudioTrack succeeded it will return the requested index
mCurrentOptions.setMediaSourceId(getCurrentMediaSource().getId());
mCurrentOptions.setAudioStreamIndex(index);
@@ -905,20 +943,23 @@ public class PlaybackController {
public void switchSubtitleStream(int index) {
if (!hasInitializedVideoManager())
return;
// get current timestamp first
refreshCurrentPosition();
Timber.d("Setting subtitle index to: %d", index);
mCurrentOptions.setSubtitleStreamIndex(index);
mDefaultSubIndex = index;
// clear subtitles first
if (mFragment != null) mFragment.addManualSubtitles(null);
mVideoManager.disableSubs();
// clear the default in case there's an error loading the subtitles
mDefaultSubIndex = -1;
// handle setting subtitles as disabled
// restart playback if turning off burnt-in subtitles
if (index < 0) {
mCurrentOptions.setSubtitleStreamIndex(-1);
if (burningSubs) {
stop();
play(mCurrentPosition, -1);
} else {
if (mFragment != null) mFragment.addManualSubtitles(null);
mVideoManager.disableSubs();
}
return;
}
@@ -929,77 +970,77 @@ public class PlaybackController {
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.subtitle_error));
return;
}
// handle according to delivery method
SubtitleStreamInfo streamInfo = getSubtitleStreamInfo(index);
if (streamInfo == null) {
if (mFragment != null)
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_unable_load_subs));
} else {
switch (streamInfo.getDeliveryMethod()) {
return;
}
case Encode:
// Gonna need to burn in so start a transcode with the sub index
stop();
if (!mVideoManager.isNativeMode()) {
// handle switching on or between burnt-in subtitles, or switching to non-burnt subtitles
// if switching from burnt-in subtitles to another type, playback still needs to be restarted
if (burningSubs || streamInfo.getDeliveryMethod() == SubtitleDeliveryMethod.Encode) {
stop();
if (mFragment != null && streamInfo.getDeliveryMethod() == SubtitleDeliveryMethod.Encode)
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_burn_sub_warning));
play(mCurrentPosition, index);
return;
}
// when burnt-in subtitles are selected, mCurrentOptions SubtitleStreamIndex is set in startItem() as soon as playback starts
// otherwise mCurrentOptions SubtitleStreamIndex is kept null until now so we knew subtitles needed to be enabled but weren't already
switch (streamInfo.getDeliveryMethod()) {
case Embed:
if (!mVideoManager.isNativeMode()) {
if (!mVideoManager.setSubtitleTrack(index, getCurrentlyPlayingItem().getMediaStreams())) {
// error selecting internal subs
if (mFragment != null)
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_burn_sub_warning));
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_unable_load_subs));
} else {
mCurrentOptions.setSubtitleStreamIndex(index);
mDefaultSubIndex = index;
}
play(mCurrentPosition, index);
break;
case Embed:
if (!mVideoManager.isNativeMode()) {
if (mFragment != null)
mFragment.addManualSubtitles(null); // in case these were on
if (!mVideoManager.setSubtitleTrack(index, getCurrentlyPlayingItem().getMediaStreams())) {
// error selecting internal subs
if (mFragment != null)
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_unable_load_subs));
}
break;
}
// not using vlc - fall through to external handling
case External:
if (mFragment != null) mFragment.addManualSubtitles(null);
mVideoManager.disableSubs();
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>() {
}
// not using vlc - fall through to external handling
case External:
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>() {
@Override
public void onResponse(final SubtitleTrackInfo info) {
@Override
public void onResponse(final SubtitleTrackInfo info) {
if (info != null) {
Timber.d("Adding json subtitle track to player");
if (mFragment != null) mFragment.addManualSubtitles(info);
} else {
Timber.e("Empty subtitle result");
if (mFragment != null) {
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_unable_load_subs));
mFragment.showSubLoadingMsg(false);
}
}
}
@Override
public void onError(Exception ex) {
Timber.e(ex, "Error downloading subtitles");
if (info != null) {
Timber.d("Adding json subtitle track to player");
if (mFragment != null) mFragment.addManualSubtitles(info);
mCurrentOptions.setSubtitleStreamIndex(index);
mDefaultSubIndex = index;
} else {
Timber.e("Empty subtitle result");
if (mFragment != null) {
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_unable_load_subs));
mFragment.showSubLoadingMsg(false);
}
}
}
});
break;
case Hls:
break;
}
@Override
public void onError(Exception ex) {
Timber.e(ex, "Error downloading subtitles");
if (mFragment != null) {
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_unable_load_subs));
mFragment.showSubLoadingMsg(false);
}
}
});
break;
case Hls:
break;
}
}
public void pause() {
@@ -1419,7 +1460,6 @@ public class PlaybackController {
if (mPlaybackState == PlaybackState.PAUSED) {
mPlaybackState = PlaybackState.PLAYING;
} else {
// select or disable subtitles
Integer currentSubtitleIndex = mCurrentOptions.getSubtitleStreamIndex();
if (mDefaultSubIndex >= 0 && currentSubtitleIndex != null && currentSubtitleIndex == mDefaultSubIndex) {

View File

@@ -44,9 +44,10 @@ import org.koin.java.KoinJavaComponent;
import org.videolan.libvlc.LibVLC;
import org.videolan.libvlc.Media;
import org.videolan.libvlc.interfaces.IVLCVout;
import org.videolan.libvlc.MediaPlayer;
import org.videolan.libvlc.MediaPlayer.TrackDescription;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import timber.log.Timber;
@@ -67,7 +68,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
private PlayerView mExoPlayerView;
private AspectRatioFrameLayout mAspectRatioFrameLayout;
private LibVLC mLibVLC;
private org.videolan.libvlc.MediaPlayer mVlcPlayer;
private MediaPlayer mVlcPlayer;
private Media mCurrentMedia;
private VlcEventHandler mVlcHandler = new VlcEventHandler();
private Handler mHandler = new Handler();
@@ -77,7 +78,6 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
private int mVideoVisibleWidth;
private int mSarNum;
private int mSarDen;
private Integer exoplayerAudioIndex = null;
private long mForcedTime = -1;
private long mLastTime = -1;
@@ -149,7 +149,6 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
@Override
public void onTracksInfoChanged(TracksInfo tracksInfo) {
Timber.d("Tracks info changed");
exoplayerAudioIndex = null;
}
});
}
@@ -297,7 +296,6 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
mActivity.finish();
return;
}
exoplayerAudioIndex = null;
mExoPlayer.setPlayWhenReady(true);
normalWidth = mExoPlayerView.getLayoutParams().width;
normalHeight = mExoPlayerView.getLayoutParams().height;
@@ -339,7 +337,6 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
mExoPlayer.setTrackSelectionParameters(mExoPlayer.getTrackSelectionParameters()
.buildUpon()
.setTrackSelectionOverrides(overrides).build());
exoplayerAudioIndex = null;
} else if (mVlcPlayer != null) {
mVlcPlayer.stop();
}
@@ -421,37 +418,43 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
}
}
public boolean setSubtitleTrack(int index, @Nullable List<MediaStream> allStreams) {
if (!nativeMode && allStreams != null) {
//find the relative order of our sub index within the sub tracks in VLC
int vlcIndex = 1; // start at 1 to account for "disabled"
for (MediaStream stream : allStreams) {
if (stream.getType() == MediaStreamType.Subtitle && !stream.getIsExternal()) {
if (stream.getIndex() == index) {
break;
}
vlcIndex++;
}
}
org.videolan.libvlc.MediaPlayer.TrackDescription vlcSub;
try {
vlcSub = getSubtitleTracks()[vlcIndex];
} catch (IndexOutOfBoundsException e) {
Timber.e("Could not locate subtitle with index %s in vlc track info", index);
return false;
} catch (NullPointerException e) {
Timber.e("No subtitle tracks found in player trying to set subtitle with index %s in vlc track info", index);
return false;
}
Timber.i("Setting Vlc sub to %s", vlcSub.name);
return mVlcPlayer.setSpuTrack(vlcSub.id);
private int offsetStreamIndex(int index, boolean adjustByAdding, @Nullable List<MediaStream> allStreams) {
if (index < 0 || allStreams == null)
return -1;
for (MediaStream stream : allStreams) {
if (!stream.getIsExternal())
break;
index += adjustByAdding ? 1 : -1;
}
return false;
return index < 0 || index >= allStreams.size() ? -1 : index;
}
public boolean setSubtitleTrack(int index, @Nullable List<MediaStream> allStreams) {
if (isNativeMode() || allStreams == null)
return false;
int vlcIndex = offsetStreamIndex(index, false, allStreams);
if (vlcIndex < 0)
return false;
TrackDescription vlcSub = null;
for (TrackDescription subTrack: mVlcPlayer.getSpuTracks()) {
Timber.d("libvlc subtitle track %s %s", subTrack.id, subTrack.name);
if (subTrack.id == vlcIndex)
vlcSub = subTrack;
}
if (vlcSub == null) {
Timber.e("Could not locate subtitle with index %s in vlc track info", vlcIndex);
return false;
}
Timber.i("Setting Vlc sub to %s", vlcSub.name);
return mVlcPlayer.setSpuTrack(vlcSub.id);
}
public int getExoPlayerTrack(@Nullable MediaStreamType streamType, @Nullable List<MediaStream> allStreams) {
@@ -460,13 +463,13 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
if (streamType != MediaStreamType.Subtitle && streamType != MediaStreamType.Audio)
return -1;
if (exoplayerAudioIndex != null && streamType == MediaStreamType.Audio)
return exoplayerAudioIndex;
int chosenTrackType = streamType == MediaStreamType.Subtitle ? C.TRACK_TYPE_TEXT : C.TRACK_TYPE_AUDIO;
int matchedIndex = -2;
TracksInfo exoTracks = mExoPlayer.getCurrentTracksInfo();
for (TracksInfo.TrackGroupInfo groupInfo : exoTracks.getTrackGroupInfos()) {
if (matchedIndex > -2)
break;
// Group level information.
@C.TrackType int trackType = groupInfo.getTrackType();
TrackGroup group = groupInfo.getTrackGroup();
@@ -475,62 +478,50 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
Format trackFormat = group.getFormat(i);
if (trackType == chosenTrackType) {
if (groupInfo.isTrackSelected(i)) {
// we found the track, set to -1 first to handle failed int parsing
matchedIndex = -1;
if (trackFormat.id != null) {
int id;
try {
id = Integer.parseInt(trackFormat.id) - 1;
id = Integer.parseInt(trackFormat.id);
} catch (NumberFormatException e) {
Timber.d("failed to parse track ID [%s]", trackFormat.id);
return -1;
}
if (id >= 0 && id < allStreams.size()) {
Timber.d("re-retrieved exoplayer track index %s", id);
if (streamType == MediaStreamType.Audio)
exoplayerAudioIndex = id;
return id;
break;
}
matchedIndex = id;
}
return -1;
break;
}
}
}
}
return -1;
// offset the stream index to account for external streams
int exoTrackID = offsetStreamIndex(matchedIndex, true, allStreams);
if (exoTrackID < 0)
return -1;
Timber.d("re-retrieved exoplayer track index %s", exoTrackID);
return exoTrackID;
}
public boolean setExoPlayerTrack(int index, @Nullable MediaStreamType streamType, @Nullable List<MediaStream> allStreams) {
if (!nativeMode || !isInitialized() || streamType == null || allStreams == null || index < 0 || index >= allStreams.size())
return false;
if (streamType != MediaStreamType.Subtitle && streamType != MediaStreamType.Audio)
if (!nativeMode || !isInitialized() || allStreams == null || streamType != MediaStreamType.Subtitle && streamType != MediaStreamType.Audio)
return false;
int chosenTrackType = streamType == MediaStreamType.Subtitle ? C.TRACK_TYPE_TEXT : C.TRACK_TYPE_AUDIO;
TracksInfo exoTracks = mExoPlayer.getCurrentTracksInfo();
List<String> internallyRenderedSubFormats = Arrays.asList("ass", "ssa");
// make sure the chosen track exists, is the correct type, and isn't external
boolean isValidMediaStream = false;
for (MediaStream stream : allStreams) {
Timber.d("MediaStream track %s type %s label %s codec %s isExternal %s", stream.getIndex(), stream.getType(), stream.getTitle(), stream.getCodec(), stream.getIsExternal());
if (stream.getType() == streamType) {
if (stream.getIndex() == index) {
if (stream.getIsExternal())
continue;
if (streamType == MediaStreamType.Subtitle && !internallyRenderedSubFormats.contains(stream.getCodec())) {
continue;
}
isValidMediaStream = true;
}
}
}
if (!isValidMediaStream)
MediaStream candidate = allStreams.get(index);
if (candidate.getIsExternal() || candidate.getType() != streamType)
return false;
// MediaStream IDs start at 0 and exoplayer tracks start at 1
// force selection of the chosen track by setting its TrackGroup as the only allowed group for its type
int exoTrackID = offsetStreamIndex(index, false, allStreams);
if (exoTrackID < 0)
return false;
// print the streams for debugging
for (MediaStream stream : allStreams) {
Timber.d("MediaStream track %s type %s label %s codec %s isExternal %s", stream.getIndex(), stream.getType(), stream.getTitle(), stream.getCodec(), stream.getIsExternal());
}
// design choices for exoplayer track selection overrides:
// * build upon the prior parameters so we can mix overrides of different track types without erasing priors
@@ -539,8 +530,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
// if we want most formats to be handled by the external subtitle handler (which has adjustable size, background), we leave sub track selection disabled
// if we decide to use exoplayer to render a specific subtitle format, allow subtitle track selection and restrict selection to the chosen group
int exoTrackID = index + 1;
TracksInfo exoTracks = mExoPlayer.getCurrentTracksInfo();
TrackGroup matchedGroup = null;
for (TracksInfo.TrackGroupInfo groupInfo : exoTracks.getTrackGroupInfos()) {
// Group level information.
@@ -597,63 +587,51 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
return true;
}
public int getVLCAudioTrack() {
public int getVLCAudioTrack(@Nullable List<MediaStream> allStreams) {
if (!isInitialized() || nativeMode)
return -1;
return mVlcPlayer.getAudioTrack();
int ndx = offsetStreamIndex(mVlcPlayer.getAudioTrack(), true, allStreams);
Timber.d("re-retrieved libVLC audio track index %s", ndx);
return ndx;
}
public int setVLCAudioTrack(int ndx) {
if (!isInitialized() || ndx < 0)
public int setVLCAudioTrack(int ndx, @Nullable List<MediaStream> allStreams) {
if (!isInitialized() || isNativeMode())
return -1;
if (isNativeMode()) {
Timber.e("Cannot set audio track in native mode");
int vlcID = offsetStreamIndex(ndx, false, allStreams);
if (vlcID < 0)
return -1;
}
//debug
Timber.d("Setting VLC audio track index to: %d", ndx);
for (org.videolan.libvlc.MediaPlayer.TrackDescription track : mVlcPlayer.getAudioTracks()) {
TrackDescription vlcTrack = null;
Timber.d("Setting VLC audio track index to: %d", vlcID);
int vlcNdx = 0;
for (TrackDescription track : mVlcPlayer.getAudioTracks()) {
Timber.d("VLC Audio Track: %s / %d", track.name, track.id);
}
//
boolean matched = false;
for (org.videolan.libvlc.MediaPlayer.TrackDescription track : mVlcPlayer.getAudioTracks()) {
if (track.id == ndx) {
matched = true;
break;
}
if (track.id == vlcID)
vlcTrack = track;
if (vlcTrack == null)
vlcNdx++;
}
org.videolan.libvlc.MediaPlayer.TrackDescription vlcTrack = null;
if (matched) {
try {
vlcTrack = mVlcPlayer.getAudioTracks()[ndx];
} catch (IndexOutOfBoundsException e) {
Timber.e("Could not locate audio with index %s in vlc track info", ndx);
return -1;
} catch (NullPointerException e) {
Timber.e("Could not locate audio track with index %s in vlc, null exception", ndx);
return -1;
}
}
if (!matched || vlcTrack == null) {
Timber.e("Could not locate audio track with index %s in vlc", ndx);
if (vlcTrack == null) {
Timber.e("Could not locate audio track with index %s in vlc", vlcID);
return -1;
} else if (mVlcPlayer.getAudioTrack() == ndx) {
}
if (mVlcPlayer.getAudioTrack() == vlcID) {
Timber.d("provided index points to the audio track already in use, aborting");
return -1;
return ndx;
}
if (mVlcPlayer.setAudioTrack(vlcTrack.id)) {
Timber.i("Setting by ID was successful");
} else {
Timber.i("Setting by ID not successful, trying index");
mVlcPlayer.setAudioTrack(ndx);
Timber.i("Setting by ID not successful, trying index %s", vlcNdx);
mVlcPlayer.setAudioTrack(vlcNdx);
}
return ndx;
}
@@ -729,10 +707,6 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
}
}
public org.videolan.libvlc.MediaPlayer.TrackDescription[] getSubtitleTracks() {
return nativeMode ? null : mVlcPlayer.getSpuTracks();
}
public void destroy() {
stopPlayback();
releasePlayer();
@@ -746,7 +720,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
// Create a new media player
ArrayList<String> options = new ArrayList<>(20);
options.add("--network-caching=" + buffer);
options.add("--no-audio-time-stretch");
options.add("--audio-time-stretch");
options.add("--avcodec-skiploopfilter");
options.add("1");
options.add("--avcodec-skip-frame");
@@ -772,7 +746,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
mLibVLC = new LibVLC(mActivity, options);
Timber.i("Network buffer set to %d", buffer);
mVlcPlayer = new org.videolan.libvlc.MediaPlayer(mLibVLC);
mVlcPlayer = new MediaPlayer(mLibVLC);
setVlcAudioOptions();
mSurfaceHolder.addCallback(mSurfaceCallback);

View File

@@ -13,13 +13,10 @@ class ButtonRemapPreference(
) : DialogPreference(context, attrs) {
override fun getDialogLayoutResource() = R.layout.preference_button_remap
private var innerKeyCode: Int = -1
var keyCode: Int
get() = innerKeyCode
var keyCode: Int = -1
set(value) {
innerKeyCode = value
notifyDependencyChange(false)
notifyChanged()
field = value
callChangeListener(value)
}
var defaultKeyCode: Int = -1

View File

@@ -7,6 +7,7 @@ import org.jellyfin.androidtv.preference.constant.AudioBehavior
import org.jellyfin.androidtv.preference.constant.NEXTUP_TIMER_DISABLED
import org.jellyfin.androidtv.preference.constant.NextUpBehavior
import org.jellyfin.androidtv.preference.constant.PreferredVideoPlayer
import org.jellyfin.androidtv.preference.constant.RefreshRateSwitchingBehavior
import org.jellyfin.androidtv.ui.preference.custom.DurationSeekBarPreference
import org.jellyfin.androidtv.ui.preference.dsl.OptionsFragment
import org.jellyfin.androidtv.ui.preference.dsl.checkbox
@@ -134,10 +135,9 @@ class PlaybackPreferencesScreen : OptionsFragment() {
depends { userPreferences[UserPreferences.videoPlayer] == PreferredVideoPlayer.EXTERNAL }
}
checkbox {
enum<RefreshRateSwitchingBehavior> {
setTitle(R.string.lbl_refresh_switching)
setContent(R.string.pref_refresh_switching_description)
bind(userPreferences, UserPreferences.refreshRateSwitchingEnabled)
bind(userPreferences, UserPreferences.refreshRateSwitchingBehavior)
depends { DeviceUtils.is60() && userPreferences[UserPreferences.videoPlayer] != PreferredVideoPlayer.EXTERNAL }
}
}

View File

@@ -41,7 +41,7 @@ import java.util.HashMap;
import timber.log.Timber;
public class CardPresenter extends Presenter {
private static final double ASPECT_RATIO_BANNER = 1000 / 185;
private static final double ASPECT_RATIO_BANNER = 1000.0 / 185.0;
private int mStaticHeight = 300;
private ImageType mImageType = ImageType.DEFAULT;

View File

@@ -11,6 +11,7 @@
<string name="settings_title">Preferences</string>
<string name="pref_authentication_cat">Authentication</string>
<string name="chapters">Chapters</string>
<string name="state_disabled">Disabled</string>
<string name="lbl_settings">Preferences</string>
<string name="lbl_continue_watching">Continue Watching</string>
<string name="lbl_next_up">Next Up</string>
@@ -147,7 +148,7 @@
<string name="lbl_filters">Filters</string>
<string name="msg_subtitles_loading">Subtitles loading…</string>
<string name="msg_unable_load_subs">Unable to load subtitle</string>
<string name="msg_burn_sub_warning">Burned in subs can take a few minutes. Please be patient</string>
<string name="msg_burn_sub_warning">Burning in subtitles. This may take a few seconds</string>
<string name="lbl_recording">Recording</string>
<string name="lbl_no_recordings">No Recordings Available</string>
<string name="lbl_live_tv_guide">Live TV Guide</string>
@@ -204,8 +205,6 @@
<string name="lbl_open">Open</string>
<string name="lbl_show_backdrop">Show backdrops</string>
<string name="pref_show_backdrop_description">Change background image to selected items</string>
<string name="lbl_refresh_switching">Refresh rate switching</string>
<string name="pref_refresh_switching_description">Monitor refresh rate is changed to match video</string>
<string name="lbl_show_premieres">Show season premieres</string>
<string name="desc_premieres">Show a row of new episode pilots for any series you watch</string>
<string name="lbl_bitstream_dts">Bitstream DTS audio</string>
@@ -486,4 +485,17 @@
<string name="pref_direct_stream_live_off">Transcoding is used when necessary</string>
<string name="pref_direct_stream_live_on">Transcoding is disabled</string>
<string name="home_section_resume_book">Continue reading</string>
<string name="action_login">Sign in</string>
<string name="action_use_password">Use a password</string>
<string name="action_use_quickconnect">Use QuickConnect</string>
<string name="input_username">Username</string>
<string name="input_password">Password</string>
<string name="login_connect_to">Connecting to %1$s</string>
<string name="login_other_options">Other options:</string>
<string name="login_quickconnect_step_1">1. Open the Jellyfin app on your phone or webbrowser, and sign in with your account</string>
<string name="login_quickconnect_step_2">2. Open the user menu and go to the Quick Connect page</string>
<string name="login_quickconnect_step_3">3. Enter the following code:</string>
<string name="lbl_refresh_switching">Refresh rate switching</string>
<string name="pref_refresh_rate_scale_on_tv">Scale on TV</string>
<string name="pref_refresh_rate_scale_on_device">Scale on device</string>
</resources>

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=e5444a57cda4a95f90b0c9446a9e1b47d3d7f69057765bfb54bd4f482542d548
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.1-bin.zip
distributionSha256Sum=29e49b10984e585d8118b7d0bc452f944e386458df27371b49b4ac1dec4b7fda
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists