✨ Audio service
This commit is contained in:
@@ -1,8 +1,30 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:label="groovybox"
|
android:label="groovybox"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity android:name="com.ryanheise.audioservice.AudioServiceActivity"></activity>
|
||||||
|
|
||||||
|
<service android:name="com.ryanheise.audioservice.AudioService"
|
||||||
|
android:foregroundServiceType="mediaPlayback"
|
||||||
|
android:exported="true" tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.media.browse.MediaBrowserService" />
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<receiver android:name="com.ryanheise.audioservice.MediaButtonReceiver"
|
||||||
|
android:exported="true" tools:ignore="Instantiatable">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MEDIA_BUTTON" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -17,12 +39,12 @@
|
|||||||
while the Flutter UI initializes. After that, this theme continues
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
to determine the Window background behind the Flutter UI. -->
|
to determine the Window background behind the Flutter UI. -->
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"
|
||||||
/>
|
/>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<!-- Don't delete the meta-data below.
|
<!-- Don't delete the meta-data below.
|
||||||
@@ -38,8 +60,8 @@
|
|||||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||||
<queries>
|
<queries>
|
||||||
<intent>
|
<intent>
|
||||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||||
<data android:mimeType="text/plain"/>
|
<data android:mimeType="text/plain" />
|
||||||
</intent>
|
</intent>
|
||||||
</queries>
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
package dev.solsynth.groovybox
|
package dev.solsynth.groovybox
|
||||||
|
|
||||||
import io.flutter.embedding.android.FlutterActivity
|
import com.ryanheise.audioservice.AudioServiceActivity
|
||||||
|
|
||||||
class MainActivity : FlutterActivity()
|
class MainActivity : AudioServiceActivity()
|
||||||
|
|||||||
@@ -1,26 +1,200 @@
|
|||||||
import 'package:media_kit/media_kit.dart';
|
import 'package:audio_service/audio_service.dart';
|
||||||
|
import 'package:media_kit/media_kit.dart' as media_kit;
|
||||||
|
import 'package:groovybox/data/db.dart';
|
||||||
|
|
||||||
class AudioHandler {
|
class AudioHandler extends BaseAudioHandler with QueueHandler, SeekHandler {
|
||||||
final Player _player;
|
final media_kit.Player _player;
|
||||||
|
List<MediaItem> _queue = [];
|
||||||
|
int _queueIndex = 0;
|
||||||
|
|
||||||
AudioHandler() : _player = Player() {
|
AudioHandler() : _player = media_kit.Player() {
|
||||||
// Configure for audio
|
// Configure for audio
|
||||||
// _player.setPlaylistMode(PlaylistMode.loop); // Optional
|
// _player.setPlaylistMode(PlaylistMode.loop); // Optional
|
||||||
|
|
||||||
|
// Listen to player state changes and broadcast to audio_service
|
||||||
|
_player.stream.playing.listen((playing) {
|
||||||
|
_broadcastPlaybackState();
|
||||||
|
});
|
||||||
|
|
||||||
|
_player.stream.position.listen((position) {
|
||||||
|
_broadcastPlaybackState();
|
||||||
|
});
|
||||||
|
|
||||||
|
_player.stream.duration.listen((duration) {
|
||||||
|
_broadcastPlaybackState();
|
||||||
|
});
|
||||||
|
|
||||||
|
_player.stream.playlist.listen((playlist) {
|
||||||
|
if (playlist.medias.isNotEmpty) {
|
||||||
|
final currentIndex = playlist.index;
|
||||||
|
if (currentIndex >= 0 && currentIndex < _queue.length) {
|
||||||
|
_queueIndex = currentIndex;
|
||||||
|
mediaItem.add(_queue[_queueIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Player get player => _player;
|
media_kit.Player get player => _player;
|
||||||
|
|
||||||
|
// AudioService callbacks
|
||||||
|
@override
|
||||||
Future<void> play() => _player.play();
|
Future<void> play() => _player.play();
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> pause() => _player.pause();
|
Future<void> pause() => _player.pause();
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> stop() => _player.stop();
|
Future<void> stop() => _player.stop();
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> seek(Duration position) => _player.seek(position);
|
Future<void> seek(Duration position) => _player.seek(position);
|
||||||
|
|
||||||
Future<void> setSource(String path) async {
|
@override
|
||||||
await _player.open(Media(path));
|
Future<void> skipToNext() async {
|
||||||
|
if (_queueIndex < _queue.length - 1) {
|
||||||
|
_queueIndex++;
|
||||||
|
await _player.jump(_queueIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> openPlaylist(List<Media> medias, {int initialIndex = 0}) async {
|
@override
|
||||||
await _player.open(Playlist(medias, index: initialIndex), play: true);
|
Future<void> skipToPrevious() async {
|
||||||
|
if (_queueIndex > 0) {
|
||||||
|
_queueIndex--;
|
||||||
|
await _player.jump(_queueIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> skipToQueueItem(int index) async {
|
||||||
|
if (index >= 0 && index < _queue.length) {
|
||||||
|
_queueIndex = index;
|
||||||
|
await _player.jump(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> addQueueItem(MediaItem mediaItem) async {
|
||||||
|
_queue.add(mediaItem);
|
||||||
|
queue.add(_queue);
|
||||||
|
await _updatePlaylist();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> insertQueueItem(int index, MediaItem mediaItem) async {
|
||||||
|
if (index >= 0 && index <= _queue.length) {
|
||||||
|
_queue.insert(index, mediaItem);
|
||||||
|
queue.add(_queue);
|
||||||
|
await _updatePlaylist();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> removeQueueItem(MediaItem mediaItem) async {
|
||||||
|
_queue.remove(mediaItem);
|
||||||
|
queue.add(_queue);
|
||||||
|
await _updatePlaylist();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> updateQueue(List<MediaItem> queue) async {
|
||||||
|
_queue = List.from(queue);
|
||||||
|
this.queue.add(_queue);
|
||||||
|
await _updatePlaylist();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _updatePlaylist() async {
|
||||||
|
final medias = _queue.map((item) => media_kit.Media(item.id)).toList();
|
||||||
|
if (medias.isNotEmpty) {
|
||||||
|
await _player.open(media_kit.Playlist(medias, index: _queueIndex));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _broadcastPlaybackState() {
|
||||||
|
final playing = _player.state.playing;
|
||||||
|
final position = _player.state.position;
|
||||||
|
final duration = _player.state.duration;
|
||||||
|
|
||||||
|
playbackState.add(
|
||||||
|
PlaybackState(
|
||||||
|
controls: [
|
||||||
|
MediaControl.skipToPrevious,
|
||||||
|
playing ? MediaControl.pause : MediaControl.play,
|
||||||
|
MediaControl.stop,
|
||||||
|
MediaControl.skipToNext,
|
||||||
|
],
|
||||||
|
systemActions: const {
|
||||||
|
MediaAction.seek,
|
||||||
|
MediaAction.seekForward,
|
||||||
|
MediaAction.seekBackward,
|
||||||
|
},
|
||||||
|
androidCompactActionIndices: const [0, 1, 3],
|
||||||
|
processingState: AudioProcessingState.ready,
|
||||||
|
playing: playing,
|
||||||
|
updatePosition: position,
|
||||||
|
bufferedPosition: duration,
|
||||||
|
speed: 1.0,
|
||||||
|
queueIndex: _queueIndex,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// New methods that accept Track objects with proper metadata
|
||||||
|
Future<void> playTrack(Track track) async {
|
||||||
|
final mediaItem = _trackToMediaItem(track);
|
||||||
|
await updateQueue([mediaItem]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> playTracks(List<Track> tracks, {int initialIndex = 0}) async {
|
||||||
|
final mediaItems = tracks.map(_trackToMediaItem).toList();
|
||||||
|
_queueIndex = initialIndex;
|
||||||
|
await updateQueue(mediaItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
MediaItem _trackToMediaItem(Track track) {
|
||||||
|
return MediaItem(
|
||||||
|
id: track.path,
|
||||||
|
album: track.album,
|
||||||
|
title: track.title,
|
||||||
|
artist: track.artist,
|
||||||
|
duration: track.duration != null
|
||||||
|
? Duration(milliseconds: track.duration!)
|
||||||
|
: null,
|
||||||
|
artUri: track.artUri != null ? Uri.file(track.artUri!) : null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy methods for backward compatibility
|
||||||
|
Future<void> setSource(String path) async {
|
||||||
|
final mediaItem = MediaItem(
|
||||||
|
id: path,
|
||||||
|
album: 'Unknown Album',
|
||||||
|
title: _extractTitleFromPath(path),
|
||||||
|
artist: 'Unknown Artist',
|
||||||
|
);
|
||||||
|
await updateQueue([mediaItem]);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> openPlaylist(
|
||||||
|
List<media_kit.Media> medias, {
|
||||||
|
int initialIndex = 0,
|
||||||
|
}) async {
|
||||||
|
final mediaItems = medias.map((media) {
|
||||||
|
return MediaItem(
|
||||||
|
id: media.uri,
|
||||||
|
album: 'Unknown Album',
|
||||||
|
title: _extractTitleFromPath(media.uri),
|
||||||
|
artist: 'Unknown Artist',
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
_queueIndex = initialIndex;
|
||||||
|
await updateQueue(mediaItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _extractTitleFromPath(String path) {
|
||||||
|
return path.split('/').last.split('.').first;
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
|||||||
@@ -1,11 +1,30 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:media_kit/media_kit.dart';
|
import 'package:media_kit/media_kit.dart';
|
||||||
|
import 'package:audio_service/audio_service.dart' as audio_service;
|
||||||
|
import 'logic/audio_handler.dart';
|
||||||
|
import 'providers/audio_provider.dart';
|
||||||
import 'ui/shell.dart';
|
import 'ui/shell.dart';
|
||||||
|
|
||||||
void main() {
|
late AudioHandler _audioHandler;
|
||||||
|
|
||||||
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
MediaKit.ensureInitialized();
|
MediaKit.ensureInitialized();
|
||||||
|
|
||||||
|
// Initialize AudioService
|
||||||
|
_audioHandler = await audio_service.AudioService.init(
|
||||||
|
builder: () => AudioHandler(),
|
||||||
|
config: const audio_service.AudioServiceConfig(
|
||||||
|
androidNotificationChannelId: 'dev.solsynth.groovybox.channel.audio',
|
||||||
|
androidNotificationChannelName: 'GroovyBox Audio',
|
||||||
|
androidNotificationOngoing: true,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Set the audio handler for the provider
|
||||||
|
setAudioHandler(_audioHandler);
|
||||||
|
|
||||||
runApp(const ProviderScope(child: MyApp()));
|
runApp(const ProviderScope(child: MyApp()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,9 +3,15 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|||||||
|
|
||||||
part 'audio_provider.g.dart';
|
part 'audio_provider.g.dart';
|
||||||
|
|
||||||
|
// This should be set after AudioService.init in main.dart
|
||||||
|
late AudioHandler _audioHandler;
|
||||||
|
|
||||||
@Riverpod(keepAlive: true)
|
@Riverpod(keepAlive: true)
|
||||||
AudioHandler audioHandler(Ref ref) {
|
AudioHandler audioHandler(Ref ref) {
|
||||||
final handler = AudioHandler();
|
return _audioHandler;
|
||||||
ref.onDispose(() => handler.dispose());
|
}
|
||||||
return handler;
|
|
||||||
|
// Function to set the audio handler after initialization
|
||||||
|
void setAudioHandler(AudioHandler handler) {
|
||||||
|
_audioHandler = handler;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import 'package:groovybox/data/db.dart';
|
|||||||
import 'package:groovybox/data/playlist_repository.dart';
|
import 'package:groovybox/data/playlist_repository.dart';
|
||||||
import 'package:groovybox/providers/audio_provider.dart';
|
import 'package:groovybox/providers/audio_provider.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:media_kit/media_kit.dart' hide Track;
|
|
||||||
|
|
||||||
class AlbumDetailScreen extends HookConsumerWidget {
|
class AlbumDetailScreen extends HookConsumerWidget {
|
||||||
final AlbumData album;
|
final AlbumData album;
|
||||||
@@ -102,8 +101,7 @@ class AlbumDetailScreen extends HookConsumerWidget {
|
|||||||
|
|
||||||
void _playAlbum(WidgetRef ref, List<Track> tracks, {int initialIndex = 0}) {
|
void _playAlbum(WidgetRef ref, List<Track> tracks, {int initialIndex = 0}) {
|
||||||
final audioHandler = ref.read(audioHandlerProvider);
|
final audioHandler = ref.read(audioHandlerProvider);
|
||||||
final medias = tracks.map((t) => Media(t.path)).toList();
|
audioHandler.playTracks(tracks, initialIndex: initialIndex);
|
||||||
audioHandler.openPlaylist(medias, initialIndex: initialIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatDuration(int? durationMs) {
|
String _formatDuration(int? durationMs) {
|
||||||
|
|||||||
@@ -300,8 +300,7 @@ class LibraryScreen extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final audio = ref.read(audioHandlerProvider);
|
final audio = ref.read(audioHandlerProvider);
|
||||||
audio.setSource(track.path);
|
audio.playTrack(track);
|
||||||
audio.play();
|
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
// Enter selection mode
|
// Enter selection mode
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:groovybox/data/db.dart';
|
|||||||
import 'package:groovybox/data/playlist_repository.dart';
|
import 'package:groovybox/data/playlist_repository.dart';
|
||||||
import 'package:groovybox/providers/audio_provider.dart';
|
import 'package:groovybox/providers/audio_provider.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:media_kit/media_kit.dart' hide Track, Playlist;
|
|
||||||
|
|
||||||
class PlaylistDetailScreen extends HookConsumerWidget {
|
class PlaylistDetailScreen extends HookConsumerWidget {
|
||||||
final Playlist playlist;
|
final Playlist playlist;
|
||||||
@@ -114,8 +113,7 @@ class PlaylistDetailScreen extends HookConsumerWidget {
|
|||||||
int initialIndex = 0,
|
int initialIndex = 0,
|
||||||
}) {
|
}) {
|
||||||
final audioHandler = ref.read(audioHandlerProvider);
|
final audioHandler = ref.read(audioHandlerProvider);
|
||||||
final medias = tracks.map((t) => Media(t.path)).toList();
|
audioHandler.playTracks(tracks, initialIndex: initialIndex);
|
||||||
audioHandler.openPlaylist(medias, initialIndex: initialIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String _formatDuration(int? durationMs) {
|
String _formatDuration(int? durationMs) {
|
||||||
|
|||||||
@@ -5,16 +5,22 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import audio_service
|
||||||
|
import audio_session
|
||||||
import file_picker
|
import file_picker
|
||||||
import flutter_media_metadata
|
import flutter_media_metadata
|
||||||
import media_kit_libs_macos_audio
|
import media_kit_libs_macos_audio
|
||||||
import path_provider_foundation
|
import path_provider_foundation
|
||||||
|
import sqflite_darwin
|
||||||
import sqlite3_flutter_libs
|
import sqlite3_flutter_libs
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
|
||||||
|
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
|
||||||
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
FilePickerPlugin.register(with: registry.registrar(forPlugin: "FilePickerPlugin"))
|
||||||
FlutterMediaMetadataPlugin.register(with: registry.registrar(forPlugin: "FlutterMediaMetadataPlugin"))
|
FlutterMediaMetadataPlugin.register(with: registry.registrar(forPlugin: "FlutterMediaMetadataPlugin"))
|
||||||
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
|
MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin"))
|
||||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||||
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
PODS:
|
PODS:
|
||||||
|
- audio_service (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
|
- audio_session (0.0.1):
|
||||||
|
- FlutterMacOS
|
||||||
- file_picker (0.0.1):
|
- file_picker (0.0.1):
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
- flutter_media_metadata (0.0.1):
|
- flutter_media_metadata (0.0.1):
|
||||||
@@ -9,6 +14,9 @@ PODS:
|
|||||||
- path_provider_foundation (0.0.1):
|
- path_provider_foundation (0.0.1):
|
||||||
- Flutter
|
- Flutter
|
||||||
- FlutterMacOS
|
- FlutterMacOS
|
||||||
|
- sqflite_darwin (0.0.4):
|
||||||
|
- Flutter
|
||||||
|
- FlutterMacOS
|
||||||
- sqlite3 (3.51.1):
|
- sqlite3 (3.51.1):
|
||||||
- sqlite3/common (= 3.51.1)
|
- sqlite3/common (= 3.51.1)
|
||||||
- sqlite3/common (3.51.1)
|
- sqlite3/common (3.51.1)
|
||||||
@@ -36,11 +44,14 @@ PODS:
|
|||||||
- sqlite3/session
|
- sqlite3/session
|
||||||
|
|
||||||
DEPENDENCIES:
|
DEPENDENCIES:
|
||||||
|
- audio_service (from `Flutter/ephemeral/.symlinks/plugins/audio_service/darwin`)
|
||||||
|
- audio_session (from `Flutter/ephemeral/.symlinks/plugins/audio_session/macos`)
|
||||||
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
- file_picker (from `Flutter/ephemeral/.symlinks/plugins/file_picker/macos`)
|
||||||
- flutter_media_metadata (from `Flutter/ephemeral/.symlinks/plugins/flutter_media_metadata/macos`)
|
- flutter_media_metadata (from `Flutter/ephemeral/.symlinks/plugins/flutter_media_metadata/macos`)
|
||||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||||
- media_kit_libs_macos_audio (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos`)
|
- media_kit_libs_macos_audio (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos`)
|
||||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||||
|
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||||
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
- sqlite3_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin`)
|
||||||
|
|
||||||
SPEC REPOS:
|
SPEC REPOS:
|
||||||
@@ -48,6 +59,10 @@ SPEC REPOS:
|
|||||||
- sqlite3
|
- sqlite3
|
||||||
|
|
||||||
EXTERNAL SOURCES:
|
EXTERNAL SOURCES:
|
||||||
|
audio_service:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/audio_service/darwin
|
||||||
|
audio_session:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/audio_session/macos
|
||||||
file_picker:
|
file_picker:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/file_picker/macos
|
||||||
flutter_media_metadata:
|
flutter_media_metadata:
|
||||||
@@ -58,15 +73,20 @@ EXTERNAL SOURCES:
|
|||||||
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos
|
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||||
|
sqflite_darwin:
|
||||||
|
:path: Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin
|
||||||
sqlite3_flutter_libs:
|
sqlite3_flutter_libs:
|
||||||
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
|
:path: Flutter/ephemeral/.symlinks/plugins/sqlite3_flutter_libs/darwin
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
|
audio_service: aa99a6ba2ae7565996015322b0bb024e1d25c6fd
|
||||||
|
audio_session: eaca2512cf2b39212d724f35d11f46180ad3a33e
|
||||||
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
file_picker: 7584aae6fa07a041af2b36a2655122d42f578c1a
|
||||||
flutter_media_metadata: cd8641d1242ce33b60b3deae0c533ee0acc47535
|
flutter_media_metadata: cd8641d1242ce33b60b3deae0c533ee0acc47535
|
||||||
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1
|
||||||
media_kit_libs_macos_audio: 06f3cf88d6d89c7c3c87eae57689d1c6adb335b2
|
media_kit_libs_macos_audio: 06f3cf88d6d89c7c3c87eae57689d1c6adb335b2
|
||||||
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
path_provider_foundation: bb55f6dbba17d0dccd6737fe6f7f34fbd0376880
|
||||||
|
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||||
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
sqlite3: 8d708bc63e9f4ce48f0ad9d6269e478c5ced1d9b
|
||||||
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
sqlite3_flutter_libs: d13b8b3003f18f596e542bcb9482d105577eff41
|
||||||
|
|
||||||
|
|||||||
88
pubspec.lock
88
pubspec.lock
@@ -65,6 +65,38 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.13.0"
|
version: "2.13.0"
|
||||||
|
audio_service:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: audio_service
|
||||||
|
sha256: cb122c7c2639d2a992421ef96b67948ad88c5221da3365ccef1031393a76e044
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.18.18"
|
||||||
|
audio_service_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_service_platform_interface
|
||||||
|
sha256: "6283782851f6c8b501b60904a32fc7199dc631172da0629d7301e66f672ab777"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3"
|
||||||
|
audio_service_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_service_web
|
||||||
|
sha256: b8ea9243201ee53383157fbccf13d5d2a866b5dda922ec19d866d1d5d70424df
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.4"
|
||||||
|
audio_session:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: audio_session
|
||||||
|
sha256: "8f96a7fecbb718cb093070f868b4cdcb8a9b1053dce342ff8ab2fde10eb9afb7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.2"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -326,6 +358,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
|
flutter_cache_manager:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_cache_manager
|
||||||
|
sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.4.1"
|
||||||
flutter_hooks:
|
flutter_hooks:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -792,6 +832,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.3"
|
||||||
|
rxdart:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rxdart
|
||||||
|
sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.28.0"
|
||||||
safe_local_storage:
|
safe_local_storage:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -869,6 +917,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.1"
|
version: "1.10.1"
|
||||||
|
sqflite:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite
|
||||||
|
sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
|
sqflite_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_android
|
||||||
|
sha256: ecd684501ebc2ae9a83536e8b15731642b9570dc8623e0073d227d0ee2bfea88
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2+2"
|
||||||
|
sqflite_common:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_common
|
||||||
|
sha256: "6ef422a4525ecc601db6c0a2233ff448c731307906e92cabc9ba292afaae16a6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.6"
|
||||||
|
sqflite_darwin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_darwin
|
||||||
|
sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.2"
|
||||||
|
sqflite_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_platform_interface
|
||||||
|
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
sqlite3:
|
sqlite3:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ dependencies:
|
|||||||
styled_widget: ^0.4.1
|
styled_widget: ^0.4.1
|
||||||
super_sliver_list: ^0.4.1
|
super_sliver_list: ^0.4.1
|
||||||
http: ^1.0.0
|
http: ^1.0.0
|
||||||
|
audio_service: ^0.18.18
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|||||||
Reference in New Issue
Block a user