fix: only show when video streaming is enabled (#7031)
## Description ## Tests
This commit is contained in:
@@ -181,6 +181,8 @@ PODS:
|
||||
- PromisesObjC (2.4.0)
|
||||
- receive_sharing_intent (1.8.1):
|
||||
- Flutter
|
||||
- rive_common (0.0.1):
|
||||
- Flutter
|
||||
- rust_lib_photos (0.0.1):
|
||||
- Flutter
|
||||
- SDWebImage (5.21.1):
|
||||
@@ -291,6 +293,7 @@ DEPENDENCIES:
|
||||
- photo_manager (from `.symlinks/plugins/photo_manager/ios`)
|
||||
- privacy_screen (from `.symlinks/plugins/privacy_screen/ios`)
|
||||
- receive_sharing_intent (from `.symlinks/plugins/receive_sharing_intent/ios`)
|
||||
- rive_common (from `.symlinks/plugins/rive_common/ios`)
|
||||
- rust_lib_photos (from `.symlinks/plugins/rust_lib_photos/ios`)
|
||||
- sentry_flutter (from `.symlinks/plugins/sentry_flutter/ios`)
|
||||
- share_plus (from `.symlinks/plugins/share_plus/ios`)
|
||||
@@ -418,6 +421,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/privacy_screen/ios"
|
||||
receive_sharing_intent:
|
||||
:path: ".symlinks/plugins/receive_sharing_intent/ios"
|
||||
rive_common:
|
||||
:path: ".symlinks/plugins/rive_common/ios"
|
||||
rust_lib_photos:
|
||||
:path: ".symlinks/plugins/rust_lib_photos/ios"
|
||||
sentry_flutter:
|
||||
@@ -510,6 +515,7 @@ SPEC CHECKSUMS:
|
||||
privacy_screen: 3159a541f5d3a31bea916cfd4e58f9dc722b3fd4
|
||||
PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47
|
||||
receive_sharing_intent: 222384f00ffe7e952bbfabaa9e3967cb87e5fe00
|
||||
rive_common: dd421daaf9ae69f0125aa761dd96abd278399952
|
||||
rust_lib_photos: 04d3901908d2972192944083310b65abf410774c
|
||||
SDWebImage: f29024626962457f3470184232766516dee8dfea
|
||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||
|
||||
@@ -565,6 +565,7 @@
|
||||
"${BUILT_PRODUCTS_DIR}/photo_manager/photo_manager.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/privacy_screen/privacy_screen.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/receive_sharing_intent/receive_sharing_intent.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/rive_common/rive_common.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/rust_lib_photos/rust_lib_photos.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/sentry_flutter/sentry_flutter.framework",
|
||||
"${BUILT_PRODUCTS_DIR}/share_plus/share_plus.framework",
|
||||
@@ -662,6 +663,7 @@
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/photo_manager.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/privacy_screen.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/receive_sharing_intent.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/rive_common.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/rust_lib_photos.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/sentry_flutter.framework",
|
||||
"${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/share_plus.framework",
|
||||
|
||||
@@ -49,32 +49,47 @@ class ComputeController {
|
||||
|
||||
ComputeController() {
|
||||
_logger.info('ComputeController constructor');
|
||||
init();
|
||||
_logger.info('init done ');
|
||||
}
|
||||
|
||||
// Directly assign the values + Attach listener for compute controller
|
||||
Future<void> init() async {
|
||||
// Interaction Timer
|
||||
_startInteractionTimer(kDefaultInteractionTimeout);
|
||||
|
||||
// Thermal related
|
||||
_onThermalStateUpdate(await _thermal.thermalStatus);
|
||||
_thermal.onThermalStatusChanged.listen((ThermalStatus thermalState) {
|
||||
_onThermalStateUpdate(thermalState);
|
||||
});
|
||||
|
||||
// Battery State
|
||||
if (Platform.isIOS) {
|
||||
if (kDebugMode) {
|
||||
_logger.info(
|
||||
_logger.fine(
|
||||
"iOS battery info stream is not available in simulator, disabling in debug mode",
|
||||
);
|
||||
// if you need to test on physical device, uncomment this check
|
||||
return;
|
||||
} else {
|
||||
// Update Battery state for iOS
|
||||
_oniOSBatteryStateUpdate(await BatteryInfoPlugin().iosBatteryInfo);
|
||||
BatteryInfoPlugin()
|
||||
.iosBatteryInfoStream
|
||||
.listen((IosBatteryInfo? batteryInfo) {
|
||||
_oniOSBatteryStateUpdate(batteryInfo);
|
||||
});
|
||||
}
|
||||
BatteryInfoPlugin()
|
||||
.iosBatteryInfoStream
|
||||
.listen((IosBatteryInfo? batteryInfo) {
|
||||
_oniOSBatteryStateUpdate(batteryInfo);
|
||||
});
|
||||
}
|
||||
if (Platform.isAndroid) {
|
||||
} else if (Platform.isAndroid) {
|
||||
// Update Battery state for Android
|
||||
_onAndroidBatteryStateUpdate(
|
||||
await BatteryInfoPlugin().androidBatteryInfo,
|
||||
);
|
||||
BatteryInfoPlugin()
|
||||
.androidBatteryInfoStream
|
||||
.listen((AndroidBatteryInfo? batteryInfo) {
|
||||
_onAndroidBatteryStateUpdate(batteryInfo);
|
||||
});
|
||||
}
|
||||
_thermal.onThermalStatusChanged.listen((ThermalStatus thermalState) {
|
||||
_onThermalStateUpdate(thermalState);
|
||||
});
|
||||
_logger.info('init done ');
|
||||
}
|
||||
|
||||
bool requestCompute({
|
||||
|
||||
@@ -157,7 +157,25 @@ class VideoPreviewService {
|
||||
|
||||
bool isCurrentlyProcessing(int? uploadedFileID) {
|
||||
if (uploadedFileID == null) return false;
|
||||
return uploadingFileId == uploadedFileID;
|
||||
|
||||
// Also check if file is in queue or other processing states
|
||||
final item = _items[uploadedFileID];
|
||||
if (item != null) {
|
||||
switch (item.status) {
|
||||
case PreviewItemStatus.inQueue:
|
||||
case PreviewItemStatus.compressing:
|
||||
case PreviewItemStatus.uploading:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
PreviewItemStatus? getProcessingStatus(int uploadedFileID) {
|
||||
return _items[uploadedFileID]?.status;
|
||||
}
|
||||
|
||||
Future<bool> _isRecreateOperation(EnteFile file) async {
|
||||
@@ -256,15 +274,20 @@ class VideoPreviewService {
|
||||
|
||||
Future<void> chunkAndUploadVideo(
|
||||
BuildContext? ctx,
|
||||
EnteFile enteFile, [
|
||||
EnteFile enteFile, {
|
||||
/// Indicates this function is an continuation of a chunking thread
|
||||
bool continuation = false,
|
||||
// not used currently
|
||||
bool forceUpload = false,
|
||||
bool isManual = false,
|
||||
]) async {
|
||||
}) async {
|
||||
final bool isManual =
|
||||
await uploadLocksDB.isInStreamQueue(enteFile.uploadedFileID!);
|
||||
final canStream = _isPermissionGranted();
|
||||
if (!canStream) {
|
||||
_logger.info(
|
||||
"Pause preview due to disabledSteaming($isVideoStreamingEnabled) or computeController permission) - isManual: $isManual",
|
||||
);
|
||||
computeController.releaseCompute(stream: true);
|
||||
if (isVideoStreamingEnabled) _logger.info("No permission to run compute");
|
||||
clearQueue();
|
||||
return;
|
||||
@@ -311,7 +334,7 @@ class VideoPreviewService {
|
||||
}
|
||||
|
||||
// check if there is already a preview in processing
|
||||
if (uploadingFileId >= 0) {
|
||||
if (!continuation && uploadingFileId >= 0) {
|
||||
if (uploadingFileId == enteFile.uploadedFileID) return;
|
||||
|
||||
_items[enteFile.uploadedFileID!] = PreviewItem(
|
||||
@@ -562,21 +585,26 @@ class VideoPreviewService {
|
||||
_removeFile(enteFile);
|
||||
_removeFromLocks(enteFile).ignore();
|
||||
}
|
||||
// reset uploading status if this was getting processed
|
||||
if (uploadingFileId == enteFile.uploadedFileID!) {
|
||||
uploadingFileId = -1;
|
||||
}
|
||||
_logger.info(
|
||||
"[chunk] Processing ${_items.length} items for streaming, $error",
|
||||
);
|
||||
// process next file
|
||||
if (fileQueue.isNotEmpty) {
|
||||
// process next file
|
||||
_logger.info(
|
||||
"[chunk] Processing ${_items.length} items for streaming, $error",
|
||||
);
|
||||
final entry = fileQueue.entries.first;
|
||||
final file = entry.value;
|
||||
fileQueue.remove(entry.key);
|
||||
await chunkAndUploadVideo(ctx, file);
|
||||
await chunkAndUploadVideo(
|
||||
ctx,
|
||||
file,
|
||||
continuation: true,
|
||||
);
|
||||
} else {
|
||||
_logger.info(
|
||||
"[chunk] Nothing to process releasing compute, $error",
|
||||
);
|
||||
computeController.releaseCompute(stream: true);
|
||||
|
||||
uploadingFileId = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -987,8 +1015,9 @@ class VideoPreviewService {
|
||||
}
|
||||
|
||||
// generate stream for all files after cutoff date
|
||||
Future<void> _putFilesForPreviewCreation() async {
|
||||
if (!isVideoStreamingEnabled || !await canUseHighBandwidth()) return;
|
||||
// returns false if it fails to launch chuncking function
|
||||
Future<bool> _putFilesForPreviewCreation() async {
|
||||
if (!isVideoStreamingEnabled || !await canUseHighBandwidth()) return false;
|
||||
|
||||
Map<int, String> failureFiles = {};
|
||||
Map<int, String> manualQueueFiles = {};
|
||||
@@ -1124,7 +1153,7 @@ class VideoPreviewService {
|
||||
final totalFiles = fileQueue.length;
|
||||
if (totalFiles == 0) {
|
||||
_logger.info("[init] No preview to cache");
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
_logger.info(
|
||||
@@ -1136,6 +1165,7 @@ class VideoPreviewService {
|
||||
final file = entry.value;
|
||||
fileQueue.remove(entry.key);
|
||||
chunkAndUploadVideo(null, file).ignore();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _allowStream() {
|
||||
@@ -1152,9 +1182,11 @@ class VideoPreviewService {
|
||||
);
|
||||
}
|
||||
|
||||
/// To check if it's enabled, device is healthy and running streaming
|
||||
bool _isPermissionGranted() {
|
||||
return isVideoStreamingEnabled &&
|
||||
computeController.computeState == ComputeRunState.generatingStream;
|
||||
computeController.computeState == ComputeRunState.generatingStream &&
|
||||
computeController.isDeviceHealthy;
|
||||
}
|
||||
|
||||
void queueFiles({
|
||||
@@ -1169,7 +1201,11 @@ class VideoPreviewService {
|
||||
if (!isStreamAllowed) return;
|
||||
|
||||
await _ensurePreviewIdsInitialized();
|
||||
await _putFilesForPreviewCreation();
|
||||
final result = await _putFilesForPreviewCreation();
|
||||
// Cannot proceed to stream generation, would have to release compute ASAP
|
||||
if (!result) {
|
||||
computeController.releaseCompute(stream: true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,7 +499,8 @@ class FileAppBarState extends State<FileAppBar> {
|
||||
widget.file.isUploaded &&
|
||||
widget.file.fileSize != null &&
|
||||
(widget.file.pubMagicMetadata?.sv ?? 0) != 1 &&
|
||||
widget.file.ownerID == userId;
|
||||
widget.file.ownerID == userId &&
|
||||
VideoPreviewService.instance.isVideoStreamingEnabled;
|
||||
}
|
||||
|
||||
Future<void> _handleVideoStream(String streamType) async {
|
||||
|
||||
@@ -7,8 +7,8 @@ import 'package:logging/logging.dart';
|
||||
import 'package:photos/core/cache/thumbnail_in_memory_cache.dart';
|
||||
import 'package:photos/core/constants.dart';
|
||||
import 'package:photos/core/errors.dart';
|
||||
import 'package:photos/core/exceptions.dart';
|
||||
import 'package:photos/core/event_bus.dart';
|
||||
import 'package:photos/core/exceptions.dart';
|
||||
import 'package:photos/db/files_db.dart';
|
||||
import 'package:photos/db/trash_db.dart';
|
||||
import 'package:photos/events/files_updated_event.dart';
|
||||
@@ -332,7 +332,9 @@ class _ThumbnailWidgetState extends State<ThumbnailWidget> {
|
||||
}
|
||||
//Do not retry if the widget is not mounted
|
||||
if (!mounted) {
|
||||
throw WidgetUnmountedException("Thumbnail loading cancelled: widget unmounted");
|
||||
throw WidgetUnmountedException(
|
||||
"Thumbnail loading cancelled: widget unmounted",
|
||||
);
|
||||
}
|
||||
|
||||
retryAttempts++;
|
||||
|
||||
@@ -32,27 +32,34 @@ class VideoStreamChangeWidget extends StatefulWidget {
|
||||
|
||||
class _VideoStreamChangeWidgetState extends State<VideoStreamChangeWidget> {
|
||||
StreamSubscription<VideoPreviewStateChangedEvent>? _subscription;
|
||||
bool isCurrentlyProcessing = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize processing state safely in initState
|
||||
isCurrentlyProcessing = VideoPreviewService.instance
|
||||
.isCurrentlyProcessing(widget.file.uploadedFileID);
|
||||
|
||||
_subscription =
|
||||
Bus.instance.on<VideoPreviewStateChangedEvent>().listen((event) {
|
||||
final fileId = event.fileId;
|
||||
if (widget.file.uploadedFileID != fileId) {
|
||||
return; // Not for this file
|
||||
}
|
||||
|
||||
final status = event.status;
|
||||
|
||||
// Handle different states
|
||||
switch (status) {
|
||||
case PreviewItemStatus.inQueue:
|
||||
case PreviewItemStatus.uploaded:
|
||||
case PreviewItemStatus.failed:
|
||||
setState(() {});
|
||||
break;
|
||||
default:
|
||||
// Handle different states - will be false for different files or non-processing states
|
||||
final newProcessingState = widget.file.uploadedFileID == fileId && switch (status) {
|
||||
PreviewItemStatus.inQueue ||
|
||||
PreviewItemStatus.retry ||
|
||||
PreviewItemStatus.compressing ||
|
||||
PreviewItemStatus.uploading =>
|
||||
true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
// Only update state if value changed
|
||||
if (isCurrentlyProcessing != newProcessingState) {
|
||||
isCurrentlyProcessing = newProcessingState;
|
||||
setState(() {});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -63,14 +70,28 @@ class _VideoStreamChangeWidgetState extends State<VideoStreamChangeWidget> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
String _getStatusText(BuildContext context, PreviewItemStatus? status) {
|
||||
switch (status) {
|
||||
case PreviewItemStatus.inQueue:
|
||||
case PreviewItemStatus.retry:
|
||||
return AppLocalizations.of(context).queued;
|
||||
case PreviewItemStatus.compressing:
|
||||
case PreviewItemStatus.uploading:
|
||||
default:
|
||||
return AppLocalizations.of(context).creatingStream;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bool isPreviewAvailable = widget.file.uploadedFileID != null &&
|
||||
(fileDataService.previewIds.containsKey(widget.file.uploadedFileID));
|
||||
|
||||
// Check if this file is currently being processed for streaming
|
||||
final bool isCurrentlyProcessing = VideoPreviewService.instance
|
||||
.isCurrentlyProcessing(widget.file.uploadedFileID);
|
||||
// Get the current processing status for more specific messaging
|
||||
final processingStatus = widget.file.uploadedFileID != null
|
||||
? VideoPreviewService.instance
|
||||
.getProcessingStatus(widget.file.uploadedFileID!)
|
||||
: null;
|
||||
|
||||
final colorScheme = getEnteColorScheme(context);
|
||||
|
||||
@@ -125,7 +146,7 @@ class _VideoStreamChangeWidgetState extends State<VideoStreamChangeWidget> {
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
AppLocalizations.of(context).creatingStream,
|
||||
_getStatusText(context, processingStatus),
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
|
||||
Reference in New Issue
Block a user