ffi-shout 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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +278 -0
- data/lib/ffi-shout.rb +5 -0
- data/lib/shout/configuration.rb +157 -0
- data/lib/shout/connection.rb +160 -0
- data/lib/shout/constants.rb +75 -0
- data/lib/shout/delegation.rb +71 -0
- data/lib/shout/error.rb +58 -0
- data/lib/shout/http_metadata.rb +104 -0
- data/lib/shout/library_version.rb +47 -0
- data/lib/shout/metadata.rb +64 -0
- data/lib/shout/native/enums.rb +68 -0
- data/lib/shout/native/functions.rb +192 -0
- data/lib/shout/native/handle.rb +20 -0
- data/lib/shout/native/metadata_handle.rb +21 -0
- data/lib/shout/native.rb +24 -0
- data/lib/shout/version.rb +5 -0
- data/lib/shout.rb +27 -0
- metadata +77 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# The integer and string constants from shout.h.
|
|
4
|
+
module Shout
|
|
5
|
+
SHOUTERR_SUCCESS = 0
|
|
6
|
+
SHOUTERR_INSANE = -1
|
|
7
|
+
SHOUTERR_NOCONNECT = -2
|
|
8
|
+
SHOUTERR_NOLOGIN = -3
|
|
9
|
+
SHOUTERR_SOCKET = -4
|
|
10
|
+
SHOUTERR_MALLOC = -5
|
|
11
|
+
SHOUTERR_METADATA = -6
|
|
12
|
+
SHOUTERR_CONNECTED = -7
|
|
13
|
+
SHOUTERR_UNCONNECTED = -8
|
|
14
|
+
SHOUTERR_UNSUPPORTED = -9
|
|
15
|
+
SHOUTERR_BUSY = -10
|
|
16
|
+
SHOUTERR_NOTLS = -11
|
|
17
|
+
SHOUTERR_TLSBADCERT = -12
|
|
18
|
+
SHOUTERR_RETRY = -13
|
|
19
|
+
|
|
20
|
+
SHOUT_FORMAT_OGG = 0
|
|
21
|
+
SHOUT_FORMAT_MP3 = 1
|
|
22
|
+
SHOUT_FORMAT_WEBM = 2
|
|
23
|
+
SHOUT_FORMAT_WEBMAUDIO = 3
|
|
24
|
+
SHOUT_FORMAT_MATROSKA = 4
|
|
25
|
+
SHOUT_FORMAT_TEXT = 5
|
|
26
|
+
SHOUT_FORMAT_VORBIS = SHOUT_FORMAT_OGG
|
|
27
|
+
|
|
28
|
+
SHOUT_USAGE_AUDIO = 0x0001
|
|
29
|
+
SHOUT_USAGE_VISUAL = 0x0002
|
|
30
|
+
SHOUT_USAGE_TEXT = 0x0004
|
|
31
|
+
SHOUT_USAGE_SUBTITLE = 0x0008
|
|
32
|
+
SHOUT_USAGE_LIGHT = 0x0010
|
|
33
|
+
SHOUT_USAGE_UI = 0x0020
|
|
34
|
+
SHOUT_USAGE_METADATA = 0x0040
|
|
35
|
+
SHOUT_USAGE_APPLICATION = 0x0080
|
|
36
|
+
SHOUT_USAGE_CONTROL = 0x0100
|
|
37
|
+
SHOUT_USAGE_COMPLEX = 0x0200
|
|
38
|
+
SHOUT_USAGE_OTHER = 0x0400
|
|
39
|
+
SHOUT_USAGE_UNKNOWN = 0x0800
|
|
40
|
+
SHOUT_USAGE_3D = 0x1000
|
|
41
|
+
SHOUT_USAGE_4D = 0x2000
|
|
42
|
+
|
|
43
|
+
SHOUT_PROTOCOL_HTTP = 0
|
|
44
|
+
SHOUT_PROTOCOL_XAUDIOCAST = 1
|
|
45
|
+
SHOUT_PROTOCOL_ICY = 2
|
|
46
|
+
SHOUT_PROTOCOL_ROARAUDIO = 3
|
|
47
|
+
|
|
48
|
+
SHOUT_TLS_DISABLED = 0
|
|
49
|
+
SHOUT_TLS_AUTO = 1
|
|
50
|
+
SHOUT_TLS_AUTO_NO_PLAIN = 2
|
|
51
|
+
SHOUT_TLS_RFC2818 = 11
|
|
52
|
+
SHOUT_TLS_RFC2817 = 12
|
|
53
|
+
|
|
54
|
+
SHOUT_BLOCKING_DEFAULT = 255
|
|
55
|
+
SHOUT_BLOCKING_FULL = 0
|
|
56
|
+
SHOUT_BLOCKING_NONE = 1
|
|
57
|
+
|
|
58
|
+
SHOUT_AI_BITRATE = "bitrate"
|
|
59
|
+
SHOUT_AI_SAMPLERATE = "samplerate"
|
|
60
|
+
SHOUT_AI_CHANNELS = "channels"
|
|
61
|
+
SHOUT_AI_QUALITY = "quality"
|
|
62
|
+
|
|
63
|
+
SHOUT_META_NAME = "name"
|
|
64
|
+
SHOUT_META_URL = "url"
|
|
65
|
+
SHOUT_META_GENRE = "genre"
|
|
66
|
+
SHOUT_META_DESCRIPTION = "description"
|
|
67
|
+
SHOUT_META_IRC = "irc"
|
|
68
|
+
SHOUT_META_AIM = "aim"
|
|
69
|
+
SHOUT_META_ICQ = "icq"
|
|
70
|
+
|
|
71
|
+
SHOUT_CALLBACK_PASS = 1
|
|
72
|
+
|
|
73
|
+
SHOUT_THREADSAFE = 1
|
|
74
|
+
SHOUT_TLS = 1
|
|
75
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shout
|
|
4
|
+
# Class macros that define attribute readers and writers delegating to
|
|
5
|
+
# libshout's shout_get_<name> / shout_set_<name> functions.
|
|
6
|
+
module Delegation
|
|
7
|
+
# Defines <name> and <name>= for each name. With predicate: true, also
|
|
8
|
+
# defines <name>? returning a boolean (for on/off flags such as :public).
|
|
9
|
+
def delegate_to_shout(*delegations, predicate: false)
|
|
10
|
+
delegations.each do |name|
|
|
11
|
+
define_setter_delegator(name, name)
|
|
12
|
+
define_getter_delegator(name, name)
|
|
13
|
+
define_predicate_delegator(name) if predicate
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def delegate_alias_to_shout(delegations)
|
|
18
|
+
delegations.each_pair do |from, to|
|
|
19
|
+
define_setter_delegator(from, to)
|
|
20
|
+
define_getter_delegator(from, to)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def define_setter_delegator(from, to, preset = "shout")
|
|
27
|
+
define_method(:"#{from}=") do |value|
|
|
28
|
+
converted_value = TypeConvertor.convert(value)
|
|
29
|
+
ret = Native.public_send(:"#{preset}_set_#{to}", shout, converted_value)
|
|
30
|
+
Error.check_error(ret, shout)
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def define_getter_delegator(from, to, preset = "shout")
|
|
36
|
+
define_method(:"#{from}") do
|
|
37
|
+
Native.public_send(:"#{preset}_get_#{to}", shout)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def define_predicate_delegator(name)
|
|
42
|
+
define_method(:"#{name}?") do
|
|
43
|
+
TypeConvertor.convert_return(public_send(name))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Converts Ruby values to what libshout's setters expect. Enum symbols
|
|
48
|
+
# such as :mp3 are left to the FFI enums in Shout::Native.
|
|
49
|
+
class TypeConvertor
|
|
50
|
+
# Symbols that libshout treats as another name for the same value.
|
|
51
|
+
ALIASES = { vorbis: :ogg }.freeze
|
|
52
|
+
|
|
53
|
+
def self.convert(value)
|
|
54
|
+
case value
|
|
55
|
+
when true then 1
|
|
56
|
+
when false then 0
|
|
57
|
+
else ALIASES.fetch(value, value)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.convert_return(value)
|
|
62
|
+
case value
|
|
63
|
+
when 0, false, nil
|
|
64
|
+
false
|
|
65
|
+
else
|
|
66
|
+
true
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
data/lib/shout/error.rb
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Exception classes for libshout errors.
|
|
4
|
+
module Shout
|
|
5
|
+
class ShoutError < StandardError; end
|
|
6
|
+
|
|
7
|
+
class InsaneError < ShoutError; end
|
|
8
|
+
class ConnectionError < ShoutError; end
|
|
9
|
+
class LoginError < ShoutError; end
|
|
10
|
+
class SocketIOError < ShoutError; end
|
|
11
|
+
class MallocError < ShoutError; end
|
|
12
|
+
class MetadataError < ShoutError; end
|
|
13
|
+
class ConnectedError < ShoutError; end
|
|
14
|
+
class UnconnectedError < ShoutError; end
|
|
15
|
+
class UnsupportedError < ShoutError; end
|
|
16
|
+
class BusyError < ShoutError; end
|
|
17
|
+
class NoTLSError < ShoutError; end
|
|
18
|
+
class TLSBadCertError < ShoutError; end
|
|
19
|
+
class RetryError < ShoutError; end
|
|
20
|
+
|
|
21
|
+
# Maps libshout's SHOUTERR_* return codes to exception classes.
|
|
22
|
+
module Error
|
|
23
|
+
ERROR_CLASSES = {
|
|
24
|
+
SHOUTERR_INSANE => InsaneError,
|
|
25
|
+
SHOUTERR_NOCONNECT => ConnectionError,
|
|
26
|
+
SHOUTERR_NOLOGIN => LoginError,
|
|
27
|
+
SHOUTERR_SOCKET => SocketIOError,
|
|
28
|
+
SHOUTERR_MALLOC => MallocError,
|
|
29
|
+
SHOUTERR_METADATA => MetadataError,
|
|
30
|
+
SHOUTERR_CONNECTED => ConnectedError,
|
|
31
|
+
SHOUTERR_UNCONNECTED => UnconnectedError,
|
|
32
|
+
SHOUTERR_UNSUPPORTED => UnsupportedError,
|
|
33
|
+
SHOUTERR_BUSY => BusyError,
|
|
34
|
+
SHOUTERR_NOTLS => NoTLSError,
|
|
35
|
+
SHOUTERR_TLSBADCERT => TLSBadCertError,
|
|
36
|
+
SHOUTERR_RETRY => RetryError
|
|
37
|
+
}.freeze
|
|
38
|
+
|
|
39
|
+
# Raises the matching ShoutError for a negative libshout return code.
|
|
40
|
+
def self.check_error(return_code, shout_ptr = nil)
|
|
41
|
+
return unless return_code.is_a?(Integer) && return_code.negative?
|
|
42
|
+
|
|
43
|
+
error_class = ERROR_CLASSES.fetch(return_code, ShoutError)
|
|
44
|
+
raise error_class, libshout_message(shout_ptr) || "libshout error code #{return_code}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def self.libshout_message(shout_ptr)
|
|
48
|
+
ptr = shout_ptr.respond_to?(:to_ptr) ? shout_ptr.to_ptr : shout_ptr
|
|
49
|
+
return if ptr.nil? || ptr.null?
|
|
50
|
+
|
|
51
|
+
message = Native.shout_get_error(ptr)
|
|
52
|
+
message unless message.nil? || message.empty?
|
|
53
|
+
rescue StandardError
|
|
54
|
+
nil
|
|
55
|
+
end
|
|
56
|
+
private_class_method :libshout_message
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Shout
|
|
8
|
+
# Updates a mount's metadata through Icecast's +/admin/metadata+ endpoint,
|
|
9
|
+
# sending the source credentials with the very first request.
|
|
10
|
+
#
|
|
11
|
+
# This replaces +shout_set_metadata_utf8+ for the HTTP protocol. libshout
|
|
12
|
+
# 2.4.6 first sends the request without credentials and then reuses a
|
|
13
|
+
# socket that Icecast 2.5 has already closed after its 401. The empty reply
|
|
14
|
+
# starts libshout's "GET /admin/!POKE" TLS probe, Icecast answers 400 and
|
|
15
|
+
# closes again, and libshout retries forever without ever returning. See
|
|
16
|
+
# https://gitlab.xiph.org/xiph/icecast-libshout/-/issues/2341
|
|
17
|
+
class HttpMetadata
|
|
18
|
+
PATH = "/admin/metadata"
|
|
19
|
+
DEFAULT_TIMEOUT = 5
|
|
20
|
+
|
|
21
|
+
PLAIN_TLS_MODES = %i[disabled auto].freeze
|
|
22
|
+
|
|
23
|
+
# Timeout::Error covers Net::OpenTimeout, ReadTimeout and WriteTimeout;
|
|
24
|
+
# SystemCallError covers the Errno::* socket errors; IOError covers EOFError.
|
|
25
|
+
NETWORK_ERRORS = [
|
|
26
|
+
SystemCallError, IOError, Timeout::Error, ::SocketError, OpenSSL::SSL::SSLError
|
|
27
|
+
].freeze
|
|
28
|
+
|
|
29
|
+
attr_reader :configuration, :timeout
|
|
30
|
+
|
|
31
|
+
def initialize(configuration, timeout: DEFAULT_TIMEOUT)
|
|
32
|
+
@configuration = configuration
|
|
33
|
+
@timeout = timeout
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Sends +metadata+ (a Shout::Metadata) for the configured mount. Returns
|
|
37
|
+
# the server's response message, or raises a Shout::ShoutError.
|
|
38
|
+
def update(metadata)
|
|
39
|
+
handle_response(http.request(build_request(metadata)))
|
|
40
|
+
rescue *NETWORK_ERRORS => e
|
|
41
|
+
raise ConnectionError, "Metadata update failed: #{e.class}: #{e.message}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def build_request(metadata)
|
|
47
|
+
request = Net::HTTP::Get.new("#{PATH}?#{query(metadata)}")
|
|
48
|
+
request.basic_auth(configuration.user.to_s, configuration.password.to_s)
|
|
49
|
+
request["User-Agent"] = configuration.agent if configuration.agent
|
|
50
|
+
request
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def query(metadata)
|
|
54
|
+
params = [%w[mode updinfo], %w[charset UTF-8], ["mount", configuration.mount.to_s]]
|
|
55
|
+
params.concat(metadata.to_h.to_a)
|
|
56
|
+
params.map { |key, value| "#{encode(key)}=#{encode(value)}" }.join("&")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def encode(value)
|
|
60
|
+
URI.encode_uri_component(value.to_s)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def http
|
|
64
|
+
http = Net::HTTP.new(configuration.host, configuration.port)
|
|
65
|
+
http.open_timeout = timeout
|
|
66
|
+
http.read_timeout = timeout
|
|
67
|
+
http.write_timeout = timeout
|
|
68
|
+
configure_tls(http)
|
|
69
|
+
http
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def configure_tls(http)
|
|
73
|
+
mode = configuration.tls
|
|
74
|
+
return if PLAIN_TLS_MODES.include?(mode)
|
|
75
|
+
|
|
76
|
+
unless mode == :rfc2818
|
|
77
|
+
raise UnsupportedError,
|
|
78
|
+
"TLS mode #{mode.inspect} is not supported for HTTP metadata updates; use :rfc2818 or :disabled"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
http.use_ssl = true
|
|
82
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
|
83
|
+
configure_ca(http)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def configure_ca(http)
|
|
87
|
+
http.ca_file = configuration.ca_file if configuration.ca_file
|
|
88
|
+
http.ca_path = configuration.ca_directory if configuration.ca_directory
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def handle_response(response)
|
|
92
|
+
body = response.body.to_s
|
|
93
|
+
message = body[%r{<message>(.*?)</message>}m, 1]
|
|
94
|
+
detail = "HTTP #{response.code}#{": #{message}" if message}"
|
|
95
|
+
|
|
96
|
+
if response.is_a?(Net::HTTPUnauthorized) || response.is_a?(Net::HTTPForbidden)
|
|
97
|
+
raise LoginError, "Metadata update rejected (#{detail})"
|
|
98
|
+
end
|
|
99
|
+
return message if response.is_a?(Net::HTTPSuccess) && body.include?("<return>1</return>")
|
|
100
|
+
|
|
101
|
+
raise MetadataError, "Metadata update failed (#{detail})"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shout
|
|
4
|
+
# The version of the loaded libshout library (shout_version), as returned
|
|
5
|
+
# by Shout.version.
|
|
6
|
+
class LibraryVersion
|
|
7
|
+
include Comparable
|
|
8
|
+
|
|
9
|
+
# The oldest libshout with every function these bindings attach
|
|
10
|
+
# (shout_set_metadata_utf8 was added in 2.4.6).
|
|
11
|
+
MINIMUM = "2.4.6"
|
|
12
|
+
|
|
13
|
+
attr_reader :string, :major, :minor, :patch
|
|
14
|
+
|
|
15
|
+
# Raises LoadError when the loaded libshout is older than MINIMUM.
|
|
16
|
+
def self.ensure_supported!
|
|
17
|
+
version = new
|
|
18
|
+
return version if version >= Gem::Version.new(MINIMUM)
|
|
19
|
+
|
|
20
|
+
raise LoadError, "ffi-shout needs libshout #{MINIMUM} or newer, but found #{version.number}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize
|
|
24
|
+
major_ptr = FFI::MemoryPointer.new(:int)
|
|
25
|
+
minor_ptr = FFI::MemoryPointer.new(:int)
|
|
26
|
+
patch_ptr = FFI::MemoryPointer.new(:int)
|
|
27
|
+
|
|
28
|
+
@string = Native.shout_version(major_ptr, minor_ptr, patch_ptr)
|
|
29
|
+
@major = major_ptr.read_int
|
|
30
|
+
@minor = minor_ptr.read_int
|
|
31
|
+
@patch = patch_ptr.read_int
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# "major.minor.patch", e.g. "2.4.6".
|
|
35
|
+
def number
|
|
36
|
+
[major, minor, patch].join(".")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def <=>(other)
|
|
40
|
+
Gem::Version.new(number) <=> Gem::Version.new(other.to_s)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_s
|
|
44
|
+
@string
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shout
|
|
4
|
+
# Song metadata to send to the server, such as "song" => "Artist - Title".
|
|
5
|
+
# Keys and values are stored as UTF-8 strings.
|
|
6
|
+
class Metadata
|
|
7
|
+
include Enumerable
|
|
8
|
+
|
|
9
|
+
# The libshout shout_metadata_t handle (a Native::MetadataHandle).
|
|
10
|
+
attr_reader :handle
|
|
11
|
+
|
|
12
|
+
def initialize(attributes = {})
|
|
13
|
+
@handle = Native.shout_metadata_new
|
|
14
|
+
@attributes = {}
|
|
15
|
+
|
|
16
|
+
attributes.each do |key, value|
|
|
17
|
+
add(key, value)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def add(name, value)
|
|
22
|
+
key_str = name.to_s.encode("UTF-8")
|
|
23
|
+
val_str = value.to_s.encode("UTF-8")
|
|
24
|
+
|
|
25
|
+
Error.check_error(Native.shout_metadata_add(@handle, key_str, val_str))
|
|
26
|
+
|
|
27
|
+
@attributes[key_str] = val_str
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
alias []= add
|
|
31
|
+
|
|
32
|
+
def [](key)
|
|
33
|
+
@attributes[key.to_s]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def each(&)
|
|
37
|
+
return enum_for(:each) { @attributes.size } unless block_given?
|
|
38
|
+
|
|
39
|
+
@attributes.each(&)
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_h
|
|
44
|
+
@attributes.dup
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def to_ptr
|
|
48
|
+
@handle.to_ptr
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def ==(other)
|
|
52
|
+
other.is_a?(Metadata) && to_h == other.to_h
|
|
53
|
+
end
|
|
54
|
+
alias eql? ==
|
|
55
|
+
|
|
56
|
+
def hash
|
|
57
|
+
[self.class, @attributes].hash
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def inspect
|
|
61
|
+
"#<#{self.class.name} #{@attributes.inspect}>"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shout
|
|
4
|
+
# FFI enum types for the libshout constants in Shout (see constants.rb).
|
|
5
|
+
# Functions that take or return these values are attached with the enum
|
|
6
|
+
# type, so they accept and return symbols such as :mp3 or :http.
|
|
7
|
+
module Native
|
|
8
|
+
enum :shout_error, [
|
|
9
|
+
:success, SHOUTERR_SUCCESS,
|
|
10
|
+
:insane, SHOUTERR_INSANE,
|
|
11
|
+
:noconnect, SHOUTERR_NOCONNECT,
|
|
12
|
+
:nologin, SHOUTERR_NOLOGIN,
|
|
13
|
+
:socket, SHOUTERR_SOCKET,
|
|
14
|
+
:malloc, SHOUTERR_MALLOC,
|
|
15
|
+
:metadata, SHOUTERR_METADATA,
|
|
16
|
+
:connected, SHOUTERR_CONNECTED,
|
|
17
|
+
:unconnected, SHOUTERR_UNCONNECTED,
|
|
18
|
+
:unsupported, SHOUTERR_UNSUPPORTED,
|
|
19
|
+
:busy, SHOUTERR_BUSY,
|
|
20
|
+
:notls, SHOUTERR_NOTLS,
|
|
21
|
+
:tlsbadcert, SHOUTERR_TLSBADCERT,
|
|
22
|
+
:retry, SHOUTERR_RETRY
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
enum :shout_format, [
|
|
26
|
+
:ogg, SHOUT_FORMAT_OGG,
|
|
27
|
+
:mp3, SHOUT_FORMAT_MP3,
|
|
28
|
+
:webm, SHOUT_FORMAT_WEBM,
|
|
29
|
+
:webmaudio, SHOUT_FORMAT_WEBMAUDIO,
|
|
30
|
+
:matroska, SHOUT_FORMAT_MATROSKA,
|
|
31
|
+
:text, SHOUT_FORMAT_TEXT
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
enum :shout_protocol, [
|
|
35
|
+
:http, SHOUT_PROTOCOL_HTTP,
|
|
36
|
+
:xaudiocast, SHOUT_PROTOCOL_XAUDIOCAST,
|
|
37
|
+
:icy, SHOUT_PROTOCOL_ICY,
|
|
38
|
+
:roaraudio, SHOUT_PROTOCOL_ROARAUDIO
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
enum :shout_tls, [
|
|
42
|
+
:disabled, SHOUT_TLS_DISABLED,
|
|
43
|
+
:auto, SHOUT_TLS_AUTO,
|
|
44
|
+
:auto_no_plain, SHOUT_TLS_AUTO_NO_PLAIN,
|
|
45
|
+
:rfc2818, SHOUT_TLS_RFC2818,
|
|
46
|
+
:rfc2817, SHOUT_TLS_RFC2817
|
|
47
|
+
]
|
|
48
|
+
|
|
49
|
+
enum :shout_blocking, [
|
|
50
|
+
:full, SHOUT_BLOCKING_FULL,
|
|
51
|
+
:none, SHOUT_BLOCKING_NONE,
|
|
52
|
+
:default, SHOUT_BLOCKING_DEFAULT
|
|
53
|
+
]
|
|
54
|
+
|
|
55
|
+
enum :shout_control_t, [
|
|
56
|
+
:control_min, 0,
|
|
57
|
+
:control_get_server_certificate_as_pem, 1,
|
|
58
|
+
:control_get_server_certificate_chain_as_pem, 2,
|
|
59
|
+
:control_max, 32_767
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
enum :shout_event_t, [
|
|
63
|
+
:event_min, 0,
|
|
64
|
+
:event_tls_check_peer_certificate, 1,
|
|
65
|
+
:event_max, 32_767
|
|
66
|
+
]
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Shout
|
|
4
|
+
# attach_function declarations for every function in shout.h.
|
|
5
|
+
module Native
|
|
6
|
+
typedef :pointer, :shout_t
|
|
7
|
+
typedef :pointer, :shout_metadata_t
|
|
8
|
+
|
|
9
|
+
callback :shout_callback_t, %i[pointer int pointer pointer], :int
|
|
10
|
+
|
|
11
|
+
# These `attach_function` declarations are in the order of shout.h.
|
|
12
|
+
# Parameters and return values that take SHOUT_TLS_*, SHOUT_FORMAT_*,
|
|
13
|
+
# SHOUT_PROTOCOL_* or SHOUT_BLOCKING_* values use the FFI enums from
|
|
14
|
+
# enums.rb, so they accept and return symbols such as :mp3 or :http.
|
|
15
|
+
# shout_new and shout_metadata_new return a Handle / MetadataHandle,
|
|
16
|
+
# which frees itself when it is garbage collected.
|
|
17
|
+
|
|
18
|
+
# ----------------[ Generic ]----------------
|
|
19
|
+
|
|
20
|
+
# initializes the shout library. Must be called before anything else
|
|
21
|
+
attach_function :shout_init, [], :void
|
|
22
|
+
|
|
23
|
+
# shuts down the shout library, deallocating any global storage. Don't call
|
|
24
|
+
# anything afterwards
|
|
25
|
+
attach_function :shout_shutdown, [], :void
|
|
26
|
+
|
|
27
|
+
# returns a static version string. Non-null parameters will be set to the
|
|
28
|
+
# value of the library major, minor, and patch levels, respectively
|
|
29
|
+
# (shout_version is attached in native.rb, before the version check)
|
|
30
|
+
|
|
31
|
+
# Allocates and sets up a new shout_t. Returns NULL if it can't get enough
|
|
32
|
+
# memory. The returns shout_t must be disposed of with shout_free.
|
|
33
|
+
attach_function :shout_new, [], Handle
|
|
34
|
+
|
|
35
|
+
# Free all memory allocated by a shout_t
|
|
36
|
+
attach_function :shout_free, [:shout_t], :void
|
|
37
|
+
|
|
38
|
+
# Returns a statically allocated string describing the last shout error
|
|
39
|
+
# to occur. Only valid until the next libshout call on this shout_t
|
|
40
|
+
attach_function :shout_get_error, [:shout_t], :string
|
|
41
|
+
|
|
42
|
+
# Return the error code (e.g. SHOUTERR_SOCKET) for this shout instance
|
|
43
|
+
attach_function :shout_get_errno, [:shout_t], :int
|
|
44
|
+
|
|
45
|
+
# returns SHOUTERR_CONNECTED or SHOUTERR_UNCONNECTED
|
|
46
|
+
attach_function :shout_get_connected, [:shout_t], :int
|
|
47
|
+
|
|
48
|
+
# ----------------[ Configuration ]----------------
|
|
49
|
+
# Parameter manipulation functions. libshout makes copies of all parameters,
|
|
50
|
+
# the caller may free its copies after giving them to libshout. May return
|
|
51
|
+
# SHOUTERR_MALLOC
|
|
52
|
+
|
|
53
|
+
# Connection parameters
|
|
54
|
+
attach_function :shout_set_host, %i[shout_t string], :int
|
|
55
|
+
attach_function :shout_get_host, [:shout_t], :string
|
|
56
|
+
|
|
57
|
+
attach_function :shout_set_port, %i[shout_t ushort], :int
|
|
58
|
+
attach_function :shout_get_port, [:shout_t], :ushort
|
|
59
|
+
|
|
60
|
+
attach_function :shout_set_agent, %i[shout_t string], :int
|
|
61
|
+
attach_function :shout_get_agent, [:shout_t], :string
|
|
62
|
+
|
|
63
|
+
# See SHOUT_TLS_*
|
|
64
|
+
attach_function :shout_set_tls, %i[shout_t shout_tls], :int
|
|
65
|
+
attach_function :shout_get_tls, [:shout_t], :shout_tls
|
|
66
|
+
|
|
67
|
+
# Set the directory for CA certs. Default: operating system's default
|
|
68
|
+
attach_function :shout_set_ca_directory, %i[shout_t string], :int
|
|
69
|
+
attach_function :shout_get_ca_directory, [:shout_t], :string
|
|
70
|
+
|
|
71
|
+
# Set a CA cert file for checking. If you use a self signed server cert
|
|
72
|
+
# you can pass this cert using this function for verification.
|
|
73
|
+
# Default: operating system's default
|
|
74
|
+
attach_function :shout_set_ca_file, %i[shout_t string], :int
|
|
75
|
+
attach_function :shout_get_ca_file, [:shout_t], :string
|
|
76
|
+
|
|
77
|
+
# Set list of allowed ciphers.
|
|
78
|
+
attach_function :shout_set_allowed_ciphers, %i[shout_t string], :int
|
|
79
|
+
attach_function :shout_get_allowed_ciphers, [:shout_t], :string
|
|
80
|
+
|
|
81
|
+
# Authentication parameters
|
|
82
|
+
attach_function :shout_set_user, %i[shout_t string], :int
|
|
83
|
+
attach_function :shout_get_user, [:shout_t], :string
|
|
84
|
+
|
|
85
|
+
attach_function :shout_set_password, %i[shout_t string], :int
|
|
86
|
+
attach_function :shout_get_password, [:shout_t], :string
|
|
87
|
+
|
|
88
|
+
# Set a client certificate for TLS connections.
|
|
89
|
+
attach_function :shout_set_client_certificate, %i[shout_t string], :int
|
|
90
|
+
attach_function :shout_get_client_certificate, [:shout_t], :string
|
|
91
|
+
|
|
92
|
+
# Mount parameters
|
|
93
|
+
attach_function :shout_set_mount, %i[shout_t string], :int
|
|
94
|
+
attach_function :shout_get_mount, [:shout_t], :string
|
|
95
|
+
|
|
96
|
+
# Other parameters
|
|
97
|
+
# takes a SHOUT_AI_xxxx argument
|
|
98
|
+
attach_function :shout_set_audio_info, %i[shout_t string string], :int
|
|
99
|
+
attach_function :shout_get_audio_info, %i[shout_t string], :string
|
|
100
|
+
|
|
101
|
+
# takes a SHOUT_META_xxxx argument
|
|
102
|
+
attach_function :shout_set_meta, %i[shout_t string string], :int
|
|
103
|
+
attach_function :shout_get_meta, %i[shout_t string], :string
|
|
104
|
+
|
|
105
|
+
attach_function :shout_set_public, %i[shout_t uint], :int
|
|
106
|
+
attach_function :shout_get_public, [:shout_t], :uint
|
|
107
|
+
|
|
108
|
+
# Set the content language.
|
|
109
|
+
attach_function :shout_set_content_language, %i[shout_t string], :int
|
|
110
|
+
attach_function :shout_get_content_language, [:shout_t], :string
|
|
111
|
+
|
|
112
|
+
# Set the format of the content to send.
|
|
113
|
+
attach_function :shout_set_content_format, %i[shout_t shout_format uint string], :int
|
|
114
|
+
attach_function :shout_get_content_format, %i[shout_t pointer pointer pointer], :int
|
|
115
|
+
|
|
116
|
+
# takes a SHOUT_PROTOCOL_xxxxx argument
|
|
117
|
+
attach_function :shout_set_protocol, %i[shout_t shout_protocol], :int
|
|
118
|
+
attach_function :shout_get_protocol, [:shout_t], :shout_protocol
|
|
119
|
+
|
|
120
|
+
# Instructs libshout to use nonblocking I/O. Must be called before shout_open
|
|
121
|
+
attach_function :shout_set_nonblocking, %i[shout_t shout_blocking], :int
|
|
122
|
+
attach_function :shout_get_nonblocking, [:shout_t], :shout_blocking
|
|
123
|
+
|
|
124
|
+
# ----------------[ Actions ]----------------
|
|
125
|
+
# Calls that may wait on the network are attached with `blocking: true`
|
|
126
|
+
# so they release Ruby's GVL and don't freeze other threads.
|
|
127
|
+
|
|
128
|
+
# Opens a connection to the server. All parameters must already be set
|
|
129
|
+
attach_function :shout_open, [:shout_t], :int, blocking: true
|
|
130
|
+
|
|
131
|
+
# Closes a connection to the server
|
|
132
|
+
attach_function :shout_close, [:shout_t], :int, blocking: true
|
|
133
|
+
|
|
134
|
+
# Send data to the server, parsing it for format specific timing info
|
|
135
|
+
attach_function :shout_send, %i[shout_t pointer size_t], :int, blocking: true
|
|
136
|
+
|
|
137
|
+
# Send unparsed data to the server.
|
|
138
|
+
# Returns the number of bytes written, or < 0 on error.
|
|
139
|
+
attach_function :shout_send_raw, %i[shout_t pointer size_t], :ssize_t, blocking: true
|
|
140
|
+
|
|
141
|
+
# return the number of bytes currently on the write queue (only makes sense in
|
|
142
|
+
# nonblocking mode).
|
|
143
|
+
attach_function :shout_queuelen, [:shout_t], :ssize_t
|
|
144
|
+
|
|
145
|
+
# Puts caller to sleep until it is time to send more data to the server
|
|
146
|
+
attach_function :shout_sync, [:shout_t], :void, blocking: true
|
|
147
|
+
|
|
148
|
+
# Amount of time in ms caller should wait before sending again
|
|
149
|
+
attach_function :shout_delay, [:shout_t], :int
|
|
150
|
+
|
|
151
|
+
# ----------------[ MP3/AAC ONLY ]----------------
|
|
152
|
+
|
|
153
|
+
# Sets MP3 metadata. Returns one of SHOUTERR_XXX.
|
|
154
|
+
attach_function :shout_set_metadata_utf8, %i[shout_t shout_metadata_t], :int, blocking: true
|
|
155
|
+
|
|
156
|
+
# Allocates a new metadata structure. Must be freed by shout_metadata_free.
|
|
157
|
+
attach_function :shout_metadata_new, [], MetadataHandle
|
|
158
|
+
|
|
159
|
+
# Free resources allocated by shout_metadata_t
|
|
160
|
+
attach_function :shout_metadata_free, [:shout_metadata_t], :void
|
|
161
|
+
|
|
162
|
+
# Add a parameter to the metadata structure.
|
|
163
|
+
attach_function :shout_metadata_add, %i[shout_metadata_t string string], :int
|
|
164
|
+
|
|
165
|
+
# ----------------[ Advanced ]----------------
|
|
166
|
+
|
|
167
|
+
attach_function :shout_control, %i[shout_t int varargs], :int
|
|
168
|
+
attach_function :shout_set_callback, %i[shout_t shout_callback_t pointer], :int
|
|
169
|
+
|
|
170
|
+
# ----------------[ Obsolete ]----------------
|
|
171
|
+
|
|
172
|
+
attach_function :shout_set_name, %i[shout_t string], :int
|
|
173
|
+
attach_function :shout_get_name, [:shout_t], :string
|
|
174
|
+
|
|
175
|
+
attach_function :shout_set_url, %i[shout_t string], :int
|
|
176
|
+
attach_function :shout_get_url, [:shout_t], :string
|
|
177
|
+
|
|
178
|
+
attach_function :shout_set_genre, %i[shout_t string], :int
|
|
179
|
+
attach_function :shout_get_genre, [:shout_t], :string
|
|
180
|
+
|
|
181
|
+
attach_function :shout_set_description, %i[shout_t string], :int
|
|
182
|
+
attach_function :shout_get_description, [:shout_t], :string
|
|
183
|
+
|
|
184
|
+
attach_function :shout_set_format, %i[shout_t shout_format], :int
|
|
185
|
+
attach_function :shout_get_format, [:shout_t], :shout_format
|
|
186
|
+
|
|
187
|
+
attach_function :shout_set_dumpfile, %i[shout_t string], :int
|
|
188
|
+
attach_function :shout_get_dumpfile, [:shout_t], :string
|
|
189
|
+
|
|
190
|
+
attach_function :shout_set_metadata, %i[shout_t shout_metadata_t], :int, blocking: true
|
|
191
|
+
end
|
|
192
|
+
end
|