audite 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.
- data/.gitignore +4 -0
- data/README.md +51 -0
- data/audite.gemspec +21 -0
- data/bin/audite +44 -0
- data/ext/mpg123/extconf.rb +13 -0
- data/ext/mpg123/mpg123.c +139 -0
- data/ext/portaudio/extconf.rb +13 -0
- data/ext/portaudio/portaudio.c +151 -0
- data/lib/audite.rb +153 -0
- metadata +58 -0
data/.gitignore
ADDED
data/README.md
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
Audite - a portable mp3 player in Ruby
|
2
|
+
======================================
|
3
|
+
|
4
|
+
Audite is a portable ruby library for playing mp3 files built on
|
5
|
+
libmp123 and portaudio.
|
6
|
+
|
7
|
+
## Features
|
8
|
+
|
9
|
+
* Nonblocking playback using native threads
|
10
|
+
* Realtime seeking
|
11
|
+
* Realtime events
|
12
|
+
* Progress information
|
13
|
+
* Simple level meter
|
14
|
+
|
15
|
+
## Player
|
16
|
+
|
17
|
+
Audite ships an example player, just try `audite test.mp3`.
|
18
|
+
|
19
|
+
## Example
|
20
|
+
|
21
|
+
```
|
22
|
+
require 'audite'
|
23
|
+
|
24
|
+
player = Audite.new
|
25
|
+
|
26
|
+
player.events.on(:complete) do
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
|
30
|
+
player.events.on(:level) do |level|
|
31
|
+
puts level
|
32
|
+
end
|
33
|
+
|
34
|
+
player.events.on(:position_change) do |pos|
|
35
|
+
puts pos
|
36
|
+
end
|
37
|
+
|
38
|
+
player.load('test.mp3')
|
39
|
+
player.start_stream
|
40
|
+
player.forward(20)
|
41
|
+
|
42
|
+
```
|
43
|
+
|
44
|
+
## OSX Install
|
45
|
+
|
46
|
+
```
|
47
|
+
brew install portaudio
|
48
|
+
brew install mpg123
|
49
|
+
|
50
|
+
gem install audite
|
51
|
+
```
|
data/audite.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "audite"
|
5
|
+
s.version = "0.1.0"
|
6
|
+
s.author = "Matthias Georgi"
|
7
|
+
s.email = "matti.georgi@gmail.com"
|
8
|
+
s.homepage = "http://georgi.github.com/audite"
|
9
|
+
s.summary = "Portable mp3 player"
|
10
|
+
s.description = "Portable mp3 player built on mpg123 and portaudio"
|
11
|
+
|
12
|
+
s.bindir = 'bin'
|
13
|
+
s.files = `git ls-files`.split("\n")
|
14
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
|
17
|
+
s.extra_rdoc_files = ["README.md"]
|
18
|
+
|
19
|
+
s.extensions << 'ext/mpg123/extconf.rb'
|
20
|
+
s.extensions << 'ext/portaudio/extconf.rb'
|
21
|
+
end
|
data/bin/audite
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'audite'
|
4
|
+
require 'curses'
|
5
|
+
|
6
|
+
player = Audite.new
|
7
|
+
player.load(ARGV[0])
|
8
|
+
player.start_stream
|
9
|
+
|
10
|
+
player.events.on(:complete) do
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
|
14
|
+
player.events.on(:level) do |level|
|
15
|
+
p = (level * Curses.cols).ceil
|
16
|
+
Curses.setpos(2, 0)
|
17
|
+
Curses.addstr(">" * p + " " * (Curses.cols - p))
|
18
|
+
Curses.refresh
|
19
|
+
end
|
20
|
+
|
21
|
+
player.events.on(:position_change) do |pos|
|
22
|
+
p = ((pos / player.length_in_seconds) * Curses.cols).ceil
|
23
|
+
Curses.setpos(0, 0)
|
24
|
+
Curses.addstr("playing #{ARGV[0]} #{player.position.ceil} seconds of #{player.length_in_seconds.ceil} total")
|
25
|
+
Curses.setpos(1, 0)
|
26
|
+
Curses.addstr("#" * p + " " * (Curses.cols - p))
|
27
|
+
Curses.refresh
|
28
|
+
end
|
29
|
+
|
30
|
+
Curses.init_screen
|
31
|
+
Curses.noecho
|
32
|
+
Curses.stdscr.keypad(true)
|
33
|
+
Curses.clear
|
34
|
+
|
35
|
+
while c = Curses.getch
|
36
|
+
case c
|
37
|
+
when Curses::KEY_LEFT
|
38
|
+
player.rewind
|
39
|
+
when Curses::KEY_RIGHT
|
40
|
+
player.forward
|
41
|
+
when ' '
|
42
|
+
player.toggle
|
43
|
+
end
|
44
|
+
end
|
data/ext/mpg123/mpg123.c
ADDED
@@ -0,0 +1,139 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
|
3
|
+
#include <stdio.h>
|
4
|
+
#include <strings.h>
|
5
|
+
#include <mpg123.h>
|
6
|
+
|
7
|
+
void cleanup(mpg123_handle *mh)
|
8
|
+
{
|
9
|
+
mpg123_close(mh);
|
10
|
+
mpg123_delete(mh);
|
11
|
+
}
|
12
|
+
|
13
|
+
static VALUE rb_cMpg123;
|
14
|
+
|
15
|
+
VALUE rb_mpg123_new(VALUE klass, VALUE filename) {
|
16
|
+
int err = MPG123_OK;
|
17
|
+
mpg123_handle *mh;
|
18
|
+
VALUE mpg123;
|
19
|
+
long rate;
|
20
|
+
int channels, encoding;
|
21
|
+
|
22
|
+
Check_Type(filename, T_STRING);
|
23
|
+
|
24
|
+
if ((mh = mpg123_new(NULL, &err)) == NULL) {
|
25
|
+
rb_raise(rb_eStandardError, "%s", mpg123_plain_strerror(err));
|
26
|
+
}
|
27
|
+
|
28
|
+
mpg123_param(mh, MPG123_ADD_FLAGS, MPG123_FORCE_FLOAT, 0.);
|
29
|
+
|
30
|
+
if (mpg123_open(mh, (char*) RSTRING_PTR(filename)) != MPG123_OK ||
|
31
|
+
mpg123_getformat(mh, &rate, &channels, &encoding) != MPG123_OK) {
|
32
|
+
rb_raise(rb_eStandardError, "%s", mpg123_strerror(mh));
|
33
|
+
}
|
34
|
+
|
35
|
+
if (encoding != MPG123_ENC_FLOAT_32) {
|
36
|
+
rb_raise(rb_eStandardError, "bad encoding");
|
37
|
+
}
|
38
|
+
|
39
|
+
mpg123_format_none(mh);
|
40
|
+
mpg123_format(mh, rate, channels, encoding);
|
41
|
+
|
42
|
+
return Data_Wrap_Struct(rb_cMpg123, 0, cleanup, mh);
|
43
|
+
}
|
44
|
+
|
45
|
+
VALUE rb_mpg123_close(VALUE self)
|
46
|
+
{
|
47
|
+
mpg123_close(DATA_PTR(self));
|
48
|
+
return self;
|
49
|
+
}
|
50
|
+
|
51
|
+
|
52
|
+
VALUE rb_mpg123_read(VALUE self, VALUE _size)
|
53
|
+
{
|
54
|
+
int size = FIX2INT(_size);
|
55
|
+
float *buffer = malloc(size * sizeof(float));
|
56
|
+
VALUE result = rb_ary_new2(size);
|
57
|
+
mpg123_handle *mh = NULL;
|
58
|
+
size_t done = 0;
|
59
|
+
int i;
|
60
|
+
int err = MPG123_OK;
|
61
|
+
|
62
|
+
Data_Get_Struct(self, mpg123_handle, mh);
|
63
|
+
err = mpg123_read(mh, (unsigned char *) buffer, size * sizeof(float), &done);
|
64
|
+
|
65
|
+
if (err == MPG123_OK || err == MPG123_DONE) {
|
66
|
+
for (i = 0; i < size; i++) {
|
67
|
+
rb_ary_store(result, i, rb_float_new(buffer[i]));
|
68
|
+
}
|
69
|
+
return result;
|
70
|
+
}
|
71
|
+
else {
|
72
|
+
rb_raise(rb_eStandardError, "%s", mpg123_plain_strerror(err));
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
VALUE rb_mpg123_length(VALUE self)
|
77
|
+
{
|
78
|
+
return INT2FIX(mpg123_length(DATA_PTR(self)));
|
79
|
+
}
|
80
|
+
|
81
|
+
VALUE rb_mpg123_spf(VALUE self)
|
82
|
+
{
|
83
|
+
return rb_float_new(mpg123_spf(DATA_PTR(self)));
|
84
|
+
}
|
85
|
+
|
86
|
+
VALUE rb_mpg123_tpf(VALUE self)
|
87
|
+
{
|
88
|
+
return rb_float_new(mpg123_tpf(DATA_PTR(self)));
|
89
|
+
}
|
90
|
+
|
91
|
+
VALUE rb_mpg123_tell(VALUE self)
|
92
|
+
{
|
93
|
+
return INT2FIX(mpg123_tell(DATA_PTR(self)));
|
94
|
+
}
|
95
|
+
|
96
|
+
VALUE rb_mpg123_tellframe(VALUE self)
|
97
|
+
{
|
98
|
+
return INT2FIX(mpg123_tellframe(DATA_PTR(self)));
|
99
|
+
}
|
100
|
+
|
101
|
+
VALUE rb_mpg123_seek(VALUE self, VALUE offset)
|
102
|
+
{
|
103
|
+
return INT2FIX(mpg123_seek(DATA_PTR(self), FIX2INT(offset), SEEK_SET));
|
104
|
+
}
|
105
|
+
|
106
|
+
VALUE rb_mpg123_seek_frame(VALUE self, VALUE offset)
|
107
|
+
{
|
108
|
+
return INT2FIX(mpg123_seek_frame(DATA_PTR(self), FIX2INT(offset), SEEK_SET));
|
109
|
+
}
|
110
|
+
|
111
|
+
VALUE rb_mpg123_timeframe(VALUE self, VALUE seconds)
|
112
|
+
{
|
113
|
+
return INT2FIX(mpg123_timeframe(DATA_PTR(self), NUM2DBL(seconds)));
|
114
|
+
}
|
115
|
+
|
116
|
+
void Init_mpg123(void) {
|
117
|
+
int err = MPG123_OK;
|
118
|
+
|
119
|
+
err = mpg123_init();
|
120
|
+
|
121
|
+
if (err != MPG123_OK) {
|
122
|
+
rb_raise(rb_eStandardError, "%s", mpg123_plain_strerror(err));
|
123
|
+
}
|
124
|
+
|
125
|
+
rb_cMpg123 = rb_define_class("Mpg123", rb_cObject);
|
126
|
+
|
127
|
+
rb_define_singleton_method(rb_cMpg123, "new", rb_mpg123_new, 1);
|
128
|
+
|
129
|
+
rb_define_method(rb_cMpg123, "close", rb_mpg123_close, 0);
|
130
|
+
rb_define_method(rb_cMpg123, "read", rb_mpg123_read, 1);
|
131
|
+
rb_define_method(rb_cMpg123, "length", rb_mpg123_length, 0);
|
132
|
+
rb_define_method(rb_cMpg123, "spf", rb_mpg123_spf, 0);
|
133
|
+
rb_define_method(rb_cMpg123, "tpf", rb_mpg123_tpf, 0);
|
134
|
+
rb_define_method(rb_cMpg123, "tell", rb_mpg123_tell, 0);
|
135
|
+
rb_define_method(rb_cMpg123, "tellframe", rb_mpg123_tellframe, 0);
|
136
|
+
rb_define_method(rb_cMpg123, "seek", rb_mpg123_seek, 1);
|
137
|
+
rb_define_method(rb_cMpg123, "seek_frame", rb_mpg123_seek_frame, 1);
|
138
|
+
rb_define_method(rb_cMpg123, "timeframe", rb_mpg123_timeframe, 1);
|
139
|
+
}
|
@@ -0,0 +1,151 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <pthread.h>
|
3
|
+
#include <portaudio.h>
|
4
|
+
|
5
|
+
typedef struct {
|
6
|
+
PaStream *stream;
|
7
|
+
int size;
|
8
|
+
float *buffer;
|
9
|
+
pthread_mutex_t mutex;
|
10
|
+
pthread_cond_t cond;
|
11
|
+
} Portaudio;
|
12
|
+
|
13
|
+
static VALUE rb_cPortaudio;
|
14
|
+
|
15
|
+
void free_portaudio(void *ptr)
|
16
|
+
{
|
17
|
+
Portaudio *portaudio = (Portaudio *) ptr;
|
18
|
+
|
19
|
+
if (portaudio) {
|
20
|
+
pthread_cond_destroy(&portaudio->cond);
|
21
|
+
pthread_mutex_destroy(&portaudio->mutex);
|
22
|
+
|
23
|
+
if (portaudio->buffer) {
|
24
|
+
free(portaudio->buffer);
|
25
|
+
}
|
26
|
+
|
27
|
+
Pa_CloseStream(portaudio->stream);
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
|
32
|
+
static int paCallback(const void *inputBuffer,
|
33
|
+
void *outputBuffer,
|
34
|
+
unsigned long framesPerBuffer,
|
35
|
+
const PaStreamCallbackTimeInfo* timeInfo,
|
36
|
+
PaStreamCallbackFlags statusFlags,
|
37
|
+
void *userData )
|
38
|
+
{
|
39
|
+
Portaudio *portaudio = (Portaudio *) userData;
|
40
|
+
float *out = (float*) outputBuffer;
|
41
|
+
unsigned int i;
|
42
|
+
|
43
|
+
pthread_mutex_lock(&portaudio->mutex);
|
44
|
+
|
45
|
+
for (i = 0; i < framesPerBuffer * 2; i++) {
|
46
|
+
*out++ = portaudio->buffer[i];
|
47
|
+
}
|
48
|
+
|
49
|
+
pthread_mutex_unlock(&portaudio->mutex);
|
50
|
+
pthread_cond_signal(&portaudio->cond);
|
51
|
+
|
52
|
+
return 0;
|
53
|
+
}
|
54
|
+
|
55
|
+
VALUE rb_portaudio_new(VALUE klass)
|
56
|
+
{
|
57
|
+
PaError err;
|
58
|
+
VALUE self;
|
59
|
+
Portaudio *portaudio = (Portaudio *) malloc(sizeof(Portaudio));
|
60
|
+
|
61
|
+
pthread_mutex_init(&portaudio->mutex, NULL);
|
62
|
+
pthread_cond_init(&portaudio->cond, NULL);
|
63
|
+
|
64
|
+
portaudio->size = 4096 * 2;
|
65
|
+
portaudio->buffer = (float *) malloc(sizeof(float) * portaudio->size);
|
66
|
+
|
67
|
+
err = Pa_OpenDefaultStream(&portaudio->stream,
|
68
|
+
0, /* no input channels */
|
69
|
+
2, /* stereo output */
|
70
|
+
paFloat32, /* 32 bit floating point output */
|
71
|
+
44100, /* 44100 sample rate*/
|
72
|
+
4096, /* frames per buffer */
|
73
|
+
paCallback, /* this is your callback function */
|
74
|
+
(void*) portaudio);
|
75
|
+
|
76
|
+
if (err != paNoError) {
|
77
|
+
rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
|
78
|
+
}
|
79
|
+
|
80
|
+
self = Data_Wrap_Struct(rb_cPortaudio, 0, free_portaudio, portaudio);
|
81
|
+
|
82
|
+
return self;
|
83
|
+
}
|
84
|
+
|
85
|
+
|
86
|
+
VALUE portaudio_wait(void *ptr)
|
87
|
+
{
|
88
|
+
Portaudio *portaudio = (Portaudio *) ptr;
|
89
|
+
|
90
|
+
pthread_cond_wait(&portaudio->cond, &portaudio->mutex);
|
91
|
+
|
92
|
+
return Qnil;
|
93
|
+
}
|
94
|
+
|
95
|
+
VALUE rb_portaudio_write(VALUE self, VALUE buffer)
|
96
|
+
{
|
97
|
+
int i;
|
98
|
+
Portaudio *portaudio;
|
99
|
+
Data_Get_Struct(self, Portaudio, portaudio);
|
100
|
+
|
101
|
+
Check_Type(buffer, T_ARRAY);
|
102
|
+
|
103
|
+
for (i = 0; i < portaudio->size; i++) {
|
104
|
+
portaudio->buffer[i] = NUM2DBL(rb_ary_entry(buffer, i));
|
105
|
+
}
|
106
|
+
|
107
|
+
rb_thread_blocking_region(portaudio_wait, portaudio, RUBY_UBF_IO, NULL);
|
108
|
+
|
109
|
+
return self;
|
110
|
+
}
|
111
|
+
|
112
|
+
VALUE rb_portaudio_start(VALUE self)
|
113
|
+
{
|
114
|
+
Portaudio *portaudio;
|
115
|
+
Data_Get_Struct(self, Portaudio, portaudio);
|
116
|
+
int err = Pa_StartStream(portaudio->stream);
|
117
|
+
|
118
|
+
if (err != paNoError) {
|
119
|
+
rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
|
120
|
+
}
|
121
|
+
|
122
|
+
return self;
|
123
|
+
}
|
124
|
+
|
125
|
+
VALUE rb_portaudio_stop(VALUE self)
|
126
|
+
{
|
127
|
+
Portaudio *portaudio;
|
128
|
+
Data_Get_Struct(self, Portaudio, portaudio);
|
129
|
+
int err = Pa_StopStream(portaudio->stream);
|
130
|
+
|
131
|
+
if (err != paNoError) {
|
132
|
+
rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
|
133
|
+
}
|
134
|
+
|
135
|
+
return self;
|
136
|
+
}
|
137
|
+
|
138
|
+
void Init_portaudio(void) {
|
139
|
+
int err = Pa_Initialize();
|
140
|
+
|
141
|
+
if (err != paNoError) {
|
142
|
+
rb_raise(rb_eStandardError, "%s", Pa_GetErrorText(err));
|
143
|
+
}
|
144
|
+
|
145
|
+
rb_cPortaudio = rb_define_class("Portaudio", rb_cObject);
|
146
|
+
|
147
|
+
rb_define_singleton_method(rb_cPortaudio, "new", rb_portaudio_new, 0);
|
148
|
+
rb_define_method(rb_cPortaudio, "write", rb_portaudio_write, 1);
|
149
|
+
rb_define_method(rb_cPortaudio, "start", rb_portaudio_start, 0);
|
150
|
+
rb_define_method(rb_cPortaudio, "stop", rb_portaudio_stop, 0);
|
151
|
+
}
|
data/lib/audite.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'portaudio'
|
2
|
+
require 'mpg123'
|
3
|
+
|
4
|
+
class Audite
|
5
|
+
class Events
|
6
|
+
def initialize
|
7
|
+
@handlers = Hash.new {|h,k| h[k] = [] }
|
8
|
+
end
|
9
|
+
|
10
|
+
def on(event, &block)
|
11
|
+
@handlers[event] << block
|
12
|
+
end
|
13
|
+
|
14
|
+
def trigger(event, *args)
|
15
|
+
@handlers[event].each do |handler|
|
16
|
+
handler.call(*args)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
attr_reader :events
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
@events = Events.new
|
25
|
+
@stream = Portaudio.new
|
26
|
+
@thread = start_thread
|
27
|
+
end
|
28
|
+
|
29
|
+
def start_thread
|
30
|
+
Thread.start do
|
31
|
+
loop do
|
32
|
+
@stream.write(process(4096))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def process(samples)
|
38
|
+
if tell >= length
|
39
|
+
events.trigger(:complete)
|
40
|
+
stop_stream
|
41
|
+
(0..samples).map { 0 }
|
42
|
+
|
43
|
+
elsif slice = read(samples * 2)
|
44
|
+
events.trigger(:level, level(slice))
|
45
|
+
events.trigger(:position_change, position)
|
46
|
+
|
47
|
+
slice
|
48
|
+
else
|
49
|
+
(0..samples).map { 0 }
|
50
|
+
end
|
51
|
+
|
52
|
+
rescue => e
|
53
|
+
puts e.message
|
54
|
+
puts e.backtrace
|
55
|
+
end
|
56
|
+
|
57
|
+
def start_stream
|
58
|
+
unless @active
|
59
|
+
@active = true
|
60
|
+
@stream.start
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def stop_stream
|
65
|
+
if @active
|
66
|
+
@active = false
|
67
|
+
@stream.stop
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def toggle
|
72
|
+
if @active
|
73
|
+
stop_stream
|
74
|
+
else
|
75
|
+
start_stream
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def load(file)
|
80
|
+
@mp3 = Mpg123.new(file)
|
81
|
+
end
|
82
|
+
|
83
|
+
def time_per_frame
|
84
|
+
@mp3.tpf
|
85
|
+
end
|
86
|
+
|
87
|
+
def samples_per_frame
|
88
|
+
@mp3.spf
|
89
|
+
end
|
90
|
+
|
91
|
+
def tell
|
92
|
+
@mp3.tell
|
93
|
+
end
|
94
|
+
|
95
|
+
def length
|
96
|
+
@mp3.length
|
97
|
+
end
|
98
|
+
|
99
|
+
def read(samples)
|
100
|
+
@mp3.read(samples)
|
101
|
+
end
|
102
|
+
|
103
|
+
def seconds_to_frames(seconds)
|
104
|
+
seconds / time_per_frame
|
105
|
+
end
|
106
|
+
|
107
|
+
def seconds_to_samples(seconds)
|
108
|
+
seconds_to_frames(seconds) * samples_per_frame
|
109
|
+
end
|
110
|
+
|
111
|
+
def samples_to_seconds(samples)
|
112
|
+
samples_to_frames(samples) * time_per_frame
|
113
|
+
end
|
114
|
+
|
115
|
+
def samples_to_frames(samples)
|
116
|
+
samples / samples_per_frame
|
117
|
+
end
|
118
|
+
|
119
|
+
def seek(seconds)
|
120
|
+
samples = seconds_to_samples(seconds)
|
121
|
+
|
122
|
+
if (0..length).include?(samples)
|
123
|
+
@mp3.seek(samples)
|
124
|
+
events.trigger(:position_change, position)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def position
|
129
|
+
samples_to_seconds(tell)
|
130
|
+
end
|
131
|
+
|
132
|
+
def length_in_seconds
|
133
|
+
samples_to_seconds(length)
|
134
|
+
end
|
135
|
+
|
136
|
+
def level(slice)
|
137
|
+
slice.inject(0) {|s, i| s + i.abs } / slice.size
|
138
|
+
end
|
139
|
+
|
140
|
+
def rewind(seconds = 1)
|
141
|
+
if @mp3
|
142
|
+
start_stream
|
143
|
+
seek(position - seconds)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def forward(seconds = 1)
|
148
|
+
if @mp3
|
149
|
+
start_stream
|
150
|
+
seek(position + seconds)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
metadata
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: audite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matthias Georgi
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-26 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Portable mp3 player built on mpg123 and portaudio
|
15
|
+
email: matti.georgi@gmail.com
|
16
|
+
executables:
|
17
|
+
- audite
|
18
|
+
extensions:
|
19
|
+
- ext/mpg123/extconf.rb
|
20
|
+
- ext/portaudio/extconf.rb
|
21
|
+
extra_rdoc_files:
|
22
|
+
- README.md
|
23
|
+
files:
|
24
|
+
- .gitignore
|
25
|
+
- README.md
|
26
|
+
- audite.gemspec
|
27
|
+
- bin/audite
|
28
|
+
- ext/mpg123/extconf.rb
|
29
|
+
- ext/mpg123/mpg123.c
|
30
|
+
- ext/portaudio/extconf.rb
|
31
|
+
- ext/portaudio/portaudio.c
|
32
|
+
- lib/audite.rb
|
33
|
+
homepage: http://georgi.github.com/audite
|
34
|
+
licenses: []
|
35
|
+
post_install_message:
|
36
|
+
rdoc_options: []
|
37
|
+
require_paths:
|
38
|
+
- lib
|
39
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ! '>='
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
version: '0'
|
51
|
+
requirements: []
|
52
|
+
rubyforge_project:
|
53
|
+
rubygems_version: 1.8.24
|
54
|
+
signing_key:
|
55
|
+
specification_version: 3
|
56
|
+
summary: Portable mp3 player
|
57
|
+
test_files: []
|
58
|
+
has_rdoc:
|