I'm using just_audio plugin and it has on description a feature: Read from byte stream.
Basically when I put a file (from url) to play, I'm saving the bytes from file so after this step I want to play it locally.
I have a question about how play from byte stream. Can anyone provide an example how to do this? I need to put this on my playlist so it has to be a child of ConcatanatingAudioSource.
The only Audio Source that I found was using it from Uri.
final _playlist = ConcatenatingAudioSource(
children: [
AudioSource.uri(
Uri.parse(
"https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3"),
tag: AudioMetadata(
album: "Science Friday",
title: "ddddd",
artwork:
"https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg",
),
)
]
)
This is how I save the bytes:
void getBytes() async {
Uri uri = Uri.parse(url);
var rng = new Random();
// get temporary directory of device.
Directory tempDir = await getTemporaryDirectory();
// get temporary path from temporary directory.
String tempPath = tempDir.path;
// create a new file in temporary path with random file name.
File file = new File('$tempPath' + (rng.nextInt(100)).toString() + '.mp3');
// call http.get method and pass imageUrl into it to get response.
http.Response response = await http.get(uri);
// write bodyBytes received in response to file.
await file.writeAsBytes(response.bodyBytes);
}
Thanks in advance
So it seems that you need to create your own class as an extension of StreamAudioSource.
import 'dart:typed_data';
import 'package:just_audio/just_audio.dart';
class MyJABytesSource extends StreamAudioSource {
final Uint8List _buffer;
MyJABytesSource(this._buffer) : super(tag: 'MyAudioSource');
#override
Future<StreamAudioResponse> request([int? start, int? end]) async {
// Returning the stream audio response with the parameters
return StreamAudioResponse(
sourceLength: _buffer.length,
contentLength: (start ?? 0) - (end ?? _buffer.length),
offset: start ?? 0,
stream: Stream.fromIterable([_buffer.sublist(start ?? 0, end)]),
contentType: 'audio/wav',
);
}
}
And then invoke it like so
await thePlayer.setAudioSource(MyJABytesSource(bytes));
You can call thePlayer.play(). after, but I prefer to use this as a listener.
thePlayer.processingStateStream.listen((ja.ProcessingState state) {
if (state == ja.ProcessingState.ready) {
// I'm using flutter_cache_manager, and it serves all the file
// under the same name, which is fine, but I think this is why
// I need to pause before I can play again.
// (For tracks after the first, the first plays fine.)
// You probably won't need to pause, but I'm not sure.
thePlayer.pause();
thePlayer.play();
} else if (state == ja.ProcessingState.completed) {
// What to do when it completes.
}
});
The nice part about doing this way, is that you don't actually need await keyword, which can be situationally useful. I have it there just to make clear that this is an async function.
Related
I have displayed images and played audio from GET request. Now I need to store the images and .mp3 audio files locally. Is there any way to achieve this. I need to store List of images and audio files. Thank You.
This is the way I get response from API.
"data": [
{
"id": 1052,
"title": "1",
"audio_file": "",
"desc": null,
"display_title": 1,
"audio_src": null,
"image_src": "https://topik.com/storage/uploads/topik/images/20220620092536.jpg"
},
{
"id": 1053,
"title": "2",
"audio_file": "",
"desc": null,
"display_title": 1,
"audio_src": null,
"image_src": "https://topik.com/storage/uploads/topik/images/20220620092545.jpg"
},
]
You can use the path and path_provider to implement this. Sample code for the Api response you shared:
import 'dart:io';
import 'package:http/http.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
/// Returns the response from the api in a Map
Map getResponseMap(String apiUrl) {
var response = http.get(apiUrl);
Map responseMap = jsonDecode(response) as Map<String, dynamic>;
return responseMap;
}
/// Returns the list of files from the api response.
List<Map> getItemsListFromResponseMap(Map responseMap) {
var list = responseMap['data'];
return list.cast<Map>(); // cast the list to a list of Maps.
}
/// Returns the file name with extension.
String getFileNameFromUrl(String url) {
String fileName = url.split('/').toList().last;
return fileName;
}
Future<void> downloadAudioFromAPIResponse(Map singleResponseItem) async {
String audioUrl = singleResponseItem['audio_src'];
await saveNetworkFileToLocalDirectory(audioUrl); // Your file will be saved to the specified directoty in the [saveNetworkFileToLocalDirectory] function below.
}
Future<void> downloadImageFromAPIResponse(Map singleResponseItem) async {
String imgUrl = singleResponseItem['image_src'];
await saveNetworkFileToLocalDirectory(imgUrl); // Your file will be saved to the specified directoty in the [saveNetworkFileToLocalDirectory] function below.
}
Future<void> saveNetworkFileToLocalDirectory(String fileSrcUrl) async {
var response = await http.get(fileSrcUrl);
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String filePath = join(documentDirectory.path, getFileNameFromUrl(fileSrcUrl));
File file = new File(filePath);
await file.writeAsBytes(response.bodyBytes);
// The file has been written at the filePath specified, in this case,
// The app's document directory.
}
You can change the path to which the file is written by changing the getApplicationDocumentsDirectory to something else. For available paths, check out path_provider.
You can use the sample code to download all the files in your response as:
Map mainResponse = getResponseMap("YourApiRequestUrlHere");
// Get list of all item in the response by API.
List<Map> individualItems = getItemsListFromResponseMap(mainResponse);
// pre-evalute the count to improve performance by not having to call .length every time the loop finishes execution.
int count = individualItems.length;
for (int i = 0; i < count; i++) {
Map item = individualItems[i]; // get a single item.
if(item['audio_src'] != null && item['image_src'] != null) {
await downloadAudioFromAPIResponse(item);
await downloadImageFromAPIResponse(item);
} else if (item['audio_src'] != null) {
await downloadAudioFromAPIResponse(item);
} else if (item['image_src'] != null) {
await downloadImageFromAPIResponse(item);
} else {
continue;
}
}
If my answer was helpful, please mark my answer as Correct. Thank you.
After converting json object to dart object
use
Internet_file package
Uint8List bytes = await InternetFile.get(
data.image_src,
headers: headers, /* in case you want to set auth otherwise remove it*/
process: (percentage) {
print('downloadPercentage: $percentage');
},
);
Now use PathProvider
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
print("temp path : " + tempDir.toString());
await writeToFile(bytes, '${tempPath}/filename.png');
print("written");
I'm a junior working with flutter and hit a problem.
I need to open a file, read and compare some data everytime the app opens and then change some of that data as the app progress. We tried using .txt files to read and write some text, but when we had to look for something in the file was too complicated to change it and the file is not accessibe only on the device running the app. We also thought of using xml files but I don't know if is a good idea.
What would be a pratical solution for this situation, as the file needs to be opened all the time. Thanks.
Let's say our JSON looks like this:
{
title: 'some text',
values: [1,5,2,4,1,3],
}
And we want to make a UI that allows us to add values and to edit the title. First let's write a method to write and read from the JSON file:
Future<void> _write(Map<String, dynamic> value) async {
File f = File(_fileLocation);
String jsonStr = jsonEncode(value);
await f.writeAsString(jsonStr);
}
Future<Map<String, dynamic>> _read() async {
File f = File(_fileLocation); // maybe move this into a getter
final jsonStr = await f.readAsString();
return jsonDecode(jsonStr) as Map<String, dynamic>;
}
This way, the rest of the app should be trivial, but let's add a method to update the title and a method to add a new number:
Future<void> _updateTitle(String title) async {
var values = await _read();
values['title'] = title;
await _write(values);
}
Future<void> _addNumber(int number) async {
var values = await _read();
values['values'].push(number);
await _write(values);
}
Types with JSON and Dart can be a bit weird, so it is possible you need to use the as keyword when reading from the list:
Future<void> _addNumber(int number) async {
var values = await _read();
var valueList = (values['values'] as List<int>);
valueList.push(number);
values['values'] = valueList;
await _write(values);
}
Hopefully, this example helps
I followed the guide in the flutter doc and didnt understand how it works.
I have a existing file containing users info, and I want to be able to write/update the file.
Here's the relevant parts of my code:
Future<bool> sendMessage(String sender, String target, String body) async {
String tempAccountData = await loadMessage();
List<Map<String, dynamic>> accountData =
List<Map<String, dynamic>>.from(jsonDecode(tempAccountData));
//find valid target
print(accountData);
print(target);
for (Map<String, dynamic> accountInfo in accountData) {
if (accountInfo["username"] == target) {
//write message
accountInfo["messages"]
.add({"time": DateTime.now(), "sender": sender, "message": body});
writeMessage(accountData);
return true;
}
}
return false;
}
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<File> get _localFile async {
final path = await _localPath;
print(path);
return File('$path/accountInfo.json');
}
Future<File> writeMessage(List<Map<String, dynamic>> accountData) async {
File file = await _localFile;
// Write the file
return file.writeAsString('$accountData');
}
I didnt know what the path is, and I put the file into assets/data as seen in the screenshot below: (I'm trying to read/write accountInfo.json from accountInfo.dart)
It's not possible to modify a file in the assets during runtime.
What you can do is write all the data in the Json to a file in one of the device's directories and then modify that file when needed or use a database to store all the data.
There are a few steps that need to happen and things you'll need to know. First of all, assets are immutable. This means you can read from them, but you're not able to write to them. What you'll need is a local file. A file that lives on the file system of the device where the app is installed. This file can be modified, and will allow writing to it.
The process would go as follows:
Check if there's already a local file
If not, create it and put the contents of an initial asset file onto it.
Now that the mutable local file exists, read and write from it.
Here's a code sample of a class that takes care of that:
import 'dart:io';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
const initialAssetFile = 'assets/initial.json';
const localFilename = 'data.json';
class Repository {
/// Initially check if there is already a local file.
/// If not, create one with the contents of the initial json in assets
Future<File> _initializeFile() async {
final localDirectory = await getApplicationDocumentsDirectory();
final file = File('$localDirectory/$localFilename');
if (!await file.exists()) {
// read the file from assets first and create the local file with its contents
final initialContent = await rootBundle.loadString(initialAssetFile);
await file.create();
await file.writeAsString(initialContent);
}
return file;
}
Future<String> readFile() async {
final file = await _initializeFile();
return await file.readAsString();
}
Future<void> writeToFile(String data) async {
final file = await _initializeFile();
await file.writeAsString(data);
}
}
It will basically check before every read or write if the local file has already been created, and will take care of that if not.
Note that there's no JSON specific things happening here, this is just default file IO. JSON encoding/decoding should happen before/after this.
You'll just need to add an initial file into the assets/ folder and specify that in your pubspec.yaml as follows:
flutter:
assets:
- assets/initial.json # the file containing the initial data
# - assets/ # you can also just add the whole directory
Once that's done, you should be able to use readFile() and writeFile(String data) from this Repository class.
(It might be important to make sure WidgetsFlutterBinding.ensureInitialized(); has been called in order to interact with the local file system)
First, I created a sample HttpServer console app using shelf-related packages to make sure I have the correct code to handle the image upload URI handler.
The console app was able to receive and save the image correctly then I tried the same code to Flutter Android app with only one minor difference which is the location of the saved image file.
Here's the code:
import 'package:shelf_router/shelf_router.dart' as srouter;
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:mime/mime.dart';
import 'package:path_provider/path_provider.dart' as prod;
var app = srouter.Router();
final directory = await prod.getExternalStorageDirectory();
app.post('/upload', (Request request) async {
String? boundary = request.headers['content-type'];
final payload = await request.read();
final boundWord = 'boundary=';
if (boundary != null) {
var boundIndex = boundary.indexOf(boundWord);
boundary = boundary.substring(boundIndex + boundWord.length);
final transformer = MimeMultipartTransformer(boundary);
final parts = await transformer.bind(payload).toList();
for (var part in parts) {
final content = await part.toList();
await File('${directory?.path}/newImgFile.png').writeAsBytes(content[0]);
}
}
return Response.ok('Upload done');
});
await io.serve(app, '0.0.0.0', 8080);
When I ran this and I send an image file using curl, it seems that it does everything correctly but when I checked the saved newImgFile, it's incorrect.
The file size doesn't match at all. I had my test image is in 900 KB but it was saved with much less than that (I think it was 30 KB).
I did notice one thing that's different on the content after this code line:
final content = await part.toList();
With the same image upload, content is a list of only 1 item for HttpServer console app but for HttpServer Flutter Android app, content is a list with 2 items.
I don't know if that's going to help in solving this issue but that's a difference that I am noticing.
I think the most useful conceptual model of MIME multipart uploads is:
any number of parts, in any order, each comprised of
any number of chunks, in consecutive order
Based on what you've described, you are receiving multiple chunks for at least one of the parts, which you will need to deal with. There is another issue with your code that may crop up, and that is the assumption that the part you care about is last (eg, you may have multiple parts, but you are overwriting the file with the last one anyway).
Putting them both together, you could do something like the following:
import 'package:http_parser/http_parser.dart';
...
app.post('/upload', (Request request) async {
final contentType = request.headers['content-type'];
if (contentType == null) {
return Response(400, body: 'Missing content-type');
}
final mediaType = MediaType.parse(contentType);
if (mediaType.mimeType != 'multipart/form-data') {
return Response(400, body: 'Invalid content-type');
}
final boundary = mediaType.parameters['boundary'];
if (boundary == null) {
return Response(400, body: 'Missing boundary');
}
final payload = request.read();
final parts = await MimeMultipartTransformer(boundary).bind(payload).toList();
for (final part in parts) {
if (part.headers['content-type'] != 'image/png') {
continue;
}
final file = File('${directory?.path}/newImgFile.png');
if (await file.exists()) {
await file.delete();
}
final chunks = await part.toList();
for (final chunk in chunks) {
await file.writeAsBytes(chunk, mode: FileMode.append);
}
return Response.ok('Upload done');
}
return Response(400, body: 'No good parts');
});
which will:
check whether the request Content-Type is valid (using MediaType)
find the first part that has the appropriate image/png content type
delete the destination file if it already exists
append each chunk to the newly created destination file
Going further, you could take advantage of the fact that both MimeMultipartTransformer.bind() and MimeMultipart implement Stream and do something like the following:
app.post('/upload', (Request request) async {
final contentType = request.headers['content-type'];
if (contentType == null) {
return Response(400, body: 'Missing content-type');
}
final mediaType = MediaType.parse(contentType);
if (mediaType.mimeType != 'multipart/form-data') {
return Response(400, body: 'Invalid content-type');
}
final boundary = mediaType.parameters['boundary'];
if (boundary == null) {
return Response(400, body: 'Missing boundary');
}
final payload = request.read();
final parts = MimeMultipartTransformer(boundary).bind(payload).where((part) {
return part.headers['content-type'] == 'image/png';
});
final partsIterator = StreamIterator(parts);
while (await partsIterator.moveNext()) {
final part = partsIterator.current;
final file = File('${directory?.path}/newImgFile.png');
if (await file.exists()) {
await file.delete();
}
final chunksIterator = StreamIterator(part);
while (await chunksIterator.moveNext()) {
final chunk = chunksIterator.current;
await file.writeAsBytes(chunk, mode: FileMode.append);
}
return Response.ok('Upload done');
}
return Response(400, body: 'No good parts');
});
which:
does some filtering upfront to ensure only image/png parts are considered, and
uses StreamIterator to allow iterating over the stream with await
I am working on a Flutter project to syntehsise an string to an audio file. For this reason, I have added flutter_tts as a dependency and implemented the following method with different approaches in order to check the existence of the generated file:
/// Synthesises the current audio cue into an audio file
static Future<void> synthesiseStringToAudioFile() async {
Future<String> finalPath;
Future<File> finalFile;
Uri uriToFile;
String absolutePath;
bool existsPath;
bool existsManually;
bool exists3;
await flutterTts
.synthesizeToFile("This is my first audio synthesizer in Flutter",
audioFileName)
.then((value) => {
// File has been successfully created
if (value == 1)
{
// Gets the path to the generated audio file
finalPath = pathToFile,
finalPath.then((path) async => {
print('AFile :Path to audio file: $path'),
// Check if exists
existsPath = FileSystemEntity.typeSync(path) != FileSystemEntityType.notFound,
print("AFile : Exists? $existsPath"),
existsManually = await File('/storage/emulated/0/Android/data/mypath/files/temp_audio_cue.wav').exists(), // Requieres async function
print("AFile : Exists2? $existsManually"), // RETURNS TRUE
exists3 = await File(path).exists(),
print("AFile : Exists3? $exists3")
}),
// Gets the generated file
finalFile = localFile,
finalFile.then((file) => {
// Absolute path
absolutePath = file.absolute.path,
print('AFile : AbsolutePath: $absolutePath'),
// Check the URI
uriToFile = file.uri,
print('AFile : URI to audio file: $uriToFile'),
}),
}
else
{print('There was an error during the synthezisation')}
});
}
static void setAudioFileName() {
audioFileName = Platform.isAndroid ? "temp_audio_cue.wav" : "temp_audio_cue.caf";
}
/// Gets the path to the file to be accessed
static Future<String> get pathToFile async {
final path = await localPath;
return '$path/$audioFileName';
}
/// Gets the path to the local directory
static Future<String> get localPath async {
final dir = await getApplicationDocumentsDirectory();
return dir.path;
}
Once the synthesisation is completed, flutterTts.synthesizeToFile() logs in console the following message:
D/TTS (10335): Successfully created file :
/storage/emulated/0/Android/data/mypath/files/temp_audio_cue.wav
so if I check the existence of the file manually (as I do with existManually) will get a true value, but I am not able to do it trying to get dynamically the path as in the other examples I am trying but the ones I am getting are:
/data/user/0/mypath/app_flutter/temp_audio_cue.wav
so it is missing the beginning
/storage/emulated/0/Android/
I was wondering what is the correct way to get the path to the file (missing)?
With path_provider in Android save in getExternalStorageDirectory and in iOS save in getApplicationDocumentsDirectory..
If you want to get this path : /storage/emulated/0
Use path_provider_ex package, which provides root and app files directory for both "external storage" (internal flash) and SD card (if present), as well as available space for each storage.
you can use path_provider package of flutter
Directory appDocDir = await getApplicationDocumentsDirectory();
String appDocPath = appDocDir.path;
https://pub.dev/packages/path_provider
I am using filesystem_picker to return absolute paths from storage and then using File('path_string') or Directory('path_string') to get the actual file.
Using manageExternalStorage permissions allows this work, but keep in mind:
"The Google Play store has a policy that limits usage of MANAGE_EXTERNAL_STORAGE".
This also may not work depending on the SDK you are using and/or conflicts from other packages.
import 'package:filesystem_picker/filesystem_picker.dart';
import 'package:permission_handler/permission_handler.dart';
Directory? rootDir;
late String tempDir;
_getFile(){
await _pickFile();
String path = tempDir;
var file = File(path);
// Do stuff with file
}
// Call this before _pickFile(), ideally inside initState()
Future<void> _prepareStorage() async {
rootDir = Directory('/storage/emulated/0/');
var storageExternal = await Permission.manageExternalStorage.status;
if (storageExternal != PermissionStatus.granted) {
await Permission.manageExternalStorage.request();
}
bool b = storageExternal == PermissionStatus.granted;
//mPrint("STORAGE ACCESS IS : $b");
}
Future<void> _pickFile(BuildContext context) async {
await FilesystemPicker.open(
title: 'Select file',
context: context,
rootDirectory: rootDir!,
fsType: FilesystemType.file,
pickText: 'Select file to add',
folderIconColor: Colors.teal,
requestPermission: () async => await Permission.manageExternalStorage.request().isGranted,
).then((value){
if (value != null){
tempDir = value.toString();
}
else{
tempDir = "";
}
//mPrint(tempDir);
});
}
Add the following to AndroidManifest.xml:
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:label="test_flutter"
android:name="${applicationName}"
android:icon="#mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"
>
I tried and it worked
import 'package:path_provider/path_provider.dart';
Future<File> getImageFileFromAssets(Asset asset) async {
final byteData = await asset.getByteData();
final tempFile =
File("${(await getTemporaryDirectory()).path}/${asset.name}");
final file = await tempFile.writeAsBytes(
byteData.buffer
.asUint8List(byteData.offsetInBytes, byteData.lengthInBytes),
);
return file;
}