MusicPlayer/lib/ui/music_player.dart

330 lines
9.5 KiB
Dart
Raw Normal View History

2022-03-24 09:57:55 +00:00
// ignore_for_file: avoid_print
2022-03-23 20:15:29 +00:00
import 'dart:math';
2022-03-23 20:27:05 +00:00
import 'dart:ui';
2022-03-23 20:15:29 +00:00
2022-03-24 10:24:40 +00:00
import 'package:chewie_audio/chewie_audio.dart';
2022-03-24 08:57:47 +00:00
import 'package:fluent_ui/fluent_ui.dart' as fluent;
2022-03-24 08:20:43 +00:00
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
2022-03-23 20:15:29 +00:00
import 'package:flutter/material.dart';
import 'package:music_web_player/api.dart';
import 'package:music_web_player/ui/cover_image.dart';
2022-03-24 10:24:40 +00:00
import 'package:video_player/video_player.dart';
2022-03-23 20:15:29 +00:00
2022-03-24 11:06:11 +00:00
extension DurationExt on Duration {
String get formatted {
return "$inMinutes:${(inSeconds % 60).toString().padLeft(2, '0')}";
}
}
2022-03-24 13:43:28 +00:00
const double playlistWidth = 300;
2022-03-23 20:15:29 +00:00
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();
2022-03-24 10:24:40 +00:00
VideoPlayerController? _videoPlayerController;
ChewieAudioController? _chewieAudioController;
2022-03-24 10:43:02 +00:00
bool get _isPlaying => _videoPlayerController?.value.isPlaying ?? false;
Duration? get _duration => _videoPlayerController?.value.duration;
Duration? get _position => _videoPlayerController?.value.position;
2022-03-24 09:57:55 +00:00
2022-03-24 13:43:28 +00:00
final List<MusicEntry> _stack = [];
2022-03-23 20:15:29 +00:00
int currMusicPos = 0;
2022-03-24 13:43:28 +00:00
var _showPlaylist = true;
2022-03-24 13:50:33 +00:00
final _filterController = fluent.TextEditingController();
MusicsList? _filteredList;
2022-03-23 20:15:29 +00:00
MusicEntry get currMusic {
if (currMusicPos < 0) currMusicPos = 0;
// Automatically choose next music if required
2022-03-24 13:43:28 +00:00
if (currMusicPos >= _stack.length) {
2022-03-23 20:15:29 +00:00
var nextId = rng.nextInt(widget.musicsList.length);
2022-03-24 13:43:28 +00:00
_stack.add(widget.musicsList[nextId]);
2022-03-23 20:15:29 +00:00
}
2022-03-24 13:43:28 +00:00
return _stack[currMusicPos];
2022-03-23 20:15:29 +00:00
}
2022-03-24 09:57:55 +00:00
Future<void> _play() async {
2022-03-24 10:24:40 +00:00
if (_chewieAudioController != null) {
await _chewieAudioController!.play();
2022-03-24 09:57:55 +00:00
} else {
2022-03-24 10:24:40 +00:00
_videoPlayerController =
VideoPlayerController.network(currMusic.musicURL);
await _videoPlayerController!.initialize();
_chewieAudioController = ChewieAudioController(
videoPlayerController: _videoPlayerController!,
autoPlay: true,
showControls: false);
_videoPlayerController!.addListener(() => setState(() {
// Automatically play next music if required
if (_videoPlayerController != null && _duration == _position) {
if (_duration?.inSeconds == _position?.inSeconds &&
(_duration?.inSeconds ?? 0) > 0) {
_playNext();
}
}
}));
2022-03-24 10:43:02 +00:00
}
2022-03-24 09:57:55 +00:00
}
Future<void> _stop() async {
2022-03-24 10:24:40 +00:00
_chewieAudioController?.dispose();
_videoPlayerController?.dispose();
_chewieAudioController = null;
_videoPlayerController = null;
2022-03-24 09:57:55 +00:00
}
void _pause() async {
2022-03-24 10:24:40 +00:00
await _chewieAudioController?.pause();
2022-03-24 09:57:55 +00:00
}
void _playPrevious() async {
currMusicPos -= 1;
await _stop();
await _play();
}
void _playNext() async {
currMusicPos += 1;
await _stop();
await _play();
}
2022-03-24 13:43:28 +00:00
void _playMusic(MusicEntry music) async {
_stack.insert(currMusicPos + 1, music);
_playNext();
print(_stack);
}
2022-03-24 10:56:21 +00:00
void _updatePosition(Duration d) => _videoPlayerController?.seekTo(d);
2022-03-24 13:50:33 +00:00
void _refreshFilteredList() {
final value = _filterController.value.text.toLowerCase();
if (value.isEmpty) {
setState(() => _filteredList = null);
return;
}
setState(() {
_filteredList = widget.musicsList
.where((m) => m.fullName.toLowerCase().contains(value))
.toList();
});
}
@override
void dispose() {
super.dispose();
_stop();
}
2022-03-23 20:15:29 +00:00
@override
Widget build(BuildContext context) {
return LayoutBuilder(
2022-03-24 13:43:28 +00:00
builder: (context, constraints) {
final mainAreaWidth =
constraints.maxWidth - (_showPlaylist ? playlistWidth : 0);
return fluent.Row(
children: [
SizedBox(
width: mainAreaWidth,
child: Stack(
children: [
// Background image
CoverImage(
music: currMusic,
width: mainAreaWidth,
height: constraints.maxHeight,
fit: BoxFit.cover,
),
2022-03-23 20:27:05 +00:00
2022-03-24 13:43:28 +00:00
// Blur background image
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: mainAreaWidth,
height: constraints.maxHeight,
),
),
),
),
fluent.SizedBox(
width: mainAreaWidth,
child: _buildPlayerWidget(),
),
Positioned(
top: 10, right: 10, child: _buildToggleListButton()),
],
2022-03-23 20:32:33 +00:00
),
2022-03-23 20:27:05 +00:00
),
2022-03-24 13:43:28 +00:00
// Playlist
_showPlaylist
? SizedBox(
width: playlistWidth,
2022-03-24 13:50:33 +00:00
height: constraints.maxHeight,
2022-03-24 13:43:28 +00:00
child: _buildPlaylistPane(),
)
: Container(),
],
);
},
2022-03-23 20:15:29 +00:00
);
}
2022-03-24 13:43:28 +00:00
Widget _buildPlayerWidget() => 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),
),
2022-03-24 08:20:43 +00:00
),
2022-03-24 13:43:28 +00:00
const SizedBox(height: 40),
Text(
currMusic.title,
style: const TextStyle(fontSize: 22),
textAlign: TextAlign.center,
2022-03-24 08:20:43 +00:00
),
2022-03-24 13:43:28 +00:00
const SizedBox(height: 20),
Text(currMusic.artist, textAlign: TextAlign.center),
const fluent.SizedBox(height: 40),
fluent.Row(
children: [
DurationText(_position),
const SizedBox(width: 15),
Flexible(
child: fluent.Slider(
max: _duration?.inSeconds as double? ?? 0,
value: _position?.inSeconds as double? ?? 0,
onChanged: (d) =>
_updatePosition(Duration(seconds: d.toInt())),
label: _position?.formatted,
),
2022-03-24 11:06:11 +00:00
),
2022-03-24 13:43:28 +00:00
const SizedBox(width: 15),
DurationText(_duration),
],
),
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(_isPlaying
? fluent.FluentIcons.pause
: fluent.FluentIcons.play),
onPressed: _isPlaying ? _pause : _play,
),
const Spacer(),
IconButton(
icon: const PlayerIcon(fluent.FluentIcons.next),
onPressed: _playNext,
),
],
)
],
),
2022-03-24 08:20:43 +00:00
),
2022-03-24 13:43:28 +00:00
);
Widget _buildToggleListButton() => fluent.ToggleButton(
checked: _showPlaylist,
onChanged: (s) => setState(() => _showPlaylist = s),
child: const fluent.Icon(fluent.FluentIcons.playlist_music),
);
Widget _buildPlaylistPane() {
2022-03-24 13:50:33 +00:00
return Column(
children: [
fluent.TextBox(
controller: _filterController,
placeholder: "Filter list...",
onChanged: (s) => _refreshFilteredList(),
),
Flexible(
child: ListView.builder(
itemBuilder: (c, i) {
final music = (_filteredList ?? widget.musicsList)[i];
return ListTile(
title: Text(music.title),
subtitle: Text(music.artist),
selected: currMusic == music,
onTap: () => _playMusic(music),
);
},
itemCount: (_filteredList ?? widget.musicsList).length,
),
),
],
2022-03-24 08:20:43 +00:00
);
2022-03-23 20:15:29 +00:00
}
}
2022-03-24 08:57:47 +00:00
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);
}
2022-03-24 11:06:11 +00:00
class DurationText extends StatelessWidget {
final Duration? duration;
const DurationText(this.duration, {Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(
duration?.formatted ?? "0:00",
style: const TextStyle(fontSize: 10),
);
}
}