Skip to content

Commit

Permalink
add output switcher button to player screen on Android
Browse files Browse the repository at this point in the history
  • Loading branch information
Chaphasilor committed Dec 11, 2024
1 parent 8ac5dcf commit bfa9027
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 4 deletions.
1 change: 1 addition & 0 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ flutter {

dependencies {
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.mediarouter:mediarouter:1.7.0'
}
7 changes: 6 additions & 1 deletion android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
android:allowBackup="false"
android:fullBackupContent="false"
>
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:exported="true">
<activity android:name=".MainActivity" android:launchMode="singleTop" android:theme="@style/LaunchTheme" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:hardwareAccelerated="true" android:windowSoftInputMode="adjustResize" android:exported="true">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
Expand Down Expand Up @@ -60,6 +60,11 @@
</intent-filter>
</receiver>

<receiver
android:name="androidx.mediarouter.media.MediaTransferReceiver"
android:exported="true">
</receiver>

<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/automotive_app_desc" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,54 @@
package com.unicornsonlsd.finamp

import androidx.annotation.NonNull
import androidx.mediarouter.app.SystemOutputSwitcherDialogController
import androidx.mediarouter.media.MediaRouter
import com.ryanheise.audioservice.AudioServiceActivity
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
}
class MainActivity: AudioServiceActivity() {
private val CHANNEL = "com.unicornsonlsd.finamp/output_switcher"

override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
println("calling method: '${call.method}'")
if (call.method == "showOutputSwitcherDialog") {
showOutputSwitcherDialog()
result.success(null)
} else if (call.method == "getRoutes") {
val router = MediaRouter.getInstance(this)
val routes = router.routes
routes.forEach { route ->
println("Route: ${route.name}, connection state: ${route.connectionState}, system route: ${route.isSystemRoute}, default: ${route.isDefault}, device speaker: ${route.isDeviceSpeaker}, bluetooth: ${route.isBluetooth}, volume: ${route.volume}, provider: ${route.provider.packageName}")
}
} else if (call.method == "setOutputToDeviceSpeaker") {
val router = MediaRouter.getInstance(this)
val routes = router.getRoutes()
routes.forEach { route ->
println("Route: ${route.name}, connection state: ${route.connectionState}, system route: ${route.isSystemRoute}, default: ${route.isDefault}, device speaker: ${route.isDeviceSpeaker}, bluetooth: ${route.isBluetooth}, volume: ${route.volume}, provider: ${route.provider.packageName}")
}
val deviceSpeakerRoute = routes.first { route -> route.isDeviceSpeaker }
router.selectRoute(deviceSpeakerRoute)
} else if (call.method == "setOutputToBluetoothDevice") {
val router = MediaRouter.getInstance(this)
val routes = router.getRoutes()
routes.forEach { route ->
println("Route: ${route.name}, connection state: ${route.connectionState}, system route: ${route.isSystemRoute}, default: ${route.isDefault}, device speaker: ${route.isDeviceSpeaker}, bluetooth: ${route.isBluetooth}, volume: ${route.volume}, provider: ${route.provider.packageName}")
}
val bluetoothRoute = routes.first { route -> route.isBluetooth }
router.selectRoute(bluetoothRoute)
}
else {
println("Method not found: '${call.method}'")
result.notImplemented()
}
}
}

private fun showOutputSwitcherDialog() {
SystemOutputSwitcherDialogController.showDialog(this)
}
}
5 changes: 4 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui';

import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
Expand Down Expand Up @@ -110,6 +111,8 @@ void main() async {
flutterLogger.severe(error, error, details.stack);
};

DartPluginRegistrant.ensureInitialized();

await findSystemLocale();
await initializeDateFormatting();

Expand Down Expand Up @@ -443,7 +446,7 @@ class _FinampState extends ConsumerState<Finamp> with WindowListener {
}

@override
Widget build(BuildContext context) {
Widget build(BuildContext context) {
return ProviderScope(
child: GestureDetector(
onTap: () {
Expand Down
13 changes: 13 additions & 0 deletions lib/screens/player_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import 'package:finamp/screens/lyrics_screen.dart';
import 'package:finamp/services/current_track_metadata_provider.dart';
import 'package:finamp/services/feedback_helper.dart';
import 'package:finamp/services/finamp_settings_helper.dart';
import 'package:finamp/services/music_player_background_task.dart';
import 'package:finamp/services/queue_service.dart';
import 'package:finamp/services/theme_provider.dart';
import 'package:flutter/material.dart';
Expand Down Expand Up @@ -250,6 +251,18 @@ class _PlayerScreenContent extends ConsumerWidget {
),
),
),
if (Platform.isAndroid)
IconButton(
icon: Icon(TablerIcons.cast),
onPressed: () {
final audioHandler =
GetIt.instance<MusicPlayerBackgroundTask>();
audioHandler.getRoutes();
// audioHandler.setOutputToDeviceSpeaker();
// audioHandler.setOutputToBluetoothDevice();
audioHandler.showOutputSwitcherDialog();
},
),
],
),
// Required for sleep timer input
Expand Down
46 changes: 46 additions & 0 deletions lib/services/music_player_background_task.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'package:finamp/models/finamp_models.dart';
import 'package:finamp/models/jellyfin_models.dart' as jellyfin_models;
import 'package:finamp/services/favorite_provider.dart';
import 'package:finamp/services/jellyfin_api_helper.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:get_it/get_it.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Expand Down Expand Up @@ -60,6 +61,51 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler {
ValueListenable<Timer?> get sleepTimer => _sleepTimer;

double iosBaseVolumeGainFactor = 1.0;

final outputSwitcherChannel =
MethodChannel('com.unicornsonlsd.finamp/output_switcher');

Future<void> showOutputSwitcherDialog() async {
try {
print("Showing output switcher dialog");
await outputSwitcherChannel.invokeMethod('showOutputSwitcherDialog');
print("Output switcher dialog shown");
} on PlatformException catch (e) {
print("Failed to show output switcher dialog: ${e.message}");
} catch (e) {
print("Failed to show output switcher dialog: $e");
}
}

Future<void> getRoutes() async {
try {
await outputSwitcherChannel.invokeMethod('getRoutes');
} on PlatformException catch (e) {
print("Failed to get routes: ${e.message}");
} catch (e) {
print("Failed to get routes: $e");
}
}

Future<void> setOutputToDeviceSpeaker() async {
try {
await outputSwitcherChannel.invokeMethod('setOutputToDeviceSpeaker');
} on PlatformException catch (e) {
print("Failed to switch output: ${e.message}");
} catch (e) {
print("Failed to switch output: $e");
}
}

Future<void> setOutputToBluetoothDevice() async {
try {
await outputSwitcherChannel.invokeMethod('setOutputToBluetoothDevice');
} on PlatformException catch (e) {
print("Failed to switch output: ${e.message}");
} catch (e) {
print("Failed to switch output: $e");
}
}

MusicPlayerBackgroundTask() {
_audioServiceBackgroundTaskLogger.info("Starting audio service");
Expand Down

0 comments on commit bfa9027

Please sign in to comment.