react-native-fast-image 8.29.0 → 8.30.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -19
- package/RNFastImage.podspec +3 -0
- package/android/build.gradle +23 -3
- package/android/consumer-rules.pro +9 -5
- package/android/src/glide-4.15/java/com/dylanvann/fastimage/FastImageAnimated.java +244 -0
- package/android/src/glide-older/java/com/dylanvann/fastimage/FastImageAnimated.java +36 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageAnimatable.java +13 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApng.java +141 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApngRenderer.java +152 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +6 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSharedDownloads.java +292 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +27 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +93 -0
- package/dist/index.d.ts +9 -6
- package/ios/FastImage/FFFastImageSource.m +6 -6
- package/package.json +3 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
package com.dylanvann.fastimage;
|
|
2
|
+
|
|
3
|
+
import android.graphics.Rect;
|
|
4
|
+
import android.graphics.drawable.Drawable;
|
|
5
|
+
|
|
6
|
+
import androidx.annotation.NonNull;
|
|
7
|
+
import androidx.annotation.Nullable;
|
|
8
|
+
|
|
9
|
+
import com.bumptech.glide.load.engine.Resource;
|
|
10
|
+
import com.github.penfeizhou.animation.apng.APNGDrawable;
|
|
11
|
+
import com.github.penfeizhou.animation.apng.decode.APNGDecoder;
|
|
12
|
+
import com.github.penfeizhou.animation.loader.ByteBufferLoader;
|
|
13
|
+
import com.github.penfeizhou.animation.loader.Loader;
|
|
14
|
+
|
|
15
|
+
import java.nio.ByteBuffer;
|
|
16
|
+
|
|
17
|
+
// APNGs with APNG4Android (FastImageApng, which only uses this when it's
|
|
18
|
+
// there). Glide keeps the image's data in its memory cache, and each view
|
|
19
|
+
// gets a drawable of its own over it, so views don't share playback: making
|
|
20
|
+
// one only reads the image's header, and it decodes its frames (at a sample
|
|
21
|
+
// size from the size it's drawn at) on APNG4Android's threads as it plays.
|
|
22
|
+
final class FastImageApngRenderer {
|
|
23
|
+
private FastImageApngRenderer() {
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Throws NoClassDefFoundError without APNG4Android.
|
|
27
|
+
static boolean available() {
|
|
28
|
+
return APNGDrawable.class.getName() != null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private static Loader loader(ByteBuffer data) {
|
|
32
|
+
return new ByteBufferLoader() {
|
|
33
|
+
@Override
|
|
34
|
+
public ByteBuffer getByteBuffer() {
|
|
35
|
+
ByteBuffer buffer = data.duplicate();
|
|
36
|
+
buffer.position(0);
|
|
37
|
+
return buffer;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Null if APNG4Android can't read it (Glide then decodes it as a PNG).
|
|
43
|
+
@Nullable
|
|
44
|
+
static Resource<Drawable> decode(@NonNull ByteBuffer data, int plays) {
|
|
45
|
+
// Reads the header, waiting for APNG4Android's thread.
|
|
46
|
+
Rect bounds = new APNGDecoder(loader(data), null).getBounds();
|
|
47
|
+
if (bounds.isEmpty()) return null;
|
|
48
|
+
return new ApngResource(data, plays, bounds);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
private static final class ApngResource implements Resource<Drawable> {
|
|
52
|
+
private final ByteBuffer data;
|
|
53
|
+
private final int plays;
|
|
54
|
+
private final Rect bounds;
|
|
55
|
+
|
|
56
|
+
ApngResource(ByteBuffer data, int plays, Rect bounds) {
|
|
57
|
+
this.data = data;
|
|
58
|
+
this.plays = plays;
|
|
59
|
+
this.bounds = bounds;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@NonNull
|
|
63
|
+
@Override
|
|
64
|
+
public Class<Drawable> getResourceClass() {
|
|
65
|
+
return Drawable.class;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@NonNull
|
|
69
|
+
@Override
|
|
70
|
+
public Drawable get() {
|
|
71
|
+
return new ApngDrawable(loader(data), plays);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// The data, and a frame at full size (each view's drawable has its
|
|
75
|
+
// own, at the size it's drawn at).
|
|
76
|
+
@Override
|
|
77
|
+
public int getSize() {
|
|
78
|
+
return data.limit() + bounds.width() * bounds.height() * 4;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@Override
|
|
82
|
+
public void recycle() {
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
private static final class ApngDrawable extends APNGDrawable implements FastImageAnimatable {
|
|
87
|
+
// How many times the file says it plays (0: forever).
|
|
88
|
+
private final int plays;
|
|
89
|
+
private volatile boolean paused = false;
|
|
90
|
+
// Once a frame has been rendered (on the decoder's thread).
|
|
91
|
+
private volatile boolean rendered = false;
|
|
92
|
+
|
|
93
|
+
ApngDrawable(Loader loader, int plays) {
|
|
94
|
+
super(loader);
|
|
95
|
+
this.plays = plays;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@Override
|
|
99
|
+
public void setLoopCount(int loopCount, boolean restart) {
|
|
100
|
+
// Plays (0: forever), as APNG4Android counts them.
|
|
101
|
+
setLoopLimit(loopCount == -1 ? plays : loopCount);
|
|
102
|
+
if (restart) {
|
|
103
|
+
reset();
|
|
104
|
+
start();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@Override
|
|
109
|
+
public void setPaused(boolean paused) {
|
|
110
|
+
this.paused = paused;
|
|
111
|
+
// It starts and stops itself as it's shown and hidden, but not
|
|
112
|
+
// while paused.
|
|
113
|
+
setAutoPlay(!paused);
|
|
114
|
+
if (paused) {
|
|
115
|
+
// Before the first frame, onStart pauses once it's rendered:
|
|
116
|
+
// the decoder's start clears a pause, then skips rendering if
|
|
117
|
+
// one came in meanwhile, which left the view blank.
|
|
118
|
+
if (rendered) pause();
|
|
119
|
+
} else {
|
|
120
|
+
resume();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// From its first frame. The target starts it right after
|
|
125
|
+
// setVisible(true) has (auto play): APNG4Android's start() stops and
|
|
126
|
+
// starts a running decoder, and a stop made while the decoder is
|
|
127
|
+
// still starting can win over that start, leaving it stopped on its
|
|
128
|
+
// first frame. A running one is reset to its first frame instead.
|
|
129
|
+
@Override
|
|
130
|
+
public void start() {
|
|
131
|
+
if (isRunning()) {
|
|
132
|
+
getFrameSeqDecoder().reset();
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
super.start();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
@Override
|
|
139
|
+
public void onRender(ByteBuffer byteBuffer) {
|
|
140
|
+
super.onRender(byteBuffer);
|
|
141
|
+
rendered = true;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// On its decoder thread, once starting has drawn the first frame:
|
|
145
|
+
// starting clears a pause made before it got there, so pause again.
|
|
146
|
+
@Override
|
|
147
|
+
public void onStart() {
|
|
148
|
+
super.onStart();
|
|
149
|
+
if (paused) getFrameSeqDecoder().pause();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java
CHANGED
|
@@ -121,6 +121,8 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
121
121
|
OkHttpClient client = builder.build();
|
|
122
122
|
registry.replace(GlideUrl.class, InputStream.class, new UrlLoaderFactory(client));
|
|
123
123
|
FastImageSvg.register(registry, glide.getBitmapPool());
|
|
124
|
+
FastImageAnimated.register(context, glide, registry);
|
|
125
|
+
FastImageApng.register(registry);
|
|
124
126
|
|
|
125
127
|
// `cache: 'web'` skips Glide's caches and relies on HTTP caching, so
|
|
126
128
|
// those urls get a client with an HTTP cache (#280): one of their own,
|
|
@@ -156,7 +158,10 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
156
158
|
return new ModelLoader<GlideUrl, InputStream>() {
|
|
157
159
|
@Override
|
|
158
160
|
public LoadData<InputStream> buildLoadData(@NonNull GlideUrl model, int width, int height, @NonNull Options options) {
|
|
159
|
-
|
|
161
|
+
LoadData<InputStream> data = loader.buildLoadData(model, width, height, options);
|
|
162
|
+
// A view loading an image that's being preloaded waits
|
|
163
|
+
// for the preload's download.
|
|
164
|
+
return data == null ? null : FastImageSharedDownloads.share(data, model, options);
|
|
160
165
|
}
|
|
161
166
|
|
|
162
167
|
@Override
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
package com.dylanvann.fastimage;
|
|
2
|
+
|
|
3
|
+
import androidx.annotation.NonNull;
|
|
4
|
+
import androidx.annotation.Nullable;
|
|
5
|
+
|
|
6
|
+
import com.bumptech.glide.Priority;
|
|
7
|
+
import com.bumptech.glide.load.DataSource;
|
|
8
|
+
import com.bumptech.glide.load.Option;
|
|
9
|
+
import com.bumptech.glide.load.Options;
|
|
10
|
+
import com.bumptech.glide.load.data.DataFetcher;
|
|
11
|
+
import com.bumptech.glide.load.model.GlideUrl;
|
|
12
|
+
import com.bumptech.glide.load.model.ModelLoader;
|
|
13
|
+
|
|
14
|
+
import java.io.File;
|
|
15
|
+
import java.io.FileInputStream;
|
|
16
|
+
import java.io.IOException;
|
|
17
|
+
import java.io.InputStream;
|
|
18
|
+
import java.util.ArrayList;
|
|
19
|
+
import java.util.List;
|
|
20
|
+
import java.util.Map;
|
|
21
|
+
import java.util.concurrent.ConcurrentHashMap;
|
|
22
|
+
import java.util.concurrent.Executors;
|
|
23
|
+
import java.util.concurrent.ScheduledExecutorService;
|
|
24
|
+
import java.util.concurrent.ThreadFactory;
|
|
25
|
+
import java.util.concurrent.TimeUnit;
|
|
26
|
+
import java.util.concurrent.atomic.AtomicBoolean;
|
|
27
|
+
|
|
28
|
+
// Lets a view load an image that a preload is downloading from the preload's
|
|
29
|
+
// file, instead of downloading it again. Glide only shares a download between
|
|
30
|
+
// requests for the same size (and a preload loads the original size), so a
|
|
31
|
+
// view mounted while its image was being preloaded downloaded it a second
|
|
32
|
+
// time. A preload's download registers here once it has started (not while
|
|
33
|
+
// it's queued); a view's download of the same url then waits for the
|
|
34
|
+
// preload's file and reads it, and downloads as usual if the preload fails.
|
|
35
|
+
// The wait holds no thread: the view's fetch returns, and goes on when the
|
|
36
|
+
// preload has finished, as an OkHttp download does. A finished download stays
|
|
37
|
+
// for a while: a view's request that looked in the disk cache before the
|
|
38
|
+
// preload stored the file, and gets to downloading after, reads it too.
|
|
39
|
+
final class FastImageSharedDownloads {
|
|
40
|
+
// Set on a preload's request (see FastImageViewModule.loadFile).
|
|
41
|
+
static final Option<Boolean> PRELOAD = Option.memory("com.dylanvann.fastimage.Preload", false);
|
|
42
|
+
|
|
43
|
+
// The longest a view waits for a preload's download, before downloading
|
|
44
|
+
// the image itself.
|
|
45
|
+
private static final long WAIT_MS = 30_000;
|
|
46
|
+
// How long a finished download's file is used.
|
|
47
|
+
private static final long KEEP_MS = 60_000;
|
|
48
|
+
|
|
49
|
+
// Goes on with waiting views (opening the file, or starting their own
|
|
50
|
+
// download), off the main thread, where preloads finish.
|
|
51
|
+
private static final ScheduledExecutorService executor =
|
|
52
|
+
Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
|
|
53
|
+
@Override
|
|
54
|
+
public Thread newThread(@NonNull Runnable runnable) {
|
|
55
|
+
Thread thread = new Thread(runnable, "FastImageSharedDownloads");
|
|
56
|
+
thread.setDaemon(true);
|
|
57
|
+
return thread;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// A view's fetch waiting for a download. It goes on once: with the file,
|
|
62
|
+
// or without it (the preload failed, or took too long).
|
|
63
|
+
private static final class Waiter {
|
|
64
|
+
private final Fetcher fetcher;
|
|
65
|
+
private final Priority priority;
|
|
66
|
+
private final DataFetcher.DataCallback<? super InputStream> callback;
|
|
67
|
+
private final AtomicBoolean went = new AtomicBoolean();
|
|
68
|
+
|
|
69
|
+
Waiter(Fetcher fetcher, Priority priority, DataFetcher.DataCallback<? super InputStream> callback) {
|
|
70
|
+
this.fetcher = fetcher;
|
|
71
|
+
this.priority = priority;
|
|
72
|
+
this.callback = callback;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
void go(@Nullable File file) {
|
|
76
|
+
if (went.compareAndSet(false, true)) fetcher.resume(file, priority, callback);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Cancelled: never goes on.
|
|
80
|
+
void drop() {
|
|
81
|
+
went.set(true);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private static final class Download {
|
|
86
|
+
// Guarded by this.
|
|
87
|
+
private boolean done;
|
|
88
|
+
private final List<Waiter> waiters = new ArrayList<>();
|
|
89
|
+
@Nullable
|
|
90
|
+
volatile File file;
|
|
91
|
+
volatile long finishedAt;
|
|
92
|
+
|
|
93
|
+
synchronized boolean isDone() {
|
|
94
|
+
return done;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
boolean isStale(long now) {
|
|
98
|
+
return isDone() && now - finishedAt > KEEP_MS;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
void await(final Waiter waiter) {
|
|
102
|
+
synchronized (this) {
|
|
103
|
+
if (!done) {
|
|
104
|
+
waiters.add(waiter);
|
|
105
|
+
executor.schedule(new Runnable() {
|
|
106
|
+
@Override
|
|
107
|
+
public void run() {
|
|
108
|
+
remove(waiter);
|
|
109
|
+
waiter.go(null);
|
|
110
|
+
}
|
|
111
|
+
}, WAIT_MS, TimeUnit.MILLISECONDS);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
goOn(waiter, file);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
synchronized void remove(Waiter waiter) {
|
|
119
|
+
waiters.remove(waiter);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Returns false if it had finished already.
|
|
123
|
+
boolean finish(@Nullable File file, long now) {
|
|
124
|
+
List<Waiter> waiting;
|
|
125
|
+
synchronized (this) {
|
|
126
|
+
if (done) return false;
|
|
127
|
+
done = true;
|
|
128
|
+
this.file = file;
|
|
129
|
+
finishedAt = now;
|
|
130
|
+
waiting = new ArrayList<>(waiters);
|
|
131
|
+
waiters.clear();
|
|
132
|
+
}
|
|
133
|
+
for (Waiter waiter : waiting) goOn(waiter, file);
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private static void goOn(final Waiter waiter, @Nullable final File file) {
|
|
138
|
+
executor.execute(new Runnable() {
|
|
139
|
+
@Override
|
|
140
|
+
public void run() {
|
|
141
|
+
waiter.go(file);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Preloads' downloads in progress, or finished with their file, by the
|
|
148
|
+
// url's cache key.
|
|
149
|
+
private static final ConcurrentHashMap<String, Download> downloads = new ConcurrentHashMap<>();
|
|
150
|
+
|
|
151
|
+
private FastImageSharedDownloads() {
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// A preload's download of the image with this cache key has ended: with
|
|
155
|
+
// its file in Glide's disk cache, or null if it failed or was cancelled.
|
|
156
|
+
// Views waiting for it go on.
|
|
157
|
+
static void finished(String key, @Nullable File file) {
|
|
158
|
+
long now = System.currentTimeMillis();
|
|
159
|
+
for (Map.Entry<String, Download> entry : downloads.entrySet()) {
|
|
160
|
+
if (entry.getValue().isStale(now)) downloads.remove(entry.getKey(), entry.getValue());
|
|
161
|
+
}
|
|
162
|
+
Download download = downloads.get(key);
|
|
163
|
+
if (download == null) return;
|
|
164
|
+
if (download.finish(file, now) && file == null) downloads.remove(key, download);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// The url loader's load data, with a fetcher that shares preloads'
|
|
168
|
+
// downloads.
|
|
169
|
+
static ModelLoader.LoadData<InputStream> share(
|
|
170
|
+
ModelLoader.LoadData<InputStream> data, GlideUrl model, Options options) {
|
|
171
|
+
boolean preload = Boolean.TRUE.equals(options.get(PRELOAD));
|
|
172
|
+
return new ModelLoader.LoadData<>(
|
|
173
|
+
data.sourceKey,
|
|
174
|
+
data.alternateKeys,
|
|
175
|
+
new Fetcher(data.fetcher, model.getCacheKey(), preload));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private static final class Fetcher implements DataFetcher<InputStream> {
|
|
179
|
+
private final DataFetcher<InputStream> fetcher;
|
|
180
|
+
private final String key;
|
|
181
|
+
private final boolean preload;
|
|
182
|
+
private volatile boolean cancelled;
|
|
183
|
+
// While it waits for a preload's download.
|
|
184
|
+
@Nullable
|
|
185
|
+
private volatile Download download;
|
|
186
|
+
@Nullable
|
|
187
|
+
private volatile Waiter waiter;
|
|
188
|
+
// The preload's file, when the image came from it.
|
|
189
|
+
@Nullable
|
|
190
|
+
private volatile InputStream stream;
|
|
191
|
+
|
|
192
|
+
Fetcher(DataFetcher<InputStream> fetcher, String key, boolean preload) {
|
|
193
|
+
this.fetcher = fetcher;
|
|
194
|
+
this.key = key;
|
|
195
|
+
this.preload = preload;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@Override
|
|
199
|
+
public void loadData(@NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) {
|
|
200
|
+
if (preload) {
|
|
201
|
+
// A second preload of the url (with other options) keeps the
|
|
202
|
+
// first's entry while it's downloading; one after it finished
|
|
203
|
+
// downloads again (the file left the disk cache).
|
|
204
|
+
Download current = downloads.get(key);
|
|
205
|
+
if (current == null || current.isDone()) downloads.put(key, new Download());
|
|
206
|
+
fetcher.loadData(priority, new DataCallback<InputStream>() {
|
|
207
|
+
@Override
|
|
208
|
+
public void onDataReady(@Nullable InputStream data) {
|
|
209
|
+
callback.onDataReady(data);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@Override
|
|
213
|
+
public void onLoadFailed(@NonNull Exception e) {
|
|
214
|
+
finished(key, null);
|
|
215
|
+
callback.onLoadFailed(e);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
Download current = downloads.get(key);
|
|
221
|
+
if (current == null || current.isStale(System.currentTimeMillis())) {
|
|
222
|
+
fetcher.loadData(priority, callback);
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
Waiter waiting = new Waiter(this, priority, callback);
|
|
226
|
+
download = current;
|
|
227
|
+
waiter = waiting;
|
|
228
|
+
current.await(waiting);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Goes on after waiting: reads the preload's file, or downloads the
|
|
232
|
+
// image if there's none.
|
|
233
|
+
void resume(@Nullable File file, Priority priority, DataCallback<? super InputStream> callback) {
|
|
234
|
+
download = null;
|
|
235
|
+
waiter = null;
|
|
236
|
+
if (cancelled) return;
|
|
237
|
+
if (file != null) {
|
|
238
|
+
try {
|
|
239
|
+
stream = new FileInputStream(file);
|
|
240
|
+
callback.onDataReady(stream);
|
|
241
|
+
return;
|
|
242
|
+
} catch (IOException e) {
|
|
243
|
+
// Gone from the disk cache meanwhile: download it.
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
fetcher.loadData(priority, callback);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
@Override
|
|
250
|
+
public void cleanup() {
|
|
251
|
+
InputStream opened = stream;
|
|
252
|
+
if (opened != null) {
|
|
253
|
+
try {
|
|
254
|
+
opened.close();
|
|
255
|
+
} catch (IOException e) {
|
|
256
|
+
// Closed as far as it could.
|
|
257
|
+
}
|
|
258
|
+
stream = null;
|
|
259
|
+
}
|
|
260
|
+
fetcher.cleanup();
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
@Override
|
|
264
|
+
public void cancel() {
|
|
265
|
+
cancelled = true;
|
|
266
|
+
Download waitingFor = download;
|
|
267
|
+
Waiter waiting = waiter;
|
|
268
|
+
if (waitingFor != null && waiting != null) {
|
|
269
|
+
waitingFor.remove(waiting);
|
|
270
|
+
waiting.drop();
|
|
271
|
+
}
|
|
272
|
+
// The preload's download stops: views waiting for it download
|
|
273
|
+
// the image themselves.
|
|
274
|
+
if (preload) finished(key, null);
|
|
275
|
+
fetcher.cancel();
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
@NonNull
|
|
279
|
+
@Override
|
|
280
|
+
public Class<InputStream> getDataClass() {
|
|
281
|
+
return InputStream.class;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// From the preload's file: as from Glide's disk cache, which it is in,
|
|
285
|
+
// so Glide doesn't store it there again.
|
|
286
|
+
@NonNull
|
|
287
|
+
@Override
|
|
288
|
+
public DataSource getDataSource() {
|
|
289
|
+
return stream != null ? DataSource.DATA_DISK_CACHE : fetcher.getDataSource();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
@@ -169,7 +169,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
169
169
|
pendingPreloads.add(new Runnable() {
|
|
170
170
|
@Override
|
|
171
171
|
public void run() {
|
|
172
|
-
loadFile(context, imageSource.getSourceForLoad(), preloadOptions, new FileCallback() {
|
|
172
|
+
loadFile(context, imageSource.getSourceForLoad(), preloadOptions, true, new FileCallback() {
|
|
173
173
|
@Override
|
|
174
174
|
public void run(@Nullable final File file, @Nullable String error) {
|
|
175
175
|
if (file == null) {
|
|
@@ -196,7 +196,8 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
196
196
|
});
|
|
197
197
|
continue;
|
|
198
198
|
}
|
|
199
|
-
|
|
199
|
+
// Decodes it into the memory cache.
|
|
200
|
+
final Runnable decode = new Runnable() {
|
|
200
201
|
@Override
|
|
201
202
|
public void run() {
|
|
202
203
|
Glide
|
|
@@ -222,6 +223,22 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
222
223
|
})
|
|
223
224
|
.preload();
|
|
224
225
|
}
|
|
226
|
+
};
|
|
227
|
+
// A remote image is downloaded to the disk cache first,
|
|
228
|
+
// so views that load it meanwhile can wait for the file
|
|
229
|
+
// (FastImageSharedDownloads), then decoded from there.
|
|
230
|
+
final boolean download = !imageSource.isWebCache() && imageSource.isRemote();
|
|
231
|
+
pendingPreloads.add(!download ? decode : new Runnable() {
|
|
232
|
+
@Override
|
|
233
|
+
public void run() {
|
|
234
|
+
loadFile(context, imageSource.getSourceForLoad(), preloadOptions, true, new FileCallback() {
|
|
235
|
+
@Override
|
|
236
|
+
public void run(@Nullable File file, @Nullable String error) {
|
|
237
|
+
if (file == null) done.run(failure(error));
|
|
238
|
+
else decode.run();
|
|
239
|
+
}
|
|
240
|
+
});
|
|
241
|
+
}
|
|
225
242
|
});
|
|
226
243
|
}
|
|
227
244
|
if (count == 0) {
|
|
@@ -250,21 +267,26 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
250
267
|
|
|
251
268
|
// Downloads the image into Glide's disk cache without decoding it (if it
|
|
252
269
|
// isn't there), then calls back with its file or the error, on the UI
|
|
253
|
-
// thread.
|
|
254
|
-
|
|
270
|
+
// thread. For a preload (`shared`), views that load the image meanwhile
|
|
271
|
+
// wait for the download and read its file (FastImageSharedDownloads).
|
|
272
|
+
private static void loadFile(Context context, Object model, RequestOptions options, boolean shared, final FileCallback callback) {
|
|
273
|
+
final String key = shared && model instanceof GlideUrl ? ((GlideUrl) model).getCacheKey() : null;
|
|
255
274
|
Glide.with(context)
|
|
256
275
|
.asFile()
|
|
257
276
|
.load(model)
|
|
258
277
|
.apply(options)
|
|
278
|
+
.set(FastImageSharedDownloads.PRELOAD, shared)
|
|
259
279
|
.listener(new RequestListener<File>() {
|
|
260
280
|
@Override
|
|
261
281
|
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<File> target, boolean isFirstResource) {
|
|
282
|
+
if (key != null) FastImageSharedDownloads.finished(key, null);
|
|
262
283
|
callback.run(null, FastImageRequestListener.errorMessage(e));
|
|
263
284
|
return false;
|
|
264
285
|
}
|
|
265
286
|
|
|
266
287
|
@Override
|
|
267
288
|
public boolean onResourceReady(File file, Object model, Target<File> target, DataSource dataSource, boolean isFirstResource) {
|
|
289
|
+
if (key != null) FastImageSharedDownloads.finished(key, file);
|
|
268
290
|
callback.run(file, null);
|
|
269
291
|
return false;
|
|
270
292
|
}
|
|
@@ -400,7 +422,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
|
|
|
400
422
|
return new Runnable() {
|
|
401
423
|
@Override
|
|
402
424
|
public void run() {
|
|
403
|
-
loadFile(context, imageSource.getSourceForLoad(), options, new FileCallback() {
|
|
425
|
+
loadFile(context, imageSource.getSourceForLoad(), options, false, new FileCallback() {
|
|
404
426
|
@Override
|
|
405
427
|
public void run(@Nullable File file, @Nullable String error) {
|
|
406
428
|
finishDownload(promise, file != null ? pathResult(file) : failure(error));
|