Skip to content

Commit

Permalink
Merge pull request #215 from takenet/feature/531586-improve-video-sto…
Browse files Browse the repository at this point in the history
…rage

feat: adding fileName field on controller and bubble
  • Loading branch information
RaulRodrigo06 authored Oct 20, 2023
2 parents f171264 + d6fa47a commit 5c489fa
Show file tree
Hide file tree
Showing 11 changed files with 138 additions and 74 deletions.
9 changes: 5 additions & 4 deletions lib/blip_ds.dart
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export 'src/themes/texts/styles/ds_headline_small_text_style.theme.dart'
export 'src/themes/texts/utils/ds_font_families.theme.dart' show DSFontFamilies;
export 'src/themes/texts/utils/ds_font_weights.theme.dart' show DSFontWeights;
export 'src/utils/ds_animate.util.dart' show DSAnimate;
export 'src/utils/ds_directory_formatter.util.dart' show DSDirectoryFormatter;
export 'src/utils/ds_linkify.util.dart' show DSLinkify;
export 'src/utils/ds_utils.util.dart' show DSUtils;
export 'src/widgets/animations/ds_animated_size.widget.dart'
Expand Down Expand Up @@ -77,10 +78,10 @@ export 'src/widgets/buttons/ds_icon_button.widget.dart' show DSIconButton;
export 'src/widgets/buttons/ds_pause_button.widget.dart' show DSPauseButton;
export 'src/widgets/buttons/ds_play_button.widget.dart' show DSPlayButton;
export 'src/widgets/buttons/ds_primary_button.widget.dart' show DSPrimaryButton;
export 'src/widgets/buttons/ds_secondary_button.widget.dart'
show DSSecondaryButton;
export 'src/widgets/buttons/ds_request_location_button.widget.dart'
show DSRequestLocationButton;
export 'src/widgets/buttons/ds_secondary_button.widget.dart'
show DSSecondaryButton;
export 'src/widgets/buttons/ds_send_button.widget.dart' show DSSendButton;
export 'src/widgets/buttons/ds_tertiary_button.widget.dart'
show DSTertiaryButton;
Expand All @@ -101,6 +102,8 @@ export 'src/widgets/chat/ds_location_message_bubble.widget.dart'
export 'src/widgets/chat/ds_message_bubble.widget.dart' show DSMessageBubble;
export 'src/widgets/chat/ds_message_bubble_detail.widget.dart'
show DSMessageBubbleDetail;
export 'src/widgets/chat/ds_request_location_bubble.widget.dart'
show DSRequestLocationBubble;
export 'src/widgets/chat/ds_survey_message_bubble.widget.dart'
show DSSurveyMessageBubble;
export 'src/widgets/chat/ds_text_message_bubble.widget.dart'
Expand Down Expand Up @@ -155,5 +158,3 @@ export 'src/widgets/utils/ds_group_card.widget.dart' show DSGroupCard;
export 'src/widgets/utils/ds_header.widget.dart' show DSHeader;
export 'src/widgets/utils/ds_progress_bar.widget.dart' show DSProgressBar;
export 'src/widgets/utils/ds_user_avatar.widget.dart' show DSUserAvatar;
export 'src/widgets/chat/ds_request_location_bubble.widget.dart'
show DSRequestLocationBubble;
80 changes: 53 additions & 27 deletions lib/src/controllers/chat/ds_video_message_bubble.controller.dart
Original file line number Diff line number Diff line change
@@ -1,34 +1,37 @@
import 'dart:convert';
import 'dart:io';

import 'package:crypto/crypto.dart';
import 'package:ffmpeg_kit_flutter_full_gpl/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter_full_gpl/return_code.dart';
import 'package:file_sizes/file_sizes.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';

import '../../models/ds_toast_props.model.dart';
import '../../services/ds_file.service.dart';
import '../../services/ds_toast.service.dart';
import '../../utils/ds_directory_formatter.util.dart';
import '../../widgets/chat/video/ds_video_error.dialog.dart';

