common: Remove platform interface

This commit is contained in:
Elias Projahn 2022-05-07 19:06:09 +02:00
parent 8987735797
commit b14dcd67f2
16 changed files with 44 additions and 204 deletions

View file

@ -1,5 +1,4 @@
export 'src/app.dart';
export 'src/library.dart';
export 'src/platform.dart';
export 'src/playback.dart';
export 'src/settings.dart';
export 'src/settings.dart';

View file

@ -1,12 +1,11 @@
import 'dart:async';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'backend.dart';
import 'screens/home.dart';
import 'settings.dart';
import 'platform.dart';
import 'playback.dart';
import 'widgets/player_bar.dart';
@ -24,14 +23,10 @@ class MusicusApp extends StatelessWidget {
/// An object handling playback.
final MusicusPlayback playback;
/// An object handling platform dependent functionality.
final MusicusPlatform platform;
MusicusApp({
@required this.dbPath,
@required this.settingsStorage,
@required this.playback,
@required this.platform,
});
@override
@ -39,7 +34,6 @@ class MusicusApp extends StatelessWidget {
return MusicusBackend(
settingsStorage: settingsStorage,
playback: playback,
platform: platform,
child: Builder(
builder: (context) {
final backend = MusicusBackend.of(context);
@ -91,7 +85,7 @@ class MusicusApp extends StatelessWidget {
leading: const Icon(Icons.folder_open),
title: Text('Choose path'),
onTap: () async {
final uri = await platform.chooseBasePath();
final uri = await FilePicker.platform.getDirectoryPath();
if (uri != null) {
backend.settings.setMusicLibraryPath(uri);
}

View file

@ -2,7 +2,6 @@ import 'package:flutter/widgets.dart';
import 'package:musicus_database/musicus_database.dart';
import 'library.dart';
import 'platform.dart';
import 'playback.dart';
import 'settings.dart';
@ -43,9 +42,6 @@ class MusicusBackend extends StatefulWidget {
/// An object handling playback.
final MusicusPlayback playback;
/// An object handling platform dependent functionality.
final MusicusPlatform platform;
/// The first child below the backend widget.
///
/// This widget should keep track of the current backend status and block
@ -56,7 +52,6 @@ class MusicusBackend extends StatefulWidget {
MusicusBackend({
@required this.settingsStorage,
@required this.playback,
@required this.platform,
@required this.child,
});
@ -76,7 +71,6 @@ class MusicusBackendState extends State<MusicusBackend> {
MusicusPlayback playback;
MusicusSettings settings;
MusicusPlatform platform;
MusicusLibrary library;
MusicusClientDatabase get db => library.db;
@ -90,7 +84,6 @@ class MusicusBackendState extends State<MusicusBackend> {
/// Initialize resources.
Future<void> _load() async {
playback = widget.playback;
await playback.setup();
settings = MusicusSettings(widget.settingsStorage);
await settings.load();
@ -104,9 +97,6 @@ class MusicusBackendState extends State<MusicusBackend> {
final path = settings.musicLibraryPath.valueOrNull;
platform = widget.platform;
platform.setBasePath(path);
// This will change the status for us.
_updateMusicLibrary(path);
}
@ -118,9 +108,11 @@ class MusicusBackendState extends State<MusicusBackend> {
status = MusicusBackendStatus.setup;
});
} else {
platform.setBasePath(path);
library = MusicusLibrary(path, platform);
library = MusicusLibrary(path);
await library.load();
await playback.setup(library);
setState(() {
status = MusicusBackendStatus.ready;
});

View file

@ -8,8 +8,6 @@ import 'package:drift/isolate.dart';
import 'package:drift/native.dart';
import 'package:musicus_database/musicus_database.dart';
import 'platform.dart';
/// Manager for all available tracks and their representation on disk.
class MusicusLibrary {
/// Starts the database isolate.
@ -19,6 +17,7 @@ class MusicusLibrary {
static void _dbIsolateEntrypoint(_IsolateStartRequest request) {
final executor = NativeDatabase(File(request.path));
final driftIsolate =
DriftIsolate.inCurrent(() => DatabaseConnection.fromExecutor(executor));
@ -31,10 +30,7 @@ class MusicusLibrary {
/// The actual music library database.
MusicusClientDatabase db;
/// Access to platform dependent functionality.
final MusicusPlatform platform;
MusicusLibrary(this.basePath, this.platform);
MusicusLibrary(this.basePath);
/// Load all available tracks.
///

View file

@ -1,82 +0,0 @@
/// Object representing a document in Storage Access Framework terms.
class Document {
/// Unique ID for the document.
///
/// The platform implementation thould be able to get the content of the
/// document based on this value.
final String id;
/// Name of the document (i.e. file name).
final String name;
/// Document ID of the parent document.
final String parent;
/// Whether this document represents a directory.
final bool isDirectory;
Document({
this.id,
this.name,
this.parent,
this.isDirectory,
});
// Use Map<dynamic, dynamic> here, as we get casting errors otherwise. This
// won't be typesafe anyway.
Document.fromJson(Map<dynamic, dynamic> json)
: id = json['id'],
name = json['name'],
parent = json['parent'],
isDirectory = json['isDirectory'];
}
/// Platform dependent code for the Musicus backend.
abstract class MusicusPlatform {
/// An identifier for the root directory of the music library.
///
/// This will be the string, that is stored as musicLibraryPath in the
/// settings object.
String basePath;
MusicusPlatform();
/// This will be called, when the music library path was changed.
void setBasePath(String path) {
basePath = path;
}
/// Choose a root level directory for the music library.
///
/// This should return a string representation of the chosen directory
/// suitable for storage as [basePath].
Future<String> chooseBasePath();
/// Get all documents in a directory.
///
/// [parentId] will be the ID of the directory document. If [parentId] is
/// null, the children of the root directory will be returned.
Future<List<Document>> getChildren(String parentId);
/// Read the contents of a document by ID.
Future<String> readDocument(String id);
/// Read from a document by name.
///
/// [parentId] is the document ID of the parent directory.
Future<String> readDocumentByName(String parentId, String fileName);
/// Get a string identifying a document.
///
/// [parentId] is the document ID of the parent directory. The return value
/// should be a string, that the playback object can use to find and play the
/// file. It will be included in [InternalTrack] objects by the music
/// library.
Future<String> getIdentifier(String parentId, String fileName);
/// Write to a document by name.
///
/// [parentId] is the document ID of the parent directory.
Future<void> writeDocumentByName(
String parentId, String fileName, String contents);
}

View file

@ -1,5 +1,5 @@
import 'package:meta/meta.dart';
import 'package:musicus_database/musicus_database.dart';
import 'package:musicus_common/musicus_common.dart';
import 'package:rxdart/rxdart.dart';
/// Base class for Musicus playback.
@ -13,7 +13,7 @@ abstract class MusicusPlayback {
/// The current playlist.
///
/// If the player is not active, this will be an empty list.
final playlist = BehaviorSubject.seeded(<Track>[]);
final playlist = BehaviorSubject.seeded(<String>[]);
/// Index of the currently played (or paused) track within the playlist.
///
@ -23,7 +23,7 @@ abstract class MusicusPlayback {
/// The currently played track.
///
/// This will be null, if there is no current track.
final currentTrack = BehaviorSubject<Track>.seeded(null);
final currentTrack = BehaviorSubject<String>.seeded(null);
/// Whether we are currently playing or not.
///
@ -46,10 +46,10 @@ abstract class MusicusPlayback {
/// Initialize the player.
///
/// This will be called after the database was initialized.
Future<void> setup();
Future<void> setup(MusicusLibrary library);
/// Add a list of tracks to the players playlist.
Future<void> addTracks(List<Track> tracks);
Future<void> addTracks(List<String> tracks);
/// Remove the track at [index] from the playlist.
Future<void> removeTrack(int index);
@ -96,16 +96,15 @@ abstract class MusicusPlayback {
/// Update [position] and [normalizedPosition].
///
/// Requires [duration] to be up to date
void updatePosition(int positionMs) {
position.add(Duration(milliseconds: positionMs));
_setNormalizedPosition(positionMs / duration.value.inMilliseconds);
void updatePosition(Duration pos) {
position.add(pos);
_setNormalizedPosition(pos.inMilliseconds / duration.value.inMilliseconds);
}
/// Update [position], [duration] and [normalizedPosition].
void updateDuration(int positionMs, int durationMs) {
position.add(Duration(milliseconds: positionMs));
duration.add(Duration(milliseconds: durationMs));
_setNormalizedPosition(positionMs / durationMs);
void updateDuration(Duration dur) {
duration.add(dur);
_setNormalizedPosition(position.value.inMilliseconds / dur.inMilliseconds);
}
/// Update [normalizedPosition] ensuring its value is between 0.0 and 1.0.
@ -124,6 +123,9 @@ abstract class MusicusPlayback {
/// Requires [playlist] to be up to date.
void updateCurrentTrack(int index) {
currentIndex.add(index);
currentTrack.add(playlist.value[index]);
if (playlist.value != null && index >= 0 && index < playlist.value.length) {
currentTrack.add(playlist.value[index]);
}
}
}

View file

@ -17,7 +17,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
StreamSubscription<bool> playerActiveSubscription;
StreamSubscription<List<Track>> playlistSubscription;
StreamSubscription<List<String>> playlistSubscription;
List<Widget> widgets = [];
StreamSubscription<double> positionSubscription;
@ -63,7 +63,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
}
/// Go through the tracks of [playlist] and preprocess them for displaying.
Future<void> updateProgram(List<Track> playlist) async {
Future<void> updateProgram(List<String> playlist) async {
List<Widget> newWidgets = [];
// The following variables exist to adapt the resulting ProgramItem to its
@ -84,7 +84,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
// The widgets displayed for this track.
List<Widget> children = [];
final track = playlist[i];
final track = await backend.db.tracksById(playlist[i]).getSingle();
final recordingId = track.recording;
final partIds = track.workParts;

View file

@ -1,11 +1,9 @@
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../backend.dart';
class SettingsScreen extends StatelessWidget {
static const _platform = MethodChannel('de.johrpan.musicus/platform');
@override
Widget build(BuildContext context) {
final backend = MusicusBackend.of(context);
@ -25,7 +23,7 @@ class SettingsScreen extends StatelessWidget {
subtitle: Text(snapshot.data ?? 'Choose folder'),
isThreeLine: snapshot.hasData,
onTap: () async {
final uri = await backend.platform.chooseBasePath();
final uri = await FilePicker.platform.getDirectoryPath();
if (uri != null) {
settings.setMusicLibraryPath(uri);

View file

@ -32,8 +32,9 @@ class WorkScreen extends StatelessWidget {
performanceInfos: recordingInfo.performances,
),
onTap: () async {
final tracks = await backend.db.tracksByRecording(recordingId).get();
backend.playback.addTracks(tracks);
final tracks =
await backend.db.tracksByRecording(recordingId).get();
backend.playback.addTracks(tracks.map((t) => t.id).toList());
},
);
},

View file

@ -2,9 +2,6 @@ import 'dart:async';
import 'package:flutter/material.dart';
import '../backend.dart';
import '../widgets/texts.dart';
/// A list view supporting pagination and searching.
///
/// The [fetch] function will be called, when the user has scrolled to the end

View file

@ -15,7 +15,7 @@ class PlayerBar extends StatefulWidget {
class _PlayerBarState extends State<PlayerBar> {
MusicusBackendState _backend;
StreamSubscription<Track> _currentTrackSubscribtion;
StreamSubscription<String> _currentTrackSubscribtion;
WorkInfo _workInfo;
List<int> _partIds;
@ -26,9 +26,10 @@ class _PlayerBarState extends State<PlayerBar> {
_backend = MusicusBackend.of(context);
_currentTrackSubscribtion?.cancel();
_currentTrackSubscribtion = _backend.playback.currentTrack.listen((track) {
_currentTrackSubscribtion =
_backend.playback.currentTrack.listen((track) async {
if (track != null) {
_setTrack(track);
_setTrack(await _backend.db.tracksById(track).getSingle());
}
});
}

View file

@ -1,12 +1,14 @@
name: musicus_common
version: 0.1.0
description: Common building blocks for Musicus client apps.
publish_to: none
environment:
sdk: ">=2.3.0 <3.0.0"
dependencies:
drift: ^1.0.0
file_picker: ^4.5.1
flutter:
sdk: flutter
flutter_markdown:

View file

@ -4,7 +4,6 @@ import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart' as pp;
import 'settings.dart';
import 'platform.dart';
import 'playback.dart';
Future<void> main() async {
@ -16,7 +15,6 @@ Future<void> main() async {
runApp(MusicusApp(
dbPath: dbPath,
settingsStorage: SettingsStorage(),
platform: MusicusDesktopPlatform(),
playback: MusicusDesktopPlayback(),
));
}

View file

@ -1,58 +0,0 @@
import 'dart:io';
import 'package:file_picker/file_picker.dart';
import 'package:musicus_common/musicus_common.dart';
import 'package:path/path.dart' as p;
class MusicusDesktopPlatform extends MusicusPlatform {
@override
Future<String> chooseBasePath() async {
return await FilePicker.platform.getDirectoryPath();
}
@override
Future<List<Document>> getChildren(String parentId) async {
final List<Document> result = [];
final parent = Directory(parentId ?? basePath);
await for (final fse in parent.list()) {
result.add(Document(
id: fse.path,
name: p.basename(fse.path),
parent: parentId,
isDirectory: fse is Directory,
));
}
return result;
}
@override
Future<String> getIdentifier(String parentId, String fileName) async {
return p.absolute(parentId, fileName);
}
@override
Future<String> readDocument(String id) async {
try {
return await File(id).readAsString();
} on FileSystemException {
return null;
}
}
@override
Future<String> readDocumentByName(String parentId, String fileName) async {
try {
return await File(p.absolute(parentId, fileName)).readAsString();
} on FileSystemException {
return null;
}
}
@override
Future<void> writeDocumentByName(
String parentId, String fileName, String contents) async {
await File(p.absolute(parentId, fileName)).writeAsString(contents);
}
}

View file

@ -1,13 +1,12 @@
import 'package:musicus_common/musicus_common.dart';
import 'package:musicus_database/musicus_database.dart';
class MusicusDesktopPlayback extends MusicusPlayback {
@override
Future<void> setup() async {}
Future<void> setup(MusicusLibrary library) async {}
@override
Future<void> addTracks(List<Track> tracks) async {
final List<Track> newPlaylist = List.from(playlist.value);
Future<void> addTracks(List<String> tracks) async {
final List<String> newPlaylist = List.from(playlist.value);
newPlaylist.addAll(tracks);
playlist.add(newPlaylist);
active.add(true);
@ -20,7 +19,7 @@ class MusicusDesktopPlayback extends MusicusPlayback {
@override
Future<void> removeTrack(int index) async {
final List<Track> tracks = List.from(playlist.value);
final List<String> tracks = List.from(playlist.value);
tracks.removeAt(index);
playlist.add(tracks);
}
@ -29,7 +28,7 @@ class MusicusDesktopPlayback extends MusicusPlayback {
Future<void> seekTo(double pos) async {
if (active.value && pos >= 0.0 && pos <= 1.0) {
final durationMs = duration.value.inMilliseconds;
updatePosition((pos * durationMs).floor());
updatePosition(Duration(milliseconds: (pos * durationMs).floor()));
}
}

View file

@ -1,6 +1,7 @@
name: musicus_desktop
version: 0.1.0
description: Desktop version of the classical music player and organizer.
publish_to: none
environment:
sdk: ">=2.3.0 <3.0.0"