示例#1
0
 private void CheckForDup(VideoFileInfo fileInfo)
 {
     //Make sure file doesn't already exist
     if (File.Exists(fileInfo.GetFullFile()))
     {
         VideoFileInfo newFileInfo = CloneFileInfo(fileInfo);
         newFileInfo.RandomizeFileName();
         File.Move(fileInfo.GetFullFile(), newFileInfo.GetFullFile());
         fileInfo = newFileInfo;
     }
 }
示例#2
0
        public void SetConcatFile(string _baseFilePath)
        {
            concatFile = new VideoFileInfo
            {
                baseFileName = fileName + "_concat",
                exten        = ".ts",
                baseFilePath = _baseFilePath
            };

            CheckForDup(concatFile);
        }
示例#3
0
 public void SetMuxedFile(string _baseFilePath)
 {
     //Take care of video file
     muxedFile = new VideoFileInfo
     {
         baseFileName = fileName,
         exten        = ".mp4",
         baseFilePath = _baseFilePath
     };
     CheckForDup(muxedFile);
 }
示例#4
0
        public VideoFileInfo AddCaptureFile(string _baseFilePath)
        {
            VideoFileInfo fileInfo = new VideoFileInfo
            {
                baseFileName = fileName,
                exten        = ".ts",
                fileNumber   = numberOfFiles,
                baseFilePath = _baseFilePath
            };

            //Increment file count
            numberOfFiles++;

            //Make sure file doesn't already exist
            CheckForDup(fileInfo);

            //Add to list
            fileCaptureList.Add(fileInfo);

            return(fileInfo);
        }
