sleeproom 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SleepRoom
4
+ module Record
5
+ module API
6
+ class RoomAPI
7
+ def initialize(room_url_key)
8
+ @url = ROOM_API + "?room_url_key=" + room_url_key
9
+ @json = nil
10
+ get
11
+ end
12
+
13
+ def get(task: Async::Task.current)
14
+ @json = API.get(@url).wait
15
+ end
16
+
17
+ def live?
18
+ @json["is_live"]
19
+ end
20
+
21
+ def broadcast_key
22
+ @json["broadcast_key"].to_s
23
+ end
24
+
25
+ def broadcast_host
26
+ @json["broadcast_host"].to_s
27
+ end
28
+
29
+ def room_id
30
+ @json["room_id"]
31
+ end
32
+
33
+ def room_name
34
+ @json["room_name"]
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SleepRoom
4
+ module Record
5
+ module API
6
+ class StreamingAPI
7
+ def initialize(room_id)
8
+ @url = STREAMING_API + "?room_id=" + room_id.to_s + "&ignore_low_stream=1"
9
+ @json = nil
10
+ get
11
+ end
12
+
13
+ def get(task: Async::Task.current)
14
+ @json = API.get(@url).wait
15
+ end
16
+
17
+ def streaming_url
18
+ @json["streaming_url_list"].sort_by{|hash| -hash["quality"]}.first["url"]
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+ require "sleeproom/record/write_status"
3
+
4
+ module SleepRoom
5
+ module Record
6
+ class Showroom
7
+ SITE = "showroom"
8
+ def initialize(room:, group: "default", queue:)
9
+ @room = room
10
+ @group = group
11
+ @queue = queue
12
+ @running = false
13
+ @downlaoding = false
14
+ @reconnection = false
15
+ end
16
+
17
+ # @param user [String]
18
+ # @return [Boolean]
19
+ def record(reconnection: false)
20
+ room = @room
21
+ Async do |task|
22
+ api = API::RoomAPI.new(room)
23
+ task.async do |t|
24
+ while api.live?
25
+ if status = SleepRoom.load_config(:status).find{|hash| hash[:room] == room}
26
+ if !status[:pid].nil?
27
+ break if SleepRoom.running?(pid) == false
28
+ else
29
+ break
30
+ end
31
+ else
32
+ break
33
+ end
34
+ t.sleep 60
35
+ end
36
+ end.wait
37
+ @room_id = api.room_id
38
+ @room_name = api.room_name
39
+ @is_live = api.live?
40
+ @broadcast_host = api.broadcast_host
41
+ @broadcast_key = api.broadcast_key
42
+ if @is_live
43
+ start_time = Time.now
44
+ log("Live broadcast.")
45
+ streaming_url = parse_streaming_url
46
+ output = build_output
47
+ pid = SleepRoom::Record.call_minyami(url: streaming_url, output: output)
48
+ downloading(streaming_url, pid, start_time)
49
+ record
50
+ else
51
+ log("Status: Stop.")
52
+ waiting_live(ws: :init)
53
+ Async do |task|
54
+ while true
55
+ if @running == false && @reconnection == false
56
+ start_websocket
57
+ elsif @reconnection == true
58
+ api = API::RoomAPI.new(room)
59
+ @room_id = api.room_id
60
+ @room_name = api.room_name
61
+ @is_live = api.live?
62
+ @broadcast_host = api.broadcast_host
63
+ @broadcast_key = api.broadcast_key
64
+ start_websocket
65
+ @reconnection = false
66
+ end
67
+ task.sleep 10
68
+ end
69
+ end
70
+ end
71
+ rescue => e
72
+ add_error(e)
73
+ SleepRoom.error(e.full_message)
74
+ log("Retry...")
75
+ task.sleep 5
76
+ retry
77
+ end
78
+ end
79
+
80
+ def log(str)
81
+ SleepRoom.info("[#{@room}] #{str}")
82
+ end
83
+
84
+ private
85
+ def start_websocket()
86
+ Async do |task|
87
+ @running = true
88
+ log("Broadcast Key: #{@broadcast_key}")
89
+ waiting_live(ws: :init)
90
+ ws = WebSocket.new(room: @room, broadcast_key: @broadcast_key, url: @broadcast_host)
91
+ @ws = ws
92
+ # ws status
93
+ ws_task = task.async do |sub|
94
+ ws.running = true
95
+ ws.connect(task: sub) do |message|
96
+ case message["t"].to_i
97
+ when 101
98
+ log("Live stop.")
99
+ ws.running = false
100
+ @running = false
101
+ record
102
+ when 104
103
+ log("Live start.")
104
+ start_time = Time.now
105
+ streaming_url = parse_streaming_url
106
+ output = build_output
107
+ pid = SleepRoom::Record.call_minyami(url: streaming_url, output: output)
108
+ downloading(streaming_url, pid, start_time)
109
+ ws.running = false
110
+ @running = false
111
+ @reconnection = true
112
+ else
113
+ # other
114
+ end
115
+ end
116
+ rescue => e
117
+ SleepRoom.error("WS Stop.")
118
+ SleepRoom.error(e.full_message)
119
+ ws.running = false
120
+ @running = false
121
+ add_error(e)
122
+ end
123
+
124
+ Async do |task|
125
+ while @running && @downlaoding == false
126
+ status = ws.status
127
+ if !status[:last_ack].nil? && Time.now.to_i - status[:last_ack].to_i > 65
128
+ ws.running = false
129
+ @running = false
130
+ task.stop
131
+ end
132
+ waiting_live(status)
133
+ task.sleep 30
134
+ end
135
+ end
136
+ task.children.each(&:wait)
137
+ ensure
138
+ ws.running = false
139
+ @running = false
140
+ end
141
+ end
142
+
143
+ def parse_streaming_url(task: Async::Task.current)
144
+ api = API::StreamingAPI.new(@room_id)
145
+ streaming_url_list = api.streaming_url
146
+ end
147
+
148
+ def build_output(task: Async::Task.current)
149
+ room = @room
150
+ group = @group
151
+ tmp_str = configatron.default_save_name
152
+ tmp_str = tmp_str.sub("\%TIME\%", Time.now.strftime("%Y-%m-%d-%H-%M-%S")) if tmp_str.include?("\%TIME\%")
153
+ tmp_str = tmp_str.sub("\%ROOMNAME\%", room) if tmp_str.include?("\%ROOMNAME\%")
154
+ File.join(group, room, "showroom", tmp_str)
155
+ end
156
+
157
+ def downloading(streaming_url, pid, start_time, task: Async::Task.current)
158
+ @downlaoding = true
159
+ @queue.add({
160
+ room: @room,
161
+ start_time: start_time,
162
+ name: @room_name,
163
+ group: @group,
164
+ live: true,
165
+ status: :downloading,
166
+ streaming_url: streaming_url,
167
+ download_pid: pid
168
+ })
169
+ task.async do |t|
170
+ while true
171
+ if !SleepRoom.running?(pid) && !API::RoomAPI.new(@room).live?
172
+ log("Download complete.")
173
+ @downlaoding = false
174
+ @queue.add({
175
+ room: @room,
176
+ name: @room_name,
177
+ group: @group,
178
+ live: API::RoomAPI.new(@room).live?,
179
+ status: :complete,
180
+ })
181
+ break
182
+ end
183
+ t.sleep 60
184
+ end
185
+ end.wait
186
+ end
187
+
188
+ def add_error(error)
189
+ @queue.add({
190
+ room: @room,
191
+ name: @room_name,
192
+ group: @group,
193
+ status: :retry,
194
+ error: error.message
195
+ })
196
+ end
197
+
198
+ def waiting_live(status)
199
+ @queue.add({
200
+ room: @room,
201
+ live: false,
202
+ group: @group,
203
+ name: @room_name,
204
+ status: :waiting,
205
+ ws: status
206
+ })
207
+ end
208
+ end
209
+ end
210
+ end
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+ require "terminal-table"
3
+
4
+ module SleepRoom
5
+ module Record
6
+ class Tasks
7
+ # @return [void]
8
+ def self.start
9
+ Async do |_task|
10
+ count = 0
11
+ write_status = WriteStatus.new
12
+ SleepRoom.reload_config
13
+ if SleepRoom.running?
14
+ SleepRoom.error("PID #{SleepRoom.load_pid} Process is already running.")
15
+ exit
16
+ else
17
+ SleepRoom.write_config_file(:status, [])
18
+ end
19
+ SleepRoom.create_pid(Process.pid)
20
+ lists = SleepRoom.load_config(:record)
21
+ lists.each do |group, list|
22
+ SleepRoom.info("Empty list.") if list.empty?
23
+ list.each do |room|
24
+ record = SleepRoom::Record::Showroom.new(room: room["room"], group: group, queue: write_status)
25
+ record.record
26
+ count += 1
27
+ end
28
+ rescue
29
+ SleepRoom.error("Cannot parse Recording list.")
30
+ end
31
+ write_status.run
32
+ SleepRoom.info("共启动 #{count} 个任务.")
33
+ wait
34
+ rescue => e
35
+ puts e.full_message
36
+ end
37
+ rescue Exception
38
+ SleepRoom.create_pid(nil) unless SleepRoom.running?
39
+ puts "Exit..."
40
+ end
41
+
42
+ # @return [void]
43
+ def self.stop
44
+ SleepRoom.reload_config
45
+ raise "未实现"
46
+ end
47
+
48
+ # @return [void]
49
+ def self.status
50
+ Async do
51
+ SleepRoom.reload_config
52
+ status = SleepRoom.load_status
53
+ pid = SleepRoom.load_config(:pid)
54
+ if !SleepRoom.running?(pid) || status.empty? || pid.nil?
55
+ lists = SleepRoom.load_config(:record)
56
+ SleepRoom.info("No tasks running.")
57
+ lists.each do |group, list|
58
+ next if list.empty?
59
+ rows = []
60
+ title = group
61
+ headings = list[0].keys
62
+ list.each do |hash|
63
+ rows.push(hash.values)
64
+ end
65
+ puts Terminal::Table.new(title: "[Recording list] Group: #{title}",:rows => rows, headings: headings)
66
+ end
67
+ else
68
+ rows = []
69
+ headings = status[0].keys
70
+ status.each do |hash|
71
+ rows.push(
72
+ hash.values.map do |s|
73
+ if s.is_a?(Hash)
74
+ "#{(s[:last_ack].is_a?(Time) ? "[ACK]" + s[:last_ack].strftime("%H:%M:%S").to_s : "nil")}"
75
+ elsif s.is_a?(Time)
76
+ s.strftime("%H:%M:%S")
77
+ else
78
+ s.to_s
79
+ end
80
+ end
81
+ )
82
+ end
83
+ puts Terminal::Table.new(title: "Status [PID #{pid}] (#{status.count})",:rows => rows, headings: headings)
84
+ end
85
+ end
86
+ end
87
+
88
+ def self.add(room, group)
89
+ Async do
90
+ group = "default" if group.empty?
91
+ old_record = SleepRoom.load_config(:record)
92
+ name = API::RoomAPI.new(room).room_name
93
+ input_record = {"room" => room, "name" => name}
94
+ if !old_record[group].nil? && new_record = old_record[group].find{|h| h = input_record if h["room"] == room}
95
+ SleepRoom.error("Room #{room} already exists.")
96
+ else
97
+ old_record[group] = [] if old_record[group].nil?
98
+ old_record[group].push(input_record)
99
+ new_record = old_record
100
+ SleepRoom.write_config_file(:record, new_record)
101
+ SleepRoom.info("Added success.")
102
+ end
103
+ end
104
+ end
105
+
106
+ def self.remove(room)
107
+ old_record = SleepRoom.load_config(:record)
108
+ new_record = old_record.each {|k, v| v.delete_if { |h| h["room"] == room }}
109
+ SleepRoom.write_config_file(:record, new_record)
110
+ SleepRoom.info("Remove success.")
111
+ end
112
+
113
+ private
114
+ def self.wait
115
+ Async do |task|
116
+ while true
117
+ task.sleep 1
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,91 @@
1
+ require "sleeproom/async/websocket"
2
+ require "async/http/endpoint"
3
+ require "async/websocket/client"
4
+ require "json"
5
+
6
+ module SleepRoom
7
+ module Record
8
+ class WebSocket
9
+ def initialize(room:, broadcast_key:, url:)
10
+ @room = room
11
+ @url = "wss://" + url
12
+ @broadcast_key = broadcast_key
13
+ @running = false
14
+ @status = {}
15
+ end
16
+
17
+ def connect(task: Async::Task.current)
18
+ url = @url
19
+ endpoint = Async::HTTP::Endpoint.parse(url)
20
+ Async::WebSocket::Client.connect(endpoint, handler: WebSocketConnection) do |connection|
21
+ @connection = connection
22
+ @running = true
23
+ connection.write("SUB\t#{@broadcast_key}")
24
+ connection.flush
25
+ log("Connect to websocket server.")
26
+ @status[:last_update] = Time.now
27
+
28
+ ping_task = task.async do |sub|
29
+ while @running
30
+ sub.sleep 60
31
+ @status[:last_ping] = Time.now
32
+ connection.write("PING\tshowroom")
33
+ connection.flush
34
+ end
35
+ end
36
+
37
+ status_task = task.async do |sub|
38
+ while true
39
+ sub.sleep 1
40
+ if @running == false
41
+ connection.close
42
+ end
43
+ end
44
+ end
45
+
46
+ while message = connection.read
47
+ @status[:last_update]
48
+ if message == "ACK\tshowroom"
49
+ @status[:last_ack] = Time.now if message == "ACK\tshowroom"
50
+ end
51
+ if message.start_with?("MSG")
52
+ @status[:last_msg] = Time.now
53
+ begin
54
+ yield JSON.parse(message.split("\t")[2])
55
+ rescue => e
56
+ SleepRoom.error(e.message)
57
+ end
58
+ end
59
+ end
60
+ rescue => e
61
+ SleepRoom.error(e.message)
62
+ ensure
63
+ ping_task&.stop
64
+ connection.close
65
+ log("WebSocket closed.")
66
+ end
67
+ end
68
+
69
+ def running?
70
+ @running
71
+ end
72
+
73
+ def running=(bool)
74
+ @running = bool
75
+ end
76
+
77
+ def stop
78
+ @running = false
79
+ @connection.close
80
+ end
81
+
82
+ def status
83
+ @status
84
+ end
85
+
86
+ def log(str)
87
+ SleepRoom.info("[#{@room}] #{str}")
88
+ end
89
+ end
90
+ end
91
+ end