diff --git a/lib/api/player.dart b/lib/api/player.dart index 3f4af45..e98707a 100644 --- a/lib/api/player.dart +++ b/lib/api/player.dart @@ -57,15 +57,15 @@ class PlayerHelper { } }); //Start audio_service - _startService(); + startService(); } - Future _startService() async { + Future startService() async { if (AudioService.running) return; await AudioService.start( backgroundTaskEntrypoint: backgroundTaskEntrypoint, androidEnableQueue: true, - androidStopForegroundOnPause: true, + androidStopForegroundOnPause: false, androidNotificationOngoing: false, androidNotificationClickStartsActivity: true, androidNotificationChannelDescription: 'Freezer', @@ -74,6 +74,7 @@ class PlayerHelper { ); } + //Repeat toggle Future changeRepeat() async { //Change to next repeat type @@ -97,7 +98,7 @@ class PlayerHelper { //Replace queue, play specified track id Future _loadQueuePlay(List queue, String trackId) async { - await _startService(); + await startService(); await settings.updateAudioServiceQuality(); await AudioService.updateQueue(queue); await AudioService.playFromMediaId(trackId); @@ -128,7 +129,7 @@ class PlayerHelper { } //Load tracks as queue, play track id, set queue source Future playFromTrackList(List tracks, String trackId, QueueSource queueSource) async { - await _startService(); + await startService(); List queue = tracks.map((track) => track.toMediaItem()).toList(); await setQueueSource(queueSource); @@ -164,7 +165,7 @@ class PlayerHelper { } Future setQueueSource(QueueSource queueSource) async { - await _startService(); + await startService(); this.queueSource = queueSource; await AudioService.customAction('queueSource', queueSource.toJson()); @@ -276,10 +277,10 @@ class AudioPlayerTask extends BackgroundAudioTask { } @override - Future onSkipToNext() { + Future onSkipToNext() async { //If repeating allowed if (repeatType == 2) { - _skip(0); + await _skip(0); return null; } _skip(1); @@ -405,6 +406,12 @@ class AudioPlayerTask extends BackgroundAudioTask { onSeekTo(_audioPlayer.playbackEvent.position + offset); } + @override + Future onUpdateMediaItem(MediaItem mediaItem) async { + _queue[_queueIndex] = mediaItem; + AudioServiceBackground.setMediaItem(mediaItem); + } + //Audio interruptions @override void onAudioFocusLost(AudioInterruption interruption) { @@ -492,7 +499,7 @@ class AudioPlayerTask extends BackgroundAudioTask { return url; } - Future _getTrackUri(MediaItem mi) async { + Future _getTrackUri(MediaItem mi, {int quality}) async { String prefix = 'DEEZER|${mi.id}|'; //Check if song is available offline @@ -505,13 +512,28 @@ class AudioPlayerTask extends BackgroundAudioTask { id: mi.id, playbackDetails: jsonDecode(mi.extras['playbackDetails']) //JSON Because of audio_service bug ); - ConnectivityResult conn = await Connectivity().checkConnectivity(); - if (conn == ConnectivityResult.wifi) { - return prefix + t.getUrl(wifiQuality); + + //Check connection + if (quality == null) { + ConnectivityResult conn = await Connectivity().checkConnectivity(); + quality = mobileQuality; + if (conn == ConnectivityResult.wifi) quality = wifiQuality; + } + String url = t.getUrl(quality); + + //Quality fallback + Dio dio = Dio(); + try { + await dio.head(url); + return prefix + url; + } catch (e) { + if (quality == 9) return _getTrackUri(mi, quality: 3); + if (quality == 3) return _getTrackUri(mi, quality: 1); + throw Exception('No available quality!'); } - return prefix + t.getUrl(mobileQuality); } + Future _getQualityString(String uri, Duration duration) async { //Get url/path String url = uri; diff --git a/lib/ui/player_screen.dart b/lib/ui/player_screen.dart index 3988761..59f095e 100644 --- a/lib/ui/player_screen.dart +++ b/lib/ui/player_screen.dart @@ -7,6 +7,7 @@ import 'package:freezer/api/deezer.dart'; import 'package:freezer/api/player.dart'; import 'package:freezer/ui/menu.dart'; import 'package:freezer/ui/tiles.dart'; +import 'package:async/async.dart'; import 'cached_image.dart'; import '../api/definitions.dart'; @@ -14,6 +15,7 @@ import 'player_bar.dart'; + class PlayerScreen extends StatefulWidget { @override _PlayerScreenState createState() => _PlayerScreenState(); @@ -29,25 +31,151 @@ class _PlayerScreenState extends State { return Scaffold( body: SafeArea( child: StreamBuilder( - stream: AudioService.playbackStateStream, + stream: StreamZip([AudioService.playbackStateStream, AudioService.currentMediaItemStream]), builder: (BuildContext context, AsyncSnapshot snapshot) { //Disable lyrics when skipping songs, loading - PlaybackState s = snapshot.data; - if (s != null && s.processingState != AudioProcessingState.ready && s.processingState != AudioProcessingState.buffering) _lyrics = false; + if (snapshot.data is PlaybackState && + snapshot.data.processingState != AudioProcessingState.ready && + snapshot.data.processingState != AudioProcessingState.buffering) _lyrics = false; - return OrientationBuilder( - builder: (context, orientation) { - //Landscape - if (orientation == Orientation.landscape) { - return Row( + //When disconnected + if (AudioService.currentMediaItem == null) { + playerHelper.startService(); + return Center(child: CircularProgressIndicator(),); + } + + return OrientationBuilder( + builder: (context, orientation) { + //Landscape + if (orientation == Orientation.landscape) { + return Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(16, 0, 16, 8), + child: Container( + width: 320, + child: Stack( + children: [ + CachedImage( + url: AudioService.currentMediaItem.artUri, + ), + if (_lyrics) LyricsWidget( + artUri: AudioService.currentMediaItem.artUri, + trackId: AudioService.currentMediaItem.id, + lyrics: Track.fromMediaItem(AudioService.currentMediaItem).lyrics, + height: 320.0, + ), + ], + ), + ) + ), + SizedBox( + width: MediaQuery.of(context).size.width / 2 - 32, + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: EdgeInsets.fromLTRB(8, 16, 8, 0), + child: Container( + width: 300, + child: PlayerScreenTopRow(), + ) + ), + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AudioService.currentMediaItem.displayTitle, + maxLines: 1, + textAlign: TextAlign.center, + overflow: TextOverflow.clip, + style: TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold + ), + ), + Container(height: 4,), + Text( + AudioService.currentMediaItem.displaySubtitle, + maxLines: 1, + textAlign: TextAlign.center, + overflow: TextOverflow.clip, + style: TextStyle( + fontSize: 18.0, + color: Theme.of(context).primaryColor, + ), + ), + ], + ), + Container( + width: 320, + child: SeekBar(), + ), + Container( + width: 320, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.max, + children: [ + PrevNextButton(iconSize, prev: true,), + PlayPauseButton(iconSize), + PrevNextButton(iconSize) + ], + ), + ), + Padding( + padding: EdgeInsets.fromLTRB(8, 0, 8, 16), + child: Container( + width: 300, + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: Icon(Icons.subtitles), + onPressed: () { + setState(() => _lyrics = !_lyrics); + }, + ), + Text( + AudioService.currentMediaItem.extras['qualityString'] + ), + IconButton( + icon: Icon(Icons.more_vert), + onPressed: () { + Track t = Track.fromMediaItem(AudioService.currentMediaItem); + MenuSheet m = MenuSheet(context); + m.defaultTrackMenu(t); + }, + ) + ], + ), + ) + ) + ], + ), + ) + ], + ); + } + + //Portrait + return Column( mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Padding( - padding: EdgeInsets.fromLTRB(16, 0, 16, 8), + padding: EdgeInsets.fromLTRB(28, 16, 28, 0), + child: PlayerScreenTopRow() + ), + Padding( + padding: EdgeInsets.fromLTRB(16, 8, 16, 8), child: Container( - width: 320, + height: 360, child: Stack( children: [ CachedImage( @@ -57,199 +185,80 @@ class _PlayerScreenState extends State { artUri: AudioService.currentMediaItem.artUri, trackId: AudioService.currentMediaItem.id, lyrics: Track.fromMediaItem(AudioService.currentMediaItem).lyrics, - height: 320.0, + height: 360.0, ), ], ), ) ), - SizedBox( - width: MediaQuery.of(context).size.width / 2 - 32, - child: Column( + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + AudioService.currentMediaItem.displayTitle, + maxLines: 1, + textAlign: TextAlign.center, + overflow: TextOverflow.clip, + style: TextStyle( + fontSize: 24.0, + fontWeight: FontWeight.bold + ), + ), + Container(height: 4,), + Text( + AudioService.currentMediaItem.displaySubtitle, + maxLines: 1, + textAlign: TextAlign.center, + overflow: TextOverflow.clip, + style: TextStyle( + fontSize: 18.0, + color: Theme.of(context).primaryColor, + ), + ), + ], + ), + SeekBar(), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + mainAxisSize: MainAxisSize.max, + children: [ + PrevNextButton(iconSize, prev: true,), + PlayPauseButton(iconSize), + PrevNextButton(iconSize) + ], + ), + //Container(height: 8.0,), + Padding( + padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), + child: Row( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Padding( - padding: EdgeInsets.fromLTRB(8, 16, 8, 0), - child: Container( - width: 300, - child: PlayerScreenTopRow(), - ) + IconButton( + icon: Icon(Icons.subtitles), + onPressed: () { + setState(() => _lyrics = !_lyrics); + }, ), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AudioService.currentMediaItem.displayTitle, - maxLines: 1, - textAlign: TextAlign.center, - overflow: TextOverflow.clip, - style: TextStyle( - fontSize: 24.0, - fontWeight: FontWeight.bold - ), - ), - Container(height: 4,), - Text( - AudioService.currentMediaItem.displaySubtitle, - maxLines: 1, - textAlign: TextAlign.center, - overflow: TextOverflow.clip, - style: TextStyle( - fontSize: 18.0, - color: Theme.of(context).primaryColor, - ), - ), - ], + Text( + AudioService.currentMediaItem.extras['qualityString'] ), - Container( - width: 320, - child: SeekBar(), - ), - Container( - width: 320, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - mainAxisSize: MainAxisSize.max, - children: [ - PrevNextButton(iconSize, prev: true,), - PlayPauseButton(iconSize), - PrevNextButton(iconSize) - ], - ), - ), - Padding( - padding: EdgeInsets.fromLTRB(8, 0, 8, 16), - child: Container( - width: 300, - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: Icon(Icons.subtitles), - onPressed: () { - setState(() => _lyrics = !_lyrics); - }, - ), - Text( - AudioService.currentMediaItem.extras['qualityString'] - ), - IconButton( - icon: Icon(Icons.more_vert), - onPressed: () { - Track t = Track.fromMediaItem(AudioService.currentMediaItem); - MenuSheet m = MenuSheet(context); - m.defaultTrackMenu(t); - }, - ) - ], - ), - ) + IconButton( + icon: Icon(Icons.more_vert), + onPressed: () { + Track t = Track.fromMediaItem(AudioService.currentMediaItem); + MenuSheet m = MenuSheet(context); + m.defaultTrackMenu(t); + }, ) ], ), ) ], ); - } - //Portrait - return Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: EdgeInsets.fromLTRB(28, 16, 28, 0), - child: PlayerScreenTopRow() - ), - Padding( - padding: EdgeInsets.fromLTRB(16, 8, 16, 8), - child: Container( - height: 360, - child: Stack( - children: [ - CachedImage( - url: AudioService.currentMediaItem.artUri, - ), - if (_lyrics) LyricsWidget( - artUri: AudioService.currentMediaItem.artUri, - trackId: AudioService.currentMediaItem.id, - lyrics: Track.fromMediaItem(AudioService.currentMediaItem).lyrics, - height: 360.0, - ), - ], - ), - ) - ), - Column( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - AudioService.currentMediaItem.displayTitle, - maxLines: 1, - textAlign: TextAlign.center, - overflow: TextOverflow.clip, - style: TextStyle( - fontSize: 24.0, - fontWeight: FontWeight.bold - ), - ), - Container(height: 4,), - Text( - AudioService.currentMediaItem.displaySubtitle, - maxLines: 1, - textAlign: TextAlign.center, - overflow: TextOverflow.clip, - style: TextStyle( - fontSize: 18.0, - color: Theme.of(context).primaryColor, - ), - ), - ], - ), - SeekBar(), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - mainAxisSize: MainAxisSize.max, - children: [ - PrevNextButton(iconSize, prev: true,), - PlayPauseButton(iconSize), - PrevNextButton(iconSize) - ], - ), - //Container(height: 8.0,), - Padding( - padding: EdgeInsets.symmetric(vertical: 4.0, horizontal: 16.0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: Icon(Icons.subtitles), - onPressed: () { - setState(() => _lyrics = !_lyrics); - }, - ), - Text( - AudioService.currentMediaItem.extras['qualityString'] - ), - IconButton( - icon: Icon(Icons.more_vert), - onPressed: () { - Track t = Track.fromMediaItem(AudioService.currentMediaItem); - MenuSheet m = MenuSheet(context); - m.defaultTrackMenu(t); - }, - ) - ], - ), - ) - ], - ); - - }, - ); + }, + ); }, ), )