Master the Art of Video Sharing: Flutter & Appwrite Tutorial - Send Videos to Your Loved Ones! Episode [16]
Key Topics covered:
- Trim Video Before Sending It
- Send Videos in Real-time
- Display Videos
Description:
In this tutorial, we'll explore how to send videos using Flutter and Appwrite. If you're a beginner looking to enhance your app development skills, this step-by-step guide is perfect for you. You'll learn how to handle image selection, encode the videos, and send them securely to your Appwrite backend. We'll cover the necessary code snippets and explain the underlying concepts, ensuring that you grasp the core concepts behind the implementation.
Add the following dependencies in your pubspec.yaml
dependencies:
.........
video_player: ^2.6.1
path: ^1.8.2
video_trimmer: ^2.0.0
Update an extensions file.
- lib/core/extensions/extensions.dart
In OnBuildContext , implement the logic to pick and trim a video
Future<File?> pickVideo(BuildContext context,final ValueChanged<File> onChanged) async {
try{
final file = await ImagePicker().pickVideo(source: ImageSource.gallery);
if(mounted && file != null){
Navigator.push(context, MaterialPageRoute(builder: (context) => TrimmerView(File(file.path), onChanged),));
return null;
}
}catch(e){
print(e);
}
return null;
}
Code Snippet(TrimmerView.dart):
Create an TrimmerView.dart. We need to trim a video before sending it.
- lib/widget/TrimmerView.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:video_trimmer/video_trimmer.dart';
class TrimmerView extends StatefulWidget {
final File file;
final ValueChanged<File> onChanged;
TrimmerView(this.file,this.onChanged);
@override
_TrimmerViewState createState() => _TrimmerViewState();
}
class _TrimmerViewState extends State<TrimmerView> {
final Trimmer _trimmer = Trimmer();
double _startValue = 0.0;
double _endValue = 0.0;
bool _isPlaying = false;
bool _progressVisibility = false;
Future<String?> _saveVideo() async {
setState(() {
_progressVisibility = true;
});
String? _value;
await _trimmer
.saveTrimmedVideo(startValue: _startValue, endValue: _endValue, onSave: (String? outputPath) {
// callback
if(outputPath != null){
widget.onChanged(File(outputPath));
Navigator.pop(context);
}
})
.then((value) {
setState(() {
_progressVisibility = false;
});
});
return _value;
}
void _loadVideo() {
_trimmer.loadVideo(videoFile: widget.file);
}
@override
void initState() {
super.initState();
_loadVideo();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Video Trimmer"),
),
body: Builder(
builder: (context) => Center(
child: Container(
padding: EdgeInsets.only(bottom: 30.0),
color: Colors.black,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Visibility(
visible: _progressVisibility,
child: LinearProgressIndicator(
backgroundColor: Colors.red,
),
),
ElevatedButton(
onPressed: _progressVisibility
? null
: () async {
_saveVideo().then((outputPath) {
print('OUTPUT PATH: $outputPath');
});
},
child: const Text("Done"),
),
Expanded(
child: VideoViewer(trimmer: _trimmer),
),
Center(
child: TrimViewer(
trimmer: _trimmer,
viewerHeight: 50.0,
viewerWidth: MediaQuery.of(context).size.width,
maxVideoLength: const Duration(seconds: 10),
onChangeStart: (value) => _startValue = value,
onChangeEnd: (value) => _endValue = value,
onChangePlaybackState: (value) =>
setState(() => _isPlaying = value),
),
),
TextButton(
child: _isPlaying
? Icon(
Icons.pause,
size: 80.0,
color: Colors.white,
)
: Icon(
Icons.play_arrow,
size: 80.0,
color: Colors.white,
),
onPressed: () async {
bool playbackState = await _trimmer.videoPlaybackControl(
startValue: _startValue,
endValue: _endValue,
);
setState(() {
_isPlaying = playbackState;
});
},
)
],
),
),
),
),
);
}
}
Code Snippet(MessageAppwrite.dart) modification:
Add property fileName
- lib/model/MessageAppwrite.dart
String? fileName;
Code Snippet(MessageState.dart) modification:
Add property File
- lib/model/MessageState.dart
import 'package:appwrite/models.dart';
@Default(null) File? file,
Don't forget to run the following command in your terminal to generate added properties flutter packages pub run build_runner build
Code Snippet(MessageScreen.dart) modification:
Add property File
- lib/pages/dashboard/chat/MessageScreen.dart
// change Icons.camera_alt, to Icons.attach_file. onPressed of icon should call _modalBottomSheet();
child: IconButton(
splashColor: Colors.white,
icon: Icon(
Icons.attach_file,
color: Colors.black,
),
onPressed: () {
_modalBottomSheet();
},
add ImageSource on pickImage() parameters
- lib/pages/dashboard/chat/MessageScreen.dart
void pickImage(ImageSource source) async {
Navigator.pop(context);
final file = await context.pickAndCropImage(3 / 4, source);
if (file != null) {
File? fileUploaded = await messageNotifier?.uploadMedia(
realm.ObjectId().hexString, file.path);
if (fileUploaded != null) {
sendMessage("IMAGE", fileUploaded.$id, file: fileUploaded);
}
}
}
add pickVideo() method
- lib/pages/dashboard/chat/MessageScreen.dart
void pickVideo() async {
Navigator.pop(context);
context.pickVideo(
context,
(file) async {
File? fileUploaded = await messageNotifier?.uploadMedia(
realm.ObjectId().hexString, file.path);
if (fileUploaded != null) {
sendMessage("VIDEO", fileUploaded.$id, file: fileUploaded);
}
},
);
}
Add a third parameter on sendMessage method which is a File
- lib/pages/dashboard/chat/MessageScreen.dart
void sendMessage(String type, String message, {File? file}) {
if (file != null) {
messageNotifier?.onChangedUploadedFile(file);
}
..........
}
Add BottomSheet Modal
- lib/pages/dashboard/chat/MessageScreen.dart
void _modalBottomSheet() {
showModalBottomSheet(
context: context,
builder: (builder) {
return Container(
height: 250.0,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), topRight: Radius.circular(10)),
),
child: Container(
width: double.infinity,
color: Colors.white,
child: Column(
children: [
const SizedBox(
height: 10,
),
const Text("Select an Option"),
const SizedBox(
height: 10,
),
InkWell(
onTap: () {
pickVideo();
},
child: Container(
alignment: Alignment.center,
width: double.infinity,
height: 38,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 10,
),
Icon(
Icons.video_camera_front,
color: Colors.grey,
),
const Text("Video"),
Spacer()
],
),
),
),
Divider(),
InkWell(
onTap: () {
pickImage(ImageSource.gallery);
},
child: Container(
alignment: Alignment.center,
width: double.infinity,
height: 38,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 10,
),
Icon(
Icons.image,
color: Colors.grey,
),
const Text("Image"),
Spacer()
],
),
),
),
Divider(),
InkWell(
onTap: () {
pickImage(ImageSource.camera);
},
child: Container(
alignment: Alignment.center,
width: double.infinity,
height: 38,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(
height: 10,
),
Icon(
Icons.camera_alt,
color: Colors.grey,
),
const Text("Camera"),
Spacer()
],
),
),
),
],
),
),
);
},
);
}
Code Snippet(MessageViewModel.dart) modification:
Add code to save file name when sending a media in sendMessage() method
- lib/pages/dashboard/chat/MessageViewModel.dart
// before send a video or image. Let's do this
if(state.file != null){
message.fileName = state.file?.name;
}
// after you send a message let's clear the file
state = state.copyWith(
file: null
);
// add this method to set the file in our state
void onChangedUploadedFile(File file) {
state = state.copyWith(
file : file
);
}
Conclusion:
We managed to pick an video from gallery and send it. We used an existing code to upload videos in the MessageViewModel.dart. In the next tutorial, we are going to save images and videos on local device for a better performance. Don't forget to share and join our Discord Channel. May you please subscribe to our YouTube Channel.