Skip to content

Commit

Permalink
feat: customizable stream/download file formats (#757)
Browse files Browse the repository at this point in the history
* feat: add codec configuration in settings

* fix: show no value for codec configuration in smaller screen

* feat: implement configurable codec for download & streaming music
  • Loading branch information
KRTirtho authored Sep 28, 2023
1 parent 5a758d8 commit e54762b
Show file tree
Hide file tree
Showing 12 changed files with 208 additions and 74 deletions.
2 changes: 2 additions & 0 deletions lib/collections/spotube_icons.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,4 +98,6 @@ abstract class SpotubeIcons {
static const edit = FeatherIcons.edit;
static const web = FeatherIcons.globe;
static const amoled = FeatherIcons.sunset;
static const file = FeatherIcons.file;
static const stream = Icons.stream_rounded;
}
1 change: 0 additions & 1 deletion lib/components/player/player_actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/duration.dart';
import 'package:spotube/models/local_track.dart';
import 'package:spotube/models/logger.dart';
import 'package:spotube/models/spotube_track.dart';
import 'package:spotube/provider/download_manager_provider.dart';
import 'package:spotube/provider/authentication_provider.dart';
import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart';
Expand Down
4 changes: 3 additions & 1 deletion lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -268,5 +268,7 @@
"normalize_audio": "Normalize audio",
"change_cover": "Change cover",
"add_cover": "Add cover",
"restore_defaults": "Restore defaults"
"restore_defaults": "Restore defaults",
"download_music_codec": "Download music codec",
"streaming_music_codec": "Streaming music codec"
}
31 changes: 24 additions & 7 deletions lib/models/spotube_track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,23 @@ class TrackNotFoundException implements Exception {
class SpotubeTrack extends Track {
final YoutubeVideoInfo ytTrack;
final String ytUri;
final MusicCodec codec;

final List<YoutubeVideoInfo> siblings;

SpotubeTrack(
this.ytTrack,
this.ytUri,
this.siblings,
this.codec,
) : super();

SpotubeTrack.fromTrack({
required Track track,
required this.ytTrack,
required this.ytUri,
required this.siblings,
required this.codec,
}) : super() {
album = track.album;
artists = track.artists;
Expand Down Expand Up @@ -149,6 +152,7 @@ class SpotubeTrack extends Track {
static Future<SpotubeTrack> fetchFromTrack(
Track track,
YoutubeEndpoints client,
MusicCodec codec,
) async {
final matchedCachedTrack = await MatchedTrack.box.get(track.id!);
var siblings = <YoutubeVideoInfo>[];
Expand All @@ -157,16 +161,17 @@ class SpotubeTrack extends Track {
if (matchedCachedTrack != null &&
matchedCachedTrack.searchMode == client.preferences.searchMode) {
(ytVideo, ytStreamUrl) = await client.video(
matchedCachedTrack.youtubeId,
matchedCachedTrack.searchMode,
);
matchedCachedTrack.youtubeId, matchedCachedTrack.searchMode, codec);
} else {
siblings = await fetchSiblings(track, client);
if (siblings.isEmpty) {
throw TrackNotFoundException(track);
}
(ytVideo, ytStreamUrl) =
await client.video(siblings.first.id, siblings.first.searchMode);
(ytVideo, ytStreamUrl) = await client.video(
siblings.first.id,
siblings.first.searchMode,
codec,
);

await MatchedTrack.box.put(
track.id!,
Expand All @@ -183,6 +188,7 @@ class SpotubeTrack extends Track {
ytTrack: ytVideo,
ytUri: ytStreamUrl,
siblings: siblings,
codec: codec,
);
}

Expand All @@ -193,8 +199,12 @@ class SpotubeTrack extends Track {
// sibling tracks that were manually searched and swapped
final isStepSibling = siblings.none((element) => element.id == video.id);

final (ytVideo, ytStreamUrl) =
await client.video(video.id, siblings.first.searchMode);
final (ytVideo, ytStreamUrl) = await client.video(
video.id,
siblings.first.searchMode,
// siblings are always swapped when streaming
client.preferences.streamMusicCodec,
);

if (!isStepSibling) {
await MatchedTrack.box.put(
Expand All @@ -215,6 +225,7 @@ class SpotubeTrack extends Track {
video,
...siblings.where((element) => element.id != video.id),
],
codec: client.preferences.streamMusicCodec,
);
}

Expand All @@ -226,6 +237,10 @@ class SpotubeTrack extends Track {
siblings: List.castFrom<dynamic, Map<String, dynamic>>(map["siblings"])
.map((sibling) => YoutubeVideoInfo.fromJson(sibling))
.toList(),
codec: MusicCodec.values.firstWhere(
(element) => element.name == map["codec"],
orElse: () => MusicCodec.m4a,
),
);
}

Expand All @@ -242,6 +257,7 @@ class SpotubeTrack extends Track {
ytTrack: ytTrack,
ytUri: ytUri,
siblings: siblings,
codec: codec,
);
}

Expand All @@ -268,6 +284,7 @@ class SpotubeTrack extends Track {
"ytTrack": ytTrack.toJson(),
"ytUri": ytUri,
"siblings": siblings.map((sibling) => sibling.toJson()).toList(),
"codec": codec.name,
};
}
}
38 changes: 38 additions & 0 deletions lib/pages/settings/settings.dart
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,44 @@ class SettingsPage extends HookConsumerWidget {
value: preferences.normalizeAudio,
onChanged: preferences.setNormalizeAudio,
),
AdaptiveSelectTile<MusicCodec>(
secondary: const Icon(SpotubeIcons.stream),
title: Text(context.l10n.streaming_music_codec),
value: preferences.streamMusicCodec,
showValueWhenUnfolded: false,
options: MusicCodec.values
.map((e) => DropdownMenuItem(
value: e,
child: Text(
e.label,
style: theme.textTheme.labelMedium,
),
))
.toList(),
onChanged: (value) {
if (value == null) return;
preferences.setStreamMusicCodec(value);
},
),
AdaptiveSelectTile<MusicCodec>(
secondary: const Icon(SpotubeIcons.file),
title: Text(context.l10n.download_music_codec),
value: preferences.downloadMusicCodec,
showValueWhenUnfolded: false,
options: MusicCodec.values
.map((e) => DropdownMenuItem(
value: e,
child: Text(
e.label,
style: theme.textTheme.labelMedium,
),
))
.toList(),
onChanged: (value) {
if (value == null) return;
preferences.setDownloadMusicCodec(value);
},
),
],
),
SectionCardWithHeading(
Expand Down
14 changes: 10 additions & 4 deletions lib/provider/download_manager_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ class DownloadManagerProvider extends ChangeNotifier {
await oldFile.exists()) {
await oldFile.rename(savePath);
}
if (status != DownloadStatus.completed) return;
if (status != DownloadStatus.completed ||
//? WebA audiotagging is not supported yet
//? Although in future by converting weba to opus & then tagging it
//? is possible using vorbis comments
downloadCodec == MusicCodec.weba) return;

final file = File(request.path);

Expand Down Expand Up @@ -89,6 +93,8 @@ class DownloadManagerProvider extends ChangeNotifier {
YoutubeEndpoints get yt => ref.read(youtubeProvider);
String get downloadDirectory =>
ref.read(userPreferencesProvider.select((s) => s.downloadLocation));
MusicCodec get downloadCodec =>
ref.read(userPreferencesProvider.select((s) => s.downloadMusicCodec));

int get $downloadCount => dl
.getAllDownloads()
Expand Down Expand Up @@ -130,7 +136,7 @@ class DownloadManagerProvider extends ChangeNotifier {

String getTrackFileUrl(Track track) {
final name =
"${track.name} - ${TypeConversionUtils.artists_X_String(track.artists ?? <Artist>[])}.m4a";
"${track.name} - ${TypeConversionUtils.artists_X_String(track.artists ?? <Artist>[])}.${downloadCodec.name}";
return join(downloadDirectory, PrimitiveUtils.toSafeFileName(name));
}

Expand Down Expand Up @@ -166,15 +172,15 @@ class DownloadManagerProvider extends ChangeNotifier {
await oldFile.rename("$savePath.old");
}

if (track is SpotubeTrack) {
if (track is SpotubeTrack && track.codec == downloadCodec) {
final downloadTask = await dl.addDownload(track.ytUri, savePath);
if (downloadTask != null) {
$history.add(track);
}
} else {
$backHistory.add(track);
final spotubeTrack =
await SpotubeTrack.fetchFromTrack(track, yt).then((d) {
await SpotubeTrack.fetchFromTrack(track, yt, downloadCodec).then((d) {
$backHistory.remove(track);
return d;
});
Expand Down
1 change: 1 addition & 0 deletions lib/provider/proxy_playlist/next_fetcher_mixin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ mixin NextFetcher on StateNotifier<ProxyPlaylist> {
final future = SpotubeTrack.fetchFromTrack(
track,
youtube,
preferences.streamMusicCodec,
);
if (i == 0) {
return await future;
Expand Down
18 changes: 13 additions & 5 deletions lib/provider/proxy_playlist/proxy_playlist_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -185,10 +185,12 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
),
);
} catch (e) {
currentSegments.value = (
source: audioPlayer.currentSource!,
segments: [],
);
if (audioPlayer.currentSource != null) {
currentSegments.value = (
source: audioPlayer.currentSource!,
segments: [],
);
}
} finally {
isFetchingSegments.value = false;
}
Expand Down Expand Up @@ -223,7 +225,11 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>

final nthFetchedTrack = switch (track.runtimeType) {
SpotubeTrack => track as SpotubeTrack,
_ => await SpotubeTrack.fetchFromTrack(track, youtube),
_ => await SpotubeTrack.fetchFromTrack(
track,
youtube,
preferences.streamMusicCodec,
),
};

await audioPlayer.replaceSource(
Expand Down Expand Up @@ -309,10 +315,12 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier<ProxyPlaylist>
final addableTrack = await SpotubeTrack.fetchFromTrack(
tracks.elementAtOrNull(initialIndex) ?? tracks.first,
youtube,
preferences.streamMusicCodec,
).catchError((e, stackTrace) {
return SpotubeTrack.fetchFromTrack(
tracks.elementAtOrNull(initialIndex + 1) ?? tracks.first,
youtube,
preferences.streamMusicCodec,
);
});

Expand Down
Loading

0 comments on commit e54762b

Please sign in to comment.