buffer byte loading

This commit is contained in:
Thibault Deckers 2022-08-31 18:23:24 +02:00
parent 5ce66fbd8a
commit 04b6576190
11 changed files with 161 additions and 46 deletions

View file

@ -12,8 +12,10 @@ import android.os.Bundle
import android.util.Log
import android.widget.RemoteViews
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.DeviceHandler
import deckers.thibault.aves.channel.calls.MediaFetchHandler
import deckers.thibault.aves.channel.calls.MediaFetchBytesHandler
import deckers.thibault.aves.channel.calls.MediaFetchObjectHandler
import deckers.thibault.aves.channel.calls.MediaStoreHandler
import deckers.thibault.aves.channel.streams.ImageByteStreamHandler
import deckers.thibault.aves.channel.streams.MediaStoreStreamHandler
@ -190,7 +192,8 @@ class HomeWidgetProvider : AppWidgetProvider() {
// - need Context
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(context))
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(context))
MethodChannel(messenger, MediaFetchHandler.CHANNEL).setMethodCallHandler(MediaFetchHandler(context))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(context))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(context))
// result streaming: dart -> platform ->->-> dart
// - need Context

View file

@ -16,6 +16,7 @@ import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
@ -70,7 +71,8 @@ open class MainActivity : FlutterActivity() {
MethodChannel(messenger, GeocodingHandler.CHANNEL).setMethodCallHandler(GeocodingHandler(this))
MethodChannel(messenger, GlobalSearchHandler.CHANNEL).setMethodCallHandler(GlobalSearchHandler(this))
MethodChannel(messenger, HomeWidgetHandler.CHANNEL).setMethodCallHandler(HomeWidgetHandler(this))
MethodChannel(messenger, MediaFetchHandler.CHANNEL).setMethodCallHandler(MediaFetchHandler(this))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))

View file

@ -4,6 +4,7 @@ import android.service.dreams.DreamService
import android.util.Log
import android.view.View
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.window.ServiceWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
@ -99,7 +100,8 @@ class ScreenSaverService : DreamService() {
// - need Context
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
MethodChannel(messenger, MediaFetchHandler.CHANNEL).setMethodCallHandler(MediaFetchHandler(this))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(this))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
MethodChannel(messenger, MediaStoreHandler.CHANNEL).setMethodCallHandler(MediaStoreHandler(this))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))

View file

