I'm trying to preview (thumbnail) PDF documents that are remotely, using the Glide library from bumptech, version 4.8.0. To achieve this, following the excellent tutorial Writing a custom ModelLoader, I've written a custom ModelLoader, a custom DataFetcher for the buildLoadData method; added the AppGlideModule, implemented ModelLoaderFactory and registered my ModelLoader. Inside the DataFetcher I've added some logic to process the following two cases:
- The content is an image. Works like a charm!
- The content is a PDF document. W/Glide: Load failed for https://www.testserver.net/folder/sample.pdf with size [522x600] class com.bumptech.glide.load.engine.GlideException: Failed to load resource
One approach has been to download the PDF file locally, and then render it (this DOES work), but it adds a considerable delay when having to download a file from a url and copy it locally; on the other hand, it doesn't take advantage of Glide's use of the cache.
Should I add another extra ModelLoader to use OkHttp3 instead of Volley (default)? Any ideas? Thanks in advance!
public final class MyModelLoader implements ModelLoader<File, InputStream> {
private final Context context;
public MyModelLoader(Context context) {
this.context = context;
}
@NonNull
@Override
public ModelLoader.LoadData<InputStream> buildLoadData(@NonNull File model, int width, int height, @NonNull Options options) {
return new ModelLoader.LoadData<>(new ObjectKey(model), new MyDataFetcher(context, model));
}
@Override
public boolean handles(@NonNull File file) {
return true;
}
}
public class MyDataFetcher implements DataFetcher<InputStream> {
@SuppressWarnings("FieldCanBeLocal")
private final Context context;
private final File file;
private InputStream inputStream;
public MyDataFetcher(Context context, File file) {
this.context = context;
this.file = file;
}
@Override
public void loadData(@NonNull Priority priority, @NonNull DataCallback<? super InputStream> callback) {
try {
if (isPdf(file)) {
//We have a PDF document in "file" -- fail (if document is remote)
try {
//render first page of document PDF to bitmap, and pass to method 'onDataReady' as a InputStream
PdfRenderer pdfRenderer = new PdfRenderer(ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY));
PdfRenderer.Page page = pdfRenderer.openPage(0);
int width = 2048;
int height = (page.getHeight() * (width / page.getWidth()));
Bitmap pageBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
page.render(pageBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
pageBitmap.compress(Bitmap.CompressFormat.PNG, 0, outputStream);
ByteArrayInputStream stream = new ByteArrayInputStream(outputStream.toByteArray());
callback.onDataReady(stream);
} catch (IOException ignored) {}
} else {
//We have an image in "file" -- OK
FileInputStream fileInputStream = new FileInputStream(file);
callback.onDataReady(fileInputStream);
}
} catch (IOException ignored) {}
}
// checks for file content
public boolean isPdf(File f) throws IOException {
URLConnection connection = f.toURL().openConnection();
String mimeType = connection.getContentType();
return mimeType.equals("application/pdf");
}
@Override
public void cleanup() {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException ignored) {}
}
}
@Override
public void cancel() {
//empty
}
@NonNull
@Override
public Class<InputStream> getDataClass() {
return InputStream.class;
}
@NonNull
@Override
public DataSource getDataSource() {
return DataSource.REMOTE;
}
}
public class MyModelLoaderFactory implements ModelLoaderFactory<File, InputStream> {
private final Context context;
public MyModelLoaderFactory(Context context) {
this.context = context;
}
@NonNull
@Override
public ModelLoader<File, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
return new MyModelLoader(context);
}
@Override
public void teardown() {
//empty
}
}
@GlideModule public class MyAppGlideModule extends AppGlideModule {
@Override
public void registerComponents(@NonNull Context context, @NonNull Glide glide, Registry registry) {
registry.prepend(File.class, InputStream.class, new MyModelLoaderFactory(context));
}
}
Finally, after all of the above, the call is of the form:
GlideApp.with(image.getContext()).load("resource_url").into(image);
Where "resouce_url" could be: https://www.testserver.net/folder/sample.pdf, eg.