iTunes im Notification Center – mit Album Cover!

Ich bin mal so frei gewesen und hab Spotify-Support hinzugefügt. Den letzten Abschnitt aus adjustIconForAlbumArtworkOfCurrentTrack habe ich in updateTrackInfo_ verschoben. Womit latestTrackFlag entfällt.

NC wird nicht mehr ständig neugestartet solange sich das Album nicht ändert. Außerdem überprüft das Skript zu Beginn, ob iTunes bzw. Spotify bereits im Vordergrund sind, und verzichtet in diesem Fall auf eine Benachrichtigung.

Code:
-- main.scpt
-- Cocoa-AppleScript Applet
--
--	 ----------------------------------------------------------------------			  			    
--												   
--	   OS X Mountain Lion song notifications with album cover art support
--											          
--	 ----------------------------------------------------------------------
--
--   	 v.pre-2012082501
--		Latest changes:
--			- Adds support for Spotify
-- 			- Notifications are silenced if the music player is currently the frontmost app 
--			- NotificationCenter process is not restarted anymore when tracks from the same album are played
--			- solves issues that occur when the user is rapidly changing tracks
--
--

global currentTrackName, currentTrackAlbum, currentTrackArtist, currentTrackArtworkCount
property latestAlbum : ""

set dnc to current application's NSDistributedNotificationCenter's defaultCenter
tell dnc to addObserver_selector_name_object_(me, "updateTrackInfo:", "com.apple.iTunes.playerInfo", missing value)
tell dnc to addObserver_selector_name_object_(me, "updateTrackInfo:", "com.spotify.client.PlaybackStateChanged", missing value)

