tf-max: fix for max-limit for timeshifting [5/5]

PD#SWPL-25878

Problem:
max-limit fail

Solution:
fixed max-limit

Verify:
Patchbuild

Change-Id: I1cc0e3a31b1c4823c32e814804bcee915e4fa732
Signed-off-by: Zhiqiang Han <zhiqiang.han@amlogic.com>
diff --git a/include/dvr_wrapper.h b/include/dvr_wrapper.h
index 335dfbf..abf091f 100644
--- a/include/dvr_wrapper.h
+++ b/include/dvr_wrapper.h
@@ -51,18 +51,20 @@
 } DVR_WrapperPidsInfo_t;
 
 typedef struct {
-  DVR_PlaybackPlayState_t state;
-  DVR_WrapperInfo_t info_cur;
-  DVR_WrapperInfo_t info_full;
-  DVR_PlaybackPids_t pids;
-  float speed;
-  DVR_PlaybackSegmentFlag_t flags;
+  DVR_PlaybackPlayState_t state;      /**< DVR playback state*/
+  DVR_WrapperInfo_t info_cur;         /**< DVR playback current information*/
+  DVR_WrapperInfo_t info_full;        /**< DVR playback total(non-obsolete) infomation*/
+  DVR_PlaybackPids_t pids;            /**< DVR playback pids information*/
+  float speed;                        /**< DVR playback current speed*/
+  DVR_PlaybackSegmentFlag_t flags;    /**< DVR playback flags*/
+  DVR_WrapperInfo_t info_obsolete;    /**< DVR playback obsolete information, take into account for timeshift*/
 } DVR_WrapperPlaybackStatus_t;
 
 typedef struct {
   DVR_RecordState_t state;            /**< DVR record state*/
   DVR_WrapperInfo_t info;             /**< DVR record information*/
   DVR_WrapperPidsInfo_t pids;         /**< DVR record pids info*/
+  DVR_WrapperInfo_t info_obsolete;    /**< DVR record obsolete information, take into account for timeshift*/
 } DVR_WrapperRecordStatus_t;
 
 /**Record wrapper open parameters.*/
@@ -71,8 +73,8 @@
   char                  location[DVR_MAX_LOCATION_SIZE]; /**< Location of the record file.*/
   DVR_Bool_t            is_timeshift;                    /**< The record file is used by timeshift.*/
   loff_t                segment_size;                    /**< Segment file's size.*/
-  loff_t                max_size;                        /**< Maximum record file size in bytes.*/
-  time_t                max_time;                        /**< Maximum record time in milliseconds.*/
+  loff_t                max_size;                        /**< Maximum record file size in bytes(for timeshift).*/
+  time_t                max_time;                        /**< Maximum record time in milliseconds(for timeshift).*/
   DVR_RecordFlag_t      flags;                           /**< Flags.*/
   DVR_CryptoPeriod_t    crypto_period;                   /**< Crypto period.*/
   DVR_CryptoFunction_t  crypto_fn;                       /**< Crypto callback function.*/
diff --git a/include/list.h b/include/list.h
index 194d651..fe41289 100644
--- a/include/list.h
+++ b/include/list.h
@@ -385,6 +385,33 @@
 	list_entry((ptr)->next, type, member)
 
 /**
+ * list_last_entry - get the last element from a list
+ * @ptr:	the list head to take the element from.
+ * @type:	the type of the struct this is embedded in.
+ * @member:	the name of the list_head within the struct.
+ *
+ * Note, that list is expected to be not empty.
+ */
+#define list_last_entry(ptr, type, member) \
+	list_entry((ptr)->prev, type, member)
+
+/**
+ * list_next_entry - get the next element in list
+ * @pos:	the type * to cursor
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_next_entry(pos, member) \
+	list_entry((pos)->member.next, typeof(*(pos)), member)
+
+/**
+ * list_prev_entry - get the prev element in list
+ * @pos:	the type * to cursor
+ * @member:	the name of the list_head within the struct.
+ */
+#define list_prev_entry(pos, member) \
+	list_entry((pos)->member.prev, typeof(*(pos)), member)
+
+/**
  * list_for_each	-	iterate over a list
  * @pos:	the &struct list_head to use as a loop cursor.
  * @head:	the head for your list.
diff --git a/src/dvr_wrapper.c b/src/dvr_wrapper.c
index a25b85c..8c54fd9 100644
--- a/src/dvr_wrapper.c
+++ b/src/dvr_wrapper.c
@@ -14,8 +14,6 @@
 #include "AmTsPlayer.h"
 
 #include "list.h"
-#define list_last_entry(ptr, type, member) \
-  list_entry((ptr)->prev, type, member)
 
 #include "dvr_wrapper.h"
 
@@ -61,10 +59,12 @@
       DVR_RecordEventFunction_t       event_fn;
       void                            *event_userdata;
 
-      /*total status = seg_status + status*/
+      /*total status = seg_status + status + obsolete*/
       DVR_RecordStatus_t              seg_status;            /**<status of current segment*/
       DVR_WrapperRecordStatus_t       status;                /**<status of remaining segments*/
       uint64_t                        next_segment_id;
