Skip to content

Commit

Permalink
Add support for dotLottie (#1660)
Browse files Browse the repository at this point in the history
Fix parsing .lottie files
the dotLottie format  (dotlottie.io) is a zip file with animation.json bundled along with image resources, and manifest

**Changes**
* update zipStreamSync() to handle dotLottie use case
  * ignore manifest.json otherwise lottie will try to use it

* update network loader to check for .lottie and treat it as a .zip

* add support for .lottie and in rawRes
  * use magic header to determine files and use zipStreamSync() instead of fromJsonSync()
  * this has the bonus side effect of allowing .zip files in rawRes as well (which doesn't appear to have been supported before)
  • Loading branch information
kudanai authored Oct 27, 2020
1 parent d0f006f commit d3dd2c8
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import com.airbnb.lottie.model.LottieCompositionCache;
import com.airbnb.lottie.parser.LottieCompositionMoshiParser;
import com.airbnb.lottie.parser.moshi.JsonReader;

import com.airbnb.lottie.utils.Logger;
import com.airbnb.lottie.utils.Utils;

import org.json.JSONObject;
Expand All @@ -27,8 +27,10 @@
import androidx.annotation.Nullable;
import androidx.annotation.RawRes;
import androidx.annotation.WorkerThread;
import okio.BufferedSource;
import okio.Okio;

import static com.airbnb.lottie.parser.moshi.JsonReader.*;
import static com.airbnb.lottie.parser.moshi.JsonReader.of;
import static com.airbnb.lottie.utils.Utils.closeQuietly;
import static okio.Okio.buffer;
import static okio.Okio.source;
Expand All @@ -49,6 +51,13 @@ public class LottieCompositionFactory {
*/
private static final Map<String, LottieTask<LottieComposition>> taskCache = new HashMap<>();

/**
* reference magic bytes for zip compressed files.
* useful to determine if an InputStream is a zip file or not
*/
private static final byte[] MAGIC = new byte[] { 0x50, 0x4b, 0x03, 0x04 };


private LottieCompositionFactory() {
}

Expand Down Expand Up @@ -181,7 +190,7 @@ public static LottieResult<LottieComposition> fromAssetSync(Context context, Str
@WorkerThread
public static LottieResult<LottieComposition> fromAssetSync(Context context, String fileName, @Nullable String cacheKey) {
try {
if (fileName.endsWith(".zip")) {
if (fileName.endsWith(".zip") || fileName.endsWith(".lottie")) {
return fromZipStreamSync(new ZipInputStream(context.getAssets().open(fileName)), cacheKey);
}
return fromJsonInputStreamSync(context.getAssets().open(fileName), cacheKey);
Expand Down Expand Up @@ -253,7 +262,11 @@ public static LottieResult<LottieComposition> fromRawResSync(Context context, @R
@WorkerThread
public static LottieResult<LottieComposition> fromRawResSync(Context context, @RawRes int rawRes, @Nullable String cacheKey) {
try {
return fromJsonInputStreamSync(context.getResources().openRawResource(rawRes), cacheKey);
BufferedSource source = Okio.buffer(source(context.getResources().openRawResource(rawRes)));
if (isZipCompressed(source)) {
return fromZipStreamSync(new ZipInputStream(source.inputStream()), cacheKey);
}
return fromJsonInputStreamSync(source.inputStream(), cacheKey);
} catch (Resources.NotFoundException e) {
return new LottieResult<>(e);
}
Expand Down Expand Up @@ -423,6 +436,8 @@ private static LottieResult<LottieComposition> fromZipStreamSyncInternal(ZipInpu
final String entryName = entry.getName();
if (entryName.contains("__MACOSX")) {
inputStream.closeEntry();
} else if (entry.getName().equalsIgnoreCase("manifest.json")) { //ignore .lottie manifest
inputStream.closeEntry();
} else if (entry.getName().contains(".json")) {
com.airbnb.lottie.parser.moshi.JsonReader reader = of(buffer(source(inputStream)));
composition = LottieCompositionFactory.fromJsonReaderSyncInternal(reader, null, false).getValue();
Expand Down Expand Up @@ -465,6 +480,26 @@ private static LottieResult<LottieComposition> fromZipStreamSyncInternal(ZipInpu
return new LottieResult<>(composition);
}

/**
* Check if a given InputStream points to a .zip compressed file
*/
private static Boolean isZipCompressed(BufferedSource inputSource) {

try {
BufferedSource peek = inputSource.peek();
for (byte b: MAGIC) {
if(peek.readByte() != b)
return false;
}
peek.close();
return true;
} catch (Exception e) {
Logger.error("Failed to check zip file header", e);
return false;
}

}

@Nullable
private static LottieImageAsset findImageAssetForFileName(LottieComposition composition, String fileName) {
for (LottieImageAsset asset : composition.getImages().values()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private LottieResult<LottieComposition> fromInputStream(@NonNull String url, @No
// in the result which is more useful than failing here.
contentType = "application/json";
}
if (contentType.contains("application/zip")) {
if (contentType.contains("application/zip") || url.split("\\?")[0].endsWith(".lottie")) {
Logger.debug("Handling zip response.");
extension = FileExtension.ZIP;
result = fromZipStream(url, inputStream, cacheKey);
Expand Down

0 comments on commit d3dd2c8

Please sign in to comment.