on updateTrackInfo_(aNotification)
	tell current application's NSDictionary to set trackInfo to alloc()'s initWithDictionary_(aNotification's userInfo)
	tell application "System Events" to set frontmostApp to name of the first process whose frontmost is true
	if aNotification's |name| as text = "com.apple.iTunes.playerInfo" then
		set notifier to "iTunes"
	else if aNotification's |name| as text = "com.spotify.client.PlaybackStateChanged" then
		set notifier to "Spotify"
	end if
	if (the frontmostApp is the notifier) or (trackInfo's valueForKey_("Player State") as text) is not "playing" then return
	set currentTrackAlbum to trackInfo's valueForKey_("Album") as text
	set currentTrackArtist to trackInfo's valueForKey_("Artist") as text
	set currentTrackName to trackInfo's valueForKey_("Name") as text
	set albumHasChanged to false
	
	-- adjust app icon if the album  has changed
	if currentTrackAlbum ≠ latestAlbum then
		set albumHasChanged to true
		if notifier = "iTunes" then
			set currentTrackArtworkCount to trackInfo's valueForKey_("Artwork Count") as integer
		else if notifier = "Spotify" then
			set currentTrackArtworkCount to trackInfo's valueForKey_("Has Artwork") as integer
		end if
		adjustIconForAlbumArtworkOfCurrentTrackPlayingOn(notifier)
		
		-- only continue if the track that triggered the notification is still the latest one
		if notifier = "iTunes" then
			tell application "iTunes" to set actualCurrentTrackName to (the name of the current track)
		else if notifier = "Spotify" then
			tell application "Spotify" to set actualCurrentTrackName to (the name of the current track)
		end if
		if actualCurrentTrackName ≠ currentTrackName then return
		set latestAlbum to currentTrackAlbum
	end if
	
	
	-- (have to) restart NotificationCenter for the changed Icon to be registered
	if (albumHasChanged) then
		do shell script "killAll NotificationCenter;"
		delay 0.5
		repeat while (appIsRunning("NotificationCenter") is false)
			delay 0.2
		end repeat
	end if
	
	sendMessage(currentTrackName, currentTrackArtist, currentTrackAlbum)
	
end updateTrackInfo_



on adjustIconForAlbumArtworkOfCurrentTrackPlayingOn(appName)
	set thePath to current application's NSBundle's mainBundle's resourcePath as text
	set thefile to thePath & "/Icon.png"
	
	if currentTrackArtworkCount ≠ 0 then
		if (appName = "iTunes") then
			tell application "iTunes" to set currentTrackArtwork to raw data of artwork 1 of current track
		else if (appName = "Spotify") then
			tell application "Spotify" to set currentTrackArtwork to artwork of current track
		end if
		
		try
			set thefilehandle to open for access (thefile) with write permission
			write currentTrackArtwork to thefilehandle starting at 0
			close access thefilehandle
		on error err
			close access thefilehandle
		end try
		do shell script "sips --resampleHeightWidth 128 128 " & quoted form of thefile & " -o " & quoted form of thefile & "; sips -s format icns " & quoted form of thefile & " -o '" & thePath & "/Applet.icns';"
	else
		do shell script "rm '" & thePath & "/Applet.icns'; cp '" & thePath & "/Applet2.icns' '" & thePath & "/Applet.icns'"
	end if
end adjustIconForAlbumArtworkOfCurrentTrackPlayingOn


-- dispatch a message to ML's NC
on sendMessage(myTitle, mySubtitle, myContent)
	tell current application's NSUserNotification to set thisNote to alloc()'s init()
	set thisNote's title to myTitle
	set thisNote's subtitle to mySubtitle
	set thisNote's informativeText to myContent
	tell current application's NSUserNotificationCenter's defaultUserNotificationCenter to deliverNotification_(thisNote)
end sendMessage

-- check whether a process exists
on appIsRunning(appName)
	tell application "System Events" to (name of processes) contains appName
end appIsRunning
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Pill
Super!

Hier noch der Link zum fertigen Programm:

http://cl.ly/1w3X3O2d1T35

PS: Ich habe noch geändert, dass fehlende Track-Informationen durch einen Bindestrich an Stelle von missing value angezeigt werden:

Code:
-- main.scpt
-- Cocoa-AppleScript Applet
--
--	 ----------------------------------------------------------------------			  			    
--												   
--	   OS X Mountain Lion song notifications with album cover art support
--											          
--	 ----------------------------------------------------------------------
--
--   	 v.pre-2012082501
--		Latest changes:
--			- Adds support for Spotify
-- 			- Notifications are silenced if the music player is currently the frontmost app 
--			- NotificationCenter process is not restarted anymore when tracks from the same album are played
--			- solves issues that occur when the user is rapidly changing tracks
--

global currentTrackName, currentTrackAlbum, currentTrackArtist, currentTrackArtworkCount
property latestAlbum : ""

set dnc to current application's NSDistributedNotificationCenter's defaultCenter
tell dnc to addObserver_selector_name_object_(me, "updateTrackInfo:", "com.apple.iTunes.playerInfo", missing value)
tell dnc to addObserver_selector_name_object_(me, "updateTrackInfo:", "com.spotify.client.PlaybackStateChanged", missing value)

on updateTrackInfo_(aNotification)
	tell current application's NSDictionary to set trackInfo to alloc()'s initWithDictionary_(aNotification's userInfo)
	tell application "System Events" to set frontmostApp to name of the first process whose frontmost is true
	if aNotification's |name| as text = "com.apple.iTunes.playerInfo" then
		set notifier to "iTunes"
	else if aNotification's |name| as text = "com.spotify.client.PlaybackStateChanged" then
		set notifier to "Spotify"
	end if
	if (the frontmostApp is the notifier) or (trackInfo's valueForKey_("Player State") as text) is not "playing" then return
	set currentTrackAlbum to trackInfo's valueForKey_("Album") as text
	set currentTrackArtist to trackInfo's valueForKey_("Artist") as text
	set currentTrackName to trackInfo's valueForKey_("Name") as text
	set albumHasChanged to false
	--substitute "missing value" with "-"
	if currentTrackAlbum is "missing value" then set currentTrackAlbum to "–"
	if currentTrackArtist is "missing value" then set currentTrackArtist to "–"
	if currentTrackName is "missing value" then set currentTrackName to "–"
	
	-- adjust app icon if the album  has changed
	if currentTrackAlbum ≠ latestAlbum then
		set albumHasChanged to true
		if notifier = "iTunes" then
			set currentTrackArtworkCount to trackInfo's valueForKey_("Artwork Count") as integer
		else if notifier = "Spotify" then
			set currentTrackArtworkCount to trackInfo's valueForKey_("Has Artwork") as integer
		end if
		adjustIconForAlbumArtworkOfCurrentTrackPlayingOn(notifier)
		
		-- only continue if the track that triggered the notification is still the latest one
		if notifier = "iTunes" then
			tell application "iTunes" to set actualCurrentTrackName to (the name of the current track)
		else if notifier = "Spotify" then
			tell application "Spotify" to set actualCurrentTrackName to (the name of the current track)
		end if
		if actualCurrentTrackName ≠ currentTrackName then return
		set latestAlbum to currentTrackAlbum
	end if
	
	
	-- (have to) restart NotificationCenter for the changed Icon to be registered
	if (albumHasChanged) then
		do shell script "killAll NotificationCenter;"
		delay 0.5
		repeat while (appIsRunning("NotificationCenter") is false)
			delay 0.2
		end repeat
	end if
	
	sendMessage(currentTrackName, currentTrackArtist, currentTrackAlbum)
	
end updateTrackInfo_

on adjustIconForAlbumArtworkOfCurrentTrackPlayingOn(appName)
	set thePath to current application's NSBundle's mainBundle's resourcePath as text
	set thefile to thePath & "/Icon.png"
	if currentTrackArtworkCount ≠ 0 then
		if (appName = "iTunes") then
			tell application "iTunes" to set currentTrackArtwork to raw data of artwork 1 of current track
		else if (appName = "Spotify") then
			tell application "Spotify" to set currentTrackArtwork to artwork of current track
		end if
		try
			set thefilehandle to open for access (thefile) with write permission
			write currentTrackArtwork to thefilehandle starting at 0
			close access thefilehandle
		on error err
			close access thefilehandle
		end try
		do shell script "sips --resampleHeightWidth 128 128 " & quoted form of thefile & " -o " & quoted form of thefile & "; sips -s format icns " & quoted form of thefile & " -o '" & thePath & "/Applet.icns';"
	else
		do shell script "rm '" & thePath & "/Applet.icns'; cp '" & thePath & "/Applet2.icns' '" & thePath & "/Applet.icns'"
	end if
end adjustIconForAlbumArtworkOfCurrentTrackPlayingOn

-- dispatch a message to ML's NC
on sendMessage(myTitle, mySubtitle, myContent)
	tell current application's NSUserNotification to set thisNote to alloc()'s init()
	set thisNote's title to myTitle
	set thisNote's subtitle to mySubtitle
	set thisNote's informativeText to myContent
	tell current application's NSUserNotificationCenter's defaultUserNotificationCenter to deliverNotification_(thisNote)
end sendMessage

-- check whether a process exists
on appIsRunning(appName)
	tell application "System Events" to (name of processes) contains appName
end appIsRunning
 
Zuletzt bearbeitet:
Hallo!
Super Thread!
Tolles Projekt mit einer coolen Ausgangsidee und jede Menge Futter für Leute wie mich, die bisher nur mal die Tür zu ASOC geöffnet hatten, um zu sehen, wie es dahinter aussieht!

Ok. Eine Kleinigkeit kann ich auch beisteuern. Nichts bewegendes, nur zum Abrunden:

Code:
...
do shell script "killAll NotificationCenter;"
delay 0.5
repeat while (appIsRunning("NotificationCenter") is false)
delay 0.2
end repeat
[COLOR="#008080"]do shell script "killAll usernoted"[/COLOR]
...
Aktualisiert das Icon in den Systemeinstellungen-Benachrichtigungen...

Dann eine Verständnis-Frage. ASOC Apps können doch nicht nur Notifikationen lesen sondern auch abschicken. Wenn man das App im (System-)Notification Center registriert und so etwas wie "set theIcon to theApp's icon()" festlegt, besteht doch die Möglichkeit, dass die Mitteilungszentrale das abgreift, oder liege ich da falsch?

Gruß
 
Zuletzt bearbeitet:
PS: Ich habe noch geändert, dass fehlende Track-Informationen durch einen Bindestrich an Stelle von missing value angezeigt werden:

Oh, sehr gut! Meine iTunes-Tags sind zu vollständig um das zu bemerken... :p

do shell script "killAll usernoted"
Aktualisiert das Icon in den Systemeinstellungen-Benachrichtigungen...
Bei mir scheint das auch jetzt schon aktualisiert zu werden (wenn ich die Systemeinstellungen neu öffne)? Bei Dir nicht?


ASOC Apps können doch nicht nur Notifikationen lesen sondern auch abschicken. Wenn man das App im (System-)Notification Center registriert und so etwas wie "set theIcon to theApp's icon()" festlegt, besteht doch die Möglichkeit, dass die Mitteilungszentrale das abgreift, oder liege ich da falsch?
NC beobachtet ja denke ich nur Nachrichten mit Namen für die es sich auch registriert hat? Fürchte, (wenn ich Dich richtig verstehe) Du wirst dem Prozess da nicht irgendwas zur Ausführung unterjubeln können, was nicht bereits vorgesehen ist. Wäre natürlich toll, wenn sich eine Möglichkeit fände, NC nie neustarten zu müssen.

--

Die Überprüfung darauf ob der Titel noch aktuell ist, scheint übrigens doch überflüssig zu sein (mit dem Verschieben des "killall" Skripts, der latestAlbum-Variable und der zusätzlichen Delays scheint nach meinen Tests alles im Prinzip so zu funktionieren wie man es erwartet). Die albumHasChanged-Variable ist auch überflüssig. Außerdem kann man sich noch die eine if-Abfrage sparen, indem man direkt in der ersten Abfrage bzgl. des "notifier" das artworkKey in einer Variable speichert und später nutzt:

Code:
on updateTrackInfo_(aNotification)
	tell current application's NSDictionary to set trackInfo to alloc()'s initWithDictionary_(aNotification's userInfo)
	tell application "System Events" to set frontmostApp to name of the first process whose frontmost is true
	if aNotification's |name| as text = "com.apple.iTunes.playerInfo" then
		set notifier to "iTunes"
		set artworkKey to "Artwork Count"
	else
		set notifier to "Spotify"
		set artworkKey to "Has Artwork"
	end if
	if (the frontmostApp is the notifier) or (trackInfo's valueForKey_("Player State") as text) is not "playing" then return
	set currentTrackAlbum to trackInfo's valueForKey_("Album") as text
	set currentTrackArtist to trackInfo's valueForKey_("Artist") as text
	set currentTrackName to trackInfo's valueForKey_("Name") as text
	--substitute "missing value" with "-"
	if currentTrackAlbum is "missing value" then set currentTrackAlbum to "–"
	if currentTrackArtist is "missing value" then set currentTrackArtist to "–"
	if currentTrackName is "missing value" then set currentTrackName to "–"

	-- adjust app icon if the album  has changed, restart NC to make it notice the change, then wait for NC to become alive
	if currentTrackAlbum ≠ latestAlbum then
		set currentTrackArtworkCount to trackInfo's valueForKey_(artworkKey) as integer
		adjustIconForAlbumArtworkOfCurrentTrackPlayingOn(notifier)
		set latestAlbum to currentTrackAlbum
		do shell script "killAll NotificationCenter;"
		delay 0.5
		repeat while (appIsRunning("NotificationCenter") is false)
			delay 0.2
		end repeat
	end if
	
	sendMessage(currentTrackName, currentTrackArtist, currentTrackAlbum)
end updateTrackInfo_
 
Zuletzt bearbeitet:
Ich habe das Ändern des Icons jetzt in ASOC umgesetzt. Außerdem werden jetzt die Meldungen aus dem NC gelöscht, wenn keine Musik wiedergegeben wird:

http://cl.ly/3V0047091j3m

Code:
-- main.scpt
-- Cocoa-AppleScript Applet
--
--	 ----------------------------------------------------------------------			  			    
--												   
--	   OS X Mountain Lion song notifications with album cover art support
--											          
--	 ----------------------------------------------------------------------
--
--   	 v.pre-2012082501
--		Latest changes:
--			- when paused, delivered notifications will be deleted
--			- Image to icon via ASOC
--			- Adds support for Spotify
-- 			- Notifications are silenced if the music player is currently the frontmost app 
--			- NotificationCenter process is not restarted anymore when tracks from the same album are played
--			- solves issues that occur when the user is rapidly changing tracks
--

global currentTrackName, currentTrackAlbum, currentTrackArtist, currentTrackArtworkCount
property latestAlbum : ""

set dnc to current application's NSDistributedNotificationCenter's defaultCenter
tell dnc to addObserver_selector_name_object_(me, "updateTrackInfo:", "com.apple.iTunes.playerInfo", missing value)
tell dnc to addObserver_selector_name_object_(me, "updateTrackInfo:", "com.spotify.client.PlaybackStateChanged", missing value)

on updateTrackInfo_(aNotification)
	tell current application's NSDictionary to set trackInfo to alloc()'s initWithDictionary_(aNotification's userInfo)
	tell application "System Events" to set frontmostApp to name of the first process whose frontmost is true
	if aNotification's |name| as text = "com.apple.iTunes.playerInfo" then
		set notifier to "iTunes"
		set artworkKey to "Artwork Count"
	else
		set notifier to "Spotify"
		set artworkKey to "Has Artwork"
	end if
	if (the frontmostApp is the notifier) or (trackInfo's valueForKey_("Player State") as text) is not "playing" then
		--if not playing, delete delivered notifications
		tell current application's NSUserNotificationCenter's defaultUserNotificationCenter to removeAllDeliveredNotifications()
		return
	end if
	set currentTrackAlbum to trackInfo's valueForKey_("Album") as text
	set currentTrackArtist to trackInfo's valueForKey_("Artist") as text
	set currentTrackName to trackInfo's valueForKey_("Name") as text
	--substitute "missing value" with "-"
	if currentTrackAlbum is "missing value" then set currentTrackAlbum to "–"
	if currentTrackArtist is "missing value" then set currentTrackArtist to "–"
	if currentTrackName is "missing value" then set currentTrackName to "–"
	
	-- adjust app icon if the album  has changed, restart NC to make it notice the change, then wait for NC to become alive
	if currentTrackAlbum ≠ latestAlbum then
		set currentTrackArtworkCount to trackInfo's valueForKey_(artworkKey) as integer
		adjustIconForAlbumArtworkOfCurrentTrackPlayingOn(notifier)
		set latestAlbum to currentTrackAlbum
		do shell script "killAll NotificationCenter;"
		delay 0.5
		repeat while (appIsRunning("NotificationCenter") is false)
			delay 0.2
		end repeat
	end if
	
	sendMessage(currentTrackName, currentTrackArtist, currentTrackAlbum)
end updateTrackInfo_

on adjustIconForAlbumArtworkOfCurrentTrackPlayingOn(appName)
	set thePathToResource to current application's NSBundle's mainBundle's resourcePath as text
	set thePath to POSIX path of (path to current application)
	set theFile to thePathToResource & "/Icon.png"
	set theStandardFile to thePathToResource & "/Applet2.icns"
	if currentTrackArtworkCount ≠ 0 then
		if (appName = "iTunes") then
			tell application "iTunes" to set currentTrackArtwork to raw data of artwork 1 of current track
		else if (appName = "Spotify") then
			tell application "Spotify" to set currentTrackArtwork to artwork of current track
		end if
		try
			set theFilehandle to open for access (theFile) with write permission
			write currentTrackArtwork to theFilehandle starting at 0
			close access theFilehandle
		on error err
			close access theFilehandle
		end try
		setImageToIcon(theFile, thePath)
	else
		setImageToIcon(theStandardFile, thePath)
	end if
end adjustIconForAlbumArtworkOfCurrentTrackPlayingOn

-- dispatch a message to ML's NC
on sendMessage(myTitle, mySubtitle, myContent)
	tell current application's NSUserNotification to set thisNote to alloc()'s init()
	set thisNote's title to myTitle
	set thisNote's subtitle to mySubtitle
	set thisNote's informativeText to myContent
	tell current application's NSUserNotificationCenter's defaultUserNotificationCenter to deliverNotification_(thisNote)
end sendMessage

-- check whether a process exists
on appIsRunning(appName)
	tell application "System Events" to (name of processes) contains appName
end appIsRunning

-- assign an icon to a file
on setImageToIcon(theImage, theFile)
	set mySharedWorkspace to current application's NSWorkspace's sharedWorkspace()
	set theIcon to current application's NSImage's alloc's initWithContentsOfFile_(theImage)
	return mySharedWorkspace's setIcon_forFile_options_(theIcon, theFile, 0) as boolean
end setImageToIcon
 
  • Gefällt mir
Reaktionen: kermitd
Super! Damit jetzt allerdings wieder Probleme mit nicht mehr aktuellen Notifications bzw. Icons (beim schnellen Trackwechseln)... :D

Und der RAM-Verbrauch schnellt in die Höhe...sind Applets garbage collected in 10.8?
 
Zuletzt bearbeitet:
sind Applets garbage collected in 10.8?
Wenn dann ARC. Habe gerade mal in der Doku nachgesehen, aber die schweigt sich dazu aus.
Sende doch den instanzierten Dingern mal nen -release bzw. autorelease und beobachte das Verhalten.

Viele Grüße
 
(beim schnellen Trackwechseln)
Das lässt sich "leicht" lösen.

Nimm einen NSTimer, den Du beim Titelwechsel erzeugst und nach z.B. einer Sekunde feuerst.
Kommt innerhalb dieser einen Sekunde wieder ein Titelwechsel, dann brichst Du den Timer ab und legst einen neuen an.

Somit "glättet" man die schnellen Titelwechsel und zeigt dann erst die Info an, wenn der Titel auch wirklich angespielt wird.

Viele Grüße
 
Was mir noch einfällt:

Ihr manipuliert das Icon der App um das Cover entsprechend anzuzeigen und startet dann immer das Notification Center neu.

Probiert mal das:

NSApplication -setApplicationIconImage:

… damit müsste das doch alles wegfallen.

Nur eine Vermutung.

Viele Grüße
 
Bei mir scheint das auch jetzt schon aktualisiert zu werden (wenn ich die Systemeinstellungen neu öffne)? Bei Dir nicht?
Ja ... so würde es ohne erneutes Öffnen aktualisiert werden..

Danke auch für deine Erklärung. Ich beginne langsam, etwas klarer zu sehen.
 
Zuletzt bearbeitet:
Wenn dann ARC. Habe gerade mal in der Doku nachgesehen, aber die schweigt sich dazu aus.
Sende doch den instanzierten Dingern mal nen -release bzw. autorelease und beobachte das Verhalten.

Wenn ich das richtig sehe, sind release-Statements wirkungslos in Applets. Oder es gibt da einen Trick bzgl. der Syntax. ARC scheint auch nicht eingesetzt zu werden. Das selbe Applet wird offenbar Garbage collected wenn es unter 10.6 ausgeführt wird, wohingegen unter 10.8 weder die GC läuft (oder erst spät einsetzt? EDIT: Also irgendeine Form von automatischem Memory-Management muss da doch laufen, setzt nur für meinen Geschmack ziemlich spät ein. Die Notifier-App scheint bei etwa 100MB gehalten zu werden, wenn sie erstmal dort gelandet ist), noch es eine Möglichkeit zum manuellen Management gibt?! Dabei beschränkt sich das Problem auf Applets. In Xcode erstellte ASObjc-Apps sind nicht betroffen.
Einfach mal anhand folgenden Codebeispiels (und einem ~25KB PNG) verglichen:

Code:
NSImage *anImage;
for (int i=0;i<80000;i++) anImage = [[NSImage alloc] initWithContentsOfFile:@"/photo.png"];
[NSApp setApplicationIconImage:anImage];
bzw.
Code:
repeat 80000 times
	set anImage to current application's NSImage's alloc's initWithContentsOfFile_("/photo.png")
end repeat
tell current application's NSApp to setApplicationIconImage_(anImage)


Code:
ASObjc-Applet unter 10.8		Finish:  1,5GB   (Max:  1,5GB) 
ASObjc-Applet unter 10.6  		Finish:   70MB   (Max:   70MB)
(release-Statements wirkungslos)

ASObjc-App 10.8				Finish:   80MB   (Max:  2,4GB)
(ohne release-Statements – ARC-Setting ob an oder aus kein Unterschied, ARC scheint so oder so eingesetzt zu werden)

Cocoa-App (ARC/manuelles release) 10.8	Finish:   70MB   (Max:    2GB) 
Cocoa-App (GC) 10.6			Finish:   65MB   (Max:   65MB) 
Cocoa-App (manuelles release) 10.6	Finish:   90MB   (Max:  800MB)

Nimm einen NSTimer, den Du beim Titelwechsel erzeugst und nach z.B. einer Sekunde feuerst.
Kommt innerhalb dieser einen Sekunde wieder ein Titelwechsel, dann brichst Du den Timer ab und legst einen neuen an

Danke für den wunderbaren Hinweis! Funktioniert sehr gut. Meine Implementation säh' so aus:

Code:
global trackNotification
property myTimer : missing value

on updateTrackInfo_(aNotification)
	set trackNotification to aNotification
	if (myTimer = missing value) then
		set myTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.75, me, "timerFired:", missing value, true)
	else if (myTimer's isValid() as boolean) then
		tell myTimer to invalidate()
		set myTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.75, me, "timerFired:", missing value, true)
	else
		set myTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.75, me, "timerFired:", missing value, true)
	end if
end updateTrackInfo_

on timerFired_(theTimer)
	tell myTimer to invalidate()
	(*
		..Inhalt der bisherigen updateTrackInfo-Funktion mit aNotification jeweils ersetzt durch trackNotification ...
	*)
end timerFired_

NSApplication -setApplicationIconImage:

… damit müsste das doch alles wegfallen.

Leider nicht. In der Doku heißt es, dass die Methode "sets the icon in the dock application tile. ". Und das war's anscheinend dann auch. Das Icon des App-Bundles bleibt das Gleiche. Auch NC nimmt dooferweise keine Notiz. Ansonsten ja ein guter Ansatz… :/

Ich würde das App nicht als Agent laufen lassen
Ja, weiß auch nicht, ob das die beste Idee ist – Pill hat wahrscheinlich das Icon im Dock genervt... :p
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: Pill
Hallo,

ich habe einen Vorschlag und fasse stichwortartig zusammen:

- macht zwei Anwendungen aus der bestehenden Anwendung
- eine Hauptanwendung, die die Nachrichten von iTunes abfängt
- eine Helferanwendung, die die Informationen an die Mitteilungszentrale sendet

- die Helferanwendung wird als Ressource in der Hauptanwendung hinterlegt
- bekommt die Hauptanwendung eine Nachricht, dann kopiert sie die Helferanwendung irgendwo hin, tmp etc.
- die Hauptanwendung öffnet die Helferanwendung und schickt ihr die Titelinfos, z.B. via Parameter oder einfach nochmals eine Nachricht ins System senden
- die Helferanwendung verarbeitet die Daten, setzt selbst das eigen Icon um, und schickt die Nachricht an die Mitteilungszentrale
- jetzt kann man überlegen…
- soll sich die Helferanwendung nach x Sekunden selbst beende
- soll die Hauptanwendung die Helferanwendung beim nächsten Titelwechsel beenden

Die Voreile liegen auf der Hand:
- die eigentliche App wird zur Laufzeit nicht manipuliert
- das Speicherproblem ist damit erledigt, da die Helferanwendung immer neu gestartet wird, nach Beenden ist der Speicher immer wieder frei
- Neustart der Mitteilungszentrale entfällt komplett
- in der Mitteilungszentrale ist immer nur ein Titel sichtbar
- der ganze "dirty" Kram wird auf einen Punkt zentriert

Leider habe ich keine Zeit zum Code tippseln. Ich kann nur "kluge Sprüche schwingen" ;)

Viele Grüße
 
  • Gefällt mir
Reaktionen: kermitd und Pill
@kermitd: sieht gut aus, hab ich eingebaut: http://cl.ly/3W3N3u2y0y3e

@little_pixel: könnte funktionieren. Ich bin jetzt die nächsten paar Tage unterwegs, wenn das bis dahin niemand anderes probiert hat, werd ich mich danach an der Umsetzung versuchen.
 
  • Gefällt mir
Reaktionen: kermitd
Keine schlechte Idee! Sowas in der Art hatte ich auch schon angedacht.

Ich schlage für's erste eine etwas unproblematischere Lösung vor, die soweit schonmal gut zu funktionieren scheint und keine große Änderung erfordert. Der Witz dabei ist, dass es ausreicht, das Bundle-Identifier zu ändern, damit NC zwischen Neustarts der Notifer-Anwendung das geänderte Icon erkennt. Dafür stecken wir eine Template.plist in den Resources-Ordner unserer Anwendung mit eingetragenem CFBundleIdentifier "com.pill.nowPlaying.CurrentTrack00000000". Als einzige Anpassung des bisherigen Codes (neben des Streichens des Parts wo NC gekillt wird) ist dann das hier unmittelbar folgend auf die sendMessage-Anweisung erforderlich:

Code:
adjustBundleIdentifier()
	
set dnc to current application's NSDistributedNotificationCenter's defaultCenter
tell dnc to postNotificationName_object_userInfo_deliverImmediately_("de.pill.nowPlaying.Quit", "Player Info", missing value, true)
quit me

Code:
on adjustBundleIdentifier()
	set oldIdentifier to current application's NSBundle's mainBundle()'s infoDictionary()'s objectForKey_("CFBundleIdentifier") as text
	set counterString to current application's NSString's alloc()'s initWithString_((items 33 thru 40 of oldIdentifier) as string)
	set counter to (counterString's intValue())
	if (counter < 99999998) then
		set counter to counter + 1
	else
		set counter to 0
	end if
	set counterString to counter as string
	repeat while length of counterString < 8
		set counterString to "0" & counterString
	end repeat
	set newIdentifier to "com.pill.nowPlaying.CurrentTrack" & counterString
	set TemplatePListPath to ((current application's NSBundle's mainBundle's resourcePath) as text) & "/Template.plist"
	tell application "System Events" to tell property list file TemplatePListPath to tell contents to set value of property list item "CFBundleIdentifier" to newIdentifier
	do shell script "cp " & TemplatePListPath & " " & POSIX path of (path to current application) & "Contents/Info.plist"
end adjustBundleIdentifier

Die Anwendung aktualisiert nun jedes mal nachdem sie eine Song-Benachrichtigung abgesandt hat ihren CFBundleIdentifier, postet dann eine Notifikation, und beendet sich. Ein anderes Applet läuft kontinuierlich mit folgendem Code:

Code:
set dnc to current application's NSDistributedNotificationCenter's defaultCenter
tell dnc to addObserver_selector_name_object_(me, "restartNowPlaying", "de.pill.nowPlaying.Quit", missing value)

my restartNowPlaying()

on restartNowPlaying()
	do shell script "open " & POSIX path of (path to current application) & "Contents/Resources/CurrentTrackNotification.app"
end restartNowPlaying

Et voilà! Das sollte es eigentlich sein...

Ans Ende der sendMessage-Routine noch tell current application's NSUserNotificationCenter's defaultUserNotificationCenter to removeAllDeliveredNotifications(), da ansonsten das Notification Center mit später nicht mehr von unserer App zu entfernenden Notifications überläuft.
 
Zuletzt bearbeitet:
Hier mal mein Vorschlag einer Umsetzung der von little_pixel angedachten Änderungen. Es existiert wie bereits beschrieben eine Hauptanwendung und eine Helfer-App, wobei für den Nutzer scheinbar bloß die Hauptanwendung ausgeführt wird (mit minimalem Speicherbedarf). Die Helfer-App wird nur bei Bedarf im Hintergrund aktiv.

-> http://cl.ly/0F092F1R1u0y

Die Helfer-App ist für das Absetzen der Notification zuständig, und liegt ursprünglich als "CurrentTrackNotificationAppTemplate" (ohne .app-Endung) im Resources-Ordner der Hauptanwendung. Die Hauptanwendung überprüft bei ihrem Start, ob im (Benutzer-)"Application Support"-Ordner des Zielsystems bereits ein Unterordner für sie existiert. Wenn nicht, wird ein solcher erstellt, in diesen die Helfer-App unter dem Namen "CurrentTrackNotification-0.app" kopiert, sowie zusätzlich eine Kopie der Helfer-App-Info.plist unter dem Namen "TemplateInfo.plist" erstellt, als auch ein PNG-File für das Default-Artwork erzeugt. Äußerdem wird eine CurrentTrack.plist generiert, die als Mittler für die Übergabe der Trackinfos von der Hauptanwendung an die Helfer-App fungiert.

Die Hauptanwendung beobachtet nun wie gehabt systemweite iTunes-Meldungen. Sie schreibt aber jetzt die Daten des aktuellen Titels in die CurrentTrack.plist, sowie das Cover in die CurrentTrackArtwork.png, worauf folgend die Helfer-App gestartet wird, welche diese Daten liest und, nach Anpassung des eigenen Ions, an das NC schickt. Dann schließt sie sich sofort wieder. Damit das sich stetig ändernde Icon der Helfer-App auch ohne einen Neustart des NC-Prozesses registriert wird, passt die Hauptanwendung vor jeder Ausführung der Helper-App deren Namen und Bundle-ID an.

Die gesammelten benutzten Bundle-IDs werden nach Beendigung der Hauptanwendung in der applicationShouldTerminate_ -Methode (im CocoaAppletAppDelegate.scpt das sich im Resources-Ordner befindet) aus der NC-Datenbank gelöscht – was vielleicht nicht unbedingt notwendig, aber wohl eine gute Idee ist (ich weiß nicht, ob NC die Identifiers früher oder später selbst löschen würde).

Hier der Code der Helfer-App und, im nächsten Beitrag, der Hauptanwendung:

Code:
-- main.scpt
-- Cocoa-AppleScript Applet
--
--	 ----------------------------------------------------------------------			  			    
--												   
--	   OS X Mountain Lion song notifications with album cover art support
--		Helper app – dispatch of notifications										          
--	 ----------------------------------------------------------------------
--
-- (pre-20120831a)
-- v.0.5

set pathToApplicationSupportFolderForThisApp to (current application's NSFileManager's defaultManager's URLsForDirectory_inDomains_(current application's NSApplicationSupportDirectory, current application's NSUserDomainMask)'s objectAtIndex_(0)'s |path|() as text) & "/" & "com.pill.nowPlaying"

tell application "System Events" to tell property list file (pathToApplicationSupportFolderForThisApp & "/CurrentTrack.plist") to tell contents
	set currentTrackName to the value of property list item "Name"
	set currentTrackArtist to the value of property list item "Artist"
	set currentTrackAlbum to the value of property list item "Album"
end tell
tell current application's NSWorkspace's sharedWorkspace() to setIcon_forFile_options_(current application's NSImage's alloc's initWithContentsOfFile_((pathToApplicationSupportFolderForThisApp & "/CurrentTrackArtwork.png")), POSIX path of (path to current application), 0)

tell current application's NSUserNotification to set thisNote to alloc()'s init()
set thisNote's title to the currentTrackName
set thisNote's subtitle to the currentTrackArtist
set thisNote's informativeText to the currentTrackAlbum
tell current application's NSUserNotificationCenter's defaultUserNotificationCenter
	deliverNotification_(thisNote)
	removeAllDeliveredNotifications()
end tell

quit me
 
Code:
-- main.scpt
-- Cocoa-AppleScript Applet
--
--	 ----------------------------------------------------------------------			  			    
--												   
--	   OS X Mountain Lion song notifications with album cover art support
--		Now Playing										          
--	 ----------------------------------------------------------------------
--
-- (pre-20120831a)
-- v.0.5

global trackNotification, currentTrackName, currentTrackAlbum, currentTrackArtist, currentTrackArtworkCount
property myTimer : missing value
property pathToTrackNotifierAppInResourceFolder : ""
property pathToApplicationSupportFolderForThisApp : ""
property pathToTrackNotifierApp : ""
property pathToArtworkPNG : ""
property pathToDefaultArtworkPNG : ""
property pathToCurrentTrackPList : ""
property pathToTemplateInfoPList : ""

set pathToTrackNotifierAppInResourceFolder to ((current application's NSBundle's mainBundle's resourcePath) as text) & "/CurrentTrackNotificationAppTemplate"
set pathToApplicationSupportFolderForThisApp to (current application's NSFileManager's defaultManager's URLsForDirectory_inDomains_(current application's NSApplicationSupportDirectory, current application's NSUserDomainMask)'s objectAtIndex_(0)'s |path|() as text) & "/" & "com.pill.nowPlaying"
set pathToTrackNotifierApp to pathToApplicationSupportFolderForThisApp & "/CurrentTrackNotification-0.app"
set pathToArtworkPNG to pathToApplicationSupportFolderForThisApp & "/CurrentTrackArtwork.png"
set pathToDefaultArtworkPNG to pathToApplicationSupportFolderForThisApp & "/DefaultArtwork.png"
set pathToCurrentTrackPList to pathToApplicationSupportFolderForThisApp & "/CurrentTrack.plist"
set pathToTemplateInfoPList to pathToApplicationSupportFolderForThisApp & "/TemplateInfo.plist"


if ((current application's NSFileManager's defaultManager's fileExistsAtPath_(pathToApplicationSupportFolderForThisApp)) as boolean is false) then handleFirstLaunchTasks()



tell current application's NSDistributedNotificationCenter's defaultCenter
	addObserver_selector_name_object_(me, "updateTrackInfo:", "com.apple.iTunes.playerInfo", missing value)
	addObserver_selector_name_object_(me, "updateTrackInfo:", "com.spotify.client.PlaybackStateChanged", missing value)
end tell

on updateTrackInfo_(aNotification)
	set trackNotification to aNotification
	if (myTimer = missing value) then
		set myTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.75, me, "timerFired:", missing value, true)
	else if (myTimer's isValid() as boolean) then
		tell myTimer to invalidate()
		set myTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.75, me, "timerFired:", missing value, true)
	else
		set myTimer to current application's NSTimer's scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(0.75, me, "timerFired:", missing value, true)
	end if
end updateTrackInfo_

on timerFired_(theTimer)
	tell myTimer to invalidate()
	tell current application's NSDictionary to set trackInfo to alloc()'s initWithDictionary_(trackNotification's userInfo)
	tell application "System Events" to set frontmostApp to name of the first process whose frontmost is true
	if trackNotification's |name| as text = "com.apple.iTunes.playerInfo" then
		set notifier to "iTunes"
		set artworkKey to "Artwork Count"
	else if trackNotification's |name| as text = "com.spotify.client.PlaybackStateChanged" then
		set notifier to "Spotify"
		set artworkKey to "Has Artwork"
	end if
	if (the frontmostApp is the notifier) or (trackInfo's valueForKey_("Player State") as text) is not "playing" then return
	set currentTrackAlbum to trackInfo's valueForKey_("Album") as text
	set currentTrackArtist to trackInfo's valueForKey_("Artist") as text
	set currentTrackName to trackInfo's valueForKey_("Name") as text
	set currentTrackArtworkCount to trackInfo's valueForKey_(artworkKey) as integer
	if currentTrackAlbum is "missing value" then set currentTrackAlbum to ""
	if currentTrackArtist is "missing value" then set currentTrackArtist to ""
	if currentTrackName is "missing value" then set currentTrackName to "–"
	
	tell application "System Events" to tell property list file pathToCurrentTrackPList to tell contents
		set value of property list item "Artwork" to currentTrackArtworkCount
		set value of property list item "Name" to currentTrackName
		set value of property list item "Artist" to currentTrackArtist
		set value of property list item "Album" to currentTrackAlbum
	end tell
	
	copyArtworkOfCurrentTrackPlayingOn_(notifier)
	
	-- update the info.plist template's bundle identifier
	tell application "System Events" to tell property list file pathToTemplateInfoPList to tell contents to set oldIdentifier to value of property list item "CFBundleIdentifier"
	set oldCounter to current application's NSString's alloc()'s initWithString_((items 33 thru 40 of oldIdentifier) as string)'s intValue()
	set newCounter to oldCounter + 1
	set adjustedCounterString to newCounter as string
	repeat while length of adjustedCounterString < 8
		set adjustedCounterString to "0" & adjustedCounterString
	end repeat
	tell application "System Events" to tell property list file pathToTemplateInfoPList to tell contents to set value of property list item "CFBundleIdentifier" to "com.pill.nowPlaying.CurrentTrack" & adjustedCounterString
	
	-- create new track notifier app with adjusted bundle identifier
	set formerApp to pathToApplicationSupportFolderForThisApp & "/CurrentTrackNotification-" & oldCounter & ".app"
	set appToLaunch to pathToApplicationSupportFolderForThisApp & "/CurrentTrackNotification-" & newCounter & ".app"
	do shell script "rm " & quoted form of formerApp & "/Contents/Info.plist;mv " & quoted form of formerApp & " " & quoted form of appToLaunch & "; cp " & quoted form of pathToTemplateInfoPList & " " & quoted form of appToLaunch & "/Contents/Info.plist"
	
	tell current application's NSWorkspace's sharedWorkspace to launchApplication_(appToLaunch)
	
	
end timerFired_


on copyArtworkOfCurrentTrackPlayingOn_(appName)
	if currentTrackArtworkCount ≠ 0 then
		if (appName = "iTunes") then
			tell application "iTunes" to set currentTrackArtwork to raw data of artwork 1 of current track
		else if (appName = "Spotify") then
			tell application "Spotify" to set currentTrackArtwork to artwork of current track
		end if
		try
			set theFilehandle to open for access (pathToArtworkPNG) with write permission
			write currentTrackArtwork to theFilehandle starting at 0
			close access theFilehandle
		on error err
			close access theFilehandle
		end try
	else
		tell current application's NSFileManager's defaultManager
			removeItemAtPath_error_(pathToArtworkPNG, missing value)
			copyItemAtPath_toPath_error_(pathToDefaultArtworkPNG, pathToArtworkPNG, missing value)
		end tell
	end if
end copyArtworkOfCurrentTrackPlayingOn_

on handleFirstLaunchTasks()
	tell current application's NSFileManager's defaultManager
		createDirectoryAtPath_withIntermediateDirectories_attributes_error_(pathToApplicationSupportFolderForThisApp, false, missing value, missing value)
		copyItemAtPath_toPath_error_(pathToTrackNotifierAppInResourceFolder, pathToTrackNotifierApp, missing value)
		copyItemAtPath_toPath_error_(pathToTrackNotifierAppInResourceFolder & "/Contents/Info.plist", pathToTemplateInfoPList, missing value)
	end tell
	tell current application's NSBitmapImageRep's imageRepWithData_(current application's NSApp's applicationIconImage()'s TIFFRepresentation())'s representationUsingType_properties_(current application's NSPNGFileType, missing value) to writeToFile_atomically_(pathToDefaultArtworkPNG, false)
	tell application "System Events"
		tell property list file pathToTemplateInfoPList to tell contents
			set previousValue to value
			set value to (previousValue & {|NSUIElement|:"1"})
			set value of property list item "CFBundleName" to "CurrentTrack"
			set value of property list item "CFBundleIdentifier" to ("com.pill.nowPlaying.CurrentTrack" & "00000000")
		end tell
		set the dict to make new property list item with properties {kind:record}
		set thisPList to make new property list file with properties {contents:dict, name:pathToCurrentTrackPList}
		make new property list item at end of property list items of contents of thisPList with properties {kind:string, name:"Name", value:"Name"}
		make new property list item at end of property list items of contents of thisPList with properties {kind:string, name:"Artist", value:"Artist"}
		make new property list item at end of property list items of contents of thisPList with properties {kind:string, name:"Album", value:"Album"}
		make new property list item at end of property list items of contents of thisPList with properties {kind:number, name:"Artwork", value:0}
	end tell
	do shell script "killall NotificationCenter"
end handleFirstLaunchTasks

Und in CocoaAppletAppDelegate.scpt's applicationShouldTerminate_(sender):
Code:
do shell script "sqlite3 ~/Library/Application\\ Support/NotificationCenter/*.db \"DELETE from app_info where bundleid like 'com.pill.nowPlaying.%'\""
 
Zuletzt bearbeitet:
Chapeau!

Ich kann mich nur wiederholen: ein tolles Lehrstück. Gespickt mit coolen Ideen (wie z.B. meiner Meinung nach die fortlaufende Bundle ID Nummer)
Und ich freue mich schon auf den Feinschliff!

Ich bin nur drüber geflogen, aber würde etwas dagegen sprechen, das Hilfsprogramm und die nötigen Files in einem Unterordner des Resources Ordners der Hauptanwendung zu legen / generieren ?
Sonst hinterlässt man halt Leichen (die zwar nicht müffeln, aber rumliegen) ... falls das Programm von einem User mal nicht mehr genutzt wird.

Und falls es dabei bleibt, so könnte man doch das Dock Icon nutzen, um das momentane Album anzuzeigen...

Gruß
 
Hallo,

das Script muss immer Manuell geöffnet werden oder?
 
Ich bin nur drüber geflogen, aber würde etwas dagegen sprechen, das Hilfsprogramm und die nötigen Files in einem Unterordner des Resources Ordners der Hauptanwendung zu legen / generieren ?
Es ist eigentlich nicht vorgesehen, ins App-Bundle zu schreiben, insbesondere nicht zum Zeitpunkt der Ausführung. Probleme gibt das insbesondere, wenn das Bundle signiert ist, aber auch sonst sollte man das glaube ich wenn es geht vermeiden. Man könnte natürlich am Ende jeder Ausführung die Support-Dateien löschen, und bei Programmstart wieder an Ort und Stelle kopieren…

könnte man doch das Dock Icon nutzen, um das momentane Album anzuzeigen...
Auf jeden Fall. Sollte dann vielleicht optional sein.

Ich habe noch eine Reihe von Änderungen vorgenommen, und dabei mal eine Objective C Version erstellt.

=> Die kompilierte Cocoa-App

attachment.php


// Sourcecode der Hauptanwendung (AppDelegate.m) sowie der Helper-App (AppDelegate.m).


Statt praktisch grenzenlos neue Bundle-IDs zu vergeben, wird für jedes Artwork-enhaltende Album eine (mit hoher Wahrscheinlichkeit) eindeutige Bundle-ID per Hashfunktion erstellt. Tracks ohne Artwork bekommen eine Default-Bundle-ID. Benutzte Bundle-IDs werden beim Beenden der Hauptanwendung gelöscht und der im Hintergrund laufende Daemon für die Notifications im gleichen Zug neugestartet (auf jeden Falle ratsam und für den Nutzer nicht sichtbar). Dadurch wird effektiv verhindert, dass sich die NC-Datenbank aufbläht. Außerdem werden gleiche Namen des Track-Notifiers immer wieder mit gleichem Icon und gleicher Bundle-ID belegt. So dass NC auf das im Cache gehaltene Icon zurückgreifen kann.

Die Hauptanwendung kommuniziert mit der Helferanwendung mit Hilfe von drei Nachrichten:
  1. terminationRequest
  2. notificationDispatchRequest
  3. notificationRemovalRequest

  • Falls die Helper app bei einem Liedwechsel noch nicht läuft, wird sie gestartet (und schickt dann automatisch eine NC-Nachricht ab)
  • Die Helper app bleibt solange geöffnet, wie Musik abgespielt wird, und sich das Album nicht ändert.
  • Für Liedwechsel bei gleichbleibendem Album und bereits geöffneter Helper-App wird ein notificationDispatchRequest gesendet
  • Der laufende Track – und nur dieser – bleibt im Notification Center hinterlegt
attachment.php

  • Der Eintrag im Notification Center wird bei gestoppter oder pausierter Musik entfernt (wie es Pill ja auch bereits in Post #25 vorgesehen hatte), oder wenn die Hauptanwendung beendet wird (jeweils per notificationRemovalRequest)
  • Die Helper app schließt sich automatisch 10s nachdem sie den letzten verbleibenden Eintrag aus dem NC entfernt hat, oder wenn die Hauptanwendung beendet wird (terminationRequest)
  • Die Helper app wird bei einem Albumwechsel automatisch durch eine Version mit angepasstem Icon ausgetauscht (terminationRequest)
  • Bloß angespielte Lieder, die der Nutzer im Bruchteil einer Sekunde wieder pausiert, werden nicht angezeigt
 

Anhänge

  • memory.jpg
    memory.jpg
    18 KB · Aufrufe: 271
  • note.png
    note.png
    54,8 KB · Aufrufe: 264
  • Gefällt mir
Reaktionen: Herr Patschulke, tuedel125 und bikkuri
Zurück
Oben Unten