How to change file extension in Flutter? - dart

I have to download media files from a website which returns a video URL like this:
https://scontent-dfw5-2.cdninstagram.com/vp/1cdfc59a008aa7609b5a91d7fdf58a81/5CB6F8B1/t50.12441-16/57499829_322195418440891_1171410395492073139_n.mp4?_nc_ht=scontent-dfw5-2.cdninstagram.com
I download this video file using flutter_downlaoder.
Downloaded file does not contain any extension so i have to add it myself.
I have to rename downloaded file in following format:
[username]_[timestamp].mp4
Problem is that when i download this video file without extension and add it manually by renaming from storage directory, video plays fine but when i rename programmatically it doesn't play and gives 'Video Format Error'.
I have shared my code below. How to fix this issue?
Future downloadMedia(String userName, String videoURL, String downloadPath, BuildContext context) async {
String filename = "${userName}_${DateTime.now().millisecondsSinceEpoch}";
String downloadURL = videoURL;
String taskId = await FlutterDownloader.enqueue(
url: downloadURL,
savedDir: downloadPath,
fileName: filename,
showNotification: false,
openFileFromNotification: false);
FlutterDownloader.registerCallback((id, status, progress) async {
if (progress.toInt() >= 100) {
String newFilename = "$filename.mp4";
Future.delayed(const Duration(milliseconds: 500), () {
File(downloadPath + "/" + filename)
.renameSync(downloadPath + "/" + newFilename);
});
}
});
}

I searched around and found that renaming video file will always mess up video codec settings, so best way is to handle it using FFMpeg.
import 'package:flutter_downloader/flutter_downloader.dart';
import 'package:flutter_ffmpeg/flutter_ffmpeg.dart';
class Download {
final FlutterFFmpeg _flutterFFmpeg = FlutterFFmpeg();
Future downloadMedia(String userName, String videoURL, String downloadPath,
BuildContext context) async {
String filename = "${userName}_${DateTime.now().millisecondsSinceEpoch}";
String filenameOriginal =
"${userName}_${DateTime.now().millisecondsSinceEpoch}";
String downloadURL = videoURL;
var started = false;
await FlutterDownloader.enqueue(
url: downloadURL,
savedDir: downloadPath,
fileName: filename,
showNotification: false,
openFileFromNotification: false);
FlutterDownloader.registerCallback((id, status, progress) async {
if (progress.toInt() >= 100) {
started = true;
var inputFile = "${downloadPath + "/" + filename}";
var outputFile = "${downloadPath + "/" + filenameOriginal}_${DateTime.now().millisecondsSinceEpoch}.mp4";
_flutterFFmpeg
.execute("-i inputFile -c copy outputFile")
.then((rc) => print("FFmpeg process exited with rc $rc"));
//Export file as Mp4 Video
}
});
}
}
Exported video file can easily be played in VLC player or MX player apps.

Related

how to display a file using react-native

