· 5 years ago · Sep 04, 2020, 10:50 AM
1/// Video Uploader :
2
3class VideoUploader {
4 static Map<String, StreamController<double>> uploadProgressByVideoId = {};
5 static StreamController<bool> updateDataBaseData = StreamController<bool>.broadcast();
6
7 static Stream<double> getUploadProgressByVideoId(String videoId) =>
8 uploadProgressByVideoId[videoId] != null ? uploadProgressByVideoId[videoId].stream : null;
9
10 static Stream<bool> getUpdateDataBaseData() => updateDataBaseData.stream;
11
12 static Tus _createTUSClientForVideo(String videoId, Logger logger) {
13 var apiUrlBase = GlobalConfiguration().get(ConfigVariables.apiUrlField);
14 var apiKey = GlobalConfiguration().get(ConfigVariables.apiKeyField);
15
16 // If you need to test TUS upload against TUS demo playground
17// var tusTestPlatformUrl = "https://master.tus.io/files/";
18// var tusClient = Tus(tusTestPlatformUrl);
19
20 var tusClient = Tus('$apiUrlBase/upload');
21
22 // Setup tus headers
23 tusClient.headers = <String, String>{
24 "apiKey": apiKey,
25 };
26
27 // Callbacks for tus events
28 tusClient.onError = (String error, Tus tus) {
29 logger.severe("Error happened in upload of $videoId", error);
30 ApiVideoRepository().updateState(videoId, {'uploadState': Strings.transfertError});
31 updateDataBaseData.add(true);
32 uploadProgressByVideoId[videoId].addError(error);
33 };
34
35 tusClient.onProgress = (int bytesWritten, int bytesTotal, double progress, Tus tus) {
36 logger.info("Progress: $progress");
37 uploadProgressByVideoId[videoId].add(progress * 100);
38 };
39
40 tusClient.onComplete = (String result, Tus tus) {
41 logger.info("Upload complete, file can be found: $result");
42 uploadProgressByVideoId[videoId].add(100);
43 uploadProgressByVideoId[videoId].close();
44 ApiVideoRepository().updateState(videoId, {'uploadState': Strings.finished});
45 updateDataBaseData.add(true);
46 uploadProgressByVideoId.remove(videoId);
47 };
48
49 return tusClient;
50 }
51
52 static void _initTUSConnection(Tus tusClient, Logger logger) async {
53 // Initialize the tus client
54 var response = await tusClient.initializeWithEndpoint();
55 response.forEach((dynamic key, dynamic value) {
56 logger.info("TUS Init response - [$key] $value");
57 });
58 }
59
60 static void insertVideoApiInfosInDB(String uuid, VideoFormData videoFormInfo) {
61 final videoApiInfos = ApiVideo.fromVideoFormData(uuid, videoFormInfo, null);
62 ApiVideoRepository().insert(videoApiInfos);
63 updateDataBaseData.add(true);
64 }
65
66 static void constructVideoAndUpload(File videoFile, File thumbnailFile, VideoFormData videoFormInfos) async {
67 var logger = Logger('VideoUploader');
68 var uniqueId = Uuid().v4();
69
70 logger.info('insert video $uniqueId informations in database');
71 insertVideoApiInfosInDB(uniqueId, videoFormInfos);
72
73 final codecs = await getCodecsOfVideo(videoFile.path);
74
75 final String newVideoPath = await renameVideoFileAddingMetadata(
76 videoFile.path,
77 {
78 'author': 'testAuthor1',
79 'title': 'title',
80 'description': 'description',
81 'copyright': 'Copyright ${DateTime.now().year} - Ouest France',
82 'editor': 'Ouest France',
83 'tags': ['Janzé', 'Moule'],
84 'codecs': codecs,
85 'lang': 'fr',
86 },
87 uniqueId);
88
89 final newThumbnailPath = await renameImageFileWithUUID(thumbnailFile.path, uniqueId);
90 logger.info('uploading file from path: $newVideoPath');
91 VideoUploader.upload(newVideoPath, uniqueId);
92 final apiClient = ApiClient();
93 apiClient.uploadThumbnail(newThumbnailPath, uniqueId);
94 final videoApiInfos = ApiVideo.fromVideoFormData(uniqueId, videoFormInfos, newVideoPath);
95 apiClient.updateVideoInfos(uniqueId, videoApiInfos);
96 if (!videoFormInfos.needsValidation) {
97 // todo: Add tag object + check if typologies corresponds to what the API wants
98 apiClient.addDiffusion(new ApiDiffusionNumerique(uniqueId, typologies: videoApiInfos.typologies));
99 }
100 }
101
102 static upload(String videoFilePath, String uuid) async {
103 Logger logger = Logger('VideoUploader');
104 ApiVideoRepository().updateState(uuid, {'uploadState': Strings.inProgress, 'videoPath': videoFilePath});
105 updateDataBaseData.add(true);
106 final Tus _tusClient = _createTUSClientForVideo(uuid, logger);
107 await _initTUSConnection(_tusClient, logger);
108
109 final mimeType = mime(videoFilePath);
110
111 createStreamForVideoUpload(uuid);
112 await _tusClient.createUploadFromFile(
113 videoFilePath,
114 metadata: <String, String>{"uuid": uuid, "content_type": mimeType},
115 ).whenComplete(() => logger.info("TUS Upload complete"));
116 }
117
118 static createStreamForVideoUpload(String videoId) {
119 final progressController = StreamController<double>();
120 uploadProgressByVideoId[videoId] = progressController;
121 progressController.add(0);
122 }
123}
124
125
126/// Widget
127
128import 'package:flutter/cupertino.dart';
129import 'package:flutter/material.dart';
130import 'package:logging/logging.dart';
131import 'package:mobile_app/services/base_bloc.dart';
132import 'package:mobile_app/services/upload_bloc.dart';
133
134import '../lang/strings.dart';
135import '../model/api_video.dart';
136import '../model/published_video_information.dart';
137import '../repository/api_video_repository.dart';
138import '../services/video_uploader.dart';
139
140class OFVideoListDataTable extends StatefulWidget {
141 final String searchText;
142 final String state;
143 final Key key;
144 OFVideoListDataTable(this.state, this.searchText, this.key) : super(key: key);
145 @override
146 State<StatefulWidget> createState() => _OFVideoListDataTableState();
147}
148
149class _OFVideoListDataTableState extends State<OFVideoListDataTable> {
150 List<PublishedVideoInformations> publishedVideosInformations = [];
151 Logger logger = Logger('OFVideoListDataTable');
152
153 Stream<double> _uploadStream = null;
154
155 get uploadStream => _uploadStream;
156
157 set uploadStream(uploadStream) {
158 _uploadStream = uploadStream;
159 }
160
161 get updateDataBaseData => null;
162
163 void initState() {
164 super.initState();
165 final updateDataBaseData = VideoUploader.getUpdateDataBaseData();
166 fetch();
167 updateDataBaseData.listen((event) {
168 if (event) fetch();
169 });
170 }
171
172 PublishedVideoInformations updateProgress(PublishedVideoInformations publishedVideoInformations) {
173 if (publishedVideoInformations.uploadState == Strings.videoPreparation) {
174 publishedVideoInformations.uploadPercentage = Strings.videoPreparation;
175 return publishedVideoInformations;
176 }
177
178 if (publishedVideoInformations.uploadState == Strings.finished) {
179 publishedVideoInformations.uploadPercentage = '100%';
180 return publishedVideoInformations;
181 }
182
183 if (publishedVideoInformations.id != null) {
184 uploadStream = VideoUploader.getUploadProgressByVideoId(publishedVideoInformations.id);
185 print(uploadStream);
186 if (uploadStream != null) {
187 try {
188 uploadStream.listen((newProgress) {
189 publishedVideoInformations.uploadPercentage = "$newProgress %";
190 });
191 } catch (e) {
192 publishedVideoInformations.uploadPercentage = '0%';
193 }
194 } else
195 publishedVideoInformations.uploadPercentage = '0%';
196 } else
197 publishedVideoInformations.uploadPercentage = '0%';
198 return publishedVideoInformations;
199 }
200
201 void fetch() async {
202 List<PublishedVideoInformations> publishedVideosInformations1;
203 Future<List<ApiVideo>> apiVideosListFuture;
204 if (widget.searchText != null && widget.state != Strings.all) {
205 apiVideosListFuture = ApiVideoRepository().getByTitleAndState(widget.searchText, widget.state);
206 } else if (widget.searchText != null) {
207 apiVideosListFuture = ApiVideoRepository().getByTitle(widget.searchText);
208 } else if (widget.state != Strings.all) {
209 apiVideosListFuture = ApiVideoRepository().getByState(widget.state);
210 } else {
211 apiVideosListFuture = ApiVideoRepository().getAll();
212 }
213 publishedVideosInformations1 =
214 (await apiVideosListFuture).map((e) => PublishedVideoInformations.fromApiVideo(e)).toList();
215 setState(() {
216 publishedVideosInformations = publishedVideosInformations1;
217 });
218
219 publishedVideosInformations = publishedVideosInformations.map((e) => updateProgress(e)).toList();
220 }
221
222 @override
223 Widget build(BuildContext context) {
224 return DataTable(
225 columnSpacing: 10.0,
226 horizontalMargin: 5,
227 columns: [
228 DataColumn(
229 label: Text(
230 Strings.video,
231 style: Theme.of(context).textTheme.subtitle1,
232 ),
233 ),
234 DataColumn(
235 label: Text(
236 Strings.publishHeader,
237 style: Theme.of(context).textTheme.subtitle1,
238 ),
239 numeric: true,
240 ),
241 ],
242 rows: publishedVideosInformations
243 .map(
244 (e) => DataRow(
245 cells: [
246 DataCell(
247 Container(
248 width: MediaQuery.of(context).size.width * 0.6,
249 child: Text(
250 e.titre != null ? e.titre : 'No title found',
251 maxLines: 2,
252 overflow: TextOverflow.ellipsis,
253 ),
254 ),
255 ),
256 DataCell(
257 e.uploadState != Strings.transfertError
258 ? StreamBuilder(
259 stream: uploadStream,
260 builder: (context, snapshot) {
261 if (snapshot.hasData) {
262 print("snapshot data");
263 print(snapshot.data);
264 }
265 return Text(e.uploadPercentage != null ? e.uploadPercentage : '0%');
266 },
267 )
268 : GestureDetector(
269 onTap: () {
270 String videoFilePath = e.toString();
271 logger.info(e.toString());
272 logger.info('uploading file with path: $videoFilePath');
273 VideoUploader.upload(e.videoFilePath, e.id);
274 },
275 child: Icon(
276 Icons.warning,
277 color: Colors.red,
278 ),
279 ),
280 ),
281 ],
282 ),
283 )
284 .toList(),
285 );
286 }
287}
288