budik 1.0.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/.coveralls.yml +1 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +176 -0
- data/Rakefile +23 -0
- data/bin/budik +4 -0
- data/bin/console +14 -0
- data/bin/setup +10 -0
- data/budik.gemspec +55 -0
- data/config/templates/lang/en.yml +37 -0
- data/config/templates/options/linux.yml +39 -0
- data/config/templates/options/rpi.yml +39 -0
- data/config/templates/options/windows.yml +39 -0
- data/config/templates/sources/sources.yml +34 -0
- data/lib/budik.rb +152 -0
- data/lib/budik/command.rb +199 -0
- data/lib/budik/config.rb +149 -0
- data/lib/budik/devices.rb +146 -0
- data/lib/budik/io.rb +58 -0
- data/lib/budik/player.rb +174 -0
- data/lib/budik/rng.rb +154 -0
- data/lib/budik/sources.rb +182 -0
- data/lib/budik/storage.rb +91 -0
- data/lib/budik/version.rb +15 -0
- metadata +288 -0
data/lib/budik/config.rb
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
# = config.rb
|
2
|
+
# This file contains methods for configuring the application.
|
3
|
+
#
|
4
|
+
# == Contact
|
5
|
+
#
|
6
|
+
# Author:: Petr Schmied (mailto:jblack@paworld.eu)
|
7
|
+
# Website:: http://www.paworld.eu
|
8
|
+
# Date:: September 20, 2015
|
9
|
+
|
10
|
+
module Budik
|
11
|
+
# 'Config' class loads and manages app configuration.
|
12
|
+
class Config
|
13
|
+
include Singleton
|
14
|
+
|
15
|
+
# Installs the application if not installed.
|
16
|
+
# Loads options, sources and language.
|
17
|
+
def initialize
|
18
|
+
@templates_dir = File.dirname(__FILE__) + '/../../config/templates/'
|
19
|
+
install unless installed?
|
20
|
+
|
21
|
+
@options = YAML.load_file(Dir.home + '/.budik/options.yml')
|
22
|
+
@sources = YAML.load_file(File.expand_path(@options['sources']['path']))
|
23
|
+
@lang = init_lang
|
24
|
+
end
|
25
|
+
|
26
|
+
# Sets application's language.
|
27
|
+
#
|
28
|
+
# - *Returns*:
|
29
|
+
# - R18n::Translation object.
|
30
|
+
#
|
31
|
+
def init_lang
|
32
|
+
R18n.default_places = Dir.home + '/.budik/lang/'
|
33
|
+
R18n.set(@options['lang'])
|
34
|
+
R18n.t
|
35
|
+
end
|
36
|
+
|
37
|
+
# Language strings, options and sources.
|
38
|
+
attr_accessor :lang, :options, :sources
|
39
|
+
|
40
|
+
# Opens options file for editing.
|
41
|
+
def edit
|
42
|
+
options_path = File.expand_path('~/.budik/options.yml')
|
43
|
+
open_file(options_path)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Installs the application.
|
47
|
+
def install
|
48
|
+
dir = Dir.home + '/.budik/'
|
49
|
+
FileUtils.mkdir_p([dir, dir + 'lang/', dir + 'downloads/'])
|
50
|
+
|
51
|
+
install_options(dir)
|
52
|
+
install_sources(dir)
|
53
|
+
install_lang(dir)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Creates options file from template.
|
57
|
+
#
|
58
|
+
# - *Args*:
|
59
|
+
# - +dir+ -> Directory containing app's configuration (String).
|
60
|
+
#
|
61
|
+
def install_options(dir)
|
62
|
+
options = @templates_dir + 'options/' + platform?.to_s + '.yml'
|
63
|
+
FileUtils.cp options, dir + 'options.yml'
|
64
|
+
end
|
65
|
+
|
66
|
+
# Creates sources file from template.
|
67
|
+
#
|
68
|
+
# - *Args*:
|
69
|
+
# - +dir+ -> Directory containing app's configuration (String).
|
70
|
+
#
|
71
|
+
def install_sources(dir)
|
72
|
+
sources = @templates_dir + 'sources/sources.yml'
|
73
|
+
FileUtils.cp sources, dir unless File.file? dir + sources
|
74
|
+
end
|
75
|
+
|
76
|
+
# Creates default language file from template.
|
77
|
+
#
|
78
|
+
# - *Args*:
|
79
|
+
# - +dir+ -> Directory containing app's configuration (String).
|
80
|
+
#
|
81
|
+
def install_lang(dir)
|
82
|
+
lang = @templates_dir + 'lang/en.yml'
|
83
|
+
FileUtils.cp lang, dir + 'lang/'
|
84
|
+
end
|
85
|
+
|
86
|
+
# Checks if the application is already installed.
|
87
|
+
def installed?
|
88
|
+
File.file?(Dir.home + '/.budik/options.yml')
|
89
|
+
end
|
90
|
+
|
91
|
+
# Opens file in default editor depending on platform.
|
92
|
+
#
|
93
|
+
# - *Args*:
|
94
|
+
# - +file+ -> File to open (String).
|
95
|
+
#
|
96
|
+
def open_file(file)
|
97
|
+
if @options['os'] == 'windows'
|
98
|
+
system('@powershell -Command "' + file + '"')
|
99
|
+
else
|
100
|
+
editor = ENV['EDITOR'] ? ENV['EDITOR'] : 'vi'
|
101
|
+
system(editor + ' "' + file + '"')
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Returns current platform application's running on.
|
106
|
+
#
|
107
|
+
# - *Returns*:
|
108
|
+
# - :windows, :linux or :rpi
|
109
|
+
#
|
110
|
+
def platform?
|
111
|
+
os = Sys::Platform.linux? ? :linux : :windows
|
112
|
+
rpi?(os) ? :rpi : os
|
113
|
+
end
|
114
|
+
|
115
|
+
# Resets app's configuration.
|
116
|
+
def reset
|
117
|
+
options = @templates_dir + 'options/' + platform?.to_s + '.yml'
|
118
|
+
FileUtils.cp(options, Dir.home + '/.budik/options.yml')
|
119
|
+
end
|
120
|
+
|
121
|
+
# Checks if application is running on Raspberry Pi.
|
122
|
+
#
|
123
|
+
# - *Args*:
|
124
|
+
# - +os+ -> Operating system (:windows or :linux)
|
125
|
+
# - *Returns*:
|
126
|
+
# - true or false
|
127
|
+
#
|
128
|
+
def rpi?(os)
|
129
|
+
return false unless os == :linux
|
130
|
+
cpuinfo = File.read('/proc/cpuinfo')
|
131
|
+
hardware = cpuinfo.scan(/[hH]ardware\s*:\s*(\w+)/).first.first
|
132
|
+
hardware =~ /BCM270[89]/
|
133
|
+
rescue
|
134
|
+
false
|
135
|
+
end
|
136
|
+
|
137
|
+
# Creates and/or opens language file for translation.
|
138
|
+
#
|
139
|
+
# - *Args*:
|
140
|
+
# - +lang+ -> Language code (String)
|
141
|
+
#
|
142
|
+
def translate(lang)
|
143
|
+
template = @templates_dir + 'lang/en.yml'
|
144
|
+
new_lang = Dir.home + '/.budik/lang/' + lang + '.yml'
|
145
|
+
FileUtils.cp template, new_lang
|
146
|
+
open_file(new_lang)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,146 @@
|
|
1
|
+
# = devices.rb
|
2
|
+
# This file contains methods for managing devices (storage and TV).
|
3
|
+
#
|
4
|
+
# == Contact
|
5
|
+
#
|
6
|
+
# Author:: Petr Schmied (mailto:jblack@paworld.eu)
|
7
|
+
# Website:: http://www.paworld.eu
|
8
|
+
# Date:: September 20, 2015
|
9
|
+
|
10
|
+
module Budik
|
11
|
+
# 'Devices' class manages display and storage devices.
|
12
|
+
class Devices
|
13
|
+
include Singleton
|
14
|
+
|
15
|
+
# Loads TV and storage settings.
|
16
|
+
def initialize
|
17
|
+
options = Config.instance.options
|
18
|
+
@tv = {}
|
19
|
+
@storage = { mounted: nil, awake: nil, unmount: false }
|
20
|
+
|
21
|
+
tv_load(options['tv'])
|
22
|
+
storage_load(options['sources']['download'])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns TV and storage settings.
|
26
|
+
attr_accessor :storage, :tv
|
27
|
+
|
28
|
+
# Loads storage settings.
|
29
|
+
#
|
30
|
+
# - *Args*:
|
31
|
+
# - +options+ -> Storage options (Hash).
|
32
|
+
#
|
33
|
+
def storage_load(options)
|
34
|
+
@storage[:device] = options['device']
|
35
|
+
@storage[:partition] = options['partition']
|
36
|
+
@storage[:dir] = options['dir']
|
37
|
+
|
38
|
+
part_sub = { '$partition': @storage[:partition] }
|
39
|
+
dev_sub = { '$device': @storage[:device] }
|
40
|
+
|
41
|
+
storage_parse_cmd('mount', options['mount'], part_sub, mounted: false)
|
42
|
+
storage_parse_cmd('unmount', options['unmount'], part_sub, unmount: true)
|
43
|
+
storage_parse_cmd('sleep', options['sleep'], dev_sub, awake: false)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Substitutes device and partition in (un)mount and sleep commands.
|
47
|
+
#
|
48
|
+
# == Example
|
49
|
+
#
|
50
|
+
# cmd = 'sleep'
|
51
|
+
# template = 'sudo hdparm -y $device'
|
52
|
+
# subst = { '$device': '/dev/sda' }
|
53
|
+
# state_mods = { awake: false }
|
54
|
+
#
|
55
|
+
# Parsed command: 'sudo hdparm -y /dev/sda'
|
56
|
+
# State 'awake' set to false
|
57
|
+
#
|
58
|
+
# - *Args*:
|
59
|
+
# - +cmd+ -> Command ('mount', 'unmount' or 'sleep').
|
60
|
+
# - +template+ -> Command template (String).
|
61
|
+
# - +subst+ -> Variable to substitute (Hash, variable: value).
|
62
|
+
# - +state_mods+ -> State modifiers (Hash).
|
63
|
+
#
|
64
|
+
def storage_parse_cmd(cmd, template, subst, state_mods = {})
|
65
|
+
return if template.empty?
|
66
|
+
|
67
|
+
cmd = (cmd + '_command').to_sym
|
68
|
+
var, val = subst.first
|
69
|
+
@storage[cmd] = template.gsub(var.to_s, val)
|
70
|
+
state_mods.each { |state, setting| @storage[state] = setting }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Mounts partition if needed and if not already mounted
|
74
|
+
# If applicable, sets 'mounted' and 'awake' states to true
|
75
|
+
def storage_mount
|
76
|
+
unless @storage[:mounted].nil? || @storage[:mounted] == true
|
77
|
+
system(@storage[:mount_command])
|
78
|
+
end
|
79
|
+
|
80
|
+
@storage[:mounted] = true unless @storage[:mounted].nil?
|
81
|
+
@storage[:awake] = true unless @storage[:awake].nil?
|
82
|
+
end
|
83
|
+
|
84
|
+
# Unmounts partition if needed and if mounted
|
85
|
+
# If applicable, sets 'mounted' state to false
|
86
|
+
def storage_unmount
|
87
|
+
unmount = !@storage[:unmount]
|
88
|
+
unless unmount || @storage[:mounted].nil? || @storage[:mounted] == false
|
89
|
+
system(@storage[:unmount_command])
|
90
|
+
end
|
91
|
+
|
92
|
+
@storage[:mounted] = false unless @storage[:mounted].nil?
|
93
|
+
end
|
94
|
+
|
95
|
+
# Spins device down if needed and if awake
|
96
|
+
# If applicable, sets 'awake' state to false
|
97
|
+
def storage_sleep
|
98
|
+
sleep_check = @storage[:awake].nil? || @storage[:awake] == false
|
99
|
+
|
100
|
+
unless sleep_check || @storage[:mounted] == true
|
101
|
+
system(@storage[:sleep_command])
|
102
|
+
end
|
103
|
+
|
104
|
+
@storage[:awake] = false unless @storage[:awake].nil?
|
105
|
+
end
|
106
|
+
|
107
|
+
# Loads TV settings if TV is available.
|
108
|
+
#
|
109
|
+
# - *Args*:
|
110
|
+
# - +options+ -> TV options (Hash).
|
111
|
+
#
|
112
|
+
def tv_load(options)
|
113
|
+
if options['available']
|
114
|
+
@tv[:use_if_no_video] = options['use_if_no_video']
|
115
|
+
@tv[:wait_secs_after_on] = options['wait_secs_after_on']
|
116
|
+
@tv[:on] = false
|
117
|
+
else
|
118
|
+
@tv[:on] = nil
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Turns on TV if needed and if not already on
|
123
|
+
# Gives TV time to turn on, then sets active HDMI as active source
|
124
|
+
# If applicable, sets 'on' state to true
|
125
|
+
def tv_on
|
126
|
+
unless @tv[:on].nil? || @tv[:on] == true
|
127
|
+
system('echo "on 0" | cec-client -s >/dev/null')
|
128
|
+
sleep(@tv[:wait_secs_after_on]) unless @tv[:wait_secs_after_on].nil?
|
129
|
+
system('echo "as" | cec-client -s >/dev/null')
|
130
|
+
end
|
131
|
+
|
132
|
+
@tv[:on] = true unless @tv[:on].nil?
|
133
|
+
end
|
134
|
+
|
135
|
+
# Turns off TV if needed and if on
|
136
|
+
# If applicable, sets 'on' state to false
|
137
|
+
# Doesn't work on my TV
|
138
|
+
def tv_off
|
139
|
+
unless @tv[:on].nil? || @tv[:on] == false
|
140
|
+
system('echo "standby 0" | cec-client -s >/dev/null')
|
141
|
+
end
|
142
|
+
|
143
|
+
@tv[:on] = false unless @tv[:on].nil?
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
data/lib/budik/io.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# = io.rb
|
2
|
+
# This file contains methods for managing application's input and output.
|
3
|
+
#
|
4
|
+
# == Contact
|
5
|
+
#
|
6
|
+
# Author:: Petr Schmied (mailto:jblack@paworld.eu)
|
7
|
+
# Website:: http://www.paworld.eu
|
8
|
+
# Date:: September 20, 2015
|
9
|
+
|
10
|
+
module Budik
|
11
|
+
# 'Output' class provides information to the user via console.
|
12
|
+
class IO
|
13
|
+
include Singleton
|
14
|
+
|
15
|
+
# Loads output strings in currently set language.
|
16
|
+
def initialize
|
17
|
+
@strings = Config.instance.lang.output
|
18
|
+
end
|
19
|
+
|
20
|
+
# Outputs table formatted information about selected source
|
21
|
+
# to the console.
|
22
|
+
#
|
23
|
+
# - *Args*:
|
24
|
+
# - +source+ -> Selected source (Hash).
|
25
|
+
# - +number+ -> Number of selected source (Fixnum).
|
26
|
+
#
|
27
|
+
def run_info_table(source, number)
|
28
|
+
title = 'Budik - ' + DateTime.now.strftime('%d/%m/%Y %H:%M')
|
29
|
+
|
30
|
+
rows = []
|
31
|
+
rows << [@strings.alarm, source[:name]]
|
32
|
+
rows << [@strings.category, source[:category].to_s]
|
33
|
+
rows << [@strings.number, number.to_s]
|
34
|
+
|
35
|
+
Terminal::Table.new title: title, rows: rows
|
36
|
+
end
|
37
|
+
|
38
|
+
# Outputs formatted list of sources to the console.
|
39
|
+
#
|
40
|
+
# - *Args*:
|
41
|
+
# - +sources+ -> Parsed sources (Array of Hashes).
|
42
|
+
#
|
43
|
+
def sources_print(sources)
|
44
|
+
sources.each_with_index do |source, index|
|
45
|
+
puts '[' + index.to_s.light_white + '] ' + source[:name].yellow
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Outputs information about source being downloaded.
|
50
|
+
#
|
51
|
+
# - *Args*:
|
52
|
+
# - +source+ -> Source being downloaded (Hash).
|
53
|
+
#
|
54
|
+
def storage_download_info(source)
|
55
|
+
puts @strings.downloading + source[:name]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/budik/player.rb
ADDED
@@ -0,0 +1,174 @@
|
|
1
|
+
# = player.rb
|
2
|
+
# This file contains methods for managing media players.
|
3
|
+
#
|
4
|
+
# == Contact
|
5
|
+
#
|
6
|
+
# Author:: Petr Schmied (mailto:jblack@paworld.eu)
|
7
|
+
# Website:: http://www.paworld.eu
|
8
|
+
# Date:: September 20, 2015
|
9
|
+
|
10
|
+
module Budik
|
11
|
+
# 'Player' class handles communication between app and media players.
|
12
|
+
class Player
|
13
|
+
include Singleton
|
14
|
+
|
15
|
+
# Sets player and loads its options.
|
16
|
+
def initialize
|
17
|
+
player_options = Config.instance.options['player']
|
18
|
+
@player = player_options['player']
|
19
|
+
|
20
|
+
if @player == 'omxplayer'
|
21
|
+
@player_options = player_options['omxplayer']
|
22
|
+
else
|
23
|
+
@player_options = player_options['vlc']
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Gets current player and its options.
|
28
|
+
attr_accessor :player, :player_options
|
29
|
+
|
30
|
+
# Plays a source using currently set player.
|
31
|
+
#
|
32
|
+
# - *Args*:
|
33
|
+
# - +source+ -> Source to play (Hash).
|
34
|
+
#
|
35
|
+
def play(source)
|
36
|
+
if @player == 'omxplayer'
|
37
|
+
omxplayer(source)
|
38
|
+
else
|
39
|
+
vlc(source)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# Plays a source using omxplayer.
|
44
|
+
#
|
45
|
+
# - *Args*:
|
46
|
+
# - +source+ -> Source to play (Hash).
|
47
|
+
#
|
48
|
+
def omxplayer(source)
|
49
|
+
source[:path].each_with_index do |item, index|
|
50
|
+
Open3.popen3(omx_build_command(item)) do |i, _o, _e, _t|
|
51
|
+
omx_volume_control(i) if index == 0
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Builds omxplayer's command with required parameters.
|
57
|
+
#
|
58
|
+
# - *Args*:
|
59
|
+
# - +item+ -> Item to play (path, String).
|
60
|
+
#
|
61
|
+
def omx_build_command(item)
|
62
|
+
command = @player_options['path']
|
63
|
+
args = '--vol ' + @player_options['default_volume'].to_s
|
64
|
+
command + ' ' + args + ' "' + Storage.instance.locate_item(item) + '"'
|
65
|
+
end
|
66
|
+
|
67
|
+
# Fades in volume using omxplayer's volup command.
|
68
|
+
#
|
69
|
+
# - *Args*:
|
70
|
+
# - +i+ -> Stdin object.
|
71
|
+
#
|
72
|
+
def omx_volume_control(i)
|
73
|
+
7.times do
|
74
|
+
sleep(@player_options['volume_step_secs'])
|
75
|
+
i.print '+'
|
76
|
+
end
|
77
|
+
i.close
|
78
|
+
end
|
79
|
+
|
80
|
+
# Plays a source using vlc.
|
81
|
+
#
|
82
|
+
# - *Args*:
|
83
|
+
# - +source+ -> Source to play (Hash).
|
84
|
+
#
|
85
|
+
def vlc(source)
|
86
|
+
vlc_pid = spawn(vlc_build_command(source))
|
87
|
+
sleep(@player_options['wait_secs_after_run'])
|
88
|
+
vlc_volume_control(vlc_rc_connect)
|
89
|
+
|
90
|
+
Process.wait(vlc_pid)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Builds VLC's command with required parameters.
|
94
|
+
#
|
95
|
+
# - *Args*:
|
96
|
+
# - +source+ -> Source to play (Hash).
|
97
|
+
#
|
98
|
+
def vlc_build_command(source)
|
99
|
+
vlc_path = Marshal.load(Marshal.dump(@player_options['path']))
|
100
|
+
vlc_path.gsub!(/(^|$)/, '"') if vlc_path =~ /\s/
|
101
|
+
|
102
|
+
args = vlc_build_args
|
103
|
+
files = vlc_cmd_add_items(source)
|
104
|
+
|
105
|
+
vlc_path + args[:rc] + args[:volume] + args[:fullscreen] + files
|
106
|
+
end
|
107
|
+
|
108
|
+
# Builds list of options/arguments fo VLC command
|
109
|
+
def vlc_build_args
|
110
|
+
rc_host = @player_options['rc_host']
|
111
|
+
rc_port = @player_options['rc_port']
|
112
|
+
rc = ' --extraintf rc --rc-host ' + rc_host + ':' + rc_port.to_s
|
113
|
+
|
114
|
+
volume = ' --volume-step ' + @player_options['volume_step'].to_s
|
115
|
+
fullscreen = @player_options['fullscreen'] ? ' --fullscreen ' : ' '
|
116
|
+
|
117
|
+
{ rc: rc, volume: volume, fullscreen: fullscreen }
|
118
|
+
end
|
119
|
+
|
120
|
+
# Parses source and adds its items to the VLC command.
|
121
|
+
# Adds 'vlc://quit' to automatically quit VLC after play is over.
|
122
|
+
#
|
123
|
+
# - *Args*:
|
124
|
+
# - +source+ -> Source to play (Hash).
|
125
|
+
#
|
126
|
+
def vlc_cmd_add_items(source)
|
127
|
+
files = ''
|
128
|
+
source[:path].each do |item|
|
129
|
+
item_path = Storage.instance.locate_item(item).gsub(%r{^/}, '')
|
130
|
+
files += (vlc_cmd_item_prefix(item_path) + item_path + '" ')
|
131
|
+
end
|
132
|
+
files += 'vlc://quit'
|
133
|
+
end
|
134
|
+
|
135
|
+
# Adds 'file:///' prefix to local file paths so VLC plays them
|
136
|
+
# correctly.
|
137
|
+
#
|
138
|
+
# - *Args*:
|
139
|
+
# - +item_path+ -> Path to item.
|
140
|
+
#
|
141
|
+
def vlc_cmd_item_prefix(item_path)
|
142
|
+
is_url = (item_path =~ /\A#{URI.regexp(%w(http https))}\z/)
|
143
|
+
is_url ? '"' : '"file:///'
|
144
|
+
end
|
145
|
+
|
146
|
+
# Makes a connection to VLC's remote control interface.
|
147
|
+
# FIXME: Possible infinite loop
|
148
|
+
def vlc_rc_connect
|
149
|
+
rc_host = @player_options['rc_host']
|
150
|
+
rc_port = @player_options['rc_port']
|
151
|
+
loop do
|
152
|
+
begin
|
153
|
+
rc = TCPSocket.open(rc_host, rc_port)
|
154
|
+
return rc
|
155
|
+
rescue
|
156
|
+
next
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
# Fades in volume using VLC's remote control interface.
|
162
|
+
#
|
163
|
+
# - *Args*:
|
164
|
+
# - +rc+ -> IO object (returned by TCPSocket.open).
|
165
|
+
#
|
166
|
+
def vlc_volume_control(rc)
|
167
|
+
rc.puts 'volume ' + @player_options['default_volume'].to_s
|
168
|
+
128.times do
|
169
|
+
sleep(@player_options['volume_fadein_secs'])
|
170
|
+
rc.puts 'volup ' + @player_options['volume_step'].to_s
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|