示例#5
0
        //A key member function (along with BuildRecordSchedule) which does the bulk of the work
        //
        //I'll be a bit more verbose then usual so I can remember later what I was thinking
        private void CaptureStream(TextWriter logWriter, string hashValue, ChannelHistory channelHistory, RecordInfo recordInfo, VideoFileManager videoFileManager)
        {
            //Process manager for ffmpeg
            //
            //This is there ffmpeg is called, and a watchdog timer created to make sure things are going ok
            ProcessManager processManager = new ProcessManager(configuration);

            //Test internet connection and get a baseline
            //Right now, the baseline is not used for anything other than a number in the logs.
            //This could be taken out...
            long internetSpeed = TestInternet(logWriter);

            //Create servers object
            //This is the list of live247 servers we'll use to cycle through, finding the best quality stream
            Servers servers = new Servers(configuration["ServerList"]);

            //Create the server/channel selector object
            //
            //This object is what uses the heurstics to determine the right server/channel combo to use.
            //Factors like language, quality, and rate factor in.
            ServerChannelSelector scs = new ServerChannelSelector(logWriter, channelHistory, servers, recordInfo);

            //Marking time we started and when we should be done
            DateTime captureStarted   = DateTime.Now;
            DateTime captureTargetEnd = recordInfo.GetStartDT().AddMinutes(recordInfo.GetDuration());

            if (!string.IsNullOrEmpty(configuration["debug"]))
            {
                captureTargetEnd = DateTime.Now.AddMinutes(1);
            }
            DateTime lastStartedTime = captureStarted;
            TimeSpan duration        = (captureTargetEnd.AddMinutes(1)) - captureStarted; //the 1 minute takes care of alignment slop

            //Update capture history
            //This saves to channelhistory.json file and is used to help determine the initial order of server/channel combos
            channelHistory.GetChannelHistoryInfo(scs.GetChannelNumber()).recordingsAttempted += 1;
            channelHistory.GetChannelHistoryInfo(scs.GetChannelNumber()).lastAttempt          = DateTime.Now;

            //Build output file - the resulting capture file.
            //See VideoFileManager for specifics around file management (e.g. there can be multiple if errors are encountered etc)
            VideoFileInfo videoFileInfo = videoFileManager.AddCaptureFile(configuration["outputPath"]);

            //Email that show started
            if (recordInfo.emailFlag)
            {
                new Mailer().SendShowStartedMail(configuration, recordInfo);
            }

            //Build ffmpeg capture command line with first channel and get things rolling
            string cmdLineArgs = BuildCaptureCmdLineArgs(scs.GetServerName(), scs.GetChannelNumber(), hashValue, videoFileInfo.GetFullFile());

            logWriter.WriteLine($"=========================================");
            logWriter.WriteLine($"{DateTime.Now}: Starting {captureStarted} on server/channel {scs.GetServerName()}/{scs.GetChannelNumber()}.  Expect to be done by {captureTargetEnd}.");
            logWriter.WriteLine($"                      {configuration["ffmpegPath"]} {cmdLineArgs}");
            CaptureProcessInfo captureProcessInfo = processManager.ExecProcess(logWriter, configuration["ffmpegPath"], cmdLineArgs, (int)duration.TotalMinutes, videoFileInfo.GetFullFile(), recordInfo.cancellationToken, scs.IsBestSelected());

            logWriter.WriteLine($"{DateTime.Now}: Exited Capture.  Exit Code: {captureProcessInfo.process.ExitCode}");

            //Main loop to capture
            //
            //This loop is never entered if the first capture section completes without incident.  However, it is not uncommon
            //for errors to occur, and this loop takes care of determining the right next server/channel combo
            //as well as making sure that we don't just try forever.
            int numRetries = Convert.ToInt32(configuration["numberOfRetries"]);
            int retryNum   = 0;

            for (retryNum = 0; DateTime.Now <= captureTargetEnd && retryNum < numRetries && !recordInfo.cancelledFlag; retryNum++)
            {
                logWriter.WriteLine($"{DateTime.Now}: Capture Failed for server/channel {scs.GetServerName()}/{scs.GetChannelNumber()}. Retry {retryNum+1} of {configuration["numberOfRetries"]}");

                //Let's make sure the interwebs are still there.  If not, let's loop until they come back or the show ends.
                while (!IsInternetOk() && DateTime.Now <= captureTargetEnd)
                {
                    bool logFlag = false;
                    if (!logFlag)
                    {
                        logWriter.WriteLine($"{DateTime.Now}: Interwebs are down.  Checking every minute until back or show ends");
                    }
                    TimeSpan oneMinute = new TimeSpan(0, 1, 0);
                    Thread.Sleep(oneMinute);
                }

                //Check to see if we need to re-authenticate  (most tokens have a lifespan)
                int authMinutes = Convert.ToInt16(configuration["authMinutes"]);
                if (DateTime.Now > captureStarted.AddMinutes(authMinutes))
                {
                    logWriter.WriteLine($"{DateTime.Now}: It's been more than {authMinutes} authMinutes.  Time to re-authenticate");
                    Task <string> authTask = Authenticate();
                    hashValue = authTask.Result;
                    if (string.IsNullOrEmpty(hashValue))
                    {
                        Console.WriteLine($"{DateTime.Now}: ERROR: Unable to authenticate.  Check username and password?");
                        throw new Exception("Unable to authenticate during a retry");
                    }
                }

                //Log avg streaming rate for channel history
                logWriter.WriteLine($"{DateTime.Now}: Avg rate is {captureProcessInfo.avgKBytesSec}KB/s for {scs.GetServerName()}/{scs.GetChannelNumber()}");

                //Set new avg streaming rate for channel history
                channelHistory.SetServerAvgKBytesSec(scs.GetChannelNumber(), scs.GetServerName(), captureProcessInfo.avgKBytesSec);

                //Go to next channel if channel has been alive for less than 15 minutes
                //The idea is that if a server/channel has been stable for at least 15 minutes, no sense trying to find another.  (could make it worse)
                TimeSpan fifteenMin = new TimeSpan(0, 15, 0);
                if ((DateTime.Now - lastStartedTime) < fifteenMin)
                {
                    //Set rate for current server/channel pair
                    scs.SetAvgKBytesSec(captureProcessInfo.avgKBytesSec);

                    //Get correct server and channel (determined by heuristics)
                    if (!scs.IsBestSelected())
                    {
                        scs.GetNextServerChannel();
                        retryNum = -1; //reset retries since we haven't got through the server/channel list yet
                    }
                }
                else
                {
                    retryNum = -1; //reset retries since it's been more than 15 minutes
                }

                //Set new started time and calc new timer
                TimeSpan timeJustRecorded = DateTime.Now - lastStartedTime;
                lastStartedTime = DateTime.Now;
                TimeSpan timeLeft = captureTargetEnd - DateTime.Now;

                //Update channel history
                channelHistory.GetChannelHistoryInfo(scs.GetChannelNumber()).hoursRecorded       += timeJustRecorded.TotalHours;
                channelHistory.GetChannelHistoryInfo(scs.GetChannelNumber()).recordingsAttempted += 1;
                channelHistory.GetChannelHistoryInfo(scs.GetChannelNumber()).lastAttempt          = DateTime.Now;

                //Build output file
                videoFileInfo = videoFileManager.AddCaptureFile(configuration["outputPath"]);

                //Now get capture setup and going again
                cmdLineArgs = BuildCaptureCmdLineArgs(scs.GetServerName(), scs.GetChannelNumber(), hashValue, videoFileInfo.GetFullFile());
                logWriter.WriteLine($"{DateTime.Now}: Starting Capture (again) on server/channel {scs.GetServerName()}/{scs.GetChannelNumber()}");
                logWriter.WriteLine($"                      {configuration["ffmpegPath"]} {cmdLineArgs}");
                captureProcessInfo = processManager.ExecProcess(logWriter, configuration["ffmpegPath"], cmdLineArgs, (int)timeLeft.TotalMinutes + 1, videoFileInfo.GetFullFile(), recordInfo.cancellationToken, scs.IsBestSelected());
            }
            recordInfo.completedFlag = true;
            logWriter.WriteLine($"{DateTime.Now}: Done Capturing Stream.");

            //Update capture history and save
            TimeSpan finalTimeJustRecorded = DateTime.Now - lastStartedTime;

            channelHistory.GetChannelHistoryInfo(scs.GetChannelNumber()).hoursRecorded += finalTimeJustRecorded.TotalHours;
            channelHistory.GetChannelHistoryInfo(scs.GetChannelNumber()).lastSuccess    = DateTime.Now;
            channelHistory.SetServerAvgKBytesSec(scs.GetChannelNumber(), scs.GetServerName(), captureProcessInfo.avgKBytesSec);
            channelHistory.Save();

            //check if actually done and didn't error out early
            //We assume too many retries as that's really the only way out of the loop (outside of an exception which is caught elsewhere)
            if (DateTime.Now < captureTargetEnd)
            {
                if (recordInfo.cancelledFlag)
                {
                    logWriter.WriteLine($"{DateTime.Now}: Cancelled {recordInfo.description}");
                }
                else
                {
                    logWriter.WriteLine($"{DateTime.Now}: ERROR!  Too many retries - {recordInfo.description}");
                }

                //set partial flag
                recordInfo.partialFlag = true;

                //Send alert mail
                string body = recordInfo.description + " partially recorded due to too many retries or cancellation.  Time actually recorded is " + finalTimeJustRecorded.TotalHours;
                new Mailer().SendErrorMail(configuration, "Partial: " + recordInfo.description, body);
            }
        }