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/app.dart';
export 'src/library.dart'; export 'src/library.dart';
export 'src/platform.dart';
export 'src/playback.dart'; export 'src/playback.dart';
export 'src/settings.dart'; export 'src/settings.dart';

View file

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

View file

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

View file

@ -8,8 +8,6 @@ import 'package:drift/isolate.dart';
import 'package:drift/native.dart'; import 'package:drift/native.dart';
import 'package:musicus_database/musicus_database.dart'; import 'package:musicus_database/musicus_database.dart';
import 'platform.dart';
/// Manager for all available tracks and their representation on disk. /// Manager for all available tracks and their representation on disk.
class MusicusLibrary { class MusicusLibrary {
/// Starts the database isolate. /// Starts the database isolate.
@ -19,6 +17,7 @@ class MusicusLibrary {
static void _dbIsolateEntrypoint(_IsolateStartRequest request) { static void _dbIsolateEntrypoint(_IsolateStartRequest request) {
final executor = NativeDatabase(File(request.path)); final executor = NativeDatabase(File(request.path));
final driftIsolate = final driftIsolate =
DriftIsolate.inCurrent(() => DatabaseConnection.fromExecutor(executor)); DriftIsolate.inCurrent(() => DatabaseConnection.fromExecutor(executor));
@ -31,10 +30,7 @@ class MusicusLibrary {
/// The actual music library database. /// The actual music library database.
MusicusClientDatabase db; MusicusClientDatabase db;
/// Access to platform dependent functionality. MusicusLibrary(this.basePath);
final MusicusPlatform platform;
MusicusLibrary(this.basePath, this.platform);
/// Load all available tracks. /// 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:meta/meta.dart';
import 'package:musicus_database/musicus_database.dart'; import 'package:musicus_common/musicus_common.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
/// Base class for Musicus playback. /// Base class for Musicus playback.
@ -13,7 +13,7 @@ abstract class MusicusPlayback {
/// The current playlist. /// The current playlist.
/// ///
/// If the player is not active, this will be an empty list. /// 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. /// Index of the currently played (or paused) track within the playlist.
/// ///
@ -23,7 +23,7 @@ abstract class MusicusPlayback {
/// The currently played track. /// The currently played track.
/// ///
/// This will be null, if there is no current 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. /// Whether we are currently playing or not.
/// ///
@ -46,10 +46,10 @@ abstract class MusicusPlayback {
/// Initialize the player. /// Initialize the player.
/// ///
/// This will be called after the database was initialized. /// 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. /// 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. /// Remove the track at [index] from the playlist.
Future<void> removeTrack(int index); Future<void> removeTrack(int index);
@ -96,16 +96,15 @@ abstract class MusicusPlayback {
/// Update [position] and [normalizedPosition]. /// Update [position] and [normalizedPosition].
/// ///
/// Requires [duration] to be up to date /// Requires [duration] to be up to date
void updatePosition(int positionMs) { void updatePosition(Duration pos) {
position.add(Duration(milliseconds: positionMs)); position.add(pos);
_setNormalizedPosition(positionMs / duration.value.inMilliseconds); _setNormalizedPosition(pos.inMilliseconds / duration.value.inMilliseconds);
} }
/// Update [position], [duration] and [normalizedPosition]. /// Update [position], [duration] and [normalizedPosition].
void updateDuration(int positionMs, int durationMs) { void updateDuration(Duration dur) {
position.add(Duration(milliseconds: positionMs)); duration.add(dur);
duration.add(Duration(milliseconds: durationMs)); _setNormalizedPosition(position.value.inMilliseconds / dur.inMilliseconds);
_setNormalizedPosition(positionMs / durationMs);
} }
/// Update [normalizedPosition] ensuring its value is between 0.0 and 1.0. /// 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. /// Requires [playlist] to be up to date.
void updateCurrentTrack(int index) { void updateCurrentTrack(int index) {
currentIndex.add(index); currentIndex.add(index);
if (playlist.value != null && index >= 0 && index < playlist.value.length) {
currentTrack.add(playlist.value[index]); currentTrack.add(playlist.value[index]);
} }
} }
}

View file

@ -17,7 +17,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
StreamSubscription<bool> playerActiveSubscription; StreamSubscription<bool> playerActiveSubscription;
StreamSubscription<List<Track>> playlistSubscription; StreamSubscription<List<String>> playlistSubscription;
List<Widget> widgets = []; List<Widget> widgets = [];
StreamSubscription<double> positionSubscription; StreamSubscription<double> positionSubscription;
@ -63,7 +63,7 @@ class _ProgramScreenState extends State<ProgramScreen> {
} }
/// Go through the tracks of [playlist] and preprocess them for displaying. /// 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 = []; List<Widget> newWidgets = [];
// The following variables exist to adapt the resulting ProgramItem to its // 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. // The widgets displayed for this track.
List<Widget> children = []; List<Widget> children = [];
final track = playlist[i]; final track = await backend.db.tracksById(playlist[i]).getSingle();
final recordingId = track.recording; final recordingId = track.recording;
final partIds = track.workParts; 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/material.dart';
import 'package:flutter/services.dart';
import '../backend.dart'; import '../backend.dart';
class SettingsScreen extends StatelessWidget { class SettingsScreen extends StatelessWidget {
static const _platform = MethodChannel('de.johrpan.musicus/platform');
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final backend = MusicusBackend.of(context); final backend = MusicusBackend.of(context);
@ -25,7 +23,7 @@ class SettingsScreen extends StatelessWidget {
subtitle: Text(snapshot.data ?? 'Choose folder'), subtitle: Text(snapshot.data ?? 'Choose folder'),
isThreeLine: snapshot.hasData, isThreeLine: snapshot.hasData,
onTap: () async { onTap: () async {
final uri = await backend.platform.chooseBasePath(); final uri = await FilePicker.platform.getDirectoryPath();
if (uri != null) { if (uri != null) {
settings.setMusicLibraryPath(uri); settings.setMusicLibraryPath(uri);

View file

@ -32,8 +32,9 @@ class WorkScreen extends StatelessWidget {
performanceInfos: recordingInfo.performances, performanceInfos: recordingInfo.performances,
), ),
onTap: () async { onTap: () async {
final tracks = await backend.db.tracksByRecording(recordingId).get(); final tracks =
backend.playback.addTracks(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 'package:flutter/material.dart';
import '../backend.dart';
import '../widgets/texts.dart';
/// A list view supporting pagination and searching. /// A list view supporting pagination and searching.
/// ///
/// The [fetch] function will be called, when the user has scrolled to the end /// 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> { class _PlayerBarState extends State<PlayerBar> {
MusicusBackendState _backend; MusicusBackendState _backend;
StreamSubscription<Track> _currentTrackSubscribtion; StreamSubscription<String> _currentTrackSubscribtion;
WorkInfo _workInfo; WorkInfo _workInfo;
List<int> _partIds; List<int> _partIds;
@ -26,9 +26,10 @@ class _PlayerBarState extends State<PlayerBar> {
_backend = MusicusBackend.of(context); _backend = MusicusBackend.of(context);
_currentTrackSubscribtion?.cancel(); _currentTrackSubscribtion?.cancel();
_currentTrackSubscribtion = _backend.playback.currentTrack.listen((track) { _currentTrackSubscribtion =
_backend.playback.currentTrack.listen((track) async {
if (track != null) { if (track != null) {
_setTrack(track); _setTrack(await _backend.db.tracksById(track).getSingle());
} }
}); });
} }

View file

@ -1,12 +1,14 @@
name: musicus_common name: musicus_common
version: 0.1.0 version: 0.1.0
description: Common building blocks for Musicus client apps. description: Common building blocks for Musicus client apps.
publish_to: none
environment: environment:
sdk: ">=2.3.0 <3.0.0" sdk: ">=2.3.0 <3.0.0"
dependencies: dependencies:
drift: ^1.0.0 drift: ^1.0.0
file_picker: ^4.5.1
flutter: flutter:
sdk: flutter sdk: flutter
flutter_markdown: 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 'package:path_provider/path_provider.dart' as pp;
import 'settings.dart'; import 'settings.dart';
import 'platform.dart';
import 'playback.dart'; import 'playback.dart';
Future<void> main() async { Future<void> main() async {
@ -16,7 +15,6 @@ Future<void> main() async {
runApp(MusicusApp( runApp(MusicusApp(
dbPath: dbPath, dbPath: dbPath,
settingsStorage: SettingsStorage(), settingsStorage: SettingsStorage(),
platform: MusicusDesktopPlatform(),
playback: MusicusDesktopPlayback(), 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_common/musicus_common.dart';
import 'package:musicus_database/musicus_database.dart';
class MusicusDesktopPlayback extends MusicusPlayback { class MusicusDesktopPlayback extends MusicusPlayback {
@override @override
Future<void> setup() async {} Future<void> setup(MusicusLibrary library) async {}
@override @override
Future<void> addTracks(List<Track> tracks) async { Future<void> addTracks(List<String> tracks) async {
final List<Track> newPlaylist = List.from(playlist.value); final List<String> newPlaylist = List.from(playlist.value);
newPlaylist.addAll(tracks); newPlaylist.addAll(tracks);
playlist.add(newPlaylist); playlist.add(newPlaylist);
active.add(true); active.add(true);
@ -20,7 +19,7 @@ class MusicusDesktopPlayback extends MusicusPlayback {
@override @override
Future<void> removeTrack(int index) async { 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); tracks.removeAt(index);
playlist.add(tracks); playlist.add(tracks);
} }
@ -29,7 +28,7 @@ class MusicusDesktopPlayback extends MusicusPlayback {
Future<void> seekTo(double pos) async { Future<void> seekTo(double pos) async {
if (active.value && pos >= 0.0 && pos <= 1.0) { if (active.value && pos >= 0.0 && pos <= 1.0) {
final durationMs = duration.value.inMilliseconds; final durationMs = duration.value.inMilliseconds;
updatePosition((pos * durationMs).floor()); updatePosition(Duration(milliseconds: (pos * durationMs).floor()));
} }
} }

View file

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