mpvlib 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/LICENSE.txt +21 -0
- data/README.md +40 -0
- data/Rakefile +11 -0
- data/lib/mpvlib.rb +24 -0
- data/lib/mpvlib/base.rb +17 -0
- data/lib/mpvlib/data.rb +39 -0
- data/lib/mpvlib/error.rb +51 -0
- data/lib/mpvlib/event.rb +223 -0
- data/lib/mpvlib/ffi_additions.rb +39 -0
- data/lib/mpvlib/handle.rb +287 -0
- data/lib/mpvlib/version.rb +5 -0
- data/mpvlib.gemspec +31 -0
- data/test/error_test.rb +18 -0
- data/test/handle_test.rb +78 -0
- data/test/test_helper.rb +5 -0
- data/test/version_test.rb +23 -0
- metadata +177 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dd45a21ad466b308f0f6b8b64a2cea844715a54807a9cdeb9c5166c76dbb0d90
|
4
|
+
data.tar.gz: c802162a92c36d9c48556f89d44d36af4ff32ee89db0ff0fee4fecc813e07162
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 263f892740350850158ff9e05be1f99dbb6b98696bf9d24049ced3667bdbb659708a0d10f229c27590409b0e9653ea5a6ea9024dc2e1d1b873d5da90cb50d407
|
7
|
+
data.tar.gz: dae2ff18d54345e6463d96df20562b076fd8ffa575e74e5eec51ebd3ac6fdbf84fcdd99a27d7bb257804b55e7ff3d8361471521a7fa047310e5df06b20582a52
|
data/.gitignore
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 John Labovitz
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# mpvlib
|
2
|
+
|
3
|
+
FIXME
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'mpvlib'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
|
16
|
+
$ bundle
|
17
|
+
|
18
|
+
Or install it yourself as:
|
19
|
+
|
20
|
+
$ gem install mpvlib
|
21
|
+
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
|
28
|
+
## Development
|
29
|
+
|
30
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
31
|
+
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/jslabovitz/mpvlib.
|
36
|
+
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/lib/mpvlib.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'pp'
|
2
|
+
require 'path'
|
3
|
+
require 'json'
|
4
|
+
require 'hashstruct'
|
5
|
+
require 'logger'
|
6
|
+
require 'ffi'
|
7
|
+
require 'mpvlib/ffi_additions'
|
8
|
+
|
9
|
+
# See <libmpv/client.h>.
|
10
|
+
|
11
|
+
module MPV
|
12
|
+
|
13
|
+
extend FFI::Library
|
14
|
+
|
15
|
+
ffi_lib 'libmpv'
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
require 'mpvlib/client_api_version'
|
20
|
+
require 'mpvlib/data'
|
21
|
+
require 'mpvlib/error'
|
22
|
+
require 'mpvlib/event'
|
23
|
+
require 'mpvlib/base'
|
24
|
+
require 'mpvlib/handle'
|
data/lib/mpvlib/base.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
module MPV
|
2
|
+
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def self.finalize(method, ptr)
|
6
|
+
proc {
|
7
|
+
MPV.send(method, ptr) unless ptr == 0
|
8
|
+
}
|
9
|
+
end
|
10
|
+
|
11
|
+
def define_finalizer(method, ptr)
|
12
|
+
ObjectSpace.define_finalizer(self, self.class.finalize(method, ptr))
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
data/lib/mpvlib/data.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
module MPV
|
2
|
+
|
3
|
+
enum :mpv_format, [
|
4
|
+
:MPV_FORMAT_NONE, 0,
|
5
|
+
:MPV_FORMAT_STRING, 1,
|
6
|
+
:MPV_FORMAT_OSD_STRING, 2,
|
7
|
+
:MPV_FORMAT_FLAG, 3,
|
8
|
+
:MPV_FORMAT_INT64, 4,
|
9
|
+
:MPV_FORMAT_DOUBLE, 5,
|
10
|
+
:MPV_FORMAT_NODE, 6,
|
11
|
+
:MPV_FORMAT_NODE_ARRAY, 7,
|
12
|
+
:MPV_FORMAT_NODE_MAP, 8,
|
13
|
+
:MPV_FORMAT_BYTE_ARRAY, 9,
|
14
|
+
]
|
15
|
+
|
16
|
+
# typedef struct mpv_node
|
17
|
+
# typedef struct mpv_node_list
|
18
|
+
# typedef struct mpv_byte_array
|
19
|
+
# void mpv_free_node_contents(mpv_node *node);
|
20
|
+
|
21
|
+
attach_function :mpv_free, [:void], :void
|
22
|
+
|
23
|
+
def self.convert_data(data, format)
|
24
|
+
begin
|
25
|
+
ptr = data.read_pointer
|
26
|
+
case format
|
27
|
+
when :MPV_FORMAT_NONE
|
28
|
+
nil
|
29
|
+
when :MPV_FORMAT_STRING, :MPV_FORMAT_OSD_STRING
|
30
|
+
ptr.read_string
|
31
|
+
else
|
32
|
+
raise "Unknown format: #{format.inspect}"
|
33
|
+
end
|
34
|
+
rescue FFI::NullPointerError
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/mpvlib/error.rb
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
module MPV
|
2
|
+
|
3
|
+
enum :mpv_error, [
|
4
|
+
:MPV_ERROR_SUCCESS, 0,
|
5
|
+
:MPV_ERROR_EVENT_QUEUE_FULL, -1,
|
6
|
+
:MPV_ERROR_NOMEM, -2,
|
7
|
+
:MPV_ERROR_UNINITIALIZED, -3,
|
8
|
+
:MPV_ERROR_INVALID_PARAMETER, -4,
|
9
|
+
:MPV_ERROR_OPTION_NOT_FOUND, -5,
|
10
|
+
:MPV_ERROR_OPTION_FORMAT, -6,
|
11
|
+
:MPV_ERROR_OPTION_ERROR, -7,
|
12
|
+
:MPV_ERROR_PROPERTY_NOT_FOUND, -8,
|
13
|
+
:MPV_ERROR_PROPERTY_FORMAT, -9,
|
14
|
+
:MPV_ERROR_PROPERTY_UNAVAILABLE, -10,
|
15
|
+
:MPV_ERROR_PROPERTY_ERROR, -11,
|
16
|
+
:MPV_ERROR_COMMAND, -12,
|
17
|
+
:MPV_ERROR_LOADING_FAILED, -13,
|
18
|
+
:MPV_ERROR_AO_INIT_FAILED, -14,
|
19
|
+
:MPV_ERROR_VO_INIT_FAILED, -15,
|
20
|
+
:MPV_ERROR_NOTHING_TO_PLAY, -16,
|
21
|
+
:MPV_ERROR_UNKNOWN_FORMAT, -17,
|
22
|
+
:MPV_ERROR_UNSUPPORTED, -18,
|
23
|
+
:MPV_ERROR_NOT_IMPLEMENTED, -19,
|
24
|
+
]
|
25
|
+
|
26
|
+
attach_function :mpv_error_string, [:int], :string
|
27
|
+
|
28
|
+
class Error < StandardError
|
29
|
+
|
30
|
+
def self.raise_on_failure(msg=nil, &block)
|
31
|
+
error = yield
|
32
|
+
raise new(error, msg) if error && error < 0
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.error_to_string(error)
|
36
|
+
MPV.mpv_error_string(error)
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(error, msg=nil)
|
40
|
+
super('MPV error: %s (%s): %s' %
|
41
|
+
[
|
42
|
+
self.class.error_to_string(error),
|
43
|
+
error,
|
44
|
+
msg,
|
45
|
+
]
|
46
|
+
)
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
data/lib/mpvlib/event.rb
ADDED
@@ -0,0 +1,223 @@
|
|
1
|
+
module MPV
|
2
|
+
|
3
|
+
MPVEventID = enum :mpv_event_id, [
|
4
|
+
:MPV_EVENT_NONE, 0,
|
5
|
+
:MPV_EVENT_SHUTDOWN, 1,
|
6
|
+
:MPV_EVENT_LOG_MESSAGE, 2,
|
7
|
+
:MPV_EVENT_GET_PROPERTY_REPLY, 3,
|
8
|
+
:MPV_EVENT_SET_PROPERTY_REPLY, 4,
|
9
|
+
:MPV_EVENT_COMMAND_REPLY, 5,
|
10
|
+
:MPV_EVENT_START_FILE, 6,
|
11
|
+
:MPV_EVENT_END_FILE, 7,
|
12
|
+
:MPV_EVENT_FILE_LOADED, 8,
|
13
|
+
:MPV_EVENT_TRACKS_CHANGED, 9,
|
14
|
+
:MPV_EVENT_TRACK_SWITCHED, 10,
|
15
|
+
:MPV_EVENT_IDLE, 11,
|
16
|
+
:MPV_EVENT_PAUSE, 12,
|
17
|
+
:MPV_EVENT_UNPAUSE, 13,
|
18
|
+
:MPV_EVENT_TICK, 14,
|
19
|
+
:MPV_EVENT_SCRIPT_INPUT_DISPATCH, 15,
|
20
|
+
:MPV_EVENT_CLIENT_MESSAGE, 16,
|
21
|
+
:MPV_EVENT_VIDEO_RECONFIG, 17,
|
22
|
+
:MPV_EVENT_AUDIO_RECONFIG, 18,
|
23
|
+
:MPV_EVENT_METADATA_UPDATE, 19,
|
24
|
+
:MPV_EVENT_SEEK, 20,
|
25
|
+
:MPV_EVENT_PLAYBACK_RESTART, 21,
|
26
|
+
:MPV_EVENT_PROPERTY_CHANGE, 22,
|
27
|
+
:MPV_EVENT_CHAPTER_CHANGE, 23,
|
28
|
+
:MPV_EVENT_QUEUE_OVERFLOW, 24,
|
29
|
+
]
|
30
|
+
|
31
|
+
attach_function :mpv_event_name, [:mpv_event_id], :string
|
32
|
+
|
33
|
+
MPVEventNames = Hash[
|
34
|
+
MPVEventID.to_hash.map { |symbol, value| [MPV.mpv_event_name(value), symbol] }
|
35
|
+
]
|
36
|
+
|
37
|
+
class MPVEventProperty < FFI::Struct
|
38
|
+
|
39
|
+
layout \
|
40
|
+
:name, :string,
|
41
|
+
:format, :mpv_format,
|
42
|
+
:data, :pointer
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
enum :mpv_log_level, [
|
47
|
+
:MPV_LOG_LEVEL_NONE, 0,
|
48
|
+
:MPV_LOG_LEVEL_FATAL, 10,
|
49
|
+
:MPV_LOG_LEVEL_ERROR, 20,
|
50
|
+
:MPV_LOG_LEVEL_WARN, 30,
|
51
|
+
:MPV_LOG_LEVEL_INFO, 40,
|
52
|
+
:MPV_LOG_LEVEL_V, 50,
|
53
|
+
:MPV_LOG_LEVEL_DEBUG, 60,
|
54
|
+
:MPV_LOG_LEVEL_TRACE, 70,
|
55
|
+
]
|
56
|
+
|
57
|
+
class MPVEventLogMessage < FFI::Struct
|
58
|
+
|
59
|
+
layout \
|
60
|
+
:prefix, :string,
|
61
|
+
:level, :string,
|
62
|
+
:text, :string,
|
63
|
+
:log_level, :mpv_log_level
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
enum :mpv_end_file_reason, [
|
68
|
+
:MPV_END_FILE_REASON_EOF, 0,
|
69
|
+
:MPV_END_FILE_REASON_STOP, 2,
|
70
|
+
:MPV_END_FILE_REASON_QUIT, 3,
|
71
|
+
:MPV_END_FILE_REASON_ERROR, 4,
|
72
|
+
:MPV_END_FILE_REASON_REDIRECT, 5,
|
73
|
+
]
|
74
|
+
|
75
|
+
class MPVEventEndFile < FFI::Struct
|
76
|
+
|
77
|
+
layout \
|
78
|
+
:reason, :int,
|
79
|
+
:error, :int
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
# typedef struct mpv_event_script_input_dispatch -- DEPRECATED
|
84
|
+
|
85
|
+
class MPVEventClientMessage < FFI::Struct
|
86
|
+
|
87
|
+
layout \
|
88
|
+
:num_args, :int,
|
89
|
+
:args, :pointer
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
class MPVEvent < FFI::Struct
|
94
|
+
|
95
|
+
layout \
|
96
|
+
:event_id, :mpv_event_id,
|
97
|
+
:error, :int,
|
98
|
+
:reply_userdata, :uint64,
|
99
|
+
:data, :pointer
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
class Event
|
104
|
+
|
105
|
+
class None < Event; end
|
106
|
+
|
107
|
+
class Shutdown < Event; end
|
108
|
+
|
109
|
+
class LogMessage < Event
|
110
|
+
|
111
|
+
attr_accessor :prefix
|
112
|
+
attr_accessor :level
|
113
|
+
attr_accessor :text
|
114
|
+
attr_accessor :log_level
|
115
|
+
|
116
|
+
def initialize(mpv_event)
|
117
|
+
super
|
118
|
+
data = MPVEventLogMessage.new(mpv_event[:data])
|
119
|
+
@prefix = data[:prefix]
|
120
|
+
@level = data[:level]
|
121
|
+
@text = data[:text]
|
122
|
+
@log_level = data[:log_level]
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
class GetPropertyReply < Event
|
128
|
+
|
129
|
+
attr_accessor :name
|
130
|
+
attr_accessor :value
|
131
|
+
|
132
|
+
def initialize(mpv_event)
|
133
|
+
super
|
134
|
+
data = MPVEventProperty.new(mpv_event[:data])
|
135
|
+
@name = data[:name]
|
136
|
+
@value = MPV.convert_data(data[:data], data[:format])
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
class SetProperty < Event; end
|
142
|
+
|
143
|
+
class CommandReply < Event; end
|
144
|
+
|
145
|
+
class StartFile < Event; end
|
146
|
+
|
147
|
+
class EndFile < Event
|
148
|
+
|
149
|
+
attr_accessor :reason
|
150
|
+
attr_accessor :error
|
151
|
+
|
152
|
+
def initialize(mpv_event)
|
153
|
+
super
|
154
|
+
data = MPVEventEndFile.new(mpv_event[:data])
|
155
|
+
@reason = data[:reason]
|
156
|
+
@error = (data[:error] < 0) ? MPV::Error.new(data[:error]) : nil
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
class FileLoaded < Event; end
|
162
|
+
|
163
|
+
class TracksChanged < Event; end
|
164
|
+
|
165
|
+
class TrackSwitched < Event; end
|
166
|
+
|
167
|
+
class Idle < Event; end
|
168
|
+
|
169
|
+
class Pause < Event; end
|
170
|
+
|
171
|
+
class Unpause < Event; end
|
172
|
+
|
173
|
+
class Tick < Event; end
|
174
|
+
|
175
|
+
class ScriptInputDispatch < Event; end
|
176
|
+
|
177
|
+
class ClientMessage < Event
|
178
|
+
|
179
|
+
attr_accessor :args
|
180
|
+
|
181
|
+
def initialize(mpv_event)
|
182
|
+
super
|
183
|
+
data = MPVEventLogMessage.new(mpv_event[:data])
|
184
|
+
@args = data[:args].read_array_of_strings(data[:num_args])
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
188
|
+
|
189
|
+
class VideoReconfig < Event; end
|
190
|
+
|
191
|
+
class AudioReconfig < Event; end
|
192
|
+
|
193
|
+
class MetadataUpdate < Event; end
|
194
|
+
|
195
|
+
class Seek < Event; end
|
196
|
+
|
197
|
+
class PlaybackRestart < Event; end
|
198
|
+
|
199
|
+
class PropertyChange < GetPropertyReply; end
|
200
|
+
|
201
|
+
class ChapterChange < Event; end
|
202
|
+
|
203
|
+
class QueueOverflow < Event; end
|
204
|
+
|
205
|
+
def self.new_from_mpv_event(mpv_event)
|
206
|
+
event_class_name = mpv_event[:event_id].to_s.sub(/^MPV_EVENT_/, '').split('_').map(&:capitalize).join
|
207
|
+
event_class = const_get(event_class_name)
|
208
|
+
event_class.new(mpv_event)
|
209
|
+
end
|
210
|
+
|
211
|
+
attr_accessor :event_id
|
212
|
+
attr_accessor :error
|
213
|
+
attr_accessor :reply_id
|
214
|
+
|
215
|
+
def initialize(mpv_event)
|
216
|
+
@event_id = mpv_event[:event_id]
|
217
|
+
@error = (mpv_event[:error] < 0) ? MPV::Error.new(mpv_event[:error]) : nil
|
218
|
+
@reply_id = mpv_event[:reply_userdata]
|
219
|
+
end
|
220
|
+
|
221
|
+
end
|
222
|
+
|
223
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class FFI::Pointer
|
2
|
+
|
3
|
+
# after https://dzone.com/articles/getting-array-strings-char-ffi
|
4
|
+
|
5
|
+
def read_array_of_strings(num=nil)
|
6
|
+
elements = []
|
7
|
+
loc = self
|
8
|
+
until (element = loc.read_pointer).null?
|
9
|
+
elements << element.read_string
|
10
|
+
loc += FFI::Type::POINTER.size
|
11
|
+
break if num && elements.length == num
|
12
|
+
end
|
13
|
+
elements
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
class FFI::MemoryPointer
|
19
|
+
|
20
|
+
# after http://zegoggl.es/2009/05/ruby-ffi-recipes.html
|
21
|
+
|
22
|
+
def self.from_array_of_strings(strings)
|
23
|
+
string_ptrs = strings.map { |s| FFI::MemoryPointer.from_string(s) } + [nil]
|
24
|
+
strings_ptr = new(:pointer, string_ptrs.length)
|
25
|
+
string_ptrs.each_with_index do |ptr, i|
|
26
|
+
strings_ptr[i].put_pointer(0, ptr)
|
27
|
+
end
|
28
|
+
strings_ptr
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
class FFI::Struct
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"<#{self}> " + members.map { |key| "#{key}=#{self[key].inspect}"}.join(', ')
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,287 @@
|
|
1
|
+
module MPV
|
2
|
+
|
3
|
+
typedef :pointer, :mpv_handle
|
4
|
+
|
5
|
+
attach_function :mpv_create, [], :mpv_handle
|
6
|
+
attach_function :mpv_initialize, [:mpv_handle], :int
|
7
|
+
attach_function :mpv_detach_destroy, [:mpv_handle], :void
|
8
|
+
attach_function :mpv_terminate_destroy, [:mpv_handle], :void
|
9
|
+
|
10
|
+
attach_function :mpv_client_name, [:mpv_handle], :string
|
11
|
+
|
12
|
+
# mpv_handle *mpv_create_client(mpv_handle *ctx, const char *name);
|
13
|
+
|
14
|
+
# int mpv_load_config_file(mpv_handle *ctx, const char *filename);
|
15
|
+
|
16
|
+
# void mpv_suspend(mpv_handle *ctx);
|
17
|
+
# void mpv_resume(mpv_handle *ctx);
|
18
|
+
|
19
|
+
attach_function :mpv_get_time_us, [:mpv_handle], :int64
|
20
|
+
|
21
|
+
# int mpv_set_option(mpv_handle *ctx, const char *name, mpv_format format, void *data);
|
22
|
+
attach_function :mpv_set_option_string, [:mpv_handle, :string, :string], :int
|
23
|
+
|
24
|
+
attach_function :mpv_command, [:mpv_handle, :pointer], :int
|
25
|
+
|
26
|
+
# int mpv_command_node(mpv_handle *ctx, mpv_node *args, mpv_node *result);
|
27
|
+
# int mpv_command_string(mpv_handle *ctx, const char *args);
|
28
|
+
|
29
|
+
attach_function :mpv_command_async, [:mpv_handle, :uint64, :pointer], :int
|
30
|
+
# int mpv_command_node_async(mpv_handle *ctx, uint64_t reply_userdata, mpv_node *args);
|
31
|
+
|
32
|
+
# attach_function :mpv_set_property, [:mpv_handle, :string, :mpv_format, :void], :int
|
33
|
+
attach_function :mpv_set_property_string, [:mpv_handle, :string, :string], :int
|
34
|
+
# int mpv_set_property_async(mpv_handle *ctx, uint64_t reply_userdata, const char *name, mpv_format format, void *data);
|
35
|
+
|
36
|
+
# int mpv_get_property(mpv_handle *ctx, const char *name, mpv_format format, void *data);
|
37
|
+
attach_function :mpv_get_property_string, [:mpv_handle, :string], :string
|
38
|
+
# char *mpv_get_property_osd_string(mpv_handle *ctx, const char *name);
|
39
|
+
# int mpv_get_property_async(mpv_handle *ctx, uint64_t reply_userdata, const char *name, mpv_format format);
|
40
|
+
|
41
|
+
attach_function :mpv_observe_property, [:mpv_handle, :uint64, :string, :mpv_format], :int
|
42
|
+
# int mpv_unobserve_property(mpv_handle *mpv, uint64_t registered_reply_userdata);
|
43
|
+
|
44
|
+
# int mpv_request_event(mpv_handle *ctx, mpv_event_id event, int enable);
|
45
|
+
|
46
|
+
attach_function :mpv_request_log_messages, [:mpv_handle, :string], :int
|
47
|
+
|
48
|
+
attach_function :mpv_wait_event, [:mpv_handle, :double], MPVEvent.by_ref
|
49
|
+
|
50
|
+
# void mpv_wakeup(mpv_handle *ctx);
|
51
|
+
|
52
|
+
# void mpv_set_wakeup_callback(mpv_handle *ctx, void (*cb)(void *d), void *d);
|
53
|
+
|
54
|
+
attach_function :mpv_get_wakeup_pipe, [:mpv_handle], :int
|
55
|
+
|
56
|
+
# void mpv_wait_async_requests(mpv_handle *ctx);
|
57
|
+
|
58
|
+
# typedef enum mpv_sub_api
|
59
|
+
# void *mpv_get_sub_api(mpv_handle *ctx, mpv_sub_api sub_api);
|
60
|
+
|
61
|
+
class Handle < Base
|
62
|
+
|
63
|
+
attr_accessor :playlist_file
|
64
|
+
attr_accessor :audio_device
|
65
|
+
attr_accessor :mpv_log_level
|
66
|
+
attr_accessor :equalizer
|
67
|
+
attr_accessor :delegate
|
68
|
+
attr_reader :wakeup_pipe
|
69
|
+
|
70
|
+
def initialize(
|
71
|
+
playlist_file: nil,
|
72
|
+
audio_device: nil,
|
73
|
+
mpv_log_level: nil,
|
74
|
+
equalizer: nil,
|
75
|
+
delegate:,
|
76
|
+
**params
|
77
|
+
)
|
78
|
+
@playlist_file = Path.new(playlist_file || '/tmp/playlist.txt')
|
79
|
+
@audio_device = audio_device
|
80
|
+
@mpv_log_level = mpv_log_level || 'error'
|
81
|
+
@equalizer = equalizer
|
82
|
+
@delegate = delegate
|
83
|
+
@property_observers = {}
|
84
|
+
@event_observers = {}
|
85
|
+
@reply_id = 1
|
86
|
+
@mpv_handle = MPV.mpv_create
|
87
|
+
MPV::Error.raise_on_failure("initialize") {
|
88
|
+
MPV.mpv_initialize(@mpv_handle)
|
89
|
+
}
|
90
|
+
define_finalizer(:mpv_terminate_destroy, @mpv_handle)
|
91
|
+
fd = MPV.mpv_get_wakeup_pipe(@mpv_handle)
|
92
|
+
raise StandardError, "Couldn't get wakeup pipe from MPV" if fd < 0
|
93
|
+
@wakeup_pipe = IO.new(fd)
|
94
|
+
register_event('log-message') { |e| handle_log_message(e) }
|
95
|
+
request_log_messages(@mpv_log_level)
|
96
|
+
set_option('audio-device', @audio_device) if @audio_device
|
97
|
+
command('af', 'add', "equalizer=#{@equalizer.join(':')}") if @equalizer
|
98
|
+
set_option('audio-display', 'no')
|
99
|
+
set_option('vo', 'null')
|
100
|
+
set_property('volume', '100')
|
101
|
+
observe_property('playlist') { |e| @delegate.playlist_changed(e) }
|
102
|
+
observe_property('pause') { |e| @delegate.pause_changed(e) }
|
103
|
+
register_event('playback-restart') { |e| @delegate.playback_restart(e) }
|
104
|
+
end
|
105
|
+
|
106
|
+
def client_name
|
107
|
+
MPV.mpv_client_name(@mpv_handle)
|
108
|
+
end
|
109
|
+
|
110
|
+
def get_time_us
|
111
|
+
MPV.mpv_get_time_us(@mpv_handle)
|
112
|
+
end
|
113
|
+
|
114
|
+
def set_option(name, value)
|
115
|
+
#FIXME: allow non-string values
|
116
|
+
MPV.mpv_set_option_string(@mpv_handle, name, value.to_s)
|
117
|
+
end
|
118
|
+
|
119
|
+
def command(*args)
|
120
|
+
MPV::Error.raise_on_failure("command: args = %p" % args) {
|
121
|
+
MPV.mpv_command(@mpv_handle, FFI::MemoryPointer.from_array_of_strings(args.map(&:to_s)))
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
125
|
+
def command_async(*args, &block)
|
126
|
+
@reply_id += 1
|
127
|
+
MPV::Error.raise_on_failure("command_async: args = %p" % args) {
|
128
|
+
MPV.mpv_command_async(@mpv_handle, @reply_id, FFI::MemoryPointer.from_array_of_strings(args.map(&:to_s)))
|
129
|
+
}
|
130
|
+
@property_observers[@reply_id] = block
|
131
|
+
@reply_id
|
132
|
+
end
|
133
|
+
|
134
|
+
def set_property(name, data)
|
135
|
+
#FIXME: allow non-string values
|
136
|
+
MPV::Error.raise_on_failure("set_property: name = %p, data = %p" % [name, data]) {
|
137
|
+
MPV.mpv_set_property_string(@mpv_handle, name, data.to_s)
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_property(name)
|
142
|
+
#FIXME: allow non-string values
|
143
|
+
MPV.mpv_get_property_string(@mpv_handle, name)
|
144
|
+
end
|
145
|
+
|
146
|
+
def observe_property(name, &block)
|
147
|
+
@reply_id += 1
|
148
|
+
MPV::Error.raise_on_failure("set_property: name = %p" % name) {
|
149
|
+
MPV.mpv_observe_property(@mpv_handle, @reply_id, name, :MPV_FORMAT_STRING)
|
150
|
+
}
|
151
|
+
@property_observers[@reply_id] = block
|
152
|
+
@reply_id
|
153
|
+
end
|
154
|
+
|
155
|
+
def register_event(name, &block)
|
156
|
+
event_id = MPVEventNames[name] or raise "No such event: #{name.inspect}"
|
157
|
+
@event_observers[event_id] ||= []
|
158
|
+
@event_observers[event_id] << block
|
159
|
+
end
|
160
|
+
|
161
|
+
def wait_event(timeout: nil)
|
162
|
+
Event.new_from_mpv_event(MPV.mpv_wait_event(@mpv_handle, timeout || -1))
|
163
|
+
end
|
164
|
+
|
165
|
+
def event_loop(timeout: nil)
|
166
|
+
loop do
|
167
|
+
event = wait_event(timeout: timeout)
|
168
|
+
break if event.kind_of?(MPV::Event::None)
|
169
|
+
raise event.error if event.error
|
170
|
+
if event.reply_id && event.reply_id != 0 && (observer = @property_observers[event.reply_id])
|
171
|
+
observer.call(event)
|
172
|
+
end
|
173
|
+
if (observers = @event_observers[event.event_id])
|
174
|
+
observers.each { |o| o.call(event) }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def run_event_loop
|
180
|
+
@wakeup_pipe.read_nonblock(1024)
|
181
|
+
event_loop(timeout: 0)
|
182
|
+
end
|
183
|
+
|
184
|
+
def request_log_messages(level, &block)
|
185
|
+
MPV::Error.raise_on_failure("request_log_messages: level = %p" % level) {
|
186
|
+
MPV.mpv_request_log_messages(@mpv_handle, level)
|
187
|
+
}
|
188
|
+
register_event('log-message', &block) if block_given?
|
189
|
+
end
|
190
|
+
|
191
|
+
def playlist_position
|
192
|
+
(v = get_property('playlist-pos')) && v.to_i
|
193
|
+
end
|
194
|
+
|
195
|
+
def playlist_position=(position)
|
196
|
+
set_property('playlist-pos', position)
|
197
|
+
end
|
198
|
+
|
199
|
+
def playlist_count
|
200
|
+
(v = get_property('playlist/count')) && v.to_i
|
201
|
+
end
|
202
|
+
|
203
|
+
def time_position
|
204
|
+
(v = get_property('time-pos')) && v.to_f
|
205
|
+
end
|
206
|
+
|
207
|
+
def time_position=(position)
|
208
|
+
set_property('time-pos', position)
|
209
|
+
end
|
210
|
+
|
211
|
+
def loadlist(path)
|
212
|
+
command('loadlist', path.to_s)
|
213
|
+
end
|
214
|
+
|
215
|
+
def playlist_previous
|
216
|
+
command('playlist-prev')
|
217
|
+
end
|
218
|
+
|
219
|
+
def playlist_next
|
220
|
+
command('playlist-next')
|
221
|
+
end
|
222
|
+
|
223
|
+
def pause
|
224
|
+
case get_property('pause')
|
225
|
+
when 'no', nil
|
226
|
+
false
|
227
|
+
when 'yes'
|
228
|
+
true
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def pause=(state)
|
233
|
+
set_property('pause', state ? 'yes' : 'no')
|
234
|
+
end
|
235
|
+
|
236
|
+
def seek_by(seconds)
|
237
|
+
command('seek', seconds)
|
238
|
+
end
|
239
|
+
|
240
|
+
def seek_to_percent(percent)
|
241
|
+
command('seek', percent, 'absolute-percent')
|
242
|
+
end
|
243
|
+
|
244
|
+
def playlist_filename(position)
|
245
|
+
if (filename = get_property("playlist/#{position}/filename")) && !filename.empty?
|
246
|
+
Path.new(filename)
|
247
|
+
else
|
248
|
+
nil
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def playlist
|
253
|
+
JSON.parse(get_property('playlist')).map { |h| HashStruct.new(h) }
|
254
|
+
end
|
255
|
+
|
256
|
+
def play(playlist=nil)
|
257
|
+
if playlist
|
258
|
+
@playlist_file.dirname.mkpath
|
259
|
+
@playlist_file.open('w') do |io|
|
260
|
+
playlist.each { |p| io.puts(p) }
|
261
|
+
end
|
262
|
+
end
|
263
|
+
loadlist(@playlist_file)
|
264
|
+
end
|
265
|
+
|
266
|
+
MPVLogMessageLevels = {
|
267
|
+
'none' => Logger::UNKNOWN,
|
268
|
+
'fatal' => Logger::FATAL,
|
269
|
+
'error' => Logger::ERROR,
|
270
|
+
'warn' => Logger::WARN,
|
271
|
+
'info' => Logger::INFO,
|
272
|
+
'v' => Logger::DEBUG,
|
273
|
+
'debug' => Logger::DEBUG,
|
274
|
+
'trace' => Logger::DEBUG,
|
275
|
+
}
|
276
|
+
|
277
|
+
def handle_log_message(log_message)
|
278
|
+
severity = MPVLogMessageLevels[log_message.level] || Logger::UNKNOWN
|
279
|
+
@delegate.add_log_message(
|
280
|
+
severity,
|
281
|
+
'%15s: %s' % [log_message.prefix, log_message.text.chomp],
|
282
|
+
'MPV')
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|
286
|
+
|
287
|
+
end
|
data/mpvlib.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require_relative 'lib/mpvlib/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = 'mpvlib'
|
7
|
+
s.version = MPV::VERSION
|
8
|
+
s.summary = %q{Ruby bindings to the MPV media player, via libmpv.}
|
9
|
+
s.description = %q{
|
10
|
+
mpvlib provides Ruby bindings to the MPV media player, via libmpv.
|
11
|
+
}
|
12
|
+
s.author = 'John Labovitz'
|
13
|
+
s.email = 'johnl@johnlabovitz.com'
|
14
|
+
s.homepage = 'http://github.com/jslabovitz/mpvlib'
|
15
|
+
s.license = 'MIT'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map { |f| File.basename(f) }
|
20
|
+
s.require_path = 'lib'
|
21
|
+
|
22
|
+
s.add_dependency 'ffi'
|
23
|
+
s.add_dependency 'json', '~> 2.1'
|
24
|
+
s.add_dependency 'hashstruct', '~> 1.3'
|
25
|
+
s.add_dependency 'path', '~> 2.0'
|
26
|
+
|
27
|
+
s.add_development_dependency 'rake', '~> 12.3'
|
28
|
+
s.add_development_dependency 'rubygems-tasks', '~> 0.2'
|
29
|
+
s.add_development_dependency 'minitest', '~> 5.11'
|
30
|
+
s.add_development_dependency 'minitest-power_assert', '~> 0.3'
|
31
|
+
end
|
data/test/error_test.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module MPV
|
4
|
+
|
5
|
+
class ErrorTest < Minitest::Test
|
6
|
+
|
7
|
+
def test_error_string
|
8
|
+
assert { MPV::Error.error_to_string(:MPV_ERROR_SUCCESS) == 'success' }
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_error_exception
|
12
|
+
error = MPV::Error.new(:MPV_ERROR_SUCCESS)
|
13
|
+
assert { error.kind_of?(MPV::Error) }
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
data/test/handle_test.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module MPV
|
4
|
+
|
5
|
+
class HandleTest < Minitest::Test
|
6
|
+
|
7
|
+
def setup
|
8
|
+
@mpv = MPV::Handle.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_client_name
|
12
|
+
assert { @mpv.client_name == 'main' }
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_time_us
|
16
|
+
assert { @mpv.get_time_us.kind_of?(Numeric) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_good_command
|
20
|
+
assert {
|
21
|
+
@mpv.command('stop')
|
22
|
+
true
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_bad_command
|
27
|
+
assert_raises(MPV::Error) {
|
28
|
+
@mpv.command('xyzzy')
|
29
|
+
true
|
30
|
+
}
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_get_property
|
34
|
+
assert { @mpv.get_property('volume').to_f == 100.0 }
|
35
|
+
end
|
36
|
+
|
37
|
+
def test_set_property
|
38
|
+
name, data = 'volume', 50.0
|
39
|
+
@mpv.set_property(name, data)
|
40
|
+
assert { @mpv.get_property(name).to_f == data }
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_observe_property
|
44
|
+
expected_property, expected_value = 'volume', 50.0
|
45
|
+
@mpv.set_property(expected_property, expected_value.to_s)
|
46
|
+
actual_property = actual_value = nil
|
47
|
+
stop = false
|
48
|
+
@mpv.observe_property(expected_property) do |property|
|
49
|
+
actual_property = property.name
|
50
|
+
actual_value = property.value
|
51
|
+
stop = true
|
52
|
+
end
|
53
|
+
until stop
|
54
|
+
@mpv.event_loop(timeout: 0)
|
55
|
+
end
|
56
|
+
assert { actual_property == expected_property }
|
57
|
+
assert { actual_value.to_s.to_f == expected_value }
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_process_events
|
61
|
+
stop = false
|
62
|
+
@mpv.request_log_messages('v') do |event|
|
63
|
+
stop = true if event.text =~ /Set property string/
|
64
|
+
end
|
65
|
+
@mpv.set_property('volume', 50.0)
|
66
|
+
until stop
|
67
|
+
if (IO.select([@mpv.get_wakeup_pipe], [], [], 1))
|
68
|
+
@mpv.get_wakeup_pipe.read_nonblock(1024)
|
69
|
+
@mpv.event_loop(timeout: 0)
|
70
|
+
else
|
71
|
+
raise 'timeout'
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
module MPV
|
4
|
+
|
5
|
+
class VersionTest < Minitest::Test
|
6
|
+
|
7
|
+
MinimumAPIVersion = [1, 20]
|
8
|
+
|
9
|
+
def test_api_version
|
10
|
+
version = MPV.client_api_version
|
11
|
+
minimum_api_version = MPV.make_version(*MinimumAPIVersion)
|
12
|
+
assert { version >= minimum_api_version }
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_api_version_elements
|
16
|
+
major, minor = MPV.client_api_version_elements
|
17
|
+
assert { major.kind_of?(Numeric) }
|
18
|
+
assert { minor.kind_of?(Numeric) }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,177 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: mpvlib
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.1'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- John Labovitz
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: ffi
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: json
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.1'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.1'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: hashstruct
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.3'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: path
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rake
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '12.3'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '12.3'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubygems-tasks
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0.2'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0.2'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: minitest
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '5.11'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '5.11'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: minitest-power_assert
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - "~>"
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0.3'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - "~>"
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0.3'
|
125
|
+
description: "\n mpvlib provides Ruby bindings to the MPV media player, via libmpv.\n
|
126
|
+
\ "
|
127
|
+
email: johnl@johnlabovitz.com
|
128
|
+
executables: []
|
129
|
+
extensions: []
|
130
|
+
extra_rdoc_files: []
|
131
|
+
files:
|
132
|
+
- ".gitignore"
|
133
|
+
- LICENSE.txt
|
134
|
+
- README.md
|
135
|
+
- Rakefile
|
136
|
+
- lib/mpvlib.rb
|
137
|
+
- lib/mpvlib/base.rb
|
138
|
+
- lib/mpvlib/data.rb
|
139
|
+
- lib/mpvlib/error.rb
|
140
|
+
- lib/mpvlib/event.rb
|
141
|
+
- lib/mpvlib/ffi_additions.rb
|
142
|
+
- lib/mpvlib/handle.rb
|
143
|
+
- lib/mpvlib/version.rb
|
144
|
+
- mpvlib.gemspec
|
145
|
+
- test/error_test.rb
|
146
|
+
- test/handle_test.rb
|
147
|
+
- test/test_helper.rb
|
148
|
+
- test/version_test.rb
|
149
|
+
homepage: http://github.com/jslabovitz/mpvlib
|
150
|
+
licenses:
|
151
|
+
- MIT
|
152
|
+
metadata: {}
|
153
|
+
post_install_message:
|
154
|
+
rdoc_options: []
|
155
|
+
require_paths:
|
156
|
+
- lib
|
157
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
158
|
+
requirements:
|
159
|
+
- - ">="
|
160
|
+
- !ruby/object:Gem::Version
|
161
|
+
version: '0'
|
162
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
requirements: []
|
168
|
+
rubyforge_project:
|
169
|
+
rubygems_version: 2.7.4
|
170
|
+
signing_key:
|
171
|
+
specification_version: 4
|
172
|
+
summary: Ruby bindings to the MPV media player, via libmpv.
|
173
|
+
test_files:
|
174
|
+
- test/error_test.rb
|
175
|
+
- test/handle_test.rb
|
176
|
+
- test/test_helper.rb
|
177
|
+
- test/version_test.rb
|