+
+      DVR_WrapperInfo_t               obsolete;             /**<data obsolete due to the max limit*/
     } record;
 
     struct {
@@ -80,6 +80,8 @@
       DVR_PlaybackEvent_t             last_event;
       float                           speed;
       DVR_Bool_t                      reach_end;
+
+      DVR_WrapperInfo_t               obsolete;
     } playback;
   };
 } DVR_WrapperCtx_t;
@@ -198,8 +200,8 @@
 static DVR_Result_t wrapper_record_event_handler(DVR_RecordEvent_t event, void *params, void *userdata);
 static DVR_Result_t wrapper_playback_event_handler(DVR_PlaybackEvent_t event, void *params, void *userdata);
 
-static int process_generateRecordStatus(DVR_WrapperCtx_t *ctx);
-static int process_generatePlaybackStatus(DVR_WrapperCtx_t *ctx);
+static int process_generateRecordStatus(DVR_WrapperCtx_t *ctx, DVR_WrapperRecordStatus_t *status);
+static int process_generatePlaybackStatus(DVR_WrapperCtx_t *ctx, DVR_WrapperPlaybackStatus_t *status);
 
 
 static DVR_WrapperEventCtx_t *ctx_getEvent(struct list_head *list, pthread_mutex_t *list_lock)
@@ -743,23 +745,56 @@
 
 static int wrapper_removePlaybackSegment(DVR_WrapperCtx_t *ctx, DVR_RecordSegmentInfo_t *seg_info)
 {
-  int error;
+  int error = -1;
+  DVR_WrapperPlaybackSegmentInfo_t *pseg = NULL, *pseg_tmp;
 
   DVR_WRAPPER_DEBUG(1, "timeshift, remove playback(sn:%ld) segment(%lld) ...\n", ctx->sn, seg_info->id);
 
-  error = dvr_playback_remove_segment(ctx->playback.player, seg_info->id);
-  if (error) {
-    /*remove playack segment fail*/
-  } else {
-    DVR_WrapperPlaybackSegmentInfo_t *pseg;
-
-    /*normally, should be the earliest segment in the list*/
-    pseg = list_last_entry(&ctx->segments, DVR_WrapperPlaybackSegmentInfo_t, head);
+  list_for_each_entry_safe_reverse(pseg, pseg_tmp, &ctx->segments, head) {
     if (pseg->seg_info.id == seg_info->id) {
+
+      if (ctx->current_segment_id == seg_info->id) {
+        DVR_WrapperPlaybackSegmentInfo_t *next_seg;
+
+        /*drive the player out of this will-be-deleted segment*/
+        next_seg = list_prev_entry(pseg, head);
+
+        if (ctx->playback.speed != 100.0f) {
+          error = dvr_playback_resume(ctx->playback.player);
+          DVR_WRAPPER_DEBUG(1, "timeshift, playback(sn:%ld), resume for new start (%d)\n", ctx->sn, error);
+        }
+
+        error = dvr_playback_seek(ctx->playback.player, next_seg->seg_info.id, 0);
+        DVR_WRAPPER_DEBUG(1, "timeshift, playback(sn:%ld), seek(seg:%llu 0) from new start (%d)\n", ctx->sn, next_seg->seg_info.id, error);
+
+        if (ctx->playback.speed == 0.0f) {
+          error = dvr_playback_pause(ctx->playback.player, DVR_FALSE);
+          DVR_WRAPPER_DEBUG(1, "timeshift, playback(sn:%ld), keep last paused from new start (%d)\n", ctx->sn, error);
+        } else if (ctx->playback.speed != 100.0f) {
+          DVR_PlaybackSpeed_t dvr_speed = {
+             .speed = { ctx->playback.speed },
+             .mode = ( ctx->playback.speed > 0) ? DVR_PLAYBACK_FAST_FORWARD : DVR_PLAYBACK_FAST_BACKWARD
+          };
+          error = dvr_playback_set_speed(ctx->playback.player, dvr_speed);
+          DVR_WRAPPER_DEBUG(1, "timeshift, playback(sn:%ld), keep last speed(x%f) from new start (%d)\n", ctx->sn,ctx->playback.speed, error);
+        }
+      }
+
+      error = dvr_playback_remove_segment(ctx->playback.player, seg_info->id);
+      if (error) {
+        /*remove playack segment fail*/
+        DVR_WRAPPER_DEBUG(1, "timeshift, playback(sn:%ld), failed to remove segment(%llu) (%d)\n", ctx->sn, seg_info->id, error);
+      }
+
       list_del(&pseg->head);
+
+      /*record the obsolete*/
+      ctx->playback.obsolete.time += pseg->seg_info.duration;
+      ctx->playback.obsolete.size += pseg->seg_info.size;
+      ctx->playback.obsolete.pkts += pseg->seg_info.nb_packets;
+
       free(pseg);
-    } else {
-      /*what's going on*/
+      break;
     }
   }
 
@@ -771,7 +806,7 @@
 static int wrapper_removeRecordSegment(DVR_WrapperCtx_t *ctx, DVR_WrapperRecordSegmentInfo_t *seg_info)
 {
   int error;
-  DVR_WrapperRecordSegmentInfo_t *pseg;
+  DVR_WrapperRecordSegmentInfo_t *pseg, *pseg_tmp;
 
   DVR_WRAPPER_DEBUG(1, "timeshift, remove record(sn:%ld) segment(%lld) ...\n", ctx->sn, seg_info->info.id);
 
@@ -790,13 +825,18 @@
     }
   }
 
-  /*normally, the earliest segment will be removed*/
-  pseg = list_last_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head);
-  if (pseg->info.id == seg_info->info.id) {
-    list_del(&pseg->head);
-    free(pseg);
-  } else {
-    /*should not get here*/
+  list_for_each_entry_safe_reverse(pseg, pseg_tmp, &ctx->segments, head) {
+    if (pseg->info.id == seg_info->info.id) {
+      list_del(&pseg->head);
+
+      /*record the obsolete*/
+      ctx->record.obsolete.time += pseg->info.duration;
+      ctx->record.obsolete.size += pseg->info.size;
+      ctx->record.obsolete.pkts += pseg->info.nb_packets;
+
+      free(pseg);
+      break;
+    }
   }
 
   error = dvr_segment_delete(ctx->record.param_open.location, seg_info->info.id);
@@ -887,7 +927,9 @@
 
   error = dvr_record_close(ctx->record.recorder);
 
-  sn_timeshift_record = 0;
+  if (ctx->record.param_open.is_timeshift)
+    sn_timeshift_record = 0;
+
   ctx_freeSegments(ctx);
 
   DVR_WRAPPER_DEBUG(1, "record(sn:%ld) closed = (%d).\n", ctx->sn, error);
@@ -1032,11 +1074,7 @@
   DVR_WRAPPER_DEBUG(1, "get record(sn:%ld) status ...\n", ctx->sn);
   DVR_RETURN_IF_FALSE_WITH_UNLOCK(ctx_valid(ctx), &ctx->lock);
 
-  error = process_generateRecordStatus(ctx);
-  s = ctx->record.status;
-  s.info.time += ctx->record.seg_status.info.duration;
-  s.info.size += ctx->record.seg_status.info.size;
-  s.info.pkts += ctx->record.seg_status.info.nb_packets;
+  error = process_generateRecordStatus(ctx, &s);
 
   DVR_WRAPPER_DEBUG(1, "record(sn:%ld) state/time/size/pkts(%d/%ld/%lld/%u) (%d)\n",
     ctx->sn,
@@ -1202,6 +1240,8 @@
   uint32_t i;
   DVR_RecordSegmentInfo_t seg_info_1st;
   int got_1st_seg;
+  DVR_WrapperCtx_t *ctx_record;/*for timeshift*/
+
 
   DVR_RETURN_IF_FALSE(playback);
   DVR_RETURN_IF_FALSE(p_pids);
@@ -1223,56 +1263,93 @@
 
   DVR_RETURN_IF_FALSE_WITH_UNLOCK(ctx_valid(ctx), &ctx->lock);
 
+  ctx_record = NULL;
+  if (ctx->playback.param_open.is_timeshift) {
+    /*lock the recorder to avoid changing the recording segments*/
+    ctx_record = ctx_getRecord(sn_timeshift_record);
+
+    if (ctx_record) {
+      pthread_mutex_lock(&ctx_record->lock);
+      if (!ctx_valid(ctx_record)
+        || ctx_record->sn != sn_timeshift_record) {
+        DVR_WRAPPER_DEBUG(1, "timeshift, record is not for timeshifting, FATAL error\n");
+        pthread_mutex_unlock(&ctx_record->lock);
+      } else {
+        DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) record(sn:%ld) locked ok due to timeshift\n",
+          ctx->sn, ctx_record->sn);
+      }
+    }
+    DVR_RETURN_IF_FALSE_WITH_UNLOCK(ctx_record, &ctx->lock);
+  }
+
   /*obtain all segments in a list*/
   segment_nb = 0;
   p_segment_ids = NULL;
   error = dvr_segment_get_list(ctx->playback.param_open.location, &segment_nb, &p_segment_ids);
-  DVR_RETURN_IF_FALSE_WITH_UNLOCK(!error, &ctx->lock);
+  if (!error) {
+    got_1st_seg = 0;
+    for (i = 0; i < segment_nb; i++) {
+      DVR_RecordSegmentInfo_t seg_info;
+      DVR_PlaybackSegmentFlag_t flags;
 
-  got_1st_seg = 0;
-  for (i = 0; i < segment_nb; i++) {
-    DVR_RecordSegmentInfo_t seg_info;
-    DVR_PlaybackSegmentFlag_t flags;
+      error = dvr_segment_get_info(ctx->playback.param_open.location, p_segment_ids[i], &seg_info);
+      if (error) {
+        error = DVR_FAILURE;
+        DVR_WRAPPER_DEBUG(1, "fail to get seg info (location:%s, seg:%llu), (error:%d)\n",
+          ctx->playback.param_open.location, p_segment_ids[i], error);
+        break;
+      }
 
-    error = dvr_segment_get_info(ctx->playback.param_open.location, p_segment_ids[i], &seg_info);
-    if (error) {
-      error = DVR_FAILURE;
-      DVR_WRAPPER_DEBUG(1, "fail to get seg info (location:%s, seg:%llu), (error:%d)\n",
-        ctx->playback.param_open.location, p_segment_ids[i], error);
-      break;
+      flags = DVR_PLAYBACK_SEGMENT_DISPLAYABLE | DVR_PLAYBACK_SEGMENT_CONTINUOUS;
+      error = wrapper_addPlaybackSegment(ctx, &seg_info, p_pids, flags);
+      if (error)
+        break;
+
+      /*copy the 1st segment*/
+      if (got_1st_seg == 0) {
+          seg_info_1st = seg_info;
+          got_1st_seg = 1;
+      }
     }
+    free(p_segment_ids);
 
-    flags = DVR_PLAYBACK_SEGMENT_DISPLAYABLE | DVR_PLAYBACK_SEGMENT_CONTINUOUS;
-    error = wrapper_addPlaybackSegment(ctx, &seg_info, p_pids, flags);
-    if (error)
-      break;
+    /* return if no segment or fail to add */
+    if (!error && got_1st_seg) {
 
-    /*copy the 1st segment*/
-    if (got_1st_seg == 0) {
-        seg_info_1st = seg_info;
-        got_1st_seg = 1;
+      /*copy the obsolete infomation, must for timeshifting*/
+      if (ctx->playback.param_open.is_timeshift && ctx_record) {
+        ctx->playback.obsolete = ctx_record->record.obsolete;
+      }
+
+      DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) (%d) segments added\n", ctx->sn, i);
+
+      ctx->playback.reach_end = DVR_FALSE;
+      if ((flags&DVR_PLAYBACK_STARTED_PAUSEDLIVE) == DVR_PLAYBACK_STARTED_PAUSEDLIVE)
+        ctx->playback.speed = 0.0f;
+      else
+        ctx->playback.speed = 100.0f;
+
+      ctx->playback.pids_req = *p_pids;
+
+      error = dvr_playback_seek(ctx->playback.player, seg_info_1st.id, 0);
+      DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) seek(seg:%llu 0) for start (%d)\n",
+        ctx->sn, seg_info_1st.id, error);
+
+      error = dvr_playback_start(ctx->playback.player, flags);
+
+      DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) started (%d)\n", ctx->sn, error);
     }
   }
-  free(p_segment_ids);
 
-  /* return if no segment or fail to add */
-  DVR_RETURN_IF_FALSE_WITH_UNLOCK(!error && got_1st_seg, &ctx->lock);
+  if (ctx->playback.param_open.is_timeshift) {
+    /*unlock the recorder locked above*/
+    if (ctx_record && ctx_valid(ctx_record)) {
+        pthread_mutex_unlock(&ctx_record->lock);
+        DVR_WRAPPER_DEBUG(1, "playback(sn:%ld), record(sn:%ld) unlocked ok due to timeshift\n",
+          ctx->sn, ctx_record->sn);
+    }
+  }
 
-  DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) (%d) segments added\n", ctx->sn, i);
-  ctx->playback.reach_end = DVR_FALSE;
-  if ((flags&DVR_PLAYBACK_STARTED_PAUSEDLIVE) == DVR_PLAYBACK_STARTED_PAUSEDLIVE)
-    ctx->playback.speed = 0.0f;
-  else
-    ctx->playback.speed = 100.0f;
-  ctx->playback.pids_req = *p_pids;
-
-  error = dvr_playback_seek(ctx->playback.player, seg_info_1st.id, 0);
-  DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) seek(seg:%llu 0) for start (%d)\n",
-    ctx->sn, seg_info_1st.id, error);
-
-  error = dvr_playback_start(ctx->playback.player, flags);
-
-  DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) started (%d)\n", ctx->sn, error);
 
   pthread_mutex_unlock(&ctx->lock);
 
@@ -1434,21 +1511,24 @@
   list_for_each_entry_reverse(pseg, &ctx->segments, head) {
     segment_id = pseg->seg_info.id;
 
-    if ((pre_off + pseg->seg_info.duration) > time_offset)
-      break;
+    if ((ctx->playback.obsolete.time + pre_off + pseg->seg_info.duration) > time_offset)
+        break;
 
     last_segment_id = pseg->seg_info.id;
     pre_off += pseg->seg_info.duration;
   }
 
   if (last_segment_id == segment_id) {
-      off = time_offset;
+    /*1.only one seg with id:0, 2.offset exceeds the total duration*/
+    off = time_offset;
+  } else if (ctx->playback.obsolete.time >= time_offset) {
+    off = 0;
   } else {
-      off = time_offset - pre_off;
+    off = time_offset - pre_off;
   }
 
-
-  DVR_WRAPPER_DEBUG(1, "seek playback(sn:%ld) (seg:%lld, off:%d)\n", ctx->sn, segment_id, off);
+  DVR_WRAPPER_DEBUG(1, "seek playback(sn:%ld) (seg:%lld, off:%d)\n",
+    ctx->sn, segment_id, off);
   error = dvr_playback_seek(ctx->playback.player, segment_id, off);
   DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) seeked(off:%d) (%d)\n", ctx->sn, time_offset, error);
 
@@ -1532,20 +1612,19 @@
   DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) get status (%d)\n", ctx->sn, error);
 
   ctx->playback.seg_status = play_status;
-  error = process_generatePlaybackStatus(ctx);
-  s = ctx->playback.status;
-  s.info_cur.time += ctx->playback.seg_status.time_cur;
-  /*size and packets are not available*/
+  error = process_generatePlaybackStatus(ctx, &s);
+
   if (ctx->playback.reach_end == DVR_TRUE && ctx->playback.param_open.is_timeshift == DVR_FALSE) {
     //reach end need set full time to cur.so app can exist playback.
     DVR_WRAPPER_DEBUG(1, "set cur time to full time, reach end occur");
     s.info_cur.time = s.info_full.time;
   }
-  DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) state/cur/full(%d/%ld/%ld) (%d)\n",
+  DVR_WRAPPER_DEBUG(1, "playback(sn:%ld) state/cur/full/obsl(%d/%ld/%ld/%ld) (%d)\n",
     ctx->sn,
     s.state,
     s.info_cur.time,
     s.info_full.time,
+    s.info_obsolete.time,
     error);
 
   *status = s;
@@ -1619,13 +1698,15 @@
 
 static inline int process_notifyRecord(DVR_WrapperCtx_t *ctx, DVR_RecordEvent_t evt, DVR_WrapperRecordStatus_t *status)
 {
-  DVR_WRAPPER_DEBUG(1, "notify(sn:%ld) evt(0x%x) statistic:time/size/pkts(%lu/%lld/%u) fn:%p\n",
+  DVR_WRAPPER_DEBUG(1, "notify(sn:%ld) evt(0x%x) statistic:time/size/pkts(%ld/%lld/%u) obsl:(%ld/%llu/%u)\n",
     ctx->sn,
     evt,
     status->info.time,
     status->info.size,
     status->info.pkts,
-    ctx->record.event_fn);
+    status->info_obsolete.time,
+    status->info_obsolete.size,
+    status->info_obsolete.pkts);
 
   if (ctx->record.event_fn)
     return ctx->record.event_fn(evt, status, ctx->record.event_userdata);
@@ -1667,12 +1748,12 @@
 }
 
 /*should run periodically to update the current status*/
-static int process_generateRecordStatus(DVR_WrapperCtx_t *ctx)
+static int process_generateRecordStatus(DVR_WrapperCtx_t *ctx, DVR_WrapperRecordStatus_t *status)
 {
   /*the current seg is not covered in the statistics*/
   DVR_WrapperRecordSegmentInfo_t *pseg;
 
-  /*re-calculate the all segments, except the current*/
+  /*re-calculate the all segments*/
   memset(&ctx->record.status, 0, sizeof(ctx->record.status));
 
   ctx->record.status.state = ctx->record.seg_status.state;
@@ -1690,7 +1771,17 @@
     }
   }
 
+  ctx->record.status.info_obsolete = ctx->record.obsolete;
+
   wrapper_updateRecordSegment(ctx, &ctx->record.seg_status.info, U_ALL);
+
+  if (status) {
+    *status = ctx->record.status;
+    status->info.time += ctx->record.seg_status.info.duration;
+    status->info.size += ctx->record.seg_status.info.size;
+    status->info.pkts += ctx->record.seg_status.info.nb_packets;
+  }
+
   return DVR_SUCCESS;
 }
 
@@ -1720,74 +1811,13 @@
         } break;
         case DVR_RECORD_STATE_STARTED:
         {
-          int check_ok;
-
           ctx->record.seg_status = evt->record.status;
 
-          process_generateRecordStatus(ctx);
-
-          status = ctx->record.status;
-          status.info.time += evt->record.status.info.duration;
-          status.info.size += evt->record.status.info.size;
-          status.info.pkts += evt->record.status.info.nb_packets;
-
+          process_generateRecordStatus(ctx, &status);
           process_notifyRecord(ctx, evt->record.event, &status);
 
-          check_ok = 1;
-          if (ctx->record.param_open.max_time
-              && status.info.time >= ctx->record.param_open.max_time) {
-            if (!ctx->record.param_open.is_timeshift) {
-              /*not timeshifting, just stop*/
-              int error = dvr_record_close(ctx->record.recorder);
-              DVR_WRAPPER_DEBUG(1, "stop record(%lu)=%d, reaches time limit, curr(%ld) max(%ld)\n",
-                ctx->sn, error,
-                status.info.time, ctx->record.param_open.max_time);
-                status.state = DVR_RECORD_STATE_CLOSED;
-                process_notifyRecord(ctx, evt->record.event, &status);
-                check_ok = 0;
-            } else {
-              /*timeshifting, remove the 1st segment and notify the player*/
-              DVR_WrapperRecordSegmentInfo_t *pseg;
-              pseg = list_last_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head);
-              if (pseg == list_first_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head)) {
-                /*only one segment, waiting for more*/
-                DVR_WRAPPER_DEBUG(1, "warning: the size(%lld) of max_time(%ld) of record < max size of segment(%lld)\n",
-                  status.info.size,
-                  ctx->record.param_open.max_time,
-                  ctx->record.param_open.segment_size);
-              } else {
-                record_removeSegment(ctx, pseg);
-              }
-            }
-          }
-
-          if (ctx->record.param_open.max_size
-              && status.info.size >= ctx->record.param_open.max_size) {
-            if (!ctx->record.param_open.is_timeshift) {
-              int error = dvr_record_close(ctx->record.recorder);
-              DVR_WRAPPER_DEBUG(1, "stop record(%lu)=%d, reaches size limit, curr(%lld) max(%lld)\n",
-                ctx->sn, error,
-                status.info.size, ctx->record.param_open.max_size);
-                status.state = DVR_RECORD_STATE_CLOSED;
-                process_notifyRecord(ctx, evt->record.event, &status);
-                check_ok = 0;
-            } else {
-              DVR_WrapperRecordSegmentInfo_t *pseg;
-              pseg = list_last_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head);
-              if (pseg == list_first_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head)) {
-                /*only one segment, waiting for more*/
-                DVR_WRAPPER_DEBUG(1, "warning: the size(%lld) of record < max size of segment(%lld)\n",
-                  status.info.size,
-                  ctx->record.param_open.segment_size);
-              } else {
-                record_removeSegment(ctx, pseg);
-              }
-            }
-          }
-
           /*restart to next segment*/
