diff --git a/lib/api/events.dart b/lib/api/events.dart index fba24512..2c6b4c61 100644 --- a/lib/api/events.dart +++ b/lib/api/events.dart @@ -129,12 +129,12 @@ extension EventsExtension on API { ? int.parse(eventObject['content']['media_id']) : null, mediaURL: eventObject.containsKey('content') - ? Uri.parse( - (eventObject['content']['content'] as String).replaceAll( - 'https://', - 'https://${Uri.encodeComponent(server.login)}:${Uri.encodeComponent(server.password)}@', - ), - ) + ? Uri.parse(eventObject['content']['content'] as String + // .replaceAll( + // 'https://', + // 'https://${Uri.encodeComponent(server.login)}:${Uri.encodeComponent(server.password)}@', + // ), + ) : null, ); return event; diff --git a/lib/main.dart b/lib/main.dart index 4bec60ef..80b9d323 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -270,12 +270,14 @@ class _UnityAppState extends State switch (settings.kStreamOnBackground.value) { case NetworkUsage.auto: case NetworkUsage.wifiOnly: - debugPrint('Pausing all streams'); final connectionType = await Connectivity().checkConnectivity(); if ([ ConnectivityResult.bluetooth, ConnectivityResult.mobile, - ].any(connectionType.contains)) UnityPlayers.pauseAll(); + ].any(connectionType.contains)) { + debugPrint('Pausing all streams'); + UnityPlayers.pauseAll(); + } break; case NetworkUsage.never: diff --git a/lib/models/event.dart b/lib/models/event.dart index 48dd0096..d1d54e61 100644 --- a/lib/models/event.dart +++ b/lib/models/event.dart @@ -206,6 +206,20 @@ class Event { } } + String get mediaPath { + return '${mediaURL!.scheme}://' + '${Uri.encodeComponent(server.login)}' + ':' + '${Uri.encodeComponent(server.password)}' + '@' + '${mediaURL!.host}' + ':' + '${mediaURL!.port}' + '${mediaURL!.path}' + '?' + '${mediaURL!.query}'; + } + Event copyWith({ Server? server, int? id, diff --git a/lib/providers/downloads_provider.dart b/lib/providers/downloads_provider.dart index 3a975a73..1a0a0e12 100644 --- a/lib/providers/downloads_provider.dart +++ b/lib/providers/downloads_provider.dart @@ -119,10 +119,9 @@ class DownloadsManager extends UnityProvider { } } - if (dir == null) { - final docsDir = await getApplicationSupportDirectory(); - dir = Directory(path.join(docsDir.path, 'downloads')); - } + dir ??= Directory( + path.join((await getApplicationSupportDirectory()).path, 'downloads'), + ); } on StateError catch (e) { debugPrint('Failed to get default downloads directory: $e'); } catch (error, stack) { @@ -297,6 +296,10 @@ class DownloadsManager extends UnityProvider { /// Downloads the given [event] and returns the path of the downloaded file Future _downloadEventFile(Event event, String dir) async { + if (event.mediaURL == null) { + throw ArgumentError('The event does not have a mediaURL'); + } + if (downloading.entries.any((de) => de.key.id == event.id)) { return downloading.entries .firstWhere((de) => de.key.id == event.id) @@ -304,7 +307,7 @@ class DownloadsManager extends UnityProvider { .$2; } writeLogToFile( - 'downloads(${event.id}): $dir at ${event.mediaURL!}', + 'downloads(${event.id}): $dir at ${event.mediaPath}', print: true, ); final home = HomeProvider.instance @@ -329,22 +332,41 @@ class DownloadsManager extends UnityProvider { ); } - await Dio().downloadUri( - event.mediaURL!, - downloadPath, - options: Options( - headers: {HttpHeaders.acceptEncodingHeader: '*'}, // disable gzip - ), - onReceiveProgress: (received, total) { - if (total != -1) { - downloading[event] = (received / total, fileName); - writeLogToFile('downloads(${event.id}): ${received / total}'); - notifyListeners(); - } - }, - ); - - home.notLoading(UnityLoadingReason.downloadEvent); + try { + await Dio().download( + event.mediaPath, + downloadPath, + options: Options( + headers: { + HttpHeaders.acceptEncodingHeader: '*', // disable gzip + HttpHeaders.cookieHeader: event.server.cookie!, + }, + ), + onReceiveProgress: (received, total) { + if (total != -1) { + downloading[event] = (received / total, fileName); + writeLogToFile('downloads(${event.id}): ${received / total}'); + notifyListeners(); + } + }, + ); + } on DioException catch (error, stack) { + handleError( + error, + stack, + 'Failed to download event file from ${event.mediaPath}' + ' (${error.response?.statusCode}): ' + '${error.message}. ', + ); + } catch (error, stack) { + handleError( + error, + stack, + 'Failed to download event file', + ); + } finally { + home.notLoading(UnityLoadingReason.downloadEvent); + } return downloadPath; } diff --git a/lib/screens/events_timeline/events_playback.dart b/lib/screens/events_timeline/events_playback.dart index 10bee0c9..1decb5e5 100644 --- a/lib/screens/events_timeline/events_playback.dart +++ b/lib/screens/events_timeline/events_playback.dart @@ -95,58 +95,57 @@ class _EventsPlaybackState extends EventsScreenState { endDate: endDate, ); - final devices = >{}; + if (mounted) { + final devices = >{}; - final events = eventsProvider.loadedEvents!.filteredEvents - ..sort((a, b) { - // Sort the events in a way that the continuous events are displayed first - // Ideally, in the Timeline, the motion events should be displayed on - // top of the continuous events. We need to sort the continuous events - // so that the continuous events don't get on top of the motion events. - final aIsContinuous = a.type == EventType.continuous; - final bIsContinuous = b.type == EventType.continuous; - if (aIsContinuous && !bIsContinuous) return -1; - if (!aIsContinuous && bIsContinuous) return 1; - return 0; - }); - for (final event in events) { - if (event.isAlarm || event.mediaURL == null) continue; + final events = eventsProvider.loadedEvents!.filteredEvents + ..sort((a, b) { + // Sort the events in a way that the continuous events are displayed first + // Ideally, in the Timeline, the motion events should be displayed on + // top of the continuous events. We need to sort the continuous events + // so that the continuous events don't get on top of the motion events. + final aIsContinuous = a.type == EventType.continuous; + final bIsContinuous = b.type == EventType.continuous; + if (aIsContinuous && !bIsContinuous) return -1; + if (!aIsContinuous && bIsContinuous) return 1; + return 0; + }); + for (final event in events) { + if (event.isAlarm || event.mediaURL == null) continue; - if (!DateUtils.isSameDay(event.published, date) || - !DateUtils.isSameDay(event.published.add(event.duration), date)) { - continue; - } - - final device = event.server.devices.firstWhere( - (d) => d.id == event.deviceID, - orElse: () => Device.dump( - name: event.deviceName, - id: event.deviceID, - ), - ); - devices[device] ??= []; + if (!DateUtils.isSameDay(event.published, date) || + !DateUtils.isSameDay(event.published.add(event.duration), date)) { + continue; + } - // This ensures that events that happened at the same time are not - // displayed on the same device. - // - // if (devices[device]!.any((e) { - // return e.published.isInBetween(event.published, event.updated, - // allowSameMoment: true) || - // e.updated.isInBetween(event.published, event.updated, - // allowSameMoment: true) || - // event.published - // .isInBetween(e.published, e.updated, allowSameMoment: true) || - // event.updated - // .isInBetween(e.published, e.updated, allowSameMoment: true); - // })) continue; + final device = event.server.devices.firstWhere( + (d) => d.id == event.deviceID, + orElse: () => Device.dump( + name: event.deviceName, + id: event.deviceID, + ), + ); + devices[device] ??= []; - devices[device]!.add(event); - } + // This ensures that events that happened at the same time are not + // displayed on the same device. + // + // if (devices[device]!.any((e) { + // return e.published.isInBetween(event.published, event.updated, + // allowSameMoment: true) || + // e.updated.isInBetween(event.published, event.updated, + // allowSameMoment: true) || + // event.published + // .isInBetween(e.published, e.updated, allowSameMoment: true) || + // event.updated + // .isInBetween(e.published, e.updated, allowSameMoment: true); + // })) continue; - final parsedTiles = - devices.entries.map((e) => e.buildTimelineTile(context)).toList(); + devices[device]!.add(event); + } - if (mounted) { + final parsedTiles = + devices.entries.map((e) => e.buildTimelineTile(context)).toList(); setState(() { timeline = Timeline( tiles: parsedTiles, @@ -367,7 +366,7 @@ extension DevicesMapExtension on MapEntry> { downloads.getDownloadedPathForEvent(event.id), windows: isDesktopPlatform && Platform.isWindows, ).toString() - : event.mediaURL!.toString(); + : event.mediaPath; return TimelineEvent( startTime: event.published, diff --git a/lib/screens/players/event_player_desktop.dart b/lib/screens/players/event_player_desktop.dart index 17986b14..3a77f037 100644 --- a/lib/screens/players/event_player_desktop.dart +++ b/lib/screens/players/event_player_desktop.dart @@ -144,9 +144,9 @@ class _EventPlayerDesktopState extends State { windows: Platform.isWindows, ).toString(); } - return event.mediaURL.toString(); + return event.mediaPath; }() - : event.mediaURL.toString(); + : event.mediaPath; if (mediaUrl != videoController.dataSource) { debugPrint( @@ -193,7 +193,7 @@ class _EventPlayerDesktopState extends State { Expanded( child: InteractiveViewer( child: UnityVideoView( - heroTag: currentEvent.mediaURL, + heroTag: currentEvent.mediaPath, player: videoController, fit: fit, paneBuilder: (context, player) { diff --git a/lib/screens/players/event_player_mobile.dart b/lib/screens/players/event_player_mobile.dart index 583d05e4..922b94ad 100644 --- a/lib/screens/players/event_player_mobile.dart +++ b/lib/screens/players/event_player_mobile.dart @@ -84,7 +84,7 @@ class __EventPlayerMobileState extends State<_EventPlayerMobile> { downloads.getDownloadedPathForEvent(widget.event.id), windows: Platform.isWindows, ).toString() - : widget.event.mediaURL.toString(); + : widget.event.mediaPath; debugPrint(mediaUrl); if (videoController.dataSource != mediaUrl) { @@ -120,7 +120,7 @@ class __EventPlayerMobileState extends State<_EventPlayerMobile> { Expanded( child: SafeArea( child: UnityVideoView( - heroTag: widget.event.mediaURL, + heroTag: widget.event.mediaPath, player: videoController, fit: settings.kVideoFit.value, videoBuilder: (context, video) { diff --git a/lib/utils/logging.dart b/lib/utils/logging.dart index 6b7f5232..09da42d7 100644 --- a/lib/utils/logging.dart +++ b/lib/utils/logging.dart @@ -24,11 +24,13 @@ import 'package:logging/logging.dart'; import 'package:path/path.dart' as path; import 'package:path_provider/path_provider.dart'; -void setupLogging() { +late Directory supportDir; +Future setupLogging() async { Logger.root.level = Level.ALL; // You can set the log level as needed. Logger.root.onRecord.listen((record) { debugPrint('${record.level.name}: ${record.time}: ${record.message}'); }); + supportDir = await getApplicationSupportDirectory(); } void handleError( @@ -44,10 +46,12 @@ void handleError( } Future getLogFile() async { - final dir = await getApplicationSupportDirectory(); - final file = File(path.join(dir.path, 'logs.txt')); - - return file; + try { + return File(path.join(supportDir.path, 'logs.txt')); + } catch (e) { + debugPrint('Error getting log file: $e'); + return File('./logs.txt'); + } } Future writeErrorToFile( diff --git a/lib/utils/video_player.dart b/lib/utils/video_player.dart index abfcc5ac..71c4a871 100644 --- a/lib/utils/video_player.dart +++ b/lib/utils/video_player.dart @@ -160,7 +160,7 @@ class UnityPlayers with ChangeNotifier { ); }, ) - ..setDataSource(event.mediaURL!.toString()) + ..setDataSource(event.mediaPath.toString()) ..setVolume(1.0) ..setSpeed(1.0);