This repository has been archived by the owner on Jun 1, 2019. It is now read-only.
/
mplayer.rb
194 lines (166 loc) · 5.56 KB
/
mplayer.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
require 'net/http'
class MPlayer
include DRbUndumped
attr_reader :client, :buffer, :stream_buffer, :offset, :thread, :thread2
attr_reader :stream, :process, :state, :total_size, :streamed_size
attr_reader :position, :position_str, :length, :length_str, :paused
def self.stream server, key, client=nil
url = "http://#{server}/stream.php"
url = URI.parse url
http = Net::HTTP.new url.host, url.port
req = Net::HTTP::Post.new url.request_uri, {'Cookie' => "PHPSESSID=#{$session}"}
req.set_form_data({'streamKey' => key}, ';')
mplayer = self.new client
mplayer.play_from_http http, req
mplayer.close
end
def initialize client=nil
@file = "/tmp/remora-#{(rand*100000000).to_i}"
`mkfifo #{@file}` # TODO: do this nicer
if client.use_aoss
@process = IO.popen("aoss mplayer #{@file} -demuxer lavf -slave 2>&1", 'w+')
else
@process = IO.popen("mplayer #{@file} -demuxer lavf -slave 2>&1", 'w+')
end
@stream = File.open(@file, 'w')
@buffer = ''
@stream_buffer = ''
@offset = 0
@state = :uninited
@client = client
@client.player = self
@paused = false
@process.puts "volume #{@client.volume} 1"
end
def pause
@process.puts 'pause'
@paused = !@paused
end
def stop
@thread2.kill rescue nil
@process.puts 'stop'
@client.player = nil
close
@client.now_playing = nil
@client.display.panes[:np].controls[:song_name].text = 'Nothing'
@client.display.panes[:np].controls[:cue].value = 0
@client.display.panes[:np].controls[:cue].value2 = 0
@client.display.panes[:np].controls[:position].text = ''
@client.display.dirty! :np
end
def close
@thread.kill rescue nil
@stream.close rescue nil
@process.close rescue nil
`rm #{@file}` if File.exists? @file
end
def time_to_s seconds
sec ||= 0
minutes = seconds.to_i/60
"#{minutes}:#{(seconds.to_i-(minutes*60)).to_s.rjust 2, '0'}"
end
def handle_stdout
@last_pos ||= 0
@buffer += @process.read_nonblock(1024).gsub("\r", "\n")
while @buffer.include?("\n")
line = @buffer.slice!(0, @buffer.index("\n") + 1).chomp
if line =~ /^A: +([0-9.]+) \(([0-9.:]+)\) of ([0-9.]+) \(([0-9.:]+)\) +([0-9.,?]+)%/
@position, @position_str = $1.to_i, $2
@length, @length_str = $3.to_i, $4
elsif line =~ /^A: +([0-9.]+) \(([0-9.:]+)\) of 0\.0 \(unknown\) +([0-9.,?]+)%/
@position, @position_str = $1.to_i, $2
else
control = @client.display.panes[:log].controls[:output]
control.data << line.gsub("\e[J", '')
control.data.shift while control.data.size > control.height
@client.display.dirty! :log
next
end
@client.display.panes[:np].controls[:cue].value2 = @position
@client.display.panes[:np].controls[:position].text = "#{time_to_s @position} / #{time_to_s @client.now_playing.duration} (" + (@state == :playing ? "-#{time_to_s @client.now_playing.duration - @position})" : "#{@stream_buffer.size / 1024} / #{@total_size / 1024} KiB)")
if @last_pos < @position
@client.display.dirty! :np
@last_pos = @position
end
end
rescue Errno::EAGAIN
end
def play_from_http http, req
@thread = Thread.new do
begin
stream_from_http http, req
@state = :playing
rescue => ex
$display.close
puts ex.class, ex.message, ex.backtrace
exit
end
end
@thread2 = Thread.current
sleep 0.1 until @total_size && @total_size > 0
until @stream_buffer.size >= @total_size && @offset >= @total_size
sleep 0.1 until @stream_buffer.size > @offset
data = @stream_buffer[@offset, 512]
@offset += data.size
@stream.write data
@stream.flush
handle_stdout
end
wait_for_exit
rescue IOError, Errno::EPIPE => ex
$display.close
puts ex.class, ex.message, ex.backtrace
close rescue nil
exit
end
def stream_from_http http, req
http.request(req) do |res|
@state = :starting_stream
@total_size = res.header['Content-Length'].to_i
if @total_size && @total_size > 0
@client.display.panes[:np].controls[:cue].maximum = @total_size
@client.display.panes[:np].controls[:cue].value = 0
@client.display.dirty! :np
end
last_report = 0
res.read_body do |chunk|
if chunk.size > 0
@stream_buffer << chunk
@state = :streaming
@client.display.panes[:np].controls[:cue].value = @stream_buffer.size
@client.display.panes[:np].controls[:position].text = "#{time_to_s @position} / #{time_to_s @client.now_playing.duration} (#{@stream_buffer.size / 1024} / #{@total_size / 1024} KiB)"
last_report += chunk.size
if last_report > 102400
@client.display.dirty! :np
last_report -= 102400
end
end
end
case res
when Net::HTTPSuccess
#puts "success"
when Net::HTTPRedirection
url = URI.parse(res['location'])
stream_from_http Net::HTTP.new(url.host, url.port), Net::HTTP::Get.new(url.request_uri, {'Cookie' => "PHPSESSID=#{$session}"})
break
else
puts "error!"
exit
end
end
rescue => ex
$display.close
puts ex.class, ex.message, ex.backtrace
exit
end
def wait_for_exit
@state = :waiting
@stream.close
until @process.eof?
handle_stdout
sleep 0.1
end
@state = :complete
close
end
end