budik 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|