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
42 changes: 40 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,46 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.dart_tool/
.atom/
.build/
.buildlog/
.history
.svn/
.swiftpm/
migrate_working_dir/

# IntelliJ related
*.iml
*.ipr
*.iws
.idea/

# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
.vscode/

# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/

# Symbolication related
app.*.symbols

# Obfuscation related
app.*.map.json

build/
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release
Empty file removed .idea/.gitignore
Empty file.
8 changes: 0 additions & 8 deletions .idea/modules.xml

This file was deleted.

9 changes: 0 additions & 9 deletions .idea/nowplaying.iml

This file was deleted.

6 changes: 0 additions & 6 deletions .idea/vcs.xml

This file was deleted.

63 changes: 0 additions & 63 deletions .idea/workspace.xml

This file was deleted.

116 changes: 67 additions & 49 deletions ios/Classes/SwiftNowPlayingPlugin.swift
Original file line number Diff line number Diff line change
@@ -1,58 +1,76 @@
import Flutter
import UIKit
import MediaPlayer
import UIKit

public class SwiftNowPlayingPlugin: NSObject, FlutterPlugin {
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "gomes.com.es/nowplaying", binaryMessenger: registrar.messenger())
let instance = SwiftNowPlayingPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}

var trackData: [String: Any?] = [:]
let imageSize: CGSize = CGSize(width: 400, height: 400)