@ -8,6 +8,7 @@ import android.os.Handler
import android.os.Looper
import android.util.Log
import app.loup.streams_channel.StreamsChannel
import deckers.thibault.aves.channel.AvesByteSendingMethodCodec
import deckers.thibault.aves.channel.calls.*
import deckers.thibault.aves.channel.calls.window.ActivityWindowHandler
import deckers.thibault.aves.channel.calls.window.WindowHandler
@ -34,7 +35,8 @@ class WallpaperActivity : FlutterActivity() {
// - need Context
MethodChannel(messenger, DeviceHandler.CHANNEL).setMethodCallHandler(DeviceHandler(this))
MethodChannel(messenger, EmbeddedDataHandler.CHANNEL).setMethodCallHandler(EmbeddedDataHandler(this))
MethodChannel(messenger, MediaFetchHandler.CHANNEL).setMethodCallHandler(MediaFetchHandler(this))
MethodChannel(messenger, MediaFetchBytesHandler.CHANNEL, AvesByteSendingMethodCodec.INSTANCE).setMethodCallHandler(MediaFetchBytesHandler(context))
MethodChannel(messenger, MediaFetchObjectHandler.CHANNEL).setMethodCallHandler(MediaFetchObjectHandler(this))
MethodChannel(messenger, MetadataFetchHandler.CHANNEL).setMethodCallHandler(MetadataFetchHandler(this))
MethodChannel(messenger, StorageHandler.CHANNEL).setMethodCallHandler(StorageHandler(this))
// - need ContextWrapper

View file

@ -0,0 +1,52 @@
package deckers.thibault.aves.channel
import android.util.Log
import deckers.thibault.aves.utils.LogUtils
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodCodec
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.common.StandardMethodCodec
import java.nio.ByteBuffer
class AvesByteSendingMethodCodec private constructor() : MethodCodec {
override fun decodeMethodCall(methodCall: ByteBuffer): MethodCall {
return STANDARD.decodeMethodCall(methodCall)
}
override fun decodeEnvelope(envelope: ByteBuffer): Any {
return STANDARD.decodeEnvelope(envelope)
}
override fun encodeMethodCall(methodCall: MethodCall): ByteBuffer {
return STANDARD.encodeMethodCall(methodCall)
}
override fun encodeSuccessEnvelope(result: Any?): ByteBuffer {
if (result is ByteArray) {
val size = result.size
return ByteBuffer.allocateDirect(4 + size).apply {
put(0)
put(result)
}
}
Log.e(LOG_TAG, "encodeSuccessEnvelope failed with result=$result")
return ByteBuffer.allocateDirect(0)
}
override fun encodeErrorEnvelope(errorCode: String, errorMessage: String?, errorDetails: Any?): ByteBuffer {
Log.e(LOG_TAG, "encodeErrorEnvelope failed with errorCode=$errorCode, errorMessage=$errorMessage, errorDetails=$errorDetails")
return ByteBuffer.allocateDirect(0)
}
override fun encodeErrorEnvelopeWithStacktrace(errorCode: String, errorMessage: String?, errorDetails: Any?, errorStacktrace: String?): ByteBuffer {
Log.e(LOG_TAG, "encodeErrorEnvelopeWithStacktrace failed with errorCode=$errorCode, errorMessage=$errorMessage, errorDetails=$errorDetails, errorStacktrace=$errorStacktrace")
return ByteBuffer.allocateDirect(0)
}
companion object {
private val LOG_TAG = LogUtils.createTag<AvesByteSendingMethodCodec>()
val INSTANCE = AvesByteSendingMethodCodec()
private val STANDARD = StandardMethodCodec(StandardMessageCodec.INSTANCE)
}
}

View file

@ -3,16 +3,11 @@ package deckers.thibault.aves.channel.calls
import android.content.Context
import android.graphics.Rect
import android.net.Uri
import com.bumptech.glide.Glide
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.channel.calls.Coresult.Companion.safeSuspend
import deckers.thibault.aves.channel.calls.fetchers.RegionFetcher
import deckers.thibault.aves.channel.calls.fetchers.SvgRegionFetcher
import deckers.thibault.aves.channel.calls.fetchers.ThumbnailFetcher
import deckers.thibault.aves.channel.calls.fetchers.TiffRegionFetcher
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback
import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider
import deckers.thibault.aves.utils.MimeTypes
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
@ -23,7 +18,7 @@ import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
class MediaFetchHandler(private val context: Context) : MethodCallHandler {
class MediaFetchBytesHandler(private val context: Context) : MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
private val density = context.resources.displayMetrics.density
@ -31,34 +26,12 @@ class MediaFetchHandler(private val context: Context) : MethodCallHandler {
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getEntry" -> ioScope.launch { safe(call, result, ::getEntry) }
"getThumbnail" -> ioScope.launch { safeSuspend(call, result, ::getThumbnail) }
"getRegion" -> ioScope.launch { safeSuspend(call, result, ::getRegion) }
"clearSizedThumbnailDiskCache" -> ioScope.launch { safe(call, result, ::clearSizedThumbnailDiskCache) }
else -> result.notImplemented()
}
}
private fun getEntry(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType") // MIME type is optional
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
if (uri == null) {
result.error("getEntry-args", "missing arguments", null)
return
}
val provider = getProvider(uri)
if (provider == null) {
result.error("getEntry-provider", "failed to find provider for uri=$uri", null)
return
}
provider.fetchSingle(context, uri, mimeType, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = result.success(fields)
override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri", throwable.message)
})
}
private suspend fun getThumbnail(call: MethodCall, result: MethodChannel.Result) {
val uri = call.argument<String>("uri")
val mimeType = call.argument<String>("mimeType")
@ -137,12 +110,7 @@ class MediaFetchHandler(private val context: Context) : MethodCallHandler {
}
}
private fun clearSizedThumbnailDiskCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Glide.get(context).clearDiskCache()
result.success(null)
}
companion object {
const val CHANNEL = "deckers.thibault/aves/media_fetch"
const val CHANNEL = "deckers.thibault/aves/media_fetch_bytes"
}
}

View file

@ -0,0 +1,57 @@
package deckers.thibault.aves.channel.calls
import android.content.Context
import android.net.Uri
import com.bumptech.glide.Glide
import deckers.thibault.aves.channel.calls.Coresult.Companion.safe
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.model.provider.ImageProvider.ImageOpCallback
import deckers.thibault.aves.model.provider.ImageProviderFactory.getProvider
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
class MediaFetchObjectHandler(private val context: Context) : MethodCallHandler {
private val ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"getEntry" -> ioScope.launch { safe(call, result, ::getEntry) }
"clearSizedThumbnailDiskCache" -> ioScope.launch { safe(call, result, ::clearSizedThumbnailDiskCache) }
else -> result.notImplemented()
}
}
private fun getEntry(call: MethodCall, result: MethodChannel.Result) {
val mimeType = call.argument<String>("mimeType") // MIME type is optional
val uri = call.argument<String>("uri")?.let { Uri.parse(it) }
if (uri == null) {
result.error("getEntry-args", "missing arguments", null)
return
}
val provider = getProvider(uri)
if (provider == null) {
result.error("getEntry-provider", "failed to find provider for uri=$uri", null)
return
}
provider.fetchSingle(context, uri, mimeType, object : ImageOpCallback {
override fun onSuccess(fields: FieldMap) = result.success(fields)
override fun onFailure(throwable: Throwable) = result.error("getEntry-failure", "failed to get entry for uri=$uri", throwable.message)
})
}
private fun clearSizedThumbnailDiskCache(@Suppress("unused_parameter") call: MethodCall, result: MethodChannel.Result) {
Glide.get(context).clearDiskCache()
result.success(null)
}
companion object {
const val CHANNEL = "deckers.thibault/aves/media_fetch_object"
}
}

