Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 0 additions & 40 deletions ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import app_links
import UIKit
import Flutter
import MediaPlayer
import Intents
import AVFoundation

Expand All @@ -18,11 +17,6 @@ let flutterEngine = FlutterEngine(name: "SharedEngine", project: nil, allowHeadl
flutterEngine.run()
GeneratedPluginRegistrant.register(with: flutterEngine)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you know anything about the UIScene migration? The carplay code seems to have left us in a strange, partially migrated state, and I'd like to try to bring us closer to the flutter guidelines.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm fairly weak on it. I've had a look now, and I see I did make it a bit worse last time. I can do a follow up commit later to combine the plists and get this cleaned up. Keen to not expand this MR too much more - I'll make a ticket for it.


// Set up method channel for playback state sync to MPNowPlayingInfoCenter
// TODO: This is a workaround because audio_service doesn't set playbackState on iOS.
// Consider contributing a fix to audio_service to set MPNowPlayingInfoCenter.playbackState on iOS.
setupPlaybackStateChannel()

// Set up method channel for Siri media intent handling
setupSiriIntentChannel()

Expand Down Expand Up @@ -91,40 +85,6 @@ private func setExcludeFromiCloudBackup(_ dir: URL, isExcluded: Bool) throws {
try mutableDir.setResourceValues(values)
}

// TODO: This is a workaround because audio_service doesn't set MPNowPlayingInfoCenter.playbackState on iOS.
// The audio_service plugin only sets playbackState on macOS (see AudioServicePlugin.m line 293-295).
// This causes CarPlay's Now Playing screen to not reflect the correct play/pause state when
// playback is started from the phone. Consider contributing a fix upstream to audio_service.

extension AppDelegate {
func setupPlaybackStateChannel() {
let channel = FlutterMethodChannel(
name: "\(Bundle.main.bundleIdentifier!)/playback_state",
binaryMessenger: flutterEngine.binaryMessenger
)

channel.setMethodCallHandler { [weak self] (call, result) in
switch call.method {
case "setPlaybackState":
guard let args = call.arguments as? [String: Any],
let isPlaying = args["isPlaying"] as? Bool else {
result(FlutterError(code: "INVALID_ARGS", message: "Missing isPlaying argument", details: nil))
return
}

if #available(iOS 13.0, *) {
let center = MPNowPlayingInfoCenter.default()
center.playbackState = isPlaying ? .playing : .paused
}
result(nil)

default:
result(FlutterMethodNotImplemented)
}
}
}
}

// Handles voice commands like "Hey Siri, play [track/artist] on Finamp"

private var siriIntentChannel: FlutterMethodChannel?
Expand Down
10 changes: 10 additions & 0 deletions lib/components/MusicScreen/sort_and_filter_row.dart
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,16 @@ extension type const ResolvedSortConfig._(SortAndFilterConfiguration config) imp
return ResolvedSortConfig._(config.copyWith(genreFilter: genre));
}

/// Replaces any existing letter filter with [letter] ("A".."Z" or "#") and forces ascending sort-name order.
ResolvedSortConfig copyWithLetter(String letter) {
final processedFilters = config.filters.toSet();
processedFilters.removeWhere((x) => x.type == ItemFilterType.startsWithCharacter);
processedFilters.add(ItemFilter(type: ItemFilterType.startsWithCharacter, extras: letter));
return ResolvedSortConfig._(
config.copyWith(sortBy: SortBy.sortName, sortOrder: SortOrder.ascending, filters: processedFilters),
);
}

ResolvedSortConfig.skipResolving(this.config);

static const defaultSort = ResolvedSortConfig._(
Expand Down
7 changes: 7 additions & 0 deletions lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,13 @@ Future<void> _setupPlaybackServices() async {
// notificationColor: TODO use the theme color for older versions of Android,
// We will handle preloading artwork ourselves
preloadArtwork: false,
// Keep iOS now playing info + remote command handlers registered when
// the service stops, so Finamp stays the system's resume candidate for
// CarPlay/Bluetooth reconnects instead of losing now-playing status to
// whichever app had it before. Requires the audio_service fork
// overrides in pubspec.yaml (the flag spans audio_service and
// audio_service_platform_interface).
iosKeepNowPlayingOnStop: true,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this actually needed/desirable? As far as I can tell, the stopService command this is intercepting only occurs when we actually call stop() against just_audio, and we only do that on explicit user request. I would imagine if the user has stopped and cleared the queue, they don't actually want us to be taking the now-playing status like this. Or is there something more complex going on?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was because I kept finding Finamp would lose the now playing/last playing status to other apps which are grabby such as Apple's Podcast app. It appears that audio_service calls stopService on transitions to idle, so then we lose the resume. This change did fix it for me, and I've not really noticed any issues in use. I might otherwise be able to just intercept remote stops and make them a pause or something?

androidBrowsableRootExtras: <String, dynamic>{
// support showing search button on Android Auto as well as alternative search results on the player screen after voice search
"android.media.browse.SEARCH_SUPPORTED": true,
Expand Down
10 changes: 3 additions & 7 deletions lib/screens/queue_restore_screen.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import 'package:finamp/components/finamp_app_bar_back_button.dart';
import 'package:finamp/l10n/app_localizations.dart';
import 'package:flutter/material.dart';
import 'package:hive_ce/hive.dart';
import 'package:get_it/get_it.dart';

import '../components/QueueRestoreScreen/queue_restore_tile.dart';
import '../models/finamp_models.dart';
import '../services/queue_service.dart';

class QueueRestoreScreen extends StatelessWidget {
const QueueRestoreScreen({super.key});
Expand All @@ -13,11 +13,7 @@ class QueueRestoreScreen extends StatelessWidget {

@override
Widget build(BuildContext context) {
final queuesBox = Hive.box<FinampStorableQueueInfo>("Queues");
var queueMap = queuesBox.toMap();
queueMap.remove("latest");
var queueList = queueMap.values.toList();
queueList.sort((x, y) => y.creation - x.creation);
final queueList = GetIt.instance<QueueService>().getRecentQueueHistory();

return Scaffold(
appBar: AppBar(title: Text(AppLocalizations.of(context)!.queuesScreen), leading: FinampAppBarBackButton()),
Expand Down
Loading
Loading