mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-12 23:45:18 +00:00
Stop player on close support
PlaylistCard image placeholder Player album placeholder PlayerDI provider for global avilability of MPVPlayer
This commit is contained in:
parent
f367d8f5c3
commit
6ea222c5b0
13
.vscode/launch.json
vendored
Normal file
13
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Flutter",
|
||||
"type": "dart",
|
||||
"request": "launch",
|
||||
"program": "lib/main.dart"
|
||||
}
|
||||
|
||||
],
|
||||
"compounds": []
|
||||
}
|
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1,3 +1 @@
|
||||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
||||
{}
|
10
.vscode/tasks.json
vendored
10
.vscode/tasks.json
vendored
@ -1,12 +1,4 @@
|
||||
{
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"problemMatcher": [],
|
||||
"label": "npm: start",
|
||||
"detail": "qode ./dist/index.js"
|
||||
}
|
||||
]
|
||||
"tasks": []
|
||||
}
|
@ -7,6 +7,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotify/spotify.dart' hide Image;
|
||||
import 'package:spotube/components/CategoryCard.dart';
|
||||
import 'package:spotube/components/Login.dart';
|
||||
import 'package:spotube/components/PageWindowTitleBar.dart';
|
||||
import 'package:spotube/components/Player.dart' as player;
|
||||
import 'package:spotube/components/Settings.dart';
|
||||
import 'package:spotube/components/UserLibrary.dart';
|
||||
@ -135,12 +136,10 @@ class _HomeState extends State<Home> {
|
||||
Theme.of(context).navigationRailTheme.backgroundColor,
|
||||
child: MoveWindow(),
|
||||
),
|
||||
Expanded(child: MoveWindow())
|
||||
Expanded(child: MoveWindow()),
|
||||
const TitleBarActionButtons(),
|
||||
],
|
||||
)),
|
||||
MinimizeWindowButton(animate: true),
|
||||
MaximizeWindowButton(animate: true),
|
||||
CloseWindowButton(animate: true),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -1,5 +1,52 @@
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mpv_dart/mpv_dart.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:spotube/provider/PlayerDI.dart';
|
||||
|
||||
class TitleBarActionButtons extends StatelessWidget {
|
||||
const TitleBarActionButtons({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MPVPlayer player = context.watch<PlayerDI>().player;
|
||||
return Row(
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
appWindow.minimize();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all(Theme.of(context).iconTheme.color),
|
||||
),
|
||||
child: const Icon(Icons.minimize_rounded)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
appWindow.maximizeOrRestore();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all(Theme.of(context).iconTheme.color),
|
||||
),
|
||||
child: const Icon(Icons.crop_square_rounded)),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
player.stop();
|
||||
appWindow.close();
|
||||
},
|
||||
style: ButtonStyle(
|
||||
foregroundColor:
|
||||
MaterialStateProperty.all(Theme.of(context).iconTheme.color),
|
||||
overlayColor: MaterialStateProperty.all(Colors.redAccent),
|
||||
),
|
||||
child: const Icon(
|
||||
Icons.close_rounded,
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PageWindowTitleBar extends StatelessWidget {
|
||||
final Widget? leading;
|
||||
@ -13,9 +60,7 @@ class PageWindowTitleBar extends StatelessWidget {
|
||||
children: [
|
||||
if (leading != null) leading!,
|
||||
Expanded(child: MoveWindow(child: Center(child: center))),
|
||||
MinimizeWindowButton(animate: true),
|
||||
MaximizeWindowButton(animate: true),
|
||||
CloseWindowButton(animate: true),
|
||||
const TitleBarActionButtons()
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:mpv_dart/mpv_dart.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
import 'package:spotube/provider/PlayerDI.dart';
|
||||
import 'package:spotube/provider/SpotifyDI.dart';
|
||||
|
||||
class Player extends StatefulWidget {
|
||||
@ -18,8 +19,6 @@ class Player extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _PlayerState extends State<Player> {
|
||||
late MPVPlayer player;
|
||||
|
||||
bool _isPlaying = false;
|
||||
bool _shuffled = false;
|
||||
double _duration = 0;
|
||||
@ -29,30 +28,14 @@ class _PlayerState extends State<Player> {
|
||||
double _volume = 0;
|
||||
@override
|
||||
void initState() {
|
||||
player = MPVPlayer(
|
||||
// verbose: true,
|
||||
// debug: true,
|
||||
audioOnly: true,
|
||||
mpvArgs: [
|
||||
"--ytdl-raw-options-set=format=140,http-chunk-size=300000",
|
||||
"--script-opts=ytdl_hook-ytdl_path=yt-dlp",
|
||||
],
|
||||
);
|
||||
|
||||
(() async {
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) async {
|
||||
try {
|
||||
MPVPlayer player = context.read<PlayerDI>().player;
|
||||
await player.start();
|
||||
double volume = await player.getProperty<double>("volume");
|
||||
setState(() {
|
||||
_volume = volume / 100;
|
||||
});
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print("[PLAYER]: $e");
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
player.on(MPVEvents.paused, null, (ev, context) {
|
||||
setState(() {
|
||||
_isPlaying = false;
|
||||
@ -65,7 +48,6 @@ class _PlayerState extends State<Player> {
|
||||
});
|
||||
});
|
||||
|
||||
WidgetsBinding.instance?.addPostFrameCallback((timeStamp) {
|
||||
player.on(MPVEvents.status, null, (ev, _) async {
|
||||
Map data = ev.eventData as Map;
|
||||
Playback playback = context.read<Playback>();
|
||||
@ -97,12 +79,18 @@ class _PlayerState extends State<Player> {
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
if (kDebugMode) {
|
||||
print("[PLAYER]: $e");
|
||||
}
|
||||
}
|
||||
});
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
MPVPlayer player = context.read<PlayerDI>().player;
|
||||
player.removeAllByEvent(MPVEvents.paused);
|
||||
player.removeAllByEvent(MPVEvents.resumed);
|
||||
player.removeAllByEvent(MPVEvents.status);
|
||||
@ -115,7 +103,7 @@ class _PlayerState extends State<Player> {
|
||||
}).join("\n");
|
||||
}
|
||||
|
||||
Future playPlaylist(CurrentPlaylist playlist) async {
|
||||
Future playPlaylist(MPVPlayer player, CurrentPlaylist playlist) async {
|
||||
try {
|
||||
if (player.isRunning() && playlist.id != _currentPlaylistId) {
|
||||
var playlistPath = "/tmp/playlist-${playlist.id}.txt";
|
||||
@ -146,12 +134,14 @@ class _PlayerState extends State<Player> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
MPVPlayer player = context.watch<PlayerDI>().player;
|
||||
|
||||
return Container(
|
||||
color: Theme.of(context).backgroundColor,
|
||||
child: Consumer<Playback>(
|
||||
builder: (context, playback, widget) {
|
||||
if (playback.currentPlaylist != null) {
|
||||
playPlaylist(playback.currentPlaylist!);
|
||||
playPlaylist(player, playback.currentPlaylist!);
|
||||
}
|
||||
|
||||
String? albumArt = playback.currentTrack?.album?.images?.last.url;
|
||||
@ -166,6 +156,13 @@ class _PlayerState extends State<Player> {
|
||||
imageUrl: albumArt,
|
||||
maxHeightDiskCache: 50,
|
||||
maxWidthDiskCache: 50,
|
||||
placeholder: (context, url) {
|
||||
return Container(
|
||||
height: 50,
|
||||
width: 50,
|
||||
color: Colors.green[400],
|
||||
);
|
||||
},
|
||||
),
|
||||
// title of the currently playing track
|
||||
Flexible(
|
||||
|
@ -47,7 +47,13 @@ class _PlaylistCardState extends State<PlaylistCard> {
|
||||
ClipRRect(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl: widget.playlist.images![0].url!),
|
||||
imageUrl: widget.playlist.images![0].url!,
|
||||
progressIndicatorBuilder: (context, url, progress) {
|
||||
return CircularProgressIndicator.adaptive(
|
||||
value: progress.progress,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Positioned.directional(
|
||||
textDirection: TextDirection.ltr,
|
||||
|
@ -23,19 +23,35 @@ class _PlaylistViewState extends State<PlaylistView> {
|
||||
return (TableRow(
|
||||
children: [
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
(track.key + 1).toString(),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
)),
|
||||
TableCell(
|
||||
child: Row(
|
||||
children: [
|
||||
if (thumbnailUrl != null)
|
||||
CachedNetworkImage(
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
child: CachedNetworkImage(
|
||||
placeholder: (context, url) {
|
||||
return Container(
|
||||
height: 40,
|
||||
width: 40,
|
||||
color: Colors.green[300],
|
||||
);
|
||||
},
|
||||
imageUrl: thumbnailUrl,
|
||||
maxHeightDiskCache: 40,
|
||||
maxWidthDiskCache: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Flexible(
|
||||
child: Column(
|
||||
@ -62,17 +78,23 @@ class _PlaylistViewState extends State<PlaylistView> {
|
||||
),
|
||||
),
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
track.value.album?.name ?? "",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
TableCell(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
duration,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
)
|
||||
],
|
||||
));
|
||||
@ -148,7 +170,10 @@ class _PlaylistViewState extends State<PlaylistView> {
|
||||
snapshot.hasError
|
||||
? const Center(child: Text("Error occurred"))
|
||||
: !snapshot.hasData
|
||||
? const CircularProgressIndicator.adaptive()
|
||||
? const Expanded(
|
||||
child: Center(
|
||||
child: CircularProgressIndicator.adaptive()),
|
||||
)
|
||||
: Expanded(
|
||||
child: Scrollbar(
|
||||
child: ListView(
|
||||
@ -159,7 +184,7 @@ class _PlaylistViewState extends State<PlaylistView> {
|
||||
0: FixedColumnWidth(40),
|
||||
1: FlexColumnWidth(),
|
||||
2: FlexColumnWidth(),
|
||||
3: FixedColumnWidth(40),
|
||||
3: FixedColumnWidth(45),
|
||||
},
|
||||
children: [
|
||||
TableRow(
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:bitsdojo_window/bitsdojo_window.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mpv_dart/mpv_dart.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:spotify/spotify.dart';
|
||||
@ -7,6 +8,7 @@ import 'package:spotube/components/Home.dart';
|
||||
import 'package:spotube/models/LocalStorageKeys.dart';
|
||||
import 'package:spotube/provider/Auth.dart';
|
||||
import 'package:spotube/provider/Playback.dart';
|
||||
import 'package:spotube/provider/PlayerDI.dart';
|
||||
import 'package:spotube/provider/SpotifyDI.dart';
|
||||
|
||||
void main() {
|
||||
@ -59,6 +61,15 @@ class MyApp extends StatelessWidget {
|
||||
);
|
||||
}),
|
||||
ChangeNotifierProvider<Playback>(create: (context) => Playback()),
|
||||
ChangeNotifierProvider<PlayerDI>(
|
||||
create: (context) => PlayerDI(MPVPlayer(
|
||||
audioOnly: true,
|
||||
mpvArgs: [
|
||||
"--ytdl-raw-options-set=format=140,http-chunk-size=300000",
|
||||
"--script-opts=ytdl_hook-ytdl_path=yt-dlp",
|
||||
],
|
||||
)),
|
||||
)
|
||||
],
|
||||
child: MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
@ -70,6 +81,7 @@ class MyApp extends StatelessWidget {
|
||||
buttonColor: Colors.green,
|
||||
),
|
||||
shadowColor: Colors.grey[300],
|
||||
backgroundColor: Colors.white,
|
||||
textTheme: TextTheme(
|
||||
bodyText1: TextStyle(color: Colors.grey[850]),
|
||||
headline1: TextStyle(color: Colors.grey[850]),
|
||||
@ -112,7 +124,7 @@ class MyApp extends StatelessWidget {
|
||||
backgroundColor: Colors.blueGrey[900],
|
||||
scaffoldBackgroundColor: Colors.blueGrey[900],
|
||||
dialogBackgroundColor: Colors.blueGrey[800],
|
||||
shadowColor: Colors.black12,
|
||||
shadowColor: Colors.black26,
|
||||
buttonTheme: const ButtonThemeData(
|
||||
buttonColor: Colors.green,
|
||||
),
|
||||
|
15
lib/provider/PlayerDI.dart
Normal file
15
lib/provider/PlayerDI.dart
Normal file
@ -0,0 +1,15 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:mpv_dart/mpv_dart.dart';
|
||||
|
||||
class PlayerDI extends ChangeNotifier {
|
||||
MPVPlayer _player;
|
||||
|
||||
PlayerDI(this._player);
|
||||
|
||||
get player => _player;
|
||||
|
||||
setPlayer(MPVPlayer player) {
|
||||
_player = player;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user