internal void step(RecorderHub hub) { if (lastCheckTask != null && !lastCheckTask.IsCompleted) { return; } var recording = hub.getRecording(roomName); if (recording?.isRunning == true) { return; } var now = UnixTime.now; var lastRecordEnd = recording?.timeExit ?? 0L; if (now - lastRecordEnd < 300000L) { // 前回の録画が終わってから5分間は 最も短い間隔でチェックする if (now < lastCheckTime + 5000L) { return; } } else { // 最低限10秒は待つ if (now < lastCheckTime + 10000L) { return; } // 分数が0,5,10, ... に近いなら間隔を短くする var dtNow = now.toDateTime(); var x = Math.Abs((dtNow.Minute * 60 + dtNow.Second) % 300 - 30) / 30; var interval = x switch { 0 => UnixTime.second1 * 10, 1 => UnixTime.second1 * 20, 8 => UnixTime.second1 * 20, _ => UnixTime.second1 * 30, }; if (now < lastCheckTime + interval) { return; } } lastCheckTime = now; lastCheckTask = Task.Run(async() => await check(hub)); }
internal String getFolder(RecorderHub hub) { var dir = rePathDelimiter.Replace($"{hub.saveDir}/{roomName}", "/"); try { Directory.CreateDirectory(dir); } catch (Exception ex) { log.e(ex, "CreateDirectory() failed."); } return(dir); }
public Recording(RecorderHub hub, String roomName, String url, String folder) { this.url = url; this.roomName = roomName; var timeStr = UnixTime.now.formatFileTime(); // todo 保存フォルダの設定 var file = $"{folder}/{timeStr}-{roomName}.ts"; var count = 1; while (File.Exists(file)) { ++count; file = $"{folder}/{timeStr}-{roomName}-{++count}.ts"; } var p = new Process(); p.StartInfo.CreateNoWindow = true; p.StartInfo.FileName = hub.ffmpegPath; p.StartInfo.Arguments = $"{Config.ffmpegOptions} -user_agent \"{Config.userAgent}\" -i \"{url}\" -c copy \"{ file}\""; p.StartInfo.UseShellExecute = false; // require to read output p.StartInfo.RedirectStandardOutput = true; p.StartInfo.RedirectStandardError = true; p.EnableRaisingEvents = true; p.Exited += (sender, args) => { timeExit = UnixTime.now; log.d($"{roomName}: recording process was exited."); hub.mainWindow.play(NotificationSound.recordingEnd); hub.showStatus(); }; p.Start(); Task.Run(async() => await readStream(p.StandardOutput)); Task.Run(async() => await readStream(p.StandardError)); this.process = p; hub.showStatus(); hub.mainWindow.play(NotificationSound.recordingStart); }
async Task check(RecorderHub hub) { try { if (!File.Exists(hub.ffmpegPath)) { log.e($"ffmpegPath '${hub.ffmpegPath}' is not valid."); } var folder = getFolder(hub); if (!Directory.Exists(folder)) { log.e($"folder '${folder}' is not valid."); } // オンライブ部屋一覧にあるストリーミング情報はあまり信用できないので読み直す var url = $"{Config.URL_TOP}api/live/streaming_url?room_id={roomId}&ignore_low_stream=1&_={UnixTime.now / 1000L}"; var request = new HttpRequestMessage(HttpMethod.Get, url); request.Headers.Add("Accept", "application/json"); var response = await Config.httpClient.SendAsync(request).ConfigureAwait(false); var now = UnixTime.now; var content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var root = JToken.Parse(content); var streaming_url_list = root.Value <JArray>("streaming_url_list"); if (streaming_url_list == null) { return; } if (streaming_url_list.Count == 0) { log.d($"{roomName}: there is streaming_url_list, but it's empty."); return; } var list = new List <StreamingInfo>(); foreach (JObject src in streaming_url_list) { try { var si = new StreamingInfo( src.Value <String>("url") !, src.Value <String>("type") !, src.Value <Boolean>("is_default"), src.Value <Int64>("quality") ); if (si.type != "hls") { log.d($"{roomName}: not hls. type={si.type}"); continue; } list.Add(si); } catch (Exception ex) { log.e(ex, $"{roomName}: StreamingInfo parse failed."); } } if (list.Count == 0) { log.d($"{roomName}: StreamingInfo list is empty. original size={streaming_url_list.Count}"); return; } list.Sort(); var streamUrl = list[0].url; var recording = hub.getRecording(roomName); if (recording?.isRunning == true && recording?.url == streamUrl) { log.d($"{roomName}: already recording {streamUrl}"); return; } // streamUrl が決定しても動画が開始しているとは限らない request = new HttpRequestMessage(HttpMethod.Get, streamUrl); response = await Config.httpClient.SendAsync(request).ConfigureAwait(false); var code = response.codeInt(); if (!response.IsSuccessStatusCode) { log.d($"{roomName}: {code} {response.ReasonPhrase} {streamUrl}"); return; } content = await response.Content.ReadAsStringAsync().ConfigureAwait(false); var reLineFeed = new Regex("[\x0d\x0a]+"); String?chunk = null; foreach (var a in reLineFeed.Split(content)) { var line = a.Trim(); if (line.Length == 0 || line.StartsWith("#")) { continue; } log.d(line); if (chunk == null) { chunk = line; } } if (chunk == null) { log.d($"{roomName}: missing chunk in playlsit."); return; } if (!Uri.TryCreate(new Uri(streamUrl), chunk, out var chunkUrl)) { log.d($"{roomName}: can't combile chunk url. {chunk}"); return; } request = new HttpRequestMessage(HttpMethod.Head, chunkUrl); response = await Config.httpClient.SendAsync(request).ConfigureAwait(false); code = response.codeInt(); if (!response.IsSuccessStatusCode) { log.d($"{roomName}: {code} {response.ReasonPhrase} {chunkUrl}"); return; } if (hub.isDisposed) { log.d($"{roomName}: hub was disposed."); return; } log.d($"{roomName}: recording start!"); hub.setRecodring(roomName, streamUrl, folder); } catch (Exception ex) { log.e(ex, $"{roomName}: check failed."); } }