flutter audio player play sound not working in IOS - ios

I am using flutter plugin audioplayers: ^0.7.8, the below code is working in Android but not working in IOS. I run the code in real ios device and click the button. It suppose the play the mp3 file, but no sound at all. Please help to solve this issue.
I already set up the info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
Here with the print out from the console:
flutter: finished loading, uri=file:///var/mobile/Containers/Data/Application/E3A576E2-0F21-44CF-AF99-319D539767D0/Library/Caches/demo.mp3
Syncing files to device iPhone...
flutter: _platformCallHandler call audio.onCurrentPosition {playerId: 273e1d27-b6e8-4516-bb3f-967a41dff308, value: 0}
flutter: _platformCallHandler call audio.onError {playerId: 273e1d27-b6e8-4516-bb3f-967a41dff308, value: AVPlayerItemStatus.failed}
Here with my code:
class _MyHomePageState extends State<MyHomePage> {
AudioPlayer audioPlugin = AudioPlayer();
String mp3Uri;
#override
void initState() {
AudioPlayer.logEnabled = true;
_load();
}
Future<Null> _load() async {
final ByteData data = await rootBundle.load('assets/demo.mp3');
Directory tempDir = await getTemporaryDirectory();
File tempFile = File('${tempDir.path}/demo.mp3');
await tempFile.writeAsBytes(data.buffer.asUint8List(), flush: true);
mp3Uri = tempFile.uri.toString();
print('finished loading, uri=$mp3Uri');
}
void _playSound() {
if (mp3Uri != null) {
audioPlugin.play(mp3Uri, isLocal: true,
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Audio Player Demo Home Page'),
),
body: Center(),
floatingActionButton: FloatingActionButton(
onPressed: _playSound,
tooltip: 'Play',
child: const Icon(Icons.play_arrow),
),
);
}
}

If you want to use a local file, you have to use AudioCache.
Looking at the documentation, it says at the bottom:
AudioCache
In order to play Local Assets, you must use the AudioCache class.
Flutter does not provide an easy way to play audio on your assets, but this class helps a lot. It actually copies the asset to a temporary folder in the device, where it is then played as a Local File.It works as a cache because it keep track of the copied files so that you can replay then without delay.
To get audio to play, this is what I figured out what we need to do:
import 'package:flutter/material.dart';
import 'package:audioplayers/audio_cache.dart';
AudioCache audioPlayer = AudioCache();
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override _MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override initState(){
super.initState();
audioPlayer.play("Jingle_Bells.mp3");
}
#override Widget build(BuildContext context) {
//This we do not care about
}
}
Important:
It automatically places 'assets/' in front of your path. This means you if you wanted to load assets/Jingle_Bells.mp3, you would simply put audioPlayer.play("Jingle_Bells.mp3");. If you entered audioPlayer.play("assets/Jingle_Bells.mp3"); instead, AudioPlayers would actually load assets/assets/Jingle_Bells.mp3 instead.
``

For iOS you must add file:// in front of your local url.
For Android this approach will also work but it's not required.

Related

How do you make local storage persist after the app has been closed in Flutter?

I'm using the package localstorage - https://pub.dartlang.org/packages/localstorage#-installing-tab-
This is my code:
import 'package:flutter/material.dart';
import 'package:localstorage/localstorage.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
_MyHomePageState createState() => _MyHomePageState();
}
final LocalStorage storage = new LocalStorage("level");
class _MyHomePageState extends State<MyHomePage> {
void initState() {
super.initState();
//storage.setItem("level", 0);
printStorage();
}
void printStorage() {
print("level stored: " + storage.getItem("level").toString());
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("LocalStorage Example"),
),
body: Center(
),
);
}
}
When storage.setItem("level", 0) is not commented out the app works fine and prints out "level stored: 0". However after closing the app, commenting out storage.setItem("level", 0), saving the app and rerunning the app the app prints out "level stored: null".
How do you make the storage persist from the last time the app was run?
I am using the Xcode iPhone simulator.
Any help is greatly appreciated!
By looking at the package’s source code it looks like you’re not giving it enough time to load the data from the underlying async call. That’s why it exposes a flag property for you to check if it’s ready to perform read/write operations.
Just use it before printing as so:
storage.ready.then((_) => printStorage());
I had similar questions, but after hours of testing and digging around, I can confirm that the localstorage package does persist data even after the app has been closed. If you want to see the actual database file for testing purposes (you can see the contents change live during iOS simulation) look here on macOS:
/Users/YOURUSERNAME/Library/Developer/CoreSimulator/Devices/SOMEDEVICENUMBER/data/Containers/Data/Application/SOMEAPPLICATIONNUMBER/Documents/level.json
So this means it just becomes a matter of waiting the few nanoseconds for the localstorage connection to be ready before trying to read and use the values. Being new to Dart/Flutter, for me this means experimenting with the correct combination and location of async/await, or Future/then.
For example, this change will work correctly in your sample app:
void printStorage() async {
//this will still print null:
print("before ready: " + testDb.getItem("level").toString());
//wait until ready
await storage.ready;
//this will now print 0
print("after ready: " + testDb.getItem("level").toString());
}
In my actual app, if I forget to correctly place an "await" or "then", I get a lot of null errors before the actual value gets retrieved/assigned.
BTW in your file (level.json) you have only one element, {“level”, “0”}. For this scenario with simple data needs, using the shared_preferences package is an alternative consideration. But I like localstorage because it is clean and simple.

