long long MythProgramInfo::uid() { long long retval=RecStart(); retval<<=32; retval+=ChannelID(); if(retval>0) retval=-retval; return retval; }
vector<CTunerBankCtrl::CHECK_RESULT> CTunerBankCtrl::Check(vector<DWORD>* startedReserveIDList) { vector<CHECK_RESULT> retList; if( this->hTunerProcess && WaitForSingleObject(this->hTunerProcess, 0) != WAIT_TIMEOUT ){ //チューナが予期せず閉じられた CloseTuner(); this->specialState = TR_IDLE; //TR_IDLEでない全予約を葬る for( map<DWORD, TUNER_RESERVE>::const_iterator itr = this->reserveMap.begin(); itr != this->reserveMap.end(); ){ if( itr->second.state != TR_IDLE ){ CHECK_RESULT ret; ret.type = CHECK_ERR_REC; ret.reserveID = itr->first; retList.push_back(ret); this->reserveMap.erase(itr++); }else{ itr++; } } } CWatchBlock watchBlock(&this->watchContext); CSendCtrlCmd ctrlCmd; if( this->hTunerProcess ){ //チューナ起動時にはこれを再度呼ぶこと ctrlCmd.SetPipeSetting(CMD2_VIEW_CTRL_WAIT_CONNECT, CMD2_VIEW_CTRL_PIPE, this->tunerPid); } if( this->specialState == TR_EPGCAP ){ DWORD status; if( ctrlCmd.SendViewGetStatus(&status) == CMD_SUCCESS ){ if( status != VIEW_APP_ST_GET_EPG ){ //取得終わった OutputDebugString(L"epg end\r\n"); CloseTuner(); this->specialState = TR_IDLE; } }else{ //エラー OutputDebugString(L"epg err\r\n"); CloseTuner(); this->specialState = TR_IDLE; } }else if( this->specialState == TR_NWTV ){ //ネットワークモードではGUIキープできないのでBonDriverが変更されるかもしれない //BonDriverが変更されたチューナはこのバンクの管理下に置けないので、ネットワークモードを解除する wstring bonDriver; if( ctrlCmd.SendViewGetBonDrivere(&bonDriver) == CMD_SUCCESS && CompareNoCase(bonDriver, this->bonFileName) != 0 ){ if( ctrlCmd.SendViewSetID(-1) == CMD_SUCCESS ){ CBlockLock lock(&this->watchContext.lock); CloseHandle(this->hTunerProcess); this->hTunerProcess = NULL; this->specialState = TR_IDLE; }else{ //ID剥奪に失敗したので消えてもらうしかない CloseNWTV(); } //TODO: 汎用のログ用メッセージが存在しないので、やむを得ずNOTIFY_UPDATE_REC_ENDで警告する this->notifyManager.AddNotifyMsg(NOTIFY_UPDATE_REC_END, L"BonDriverが変更されたためNetworkモードを解除しました\r\n変更したBonDriverに録画の予定がないか注意してください"); } }else if( this->hTunerProcess && this->tunerChLocked == false ){ //GUIキープされていないのでBonDriverが変更されるかもしれない wstring bonDriver; if( ctrlCmd.SendViewGetBonDrivere(&bonDriver) == CMD_SUCCESS && CompareNoCase(bonDriver, this->bonFileName) != 0 ){ if( ctrlCmd.SendViewSetID(-1) == CMD_SUCCESS ){ CBlockLock lock(&this->watchContext.lock); CloseHandle(this->hTunerProcess); this->hTunerProcess = NULL; }else{ //ID剥奪に失敗したので消えてもらうしかない CloseTuner(); } //TR_IDLEでない全予約を葬る for( map<DWORD, TUNER_RESERVE>::const_iterator itr = this->reserveMap.begin(); itr != this->reserveMap.end(); ){ if( itr->second.state != TR_IDLE ){ CHECK_RESULT ret; ret.type = CHECK_ERR_REC; ret.reserveID = itr->first; retList.push_back(ret); this->reserveMap.erase(itr++); }else{ itr++; } } } } this->delayTime = 0; this->epgCapDelayTime = 0; if( this->hTunerProcess && this->specialState != TR_NWTV ){ //PC時計との誤差取得 int delaySec; if( ctrlCmd.SendViewGetDelay(&delaySec) == CMD_SUCCESS ){ //誤った値を掴んでおかしなことにならないよう、EPG取得中の値は状態遷移の参考にしない if( this->specialState == TR_EPGCAP ){ this->epgCapDelayTime = delaySec * I64_1SEC; }else{ this->delayTime = delaySec * I64_1SEC; } } } __int64 now = GetNowI64Time() + this->delayTime; //終了時間を過ぎた予約を回収し、TR_IDLE->TR_READY以外の遷移をする vector<pair<__int64, DWORD>> idleList; bool ngResetLock = false; for( map<DWORD, TUNER_RESERVE>::iterator itrRes = this->reserveMap.begin(); itrRes != this->reserveMap.end(); ){ TUNER_RESERVE& r = itrRes->second; CHECK_RESULT ret; ret.type = 0; switch( r.state ){ case TR_IDLE: if( r.startTime + r.endMargin + r.durationSecond * I64_1SEC < now ){ ret.type = CHECK_ERR_PASS; } //開始順が秒精度なので、前後関係を確実にするため開始時間は必ず秒精度で扱う else if( (r.startTime - r.startMargin - this->recWakeTime) / I64_1SEC < now / I64_1SEC ){ //録画開始recWakeTime前〜 idleList.push_back(std::make_pair(r.startOrder, r.reserveID)); } break; case TR_READY: if( r.startTime + r.endMargin + r.durationSecond * I64_1SEC < now ){ for( int i = 0; i < 2; i++ ){ if( r.ctrlID[i] != 0 ){ ctrlCmd.SendViewDeleteCtrl(r.ctrlID[i]); } } ret.type = CHECK_ERR_PASS; } //パイプコマンドにはチャンネル変更の完了を調べる仕組みがないので、妥当な時間だけ待つ else if( GetTickCount() - this->tunerChChgTick > 5000 && r.startTime - r.startMargin < now ){ //録画開始〜 if( RecStart(r, now) ){ //途中から開始されたか r.notStartHead = r.startTime - r.startMargin + 60 * I64_1SEC < now; r.appendPgInfo = false; r.savedPgInfo = false; r.state = TR_REC; if( r.recMode == RECMODE_VIEW ){ //視聴予約でない予約が1つでもあれば「視聴モード」にしない map<DWORD, TUNER_RESERVE>::const_iterator itr; for( itr = this->reserveMap.begin(); itr != this->reserveMap.end(); itr++ ){ if( itr->second.state != TR_IDLE && itr->second.recMode != RECMODE_VIEW ){ break; } } if( itr == this->reserveMap.end() ){ //「視聴モード」にするとGUIキープが解除されてしまうためチャンネルを把握することはできない ctrlCmd.SendViewSetStandbyRec(2); this->tunerChLocked = false; if( this->recView ){ ctrlCmd.SendViewExecViewApp(); } } } if( startedReserveIDList ){ startedReserveIDList->push_back(r.reserveID); } }else{ //開始できなかった ret.type = CHECK_ERR_RECSTART; } } break; case TR_REC: { //ステータス確認 DWORD status; if( r.recMode != RECMODE_VIEW && ctrlCmd.SendViewGetStatus(&status) == CMD_SUCCESS && status != VIEW_APP_ST_REC ){ //キャンセルされた? ret.type = CHECK_ERR_REC; this->tunerResetLock = true; }else if( r.startTime + r.endMargin + r.durationSecond * I64_1SEC < now ){ ret.type = CHECK_ERR_REC; ret.continueRec = false; ret.drops = 0; ret.scrambles = 0; bool isMainCtrl = true; for( int i = 0; i < 2; i++ ){ if( r.ctrlID[i] != 0 ){ if( r.recMode == RECMODE_VIEW ){ if( isMainCtrl ){ ret.type = CHECK_END; } }else{ SET_CTRL_REC_STOP_PARAM param; param.ctrlID = r.ctrlID[i]; param.saveErrLog = this->saveErrLog; SET_CTRL_REC_STOP_RES_PARAM resVal; if( ctrlCmd.SendViewStopRec(param, &resVal) != CMD_SUCCESS ){ if( isMainCtrl ){ ret.type = CHECK_ERR_RECEND; } }else if( isMainCtrl ){ ret.type = resVal.subRecFlag ? CHECK_END_END_SUBREC : r.notStartHead ? CHECK_END_NOT_START_HEAD : r.savedPgInfo == false ? CHECK_END_NOT_FIND_PF : CHECK_END; ret.recFilePath = resVal.recFilePath; ret.drops = resVal.drop; ret.scrambles = resVal.scramble; ret.epgStartTime = r.epgStartTime; ret.epgEventName = r.epgEventName; } } ctrlCmd.SendViewDeleteCtrl(r.ctrlID[i]); isMainCtrl = false; } } //録画終了に伴ってGUIキープが解除されたかもしれない this->tunerResetLock = true; }else{ //番組情報確認 if( r.savedPgInfo == false && r.recMode != RECMODE_VIEW ){ GET_EPG_PF_INFO_PARAM val; val.ONID = r.onid; val.TSID = r.tsid; val.SID = r.sid; val.pfNextFlag = FALSE; EPGDB_EVENT_INFO resVal; if( ctrlCmd.SendViewGetEventPF(&val, &resVal) == CMD_SUCCESS && resVal.StartTimeFlag && resVal.DurationFlag && ConvertI64Time(resVal.start_time) <= r.startTime + 30 * I64_1SEC && r.startTime + 30 * I64_1SEC < ConvertI64Time(resVal.start_time) + resVal.durationSec * I64_1SEC && (r.eid == 0xFFFF || r.eid == resVal.event_id) ){ //開始時間から30秒は過ぎているのでこの番組情報が録画中のもののはず r.savedPgInfo = true; r.epgStartTime = resVal.start_time; r.epgEventName = resVal.shortInfo ? resVal.shortInfo->event_name : L""; //ごく稀にAPR(改行)を含むため Replace(r.epgEventName, L"\r\n", L""); if( this->saveProgramInfo ){ for( int i = 0; i < 2; i++ ){ wstring recPath; if( r.ctrlID[i] != 0 && ctrlCmd.SendViewGetRecFilePath(r.ctrlID[i], &recPath) == CMD_SUCCESS ){ SaveProgramInfo(recPath.c_str(), resVal, r.appendPgInfo); } } } } } //まだ録画中の予約があるのでGUIキープを再設定してはいけない ngResetLock = true; } } break; } if( ret.type != 0 ){ ret.reserveID = itrRes->first; retList.push_back(ret); this->reserveMap.erase(itrRes++); }else{ itrRes++; } } //TR_IDLE->TR_READYの遷移を待つ予約を開始順に並べる std::sort(idleList.begin(), idleList.end()); //TR_IDLE->TR_READY(TR_REC)の遷移をする for( vector<pair<__int64, DWORD>>::const_iterator itrIdle = idleList.begin(); itrIdle != idleList.end(); itrIdle++ ){ map<DWORD, TUNER_RESERVE>::iterator itrRes = this->reserveMap.find(itrIdle->second); TUNER_RESERVE& r = itrRes->second; CHECK_RESULT ret; ret.type = 0; if( this->hTunerProcess == NULL ){ //チューナを起動する SET_CH_INFO initCh; initCh.ONID = r.onid; initCh.TSID = r.tsid; initCh.SID = r.sid; initCh.useSID = TRUE; initCh.useBonCh = FALSE; bool nwUdpTcp = this->recNW || r.recMode == RECMODE_VIEW; if( OpenTuner(this->recMinWake, nwUdpTcp, nwUdpTcp, true, &initCh) ){ this->tunerONID = r.onid; this->tunerTSID = r.tsid; this->tunerChLocked = true; this->tunerResetLock = false; this->tunerChChgTick = GetTickCount(); this->notifyManager.AddNotifyMsg(NOTIFY_UPDATE_PRE_REC_START, this->bonFileName); ctrlCmd.SetPipeSetting(CMD2_VIEW_CTRL_WAIT_CONNECT, CMD2_VIEW_CTRL_PIPE, this->tunerPid); r.retryOpenCount = 0; }else if( ++r.retryOpenCount >= 4 || r.retryOpenCount == 2 && CloseOtherTuner() == false ){ //試行2回→他チューナ終了成功時さらに2回→起動できなかった ret.type = CHECK_ERR_OPEN; } }else{ r.retryOpenCount = 0; } if( this->hTunerProcess && (r.startTime - r.startMargin) / I64_1SEC - READY_MARGIN < now / I64_1SEC ){ //録画開始READY_MARGIN秒前〜 //原作では録画制御作成は通常録画時60秒前、割り込み録画時15秒前だが //作成を前倒しする必要は特にないのと、チャンネル変更からEIT[p/f]取得までの時間を確保できるようこの秒数にした if( this->specialState == TR_EPGCAP ){ //EPG取得をキャンセル(遷移中断) OutputDebugString(L"epg cancel\r\n"); //CSendCtrlCmd::SendViewEpgCapStop()は送らない(即座にチューナ閉じるので意味がないため) CloseTuner(); this->specialState = TR_IDLE; break; }else if( this->specialState == TR_NWTV ){ //ネットワークモードを解除 wstring bonDriver; DWORD status; if( ctrlCmd.SendViewGetBonDrivere(&bonDriver) == CMD_SUCCESS && CompareNoCase(bonDriver, this->bonFileName) == 0 && ctrlCmd.SendViewGetStatus(&status) == CMD_SUCCESS && (status == VIEW_APP_ST_NORMAL || status == VIEW_APP_ST_ERR_CH_CHG) ){ //プロセスを引き継ぐ this->tunerONID = r.onid; this->tunerTSID = r.tsid; this->tunerChLocked = false; this->tunerResetLock = false; this->specialState = TR_IDLE; }else{ //ネットワークモード終了(遷移中断) CloseNWTV(); break; } } if( this->tunerONID != r.onid || this->tunerTSID != r.tsid ){ //チャンネル違うので、TR_IDLEでない全予約の優先度を比べる map<DWORD, TUNER_RESERVE>::const_iterator itr; for( itr = this->reserveMap.begin(); itr != this->reserveMap.end(); itr++ ){ if( itr->second.state != TR_IDLE && itr->second.effectivePriority < r.effectivePriority ){ break; } } if( itr == this->reserveMap.end() ){ //TR_IDLEでない全予約は自分よりも弱いので葬る for( itr = this->reserveMap.begin(); itr != this->reserveMap.end(); ){ if( itr->second.state != TR_IDLE ){ CHECK_RESULT retOther; retOther.type = CHECK_ERR_REC; retOther.reserveID = itr->first; retOther.continueRec = false; retOther.drops = 0; retOther.scrambles = 0; bool isMainCtrl = true; for( int i = 0; i < 2; i++ ){ if( itr->second.ctrlID[i] != 0 ){ if( itr->second.state == TR_REC ){ if( isMainCtrl ){ retOther.type = CHECK_END_NEXT_START_END; } if( itr->second.recMode != RECMODE_VIEW ){ SET_CTRL_REC_STOP_PARAM param; param.ctrlID = itr->second.ctrlID[i]; param.saveErrLog = this->saveErrLog; SET_CTRL_REC_STOP_RES_PARAM resVal; if( ctrlCmd.SendViewStopRec(param, &resVal) != CMD_SUCCESS ){ if( isMainCtrl ){ retOther.type = CHECK_ERR_RECEND; } }else if( isMainCtrl ){ retOther.recFilePath = resVal.recFilePath; retOther.drops = resVal.drop; retOther.scrambles = resVal.scramble; } } } ctrlCmd.SendViewDeleteCtrl(itr->second.ctrlID[i]); isMainCtrl = false; } } retList.push_back(retOther); this->reserveMap.erase(itr++); }else{ itr++; } } this->tunerONID = r.onid; this->tunerTSID = r.tsid; this->tunerChLocked = false; this->tunerResetLock = false; } } if( this->tunerONID == r.onid && this->tunerTSID == r.tsid ){ if( this->tunerChLocked == false ){ //チャンネル変更 SET_CH_INFO chgCh; chgCh.ONID = r.onid; chgCh.TSID = r.tsid; chgCh.SID = r.sid; chgCh.useSID = TRUE; chgCh.useBonCh = FALSE; //「予約録画待機中」 ctrlCmd.SendViewSetStandbyRec(1); if( ctrlCmd.SendViewSetCh(&chgCh) == CMD_SUCCESS ){ this->tunerChLocked = true; this->tunerResetLock = false; this->tunerChChgTick = GetTickCount(); } } if( this->tunerChLocked ){ //同一チャンネルなので録画制御を作成できる bool continueRec = false; for( map<DWORD, TUNER_RESERVE>::const_iterator itr = this->reserveMap.begin(); itr != this->reserveMap.end(); itr++ ){ if( itr->second.continueRecFlag && itr->second.state == TR_REC && itr->second.sid == r.sid && itr->second.recMode != RECMODE_VIEW && itr->second.recMode == r.recMode && itr->second.enableCaption == r.enableCaption && itr->second.enableData == r.enableData && itr->second.partialRecMode == r.partialRecMode ){ //連続録画なので、同一制御IDで録画開始されたことにする。TR_RECまで遷移するので注意 r.state = TR_REC; r.ctrlID[0] = itr->second.ctrlID[0]; r.ctrlID[1] = itr->second.ctrlID[1]; r.notStartHead = r.startTime - r.startMargin + 60 * I64_1SEC < now; r.appendPgInfo = itr->second.appendPgInfo || itr->second.savedPgInfo; r.savedPgInfo = false; //引継ぎ元を葬る CHECK_RESULT retOther; retOther.type = CHECK_ERR_REC; retOther.reserveID = itr->first; retOther.continueRec = true; retOther.drops = 0; retOther.scrambles = 0; for( int i = 0; i < 2; i++ ){ if( itr->second.ctrlID[i] != 0 ){ if( ctrlCmd.SendViewGetRecFilePath(itr->second.ctrlID[i], &retOther.recFilePath) == CMD_SUCCESS ){ retOther.type = itr->second.notStartHead ? CHECK_END_NOT_START_HEAD : itr->second.savedPgInfo == false ? CHECK_END_NOT_FIND_PF : CHECK_END; retOther.epgStartTime = itr->second.epgStartTime; retOther.epgEventName = itr->second.epgEventName; } break; } } retList.push_back(retOther); this->reserveMap.erase(itr); continueRec = true; break; } } if( continueRec == false ){ if( CreateCtrl(&r.ctrlID[0], &r.ctrlID[1], r) ){ r.state = TR_READY; }else{ //作成できなかった ret.type = CHECK_ERR_CTRL; } } } } } if( ret.type != 0 ){ ret.reserveID = itrRes->first; retList.push_back(ret); this->reserveMap.erase(itrRes); } } if( IsNeedOpenTuner() == false ){ //チューナが必要なくなった CloseTuner(); } if( this->hTunerProcess && this->specialState == TR_IDLE && this->tunerResetLock ){ if( ngResetLock == false ){ //「予約録画待機中」 ctrlCmd.SendViewSetStandbyRec(1); } this->tunerResetLock = false; } return retList; }