mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-09-13 07:55:18 +00:00

Add features to display current playing track information and support media controls on iOS. * **HomePlayerWidget.swift** - Add track title, artist name, and album art display in the HomePlayerWidget. - Update SimpleEntry struct to include trackTitle, artistName, and albumArt properties. - Modify placeholder, getSnapshot, and getTimeline methods to include new properties. - Update HomePlayerWidgetEntryView to display new track information. * **Info.plist** - Add support for media controls on the lock screen and control center. - Add integration with Siri for voice commands to control playback. * **main.dart** - Register the HomePlayerWidget for iOS by adding glanceProvider listener. * **glance.dart** - Add code to update the HomePlayerWidget with the current track information, including track title, artist name, and album art.
173 lines
4.4 KiB
Dart
173 lines
4.4 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
|
import 'package:home_widget/home_widget.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|
import 'package:http/http.dart';
|
|
import 'package:logger/logger.dart';
|
|
import 'package:spotify/spotify.dart';
|
|
import 'package:spotube/provider/audio_player/audio_player.dart';
|
|
import 'package:spotube/provider/server/server.dart';
|
|
import 'package:spotube/services/audio_player/audio_player.dart';
|
|
import 'package:spotube/services/logger/logger.dart';
|
|
import 'package:spotube/utils/platform.dart';
|
|
|
|
@pragma("vm:entry-point")
|
|
Future<void> glanceBackgroundCallback(Uri? data) async {
|
|
final logger = Logger();
|
|
try {
|
|
if (data == null ||
|
|
data.host != "playback" ||
|
|
data.pathSegments.isEmpty ||
|
|
data.queryParameters["serverAddress"] == null) {
|
|
return;
|
|
}
|
|
|
|
final command = data.pathSegments.first;
|
|
final res = await get(
|
|
Uri.parse(
|
|
"http://${data.queryParameters["serverAddress"]}/playback/$command",
|
|
),
|
|
);
|
|
|
|
if (res.statusCode != 200) {
|
|
throw Exception("Failed to execute command: $command\nBody: ${res.body}");
|
|
}
|
|
} catch (e) {
|
|
logger.e("[GlanceBackgroundCallback] $e");
|
|
}
|
|
}
|
|
|
|
Future<bool?> _saveWidgetData<T>(String key, T? value) async {
|
|
try {
|
|
if (!kIsMobile) return null;
|
|
|
|
return await HomeWidget.saveWidgetData<T>(key, value);
|
|
} catch (e, stack) {
|
|
AppLogger.reportError(e, stack);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
Future<void> _updateWidget() async {
|
|
try {
|
|
if (!kIsMobile) return;
|
|
|
|
if (kIsAndroid) {
|
|
await HomeWidget.updateWidget(
|
|
androidName: 'HomePlayerWidgetReceiver',
|
|
qualifiedAndroidName:
|
|
'oss.krtirtho.spotube.glance.HomePlayerWidgetReceiver',
|
|
);
|
|
}
|
|
if (kIsIOS) {
|
|
await HomeWidget.updateWidget(
|
|
name: 'HomePlayerWidget',
|
|
iOSName: 'HomePlayerWidget',
|
|
);
|
|
}
|
|
} on Exception catch (e, stack) {
|
|
AppLogger.reportError(e, stack);
|
|
}
|
|
}
|
|
|
|
Future<void> _sendActiveTrack(Track? track) async {
|
|
if (track == null) {
|
|
await _saveWidgetData("activeTrack", null);
|
|
await _updateWidget();
|
|
return;
|
|
}
|
|
|
|
final jsonTrack = track.toJson();
|
|
|
|
final image = track.album?.images?.first;
|
|
final cachedImage = await DefaultCacheManager().getSingleFile(image!.url!);
|
|
final data = {
|
|
...jsonTrack,
|
|
"album": {
|
|
...jsonTrack["album"],
|
|
"images": [
|
|
{
|
|
...image.toJson(),
|
|
"path": cachedImage.path,
|
|
}
|
|
]
|
|
}
|
|
};
|
|
|
|
await _saveWidgetData("activeTrack", jsonEncode(data));
|
|
await _saveWidgetData("trackTitle", track.name);
|
|
await _saveWidgetData("artistName", track.artists?.map((artist) => artist.name).join(", "));
|
|
await _saveWidgetData("albumArt", cachedImage.path);
|
|
|
|
await _updateWidget();
|
|
}
|
|
|
|
final glanceProvider = Provider((ref) {
|
|
final server = ref.read(serverProvider);
|
|
final activeTrack = ref.read(audioPlayerProvider).activeTrack;
|
|
|
|
server.whenData(
|
|
(value) async {
|
|
final (:server, :port) = value;
|
|
|
|
await _saveWidgetData(
|
|
"playbackServerAddress",
|
|
"${server.address.host}:$port",
|
|
);
|
|
await _updateWidget();
|
|
},
|
|
);
|
|
|
|
_sendActiveTrack(activeTrack);
|
|
|
|
ref.listen(serverProvider, (prev, next) async {
|
|
next.whenData(
|
|
(value) async {
|
|
final (:server, :port) = value;
|
|
|
|
await _saveWidgetData(
|
|
"playbackServerAddress",
|
|
"${server.address.host}:$port",
|
|
);
|
|
await _updateWidget();
|
|
},
|
|
);
|
|
});
|
|
|
|
ref.listen(
|
|
audioPlayerProvider,
|
|
(previous, next) async {
|
|
try {
|
|
if (previous?.activeTrack != next.activeTrack &&
|
|
next.activeTrack != null) {
|
|
await _sendActiveTrack(next.activeTrack);
|
|
}
|
|
} catch (e, stack) {
|
|
AppLogger.reportError(e, stack);
|
|
}
|
|
},
|
|
);
|
|
|
|
final subscriptions = [
|
|
audioPlayer.playingStream.listen((playing) async {
|
|
await _saveWidgetData("isPlaying", playing);
|
|
await _updateWidget();
|
|
}),
|
|
audioPlayer.positionStream.listen((position) async {
|
|
await _saveWidgetData("position", position.inSeconds);
|
|
await _updateWidget();
|
|
}),
|
|
audioPlayer.durationStream.listen((duration) async {
|
|
await _saveWidgetData("duration", duration.inSeconds);
|
|
await _updateWidget();
|
|
}),
|
|
];
|
|
|
|
ref.onDispose(() {
|
|
for (final subscription in subscriptions) {
|
|
subscription.cancel();
|
|
}
|
|
});
|
|
});
|