rockbox_ffi 0.1.2 → 0.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 20f77ffe0b56e69c1749a47007c36837cd2b1a17a1c9cf36fd76a3ce42411af8
4
- data.tar.gz: 620692c18aada83e7aa9b1d1e282f159fd804a9db0e2303677f825a04367fa81
3
+ metadata.gz: 65435e6c7a7b4ef8e59f3bc088ce2c9eb978b34d49e134c3e42413e9790bb333
4
+ data.tar.gz: 0d14838882902eaa6818a9726518f0c782848ad34e09b6afe6ca9ddbbf6cdd1a
5
5
  SHA512:
6
- metadata.gz: 2217930492eb1d545fa86c0ffad14b90ec9144712320d03c46e595aba2fdc31d28ed1d256040d165a78c967465b3645b146117a40d45ce57ac06269d2e6da079
7
- data.tar.gz: c3bed2b5f9238be0d072d46a82b5c6e697ea61dc041915389c8db7a139b3cb2d3805caff51e76571de703f3333653c5dbed0ca27f37ef7af0a4a9d9a2ca8e093
6
+ metadata.gz: f4275075ebc6d35b7b495dfc3a081b97960f5dc81f8f279372acf5b852fc0723776f536f9f87602811499a23d1653de9a8c5b072144444fe15dd0cd4b4dc1066
7
+ data.tar.gz: 5b4a35ca244aed1f570cf2d639a131eb8244645fe4ebe86513193345a95716107c72c67f7037f195e8048bff4f885c1d3c8af1fdd218ffabcf200860b841e363
data/README.md CHANGED
@@ -10,6 +10,10 @@ via [`fiddle`](https://docs.ruby-lang.org/en/master/Fiddle.html) (Ruby stdlib)
10
10
  over the prebuilt `librockbox_ffi` shared library. No native extension is
11
11
  compiled — the gem `dlopen`s the shared library at load time.
12
12
 
13
+ > 📖 **Sound settings reference** — the equalizer, tone, crossfeed, compressor
14
+ > and other DSP controls mirror Rockbox's own. See the official
15
+ > [Rockbox manual — Sound Settings](https://download.rockbox.org/daily/manual/rockbox-ipodvideo/rockbox-buildch6.html).
16
+
13
17
  ## Setup
14
18
 
15
19
  Build the shared library once (from the repo root):
@@ -95,7 +99,7 @@ ruby -Ilib examples/play.rb [path] # play a file through the output device
95
99
  ## Interactive console
96
100
 
97
101
  ```sh
98
- ./bin/console # or: rake console
102
+ bundle exec rake console # or: ./bin/console
99
103
  ```
100
104
 
101
105
  Drops into IRB with `RockboxFFI` loaded and `FIXTURE` pointing at a sample
@@ -108,6 +112,20 @@ p.set_queue([FIXTURE]); p.play
108
112
  p.status[:state] # => "playing"
109
113
  ```
110
114
 
115
+ The console bundles `irb` + `reline`, so **Tab autocompletion** and **syntax
116
+ highlighting** work out of the box — start typing `RockboxFFI::` and press
117
+ `Tab`. Both are on by default; toggle per-session with `irb --noautocomplete`,
118
+ or persist preferences in `~/.irbrc`:
119
+
120
+ ```ruby
121
+ IRB.conf[:USE_AUTOCOMPLETE] = true
122
+ IRB.conf[:USE_COLORIZE] = true
123
+ ```
124
+
125
+ > Run the console under a modern Ruby (3.x/4.x, e.g. Homebrew's
126
+ > `/opt/homebrew/opt/ruby`). macOS system Ruby 2.6 can't build `fiddle`'s
127
+ > native extension, so `bundle install` fails there.
128
+
111
129
  ## Test
112
130
 
113
131
  ```sh
data/examples/play.rb CHANGED
@@ -13,9 +13,16 @@ FIXTURE = File.join(REPO, "crates", "rocksky", "fixtures", "08 - Internet Money
13
13
  file = ARGV[0] || FIXTURE
14
14
 
15
15
  player = RockboxFFI::Player.new(volume: 0.8)
16
- player.set_queue([file])
17
- player.play
16
+ # Mutating setters return self, so the setup reads as one fluent chain.
17
+ # DSP: Bass Boost preset + a +7 dB bass / +4 dB treble lift.
18
+ player
19
+ .set_queue([file])
20
+ .set_eq_preset(RockboxFFI::EqPreset::BASS_BOOST)
21
+ .set_bass(7)
22
+ .set_treble(4)
23
+ .play
18
24
  puts "▶ playing #{file}"
25
+ puts "eq: BassBoost preset, bass +7 dB, treble +4 dB"
19
26
 
20
27
  # Reinstall a SIGINT handler AFTER the player boots: the native audio engine
21
28
  # installs its own signal handler while starting the output device, which
@@ -18,6 +18,13 @@ module RockboxFFI
18
18
  ALBUM = 2
19
19
  end
20
20
 
21
+ # Values for Player#set_repeat / #repeat.
22
+ module RepeatMode
23
+ OFF = 0
24
+ ONE = 1
25
+ ALL = 2
26
+ end
27
+
21
28
  module CrossfadeMode
22
29
  OFF = 0
23
30
  AUTO_SKIP = 1
@@ -32,6 +39,19 @@ module RockboxFFI
32
39
  MIX = 1
33
40
  end
34
41
 
42
+ # Where inserted tracks land in the queue (Player#insert / #import_m3u).
43
+ # INDEX (7) uses the explicit +index+ argument.
44
+ module InsertPosition
45
+ PREPEND = 0
46
+ INSERT = 1
47
+ INSERT_NEXT = 2
48
+ INSERT_LAST = 3
49
+ INSERT_SHUFFLED = 4
50
+ INSERT_LAST_SHUFFLED = 5
51
+ REPLACE = 6
52
+ INDEX = 7
53
+ end
54
+
35
55
  module ChannelConfig
36
56
  STEREO = 0
37
57
  MONO = 1
@@ -41,4 +61,47 @@ module RockboxFFI
41
61
  KARAOKE = 5
42
62
  SWAP = 6
43
63
  end
64
+
65
+ # Built-in EQ presets for Player#set_eq_preset.
66
+ module EqPreset
67
+ FLAT = 0
68
+ ACOUSTIC = 1
69
+ BASS_BOOST = 2
70
+ BASS_REDUCER = 3
71
+ CLASSICAL = 4
72
+ DANCE = 5
73
+ DEEP = 6
74
+ ELECTRONIC = 7
75
+ HIP_HOP = 8
76
+ JAZZ = 9
77
+ LATIN = 10
78
+ LOUDNESS = 11
79
+ LOUNGE = 12
80
+ PIANO = 13
81
+ POP = 14
82
+ RNB = 15
83
+ ROCK = 16
84
+ SMALL_SPEAKERS = 17
85
+ TREBLE_BOOST = 18
86
+ TREBLE_REDUCER = 19
87
+ VOCAL_BOOST = 20
88
+ end
89
+
90
+ # Crossfeed mode for Player#set_crossfeed.
91
+ module CrossfeedMode
92
+ OFF = 0
93
+ MEIER = 1
94
+ CUSTOM = 2
95
+ end
96
+
97
+ # Channel mode for Player#set_channel_mode.
98
+ module ChannelMode
99
+ STEREO = 0
100
+ MONO = 1
101
+ CUSTOM = 2
102
+ MONO_LEFT = 3
103
+ MONO_RIGHT = 4
104
+ KARAOKE = 5
105
+ SWAP = 6
106
+ end
44
107
  end
@@ -101,9 +101,12 @@ module RockboxFFI
101
101
  # ---- player -------------------------------------------------------
102
102
  extern "void* rb_player_new()"
103
103
  extern "void* rb_player_new_with_config(uint32_t, float, float, int32_t, float, bool, int32_t, uint32_t, uint32_t, uint32_t, uint32_t, int32_t)"
104
+ extern "void* rb_player_new_with_config_ex(uint32_t, float, float, int32_t, float, bool, int32_t, uint32_t, uint32_t, uint32_t, uint32_t, int32_t, void*, uint32_t)"
104
105
  extern "void rb_player_free(void*)"
105
106
  extern "void rb_player_set_queue_json(void*, void*)"
106
107
  extern "void rb_player_enqueue(void*, void*)"
108
+ extern "void rb_player_insert_json(void*, void*, int32_t, size_t)"
109
+ extern "void* rb_player_queue_json(void*)"
107
110
  extern "void rb_player_play(void*)"
108
111
  extern "void rb_player_pause(void*)"
109
112
  extern "void rb_player_toggle(void*)"
@@ -118,6 +121,46 @@ module RockboxFFI
118
121
  extern "float rb_player_volume(void*)"
119
122
  extern "uint32_t rb_player_sample_rate(void*)"
120
123
  extern "void* rb_player_status_json(void*)"
124
+ extern "void rb_player_set_shuffle(void*, bool)"
125
+ extern "bool rb_player_is_shuffle_enabled(void*)"
126
+ extern "void rb_player_set_repeat(void*, int32_t)"
127
+ extern "int32_t rb_player_repeat(void*)"
128
+
129
+ # ---- player DSP ---------------------------------------------------
130
+ extern "void rb_player_set_eq_enabled(void*, bool)"
131
+ extern "bool rb_player_is_eq_enabled(void*)"
132
+ extern "void rb_player_set_eq_band(void*, size_t, int32_t, float, float)"
133
+ extern "void rb_player_set_eq_precut(void*, float)"
134
+ extern "void rb_player_set_eq_preset(void*, int32_t)"
135
+ extern "void rb_player_set_tone(void*, int32_t, int32_t, int32_t, int32_t)"
136
+ extern "void rb_player_set_bass(void*, int32_t)"
137
+ extern "void rb_player_set_treble(void*, int32_t)"
138
+ extern "void rb_player_set_bass_cutoff(void*, int32_t)"
139
+ extern "void rb_player_set_treble_cutoff(void*, int32_t)"
140
+ extern "void rb_player_set_crossfeed(void*, int32_t, int32_t, int32_t, int32_t, int32_t)"
141
+ extern "void rb_player_set_bass_enhancement(void*, int32_t, int32_t)"
142
+ extern "void rb_player_set_fatigue_reduction(void*, int32_t)"
143
+ extern "void rb_player_set_surround(void*, int32_t, int32_t, int32_t, int32_t)"
144
+ extern "void rb_player_set_channel_mode(void*, int32_t)"
145
+ extern "void rb_player_set_stereo_width(void*, int32_t)"
146
+ extern "void rb_player_set_compressor(void*, int32_t, int32_t, int32_t, int32_t, int32_t, int32_t)"
147
+ extern "void rb_player_set_dither(void*, bool)"
148
+ extern "void rb_player_set_pitch(void*, int32_t)"
149
+ extern "void* rb_player_dsp_settings_json(void*)"
150
+
151
+ # ---- resume -------------------------------------------------------
152
+ extern "void* rb_player_resume(void*)"
153
+ extern "void rb_player_save_resume(void*)"
154
+ extern "void rb_player_clear_resume(void*)"
155
+ extern "void* rb_load_resume_json(void*)"
156
+
157
+ # ---- m3u / m3u8 playlists -----------------------------------------
158
+ extern "void* rb_player_import_m3u(void*, void*, int32_t, size_t)"
159
+ extern "void* rb_player_load_m3u(void*, void*)"
160
+ extern "int32_t rb_player_export_m3u(void*, void*)"
161
+ extern "void* rb_m3u_read_json(void*)"
162
+ extern "int32_t rb_m3u_write_json(void*, void*)"
163
+ extern "bool rb_is_url(void*)"
121
164
  end
122
165
 
123
166
  # true/false → 1/0 for the ABI's `bool` (declared as int above).
@@ -11,6 +11,11 @@ module RockboxFFI
11
11
  #
12
12
  # ReplayGain +mode+ here uses the *player* values: 0 off, 1 track, 2 album
13
13
  # (see ReplayGainMode) — distinct from the DSP encoding.
14
+ #
15
+ # Mutating setters (queue, transport, settings, DSP) return +self+ so calls
16
+ # can be fluently chained, e.g.
17
+ # player.set_queue([file]).set_shuffle(true).play
18
+ # Getters/queries and lifecycle methods keep their own return values.
14
19
  class Player
15
20
  DEFAULT_CONFIG = {
16
21
  sample_rate: 0, # 0 => device default
@@ -24,7 +29,9 @@ module RockboxFFI
24
29
  fade_out_duration_ms: 2000,
25
30
  fade_in_delay_ms: 0,
26
31
  fade_in_duration_ms: 2000,
27
- mix_mode: MixMode::CROSSFADE
32
+ mix_mode: MixMode::CROSSFADE,
33
+ resume_file: nil, # an .m3u8 to auto-persist queue + position to
34
+ resume_save_interval_ms: 0 # 0 => 5 s default
28
35
  }.freeze
29
36
 
30
37
  # Open a Player; if a block is given, close it automatically afterwards.
@@ -47,16 +54,19 @@ module RockboxFFI
47
54
  end
48
55
 
49
56
  # Create a player with configuration overrides (see DEFAULT_CONFIG keys).
50
- # sample_rate: 0 means the device default.
57
+ # sample_rate: 0 means the device default. Passing +resume_file:+ enables
58
+ # auto-persisting the queue + exact position to that .m3u8 file.
51
59
  def initialize(**opts)
52
60
  c = DEFAULT_CONFIG.merge(opts)
53
- ptr = Lib.rb_player_new_with_config(
61
+ resume_file = c[:resume_file].nil? ? nil : c[:resume_file].to_s
62
+ ptr = Lib.rb_player_new_with_config_ex(
54
63
  Integer(c[:sample_rate]), Float(c[:buffer_seconds]), Float(c[:volume]),
55
64
  Integer(c[:replaygain_mode]), Float(c[:replaygain_preamp_db]),
56
65
  RockboxFFI.b(c[:replaygain_prevent_clipping]), Integer(c[:crossfade_mode]),
57
66
  Integer(c[:fade_out_delay_ms]), Integer(c[:fade_out_duration_ms]),
58
67
  Integer(c[:fade_in_delay_ms]), Integer(c[:fade_in_duration_ms]),
59
- Integer(c[:mix_mode])
68
+ Integer(c[:mix_mode]), resume_file,
69
+ Integer(c[:resume_save_interval_ms])
60
70
  )
61
71
  init_ptr(ptr)
62
72
  end
@@ -80,50 +90,83 @@ module RockboxFFI
80
90
  private_class_method :finalizer
81
91
 
82
92
  # -- queue ------------------------------------------------------------
93
+ # Replace the queue. Each entry may be a local file path, an http(s)://
94
+ # URL to a finite remote file, or a live-radio / streaming URL.
83
95
  def set_queue(paths)
84
96
  Lib.rb_player_set_queue_json(@ptr, JSON.generate(Array(paths).map(&:to_s)))
97
+ self
85
98
  end
86
99
 
100
+ # Append one track to the queue. +path+ may be a local file path, an
101
+ # http(s):// URL to a finite remote file, or a live-radio / streaming URL.
87
102
  def enqueue(path)
88
103
  Lib.rb_player_enqueue(@ptr, path.to_s)
104
+ self
105
+ end
106
+
107
+ # Insert +paths+ (a path/URL or Array of them) into the queue at
108
+ # +position+ (see InsertPosition). +index+ is only used when position is
109
+ # InsertPosition::INDEX (7).
110
+ def insert(paths, position, index = 0)
111
+ Lib.rb_player_insert_json(
112
+ @ptr, JSON.generate(Array(paths).map(&:to_s)), Integer(position), Integer(index)
113
+ )
114
+ self
115
+ end
116
+
117
+ # The current queue as an Array of String paths/URLs.
118
+ def queue
119
+ s = RockboxFFI.take_string(Lib.rb_player_queue_json(@ptr))
120
+ return [] if s.nil?
121
+
122
+ JSON.parse(s)
89
123
  end
90
124
 
91
125
  # -- transport --------------------------------------------------------
92
126
  def play
93
127
  Lib.rb_player_play(@ptr)
128
+ self
94
129
  end
95
130
 
96
131
  def pause
97
132
  Lib.rb_player_pause(@ptr)
133
+ self
98
134
  end
99
135
 
100
136
  def toggle
101
137
  Lib.rb_player_toggle(@ptr)
138
+ self
102
139
  end
103
140
 
104
141
  def stop
105
142
  Lib.rb_player_stop(@ptr)
143
+ self
106
144
  end
107
145
 
108
146
  def next
109
147
  Lib.rb_player_next(@ptr)
148
+ self
110
149
  end
111
150
 
112
151
  def previous
113
152
  Lib.rb_player_previous(@ptr)
153
+ self
114
154
  end
115
155
 
116
156
  def skip_to(index)
117
157
  Lib.rb_player_skip_to(@ptr, Integer(index))
158
+ self
118
159
  end
119
160
 
120
161
  def seek_ms(ms)
121
162
  Lib.rb_player_seek_ms(@ptr, Integer(ms))
163
+ self
122
164
  end
123
165
 
124
166
  # -- settings ---------------------------------------------------------
125
167
  def set_volume(vol)
126
168
  Lib.rb_player_set_volume(@ptr, Float(vol))
169
+ self
127
170
  end
128
171
 
129
172
  def volume
@@ -141,11 +184,173 @@ module RockboxFFI
141
184
  @ptr, Integer(mode), Integer(fade_out_delay_ms), Integer(fade_out_duration_ms),
142
185
  Integer(fade_in_delay_ms), Integer(fade_in_duration_ms), Integer(mix_mode)
143
186
  )
187
+ self
144
188
  end
145
189
 
146
190
  # mode: see ReplayGainMode (OFF=0, TRACK=1, ALBUM=2).
147
191
  def set_replaygain(mode, preamp_db, prevent_clipping)
148
192
  Lib.rb_player_set_replaygain(@ptr, Integer(mode), Float(preamp_db), RockboxFFI.b(prevent_clipping))
193
+ self
194
+ end
195
+
196
+ # Enable/disable shuffle.
197
+ def set_shuffle(enabled)
198
+ Lib.rb_player_set_shuffle(@ptr, RockboxFFI.b(enabled))
199
+ self
200
+ end
201
+
202
+ # Whether shuffle is currently enabled.
203
+ def shuffle_enabled?
204
+ !Lib.rb_player_is_shuffle_enabled(@ptr).zero?
205
+ end
206
+
207
+ # Set the repeat mode (see RepeatMode: OFF=0, ONE=1, ALL=2).
208
+ def set_repeat(mode)
209
+ Lib.rb_player_set_repeat(@ptr, Integer(mode))
210
+ self
211
+ end
212
+
213
+ # The current repeat mode as an Integer (see RepeatMode).
214
+ def repeat
215
+ Lib.rb_player_repeat(@ptr)
216
+ end
217
+
218
+ # -- DSP --------------------------------------------------------------
219
+ # Enable/disable the parametric equalizer.
220
+ def set_eq_enabled(enabled)
221
+ Lib.rb_player_set_eq_enabled(@ptr, RockboxFFI.b(enabled))
222
+ self
223
+ end
224
+
225
+ # Whether the parametric equalizer is currently enabled.
226
+ def eq_enabled?
227
+ !Lib.rb_player_is_eq_enabled(@ptr).zero?
228
+ end
229
+
230
+ # Configure one EQ band: +band+ index, +cutoff_hz+ center frequency,
231
+ # +q+ factor, +gain_db+ gain in dB.
232
+ def set_eq_band(band, cutoff_hz, q, gain_db)
233
+ Lib.rb_player_set_eq_band(@ptr, Integer(band), Integer(cutoff_hz), Float(q), Float(gain_db))
234
+ self
235
+ end
236
+
237
+ # Global EQ pre-cut in dB.
238
+ def set_eq_precut(db)
239
+ Lib.rb_player_set_eq_precut(@ptr, Float(db))
240
+ self
241
+ end
242
+
243
+ # Apply a built-in EQ preset (see EqPreset).
244
+ def set_eq_preset(preset)
245
+ Lib.rb_player_set_eq_preset(@ptr, Integer(preset))
246
+ self
247
+ end
248
+
249
+ # Bass/treble tone controls with explicit cutoff frequencies.
250
+ def set_tone(bass_db, treble_db, bass_cutoff_hz, treble_cutoff_hz)
251
+ Lib.rb_player_set_tone(
252
+ @ptr, Integer(bass_db), Integer(treble_db),
253
+ Integer(bass_cutoff_hz), Integer(treble_cutoff_hz)
254
+ )
255
+ self
256
+ end
257
+
258
+ # Bass gain in dB.
259
+ def set_bass(bass_db)
260
+ Lib.rb_player_set_bass(@ptr, Integer(bass_db))
261
+ self
262
+ end
263
+
264
+ # Treble gain in dB.
265
+ def set_treble(treble_db)
266
+ Lib.rb_player_set_treble(@ptr, Integer(treble_db))
267
+ self
268
+ end
269
+
270
+ # Bass tone-control cutoff frequency in Hz.
271
+ def set_bass_cutoff(hz)
272
+ Lib.rb_player_set_bass_cutoff(@ptr, Integer(hz))
273
+ self
274
+ end
275
+
276
+ # Treble tone-control cutoff frequency in Hz.
277
+ def set_treble_cutoff(hz)
278
+ Lib.rb_player_set_treble_cutoff(@ptr, Integer(hz))
279
+ self
280
+ end
281
+
282
+ # Crossfeed for headphone listening. +mode+ (see CrossfeedMode: OFF=0,
283
+ # MEIER=1, CUSTOM=2), plus direct/cross/high-frequency gains and the
284
+ # high-frequency cutoff (Hz) used in CUSTOM mode.
285
+ def set_crossfeed(mode, direct_gain, cross_gain, hf_gain, hf_cutoff)
286
+ Lib.rb_player_set_crossfeed(
287
+ @ptr, Integer(mode), Integer(direct_gain), Integer(cross_gain),
288
+ Integer(hf_gain), Integer(hf_cutoff)
289
+ )
290
+ self
291
+ end
292
+
293
+ # Bass enhancement: +strength+ and +precut+ (in dB).
294
+ def set_bass_enhancement(strength, precut)
295
+ Lib.rb_player_set_bass_enhancement(@ptr, Integer(strength), Integer(precut))
296
+ self
297
+ end
298
+
299
+ # Listening-fatigue reduction (treble roll-off): +strength+.
300
+ def set_fatigue_reduction(strength)
301
+ Lib.rb_player_set_fatigue_reduction(@ptr, Integer(strength))
302
+ self
303
+ end
304
+
305
+ # Surround effect: delay (ms), balance, low/high cutoff frequencies (Hz).
306
+ def set_surround(delay_ms, balance, cutoff_low_hz, cutoff_high_hz)
307
+ Lib.rb_player_set_surround(
308
+ @ptr, Integer(delay_ms), Integer(balance),
309
+ Integer(cutoff_low_hz), Integer(cutoff_high_hz)
310
+ )
311
+ self
312
+ end
313
+
314
+ # Channel mode (see ChannelMode).
315
+ def set_channel_mode(mode)
316
+ Lib.rb_player_set_channel_mode(@ptr, Integer(mode))
317
+ self
318
+ end
319
+
320
+ # Stereo width as a percentage.
321
+ def set_stereo_width(percent)
322
+ Lib.rb_player_set_stereo_width(@ptr, Integer(percent))
323
+ self
324
+ end
325
+
326
+ # Dynamic-range compressor: threshold (dB), makeup gain, ratio, knee,
327
+ # attack (ms), release (ms).
328
+ def set_compressor(threshold_db, makeup_gain, ratio, knee, attack_ms, release_ms)
329
+ Lib.rb_player_set_compressor(
330
+ @ptr, Integer(threshold_db), Integer(makeup_gain), Integer(ratio),
331
+ Integer(knee), Integer(attack_ms), Integer(release_ms)
332
+ )
333
+ self
334
+ end
335
+
336
+ # Enable/disable output dithering.
337
+ def set_dither(enabled)
338
+ Lib.rb_player_set_dither(@ptr, RockboxFFI.b(enabled))
339
+ self
340
+ end
341
+
342
+ # Pitch shift ratio.
343
+ def set_pitch(ratio)
344
+ Lib.rb_player_set_pitch(@ptr, Integer(ratio))
345
+ self
346
+ end
347
+
348
+ # A snapshot of the current DSP settings as a Hash with symbol keys.
349
+ def dsp_settings
350
+ s = RockboxFFI.take_string(Lib.rb_player_dsp_settings_json(@ptr))
351
+ raise "rb_player_dsp_settings_json returned NULL" if s.nil?
352
+
353
+ JSON.parse(s, symbolize_names: true)
149
354
  end
150
355
 
151
356
  # -- status -----------------------------------------------------------
@@ -157,6 +362,51 @@ module RockboxFFI
157
362
  JSON.parse(s, symbolize_names: true)
158
363
  end
159
364
 
365
+ # -- resume -----------------------------------------------------------
366
+ # Restore the queue + exact position from the resume file (does NOT
367
+ # auto-play). Returns a Hash {tracks:, index:, elapsed_ms:} or nil.
368
+ def resume
369
+ s = RockboxFFI.take_string(Lib.rb_player_resume(@ptr))
370
+ return nil if s.nil?
371
+
372
+ JSON.parse(s, symbolize_names: true)
373
+ end
374
+
375
+ # Force-persist the current queue + position to the resume file now.
376
+ def save_resume
377
+ Lib.rb_player_save_resume(@ptr)
378
+ end
379
+
380
+ # Delete the resume file.
381
+ def clear_resume
382
+ Lib.rb_player_clear_resume(@ptr)
383
+ end
384
+
385
+ # -- m3u / m3u8 playlists ---------------------------------------------
386
+ # Import a playlist file into the queue at +position+ (see InsertPosition;
387
+ # +index+ only used for INDEX). Returns the imported paths as an Array.
388
+ def import_m3u(path, position, index = 0)
389
+ s = RockboxFFI.take_string(
390
+ Lib.rb_player_import_m3u(@ptr, path.to_s, Integer(position), Integer(index))
391
+ )
392
+ return [] if s.nil?
393
+
394
+ JSON.parse(s)
395
+ end
396
+
397
+ # Replace the queue with a playlist file. Returns the loaded paths as an Array.
398
+ def load_m3u(path)
399
+ s = RockboxFFI.take_string(Lib.rb_player_load_m3u(@ptr, path.to_s))
400
+ return [] if s.nil?
401
+
402
+ JSON.parse(s)
403
+ end
404
+
405
+ # Export the current queue to an .m3u8 (atomic). Returns true on success.
406
+ def export_m3u(path)
407
+ Lib.rb_player_export_m3u(@ptr, path.to_s).zero?
408
+ end
409
+
160
410
  private
161
411
 
162
412
  def init_ptr(ptr)
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module RockboxFFI
4
- VERSION = "0.1.2"
2
+ VERSION = "0.3.0"
5
3
  end
data/lib/rockbox_ffi.rb CHANGED
@@ -12,9 +12,41 @@ require "rockbox_ffi/metadata"
12
12
  require "rockbox_ffi/dsp"
13
13
  require "rockbox_ffi/player"
14
14
 
15
+ require "json"
16
+
15
17
  module RockboxFFI
16
18
  # ABI major version of the loaded library (bumped on breaking changes).
17
19
  def self.abi_version
18
20
  Lib.rb_ffi_abi_version
19
21
  end
22
+
23
+ # Peek at a resume file without a Player. Returns a Hash
24
+ # {tracks:, index:, elapsed_ms:} or nil if the file is absent/invalid.
25
+ def self.load_resume(path)
26
+ s = take_string(Lib.rb_load_resume_json(path.to_s))
27
+ return nil if s.nil?
28
+
29
+ JSON.parse(s, symbolize_names: true)
30
+ end
31
+
32
+ # Parse a playlist (.m3u / .m3u8) into an Array of Hashes
33
+ # {path:, duration_ms:, title:}. Returns nil on failure.
34
+ def self.m3u_read(path)
35
+ s = take_string(Lib.rb_m3u_read_json(path.to_s))
36
+ return nil if s.nil?
37
+
38
+ JSON.parse(s, symbolize_names: true)
39
+ end
40
+
41
+ # Write +paths+ (a path/URL or Array of them) as an .m3u8. Returns true on
42
+ # success.
43
+ def self.m3u_write(path, paths)
44
+ Lib.rb_m3u_write_json(path.to_s, JSON.generate(Array(paths).map(&:to_s))).zero?
45
+ end
46
+
47
+ # Whether +s+ looks like an http(s):// URL.
48
+ def self.is_url?(s)
49
+ r = Lib.rb_is_url(s.to_s)
50
+ r == true || r == 1
51
+ end
20
52
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rockbox_ffi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tsiry Sandratraina
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-09 00:00:00.000000000 Z
11
+ date: 2026-07-12 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: fiddle