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);
}
}
Related
I'm building a server and I need to serve binary files.
My code is this:
//lib/handler.dart
import 'dart:io';
import 'package:mime/mime.dart';
class FileResponse {
Object name;
ContentType type;
FileResponse(this.name, this.type);
}
Object tr(String f) {
Object res = File(f).readAsBytesSync();
try {
File(f).readAsStringSync();
res = File(f).readAsStringSync();
} catch(_) {}
return res;
}
FileResponse fileHandler(String uri) {
if(uri == "/") uri = "/index.html";
if(File("/Users/jett/Sites$uri").existsSync()) {
ContentType tpe = ContentType.parse(lookupMimeType(uri)!);
Object resp = tr("/Users/jett/Sites/$uri");
return FileResponse(resp, tpe);
} else if(File("/Users/jett/Sites$uri/index.html").existsSync()) {
ContentType tpe = ContentType.parse(lookupMimeType(uri)!);
Object resp = tr("/Users/jett/Sites/$uri/index.html");
return FileResponse(resp, tpe);
} else if(File("/Users/jett/Sites${uri}index.html").existsSync()) {
ContentType tpe = ContentType.parse(lookupMimeType(uri)!);
Object resp = tr("/Users/jett/Sites/${uri}index.html");
return FileResponse(resp, tpe);
} else {
return FileResponse(File("/Users/jett/Sites/404.html").readAsStringSync(), ContentType.html);
}
}
//bin/srv.dart
import 'package:srv/handler.dart';
import 'dart:io';
void main() async {
final HttpServer srvs = await HttpServer.bindSecure('127.0.0.1', 443, SecurityContext()..useCertificateChain("/Users/jett/srv/fc.pem"));
final HttpServer srv = await HttpServer.bind('127.0.0.1', 80);
await for(final HttpRequest request in srv) {
print(request.uri.path);
FileResponse res = fileHandler(request.uri.path);
request.response
..headers.contentType = res.type
..write(res.name);
request.response.close();
}
}
I created an empty server and added a handler to it. The file handler checks for an existing file or a 404 page. The text files work fine(html js css json)... but the image and video files crash.
And the docs tell me this:
// TODO(ajohnsen): Add documentation of how to pipe a file to the response.
So how do I pipe a binary file into the response.
I was wondering if anyone knew how to correctly perform asynchronous file I/O in Dart using Streams, because clearly my method is not working. I have seen other people's solutions by converting the stream to a list, but I don't really want to do that because of speed concerns and was wondering if there was any way to do this operation using just streams?
Anyways, here is my code:
import 'dart:io';
import 'dart:collection';
import 'dart:convert';
final validCharacters = RegExp(r'^[a-zA-Z]+$'); // a-z, A-Z
main() async {
final dict1File = 'american-english';
final dict2File = 'british-english';
final cleanedDictFile = 'Cleansed_English_Dictionary';
SplayTreeSet<String> wordSet = new SplayTreeSet<String>();
await readFile(dict1File, wordSet);
await readFile(dict2File, wordSet);
await writeFile(cleanedDictFile, wordSet);
}
readFile(String fileName, SplayTreeSet<String> wordSet) async {
File file = new File(fileName);
if (await file.exists()) {
file
.openRead()
.transform(Utf8Decoder())
.transform(LineSplitter())
.where((String data) => validCharacters.hasMatch(data))
.listen((String data) => wordSet.add(data),
onDone: () => print('${wordSet.length}'), onError: (e) => print(e));
} else {
print('The desired file $fileName does not exist');
}
}
writeFile(String fileName, SplayTreeSet<String> wordSet) async {
File file = new File(fileName);
if (await file.exists()) {
file.openWrite().writeAll(wordSet, '\n');
} else {
print('The desired file $fileName does not exist');
}
}
Here is me performing a similar operation in Java (my native language) :p
import java.io.*;
import java.util.Set;
import java.util.TreeSet;
public class DictCleaner {
public static void main(String[] args) throws IOException {
System.out.println("Beginning process");
Set<String> words = new TreeSet<>();
long time1 = System.currentTimeMillis();
readFile("american-english", words);
readFile("british-english", words);
long time2 = System.currentTimeMillis();
System.out.println(
"Time to read files: " + (time2 - time1) + " milliseconds"
);
System.out.println("Number of words: " + words.size());
String filename = "Cleansed_English_Dictionary";
System.out.println("Outputting new file, " + filename);
outputFile(filename, words);
long time3 = System.currentTimeMillis();
System.out.println(
"Total execution time: " + (time3 - time1) + " milliseconds"
);
File file = new File(filename);
double fileSize = file.length() >> 10;
System.out.println(
"Output file size of " + filename + "= " + fileSize + " kilobytes"
);
}
public static void readFile(String fileName, Set<String> words)
throws IOException {
BufferedReader br;
try {
br = new BufferedReader(new FileReader(fileName));
String currentLine;
while ((currentLine = br.readLine()) != null) {
if (currentLine.matches("^[a-zA-Z]") && currentLine.length() <= 15) {
words.add(currentLine);
}
}
br.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
public static void outputFile(String filename, Set<String> words)
throws FileNotFoundException {
BufferedOutputStream bos = new BufferedOutputStream(
new FileOutputStream(filename)
);
try {
for (String s : words) {
bos.write(s.getBytes());
bos.write(System.lineSeparator().getBytes());
}
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Anyways, I am basically just cleaning up a dictionary for use with my app. Does anyone have any guidance on how I can make the Dart code work, or perhaps a more generic explanation?
My new code after comments!
import 'dart:io';
import 'dart:collection';
import 'dart:convert';
final validCharacters = RegExp(r'^[a-zA-Z]+$'); // a-z, A-Z
main() async {
Stopwatch stopwatch = new Stopwatch()..start();
final dict1File = 'american-english';
final dict2File = 'british-english';
final cleanedDictFile = 'Cleansed_English_Dictionary';
SplayTreeSet<String> wordSet = new SplayTreeSet<String>();
await readFile(dict1File, wordSet);
await readFile(dict2File, wordSet);
await writeFile(cleanedDictFile, wordSet);
print('Total execution time: ${stopwatch.elapsed}');
}
Future<void> readFile(String fileName, SplayTreeSet<String> wordSet) async {
File file = new File(fileName);
if (await file.exists()) {
await for (final data in file
.openRead()
.transform(Utf8Decoder())
.transform(LineSplitter())
.where((String data) => validCharacters.hasMatch(data))) {
wordSet.add(data);
}
print('${wordSet.length}');
} else {
print('The desired file $fileName does not exist');
}
}
Future<void> writeFile(String fileName, SplayTreeSet<String> wordSet) async {
File file = new File(fileName);
if (await file.exists()) {
IOSink sink = file.openWrite();
sink.writeAll(wordSet, '\n');
sink.close();
} else {
print('The desired file $fileName does not exist');
}
}
The problem is that .listen does not halt the program until all events has been consumed.
I think the most clean way to fix this is to use "await for-each" like this:
Future<void> readFile(String fileName, SplayTreeSet<String> wordSet) async {
File file = new File(fileName);
if (await file.exists()) {
await for (final data in file
.openRead()
.transform(Utf8Decoder())
.transform(LineSplitter())
.where((String data) => validCharacters.hasMatch(data))) {
wordSet.add(data);
}
print('${wordSet.length}');
} else {
print('The desired file $fileName does not exist');
}
}
More optimized solution
As requested, I have tried to make the program run faster. If performance are the main goal, you can often save some time by using the "sync" edition of the IO file operations. In this case, the program are really not doing anything else, so it is fine to let the main thread wait on e.g. reading the file.
One optimization is more about code readability but Dart does already provide readAsLines and readAsLinesSync on File so we don't need to use a Utf8Decoder and LineSplitter if we just want to split the lines. This will potentially take some more memory since we now get a list containing all lines instead of iterating each line. The list are short-lived so it is properly not a problem for this program. But also, the list contains String objects where most of them are going to be used in our Set so most of the used memory is not really something we can avoid.
Another optimization is the writing. Here we generate the final string first in memory (wordSet.join('\n')) and then write the whole string to disk as one operation. This is much faster but does take some additional memory.
import 'dart:collection';
import 'dart:io';
final validCharacters = RegExp(r'^[a-zA-Z]+$'); // a-z, A-Z
void main() {
const dict1File = 'american-english-insane';
const dict2File = 'british-english-insane';
const cleanedDictFile = 'Cleansed_English_Dictionary';
final wordSet = SplayTreeSet<String>();
readFile(dict1File, wordSet);
readFile(dict2File, wordSet);
writeFile(cleanedDictFile, wordSet);
}
void readFile(String fileName, Set<String> wordSet) {
final file = File(fileName);
if (file.existsSync()) {
file
.readAsLinesSync()
.where((data) => validCharacters.hasMatch(data))
.forEach(wordSet.add);
print(wordSet.length);
} else {
print('The desired file $fileName does not exist');
}
}
void writeFile(String fileName, Iterable<String> wordSet) =>
File(fileName).writeAsStringSync(wordSet.join('\n'));
I am trying to upload image from client (flutter) to server (Aqueduct.io) using MultipartRequest.
It's working, but currently file names are assigned the current time, how can I pass the filename from a client and parse it on a server side?
Client code:
final String imageName = nameController.text.replaceAll(" ", "");
var postUri = Uri.parse("http://***:8888/media");
var request = new http.MultipartRequest("POST", postUri);
request.files.add(new http.MultipartFile.fromBytes('file', image,
filename: imageName, contentType: MediaType('image', 'jpeg')));
request.send().then((response) {
if (response.statusCode == 200) print("Uploaded!");
});
}
Server code:
import 'dart:async';
import 'dart:io';
import 'package:aqueduct/aqueduct.dart';
import 'package:mime/mime.dart';
import 'package:http_server/http_server.dart';
class MediaController extends ResourceController {
MediaController() {
acceptedContentTypes = [ContentType("multipart", "form-data")];
}
#Operation.post()
Future<Response> postMultipartForm() async {
final transformer = MimeMultipartTransformer(
request.raw.headers.contentType.parameters["boundary"]);
final bodyStream =
Stream.fromIterable([await request.body.decode<List<int>>()]);
final parts = await transformer.bind(bodyStream).toList();
for (var part in parts) {
final HttpMultipartFormData multipart = HttpMultipartFormData.parse(part);
final content = multipart.cast<List<int>>();
final filePath =
"public/" + DateTime.now().millisecondsSinceEpoch.toString() + ".jpg"; // <---current filename implementation
final IOSink sink = File(filePath).openWrite();
await for (List<int> item in content) {
sink.add(item);
}
await sink.flush();
await sink.close();
}
return Response.ok({});
}
}
Okay, I have the asnwer
import 'dart:async';
import 'dart:io';
import 'package:aqueduct/aqueduct.dart';
import 'package:mime/mime.dart';
import 'package:http_server/http_server.dart';
class MediaController extends ResourceController {
MediaController() {
acceptedContentTypes = [ContentType("multipart", "form-data")];
}
#Operation.post()
Future<Response> postMultipartForm() async {
final transformer = MimeMultipartTransformer(
request.raw.headers.contentType.parameters["boundary"]);
final bodyStream =
Stream.fromIterable([await request.body.decode<List<int>>()]);
final parts = await transformer.bind(bodyStream).toList();
for (var part in parts) {
final HttpMultipartFormData multipart = HttpMultipartFormData.parse(part);
List<String> tokens = part.headers['content-disposition'].split(";");
String filename;
for (var i = 0; i < tokens.length; i++) {
if (tokens[i].contains('filename')) {
filename = tokens[i]
.substring(tokens[i].indexOf("=") + 2, tokens[i].length - 1);
}
}
print('file $filename.jpg uploaded');
final content = multipart.cast<List<int>>();
final filePath =
// "public/" + DateTime.now().millisecondsSinceEpoch.toString() + ".jpg";
'public/$filename.jpg';
final IOSink sink = File(filePath).openWrite();
await for (List<int> item in content) {
sink.add(item);
}
await sink.flush();
await sink.close();
}
return Response.ok({});
}
}
So, I am trying to save an image file from the camera then upload it to firebase. Unfortunately, the firebase storage plugin has some errors and doesn't return errors in the catch so I need to check wifi connection before i do so, just in case i need to cache the image and upload it later. Once the image file has uploaded i then create some JSON and send that to firebase database where the app pulls down relevant information.
Note: sometimes this code works and the image is not empty, othertimes the image comesback empty so im guessing its a timing issue?
Future saveImageFile(UploadImage image) async {
await storage.init();
var imageFile = storage.saveImageFile(image.file, image.getName());
instance.setInt("ImageCount", imageCount + 1);
checkConnectionThenUploadImage(imageFile);
}
//From storage class
File saveImageFile(File toBeSaved, String fileName) {
final filePath = '$imageDirectory$fileName';
var file = new File(filePath)
..createSync(recursive: true)
..writeAsBytes(toBeSaved.readAsBytesSync());
return file;
}
checkConnectionThenUploadImage(File image) {
checkConnectivity().then((isConnected) async {
if (!isConnected) {
instance.setBool("hasImagesToUpload", true);
} else {
await saveImageToStorage(image);
}
}).catchError((error) {
print("Error getting connectivity status, was error: $error");
});
}
saveImageToStorage(File imageFile) async {
final fileName = getNameFromFile(imageFile);
final StorageReference ref = FirebaseStorage.instance.ref().child("AllUsers").child(uuid).child(fileName);
final StorageUploadTask uploadTask = ref.putFile(imageFile, const StorageMetadata(contentLanguage: "en"));
final url = (await uploadTask.future).downloadUrl;
if (url != null) { //Normally you could catch an error here but the plugin has a bug so it needs to be checked in other ways
final fireImage = new FireImage(getNameFromFile(imageFile), storage.getDateFromFileName(fileName), imageCount, "", url.toString());
saveImageJsonToDatabase(fireImage);
storage.deleteImageFile(fileName);
} else {
checkConnectionThenUploadImage(imageFile);
}
}
saveImageJsonToDatabase(FireImage image) async {
await storage.init();
storage.saveJsonFile(image);
checkConnectivity().then((isConnected) {
if (!isConnected) {
instance.setBool("hasJsonToUpload", true);
} else {
final DatabaseReference dataBaseReference = FirebaseDatabase.instance.reference().child("AllUsers").child(uuid);
dataBaseReference.child("images").push().set(image.toJson()).whenComplete (() {
storage.deleteJsonFile(basename(image.name));
}).catchError((error) { //catching errors works with firebase database
saveImageJsonToDatabase(image);
});
}
});
}
//From storage class
deleteImageFile(String fileName) async {
final filePath = '$imageDirectory$fileName';
File(filePath).delete();
}
The image gets uploaded and the json is created but when i try to view the image using the download url from firebase storage it says the image is empty. The only clue i have is that this is a timing issue because it only happens occasionally.
Can anyone see where im going wrong?
I'm trying to stream local audio files in m4a (aac) similar to Tiberiu-IonuČ› Stan (http://stackoverflow.com/questions/2036107/aac-mp4-not-working-in-actionscript-3s-netstream):
package
{
import flash.net.NetConnection;
import flash.net.NetStream;
import flash.events.NetStatusEvent;
import flash.events.AsyncErrorEvent;
import flash.events.Event;
public class Mysound
{
private var _connection:NetConnection;
private var _netStream:NetStream;
private var _filePath:String;
private var _client:Object;
public function MainDocument(filePath:String):void
{
_filePath = filePath;
connect();
}
private function connect():void
{
_connection=new NetConnection();
_connection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHandler);
_connection.addEventListener(AsyncErrorEvent.ASYNC_ERROR, asyncErrorHandler);
_connection.connect(null);
}
private function netStatusHandler(event:NetStatusEvent):void
{
switch (event.info.code)
{
case "NetConnection.Connect.Success":
requestAudio();
break;
}
}
private function requestAudio():void
{
_netStream=new NetStream(_connection);
_netStream.addEventListener(NetStatusEvent.NET_STATUS, this._netStatusHandler);
_netStream.addEventListener(AsyncErrorEvent.ASYNC_ERROR, this._asyncErrorHandler);
_client = new Object();
_client.onMetaData = onMetaData;
_netStream.client = _client;
_netStream.backBufferTime = 0;
_netStream.bufferTime = 0.5;
_netStream.bufferTimeMax = 5;
_netStream.play(filePath);
}
private function asyncErrorHandler(event:AsyncErrorEvent):void
{
trace(event);
}
private function onMetaData(metadata:Object):void
{
var str:String = "";
for (var key:String in metadata) {
str += key + ": " + metadata[key];
}
trace(str);
}
}
}
It works for me on emulator but it doesn't on devices (ipads).
I found out that netStatusHandler on device catches status "NetStream.Play.Failed", but i have no idea why, I know that it correctly reads file as it does get correct metadata, it also starts to buffer sound, but fails to play it. Files are in folder next to my app swf so it shouln't be sandbox problem. What else should I try to get it working?