From 01a195ff9496cd6c2cc6354e86906235ade1b9d6 Mon Sep 17 00:00:00 2001
From: bin <17426470+boyan01@users.noreply.github.com>
Date: Sun, 12 Feb 2023 19:01:47 +0800
Subject: [PATCH 1/2] remove channel service
---
example/ios/Podfile.lock | 6 -
example/ios/Runner.xcodeproj/project.pbxproj | 4 +-
example/ios/Runner/Info.plist | 2 +
example/lib/main.dart | 30 ---
example/lib/player/background.dart | 5 +-
ios/Classes/MusicMetadata.swift | 20 +-
ios/Classes/MusicPlayer.swift | 40 +++-
ios/Classes/SwiftMusicPlayerPlugin.swift | 170 +++++----------
lib/music_player.dart | 2 +-
lib/src/channel_service.dart | 212 -------------------
lib/src/channel_ui.dart | 42 ++++
lib/src/internal/serialization.dart | 13 --
lib/src/player/music_metadata.dart | 2 +-
lib/src/utils/interceptor.dart | 42 ++++
14 files changed, 182 insertions(+), 408 deletions(-)
delete mode 100644 lib/src/channel_service.dart
create mode 100644 lib/src/utils/interceptor.dart
diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock
index 245564b..ea58a1b 100644
--- a/example/ios/Podfile.lock
+++ b/example/ios/Podfile.lock
@@ -4,13 +4,10 @@ PODS:
- Flutter
- SwiftAudioEx (~> 0.15.3)
- SwiftAudioEx (0.15.3)
- - system_clock (0.0.1):
- - Flutter
DEPENDENCIES:
- Flutter (from `Flutter`)
- music_player (from `.symlinks/plugins/music_player/ios`)
- - system_clock (from `.symlinks/plugins/system_clock/ios`)
SPEC REPOS:
trunk:
@@ -21,14 +18,11 @@ EXTERNAL SOURCES:
:path: Flutter
music_player:
:path: ".symlinks/plugins/music_player/ios"
- system_clock:
- :path: ".symlinks/plugins/system_clock/ios"
SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
music_player: 2cb8e84bc904013b04392560f0b94d1e96e71e14
SwiftAudioEx: 83eabba2940924fc1c0d5cb0896049921365229c
- system_clock: 3efb51d18e565092e2a97bc605c6255f328bd13a
PODFILE CHECKSUM: 4e8f8b2be68aeea4c0d5beb6ff1e79fface1d048
diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj
index eaf4c04..bba17e1 100644
--- a/example/ios/Runner.xcodeproj/project.pbxproj
+++ b/example/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 51;
+ objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -221,6 +221,7 @@
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -235,6 +236,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
+ alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
diff --git a/example/ios/Runner/Info.plist b/example/ios/Runner/Info.plist
index 55bfd55..9ad7a15 100644
--- a/example/ios/Runner/Info.plist
+++ b/example/ios/Runner/Info.plist
@@ -43,5 +43,7 @@
CADisableMinimumFrameDurationOnPhone
+ UIApplicationSupportsIndirectInputEvents
+
diff --git a/example/lib/main.dart b/example/lib/main.dart
index f3a752e..98bb39c 100644
--- a/example/lib/main.dart
+++ b/example/lib/main.dart
@@ -1,8 +1,6 @@
import 'package:flutter/material.dart';
-import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:music_player_example/page_play_queue.dart';
-import 'package:music_player_example/player/background.dart';
import 'package:overlay_support/overlay_support.dart';
import 'player/player.dart';
@@ -55,34 +53,6 @@ void main() {
runApp(ExampleApp());
}
-@pragma("vm:entry-point")
-void playerBackgroundService() async {
- debugPrint("start playerBackgroundService");
- Logger.root.onRecord.listen((record) {
- print('${record.level.name}: ${record.time}: ${record.message}');
- });
- await Future.delayed(const Duration(milliseconds: 100));
- runBackgroundService(
- config: Config(
- pauseWhenTaskRemoved: false,
- ),
- playUriInterceptor: (mediaId, fallbackUrl) async {
- debugPrint("get media play uri : $mediaId , $fallbackUrl");
- if (mediaId == 'rise') return "asset:///tracks/rise.mp3";
- return fallbackUrl;
- },
- imageLoadInterceptor: (metadata) async {
- debugPrint("load image for ${metadata.mediaId} , ${metadata.title}");
- if (metadata.mediaId == "bamboo") {
- final data = await rootBundle.load("images/bamboo.jpg");
- return Uint8List.view(data.buffer);
- }
- return null;
- },
- playQueueInterceptor: ExamplePlayQueueInterceptor(),
- );
-}
-
class ExampleApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
diff --git a/example/lib/player/background.dart b/example/lib/player/background.dart
index 56d10ea..4966242 100644
--- a/example/lib/player/background.dart
+++ b/example/lib/player/background.dart
@@ -4,9 +4,8 @@ import 'package:music_player_example/main.dart';
class ExamplePlayQueueInterceptor extends PlayQueueInterceptor {
@override
- Future> fetchMoreMusic(
- BackgroundPlayQueue queue, PlayMode playMode) async {
- if (queue.queueId == "fm" && queue.queue.length == 1) {
+ Future> fetchMoreMusic(PlayMode playMode) async {
+ if (playMode.index == 4) {
return medias.getRange(1, 3).toList();
}
throw MissingPluginException();
diff --git a/ios/Classes/MusicMetadata.swift b/ios/Classes/MusicMetadata.swift
index ef39764..7f96f1a 100644
--- a/ios/Classes/MusicMetadata.swift
+++ b/ios/Classes/MusicMetadata.swift
@@ -9,7 +9,7 @@ class MusicMetadata: Equatable {
private let data: [String: Any]
public init(map: [String: Any]) {
- self.data = map
+ data = map
}
convenience init?(any: Any?) {
@@ -61,12 +61,12 @@ class MetadataAudioItem: AudioItem, RemoteCommandable {
private let uri: String
- private let servicePlugin: MusicPlayerServicePlugin
+ private let source: MusicPlayerSource
- public init(metadata: MusicMetadata, uri: String, servicePlugin: MusicPlayerServicePlugin) {
+ public init(metadata: MusicMetadata, uri: String, source: MusicPlayerSource) {
self.metadata = metadata
self.uri = uri
- self.servicePlugin = servicePlugin
+ self.source = source
}
func getSourceUrl() -> String {
@@ -74,15 +74,7 @@ class MetadataAudioItem: AudioItem, RemoteCommandable {
return uri
}
if "asset".caseInsensitiveCompare(url.scheme ?? "") == .orderedSame {
- if let registrar = servicePlugin.registrar {
- let assetKey = registrar.lookupKey(forAsset: url.path)
- guard let path = Bundle.main.path(forResource: assetKey, ofType: nil) else {
- debugPrint("resource not found : \(assetKey)")
- return uri
- }
- debugPrint("resource found: \(path)")
- return URL(fileURLWithPath: path).absoluteString
- }
+ return source.loadAssetResource(url: url)
}
return uri
}
@@ -104,6 +96,6 @@ class MetadataAudioItem: AudioItem, RemoteCommandable {
}
func getArtwork(_ handler: @escaping (UIImage?) -> Void) {
- servicePlugin.loadImage(metadata: metadata, completion: handler)
+ source.loadImage(metadata: metadata, completion: handler)
}
}
diff --git a/ios/Classes/MusicPlayer.swift b/ios/Classes/MusicPlayer.swift
index 3ff597c..3878be6 100644
--- a/ios/Classes/MusicPlayer.swift
+++ b/ios/Classes/MusicPlayer.swift
@@ -7,6 +7,19 @@ import Foundation
import MediaPlayer
import SwiftAudioEx
+protocol MusicPlayerSource {
+ func getPlayUrl(mediaId: String, fallback: String?, completion: @escaping (String?) -> Void)
+
+ func loadImage(metadata: MusicMetadata, completion: @escaping (UIImage?) -> Void)
+
+ func onNextNoMoreMusic(_ queue: PlayQueue, _ mode: PlayMode, completion: @escaping (MusicMetadata?) -> Void)
+
+ func onPreviousNoMoreMusic(_ queue: PlayQueue, _ mode: PlayMode, completion: @escaping (MusicMetadata?) -> Void)
+
+ // load flutter asset resource path by player item metadata url.
+ func loadAssetResource(url: URL) -> String
+}
+
///
/// Music Player.
///
@@ -24,7 +37,7 @@ class MusicPlayer: NSObject, MusicPlayerSession {
if reason == .playedUntilEnd {
if self.playMode == .single {
self.player.seek(to: 0)
- self.player.play();
+ self.player.play()
return
}
self.skipToNext()
@@ -78,7 +91,7 @@ class MusicPlayer: NSObject, MusicPlayerSession {
private var playbackError: PlaybackError?
- private let servicePlugin = MusicPlayerServicePlugin.shared
+ var playerSource: MusicPlayerSource?
var playMode: PlayMode = .sequence {
didSet {
@@ -196,9 +209,16 @@ class MusicPlayer: NSObject, MusicPlayerSession {
completion(nil)
return
}
- servicePlugin.getPlayUrl(mediaId: metadata.mediaId, fallback: metadata.mediaUri) { url in
+
+ guard let source = playerSource else {
+ assert(false, "playerSource not avaliable.")
+ completion(nil)
+ return
+ }
+
+ source.getPlayUrl(mediaId: metadata.mediaId, fallback: metadata.mediaUri) { url in
if let url = url {
- completion(MetadataAudioItem(metadata: metadata, uri: url, servicePlugin: self.servicePlugin))
+ completion(MetadataAudioItem(metadata: metadata, uri: url, source: source))
} else {
completion(nil)
}
@@ -271,7 +291,11 @@ class MusicPlayer: NSObject, MusicPlayerSession {
if metadata != nil {
completion(metadata)
} else {
- servicePlugin.onNextNoMoreMusic(playQueue, playMode, completion: completion)
+ guard let playerSource = playerSource else {
+ completion(nil)
+ return
+ }
+ playerSource.onNextNoMoreMusic(playQueue, playMode, completion: completion)
}
}
@@ -280,7 +304,11 @@ class MusicPlayer: NSObject, MusicPlayerSession {
if metadata != nil {
completion(metadata)
} else {
- servicePlugin.onPreviousNoMoreMusic(playQueue, playMode, completion: completion)
+ guard let playerSource = playerSource else {
+ completion(nil)
+ return
+ }
+ playerSource.onPreviousNoMoreMusic(playQueue, playMode, completion: completion)
}
}
diff --git a/ios/Classes/SwiftMusicPlayerPlugin.swift b/ios/Classes/SwiftMusicPlayerPlugin.swift
index 3be4165..d9c9d51 100644
--- a/ios/Classes/SwiftMusicPlayerPlugin.swift
+++ b/ios/Classes/SwiftMusicPlayerPlugin.swift
@@ -8,11 +8,15 @@ public class SwiftMusicPlayerUiPlugin: NSObject, FlutterPlugin {
let playerCallback: MusicPlayerCallback
+ private let registrar: FlutterPluginRegistrar
+
public init(channel: FlutterMethodChannel, registrar: FlutterPluginRegistrar) {
self.channel = channel
- self.playerCallback = ChannelPlayerCallback(channel)
+ playerCallback = ChannelPlayerCallback(channel)
+ self.registrar = registrar
super.init()
player.addCallback(playerCallback)
+ player.playerSource = self
}
let player: MusicPlayer = MusicPlayer.shared
@@ -23,7 +27,6 @@ public class SwiftMusicPlayerUiPlugin: NSObject, FlutterPlugin {
let channel = FlutterMethodChannel(name: UI_CHANNEL_NAME, binaryMessenger: registrar.messenger())
let instance = SwiftMusicPlayerUiPlugin(channel: channel, registrar: registrar)
registrar.addMethodCallDelegate(instance, channel: channel)
- MusicPlayerServicePlugin.shared.start()
}
public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
@@ -99,7 +102,7 @@ private class ChannelPlayerCallback: MusicPlayerCallback {
private let methodChannel: FlutterMethodChannel
init(_ channel: FlutterMethodChannel) {
- self.methodChannel = channel
+ methodChannel = channel
}
func onPlaybackStateChanged(_ state: PlaybackState) {
@@ -119,143 +122,68 @@ private class ChannelPlayerCallback: MusicPlayerCallback {
}
}
-public class MusicPlayerServicePlugin: NSObject, FlutterPlugin {
- public static let shared = MusicPlayerServicePlugin()
-
- public static func register(with registrar: FlutterPluginRegistrar) {
- fatalError("do not call register!")
- }
-
- private let engine: FlutterEngine
-
- private var started: Bool = false
-
- private var channel: FlutterMethodChannel?
-
- public var registrar: FlutterPluginRegistrar?
-
- override private init() {
- engine = FlutterEngine(name: "player-service-engine")
- super.init()
- }
-
- func start() {
- if started {
- return
- }
- started = true
- if !engine.run(withEntrypoint: "playerBackgroundService", libraryURI: nil) {
- debugPrint("run 'playerBackgroundService' failed.")
- }
- DispatchQueue.main.async(execute: waitEngineRunning)
- }
-
- private func waitEngineRunning() {
- if engine.isolateId != nil {
- debugPrint("service engine: \(engine.isolateId ?? "nil")")
- let registrar = engine.registrar(forPlugin: String(describing: type(of: MusicPlayerServicePlugin.self)))!
- let channel = FlutterMethodChannel(name: "tech.soit.quiet/background_callback", binaryMessenger: registrar.messenger())
- registrar.addMethodCallDelegate(self, channel: channel)
- // invoke GeneratedPluginRegistrant by selector.
- if let a = NSClassFromString("GeneratedPluginRegistrant") as? NSObject.Type {
- a.perform(NSSelectorFromString("registerWithRegistry:"), with: engine as FlutterPluginRegistry)
- } else {
- debugPrint("Tried to automatically register plugins with FlutterEngine \(engine) but could not find and invoke the GeneratedPluginRegistrant.")
- }
- self.registrar = registrar
- self.channel = channel
- } else {
- DispatchQueue.main.async(execute: waitEngineRunning)
- }
- }
-
- public func handle(_ call: FlutterMethodCall, result: FlutterResult) {
- switch call.method {
- case "insertToPlayQueue":
- let arg = call.arguments as! [String: Any]
- let list = (arg["list"] as! [[String: Any]]).map { map -> MusicMetadata in
- MusicMetadata(map: map)
- }
- let index = arg["index"] as! Int
- MusicPlayer.shared.insertMetadataList(list, index)
- result(nil)
- break
- case "updateConfig":
- // TODO: config handle
- result(nil)
- break
- default:
- result(FlutterMethodNotImplemented)
- }
- }
-
+extension SwiftMusicPlayerUiPlugin: MusicPlayerSource {
func getPlayUrl(mediaId: String, fallback: String?, completion: @escaping (String?) -> Void) {
- if let channel = self.channel {
- channel.invokeMethod("getPlayUrl", arguments: ["id": mediaId, "url": fallback]) { any in
- if let result = any as? String {
- completion(result)
- } else if FlutterMethodNotImplemented.isEqual(any) {
- completion(fallback)
- } else {
- completion(nil)
- }
+ channel.invokeMethod("getPlayUrl", arguments: ["id": mediaId, "url": fallback]) { any in
+ if let result = any as? String {
+ completion(result)
+ } else if FlutterMethodNotImplemented.isEqual(any) {
+ completion(fallback)
+ } else {
+ completion(nil)
}
- } else {
- completion(fallback)
}
}
func loadImage(metadata: MusicMetadata, completion: @escaping (UIImage?) -> Void) {
- if let channel = self.channel {
- channel.invokeMethod("loadImage", arguments: metadata.toMap()) { result in
- if let result = result as? FlutterStandardTypedData {
- completion(UIImage(data: result.data))
- } else {
- completion(nil)
- }
+ channel.invokeMethod("loadImage", arguments: metadata.toMap()) { result in
+ if let result = result as? FlutterStandardTypedData {
+ completion(UIImage(data: result.data))
+ } else {
+ completion(nil)
}
- } else {
- completion(nil)
}
}
func onNextNoMoreMusic(_ queue: PlayQueue, _ mode: PlayMode, completion: @escaping (MusicMetadata?) -> Void) {
- if let channel = channel {
- channel.invokeMethod("onPlayNextNoMoreMusic", arguments: [
- "queue": queue.toMap(),
- "playMode": mode.rawValue,
- ]) { result in
- if FlutterMethodNotImplemented.isEqual(result) {
- if mode == .shuffle {
- queue.generateShuffleList()
- }
- completion(queue.getNext(nil, playMode: mode))
- } else {
- completion(MusicMetadata(any: result))
+ channel.invokeMethod("onPlayNextNoMoreMusic", arguments: [
+ "queue": queue.toMap(),
+ "playMode": mode.rawValue,
+ ]) { result in
+ if FlutterMethodNotImplemented.isEqual(result) {
+ if mode == .shuffle {
+ queue.generateShuffleList()
}
+ completion(queue.getNext(nil, playMode: mode))
+ } else {
+ completion(MusicMetadata(any: result))
}
- } else {
- completion(queue.getNext(nil, playMode: mode))
}
}
func onPreviousNoMoreMusic(_ queue: PlayQueue, _ mode: PlayMode, completion: @escaping (MusicMetadata?) -> Void) {
- if let channel = channel {
- channel.invokeMethod("onPlayPreviousNoMoreMusic", arguments: [
- "queue": queue.toMap(),
- "playMode": mode.rawValue,
- ]) { result in
- if FlutterMethodNotImplemented.isEqual(result) {
- if mode == .shuffle {
- queue.generateShuffleList()
- }
- completion(queue.getPrevious(nil, playMode: mode))
- } else {
- completion(MusicMetadata(any: result))
+ channel.invokeMethod("onPlayPreviousNoMoreMusic", arguments: [
+ "queue": queue.toMap(),
+ "playMode": mode.rawValue,
+ ]) { result in
+ if FlutterMethodNotImplemented.isEqual(result) {
+ if mode == .shuffle {
+ queue.generateShuffleList()
}
+ completion(queue.getPrevious(nil, playMode: mode))
+ } else {
+ completion(MusicMetadata(any: result))
}
- } else {
- completion(queue.getPrevious(nil, playMode: mode))
}
}
+
+ func loadAssetResource(url: URL) -> String {
+ let assetKey = registrar.lookupKey(forAsset: url.path)
+ guard let path = Bundle.main.path(forResource: assetKey, ofType: nil) else {
+ debugPrint("resource not found : \(assetKey)")
+ return url.absoluteString
+ }
+ debugPrint("resource found: \(path)")
+ return URL(fileURLWithPath: path).absoluteString
+ }
}
diff --git a/lib/music_player.dart b/lib/music_player.dart
index 45b8bdc..937a712 100644
--- a/lib/music_player.dart
+++ b/lib/music_player.dart
@@ -1,4 +1,3 @@
-export 'src/channel_service.dart';
export 'src/channel_ui.dart';
export "src/model/playback_error.dart";
export 'src/player/music_metadata.dart';
@@ -6,4 +5,5 @@ export 'src/player/play_mode.dart';
export 'src/player/play_queue.dart';
export 'src/player/playback_state.dart';
export 'src/player/transport_controls.dart';
+export 'src/utils/interceptor.dart';
export 'src/utils/progress_track_container.dart';
diff --git a/lib/src/channel_service.dart b/lib/src/channel_service.dart
deleted file mode 100644
index 8489c1b..0000000
--- a/lib/src/channel_service.dart
+++ /dev/null
@@ -1,212 +0,0 @@
-import 'package:flutter/foundation.dart';
-import 'package:flutter/services.dart';
-import 'package:flutter/widgets.dart';
-import 'package:logging/logging.dart';
-import 'package:music_player/music_player.dart';
-import 'package:music_player/src/internal/serialization.dart';
-import 'package:music_player/src/player/music_player.dart';
-
-///
-/// Interceptor when player try to load a media source
-///
-///
-/// [mediaId] The id of media. [MediaMetadata.mediaId]
-/// [fallbackUri] media origin uri.
-///
-/// @return media uri which should
-///
-typedef PlayUriInterceptor = Future Function(
- String? mediaId, String? fallbackUri);
-
-typedef ImageLoadInterceptor = Future Function(
- MusicMetadata metadata);
-
-class Config {
- final bool enableCache;
-
- final String? userAgent;
-
- /// For android only.
- /// pause player when the user has removed a task
- /// that comes from the service's application.
- final bool pauseWhenTaskRemoved;
-
- const Config({
- this.enableCache = false,
- this.userAgent,
- this.pauseWhenTaskRemoved = true,
- });
-
- Map toMap() {
- return {
- 'enableCache': enableCache,
- 'userAgent': userAgent,
- 'pauseWhenTaskRemoved': pauseWhenTaskRemoved,
- };
- }
-}
-
-///
-/// handle background callback
-///
-Future runBackgroundService({
- Config config = const Config(),
- PlayUriInterceptor? playUriInterceptor,
- ImageLoadInterceptor? imageLoadInterceptor,
- PlayQueueInterceptor? playQueueInterceptor,
-}) async {
- final log = Logger("runBackgroundService");
- WidgetsFlutterBinding.ensureInitialized();
- // decrease background image memory
- PaintingBinding.instance.imageCache.maximumSize = 20 << 20; // 20 MB
- const serviceChannel = MethodChannel("tech.soit.quiet/background_callback");
- final player = BackgroundMusicPlayer._internal(serviceChannel, MusicPlayer());
- playQueueInterceptor?._player = player;
- serviceChannel.setMethodCallHandler((call) async {
- log.fine("background: ${call.method} args = ${call.arguments}");
- switch (call.method) {
- case 'loadImage':
- if (imageLoadInterceptor != null) {
- return await imageLoadInterceptor(
- MusicMetadata.fromMap(call.arguments));
- }
- throw MissingPluginException();
- case 'getPlayUrl':
- if (playUriInterceptor != null) {
- final String? id = call.arguments['id'];
- final String? fallbackUrl = call.arguments['url'];
- return await playUriInterceptor(id, fallbackUrl);
- }
- throw MissingPluginException();
- case "onPlayNextNoMoreMusic":
- if (playQueueInterceptor != null) {
- return (await playQueueInterceptor.onPlayNextNoMoreMusic(
- createBackgroundQueue(call.arguments["queue"] as Map),
- PlayMode(call.arguments["playMode"] as int?),
- ))
- ?.toMap();
- }
- throw MissingPluginException();
- case "onPlayPreviousNoMoreMusic":
- if (playQueueInterceptor != null) {
- return (await playQueueInterceptor.onPlayPreviousNoMoreMusic(
- createBackgroundQueue(call.arguments["queue"] as Map),
- PlayMode(call.arguments["playMode"] as int?),
- ))
- .toMap();
- }
- throw MissingPluginException();
- default:
- throw MissingPluginException("can not hanle : ${call.method} ");
- }
- });
- serviceChannel.invokeMethod('updateConfig', config.toMap());
-}
-
-class BackgroundMusicPlayer extends Player {
- final MethodChannel _serviceChannel;
-
- final MusicPlayer _player;
-
- BackgroundMusicPlayer._internal(this._serviceChannel, this._player);
-
- @override
- ValueListenable get metadataListenable =>
- _player.metadataListenable;
-
- @override
- MusicPlayerValue get value => _player.value;
-
- @override
- ValueListenable get playModeListenable =>
- _player.playModeListenable;
-
- @override
- ValueListenable get playbackStateListenable =>
- _player.playbackStateListenable;
-
- @override
- ValueListenable get queueListenable => _player.queueListenable;
-
- Future insertToPlayQueue(List list, int index) async {
- assert(() {
- if (index < 0 || index > value.queue.queue.length) {
- throw RangeError.range(index, 0, value.queue.queue.length);
- }
- return true;
- }());
- await _serviceChannel.invokeMethod("insertToPlayQueue", {
- "index": index,
- "list": list.map((e) => e.toMap()).toList(),
- });
- }
-}
-
-abstract class BackgroundPlayerCallback {
- void onPlaybackStateChanged(
- BackgroundMusicPlayer player, PlaybackState state);
-
- void onMetadataChange(BackgroundMusicPlayer player, MusicMetadata metadata);
-
- void onPlayQueueChanged(BackgroundMusicPlayer player, PlayQueue queue);
-
- void onPlayModeChanged(BackgroundMusicPlayer player, PlayMode playMode);
-}
-
-class PlayQueueInterceptor {
- void noImplement() {
- throw MissingPluginException();
- }
-
- BackgroundMusicPlayer? _player;
-
- BackgroundMusicPlayer? get player => _player;
-
- Future onPlayNextNoMoreMusic(
- BackgroundPlayQueue queue, PlayMode playMode) async {
- final list = await fetchMoreMusic(queue, playMode);
- debugPrint("fetched : $list");
- if (list.isNotEmpty) {
- await player!.insertToPlayQueue(list, player!.value.queue.queue.length);
- return list.first;
- } else {
- return null;
- }
- }
-
- ///
- /// Throw MissingPluginException() to use default playNext behavior.
- /// Default Behavior:
- /// 1. playMode is [PlayMode.sequence], auto play queue first item
- /// 2. playMode is [PlayMode.shuffle], auto generate a new shuffle list, then play from first.
- /// 3. playMode is [PlayMode.undefined(any)]. stop play.
- ///
- /// return null to stop play.
- ///
- Future> fetchMoreMusic(
- BackgroundPlayQueue queue, PlayMode playMode) {
- throw MissingPluginException();
- }
-
- Future onPlayPreviousNoMoreMusic(
- BackgroundPlayQueue queue, PlayMode playMode) {
- throw MissingPluginException();
- }
-}
-
-class BackgroundPlayQueue extends PlayQueue {
- /// shuffle mediaId list for shuffle playMode
- final List shuffleQueue;
-
- BackgroundPlayQueue({
- required this.shuffleQueue,
- required String queueId,
- required String queueTitle,
- Map? extras,
- required List queue,
- }) : super(
- queueId: queueId,
- queueTitle: queueTitle,
- extras: extras,
- queue: queue);
-}
diff --git a/lib/src/channel_ui.dart b/lib/src/channel_ui.dart
index d50acf5..7c0f96a 100644
--- a/lib/src/channel_ui.dart
+++ b/lib/src/channel_ui.dart
@@ -58,6 +58,10 @@ class MusicPlayer extends Player {
@override
ValueListenable get metadataListenable => _metadata;
+ PlayUriInterceptor? playUriInterceptor;
+ ImageLoadInterceptor? imageLoadInterceptor;
+ PlayQueueInterceptor? playQueueInterceptor;
+
final ValueNotifier _queue =
ValueNotifier(const PlayQueue.empty());
final ValueNotifier _playbackState =
@@ -82,6 +86,38 @@ class MusicPlayer extends Player {
case 'onPlayModeChanged':
_playMode.value = PlayMode(call.arguments as int?);
break;
+ case 'loadImage':
+ if (imageLoadInterceptor != null) {
+ return await imageLoadInterceptor!(
+ MusicMetadata.fromMap(call.arguments),
+ );
+ }
+ throw MissingPluginException();
+ case 'getPlayUrl':
+ if (playUriInterceptor != null) {
+ final String? id = call.arguments['id'];
+ final String? fallbackUrl = call.arguments['url'];
+ return await playUriInterceptor!(id, fallbackUrl);
+ }
+ throw MissingPluginException();
+ case "onPlayNextNoMoreMusic":
+ if (playQueueInterceptor != null) {
+ return await playQueueInterceptor!
+ .onPlayNextNoMoreMusic(
+ PlayMode(call.arguments["playMode"] as int?),
+ )
+ .toMap();
+ }
+ throw MissingPluginException();
+ case "onPlayPreviousNoMoreMusic":
+ if (playQueueInterceptor != null) {
+ return playQueueInterceptor!
+ .onPlayPreviousNoMoreMusic(
+ PlayMode(call.arguments["playMode"] as int?),
+ )
+ .toMap();
+ }
+ throw MissingPluginException();
default:
throw UnimplementedError();
}
@@ -143,3 +179,9 @@ class MusicPlayerValue {
return _empty;
}
}
+
+extension _ToMap on Future {
+ Future