attach method
Attach this RxPhoto to the Frame's dataResponse characteristic stream.
If isRaw is true, then quality and resolution must be specified and match the raw image requested from Frame
so that the correct jpeg header can be prepended.
Implementation
Stream<Uint8List> attach(Stream<List<int>> dataResponse) {
// TODO check for illegal state - attach() already called on this RxPhoto etc?
// might be possible though after a clean close(), do I want to prevent it?
// the image data as a list of bytes that accumulates with each packet
List<int> imageData = List.empty(growable: true);
int rawOffset = 0;
// if isRaw is true, a jpeg header must be prepended to the raw image data
if (isRaw) {
// fetch the jpeg header for this quality level and resolution
String key = '${quality}_$resolution';
if (!jpegHeaderMap.containsKey(key)) {
throw Exception('No jpeg header found for quality level $quality and resolution $resolution - request full jpeg once before requesting raw');
}
// add the jpeg header bytes for this quality level (623 bytes)
imageData.addAll(jpegHeaderMap[key]!);
}
// the subscription to the underlying data stream
StreamSubscription<List<int>>? dataResponseSubs;
// Our stream controller that transforms/accumulates the raw data into images (as bytes)
_controller = StreamController();
_controller!.onListen = () {
_log.fine('ImageDataResponse stream subscribed');
dataResponseSubs = dataResponse
.where(
(data) => data[0] == nonFinalChunkFlag || data[0] == finalChunkFlag)
.listen((data) {
if (data[0] == nonFinalChunkFlag) {
imageData += data.sublist(1);
rawOffset += data.length - 1;
}
// the last chunk has a first byte of finalChunkFlag so stop after this
else if (data[0] == finalChunkFlag) {
imageData += data.sublist(1);
rawOffset += data.length - 1;
Uint8List finalImageBytes = Uint8List.fromList(imageData);
// if this image is a full jpeg, save the jpeg header for this quality level and resolution
// so that it can be prepended to raw images of the same quality level and resolution
if (!isRaw) {
String key = '${quality}_$resolution';
if (!jpegHeaderMap.containsKey(key)) {
jpegHeaderMap[key] = finalImageBytes.sublist(0, 623);
}
}
// When full image data is received,
// rotate the image counter-clockwise 90 degrees to make it upright
// unless requested otherwise (to save processing)
if (upright) {
image_lib.Image? im = image_lib.decodeJpg(finalImageBytes);
im = image_lib.copyRotate(im!, angle: 270);
// emit the rotated jpeg bytes
_controller!.add(image_lib.encodeJpg(im));
}
else {
// emit the original rotation jpeg bytes
_controller!.add(finalImageBytes);
}
// clear the buffer
imageData.clear();
rawOffset = 0;
// and close the stream
_controller!.close();
}
_log.finer(() => 'Chunk size: ${data.length - 1}, rawOffset: $rawOffset');
}, onDone: _controller!.close, onError: _controller!.addError);
_log.fine('Controller being listened to');
};
_controller!.onCancel = () {
_log.fine('ImageDataResponse stream unsubscribed');
dataResponseSubs?.cancel();
_controller!.close();
};
return _controller!.stream;
}