diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index cc9990e7..816683ca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -73,5 +73,20 @@ android:name="autoStoreLocales" android:value="true" /> + + + + + + + + + + - \ No newline at end of file + diff --git a/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideRequest.java b/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideRequest.java index fe57c163..8e49111f 100644 --- a/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideRequest.java +++ b/app/src/main/java/com/cappielloantonio/tempo/glide/CustomGlideRequest.java @@ -1,6 +1,7 @@ package com.cappielloantonio.tempo.glide; import android.content.Context; +import android.graphics.Bitmap; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.util.Log; @@ -16,6 +17,7 @@ import com.bumptech.glide.load.resource.bitmap.CenterCrop; import com.bumptech.glide.load.resource.bitmap.RoundedCorners; import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions; import com.bumptech.glide.request.RequestOptions; +import com.bumptech.glide.request.target.CustomTarget; import com.bumptech.glide.signature.ObjectKey; import com.cappielloantonio.tempo.App; import com.cappielloantonio.tempo.R; @@ -109,6 +111,18 @@ public class CustomGlideRequest { return uri.toString(); } + public static void loadAlbumArtBitmap(Context context, + String coverId, + int size, + CustomTarget target) { + String url = createUrl(coverId, size); + Glide.with(context) + .asBitmap() + .load(url) + .apply(createRequestOptions(context, coverId, ResourceType.Album)) + .into(target); + } + public static class Builder { private final RequestManager requestManager; private Object item; diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java new file mode 100644 index 00000000..c035fa78 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetActions.java @@ -0,0 +1,44 @@ +package com.cappielloantonio.tempo.widget; + +import android.content.ComponentName; +import android.content.Context; +import android.util.Log; + +import androidx.media3.session.MediaController; +import androidx.media3.session.SessionToken; + +import com.cappielloantonio.tempo.service.MediaService; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; + +import java.util.concurrent.ExecutionException; + +public final class WidgetActions { + public static void dispatchToMediaSession(Context ctx, String action) { + Log.d("TempoWidget", "dispatch action=" + action); + Context appCtx = ctx.getApplicationContext(); + SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class)); + ListenableFuture future = new MediaController.Builder(appCtx, token).buildAsync(); + future.addListener(() -> { + try { + if (!future.isDone()) return; + MediaController c = future.get(); + Log.d("TempoWidget", "controller connected, isPlaying=" + c.isPlaying()); + switch (action) { + case WidgetProvider.ACT_PLAY_PAUSE: + if (c.isPlaying()) c.pause(); else c.play(); + break; + case WidgetProvider.ACT_NEXT: + c.seekToNext(); + break; + case WidgetProvider.ACT_PREV: + c.seekToPrevious(); + break; + } + c.release(); + } catch (ExecutionException | InterruptedException e) { + Log.e("TempoWidget", "dispatch failed", e); + } + }, MoreExecutors.directExecutor()); + } +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java new file mode 100644 index 00000000..2b4b1f3c --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider.java @@ -0,0 +1,82 @@ +package com.cappielloantonio.tempo.widget; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.Context; +import android.content.Intent; +import android.widget.RemoteViews; + +import com.cappielloantonio.tempo.R; +import android.app.TaskStackBuilder; +import android.app.PendingIntent; +import com.cappielloantonio.tempo.ui.activity.MainActivity; +import android.util.Log; + +public class WidgetProvider extends AppWidgetProvider { + private static final String TAG = "TempoWidget"; + public static final String ACT_PLAY_PAUSE = "tempo.widget.PLAY_PAUSE"; + public static final String ACT_NEXT = "tempo.widget.NEXT"; + public static final String ACT_PREV = "tempo.widget.PREV"; + + @Override public void onUpdate(Context ctx, AppWidgetManager mgr, int[] ids) { + for (int id : ids) { + RemoteViews rv = WidgetUpdateManager.chooseBuild(ctx, id); + attachIntents(ctx, rv, id); + mgr.updateAppWidget(id, rv); + } + } + + @Override public void onReceive(Context ctx, Intent intent) { + super.onReceive(ctx, intent); + String a = intent.getAction(); + Log.d(TAG, "onReceive action=" + a); + if (ACT_PLAY_PAUSE.equals(a) || ACT_NEXT.equals(a) || ACT_PREV.equals(a)) { + WidgetActions.dispatchToMediaSession(ctx, a); + } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(a)) { + WidgetUpdateManager.refreshFromController(ctx); + } + } + + @Override public void onAppWidgetOptionsChanged(Context context, AppWidgetManager appWidgetManager, int appWidgetId, android.os.Bundle newOptions) { + super.onAppWidgetOptionsChanged(context, appWidgetManager, appWidgetId, newOptions); + RemoteViews rv = WidgetUpdateManager.chooseBuild(context, appWidgetId); + attachIntents(context, rv, appWidgetId); + appWidgetManager.updateAppWidget(appWidgetId, rv); + WidgetUpdateManager.refreshFromController(context); + } + + public static void attachIntents(Context ctx, RemoteViews rv) { + attachIntents(ctx, rv, 0); + } + + public static void attachIntents(Context ctx, RemoteViews rv, int requestCodeBase) { + PendingIntent playPause = PendingIntent.getBroadcast( + ctx, + requestCodeBase + 0, + new Intent(ctx, WidgetProvider4x1.class).setAction(ACT_PLAY_PAUSE), + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT + ); + PendingIntent next = PendingIntent.getBroadcast( + ctx, + requestCodeBase + 1, + new Intent(ctx, WidgetProvider4x1.class).setAction(ACT_NEXT), + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT + ); + PendingIntent prev = PendingIntent.getBroadcast( + ctx, + requestCodeBase + 2, + new Intent(ctx, WidgetProvider4x1.class).setAction(ACT_PREV), + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT + ); + + rv.setOnClickPendingIntent(R.id.btn_play_pause, playPause); + rv.setOnClickPendingIntent(R.id.btn_next, next); + rv.setOnClickPendingIntent(R.id.btn_prev, prev); + + PendingIntent launch = TaskStackBuilder.create(ctx) + .addNextIntentWithParentStack(new Intent(ctx, MainActivity.class)) + .getPendingIntent(requestCodeBase + 10, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); + rv.setOnClickPendingIntent(R.id.root, launch); + } +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider4x1.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider4x1.java new file mode 100644 index 00000000..79ef6af1 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetProvider4x1.java @@ -0,0 +1,8 @@ +package com.cappielloantonio.tempo.widget; + +/** + * AppWidget provider entry for the 4x1 widget card. Inherits all behavior + * from {@link WidgetProvider}. + */ +public class WidgetProvider4x1 extends WidgetProvider {} + diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java new file mode 100644 index 00000000..6de08905 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetUpdateManager.java @@ -0,0 +1,146 @@ +package com.cappielloantonio.tempo.widget; + +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.graphics.Bitmap; +import android.text.TextUtils; +import android.graphics.drawable.Drawable; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.transition.Transition; +import com.cappielloantonio.tempo.glide.CustomGlideRequest; +import com.cappielloantonio.tempo.R; +import androidx.media3.session.MediaController; +import androidx.media3.session.SessionToken; +import com.cappielloantonio.tempo.service.MediaService; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.concurrent.ExecutionException; + +public final class WidgetUpdateManager { + + public static void updateFromState(Context ctx, + String title, + String artist, + Bitmap art, + boolean playing) { + if (TextUtils.isEmpty(title)) title = ctx.getString(R.string.widget_not_playing); + if (TextUtils.isEmpty(artist)) artist = ctx.getString(R.string.widget_placeholder_subtitle); + + AppWidgetManager mgr = AppWidgetManager.getInstance(ctx); + int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class)); + for (int id : ids) { + android.widget.RemoteViews rv = choosePopulate(ctx, title, artist, art, playing, id); + WidgetProvider.attachIntents(ctx, rv, id); + mgr.updateAppWidget(id, rv); + } + } + + public static void pushNow(Context ctx) { + AppWidgetManager mgr = AppWidgetManager.getInstance(ctx); + int[] ids = mgr.getAppWidgetIds(new ComponentName(ctx, WidgetProvider4x1.class)); + for (int id : ids) { + android.widget.RemoteViews rv = chooseBuild(ctx, id); + WidgetProvider.attachIntents(ctx, rv, id); + mgr.updateAppWidget(id, rv); + } + } + + public static void updateFromState(Context ctx, + String title, + String artist, + String coverArtId, + boolean playing) { + 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 boolean p = playing; + + if (!TextUtils.isEmpty(coverArtId)) { + CustomGlideRequest.loadAlbumArtBitmap( + appCtx, + coverArtId, + com.cappielloantonio.tempo.util.Preferences.getImageSize(), + new CustomTarget() { + @Override public void onResourceReady(Bitmap resource, Transition transition) { + AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx); + int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class)); + for (int id : ids) { + android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, resource, p, id); + WidgetProvider.attachIntents(appCtx, rv, id); + mgr.updateAppWidget(id, rv); + } + } + + @Override public void onLoadCleared(Drawable placeholder) { + AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx); + int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class)); + for (int id : ids) { + android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, null, p, id); + WidgetProvider.attachIntents(appCtx, rv, id); + mgr.updateAppWidget(id, rv); + } + } + } + ); + } else { + AppWidgetManager mgr = AppWidgetManager.getInstance(appCtx); + int[] ids = mgr.getAppWidgetIds(new ComponentName(appCtx, WidgetProvider4x1.class)); + for (int id : ids) { + android.widget.RemoteViews rv = choosePopulate(appCtx, t, a, null, p, id); + WidgetProvider.attachIntents(appCtx, rv, id); + mgr.updateAppWidget(id, rv); + } + } + } + + public static void refreshFromController(Context ctx) { + final Context appCtx = ctx.getApplicationContext(); + SessionToken token = new SessionToken(appCtx, new ComponentName(appCtx, MediaService.class)); + ListenableFuture future = new MediaController.Builder(appCtx, token).buildAsync(); + future.addListener(() -> { + try { + if (!future.isDone()) return; + MediaController c = future.get(); + androidx.media3.common.MediaItem mi = c.getCurrentMediaItem(); + String title = null, artist = null, coverId = null; + if (mi != null && mi.mediaMetadata != null) { + if (mi.mediaMetadata.title != null) title = mi.mediaMetadata.title.toString(); + if (mi.mediaMetadata.artist != null) artist = mi.mediaMetadata.artist.toString(); + if (mi.mediaMetadata.extras != null) { + if (title == null) title = mi.mediaMetadata.extras.getString("title"); + if (artist == null) artist = mi.mediaMetadata.extras.getString("artist"); + coverId = mi.mediaMetadata.extras.getString("coverArtId"); + } + } + updateFromState(appCtx, + title != null ? title : appCtx.getString(R.string.widget_not_playing), + artist != null ? artist : appCtx.getString(R.string.widget_placeholder_subtitle), + coverId, + c.isPlaying()); + c.release(); + } catch (ExecutionException | InterruptedException ignored) { + } + }, MoreExecutors.directExecutor()); + } + + public static android.widget.RemoteViews chooseBuild(Context ctx, int appWidgetId) { + if (isLarge(ctx, appWidgetId)) return WidgetViewsFactory.buildLarge(ctx); + return WidgetViewsFactory.buildCompact(ctx); + } + + private static android.widget.RemoteViews choosePopulate(Context ctx, String title, String artist, Bitmap art, boolean playing, int appWidgetId) { + if (isLarge(ctx, appWidgetId)) return WidgetViewsFactory.populateLarge(ctx, title, artist, art, playing); + return WidgetViewsFactory.populate(ctx, title, artist, art, playing); + } + + private static boolean isLarge(Context ctx, int appWidgetId) { + AppWidgetManager mgr = AppWidgetManager.getInstance(ctx); + android.os.Bundle opts = mgr.getAppWidgetOptions(appWidgetId); + int minH = opts != null ? opts.getInt(AppWidgetManager.OPTION_APPWIDGET_MIN_HEIGHT) : 0; + int threshold = ctx.getResources().getInteger(com.cappielloantonio.tempo.R.integer.widget_large_min_height_dp); + return minH >= threshold; // dp threshold for 2-row height + } + + +} diff --git a/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java new file mode 100644 index 00000000..9dec3d60 --- /dev/null +++ b/app/src/main/java/com/cappielloantonio/tempo/widget/WidgetViewsFactory.java @@ -0,0 +1,68 @@ +package com.cappielloantonio.tempo.widget; + +import android.content.Context; +import android.graphics.Bitmap; +import android.widget.RemoteViews; +import com.cappielloantonio.tempo.R; + +public final class WidgetViewsFactory { + + public static RemoteViews buildCompact(Context ctx) { + return build(ctx, R.layout.widget_layout_compact); + } + + public static RemoteViews buildLarge(Context ctx) { + return build(ctx, R.layout.widget_layout_large); + } + + + private static RemoteViews build(Context ctx, int layoutRes) { + RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes); + rv.setTextViewText(R.id.title, ctx.getString(R.string.widget_not_playing)); + rv.setTextViewText(R.id.subtitle, ctx.getString(R.string.widget_placeholder_subtitle)); + rv.setImageViewResource(R.id.btn_play_pause, R.drawable.ic_play); + // Show Tempo logo when nothing is playing + rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo); + return rv; + } + + public static RemoteViews populate(Context ctx, + String title, + String subtitle, + Bitmap art, + boolean playing) { + return populateWithLayout(ctx, title, subtitle, art, playing, R.layout.widget_layout_compact); + } + + public static RemoteViews populateLarge(Context ctx, + String title, + String subtitle, + Bitmap art, + boolean playing) { + return populateWithLayout(ctx, title, subtitle, art, playing, R.layout.widget_layout_large); + } + + + private static RemoteViews populateWithLayout(Context ctx, + String title, + String subtitle, + Bitmap art, + boolean playing, + int layoutRes) { + RemoteViews rv = new RemoteViews(ctx.getPackageName(), layoutRes); + rv.setTextViewText(R.id.title, title); + rv.setTextViewText(R.id.subtitle, subtitle); + + if (art != null) { + rv.setImageViewBitmap(R.id.album_art, art); + } else { + // Fallback to app logo when art is missing + rv.setImageViewResource(R.id.album_art, R.drawable.ic_splash_logo); + } + + rv.setImageViewResource(R.id.btn_play_pause, + playing ? R.drawable.ic_pause : R.drawable.ic_play); + + return rv; + } +} diff --git a/app/src/main/res/drawable/widget_bg.xml b/app/src/main/res/drawable/widget_bg.xml new file mode 100644 index 00000000..c569bbeb --- /dev/null +++ b/app/src/main/res/drawable/widget_bg.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/app/src/main/res/layout/widget_layout_compact.xml b/app/src/main/res/layout/widget_layout_compact.xml new file mode 100644 index 00000000..8f139917 --- /dev/null +++ b/app/src/main/res/layout/widget_layout_compact.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_layout_large.xml b/app/src/main/res/layout/widget_layout_large.xml new file mode 100644 index 00000000..08a97931 --- /dev/null +++ b/app/src/main/res/layout/widget_layout_large.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_preview_compact.xml b/app/src/main/res/layout/widget_preview_compact.xml new file mode 100644 index 00000000..e369ffb6 --- /dev/null +++ b/app/src/main/res/layout/widget_preview_compact.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/colors_widget.xml b/app/src/main/res/values-night/colors_widget.xml new file mode 100644 index 00000000..7bda2da7 --- /dev/null +++ b/app/src/main/res/values-night/colors_widget.xml @@ -0,0 +1,7 @@ + + + #CC000000 + #FFFFFFFF + #B3FFFFFF + #FFFFFFFF + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 698b8eb9..dc6f4c3e 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -90,6 +90,7 @@ İndirilenler İki veya daha fazla filtre seçin Filtre + Sanatçıları filtrele Türleri filtrele (%1$d) (+%1$d) @@ -116,6 +117,7 @@ İndir Bu parçaların indirilmesi önemli miktarda veri kullanabilir Eşitlenecek bazı yıldızlı parçalar var gibi görünüyor + Yıldız ile işaretlenen albümler çevrimdışı kullanılabilir olacak En iyiler Keşfet Tümünü karıştır @@ -164,6 +166,7 @@ Ekle Çalma listesine ekle Tümünü indir + Albümü oyla İndir Tümü İndirilenler @@ -192,6 +195,7 @@ Yıl %1$.2fx Çalma sırasını temizle + Kayıtlı oynatma sırası Sunucu önceliği Bilinmeyen format Dönüştürme @@ -311,6 +315,7 @@ Etkinleştirildiğinde podcast bölümü görüntülenir. Tam etkili olması için uygulamayı yeniden başlatın. Ses kalitesini göster Her ses parçası için bit hızı ve ses formatı gösterilecektir. + " " Öğe değerlemesini göster Etkinleştirildiğinde, öğenin puanı ve favori olarak işaretlenip işaretlenmediği görüntülenir. Eşitleme zamanlayıcısı @@ -340,6 +345,7 @@ Dönüştürülmüş medyayı indir. Etkinleştirilirse indirme uç noktası kullanılmaz, bunun yerine aşağıdaki ayarlar geçerli olur. \n\n “İndirmeler için dönüştürme formatı” “Doğrudan indir” olarak ayarlanırsa dosyanın bit hızı değiştirilmez. Dosya anlık olarak dönüştürüldüğünde, istemci genellikle parçanın süresini göstermez. Bu işlevi destekleyen sunuculardan çalınan parçanın süresini tahmin etmeleri istenebilir, ancak yanıt süreleri daha uzun olabilir. + Çevrimdışı kullanım için yıldızlı albümleri senkronize et Etkinleştirildiğinde, yıldızlı parçalar çevrimdışı kullanım için indirilecektir. Çevrimdışı kullanım için yıldızlı parçaları eşitle Tema @@ -395,6 +401,8 @@ Devam et ve indir Yıldızlı parçaların indirilmesi yüksek miktarda veri gerektirebilir. Yıldızlı parçaları eşitle + Yıldızlı albümleri indirmek yüksek miktarda veri kullanımı gerektirebilir. + Yıldızlı albümleri senkronize et Değişikliklerin geçerli olması için uygulamayı yeniden başlatın. Önbelleğe alınmış dosyaların hedefini bir depolamadan diğerine değiştirmek, önceki depolamadaki önbellek dosyalarının silinmesine yol açabilir. Depolama seçeneğini seç @@ -433,4 +441,14 @@ unDraw İllüstrasyonlarıyla bu uygulamayı daha güzel hale getirmemize yardımcı olan unDraw’a özel teşekkürler. https://undraw.co/ + Yıldızlı Albümleri Senkronize Et + Tempo Widget + Şu an oynatılmıyor + Tempo’yu aç + Albüm kapağı + Çal/Duraklat + Sonraki parça + Önceki parça + Şarkının yıldız derecelendirmesini göster + "Etkinleştirildiğinde yıldızlı albümler çevrimdışı kullanım için indirilecek. " diff --git a/app/src/main/res/values/colors_widget.xml b/app/src/main/res/values/colors_widget.xml new file mode 100644 index 00000000..75a9f7f1 --- /dev/null +++ b/app/src/main/res/values/colors_widget.xml @@ -0,0 +1,8 @@ + + + + #CCFFFFFF + #DE000000 + #99000000 + #DE000000 + diff --git a/app/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml new file mode 100644 index 00000000..7b968814 --- /dev/null +++ b/app/src/main/res/values/integers.xml @@ -0,0 +1,4 @@ + + + 100 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 40febf58..483b4958 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -439,6 +439,13 @@ unDraw A special thanks goes to unDraw without whose illustrations we could not have made this application more beautiful. https://undraw.co/ + Tempo Widget + Not playing + Open Tempo + Album artwork + Play or pause + Next track + Previous track %d album to sync %d albums to sync diff --git a/app/src/main/res/xml/widget_info.xml b/app/src/main/res/xml/widget_info.xml new file mode 100644 index 00000000..03e072ce --- /dev/null +++ b/app/src/main/res/xml/widget_info.xml @@ -0,0 +1,10 @@ + + diff --git a/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt index 66b12ae5..de6a6f06 100644 --- a/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt +++ b/app/src/notquitemy/java/com/cappielloantonio/tempo/service/MediaService.kt @@ -23,6 +23,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory import com.cappielloantonio.tempo.util.Preferences import com.cappielloantonio.tempo.util.ReplayGainUtil +import com.cappielloantonio.tempo.widget.WidgetUpdateManager import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture @@ -260,6 +261,7 @@ class MediaService : MediaLibraryService() { if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) { MediaManager.setLastPlayedTimestamp(mediaItem) } + updateWidget() } override fun onTracksChanged(tracks: Tracks) { @@ -279,6 +281,7 @@ class MediaService : MediaLibraryService() { } else { MediaManager.scrobble(player.currentMediaItem, false) } + updateWidget() } override fun onPlaybackStateChanged(playbackState: Int) { @@ -290,6 +293,7 @@ class MediaService : MediaLibraryService() { MediaManager.scrobble(player.currentMediaItem, true) MediaManager.saveChronology(player.currentMediaItem) } + updateWidget() } override fun onPositionDiscontinuity( @@ -383,5 +387,25 @@ class MediaService : MediaLibraryService() { .build() } + private fun updateWidget() { + val mi = player.currentMediaItem + val title = mi?.mediaMetadata?.title?.toString() + ?: mi?.mediaMetadata?.extras?.getString("title") + val artist = mi?.mediaMetadata?.artist?.toString() + ?: mi?.mediaMetadata?.extras?.getString("artist") + val coverId = mi?.mediaMetadata?.extras?.getString("coverArtId") + WidgetUpdateManager.updateFromState( + this, + title ?: "", + artist ?: "", + coverId, + player.isPlaying + ) + } + + private fun getRenderersFactory() = DownloadUtil.buildRenderersFactory(this, false) -} \ No newline at end of file + + private fun getMediaSourceFactory() = + DefaultMediaSourceFactory(this).setDataSourceFactory(DownloadUtil.getDataSourceFactory(this)) +} diff --git a/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt index 99d6dc22..dadf0d7d 100644 --- a/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt +++ b/app/src/play/java/com/cappielloantonio/tempo/service/MediaService.kt @@ -25,6 +25,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory import com.cappielloantonio.tempo.util.Preferences import com.cappielloantonio.tempo.util.ReplayGainUtil +import com.cappielloantonio.tempo.widget.WidgetUpdateManager import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability @@ -161,6 +162,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) { MediaManager.setLastPlayedTimestamp(mediaItem) } + updateWidget() } override fun onTracksChanged(tracks: Tracks) { @@ -180,6 +182,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { } else { MediaManager.scrobble(player.currentMediaItem, false) } + updateWidget() } override fun onPlaybackStateChanged(playbackState: Int) { @@ -192,6 +195,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { MediaManager.scrobble(player.currentMediaItem, true) MediaManager.saveChronology(player.currentMediaItem) } + updateWidget() } override fun onPositionDiscontinuity( @@ -229,6 +233,25 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { }) } + private fun updateWidget() { + val mi = player.currentMediaItem + val title = mi?.mediaMetadata?.title?.toString() + ?: mi?.mediaMetadata?.extras?.getString("title") + val artist = mi?.mediaMetadata?.artist?.toString() + ?: mi?.mediaMetadata?.extras?.getString("artist") + val coverId = mi?.mediaMetadata?.extras?.getString("coverArtId") + + WidgetUpdateManager.updateFromState( + this, + title ?: "", + artist ?: "", + coverId, + player.isPlaying, + player.currentPosition, + player.duration + ) + } + private fun initializeLoadControl(): DefaultLoadControl { return DefaultLoadControl.Builder() .setBufferDurationsMs( diff --git a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt index 486d2352..4c9c77aa 100644 --- a/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt +++ b/app/src/tempo/java/com/cappielloantonio/tempo/service/MediaService.kt @@ -25,6 +25,7 @@ import com.cappielloantonio.tempo.util.DownloadUtil import com.cappielloantonio.tempo.util.DynamicMediaSourceFactory import com.cappielloantonio.tempo.util.Preferences import com.cappielloantonio.tempo.util.ReplayGainUtil +import com.cappielloantonio.tempo.widget.WidgetUpdateManager import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.common.ConnectionResult import com.google.android.gms.common.GoogleApiAvailability @@ -161,6 +162,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_SEEK || reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) { MediaManager.setLastPlayedTimestamp(mediaItem) } + updateWidget() } override fun onTracksChanged(tracks: Tracks) { @@ -180,6 +182,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { } else { MediaManager.scrobble(player.currentMediaItem, false) } + updateWidget() } override fun onPlaybackStateChanged(playbackState: Int) { @@ -192,6 +195,7 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { MediaManager.scrobble(player.currentMediaItem, true) MediaManager.saveChronology(player.currentMediaItem) } + updateWidget() } override fun onPositionDiscontinuity( @@ -229,6 +233,24 @@ class MediaService : MediaLibraryService(), SessionAvailabilityListener { }) } + private fun updateWidget() { + val mi = player.currentMediaItem + val title = mi?.mediaMetadata?.title?.toString() + ?: mi?.mediaMetadata?.extras?.getString("title") + val artist = mi?.mediaMetadata?.artist?.toString() + ?: mi?.mediaMetadata?.extras?.getString("artist") + val coverId = mi?.mediaMetadata?.extras?.getString("coverArtId") + WidgetUpdateManager.updateFromState( + this, + title ?: "", + artist ?: "", + coverId, + player.isPlaying, + player.currentPosition, + player.duration + ) + } + private fun initializeLoadControl(): DefaultLoadControl { return DefaultLoadControl.Builder() .setBufferDurationsMs(