Commit 0d00b8aa authored by Matthijs Tijink's avatar Matthijs Tijink

Add album cover art support.

Summary:
Fetches http(s) album art urls, as supplied by MPRIS, to display as album art. Fetched urls and failed fetches are cached to prevent unneccessary network activity.

The bulk of the code is in the fetching+caching class `AlbumArtCache`.

Takes the comments from d52be109 into account:
 - The images (HTTP(S) only for now) are cached. This is limited to 5 MB on disk or 10 entries in memory.
 - The image gets viewing space depending on the remaining screen space. Thus, controls should never be pushed off-screen.
 - "Edge cases" like going from cover art to no cover art is handled correctly (actually a result of earlier mpris code changes).

Additionally, it adds a landscape mode to the MPRIS activity, which shows the cover art and controls side by side.

Desktop part is in D9563.

FEATURE: 345015

Test Plan: Works both with/without album art. Switching players and tracks correctly changes the album art.

Reviewers: #kde_connect, #vdg, albertvaka

Reviewed By: #kde_connect, albertvaka

Subscribers: albertvaka, ngraham, nicolasfella, apol

Differential Revision: https://phabricator.kde.org/D9564
parent e9a35d3b
......@@ -82,6 +82,7 @@ dependencies {
implementation 'com.android.support:support-v4:25.4.0'
implementation 'com.android.support:appcompat-v7:25.4.0'
implementation 'com.android.support:design:25.4.0'
implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps
implementation 'org.apache.sshd:sshd-core:0.8.0' //0.9 seems to fail on Android 6 and 1.+ requires java.nio.file, which doesn't exist in Android
......
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#000000"
android:pathData="M12,3v10.55c-0.59,-0.34 -1.27,-0.55 -2,-0.55 -2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4V7h4V3h-6z"/>
</vector>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:paddingLeft="25dp"
android:paddingTop="25dp"
android:paddingRight="25dp"
android:paddingBottom="25dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/album_art"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:layout_marginRight="25dp"
android:scaleType="fitCenter" />
<include
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
layout="@layout/mpris_control" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingLeft="25dp"
android:paddingTop="25dp"
android:paddingRight="25dp"
android:paddingBottom="25dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/album_art"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="4"
android:layout_marginBottom="25dp"
android:scaleType="fitCenter" />
<include
android:layout_width="match_parent"
android:layout_height="wrap_content"
layout="@layout/mpris_control" />
</LinearLayout>
......@@ -7,10 +7,7 @@
android:layout_height="match_parent"
android:id="@+id/mpris_control_view"
android:gravity="center"
android:paddingLeft="30dip"
android:paddingTop="5dip"
android:paddingRight="30dip"
android:paddingBottom="5dip">
android:layout_gravity="center">
<TextView
android:layout_width="wrap_content"
......
This diff is collapsed.
......@@ -21,11 +21,14 @@
package org.kde.kdeconnect.Plugins.MprisPlugin;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
......@@ -33,6 +36,7 @@ import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.SeekBar;
import android.widget.Spinner;
import android.widget.TextView;
......@@ -196,6 +200,15 @@ public class MprisActivity extends AppCompatActivity {
nowPlaying.setText(song);
}
Bitmap albumArt = playerStatus.getAlbumArt();
if (albumArt == null) {
Drawable placeholder_art = DrawableCompat.wrap(getResources().getDrawable(R.drawable.ic_album_art_placeholder));
DrawableCompat.setTint(placeholder_art, getResources().getColor(R.color.primary));
((ImageView) findViewById(R.id.album_art)).setImageDrawable(placeholder_art);
} else {
((ImageView) findViewById(R.id.album_art)).setImageBitmap(albumArt);
}
if (playerStatus.isSeekAllowed()) {
((TextView) findViewById(R.id.time_textview)).setText(milisToProgress(playerStatus.getLength()));
SeekBar positionSeek = (SeekBar)findViewById(R.id.positionSeek);
......@@ -276,7 +289,7 @@ public class MprisActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mpris_control);
setContentView(R.layout.activity_mpris);
final String targetPlayerName = getIntent().getStringExtra("player");
getIntent().removeExtra("player");
......
......@@ -22,6 +22,7 @@ package org.kde.kdeconnect.Plugins.MprisPlugin;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
......@@ -32,6 +33,8 @@ import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
......@@ -46,6 +49,7 @@ public class MprisPlugin extends Plugin {
private String title = "";
private String artist = "";
private String album = "";
private String albumArtUrl = "";
private int volume = 50;
private long length = -1;
private long lastPosition = 0;
......@@ -114,6 +118,18 @@ public class MprisPlugin extends Plugin {
return seekAllowed && getLength() >= 0 && getPosition() >= 0 && !isSpotify();
}
public boolean hasAlbumArt() {
return !albumArtUrl.isEmpty();
}
/**
* Returns the album art (if available). Note that this can return null even if hasAlbumArt() returns true.
* @return The album art, or null if not available
*/
public Bitmap getAlbumArt() {
return AlbumArtCache.getAlbumArt(albumArtUrl);
}
public boolean isSetVolumeAllowed() {
return !isSpotify();
}
......@@ -217,12 +233,16 @@ public class MprisPlugin extends Plugin {
//Always request the player list so the data is up-to-date
requestPlayerList();
AlbumArtCache.initializeDiskCache(context);
AlbumArtCache.registerPlugin(this);
return true;
}
@Override
public void onDestroy() {
players.clear();
AlbumArtCache.deregisterPlugin(this);
}
private void sendCommand(String player, String method, String value) {
......@@ -261,6 +281,12 @@ public class MprisPlugin extends Plugin {
playerStatus.goNextAllowed = np.getBoolean("canGoNext", playerStatus.goNextAllowed);
playerStatus.goPreviousAllowed = np.getBoolean("canGoPrevious", playerStatus.goPreviousAllowed);
playerStatus.seekAllowed = np.getBoolean("canSeek", playerStatus.seekAllowed);
String newAlbumArtUrlstring = np.getString("albumArtUrl", playerStatus.albumArtUrl);
try {
//Turn the url into canonical form (and check its validity)
URL newAlbumArtUrl = new URL(newAlbumArtUrlstring);
playerStatus.albumArtUrl = newAlbumArtUrlstring.toString();
} catch (MalformedURLException ignored) {}
for (String key : playerStatusUpdated.keySet()) {
try {
playerStatusUpdated.get(key).dispatchMessage(new Message());
......@@ -396,4 +422,24 @@ public class MprisPlugin extends Plugin {
return context.getString(R.string.open_mpris_controls);
}
public void fetchedAlbumArt(String url) {
boolean doEmitUpdate = false;
for (MprisPlayer player : players.values()) {
if (url.equals(player.albumArtUrl)) {
doEmitUpdate = true;
}
}
if (doEmitUpdate) {
for (String key : playerStatusUpdated.keySet()) {
try {
playerStatusUpdated.get(key).dispatchMessage(new Message());
} catch(Exception e) {
e.printStackTrace();
Log.e("MprisControl","Exception");
playerStatusUpdated.remove(key);
}
}
}
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment