mirror of
https://github.com/antebudimir/tempus.git
synced 2026-01-01 09:53:33 +00:00
Merge branch 'development' into playlist-duplicates
This commit is contained in:
commit
84de93a4f1
66 changed files with 4650 additions and 353 deletions
|
|
@ -116,4 +116,13 @@ object Constants {
|
|||
const val HOME_SECTOR_RECENTLY_ADDED = "HOME_SECTOR_RECENTLY_ADDED"
|
||||
const val HOME_SECTOR_PINNED_PLAYLISTS = "HOME_SECTOR_PINNED_PLAYLISTS"
|
||||
const val HOME_SECTOR_SHARED = "HOME_SECTOR_SHARED"
|
||||
|
||||
const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON = "android.media3.session.demo.SHUFFLE_ON"
|
||||
const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF = "android.media3.session.demo.SHUFFLE_OFF"
|
||||
const val CUSTOM_COMMAND_TOGGLE_HEART_ON = "android.media3.session.demo.HEART_ON"
|
||||
const val CUSTOM_COMMAND_TOGGLE_HEART_OFF = "android.media3.session.demo.HEART_OFF"
|
||||
const val CUSTOM_COMMAND_TOGGLE_HEART_LOADING = "android.media3.session.demo.HEART_LOADING"
|
||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_OFF = "android.media3.session.demo.REPEAT_OFF"
|
||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ONE = "android.media3.session.demo.REPEAT_ONE"
|
||||
const val CUSTOM_COMMAND_TOGGLE_REPEAT_MODE_ALL = "android.media3.session.demo.REPEAT_ALL"
|
||||
}
|
||||
|
|
@ -78,32 +78,26 @@ public final class DownloadUtil {
|
|||
return httpDataSourceFactory;
|
||||
}
|
||||
|
||||
public static synchronized DataSource.Factory getDataSourceFactory(Context context) {
|
||||
if (dataSourceFactory == null) {
|
||||
context = context.getApplicationContext();
|
||||
public static synchronized DataSource.Factory getUpstreamDataSourceFactory(Context context) {
|
||||
DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory());
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
|
||||
return dataSourceFactory;
|
||||
}
|
||||
|
||||
DefaultDataSource.Factory upstreamFactory = new DefaultDataSource.Factory(context, getHttpDataSourceFactory());
|
||||
|
||||
if (Preferences.getStreamingCacheSize() > 0) {
|
||||
CacheDataSource.Factory streamCacheFactory = new CacheDataSource.Factory()
|
||||
.setCache(getStreamingCache(context))
|
||||
.setUpstreamDataSourceFactory(upstreamFactory);
|
||||
|
||||
ResolvingDataSource.Factory resolvingFactory = new ResolvingDataSource.Factory(
|
||||
new StreamingCacheDataSource.Factory(streamCacheFactory),
|
||||
dataSpec -> {
|
||||
DataSpec.Builder builder = dataSpec.buildUpon();
|
||||
builder.setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
|
||||
return builder.build();
|
||||
}
|
||||
);
|
||||
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(resolvingFactory, getDownloadCache(context));
|
||||
} else {
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(upstreamFactory, getDownloadCache(context));
|
||||
}
|
||||
}
|
||||
public static synchronized DataSource.Factory getCacheDataSourceFactory(Context context) {
|
||||
CacheDataSource.Factory streamCacheFactory = new CacheDataSource.Factory()
|
||||
.setCache(getStreamingCache(context))
|
||||
.setUpstreamDataSourceFactory(getUpstreamDataSourceFactory(context));
|
||||
|
||||
ResolvingDataSource.Factory resolvingFactory = new ResolvingDataSource.Factory(
|
||||
new StreamingCacheDataSource.Factory(streamCacheFactory),
|
||||
dataSpec -> {
|
||||
DataSpec.Builder builder = dataSpec.buildUpon();
|
||||
builder.setFlags(dataSpec.flags & ~DataSpec.FLAG_DONT_CACHE_IF_LENGTH_UNKNOWN);
|
||||
return builder.build();
|
||||
}
|
||||
);
|
||||
dataSourceFactory = buildReadOnlyCacheDataSource(resolvingFactory, getDownloadCache(context));
|
||||
return dataSourceFactory;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,69 @@
|
|||
package com.cappielloantonio.tempo.util
|
||||
|
||||
import android.content.Context
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.exoplayer.drm.DrmSessionManagerProvider
|
||||
import androidx.media3.exoplayer.hls.HlsMediaSource
|
||||
import androidx.media3.exoplayer.source.MediaSource
|
||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy
|
||||
import androidx.media3.extractor.DefaultExtractorsFactory
|
||||
import androidx.media3.extractor.ExtractorsFactory
|
||||
|
||||
@UnstableApi
|
||||
class DynamicMediaSourceFactory(
|
||||
private val context: Context
|
||||
) : MediaSource.Factory {
|
||||
|
||||
override fun createMediaSource(mediaItem: MediaItem): MediaSource {
|
||||
val mediaType: String? = mediaItem.mediaMetadata.extras?.getString("type", "")
|
||||
|
||||
val streamingCacheSize = Preferences.getStreamingCacheSize()
|
||||
val bypassCache = mediaType == Constants.MEDIA_TYPE_RADIO
|
||||
|
||||
val useUpstream = when {
|
||||
streamingCacheSize.toInt() == 0 -> true
|
||||
streamingCacheSize > 0 && bypassCache -> true
|
||||
streamingCacheSize > 0 && !bypassCache -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
val dataSourceFactory: DataSource.Factory = if (useUpstream) {
|
||||
DownloadUtil.getUpstreamDataSourceFactory(context)
|
||||
} else {
|
||||
DownloadUtil.getCacheDataSourceFactory(context)
|
||||
}
|
||||
|
||||
return when {
|
||||
mediaItem.localConfiguration?.mimeType == MimeTypes.APPLICATION_M3U8 ||
|
||||
mediaItem.localConfiguration?.uri?.lastPathSegment?.endsWith(".m3u8", ignoreCase = true) == true -> {
|
||||
HlsMediaSource.Factory(dataSourceFactory).createMediaSource(mediaItem)
|
||||
}
|
||||
|
||||
else -> {
|
||||
val extractorsFactory: ExtractorsFactory = DefaultExtractorsFactory()
|
||||
ProgressiveMediaSource.Factory(dataSourceFactory, extractorsFactory)
|
||||
.createMediaSource(mediaItem)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun setDrmSessionManagerProvider(drmSessionManagerProvider: DrmSessionManagerProvider): MediaSource.Factory {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun setLoadErrorHandlingPolicy(loadErrorHandlingPolicy: LoadErrorHandlingPolicy): MediaSource.Factory {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getSupportedTypes(): IntArray {
|
||||
return intArrayOf(
|
||||
C.CONTENT_TYPE_HLS,
|
||||
C.CONTENT_TYPE_OTHER
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,7 @@ import androidx.media3.common.MediaItem;
|
|||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.MimeTypes;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.HeartRating;
|
||||
|
||||
import com.cappielloantonio.tempo.App;
|
||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||
|
|
@ -16,6 +17,7 @@ import com.cappielloantonio.tempo.repository.DownloadRepository;
|
|||
import com.cappielloantonio.tempo.subsonic.models.Child;
|
||||
import com.cappielloantonio.tempo.subsonic.models.InternetRadioStation;
|
||||
import com.cappielloantonio.tempo.subsonic.models.PodcastEpisode;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
|
@ -83,6 +85,13 @@ public class MappingUtil {
|
|||
.setAlbumTitle(media.getAlbum())
|
||||
.setArtist(media.getArtist())
|
||||
.setArtworkUri(artworkUri)
|
||||
.setUserRating(new HeartRating(media.getStarred() != null))
|
||||
.setSupportedCommands(
|
||||
ImmutableList.of(
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_ON,
|
||||
Constants.CUSTOM_COMMAND_TOGGLE_HEART_OFF
|
||||
)
|
||||
)
|
||||
.setExtras(bundle)
|
||||
.setIsBrowsable(false)
|
||||
.setIsPlayable(true)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ object Preferences {
|
|||
private const val WIFI_ONLY = "wifi_only"
|
||||
private const val DATA_SAVING_MODE = "data_saving_mode"
|
||||
private const val SERVER_UNREACHABLE = "server_unreachable"
|
||||
private const val SYNC_STARRED_ARTISTS_FOR_OFFLINE_USE = "sync_starred_artists_for_offline_use"
|
||||
private const val SYNC_STARRED_ALBUMS_FOR_OFFLINE_USE = "sync_starred_albums_for_offline_use"
|
||||
private const val SYNC_STARRED_TRACKS_FOR_OFFLINE_USE = "sync_starred_tracks_for_offline_use"
|
||||
private const val QUEUE_SYNCING = "queue_syncing"
|
||||
|
|
@ -45,6 +46,7 @@ object Preferences {
|
|||
private const val ROUNDED_CORNER_SIZE = "rounded_corner_size"
|
||||
private const val PODCAST_SECTION_VISIBILITY = "podcast_section_visibility"
|
||||
private const val RADIO_SECTION_VISIBILITY = "radio_section_visibility"
|
||||
private const val AUTO_DOWNLOAD_LYRICS = "auto_download_lyrics"
|
||||
private const val MUSIC_DIRECTORY_SECTION_VISIBILITY = "music_directory_section_visibility"
|
||||
private const val REPLAY_GAIN_MODE = "replay_gain_mode"
|
||||
private const val AUDIO_TRANSCODE_PRIORITY = "audio_transcode_priority"
|
||||
|
|
@ -70,7 +72,9 @@ object Preferences {
|
|||
private const val CONTINUOUS_PLAY = "continuous_play"
|
||||
private const val LAST_INSTANT_MIX = "last_instant_mix"
|
||||
private const val ALLOW_PLAYLIST_DUPLICATES = "allow_playlist_duplicates"
|
||||
|
||||
private const val EQUALIZER_ENABLED = "equalizer_enabled"
|
||||
private const val EQUALIZER_BAND_LEVELS = "equalizer_band_levels"
|
||||
private const val MINI_SHUFFLE_BUTTON_VISIBILITY = "mini_shuffle_button_visibility"
|
||||
|
||||
@JvmStatic
|
||||
fun getServer(): String? {
|
||||
|
|
@ -162,6 +166,24 @@ object Preferences {
|
|||
App.getInstance().preferences.edit().putString(OPEN_SUBSONIC_EXTENSIONS, Gson().toJson(extension)).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isAutoDownloadLyricsEnabled(): Boolean {
|
||||
val preferences = App.getInstance().preferences
|
||||
|
||||
if (preferences.contains(AUTO_DOWNLOAD_LYRICS)) {
|
||||
return preferences.getBoolean(AUTO_DOWNLOAD_LYRICS, false)
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setAutoDownloadLyricsEnabled(isEnabled: Boolean) {
|
||||
App.getInstance().preferences.edit()
|
||||
.putBoolean(AUTO_DOWNLOAD_LYRICS, isEnabled)
|
||||
.apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getLocalAddress(): String? {
|
||||
return App.getInstance().preferences.getString(LOCAL_ADDRESS, null)
|
||||
|
|
@ -303,6 +325,18 @@ object Preferences {
|
|||
.apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isStarredArtistsSyncEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(SYNC_STARRED_ARTISTS_FOR_OFFLINE_USE, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setStarredArtistsSyncEnabled(isStarredSyncEnabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(
|
||||
SYNC_STARRED_ARTISTS_FOR_OFFLINE_USE, isStarredSyncEnabled
|
||||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isStarredAlbumsSyncEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(SYNC_STARRED_ALBUMS_FOR_OFFLINE_USE, false)
|
||||
|
|
@ -327,6 +361,16 @@ object Preferences {
|
|||
).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showShuffleInsteadOfHeart(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(MINI_SHUFFLE_BUTTON_VISIBILITY, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setShuffleInsteadOfHeart(enabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(MINI_SHUFFLE_BUTTON_VISIBILITY, enabled).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun showServerUnreachableDialog(): Boolean {
|
||||
return App.getInstance().preferences.getLong(
|
||||
|
|
@ -552,4 +596,31 @@ object Preferences {
|
|||
fun allowPlaylistDuplicates(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(ALLOW_PLAYLIST_DUPLICATES, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setEqualizerEnabled(enabled: Boolean) {
|
||||
App.getInstance().preferences.edit().putBoolean(EQUALIZER_ENABLED, enabled).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun isEqualizerEnabled(): Boolean {
|
||||
return App.getInstance().preferences.getBoolean(EQUALIZER_ENABLED, false)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun setEqualizerBandLevels(bandLevels: ShortArray) {
|
||||
val asString = bandLevels.joinToString(",")
|
||||
App.getInstance().preferences.edit().putString(EQUALIZER_BAND_LEVELS, asString).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getEqualizerBandLevels(bandCount: Short): ShortArray {
|
||||
val str = App.getInstance().preferences.getString(EQUALIZER_BAND_LEVELS, null)
|
||||
if (str.isNullOrBlank()) {
|
||||
return ShortArray(bandCount.toInt())
|
||||
}
|
||||
val parts = str.split(",")
|
||||
if (parts.size < bandCount) return ShortArray(bandCount.toInt())
|
||||
return ShortArray(bandCount.toInt()) { i -> parts[i].toShortOrNull() ?: 0 }
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue