How to dynamically populate AudioSource with multiple audio data from firestore in just_audio? - just-audio

I have been trying to dynamically populate AudioSource.uri() with data from firestore.
I uploaded some songs into firestore database and I wanted to use the data for a just_audio playlist in my app. I have done everything possible, and I really am not sure why its not working.
I don't want to add the song urls and other data statically as shown in the plugin example.
Here are my attempts:
First I fetched the song data using a StreamBuilder and passed it as a DocumentSnapshot List to the JustAudioPlaylist() page;
List<DocumentSnapshot> _list;
_list = snapshot.data.docs;
Flexible(
child: ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => JustAudioPlaylist(
songs: [_list[index]],
),
));
},
child:
Container(child: Center(child: Text('My Playlists'))),
);
}),
)
Then, here's the JustAudioPlaylist page where I expected to retrieve and populate the AudioSource.uri().
class JustAudioPlaylist extends StatefulWidget {
final List songs;
JustAudioPlaylist({this.songs});
#override
_JustAudioPlaylistState createState() => _JustAudioPlaylistState();
}
class _JustAudioPlaylistState extends State<JustAudioPlaylist> {
AudioPlayer _player;
int _addedCount = 0;
var _playlist;
#override
void initState() {
_playlist
.addAll(widget.songs.map((song) => ConcatenatingAudioSource(children: [
AudioSource.uri(
Uri.parse(song['song']),
tag: AudioMetadata(
album: "Science Friday",
title: song['songTitle'],
artwork: song['songImage'],
),
),
])));
I am not sure why its not working, but it produces an error "addAll was called on null". Please can anyone help?

Your relevant code is:
_playlist.addAll(...);
The error means _playlist is null. That is, _playlist is an uninitialised variable and doesn't actually contain any playlist object. I can see you declare the variable so it starts off empty:
var _playlist;
But you never actually store anything into this variable, like _playlist = ...something.... So your _playlist variable starts off null and continues to remain null.
You could do this instead:
_playlist = ConcatenatingAudioSource(children: []);
// and then later...
_playlist.addAll(widget.songs.map(...etc...));
Although addAll is intended for dynamically modifying the playlist after it's already created. But in your case, you know which songs you want to play at initialisation time, so you may as well just initialise the playlist right at the beginning and you won't have to add to it later:
_playlist = ConcatenatingAudioSource(
children: widget.songs.map(...etc...)
);

Related

Flutter with SQFlite error on pushing to database

I convert the data from my class instance to a map then pass that map into a database which I think is set up correctly but am getting the following error:
Tried calling: insert(conflictAlgorithm: Instance of 'ConflictAlgorithm', data: _LinkedHashMap len:5, table: "sessionchunk")
Found: insert(String, Map<String, Object?>, {String? nullColumnHack, ConflictAlgorithm? conflictAlgorithm}) => Future<int>
heres my code:
globals.dart (bad file name, more of a database helper file)
library data_practice.globals;
import 'models/sessionModel.dart';
import "package:sqflite/sqflite.dart";
import "package:path/path.dart";
var database;
Future<Database> initDatabase() async {
return database = openDatabase(
// path to the database
join(await getDatabasesPath(), 'user_data.db'),
// database version
version: 1,
// on create
onCreate: (db, version) {
return db.execute(
"CREATE TABLE sessionchunk(id INTEGER PRIMARY KEY, worktime INTEGER, breaktime INTEGER, intention TEXT, progress TEXT)");
});
}
// create an instance of this to get access to the db
pushChunk(SessionChunk chunk) async {
//create instance of the mysql database, this might cause issues
//by having multiple instances??
var db = await database;
//takes table name,
await db.insert(
table: 'sessionchunk',
data: chunk.toMap(),
conflictAlgorithm: ConflictAlgorithm.replace);
}
sessionModel.dart (this is the main model that I'm trying to store into the sessionchunk table)
import "package:path/path.dart";
import "package:sqflite/sqflite.dart";
import "package:data_practice/main.dart";
import "package:data_practice/globals.dart" as globals;
class SessionChunk {
int id = 0;
int workTime = 40;
int breakTime = 10;
String intention = "finish backend";
String progress = "done";
SessionChunk({required id, worktime, breakTime, intention, progress});
// helper method: converts chunk data into map for sqflite db
Map<String, dynamic> toMap() {
return {
"id": this.id,
"worktime": this.workTime,
"breaktime": this.breakTime,
"intention": this.intention,
"progress": this.progress,
};
}
// helper method: prints current values of refrenced session instance
#override
String toString() {
return 'SessionChunk{id: $id, workTime: $workTime, breakTime: $breakTime, intention: $intention, progress: $progress}';
}
}
then my main.dart file:
import 'package:flutter/material.dart';
import "package:path/path.dart";
import "package:sqflite/sqflite.dart";
import "./models/sessionModel.dart";
import 'globals.dart' as globals;
//TODO: left off at step 5 in on https://docs.flutter.dev/cookbook/persistence/sqlite
void main() async {
WidgetsFlutterBinding.ensureInitialized();
Database db = await globals.initDatabase();
print(db);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'Push to database, press below',
),
TextButton(
onPressed: () {
var chunk = SessionChunk(
id: 1,
);
globals.pushChunk(chunk);
},
child: Text("push"),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}
Any help with how doing this would be greatly appreciated, pretty new to all this so please explain like Im 5! Happy Friday :)

MediaState stream ConnectionState stays on waiting - audio_service flutter

I'm trying to provide my audioHandler on my Player class, but something weird is happening
When I enter the screen, the StreamBuilder will go active just fine but if i pop and navigate to the screen again the stream connect will stay on 'waiting' forever, unless i play the audio. This causes some weird behaviors. What m i doing wrong?
relevant code
Player class
final audioHandlerProvider = Provider<AudioHandler>((ref) {
AudioHandler _audioHandler = ref.read(audioHandlerServiceProvider);
return _audioHandler;
});
class _PlayerClicVozzState extends State<PlayerClicVozz> {
#override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
backgroundColor: Color(0xff131313),
appBar: AppBar(
automaticallyImplyLeading: false,
actions: [
IconButton(
icon: Icon(Icons.clear, color: Colors.white),
onPressed: () => Navigator.of(context).pop(),
),
],
backgroundColor: Colors.transparent,
elevation: 0,
),
body: Center(
child: Consumer(builder: (context, watch, child) {
final res = watch(audioHandlerProvider);
return StreamBuilder<MediaState>(
stream: _mediaStateStream(res),
builder: (context, snapshot) {
final mediaState = snapshot.data;
return SeekBar(
duration: mediaState?.mediaItem?.duration ?? Duration.zero,
position: mediaState?.position ?? Duration.zero,
onChangeEnd: (newPosition) {
res.seek(newPosition);
},
);
},
);
...
audioservice init
late AudioHandler _audioHandler;
final audioHandlerServiceProvider = Provider<AudioHandler>((ref) {
return _audioHandler;
});
Future<void> main() async {
_audioHandler = await AudioService.init(
builder: () => AudioPlayerHandler(),
config: AudioServiceConfig(
androidNotificationChannelId: 'com.mycompany.myapp.channel.audio',
androidNotificationChannelName: 'Audio playback',
androidNotificationOngoing: true,
),
);
...
My audiohandler is exatcly the same as the plugin example
import 'package:audio_service/audio_service.dart';
import 'package:just_audio/just_audio.dart';
class AudioPlayerHandler extends BaseAudioHandler with SeekHandler {
static final _item = MediaItem(
id: 'https://s3.amazonaws.com/scifri-episodes/scifri20181123-episode.mp3',
album: "Science Friday",
title: "A Salute To Head-Scratching Science",
artist: "Science Friday and WNYC Studios",
duration: const Duration(milliseconds: 5739820),
artUri: Uri.parse(
'https://media.wnyc.org/i/1400/1400/l/80/1/ScienceFriday_WNYCStudios_1400.jpg'),
);
final _player = AudioPlayer();
/// Initialise our audio handler.
AudioPlayerHandler() {
// So that our clients (the Flutter UI and the system notification) know
// what state to display, here we set up our audio handler to broadcast all
// playback state changes as they happen via playbackState...
_player.playbackEventStream.map(_transformEvent).pipe(playbackState);
// ... and also the current media item via mediaItem.
mediaItem.add(_item);
// Load the player.
_player.setAudioSource(AudioSource.uri(Uri.parse(_item.id)));
}
// In this simple example, we handle only 4 actions: play, pause, seek and
// stop. Any button press from the Flutter UI, notification, lock screen or
// headset will be routed through to these 4 methods so that you can handle
// your audio playback logic in one place.
#override
Future<void> play() => _player.play();
#override
Future<void> pause() => _player.pause();
#override
Future<void> seek(Duration position) => _player.seek(position);
#override
Future<void> stop() => _player.stop();
/// Transform a just_audio event into an audio_service state.
///
/// This method is used from the constructor. Every event received from the
/// just_audio player will be transformed into an audio_service state so that
/// it can be broadcast to audio_service clients.
PlaybackState _transformEvent(PlaybackEvent event) {
return PlaybackState(
controls: [
MediaControl.rewind,
if (_player.playing) MediaControl.pause else MediaControl.play,
MediaControl.stop,
MediaControl.fastForward,
],
systemActions: const {
MediaAction.seek,
MediaAction.seekForward,
MediaAction.seekBackward,
},
androidCompactActionIndices: const [0, 1, 3],
processingState: const {
ProcessingState.idle: AudioProcessingState.idle,
ProcessingState.loading: AudioProcessingState.loading,
ProcessingState.buffering: AudioProcessingState.buffering,
ProcessingState.ready: AudioProcessingState.ready,
ProcessingState.completed: AudioProcessingState.completed,
}[_player.processingState]!,
playing: _player.playing,
updatePosition: _player.position,
bufferedPosition: _player.bufferedPosition,
speed: _player.speed,
queueIndex: event.currentIndex,
);
}
}
MediaStateStream and QueueStateStream
Stream<MediaState> _mediaStateStream(AudioHandler audioHandler) {
return Rx.combineLatest2<MediaItem?, Duration, MediaState>(
audioHandler.mediaItem,
AudioService.position,
(mediaItem, position) => MediaState(mediaItem, position));
}
_queueStateStream(AudioHandler audioHandler) {
return Rx.combineLatest2<List<MediaItem>?, MediaItem?, QueueState>(
audioHandler.queue,
audioHandler.mediaItem,
(queue, mediaItem) => QueueState(queue, mediaItem));
}
When you subscribe to a stream, you only start receiving new events that are emitted after the moment that you subscribe, and you may have a period of waiting for that next event.
In your implementation of _mediaStateStream you are making use of AudioService.position which only emits events when the position is changing (i.e. not paused or stalled). So even though the stream may have emitted position events in the past, if you subscribe to that stream again while paused or stalled, you will be in a waiting state until the next position event arrives which is after playback resumes again.
I would suggest wrapping your stream in rxdart's BeehaviorSubject so that it retains a memory of the last event and re-emits the last event to new listeners. Also, you could seed this BehaviorSubject with the very first value to ensure there is no waiting period even for the first listener:
_mediaStateSubject = BehaviorSubject.seeded(MediaState(
handler.mediaItem.valueOrNull,
handler.playbackState.position))
..addStream(_mediaStateStream(handler));
Then you can listen to _mediaStateSubject instead of _mediaStateStream.

How to dynamically populate MediaItem from firestore in audio_service?

I have been trying to dynamically populate MediaItem with some audio data from firestore.
I am using the exact plugin example, but this time mediaItems is being sourced dynamically from firestore. I have reviewed my code multiple times, but I can't figure out what I am doing wrong.
Here are my attempts:
First I fetched the song data using a StreamBuilder and passed it as a DocumentSnapshot List to the AudioServicePlayer() page.
List<DocumentSnapshot> _list;
_list = snapshot.data.docs;
Flexible(
child: ListView.builder(
itemCount: 1,
itemBuilder: (BuildContext context, int index) {
return InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => JustAudioPlaylist(
songs: [_list[index]],
),
));
},
child:
Container(child: Center(child: Text('My Playlists'))),
);
}),
)
I successfully received the QueryDocumentSnapshot as expected. But when I tried to populate MediaItem with widget.songs List, it returns just a blank white page with no error. I can't figure out what I am doing wrong here;
class AudioServicePlayer extends StatefulWidget {
static const String id = 'audio-service';
List<DocumentSnapshot> songs = [];
AudioServicePlayer({this.songs});
#override
_AudioServicePlayerState createState() => _AudioServicePlayerState();
}
class _AudioServicePlayerState extends State<AudioServicePlayer> {
MediaLibrary _mediaLibrary = MediaLibrary();
#override
void initState() {
_mediaLibrary._items.addAll(widget.songs
.map((song) => MediaItem(
// This can be any unique id, but we use the audio URL for convenience.
id: song['song'],
album: "Science Friday",
title: song['songTitle'],
artist: song['artist']['artistName'],
duration: Duration(milliseconds: 5739820),
artUri: Uri.parse(song['songImage']).toString(),
))
.toList());
super.initState();
}

How to put json-data into a list in flutter?

When I try to get data from this API https://api.met.no/weatherapi/locationforecast/2.0/complete?lat=10&lon=10 it gets me a long array of some sort with all the timeseries. In the end, I would like to display some data from each time which has its own place in the downloaded array. I want to covert all data to a list so I can manipulate the data but i get errors like these type '_InternalLinkedHashMap<String, dynamic>' is not a subtype of type 'String.
This is my code
List<dynamic> timeseriesglobal = [];
void loadForecast() async{
//Getting the data from API
Response response = await get("https://api.met.no/weatherapi/locationforecast/2.0/complete?lat=57.047218&lon=9.920100");
var results = jsonDecode(response.body);
timeseriesglobal = results["properties"]["timeseries"] as List;
}
And in the end i have this code for displaying the data
child: ListView.builder(
itemCount: timeseriesglobal.length,
itemBuilder: (context,index){
return Card(
child: ListTile(
title: Text(
timeseriesglobal[index]
),
),
);
},
What am I doing wrong? Please help me
Provide the property name that you want to show Ex time
ListView.builder(
itemCount: timeseriesglobal.length,
itemBuilder: (context,index){
return Card(
child: ListTile(
title: Text(
timeseriesglobal[index]['time']
),
),
);
},
Create Your BaseModel from the json you are getting from the link.
Then Parse like below
var data= BaseModel.fromJson(response.body);
Now this will contain everything and you can extract whatever u want from the model
To convert the json use this link

Use textfield input to get a value output

I have made a simple textfield with a number keyboard.
The user is to put in a number (to make it simple I have set the number to sum=5).
If input is = sum, the text 'correct answer' will be printed on the screen. If the user input !=sum, the returned text will be 'wrong answer'.
To test if I actually got the numbers right I have a testprint, which functions correct.
My problem is how to transform this print output to text (so that it will show as text in the app).
I have been thinking about validating and form, but since I actually already get the correct answer printed, it shows I already get the values correct. Right?
I have tried a ton of things, but nothing has worked so far, so any help is appreciated. Thank you.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Textfields2',
home: MyHomeScreen(),
);
}
}
class MyHomeScreen extends StatefulWidget {
#override
_MyHomeScreenState createState() => _MyHomeScreenState();
}
class _MyHomeScreenState extends State<MyHomeScreen> {
final int sum = 5;
String output;
String enterAnswer;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
decoration: InputDecoration(hintText: '?'),
onChanged: (val) {
enterAnswer = val;
},
),
RaisedButton(
child: Text('Submit Answer'),
onPressed: () {
if (enterAnswer.isNotEmpty) {
if (enterAnswer == sum.toString()) {
print('Correct'); //INTO TEXT
} else {
print('Wrong Answer');//INTO TEXT
}
}
})
],
),
),
);
}
}
Below the RaisedButton add a Text Widget. It should look something like this:
Text(output)
, then replace the print() statement with a setState() function updating the output:
setState(() {output = 'Correct';});
To not get an error in the first place you have to initialize the output variable, so instead of only writing
String output;
write
String output = "";