Please try the following...
SET @workingScheduleTime := ( SELECT DATE_ADD( '2017-05-06 22:00:59', INTERVAL -20 MINUTE ) );
UPDATE TableName
JOIN ( SELECT product_id,
              CASE
                  WHEN TIME( DATE_ADD( @workingScheduleTime, INTERVAL 20 MINUTE ) ) NOT BETWEEN '05:00:01' and '22:29:59' THEN
                      @workingScheduleTime := DATE_ADD( TIMESTAMP( DATE( @workingScheduleTime ), '05:00:01' ), INTERVAL 1 DAY )
                  ELSE
                      @workingScheduleTime := DATE_ADD( @workingScheduleTime, INTERVAL 20 MINUTE )
              END AS ScheduleTime
       FROM TableName
     ) AS redatedList ON TableName.product_id = redatedList.product_id
SET TableName.ScheduleTime = redatedList.ScheduleTime;
This solution by works by looping through each record and adding 20 minutes to the value given to the previous record's ScheduleTime field.  It also allows the initial value of ScheduleTime to be specified.  Thus we will need to start with a working value of ScheduleTime that is 20 minutes before our initial value so that the first iteration of the loop will return our initial value.  Thus my solution starts with the statement...
SET @workingScheduleTime := ( SELECT DATE_ADD( '2017-05-06 22:00:59', INTERVAL -20 MINUTE ) );
With our working value thus initialised I have implemented an UPDATE of the table TableName using a SELECT statement based upon Eric's  solution found at MySQL - UPDATE query based on SELECT Query.  The statement follows...
UPDATE TableName
JOIN ( SELECT product_id,
              CASE
                  WHEN TIME( DATE_ADD( @workingScheduleTime, INTERVAL 20 MINUTE ) ) NOT BETWEEN '05:00:01' and '22:29:59' THEN
                      @workingScheduleTime := DATE_ADD( TIMESTAMP( DATE( @workingScheduleTime ), '05:00:01' ), INTERVAL 1 DAY )
                  ELSE
                      @workingScheduleTime := DATE_ADD( @workingScheduleTime, INTERVAL 20 MINUTE )
              END AS ScheduleTime
       FROM TableName
     ) AS redatedList ON TableName.product_id = redatedList.product_id
SET TableName.ScheduleTime = redatedList.ScheduleTime;
This statement starts by performing an INNER JOIN between one instance of TableName to the results of the following subquery based on their shared value of product_id...
SELECT product_id,
       CASE
           WHEN TIME( DATE_ADD( @workingScheduleTime, INTERVAL 20 MINUTE ) ) NOT BETWEEN '05:00:01' and '22:29:59' THEN
               @workingScheduleTime := DATE_ADD( TIMESTAMP( DATE( @workingScheduleTime ), '05:00:01' ), INTERVAL 1 DAY )
           ELSE
               @workingScheduleTime := DATE_ADD( @workingScheduleTime, INTERVAL 20 MINUTE )
           END AS ScheduleTime
FROM TableName
This subquery selects the value of product_id for each record in TableName and then checks if the next value of the working value will occur within a timeframe prohibited by the Questioner as being too late / too early.  If it does then the next acceptable time (05:00:01 on the next day) is chosen by the CASE statement.  If the nominee value falls within the acceptable timeframe then it is chosen by the CASE statement.  The working value is updated 
The value chosen by the CASE statement is then chosen as the subquery's new value of ScheduleTime.
As mentioned above the results of the subquery are then joined to TableName in such way that each existing record of TableName effectively has it's new value appended to it.  The SET statement uses this newly established relationship to change each existing value of ScheduleTime to its corresponding new value.
This answer was tested against a dataset created using the CREATE statement from the Question and populated using the following script...
INSERT INTO TableName ( product_id,
                        ScheduleTime )
VALUES ( '01', '2017-05-07 22:00:59' ),
       ( '02', '2017-05-07 09:09:59' ),
       ( '03', '2017-05-07 09:59:59' ),
       ( '04', '2017-05-07 09:49:59' ),
       ( '05', '2017-05-07 23:09:59' ),
       ( '06', '2017-05-07 23:09:59' ),
       ( '07', '2017-05-07 23:09:59' ),
       ( '08', '2017-05-07 23:09:59' ),
       ( '09', '2017-05-07 23:09:59' ),
       ( '10', '2017-05-07 23:09:59' ),
       ( '11', '2017-05-07 23:09:59' ),
       ( '12', '2017-05-07 23:09:59' ),
       ( '13', '2017-05-07 23:09:59' ),
       ( '14', '2017-05-07 23:09:59' ),
       ( '15', '2017-05-07 23:09:59' ),
       ( '16', '2017-05-07 23:09:59' ),
       ( '17', '2017-05-07 23:09:59' ),
       ( '18', '2017-05-07 23:09:59' ),
       ( '19', '2017-05-07 23:09:59' ),
       ( '20', '2017-05-07 23:09:59' );
The results of my code have been confirmed as appropriate by the Questioner.
Further Reading
https://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html#operator_between (on MySQL's BETWEEN and NOT BETWEEN operators)
https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_date (on MySQL's DATE() function)
https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html#function_date-add (on MySQL's DATE_ADD() function)
https://dev.mysql.com/doc/refman/5.7/en/set-statement.html (on MySQL's SET statement)
https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_time (on MySQL's TIME() function)
https://dev.mysql.com/doc/refman/5.7/en/date-and-time-functions.html#function_timestamp (on MySQL's TIMESTAMP() function)
https://dev.mysql.com/doc/refman/5.7/en/update.html (on MySQL's UPDATE statement)