enum ImageError: Error {
case notPresent(artwork: MPMediaItemArtwork)
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "track":
let musicPlayer = MPMusicPlayerController.systemMusicPlayer
if let nowPlayingItem = musicPlayer.nowPlayingItem {
let id = "\(nowPlayingItem.title ?? ""):\(nowPlayingItem.artist ?? ""):\(nowPlayingItem.albumTitle ?? "")"
if trackData["id"] == nil || (trackData["id"] as! String) != id {
trackData["id"] = id
trackData["album"] = nowPlayingItem.albumTitle
trackData["title"] = nowPlayingItem.title
trackData["artist"] = nowPlayingItem.artist
trackData["genre"] = nowPlayingItem.genre
trackData["duration"] = Int(nowPlayingItem.playbackDuration * 1000)
trackData["image"] = nowPlayingItem.artwork?.image(at: imageSize)?.pngData()
trackData["source"] = "com.apple.music"
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "gomes.com.es/nowplaying", binaryMessenger: registrar.messenger())
let instance = SwiftNowPlayingPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}

var trackData = [String: Any?]()
let imageSize = CGSize(width: 400, height: 400)

enum ImageError: Error {
case notPresent(artwork: MPMediaItemArtwork)
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
switch call.method {
case "track":
let musicPlayer = MPMusicPlayerController.systemMusicPlayer
if let nowPlayingItem = musicPlayer.nowPlayingItem {
let id = "\(nowPlayingItem.title ?? ""):\(nowPlayingItem.artist ?? ""):\(nowPlayingItem.albumTitle ?? "")"
if trackData["id"] == nil || (trackData["id"] as! String) != id {
trackData["id"] = id
trackData["album"] = nowPlayingItem.albumTitle
trackData["title"] = nowPlayingItem.title
trackData["artist"] = nowPlayingItem.artist
trackData["genre"] = nowPlayingItem.genre
trackData["duration"] = Int(nowPlayingItem.playbackDuration * 1000)
trackData["image"] =
if let artwork = nowPlayingItem.artwork {
artwork.image(at: artwork.bounds.size)?.pngData()
} else {
nil
}
trackData["source"] = "com.apple.music"
}

trackData["position"] = Int(musicPlayer.currentPlaybackTime * 1000)

switch musicPlayer.playbackState {
case MPMusicPlaybackState.playing, MPMusicPlaybackState.seekingForward, MPMusicPlaybackState.seekingBackward:
trackData["state"] = 0
case MPMusicPlaybackState.paused, MPMusicPlaybackState.interrupted:
trackData["state"] = 1
case MPMusicPlaybackState.stopped:
trackData["state"] = 2
default:
trackData["state"] = 2
}
} else {
trackData = [:]
}

trackData["position"] = Int(musicPlayer.currentPlaybackTime * 1000)

switch musicPlayer.playbackState {
case MPMusicPlaybackState.playing, MPMusicPlaybackState.seekingForward, MPMusicPlaybackState.seekingBackward:
trackData["state"] = 0
case MPMusicPlaybackState.paused, MPMusicPlaybackState.interrupted:
trackData["state"] = 1
case MPMusicPlaybackState.stopped:
trackData["state"] = 2
default:
trackData["state"] = 2
result(trackData)

case "isEnabled":
let status = MPMediaLibrary.authorizationStatus()
result(status == .authorized)

case "requestPermissions":
let status = MPMediaLibrary.authorizationStatus()
if status == .notDetermined {
MPMediaLibrary.requestAuthorization { _ in }
} else {
UIApplication.shared.open(URL(string: UIApplication.openSettingsURLString)!)
}
} else {
trackData = [:]
}

result(trackData)
break;
default:
result(FlutterMethodNotImplemented)
result(true)

default:
result(FlutterMethodNotImplemented)
}
}
}
}
21 changes: 7 additions & 14 deletions lib/nowplaying.dart
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,12 @@ class NowPlaying with WidgetsBindingObserver {
_controller.add(track);

this._resolveImages = resolver != null || resolveImages;
this.resolver =
resolver ?? (_resolveImages ? DefaultNowPlayingImageResolver() : null);
this.resolver = resolver ?? (_resolveImages ? DefaultNowPlayingImageResolver() : null);

final prefs = await SharedPreferences.getInstance();
NowPlaying.spotify.setPrefs(prefs);
if (spotifyClientId is String && spotifyClientSecret is String) {
NowPlaying.spotify.setCredentials(
clientId: spotifyClientId, clientSecret: spotifyClientSecret);
NowPlaying.spotify.setCredentials(clientId: spotifyClientId, clientSecret: spotifyClientSecret);
}

_bindToWidgetsBinding();
Expand Down Expand Up @@ -111,26 +109,21 @@ class NowPlaying with WidgetsBindingObserver {
_controller.add(this.track);
}

/// Returns true is the service has permission granted by the systme and user
/// Returns true if the service has permission granted by the system and user
Future<bool> isEnabled() async {
return isIOS || (await _channel.invokeMethod<bool>('isEnabled') ?? false);
return await _channel.invokeMethod<bool>('isEnabled') ?? false;
}

/// Opens an OS settings page
///
/// Returns true if:
/// - OS is iOS, or
/// - permission has already been given, or
/// - the settings screen has not been opened by this app before, or
/// - opening the screen this time is `force`d
/// - parameter [force] is true, or
/// - the settings screen has not been opened by this app before
///
/// Returns false if:
/// - OS is Android, and
/// - permission has not been given by the user, and
/// - parameter [force] is false, and
/// - the settings screen has been opened by this app before
Future<bool> requestPermissions({bool force = false}) async {
if (isIOS) return true;

final directory = await getApplicationDocumentsDirectory();
final file = File('${directory.path}/com.gomes.nowplaying');
if (!force && await file.exists()) return false;
Expand Down
28 changes: 9 additions & 19 deletions lib/nowplaying_track.dart
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ class NowPlayingTrack {
static final _essentialRegExp = RegExp(r'\(.*\)|\[.*\]');

static final _images = _LruMap<String, ImageProvider?>(size: 3);
static final _resolutionStates =
_LruMap<String, _NowPlayingImageResolutionState?>(size: 3);
static final _resolutionStates = _LruMap<String, _NowPlayingImageResolutionState?>(size: 3);
static final _icons = _LruMap<String?, ImageProvider>();

final String id;
Expand Down Expand Up @@ -53,10 +52,8 @@ class NowPlayingTrack {

/// An image representing the app playing the track
ImageProvider? get icon {
if (isIOS)
return const AssetImage('assets/applemusic.png', package: 'nowplaying');
if (source == 'com.acmeandroid.listen')
return const AssetImage('assets/listenapp.png', package: 'nowplaying');
if (isIOS) return const AssetImage('assets/applemusic.png', package: 'nowplaying');
if (source == 'com.acmeandroid.listen') return const AssetImage('assets/listenapp.png', package: 'nowplaying');
return _icons[this.source];
}

Expand All @@ -67,21 +64,16 @@ class NowPlayingTrack {
bool get hasImage => image != null;

/// true if the image is being resolved, else false
bool get isResolvingImage =>
_resolutionState == _NowPlayingImageResolutionState.resolving;
bool get isResolvingImage => _resolutionState == _NowPlayingImageResolutionState.resolving;

/// true if the image is empty and a resolution hasn't been attempted, else false
bool get imageNeedsResolving =>
_resolutionState == _NowPlayingImageResolutionState.unresolved;
bool get imageNeedsResolving => _resolutionState == _NowPlayingImageResolutionState.unresolved;

String get _imageId => '$artist:$album';

@override
operator ==(other) =>
other is NowPlayingTrack &&
other.id == this.id &&
other.progress == this.progress &&
other.state == this.state;
other is NowPlayingTrack && other.id == this.id && other.progress == this.progress && other.state == this.state;

/// The image for the track, probably album art
///
Expand All @@ -90,10 +82,8 @@ class NowPlayingTrack {
ImageProvider? get image => _images[_imageId];
set image(ImageProvider? image) => _images[_imageId] = image;

_NowPlayingImageResolutionState? get _resolutionState =>
_resolutionStates[_imageId];
set _resolutionState(_NowPlayingImageResolutionState? state) =>
_resolutionStates[_imageId] = state;
_NowPlayingImageResolutionState? get _resolutionState => _resolutionStates[_imageId];
set _resolutionState(_NowPlayingImageResolutionState? state) => _resolutionStates[_imageId] = state;

NowPlayingTrack({
String? id,
Expand Down Expand Up @@ -183,7 +173,7 @@ class NowPlayingTrack {
Future<void> resolveImage() async {
if (imageNeedsResolving && !hasImage) {
_resolutionState = _NowPlayingImageResolutionState.resolving;
this.image = await NowPlaying.instance.resolver.resolve(this);
this.image = await NowPlaying.instance.resolver?.resolve(this);
_resolutionState = _NowPlayingImageResolutionState.resolved;
}
}
Expand Down