mirror of
https://github.com/antebudimir/tempus.git
synced 2025-12-31 17:43:32 +00:00
improve battery consumption (#223)
This commit is contained in:
commit
24eead2d0a
5 changed files with 221 additions and 73 deletions
|
|
@ -4,7 +4,12 @@ import android.annotation.SuppressLint
|
||||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import android.app.TaskStackBuilder
|
import android.app.TaskStackBuilder
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
|
|
@ -13,15 +18,21 @@ import android.os.Bundle
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.media3.common.*
|
import androidx.media3.common.*
|
||||||
|
import androidx.media3.common.Player.REPEAT_MODE_ALL
|
||||||
|
import androidx.media3.common.Player.RepeatMode
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.exoplayer.DefaultLoadControl
|
import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.MediaSource
|
import androidx.media3.exoplayer.source.MediaSource
|
||||||
import androidx.media3.session.*
|
import androidx.media3.session.*
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.cappielloantonio.tempo.R
|
import com.cappielloantonio.tempo.R
|
||||||
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest
|
||||||
import com.cappielloantonio.tempo.repository.QueueRepository
|
import com.cappielloantonio.tempo.repository.QueueRepository
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||||
import com.cappielloantonio.tempo.util.AssetLinkUtil
|
import com.cappielloantonio.tempo.util.AssetLinkUtil
|
||||||
|
|
@ -35,6 +46,7 @@ import com.cappielloantonio.tempo.widget.WidgetUpdateManager
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
|
|
@ -53,7 +65,7 @@ class MediaService : MediaLibraryService() {
|
||||||
private var widgetUpdateScheduled = false
|
private var widgetUpdateScheduled = false
|
||||||
private val widgetUpdateRunnable = object : Runnable {
|
private val widgetUpdateRunnable = object : Runnable {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
if (!player.isPlaying) {
|
if (!player.isPlaying || !screenOn) {
|
||||||
widgetUpdateScheduled = false
|
widgetUpdateScheduled = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -62,6 +74,29 @@ class MediaService : MediaLibraryService() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var prevPlayerStates = Triple(false, false, -1)
|
||||||
|
@Volatile private var nowPlayingChanged = false
|
||||||
|
@Volatile private var artCacheUpdated = false
|
||||||
|
@Volatile private var artCache : Bitmap? = null
|
||||||
|
@Volatile private var screenOn = true
|
||||||
|
|
||||||
|
val broadCastReceiver = object : BroadcastReceiver() {
|
||||||
|
override fun onReceive(contxt: Context?, intent: Intent?) {
|
||||||
|
when (intent?.action) {
|
||||||
|
Intent.ACTION_SCREEN_ON -> {
|
||||||
|
Log.d("MediaService", "screenOn");
|
||||||
|
screenOn = true
|
||||||
|
widgetUpdateHandler.post(widgetUpdateRunnable)
|
||||||
|
}
|
||||||
|
Intent.ACTION_SCREEN_OFF -> {
|
||||||
|
Log.d("MediaService", "screenOff");
|
||||||
|
screenOn = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
inner class LocalBinder : Binder() {
|
inner class LocalBinder : Binder() {
|
||||||
fun getEqualizerManager(): EqualizerManager {
|
fun getEqualizerManager(): EqualizerManager {
|
||||||
return this@MediaService.equalizerManager
|
return this@MediaService.equalizerManager
|
||||||
|
|
@ -84,16 +119,6 @@ class MediaService : MediaLibraryService() {
|
||||||
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
|
const val ACTION_BIND_EQUALIZER = "com.cappielloantonio.tempo.service.BIND_EQUALIZER"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateMediaItems() {
|
|
||||||
Log.d("MediaService", "update items");
|
|
||||||
val n = player.mediaItemCount
|
|
||||||
val k = player.currentMediaItemIndex
|
|
||||||
val current = player.currentPosition
|
|
||||||
val items = (0 .. n-1).map{i -> MappingUtil.mapMediaItem(player.getMediaItemAt(i))}
|
|
||||||
player.clearMediaItems()
|
|
||||||
player.setMediaItems(items, k, current)
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class CustomNetworkCallback : ConnectivityManager.NetworkCallback() {
|
inner class CustomNetworkCallback : ConnectivityManager.NetworkCallback() {
|
||||||
var wasWifi = false
|
var wasWifi = false
|
||||||
|
|
||||||
|
|
@ -110,7 +135,15 @@ class MediaService : MediaLibraryService() {
|
||||||
if (isWifi != wasWifi) {
|
if (isWifi != wasWifi) {
|
||||||
wasWifi = isWifi
|
wasWifi = isWifi
|
||||||
widgetUpdateHandler.post(Runnable {
|
widgetUpdateHandler.post(Runnable {
|
||||||
updateMediaItems()
|
Log.d("MediaService", "update item due to network change");
|
||||||
|
val pos = player.currentPosition
|
||||||
|
val k = player.currentMediaItemIndex
|
||||||
|
val old = player.getMediaItemAt(k)
|
||||||
|
val item = MappingUtil.mapMediaItem(old)
|
||||||
|
if (item.requestMetadata.mediaUri != old.requestMetadata.mediaUri) {
|
||||||
|
player.replaceMediaItem(k, item)
|
||||||
|
player.seekTo(pos)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -126,6 +159,7 @@ class MediaService : MediaLibraryService() {
|
||||||
initializePlayerListener()
|
initializePlayerListener()
|
||||||
initializeEqualizerManager()
|
initializeEqualizerManager()
|
||||||
initializeNetworkListener()
|
initializeNetworkListener()
|
||||||
|
initializeScreenListener()
|
||||||
|
|
||||||
setPlayer(player)
|
setPlayer(player)
|
||||||
}
|
}
|
||||||
|
|
@ -135,6 +169,7 @@ class MediaService : MediaLibraryService() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
unregisterReceiver(broadCastReceiver)
|
||||||
releaseNetworkCallback()
|
releaseNetworkCallback()
|
||||||
equalizerManager.release()
|
equalizerManager.release()
|
||||||
stopWidgetUpdates()
|
stopWidgetUpdates()
|
||||||
|
|
@ -276,10 +311,23 @@ class MediaService : MediaLibraryService() {
|
||||||
.setLoadControl(initializeLoadControl())
|
.setLoadControl(initializeLoadControl())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
val params = player.trackSelectionParameters.buildUpon()
|
||||||
|
.setAudioOffloadPreferences(
|
||||||
|
TrackSelectionParameters.AudioOffloadPreferences.Builder().setAudioOffloadMode(
|
||||||
|
TrackSelectionParameters.AudioOffloadPreferences.AUDIO_OFFLOAD_MODE_ENABLED
|
||||||
|
).build()
|
||||||
|
).build()
|
||||||
|
player.trackSelectionParameters = params
|
||||||
player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
|
player.shuffleModeEnabled = Preferences.isShuffleModeEnabled()
|
||||||
player.repeatMode = Preferences.getRepeatMode()
|
player.repeatMode = Preferences.getRepeatMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun initializeScreenListener() {
|
||||||
|
val filter = IntentFilter(Intent.ACTION_SCREEN_ON)
|
||||||
|
filter.addAction(Intent.ACTION_SCREEN_OFF)
|
||||||
|
registerReceiver(broadCastReceiver, filter)
|
||||||
|
}
|
||||||
|
|
||||||
private fun initializeEqualizerManager() {
|
private fun initializeEqualizerManager() {
|
||||||
equalizerManager = EqualizerManager()
|
equalizerManager = EqualizerManager()
|
||||||
val audioSessionId = player.audioSessionId
|
val audioSessionId = player.audioSessionId
|
||||||
|
|
@ -315,7 +363,6 @@ class MediaService : MediaLibraryService() {
|
||||||
private fun initializeNetworkListener() {
|
private fun initializeNetworkListener() {
|
||||||
networkCallback = CustomNetworkCallback()
|
networkCallback = CustomNetworkCallback()
|
||||||
getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(networkCallback)
|
getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(networkCallback)
|
||||||
updateMediaItems()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun restorePlayerFromQueue() {
|
private fun restorePlayerFromQueue() {
|
||||||
|
|
@ -359,15 +406,35 @@ class MediaService : MediaLibraryService() {
|
||||||
override fun onTracksChanged(tracks: Tracks) {
|
override fun onTracksChanged(tracks: Tracks) {
|
||||||
ReplayGainUtil.setReplayGain(player, tracks)
|
ReplayGainUtil.setReplayGain(player, tracks)
|
||||||
val currentMediaItem = player.currentMediaItem
|
val currentMediaItem = player.currentMediaItem
|
||||||
if (currentMediaItem != null && currentMediaItem.mediaMetadata.extras != null) {
|
|
||||||
MediaManager.scrobble(currentMediaItem, false)
|
if (currentMediaItem != null) {
|
||||||
|
val item = MappingUtil.mapMediaItem(currentMediaItem)
|
||||||
|
if (item.requestMetadata.mediaUri != currentMediaItem.requestMetadata.mediaUri)
|
||||||
|
player.replaceMediaItem(player.currentMediaItemIndex, item)
|
||||||
|
|
||||||
|
if (item.mediaMetadata.extras != null) {
|
||||||
|
MediaManager.scrobble(item, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.currentMediaItemIndex + 1 == player.mediaItemCount)
|
if (player.currentMediaItemIndex + 1 < player.mediaItemCount)
|
||||||
|
player.replaceMediaItem(
|
||||||
|
player.currentMediaItemIndex + 1,
|
||||||
|
MappingUtil.mapMediaItem(player.getMediaItemAt(player.currentMediaItemIndex + 1)))
|
||||||
|
|
||||||
|
if (player.currentMediaItemIndex + 1 == player.mediaItemCount) {
|
||||||
|
if (player.repeatMode == REPEAT_MODE_ALL && player.mediaItemCount > 1)
|
||||||
|
player.replaceMediaItem(
|
||||||
|
0,
|
||||||
|
MappingUtil.mapMediaItem(player.getMediaItemAt(0)))
|
||||||
MediaManager.continuousPlay(player.currentMediaItem)
|
MediaManager.continuousPlay(player.currentMediaItem)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
|
nowPlayingChanged = true
|
||||||
|
artCacheUpdated = false
|
||||||
|
artCache = null
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
MediaManager.setPlayingPausedTimestamp(
|
MediaManager.setPlayingPausedTimestamp(
|
||||||
player.currentMediaItem,
|
player.currentMediaItem,
|
||||||
|
|
@ -381,7 +448,8 @@ class MediaService : MediaLibraryService() {
|
||||||
} else {
|
} else {
|
||||||
stopWidgetUpdates()
|
stopWidgetUpdates()
|
||||||
}
|
}
|
||||||
updateWidget()
|
if (screenOn)
|
||||||
|
updateWidget()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
|
|
@ -494,6 +562,16 @@ class MediaService : MediaLibraryService() {
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class CustomGlideTarget : CustomTarget<Bitmap>() {
|
||||||
|
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||||
|
artCache = resource
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
|
artCache = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateWidget() {
|
private fun updateWidget() {
|
||||||
val mi = player.currentMediaItem
|
val mi = player.currentMediaItem
|
||||||
val title = mi?.mediaMetadata?.title?.toString()
|
val title = mi?.mediaMetadata?.title?.toString()
|
||||||
|
|
@ -512,21 +590,39 @@ class MediaService : MediaLibraryService() {
|
||||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
|
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
|
||||||
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||||
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||||
WidgetUpdateManager.updateFromState(
|
|
||||||
this,
|
if (!TextUtils.isEmpty(coverId) && nowPlayingChanged) {
|
||||||
title ?: "",
|
CustomGlideRequest.loadAlbumArtBitmap(
|
||||||
artist ?: "",
|
applicationContext,
|
||||||
album ?: "",
|
coverId,
|
||||||
coverId,
|
WidgetUpdateManager.WIDGET_SAFE_ART_SIZE,
|
||||||
player.isPlaying,
|
CustomGlideTarget())
|
||||||
player.shuffleModeEnabled,
|
}
|
||||||
player.repeatMode,
|
|
||||||
position,
|
val newPlayerState = Triple(player.isPlaying, player.shuffleModeEnabled, player.repeatMode)
|
||||||
duration,
|
if (nowPlayingChanged || prevPlayerStates != newPlayerState) {
|
||||||
songLink,
|
WidgetUpdateManager.updateFromState(
|
||||||
albumLink,
|
this,
|
||||||
artistLink
|
title ?: "",
|
||||||
)
|
artist ?: "",
|
||||||
|
album ?: "",
|
||||||
|
Optional.ofNullable(artCache),
|
||||||
|
player.isPlaying,
|
||||||
|
player.shuffleModeEnabled,
|
||||||
|
player.repeatMode,
|
||||||
|
position,
|
||||||
|
duration,
|
||||||
|
songLink,
|
||||||
|
albumLink,
|
||||||
|
artistLink
|
||||||
|
)
|
||||||
|
prevPlayerStates = newPlayerState
|
||||||
|
Log.d("MediaService", "fullUpdate");
|
||||||
|
} else {
|
||||||
|
WidgetUpdateManager.updateProgress(this, position, duration)
|
||||||
|
Log.d("MediaService", "updateProgress");
|
||||||
|
}
|
||||||
|
nowPlayingChanged = false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun scheduleWidgetUpdates() {
|
private fun scheduleWidgetUpdates() {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
|
@ -47,7 +48,7 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
private PlayerBottomSheetViewModel playerBottomSheetViewModel;
|
||||||
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
private ListenableFuture<MediaBrowser> mediaBrowserListenableFuture;
|
||||||
|
|
||||||
private Handler progressBarHandler;
|
private Handler progressBarHandler = null;
|
||||||
private Runnable progressBarRunnable;
|
private Runnable progressBarRunnable;
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
|
@ -66,6 +67,14 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (progressBarHandler != null)
|
||||||
|
progressBarHandler.post(progressBarRunnable);
|
||||||
|
Log.d("Player", "resumed");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
|
|
@ -281,6 +290,10 @@ public class PlayerBottomSheetFragment extends Fragment {
|
||||||
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
|
private void defineProgressBarHandler(MediaBrowser mediaBrowser) {
|
||||||
progressBarHandler = new Handler();
|
progressBarHandler = new Handler();
|
||||||
progressBarRunnable = () -> {
|
progressBarRunnable = () -> {
|
||||||
|
if (!isResumed()) {
|
||||||
|
Log.d("Player", "not resumed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
setProgress(mediaBrowser);
|
setProgress(mediaBrowser);
|
||||||
progressBarHandler.postDelayed(progressBarRunnable, 1000);
|
progressBarHandler.postDelayed(progressBarRunnable, 1000);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,16 @@ import android.graphics.Bitmap;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.widget.RemoteViews;
|
||||||
|
|
||||||
import com.bumptech.glide.request.target.CustomTarget;
|
import com.bumptech.glide.request.target.CustomTarget;
|
||||||
import com.bumptech.glide.request.transition.Transition;
|
import com.bumptech.glide.request.transition.Transition;
|
||||||
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest;
|
||||||
import com.cappielloantonio.tempo.R;
|
import com.cappielloantonio.tempo.R;
|
||||||
|
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.session.MediaController;
|
import androidx.media3.session.MediaController;
|
||||||
import androidx.media3.session.SessionToken;
|
import androidx.media3.session.SessionToken;
|
||||||
|
|
||||||
|
|
@ -23,17 +26,18 @@ import com.cappielloantonio.tempo.util.MusicUtil;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
public final class WidgetUpdateManager {
|
public final class WidgetUpdateManager {
|
||||||
|
|
||||||
private static final int WIDGET_SAFE_ART_SIZE = 512;
|
public static final int WIDGET_SAFE_ART_SIZE = 512;
|
||||||
|
|
||||||
public static void updateFromState(Context ctx,
|
public static void updateFromState(Context ctx,
|
||||||
String title,
|
String title,
|
||||||
String artist,
|
String artist,
|
||||||
String album,
|
String album,
|
||||||
Bitmap art,
|
Optional<Bitmap> art,
|
||||||
boolean playing,
|
boolean playing,
|
||||||
boolean shuffleEnabled,
|
boolean shuffleEnabled,
|
||||||
int repeatMode,
|
int repeatMode,
|
||||||
|
|
@ -51,13 +55,47 @@ public final class WidgetUpdateManager {
|
||||||
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
||||||
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
||||||
for (int id : ids) {
|
for (int id : ids) {
|
||||||
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, album, art, playing,
|
android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, album, art.orElse(null), playing,
|
||||||
timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id);
|
timing.elapsedText, timing.totalText, timing.progress, shuffleEnabled, repeatMode, id);
|
||||||
WidgetProvider.attachIntents(ctx, rv, id, songLink, albumLink, artistLink);
|
WidgetProvider.attachIntents(ctx, rv, id, songLink, albumLink, artistLink);
|
||||||
mgr.updateAppWidget(id, rv);
|
mgr.updateAppWidget(id, rv);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void updateProgress(Context ctx,
|
||||||
|
long positionMs,
|
||||||
|
long durationMs) {
|
||||||
|
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
|
||||||
|
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
||||||
|
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
||||||
|
for (int id : ids) {
|
||||||
|
LayoutSize size = resolveLayoutSize(ctx, id);
|
||||||
|
int layoutRes = 0;
|
||||||
|
switch (size) {
|
||||||
|
case MEDIUM:
|
||||||
|
layoutRes = R.layout.widget_layout_medium;
|
||||||
|
break;
|
||||||
|
case LARGE:
|
||||||
|
layoutRes = R.layout.widget_layout_large_short;
|
||||||
|
break;
|
||||||
|
case EXPANDED:
|
||||||
|
layoutRes = R.layout.widget_layout_large;
|
||||||
|
break;
|
||||||
|
case COMPACT:
|
||||||
|
default:
|
||||||
|
layoutRes = R.layout.widget_layout_compact;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes);
|
||||||
|
int safeProgress = Math.max(0, Math.min(timing.progress, WidgetViewsFactory.PROGRESS_MAX));
|
||||||
|
rv.setTextViewText(R.id.time_elapsed, timing.elapsedText);
|
||||||
|
rv.setTextViewText(R.id.time_total, timing.totalText);
|
||||||
|
rv.setProgressBar(R.id.progress, WidgetViewsFactory.PROGRESS_MAX, safeProgress, false);
|
||||||
|
mgr.updateAppWidget(id, rv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void pushNow(Context ctx) {
|
public static void pushNow(Context ctx) {
|
||||||
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
AppWidgetManager mgr = AppWidgetManager.getInstance(ctx);
|
||||||
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class));
|
||||||
|
|
@ -68,6 +106,7 @@ public final class WidgetUpdateManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void updateFromState(Context ctx,
|
public static void updateFromState(Context ctx,
|
||||||
String title,
|
String title,
|
||||||
String artist,
|
String artist,
|
||||||
|
|
@ -82,17 +121,6 @@ public final class WidgetUpdateManager {
|
||||||
String albumLink,
|
String albumLink,
|
||||||
String artistLink) {
|
String artistLink) {
|
||||||
final Context appCtx = ctx.getApplicationContext();
|
final Context appCtx = ctx.getApplicationContext();
|
||||||
final String t = TextUtils.isEmpty(title) ? appCtx.getString(R.string.widget_not_playing) : title;
|
|
||||||
final String a = TextUtils.isEmpty(artist) ? appCtx.getString(R.string.widget_placeholder_subtitle) : artist;
|
|
||||||
final String alb = !TextUtils.isEmpty(album) ? album : "";
|
|
||||||
final boolean p = playing;
|
|
||||||
final boolean sh = shuffleEnabled;
|
|
||||||
final int rep = repeatMode;
|
|
||||||
final TimingInfo timing = createTimingInfo(positionMs, durationMs);
|
|
||||||
final String songLinkFinal = songLink;
|
|
||||||
final String albumLinkFinal = albumLink;
|
|
||||||
final String artistLinkFinal = artistLink;
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(coverArtId)) {
|
if (!TextUtils.isEmpty(coverArtId)) {
|
||||||
CustomGlideRequest.loadAlbumArtBitmap(
|
CustomGlideRequest.loadAlbumArtBitmap(
|
||||||
appCtx,
|
appCtx,
|
||||||
|
|
@ -101,41 +129,24 @@ public final class WidgetUpdateManager {
|
||||||
new CustomTarget<Bitmap>() {
|
new CustomTarget<Bitmap>() {
|
||||||
@Override
|
@Override
|
||||||
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
|
public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
|
||||||
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
|
updateFromState(ctx, title, artist, album, Optional.of(resource),
|
||||||
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
|
playing, shuffleEnabled, repeatMode, positionMs, durationMs, songLink, albumLink, artistLink);
|
||||||
for (int id : ids) {
|
|
||||||
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, resource, p,
|
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
|
||||||
WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal);
|
|
||||||
mgr.updateAppWidget(id, rv);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onLoadCleared(Drawable placeholder) {
|
public void onLoadCleared(Drawable placeholder) {
|
||||||
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
|
updateFromState(ctx, title, artist, album, Optional.empty(),
|
||||||
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
|
playing, shuffleEnabled, repeatMode, positionMs, durationMs, songLink, albumLink, artistLink);
|
||||||
for (int id : ids) {
|
|
||||||
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
|
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
|
||||||
WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal);
|
|
||||||
mgr.updateAppWidget(id, rv);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx);
|
updateFromState(ctx, title, artist, album, Optional.empty(),
|
||||||
int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class));
|
playing, shuffleEnabled, repeatMode, positionMs, durationMs, songLink, albumLink, artistLink);
|
||||||
for (int id : ids) {
|
|
||||||
android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, alb, null, p,
|
|
||||||
timing.elapsedText, timing.totalText, timing.progress, sh, rep, id);
|
|
||||||
WidgetProvider.attachIntents(appCtx, rv, id, songLinkFinal, albumLinkFinal, artistLinkFinal);
|
|
||||||
mgr.updateAppWidget(id, rv);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public static void refreshFromController(Context ctx) {
|
public static void refreshFromController(Context ctx) {
|
||||||
final Context appCtx = ctx.getApplicationContext();
|
final Context appCtx = ctx.getApplicationContext();
|
||||||
SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class));
|
SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class));
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import com.cappielloantonio.tempo.R;
|
||||||
|
|
||||||
public final class WidgetViewsFactory {
|
public final class WidgetViewsFactory {
|
||||||
|
|
||||||
static final int PROGRESS_MAX = 1000;
|
public static final int PROGRESS_MAX = 1000;
|
||||||
private static final float ALBUM_ART_CORNER_RADIUS_DP = 6f;
|
private static final float ALBUM_ART_CORNER_RADIUS_DP = 6f;
|
||||||
|
|
||||||
private WidgetViewsFactory() {
|
private WidgetViewsFactory() {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@ import android.app.PendingIntent.FLAG_IMMUTABLE
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import android.app.TaskStackBuilder
|
import android.app.TaskStackBuilder
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.Network
|
import android.net.Network
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
|
|
@ -11,6 +13,7 @@ import android.os.Binder
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import android.text.TextUtils
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.media3.cast.CastPlayer
|
import androidx.media3.cast.CastPlayer
|
||||||
|
|
@ -25,7 +28,10 @@ import androidx.media3.exoplayer.DefaultLoadControl
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.session.MediaLibraryService
|
import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
|
import com.bumptech.glide.request.target.CustomTarget
|
||||||
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
import com.cappielloantonio.tempo.repository.AutomotiveRepository
|
||||||
|
import com.cappielloantonio.tempo.glide.CustomGlideRequest
|
||||||
import com.cappielloantonio.tempo.repository.QueueRepository
|
import com.cappielloantonio.tempo.repository.QueueRepository
|
||||||
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
import com.cappielloantonio.tempo.ui.activity.MainActivity
|
||||||
import com.cappielloantonio.tempo.util.AssetLinkUtil
|
import com.cappielloantonio.tempo.util.AssetLinkUtil
|
||||||
|
|
@ -39,6 +45,7 @@ import com.cappielloantonio.tempo.widget.WidgetUpdateManager
|
||||||
import com.google.android.gms.cast.framework.CastContext
|
import com.google.android.gms.cast.framework.CastContext
|
||||||
import com.google.android.gms.common.ConnectionResult
|
import com.google.android.gms.common.ConnectionResult
|
||||||
import com.google.android.gms.common.GoogleApiAvailability
|
import com.google.android.gms.common.GoogleApiAvailability
|
||||||
|
import java.util.Optional
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
|
|
@ -49,6 +56,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
private lateinit var librarySessionCallback: MediaLibrarySessionCallback
|
private lateinit var librarySessionCallback: MediaLibrarySessionCallback
|
||||||
private lateinit var networkCallback: CustomNetworkCallback
|
private lateinit var networkCallback: CustomNetworkCallback
|
||||||
lateinit var equalizerManager: EqualizerManager
|
lateinit var equalizerManager: EqualizerManager
|
||||||
|
@Volatile private var artCache : Optional<Optional<Bitmap>> = Optional.empty<Optional<Bitmap>>()
|
||||||
|
|
||||||
inner class LocalBinder : Binder() {
|
inner class LocalBinder : Binder() {
|
||||||
fun getEqualizerManager(): EqualizerManager {
|
fun getEqualizerManager(): EqualizerManager {
|
||||||
|
|
@ -278,6 +286,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
|
artCache = Optional.empty()
|
||||||
if (!isPlaying) {
|
if (!isPlaying) {
|
||||||
MediaManager.setPlayingPausedTimestamp(
|
MediaManager.setPlayingPausedTimestamp(
|
||||||
player.currentMediaItem,
|
player.currentMediaItem,
|
||||||
|
|
@ -339,6 +348,16 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class CustomGlideTarget : CustomTarget<Bitmap>() {
|
||||||
|
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||||
|
artCache = Optional.of(Optional.of(resource))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLoadCleared(placeholder: Drawable?) {
|
||||||
|
artCache = Optional.of(Optional.empty())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun updateWidget() {
|
private fun updateWidget() {
|
||||||
val mi = player.currentMediaItem
|
val mi = player.currentMediaItem
|
||||||
val title = mi?.mediaMetadata?.title?.toString()
|
val title = mi?.mediaMetadata?.title?.toString()
|
||||||
|
|
@ -357,12 +376,21 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener {
|
||||||
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
|
?: AssetLinkUtil.buildLink(AssetLinkUtil.TYPE_ARTIST, extras?.getString("artistId"))
|
||||||
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
val position = player.currentPosition.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||||
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
val duration = player.duration.takeIf { it != C.TIME_UNSET } ?: 0L
|
||||||
|
|
||||||
|
if (!TextUtils.isEmpty(coverId) && artCache.isEmpty) {
|
||||||
|
CustomGlideRequest.loadAlbumArtBitmap(
|
||||||
|
applicationContext,
|
||||||
|
coverId,
|
||||||
|
WidgetUpdateManager.WIDGET_SAFE_ART_SIZE,
|
||||||
|
CustomGlideTarget())
|
||||||
|
}
|
||||||
|
|
||||||
WidgetUpdateManager.updateFromState(
|
WidgetUpdateManager.updateFromState(
|
||||||
this,
|
this,
|
||||||
title ?: "",
|
title ?: "",
|
||||||
artist ?: "",
|
artist ?: "",
|
||||||
album ?: "",
|
album ?: "",
|
||||||
coverId,
|
artCache,
|
||||||
player.isPlaying,
|
player.isPlaying,
|
||||||
player.shuffleModeEnabled,
|
player.shuffleModeEnabled,
|
||||||
player.repeatMode,
|
player.repeatMode,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue