185 lines
4.9 KiB
Dart
185 lines
4.9 KiB
Dart
// ignore_for_file: avoid_print
|
|
|
|
import 'dart:math';
|
|
import 'dart:ui';
|
|
|
|
import 'package:audioplayers/audioplayers.dart';
|
|
import 'package:fluent_ui/fluent_ui.dart' as fluent;
|
|
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:music_web_player/api.dart';
|
|
import 'package:music_web_player/ui/cover_image.dart';
|
|
|
|
class MusicPlayer extends StatefulWidget {
|
|
final MusicsList musicsList;
|
|
|
|
const MusicPlayer({Key? key, required this.musicsList}) : super(key: key);
|
|
|
|
@override
|
|
State<MusicPlayer> createState() => _MusicPlayerState();
|
|
}
|
|
|
|
class _MusicPlayerState extends State<MusicPlayer> {
|
|
final rng = Random();
|
|
|
|
final audioPlayer = AudioPlayer(playerId: "player");
|
|
var _playerState = PlayerState.STOPPED;
|
|
|
|
final List<MusicEntry> stack = [];
|
|
int currMusicPos = 0;
|
|
|
|
MusicEntry get currMusic {
|
|
if (currMusicPos < 0) currMusicPos = 0;
|
|
|
|
// Automatically choose next music if required
|
|
if (currMusicPos >= stack.length) {
|
|
var nextId = rng.nextInt(widget.musicsList.length);
|
|
stack.add(widget.musicsList[nextId]);
|
|
}
|
|
|
|
return stack[currMusicPos];
|
|
}
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
|
|
audioPlayer.onPlayerError.listen((event) {
|
|
print("Player error!");
|
|
print(event);
|
|
_playNext();
|
|
});
|
|
|
|
audioPlayer.onPlayerStateChanged
|
|
.listen((s) => setState(() => {_playerState = s}));
|
|
}
|
|
|
|
Future<void> _play() async {
|
|
if (_playerState == PlayerState.PAUSED) {
|
|
await audioPlayer.resume();
|
|
} else {
|
|
await audioPlayer.play(currMusic.musicURL);
|
|
}
|
|
}
|
|
|
|
Future<void> _stop() async {
|
|
await audioPlayer.stop();
|
|
}
|
|
|
|
void _pause() async {
|
|
await audioPlayer.pause();
|
|
}
|
|
|
|
void _playPrevious() async {
|
|
currMusicPos -= 1;
|
|
await _stop();
|
|
await _play();
|
|
}
|
|
|
|
void _playNext() async {
|
|
currMusicPos += 1;
|
|
await _stop();
|
|
await _play();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return LayoutBuilder(
|
|
builder: (context, constraints) => Stack(
|
|
children: [
|
|
// Background image
|
|
CoverImage(
|
|
music: currMusic,
|
|
width: constraints.maxWidth,
|
|
height: constraints.maxHeight,
|
|
fit: BoxFit.cover,
|
|
),
|
|
|
|
ClipRRect(
|
|
// Clip it cleanly.
|
|
child: BackdropFilter(
|
|
filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
|
|
child: Container(
|
|
color: Colors.black.withOpacity(0.8),
|
|
alignment: Alignment.center,
|
|
child: SizedBox(
|
|
width: constraints.maxWidth,
|
|
height: constraints.maxHeight,
|
|
),
|
|
),
|
|
)),
|
|
|
|
_buildCenter(),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildCenter() {
|
|
return fluent.Center(
|
|
child: SizedBox(
|
|
width: 250,
|
|
child: Column(
|
|
mainAxisAlignment: fluent.MainAxisAlignment.center,
|
|
crossAxisAlignment: fluent.CrossAxisAlignment.center,
|
|
children: [
|
|
Material(
|
|
borderRadius: const BorderRadius.all(
|
|
Radius.circular(18.0),
|
|
),
|
|
clipBehavior: Clip.hardEdge,
|
|
child: CoverImage(
|
|
width: 250,
|
|
height: 250,
|
|
music: currMusic,
|
|
fit: BoxFit.cover,
|
|
backgroundColor: Colors.black12,
|
|
icon: const Icon(FluentIcons.music_note_2_24_regular, size: 90),
|
|
),
|
|
),
|
|
const SizedBox(height: 40),
|
|
Text(
|
|
currMusic.title,
|
|
style: const TextStyle(fontSize: 22),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
const SizedBox(height: 20),
|
|
Text(currMusic.artist, textAlign: TextAlign.center),
|
|
const fluent.SizedBox(height: 40),
|
|
fluent.Row(
|
|
children: [
|
|
IconButton(
|
|
icon: const PlayerIcon(fluent.FluentIcons.previous),
|
|
onPressed: currMusicPos == 0 ? null : _playPrevious,
|
|
),
|
|
const Spacer(),
|
|
IconButton(
|
|
icon: PlayerIcon(_playerState == PlayerState.PLAYING
|
|
? fluent.FluentIcons.pause
|
|
: fluent.FluentIcons.play),
|
|
onPressed:
|
|
_playerState == PlayerState.PLAYING ? _pause : _play,
|
|
),
|
|
const Spacer(),
|
|
IconButton(
|
|
icon: const PlayerIcon(fluent.FluentIcons.next),
|
|
onPressed: _playNext,
|
|
),
|
|
],
|
|
)
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class PlayerIcon extends StatelessWidget {
|
|
final IconData icon;
|
|
|
|
const PlayerIcon(this.icon, {Key? key}) : super(key: key);
|
|
|
|
@override
|
|
Widget build(BuildContext context) => Icon(icon, size: 35);
|
|
}
|