Skip to content

Commit

Permalink
Cherry pick PR #3707: Add ArtworkDownloader interface (#3723)
Browse files Browse the repository at this point in the history
Refer to the original PR: #3707

Extract and refactor the part of the code in ArtworkLoader to a new
interface ArtworkDownloader and its default implementation
ArtworkDownloaderDefault, so that we could swap the implementation in
Kimono to avoid GMS Extract the artwork downloading functionality from
ArtworkLoader into a separate interface (ArtworkDownloader) and a
default implementation
(ArtworkDownloaderDefault). This will allow the Kimono project to
provide its own implementation without relying on GMS dependencies.

b/347963541

Change-Id: I7edd475991c7dd755aa24f313aaa890db4d5798d

Co-authored-by: Colin Liang <zhongqi.liang.4u@gmail.com>
  • Loading branch information
1 parent a726465 commit 95125ce
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 58 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import dev.cobalt.coat.CobaltService;
import dev.cobalt.coat.StarboardBridge;
import dev.cobalt.libraries.services.clientloginfo.ClientLogInfoModule;
import dev.cobalt.media.ArtworkDownloaderDefault;
import dev.cobalt.util.Holder;

/**
Expand Down Expand Up @@ -49,6 +50,7 @@ public void run() {
activityHolder,
serviceHolder,
userAuthorizer,
new ArtworkDownloaderDefault(),
args,
startDeepLink);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import android.view.accessibility.CaptioningManager;
import androidx.annotation.Nullable;
import dev.cobalt.account.UserAuthorizer;
import dev.cobalt.media.ArtworkDownloader;
import dev.cobalt.media.AudioOutputManager;
import dev.cobalt.media.CaptionSettings;
import dev.cobalt.media.CobaltMediaSession;
Expand Down Expand Up @@ -123,6 +124,7 @@ public StarboardBridge(
Holder<Activity> activityHolder,
Holder<Service> serviceHolder,
UserAuthorizer userAuthorizer,
ArtworkDownloader artworkDownloader,
String[] args,
String startDeepLink) {

Expand All @@ -140,7 +142,7 @@ public StarboardBridge(
this.userAuthorizer = userAuthorizer;
this.audioOutputManager = new AudioOutputManager(appContext);
this.cobaltMediaSession =
new CobaltMediaSession(appContext, activityHolder, audioOutputManager);
new CobaltMediaSession(appContext, activityHolder, audioOutputManager, artworkDownloader);
this.audioPermissionRequester = new AudioPermissionRequester(appContext, activityHolder);
this.networkStatus = new NetworkStatus(appContext);
this.resourceOverlay = new ResourceOverlay(appContext);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright 2024 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dev.cobalt.media;

/**
* Interface to download artwork (Bitmap) from a URL, intended for use in media session metadata.
*/
public interface ArtworkDownloader {
public void downloadArtwork(String url, ArtworkLoader artworkLoader);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright 2024 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package dev.cobalt.media;

import static dev.cobalt.media.Log.TAG;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.util.Pair;
import dev.cobalt.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
* Default implementation of ArtworkDownloader using java.net.HttpURLConnection to fetch artwork.
*/
public class ArtworkDownloaderDefault implements ArtworkDownloader {

public ArtworkDownloaderDefault() {}

@Override
public void downloadArtwork(String url, ArtworkLoader artworkLoader) {
Bitmap bitmap = null;
HttpURLConnection conn = null;
InputStream is = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.e(TAG, "Could not download artwork", e);
} finally {
try {
if (conn != null) {
conn.disconnect();
conn = null;
}
if (is != null) {
is.close();
is = null;
}
} catch (Exception e) {
Log.e(TAG, "Error closing connection for artwork", e);
}
}

bitmap = artworkLoader.cropTo16x9(bitmap);
artworkLoader.onDownloadFinished(Pair.create(url, bitmap));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,21 +14,13 @@

package dev.cobalt.media;

import static dev.cobalt.media.Log.TAG;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Handler;
import android.os.Looper;
import android.util.Pair;
import android.util.Size;
import androidx.annotation.NonNull;
import dev.cobalt.util.DisplayUtil;
import dev.cobalt.util.Log;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Locale;

/** Loads MediaImage artwork, and caches one image. */
Expand All @@ -44,10 +36,12 @@ public interface Callback {
private volatile Bitmap currentArtwork = null;

private final Handler handler = new Handler(Looper.getMainLooper());
private final ArtworkDownloader artworkDownloader;
private final Callback callback;

public ArtworkLoader(Callback callback) {
public ArtworkLoader(Callback callback, ArtworkDownloader artworkDownloader) {
this.callback = callback;
this.artworkDownloader = artworkDownloader;
}

/**
Expand All @@ -66,7 +60,7 @@ public synchronized Bitmap getOrLoadArtwork(MediaImage[] artwork) {
}

requestedArtworkUrl = url;
new DownloadArtworkThread(url, handler).start();
new DownloadArtworkThread(url, this).start();
return null;
}

Expand Down Expand Up @@ -104,71 +98,52 @@ private Size parseImageSize(MediaImage image) {
}
}

private synchronized void onDownloadFinished(Pair<String, Bitmap> urlBitmapPair) {
public Bitmap cropTo16x9(Bitmap bitmap) {
// Crop to 16:9 as needed
if (bitmap != null) {
int height = bitmap.getWidth() * 9 / 16;
if (bitmap.getHeight() > height) {
int top = (bitmap.getHeight() - height) / 2;
return Bitmap.createBitmap(bitmap, 0, top, bitmap.getWidth(), height);
}
}
return bitmap;
}

public synchronized void onDownloadFinished(Pair<String, Bitmap> urlBitmapPair) {
String url = urlBitmapPair.first;
Bitmap bitmap = urlBitmapPair.second;
if (url.equals(requestedArtworkUrl)) {
requestedArtworkUrl = "";
if (bitmap != null) {
currentArtworkUrl = url;
currentArtwork = bitmap;
callback.onArtworkLoaded(bitmap);

handler.post(
new Runnable() {
@Override
public void run() {
callback.onArtworkLoaded(bitmap);
}
});
}
}
}

private class DownloadArtworkThread extends Thread {

private final String url;
private final Handler handler;
private final ArtworkLoader artworkLoader;

DownloadArtworkThread(String url, Handler handler) {
DownloadArtworkThread(String url, ArtworkLoader artworkLoader) {
super("ArtworkLoader");
this.url = url;
this.handler = handler;
this.artworkLoader = artworkLoader;
}

@Override
public void run() {
Bitmap bitmap = null;
HttpURLConnection conn = null;
InputStream is = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
is = conn.getInputStream();
bitmap = BitmapFactory.decodeStream(is);
} catch (IOException e) {
Log.e(TAG, "Could not download artwork", e);
} finally {
try {
if (conn != null) {
conn.disconnect();
}
if (is != null) {
is.close();
}
} catch (Exception e) {
Log.e(TAG, "Error closing connection for artwork", e);
}
}

// Crop to 16:9 as needed
if (bitmap != null) {
int height = bitmap.getWidth() * 9 / 16;
if (bitmap.getHeight() > height) {
int top = (bitmap.getHeight() - height) / 2;
bitmap = Bitmap.createBitmap(bitmap, 0, top, bitmap.getWidth(), height);
}
}

final Pair<String, Bitmap> urlBitmapPair = Pair.create(url, bitmap);
handler.post(
new Runnable() {
@Override
public void run() {
onDownloadFinished(urlBitmapPair);
}
});
artworkDownloader.downloadArtwork(url, artworkLoader);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,12 +119,15 @@ public void SetMetadata(
private MediaMetadata metadata = new MediaMetadata();

public CobaltMediaSession(
Context context, Holder<Activity> activityHolder, UpdateVolumeListener volumeListener) {
Context context,
Holder<Activity> activityHolder,
UpdateVolumeListener volumeListener,
ArtworkDownloader artworkDownloader) {
this.context = context;
this.activityHolder = activityHolder;

this.volumeListener = volumeListener;
artworkLoader = new ArtworkLoader(this);
artworkLoader = new ArtworkLoader(this, artworkDownloader);
setMediaSession();
}

Expand Down Expand Up @@ -349,7 +352,7 @@ public void onAudioFocusChange(int focusChange) {
switch (focusChange) {
case AudioManager.AUDIOFOCUS_LOSS_TRANSIENT:
logExtra = " (transient)";
// fall through
// fall through
case AudioManager.AUDIOFOCUS_LOSS:
Log.i(TAG, "Audiofocus loss" + logExtra);
if (currentPlaybackState == PLAYBACK_STATE_PLAYING) {
Expand Down

0 comments on commit 95125ce

Please sign in to comment.