/// <summary> /// /// </summary> /// <returns></returns> public async Task <DisconnectReason> ReceiveAsync(string vid, BrowserType browserType) { _disconnectReason = DisconnectReason.Unknown; string liveChatHtml = await GetLiveChatHtml(vid); string ytInitialData = ExtractYtInitialData(liveChatHtml); if (string.IsNullOrEmpty(ytInitialData)) { //これが無いとコメントが取れないから終了 //SendInfo("ytInitialDataの取得に失敗しました", InfoType.Error); return(DisconnectReason.YtInitialDataNotFound); } IContinuation initialContinuation; List <CommentData> initialCommentData; try { (initialContinuation, initialCommentData) = Tools.ParseYtInitialData(ytInitialData); } catch (ContinuationNotExistsException) { //放送終了 return(DisconnectReason.Finished); } catch (ChatUnavailableException) { //SendInfo("この配信ではチャットが無効になっているようです", InfoType.Error); return(DisconnectReason.ChatUnavailable); } catch (Exception ex) { _logger.LogException(ex, "未知の例外", $"ytInitialData={ytInitialData},vid={vid}"); return(DisconnectReason.Unknown); } Connected?.Invoke(this, EventArgs.Empty); //直近の過去コメントを送る。 foreach (var data in initialCommentData) { if (_receivedCommentIds.Contains(data.Id)) { continue; } else { _receivedCommentIds.Add(data.Id); } var messageContext = CreateMessageContext(data, true); MessageReceived?.Invoke(this, messageContext); } //コメント投稿に必要なものの準備 PrepareForPostingComments(liveChatHtml, ytInitialData); var tasks = new List <Task>(); Task activeCounterTask = null; IActiveCounter <string> activeCounter = null; if (_options.IsActiveCountEnabled) { activeCounter = new ActiveCounter <string>() { CountIntervalSec = _options.ActiveCountIntervalSec, MeasureSpanMin = _options.ActiveMeasureSpanMin, }; activeCounter.Updated += (s, e) => { MetadataUpdated?.Invoke(this, new Metadata { Active = e.ToString() }); }; activeCounterTask = activeCounter.Start(); tasks.Add(activeCounterTask); } IMetadataProvider metaProvider = null; var metaTask = CreateMetadataReceivingTask(ref metaProvider, browserType, vid, liveChatHtml); if (metaTask != null) { tasks.Add(metaTask); } _chatProvider = new ChatProvider(_server, _logger); _chatProvider.ActionsReceived += (s, e) => { foreach (var action in e) { if (_receivedCommentIds.Contains(action.Id)) { continue; } else { activeCounter?.Add(action.Id); _receivedCommentIds.Add(action.Id); } var messageContext = CreateMessageContext(action, false); MessageReceived?.Invoke(this, messageContext); } }; _chatProvider.InfoReceived += (s, e) => { SendInfo(e.Comment, e.Type); }; var chatTask = _chatProvider.ReceiveAsync(vid, initialContinuation, _cc); tasks.Add(chatTask); _disconnectReason = DisconnectReason.Finished; while (tasks.Count > 0) { var t = await Task.WhenAny(tasks); if (t == metaTask) { try { await metaTask; } catch (Exception ex) { _logger.LogException(ex, "metaTaskが終了した原因"); } //metaTask内でParseExceptionもしくはDisconnect() //metaTaskは終わっても良い。 tasks.Remove(metaTask); metaProvider = null; } else if (t == activeCounterTask) { try { await activeCounterTask; } catch (Exception ex) { _logger.LogException(ex, "activeCounterTaskが終了した原因"); } tasks.Remove(activeCounterTask); activeCounter = null; } else //chatTask { tasks.Remove(chatTask); try { await chatTask; } catch (ReloadException ex) { _logger.LogException(ex, "", $"vid={vid}"); _disconnectReason = DisconnectReason.Reload; } catch (Exception ex) { _logger.LogException(ex); _disconnectReason = DisconnectReason.Unknown; } _chatProvider = null; //chatTaskが終わったらmetaTaskも終了させる metaProvider.Disconnect(); try { await metaTask; } catch (Exception ex) { _logger.LogException(ex, "metaTaskが終了した原因"); } tasks.Remove(metaTask); metaProvider = null; activeCounter?.Stop(); try { await activeCounterTask; } catch (Exception ex) { _logger.LogException(ex, "activeCounterTaskが終了した原因"); } tasks.Remove(activeCounterTask); activeCounter = null; } } return(_disconnectReason); }
protected void RaiseMetadataUpdated(IMetadata metadata) { MetadataUpdated?.Invoke(this, metadata); }
private void Apply(MetadataUpdated e) { Metadata = e.Metadata; UpdatedBy = e.UserId; UpdatedDateTime = e.TimeStamp; }
//protected virtual Extract() private async Task ConnectInternalAsync(string input, IBrowserProfile browserProfile) { if (_ws != null) { throw new InvalidOperationException(""); } var cookies = GetCookies(browserProfile); _cc = CreateCookieContainer(cookies); _context = Tools.GetContext(cookies); string liveId; try { liveId = await GetLiveId(input); _liveId = liveId; } catch (InvalidInputException ex) { _logger.LogException(ex, "無効な入力値", $"input={input}"); SendSystemInfo("無効な入力値です", InfoType.Error); AfterDisconnected(); return; } var movieContext2 = await GetMovieInfo(liveId); var movieId = movieContext2.MovieId; if (movieId == 0) { SendSystemInfo("存在しないURLまたはIDです", InfoType.Error); AfterDisconnected(); return; } if (movieContext2.OnairStatus == 2) { SendSystemInfo("この放送は終了しています", InfoType.Error); AfterDisconnected(); return; } MetadataUpdated?.Invoke(this, new Metadata { Title = movieContext2.Title }); _startAt = movieContext2.StartedAt.DateTime; _500msTimer.Enabled = true; var(chats, raw) = await GetChats(movieContext2); try { foreach (var item in chats) { var comment = Tools.Parse(item); var commentData = Tools.CreateCommentData(comment, _startAt, _siteOptions); var messageContext = CreateMessageContext(comment, commentData, true); if (messageContext != null) { MessageReceived?.Invoke(this, messageContext); } } } catch (Exception ex) { _logger.LogException(ex, "", "raw=" + raw); } foreach (var user in _userStoreManager.GetAllUsers(SiteType.Openrec)) { if (!(user is IUser2 user2)) { continue; } _userDict.AddOrUpdate(user2.UserId, user2, (id, u) => u); } Reconnect: _ws = CreateOpenrecWebsocket(); _ws.Received += WebSocket_Received; var userAgent = GetUserAgent(browserProfile.Type); var wsTask = _ws.ReceiveAsync(movieId.ToString(), userAgent, cookies); var blackListProvider = CreateBlacklistProvider(); blackListProvider.Received += BlackListProvider_Received; var blackTask = blackListProvider.ReceiveAsync(movieId.ToString(), _context); var tasks = new List <Task> { wsTask, blackTask }; while (tasks.Count > 0) { var t = await Task.WhenAny(tasks); if (t == blackTask) { try { await blackTask; } catch (Exception ex) { _logger.LogException(ex); } tasks.Remove(blackTask); } else { blackListProvider.Disconnect(); try { await blackTask; } catch (Exception ex) { _logger.LogException(ex); } tasks.Remove(blackTask); SendSystemInfo("ブラックリストタスク終了", InfoType.Debug); try { await wsTask; } catch (Exception ex) { _logger.LogException(ex); } tasks.Remove(wsTask); SendSystemInfo("wsタスク終了", InfoType.Debug); } } _ws.Received -= WebSocket_Received; blackListProvider.Received -= BlackListProvider_Received; //意図的な切断では無い場合、配信がまだ続いているか確認して、配信中だったら再接続する。 //2019/03/12 heartbeatを送っているのにも関わらずwebsocketが切断されてしまう場合を確認。ブラウザでも配信中に切断されて再接続するのを確認済み。 if (!_isExpectedDisconnect) { var movieInfo = await GetMovieInfo(liveId); if (movieInfo.OnairStatus == 1) { goto Reconnect; } } }
private void MessageProvider_MetaReceived(object sender, IMetadata e) { MetadataUpdated?.Invoke(this, e); }
internal void OnMetadataUpdated(EventArgs e) { MetadataUpdated.RaiseEvent(this, e); }
private async Task ConnectInternalAsync(string input, IBrowserProfile browserProfile) { if (string.IsNullOrEmpty(input)) { SendInfo("URLを入力してください", InfoType.Error); return; } MetadataUpdated?.Invoke(this, new Metadata { Active = "-", CurrentViewers = "-", Elapsed = "-", Title = "-", TotalViewers = "-", }); _cc = CreateCookieContainer(browserProfile); var bigoId = GetBigoId(input); _webSocketLink = await Api.GetWebSocketLink(_server, _cc); _internalStudioInfo = await Api.GetInternalStudioInfo(bigoId, _server, _cc); var gifts = await Api.GetOnlineGift(DateTime.Now, _server, _cc); _giftDict = gifts.ToDictionary(kv => kv.Typeid); var title = await GetTitleAsync(bigoId, _cc); if (!string.IsNullOrEmpty(title)) { MetadataUpdated?.Invoke(this, new Metadata { Title = title, }); } var websocketUrl = "wss://wss.bigolive.tv/bigo/web"; messageProvider = new MessageProvider(new Websocket { EnableAutoSendPing = true, AutoSendPingInterval = 1000, NoDelay = true, }, new MessageParser()); messageProvider.Opened += MessageProvider_Opened; messageProvider.Received += MessageProvider_Received; try { reload: await messageProvider.ReceiveAsync(websocketUrl); if (!_disconnectedExpected) { Debug.WriteLine("BIGO reload!"); goto reload; } } catch (Exception ex) { } finally { messageProvider.Opened -= MessageProvider_Opened; messageProvider.Received -= MessageProvider_Received; messageProvider = null; } }
public async Task ConnectAsync(string input, IBrowserProfile browserProfile) { BeforeConnect(); var autoReconnectMode = false; var cc = GetCookieContainer(browserProfile); await InitLoveIconUrlDict(); string channelId; string liveId; try { (channelId, liveId) = GetLiveIdFromInput(input); } catch (InvalidUrlException) { SendSystemInfo("入力されたURLが正しくないようです", InfoType.Error); AfterDisconnected(); return; } if (string.IsNullOrEmpty(liveId)) { autoReconnectMode = true; } while (true) { if (autoReconnectMode) { MetadataUpdated?.Invoke(this, new Metadata { Title = "(放送が始まるまで待機しています)", }); try { liveId = await GetLiveIdFromChannelId(_server, channelId); } catch (LiveNotFoundException) { try { SendSystemInfo((LiveCheckIntervalMs / 1000) + "秒待ってから放送しているか確認します", InfoType.Debug); await Task.Delay(LiveCheckIntervalMs, _cts.Token); } catch (TaskCanceledException) { break; } continue; } } var(liveInfo, raw) = await GetLiveInfo(channelId, liveId); if (liveInfo.LiveStatus == "FINISHED") { SendSystemInfo("配信が終了しました", InfoType.Notice); if (autoReconnectMode) { continue; } else { break; } } else { SendSystemInfo("LiveStatus=" + liveInfo.LiveStatus, InfoType.Debug); } MetadataUpdated?.Invoke(this, new Metadata { Title = liveInfo.Title, }); var url = liveInfo.ChatUrl; _provider = CreateMessageProvider(); _provider.Opened += Provider_Opened; _provider.Received += Provider_Received; var messageProviderTask = _provider.ReceiveAsync(url); var tasks = new List <Task>(); tasks.Add(messageProviderTask); var promptyStatsProvider = CreatePromptyStatsProvider(); promptyStatsProvider.Received += PromptyStatsProvider_Received; var promptyStatsTask = promptyStatsProvider.ReceiveAsync(channelId, liveId); tasks.Add(promptyStatsTask); var blacklistProvider = CreateBlackListProvider(); blacklistProvider.Received += BlacklistProvider_Received; var blackListTask = blacklistProvider.ReceiveAsync(Tools.ExtractCookies(cc)); tasks.Add(blackListTask); while (true) { var t = await Task.WhenAny(tasks); if (t == messageProviderTask) { try { await messageProviderTask; } catch (Exception ex) { _logger.LogException(ex); } tasks.Remove(messageProviderTask); try { promptyStatsProvider.Disconnect(); await promptyStatsTask; } catch (Exception ex) { _logger.LogException(ex); } tasks.Remove(promptyStatsTask); break; } else if (t == promptyStatsTask) { try { await promptyStatsTask; } catch (Exception ex) { _logger.LogException(ex); } } else if (t == blackListTask) { try { await blackListTask; } catch (Exception ex) { _logger.LogException(ex); } } } SendSystemInfo("websocketが切断", InfoType.Debug); if (_isUserDisconnected) { break; } else { SendSystemInfo("websocketの切断がユーザによるものではないため放送ステータスを確認", InfoType.Debug); } try { await AfterMessageProviderDisconnected(); } catch (Exception) { break; } } if (autoReconnectMode) { //タイトルが(放送が始まるまで待機しています)となっている可能性を考慮して消す MetadataUpdated?.Invoke(this, new Metadata { Title = "", }); } AfterDisconnected(); }