-          if (check_ok
-              && ctx->record.param_open.segment_size
+          if (ctx->record.param_open.segment_size
               && evt->record.status.info.size >= ctx->record.param_open.segment_size) {
             DVR_WRAPPER_DEBUG(1, "start new segment for record(%lu), reaches segment size limit, cur(%zu) max(%lld)\n",
               ctx->sn,
@@ -1802,17 +1832,58 @@
               process_notifyRecord(ctx, DVR_RECORD_EVENT_WRITE_ERROR, &status);
             }
           }
+
+          if (ctx->record.param_open.is_timeshift
+              && ctx->record.param_open.max_time
+              && status.info.time >= ctx->record.param_open.max_time) {
+            DVR_WrapperRecordSegmentInfo_t *pseg;
+
+            /*as the player do not support null playlist,
+              there must be one segment existed at any time,
+              we have to keep two segments before remove one*/
+            pseg = list_last_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head);
+            if (pseg == list_first_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head)) {
+              /*only one segment, waiting for more*/
+              DVR_WRAPPER_DEBUG(1, "warning: the size(%lld) of max_time(%ld) of record < max size of segment(%lld)\n",
+                status.info.size,
+                ctx->record.param_open.max_time,
+                ctx->record.param_open.segment_size);
+            } else {
+              /*timeshifting, remove the 1st segment and notify the player*/
+              record_removeSegment(ctx, pseg);
+
+              process_generateRecordStatus(ctx, &status);
+              process_notifyRecord(ctx, evt->record.event, &status);
+            }
+          }
+
+          if (ctx->record.param_open.is_timeshift
+              && ctx->record.param_open.max_size
+              && status.info.size >= ctx->record.param_open.max_size) {
+            DVR_WrapperRecordSegmentInfo_t *pseg;
+
+            /*as the player do not support null playlist,
+              there must be one segment existed at any time,
+              we have to keep two segments before remove one*/
+            pseg = list_last_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head);
+            if (pseg == list_first_entry(&ctx->segments, DVR_WrapperRecordSegmentInfo_t, head)) {
+              /*only one segment, waiting for more*/
+              DVR_WRAPPER_DEBUG(1, "warning: the size(%lld) of record < max size of segment(%lld)\n",
+                status.info.size,
+                ctx->record.param_open.segment_size);
+            } else {
+              record_removeSegment(ctx, pseg);
+
+              process_generateRecordStatus(ctx, &status);
+              process_notifyRecord(ctx, evt->record.event, &status);
+            }
+          }
         } break;
         case DVR_RECORD_STATE_STOPPED:
         {
           ctx->record.seg_status = evt->record.status;
 
-          process_generateRecordStatus(ctx);
-
-          status.info.time += evt->record.status.info.duration;
-          status.info.size += evt->record.status.info.size;
-          status.info.pkts += evt->record.status.info.nb_packets;
-
+          process_generateRecordStatus(ctx, &status);
           process_notifyRecord(ctx, evt->record.event, &status);
         } break;
         default:
@@ -1832,13 +1903,13 @@
 
 static inline int process_notifyPlayback(DVR_WrapperCtx_t *ctx, DVR_PlaybackEvent_t evt, DVR_WrapperPlaybackStatus_t *status)
 {
-  DVR_WRAPPER_DEBUG(1, "notify(sn:%ld) evt(0x%x) statistics:state/cur/full(%d/%ld/%ld) fn:%p\n",
+  DVR_WRAPPER_DEBUG(1, "notify(sn:%ld) evt(0x%x) statistics:state/cur/full/obsl(%d/%ld/%ld/%ld)\n",
     ctx->sn,
     evt,
     status->state,
     status->info_cur.time,
     status->info_full.time,
-    ctx->playback.event_fn);
+    status->info_obsolete.time);
 
   if (ctx->playback.event_fn)
     return ctx->playback.event_fn(evt, status, ctx->playback.event_userdata);
@@ -1846,7 +1917,7 @@
 }
 
 /*should run periodically to update the current status*/
-static int process_generatePlaybackStatus(DVR_WrapperCtx_t *ctx)
+static int process_generatePlaybackStatus(DVR_WrapperCtx_t *ctx, DVR_WrapperPlaybackStatus_t *status)
 {
   /*the current seg is not covered in the statistics*/
   DVR_WrapperPlaybackSegmentInfo_t *pseg;
@@ -1872,6 +1943,13 @@
     ctx->playback.status.info_full.pkts += pseg->seg_info.nb_packets;
   }
 
+  if (status) {
+    *status = ctx->playback.status;
+    /*deal with current, lack size and pkts with the current*/
+    status->info_cur.time += ctx->playback.seg_status.time_cur;
+    status->info_obsolete.time = ctx->playback.obsolete.time;
+  }
+
   return DVR_SUCCESS;
 }
 
@@ -1901,12 +1979,8 @@
       /*copy status of segment*/
       ctx->playback.seg_status = evt->playback.status.play_status;
 
-      /*generate status of the whole playback, except current*/
-      process_generatePlaybackStatus(ctx);
-
-      status = ctx->playback.status;
-      /*deal with current, lack size and pkts with the current*/
-      status.info_cur.time += ctx->playback.seg_status.time_cur;
+      /*generate status of the whole playback*/
+      process_generatePlaybackStatus(ctx, &status);
 
       if (evt->playback.event == DVR_PLAYBACK_EVENT_REACHED_END) {
         if (ctx->playback.param_open.is_timeshift) {