class DSVideoMessageBubbleController {
final String uniqueId;
final String url;
final int mediaSize;
final Map<String, String?>? httpHeaders;
final String type;

DSVideoMessageBubbleController({
required this.uniqueId,
required this.url,
required this.mediaSize,
required this.type,
this.httpHeaders,
}) {
setThumbnail();
getStoredVideo();
}

final isDownloading = RxBool(false);
final thumbnail = RxString('');
final hasError = RxBool(false);
final isLoadingThumbnail = RxBool(false);

String size() {
return mediaSize > 0
Expand All @@ -40,32 +43,49 @@ class DSVideoMessageBubbleController {
: 'Download';
}

Future<void> setThumbnail() async {
final thumbnailFile = File(await getFullThumbnailPath());
if (await thumbnailFile.exists()) {
thumbnail.value = thumbnailFile.path;
Future<void> getStoredVideo() async {
try {
isLoadingThumbnail.value = true;
final fileName = md5.convert(utf8.encode(Uri.parse(url).path)).toString();
final fullPath = await DSDirectoryFormatter.getPath(
type: type,
fileName: fileName,
);
final fullThumbnailPath = await DSDirectoryFormatter.getPath(
type: 'image/png',
fileName: '$fileName-thumbnail',
);
final file = File(fullPath);
final thumbnailfile = File(fullThumbnailPath);
if (await thumbnailfile.exists()) {
thumbnail.value = thumbnailfile.path;
} else if (await file.exists() && thumbnail.value.isEmpty) {
await _generateThumbnail(file.path);
}
} finally {
isLoadingThumbnail.value = false;
}
}

Future<String> getFullThumbnailPath() async {
final temporaryPath = (await getTemporaryDirectory()).path;
return "$temporaryPath/VIDEO-Thumbnail-$uniqueId.png";
final fileName = md5.convert(utf8.encode(Uri.parse(url).path)).toString();
final mediaPath = await DSDirectoryFormatter.getPath(
type: 'image/png',
fileName: '$fileName-thumbnail',
);
return mediaPath;
}

Future<void> downloadVideo() async {
final fileName = md5.convert(utf8.encode(Uri.parse(url).path)).toString();
isDownloading.value = true;

try {
final path = Uri.parse(url).path;

var fileName = path.substring(path.lastIndexOf('/')).substring(1);

if (fileName.isEmpty) {
fileName = DateTime.now().toIso8601String();
}

final temporaryPath = (await getTemporaryDirectory()).path;
final outputFile = File('$temporaryPath/VIDEO-$uniqueId.mp4');
final fullPath = await DSDirectoryFormatter.getPath(
type: 'video/mp4',
fileName: fileName,
);
final outputFile = File(fullPath);

if (!await outputFile.exists()) {
final inputFilePath = await DSFileService.download(
Expand All @@ -77,6 +97,8 @@ class DSVideoMessageBubbleController {
final session = await FFmpegKit.execute(
'-hide_banner -y -i "$inputFilePath" "${outputFile.path}"');

File(inputFilePath!).delete();

final returnCode = await session.getReturnCode();

if (!ReturnCode.isSuccess(returnCode)) {
Expand All @@ -89,13 +111,7 @@ class DSVideoMessageBubbleController {
}
}

final thumbnailPath = await getFullThumbnailPath();

await FFmpegKit.execute(
'-hide_banner -y -i "${outputFile.path}" -vframes 1 "$thumbnailPath"',
);

thumbnail.value = thumbnailPath;
_generateThumbnail(outputFile.path);
} catch (_) {
hasError.value = true;

Expand All @@ -110,4 +126,14 @@ class DSVideoMessageBubbleController {
isDownloading.value = false;
}
}

Future<void> _generateThumbnail(String path) async {
final thumbnailPath = await getFullThumbnailPath();

await FFmpegKit.execute(
'-hide_banner -y -i "$path" -vframes 1 "$thumbnailPath"',
);

thumbnail.value = thumbnailPath;
}
}
3 changes: 0 additions & 3 deletions lib/src/controllers/ds_video_player.controller.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,12 @@ class DSVideoPlayerController extends GetxController {
/// the management of video controls.
DSVideoPlayerController({
required this.url,
required this.uniqueId,
this.shouldAuthenticate = false,
});

// External URL containing the video to be played
final String url;

final String uniqueId;

/// Indicates if the HTTP Requests should be authenticated or not.
final bool shouldAuthenticate;

Expand Down
42 changes: 42 additions & 0 deletions lib/src/utils/ds_directory_formatter.util.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import 'dart:io';

import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart';

abstract class DSDirectoryFormatter {
static Future<String> getPath({
required final String type,
required final String fileName,
}) async {
final cachePath = (await getApplicationCacheDirectory()).path;

final typeFolder = '${type.split('/').first.capitalizeFirst}';
final extension = type.split('/').last;

final typePrefix = '${typeFolder.substring(0, 3).toUpperCase()}-';

final newFileName =
'${!fileName.startsWith(typePrefix) ? typePrefix : ''}$fileName';

final path = await _formatDirectory(
type: typeFolder,
directory: cachePath,
);

return '$path/$newFileName.$extension';
}

static Future<String> _formatDirectory({
required final String type,
required final String directory,
}) async {
final formattedDirectory = '$directory/$type';
final directoryExists = await Directory(formattedDirectory).exists();

if (!directoryExists) {
await Directory(formattedDirectory).create(recursive: true);
}

return formattedDirectory;
}
}
3 changes: 0 additions & 3 deletions lib/src/widgets/chat/video/ds_video_body.widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class DSVideoBody extends StatelessWidget {
required this.appBarText,
required this.appBarPhotoUri,
required this.url,
required this.uniqueId,
required this.thumbnail,
this.shouldAuthenticate = false,
});
Expand All @@ -19,7 +18,6 @@ class DSVideoBody extends StatelessWidget {
final String appBarText;
final Uri? appBarPhotoUri;
final String url;
final String uniqueId;
final Widget thumbnail;
final bool shouldAuthenticate;

Expand All @@ -43,7 +41,6 @@ class DSVideoBody extends StatelessWidget {
appBarText: appBarText,
appBarPhotoUri: appBarPhotoUri,
url: url,
uniqueId: uniqueId,
shouldAuthenticate: shouldAuthenticate,
),
);
Expand Down
19 changes: 10 additions & 9 deletions lib/src/widgets/chat/video/ds_video_message_bubble.widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ class DSVideoMessageBubble extends StatefulWidget {
/// Style for bubble
final DSMessageBubbleStyle style;

// Unique id to message bubble
final String uniqueId;

/// The video size
final int mediaSize;

/// The video type
final String type;

/// Indicates if the HTTP Requests should be authenticated or not.
final bool shouldAuthenticate;

Expand All @@ -58,9 +58,9 @@ class DSVideoMessageBubble extends StatefulWidget {
required this.align,
required this.url,
required this.appBarText,
required this.uniqueId,
required this.mediaSize,
this.appBarPhotoUri,
this.type = 'video/mp4',
this.text,
this.borderRadius = const [DSBorderRadius.all],
this.shouldAuthenticate = false,
Expand All @@ -79,10 +79,10 @@ class _DSVideoMessageBubbleState extends State<DSVideoMessageBubble>
void initState() {
super.initState();
_controller = DSVideoMessageBubbleController(
uniqueId: widget.uniqueId,
url: widget.url,
mediaSize: widget.mediaSize,
httpHeaders: widget.shouldAuthenticate ? DSAuthService.httpHeaders : null,
type: widget.type,
);
}

Expand Down Expand Up @@ -135,13 +135,15 @@ class _DSVideoMessageBubbleState extends State<DSVideoMessageBubble>
size: 80.0,
color: DSColors.neutralDarkRooftop,
)
: _controller.isDownloading.value
: (_controller.isDownloading.value ||
_controller.isLoadingThumbnail.value)
? Center(
child: Container(
height: 50.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: foregroundColor),
shape: BoxShape.circle,
color: foregroundColor,
),
child: DSFadingCircleLoading(
color: backgroundLoadingColor,
size: 45.0,
Expand Down Expand Up @@ -169,7 +171,6 @@ class _DSVideoMessageBubbleState extends State<DSVideoMessageBubble>
align: widget.align,
appBarPhotoUri: widget.appBarPhotoUri,
appBarText: widget.appBarText,
uniqueId: widget.uniqueId,
url: widget.url,
shouldAuthenticate: widget.shouldAuthenticate,
thumbnail: Center(
Expand Down
18 changes: 11 additions & 7 deletions lib/src/widgets/chat/video/ds_video_player.widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import 'package:flutter/services.dart';
import 'package:get/get.dart';

import '../../../controllers/ds_video_player.controller.dart';
import '../../../themes/colors/ds_colors.theme.dart';
import '../../../themes/icons/ds_icons.dart';
import '../../../themes/system_overlay/ds_system_overlay.style.dart';
import '../../utils/ds_header.widget.dart';

Expand All @@ -16,8 +18,6 @@ class DSVideoPlayer extends StatelessWidget {
/// Avatar to be displayed in the appBarr
final Uri? appBarPhotoUri;

final String uniqueId;

/// Indicates if the HTTP Requests should be authenticated or not.
final bool shouldAuthenticate;

Expand All @@ -29,13 +29,11 @@ class DSVideoPlayer extends StatelessWidget {
Key? key,
required this.appBarText,
required String url,
required this.uniqueId,
this.appBarPhotoUri,
this.shouldAuthenticate = false,
}) : controller = Get.put(
DSVideoPlayerController(
url: url,
uniqueId: uniqueId,
),
),
super(key: key);
Expand Down Expand Up @@ -73,9 +71,15 @@ class DSVideoPlayer extends StatelessWidget {
8.0,
8.0 + MediaQuery.of(context).padding.bottom,
),
child: Chewie(
controller: controller.chewieController!,
),
child: controller.chewieController == null
? const Icon(
DSIcons.video_broken_outline,
size: 80.0,
color: DSColors.neutralDarkRooftop,
)
: Chewie(
controller: controller.chewieController!,
),
),
),
),
Expand Down
1 change: 0 additions & 1 deletion lib/src/widgets/utils/ds_card.widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,6 @@ class DSCard extends StatelessWidget {
text: media.text,
borderRadius: borderRadius,
style: style,
uniqueId: messageId ?? DateTime.now().toIso8601String(),
mediaSize: size,
shouldAuthenticate: shouldAuthenticate,
);
Expand Down
Loading

0 comments on commit 5c489fa

Please sign in to comment.