From fbb8aa17dbb814fe1d9798202fc796c6b64f2c4f Mon Sep 17 00:00:00 2001 From: Fitz Date: Wed, 6 May 2020 10:19:44 -0700 Subject: [PATCH] adding missing files. Thanks for the notice, @zaprogrammer! --- lib/notifiers.dart | 90 ++++++++++++++++++++++++++ lib/player.dart | 153 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 lib/notifiers.dart create mode 100644 lib/player.dart diff --git a/lib/notifiers.dart b/lib/notifiers.dart new file mode 100644 index 0000000..a3e48cf --- /dev/null +++ b/lib/notifiers.dart @@ -0,0 +1,90 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:flutter/material.dart'; +import 'package:webfeed/domain/rss_feed.dart'; +import 'package:webfeed/domain/rss_item.dart'; + +import 'package:path_provider/path_provider.dart'; +import 'package:path/path.dart' as path; +import 'package:webfeed/webfeed.dart'; + +class Podcast with ChangeNotifier { + EpisodeFeed _feed; + Episode _selectedItem; + + EpisodeFeed get feed => _feed; + void parse(String url) async { + final res = await http.get(url); + final xmlStr = res.body; + _feed = EpisodeFeed.parse(xmlStr); + notifyListeners(); + } + + Episode get selectedItem => _selectedItem; + set selectedItem(Episode value) { + _selectedItem = value; + notifyListeners(); + } +} + +class EpisodeFeed extends RssFeed { + final RssFeed _feed; + List items; + + EpisodeFeed(this._feed) { + items = _feed.items.map((i) => Episode(i)).toList(); + } + + RssImage get image => _feed.image; + + static EpisodeFeed parse(xmlStr) { + return EpisodeFeed(RssFeed.parse(xmlStr)); + } +} + +class Episode extends RssItem with ChangeNotifier { + String downloadLocation; + RssItem _item; + + Episode(this._item); + + String get title => _item.title; + String get description => _item.description; + String get guid => _item.guid; + + void download([Function(double) updates]) async { + final req = http.Request('GET', Uri.parse(_item.guid)); + final res = await req.send(); + if (res.statusCode != 200) + throw Exception('Unexpected HTTP code: ${res.statusCode}'); + + final contentLength = res.contentLength; + var downloadedLength = 0; + + String filePath = await _getDownloadPath(path.split(_item.guid).last); + final file = File(filePath); + res.stream + .map((chunk) { + downloadedLength += chunk.length; + if (updates != null) updates(downloadedLength / contentLength); + return chunk; + }) + .pipe(file.openWrite()) + .whenComplete(() { + // TODO save this to sharedprefs or similar. + downloadLocation = filePath; + notifyListeners(); + }) + .catchError((e) => print('An Error has occurred!!!: $e')); + } + + Future _getDownloadPath(String filename) async { + final dir = await getApplicationDocumentsDirectory(); + final prefix = dir.path; + final absolutePath = path.join(prefix, filename); + print(absolutePath); + return absolutePath; + } +} diff --git a/lib/player.dart b/lib/player.dart new file mode 100644 index 0000000..7e7d48c --- /dev/null +++ b/lib/player.dart @@ -0,0 +1,153 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_sound/flutter_sound.dart'; +import 'package:provider/provider.dart'; + +import 'package:dashcast/notifiers.dart'; + +class PlayerPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + Provider.of(context).selectedItem.title, + ), + ), + body: SafeArea(child: Player()), + ); + } +} + +class Player extends StatelessWidget { + @override + Widget build(BuildContext context) { + final podcast = Provider.of(context); + return Column( + children: [ + Flexible( + flex: 8, + child: SingleChildScrollView( + child: Column(children: [ + Image.network(podcast.feed.image.url), + Padding( + padding: const EdgeInsets.all(10), + child: Text( + podcast.selectedItem.description.trim(), + ), + ), + ]), + )), + Flexible( + flex: 2, + child: Material( + elevation: 12, + child: AudioControls(), + ), + ), + ], + ); + } +} + +class AudioControls extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Column( + children: [ + PlaybackButtons(), + ], + ); + } +} + +class PlaybackButtons extends StatefulWidget { + @override + _PlaybackButtonState createState() => _PlaybackButtonState(); +} + +class _PlaybackButtonState extends State { + bool _isPlaying = false; + FlutterSound _sound; + double _playPosition; + StreamSubscription _playerSubscription; + + @override + void initState() { + super.initState(); + _sound = FlutterSound(); + _playPosition = 0; + } + + @override + void dispose() { + _cleanup(); + super.dispose(); + } + + void _cleanup() async { + if (_sound.audioState == t_AUDIO_STATE.IS_PLAYING) + await _sound.stopPlayer(); + // TODO somehow this gets called from episode list and breaks everything. + _playerSubscription.cancel(); + } + + void _stop() async { + await _sound.stopPlayer(); + setState(() => _isPlaying = false); + } + + void _play(String url) async { + await _sound.startPlayer(url); + _playerSubscription = _sound.onPlayerStateChanged.listen((e) { + if (e != null) { + // print(e.currentPosition); + setState(() => _playPosition = (e.currentPosition / e.duration)); + } + }); + setState(() => _isPlaying = true); + } + + void _fastForward() {} + + void _rewind() {} + + @override + Widget build(BuildContext context) { + final podcast = Provider.of(context); + final item = podcast.selectedItem; + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Slider( + value: _playPosition, + onChanged: null, + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton(icon: Icon(Icons.fast_rewind), onPressed: null), + IconButton( + icon: _isPlaying ? Icon(Icons.stop) : Icon(Icons.play_arrow), + onPressed: () { + if (_isPlaying) { + _stop(); + } else { + var url = item.downloadLocation ?? item.guid; + print('Playing url: $url'); + _play(url); + } + }, + ), + IconButton( + icon: Icon(Icons.fast_forward), + onPressed: null, + ), + ], + ), + ], + ); + } +}