View file

@ -12,7 +12,7 @@ class TopoJson {
static Topology? _isoParse(String jsonData) {
try {
final data = json.decode(jsonData) as Map<String, dynamic>;
final data = jsonDecode(jsonData) as Map<String, dynamic>;
return Topology.parse(data);
} catch (error, stack) {
// an unhandled error in a spawn isolate would make the app crash

View file

@ -0,0 +1,27 @@
import 'package:flutter/services.dart';
class AvesByteReceivingMethodCodec extends StandardMethodCodec {
const AvesByteReceivingMethodCodec() : super();
@override
dynamic decodeEnvelope(ByteData envelope) {
// First byte is zero in success case, and non-zero otherwise.
if (envelope.lengthInBytes == 0) {
throw const FormatException('Expected envelope, got nothing');
}
final ReadBuffer buffer = ReadBuffer(envelope);
if (buffer.getUint8() == 0) {
return envelope.buffer.asUint8List(envelope.offsetInBytes + 1, envelope.lengthInBytes - 1);
}
final Object? errorCode = messageCodec.readValue(buffer);
final Object? errorMessage = messageCodec.readValue(buffer);
final Object? errorDetails = messageCodec.readValue(buffer);
final String? errorStacktrace = (buffer.hasRemaining) ? messageCodec.readValue(buffer) as String? : null;
if (errorCode is String && (errorMessage == null || errorMessage is String) && !buffer.hasRemaining) {
throw PlatformException(code: errorCode, message: errorMessage as String?, details: errorDetails, stacktrace: errorStacktrace);
} else {
throw const FormatException('Invalid envelope');
}
}
}

View file

@ -6,6 +6,7 @@ import 'package:aves/ref/mime_types.dart';
import 'package:aves/services/common/output_buffer.dart';
import 'package:aves/services/common/service_policy.dart';
import 'package:aves/services/common/services.dart';
import 'package:aves/services/media/byte_receiving_codec.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:streams_channel/streams_channel.dart';
@ -66,14 +67,15 @@ abstract class MediaFetchService {
}
class PlatformMediaFetchService implements MediaFetchService {
static const _platform = MethodChannel('deckers.thibault/aves/media_fetch');
static const _platformObject = MethodChannel('deckers.thibault/aves/media_fetch_object');
static const _platformBytes = MethodChannel('deckers.thibault/aves/media_fetch_bytes', AvesByteReceivingMethodCodec());
static final _byteStream = StreamsChannel('deckers.thibault/aves/media_byte_stream');
static const double _thumbnailDefaultSize = 64.0;
@override
Future<AvesEntry?> getEntry(String uri, String? mimeType) async {
try {
final result = await _platform.invokeMethod('getEntry', <String, dynamic>{
final result = await _platformObject.invokeMethod('getEntry', <String, dynamic>{
'uri': uri,
'mimeType': mimeType,
}) as Map;
@ -171,7 +173,7 @@ class PlatformMediaFetchService implements MediaFetchService {
return servicePolicy.call(
() async {
try {
final result = await _platform.invokeMethod('getRegion', <String, dynamic>{
final result = await _platformBytes.invokeMethod('getRegion', <String, dynamic>{
'uri': uri,
'mimeType': mimeType,
'pageId': pageId,
@ -211,7 +213,7 @@ class PlatformMediaFetchService implements MediaFetchService {
return servicePolicy.call(
() async {
try {
final result = await _platform.invokeMethod('getThumbnail', <String, dynamic>{
final result = await _platformBytes.invokeMethod('getThumbnail', <String, dynamic>{
'uri': uri,
'mimeType': mimeType,
'dateModifiedSecs': dateModifiedSecs,
@ -238,7 +240,7 @@ class PlatformMediaFetchService implements MediaFetchService {
@override
Future<void> clearSizedThumbnailDiskCache() async {
try {
return _platform.invokeMethod('clearSizedThumbnailDiskCache');
return _platformObject.invokeMethod('clearSizedThumbnailDiskCache');
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}

View file

@ -43,7 +43,7 @@ class _XmpDirTileState extends State<XmpDirTile> {
super.initState();
_tags = Map.from(widget.tags)..remove(schemaRegistryPrefixesKey);
final prefixesJson = widget.allTags[schemaRegistryPrefixesKey];
final Map<String, dynamic> prefixesDecoded = prefixesJson != null ? json.decode(prefixesJson) : {};
final Map<String, dynamic> prefixesDecoded = prefixesJson != null ? jsonDecode(prefixesJson) : {};
_schemaRegistryPrefixes = Map.fromEntries(prefixesDecoded.entries.map((kv) => MapEntry(kv.key, kv.value as String)));
}