-
Notifications
You must be signed in to change notification settings - Fork 9
Expand file tree
/
Copy pathExperimentalAssetLoader.kt
More file actions
148 lines (136 loc) · 5.12 KB
/
ExperimentalAssetLoader.kt
File metadata and controls
148 lines (136 loc) · 5.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package com.margelo.nitro.rive
import android.util.Log
import app.rive.AudioAsset
import app.rive.FontAsset
import app.rive.ImageAsset
import app.rive.core.CommandQueue
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
object ExperimentalAssetLoader {
private const val TAG = "ExperimentalAssetLoader"
fun registerAssets(
referencedAssets: ReferencedAssetsType?,
riveWorker: CommandQueue
) {
val assetsData = referencedAssets?.data ?: return
val scope = CoroutineScope(Dispatchers.IO)
for ((name, assetData) in assetsData) {
val source = DataSourceResolver.resolve(assetData) ?: continue
scope.launch {
try {
val loader = source.createLoader()
val data = loader.load(source)
val type = inferAssetType(name, data, assetData.type)
registerAsset(data, name, type, riveWorker)
} catch (e: Exception) {
Log.e(TAG, "Failed to load asset '$name'", e)
}
}
}
}
fun updateAssets(
referencedAssets: ReferencedAssetsType,
riveWorker: CommandQueue
) {
val assetsData = referencedAssets.data ?: return
val scope = CoroutineScope(Dispatchers.IO)
for ((name, assetData) in assetsData) {
val source = DataSourceResolver.resolve(assetData) ?: continue
scope.launch {
try {
val loader = source.createLoader()
val data = loader.load(source)
val type = inferAssetType(name, data, assetData.type)
registerAsset(data, name, type, riveWorker)
} catch (e: Exception) {
Log.e(TAG, "Failed to update asset '$name'", e)
}
}
}
}
private suspend fun registerAsset(
data: ByteArray,
name: String,
type: AssetType,
riveWorker: CommandQueue
) {
Log.i(TAG, "Registering $type asset '$name' (${data.size} bytes)")
when (type) {
AssetType.IMAGE -> {
riveWorker.unregisterImage(name)
val result = ImageAsset.fromBytes(riveWorker, data)
if (result is app.rive.Result.Success) {
result.value.register(name)
Log.i(TAG, "Image '$name' registered")
}
}
AssetType.FONT -> {
riveWorker.unregisterFont(name)
val result = FontAsset.fromBytes(riveWorker, data)
if (result is app.rive.Result.Success) {
result.value.register(name)
Log.i(TAG, "Font '$name' registered")
}
}
AssetType.AUDIO -> {
riveWorker.unregisterAudio(name)
val result = AudioAsset.fromBytes(riveWorker, data)
if (result is app.rive.Result.Success) {
result.value.register(name)
Log.i(TAG, "Audio '$name' registered")
}
}
}
}
private fun inferAssetType(name: String, data: ByteArray, explicitType: RiveAssetType?): AssetType {
// Explicit type provided by the caller — always preferred.
when (explicitType) {
RiveAssetType.IMAGE -> return AssetType.IMAGE
RiveAssetType.FONT -> return AssetType.FONT
RiveAssetType.AUDIO -> return AssetType.AUDIO
null -> Unit
}
// No explicit type — fall back to extension / magic-byte inference.
// Deprecated: provide `type` on your asset entry to avoid this.
Log.w(
TAG,
"No type provided for '$name'. Falling back to extension/magic-byte inference — " +
"set type: 'image' | 'font' | 'audio' on the asset to silence this warning."
)
val ext = name.substringAfterLast('.', "").lowercase()
return when (ext) {
"png", "jpg", "jpeg", "webp", "gif", "bmp", "svg" -> AssetType.IMAGE
"ttf", "otf", "woff", "woff2" -> AssetType.FONT
"wav", "mp3", "ogg", "flac", "aac", "m4a" -> AssetType.AUDIO
else -> inferFromMagicBytes(data)
}
}
private fun inferFromMagicBytes(data: ByteArray): AssetType {
fun ByteArray.startsWith(vararg bytes: Int) =
bytes.size <= size && bytes.indices.all { this[it] == bytes[it].toByte() }
fun ByteArray.matchesAt(offset: Int, vararg bytes: Int) =
offset + bytes.size <= size && bytes.indices.all { this[offset + it] == bytes[it].toByte() }
return when {
data.startsWith(0x89, 0x50, 0x4E, 0x47) -> AssetType.IMAGE // PNG
data.startsWith(0xFF, 0xD8, 0xFF) -> AssetType.IMAGE // JPEG
data.startsWith(0x49, 0x44, 0x33) -> AssetType.AUDIO // MP3 (ID3)
data.startsWith(0x00, 0x01, 0x00, 0x00) -> AssetType.FONT // TrueType
data.startsWith(0x4F, 0x54, 0x54, 0x4F) -> AssetType.FONT // OpenType (OTTO)
data.startsWith(0x52, 0x49, 0x46, 0x46) ->
if (data.matchesAt(8, 0x57, 0x41, 0x56, 0x45)) {
AssetType.AUDIO // WAV (WAVE)
} else if (data.matchesAt(8, 0x57, 0x45, 0x42, 0x50)) {
AssetType.IMAGE // WebP (WEBP)
} else {
RiveLog.w(TAG, "Unknown RIFF asset, assuming IMAGE. Declare asset type explicitly to avoid this.")
AssetType.IMAGE
}
else -> {
RiveLog.w(TAG, "Could not infer asset type from magic bytes, assuming IMAGE. Declare asset type explicitly to avoid this.")
AssetType.IMAGE
}
}
}
enum class AssetType { IMAGE, FONT, AUDIO }
}