rockbox 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+
5
+ module Rockbox
6
+ module Api
7
+ class Library
8
+ TRACK_FIELDS = <<~GQL
9
+ fragment TrackFields on Track {
10
+ id title artist album genre disc trackString yearString
11
+ composer comment albumArtist grouping
12
+ discnum tracknum layer year bitrate frequency
13
+ filesize length elapsed path
14
+ albumId artistId genreId albumArt
15
+ }
16
+ GQL
17
+
18
+ ALBUM_FIELDS = <<~GQL
19
+ fragment AlbumFields on Album {
20
+ id title artist year yearString albumArt md5 artistId copyrightMessage
21
+ }
22
+ GQL
23
+
24
+ ARTIST_FIELDS = <<~GQL
25
+ fragment ArtistFields on Artist { id name bio image }
26
+ GQL
27
+
28
+ def initialize(http)
29
+ @http = http
30
+ end
31
+
32
+ # ---------------------------------------------------------------------
33
+ # Albums
34
+ # ---------------------------------------------------------------------
35
+
36
+ def albums
37
+ data = @http.execute(<<~GQL)
38
+ #{ALBUM_FIELDS}
39
+ query Albums { albums { ...AlbumFields tracks { id title path length albumArt } } }
40
+ GQL
41
+ Array(data[:albums]).map { |a| Album.from_hash(a) }
42
+ end
43
+
44
+ def album(id)
45
+ data = @http.execute(<<~GQL, { id: id })
46
+ #{TRACK_FIELDS}
47
+ #{ALBUM_FIELDS}
48
+ query Album($id: String!) { album(id: $id) { ...AlbumFields tracks { ...TrackFields } } }
49
+ GQL
50
+ Album.from_hash(data[:album])
51
+ end
52
+
53
+ def liked_albums
54
+ data = @http.execute("#{ALBUM_FIELDS}\nquery LikedAlbums { likedAlbums { ...AlbumFields } }")
55
+ Array(data[:liked_albums]).map { |a| Album.from_hash(a) }
56
+ end
57
+
58
+ def like_album(id)
59
+ @http.execute("mutation LikeAlbum($id: String!) { likeAlbum(id: $id) }", { id: id })
60
+ nil
61
+ end
62
+
63
+ def unlike_album(id)
64
+ @http.execute("mutation UnlikeAlbum($id: String!) { unlikeAlbum(id: $id) }", { id: id })
65
+ nil
66
+ end
67
+
68
+ # ---------------------------------------------------------------------
69
+ # Artists
70
+ # ---------------------------------------------------------------------
71
+
72
+ def artists
73
+ data = @http.execute(<<~GQL)
74
+ #{ARTIST_FIELDS}
75
+ query Artists { artists { ...ArtistFields albums { id title albumArt year } } }
76
+ GQL
77
+ Array(data[:artists]).map { |a| Artist.from_hash(a) }
78
+ end
79
+
80
+ def artist(id)
81
+ data = @http.execute(<<~GQL, { id: id })
82
+ #{ARTIST_FIELDS}
83
+ #{TRACK_FIELDS}
84
+ query Artist($id: String!) {
85
+ artist(id: $id) {
86
+ ...ArtistFields
87
+ albums { id title albumArt year yearString md5 artistId tracks { id title path length } }
88
+ tracks { ...TrackFields }
89
+ }
90
+ }
91
+ GQL
92
+ Artist.from_hash(data[:artist])
93
+ end
94
+
95
+ # ---------------------------------------------------------------------
96
+ # Tracks
97
+ # ---------------------------------------------------------------------
98
+
99
+ def tracks
100
+ data = @http.execute("#{TRACK_FIELDS}\nquery Tracks { tracks { ...TrackFields } }")
101
+ Array(data[:tracks]).map { |t| Track.from_hash(t) }
102
+ end
103
+
104
+ def track(id)
105
+ data = @http.execute(
106
+ "#{TRACK_FIELDS}\nquery Track($id: String!) { track(id: $id) { ...TrackFields } }",
107
+ { id: id }
108
+ )
109
+ Track.from_hash(data[:track])
110
+ end
111
+
112
+ def liked_tracks
113
+ data = @http.execute("#{TRACK_FIELDS}\nquery LikedTracks { likedTracks { ...TrackFields } }")
114
+ Array(data[:liked_tracks]).map { |t| Track.from_hash(t) }
115
+ end
116
+
117
+ def like_track(id)
118
+ @http.execute("mutation LikeTrack($id: String!) { likeTrack(id: $id) }", { id: id })
119
+ nil
120
+ end
121
+
122
+ def unlike_track(id)
123
+ @http.execute("mutation UnlikeTrack($id: String!) { unlikeTrack(id: $id) }", { id: id })
124
+ nil
125
+ end
126
+
127
+ # ---------------------------------------------------------------------
128
+ # Search
129
+ # ---------------------------------------------------------------------
130
+
131
+ # @return [Rockbox::SearchResults]
132
+ def search(term)
133
+ data = @http.execute(<<~GQL, { term: term })
134
+ #{TRACK_FIELDS}
135
+ #{ALBUM_FIELDS}
136
+ #{ARTIST_FIELDS}
137
+ query Search($term: String!) {
138
+ search(term: $term) {
139
+ artists { ...ArtistFields }
140
+ albums { ...AlbumFields }
141
+ tracks { ...TrackFields }
142
+ likedTracks { ...TrackFields }
143
+ likedAlbums { ...AlbumFields }
144
+ }
145
+ }
146
+ GQL
147
+ results = data[:search] || {}
148
+ SearchResults.new(
149
+ artists: Array(results[:artists]).map { |a| Artist.from_hash(a) },
150
+ albums: Array(results[:albums]).map { |a| Album.from_hash(a) },
151
+ tracks: Array(results[:tracks]).map { |t| Track.from_hash(t) },
152
+ liked_tracks: Array(results[:liked_tracks]).map { |t| Track.from_hash(t) },
153
+ liked_albums: Array(results[:liked_albums]).map { |a| Album.from_hash(a) }
154
+ )
155
+ end
156
+
157
+ # ---------------------------------------------------------------------
158
+ # Library management
159
+ # ---------------------------------------------------------------------
160
+
161
+ def scan
162
+ @http.execute("mutation ScanLibrary { scanLibrary }")
163
+ nil
164
+ end
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+
5
+ module Rockbox
6
+ module Api
7
+ class Playback
8
+ TRACK_FIELDS = <<~GQL
9
+ fragment TrackFields on Track {
10
+ id title artist album genre disc trackString yearString
11
+ composer comment albumArtist grouping
12
+ discnum tracknum layer year bitrate frequency
13
+ filesize length elapsed path
14
+ albumId artistId genreId albumArt
15
+ }
16
+ GQL
17
+
18
+ def initialize(http)
19
+ @http = http
20
+ end
21
+
22
+ # ---------------------------------------------------------------------
23
+ # Status & current track
24
+ # ---------------------------------------------------------------------
25
+
26
+ # @return [Integer] one of {Rockbox::PlaybackStatus} constants.
27
+ def status
28
+ @http.execute("query PlaybackStatus { status }")[:status]
29
+ end
30
+
31
+ # @return [Symbol] :stopped | :playing | :paused | :unknown
32
+ def status_name
33
+ PlaybackStatus.name(status)
34
+ end
35
+
36
+ # @return [Rockbox::Track, nil]
37
+ def current_track
38
+ data = @http.execute("#{TRACK_FIELDS}\nquery CurrentTrack { currentTrack { ...TrackFields } }")
39
+ Track.from_hash(data[:current_track])
40
+ end
41
+
42
+ # @return [Rockbox::Track, nil]
43
+ def next_track
44
+ data = @http.execute("#{TRACK_FIELDS}\nquery NextTrack { nextTrack { ...TrackFields } }")
45
+ Track.from_hash(data[:next_track])
46
+ end
47
+
48
+ # @return [Integer]
49
+ def file_position
50
+ @http.execute("query FilePosition { getFilePosition }")[:get_file_position]
51
+ end
52
+
53
+ # ---------------------------------------------------------------------
54
+ # Transport controls
55
+ # ---------------------------------------------------------------------
56
+
57
+ def play(elapsed: 0, offset: 0)
58
+ @http.execute(
59
+ "mutation Play($elapsed: Long!, $offset: Long!) { play(elapsed: $elapsed, offset: $offset) }",
60
+ { elapsed: elapsed, offset: offset }
61
+ )
62
+ nil
63
+ end
64
+
65
+ def pause; @http.execute("mutation Pause { pause }"); nil; end
66
+ def resume; @http.execute("mutation Resume { resume }"); nil; end
67
+ def next!; @http.execute("mutation Next { next }"); nil; end
68
+ def previous!; @http.execute("mutation Previous { previous }"); nil; end
69
+ def stop; @http.execute("mutation Stop { hardStop }"); nil; end
70
+ def flush_and_reload; @http.execute("mutation FlushReload { flushAndReloadTracks }"); nil; end
71
+
72
+ # @param position_ms [Integer] absolute target position, in milliseconds.
73
+ def seek(position_ms)
74
+ @http.execute(
75
+ "mutation Seek($newTime: Int!) { fastForwardRewind(newTime: $newTime) }",
76
+ { new_time: position_ms }
77
+ )
78
+ nil
79
+ end
80
+
81
+ # ---------------------------------------------------------------------
82
+ # Single-call play helpers
83
+ # ---------------------------------------------------------------------
84
+
85
+ def play_track(path)
86
+ @http.execute(
87
+ "mutation PlayTrack($path: String!) { playTrack(path: $path) }",
88
+ { path: path }
89
+ )
90
+ nil
91
+ end
92
+
93
+ def play_album(album_id, shuffle: nil, position: nil)
94
+ @http.execute(
95
+ "mutation PlayAlbum($albumId: String!, $shuffle: Boolean, $position: Int) { " \
96
+ "playAlbum(albumId: $albumId, shuffle: $shuffle, position: $position) }",
97
+ { album_id: album_id, shuffle: shuffle, position: position }.compact
98
+ )
99
+ nil
100
+ end
101
+
102
+ def play_artist(artist_id, shuffle: nil, position: nil)
103
+ @http.execute(
104
+ "mutation PlayArtist($artistId: String!, $shuffle: Boolean, $position: Int) { " \
105
+ "playArtistTracks(artistId: $artistId, shuffle: $shuffle, position: $position) }",
106
+ { artist_id: artist_id, shuffle: shuffle, position: position }.compact
107
+ )
108
+ nil
109
+ end
110
+
111
+ def play_playlist(playlist_id, shuffle: nil, position: nil)
112
+ @http.execute(
113
+ "mutation PlayPlaylist($playlistId: String!, $shuffle: Boolean, $position: Int) { " \
114
+ "playPlaylist(playlistId: $playlistId, shuffle: $shuffle, position: $position) }",
115
+ { playlist_id: playlist_id, shuffle: shuffle, position: position }.compact
116
+ )
117
+ nil
118
+ end
119
+
120
+ def play_directory(path, recurse: nil, shuffle: nil, position: nil)
121
+ @http.execute(
122
+ "mutation PlayDirectory($path: String!, $recurse: Boolean, $shuffle: Boolean, $position: Int) { " \
123
+ "playDirectory(path: $path, recurse: $recurse, shuffle: $shuffle, position: $position) }",
124
+ { path: path, recurse: recurse, shuffle: shuffle, position: position }.compact
125
+ )
126
+ nil
127
+ end
128
+
129
+ def play_liked_tracks(shuffle: nil, position: nil)
130
+ @http.execute(
131
+ "mutation PlayLikedTracks($shuffle: Boolean, $position: Int) { " \
132
+ "playLikedTracks(shuffle: $shuffle, position: $position) }",
133
+ { shuffle: shuffle, position: position }.compact
134
+ )
135
+ nil
136
+ end
137
+
138
+ def play_all_tracks(shuffle: nil, position: nil)
139
+ @http.execute(
140
+ "mutation PlayAllTracks($shuffle: Boolean, $position: Int) { " \
141
+ "playAllTracks(shuffle: $shuffle, position: $position) }",
142
+ { shuffle: shuffle, position: position }.compact
143
+ )
144
+ nil
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+
5
+ module Rockbox
6
+ module Api
7
+ class Playlist
8
+ TRACK_FIELDS = <<~GQL
9
+ fragment TrackFields on Track {
10
+ id title artist album genre disc trackString yearString
11
+ composer comment albumArtist grouping
12
+ discnum tracknum layer year bitrate frequency
13
+ filesize length elapsed path
14
+ albumId artistId genreId albumArt
15
+ }
16
+ GQL
17
+
18
+ def initialize(http)
19
+ @http = http
20
+ end
21
+
22
+ # @return [Rockbox::Playlist]
23
+ def current
24
+ data = @http.execute(<<~GQL)
25
+ #{TRACK_FIELDS}
26
+ query CurrentPlaylist {
27
+ playlistGetCurrent {
28
+ amount index maxPlaylistSize firstIndex
29
+ lastInsertPos seed lastShuffledStart
30
+ tracks { ...TrackFields }
31
+ }
32
+ }
33
+ GQL
34
+ pl = data[:playlist_get_current] || {}
35
+ Rockbox::Playlist.new(
36
+ amount: pl[:amount],
37
+ index: pl[:index],
38
+ max_playlist_size: pl[:max_playlist_size],
39
+ first_index: pl[:first_index],
40
+ last_insert_pos: pl[:last_insert_pos],
41
+ seed: pl[:seed],
42
+ last_shuffled_start: pl[:last_shuffled_start],
43
+ tracks: Array(pl[:tracks]).map { |t| Track.from_hash(t) }
44
+ )
45
+ end
46
+
47
+ def amount
48
+ @http.execute("query PlaylistAmount { playlistAmount }")[:playlist_amount]
49
+ end
50
+
51
+ # ---------------------------------------------------------------------
52
+ # Queue management
53
+ # ---------------------------------------------------------------------
54
+
55
+ # @param paths [Array<String>] file paths or track IDs to insert.
56
+ # @param position [Integer] one of {Rockbox::InsertPosition} (default: NEXT).
57
+ # @param playlist_id [String, nil] target playlist; nil for the active queue.
58
+ def insert_tracks(paths, position: InsertPosition::NEXT, playlist_id: nil)
59
+ @http.execute(
60
+ "mutation InsertTracks($playlistId: String, $position: Int!, $tracks: [String!]!) { " \
61
+ "insertTracks(playlistId: $playlistId, position: $position, tracks: $tracks) }",
62
+ { playlist_id: playlist_id, position: position, tracks: paths }
63
+ )
64
+ nil
65
+ end
66
+
67
+ def insert_directory(directory, position: InsertPosition::LAST, playlist_id: nil)
68
+ @http.execute(
69
+ "mutation InsertDirectory($playlistId: String, $position: Int!, $directory: String!) { " \
70
+ "insertDirectory(playlistId: $playlistId, position: $position, directory: $directory) }",
71
+ { playlist_id: playlist_id, position: position, directory: directory }
72
+ )
73
+ nil
74
+ end
75
+
76
+ def insert_album(album_id, position: InsertPosition::LAST)
77
+ @http.execute(
78
+ "mutation InsertAlbum($albumId: String!, $position: Int!) { " \
79
+ "insertAlbum(albumId: $albumId, position: $position) }",
80
+ { album_id: album_id, position: position }
81
+ )
82
+ nil
83
+ end
84
+
85
+ def remove_track(index)
86
+ @http.execute(
87
+ "mutation RemoveTrack($index: Int!) { playlistRemoveTrack(index: $index) }",
88
+ { index: index }
89
+ )
90
+ nil
91
+ end
92
+
93
+ def clear
94
+ @http.execute("mutation ClearPlaylist { playlistRemoveAllTracks }")
95
+ nil
96
+ end
97
+
98
+ def shuffle
99
+ @http.execute("mutation ShufflePlaylist { shufflePlaylist }")
100
+ nil
101
+ end
102
+
103
+ def create(name, tracks)
104
+ @http.execute(
105
+ "mutation CreatePlaylist($name: String!, $tracks: [String!]!) { " \
106
+ "playlistCreate(name: $name, tracks: $tracks) }",
107
+ { name: name, tracks: tracks }
108
+ )
109
+ nil
110
+ end
111
+
112
+ def start(start_index: nil, elapsed: nil, offset: nil)
113
+ @http.execute(
114
+ "mutation PlaylistStart($startIndex: Int, $elapsed: Int, $offset: Int) { " \
115
+ "playlistStart(startIndex: $startIndex, elapsed: $elapsed, offset: $offset) }",
116
+ { start_index: start_index, elapsed: elapsed, offset: offset }.compact
117
+ )
118
+ nil
119
+ end
120
+
121
+ def resume
122
+ @http.execute("mutation PlaylistResume { playlistResume }")
123
+ nil
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../types"
4
+
5
+ module Rockbox
6
+ module Api
7
+ class SavedPlaylists
8
+ def initialize(http)
9
+ @http = http
10
+ end
11
+
12
+ def list(folder_id: nil)
13
+ data = @http.execute(
14
+ "query SavedPlaylists($folderId: String) { " \
15
+ "savedPlaylists(folderId: $folderId) { id name description image folderId trackCount createdAt updatedAt } }",
16
+ { folder_id: folder_id }.compact
17
+ )
18
+ Array(data[:saved_playlists]).map { |p| SavedPlaylist.from_hash(p) }
19
+ end
20
+
21
+ def get(id)
22
+ data = @http.execute(
23
+ "query SavedPlaylist($id: String!) { " \
24
+ "savedPlaylist(id: $id) { id name description image folderId trackCount createdAt updatedAt } }",
25
+ { id: id }
26
+ )
27
+ SavedPlaylist.from_hash(data[:saved_playlist])
28
+ end
29
+
30
+ def track_ids(playlist_id)
31
+ @http.execute(
32
+ "query SavedPlaylistTrackIds($playlistId: String!) { savedPlaylistTrackIds(playlistId: $playlistId) }",
33
+ { playlist_id: playlist_id }
34
+ )[:saved_playlist_track_ids] || []
35
+ end
36
+
37
+ # @example Builder-friendly
38
+ # client.saved_playlists.create(name: "Late nights") do |p|
39
+ # p.description = "After-dark vibes"
40
+ # p.track_ids = ["abc", "def"]
41
+ # end
42
+ def create(name:, description: nil, image: nil, folder_id: nil, track_ids: nil)
43
+ builder = CreateBuilder.new(name, description, image, folder_id, track_ids)
44
+ yield builder if block_given?
45
+
46
+ data = @http.execute(<<~GQL, builder.to_variables)
47
+ mutation CreateSavedPlaylist(
48
+ $name: String!, $description: String, $image: String,
49
+ $folderId: String, $trackIds: [String!]
50
+ ) {
51
+ createSavedPlaylist(
52
+ name: $name, description: $description, image: $image,
53
+ folderId: $folderId, trackIds: $trackIds
54
+ ) {
55
+ id name description image folderId trackCount createdAt updatedAt
56
+ }
57
+ }
58
+ GQL
59
+ SavedPlaylist.from_hash(data[:create_saved_playlist])
60
+ end
61
+
62
+ # @example
63
+ # client.saved_playlists.update("pl_123", name: "Renamed")
64
+ def update(id, name:, description: nil, image: nil, folder_id: nil)
65
+ @http.execute(<<~GQL, { id: id, name: name, description: description, image: image, folder_id: folder_id }.compact)
66
+ mutation UpdateSavedPlaylist(
67
+ $id: String!, $name: String!, $description: String,
68
+ $image: String, $folderId: String
69
+ ) {
70
+ updateSavedPlaylist(
71
+ id: $id, name: $name, description: $description,
72
+ image: $image, folderId: $folderId
73
+ )
74
+ }
75
+ GQL
76
+ nil
77
+ end
78
+
79
+ def delete(id)
80
+ @http.execute(
81
+ "mutation DeleteSavedPlaylist($id: String!) { deleteSavedPlaylist(id: $id) }",
82
+ { id: id }
83
+ )
84
+ nil
85
+ end
86
+
87
+ def add_tracks(playlist_id, track_ids)
88
+ @http.execute(
89
+ "mutation AddTracksToSavedPlaylist($playlistId: String!, $trackIds: [String!]!) { " \
90
+ "addTracksToSavedPlaylist(playlistId: $playlistId, trackIds: $trackIds) }",
91
+ { playlist_id: playlist_id, track_ids: track_ids }
92
+ )
93
+ nil
94
+ end
95
+
96
+ def remove_track(playlist_id, track_id)
97
+ @http.execute(
98
+ "mutation RemoveTrackFromSavedPlaylist($playlistId: String!, $trackId: String!) { " \
99
+ "removeTrackFromSavedPlaylist(playlistId: $playlistId, trackId: $trackId) }",
100
+ { playlist_id: playlist_id, track_id: track_id }
101
+ )
102
+ nil
103
+ end
104
+
105
+ def play(playlist_id)
106
+ @http.execute(
107
+ "mutation PlaySavedPlaylist($playlistId: String!) { playSavedPlaylist(playlistId: $playlistId) }",
108
+ { playlist_id: playlist_id }
109
+ )
110
+ nil
111
+ end
112
+
113
+ # ---------------------------------------------------------------------
114
+ # Folders
115
+ # ---------------------------------------------------------------------
116
+
117
+ def folders
118
+ data = @http.execute("query PlaylistFolders { playlistFolders { id name createdAt updatedAt } }")
119
+ Array(data[:playlist_folders]).map { |f| SavedPlaylistFolder.from_hash(f) }
120
+ end
121
+
122
+ def create_folder(name)
123
+ data = @http.execute(
124
+ "mutation CreatePlaylistFolder($name: String!) { " \
125
+ "createPlaylistFolder(name: $name) { id name createdAt updatedAt } }",
126
+ { name: name }
127
+ )
128
+ SavedPlaylistFolder.from_hash(data[:create_playlist_folder])
129
+ end
130
+
131
+ def delete_folder(id)
132
+ @http.execute(
133
+ "mutation DeletePlaylistFolder($id: String!) { deletePlaylistFolder(id: $id) }",
134
+ { id: id }
135
+ )
136
+ nil
137
+ end
138
+
139
+ # Builder for #create — supports the optional yield-block DSL.
140
+ class CreateBuilder
141
+ attr_accessor :name, :description, :image, :folder_id, :track_ids
142
+
143
+ def initialize(name, description, image, folder_id, track_ids)
144
+ @name = name
145
+ @description = description
146
+ @image = image
147
+ @folder_id = folder_id
148
+ @track_ids = track_ids
149
+ end
150
+
151
+ def to_variables
152
+ {
153
+ name: name,
154
+ description: description,
155
+ image: image,
156
+ folder_id: folder_id,
157
+ track_ids: track_ids
158
+ }.compact
159
+ end
160
+ end
161
+ end
162
+ end
163
+ end