-
-
Notifications
You must be signed in to change notification settings - Fork 47
/
Copy pathsmudge-connect.el
193 lines (166 loc) · 7.37 KB
/
smudge-connect.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
;;; smudge-connect.el --- Control remote and local Spotify instances -*- lexical-binding: t; -*-
;; Copyright (C) 2019-2025 Jason Dufair, Daniel Martins
;; SPDX-License-Identifier: GPL-3.0-or-later
;;; Commentary:
;; This library uses the "connect" APIs to control transport functions of
;; remote and local instances of Spotify clients. It implements a set of
;; multimethod-like functions that are dispatched in smudge-controller.el.
;; smudge-connect.el --- Smudge transport for the Spotify Connect API
;;; Code:
(require 'smudge-api)
(require 'smudge-controller)
(defun smudge-connect-player-status ()
"Get the player status of the currently playing device, if any.
Returns a JSON string in the format:
{
\"artist\": \"Aesop Rock\",
\"duration\": 265333,
\"track_number\": 9,
\"name\": \"Shrunk\",
\"player_state\": \"playing\",
\"player_shuffling\": \"t\",
\"player_repeating\": \"context\"
}"
(smudge-api-get-player-status
(lambda (status)
(if-let* ((status status)
(track (gethash "item" status))
(json (concat
"{"
(format "\"artist\":\"%s\","
(gethash "name" (car (gethash "artists" track))))
(format "\"duration\": %d,"
(gethash "duration_ms" track))
(format "\"track_number\":%d,"
(gethash "track_number" track))
(format "\"name\":\"%s\","
(gethash "name" track))
(format "\"player_state\":\"%s\","
(if (eq (gethash "is_playing" status) :false) "paused" "playing"))
(format "\"player_shuffling\":%s,"
(if (not (eq (gethash "shuffle_state" status) :false))"true" "false"))
(format "\"player_repeating\":%s"
(if (string= (gethash "repeat_state" status) "off") "false" "true"))
"}")))
(smudge-controller-update-metadata json)
(smudge-controller-update-metadata nil)))))
;;;###autoload
(defun smudge-select-device ()
"Allow for the selection of a device via Spotify Connect for transport functions."
(interactive)
(smudge-api-current-user
(lambda (user)
(if (not (string= (gethash "product" user) "premium"))
(message "This feature requires a Spotify premium subscription.")
(let ((buffer (get-buffer-create "*Devices*")))
(with-current-buffer buffer
(smudge-device-select-mode)
(smudge-device-select-update)))))))
(defmacro smudge-connect-when-device-active (body)
"Evaluate BODY when there is an active device, otherwise show an error message."
`(smudge-api-device-list
(lambda (json)
(if-let ((json json)
(devices (gethash "devices" json))
(active (> (length (seq-filter (lambda (dev) (eq (gethash "is_active" dev) t)) devices)) 0)))
(progn ,body)
(when (y-or-n-p "No active device. Would you like to select one?")
(smudge-select-device))))))
(defun smudge-connect-player-play-track (uri &optional context)
"Play a track URI via Spotify Connect in an optional CONTEXT."
(smudge-connect-when-device-active
(smudge-api-play nil uri context)))
(defun smudge-connect-player-pause ()
"Pause the currently playing track."
(smudge-connect-when-device-active
(smudge-api-pause)))
(defun smudge-connect-player-toggle-play ()
"Toggle playing status of current track."
(smudge-connect-when-device-active
(smudge-api-get-player-status
(lambda (status)
(if status
(if (not (eq (gethash "is_playing" status) :false))
(smudge-api-pause)
(smudge-api-play)))))))
(defun smudge-connect-player-next-track ()
"Skip to the next track."
(smudge-connect-when-device-active
(smudge-api-next)))
(defun smudge-connect-player-previous-track ()
"Skip to the previous track."
(smudge-connect-when-device-active
(smudge-api-previous)))
(defun smudge-connect-volume-up ()
"Turn up the volume on the actively playing device."
(smudge-connect-when-device-active
(smudge-api-get-player-status
(lambda (status)
(let ((new-volume (min (+ (smudge-connect-get-volume status) 10) 100)))
(smudge-api-set-volume
(smudge-connect-get-device-id status)
new-volume
(lambda (_)
(message "Volume increased to %d%%" new-volume))))))))
(defun smudge-connect-volume-down ()
"Turn down the volume (for what?) on the actively playing device."
(smudge-connect-when-device-active
(smudge-api-get-player-status
(lambda (status)
(let ((new-volume (max (- (smudge-connect-get-volume status) 10) 0)))
(smudge-api-set-volume
(smudge-connect-get-device-id status)
new-volume
(lambda (_)
(message "Volume decreased to %d%%" new-volume))))))))
(defun smudge-connect-volume-mute-unmute ()
"Mute/unmute the actively playing device by setting the volume to 0."
(smudge-connect-when-device-active
(smudge-api-get-player-status
(lambda (status)
(let ((volume (smudge-connect-get-volume status)))
(if (eq volume 0)
(smudge-api-set-volume (smudge-connect-get-device-id status) 100
(lambda (_) (message "Volume unmuted")))
(smudge-api-set-volume (smudge-connect-get-device-id status) 0
(lambda (_) (message "Volume muted")))))))))
(defun smudge-connect-toggle-repeat ()
"Toggle repeat for the current track."
(smudge-connect-when-device-active
(smudge-api-get-player-status
(lambda (status)
(let ((is-repeating (smudge-connect--is-repeating status)))
(smudge-api-repeat (if is-repeating "off" "context")
(lambda (_)
(if is-repeating
(message "Repeat turned off")
(message "Repeat turned on")))))))))
(defun smudge-connect-toggle-shuffle ()
"Toggle shuffle for the current track."
(smudge-connect-when-device-active
(smudge-api-get-player-status
(lambda (status)
(let ((is-shuffling (smudge-connect--is-shuffling status)))
(smudge-api-shuffle (if is-shuffling "false" "true")
(lambda (_)
(if is-shuffling
(message "Shuffling turned off")
(message "Shuffling turned on")))))))))
(defun smudge-connect-get-device-id (player-status)
"Get the id if from PLAYER-STATUS of the currently playing device, if any."
(when player-status
(gethash "id" (gethash "device" player-status))))
(defun smudge-connect-get-volume (player-status)
"Get the volume from PLAYER-STATUS of the currently playing device, if any."
(when player-status
(gethash "volume_percent" (gethash "device" player-status))))
(defun smudge-connect--is-shuffling (player-status)
"Business logic for shuffling state of PLAYER-STATUS."
(and player-status
(not (eq (gethash "shuffle_state" player-status) :false))))
(defun smudge-connect--is-repeating (player-status)
"Business logic for repeat state of PLAYER-STATUS."
(and player-status
(string= (gethash "repeat_state" player-status) "context")))
(provide 'smudge-connect)
;;; smudge-connect.el ends here