Skip to content

Commit

Permalink
Initial (not working) ffmpeg experiment
Browse files Browse the repository at this point in the history
  • Loading branch information
jmshrv committed Jul 30, 2024
1 parent 5682661 commit f4ddd33
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 45 deletions.
2 changes: 1 addition & 1 deletion android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ android {

defaultConfig {
applicationId "com.unicornsonlsd.finamp"
minSdkVersion 21
minSdkVersion 24
targetSdkVersion 34
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
Expand Down
1 change: 1 addition & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,7 @@ Future<void> setupHive() async {
Hive.registerAdapter(LyricLineAdapter());
Hive.registerAdapter(LyricDtoAdapter());
Hive.registerAdapter(LyricsAlignmentAdapter());
Hive.registerAdapter(LyricsFontSizeAdapter());

final dir = (Platform.isAndroid || Platform.isIOS)
? await getApplicationDocumentsDirectory()
Expand Down
51 changes: 51 additions & 0 deletions lib/models/ffmpeg_audio_source.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import 'dart:io';

import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/ffmpeg_kit_config.dart';
import 'package:ffmpeg_kit_flutter/ffmpeg_session.dart';
import 'package:just_audio/just_audio.dart';

class FFmpegAudioSource extends StreamAudioSource {
final Uri _uri;

File? _pipe;
FFmpegSession? _session;

FFmpegAudioSource(Uri uri, {super.tag}) : _uri = uri;

Future<FFmpegSession> createSession() async {
final newPipe = await FFmpegKitConfig.registerNewFFmpegPipe();

if (newPipe == null) {
throw "Pipe is null!";
}

_pipe = File(newPipe);

return await FFmpegKit.executeAsync(
"-i $_uri -map 0:a -f wav -y $newPipe",
null,
(log) => print(log.getMessage()),
);
}

@override
Future<StreamAudioResponse> request([int? start, int? end]) async {
_session ??= await createSession();

final stat = await _pipe!.stat();
final size = stat.size;

start ??= 0;
end ??= size;

print("FD size: $size");

return StreamAudioResponse(
sourceLength: size,
contentLength: end - start,
offset: start,
stream: _pipe!.openRead(start, end),
contentType: "audio/vnd.wave");
}
}
111 changes: 67 additions & 44 deletions lib/services/queue_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import 'package:audio_service/audio_service.dart';
import 'package:collection/collection.dart';
import 'package:finamp/components/global_snackbar.dart';
import 'package:finamp/gen/assets.gen.dart';
import 'package:finamp/models/ffmpeg_audio_source.dart';
import 'package:finamp/models/finamp_models.dart';
import 'package:finamp/models/jellyfin_models.dart' as jellyfin_models;
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Expand All @@ -27,10 +28,9 @@ import 'music_player_background_task.dart';

/// A track queueing service for Finamp.
class QueueService {

/// Used to build content:// URIs that are handled by Finamp's built-in content provider.
static final contentProviderPackageName = "com.unicornsonlsd.finamp";

final _jellyfinApiHelper = GetIt.instance<JellyfinApiHelper>();
final _audioHandler = GetIt.instance<MusicPlayerBackgroundTask>();
final _finampUserHelper = GetIt.instance<FinampUserHelper>();
Expand Down Expand Up @@ -488,8 +488,8 @@ class QueueService {
for (int i = 0; i < itemList.length; i++) {
jellyfin_models.BaseItemDto item = itemList[i];
try {
MediaItem mediaItem =
await generateMediaItem(item, contextNormalizationGain: source.contextNormalizationGain);
MediaItem mediaItem = await generateMediaItem(item,
contextNormalizationGain: source.contextNormalizationGain);
newItems.add(FinampQueueItem(
item: mediaItem,
source: source,
Expand Down Expand Up @@ -583,30 +583,31 @@ class QueueService {
required List<jellyfin_models.BaseItemDto> items,
QueueItemSource? source,
}) async {

if (_queueAudioSource.length == 0) {
return _replaceWholeQueue(
itemList: items,
source: source ?? QueueItemSource(
type: QueueItemSourceType.queue,
name: const QueueItemSourceName(type: QueueItemSourceNameType.queue),
id: "queue",
item: null,
),
source: source ??
QueueItemSource(
type: QueueItemSourceType.queue,
name: const QueueItemSourceName(
type: QueueItemSourceNameType.queue),
id: "queue",
item: null,
),
initialIndex: 0,
beginPlaying: false,
);
}

try {
if (_savedQueueState == SavedQueueState.pendingSave) {
_savedQueueState = SavedQueueState.saving;
}
List<FinampQueueItem> queueItems = [];
for (final item in items) {
queueItems.add(FinampQueueItem(
item:
await generateMediaItem(item, contextNormalizationGain: source?.contextNormalizationGain),
item: await generateMediaItem(item,
contextNormalizationGain: source?.contextNormalizationGain),
source: source ?? _order.originalSource,
type: QueueItemQueueType.queue,
));
Expand All @@ -631,16 +632,17 @@ class QueueService {
required List<jellyfin_models.BaseItemDto> items,
QueueItemSource? source,
}) async {

if (_queueAudioSource.length == 0) {
return _replaceWholeQueue(
itemList: items,
source: source ?? QueueItemSource(
type: QueueItemSourceType.queue,
name: const QueueItemSourceName(type: QueueItemSourceNameType.queue),
id: "queue",
item: null,
),
source: source ??
QueueItemSource(
type: QueueItemSourceType.queue,
name: const QueueItemSourceName(
type: QueueItemSourceNameType.queue),
id: "queue",
item: null,
),
initialIndex: 0,
beginPlaying: false,
);
Expand All @@ -653,8 +655,8 @@ class QueueService {
List<FinampQueueItem> queueItems = [];
for (final item in items) {
queueItems.add(FinampQueueItem(
item:
await generateMediaItem(item, contextNormalizationGain: source?.contextNormalizationGain),
item: await generateMediaItem(item,
contextNormalizationGain: source?.contextNormalizationGain),
source: source ??
QueueItemSource(
id: "next-up",
Expand Down Expand Up @@ -687,30 +689,31 @@ class QueueService {
required List<jellyfin_models.BaseItemDto> items,
QueueItemSource? source,
}) async {

if (_queueAudioSource.length == 0) {
return _replaceWholeQueue(
itemList: items,
source: source ?? QueueItemSource(
type: QueueItemSourceType.queue,
name: const QueueItemSourceName(type: QueueItemSourceNameType.queue),
id: "queue",
item: null,
),
source: source ??
QueueItemSource(
type: QueueItemSourceType.queue,
name: const QueueItemSourceName(
type: QueueItemSourceNameType.queue),
id: "queue",
item: null,
),
initialIndex: 0,
beginPlaying: false,
);
}

try {
if (_savedQueueState == SavedQueueState.pendingSave) {
_savedQueueState = SavedQueueState.saving;
}
List<FinampQueueItem> queueItems = [];
for (final item in items) {
queueItems.add(FinampQueueItem(
item:
await generateMediaItem(item, contextNormalizationGain: source?.contextNormalizationGain),
item: await generateMediaItem(item,
contextNormalizationGain: source?.contextNormalizationGain),
source: source ??
QueueItemSource(
id: "next-up",
Expand Down Expand Up @@ -979,7 +982,9 @@ class QueueService {
double? contextNormalizationGain,
MediaItemParentType? parentType,
String? parentId,
bool Function({ jellyfin_models.BaseItemDto? item, TabContentType? contentType })? isPlayable,
bool Function(
{jellyfin_models.BaseItemDto? item, TabContentType? contentType})?
isPlayable,
}) async {
const uuid = Uuid();

Expand All @@ -1005,17 +1010,20 @@ class QueueService {
downloadedSong = downloadsService.getSongDownload(item: item);
isDownloaded = downloadedSong != null;
} else {
downloadedCollection = await downloadsService.getCollectionInfo(item: item);
downloadedCollection =
await downloadsService.getCollectionInfo(item: item);
if (downloadedCollection != null) {
final downloadStatus = downloadsService.getStatus(downloadedCollection, null);
final downloadStatus =
downloadsService.getStatus(downloadedCollection, null);
isDownloaded = downloadStatus != DownloadItemStatus.notNeeded;
}
}

try {
downloadedImage = downloadsService.getImageDownload(item: item);
} catch (e) {
_queueServiceLogger.warning("Couldn't get the offline image for track '${item.name}' because it's not downloaded or missing a blurhash");
_queueServiceLogger.warning(
"Couldn't get the offline image for track '${item.name}' because it's not downloaded or missing a blurhash");
}

Uri? artUri;
Expand All @@ -1028,12 +1036,15 @@ class QueueService {
// try to get image file (Android Automotive needs this)
if (artUri != null) {
try {
final fileInfo = await AudioService.cacheManager.getFileFromCache(item.id);
final fileInfo =
await AudioService.cacheManager.getFileFromCache(item.id);
if (fileInfo != null) {
artUri = fileInfo.file.uri;
}
} catch (e) {
_queueServiceLogger.severe("Error setting new media artwork uri for item: ${item.id} name: ${item.name}", e);
_queueServiceLogger.severe(
"Error setting new media artwork uri for item: ${item.id} name: ${item.name}",
e);
}
}
}
Expand All @@ -1042,17 +1053,29 @@ class QueueService {
if (Platform.isAndroid) {
// replace with placeholder art
if (artUri == null) {
final applicationSupportDirectory = await getApplicationSupportDirectory();
artUri = Uri(scheme: "content", host: contentProviderPackageName, path: path_helper.join(applicationSupportDirectory.absolute.path, Assets.images.albumWhite.path));
final applicationSupportDirectory =
await getApplicationSupportDirectory();
artUri = Uri(
scheme: "content",
host: contentProviderPackageName,
path: path_helper.join(applicationSupportDirectory.absolute.path,
Assets.images.albumWhite.path));
} else {
// store the origin in fragment since it should be unused
artUri = Uri(scheme: "content", host: contentProviderPackageName, path: artUri.path, fragment: ["http", "https"].contains(artUri.scheme) ? artUri.origin : null);
artUri = Uri(
scheme: "content",
host: contentProviderPackageName,
path: artUri.path,
fragment: ["http", "https"].contains(artUri.scheme)
? artUri.origin
: null);
}
}

return MediaItem(
id: itemId?.toString() ?? uuid.v4(),
playable: isItemPlayable, // this dictates whether clicking on an item will try to play it or browse it in media browsers like Android Auto
playable:
isItemPlayable, // this dictates whether clicking on an item will try to play it or browse it in media browsers like Android Auto
album: item.album,
artist: item.artists?.join(", ") ?? item.albumArtist,
artUri: artUri,
Expand Down Expand Up @@ -1087,7 +1110,7 @@ class QueueService {
if (queueItem.item.extras!["shouldTranscode"] == true) {
return HlsAudioSource(await _songUri(queueItem.item), tag: queueItem);
} else {
return AudioSource.uri(await _songUri(queueItem.item),
return FFmpegAudioSource(await _songUri(queueItem.item),
tag: queueItem);
}
}
Expand Down
16 changes: 16 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
ffmpeg_kit_flutter:
dependency: "direct main"
description:
name: ffmpeg_kit_flutter
sha256: "843aae41823ca94a0988d975b4b6cdc6948744b9b7e2707d81a3a9cd237b0100"
url: "https://pub.dev"
source: hosted
version: "6.0.3"
ffmpeg_kit_flutter_platform_interface:
dependency: transitive
description:
name: ffmpeg_kit_flutter_platform_interface
sha256: addf046ae44e190ad0101b2fde2ad909a3cd08a2a109f6106d2f7048b7abedee
url: "https://pub.dev"
source: hosted
version: "0.2.1"
file:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ dependencies:
scroll_to_index: ^3.0.1
window_manager: ^0.3.8
url_launcher: ^6.2.6
ffmpeg_kit_flutter: ^6.0.3

dev_dependencies:
flutter_test:
Expand Down

0 comments on commit f4ddd33

Please sign in to comment.