Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e8e8bc232 | ||
|
|
ec5c751390 | ||
|
|
46b0b7aade | ||
|
|
3877739320 | ||
|
|
538eeb4f22 | ||
|
|
0d724c532a | ||
|
|
eba14e0bf4 | ||
|
|
a8c35b0ea0 | ||
|
|
17744729b2 | ||
|
|
fd5442097e | ||
|
|
f9eeae6bf0 | ||
|
|
c2e33b9b13 | ||
|
|
9c4b121574 | ||
|
|
ddb1be18bc | ||
|
|
4a4c1ed6ca | ||
|
|
c28759e165 | ||
|
|
45a5adbc0b | ||
|
|
118eacfa90 | ||
|
|
91ccf32f77 | ||
|
|
065e738773 | ||
|
|
65a7bbf501 | ||
|
|
d661c2faf3 | ||
|
|
8175dbd1c0 | ||
|
|
f068be6945 | ||
|
|
fe465db020 | ||
|
|
d6fe265b38 | ||
|
|
8eecfab5d7 | ||
|
|
0f9ffcacb9 | ||
|
|
aa7c45d462 | ||
|
|
156f5ecd3c | ||
|
|
e56fd0d024 | ||
|
|
06e245fe48 | ||
|
|
9f0fa644a7 | ||
|
|
5b213199dd | ||
|
|
b4f6273ede | ||
|
|
66c624e16c | ||
|
|
e47916f1c2 | ||
|
|
ab7875cbb3 | ||
|
|
5f15615a96 | ||
|
|
9cda86ab84 | ||
|
|
1873497fec |
30
.github/workflows/app-publish.yaml
vendored
@@ -27,6 +27,8 @@ jobs:
|
||||
- name: Sign APK
|
||||
id: signApk
|
||||
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # tag=v1
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "34.0.0"
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/apk/release
|
||||
signingKeyBase64: ${{ secrets.KEYSTORE }}
|
||||
@@ -36,6 +38,8 @@ jobs:
|
||||
- name: Sign app bundle
|
||||
id: signAab
|
||||
uses: r0adkll/sign-android-release@349ebdef58775b1e0d8099458af0816dc79b6407 # tag=v1
|
||||
env:
|
||||
BUILD_TOOLS_VERSION: "34.0.0"
|
||||
with:
|
||||
releaseDirectory: app/build/outputs/bundle/release
|
||||
signingKeyBase64: ${{ secrets.KEYSTORE }}
|
||||
@@ -61,20 +65,24 @@ jobs:
|
||||
with:
|
||||
switches: -vrptz
|
||||
path: build/jellyfin-publish/
|
||||
remote_path: /srv/repository/releases/client/androidtv/versions/v${{ env.JELLYFIN_VERSION }}
|
||||
remote_host: ${{ secrets.DEPLOY_HOST }}
|
||||
remote_user: ${{ secrets.DEPLOY_USER }}
|
||||
remote_key: ${{ secrets.DEPLOY_KEY }}
|
||||
remote_path: /srv/incoming/androidtv/v${{ env.JELLYFIN_VERSION }}/
|
||||
remote_host: ${{ secrets.REPO_HOST }}
|
||||
remote_user: ${{ secrets.REPO_USER }}
|
||||
remote_key: ${{ secrets.REPO_KEY }}
|
||||
- name: Update repo.jellyfin.org symlinks
|
||||
uses: appleboy/ssh-action@55dabf81b49d4120609345970c91507e2d734799 # v1.0.0
|
||||
with:
|
||||
host: ${{ secrets.DEPLOY_HOST }}
|
||||
username: ${{ secrets.DEPLOY_USER }}
|
||||
key: ${{ secrets.DEPLOY_KEY }}
|
||||
host: ${{ secrets.REPO_HOST }}
|
||||
username: ${{ secrets.REPO_USER }}
|
||||
key: ${{ secrets.REPO_KEY }}
|
||||
envs: JELLYFIN_VERSION
|
||||
script_stop: true
|
||||
script: |
|
||||
cd /srv/repository/releases/client/androidtv;
|
||||
rm -rf *.apk version.txt;
|
||||
ln -s versions/v${JELLYFIN_VERSION}/jellyfin-androidtv-v${JELLYFIN_VERSION}-*.apk .;
|
||||
ln -s versions/v${JELLYFIN_VERSION}/version.txt .;
|
||||
if [ -d "/srv/repository/main/client/androidtv/versions/v${{ env.JELLYFIN_VERSION }}" ] && [ -n "${{ env.JELLYFIN_VERSION }}" ]; then
|
||||
sudo rm -r /srv/repository/main/client/androidtv/versions/v${{ env.JELLYFIN_VERSION }}
|
||||
fi
|
||||
sudo mv /srv/incoming/androidtv/v${{ env.JELLYFIN_VERSION }} /srv/repository/main/client/androidtv/versions/v${{ env.JELLYFIN_VERSION }}
|
||||
cd /srv/repository/main/client/androidtv;
|
||||
sudo rm -rf *.apk version.txt;
|
||||
sudo ln -s versions/v${JELLYFIN_VERSION}/jellyfin-androidtv-v${JELLYFIN_VERSION}-*.apk .;
|
||||
sudo ln -s versions/v${JELLYFIN_VERSION}/version.txt .;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
- [GodTamIt](https://github.com/GodTamIt)
|
||||
- [sparky3387](https://github.com/sparky3387)
|
||||
- [mohd-akram](https://github.com/mohd-akram)
|
||||
- [3l0w](https://github.com/3l0w)
|
||||
|
||||
# Emby Contributors
|
||||
|
||||
|
||||
@@ -139,8 +139,10 @@ dependencies {
|
||||
implementation(libs.bundles.koin)
|
||||
|
||||
// Media players
|
||||
implementation(libs.exoplayer)
|
||||
implementation(libs.jellyfin.exoplayer.ffmpegextension)
|
||||
implementation(libs.androidx.media3.exoplayer)
|
||||
implementation(libs.androidx.media3.exoplayer.hls)
|
||||
implementation(libs.androidx.media3.ui)
|
||||
implementation(libs.jellyfin.androidx.media3.ffmpeg.decoder)
|
||||
implementation(libs.libvlc)
|
||||
|
||||
// Markdown
|
||||
|
||||
@@ -28,17 +28,25 @@
|
||||
android:name="android.hardware.microphone"
|
||||
android:required="false" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<data
|
||||
android:host="youtube.com"
|
||||
android:scheme="https" />
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name=".JellyfinApplication"
|
||||
android:allowBackup="true"
|
||||
android:appCategory="video"
|
||||
android:banner="@drawable/app_banner"
|
||||
android:banner="@mipmap/app_banner"
|
||||
android:dataExtractionRules="@xml/backup_rules"
|
||||
android:fullBackupContent="@xml/backup_content"
|
||||
android:icon="@drawable/app_icon"
|
||||
android:icon="@mipmap/app_icon"
|
||||
android:label="@string/app_name"
|
||||
android:largeHeap="true"
|
||||
android:roundIcon="@drawable/app_icon_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.Jellyfin"
|
||||
android:usesCleartextTraffic="true">
|
||||
|
||||
@@ -14,6 +14,7 @@ import org.jellyfin.androidtv.ui.navigation.Destinations
|
||||
import org.jellyfin.androidtv.ui.navigation.NavigationRepository
|
||||
import org.jellyfin.androidtv.ui.playback.MediaManager
|
||||
import org.jellyfin.androidtv.ui.playback.PlaybackControllerContainer
|
||||
import org.jellyfin.androidtv.ui.playback.rewrite.RewriteMediaManager
|
||||
import org.jellyfin.androidtv.util.apiclient.PlaybackHelper
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.exception.ApiClientException
|
||||
@@ -126,47 +127,65 @@ class SocketHandler(
|
||||
private fun onPlayMessage(message: PlayMessage) {
|
||||
val itemId = message.request.itemIds?.firstOrNull() ?: return
|
||||
|
||||
PlaybackHelper.retrieveAndPlay(
|
||||
itemId.toString(),
|
||||
false,
|
||||
message.request.startPositionTicks,
|
||||
context
|
||||
)
|
||||
runCatching {
|
||||
PlaybackHelper.retrieveAndPlay(
|
||||
itemId.toString(),
|
||||
false,
|
||||
message.request.startPositionTicks,
|
||||
context
|
||||
)
|
||||
}.onFailure { Timber.w(it, "Failed to start playback") }
|
||||
}
|
||||
|
||||
@Suppress("ComplexMethod")
|
||||
private fun onPlayStateMessage(message: PlayStateMessage) = coroutineScope.launch(Dispatchers.Main) {
|
||||
Timber.i("Received PlayStateMessage with command ${message.request.command}")
|
||||
val playbackController = playbackControllerContainer.playbackController
|
||||
// Audio playback uses the mediaManager, video playback and live tv use the playbackController
|
||||
if (mediaManager.isAudioPlayerInitialized) when (message.request.command) {
|
||||
PlaystateCommand.STOP -> mediaManager.stopAudio(true)
|
||||
PlaystateCommand.PAUSE, PlaystateCommand.UNPAUSE, PlaystateCommand.PLAY_PAUSE -> mediaManager.playPauseAudio()
|
||||
PlaystateCommand.NEXT_TRACK -> mediaManager.nextAudioItem()
|
||||
PlaystateCommand.PREVIOUS_TRACK -> mediaManager.prevAudioItem()
|
||||
// Not implemented
|
||||
PlaystateCommand.SEEK,
|
||||
PlaystateCommand.REWIND,
|
||||
PlaystateCommand.FAST_FORWARD -> Unit
|
||||
} else when (message.request.command) {
|
||||
PlaystateCommand.STOP -> playbackController?.endPlayback(true)
|
||||
PlaystateCommand.PAUSE, PlaystateCommand.UNPAUSE, PlaystateCommand.PLAY_PAUSE -> playbackController?.playPause()
|
||||
PlaystateCommand.NEXT_TRACK -> playbackController?.next()
|
||||
PlaystateCommand.PREVIOUS_TRACK -> playbackController?.prev()
|
||||
PlaystateCommand.SEEK -> playbackController?.seek(
|
||||
(message.request.seekPositionTicks ?: 0) / TICKS_TO_MS
|
||||
)
|
||||
PlaystateCommand.REWIND -> playbackController?.rewind()
|
||||
PlaystateCommand.FAST_FORWARD -> playbackController?.fastForward()
|
||||
|
||||
// Audio playback uses (Rewrite)MediaManager, (legacy) video playback uses playbackController
|
||||
when {
|
||||
// Ignore RewriteMediaManager
|
||||
mediaManager is RewriteMediaManager && mediaManager.hasAudioQueueItems() -> {
|
||||
Timber.i("Ignoring PlayStateMessage: should be handled by PlaySessionSocketService")
|
||||
return@launch
|
||||
}
|
||||
|
||||
// LegacyMediaManager
|
||||
mediaManager.hasAudioQueueItems() -> when (message.request.command) {
|
||||
PlaystateCommand.STOP -> mediaManager.stopAudio(true)
|
||||
PlaystateCommand.PAUSE, PlaystateCommand.UNPAUSE, PlaystateCommand.PLAY_PAUSE -> mediaManager.playPauseAudio()
|
||||
PlaystateCommand.NEXT_TRACK -> mediaManager.nextAudioItem()
|
||||
PlaystateCommand.PREVIOUS_TRACK -> mediaManager.prevAudioItem()
|
||||
// Not implemented
|
||||
PlaystateCommand.SEEK,
|
||||
PlaystateCommand.REWIND,
|
||||
PlaystateCommand.FAST_FORWARD -> Unit
|
||||
}
|
||||
|
||||
// PlaybackController
|
||||
else -> {
|
||||
val playbackController = playbackControllerContainer.playbackController
|
||||
when (message.request.command) {
|
||||
PlaystateCommand.STOP -> playbackController?.endPlayback(true)
|
||||
PlaystateCommand.PAUSE, PlaystateCommand.UNPAUSE, PlaystateCommand.PLAY_PAUSE -> playbackController?.playPause()
|
||||
PlaystateCommand.NEXT_TRACK -> playbackController?.next()
|
||||
PlaystateCommand.PREVIOUS_TRACK -> playbackController?.prev()
|
||||
PlaystateCommand.SEEK -> playbackController?.seek(
|
||||
(message.request.seekPositionTicks ?: 0) / TICKS_TO_MS
|
||||
)
|
||||
|
||||
PlaystateCommand.REWIND -> playbackController?.rewind()
|
||||
PlaystateCommand.FAST_FORWARD -> playbackController?.fastForward()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onDisplayContent(itemId: UUID, itemKind: BaseItemKind) {
|
||||
private fun onDisplayContent(itemId: UUID, itemKind: BaseItemKind) = coroutineScope.launch(Dispatchers.Main) {
|
||||
val playbackController = playbackControllerContainer.playbackController
|
||||
|
||||
if (playbackController?.isPlaying == true || playbackController?.isPaused == true) {
|
||||
Timber.i("Not launching $itemId: playback in progress")
|
||||
return
|
||||
return@launch
|
||||
}
|
||||
|
||||
Timber.i("Launching $itemId")
|
||||
|
||||
@@ -47,7 +47,9 @@ class BackgroundService(
|
||||
private var _backgrounds = emptyList<ImageBitmap>()
|
||||
private var _currentIndex = 0
|
||||
private var _currentBackground = MutableStateFlow<ImageBitmap?>(null)
|
||||
private var _enabled = MutableStateFlow(true)
|
||||
val currentBackground get() = _currentBackground.asStateFlow()
|
||||
val enabled get() = _enabled.asStateFlow()
|
||||
|
||||
// Helper function for [setBackground]
|
||||
private fun List<String>?.getUrls(itemId: UUID?): List<String> {
|
||||
@@ -104,6 +106,9 @@ class BackgroundService(
|
||||
private fun loadBackgrounds(backdropUrls: Set<String>) {
|
||||
if (backdropUrls.isEmpty()) return clearBackgrounds()
|
||||
|
||||
// Re-enable backgrounds if disabled
|
||||
_enabled.value = true
|
||||
|
||||
// Cancel current loading job
|
||||
loadBackgroundsJob?.cancel()
|
||||
loadBackgroundsJob = scope.launch(Dispatchers.IO) {
|
||||
@@ -122,12 +127,22 @@ class BackgroundService(
|
||||
fun clearBackgrounds() {
|
||||
loadBackgroundsJob?.cancel()
|
||||
|
||||
// Re-enable backgrounds if disabled
|
||||
_enabled.value = true
|
||||
|
||||
if (_backgrounds.isEmpty()) return
|
||||
|
||||
_backgrounds = emptyList()
|
||||
update()
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable the showing of backgrounds until any function manipulating the backgrounds is called.
|
||||
*/
|
||||
fun disable() {
|
||||
_enabled.value = false
|
||||
}
|
||||
|
||||
internal fun update() {
|
||||
val now = System.currentTimeMillis()
|
||||
if (lastBackgroundTimerUpdate > now - TRANSITION_DURATION.inWholeMilliseconds)
|
||||
|
||||
@@ -35,7 +35,7 @@ val playbackModule = module {
|
||||
|
||||
factory {
|
||||
val preferences = get<UserPreferences>()
|
||||
val useRewrite = preferences[UserPreferences.playbackRewriteAudioEnabled] && BuildConfig.DEVELOPMENT
|
||||
val useRewrite = preferences[UserPreferences.playbackRewriteAudioEnabled]
|
||||
|
||||
if (useRewrite) get<RewriteMediaManager>()
|
||||
else get<LegacyMediaManager>()
|
||||
|
||||
@@ -198,7 +198,7 @@ class LeanbackChannelWorker(
|
||||
}
|
||||
|
||||
// Update logo
|
||||
ResourcesCompat.getDrawable(context.resources, R.drawable.app_icon, context.theme)?.let {
|
||||
ResourcesCompat.getDrawable(context.resources, R.mipmap.app_icon, context.theme)?.let {
|
||||
ChannelLogoUtils.storeChannelLogo(
|
||||
context,
|
||||
ContentUris.parseId(uri),
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.jellyfin.androidtv.integration.dream.composable
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -26,5 +27,6 @@ fun DreamContentLogo() = Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.Center)
|
||||
.width(400.dp)
|
||||
.fillMaxHeight()
|
||||
)
|
||||
}
|
||||
|
||||
@@ -76,6 +76,9 @@ private suspend fun getRandomLibraryShowcase(
|
||||
sortBy = listOf(ItemSortBy.Random),
|
||||
limit = 5,
|
||||
imageTypes = listOf(ImageType.BACKDROP),
|
||||
// TODO: Add preferences for these two settings
|
||||
maxOfficialRating = "PG-13",
|
||||
// hasParentalRating = true,
|
||||
)
|
||||
|
||||
val item = response.items?.firstOrNull { item ->
|
||||
|
||||
@@ -82,7 +82,10 @@ class AsyncImageView @JvmOverloads constructor(
|
||||
}.build())
|
||||
} else {
|
||||
imageLoader.enqueue(ImageRequest.Builder(context).apply {
|
||||
crossfade(crossFadeDuration.inWholeMilliseconds.toInt())
|
||||
val crossFadeDurationMs = crossFadeDuration.inWholeMilliseconds.toInt()
|
||||
if (crossFadeDurationMs > 0) crossfade(crossFadeDurationMs)
|
||||
else crossfade(false)
|
||||
|
||||
target(this@AsyncImageView)
|
||||
data(url)
|
||||
placeholder(placeholderOrBlurHash)
|
||||
|
||||
@@ -34,7 +34,7 @@ import org.koin.compose.rememberKoinInject
|
||||
private fun AppThemeBackground() {
|
||||
val context = LocalContext.current
|
||||
val themeBackground = remember(context.theme) {
|
||||
val attributes = context.theme.obtainStyledAttributes(intArrayOf(android.R.attr.windowBackground))
|
||||
val attributes = context.theme.obtainStyledAttributes(intArrayOf(R.attr.defaultBackground))
|
||||
val drawable = attributes.getDrawable(0)
|
||||
attributes.recycle()
|
||||
|
||||
@@ -64,26 +64,29 @@ private fun AppThemeBackground() {
|
||||
fun AppBackground() {
|
||||
val backgroundService = rememberKoinInject<BackgroundService>()
|
||||
val currentBackground by backgroundService.currentBackground.collectAsState()
|
||||
val enabled by backgroundService.enabled.collectAsState()
|
||||
|
||||
AnimatedContent(
|
||||
targetState = currentBackground,
|
||||
transitionSpec = {
|
||||
val duration = (BackgroundService.TRANSITION_DURATION.inWholeMilliseconds / 2).toInt()
|
||||
fadeIn(tween(durationMillis = duration)) togetherWith fadeOut(snap(delayMillis = duration))
|
||||
},
|
||||
label = "BackgroundTransition",
|
||||
) { background ->
|
||||
if (background != null) {
|
||||
Image(
|
||||
bitmap = background,
|
||||
contentDescription = null,
|
||||
alignment = Alignment.Center,
|
||||
contentScale = ContentScale.Crop,
|
||||
colorFilter = ColorFilter.tint(colorResource(R.color.background_filter), BlendMode.SrcAtop),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
} else {
|
||||
AppThemeBackground()
|
||||
if (enabled) {
|
||||
AnimatedContent(
|
||||
targetState = currentBackground,
|
||||
transitionSpec = {
|
||||
val duration = (BackgroundService.TRANSITION_DURATION.inWholeMilliseconds / 2).toInt()
|
||||
fadeIn(tween(durationMillis = duration)) togetherWith fadeOut(snap(delayMillis = duration))
|
||||
},
|
||||
label = "BackgroundTransition",
|
||||
) { background ->
|
||||
if (background != null) {
|
||||
Image(
|
||||
bitmap = background,
|
||||
contentDescription = null,
|
||||
alignment = Alignment.Center,
|
||||
contentScale = ContentScale.Crop,
|
||||
colorFilter = ColorFilter.tint(colorResource(R.color.background_filter), BlendMode.SrcAtop),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
} else {
|
||||
AppThemeBackground()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package org.jellyfin.androidtv.ui.browsing;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.DisplayMetrics;
|
||||
@@ -19,6 +18,7 @@ import android.widget.PopupWindow;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.leanback.widget.BaseGridView;
|
||||
import androidx.leanback.widget.OnItemViewClickedListener;
|
||||
import androidx.leanback.widget.OnItemViewSelectedListener;
|
||||
@@ -85,7 +85,7 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
|
||||
private final static int CHUNK_SIZE_MINIMUM = 25;
|
||||
|
||||
private String mainTitle;
|
||||
private Activity mActivity;
|
||||
private FragmentActivity mActivity;
|
||||
private BaseRowItem mCurrentItem;
|
||||
private CompositeClickedListener mClickedListener = new CompositeClickedListener();
|
||||
private CompositeSelectedListener mSelectedListener = new CompositeSelectedListener();
|
||||
@@ -758,7 +758,7 @@ public class BrowseGridFragment extends Fragment implements View.OnKeyListener {
|
||||
libraryPreferences.set(LibraryPreferences.Companion.getFilterUnwatchedOnly(), mAdapter.getFilters().isUnwatchedOnly());
|
||||
libraryPreferences.set(LibraryPreferences.Companion.getSortBy(), mAdapter.getSortBy());
|
||||
libraryPreferences.set(LibraryPreferences.Companion.getSortOrder(), getSortOption(mAdapter.getSortBy()).order);
|
||||
CoroutineUtils.runBlocking((coroutineScope, continuation) -> libraryPreferences.commit(continuation));
|
||||
CoroutineUtils.runOnLifecycle(getLifecycle(), (coroutineScope, continuation) -> libraryPreferences.commit(continuation));
|
||||
}
|
||||
|
||||
private void addTools() {
|
||||
|
||||
@@ -1,52 +1,52 @@
|
||||
package org.jellyfin.androidtv.ui.browsing;
|
||||
|
||||
import android.os.Bundle;
|
||||
|
||||
import org.jellyfin.androidtv.R;
|
||||
import org.jellyfin.androidtv.data.querying.StdItemQuery;
|
||||
import org.jellyfin.androidtv.util.Utils;
|
||||
import org.jellyfin.apiclient.model.querying.ItemFields;
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind;
|
||||
|
||||
public class CollectionFragment extends EnhancedBrowseFragment {
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupQueries(RowLoader rowLoader) {
|
||||
if (Utils.getSafeValue(mFolder.getChildCount(), 0) > 0) {
|
||||
StdItemQuery movies = new StdItemQuery(new ItemFields[]{
|
||||
ItemFields.PrimaryImageAspectRatio,
|
||||
ItemFields.Overview,
|
||||
ItemFields.ItemCounts,
|
||||
ItemFields.DisplayPreferencesId,
|
||||
ItemFields.ChildCount,
|
||||
ItemFields.MediaStreams,
|
||||
ItemFields.MediaSources
|
||||
});
|
||||
movies.setParentId(mFolder.getId().toString());
|
||||
movies.setIncludeItemTypes(new String[]{"Movie"});
|
||||
mRows.add(new BrowseRowDef(getString(R.string.lbl_movies), movies, 100));
|
||||
StdItemQuery movies = new StdItemQuery(new ItemFields[]{
|
||||
ItemFields.PrimaryImageAspectRatio,
|
||||
ItemFields.Overview,
|
||||
ItemFields.ItemCounts,
|
||||
ItemFields.DisplayPreferencesId,
|
||||
ItemFields.ChildCount,
|
||||
ItemFields.MediaStreams,
|
||||
ItemFields.MediaSources
|
||||
});
|
||||
movies.setParentId(mFolder.getId().toString());
|
||||
movies.setIncludeItemTypes(new String[]{BaseItemKind.MOVIE.getSerialName()});
|
||||
mRows.add(new BrowseRowDef(getString(R.string.lbl_movies), movies, 100));
|
||||
|
||||
StdItemQuery series = new StdItemQuery();
|
||||
series.setParentId(mFolder.getId().toString());
|
||||
series.setIncludeItemTypes(new String[]{"Series"});
|
||||
mRows.add(new BrowseRowDef(getString(R.string.lbl_tv_series), series, 100));
|
||||
|
||||
StdItemQuery others = new StdItemQuery();
|
||||
others.setParentId(mFolder.getId().toString());
|
||||
others.setExcludeItemTypes(new String[]{"Movie", "Series"});
|
||||
mRows.add(new BrowseRowDef(getString(R.string.lbl_other), others, 100));
|
||||
|
||||
|
||||
rowLoader.loadRows(mRows);
|
||||
}
|
||||
StdItemQuery series = new StdItemQuery(new ItemFields[]{
|
||||
ItemFields.PrimaryImageAspectRatio,
|
||||
ItemFields.Overview,
|
||||
ItemFields.ItemCounts,
|
||||
ItemFields.DisplayPreferencesId,
|
||||
ItemFields.ChildCount,
|
||||
ItemFields.MediaStreams,
|
||||
ItemFields.MediaSources
|
||||
});
|
||||
series.setParentId(mFolder.getId().toString());
|
||||
series.setIncludeItemTypes(new String[]{BaseItemKind.SERIES.getSerialName()});
|
||||
mRows.add(new BrowseRowDef(getString(R.string.lbl_tv_series), series, 100));
|
||||
|
||||
StdItemQuery others = new StdItemQuery(new ItemFields[]{
|
||||
ItemFields.PrimaryImageAspectRatio,
|
||||
ItemFields.Overview,
|
||||
ItemFields.ItemCounts,
|
||||
ItemFields.DisplayPreferencesId,
|
||||
ItemFields.ChildCount,
|
||||
ItemFields.MediaStreams,
|
||||
ItemFields.MediaSources
|
||||
});
|
||||
others.setParentId(mFolder.getId().toString());
|
||||
others.setExcludeItemTypes(new String[]{BaseItemKind.MOVIE.getSerialName(), BaseItemKind.SERIES.getSerialName()});
|
||||
mRows.add(new BrowseRowDef(getString(R.string.lbl_other), others, 100));
|
||||
|
||||
rowLoader.loadRows(mRows);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.jellyfin.androidtv.ui.browsing
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
@@ -26,6 +27,7 @@ import org.jellyfin.androidtv.ui.navigation.NavigationRepository
|
||||
import org.jellyfin.androidtv.ui.screensaver.InAppScreensaver
|
||||
import org.jellyfin.androidtv.ui.startup.StartupActivity
|
||||
import org.jellyfin.androidtv.util.applyTheme
|
||||
import org.jellyfin.androidtv.util.isMediaSessionKeyEvent
|
||||
import org.koin.android.ext.android.inject
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import timber.log.Timber
|
||||
@@ -193,4 +195,38 @@ class MainActivity : FragmentActivity() {
|
||||
|
||||
screensaverViewModel.notifyInteraction(false)
|
||||
}
|
||||
|
||||
@Suppress("RestrictedApi") // False positive
|
||||
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||
// Ignore the key event that closes the screensaver
|
||||
if (!event.isMediaSessionKeyEvent() && screensaverViewModel.visible.value) {
|
||||
screensaverViewModel.notifyInteraction(canCancel = event.action == KeyEvent.ACTION_UP)
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("RestrictedApi") // False positive
|
||||
return super.dispatchKeyEvent(event)
|
||||
}
|
||||
|
||||
@Suppress("RestrictedApi") // False positive
|
||||
override fun dispatchKeyShortcutEvent(event: KeyEvent): Boolean {
|
||||
// Ignore the key event that closes the screensaver
|
||||
if (!event.isMediaSessionKeyEvent() && screensaverViewModel.visible.value) {
|
||||
screensaverViewModel.notifyInteraction(canCancel = event.action == KeyEvent.ACTION_UP)
|
||||
return true
|
||||
}
|
||||
|
||||
@Suppress("RestrictedApi") // False positive
|
||||
return super.dispatchKeyShortcutEvent(event)
|
||||
}
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
|
||||
// Ignore the touch event that closes the screensaver
|
||||
if (screensaverViewModel.visible.value) {
|
||||
screensaverViewModel.notifyInteraction(true)
|
||||
return true
|
||||
}
|
||||
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ class HomeFragment : Fragment() {
|
||||
}
|
||||
|
||||
binding.search.setOnClickListener {
|
||||
navigationRepository.navigate(Destinations.search)
|
||||
navigationRepository.navigate(Destinations.search())
|
||||
}
|
||||
|
||||
return binding.root
|
||||
|
||||
@@ -17,11 +17,14 @@ import androidx.lifecycle.repeatOnLifecycle
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.coroutines.withTimeout
|
||||
import org.jellyfin.androidtv.auth.repository.UserRepository
|
||||
import org.jellyfin.androidtv.constant.CustomMessage
|
||||
import org.jellyfin.androidtv.constant.HomeSectionType
|
||||
@@ -88,15 +91,17 @@ class HomeRowsFragment : RowsSupportFragment(), AudioEventListener, View.OnKeyLi
|
||||
|
||||
adapter = MutableObjectAdapter<Row>(PositionableListRowPresenter())
|
||||
|
||||
val currentUser = userRepository.currentUser.value
|
||||
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
val currentUser = withTimeout(30.seconds) {
|
||||
userRepository.currentUser.filterNotNull().first()
|
||||
}
|
||||
|
||||
// Start out with default sections
|
||||
val homesections = userSettingPreferences.homesections
|
||||
var includeLiveTvRows = false
|
||||
|
||||
// Check for live TV support
|
||||
if (homesections.contains(HomeSectionType.LIVE_TV) && currentUser?.policy?.enableLiveTvAccess == true) {
|
||||
if (homesections.contains(HomeSectionType.LIVE_TV) && currentUser.policy?.enableLiveTvAccess == true) {
|
||||
// This is kind of ugly, but it mirrors how web handles the live TV rows on the home screen
|
||||
// If we can retrieve one live TV recommendation, then we should display the rows
|
||||
val recommendedPrograms by api.liveTvApi.getRecommendedPrograms(
|
||||
|
||||
@@ -334,9 +334,13 @@ public class FullDetailsFragment extends Fragment implements RecordingIndicatorV
|
||||
@Override
|
||||
public void run() {
|
||||
if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.STARTED)) return;
|
||||
// View holder may be null when the base item is still loading - this is a rare case
|
||||
// which generally happens when the server is unresponsive
|
||||
MyDetailsOverviewRowPresenter.ViewHolder viewholder = mDorPresenter.getViewHolder();
|
||||
if (viewholder == null) return;
|
||||
|
||||
if (mBaseItem != null && ((mBaseItem.getRunTimeTicks() != null && mBaseItem.getRunTimeTicks() > 0) || mBaseItem.getOriginalRunTimeTicks() != null)) {
|
||||
mDorPresenter.getViewHolder().setInfoValue3(getEndTime());
|
||||
viewholder.setInfoValue3(getEndTime());
|
||||
mLoopHandler.postDelayed(this, 15000);
|
||||
}
|
||||
}
|
||||
@@ -412,6 +416,14 @@ public class FullDetailsFragment extends Fragment implements RecordingIndicatorV
|
||||
|
||||
setBaseItem(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(@Nullable Exception exception) {
|
||||
Timber.w(exception, "Failed to load item, trying to navigate back.");
|
||||
super.onError(exception);
|
||||
|
||||
navigationRepository.getValue().goBack();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.content.Context;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.jellyfin.androidtv.constant.LiveTvOption;
|
||||
import org.jellyfin.androidtv.constant.QueryType;
|
||||
import org.jellyfin.androidtv.data.model.ChapterItemInfo;
|
||||
import org.jellyfin.androidtv.preference.LibraryPreferences;
|
||||
import org.jellyfin.androidtv.preference.PreferencesRepository;
|
||||
@@ -106,6 +107,8 @@ public class ItemLauncher {
|
||||
} else if (mediaManager.hasAudioQueueItems() && rowItem instanceof AudioQueueItem && pos < mediaManager.getCurrentAudioQueueSize()) {
|
||||
Timber.d("playing audio queue item");
|
||||
mediaManager.playFrom(pos);
|
||||
} else if (adapter.getQueryType() == QueryType.Search) {
|
||||
mediaManager.playNow(context, rowItem.getBaseItem());
|
||||
} else {
|
||||
Timber.d("playing audio item");
|
||||
List<BaseItemDto> audioItemsAsList = new ArrayList<>();
|
||||
@@ -138,7 +141,7 @@ public class ItemLauncher {
|
||||
}
|
||||
|
||||
// or generic handling
|
||||
if (baseItem.isFolder()) {
|
||||
if (Utils.isTrue(baseItem.isFolder())) {
|
||||
// Some items don't have a display preferences id, but it's required for StdGridFragment
|
||||
// Use the id of the item as a workaround, it's a unique key for the specific item
|
||||
// Which is exactly what we want
|
||||
|
||||
@@ -46,7 +46,9 @@ object Destinations {
|
||||
|
||||
// General
|
||||
val home = fragmentDestination<HomeFragment>()
|
||||
val search = fragmentDestination<SearchFragment>()
|
||||
fun search(query: String? = null) = fragmentDestination<SearchFragment>(
|
||||
SearchFragment.EXTRA_QUERY to query,
|
||||
)
|
||||
val userPreferences = preferenceDestination<UserPreferencesScreen>()
|
||||
|
||||
// Browsing
|
||||
|
||||
@@ -203,7 +203,7 @@ public class CustomPlaybackOverlayFragment extends Fragment implements LiveTvGui
|
||||
}
|
||||
};
|
||||
|
||||
backgroundService.getValue().clearBackgrounds();
|
||||
backgroundService.getValue().disable();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -9,14 +9,16 @@ import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DefaultDataSourceFactory;
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource;
|
||||
|
||||
import org.jellyfin.androidtv.R;
|
||||
import org.jellyfin.androidtv.constant.QueryType;
|
||||
@@ -53,6 +55,7 @@ import java.util.Random;
|
||||
import kotlin.Lazy;
|
||||
import timber.log.Timber;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class LegacyMediaManager implements MediaManager {
|
||||
private Context context;
|
||||
|
||||
|
||||
@@ -18,25 +18,28 @@ import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.PlaybackException;
|
||||
import androidx.media3.common.PlaybackParameters;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.Timeline;
|
||||
import androidx.media3.common.TrackGroup;
|
||||
import androidx.media3.common.TrackSelectionOverride;
|
||||
import androidx.media3.common.TrackSelectionParameters;
|
||||
import androidx.media3.common.Tracks;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory;
|
||||
import androidx.media3.exoplayer.ExoPlayer;
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
||||
import androidx.media3.extractor.DefaultExtractorsFactory;
|
||||
import androidx.media3.extractor.ts.TsExtractor;
|
||||
import androidx.media3.ui.AspectRatioFrameLayout;
|
||||
import androidx.media3.ui.PlayerView;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||
import com.google.android.exoplayer2.ExoPlayer;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.PlaybackException;
|
||||
import com.google.android.exoplayer2.PlaybackParameters;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.Tracks;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||
import com.google.android.exoplayer2.source.DefaultMediaSourceFactory;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionOverride;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionParameters;
|
||||
import com.google.android.exoplayer2.ui.AspectRatioFrameLayout;
|
||||
import com.google.android.exoplayer2.ui.StyledPlayerView;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
||||
import org.jellyfin.androidtv.R;
|
||||
@@ -58,6 +61,7 @@ import java.util.Optional;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
public final static int ZOOM_FIT = 0;
|
||||
public final static int ZOOM_AUTO_CROP = 1;
|
||||
@@ -76,7 +80,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
private SurfaceView mSubtitlesSurface;
|
||||
private FrameLayout mSurfaceFrame;
|
||||
private ExoPlayer mExoPlayer;
|
||||
private StyledPlayerView mExoPlayerView;
|
||||
private PlayerView mExoPlayerView;
|
||||
private LibVLC mLibVLC;
|
||||
private MediaPlayer mVlcPlayer;
|
||||
private Media mCurrentMedia;
|
||||
@@ -94,7 +98,7 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
private long mMetaDuration = -1;
|
||||
private long mMetaVLCStreamStartPosition = -1;
|
||||
private long lastExoPlayerPosition = -1;
|
||||
private boolean nightModeEnabled = false;
|
||||
private boolean nightModeEnabled;
|
||||
|
||||
private boolean nativeMode = false;
|
||||
private boolean mSurfaceReady = false;
|
||||
@@ -202,6 +206,16 @@ public class VideoManager implements IVLCVout.OnNewVideoLayoutListener {
|
||||
defaultRendererFactory.setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON);
|
||||
exoPlayerBuilder.setRenderersFactory(defaultRendererFactory);
|
||||
|
||||
DefaultTrackSelector trackSelector = new DefaultTrackSelector(context);
|
||||
trackSelector.setParameters(trackSelector.buildUponParameters()
|
||||
.setAudioOffloadPreferences(new TrackSelectionParameters.AudioOffloadPreferences.Builder()
|
||||
.setAudioOffloadMode(TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
);
|
||||
exoPlayerBuilder.setTrackSelector(trackSelector);
|
||||
|
||||
DefaultExtractorsFactory defaultExtractorsFactory = new DefaultExtractorsFactory().setTsExtractorTimestampSearchBytes(TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES * 3);
|
||||
exoPlayerBuilder.setMediaSourceFactory(new DefaultMediaSourceFactory(context, defaultExtractorsFactory));
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ class LeanbackSearchFragment : SearchSupportFragment(), SearchSupportFragment.Se
|
||||
viewModel.searchResultsFlow
|
||||
.onEach { searchFragmentDelegate.showResults(it) }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
val query = arguments?.getString(SearchFragment.EXTRA_QUERY)
|
||||
if (!query.isNullOrBlank()) setSearchQuery(query, true)
|
||||
}
|
||||
|
||||
override fun getResultsAdapter() = searchFragmentDelegate.rowsAdapter
|
||||
|
||||
@@ -6,9 +6,14 @@ import android.os.Bundle
|
||||
import android.speech.SpeechRecognizer
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import org.jellyfin.androidtv.R
|
||||
|
||||
class SearchFragment : Fragment(R.layout.fragment_content_view) {
|
||||
companion object {
|
||||
const val EXTRA_QUERY = "query"
|
||||
}
|
||||
|
||||
private val isSpeechEnabled by lazy {
|
||||
SpeechRecognizer.isRecognitionAvailable(requireContext())
|
||||
&& ContextCompat.checkSelfPermission(
|
||||
@@ -22,14 +27,13 @@ class SearchFragment : Fragment(R.layout.fragment_content_view) {
|
||||
|
||||
// Determine fragment to use
|
||||
val searchFragment = when {
|
||||
isSpeechEnabled -> LeanbackSearchFragment()
|
||||
else -> TextSearchFragment()
|
||||
isSpeechEnabled -> LeanbackSearchFragment::class.java
|
||||
else -> TextSearchFragment::class.java
|
||||
}
|
||||
|
||||
// Add fragment
|
||||
childFragmentManager
|
||||
.beginTransaction()
|
||||
.replace(R.id.content_view, searchFragment)
|
||||
.commit()
|
||||
childFragmentManager.commit {
|
||||
replace(R.id.content_view, searchFragment, arguments)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,11 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.EditText
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.leanback.app.RowsSupportFragment
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -34,26 +37,40 @@ class TextSearchFragment : Fragment() {
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentSearchTextBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.searchBar.apply {
|
||||
onTextChanged { viewModel.searchDebounced(it) }
|
||||
onSubmit { viewModel.searchImmediately(it) }
|
||||
}
|
||||
|
||||
binding.resultsFrame.getFragment<RowsSupportFragment?>()?.let {
|
||||
it.adapter = searchFragmentDelegate.rowsAdapter
|
||||
it.onItemViewClickedListener = searchFragmentDelegate.onItemViewClickedListener
|
||||
it.onItemViewSelectedListener = searchFragmentDelegate.onItemViewSelectedListener
|
||||
val rowsSupportFragment = RowsSupportFragment().apply {
|
||||
adapter = searchFragmentDelegate.rowsAdapter
|
||||
onItemViewClickedListener = searchFragmentDelegate.onItemViewClickedListener
|
||||
onItemViewSelectedListener = searchFragmentDelegate.onItemViewSelectedListener
|
||||
}
|
||||
|
||||
childFragmentManager.commit {
|
||||
replace(binding.resultsFrame.id, rowsSupportFragment)
|
||||
}
|
||||
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.searchResultsFlow
|
||||
.onEach { searchFragmentDelegate.showResults(it) }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
val query = arguments?.getString(SearchFragment.EXTRA_QUERY)
|
||||
if (!query.isNullOrBlank()) {
|
||||
binding.searchBar.setText(query)
|
||||
viewModel.searchImmediately(query)
|
||||
binding.resultsFrame.requestFocus()
|
||||
} else {
|
||||
binding.searchBar.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
@@ -63,12 +80,23 @@ class TextSearchFragment : Fragment() {
|
||||
}
|
||||
|
||||
private fun EditText.onSubmit(onSubmit: (String) -> Unit) {
|
||||
setOnEditorActionListener { _, actionId, _ ->
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
onSubmit(text.toString())
|
||||
true
|
||||
} else {
|
||||
false
|
||||
setOnEditorActionListener { view, actionId, _ ->
|
||||
when (actionId) {
|
||||
EditorInfo.IME_ACTION_DONE,
|
||||
EditorInfo.IME_ACTION_SEARCH,
|
||||
EditorInfo.IME_ACTION_PREVIOUS -> {
|
||||
onSubmit(text.toString())
|
||||
|
||||
// Manually close IME to workaround focus issue with Fire TV
|
||||
context.getSystemService<InputMethodManager>()
|
||||
?.hideSoftInputFromWindow(view.windowToken, 0)
|
||||
|
||||
// Focus on search results
|
||||
binding.resultsFrame.requestFocus()
|
||||
true
|
||||
}
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.jellyfin.androidtv.ui.startup
|
||||
|
||||
import android.Manifest
|
||||
import android.app.SearchManager
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.widget.Toast
|
||||
@@ -137,7 +138,9 @@ class StartupActivity : FragmentActivity() {
|
||||
// Create destination
|
||||
val destination = when {
|
||||
// Search is requested
|
||||
intent.action === Intent.ACTION_SEARCH -> Destinations.search
|
||||
intent.action === Intent.ACTION_SEARCH -> Destinations.search(
|
||||
query = intent.getStringExtra(SearchManager.QUERY)
|
||||
)
|
||||
// User view item is requested
|
||||
itemId != null && itemIsUserView -> {
|
||||
val item by api.userLibraryApi.getItem(itemId = itemId)
|
||||
|
||||
@@ -6,6 +6,7 @@ import android.view.ViewGroup
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material.Surface
|
||||
@@ -33,7 +34,9 @@ fun SplashScreen() {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.app_logo),
|
||||
contentDescription = stringResource(R.string.app_name),
|
||||
modifier = Modifier.width(400.dp)
|
||||
modifier = Modifier
|
||||
.width(400.dp)
|
||||
.fillMaxHeight()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,14 @@ import androidx.lifecycle.flowWithLifecycle
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.androidtv.constant.CustomMessage
|
||||
import org.jellyfin.androidtv.data.repository.CustomMessageRepository
|
||||
|
||||
fun <T : Any> runBlocking(block: suspend CoroutineScope.() -> T) = kotlinx.coroutines.runBlocking {
|
||||
block()
|
||||
}
|
||||
fun <T : Any> runOnLifecycle(
|
||||
lifecycle: Lifecycle,
|
||||
block: suspend CoroutineScope.() -> T
|
||||
) = lifecycle.coroutineScope.launch { block() }
|
||||
|
||||
fun readCustomMessagesOnLifecycle(
|
||||
lifecycle: Lifecycle,
|
||||
|
||||
@@ -107,7 +107,7 @@ public class InfoLayoutHelper {
|
||||
if (includeRuntime) addRuntime(context, item, layout, includeEndTime);
|
||||
addSeriesStatus(context, item, layout);
|
||||
addRatingAndRes(context, item, mediaSourceIndex, layout);
|
||||
addMediaDetails(context, audioStream, layout);
|
||||
addMediaDetails(context, item, mediaSourceIndex, layout);
|
||||
}
|
||||
|
||||
private static void addText(Context context, String text, LinearLayout layout, int maxWidth) {
|
||||
@@ -392,25 +392,27 @@ public class InfoLayoutHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static void addMediaDetails(Context context, MediaStream stream, LinearLayout layout) {
|
||||
private static void addMediaDetails(Context context, BaseItemDto item, int mediaSourceIndex, LinearLayout layout) {
|
||||
|
||||
if (stream != null) {
|
||||
if (stream.getProfile() != null && stream.getProfile().contains("Dolby Atmos")) {
|
||||
MediaStream audioStream = StreamHelper.getFirstAudioStream(item, mediaSourceIndex);
|
||||
|
||||
if (audioStream != null) {
|
||||
if (audioStream.getProfile() != null && audioStream.getProfile().contains("Dolby Atmos")) {
|
||||
addBlockText(context, layout, "ATMOS");
|
||||
addSpacer(context, layout, " ");
|
||||
} else if (stream.getProfile() != null && stream.getProfile().contains("DTS:X")) {
|
||||
} else if (audioStream.getProfile() != null && audioStream.getProfile().contains("DTS:X")) {
|
||||
addBlockText(context, layout, "DTS:X");
|
||||
addSpacer(context, layout, " ");
|
||||
} else {
|
||||
String codec = null;
|
||||
if (stream.getProfile() != null && stream.getProfile().contains("DTS-HD")) {
|
||||
if (audioStream.getProfile() != null && audioStream.getProfile().contains("DTS-HD")) {
|
||||
codec = "DTS-HD";
|
||||
} else if (stream.getCodec() != null && stream.getCodec().trim().length() > 0) {
|
||||
switch (stream.getCodec().toLowerCase()) {
|
||||
} else if (audioStream.getCodec() != null &audioStream.getCodec().trim().length() > 0) {
|
||||
switch (audioStream.getCodec().toLowerCase()) {
|
||||
case "dca": codec = "DTS"; break;
|
||||
case "eac3": codec = "DD+"; break;
|
||||
case "ac3": codec = "DD"; break;
|
||||
default: codec = stream.getCodec().toUpperCase();
|
||||
default: codec = audioStream.getCodec().toUpperCase();
|
||||
}
|
||||
}
|
||||
if (codec != null) {
|
||||
@@ -418,8 +420,8 @@ public class InfoLayoutHelper {
|
||||
addSpacer(context, layout, " ");
|
||||
}
|
||||
}
|
||||
if (stream.getChannelLayout() != null && stream.getChannelLayout().trim().length() > 0) {
|
||||
addBlockText(context, layout, stream.getChannelLayout().toUpperCase());
|
||||
if (audioStream.getChannelLayout() != null && audioStream.getChannelLayout().trim().length() > 0) {
|
||||
addBlockText(context, layout, audioStream.getChannelLayout().toUpperCase());
|
||||
addSpacer(context, layout, " ");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
package org.jellyfin.androidtv.util
|
||||
|
||||
import android.os.Build
|
||||
import android.view.KeyEvent
|
||||
|
||||
/**
|
||||
* Returns whether this key event is a media key event or not.
|
||||
*/
|
||||
fun KeyEvent.isMediaSessionKeyEvent(): Boolean = when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> KeyEvent.isMediaSessionKey(keyCode)
|
||||
|
||||
else -> when (keyCode) {
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY,
|
||||
KeyEvent.KEYCODE_MEDIA_PAUSE,
|
||||
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE,
|
||||
KeyEvent.KEYCODE_HEADSETHOOK,
|
||||
KeyEvent.KEYCODE_MEDIA_STOP,
|
||||
KeyEvent.KEYCODE_MEDIA_NEXT,
|
||||
KeyEvent.KEYCODE_MEDIA_PREVIOUS,
|
||||
KeyEvent.KEYCODE_MEDIA_REWIND,
|
||||
KeyEvent.KEYCODE_MEDIA_RECORD,
|
||||
KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
@@ -1,11 +1,13 @@
|
||||
package org.jellyfin.androidtv.util;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MenuItem;
|
||||
import android.widget.PopupMenu;
|
||||
|
||||
import androidx.fragment.app.FragmentActivity;
|
||||
import androidx.lifecycle.LifecycleOwner;
|
||||
|
||||
import org.jellyfin.androidtv.R;
|
||||
import org.jellyfin.androidtv.constant.CustomMessage;
|
||||
import org.jellyfin.androidtv.data.querying.StdItemQuery;
|
||||
@@ -55,11 +57,11 @@ public class KeyProcessor {
|
||||
|
||||
private static String mCurrentItemId;
|
||||
private static BaseItemDto mCurrentItem;
|
||||
private static Activity mCurrentActivity;
|
||||
private static FragmentActivity mCurrentActivity;
|
||||
private static int mCurrentRowItemNdx;
|
||||
private static boolean isMusic;
|
||||
|
||||
public static boolean HandleKey(int key, BaseRowItem rowItem, Activity activity) {
|
||||
public static boolean HandleKey(int key, BaseRowItem rowItem, FragmentActivity activity) {
|
||||
if (rowItem == null) return false;
|
||||
MediaManager mediaManager = KoinJavaComponent.<MediaManager>get(MediaManager.class);
|
||||
switch (key) {
|
||||
@@ -188,7 +190,7 @@ public class KeyProcessor {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static PopupMenu createItemMenu(BaseRowItem rowItem, UserItemDataDto userData, Activity activity) {
|
||||
public static PopupMenu createItemMenu(BaseRowItem rowItem, UserItemDataDto userData, FragmentActivity activity) {
|
||||
BaseItemDto item = rowItem.getBaseItem();
|
||||
PopupMenu menu = new PopupMenu(activity, activity.getCurrentFocus(), Gravity.END);
|
||||
int order = 0;
|
||||
@@ -278,7 +280,7 @@ public class KeyProcessor {
|
||||
return menu;
|
||||
}
|
||||
|
||||
private static void createPlayMenu(BaseItemDto item, boolean isMusic, Activity activity) {
|
||||
private static void createPlayMenu(BaseItemDto item, boolean isMusic, FragmentActivity activity) {
|
||||
PopupMenu menu = new PopupMenu(activity, activity.getCurrentFocus(), Gravity.END);
|
||||
int order = 0;
|
||||
if (!isMusic && item.getType() != BaseItemKind.PLAYLIST) {
|
||||
@@ -360,16 +362,16 @@ public class KeyProcessor {
|
||||
});
|
||||
return true;
|
||||
case MENU_MARK_FAVORITE:
|
||||
toggleFavorite(true);
|
||||
toggleFavorite(mCurrentActivity, true);
|
||||
return true;
|
||||
case MENU_UNMARK_FAVORITE:
|
||||
toggleFavorite(false);
|
||||
toggleFavorite(mCurrentActivity, false);
|
||||
return true;
|
||||
case MENU_MARK_PLAYED:
|
||||
togglePlayed(true);
|
||||
togglePlayed(mCurrentActivity, true);
|
||||
return true;
|
||||
case MENU_UNMARK_PLAYED:
|
||||
togglePlayed(false);
|
||||
togglePlayed(mCurrentActivity, false);
|
||||
return true;
|
||||
case MENU_GOTO_NOW_PLAYING:
|
||||
NavigationRepository navigationRepository = KoinJavaComponent.get(NavigationRepository.class);
|
||||
@@ -396,10 +398,10 @@ public class KeyProcessor {
|
||||
}
|
||||
};
|
||||
|
||||
private static void togglePlayed(boolean played) {
|
||||
private static void togglePlayed(LifecycleOwner lifecycleOwner, boolean played) {
|
||||
ItemMutationRepository itemMutationRepository = KoinJavaComponent.<ItemMutationRepository>get(ItemMutationRepository.class);
|
||||
|
||||
CoroutineUtils.runBlocking((scope, continuation) ->
|
||||
CoroutineUtils.runOnLifecycle(lifecycleOwner.getLifecycle(), (scope, continuation) ->
|
||||
itemMutationRepository.setPlayed(mCurrentItem.getId(), played, continuation)
|
||||
);
|
||||
|
||||
@@ -407,10 +409,10 @@ public class KeyProcessor {
|
||||
customMessageRepository.pushMessage(CustomMessage.RefreshCurrentItem.INSTANCE);
|
||||
}
|
||||
|
||||
private static void toggleFavorite(boolean favorite) {
|
||||
private static void toggleFavorite(LifecycleOwner lifecycleOwner, boolean favorite) {
|
||||
ItemMutationRepository itemMutationRepository = KoinJavaComponent.<ItemMutationRepository>get(ItemMutationRepository.class);
|
||||
|
||||
CoroutineUtils.runBlocking((scope, continuation) ->
|
||||
CoroutineUtils.runOnLifecycle(lifecycleOwner.getLifecycle(), (scope, continuation) ->
|
||||
itemMutationRepository.setFavorite(mCurrentItem.getId(), favorite, continuation)
|
||||
);
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.jellyfin.androidtv.util.apiclient;
|
||||
|
||||
import android.content.Context;
|
||||
import android.provider.MediaStore;
|
||||
|
||||
import org.jellyfin.androidtv.R;
|
||||
import org.jellyfin.androidtv.auth.repository.SessionRepository;
|
||||
@@ -8,6 +9,8 @@ import org.jellyfin.androidtv.preference.UserPreferences;
|
||||
import org.jellyfin.androidtv.ui.navigation.Destination;
|
||||
import org.jellyfin.androidtv.ui.navigation.NavigationRepository;
|
||||
import org.jellyfin.androidtv.ui.playback.MediaManager;
|
||||
import org.jellyfin.androidtv.ui.playback.PlaybackController;
|
||||
import org.jellyfin.androidtv.ui.playback.PlaybackControllerContainer;
|
||||
import org.jellyfin.androidtv.ui.playback.PlaybackLauncher;
|
||||
import org.jellyfin.androidtv.ui.playback.VideoQueueManager;
|
||||
import org.jellyfin.androidtv.util.TimeUtils;
|
||||
@@ -298,7 +301,12 @@ public class PlaybackHelper {
|
||||
default:
|
||||
KoinJavaComponent.<VideoQueueManager>get(VideoQueueManager.class).setCurrentVideoQueue(response);
|
||||
Destination destination = playbackLauncher.getPlaybackDestination(item.getType(), pos);
|
||||
navigationRepository.navigate(destination);
|
||||
|
||||
PlaybackController playbackController = KoinJavaComponent.<PlaybackControllerContainer>get(PlaybackControllerContainer.class).getPlaybackController();
|
||||
navigationRepository.navigate(
|
||||
destination,
|
||||
playbackController != null && playbackController.hasFragment()
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,6 +7,7 @@ import org.jellyfin.androidtv.util.Utils
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.audioDirectPlayProfile
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceAV1CodecProfile
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceHevcCodecProfile
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceHevcLevelCodecProfiles
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.h264VideoLevelProfileCondition
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.h264VideoProfileCondition
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.max1080pProfileConditions
|
||||
@@ -191,8 +192,9 @@ class ExoPlayerProfile(
|
||||
)
|
||||
)
|
||||
})
|
||||
// HEVC profile
|
||||
// HEVC profiles
|
||||
add(deviceHevcCodecProfile)
|
||||
addAll(deviceHevcLevelCodecProfiles)
|
||||
// AV1 profile
|
||||
add(deviceAV1CodecProfile)
|
||||
// Limit video resolution support for older devices
|
||||
@@ -203,7 +205,8 @@ class ExoPlayerProfile(
|
||||
})
|
||||
}
|
||||
// Audio channel profile
|
||||
add(maxAudioChannelsCodecProfile(channels = 8))
|
||||
if (!Utils.downMixAudio(context)) add(maxAudioChannelsCodecProfile(channels = 8))
|
||||
else add(maxAudioChannelsCodecProfile(channels = 2))
|
||||
}.toTypedArray()
|
||||
|
||||
subtitleProfiles = arrayOf(
|
||||
|
||||
@@ -5,6 +5,7 @@ import org.jellyfin.androidtv.constant.Codec
|
||||
import org.jellyfin.androidtv.util.Utils
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.audioDirectPlayProfile
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceHevcCodecProfile
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.deviceHevcLevelCodecProfiles
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.h264VideoLevelProfileCondition
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.h264VideoProfileCondition
|
||||
import org.jellyfin.androidtv.util.profile.ProfileHelper.maxAudioChannelsCodecProfile
|
||||
@@ -116,21 +117,22 @@ class LibVlcProfile(
|
||||
photoDirectPlayProfile
|
||||
)
|
||||
|
||||
codecProfiles = arrayOf(
|
||||
codecProfiles = buildList {
|
||||
// HEVC profile
|
||||
deviceHevcCodecProfile,
|
||||
add(deviceHevcCodecProfile)
|
||||
addAll(deviceHevcLevelCodecProfiles)
|
||||
// H264 profile
|
||||
CodecProfile().apply {
|
||||
add(CodecProfile().apply {
|
||||
type = CodecType.Video
|
||||
codec = Codec.Video.H264
|
||||
conditions = arrayOf(
|
||||
h264VideoProfileCondition,
|
||||
h264VideoLevelProfileCondition
|
||||
)
|
||||
},
|
||||
})
|
||||
// Audio channel profile
|
||||
maxAudioChannelsCodecProfile(channels = 8)
|
||||
)
|
||||
add(maxAudioChannelsCodecProfile(channels = 8))
|
||||
}.toTypedArray()
|
||||
|
||||
containerProfiles = arrayOf(
|
||||
ContainerProfile().apply {
|
||||
|
||||
@@ -9,6 +9,23 @@ import timber.log.Timber
|
||||
class MediaCodecCapabilitiesTest {
|
||||
private val mediaCodecList by lazy { MediaCodecList(MediaCodecList.REGULAR_CODECS) }
|
||||
|
||||
// HEVC levels as reported by ffprobe are multiplied by 30, e.g. level 4.1 is 123
|
||||
private val hevcLevelStrings = listOf(
|
||||
CodecProfileLevel.HEVCMainTierLevel1 to "30",
|
||||
CodecProfileLevel.HEVCMainTierLevel2 to "60",
|
||||
CodecProfileLevel.HEVCMainTierLevel21 to "63",
|
||||
CodecProfileLevel.HEVCMainTierLevel3 to "90",
|
||||
CodecProfileLevel.HEVCMainTierLevel31 to "93",
|
||||
CodecProfileLevel.HEVCMainTierLevel4 to "120",
|
||||
CodecProfileLevel.HEVCMainTierLevel41 to "123",
|
||||
CodecProfileLevel.HEVCMainTierLevel5 to "150",
|
||||
CodecProfileLevel.HEVCMainTierLevel51 to "153",
|
||||
CodecProfileLevel.HEVCMainTierLevel52 to "156",
|
||||
CodecProfileLevel.HEVCMainTierLevel6 to "180",
|
||||
CodecProfileLevel.HEVCMainTierLevel61 to "183",
|
||||
CodecProfileLevel.HEVCMainTierLevel62 to "186",
|
||||
)
|
||||
|
||||
fun supportsAV1(): Boolean = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
|
||||
hasCodecForMime(MediaFormat.MIMETYPE_VIDEO_AV1)
|
||||
|
||||
@@ -24,7 +41,15 @@ class MediaCodecCapabilitiesTest {
|
||||
fun supportsHevcMain10(): Boolean = hasDecoder(
|
||||
MediaFormat.MIMETYPE_VIDEO_HEVC,
|
||||
CodecProfileLevel.HEVCProfileMain10,
|
||||
CodecProfileLevel.HEVCMainTierLevel5
|
||||
CodecProfileLevel.HEVCMainTierLevel4
|
||||
)
|
||||
|
||||
fun getHevcMainLevel(): String = getHevcLevelString(
|
||||
CodecProfileLevel.HEVCProfileMain
|
||||
)
|
||||
|
||||
fun getHevcMain10Level(): String = getHevcLevelString(
|
||||
CodecProfileLevel.HEVCProfileMain10
|
||||
)
|
||||
|
||||
fun supportsAVCHigh10(): Boolean = hasDecoder(
|
||||
@@ -33,6 +58,35 @@ class MediaCodecCapabilitiesTest {
|
||||
CodecProfileLevel.AVCLevel4
|
||||
)
|
||||
|
||||
private fun getHevcLevelString(profile: Int): String {
|
||||
val level = getDecoderLevel(MediaFormat.MIMETYPE_VIDEO_HEVC, profile)
|
||||
|
||||
return hevcLevelStrings.asReversed().find { item: Pair<Int, String> ->
|
||||
level >= item.first
|
||||
}?.second ?: "0"
|
||||
}
|
||||
|
||||
private fun getDecoderLevel(mime: String, profile: Int): Int {
|
||||
var maxLevel = 0
|
||||
|
||||
for (info in mediaCodecList.codecInfos) {
|
||||
if (info.isEncoder) continue
|
||||
|
||||
try {
|
||||
val capabilities = info.getCapabilitiesForType(mime)
|
||||
for (profileLevel in capabilities.profileLevels) {
|
||||
if (profileLevel.profile == profile) {
|
||||
maxLevel = maxOf(maxLevel, profileLevel.level)
|
||||
}
|
||||
}
|
||||
} catch (e: IllegalArgumentException) {
|
||||
Timber.d(e, "Decoder %s does not support %s", info.name, mime)
|
||||
}
|
||||
}
|
||||
|
||||
return maxLevel
|
||||
}
|
||||
|
||||
private fun hasDecoder(mime: String, profile: Int, level: Int): Boolean {
|
||||
for (info in mediaCodecList.codecInfos) {
|
||||
if (info.isEncoder) continue
|
||||
|
||||
@@ -105,6 +105,56 @@ object ProfileHelper {
|
||||
}
|
||||
}
|
||||
|
||||
val deviceHevcLevelCodecProfiles by lazy {
|
||||
buildList {
|
||||
if (MediaTest.supportsHevc()) {
|
||||
add(CodecProfile().apply {
|
||||
type = CodecType.Video
|
||||
codec = Codec.Video.HEVC
|
||||
|
||||
applyConditions = arrayOf(
|
||||
ProfileCondition(
|
||||
ProfileConditionType.Equals,
|
||||
ProfileConditionValue.VideoProfile,
|
||||
"Main"
|
||||
)
|
||||
)
|
||||
|
||||
conditions = arrayOf(
|
||||
ProfileCondition(
|
||||
ProfileConditionType.LessThanEqual,
|
||||
ProfileConditionValue.VideoLevel,
|
||||
MediaTest.getHevcMainLevel()
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
if (MediaTest.supportsHevcMain10()) {
|
||||
add(CodecProfile().apply {
|
||||
type = CodecType.Video
|
||||
codec = Codec.Video.HEVC
|
||||
|
||||
applyConditions = arrayOf(
|
||||
ProfileCondition(
|
||||
ProfileConditionType.Equals,
|
||||
ProfileConditionValue.VideoProfile,
|
||||
"Main 10"
|
||||
)
|
||||
)
|
||||
|
||||
conditions = arrayOf(
|
||||
ProfileCondition(
|
||||
ProfileConditionType.LessThanEqual,
|
||||
ProfileConditionValue.VideoLevel,
|
||||
MediaTest.getHevcMain10Level()
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val h264VideoLevelProfileCondition by lazy {
|
||||
ProfileCondition(
|
||||
ProfileConditionType.LessThanEqual,
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="320dp"
|
||||
android:height="180dp"
|
||||
android:viewportWidth="320"
|
||||
android:viewportHeight="180">
|
||||
<path
|
||||
android:fillColor="@color/logo_background"
|
||||
android:pathData="M0,0h320v180h-320z" />
|
||||
</vector>
|
||||
@@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="320dp"
|
||||
android:height="180dp"
|
||||
android:viewportWidth="320"
|
||||
android:viewportHeight="180">
|
||||
<path android:pathData="m63.761,45.877c-11.678,0 -49.27,68.152 -43.538,79.661s81.407,11.377 87.076,0 -31.862,-79.661 -43.538,-79.661zM63.766,63.355c7.651,0 32.241,44.737 28.527,52.196 -3.714,7.457 -53.296,7.541 -57.054,0s20.876,-52.196 28.527,-52.196zM63.761,79.688c-3.876,0 -16.352,22.619 -14.448,26.437s27.016,3.777 28.897,0 -10.572,-26.435 -14.448,-26.437z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="108.26714"
|
||||
android:endY="124.14592"
|
||||
android:startX="35.09854"
|
||||
android:startY="81.90271"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="@color/logo_gradient_start"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="@color/logo_gradient_stop"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="@color/logo_text"
|
||||
android:pathData="m189.717,67.665a1.689,1.689 0,0 0,-1.701 1.689l0,38.301a1.691,1.691 0,0 0,2.901 1.21,1.64 1.64,0 0,0 0.478,-1.21l0,-38.301a1.689,1.689 0,0 0,-1.678 -1.689zM202.669,67.665a1.689,1.689 0,0 0,-1.701 1.689l0,38.301a1.691,1.691 0,0 0,2.901 1.21,1.64 1.64,0 0,0 0.478,-1.21l0,-38.301a1.689,1.689 0,0 0,-1.678 -1.689zM255.261,67.89a8.741,8.741 135,0 0,-0.108 0c-2.296,-0.008 -4.128,0.64 -5.499,1.942 -1.37,1.302 -2.055,3.135 -2.055,5.499l0,5.8l-4.111,0a1.579,1.579 0,0 0,-1.127 0.423,1.517 1.517,0 0,0 -0.449,1.155 1.557,1.557 0,0 0,0.449 1.098,1.485 1.485,0 0,0 1.127,0.478l4.111,0l0,23.371a1.689,1.689 0,1 0,3.379 0l0,-23.371l6.305,0a1.559,1.559 0,0 0,1.098 -0.449,1.566 1.566,0 0,0 0,-2.253 1.553,1.553 0,0 0,-1.098 -0.451l-6.309,0l0,-5.8c-0.002,-1.389 0.328,-2.478 0.986,-3.267 0.656,-0.788 1.589,-1.184 2.799,-1.189a7.964,7.964 0,0 1,1.972 0.337,3.567 3.567,0 0,0 1.238,0.281 1.397,1.397 0,0 0,1.014 -0.45,1.53 1.53,0 0,0 0.45,-1.126c0.002,-0.713 -0.449,-1.229 -1.35,-1.549a8.741,8.741 0,0 0,-2.821 -0.478zM144.965,69.916a1.706,1.706 0,0 0,-0.096 0.002,1.813 1.813,0 0,0 -1.295,0.478 1.6,1.6 0,0 0,-0.506 1.212l0,26.25c-0.001,2.591 -0.808,4.685 -2.421,6.28 -1.613,1.595 -3.716,2.393 -6.308,2.393a7.696,7.696 0,0 1,-4.551 -1.437,9.118 9.118,0 0,1 -3.098,-3.857c-0.449,-0.637 -0.977,-0.957 -1.576,-0.957a1.754,1.754 0,0 0,-1.155 0.451,1.407 1.407,0 0,0 -0.537,1.126 1.689,1.689 0,0 0,0.283 0.901,12.704 12.704,0 0,0 4.336,5.209 10.848,10.848 135,0 0,6.306 1.944c2.366,0 4.476,-0.519 6.329,-1.557a11.187,11.187 0,0 0,4.361 -4.308,12.268 12.268,0 0,0 1.578,-6.195l0,-26.242a1.65,1.65 0,0 0,-0.48 -1.212,1.706 1.706,0 0,0 -1.171,-0.48zM266.241,71.552c-0.752,-0.001 -1.344,0.177 -1.775,0.534 -0.431,0.358 -0.647,0.874 -0.647,1.549l0,0.569c0,0.675 0.205,1.193 0.62,1.549 0.406,0.356 0.993,0.537 1.744,0.537s1.316,-0.178 1.718,-0.534 0.591,-0.874 0.591,-1.549l0,-0.569c0,-1.39 -0.751,-2.086 -2.252,-2.086zM167.631,79.893a12.363,12.363 0,0 0,-6.477 1.83c-2.027,1.221 -3.651,2.986 -4.871,5.294s-1.83,5.002 -1.83,8.081c0.001,2.929 0.62,5.509 1.858,7.742a13.18,13.18 0,0 0,5.18 5.21c2.216,1.239 4.769,1.858 7.66,1.858a12.431,12.431 0,0 0,5.766 -1.409,14.164 14.164,0 0,0 4.534,-3.603 1.67,1.67 0,0 0,0.569 -1.183,1.543 1.543,0 0,0 -1.464,-1.464 1.896,1.896 0,0 0,-1.183 0.506,9.992 9.992,0 0,1 -3.436,2.873 10.239,10.239 0,0 1,-4.843 1.126c-2.328,0.001 -4.346,-0.515 -6.054,-1.549a10.411,10.411 0,0 1,-3.942 -4.224c-0.919,-1.782 -1.378,-3.8 -1.378,-6.052l21.286,0a1.663,1.663 0,0 0,1.183 -0.48,1.53 1.53,0 0,0 0.506,-1.159c-0.037,-2.781 -0.675,-5.183 -1.915,-7.205a12.473,12.473 0,0 0,-4.814 -4.619,13.273 13.273,0 0,0 -6.335,-1.574zM290.227,79.893a11.725,11.725 0,0 0,-5.835 1.604c-1.896,1.071 -3.35,2.413 -4.361,4.027l0,-3.098a1.689,1.689 0,1 0,-3.378 0l0,25.235a1.689,1.689 0,1 0,3.378 0l0,-16.388a6.871,6.871 0,0 1,1.352 -4.082,10.102 10.102,0 0,1 8.11,-4.139c2.553,-0.003 4.44,0.756 5.66,2.275 1.22,1.519 1.83,3.5 1.83,5.94l0,16.388a1.638,1.638 0,0 0,1.689 1.689,1.669 1.669,0 0,0 1.183,-0.48 1.593,1.593 0,0 0,0.506 -1.21l0,-16.556c0,-3.378 -0.817,-6.09 -2.45,-8.138 -1.633,-2.045 -4.195,-3.068 -7.685,-3.068zM234.218,80.053a1.706,1.706 0,0 0,-1.714 1.698l0,16.79a7.099,7.099 0,0 1,-1.267 4.027,9.458 9.458,0 0,1 -3.379,3.034 9.101,9.101 0,0 1,-4.421 1.155c-2.514,-0.001 -4.363,-0.696 -5.546,-2.086s-1.775,-3.436 -1.775,-6.14l0,-16.78a1.689,1.689 0,1 0,-3.379 0l0,17.281c0,3.34 0.845,5.986 2.535,7.939 1.69,1.953 4.167,2.93 7.433,2.93a11.86,11.86 135,0 0,5.745 -1.437,10.309 10.309,0 0,0 4.054,-3.792l0,1.968c0.002,3.38 -0.909,6.046 -2.73,7.998 -1.822,1.952 -4.29,2.928 -7.406,2.928 -2.179,-0.001 -4.47,-0.659 -6.871,-1.972a1.5,1.5 0,0 0,-0.675 -0.169,1.42 1.42,0 0,0 -0.844,0.283 1.528,1.528 0,0 0,-0.569 0.844,3.402 3.402,0 0,0 -0.114,0.569c0,0.6 0.394,1.145 1.183,1.635a11.703,11.703 135,0 0,3.413 1.35,18.341 18.341,135 0,0 4.42,0.508c2.59,0 4.918,-0.544 6.983,-1.632a11.676,11.676 0,0 0,4.843 -4.788c1.164,-2.105 1.746,-4.638 1.746,-7.601l0,-24.839a1.65,1.65 0,0 0,-0.48 -1.212,1.706 1.706,0 0,0 -1.186,-0.486zM266.196,80.062a1.689,1.689 0,0 0,-1.701 1.689l0,25.905a1.691,1.691 0,0 0,2.901 1.21l-0.006,0a1.64,1.64 0,0 0,0.484 -1.21l0,-25.905a1.689,1.689 0,0 0,-1.678 -1.689zM167.633,83.158a10.239,10.239 0,0 1,4.361 0.986,9.19 9.19,0 0,1 3.519,2.873 8.895,8.895 0,0 1,1.691 4.427l0,0.451l-19.204,0c0.526,-2.821 1.718,-4.983 3.576,-6.484s3.877,-2.253 6.056,-2.253z" />
|
||||
</vector>
|
||||
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path
|
||||
android:fillColor="@color/logo_background"
|
||||
android:pathData="M0,0h256v256h-256z" />
|
||||
</vector>
|
||||
@@ -1,3 +0,0 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval"
|
||||
android:tint="@color/logo_background"></shape>
|
||||
@@ -1,24 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path android:pathData="m127.998,39.598c-23.398,0 -98.717,136.547 -87.232,159.608s163.108,22.794 174.467,0c11.359,-22.794 -63.838,-159.608 -87.232,-159.608zM128.009,74.617c15.329,0 64.6,89.634 57.157,104.581 -7.442,14.942 -106.782,15.109 -114.312,0s41.827,-104.581 57.157,-104.581zM127.998,107.341c-7.765,0 -32.763,45.317 -28.948,52.969s54.128,7.568 57.898,0 -21.183,-52.966 -28.948,-52.969z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="217.18959"
|
||||
android:endY="196.4199"
|
||||
android:startX="70.58595"
|
||||
android:startY="111.779655"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="@color/logo_gradient_start"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="@color/logo_gradient_stop"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</vector>
|
||||
@@ -1,17 +1,17 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="493dp"
|
||||
android:height="154dp"
|
||||
android:viewportWidth="1480.6"
|
||||
android:viewportHeight="465.39">
|
||||
<path android:pathData="m232.73,0c-61.59,0 -259.85,359.43 -229.62,420.13 30.23,60.7 429.34,60 459.24,0s-168.04,-420.13 -229.62,-420.13zM232.76,92.18c40.35,0 170.04,235.94 150.45,275.28 -19.59,39.33 -281.08,39.77 -300.9,0s110.1,-275.28 150.45,-275.28zM232.73,178.32c-20.44,0 -86.24,119.29 -76.2,139.43s142.48,19.92 152.4,0 -55.76,-139.42 -76.2,-139.43z">
|
||||
android:width="251dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="251"
|
||||
android:viewportHeight="72">
|
||||
<path android:pathData="M24.2116 49.1575C22.66 46.0418 32.8378 27.5873 36 27.5873C39.1667 27.5889 49.3229 46.0758 47.7883 49.1575C46.2536 52.2392 25.7633 52.2732 24.2116 49.1575Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="467.455"
|
||||
android:endY="412.789"
|
||||
android:startX="81.565"
|
||||
android:startY="189.999"
|
||||
android:type="linear">
|
||||
android:endX="71.999"
|
||||
android:endY="63.0018"
|
||||
android:startX="12"
|
||||
android:startY="30"
|
||||
android:tileMode="clamp">
|
||||
<item
|
||||
android:color="@color/logo_gradient_start"
|
||||
android:offset="0" />
|
||||
@@ -22,6 +22,46 @@
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="@color/logo_text"
|
||||
android:pathData="m897.02,114.91a8.91,8.91 0,0 0,-8.97 8.91v202a8.92,8.92 0,0 0,15.3 6.38,8.65 8.65,0 0,0 2.52,-6.38v-202a8.91,8.91 0,0 0,-8.85 -8.91zM965.33,114.91a8.91,8.91 0,0 0,-8.97 8.91v202a8.92,8.92 0,0 0,15.3 6.38,8.65 8.65,0 0,0 2.52,-6.38v-202a8.91,8.91 0,0 0,-8.85 -8.91zM1242.7,116.1a46.1,46.1 0,0 0,-0.57 0c-12.11,-0.04 -21.77,3.37 -29,10.24 -7.23,6.87 -10.84,16.53 -10.84,29v30.59h-21.68a8.33,8.33 0,0 0,-5.94 2.23,8 8,0 0,0 -2.37,6.09 8.21,8.21 0,0 0,2.37 5.79,7.83 7.83,0 0,0 5.94,2.52h21.68v123.26a8.91,8.91 0,1 0,17.82 0v-123.26h33.25a8.22,8.22 0,0 0,5.79 -2.37,8.26 8.26,0 0,0 0,-11.88 8.19,8.19 0,0 0,-5.79 -2.38h-33.27v-30.59c-0.01,-7.33 1.73,-13.07 5.2,-17.23 3.46,-4.15 8.38,-6.24 14.76,-6.27a42,42 0,0 1,10.4 1.78,18.81 18.81,0 0,0 6.53,1.48 7.37,7.37 0,0 0,5.35 -2.37,8.07 8.07,0 0,0 2.37,-5.94c0.01,-3.76 -2.37,-6.48 -7.12,-8.17a46.1,46.1 0,0 0,-14.88 -2.52zM661,126.78a9,9 0,0 0,-0.51 0.01,9.56 9.56,0 0,0 -6.83,2.52 8.44,8.44 0,0 0,-2.67 6.39v138.44c-0.01,13.67 -4.26,24.71 -12.77,33.12 -8.51,8.41 -19.6,12.62 -33.27,12.62a40.59,40.59 0,0 1,-24 -7.58,48.09 48.09,0 0,1 -16.34,-20.34c-2.37,-3.36 -5.15,-5.05 -8.31,-5.05a9.25,9.25 0,0 0,-6.09 2.38,7.42 7.42,0 0,0 -2.83,5.94 8.91,8.91 0,0 0,1.49 4.75,67 67,0 0,0 22.87,27.47 57.21,57.21 0,0 0,33.26 10.25c12.48,0 23.61,-2.74 33.38,-8.21a59,59 0,0 0,23 -22.72,64.7 64.7,0 0,0 8.32,-32.67v-138.4a8.7,8.7 0,0 0,-2.53 -6.39,9 9,0 0,0 -6.17,-2.53zM1300.61,135.41c-3.97,-0.01 -7.09,0.93 -9.36,2.82 -2.27,1.89 -3.41,4.61 -3.41,8.17v3c0,3.56 1.08,6.29 3.27,8.17 2.14,1.88 5.24,2.83 9.2,2.83s6.94,-0.94 9.06,-2.82 3.12,-4.61 3.12,-8.17v-3c0,-7.33 -3.96,-11 -11.88,-11zM780.54,179.4a65.2,65.2 0,0 0,-34.16 9.65c-10.69,6.44 -19.26,15.75 -25.69,27.92s-9.65,26.38 -9.65,42.62c0.01,15.45 3.27,29.06 9.8,40.83a69.51,69.51 0,0 0,27.32 27.48c11.69,6.53 25.15,9.8 40.4,9.8a65.56,65.56 0,0 0,30.41 -7.43,74.7 74.7,0 0,0 23.91,-19 8.81,8.81 0,0 0,3 -6.24,8.14 8.14,0 0,0 -7.72,-7.72 10,10 0,0 0,-6.24 2.67,52.7 52.7,0 0,1 -18.12,15.15 54,54 0,0 1,-25.54 5.94c-12.28,0.01 -22.92,-2.72 -31.93,-8.17a54.91,54.91 0,0 1,-20.79 -22.28c-4.85,-9.4 -7.27,-20.04 -7.27,-31.92h112.26a8.77,8.77 0,0 0,6.24 -2.53,8.07 8.07,0 0,0 2.67,-6.11c-0.19,-14.67 -3.56,-27.33 -10.1,-38a65.78,65.78 0,0 0,-25.39 -24.36,70 70,0 0,0 -33.41,-8.3zM1427.11,179.4a61.84,61.84 0,0 0,-30.77 8.46c-10,5.65 -17.67,12.73 -23,21.24v-16.34a8.91,8.91 0,1 0,-17.82 0v133.09a8.91,8.91 0,1 0,17.82 0v-86.43a36.24,36.24 0,0 1,7.13 -21.53,53.28 53.28,0 0,1 42.77,-21.83c13.47,-0.01 23.42,3.99 29.85,12 6.43,8.01 9.65,18.46 9.65,31.33v86.43a8.64,8.64 0,0 0,8.91 8.91,8.8 8.8,0 0,0 6.24,-2.53 8.4,8.4 0,0 0,2.67 -6.38v-87.32c0,-17.81 -4.31,-32.12 -12.92,-42.92 -8.61,-10.79 -22.12,-16.18 -40.53,-16.18zM1131.72,180.25a9,9 0,0 0,-9.04 8.95v88.55a37.44,37.44 0,0 1,-6.68 21.24,49.88 49.88,0 0,1 -17.82,16 48,48 0,0 1,-23.32 6.09c-13.26,-0.01 -23.01,-3.67 -29.25,-11s-9.36,-18.12 -9.36,-32.38v-88.5a8.91,8.91 0,1 0,-17.82 0v91.14c0,17.61 4.46,31.57 13.37,41.87 8.91,10.3 21.98,15.45 39.2,15.45a62.55,62.55 0,0 0,30.3 -7.58,54.37 54.37,0 0,0 21.38,-20v10.38c0.01,17.83 -4.79,31.89 -14.4,42.18 -9.61,10.29 -22.63,15.44 -39.06,15.44 -11.49,-0.01 -23.57,-3.47 -36.24,-10.4a7.91,7.91 0,0 0,-3.56 -0.89,7.49 7.49,0 0,0 -4.45,1.49 8.06,8.06 0,0 0,-3 4.45,17.94 17.94,0 0,0 -0.6,3c0,3.17 2.08,6.04 6.24,8.62a61.72,61.72 0,0 0,18 7.12,96.73 96.73,0 0,0 23.31,2.68c13.66,0 25.94,-2.87 36.83,-8.61a61.58,61.58 0,0 0,25.54 -25.25c6.14,-11.1 9.21,-24.46 9.21,-40.09v-131a8.7,8.7 0,0 0,-2.53 -6.39,9 9,0 0,0 -6.25,-2.56zM1300.37,180.29a8.91,8.91 0,0 0,-8.97 8.91v136.62a8.92,8.92 0,0 0,15.3 6.38h-0.03a8.65,8.65 0,0 0,2.55 -6.38v-136.62a8.91,8.91 0,0 0,-8.85 -8.91zM780.55,196.62a54,54 0,0 1,23 5.2,48.47 48.47,0 0,1 18.56,15.15 46.91,46.91 0,0 1,8.92 23.35v2.38h-101.28c2.77,-14.88 9.06,-26.28 18.86,-34.2s20.45,-11.88 31.94,-11.88z" />
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M0.481861 64.9959C-4.19479 55.6055 26.4765 0.000823975 36 0.000823975C45.5327 0.000823975 76.153 55.7138 71.5274 64.9959C66.9018 74.2781 5.15851 74.3864 0.481861 64.9959ZM12.7358 56.8478C15.8005 63.0003 56.2536 62.9322 59.2842 56.8478C62.3149 50.7618 42.2515 14.2613 36.0093 14.2613C29.767 14.2613 9.67118 50.6952 12.7358 56.8478Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="71.999"
|
||||
android:endY="63.0018"
|
||||
android:startX="12"
|
||||
android:startY="30"
|
||||
android:tileMode="clamp">
|
||||
<item
|
||||
android:color="@color/logo_gradient_start"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="@color/logo_gradient_stop"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M225.22 56C224.94 56 224.8 56 224.693 55.9455C224.599 55.8976 224.522 55.8211 224.474 55.727C224.42 55.62 224.42 55.48 224.42 55.2V26.8C224.42 26.52 224.42 26.38 224.474 26.273C224.522 26.1789 224.599 26.1024 224.693 26.0545C224.8 26 224.94 26 225.22 26H230.403C230.683 26 230.823 26 230.93 26.0545C231.024 26.1024 231.1 26.1789 231.148 26.273C231.203 26.38 231.203 26.52 231.203 26.8V29.6948C232.012 28.261 233.151 27.1765 234.622 26.4412C236.129 25.7059 237.821 25.3382 239.696 25.3382C241.901 25.3382 243.85 25.8162 245.541 26.7721C247.269 27.7279 248.611 29.0698 249.567 30.7978C250.523 32.489 251.001 34.4559 251.001 36.6985V55.2C251.001 55.48 251.001 55.62 250.946 55.727C250.898 55.8211 250.822 55.8976 250.728 55.9455C250.621 56 250.481 56 250.201 56H244.576C244.296 56 244.156 56 244.049 55.9455C243.955 55.8976 243.879 55.8211 243.831 55.727C243.776 55.62 243.776 55.48 243.776 55.2V38.4081C243.776 36.4228 243.207 34.8603 242.067 33.7206C240.964 32.5441 239.512 31.9559 237.71 31.9559C235.909 31.9559 234.438 32.5441 233.298 33.7206C232.196 34.8971 231.644 36.4596 231.644 38.4081V55.2C231.644 55.48 231.644 55.62 231.59 55.727C231.542 55.8211 231.465 55.8976 231.371 55.9455C231.264 56 231.124 56 230.844 56H225.22Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M213.68 22.636C213.4 22.636 213.26 22.636 213.153 22.5815C213.059 22.5336 212.983 22.4571 212.935 22.363C212.88 22.2561 212.88 22.1161 212.88 21.836V15.7154C212.88 15.4354 212.88 15.2954 212.935 15.1884C212.983 15.0944 213.059 15.0179 213.153 14.9699C213.26 14.9154 213.4 14.9154 213.68 14.9154H219.304C219.584 14.9154 219.724 14.9154 219.831 14.9699C219.925 15.0179 220.002 15.0944 220.05 15.1884C220.104 15.2954 220.104 15.4354 220.104 15.7154V21.836C220.104 22.1161 220.104 22.2561 220.05 22.363C220.002 22.4571 219.925 22.5336 219.831 22.5815C219.724 22.636 219.584 22.636 219.304 22.636H213.68Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M213.68 56C213.4 56 213.26 56 213.153 55.9455C213.059 55.8975 212.983 55.821 212.935 55.727C212.88 55.62 212.88 55.48 212.88 55.2V26.8C212.88 26.5199 212.88 26.3799 212.935 26.273C212.983 26.1789 213.059 26.1024 213.153 26.0545C213.26 26 213.4 26 213.68 26H219.304C219.584 26 219.724 26 219.831 26.0545C219.925 26.1024 220.002 26.1789 220.05 26.273C220.104 26.3799 220.104 26.5199 220.104 26.8V55.2C220.104 55.48 220.104 55.62 220.05 55.727C220.002 55.821 219.925 55.8975 219.831 55.9455C219.724 56 219.584 56 219.304 56H213.68Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M196.968 55.9455C197.075 56 197.215 56 197.495 56H203.119C203.4 56 203.54 56 203.646 55.9455C203.741 55.8976 203.817 55.8211 203.865 55.727C203.919 55.62 203.919 55.48 203.919 55.2V32.4522H209.792C210.072 32.4522 210.212 32.4522 210.319 32.3977C210.413 32.3498 210.49 32.2733 210.538 32.1792C210.592 32.0722 210.592 31.9322 210.592 31.6522V26.8C210.592 26.52 210.592 26.3799 210.538 26.273C210.49 26.1789 210.413 26.1024 210.319 26.0545C210.212 26 210.072 26 209.792 26H203.919V25.0073C203.919 23.5 204.361 22.397 205.243 21.6985C206.162 20.9632 207.412 20.5956 208.993 20.5956C209.296 20.5956 209.609 20.6102 209.932 20.6395C210.277 20.6708 210.45 20.6865 210.566 20.636C210.674 20.5897 210.751 20.5188 210.807 20.4161C210.868 20.3043 210.868 20.1418 210.868 19.8166V15.3891C210.868 15.0846 210.868 14.9323 210.807 14.7999C210.757 14.6897 210.662 14.5808 210.559 14.5156C210.437 14.4374 210.298 14.4184 210.022 14.3803C209.408 14.2959 208.753 14.2537 208.056 14.2537C204.6 14.2537 201.842 15.1912 199.783 17.0662C197.725 18.9412 196.695 21.5882 196.695 25.0073V26H190.201C190.184 26 190.167 26 190.15 26H186.53C186.229 26 186.079 26 185.95 26.0492C185.836 26.0926 185.735 26.1631 185.654 26.2547C185.563 26.3584 185.511 26.499 185.406 26.7803L177.976 46.6801L170.493 26.7777C170.387 26.4973 170.335 26.3571 170.244 26.2538C170.163 26.1625 170.062 26.0922 169.948 26.049C169.819 26 169.669 26 169.37 26H163.601C163.207 26 163.01 26 162.884 26.0829C162.775 26.1554 162.698 26.2682 162.671 26.3969C162.64 26.544 162.712 26.7273 162.857 27.094L174.281 56L173.62 57.5993C173.031 58.8493 172.351 59.8603 171.579 60.6324C170.844 61.4044 169.649 61.7904 167.995 61.7904C167.406 61.7904 166.781 61.7353 166.12 61.625C165.865 61.5968 165.622 61.5631 165.39 61.524C165.001 61.4586 164.806 61.4259 164.681 61.4722C164.564 61.5155 164.483 61.584 164.421 61.6922C164.355 61.8078 164.355 61.9901 164.355 62.3547V66.6834C164.355 66.9268 164.355 67.0485 164.4 67.1649C164.437 67.2591 164.512 67.3651 164.589 67.4307C164.684 67.5118 164.783 67.5464 164.981 67.6158C165.437 67.7751 165.927 67.8921 166.451 67.9669C167.223 68.114 168.013 68.1875 168.822 68.1875C171.653 68.1875 174.006 67.3787 175.881 65.761C177.792 64.1801 179.318 61.9375 180.458 59.0331L190.975 32.4522H196.695V55.2C196.695 55.48 196.695 55.62 196.75 55.727C196.798 55.8211 196.874 55.8976 196.968 55.9455Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M154.363 56C154.083 56 153.943 56 153.836 55.9455C153.742 55.8976 153.665 55.8211 153.617 55.727C153.563 55.62 153.563 55.48 153.563 55.2V15.0537C153.563 14.7737 153.563 14.6336 153.617 14.5267C153.665 14.4326 153.742 14.3561 153.836 14.3082C153.943 14.2537 154.083 14.2537 154.363 14.2537H159.987C160.267 14.2537 160.407 14.2537 160.514 14.3082C160.608 14.3561 160.684 14.4326 160.732 14.5267C160.787 14.6336 160.787 14.7737 160.787 15.0537V55.2C160.787 55.48 160.787 55.62 160.732 55.727C160.684 55.8211 160.608 55.8976 160.514 55.9455C160.407 56 160.267 56 159.987 56H154.363Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M142.743 56C142.463 56 142.323 56 142.216 55.9455C142.122 55.8976 142.045 55.8211 141.997 55.727C141.943 55.62 141.943 55.48 141.943 55.2V15.0537C141.943 14.7737 141.943 14.6336 141.997 14.5267C142.045 14.4326 142.122 14.3561 142.216 14.3082C142.323 14.2537 142.463 14.2537 142.743 14.2537H148.367C148.647 14.2537 148.787 14.2537 148.894 14.3082C148.988 14.3561 149.065 14.4326 149.113 14.5267C149.167 14.6336 149.167 14.7737 149.167 15.0537V55.2C149.167 55.48 149.167 55.62 149.113 55.727C149.065 55.8211 148.988 55.8976 148.894 55.9455C148.787 56 148.647 56 148.367 56H142.743Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M124.61 56.6618C121.522 56.6618 118.82 55.9632 116.503 54.5662C114.187 53.1691 112.386 51.2757 111.099 48.886C109.812 46.4963 109.169 43.8493 109.169 40.9448C109.169 37.9301 109.812 35.2647 111.099 32.9485C112.423 30.5956 114.206 28.739 116.448 27.3787C118.728 26.0184 121.264 25.3382 124.059 25.3382C127.184 25.3382 129.831 26.0184 132 27.3787C134.169 28.7022 135.823 30.4669 136.963 32.6728C138.103 34.8787 138.673 37.3235 138.673 40.0073C138.673 40.5588 138.636 41.1103 138.562 41.6618C138.541 41.9606 138.501 42.2409 138.442 42.5026C138.406 42.6674 138.387 42.7498 138.327 42.8331C138.279 42.8995 138.194 42.9677 138.119 42.9999C138.024 43.0404 137.925 43.0404 137.726 43.0404H116.669C116.889 45.2463 117.735 47.011 119.206 48.3346C120.676 49.6581 122.496 50.3198 124.665 50.3198C126.32 50.3198 127.717 49.9706 128.856 49.2721C129.735 48.7229 130.47 48.0609 131.062 47.2861C131.303 46.97 131.424 46.8119 131.546 46.7443C131.663 46.6791 131.764 46.6555 131.898 46.6613C132.037 46.6674 132.194 46.7441 132.507 46.8976L136.775 48.9917C137.014 49.109 137.134 49.1676 137.206 49.2658C137.267 49.3481 137.304 49.4618 137.304 49.5641C137.304 49.6861 137.247 49.7957 137.132 50.0148C136.177 51.8418 134.669 53.3773 132.606 54.6213C130.327 55.9816 127.661 56.6618 124.61 56.6618ZM124.059 31.2941C122.257 31.2941 120.695 31.8456 119.371 32.9485C118.084 34.0147 117.239 35.5404 116.834 37.5257H130.952C130.805 35.6875 130.088 34.1985 128.801 33.0588C127.551 31.8823 125.97 31.2941 124.059 31.2941Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M90.8009 56C90.5208 56 90.3808 56 90.2739 55.9455C90.1798 55.8976 90.1033 55.8211 90.0554 55.727C90.0009 55.62 90.0009 55.48 90.0009 55.2V49.9066C90.0009 49.6266 90.0009 49.4866 90.0554 49.3796C90.1033 49.2855 90.1798 49.209 90.2739 49.1611C90.3808 49.1066 90.5208 49.1066 90.8009 49.1066H92.3722C94.1369 49.1066 95.5339 48.6103 96.5634 47.6176C97.6295 46.5882 98.1626 45.2279 98.1626 43.5368V15.7154C98.1626 15.4354 98.1626 15.2954 98.2171 15.1884C98.2651 15.0944 98.3415 15.0179 98.4356 14.9699C98.5426 14.9154 98.6826 14.9154 98.9626 14.9154H104.918C105.198 14.9154 105.338 14.9154 105.445 14.9699C105.539 15.0179 105.615 15.0944 105.663 15.1884C105.718 15.2954 105.718 15.4354 105.718 15.7154V43.261C105.718 45.7978 105.166 48.0221 104.063 49.9338C102.997 51.8456 101.453 53.3346 99.431 54.4007C97.4457 55.4669 95.0928 56 92.3722 56H90.8009Z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:bottom="18dp"
|
||||
android:drawable="@drawable/app_icon_foreground"
|
||||
android:end="18dp"
|
||||
android:start="18dp"
|
||||
android:top="18dp"></item>
|
||||
</layer-list>
|
||||
@@ -1,4 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/app_banner_background" />
|
||||
<item android:drawable="@drawable/app_banner_foreground" />
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="320dp"
|
||||
android:height="180dp"
|
||||
android:viewportWidth="320"
|
||||
|
||||
@@ -1,28 +1,67 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="320dp"
|
||||
android:height="180dp"
|
||||
android:viewportWidth="320"
|
||||
android:viewportHeight="180">
|
||||
<path android:pathData="m63.761,45.877c-11.678,0 -49.27,68.152 -43.538,79.661s81.407,11.377 87.076,0 -31.862,-79.661 -43.538,-79.661zM63.766,63.355c7.651,0 32.241,44.737 28.527,52.196 -3.714,7.457 -53.296,7.541 -57.054,0s20.876,-52.196 28.527,-52.196zM63.761,79.688c-3.876,0 -16.352,22.619 -14.448,26.437s27.016,3.777 28.897,0 -10.572,-26.435 -14.448,-26.437z">
|
||||
<path android:pathData="M59.21,103.16C57.66,100.04 67.84,81.59 71,81.59C74.17,81.59 84.32,100.08 82.79,103.16C81.25,106.24 60.76,106.27 59.21,103.16Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="108.26714"
|
||||
android:endY="124.14592"
|
||||
android:startX="35.09854"
|
||||
android:startY="81.90271"
|
||||
android:endX="107"
|
||||
android:endY="117"
|
||||
android:startX="47"
|
||||
android:startY="84"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#AA5CC3"
|
||||
android:color="#FFAA5CC3"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#00A4DC"
|
||||
android:color="#FF00A4DC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="m189.717,67.665a1.689,1.689 0,0 0,-1.701 1.689l0,38.301a1.691,1.691 0,0 0,2.901 1.21,1.64 1.64,0 0,0 0.478,-1.21l0,-38.301a1.689,1.689 0,0 0,-1.678 -1.689zM202.669,67.665a1.689,1.689 0,0 0,-1.701 1.689l0,38.301a1.691,1.691 0,0 0,2.901 1.21,1.64 1.64,0 0,0 0.478,-1.21l0,-38.301a1.689,1.689 0,0 0,-1.678 -1.689zM255.261,67.89a8.741,8.741 135,0 0,-0.108 0c-2.296,-0.008 -4.128,0.64 -5.499,1.942 -1.37,1.302 -2.055,3.135 -2.055,5.499l0,5.8l-4.111,0a1.579,1.579 0,0 0,-1.127 0.423,1.517 1.517,0 0,0 -0.449,1.155 1.557,1.557 0,0 0,0.449 1.098,1.485 1.485,0 0,0 1.127,0.478l4.111,0l0,23.371a1.689,1.689 0,1 0,3.379 0l0,-23.371l6.305,0a1.559,1.559 0,0 0,1.098 -0.449,1.566 1.566,0 0,0 0,-2.253 1.553,1.553 0,0 0,-1.098 -0.451l-6.309,0l0,-5.8c-0.002,-1.389 0.328,-2.478 0.986,-3.267 0.656,-0.788 1.589,-1.184 2.799,-1.189a7.964,7.964 0,0 1,1.972 0.337,3.567 3.567,0 0,0 1.238,0.281 1.397,1.397 0,0 0,1.014 -0.45,1.53 1.53,0 0,0 0.45,-1.126c0.002,-0.713 -0.449,-1.229 -1.35,-1.549a8.741,8.741 0,0 0,-2.821 -0.478zM144.965,69.916a1.706,1.706 0,0 0,-0.096 0.002,1.813 1.813,0 0,0 -1.295,0.478 1.6,1.6 0,0 0,-0.506 1.212l0,26.25c-0.001,2.591 -0.808,4.685 -2.421,6.28 -1.613,1.595 -3.716,2.393 -6.308,2.393a7.696,7.696 0,0 1,-4.551 -1.437,9.118 9.118,0 0,1 -3.098,-3.857c-0.449,-0.637 -0.977,-0.957 -1.576,-0.957a1.754,1.754 0,0 0,-1.155 0.451,1.407 1.407,0 0,0 -0.537,1.126 1.689,1.689 0,0 0,0.283 0.901,12.704 12.704,0 0,0 4.336,5.209 10.848,10.848 135,0 0,6.306 1.944c2.366,0 4.476,-0.519 6.329,-1.557a11.187,11.187 0,0 0,4.361 -4.308,12.268 12.268,0 0,0 1.578,-6.195l0,-26.242a1.65,1.65 0,0 0,-0.48 -1.212,1.706 1.706,0 0,0 -1.171,-0.48zM266.241,71.552c-0.752,-0.001 -1.344,0.177 -1.775,0.534 -0.431,0.358 -0.647,0.874 -0.647,1.549l0,0.569c0,0.675 0.205,1.193 0.62,1.549 0.406,0.356 0.993,0.537 1.744,0.537s1.316,-0.178 1.718,-0.534 0.591,-0.874 0.591,-1.549l0,-0.569c0,-1.39 -0.751,-2.086 -2.252,-2.086zM167.631,79.893a12.363,12.363 0,0 0,-6.477 1.83c-2.027,1.221 -3.651,2.986 -4.871,5.294s-1.83,5.002 -1.83,8.081c0.001,2.929 0.62,5.509 1.858,7.742a13.18,13.18 0,0 0,5.18 5.21c2.216,1.239 4.769,1.858 7.66,1.858a12.431,12.431 0,0 0,5.766 -1.409,14.164 14.164,0 0,0 4.534,-3.603 1.67,1.67 0,0 0,0.569 -1.183,1.543 1.543,0 0,0 -1.464,-1.464 1.896,1.896 0,0 0,-1.183 0.506,9.992 9.992,0 0,1 -3.436,2.873 10.239,10.239 0,0 1,-4.843 1.126c-2.328,0.001 -4.346,-0.515 -6.054,-1.549a10.411,10.411 0,0 1,-3.942 -4.224c-0.919,-1.782 -1.378,-3.8 -1.378,-6.052l21.286,0a1.663,1.663 0,0 0,1.183 -0.48,1.53 1.53,0 0,0 0.506,-1.159c-0.037,-2.781 -0.675,-5.183 -1.915,-7.205a12.473,12.473 0,0 0,-4.814 -4.619,13.273 13.273,0 0,0 -6.335,-1.574zM290.227,79.893a11.725,11.725 0,0 0,-5.835 1.604c-1.896,1.071 -3.35,2.413 -4.361,4.027l0,-3.098a1.689,1.689 0,1 0,-3.378 0l0,25.235a1.689,1.689 0,1 0,3.378 0l0,-16.388a6.871,6.871 0,0 1,1.352 -4.082,10.102 10.102,0 0,1 8.11,-4.139c2.553,-0.003 4.44,0.756 5.66,2.275 1.22,1.519 1.83,3.5 1.83,5.94l0,16.388a1.638,1.638 0,0 0,1.689 1.689,1.669 1.669,0 0,0 1.183,-0.48 1.593,1.593 0,0 0,0.506 -1.21l0,-16.556c0,-3.378 -0.817,-6.09 -2.45,-8.138 -1.633,-2.045 -4.195,-3.068 -7.685,-3.068zM234.218,80.053a1.706,1.706 0,0 0,-1.714 1.698l0,16.79a7.099,7.099 0,0 1,-1.267 4.027,9.458 9.458,0 0,1 -3.379,3.034 9.101,9.101 0,0 1,-4.421 1.155c-2.514,-0.001 -4.363,-0.696 -5.546,-2.086s-1.775,-3.436 -1.775,-6.14l0,-16.78a1.689,1.689 0,1 0,-3.379 0l0,17.281c0,3.34 0.845,5.986 2.535,7.939 1.69,1.953 4.167,2.93 7.433,2.93a11.86,11.86 135,0 0,5.745 -1.437,10.309 10.309,0 0,0 4.054,-3.792l0,1.968c0.002,3.38 -0.909,6.046 -2.73,7.998 -1.822,1.952 -4.29,2.928 -7.406,2.928 -2.179,-0.001 -4.47,-0.659 -6.871,-1.972a1.5,1.5 0,0 0,-0.675 -0.169,1.42 1.42,0 0,0 -0.844,0.283 1.528,1.528 0,0 0,-0.569 0.844,3.402 3.402,0 0,0 -0.114,0.569c0,0.6 0.394,1.145 1.183,1.635a11.703,11.703 135,0 0,3.413 1.35,18.341 18.341,135 0,0 4.42,0.508c2.59,0 4.918,-0.544 6.983,-1.632a11.676,11.676 0,0 0,4.843 -4.788c1.164,-2.105 1.746,-4.638 1.746,-7.601l0,-24.839a1.65,1.65 0,0 0,-0.48 -1.212,1.706 1.706,0 0,0 -1.186,-0.486zM266.196,80.062a1.689,1.689 0,0 0,-1.701 1.689l0,25.905a1.691,1.691 0,0 0,2.901 1.21l-0.006,0a1.64,1.64 0,0 0,0.484 -1.21l0,-25.905a1.689,1.689 0,0 0,-1.678 -1.689zM167.633,83.158a10.239,10.239 0,0 1,4.361 0.986,9.19 9.19,0 0,1 3.519,2.873 8.895,8.895 0,0 1,1.691 4.427l0,0.451l-19.204,0c0.526,-2.821 1.718,-4.983 3.576,-6.484s3.877,-2.253 6.056,-2.253z" />
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M35.48,119C30.81,109.61 61.48,54 71,54C80.53,54 111.15,109.71 106.53,119C101.9,128.28 40.16,128.39 35.48,119ZM47.74,110.85C50.8,117 91.25,116.93 94.28,110.85C97.31,104.76 77.25,68.26 71.01,68.26C64.77,68.26 44.67,104.69 47.74,110.85Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="107"
|
||||
android:endY="117"
|
||||
android:startX="47"
|
||||
android:startY="84"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFAA5CC3"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF00A4DC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M260.22,110C259.94,110 259.8,110 259.69,109.95C259.6,109.9 259.52,109.82 259.47,109.73C259.42,109.62 259.42,109.48 259.42,109.2V80.8C259.42,80.52 259.42,80.38 259.47,80.27C259.52,80.18 259.6,80.1 259.69,80.05C259.8,80 259.94,80 260.22,80H265.4C265.68,80 265.82,80 265.93,80.05C266.02,80.1 266.1,80.18 266.15,80.27C266.2,80.38 266.2,80.52 266.2,80.8V83.69C267.01,82.26 268.15,81.18 269.62,80.44C271.13,79.71 272.82,79.34 274.7,79.34C276.9,79.34 278.85,79.82 280.54,80.77C282.27,81.73 283.61,83.07 284.57,84.8C285.52,86.49 286,88.46 286,90.7V109.2C286,109.48 286,109.62 285.95,109.73C285.9,109.82 285.82,109.9 285.73,109.95C285.62,110 285.48,110 285.2,110H279.58C279.3,110 279.16,110 279.05,109.95C278.95,109.9 278.88,109.82 278.83,109.73C278.78,109.62 278.78,109.48 278.78,109.2V92.41C278.78,90.42 278.21,88.86 277.07,87.72C275.96,86.54 274.51,85.96 272.71,85.96C270.91,85.96 269.44,86.54 268.3,87.72C267.2,88.9 266.64,90.46 266.64,92.41V109.2C266.64,109.48 266.64,109.62 266.59,109.73C266.54,109.82 266.46,109.9 266.37,109.95C266.26,110 266.12,110 265.84,110H260.22Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M248.68,76.64C248.4,76.64 248.26,76.64 248.15,76.58C248.06,76.53 247.98,76.46 247.93,76.36C247.88,76.26 247.88,76.12 247.88,75.84V69.72C247.88,69.44 247.88,69.3 247.93,69.19C247.98,69.09 248.06,69.02 248.15,68.97C248.26,68.92 248.4,68.92 248.68,68.92H254.3C254.58,68.92 254.72,68.92 254.83,68.97C254.93,69.02 255,69.09 255.05,69.19C255.1,69.3 255.1,69.44 255.1,69.72V75.84C255.1,76.12 255.1,76.26 255.05,76.36C255,76.46 254.93,76.53 254.83,76.58C254.72,76.64 254.58,76.64 254.3,76.64H248.68Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M248.68,110C248.4,110 248.26,110 248.15,109.95C248.06,109.9 247.98,109.82 247.93,109.73C247.88,109.62 247.88,109.48 247.88,109.2V80.8C247.88,80.52 247.88,80.38 247.93,80.27C247.98,80.18 248.06,80.1 248.15,80.05C248.26,80 248.4,80 248.68,80H254.3C254.58,80 254.72,80 254.83,80.05C254.93,80.1 255,80.18 255.05,80.27C255.1,80.38 255.1,80.52 255.1,80.8V109.2C255.1,109.48 255.1,109.62 255.05,109.73C255,109.82 254.93,109.9 254.83,109.95C254.72,110 254.58,110 254.3,110H248.68Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M231.97,109.95C232.07,110 232.21,110 232.49,110H238.12C238.4,110 238.54,110 238.65,109.95C238.74,109.9 238.82,109.82 238.87,109.73C238.92,109.62 238.92,109.48 238.92,109.2V86.45H244.79C245.07,86.45 245.21,86.45 245.32,86.4C245.41,86.35 245.49,86.27 245.54,86.18C245.59,86.07 245.59,85.93 245.59,85.65V80.8C245.59,80.52 245.59,80.38 245.54,80.27C245.49,80.18 245.41,80.1 245.32,80.05C245.21,80 245.07,80 244.79,80H238.92V79.01C238.92,77.5 239.36,76.4 240.24,75.7C241.16,74.96 242.41,74.6 243.99,74.6C244.3,74.6 244.61,74.61 244.93,74.64C245.28,74.67 245.45,74.69 245.57,74.64C245.67,74.59 245.75,74.52 245.81,74.42C245.87,74.3 245.87,74.14 245.87,73.82V69.39C245.87,69.08 245.87,68.93 245.81,68.8C245.76,68.69 245.66,68.58 245.56,68.52C245.44,68.44 245.3,68.42 245.02,68.38C244.41,68.3 243.75,68.25 243.06,68.25C239.6,68.25 236.84,69.19 234.78,71.07C232.73,72.94 231.7,75.59 231.7,79.01V80H225.2C225.18,80 225.17,80 225.15,80H221.53C221.23,80 221.08,80 220.95,80.05C220.84,80.09 220.74,80.16 220.65,80.25C220.56,80.36 220.51,80.5 220.41,80.78L212.98,100.68L205.49,80.78C205.39,80.5 205.34,80.36 205.24,80.25C205.16,80.16 205.06,80.09 204.95,80.05C204.82,80 204.67,80 204.37,80H198.6C198.21,80 198.01,80 197.88,80.08C197.77,80.16 197.7,80.27 197.67,80.4C197.64,80.54 197.71,80.73 197.86,81.09L209.28,110L208.62,111.6C208.03,112.85 207.35,113.86 206.58,114.63C205.84,115.4 204.65,115.79 202.99,115.79C202.41,115.79 201.78,115.74 201.12,115.63C200.87,115.6 200.62,115.56 200.39,115.52C200,115.46 199.81,115.43 199.68,115.47C199.56,115.52 199.48,115.58 199.42,115.69C199.35,115.81 199.35,115.99 199.35,116.36V120.68C199.35,120.93 199.35,121.05 199.4,121.17C199.44,121.26 199.51,121.36 199.59,121.43C199.68,121.51 199.78,121.55 199.98,121.62C200.44,121.78 200.93,121.89 201.45,121.97C202.22,122.11 203.01,122.19 203.82,122.19C206.65,122.19 209.01,121.38 210.88,119.76C212.79,118.18 214.32,115.94 215.46,113.03L225.98,86.45H231.7V109.2C231.7,109.48 231.7,109.62 231.75,109.73C231.8,109.82 231.87,109.9 231.97,109.95Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M189.36,110C189.08,110 188.94,110 188.84,109.95C188.74,109.9 188.66,109.82 188.62,109.73C188.56,109.62 188.56,109.48 188.56,109.2V69.05C188.56,68.77 188.56,68.63 188.62,68.53C188.66,68.43 188.74,68.36 188.84,68.31C188.94,68.25 189.08,68.25 189.36,68.25H194.99C195.27,68.25 195.41,68.25 195.51,68.31C195.61,68.36 195.68,68.43 195.73,68.53C195.79,68.63 195.79,68.77 195.79,69.05V109.2C195.79,109.48 195.79,109.62 195.73,109.73C195.68,109.82 195.61,109.9 195.51,109.95C195.41,110 195.27,110 194.99,110H189.36Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M177.74,110C177.46,110 177.32,110 177.22,109.95C177.12,109.9 177.04,109.82 177,109.73C176.94,109.62 176.94,109.48 176.94,109.2V69.05C176.94,68.77 176.94,68.63 177,68.53C177.04,68.43 177.12,68.36 177.22,68.31C177.32,68.25 177.46,68.25 177.74,68.25H183.37C183.65,68.25 183.79,68.25 183.89,68.31C183.99,68.36 184.07,68.43 184.11,68.53C184.17,68.63 184.17,68.77 184.17,69.05V109.2C184.17,109.48 184.17,109.62 184.11,109.73C184.07,109.82 183.99,109.9 183.89,109.95C183.79,110 183.65,110 183.37,110H177.74Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M159.61,110.66C156.52,110.66 153.82,109.96 151.5,108.57C149.19,107.17 147.39,105.28 146.1,102.89C144.81,100.5 144.17,97.85 144.17,94.94C144.17,91.93 144.81,89.26 146.1,86.95C147.42,84.6 149.21,82.74 151.45,81.38C153.73,80.02 156.26,79.34 159.06,79.34C162.18,79.34 164.83,80.02 167,81.38C169.17,82.7 170.82,84.47 171.96,86.67C173.1,88.88 173.67,91.32 173.67,94.01C173.67,94.56 173.64,95.11 173.56,95.66C173.54,95.96 173.5,96.24 173.44,96.5C173.4,96.67 173.39,96.75 173.33,96.83C173.28,96.9 173.19,96.97 173.12,97C173.02,97.04 172.93,97.04 172.73,97.04H151.67C151.89,99.25 152.74,101.01 154.21,102.33C155.68,103.66 157.5,104.32 159.66,104.32C161.32,104.32 162.72,103.97 163.86,103.27C164.74,102.72 165.47,102.06 166.06,101.29C166.3,100.97 166.42,100.81 166.55,100.74C166.66,100.68 166.76,100.66 166.9,100.66C167.04,100.67 167.19,100.74 167.51,100.9L171.77,102.99C172.01,103.11 172.13,103.17 172.21,103.27C172.27,103.35 172.3,103.46 172.3,103.56C172.3,103.69 172.25,103.8 172.13,104.01C171.18,105.84 169.67,107.38 167.61,108.62C165.33,109.98 162.66,110.66 159.61,110.66ZM159.06,85.29C157.26,85.29 155.69,85.85 154.37,86.95C153.08,88.01 152.24,89.54 151.83,91.53H165.95C165.8,89.69 165.09,88.2 163.8,87.06C162.55,85.88 160.97,85.29 159.06,85.29Z" />
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData="M125.8,110C125.52,110 125.38,110 125.27,109.95C125.18,109.9 125.1,109.82 125.06,109.73C125,109.62 125,109.48 125,109.2V103.91C125,103.63 125,103.49 125.06,103.38C125.1,103.29 125.18,103.21 125.27,103.16C125.38,103.11 125.52,103.11 125.8,103.11H127.37C129.14,103.11 130.53,102.61 131.56,101.62C132.63,100.59 133.16,99.23 133.16,97.54V69.72C133.16,69.44 133.16,69.3 133.22,69.19C133.26,69.09 133.34,69.02 133.44,68.97C133.54,68.92 133.68,68.92 133.96,68.92H139.92C140.2,68.92 140.34,68.92 140.45,68.97C140.54,69.02 140.62,69.09 140.66,69.19C140.72,69.3 140.72,69.44 140.72,69.72V97.26C140.72,99.8 140.17,102.02 139.06,103.93C138,105.85 136.45,107.33 134.43,108.4C132.45,109.47 130.09,110 127.37,110H125.8Z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/app_icon_background" />
|
||||
<item android:drawable="@drawable/app_icon_foreground" />
|
||||
</layer-list>
|
||||
@@ -1,9 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#000B25"
|
||||
android:pathData="M0,0h256v256h-256z" />
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
</vector>
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="oval"
|
||||
android:tint="#000B25"></shape>
|
||||
@@ -1,22 +1,41 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="256dp"
|
||||
android:height="256dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<path android:pathData="m127.998,39.598c-23.398,0 -98.717,136.547 -87.232,159.608s163.108,22.794 174.467,0c11.359,-22.794 -63.838,-159.608 -87.232,-159.608zM128.009,74.617c15.329,0 64.6,89.634 57.157,104.581 -7.442,14.942 -106.782,15.109 -114.312,0s41.827,-104.581 57.157,-104.581zM127.998,107.341c-7.765,0 -32.763,45.317 -28.948,52.969s54.128,7.568 57.898,0 -21.183,-52.966 -28.948,-52.969z">
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M47.45,59.31C46.59,57.58 52.24,47.33 54,47.33C55.76,47.33 61.4,57.6 60.55,59.31C59.7,61.02 48.31,61.04 47.45,59.31Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="217.18959"
|
||||
android:endY="196.4199"
|
||||
android:startX="70.58595"
|
||||
android:startY="111.779655"
|
||||
android:endX="74"
|
||||
android:endY="67"
|
||||
android:startX="40.67"
|
||||
android:startY="48.67"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#AA5CC3"
|
||||
android:color="#FFAA5CC3"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#00A4DC"
|
||||
android:color="#FF00A4DC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M34.27,68.11C31.67,62.89 48.71,32 54,32C59.3,32 76.31,62.95 73.74,68.11C71.17,73.27 36.87,73.33 34.27,68.11ZM41.08,63.58C42.78,67 65.25,66.96 66.94,63.58C68.62,60.2 57.47,39.92 54.01,39.92C50.54,39.92 39.37,60.16 41.08,63.58Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="74"
|
||||
android:endY="67"
|
||||
android:startX="40.67"
|
||||
android:startY="48.67"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#FFAA5CC3"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#FF00A4DC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
|
||||
13
app/src/main/res/drawable/app_icon_foreground_monochrome.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M47.45,59.31C46.59,57.58 52.24,47.33 54,47.33C55.76,47.33 61.4,57.6 60.55,59.31C59.7,61.02 48.31,61.04 47.45,59.31Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M34.27,68.11C31.67,62.89 48.71,32 54,32C59.3,32 76.31,62.95 73.74,68.11C71.17,73.27 36.87,73.33 34.27,68.11ZM41.08,63.58C42.78,67 65.25,66.96 66.94,63.58C68.62,60.2 57.47,39.92 54.01,39.92C50.54,39.92 39.37,60.16 41.08,63.58Z" />
|
||||
</vector>
|
||||
@@ -1,5 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@drawable/app_icon_background_round" />
|
||||
<item android:drawable="@drawable/app_icon_foreground" />
|
||||
</layer-list>
|
||||
@@ -1,17 +1,36 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="493dp"
|
||||
android:height="154dp"
|
||||
android:viewportWidth="1480.6"
|
||||
android:viewportHeight="465.39">
|
||||
<path android:pathData="m232.73,0c-61.59,0 -259.85,359.43 -229.62,420.13 30.23,60.7 429.34,60 459.24,0s-168.04,-420.13 -229.62,-420.13zM232.76,92.18c40.35,0 170.04,235.94 150.45,275.28 -19.59,39.33 -281.08,39.77 -300.9,0s110.1,-275.28 150.45,-275.28zM232.73,178.32c-20.44,0 -86.24,119.29 -76.2,139.43s142.48,19.92 152.4,0 -55.76,-139.42 -76.2,-139.43z">
|
||||
android:width="251dp"
|
||||
android:height="72dp"
|
||||
android:viewportWidth="251"
|
||||
android:viewportHeight="72">
|
||||
<path android:pathData="M24.2116 49.1575C22.66 46.0418 32.8378 27.5873 36 27.5873C39.1667 27.5889 49.3229 46.0758 47.7883 49.1575C46.2536 52.2392 25.7633 52.2732 24.2116 49.1575Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="467.455"
|
||||
android:endY="412.789"
|
||||
android:startX="81.565"
|
||||
android:startY="189.999"
|
||||
android:type="linear">
|
||||
android:endX="71.999"
|
||||
android:endY="63.0018"
|
||||
android:startX="12"
|
||||
android:startY="30"
|
||||
android:tileMode="clamp">
|
||||
<item
|
||||
android:color="#AA5CC3"
|
||||
android:offset="0" />
|
||||
<item
|
||||
android:color="#00A4DC"
|
||||
android:offset="1" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M0.481861 64.9959C-4.19479 55.6055 26.4765 0.000823975 36 0.000823975C45.5327 0.000823975 76.153 55.7138 71.5274 64.9959C66.9018 74.2781 5.15851 74.3864 0.481861 64.9959ZM12.7358 56.8478C15.8005 63.0003 56.2536 62.9322 59.2842 56.8478C62.3149 50.7618 42.2515 14.2613 36.0093 14.2613C29.767 14.2613 9.67118 50.6952 12.7358 56.8478Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="71.999"
|
||||
android:endY="63.0018"
|
||||
android:startX="12"
|
||||
android:startY="30"
|
||||
android:tileMode="clamp">
|
||||
<item
|
||||
android:color="#AA5CC3"
|
||||
android:offset="0" />
|
||||
@@ -23,5 +42,26 @@
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="m897.02,114.91a8.91,8.91 0,0 0,-8.97 8.91v202a8.92,8.92 0,0 0,15.3 6.38,8.65 8.65,0 0,0 2.52,-6.38v-202a8.91,8.91 0,0 0,-8.85 -8.91zM965.33,114.91a8.91,8.91 0,0 0,-8.97 8.91v202a8.92,8.92 0,0 0,15.3 6.38,8.65 8.65,0 0,0 2.52,-6.38v-202a8.91,8.91 0,0 0,-8.85 -8.91zM1242.7,116.1a46.1,46.1 0,0 0,-0.57 0c-12.11,-0.04 -21.77,3.37 -29,10.24 -7.23,6.87 -10.84,16.53 -10.84,29v30.59h-21.68a8.33,8.33 0,0 0,-5.94 2.23,8 8,0 0,0 -2.37,6.09 8.21,8.21 0,0 0,2.37 5.79,7.83 7.83,0 0,0 5.94,2.52h21.68v123.26a8.91,8.91 0,1 0,17.82 0v-123.26h33.25a8.22,8.22 0,0 0,5.79 -2.37,8.26 8.26,0 0,0 0,-11.88 8.19,8.19 0,0 0,-5.79 -2.38h-33.27v-30.59c-0.01,-7.33 1.73,-13.07 5.2,-17.23 3.46,-4.15 8.38,-6.24 14.76,-6.27a42,42 0,0 1,10.4 1.78,18.81 18.81,0 0,0 6.53,1.48 7.37,7.37 0,0 0,5.35 -2.37,8.07 8.07,0 0,0 2.37,-5.94c0.01,-3.76 -2.37,-6.48 -7.12,-8.17a46.1,46.1 0,0 0,-14.88 -2.52zM661,126.78a9,9 0,0 0,-0.51 0.01,9.56 9.56,0 0,0 -6.83,2.52 8.44,8.44 0,0 0,-2.67 6.39v138.44c-0.01,13.67 -4.26,24.71 -12.77,33.12 -8.51,8.41 -19.6,12.62 -33.27,12.62a40.59,40.59 0,0 1,-24 -7.58,48.09 48.09,0 0,1 -16.34,-20.34c-2.37,-3.36 -5.15,-5.05 -8.31,-5.05a9.25,9.25 0,0 0,-6.09 2.38,7.42 7.42,0 0,0 -2.83,5.94 8.91,8.91 0,0 0,1.49 4.75,67 67,0 0,0 22.87,27.47 57.21,57.21 0,0 0,33.26 10.25c12.48,0 23.61,-2.74 33.38,-8.21a59,59 0,0 0,23 -22.72,64.7 64.7,0 0,0 8.32,-32.67v-138.4a8.7,8.7 0,0 0,-2.53 -6.39,9 9,0 0,0 -6.17,-2.53zM1300.61,135.41c-3.97,-0.01 -7.09,0.93 -9.36,2.82 -2.27,1.89 -3.41,4.61 -3.41,8.17v3c0,3.56 1.08,6.29 3.27,8.17 2.14,1.88 5.24,2.83 9.2,2.83s6.94,-0.94 9.06,-2.82 3.12,-4.61 3.12,-8.17v-3c0,-7.33 -3.96,-11 -11.88,-11zM780.54,179.4a65.2,65.2 0,0 0,-34.16 9.65c-10.69,6.44 -19.26,15.75 -25.69,27.92s-9.65,26.38 -9.65,42.62c0.01,15.45 3.27,29.06 9.8,40.83a69.51,69.51 0,0 0,27.32 27.48c11.69,6.53 25.15,9.8 40.4,9.8a65.56,65.56 0,0 0,30.41 -7.43,74.7 74.7,0 0,0 23.91,-19 8.81,8.81 0,0 0,3 -6.24,8.14 8.14,0 0,0 -7.72,-7.72 10,10 0,0 0,-6.24 2.67,52.7 52.7,0 0,1 -18.12,15.15 54,54 0,0 1,-25.54 5.94c-12.28,0.01 -22.92,-2.72 -31.93,-8.17a54.91,54.91 0,0 1,-20.79 -22.28c-4.85,-9.4 -7.27,-20.04 -7.27,-31.92h112.26a8.77,8.77 0,0 0,6.24 -2.53,8.07 8.07,0 0,0 2.67,-6.11c-0.19,-14.67 -3.56,-27.33 -10.1,-38a65.78,65.78 0,0 0,-25.39 -24.36,70 70,0 0,0 -33.41,-8.3zM1427.11,179.4a61.84,61.84 0,0 0,-30.77 8.46c-10,5.65 -17.67,12.73 -23,21.24v-16.34a8.91,8.91 0,1 0,-17.82 0v133.09a8.91,8.91 0,1 0,17.82 0v-86.43a36.24,36.24 0,0 1,7.13 -21.53,53.28 53.28,0 0,1 42.77,-21.83c13.47,-0.01 23.42,3.99 29.85,12 6.43,8.01 9.65,18.46 9.65,31.33v86.43a8.64,8.64 0,0 0,8.91 8.91,8.8 8.8,0 0,0 6.24,-2.53 8.4,8.4 0,0 0,2.67 -6.38v-87.32c0,-17.81 -4.31,-32.12 -12.92,-42.92 -8.61,-10.79 -22.12,-16.18 -40.53,-16.18zM1131.72,180.25a9,9 0,0 0,-9.04 8.95v88.55a37.44,37.44 0,0 1,-6.68 21.24,49.88 49.88,0 0,1 -17.82,16 48,48 0,0 1,-23.32 6.09c-13.26,-0.01 -23.01,-3.67 -29.25,-11s-9.36,-18.12 -9.36,-32.38v-88.5a8.91,8.91 0,1 0,-17.82 0v91.14c0,17.61 4.46,31.57 13.37,41.87 8.91,10.3 21.98,15.45 39.2,15.45a62.55,62.55 0,0 0,30.3 -7.58,54.37 54.37,0 0,0 21.38,-20v10.38c0.01,17.83 -4.79,31.89 -14.4,42.18 -9.61,10.29 -22.63,15.44 -39.06,15.44 -11.49,-0.01 -23.57,-3.47 -36.24,-10.4a7.91,7.91 0,0 0,-3.56 -0.89,7.49 7.49,0 0,0 -4.45,1.49 8.06,8.06 0,0 0,-3 4.45,17.94 17.94,0 0,0 -0.6,3c0,3.17 2.08,6.04 6.24,8.62a61.72,61.72 0,0 0,18 7.12,96.73 96.73,0 0,0 23.31,2.68c13.66,0 25.94,-2.87 36.83,-8.61a61.58,61.58 0,0 0,25.54 -25.25c6.14,-11.1 9.21,-24.46 9.21,-40.09v-131a8.7,8.7 0,0 0,-2.53 -6.39,9 9,0 0,0 -6.25,-2.56zM1300.37,180.29a8.91,8.91 0,0 0,-8.97 8.91v136.62a8.92,8.92 0,0 0,15.3 6.38h-0.03a8.65,8.65 0,0 0,2.55 -6.38v-136.62a8.91,8.91 0,0 0,-8.85 -8.91zM780.55,196.62a54,54 0,0 1,23 5.2,48.47 48.47,0 0,1 18.56,15.15 46.91,46.91 0,0 1,8.92 23.35v2.38h-101.28c2.77,-14.88 9.06,-26.28 18.86,-34.2s20.45,-11.88 31.94,-11.88z" />
|
||||
android:pathData="M225.22 56C224.94 56 224.8 56 224.693 55.9455C224.599 55.8976 224.522 55.8211 224.474 55.727C224.42 55.62 224.42 55.48 224.42 55.2V26.8C224.42 26.52 224.42 26.38 224.474 26.273C224.522 26.1789 224.599 26.1024 224.693 26.0545C224.8 26 224.94 26 225.22 26H230.403C230.683 26 230.823 26 230.93 26.0545C231.024 26.1024 231.1 26.1789 231.148 26.273C231.203 26.38 231.203 26.52 231.203 26.8V29.6948C232.012 28.261 233.151 27.1765 234.622 26.4412C236.129 25.7059 237.821 25.3382 239.696 25.3382C241.901 25.3382 243.85 25.8162 245.541 26.7721C247.269 27.7279 248.611 29.0698 249.567 30.7978C250.523 32.489 251.001 34.4559 251.001 36.6985V55.2C251.001 55.48 251.001 55.62 250.946 55.727C250.898 55.8211 250.822 55.8976 250.728 55.9455C250.621 56 250.481 56 250.201 56H244.576C244.296 56 244.156 56 244.049 55.9455C243.955 55.8976 243.879 55.8211 243.831 55.727C243.776 55.62 243.776 55.48 243.776 55.2V38.4081C243.776 36.4228 243.207 34.8603 242.067 33.7206C240.964 32.5441 239.512 31.9559 237.71 31.9559C235.909 31.9559 234.438 32.5441 233.298 33.7206C232.196 34.8971 231.644 36.4596 231.644 38.4081V55.2C231.644 55.48 231.644 55.62 231.59 55.727C231.542 55.8211 231.465 55.8976 231.371 55.9455C231.264 56 231.124 56 230.844 56H225.22Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M213.68 22.636C213.4 22.636 213.26 22.636 213.153 22.5815C213.059 22.5336 212.983 22.4571 212.935 22.363C212.88 22.2561 212.88 22.1161 212.88 21.836V15.7154C212.88 15.4354 212.88 15.2954 212.935 15.1884C212.983 15.0944 213.059 15.0179 213.153 14.9699C213.26 14.9154 213.4 14.9154 213.68 14.9154H219.304C219.584 14.9154 219.724 14.9154 219.831 14.9699C219.925 15.0179 220.002 15.0944 220.05 15.1884C220.104 15.2954 220.104 15.4354 220.104 15.7154V21.836C220.104 22.1161 220.104 22.2561 220.05 22.363C220.002 22.4571 219.925 22.5336 219.831 22.5815C219.724 22.636 219.584 22.636 219.304 22.636H213.68Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M213.68 56C213.4 56 213.26 56 213.153 55.9455C213.059 55.8975 212.983 55.821 212.935 55.727C212.88 55.62 212.88 55.48 212.88 55.2V26.8C212.88 26.5199 212.88 26.3799 212.935 26.273C212.983 26.1789 213.059 26.1024 213.153 26.0545C213.26 26 213.4 26 213.68 26H219.304C219.584 26 219.724 26 219.831 26.0545C219.925 26.1024 220.002 26.1789 220.05 26.273C220.104 26.3799 220.104 26.5199 220.104 26.8V55.2C220.104 55.48 220.104 55.62 220.05 55.727C220.002 55.821 219.925 55.8975 219.831 55.9455C219.724 56 219.584 56 219.304 56H213.68Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M196.968 55.9455C197.075 56 197.215 56 197.495 56H203.119C203.4 56 203.54 56 203.646 55.9455C203.741 55.8976 203.817 55.8211 203.865 55.727C203.919 55.62 203.919 55.48 203.919 55.2V32.4522H209.792C210.072 32.4522 210.212 32.4522 210.319 32.3977C210.413 32.3498 210.49 32.2733 210.538 32.1792C210.592 32.0722 210.592 31.9322 210.592 31.6522V26.8C210.592 26.52 210.592 26.3799 210.538 26.273C210.49 26.1789 210.413 26.1024 210.319 26.0545C210.212 26 210.072 26 209.792 26H203.919V25.0073C203.919 23.5 204.361 22.397 205.243 21.6985C206.162 20.9632 207.412 20.5956 208.993 20.5956C209.296 20.5956 209.609 20.6102 209.932 20.6395C210.277 20.6708 210.45 20.6865 210.566 20.636C210.674 20.5897 210.751 20.5188 210.807 20.4161C210.868 20.3043 210.868 20.1418 210.868 19.8166V15.3891C210.868 15.0846 210.868 14.9323 210.807 14.7999C210.757 14.6897 210.662 14.5808 210.559 14.5156C210.437 14.4374 210.298 14.4184 210.022 14.3803C209.408 14.2959 208.753 14.2537 208.056 14.2537C204.6 14.2537 201.842 15.1912 199.783 17.0662C197.725 18.9412 196.695 21.5882 196.695 25.0073V26H190.201C190.184 26 190.167 26 190.15 26H186.53C186.229 26 186.079 26 185.95 26.0492C185.836 26.0926 185.735 26.1631 185.654 26.2547C185.563 26.3584 185.511 26.499 185.406 26.7803L177.976 46.6801L170.493 26.7777C170.387 26.4973 170.335 26.3571 170.244 26.2538C170.163 26.1625 170.062 26.0922 169.948 26.049C169.819 26 169.669 26 169.37 26H163.601C163.207 26 163.01 26 162.884 26.0829C162.775 26.1554 162.698 26.2682 162.671 26.3969C162.64 26.544 162.712 26.7273 162.857 27.094L174.281 56L173.62 57.5993C173.031 58.8493 172.351 59.8603 171.579 60.6324C170.844 61.4044 169.649 61.7904 167.995 61.7904C167.406 61.7904 166.781 61.7353 166.12 61.625C165.865 61.5968 165.622 61.5631 165.39 61.524C165.001 61.4586 164.806 61.4259 164.681 61.4722C164.564 61.5155 164.483 61.584 164.421 61.6922C164.355 61.8078 164.355 61.9901 164.355 62.3547V66.6834C164.355 66.9268 164.355 67.0485 164.4 67.1649C164.437 67.2591 164.512 67.3651 164.589 67.4307C164.684 67.5118 164.783 67.5464 164.981 67.6158C165.437 67.7751 165.927 67.8921 166.451 67.9669C167.223 68.114 168.013 68.1875 168.822 68.1875C171.653 68.1875 174.006 67.3787 175.881 65.761C177.792 64.1801 179.318 61.9375 180.458 59.0331L190.975 32.4522H196.695V55.2C196.695 55.48 196.695 55.62 196.75 55.727C196.798 55.8211 196.874 55.8976 196.968 55.9455Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M154.363 56C154.083 56 153.943 56 153.836 55.9455C153.742 55.8976 153.665 55.8211 153.617 55.727C153.563 55.62 153.563 55.48 153.563 55.2V15.0537C153.563 14.7737 153.563 14.6336 153.617 14.5267C153.665 14.4326 153.742 14.3561 153.836 14.3082C153.943 14.2537 154.083 14.2537 154.363 14.2537H159.987C160.267 14.2537 160.407 14.2537 160.514 14.3082C160.608 14.3561 160.684 14.4326 160.732 14.5267C160.787 14.6336 160.787 14.7737 160.787 15.0537V55.2C160.787 55.48 160.787 55.62 160.732 55.727C160.684 55.8211 160.608 55.8976 160.514 55.9455C160.407 56 160.267 56 159.987 56H154.363Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M142.743 56C142.463 56 142.323 56 142.216 55.9455C142.122 55.8976 142.045 55.8211 141.997 55.727C141.943 55.62 141.943 55.48 141.943 55.2V15.0537C141.943 14.7737 141.943 14.6336 141.997 14.5267C142.045 14.4326 142.122 14.3561 142.216 14.3082C142.323 14.2537 142.463 14.2537 142.743 14.2537H148.367C148.647 14.2537 148.787 14.2537 148.894 14.3082C148.988 14.3561 149.065 14.4326 149.113 14.5267C149.167 14.6336 149.167 14.7737 149.167 15.0537V55.2C149.167 55.48 149.167 55.62 149.113 55.727C149.065 55.8211 148.988 55.8976 148.894 55.9455C148.787 56 148.647 56 148.367 56H142.743Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M124.61 56.6618C121.522 56.6618 118.82 55.9632 116.503 54.5662C114.187 53.1691 112.386 51.2757 111.099 48.886C109.812 46.4963 109.169 43.8493 109.169 40.9448C109.169 37.9301 109.812 35.2647 111.099 32.9485C112.423 30.5956 114.206 28.739 116.448 27.3787C118.728 26.0184 121.264 25.3382 124.059 25.3382C127.184 25.3382 129.831 26.0184 132 27.3787C134.169 28.7022 135.823 30.4669 136.963 32.6728C138.103 34.8787 138.673 37.3235 138.673 40.0073C138.673 40.5588 138.636 41.1103 138.562 41.6618C138.541 41.9606 138.501 42.2409 138.442 42.5026C138.406 42.6674 138.387 42.7498 138.327 42.8331C138.279 42.8995 138.194 42.9677 138.119 42.9999C138.024 43.0404 137.925 43.0404 137.726 43.0404H116.669C116.889 45.2463 117.735 47.011 119.206 48.3346C120.676 49.6581 122.496 50.3198 124.665 50.3198C126.32 50.3198 127.717 49.9706 128.856 49.2721C129.735 48.7229 130.47 48.0609 131.062 47.2861C131.303 46.97 131.424 46.8119 131.546 46.7443C131.663 46.6791 131.764 46.6555 131.898 46.6613C132.037 46.6674 132.194 46.7441 132.507 46.8976L136.775 48.9917C137.014 49.109 137.134 49.1676 137.206 49.2658C137.267 49.3481 137.304 49.4618 137.304 49.5641C137.304 49.6861 137.247 49.7957 137.132 50.0148C136.177 51.8418 134.669 53.3773 132.606 54.6213C130.327 55.9816 127.661 56.6618 124.61 56.6618ZM124.059 31.2941C122.257 31.2941 120.695 31.8456 119.371 32.9485C118.084 34.0147 117.239 35.5404 116.834 37.5257H130.952C130.805 35.6875 130.088 34.1985 128.801 33.0588C127.551 31.8823 125.97 31.2941 124.059 31.2941Z" />
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M90.8009 56C90.5208 56 90.3808 56 90.2739 55.9455C90.1798 55.8976 90.1033 55.8211 90.0554 55.727C90.0009 55.62 90.0009 55.48 90.0009 55.2V49.9066C90.0009 49.6266 90.0009 49.4866 90.0554 49.3796C90.1033 49.2855 90.1798 49.209 90.2739 49.1611C90.3808 49.1066 90.5208 49.1066 90.8009 49.1066H92.3722C94.1369 49.1066 95.5339 48.6103 96.5634 47.6176C97.6295 46.5882 98.1626 45.2279 98.1626 43.5368V15.7154C98.1626 15.4354 98.1626 15.2954 98.2171 15.1884C98.2651 15.0944 98.3415 15.0179 98.4356 14.9699C98.5426 14.9154 98.6826 14.9154 98.9626 14.9154H104.918C105.198 14.9154 105.338 14.9154 105.445 14.9699C105.539 15.0179 105.615 15.0944 105.663 15.1884C105.718 15.2954 105.718 15.4354 105.718 15.7154V43.261C105.718 45.7978 105.166 48.0221 104.063 49.9338C102.997 51.8456 101.453 53.3346 99.431 54.4007C97.4457 55.4669 95.0928 56 92.3722 56H90.8009Z" />
|
||||
</vector>
|
||||
|
||||
@@ -48,9 +48,8 @@
|
||||
app:layout_constraintTop_toTopOf="@id/logo" />
|
||||
|
||||
<!-- Fragment showing search results -->
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
<FrameLayout
|
||||
android:id="@+id/results_frame"
|
||||
android:name="androidx.leanback.app.RowsSupportFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginTop="27dp"
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
MODIFIED to add Jellyfin specifics
|
||||
-->
|
||||
<merge xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:lb="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="androidx.leanback.widget.BaseCardView">
|
||||
@@ -26,6 +27,7 @@
|
||||
tools:ignore="UnusedAttribute"
|
||||
tools:visibility="visible">
|
||||
|
||||
<!-- Crossfading is broken with this specific layout, especially when using thumbnails -->
|
||||
<org.jellyfin.androidtv.ui.AsyncImageView
|
||||
android:id="@+id/main_image"
|
||||
android:layout_width="wrap_content"
|
||||
@@ -33,6 +35,7 @@
|
||||
android:background="@drawable/shape_card_image_background"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="centerCrop"
|
||||
app:crossfadeDuration="0"
|
||||
lb:layout_viewType="main"
|
||||
tools:src="@drawable/app_logo" />
|
||||
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black">
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
@@ -31,7 +30,7 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<com.google.android.exoplayer2.ui.StyledPlayerView
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/exoPlayerView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -55,6 +54,7 @@
|
||||
android:gravity="center"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="28sp"
|
||||
android:textDirection="ltr"
|
||||
app:strokeWidth="5.0"
|
||||
tools:text="Subtitles" />
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/app_icon_background" />
|
||||
<foreground android:drawable="@drawable/app_icon_foreground_padded" />
|
||||
<monochrome android:drawable="@drawable/app_icon_foreground_padded" />
|
||||
<foreground android:drawable="@drawable/app_icon_foreground" />
|
||||
|
||||
<monochrome android:drawable="@drawable/app_icon_foreground_monochrome" />
|
||||
</adaptive-icon>
|
||||
BIN
app/src/main/res/mipmap-hdpi/app_banner.png
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
app/src/main/res/mipmap-hdpi/app_icon.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
app/src/main/res/mipmap-mdpi/app_banner.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/mipmap-mdpi/app_icon.png
Normal file
|
After Width: | Height: | Size: 2.4 KiB |
BIN
app/src/main/res/mipmap-xhdpi/app_banner.png
Normal file
|
After Width: | Height: | Size: 6.9 KiB |
BIN
app/src/main/res/mipmap-xhdpi/app_icon.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/app_banner.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/app_icon.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/app_banner.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/app_icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -26,6 +26,7 @@
|
||||
<attr name="controlIconBackground" format="reference|color" />
|
||||
<attr name="controlIconForegroundActive" format="reference|color" />
|
||||
<attr name="controlIconForegroundInactive" format="reference|color" />
|
||||
<attr name="defaultBackground" format="reference|color" />
|
||||
<!-- QR drawables -->
|
||||
<attr name="qrBackground" format="color" />
|
||||
<attr name="qrForeground" format="color" />
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<item name="android:colorPrimary">@color/theme_emerald_dark</item>
|
||||
<item name="android:colorPrimaryDark">@color/theme_emerald_dark</item>
|
||||
<item name="android:colorAccent">@color/theme_emerald_light</item>
|
||||
<item name="android:windowBackground">@drawable/moviebg</item>
|
||||
<item name="defaultBackground">@drawable/moviebg</item>
|
||||
<item name="defaultSearchColor">@color/theme_emerald_light</item>
|
||||
<item name="progressPrimary">@color/theme_emerald_light</item>
|
||||
<item name="progressSecondary">@color/theme_emerald_dark</item>
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
<item name="android:colorPrimaryDark">@color/grey</item>
|
||||
<item name="android:colorAccent">@color/jellyfin_blue</item>
|
||||
<!-- Background resource or color -->
|
||||
<item name="android:windowBackground">@color/not_quite_black</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="defaultBackground">@color/not_quite_black</item>
|
||||
<!-- Background color for CardViews -->
|
||||
<item name="cardImageBackground">@color/grey</item>
|
||||
<item name="cardViewBackground">@android:color/transparent</item>
|
||||
@@ -87,7 +88,6 @@
|
||||
</style>
|
||||
|
||||
<style name="Theme.Jellyfin.Preferences" parent="Theme.Jellyfin">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
<item name="android:windowIsTranslucent">true</item>
|
||||
<item name="android:backgroundDimEnabled">true</item>
|
||||
<item name="android:windowAnimationStyle">@style/WindowAnimation.SlideRight</item>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<account-authenticator xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accountType="@string/app_id"
|
||||
android:icon="@drawable/app_icon"
|
||||
android:icon="@mipmap/app_icon"
|
||||
android:label="@string/app_name"
|
||||
android:smallIcon="@drawable/app_icon" />
|
||||
android:smallIcon="@mipmap/app_icon" />
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:hint="@string/searchable_hint"
|
||||
android:icon="@drawable/app_icon_foreground_padded"
|
||||
android:icon="@mipmap/app_icon"
|
||||
android:imeOptions="actionSearch"
|
||||
android:includeInGlobalSearch="true"
|
||||
android:label="@string/app_name"
|
||||
|
||||
|
Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 37 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 51 KiB |
@@ -15,7 +15,7 @@ androidx-core = "1.12.0"
|
||||
androidx-fragment = "1.6.2"
|
||||
androidx-leanback = "1.1.0-rc01"
|
||||
androidx-lifecycle = "2.6.2"
|
||||
androidx-media3 = "1.2.0"
|
||||
androidx-media3 = "1.3.0"
|
||||
androidx-preference = "1.2.1"
|
||||
androidx-recyclerview = "1.3.2"
|
||||
androidx-startup = "1.1.1"
|
||||
@@ -25,10 +25,9 @@ androidx-work = "2.9.0"
|
||||
blurhash = "0.2.0"
|
||||
coil = "2.5.0"
|
||||
detekt = "1.23.4"
|
||||
exoplayer = "2.19.1"
|
||||
gson = "2.8.9"
|
||||
jellyfin-androidx-media = "1.2.1+1"
|
||||
jellyfin-apiclient = "v0.7.10"
|
||||
jellyfin-exoplayer-ffmpegextension = "2.19.1+1"
|
||||
jellyfin-sdk = "1.4.6"
|
||||
junit = "4.13.2"
|
||||
koin = "3.5.0"
|
||||
@@ -79,6 +78,7 @@ androidx-lifecycle-runtime = { module = "androidx.lifecycle:lifecycle-runtime-kt
|
||||
androidx-lifecycle-service = { module = "androidx.lifecycle:lifecycle-service", version.ref = "androidx-lifecycle" }
|
||||
androidx-lifecycle-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" }
|
||||
androidx-media3-session = { module = "androidx.media3:media3-session", version.ref = "androidx-media3" }
|
||||
androidx-media3-ui = { module = "androidx.media3:media3-ui", version.ref = "androidx-media3" }
|
||||
androidx-preference = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" }
|
||||
androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "androidx-recyclerview" }
|
||||
androidx-startup = { module = "androidx.startup:startup-runtime", version.ref = "androidx-startup" }
|
||||
@@ -93,8 +93,9 @@ koin-androidx-compose = { module = "io.insert-koin:koin-androidx-compose", versi
|
||||
koin-androidx-workmanager = { module = "io.insert-koin:koin-androidx-workmanager", version.ref = "koin" }
|
||||
|
||||
# Media players
|
||||
exoplayer = { module = "com.google.android.exoplayer:exoplayer", version.ref = "exoplayer" }
|
||||
jellyfin-exoplayer-ffmpegextension = { group = "org.jellyfin.exoplayer", name = "exoplayer-ffmpeg-extension", version.ref = "jellyfin-exoplayer-ffmpegextension" }
|
||||
androidx-media3-exoplayer = { module = "androidx.media3:media3-exoplayer", version.ref = "androidx-media3" }
|
||||
androidx-media3-exoplayer-hls = { module = "androidx.media3:media3-exoplayer-hls", version.ref = "androidx-media3" }
|
||||
jellyfin-androidx-media3-ffmpeg-decoder = { group = "org.jellyfin.media3", name = "media3-ffmpeg-decoder", version.ref = "jellyfin-androidx-media" }
|
||||
libvlc = { module = "org.videolan.android:libvlc-all", version.ref = "libvlc" }
|
||||
|
||||
# Markwon
|
||||
|
||||
@@ -93,14 +93,15 @@ internal class MediaSessionPlayer(
|
||||
val previous = state.queue.peekPrevious()
|
||||
val next = state.queue.peekNext()
|
||||
|
||||
listOfNotNull(previous, current, next)
|
||||
val playlist = listOfNotNull(previous, current, next)
|
||||
.distinctBy { it.metadata.mediaId }
|
||||
.map {
|
||||
MediaItemData.Builder(requireNotNull(it.metadata.mediaId)).apply {
|
||||
setMediaItem(it.metadata.toMediaItem())
|
||||
setDurationUs(it.metadata.duration?.inWholeMicroseconds ?: C.TIME_UNSET)
|
||||
}.build()
|
||||
}.let(::setPlaylist)
|
||||
}
|
||||
setPlaylist(playlist)
|
||||
|
||||
setPlaybackState(when (state.playState.value) {
|
||||
PlayState.STOPPED -> STATE_IDLE
|
||||
@@ -109,7 +110,7 @@ internal class MediaSessionPlayer(
|
||||
PlayState.ERROR -> STATE_ENDED
|
||||
})
|
||||
|
||||
setCurrentMediaItemIndex(if (previous == null) 0 else 1)
|
||||
setCurrentMediaItemIndex(if (previous == null || playlist.size <= 1) 0 else 1)
|
||||
} else {
|
||||
setPlaybackState(STATE_IDLE)
|
||||
setCurrentMediaItemIndex(C.INDEX_UNSET)
|
||||
|
||||
@@ -91,18 +91,14 @@ class DefaultPlayerQueueState(
|
||||
override fun replaceQueue(queue: Queue) {
|
||||
Timber.d("Queue changed, setting index to 0")
|
||||
|
||||
_current.value = queue
|
||||
orderIndexProvider.reset()
|
||||
if (orderIndexProvider != defaultOrderIndexProvider) defaultOrderIndexProvider.reset()
|
||||
|
||||
currentQueueIndicesPlayed.clear()
|
||||
|
||||
coroutineScope.launch {
|
||||
when (state.playbackOrder.value) {
|
||||
PlaybackOrder.DEFAULT -> setIndex(0)
|
||||
PlaybackOrder.RANDOM,
|
||||
PlaybackOrder.SHUFFLE -> setIndex((0 until queue.size).random())
|
||||
}
|
||||
_current.value = queue
|
||||
orderIndexProvider.reset()
|
||||
if (orderIndexProvider != defaultOrderIndexProvider) defaultOrderIndexProvider.reset()
|
||||
|
||||
currentQueueIndicesPlayed.clear()
|
||||
|
||||
setIndex(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -20,13 +20,13 @@ internal class ShuffleOrderIndexProvider : OrderIndexProvider {
|
||||
} else {
|
||||
val remainingIndices = (0..queue.size).filterNot {
|
||||
it in playedIndices || it in nextIndices
|
||||
}.shuffled()
|
||||
}
|
||||
|
||||
List(min(amount, remainingItemsSize)) { i ->
|
||||
if (i <= nextIndices.lastIndex) {
|
||||
if (i < nextIndices.lastIndex) {
|
||||
nextIndices[i]
|
||||
} else {
|
||||
val index = remainingIndices[i - nextIndices.size]
|
||||
val index = remainingIndices.random()
|
||||
nextIndices.add(index)
|
||||
index
|
||||
}
|
||||
|
||||
@@ -29,9 +29,12 @@ dependencies {
|
||||
implementation(libs.kotlinx.coroutines)
|
||||
implementation(libs.kotlinx.coroutines.guava)
|
||||
|
||||
// AndroidX
|
||||
implementation(libs.androidx.core)
|
||||
|
||||
// ExoPlayer
|
||||
implementation(libs.exoplayer)
|
||||
implementation(libs.jellyfin.exoplayer.ffmpegextension)
|
||||
implementation(libs.androidx.media3.exoplayer)
|
||||
implementation(libs.jellyfin.androidx.media3.ffmpeg.decoder)
|
||||
|
||||
// Logging
|
||||
implementation(libs.timber)
|
||||
|
||||
@@ -1,13 +1,22 @@
|
||||
package org.jellyfin.playback.exoplayer
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import com.google.android.exoplayer2.C
|
||||
import com.google.android.exoplayer2.DefaultRenderersFactory
|
||||
import com.google.android.exoplayer2.ExoPlayer
|
||||
import com.google.android.exoplayer2.MediaItem
|
||||
import com.google.android.exoplayer2.PlaybackException
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.video.VideoSize
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.TrackSelectionParameters
|
||||
import androidx.media3.common.VideoSize
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.DefaultRenderersFactory
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
|
||||
import androidx.media3.extractor.DefaultExtractorsFactory
|
||||
import androidx.media3.extractor.ts.TsExtractor
|
||||
import org.jellyfin.playback.core.backend.BasePlayerBackend
|
||||
import org.jellyfin.playback.core.mediastream.MediaStream
|
||||
import org.jellyfin.playback.core.mediastream.PlayableMediaStream
|
||||
@@ -21,22 +30,40 @@ import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.ZERO
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class ExoPlayerBackend(
|
||||
private val context: Context,
|
||||
) : BasePlayerBackend() {
|
||||
companion object {
|
||||
const val TS_SEARCH_BYTES_LM = TsExtractor.TS_PACKET_SIZE * 1800
|
||||
const val TS_SEARCH_BYTES_HM = TsExtractor.DEFAULT_TIMESTAMP_SEARCH_BYTES
|
||||
}
|
||||
|
||||
private var currentStream: PlayableMediaStream? = null
|
||||
|
||||
private val exoPlayer by lazy {
|
||||
val renderersFactory = DefaultRenderersFactory(context).apply {
|
||||
setEnableDecoderFallback(true)
|
||||
setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
|
||||
}
|
||||
|
||||
ExoPlayer.Builder(context, renderersFactory)
|
||||
ExoPlayer.Builder(context)
|
||||
.setRenderersFactory(DefaultRenderersFactory(context).apply {
|
||||
setEnableDecoderFallback(true)
|
||||
setExtensionRendererMode(DefaultRenderersFactory.EXTENSION_RENDERER_MODE_ON)
|
||||
})
|
||||
.setTrackSelector(DefaultTrackSelector(context).apply {
|
||||
setParameters(buildUponParameters().apply {
|
||||
setAudioOffloadPreferences(TrackSelectionParameters.AudioOffloadPreferences.DEFAULT.buildUpon().apply {
|
||||
setAudioOffloadMode(TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED)
|
||||
}.build())
|
||||
})
|
||||
})
|
||||
.setMediaSourceFactory(DefaultMediaSourceFactory(
|
||||
context,
|
||||
DefaultExtractorsFactory().apply {
|
||||
val isLowRamDevice = context.getSystemService<ActivityManager>()?.isLowRamDevice == true
|
||||
setTsExtractorTimestampSearchBytes(when (isLowRamDevice) {
|
||||
true -> TS_SEARCH_BYTES_LM
|
||||
false -> TS_SEARCH_BYTES_HM
|
||||
})
|
||||
}
|
||||
))
|
||||
.setPauseAtEndOfMediaItems(true)
|
||||
.build()
|
||||
.also { player -> player.addListener(PlayerListener()) }
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.jellyfin.playback.exoplayer.mapping
|
||||
|
||||
import com.google.android.exoplayer2.util.MimeTypes
|
||||
import androidx.media3.common.MimeTypes
|
||||
|
||||
fun getFfmpegAudioMimeType(codec: String): String {
|
||||
return ffmpegAudioMimeTypes.getOrDefault(codec, codec)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.jellyfin.playback.exoplayer.mapping
|
||||
|
||||
import com.google.android.exoplayer2.util.MimeTypes
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
|
||||
fun getFfmpegContainerMimeType(codec: String): String {
|
||||
// Find in container mime type list
|
||||
@@ -13,6 +15,7 @@ fun getFfmpegContainerMimeType(codec: String): String {
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
val ffmpegContainerMimeTypes = mapOf(
|
||||
"aac" to MimeTypes.AUDIO_AAC,
|
||||
"alaw" to MimeTypes.AUDIO_ALAW,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.jellyfin.playback.exoplayer.support
|
||||
|
||||
import com.google.android.exoplayer2.RendererCapabilities
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.RendererCapabilities
|
||||
|
||||
enum class AdaptiveSupport {
|
||||
SEAMLESS,
|
||||
@@ -8,6 +10,7 @@ enum class AdaptiveSupport {
|
||||
NOT_SUPPORTED;
|
||||
|
||||
companion object {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun fromFlags(flags: Int) = when (RendererCapabilities.getAdaptiveSupport(flags)) {
|
||||
RendererCapabilities.ADAPTIVE_SEAMLESS -> SEAMLESS
|
||||
RendererCapabilities.ADAPTIVE_NOT_SEAMLESS -> NOT_SEAMLESS
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.jellyfin.playback.exoplayer.support
|
||||
|
||||
import com.google.android.exoplayer2.RendererCapabilities
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.RendererCapabilities
|
||||
|
||||
enum class DecoderSupport {
|
||||
PRIMARY,
|
||||
@@ -8,6 +10,7 @@ enum class DecoderSupport {
|
||||
FALLBACK;
|
||||
|
||||
companion object {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun fromFlags(flags: Int) = when (RendererCapabilities.getDecoderSupport(flags)) {
|
||||
RendererCapabilities.DECODER_SUPPORT_PRIMARY -> PRIMARY
|
||||
RendererCapabilities.DECODER_SUPPORT_FALLBACK_MIMETYPE -> FALLBACK_MIMETYPE
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package org.jellyfin.playback.exoplayer.support
|
||||
|
||||
import com.google.android.exoplayer2.BaseRenderer
|
||||
import com.google.android.exoplayer2.ExoPlayer
|
||||
import com.google.android.exoplayer2.Format
|
||||
import com.google.android.exoplayer2.RendererCapabilities
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.Format
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.BaseRenderer
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.RendererCapabilities
|
||||
import org.jellyfin.playback.core.support.PlaySupportReport
|
||||
|
||||
data class ExoPlayerPlaySupportReport(
|
||||
@@ -16,6 +18,7 @@ data class ExoPlayerPlaySupportReport(
|
||||
override val canPlay = format == FormatSupport.HANDLED || tunneling == true || hardwareAcceleration == true
|
||||
|
||||
companion object {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun fromFlags(flags: Int): ExoPlayerPlaySupportReport = ExoPlayerPlaySupportReport(
|
||||
format = FormatSupport.fromFlags(RendererCapabilities.getFormatSupport(flags)),
|
||||
adaptive = AdaptiveSupport.fromFlags(RendererCapabilities.getAdaptiveSupport(flags)),
|
||||
@@ -24,12 +27,14 @@ data class ExoPlayerPlaySupportReport(
|
||||
decoder = DecoderSupport.fromFlags(RendererCapabilities.getDecoderSupport(flags)),
|
||||
)
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun tunnelingFromFlags(flags: Int) = when (RendererCapabilities.getTunnelingSupport(flags)) {
|
||||
RendererCapabilities.TUNNELING_SUPPORTED -> true
|
||||
RendererCapabilities.TUNNELING_NOT_SUPPORTED -> false
|
||||
else -> null
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun hardwareAccelerationFromFlags(flags: Int) = when (RendererCapabilities.getHardwareAccelerationSupport(flags)) {
|
||||
RendererCapabilities.HARDWARE_ACCELERATION_SUPPORTED -> true
|
||||
RendererCapabilities.HARDWARE_ACCELERATION_NOT_SUPPORTED -> false
|
||||
@@ -41,6 +46,7 @@ data class ExoPlayerPlaySupportReport(
|
||||
fun ExoPlayer.getPlaySupportReport(format: Format): ExoPlayerPlaySupportReport =
|
||||
ExoPlayerPlaySupportReport.fromFlags(supportsFormat(format))
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
fun ExoPlayer.supportsFormat(format: Format): Int {
|
||||
var capabilities = 0
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package org.jellyfin.playback.exoplayer.support
|
||||
|
||||
import com.google.android.exoplayer2.RendererCapabilities
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.RendererCapabilities
|
||||
|
||||
enum class FormatSupport {
|
||||
HANDLED,
|
||||
@@ -10,12 +13,13 @@ enum class FormatSupport {
|
||||
UNSUPPORTED_TYPE;
|
||||
|
||||
companion object {
|
||||
@OptIn(UnstableApi::class)
|
||||
fun fromFlags(flags: Int) = when (RendererCapabilities.getFormatSupport(flags)) {
|
||||
RendererCapabilities.FORMAT_HANDLED -> HANDLED
|
||||
RendererCapabilities.FORMAT_EXCEEDS_CAPABILITIES -> EXCEEDS_CAPABILITIES
|
||||
RendererCapabilities.FORMAT_UNSUPPORTED_DRM -> UNSUPPORTED_DRM
|
||||
RendererCapabilities.FORMAT_UNSUPPORTED_SUBTYPE -> UNSUPPORTED_SUBTYPE
|
||||
RendererCapabilities.FORMAT_UNSUPPORTED_TYPE -> UNSUPPORTED_TYPE
|
||||
C.FORMAT_HANDLED -> HANDLED
|
||||
C.FORMAT_EXCEEDS_CAPABILITIES -> EXCEEDS_CAPABILITIES
|
||||
C.FORMAT_UNSUPPORTED_DRM -> UNSUPPORTED_DRM
|
||||
C.FORMAT_UNSUPPORTED_SUBTYPE -> UNSUPPORTED_SUBTYPE
|
||||
C.FORMAT_UNSUPPORTED_TYPE -> UNSUPPORTED_TYPE
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
package org.jellyfin.playback.exoplayer.support
|
||||
|
||||
import com.google.android.exoplayer2.Format
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.Format
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import org.jellyfin.playback.core.mediastream.MediaStream
|
||||
import org.jellyfin.playback.core.mediastream.MediaStreamAudioTrack
|
||||
import org.jellyfin.playback.exoplayer.mapping.getFfmpegAudioMimeType
|
||||
import org.jellyfin.playback.exoplayer.mapping.getFfmpegContainerMimeType
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
fun MediaStream.toFormat() = Format.Builder().also { f ->
|
||||
f.setId(identifier)
|
||||
f.setContainerMimeType(getFfmpegContainerMimeType(container.format))
|
||||
|
||||