rxdart BehaviorSubject does not emit last value on iOS

I am building a Flutter app and have run into the following issue. Following the BLoC pattern, my code is structured like this:
BLoC:
class Bloc {
final _subject = BehaviourSubject<Data>;
Observable<Data> get stream => _subject.stream;
void load() async {
_subject.sink.add(Data.loading());
final data = await /* ... */;
_subject.sink.add(Data.success(data));
}
}
State:
#override
Widget build(BuildContext context) {
return StreamBuilder<Data>(
stream: widget.bloc.stream,
builder: (context, snapshot) {
print(snapshot);
if (!snapshot.hasData || snapshot.data.isLoading) {
return /* loadingindicator */;
}
return /* data view */;
},
);
}
#override
void initState() {
super.initState();
widget.bloc.load();
}
On Android, everything works as expected. The build method gets called for
Data.loading() and for Data.success().
On iOS, however, I do not receive any data at all (snapshot.hasdata == false). I added a button, which on click, listens to the stream and prints
the received value. On Android, I receive the last event, as it is expected with a BehaviorSubject. On iOS, the listen method did not get called.
Button:
MaterialButton(
child: Text('Click me'),
onPressed: () {
widget.bloc.stream.listen(print); // prints Data.success on Android, nothing on iOS
},
),
Ok, I've found the problem. As #herbert pointed out to me, it was very unlikely that rxdart was the problem here.
I have looked into my code and found that my Page was being rebuilt, and because my code was looking like this, the bloc was re-instantiated. This somehow only happened on iOS and not on Android.
'page': (context) => MyPage(bloc: Bloc()),

Flutter - How to architect multiple nested BLoC?

Suppose there is a top-level BLoC called PreferenceBloc, and inside that BLoC is another BLoC called PageBloc. If the logic inside PageBloc requires on a value stream from PreferenceBloc (i.e., create a new page needs to know page configuration now), how should I architect this?
Code sample:
class PreferencesBloc{
final preferencesService=PreferencesService();
// Output interfaces of Bloc
ValueObservable<String> get mainDir => _mainDir.distinct().shareValue(seedValue: '/');
final _mainDir = BehaviorSubject<String>(seedValue: '/');
// Input interfaces of Bloc...
// .........
}
class PageBloc{
final List<PageInfo> _pageInfos=<PageInfo>[];
// Output interfaces of Bloc...
// .........
// Input interfaces of Bloc...
Sink<int> get pageCreation => _pageCreationController.sink;
final _pageCreationController = StreamController<int>();
pageBloc(){
_pageCreationController.stream.listen(_handleCreation);
}
void _handleCreation(int pos){
_pageInfo.insert(pos, PageInfo('I need the mainDir here!')); //Need info from PreferencesBloc!!!
}
}
class PreferencesProvider extends InheritedWidget{
final PreferencesBloc preferencesBloc;
//...
}
class PageProvider extends InheritedWidget{
final PageBloc pageBloc;
//...
}
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return PreferencesProvider(
child: PageProvider(
child: MaterialApp(
home: Scaffold(
body: Text("test"),
),
),
),
);
}
}
Edit: To sum up, it is convenient to communicate between Bloc and widget in flutter, but is there a good way to communicate between Bloc and Bloc?
This question was asked at Nov 18. Currently, there is already a great bloc library that support nested bloc. You can use https://pub.dartlang.org/packages/flutter_bloc in the official pub dart. So far, i have been using it for developing a quite complex app, and it is great.

Flutter Load Image from Firebase Storage

I see there are a lot of examples on how to upload an image using flutter to firebase storage but nothing on actually downloading/reading/displaying one that's already been uploaded.
In Android, I simply used Glide to display the images, how do I do so in Flutter? Do I use the NetworkImage class and if so, how do I first get the url of the image stored in Storage?
To view the images inside your storage, what you need is the name of the file in the storage. Once you've the file name for the specific image you need.
In my case if i want the testimage to be loaded,
final ref = FirebaseStorage.instance.ref().child('testimage');
// no need of the file extension, the name will do fine.
var url = await ref.getDownloadURL();
print(url);
Once you've the url,
Image.network(url);
That's all :)
New alternative answer based on one of the comments.
I don't see anywhere google is charging extra money for downloadURL.
So if you're posting some comments please attach a link to it.
Once you upload a file to storage, make that filename unique and save that name somewhere in firestore, or realtime database.
getAvatarUrlForProfile(String imageFileName) async {
final FirebaseStorage firebaseStorage = FirebaseStorage(
app: Firestore.instance.app,
storageBucket: 'gs://your-firebase-app-url.com');
Uint8List imageBytes;
await firebaseStorage
.ref()
.child(imageFileName)
.getData(100000000)
.then((value) => {imageBytes = value})
.catchError((error) => {});
return imageBytes;
}
Uint8List avatarBytes =
await FirebaseServices().getAvatarUrlForProfile(userId);
and use it like,
MemoryImage(avatarBytes)
update
In newer versions use
await ref.getDownloadURL();
See How to get full downloadUrl from UploadTaskSnapshot in Flutter?
original
someMethod() async {
var data = await FirebaseStorage.instance.ref().child("foo$rand.txt").getData();
var text = new String.fromCharCodes(data);
print(data);
}
see Download an image from Firebase to Flutter
or
final uploadTask = imageStore.putFile(imageFile);
final url = (await uploadTask.future).downloadUrl;
In the later case you'd need to store the downloadUrl somewhere and then use NetworkImage or similar to get it rendered.
Here's an example of a stateful widget that loads an image from Firebase Storage object and builds an Image object:
class _MyHomePageState extends State<MyHomePage> {
final FirebaseStorage storage = FirebaseStorage(
app: Firestore.instance.app,
storageBucket: 'gs://my-project.appspot.com');
Uint8List imageBytes;
String errorMsg;
_MyHomePageState() {
storage.ref().child('selfies/me2.jpg').getData(10000000).then((data) =>
setState(() {
imageBytes = data;
})
).catchError((e) =>
setState(() {
errorMsg = e.error;
})
);
}
#override
Widget build(BuildContext context) {
var img = imageBytes != null ? Image.memory(
imageBytes,
fit: BoxFit.cover,
) : Text(errorMsg != null ? errorMsg : "Loading...");
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new ListView(
children: <Widget>[
img,
],
));
}
}
Note that FirebaseApp initialization is handled by the Firestore class, so no further initialization code is necessary.
The way I did it to respect the Storage rules and keep the image in cache is downloading the image as a File and store in the device. Next time I want to display the image I just check if the file already exists or not.
This is my widget:
import 'dart:io';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class FirebaseImage extends StatefulWidget {
final String storagePath;
FirebaseImage({
required this.storagePath,
}) : super(key: Key(storagePath));
#override
State<FirebaseImage> createState() => _FirebaseImageState();
}
class _FirebaseImageState extends State<FirebaseImage> {
File? _file;
#override
void initState() {
init();
super.initState();
}
Future<void> init() async {
final imageFile = await getImageFile();
if (mounted) {
setState(() {
_file = imageFile;
});
}
}
Future<File?> getImageFile() async {
final storagePath = widget.storagePath;
final tempDir = await getTemporaryDirectory();
final fileName = widget.storagePath.split('/').last;
final file = File('${tempDir.path}/$fileName');
// If the file do not exists try to download
if (!file.existsSync()) {
try {
file.create(recursive: true);
await FirebaseStorage.instance.ref(storagePath).writeToFile(file);
} catch (e) {
// If there is an error delete the created file
await file.delete(recursive: true);
return null;
}
}
return file;
}
#override
Widget build(BuildContext context) {
if (_file == null) {
return const Icon(Icons.error);
}
return Image.file(
_file!,
width: 100,
height: 100,
);
}
}
Note: The code can be improved to show a loading widget, error widget, etc.

Flutter save a network image to local directory

In Flutter how to save an image from network to the local directory.
I am new to encoding and decoding images. Can anyone point me in the right direction?
If all you want is to save an image (example: a .png) to the device, you can easily achieve this with a simple get (http/http.dart) and a File (dart:io).
To do so, you can base yourself in the example below:
var response = await http.get(imgUrl);
Directory documentDirectory = await getApplicationDocumentsDirectory();
File file = new File(join(documentDirectory.path, 'imagetest.png'));
file.writeAsBytesSync(response.bodyBytes); // This is a sync operation on a real
// app you'd probably prefer to use writeAsByte and handle its Future
Note that in the case above I’ve used the ‘path_provider’ package from the dart pub. In overall you would have imported at least these items:
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
There's a simple answer and a more complicated answer to this. I'll describe the complicated one and then give you the simple one.
The complicated answer is that you could manually cache images by using a NetworkImage, resolving it, and getting the image stream. Once you have the image stream, you could save it to the filesystem using flutter's file reading & writing capabilities (this is a good resource to learn more about that) - you also need to use a plugin called PathProvider which gets the right path for both iOS and Android which is described in that link. You'd also want to keep track of all the images you'd downloaded and probably delete them after certain amount of time. You'd also have to read the files back before using them to create Image widgets.
That gives you lots of control, but is a bit of work (although not a crazy amount, but if you're new to flutter maybe not something you want to do just now depending on why you want to save the images).
The simple answer is Packages to the rescue! Someone else has already come across this problem and written a plugin that solves it for you, so you don't have to think about it!
See cached_network_image package for information about the plugin.
You need to add to your dependencies in pubspec.yaml
dependencies:
cached_network_image: "^0.3.0"
Import it:
import 'package:cached_network_image/cached_network_image.dart';
And use it!
new CachedNetworkImage(
imageUrl: "http://imageurl.png",
placeholder: new CircularProgressIndicator(),
errorWidget: new Icon(Icons.error),
),
Note that this downloads & shows the image - if you want to do those seperately you can use a new CachedNetworkImageProvider(url) and show it using new Image.
After wandering around the codes and flutter docs. I have found the methods and classes which would work for both iOS and Android and here it goes.
Helper Classs
import 'dart:async';
import 'dart:io' as Io;
import 'package:image/image.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:path_provider/path_provider.dart';
class SaveFile {
Future<String> get _localPath async {
final directory = await getApplicationDocumentsDirectory();
return directory.path;
}
Future<Io.File> getImageFromNetwork(String url) async {
var cacheManager = await CacheManager.getInstance();
Io.File file = await cacheManager.getFile(url);
return file;
}
Future<Io.File> saveImage(String url) async {
final file = await getImageFromNetwork(url);
//retrieve local path for device
var path = await _localPath;
Image image = decodeImage(file.readAsBytesSync());
Image thumbnail = copyResize(image, 120);
// Save the thumbnail as a PNG.
return new Io.File('$path/${DateTime.now().toUtc().toIso8601String()}.png')
..writeAsBytesSync(encodePng(thumbnail));
}
}
Usage of class
class HomePageState extends State<HomePage>{
Future<Null> _launched ;
Widget _showResult(BuildContext context, AsyncSnapshot<Null> snapshot){
if(!snapshot.hasError){
return Text('Image is saved');
}
else{
return const Text('Unable to save image');
}
}
Future<Null> _saveNetworkImage(String url) async{
try{
await SaveFile().saveImage(url);
}
on Error catch(e){
throw 'Error has occured while saving';
}
}
#override
Widget Build(BuildContext context){
return new Scaffold(
key: _scaffoldKey,
appBar: new AppBar(
title: new Text('Image'),
),
body: Column(
children: <Widget>[
IconButton(icon: Icon(Icons.save), onPressed: (){
setState(() {
_launched =_saveNetworkImage(url);
});
}),
new FutureBuilder<Null>(future: _launched ,builder: _showResult),
],
),
);
}
}
To save the network image in local system you need to use ImagePickerSave dart plugin. Add the dart plugin in pub.yaml file: image_picker_saver: ^0.1.0 and call below code to save the image. URL is the image URL of network image
void _onImageSaveButtonPressed(String url) async {
print("_onImageSaveButtonPressed");
var response = await http
.get(url);
debugPrint(response.statusCode.toString());
var filePath = await ImagePickerSaver.saveFile(
fileData: response.bodyBytes);
var savedFile= File.fromUri(Uri.file(filePath));
}
Follow this url flutter save network image.
Code Snippet
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:http/http.dart' as http;
class NetworkToLocalImage extends StatefulWidget{
String url;
NetworkToLocalImage(this.url);
#override
_LoadImages createState() => new _LoadImages(url);
}
class _LoadImages extends State<NetworkToLocalImage>{
String url;
String filename;
var dataBytes;
_LoadImages(this.url){
filename = Uri.parse(url).pathSegments.last;
downloadImage().then((bytes){
setState(() {
dataBytes = bytes;
});
});
}
Future<dynamic> downloadImage() async {
String dir = (await getApplicationDocumentsDirectory()).path;
File file = new File('$dir/$filename');
if (file.existsSync()) {
print('file already exist');
var image = await file.readAsBytes();
return image;
} else {
print('file not found downloading from server');
var request = await http.get(url,);
var bytes = await request.bodyBytes;//close();
await file.writeAsBytes(bytes);
print(file.path);
return bytes;
}
}
#override
Widget build(BuildContext context) {
// TODO: implement build
if(dataBytes!=null)
return new Image.memory(dataBytes);
else return new CircularProgressIndicator();
}
}
I had a lot of trouble doing this, so I wanted to expound a little on the above answers with a simple example that downloads a file on startup, saves it to a local directory. I marked a couple of lines with the comment //%%% to show lines that can be commented out the second time that the app runs. (because it doesn't need to be downloaded to be displayed... it's already on the device itself:
import 'package:flutter/material.dart';
import 'package:http/http.dart' show get;
import 'dart:io';
import 'package:path_provider/path_provider.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Test Image',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Test Image'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
initState() {
_downloadAndSavePhoto();
super.initState();
}
_downloadAndSavePhoto() async {
// Get file from internet
var url = "https://www.tottus.cl/static/img/productos/20104355_2.jpg"; //%%%
var response = await get(url); //%%%
// documentDirectory is the unique device path to the area you'll be saving in
var documentDirectory = await getApplicationDocumentsDirectory();
var firstPath = documentDirectory.path + "/images"; //%%%
//You'll have to manually create subdirectories
await Directory(firstPath).create(recursive: true); //%%%
// Name the file, create the file, and save in byte form.
var filePathAndName = documentDirectory.path + '/images/pic.jpg';
File file2 = new File(filePathAndName); //%%%
file2.writeAsBytesSync(response.bodyBytes); //%%%
setState(() {
// When the data is available, display it
imageData = filePathAndName;
dataLoaded = true;
});
}
String imageData;
bool dataLoaded = false;
#override
Widget build(BuildContext context) {
if (dataLoaded) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
// imageData holds the path AND the name of the picture.
Image.file(File(imageData), width: 600.0, height: 290.0)
],
),
),
);
} else {
return CircularProgressIndicator(
backgroundColor: Colors.cyan,
strokeWidth: 5,
);
}
}
}
And heres my pubspec.yaml file:
http: ^0.12.0+2
path_provider: 1.5.0
you can use this function to upload and get Url(download) Image from the camera using
firebase-storage
Future _upIm()async {
final StorageReference firebaseStorageRef =
FirebaseStorage.instance.ref().child(DateTime.now().toString());
final StorageUploadTask task =
firebaseStorageRef.putFile(_storedImage);
var downUrl=await (await task.onComplete).ref.getDownloadURL();
var url =downUrl.toString();
print(url);
setState(() {
uploadImage=url;
});
}
inspired by Paras,CacheManager.getInstance() is deprecated
final DefaultCacheManager defaultCacheManager = DefaultCacheManager();
final fileInfo = await defaultCacheManager.getFileFromCache(imageUrl);
ImageGallerySaver.saveImage(image);
below is saveImage function
// save raw data to gallery with permission
static Future<void> saveImage(Uint8List image) async {
try {
if (Platform.isIOS) {
await PermissionHandler().requestPermissions([PermissionGroup.photos]);
PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.photos);
if (permission == PermissionStatus.granted) {
} else {
throw 'denied';
}
} else if (Platform.isAndroid) {
await PermissionHandler().requestPermissions([PermissionGroup.storage]);
PermissionStatus permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.storage);
if (permission == PermissionStatus.granted) {
} else {
throw 'denied';
}
}
await ImageGallerySaver.saveImage(image);
} catch (e) {
throw e;
}
}
You can use like that
var response = await http.get(Uri.parse(imageUrl));
Directory documentDirectory = await getApplicationDocumentsDirectory();
File file = File(join(documentDirectory.path, 'imagetest.png'));
await GallerySaver.saveImage(file.path);
You can use image_downloader.
For ios, image is saved in Photo Library.
For Android, image is saved in Environment.DIRECTORY_DOWNLOADS or specified location. By calling inExternalFilesDir(), specification of permission becomes unnecessary.
By callback(), you can get progress status.
The following is the simplest example. It will be saved.
await ImageDownloader.downloadImage(url);

Resources