So, I have those "cards" to which are attached files.
I want to be able to display the content of these files (when possible; I do not expect to show binary files obviously, but text, pdf, images,...) to the user.
Upon a longPress on an attachment, the openAttachment() function is be called. That function downloads the file from the server if necessary and then (tries to) open it:
// Opens an attachment
const openAttachment = async (attachment) => {
try {
// Download file if not already done
const fileInfo = await FileSystem.getInfoAsync(FileSystem.cacheDirectory + attachment.name)
let uri
if (!fileInfo.exists) {
console.log('Downloading attachment')
resp = await FileSystem.downloadAsync(
server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments/${attachment.id}`,
FileSystem.cacheDirectory + attachment.name,
{
headers: {
'Authorization': token.value
},
},
)
console.log(resp)
uri = await FileSystem.getContentUriAsync(resp.uri)
} else {
console.log('File already in cache')
uri = await FileSystem.getContentUriAsync(fileInfo.uri)
}
console.log('Opening file ' + uri)
Sharing.shareAsync(uri);
} catch {
Toast.show({
type: 'error',
text1: i18n.t('error'),
text2: error.message,
})
console.log(error)
}
}
The issue always arrise at the Sharing.shareAsync(uri); line: Whatever I put there, it fails:
Sharing.shareAsync(uri) does not seem to be supported on my platform: https://docs.expo.dev/versions/latest/sdk/sharing/
Linking.openURL(uri) does not support the file:// scheme (the uri is in the form file:///var/mobile/Containers/Data/Application/5C1CB402-5ED1-4E17-B907-46111AE3FB7C/Library/Caches/test.pdf)
await WebBrowser.openBrowserAsync(uri) (from expo-web-browser) does not seem to be able to open local files
How am I supposed to do to display those files? Anyone has an idea?
Cyrille
I found a solution using react-native-file-viewer
// Opens an attachment
const openAttachment = async (attachment) => {
try {
// Download file if not already done
const fileInfo = await FileSystem.getInfoAsync(FileSystem.cacheDirectory + "attachment.name")
let uri
if (!fileInfo.exists) {
console.log('Downloading attachment')
const resp = await FileSystem.downloadAsync(
server.value + `/index.php/apps/deck/api/v1.0/boards/${route.params.boardId}/stacks/${route.params.stackId}/cards/${route.params.cardId}/attachments/${attachment.id}`,
FileSystem.cacheDirectory + attachment.name,
{
headers: {
'Authorization': token.value
},
},
)
console.log(resp)
uri = await FileSystem.getContentUriAsync(resp.uri)
} else {
console.log('File already in cache')
uri = await FileSystem.getContentUriAsync(fileInfo.uri)
}
console.log('opening file', uri)
FileViewer.open(uri)
} catch(error) {
Toast.show({
type: 'error',
text1: i18n.t('error'),
text2: error.message,
})
console.log(error)
}
}

Safari browsers above iOS 14 cannot play m3u8 videos and cannot load .ts files

Safari browsers above iOS 14 cannot play m3u8 videos and cannot load .ts files
Safari browsers below ios 14 can play
First request the m3u8 file, then request the corresponding decryption key, then perform key replacement and ts resource domain replacement, and then generate base64 and put it in video src.
There is no problem with this operation below ios 14 but not above ios 14
async iosAutoKey(xhr) {
const me = this;
const res = me.addVideoFilePrefix(xhr.response);
let resKey = "";
resKey = await api
.downloadCertificateKeyH5({
videoId: me.videoData.id,
})
.then((resKey) => {
return resKey;
});
let key = new Blob([resKey], {
type: "text/plain",
});
const keyUrl = URL.createObjectURL(key);
let blob = new Blob(
[res.replace(/URI="[\d]{13}"|URI="{REMOTE_KEY}"/, `URI="${keyUrl}"`)],
{
type: "application/vnd.apple.mpegurl",
}
);
let reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = function () {
const url = reader.result;
me.arrayBufferMap.set(me.videoData.id, url);
if (Hls.isSupported()) {
me.hlsInstance.loadSource(url);
} else if (isIOS()) {
me.theVideo.src = url;
} else {
me.$dialog.alert({
message: "The current browser does not support playing m3u8, please use the latest version of chrome",
});
}
};
},
addVideoFilePrefix(res) {
let result;
let reg = new RegExp('(http|https)://.+/group');
let prefix = `${this.vipFileSource}/group`;
if (reg.test(res)) {
result = res.replace(/(http|https):\/\/.+\/group/g, prefix);
} else {
result = res.replace(/\/group/g, prefix);
}
return result;
},
Video trigger event below ios 14
enter image description here
Video trigger event above ios 14
enter image description here
m3u8 first load
enter image description here
The m3u8 file decrypted on ios 14 cannot load the video, and the .ts file cannot be loaded
m3u8 decrypt the requested base64
enter image description here

Save binary data with Electron

in my Electron app I need to upload a file (.mp3) using a normal html input and then save it on the disk.
I'm reading the file using the browser's FileReader:
const reader = new FileReader();
reader.onload = () => {
resolver.next(reader.result as string);
resolver.complete();
};
reader.readAsBinaryString(file);
Then I sent the readed content like this:
this.electronService.ipcRenderer.on('aaaSuccess', (_, newPath) =>
this.store$.dispatch(HomeActions.changeSuccess({ soundName: action.sound.name, newPath })));
this.electronService.ipcRenderer.send('aaa', { fileName: file.name, content: base64 });
Then I pass the readed binary string to the mainProcess like this:
ipcMain.on('aaa', (event, { fileName, content }) => {
var newPath = path.join(app.getPath('userData'), fileName);
fs.writeFile(newPath, content, function (err) {
if (err) { return console.log('error is writing new file', err) }
event.reply('aaaSuccess', newPath)
});
})
This code works, but the dimension in bytes of the saved file is different from the original one, and it can't be opened using an mp3 player
Thanks a lot

audioplayer plugin in Flutter - Unable to load asset

I'm trying to create an alarm in Flutter where an alarm tone should go off after a certain time. It seems like this is easier said than done in Flutter!
Tried to use the audioplayer plugin to achieve this. Used the playLocal function wherein the asset is loaded from the rootbundle into the app directory and then played
According to an answer in the audioplayer github repo, this is the code that should do the trick:
class SoundManager {
AudioPlayer audioPlayer = new AudioPlayer();
Future playLocal(localFileName) async {
final dir = await getApplicationDocumentsDirectory();
final file = new File("${dir.path}/$localFileName");
if (!(await file.exists())) {
final soundData = await rootBundle.load("assets/$localFileName");
final bytes = soundData.buffer.asUint8List();
await file.writeAsBytes(bytes, flush: true);
}
await audioPlayer.play(file.path, isLocal: true);
}
}
I keep getting an error: "Unable to load asset". The asset (mp3/wav file) is obviously in the folder, and the folder is included in the pubspec.yaml file correctly (other image assets are loading properly from this folder, so specifying the folder itself is not the issue here)
You can use another audio library https://pub.dev/packages/audioplayers
AudioCache documentation.
https://github.com/luanpotter/audioplayers/blob/master/doc/audio_cache.md
Simple example:
import 'package:audioplayers/audio_cache.dart';
AudioCache player = AudioCache();
player.play('sounds/test_sound.m4a');
In this example my assets folder looks like this: assets/sounds/test_sound.m4a
This library cached audio as local file and then play audio
PS: If you want to play music from local files you can use AudioPlayer().
My example with listener on return, onPlayCompletion will be called when music end
AudioPlayer _advancedPlayer = AudioPlayer();
Stream<void> playFromLocal(int unitId, int id) {
var link = '/media/$unitId/words/$id.m4a';
_advancedPlayer.stop();
_advancedPlayer.release();
_advancedPlayer = AudioPlayer();
_advancedPlayer.play(Const.basePath + link, isLocal: true);
return _advancedPlayer.onPlayerCompletion;
}
This works well for both iOS and Android. Note: this downloads from url if not available locally.
AudioProvider audioProvider;
_playSound() async {
audioProvider = AudioProvider("http:...");
var soundToPlay = "myLocalSound";
String localUrl = await audioProvider.load(soundToPlay);
SoundController.play(localUrl);
}
}
audio_provider.dart
import 'dart:async';
import 'dart:io';
import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import 'package:http/http.dart';
typedef void OnError(Exception exception);
class AudioProvider {
String url;
AudioProvider(String url) {
this.url = url;
}
Future<Uint8List> _loadFileBytes(String url, {OnError onError}) async {
Uint8List bytes;
try {
bytes = await readBytes(url);
} on ClientException {
rethrow;
}
return bytes;
}
Future<String> load(fileName) async {
final dir = await getApplicationDocumentsDirectory();
final file = new File('${dir.path}/$fileName');
if (await file.exists()) {print("file exists");
return file.path;
}
var filePath = url +fileName;
final bytes = await _loadFileBytes(filePath,
onError: (Exception exception) =>
print('audio_provider.load => exception ${exception}'));
await file.writeAsBytes(bytes);
if (await file.exists()) {
return file.path;
}
return '';
}
}
soundController.dart
import 'package:flutter/foundation.dart';
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'dart:io' show Platform;
void audioPlayerHandler(AudioPlayerState value) => null;
class SoundController {
static AudioPlayer audioPlayer = AudioPlayer(mode: PlayerMode.LOW_LATENCY);
static AudioCache audioCache = AudioCache(prefix: "assets/audio/", fixedPlayer: audioPlayer);
static void play(String sound) {
if (!kIsWeb && Platform.isIOS) {
audioPlayer.monitorNotificationStateChanges(audioPlayerHandler);
}
audioPlayer.play(sound, isLocal: true);
}
}

Flutter image_picker post upload an image

I am using the Flutter Plugin Image_picker to choose images so that I want to upload image after selected the image
Future<File> _imageFile;
void _onImageButtonPressed(ImageSource source) async {
setState(() {
_imageFile = ImagePicker.pickImage(source: source);
});
}
I find this code in flutter documentation but its not work
var uri = Uri.parse("http://pub.dartlang.org/packages/create");
var request = new http.MultipartRequest("POST", url);
request.fields['user'] = 'nweiz#google.com';
request.files.add(new http.MultipartFile.fromFile(
'package',
new File('build/package.tar.gz'),
contentType: new MediaType('application', 'x-tar'));
request.send().then((response) {
if (response.statusCode == 200) print("Uploaded!");
});
Use MultipartRequest class
Upload(File imageFile) async {
var stream = new http.ByteStream(DelegatingStream.typed(imageFile.openRead()));
var length = await imageFile.length();
var uri = Uri.parse(uploadURL);
var request = new http.MultipartRequest("POST", uri);
var multipartFile = new http.MultipartFile('file', stream, length,
filename: basename(imageFile.path));
//contentType: new MediaType('image', 'png'));
request.files.add(multipartFile);
var response = await request.send();
print(response.statusCode);
response.stream.transform(utf8.decoder).listen((value) {
print(value);
});
}
Check this answer
This code works properly.
Used MultipartRequest class
void uploadImage() async {
File _image;
File pickedImage = await ImagePicker.pickImage(source: ImageSource.camera);
setState(() {
_image = pickedImage;
});
// open a byteStream
var stream = new http.ByteStream(DelegatingStream.typed(_image.openRead()));
// get file length
var length = await _image.length();
// string to uri
var uri = Uri.parse("enter here upload URL");
// create multipart request
var request = new http.MultipartRequest("POST", uri);
// if you need more parameters to parse, add those like this. i added "user_id". here this "user_id" is a key of the API request
request.fields["user_id"] = "text";
// multipart that takes file.. here this "image_file" is a key of the API request
var multipartFile = new http.MultipartFile('image_file', stream, length, filename: basename(_image.path));
// add file to multipart
request.files.add(multipartFile);
// send request to upload image
await request.send().then((response) async {
// listen for response
response.stream.transform(utf8.decoder).listen((value) {
print(value);
});
}).catchError((e) {
print(e);
});
}
name spaces:
import 'package:path/path.dart';
import 'package:async/async.dart';
import 'dart:io';
import 'package:http/http.dart' as http;
If you want the uploading function to return the server response, you can use toBytes() instead of transform(), in order to wait until data transmission is complete.
Future<String> upload() async {
String responseString = '';
// Pick image
final image = await ImagePicker().getImage(
source: ImageSource.gallery // or ImageSource.camera
imageQuality: 100,
maxWidth: 1000,
);
// Convert to File
final file = File(image.path);
// Set URI
final uri = Uri.parse('URL');
// Set the name of file parameter
final parameter = 'Name';
// Upload
final request = http.MultipartRequest('POST', uri)
..files.add(await http.MultipartFile.fromPath(parameter, file.path));
final response = await request.send();
if (response.statusCode == 200) {
responseString = String.fromCharCodes(await response.stream.toBytes());
}
return responseString;
}

Resources