Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b653d89285 | ||
|
|
c50a8356b3 | ||
|
|
9be3b5f81f | ||
|
|
a7a37054f2 | ||
|
|
49182a2a71 | ||
|
|
f473b3acb5 | ||
|
|
5caf4ac11b | ||
|
|
4ec568c311 | ||
|
|
3cdeba071c | ||
|
|
d80df757fc | ||
|
|
58b936b334 | ||
|
|
e22267b089 | ||
|
|
bf9a5ea5af | ||
|
|
8fabf7904d | ||
|
|
7bbcfb50cd | ||
|
|
b13eaea746 | ||
|
|
21b4103cb8 | ||
|
|
540eb286b3 |
@@ -123,7 +123,7 @@ class ServerRepositoryImpl(
|
||||
}
|
||||
}
|
||||
|
||||
Timber.d(buildString {
|
||||
Timber.i(buildString {
|
||||
append("Recommendations: ")
|
||||
if (greatRecommendation == null) append(0)
|
||||
else append(1)
|
||||
|
||||
@@ -67,7 +67,7 @@ fun Seekbar(
|
||||
enabled: Boolean = true,
|
||||
colors: SeekbarColors = SeekbarDefaults.colors(),
|
||||
) {
|
||||
val durationMs = duration.inWholeMilliseconds.toFloat()
|
||||
val durationMs = duration.inWholeMilliseconds.toFloat().coerceAtLeast(1f)
|
||||
val progressPercentage = progress.inWholeMilliseconds.toFloat() / durationMs
|
||||
val bufferPercentage = buffer.inWholeMilliseconds.toFloat() / durationMs
|
||||
val seekForwardPercentage = seekForwardAmount.inWholeMilliseconds.toFloat() / durationMs
|
||||
|
||||
@@ -878,7 +878,7 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
|
||||
|
||||
private void refreshCurrentItem() {
|
||||
if (mCurrentItem == null) return;
|
||||
Timber.d("Refresh item \"%s\"", mCurrentItem.getFullName(requireContext()));
|
||||
Timber.i("Refresh item \"%s\"", mCurrentItem.getFullName(requireContext()));
|
||||
ItemRowAdapterHelperKt.refreshItem(mAdapter, api.getValue(), this, mCurrentItem, () -> {
|
||||
//Now - if filtered make sure we still pass
|
||||
if (mAdapter.getFilters() == null) return null;
|
||||
|
||||
@@ -112,7 +112,7 @@ class MainActivity : FragmentActivity() {
|
||||
workManager.enqueue(OneTimeWorkRequestBuilder<LeanbackChannelWorker>().build())
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
Timber.d("MainActivity stopped")
|
||||
Timber.i("MainActivity stopped")
|
||||
sessionRepository.restoreSession(destroyOnly = true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ fun rememberPlayerProgress(
|
||||
active: Duration,
|
||||
duration: Duration,
|
||||
): Float {
|
||||
val animatable = remember { Animatable(0f) }
|
||||
val animatable = remember { Animatable(0f, 0f) }
|
||||
|
||||
LaunchedEffect(playing, duration) {
|
||||
val activeMs = active.inWholeMilliseconds.toFloat()
|
||||
|
||||
@@ -231,7 +231,7 @@ class HomeRowsFragment : RowsSupportFragment(), AudioEventListener, View.OnKeyLi
|
||||
val adapter = currentRow?.adapter as? ItemRowAdapter ?: return
|
||||
val item = currentItem ?: return
|
||||
|
||||
Timber.d("Refresh item ${item.getFullName(requireContext())}")
|
||||
Timber.i("Refresh item ${item.getFullName(requireContext())}")
|
||||
adapter.refreshItem(api, this, item)
|
||||
}
|
||||
|
||||
|
||||
@@ -250,7 +250,7 @@ public class FullDetailsFragment extends Fragment implements RecordingIndicatorV
|
||||
loadItem(lastPlayedItem.getId());
|
||||
dataRefreshService.getValue().setLastPlayedItem(null); //blank this out so a detail screen we back up to doesn't also do this
|
||||
} else {
|
||||
Timber.d("Updating info after playback");
|
||||
Timber.i("Updating info after playback");
|
||||
FullDetailsFragmentHelperKt.getItem(FullDetailsFragment.this, mBaseItem.getId(), item -> {
|
||||
if (item == null) return null;
|
||||
|
||||
|
||||
@@ -270,12 +270,10 @@ fun FullDetailsFragment.resumePlayback(v: View) {
|
||||
getString(R.string.msg_video_playback_error),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
|
||||
if (nextUpEpisode?.userData?.playbackPositionTicks == 0L) {
|
||||
} else if (nextUpEpisode.userData?.playbackPositionTicks == 0L) {
|
||||
play(nextUpEpisode, 0, false)
|
||||
} else {
|
||||
showResumeMenu(v, nextUpEpisode!!)
|
||||
showResumeMenu(v, nextUpEpisode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -230,7 +230,7 @@ public class ItemListFragment extends Fragment implements View.OnKeyListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long pos) {
|
||||
public void onProgress(long pos, long duration) {
|
||||
if (mCurrentlyPlayingRow != null) {
|
||||
mCurrentlyPlayingRow.updateCurrentTime(pos);
|
||||
}
|
||||
@@ -351,7 +351,7 @@ public class ItemListFragment extends Fragment implements View.OnKeyListener {
|
||||
}
|
||||
|
||||
private void play(List<BaseItemDto> items, int ndx, boolean shuffle) {
|
||||
Timber.d("play items: %d, ndx: %d, shuffle: %b", items.size(), ndx, shuffle);
|
||||
Timber.i("play items: %d, ndx: %d, shuffle: %b", items.size(), ndx, shuffle);
|
||||
|
||||
int pos = 0;
|
||||
BaseItemDto item = items.size() > 0 ? items.get(ndx) : null;
|
||||
|
||||
@@ -197,7 +197,7 @@ public class MusicFavoritesListFragment extends Fragment implements View.OnKeyLi
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long pos) {
|
||||
public void onProgress(long pos, long duration) {
|
||||
if (mCurrentlyPlayingRow != null) {
|
||||
mCurrentlyPlayingRow.updateCurrentTime(pos);
|
||||
}
|
||||
@@ -283,7 +283,7 @@ public class MusicFavoritesListFragment extends Fragment implements View.OnKeyLi
|
||||
};
|
||||
|
||||
private void play(List<BaseItemDto> items, int ndx, boolean shuffle) {
|
||||
Timber.d("play items: %d, ndx: %d, shuffle: %b", items.size(), ndx, shuffle);
|
||||
Timber.i("play items: %d, ndx: %d, shuffle: %b", items.size(), ndx, shuffle);
|
||||
|
||||
playbackLauncher.getValue().launch(requireContext(), items, 0, false, ndx, shuffle);
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ open class BaseItemDtoBaseRowItem @JvmOverloads constructor(
|
||||
val seriesPrimaryImage = baseItem?.seriesPrimaryImage
|
||||
|
||||
return when {
|
||||
preferSeriesPoster && seriesPrimaryImage != null -> imageHelper.getImageUrl(seriesPrimaryImage)
|
||||
preferSeriesPoster && seriesPrimaryImage != null -> imageHelper.getImageUrl(seriesPrimaryImage, fillWidth, fillHeight)
|
||||
|
||||
imageType == ImageType.BANNER -> imageHelper.getBannerImageUrl(
|
||||
requireNotNull(
|
||||
|
||||
@@ -71,7 +71,7 @@ public class ItemLauncher {
|
||||
case BaseItem:
|
||||
BaseItemDto baseItem = rowItem.getBaseItem();
|
||||
try {
|
||||
Timber.d("Item selected: %s (%s)", baseItem.getName(), baseItem.getType().toString());
|
||||
Timber.i("Item selected: %s (%s)", baseItem.getName(), baseItem.getType().toString());
|
||||
} catch (Exception e) {
|
||||
//swallow it
|
||||
}
|
||||
|
||||
@@ -468,18 +468,18 @@ public class ItemRowAdapter extends MutableObjectAdapter<Object> {
|
||||
return;
|
||||
}
|
||||
if (isCurrentlyRetrieving()) {
|
||||
Timber.d("Not loading more because currently retrieving");
|
||||
Timber.i("Not loading more because currently retrieving");
|
||||
return;
|
||||
}
|
||||
// This needs tobe based on the actual estimated cards on screen via type of presenter and WindowAlignmentOffsetPercent
|
||||
if (chunkSize > 0) {
|
||||
// we can use chunkSize as indicator on when to load
|
||||
if (pos >= (itemsLoaded - (chunkSize / 1.7))) {
|
||||
Timber.d("Loading more items trigger pos <%s> itemsLoaded <%s> from total <%s> with chunkSize <%s>", pos, itemsLoaded, totalItems, chunkSize);
|
||||
Timber.i("Loading more items trigger pos <%s> itemsLoaded <%s> from total <%s> with chunkSize <%s>", pos, itemsLoaded, totalItems, chunkSize);
|
||||
retrieveNext();
|
||||
}
|
||||
} else if (pos >= itemsLoaded - 20) {
|
||||
Timber.d("Loading more items trigger pos <%s> itemsLoaded <%s> from total <%s>", pos, itemsLoaded, totalItems);
|
||||
Timber.i("Loading more items trigger pos <%s> itemsLoaded <%s> from total <%s>", pos, itemsLoaded, totalItems);
|
||||
retrieveNext();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ fun <T : Any> ItemRowAdapter.setItems(
|
||||
items: Collection<T>,
|
||||
transform: (T, Int) -> BaseRowItem?,
|
||||
) {
|
||||
Timber.d("Creating items from $itemsLoaded existing and ${items.size} new, adapter size is ${size()}")
|
||||
Timber.i("Creating items from $itemsLoaded existing and ${items.size} new, adapter size is ${size()}")
|
||||
|
||||
val allItems = buildList {
|
||||
// Add current items before loaded items
|
||||
|
||||
@@ -68,7 +68,7 @@ class NavigationRepositoryImpl(
|
||||
override val currentAction = _currentAction.asSharedFlow()
|
||||
|
||||
override fun navigate(destination: Destination, replace: Boolean) {
|
||||
Timber.d("Navigating to $destination (via navigate function)")
|
||||
Timber.i("Navigating to $destination (via navigate function)")
|
||||
val action = when (destination) {
|
||||
is Destination.Fragment -> NavigationAction.NavigateFragment(destination, true, replace, false)
|
||||
}
|
||||
@@ -84,7 +84,7 @@ class NavigationRepositoryImpl(
|
||||
override fun goBack(): Boolean {
|
||||
if (fragmentHistory.empty()) return false
|
||||
|
||||
Timber.d("Navigating back")
|
||||
Timber.i("Navigating back")
|
||||
fragmentHistory.pop()
|
||||
_currentAction.tryEmit(NavigationAction.GoBack)
|
||||
return true
|
||||
@@ -94,7 +94,7 @@ class NavigationRepositoryImpl(
|
||||
fragmentHistory.clear()
|
||||
val actualDestination = destination ?: defaultDestination
|
||||
_currentAction.tryEmit(NavigationAction.NavigateFragment(actualDestination, true, false, clearHistory))
|
||||
Timber.d("Navigating to $actualDestination (via reset, clearHistory=$clearHistory)")
|
||||
Timber.i("Navigating to $actualDestination (via reset, clearHistory=$clearHistory)")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
|
||||
interface AudioEventListener {
|
||||
fun onPlaybackStateChange(newState: PlaybackController.PlaybackState, currentItem: BaseItemDto?) = Unit
|
||||
fun onProgress(pos: Long) = Unit
|
||||
fun onProgress(pos: Long, duration: Long) = Unit
|
||||
fun onQueueStatusChanged(hasQueue: Boolean) = Unit
|
||||
fun onQueueReplaced() = Unit
|
||||
}
|
||||
|
||||
@@ -65,7 +65,6 @@ public class AudioNowPlayingFragment extends Fragment {
|
||||
private TextView mCurrentNdx;
|
||||
private TextView mCurrentPos;
|
||||
private TextView mRemainingTime;
|
||||
private int mCurrentDuration;
|
||||
private RowsSupportFragment mRowsFragment;
|
||||
private ArrayObjectAdapter mRowsAdapter;
|
||||
private PositionableListRowPresenter mAudioQueuePresenter;
|
||||
@@ -239,7 +238,7 @@ public class AudioNowPlayingFragment extends Fragment {
|
||||
|
||||
// load the item duration and set the position to 0 since it won't be set elsewhere until playback is initialized
|
||||
if (!mediaManager.getValue().isAudioPlayerInitialized())
|
||||
setCurrentTime(0);
|
||||
setCurrentTime(0, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -258,8 +257,8 @@ public class AudioNowPlayingFragment extends Fragment {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onProgress(long pos) {
|
||||
setCurrentTime(pos);
|
||||
public void onProgress(long pos, long duration) {
|
||||
setCurrentTime(pos, duration);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -354,17 +353,17 @@ public class AudioNowPlayingFragment extends Fragment {
|
||||
mAlbumTitle.setText(null);
|
||||
}
|
||||
mCurrentNdx.setText(getString(R.string.lbl_now_playing_track, mediaManager.getValue().getCurrentAudioQueueDisplayPosition(), mediaManager.getValue().getCurrentAudioQueueDisplaySize()));
|
||||
mCurrentDuration = ((Long) ((item.getRunTimeTicks() != null ? item.getRunTimeTicks() : 0) / 10000)).intValue();
|
||||
addGenres(mGenreRow);
|
||||
backgroundService.getValue().setBackground(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void setCurrentTime(long time) {
|
||||
// Round the current time as otherwise the time played and time remaining will not be in sync
|
||||
public void setCurrentTime(long time, long duration) {
|
||||
// Round the time to seconds so both the position and remaining times are in sync
|
||||
time = Math.round(time / 1000L) * 1000L;
|
||||
|
||||
mCurrentPos.setText(TimeUtils.formatMillis(time));
|
||||
mRemainingTime.setText(mCurrentDuration > 0 ? "-" + TimeUtils.formatMillis(mCurrentDuration - time) : "");
|
||||
mRemainingTime.setText("-" + TimeUtils.formatMillis(duration - time));
|
||||
}
|
||||
|
||||
private void addGenres(TextView textView) {
|
||||
|
||||
@@ -698,7 +698,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements LiveTvGui
|
||||
@Override
|
||||
public void onStop() {
|
||||
super.onStop();
|
||||
Timber.d("Stopping!");
|
||||
Timber.i("Stopping!");
|
||||
|
||||
if (leanbackOverlayFragment != null)
|
||||
leanbackOverlayFragment.setOnKeyInterceptListener(null);
|
||||
@@ -706,7 +706,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements LiveTvGui
|
||||
// end playback from here if this fragment belongs to the current session.
|
||||
// if it doesn't, playback has already been stopped elsewhere, and the references to this have been replaced
|
||||
if (playbackControllerContainer.getValue().getPlaybackController() != null && playbackControllerContainer.getValue().getPlaybackController().getFragment() == this) {
|
||||
Timber.d("this fragment belongs to the current session, ending it");
|
||||
Timber.i("this fragment belongs to the current session, ending it");
|
||||
playbackControllerContainer.getValue().getPlaybackController().endPlayback();
|
||||
}
|
||||
|
||||
|
||||
@@ -119,12 +119,16 @@ class ExternalPlayerActivity : FragmentActivity() {
|
||||
?.sortedWith(compareBy<MediaStream> { it.isDefault }.thenBy { it.index })
|
||||
.orEmpty()
|
||||
|
||||
val subtitleUrls = externalSubtitles.map {
|
||||
val subtitleUrls = externalSubtitles.map { mediaStream ->
|
||||
// We cannot use the DeliveryUrl as that is only populated when using the playback info API, which we skip as we'll always direct
|
||||
// play when using external players. We need to infer the subtitle format based on its path (similar to how the server
|
||||
// calculates it)
|
||||
val format = mediaStream.path?.substringAfterLast('.', missingDelimiterValue = mediaStream.codec.orEmpty()) ?: "srt"
|
||||
api.subtitleApi.getSubtitleUrl(
|
||||
routeItemId = item.id,
|
||||
routeMediaSourceId = mediaSource.id.toString(),
|
||||
routeIndex = it.index,
|
||||
routeFormat = it.codec.orEmpty(),
|
||||
routeIndex = mediaStream.index,
|
||||
routeFormat = format,
|
||||
)
|
||||
}.toTypedArray()
|
||||
val subtitleNames = externalSubtitles.map { it.displayTitle ?: it.title.orEmpty() }.toTypedArray()
|
||||
|
||||
@@ -240,7 +240,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
public void playerErrorEncountered() {
|
||||
// reset the retry count if it's been more than 30s since previous error
|
||||
if (playbackRetries > 0 && Instant.now().toEpochMilli() - lastPlaybackError > 30000) {
|
||||
Timber.d("playback stabilized - retry count reset to 0 from %s", playbackRetries);
|
||||
Timber.i("playback stabilized - retry count reset to 0 from %s", playbackRetries);
|
||||
playbackRetries = 0;
|
||||
}
|
||||
|
||||
@@ -270,7 +270,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
mDisplayModes = display.getSupportedModes();
|
||||
Timber.i("** Available display refresh rates:");
|
||||
for (Display.Mode mDisplayMode : mDisplayModes) {
|
||||
Timber.d("display mode %s - %dx%d@%f", mDisplayMode.getModeId(), mDisplayMode.getPhysicalWidth(), mDisplayMode.getPhysicalHeight(), mDisplayMode.getRefreshRate());
|
||||
Timber.i("display mode %s - %dx%d@%f", mDisplayMode.getModeId(), mDisplayMode.getPhysicalWidth(), mDisplayMode.getPhysicalHeight(), mDisplayMode.getRefreshRate());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -301,7 +301,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
if (rate != sourceRate && rate != sourceRate * 2 && rate != Math.round(sourceRate * 2.5)) // Skip inappropriate rates
|
||||
continue;
|
||||
|
||||
Timber.d("qualifying display mode: %s - %dx%d@%f", mode.getModeId(), mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
|
||||
Timber.i("qualifying display mode: %s - %dx%d@%f", mode.getModeId(), mode.getPhysicalWidth(), mode.getPhysicalHeight(), mode.getRefreshRate());
|
||||
|
||||
// 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
|
||||
@@ -435,7 +435,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
BaseItemDto item = getCurrentlyPlayingItem();
|
||||
|
||||
if (item == null) {
|
||||
Timber.d("item is null - aborting play");
|
||||
Timber.w("item is null - aborting play");
|
||||
Utils.showToast(mFragment.getContext(), mFragment.getString(R.string.msg_cannot_play));
|
||||
mFragment.closePlayer();
|
||||
return;
|
||||
@@ -607,13 +607,10 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
|
||||
private void startItem(BaseItemDto item, long position, StreamInfo response) {
|
||||
if (!hasInitializedVideoManager() || !hasFragment()) {
|
||||
Timber.d("Error - attempting to play without:%s%s", hasInitializedVideoManager() ? "" : " [videoManager]", hasFragment() ? "" : " [overlay fragment]");
|
||||
Timber.w("Error - attempting to play without:%s%s", hasInitializedVideoManager() ? "" : " [videoManager]", hasFragment() ? "" : " [overlay fragment]");
|
||||
return;
|
||||
}
|
||||
|
||||
// clear options on start of every item
|
||||
clearPlaybackSessionOptions();
|
||||
|
||||
mStartPosition = position;
|
||||
mCurrentStreamInfo = response;
|
||||
mCurrentOptions.setMediaSourceId(response.getMediaSource().getId());
|
||||
@@ -633,8 +630,8 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
// get subtitle info
|
||||
mCurrentOptions.setSubtitleStreamIndex(response.getMediaSource().getDefaultSubtitleStreamIndex() != null ? response.getMediaSource().getDefaultSubtitleStreamIndex() : null);
|
||||
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", mCurrentOptions.getSubtitleStreamIndex(), response.getMediaSource().getDefaultSubtitleStreamIndex());
|
||||
Timber.i("default audio index set to %s remote default %s", mDefaultAudioIndex, response.getMediaSource().getDefaultAudioStreamIndex());
|
||||
Timber.i("default sub index set to %s remote default %s", mCurrentOptions.getSubtitleStreamIndex(), response.getMediaSource().getDefaultSubtitleStreamIndex());
|
||||
|
||||
Long mbPos = position * 10000;
|
||||
|
||||
@@ -759,15 +756,15 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
if (!(isPlaying() || isPaused()) || index < 0)
|
||||
return;
|
||||
|
||||
BaseItemDto currentItem = getCurrentlyPlayingItem();
|
||||
if (currentItem == null
|
||||
|| currentItem.getMediaStreams() == null
|
||||
|| index >= currentItem.getMediaStreams().size()) {
|
||||
MediaSourceInfo currentMediaSource = getCurrentMediaSource();
|
||||
if (currentMediaSource == null
|
||||
|| currentMediaSource.getMediaStreams() == null
|
||||
|| index >= currentMediaSource.getMediaStreams().size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
String lastAudioIsoCode = videoQueueManager.getValue().getLastPlayedAudioLanguageIsoCode();
|
||||
String currentAudioIsoCode = currentItem.getMediaStreams().get(index).getLanguage();
|
||||
String currentAudioIsoCode = currentMediaSource.getMediaStreams().get(index).getLanguage();
|
||||
|
||||
if (currentAudioIsoCode != null
|
||||
&& (lastAudioIsoCode == null || !lastAudioIsoCode.equals(currentAudioIsoCode))) {
|
||||
@@ -777,11 +774,11 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
}
|
||||
|
||||
int currAudioIndex = getAudioStreamIndex();
|
||||
Timber.d("trying to switch audio stream from %s to %s", currAudioIndex, index);
|
||||
Timber.i("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);
|
||||
Timber.i("setting mCurrentOptions audio stream index from %s to %s", mCurrentOptions.getAudioStreamIndex(), index);
|
||||
mCurrentOptions.setAudioStreamIndex(index);
|
||||
}
|
||||
return;
|
||||
@@ -790,12 +787,12 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
// get current timestamp first
|
||||
refreshCurrentPosition();
|
||||
|
||||
if (!isTranscoding() && mVideoManager.setExoPlayerTrack(index, MediaStreamType.AUDIO, getCurrentlyPlayingItem().getMediaStreams())) {
|
||||
mCurrentOptions.setMediaSourceId(getCurrentMediaSource().getId());
|
||||
if (!isTranscoding() && mVideoManager.setExoPlayerTrack(index, MediaStreamType.AUDIO, currentMediaSource.getMediaStreams())) {
|
||||
mCurrentOptions.setMediaSourceId(currentMediaSource.getId());
|
||||
mCurrentOptions.setAudioStreamIndex(index);
|
||||
} else {
|
||||
startSpinner();
|
||||
mCurrentOptions.setMediaSourceId(getCurrentMediaSource().getId());
|
||||
mCurrentOptions.setMediaSourceId(currentMediaSource.getId());
|
||||
mCurrentOptions.setAudioStreamIndex(index);
|
||||
stop();
|
||||
playInternal(getCurrentlyPlayingItem(), mCurrentPosition, mCurrentOptions);
|
||||
@@ -804,7 +801,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
Timber.d("pause called at %s", mCurrentPosition);
|
||||
Timber.i("pause called at %s", mCurrentPosition);
|
||||
// if playback is paused and the seekbar is scrubbed, it will call pause even if already paused
|
||||
if (mPlaybackState == PlaybackState.PAUSED) {
|
||||
Timber.d("already paused, ignoring");
|
||||
@@ -838,7 +835,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
|
||||
public void stop() {
|
||||
refreshCurrentPosition();
|
||||
Timber.d("stop called at %s", mCurrentPosition);
|
||||
Timber.i("stop called at %s", mCurrentPosition);
|
||||
stopReportLoop();
|
||||
if (mPlaybackState != PlaybackState.IDLE && mPlaybackState != PlaybackState.UNDEFINED) {
|
||||
mPlaybackState = PlaybackState.IDLE;
|
||||
@@ -887,7 +884,6 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
wasSeeking = false;
|
||||
burningSubs = false;
|
||||
mCurrentStreamInfo = null;
|
||||
mCurrentOptions.setAudioStreamIndex(null);
|
||||
}
|
||||
|
||||
public void next() {
|
||||
@@ -897,7 +893,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
resetPlayerErrors();
|
||||
mCurrentIndex++;
|
||||
videoQueueManager.getValue().setCurrentMediaPosition(mCurrentIndex);
|
||||
Timber.d("Moving to index: %d out of %d total items.", mCurrentIndex, mItems.size());
|
||||
Timber.i("Moving to index: %d out of %d total items.", mCurrentIndex, mItems.size());
|
||||
spinnerOff = false;
|
||||
play(0);
|
||||
}
|
||||
@@ -910,7 +906,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
resetPlayerErrors();
|
||||
mCurrentIndex--;
|
||||
videoQueueManager.getValue().setCurrentMediaPosition(mCurrentIndex);
|
||||
Timber.d("Moving to index: %d out of %d total items.", mCurrentIndex, mItems.size());
|
||||
Timber.i("Moving to index: %d out of %d total items.", mCurrentIndex, mItems.size());
|
||||
spinnerOff = false;
|
||||
play(0);
|
||||
}
|
||||
@@ -933,7 +929,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
public void seek(long pos, boolean skipToNext) {
|
||||
if (pos <= 0) pos = 0;
|
||||
|
||||
Timber.d("Trying to seek from %s to %d", mCurrentPosition, pos);
|
||||
Timber.i("Trying to seek from %s to %d", mCurrentPosition, pos);
|
||||
Timber.d("Container: %s", mCurrentStreamInfo == null ? "unknown" : mCurrentStreamInfo.getContainer());
|
||||
|
||||
if (!hasInitializedVideoManager()) {
|
||||
@@ -1032,8 +1028,8 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
refreshCurrentPosition();
|
||||
currentSkipPos = Utils.getSafeSeekPosition((currentSkipPos == 0 ? mCurrentPosition : currentSkipPos) + msec, getDuration());
|
||||
|
||||
Timber.d("Skip amount requested was %s. Calculated position is %s", msec, currentSkipPos);
|
||||
Timber.d("Duration reported as: %s current pos: %s", getDuration(), mCurrentPosition);
|
||||
Timber.i("Skip amount requested was %s. Calculated position is %s", msec, currentSkipPos);
|
||||
Timber.i("Duration reported as: %s current pos: %s", getDuration(), mCurrentPosition);
|
||||
|
||||
mSeekPosition = currentSkipPos;
|
||||
mHandler.postDelayed(skipRunnable, 800);
|
||||
@@ -1158,7 +1154,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
return;
|
||||
}
|
||||
|
||||
Timber.d("Moving to next queue item. Index: %s", (mCurrentIndex + 1));
|
||||
Timber.i("Moving to next queue item. Index: %s", (mCurrentIndex + 1));
|
||||
boolean stillWatchingEnabled = userPreferences.getValue().get(UserPreferences.Companion.getStillWatchingBehavior()) != StillWatchingBehavior.DISABLED;
|
||||
boolean nextUpEnabled = userPreferences.getValue().get(UserPreferences.Companion.getNextUpBehavior()) != NextUpBehavior.DISABLED;
|
||||
if ((stillWatchingEnabled || nextUpEnabled) && curItem.getType() != BaseItemKind.TRAILER) {
|
||||
@@ -1247,7 +1243,7 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
|
||||
@Override
|
||||
public void onCompletion() {
|
||||
Timber.d("On Completion fired");
|
||||
Timber.i("On Completion fired");
|
||||
itemComplete();
|
||||
}
|
||||
|
||||
@@ -1274,6 +1270,8 @@ public class PlaybackController implements PlaybackControllerNotifiable {
|
||||
|
||||
if (hasInitializedVideoManager()) {
|
||||
duration = mVideoManager.getDuration();
|
||||
} else if (getCurrentMediaSource() != null && getCurrentMediaSource().getRunTimeTicks() != null) {
|
||||
duration = getCurrentMediaSource().getRunTimeTicks() / 10000;
|
||||
} else if (getCurrentlyPlayingItem() != null && getCurrentlyPlayingItem().getRunTimeTicks() != null) {
|
||||
duration = getCurrentlyPlayingItem().getRunTimeTicks() / 10000;
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ public class VideoManager {
|
||||
public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @NonNull Player.PositionInfo newPosition, int reason) {
|
||||
// discontinuity for reason internal usually indicates an error, and that the player will reset to its default timestamp
|
||||
if (reason == Player.DISCONTINUITY_REASON_INTERNAL) {
|
||||
Timber.d("Caught player discontinuity (reason internal) - oldPos: %s newPos: %s", oldPosition.positionMs, newPosition.positionMs);
|
||||
Timber.i("Caught player discontinuity (reason internal) - oldPos: %s newPos: %s", oldPosition.positionMs, newPosition.positionMs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -431,7 +431,7 @@ public class VideoManager {
|
||||
id = Integer.parseInt(trackFormat.id);
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
Timber.d("failed to parse track ID [%s]", trackFormat.id);
|
||||
Timber.w("failed to parse track ID [%s]", trackFormat.id);
|
||||
break;
|
||||
}
|
||||
matchedIndex = id;
|
||||
@@ -489,7 +489,7 @@ public class VideoManager {
|
||||
boolean isSelected = groupInfo.isTrackSelected(i);
|
||||
Format trackFormat = group.getFormat(i);
|
||||
|
||||
Timber.d("track %s group %s/%s trackType %s label %s mime %s isSelected %s isSupported %s",
|
||||
Timber.i("track %s group %s/%s trackType %s label %s mime %s isSelected %s isSupported %s",
|
||||
trackFormat.id, i + 1, group.length, trackType, trackFormat.label, trackFormat.sampleMimeType, isSelected, isSupported);
|
||||
|
||||
if (trackType != chosenTrackType || trackFormat.id == null)
|
||||
@@ -505,7 +505,7 @@ public class VideoManager {
|
||||
if (id != exoTrackID)
|
||||
continue;
|
||||
} catch (NumberFormatException e) {
|
||||
Timber.d("failed to parse track ID [%s]", trackFormat.id);
|
||||
Timber.w("failed to parse track ID [%s]", trackFormat.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -519,7 +519,7 @@ public class VideoManager {
|
||||
return true;
|
||||
}
|
||||
|
||||
Timber.d("matched exoplayer track %s to mediaStream track %s", trackFormat.id, index);
|
||||
Timber.i("matched exoplayer track %s to mediaStream track %s", trackFormat.id, index);
|
||||
matchedGroup = group;
|
||||
}
|
||||
}
|
||||
@@ -532,7 +532,7 @@ public class VideoManager {
|
||||
mExoPlayerSelectionParams.setOverrideForType(new TrackSelectionOverride(matchedGroup, 0));
|
||||
mExoPlayer.setTrackSelectionParameters(mExoPlayerSelectionParams.build());
|
||||
} catch (Exception e) {
|
||||
Timber.d("Error setting track selection");
|
||||
Timber.w("Error setting track selection");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -65,8 +65,11 @@ public class VideoPlayerAdapter extends PlayerAdapter {
|
||||
|
||||
@Override
|
||||
public long getDuration() {
|
||||
return getCurrentlyPlayingItem() != null && getCurrentlyPlayingItem().getRunTimeTicks() != null ?
|
||||
getCurrentlyPlayingItem().getRunTimeTicks() / 10000 : -1;
|
||||
Long runTimeTicks = null;
|
||||
if (getCurrentMediaSource() != null) runTimeTicks = getCurrentMediaSource().getRunTimeTicks();
|
||||
if (runTimeTicks == null && getCurrentlyPlayingItem() != null) runTimeTicks = getCurrentlyPlayingItem().getRunTimeTicks();
|
||||
if (runTimeTicks != null) return runTimeTicks / 10000;
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -99,7 +99,7 @@ class RewriteMediaManager(
|
||||
launch {
|
||||
while (true) {
|
||||
notifyListeners {
|
||||
onProgress(playbackManager.state.positionInfo.active.inWholeMilliseconds)
|
||||
onProgress(playbackManager.state.positionInfo.active.inWholeMilliseconds, playbackManager.state.positionInfo.duration.inWholeMilliseconds)
|
||||
}
|
||||
delay(@Suppress("MagicNumber") 100)
|
||||
}
|
||||
|
||||
@@ -391,10 +391,17 @@ public class CardPresenter extends Presenter {
|
||||
int fillWidth = Math.round(holder.getCardWidth() * holder.mCardView.getResources().getDisplayMetrics().density);
|
||||
int fillHeight = Math.round(holder.getCardHeight() * holder.mCardView.getResources().getDisplayMetrics().density);
|
||||
|
||||
holder.updateCardViewImage(
|
||||
image == null ? rowItem.getImageUrl(holder.mCardView.getContext(), imageHelper.getValue(), mImageType, fillWidth, fillHeight) : imageHelper.getValue().getImageUrl(image),
|
||||
image == null ? null : image.getBlurHash()
|
||||
);
|
||||
final String imageUrl;
|
||||
final String blurHash;
|
||||
if (image == null) {
|
||||
imageUrl = rowItem.getImageUrl(holder.mCardView.getContext(), imageHelper.getValue(), mImageType, fillWidth, fillHeight);
|
||||
blurHash = null;
|
||||
} else {
|
||||
imageUrl = imageHelper.getValue().getImageUrl(image, fillWidth, fillHeight);
|
||||
blurHash = image.getBlurHash();
|
||||
}
|
||||
|
||||
holder.updateCardViewImage(imageUrl, blurHash);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -13,6 +13,7 @@ import org.jellyfin.androidtv.util.apiclient.itemImages
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class UserViewCardPresenter(
|
||||
val small: Boolean,
|
||||
@@ -25,10 +26,24 @@ class UserViewCardPresenter(
|
||||
fun setItem(rowItem: BaseRowItem?) {
|
||||
val baseItem = rowItem?.baseItem
|
||||
|
||||
// Determine size
|
||||
val cardWidth: Int
|
||||
val cardHeight: Int
|
||||
if (small) {
|
||||
cardWidth = 133
|
||||
cardHeight = 75
|
||||
} else {
|
||||
cardWidth = 224
|
||||
cardHeight = 126
|
||||
}
|
||||
|
||||
val fillWidth = (cardWidth * cardView.resources.displayMetrics.density).roundToInt()
|
||||
val fillHeight = (cardHeight * cardView.resources.displayMetrics.density).roundToInt()
|
||||
|
||||
// Load image
|
||||
val image = baseItem?.itemImages[ImageType.PRIMARY]
|
||||
cardView.mainImageView.load(
|
||||
url = image?.let(imageHelper::getImageUrl),
|
||||
url = image?.let { imageHelper.getImageUrl(it, fillWidth, fillHeight) },
|
||||
blurHash = image?.blurHash,
|
||||
placeholder = ContextCompat.getDrawable(cardView.context, R.drawable.tile_land_folder),
|
||||
aspectRatio = ImageHelper.ASPECT_RATIO_16_9,
|
||||
@@ -39,11 +54,7 @@ class UserViewCardPresenter(
|
||||
cardView.setTitleText(rowItem?.getName(cardView.context))
|
||||
|
||||
// Set size
|
||||
if (small) {
|
||||
cardView.setMainImageDimensions(133, 75)
|
||||
} else {
|
||||
cardView.setMainImageDimensions(224, 126)
|
||||
}
|
||||
cardView.setMainImageDimensions(cardWidth, cardHeight)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ fun SearchTextInput(
|
||||
BasicTextField(
|
||||
modifier = modifier,
|
||||
value = query,
|
||||
singleLine = true,
|
||||
interactionSource = interactionSource,
|
||||
onValueChange = { onQueryChange(it) },
|
||||
keyboardActions = KeyboardActions { onQuerySubmit() },
|
||||
|
||||
@@ -136,7 +136,7 @@ class StartupActivity : FragmentActivity() {
|
||||
}?.toUUIDOrNull()
|
||||
val itemIsUserView = intent.getBooleanExtra(EXTRA_ITEM_IS_USER_VIEW, false)
|
||||
|
||||
Timber.d("Determining next activity (action=${intent.action}, itemId=$itemId, itemIsUserView=$itemIsUserView)")
|
||||
Timber.i("Determining next activity (action=${intent.action}, itemId=$itemId, itemIsUserView=$itemIsUserView)")
|
||||
|
||||
// Start session
|
||||
(application as? JellyfinApplication)?.onSessionStart()
|
||||
@@ -167,7 +167,7 @@ class StartupActivity : FragmentActivity() {
|
||||
val intent = Intent(this, MainActivity::class.java)
|
||||
// Clear navigation history
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_TASK_ON_HOME)
|
||||
Timber.d("Opening next activity $intent")
|
||||
Timber.i("Opening next activity $intent")
|
||||
startActivity(intent)
|
||||
finishAfterTransition()
|
||||
}
|
||||
|
||||
@@ -31,7 +31,8 @@ class ImageHelper(
|
||||
const val MAX_PRIMARY_IMAGE_HEIGHT: Int = 370
|
||||
}
|
||||
|
||||
fun getImageUrl(image: JellyfinImage): String = image.getUrl(api)
|
||||
fun getImageUrl(image: JellyfinImage, fillWidth: Int, fillHeight: Int): String =
|
||||
image.getUrl(api, null, null, fillWidth, fillHeight)
|
||||
|
||||
fun getImageAspectRatio(item: BaseItemDto, preferParentThumb: Boolean): Double {
|
||||
if (preferParentThumb && (item.parentThumbItemId != null || item.seriesThumbImageTag != null)) {
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.media.MediaCodecList
|
||||
import android.media.MediaFormat
|
||||
import android.os.Build
|
||||
import android.util.Size
|
||||
import android.view.Display
|
||||
import androidx.core.content.ContextCompat
|
||||
import timber.log.Timber
|
||||
|
||||
@@ -16,13 +15,6 @@ class MediaCodecCapabilitiesTest(
|
||||
private val display by lazy { ContextCompat.getDisplayOrDefault(context) }
|
||||
private val mediaCodecList by lazy { MediaCodecList(MediaCodecList.REGULAR_CODECS) }
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
private val supportedHdrTypes by lazy {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) display.mode.supportedHdrTypes.toList()
|
||||
else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) display.hdrCapabilities.supportedHdrTypes.toList()
|
||||
else emptyList()
|
||||
}
|
||||
|
||||
// Map common Dolby Vision Profiles to their corresponding CodecProfileLevel constant
|
||||
private object DolbyVisionProfiles {
|
||||
val Profile5: Int by lazy {
|
||||
@@ -294,16 +286,4 @@ class MediaCodecCapabilitiesTest(
|
||||
|
||||
return Size(maxWidth, maxHeight)
|
||||
}
|
||||
|
||||
fun supportsDolbyVision(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && supportedHdrTypes.contains(Display.HdrCapabilities.HDR_TYPE_DOLBY_VISION)
|
||||
}
|
||||
|
||||
fun supportsHdr10(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && supportedHdrTypes.contains(Display.HdrCapabilities.HDR_TYPE_HDR10)
|
||||
}
|
||||
|
||||
fun supportsHdr10Plus(): Boolean {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && supportedHdrTypes.contains(Display.HdrCapabilities.HDR_TYPE_HDR10_PLUS)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,10 +98,6 @@ fun createDeviceProfile(
|
||||
val maxResolutionAV1 = mediaTest.getMaxResolution(MimeTypes.VIDEO_AV1)
|
||||
|
||||
/// HDR capabilities
|
||||
// Display
|
||||
val supportsDolbyVisionDisplay = mediaTest.supportsDolbyVision()
|
||||
val supportsHdr10Display = mediaTest.supportsHdr10()
|
||||
val supportsHdr10PlusDisplay = mediaTest.supportsHdr10Plus()
|
||||
|
||||
// Codecs
|
||||
// AV1
|
||||
@@ -366,32 +362,8 @@ fun createDeviceProfile(
|
||||
/// HDR exclude list
|
||||
|
||||
// TODO Use VideoRangeType enum with Jellyfin 10.11 based SDK
|
||||
val unsupportedRangeTypes = buildSet {
|
||||
if (jellyfinTenEleven) add("DOVIInvalid")
|
||||
|
||||
if (!supportsDolbyVisionDisplay) {
|
||||
add(VideoRangeType.DOVI.serialName)
|
||||
|
||||
if (jellyfinTenEleven) {
|
||||
add("DOVIWithEL")
|
||||
if (!supportsHdr10PlusDisplay) {
|
||||
add("DOVIWithHDR10Plus")
|
||||
add("DOVIWithELHDR10Plus")
|
||||
}
|
||||
}
|
||||
|
||||
if (!supportsHdr10Display) add(VideoRangeType.DOVI_WITH_HDR10.serialName)
|
||||
}
|
||||
|
||||
if (!supportsHdr10PlusDisplay) {
|
||||
add(VideoRangeType.HDR10_PLUS.serialName)
|
||||
if (!supportsHdr10Display) add(VideoRangeType.HDR10.serialName)
|
||||
}
|
||||
}
|
||||
|
||||
val unsupportedRangeTypesAv1 = buildSet {
|
||||
// Base of unsupported types for display
|
||||
addAll(unsupportedRangeTypes)
|
||||
if (jellyfinTenEleven) add("DOVIInvalid")
|
||||
|
||||
if (!supportsAV1DolbyVision) {
|
||||
add(VideoRangeType.DOVI.serialName)
|
||||
@@ -408,8 +380,7 @@ fun createDeviceProfile(
|
||||
|
||||
// TODO Use VideoRangeType enum with Jellyfin 10.11 based SDK
|
||||
val unsupportedRangeTypesHevc = buildSet {
|
||||
// Base of unsupported types for display
|
||||
addAll(unsupportedRangeTypes)
|
||||
if (jellyfinTenEleven) add("DOVIInvalid")
|
||||
|
||||
if (!supportsHevcDolbyVisionEL) {
|
||||
if (jellyfinTenEleven) {
|
||||
@@ -440,21 +411,10 @@ fun createDeviceProfile(
|
||||
// The notEquals condition will always fail the ConditionProcessor test in the server so we use applyConditions to only have the codec
|
||||
// profile be active when the media in question uses one of the unsupported range types. The server will then use the value of the
|
||||
// notEquals in the StreamBuilder to create a correct transcode pipeline
|
||||
if (unsupportedRangeTypes.isNotEmpty()) codecProfile {
|
||||
type = CodecType.VIDEO
|
||||
|
||||
conditions {
|
||||
ProfileConditionValue.VIDEO_RANGE_TYPE notEquals unsupportedRangeTypes.joinToString("|")
|
||||
}
|
||||
|
||||
applyConditions {
|
||||
ProfileConditionValue.VIDEO_RANGE_TYPE inCollection unsupportedRangeTypes
|
||||
}
|
||||
}
|
||||
|
||||
// Codecs
|
||||
// AV1
|
||||
if (unsupportedRangeTypesAv1.isNotEmpty() && unsupportedRangeTypesAv1 != unsupportedRangeTypes) codecProfile {
|
||||
if (unsupportedRangeTypesAv1.isNotEmpty()) codecProfile {
|
||||
type = CodecType.VIDEO
|
||||
codec = Codec.Video.AV1
|
||||
|
||||
@@ -468,7 +428,7 @@ fun createDeviceProfile(
|
||||
}
|
||||
|
||||
// HEVC
|
||||
if (unsupportedRangeTypesHevc.isNotEmpty() && unsupportedRangeTypesHevc != unsupportedRangeTypes) codecProfile {
|
||||
if (unsupportedRangeTypesHevc.isNotEmpty()) codecProfile {
|
||||
type = CodecType.VIDEO
|
||||
codec = Codec.Video.HEVC
|
||||
|
||||
|
||||
@@ -117,7 +117,8 @@ fun createDeviceProfileReport(
|
||||
appendLine(" - inputChannelCountRanges: ${it.joinToString(", ") { it.prettyFormat() }}")
|
||||
}
|
||||
audio.bitrateRange?.let { appendLine(" - bitrateRange: ${it.prettyFormat()}") }
|
||||
audio.supportedSampleRates?.takeIf { it.isNotEmpty() }?.let {
|
||||
// Note: Fire OS has a bug in the getter for supportedSampleRates that throws NullPointerException
|
||||
runCatching { audio.supportedSampleRates }.getOrNull()?.takeIf { it.isNotEmpty() }?.let {
|
||||
appendLine(" - supportedSampleRates: ${it.joinToString(", ")}")
|
||||
}
|
||||
audio.supportedSampleRateRanges?.takeIf { it.isNotEmpty() }?.let {
|
||||
@@ -183,20 +184,24 @@ fun createDeviceProfileReport(
|
||||
val mediaTest = MediaCodecCapabilitiesTest(context)
|
||||
|
||||
val codecHDRSupport = buildMap<String, Map<HdrFormats, Boolean>> {
|
||||
if(mediaTest.supportsAV1()) {
|
||||
put(Codec.Video.AV1, mapOf(
|
||||
HdrFormats.DOLBY_VISION to mediaTest.supportsAV1DolbyVision(),
|
||||
HdrFormats.HDR10 to mediaTest.supportsAV1HDR10(),
|
||||
HdrFormats.HDR10_PLUS to mediaTest.supportsAV1HDR10Plus()
|
||||
))
|
||||
if (mediaTest.supportsAV1()) {
|
||||
put(
|
||||
Codec.Video.AV1, mapOf(
|
||||
HdrFormats.DOLBY_VISION to mediaTest.supportsAV1DolbyVision(),
|
||||
HdrFormats.HDR10 to mediaTest.supportsAV1HDR10(),
|
||||
HdrFormats.HDR10_PLUS to mediaTest.supportsAV1HDR10Plus()
|
||||
)
|
||||
)
|
||||
}
|
||||
if(mediaTest.supportsHevc()) {
|
||||
put(Codec.Video.HEVC, mapOf(
|
||||
HdrFormats.DOLBY_VISION to mediaTest.supportsHevcDolbyVision(),
|
||||
HdrFormats.DOLBY_VISION_EL to mediaTest.supportsHevcDolbyVisionEL(),
|
||||
HdrFormats.HDR10 to mediaTest.supportsHevcHDR10(),
|
||||
HdrFormats.HDR10_PLUS to mediaTest.supportsHevcHDR10Plus()
|
||||
))
|
||||
if (mediaTest.supportsHevc()) {
|
||||
put(
|
||||
Codec.Video.HEVC, mapOf(
|
||||
HdrFormats.DOLBY_VISION to mediaTest.supportsHevcDolbyVision(),
|
||||
HdrFormats.DOLBY_VISION_EL to mediaTest.supportsHevcDolbyVisionEL(),
|
||||
HdrFormats.HDR10 to mediaTest.supportsHevcHDR10(),
|
||||
HdrFormats.HDR10_PLUS to mediaTest.supportsHevcHDR10Plus()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.api.client.extensions.videosApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import org.jellyfin.sdk.model.api.ItemFilter
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
@@ -151,8 +152,11 @@ class SdkPlaybackHelper(
|
||||
val response by api.itemsApi.getItems(
|
||||
isMissing = false,
|
||||
mediaTypes = listOf(MediaType.AUDIO),
|
||||
filters = listOf(ItemFilter.IS_NOT_FOLDER),
|
||||
sortBy = if (shuffle) listOf(ItemSortBy.RANDOM) else listOf(
|
||||
ItemSortBy.ALBUM_ARTIST,
|
||||
ItemSortBy.ALBUM,
|
||||
ItemSortBy.PARENT_INDEX_NUMBER,
|
||||
ItemSortBy.INDEX_NUMBER,
|
||||
ItemSortBy.SORT_NAME
|
||||
),
|
||||
recursive = true,
|
||||
@@ -168,7 +172,13 @@ class SdkPlaybackHelper(
|
||||
val response by api.itemsApi.getItems(
|
||||
isMissing = false,
|
||||
mediaTypes = listOf(MediaType.AUDIO),
|
||||
sortBy = if (shuffle) listOf(ItemSortBy.RANDOM) else listOf(ItemSortBy.SORT_NAME),
|
||||
filters = listOf(ItemFilter.IS_NOT_FOLDER),
|
||||
sortBy = if (shuffle) listOf(ItemSortBy.RANDOM) else listOf(
|
||||
ItemSortBy.ALBUM,
|
||||
ItemSortBy.PARENT_INDEX_NUMBER,
|
||||
ItemSortBy.INDEX_NUMBER,
|
||||
ItemSortBy.SORT_NAME
|
||||
),
|
||||
recursive = true,
|
||||
limit = ITEM_QUERY_LIMIT,
|
||||
fields = ItemRepository.itemFields,
|
||||
|
||||
@@ -4,7 +4,7 @@ accompanist = "0.37.3"
|
||||
acra = "5.13.1"
|
||||
android-compileSdk = "36"
|
||||
android-desugar = "2.1.5"
|
||||
android-gradle = "8.13.0"
|
||||
android-gradle = "8.11.1"
|
||||
android-minSdk = "21"
|
||||
android-targetSdk = "36"
|
||||
androidx-activity = "1.11.0"
|
||||
|
||||
Reference in New Issue
Block a user