changeset 5620:0a567878254b

Mp4: added "end" argument support.
author Roman Arutyunyan <arut@nginx.com>
date Thu, 20 Mar 2014 16:05:19 +0400
parents 517b5b599e3f
children 345e4fd4bb64
files src/http/modules/ngx_http_mp4_module.c
diffstat 1 files changed, 381 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/src/http/modules/ngx_http_mp4_module.c
+++ b/src/http/modules/ngx_http_mp4_module.c
@@ -27,14 +27,15 @@
 #define NGX_HTTP_MP4_CTTS_ATOM    15
 #define NGX_HTTP_MP4_CTTS_DATA    16
 #define NGX_HTTP_MP4_STSC_ATOM    17
-#define NGX_HTTP_MP4_STSC_CHUNK   18
+#define NGX_HTTP_MP4_STSC_START   18
 #define NGX_HTTP_MP4_STSC_DATA    19
-#define NGX_HTTP_MP4_STSZ_ATOM    20
-#define NGX_HTTP_MP4_STSZ_DATA    21
-#define NGX_HTTP_MP4_STCO_ATOM    22
-#define NGX_HTTP_MP4_STCO_DATA    23
-#define NGX_HTTP_MP4_CO64_ATOM    24
-#define NGX_HTTP_MP4_CO64_DATA    25
+#define NGX_HTTP_MP4_STSC_END     20
+#define NGX_HTTP_MP4_STSZ_ATOM    21
+#define NGX_HTTP_MP4_STSZ_DATA    22
+#define NGX_HTTP_MP4_STCO_ATOM    23
+#define NGX_HTTP_MP4_STCO_DATA    24
+#define NGX_HTTP_MP4_CO64_ATOM    25
+#define NGX_HTTP_MP4_CO64_DATA    26
 
 #define NGX_HTTP_MP4_LAST_ATOM    NGX_HTTP_MP4_CO64_DATA
 
@@ -62,10 +63,15 @@ typedef struct {
     uint32_t              chunks;
 
     ngx_uint_t            start_sample;
+    ngx_uint_t            end_sample;
     ngx_uint_t            start_chunk;
-    ngx_uint_t            chunk_samples;
-    uint64_t              chunk_samples_size;
+    ngx_uint_t            end_chunk;
+    ngx_uint_t            start_chunk_samples;
+    ngx_uint_t            end_chunk_samples;
+    uint64_t              start_chunk_samples_size;
+    uint64_t              end_chunk_samples_size;
     off_t                 start_offset;
+    off_t                 end_offset;
 
     size_t                tkhd_size;
     size_t                mdhd_size;
@@ -95,7 +101,8 @@ typedef struct {
     ngx_buf_t             ctts_atom_buf;
     ngx_buf_t             ctts_data_buf;
     ngx_buf_t             stsc_atom_buf;
-    ngx_buf_t             stsc_chunk_buf;
+    ngx_buf_t             stsc_start_chunk_buf;
+    ngx_buf_t             stsc_end_chunk_buf;
     ngx_buf_t             stsc_data_buf;
     ngx_buf_t             stsz_atom_buf;
     ngx_buf_t             stsz_data_buf;
@@ -104,7 +111,8 @@ typedef struct {
     ngx_buf_t             co64_atom_buf;
     ngx_buf_t             co64_data_buf;
 
-    ngx_mp4_stsc_entry_t  stsc_chunk_entry;
+    ngx_mp4_stsc_entry_t  stsc_start_chunk_entry;
+    ngx_mp4_stsc_entry_t  stsc_end_chunk_entry;
 } ngx_http_mp4_trak_t;
 
 
@@ -121,6 +129,7 @@ typedef struct {
     off_t                 end;
     off_t                 content_length;
     ngx_uint_t            start;
+    ngx_uint_t            length;
     uint32_t              timescale;
     ngx_http_request_t   *request;
     ngx_array_t           trak;
@@ -219,7 +228,7 @@ static ngx_int_t ngx_http_mp4_read_moov_
 static ngx_int_t ngx_http_mp4_read_mdat_atom(ngx_http_mp4_file_t *mp4,
     uint64_t atom_data_size);
 static size_t ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4,
-    off_t start_offset);
+    off_t start_offset, off_t end_offset);
 static ngx_int_t ngx_http_mp4_read_mvhd_atom(ngx_http_mp4_file_t *mp4,
     uint64_t atom_data_size);
 static ngx_int_t ngx_http_mp4_read_trak_atom(ngx_http_mp4_file_t *mp4,
@@ -259,25 +268,25 @@ static ngx_int_t ngx_http_mp4_read_stts_
 static ngx_int_t ngx_http_mp4_update_stts_atom(ngx_http_mp4_file_t *mp4,
     ngx_http_mp4_trak_t *trak);
 static ngx_int_t ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
-    ngx_http_mp4_trak_t *trak);
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
 static ngx_int_t ngx_http_mp4_read_stss_atom(ngx_http_mp4_file_t *mp4,
     uint64_t atom_data_size);
 static ngx_int_t ngx_http_mp4_update_stss_atom(ngx_http_mp4_file_t *mp4,
     ngx_http_mp4_trak_t *trak);
 static void ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
-    ngx_http_mp4_trak_t *trak);
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
 static ngx_int_t ngx_http_mp4_read_ctts_atom(ngx_http_mp4_file_t *mp4,
     uint64_t atom_data_size);
 static void ngx_http_mp4_update_ctts_atom(ngx_http_mp4_file_t *mp4,
     ngx_http_mp4_trak_t *trak);
 static void ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
-    ngx_http_mp4_trak_t *trak);
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
 static ngx_int_t ngx_http_mp4_read_stsc_atom(ngx_http_mp4_file_t *mp4,
     uint64_t atom_data_size);
 static ngx_int_t ngx_http_mp4_update_stsc_atom(ngx_http_mp4_file_t *mp4,
     ngx_http_mp4_trak_t *trak);
 static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
-    ngx_http_mp4_trak_t *trak);
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start);
 static ngx_int_t ngx_http_mp4_read_stsz_atom(ngx_http_mp4_file_t *mp4,
     uint64_t atom_data_size);
 static ngx_int_t ngx_http_mp4_update_stsz_atom(ngx_http_mp4_file_t *mp4,
@@ -411,8 +420,8 @@ ngx_http_mp4_handler(ngx_http_request_t 
 {
     u_char                    *last;
     size_t                     root;
-    ngx_int_t                  rc, start;
-    ngx_uint_t                 level;
+    ngx_int_t                  rc, start, end;
+    ngx_uint_t                 level, length;
     ngx_str_t                  path, value;
     ngx_log_t                 *log;
     ngx_buf_t                 *b;
@@ -517,6 +526,7 @@ ngx_http_mp4_handler(ngx_http_request_t 
     r->allow_ranges = 1;
 
     start = -1;
+    length = 0;
     r->headers_out.content_length_n = of.size;
     mp4 = NULL;
     b = NULL;
@@ -538,6 +548,26 @@ ngx_http_mp4_handler(ngx_http_request_t 
                 start = -1;
             }
         }
+
+        if (ngx_http_arg(r, (u_char *) "end", 3, &value) == NGX_OK) {
+
+            ngx_set_errno(0);
+            end = (int) (strtod((char *) value.data, NULL) * 1000);
+
+            if (ngx_errno != 0) {
+                end = -1;
+            }
+
+            if (end > 0) {
+                if (start < 0) {
+                    start = 0;
+                }
+
+                if (end > start) {
+                    length = end - start;
+                }
+            }
+        }
     }
 
     if (start >= 0) {
@@ -553,6 +583,7 @@ ngx_http_mp4_handler(ngx_http_request_t 
         mp4->file.log = r->connection->log;
         mp4->end = of.size;
         mp4->start = (ngx_uint_t) start;
+        mp4->length = length;
         mp4->request = r;
 
         switch (ngx_http_mp4_process(mp4)) {
@@ -658,15 +689,15 @@ ngx_http_mp4_handler(ngx_http_request_t 
 static ngx_int_t
 ngx_http_mp4_process(ngx_http_mp4_file_t *mp4)
 {
-    off_t                  start_offset, adjustment;
+    off_t                  start_offset, end_offset, adjustment;
     ngx_int_t              rc;
     ngx_uint_t             i, j;
     ngx_chain_t          **prev;
     ngx_http_mp4_trak_t   *trak;
     ngx_http_mp4_conf_t   *conf;
 
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
-                   "mp4 start:%ui", mp4->start);
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                   "mp4 start:%ui, length:%ui", mp4->start, mp4->length);
 
     conf = ngx_http_get_module_loc_conf(mp4->request, ngx_http_mp4_module);
 
@@ -708,6 +739,7 @@ ngx_http_mp4_process(ngx_http_mp4_file_t
     }
 
     start_offset = mp4->end;
+    end_offset = 0;
     trak = mp4->trak.elts;
 
     for (i = 0; i < mp4->trak.nelts; i++) {
@@ -755,6 +787,10 @@ ngx_http_mp4_process(ngx_http_mp4_file_t
             start_offset = trak[i].start_offset;
         }
 
+        if (end_offset < trak[i].end_offset) {
+            end_offset = trak[i].end_offset;
+        }
+
         *prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM];
         prev = &trak[i].out[NGX_HTTP_MP4_TRAK_ATOM].next;
 
@@ -766,6 +802,10 @@ ngx_http_mp4_process(ngx_http_mp4_file_t
         }
     }
 
+    if (end_offset < start_offset) {
+        end_offset = start_offset;
+    }
+
     mp4->moov_size += 8;
 
     ngx_mp4_set_32value(mp4->moov_atom_header, mp4->moov_size);
@@ -782,7 +822,7 @@ ngx_http_mp4_process(ngx_http_mp4_file_t
     }
 
     adjustment = mp4->ftyp_size + mp4->moov_size
-                 + ngx_http_mp4_update_mdat_atom(mp4, start_offset)
+                 + ngx_http_mp4_update_mdat_atom(mp4, start_offset, end_offset)
                  - start_offset;
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
@@ -1026,7 +1066,7 @@ ngx_http_mp4_read_moov_atom(ngx_http_mp4
 
     no_mdat = (mp4->mdat_atom.buf == NULL);
 
-    if (no_mdat && mp4->start == 0) {
+    if (no_mdat && mp4->start == 0 && mp4->length == 0) {
         /*
          * send original file if moov atom resides before
          * mdat atom and client requests integral file
@@ -1125,7 +1165,8 @@ ngx_http_mp4_read_mdat_atom(ngx_http_mp4
 
 
 static size_t
-ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset)
+ngx_http_mp4_update_mdat_atom(ngx_http_mp4_file_t *mp4, off_t start_offset,
+    off_t end_offset)
 {
     off_t       atom_data_size;
     u_char     *atom_header;
@@ -1133,8 +1174,9 @@ ngx_http_mp4_update_mdat_atom(ngx_http_m
     uint64_t    atom_size;
     ngx_buf_t  *atom;
 
-    atom_data_size = mp4->mdat_data.buf->file_last - start_offset;
+    atom_data_size = end_offset - start_offset;
     mp4->mdat_data.buf->file_pos = start_offset;
+    mp4->mdat_data.buf->file_last = end_offset;
 
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                    "mdat new offset @%O:%O", start_offset, atom_data_size);
@@ -1216,7 +1258,7 @@ ngx_http_mp4_read_mvhd_atom(ngx_http_mp4
     u_char                 *atom_header;
     size_t                  atom_size;
     uint32_t                timescale;
-    uint64_t                duration, start_time;
+    uint64_t                duration, start_time, length_time;
     ngx_buf_t              *atom;
     ngx_mp4_mvhd_atom_t    *mvhd_atom;
     ngx_mp4_mvhd64_atom_t  *mvhd64_atom;
@@ -1270,6 +1312,14 @@ ngx_http_mp4_read_mvhd_atom(ngx_http_mp4
 
     duration -= start_time;
 
+    if (mp4->length) {
+        length_time = (uint64_t) mp4->length * timescale / 1000;
+
+        if (duration > length_time) {
+            duration = length_time;
+        }
+    }
+
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                    "mvhd new duration:%uL, time:%.3fs",
                    duration, (double) duration / timescale);
@@ -1415,7 +1465,7 @@ ngx_http_mp4_read_tkhd_atom(ngx_http_mp4
 {
     u_char                 *atom_header;
     size_t                  atom_size;
-    uint64_t                duration, start_time;
+    uint64_t                duration, start_time, length_time;
     ngx_buf_t              *atom;
     ngx_http_mp4_trak_t    *trak;
     ngx_mp4_tkhd_atom_t    *tkhd_atom;
@@ -1465,6 +1515,14 @@ ngx_http_mp4_read_tkhd_atom(ngx_http_mp4
 
     duration -= start_time;
 
+    if (mp4->length) {
+        length_time = (uint64_t) mp4->length * mp4->timescale / 1000;
+
+        if (duration > length_time) {
+            duration = length_time;
+        }
+    }
+
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                    "tkhd new duration:%uL, time:%.3fs",
                    duration, (double) duration / mp4->timescale);
@@ -1566,7 +1624,7 @@ ngx_http_mp4_read_mdhd_atom(ngx_http_mp4
     u_char                 *atom_header;
     size_t                  atom_size;
     uint32_t                timescale;
-    uint64_t                duration, start_time;
+    uint64_t                duration, start_time, length_time;
     ngx_buf_t              *atom;
     ngx_http_mp4_trak_t    *trak;
     ngx_mp4_mdhd_atom_t    *mdhd_atom;
@@ -1618,6 +1676,14 @@ ngx_http_mp4_read_mdhd_atom(ngx_http_mp4
 
     duration -= start_time;
 
+    if (mp4->length) {
+        length_time = (uint64_t) mp4->length * timescale / 1000;
+
+        if (duration > length_time) {
+            duration = length_time;
+        }
+    }
+
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                    "mdhd new duration:%uL, time:%.3fs",
                    duration, (double) duration / timescale);
@@ -2010,7 +2076,11 @@ ngx_http_mp4_update_stts_atom(ngx_http_m
         return NGX_ERROR;
     }
 
-    if (ngx_http_mp4_crop_stts_data(mp4, trak) != NGX_OK) {
+    if (ngx_http_mp4_crop_stts_data(mp4, trak, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_mp4_crop_stts_data(mp4, trak, 0) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -2028,17 +2098,27 @@ ngx_http_mp4_update_stts_atom(ngx_http_m
 
 static ngx_int_t
 ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4,
-    ngx_http_mp4_trak_t *trak)
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
 {
-    uint32_t               count, duration;
+    uint32_t               count, duration, rest;
     uint64_t               start_time;
     ngx_buf_t             *data;
-    ngx_uint_t             start_sample, entries;
+    ngx_uint_t             start_sample, entries, start_sec;
     ngx_mp4_stts_entry_t  *entry, *end;
 
+    if (start) {
+        start_sec = mp4->start;
+
+    } else if (mp4->length) {
+        start_sec = mp4->length;
+
+    } else {
+        return NGX_OK;
+    }
+
     data = trak->out[NGX_HTTP_MP4_STTS_DATA].buf;
 
-    start_time = (uint64_t) mp4->start * trak->timescale / 1000;
+    start_time = (uint64_t) start_sec * trak->timescale / 1000;
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                    "time-to-sample start_time:%uL", start_time);
@@ -2057,8 +2137,7 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4
 
         if (start_time < (uint64_t) count * duration) {
             start_sample += (ngx_uint_t) (start_time / duration);
-            count -= (uint32_t) (start_time / duration);
-            ngx_mp4_set_32value(entry->count, count);
+            rest = (uint32_t) (start_time / duration);
             goto found;
         }
 
@@ -2079,9 +2158,18 @@ found:
     ngx_log_debug2(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                    "start_sample:%ui, new count:%uD", start_sample, count);
 
-    trak->time_to_sample_entries = entries;
-    trak->start_sample = start_sample;
-    data->pos = (u_char *) entry;
+    if (start) {
+        ngx_mp4_set_32value(entry->count, count - rest);
+        data->pos = (u_char *) entry;
+        trak->time_to_sample_entries = entries;
+        trak->start_sample = start_sample;
+
+    } else {
+        ngx_mp4_set_32value(entry->count, rest);
+        data->last = (u_char *) (entry + 1);
+        trak->time_to_sample_entries -= entries - 1;
+        trak->end_sample = trak->start_sample + start_sample;
+    }
 
     return NGX_OK;
 }
@@ -2182,7 +2270,8 @@ ngx_http_mp4_update_stss_atom(ngx_http_m
         return NGX_OK;
     }
 
-    ngx_http_mp4_crop_stss_data(mp4, trak);
+    ngx_http_mp4_crop_stss_data(mp4, trak, 1);
+    ngx_http_mp4_crop_stss_data(mp4, trak, 0);
 
     entry = (uint32_t *) data->pos;
     end = (uint32_t *) data->last;
@@ -2211,18 +2300,27 @@ ngx_http_mp4_update_stss_atom(ngx_http_m
 
 static void
 ngx_http_mp4_crop_stss_data(ngx_http_mp4_file_t *mp4,
-    ngx_http_mp4_trak_t *trak)
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
 {
     uint32_t     sample, start_sample, *entry, *end;
     ngx_buf_t   *data;
     ngx_uint_t   entries;
 
+    /* sync samples starts from 1 */
+
+    if (start) {
+        start_sample = trak->start_sample + 1;
+
+    } else if (mp4->length) {
+        start_sample = trak->end_sample + 1;
+
+    } else {
+        return;
+    }
+
     data = trak->out[NGX_HTTP_MP4_STSS_DATA].buf;
 
-    /* sync samples starts from 1 */
-    start_sample = trak->start_sample + 1;
     entries = trak->sync_samples_entries;
-
     entry = (uint32_t *) data->pos;
     end = (uint32_t *) data->last;
 
@@ -2245,8 +2343,14 @@ ngx_http_mp4_crop_stss_data(ngx_http_mp4
 
 found:
 
-    data->pos = (u_char *) entry;
-    trak->sync_samples_entries = entries;
+    if (start) {
+        data->pos = (u_char *) entry;
+        trak->sync_samples_entries = entries;
+
+    } else {
+        data->last = (u_char *) entry;
+        trak->sync_samples_entries -= entries;
+    }
 }
 
 
@@ -2349,7 +2453,8 @@ ngx_http_mp4_update_ctts_atom(ngx_http_m
         return;
     }
 
-    ngx_http_mp4_crop_ctts_data(mp4, trak);
+    ngx_http_mp4_crop_ctts_data(mp4, trak, 1);
+    ngx_http_mp4_crop_ctts_data(mp4, trak, 0);
 
     if (trak->composition_offset_entries == 0) {
         trak->out[NGX_HTTP_MP4_CTTS_ATOM].buf = NULL;
@@ -2372,17 +2477,27 @@ ngx_http_mp4_update_ctts_atom(ngx_http_m
 
 static void
 ngx_http_mp4_crop_ctts_data(ngx_http_mp4_file_t *mp4,
-    ngx_http_mp4_trak_t *trak)
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
 {
-    uint32_t               count, start_sample;
+    uint32_t               count, start_sample, rest;
     ngx_buf_t             *data;
     ngx_uint_t             entries;
     ngx_mp4_ctts_entry_t  *entry, *end;
 
+    /* sync samples starts from 1 */
+
+    if (start) {
+        start_sample = trak->start_sample + 1;
+
+    } else if (mp4->length) {
+        start_sample = trak->end_sample - trak->start_sample + 1;
+
+    } else {
+        return;
+    }
+
     data = trak->out[NGX_HTTP_MP4_CTTS_DATA].buf;
 
-    /* sync samples starts from 1 */
-    start_sample = trak->start_sample + 1;
     entries = trak->composition_offset_entries;
     entry = (ngx_mp4_ctts_entry_t *) data->pos;
     end = (ngx_mp4_ctts_entry_t *) data->last;
@@ -2395,8 +2510,7 @@ ngx_http_mp4_crop_ctts_data(ngx_http_mp4
                        start_sample, count, ngx_mp4_get_32value(entry->offset));
 
          if (start_sample <= count) {
-             count -= (start_sample - 1);
-             ngx_mp4_set_32value(entry->count, count);
+             rest = start_sample - 1;
              goto found;
          }
 
@@ -2405,15 +2519,25 @@ ngx_http_mp4_crop_ctts_data(ngx_http_mp4
          entry++;
     }
 
-    data->pos = (u_char *) end;
-    trak->composition_offset_entries = 0;
+    if (start) {
+        data->pos = (u_char *) end;
+        trak->composition_offset_entries = 0;
+    }
 
     return;
 
 found:
 
-    data->pos = (u_char *) entry;
-    trak->composition_offset_entries = entries;
+    if (start) {
+        ngx_mp4_set_32value(entry->count, count - rest);
+        data->pos = (u_char *) entry;
+        trak->composition_offset_entries = entries;
+
+    } else {
+        ngx_mp4_set_32value(entry->count, rest);
+        data->last = (u_char *) (entry + 1);
+        trak->composition_offset_entries -= entries - 1;
+    }
 }
 
 
@@ -2522,7 +2646,11 @@ ngx_http_mp4_update_stsc_atom(ngx_http_m
         return NGX_ERROR;
     }
 
-    if (ngx_http_mp4_crop_stsc_data(mp4, trak) != NGX_OK) {
+    if (ngx_http_mp4_crop_stsc_data(mp4, trak, 1) != NGX_OK) {
+        return NGX_ERROR;
+    }
+
+    if (ngx_http_mp4_crop_stsc_data(mp4, trak, 0) != NGX_OK) {
         return NGX_ERROR;
     }
 
@@ -2553,24 +2681,53 @@ ngx_http_mp4_update_stsc_atom(ngx_http_m
 
 static ngx_int_t
 ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4,
-    ngx_http_mp4_trak_t *trak)
+    ngx_http_mp4_trak_t *trak, ngx_uint_t start)
 {
-    uint32_t               start_sample, chunk, samples, id, next_chunk, n;
+    uint32_t               start_sample, chunk, samples, id, next_chunk, n,
+                           prev_samples;
     ngx_buf_t             *data, *buf;
     ngx_uint_t             entries, target_chunk, chunk_samples;
     ngx_mp4_stsc_entry_t  *entry, *end, *first;
 
+    entries = trak->sample_to_chunk_entries - 1;
+
+    if (start) {
+        start_sample = (uint32_t) trak->start_sample;
+
+    } else if (mp4->length) {
+        start_sample = (uint32_t) (trak->end_sample - trak->start_sample);
+
+        data = trak->out[NGX_HTTP_MP4_STSC_START].buf;
+
+        if (data) {
+            entry = (ngx_mp4_stsc_entry_t *) data->pos;
+            samples = ngx_mp4_get_32value(entry->samples);
+            entries--;
+
+            if (samples > start_sample) {
+                samples = start_sample;
+                ngx_mp4_set_32value(entry->samples, samples);
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                           "mp4 stsc using %uD start samples", samples);
+
+            start_sample -= samples;
+        }
+
+    } else {
+        return NGX_OK;
+    }
+
     data = trak->out[NGX_HTTP_MP4_STSC_DATA].buf;
 
-    start_sample = (uint32_t) trak->start_sample;
-    entries = trak->sample_to_chunk_entries - 1;
-
     entry = (ngx_mp4_stsc_entry_t *) data->pos;
     end = (ngx_mp4_stsc_entry_t *) data->last;
 
     chunk = ngx_mp4_get_32value(entry->chunk);
     samples = ngx_mp4_get_32value(entry->samples);
     id = ngx_mp4_get_32value(entry->id);
+    prev_samples = 0;
     entry++;
 
     while (entry < end) {
@@ -2590,6 +2747,7 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4
 
         start_sample -= n;
 
+        prev_samples = samples;
         chunk = next_chunk;
         samples = ngx_mp4_get_32value(entry->samples);
         id = ngx_mp4_get_32value(entry->id);
@@ -2632,40 +2790,80 @@ found:
                    "start chunk:%ui, samples:%uD",
                    target_chunk, chunk_samples);
 
-    data->pos = (u_char *) entry;
-
-    trak->start_chunk = target_chunk;
-    trak->chunk_samples = chunk_samples;
-
-    ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 1);
-
-    samples -= chunk_samples;
+    if (start) {
+        data->pos = (u_char *) entry;
+
+        trak->sample_to_chunk_entries = entries;
+        trak->start_chunk = target_chunk;
+        trak->start_chunk_samples = chunk_samples;
+
+        ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 1);
+
+        samples -= chunk_samples;
+
+    } else {
+        if (start_sample) {
+            data->last = (u_char *) (entry + 1);
+            trak->sample_to_chunk_entries -= entries - 1;
+            trak->end_chunk_samples = samples;
+
+        } else {
+            data->last = (u_char *) entry;
+            trak->sample_to_chunk_entries -= entries;
+            trak->end_chunk_samples = prev_samples;
+        }
+
+        if (chunk_samples) {
+            trak->end_chunk = target_chunk + 1;
+            trak->end_chunk_samples = chunk_samples;
+
+        } else {
+            trak->end_chunk = target_chunk;
+        }
+
+        samples = chunk_samples;
+        next_chunk = chunk + 1;
+    }
 
     if (chunk_samples && next_chunk - target_chunk == 2) {
 
         ngx_mp4_set_32value(entry->samples, samples);
 
-    } else if (chunk_samples) {
-
-        first = &trak->stsc_chunk_entry;
+    } else if (chunk_samples && start) {
+
+        first = &trak->stsc_start_chunk_entry;
         ngx_mp4_set_32value(first->chunk, 1);
         ngx_mp4_set_32value(first->samples, samples);
         ngx_mp4_set_32value(first->id, id);
 
-        buf = &trak->stsc_chunk_buf;
+        buf = &trak->stsc_start_chunk_buf;
         buf->temporary = 1;
         buf->pos = (u_char *) first;
         buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t);
 
-        trak->out[NGX_HTTP_MP4_STSC_CHUNK].buf = buf;
+        trak->out[NGX_HTTP_MP4_STSC_START].buf = buf;
 
         ngx_mp4_set_32value(entry->chunk, trak->start_chunk + 2);
 
-        entries++;
+        trak->sample_to_chunk_entries++;
+
+    } else if (chunk_samples) {
+
+        first = &trak->stsc_end_chunk_entry;
+        ngx_mp4_set_32value(first->chunk, trak->end_chunk - trak->start_chunk);
+        ngx_mp4_set_32value(first->samples, samples);
+        ngx_mp4_set_32value(first->id, id);
+
+        buf = &trak->stsc_end_chunk_buf;
+        buf->temporary = 1;
+        buf->pos = (u_char *) first;
+        buf->last = (u_char *) first + sizeof(ngx_mp4_stsc_entry_t);
+
+        trak->out[NGX_HTTP_MP4_STSC_END].buf = buf;
+
+        trak->sample_to_chunk_entries++;
     }
 
-    trak->sample_to_chunk_entries = entries;
-
     return NGX_OK;
 }
 
@@ -2760,7 +2958,7 @@ ngx_http_mp4_update_stsz_atom(ngx_http_m
     ngx_http_mp4_trak_t *trak)
 {
     size_t                atom_size;
-    uint32_t             *pos, *end;
+    uint32_t             *pos, *end, entries;
     ngx_buf_t            *atom, *data;
     ngx_mp4_stsz_atom_t  *stsz_atom;
 
@@ -2776,22 +2974,47 @@ ngx_http_mp4_update_stsz_atom(ngx_http_m
     data = trak->out[NGX_HTTP_MP4_STSZ_DATA].buf;
 
     if (data) {
-        if (trak->start_sample > trak->sample_sizes_entries) {
+        entries = trak->sample_sizes_entries;
+
+        if (trak->start_sample > entries) {
             ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
                           "start time is out mp4 stsz samples in \"%s\"",
                           mp4->file.name.data);
             return NGX_ERROR;
         }
 
+        entries -= trak->start_sample;
         data->pos += trak->start_sample * sizeof(uint32_t);
         end = (uint32_t *) data->pos;
 
-        for (pos = end - trak->chunk_samples; pos < end; pos++) {
-            trak->chunk_samples_size += ngx_mp4_get_32value(pos);
+        for (pos = end - trak->start_chunk_samples; pos < end; pos++) {
+            trak->start_chunk_samples_size += ngx_mp4_get_32value(pos);
         }
 
         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
-                       "chunk samples sizes:%uL", trak->chunk_samples_size);
+                       "chunk samples sizes:%uL",
+                       trak->start_chunk_samples_size);
+
+        if (mp4->length) {
+            if (trak->end_sample - trak->start_sample > entries) {
+                ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                              "end time is out mp4 stsz samples in \"%s\"",
+                              mp4->file.name.data);
+                return NGX_ERROR;
+            }
+
+            entries = trak->end_sample - trak->start_sample;
+            data->last = data->pos + entries * sizeof(uint32_t);
+            end = (uint32_t *) data->last;
+
+            for (pos = end - trak->end_chunk_samples; pos < end; pos++) {
+                trak->end_chunk_samples_size += ngx_mp4_get_32value(pos);
+            }
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                           "mp4 stsz end_chunk_samples_size:%uL",
+                           trak->end_chunk_samples_size);
+        }
 
         atom_size = sizeof(ngx_mp4_stsz_atom_t) + (data->last - data->pos);
         trak->size += atom_size;
@@ -2800,8 +3023,7 @@ ngx_http_mp4_update_stsz_atom(ngx_http_m
         stsz_atom = (ngx_mp4_stsz_atom_t *) atom->pos;
 
         ngx_mp4_set_32value(stsz_atom->size, atom_size);
-        ngx_mp4_set_32value(stsz_atom->entries,
-                            trak->sample_sizes_entries - trak->start_sample);
+        ngx_mp4_set_32value(stsz_atom->entries, entries);
     }
 
     return NGX_OK;
@@ -2882,6 +3104,7 @@ ngx_http_mp4_update_stco_atom(ngx_http_m
     ngx_http_mp4_trak_t *trak)
 {
     size_t                atom_size;
+    uint32_t              entries;
     ngx_buf_t            *atom, *data;
     ngx_mp4_stco_atom_t  *stco_atom;
 
@@ -2911,21 +3134,53 @@ ngx_http_mp4_update_stco_atom(ngx_http_m
     }
 
     data->pos += trak->start_chunk * sizeof(uint32_t);
-    atom_size = sizeof(ngx_mp4_stco_atom_t) + (data->last - data->pos);
-    trak->size += atom_size;
 
     trak->start_offset = ngx_mp4_get_32value(data->pos);
-    trak->start_offset += trak->chunk_samples_size;
+    trak->start_offset += trak->start_chunk_samples_size;
     ngx_mp4_set_32value(data->pos, trak->start_offset);
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                    "start chunk offset:%uD", trak->start_offset);
 
+    if (mp4->length) {
+
+        if (trak->end_chunk > trak->chunks) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "end time is out mp4 stco chunks in \"%s\"",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        entries = trak->end_chunk - trak->start_chunk;
+        data->last = data->pos + entries * sizeof(uint32_t);
+
+        if (entries) {
+            trak->end_offset =
+                            ngx_mp4_get_32value(data->last - sizeof(uint32_t));
+            trak->end_offset += trak->end_chunk_samples_size;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                           "end chunk offset:%O", trak->end_offset);
+        }
+
+    } else {
+        entries = trak->chunks - trak->start_chunk;
+        trak->end_offset = mp4->mdat_data.buf->file_last;
+    }
+
+    if (entries == 0) {
+        trak->start_offset = mp4->end;
+        trak->end_offset = 0;
+    }
+
+    atom_size = sizeof(ngx_mp4_stco_atom_t) + (data->last - data->pos);
+    trak->size += atom_size;
+
     atom = trak->out[NGX_HTTP_MP4_STCO_ATOM].buf;
     stco_atom = (ngx_mp4_stco_atom_t *) atom->pos;
 
     ngx_mp4_set_32value(stco_atom->size, atom_size);
-    ngx_mp4_set_32value(stco_atom->entries, trak->chunks - trak->start_chunk);
+    ngx_mp4_set_32value(stco_atom->entries, entries);
 
     return NGX_OK;
 }
@@ -3033,6 +3288,7 @@ ngx_http_mp4_update_co64_atom(ngx_http_m
     ngx_http_mp4_trak_t *trak)
 {
     size_t                atom_size;
+    uint64_t              entries;
     ngx_buf_t            *atom, *data;
     ngx_mp4_co64_atom_t  *co64_atom;
 
@@ -3062,21 +3318,53 @@ ngx_http_mp4_update_co64_atom(ngx_http_m
     }
 
     data->pos += trak->start_chunk * sizeof(uint64_t);
-    atom_size = sizeof(ngx_mp4_co64_atom_t) + (data->last - data->pos);
-    trak->size += atom_size;
 
     trak->start_offset = ngx_mp4_get_64value(data->pos);
-    trak->start_offset += trak->chunk_samples_size;
+    trak->start_offset += trak->start_chunk_samples_size;
     ngx_mp4_set_64value(data->pos, trak->start_offset);
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
                    "start chunk offset:%uL", trak->start_offset);
 
+    if (mp4->length) {
+
+        if (trak->end_chunk > trak->chunks) {
+            ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0,
+                          "end time is out mp4 co64 chunks in \"%s\"",
+                          mp4->file.name.data);
+            return NGX_ERROR;
+        }
+
+        entries = trak->end_chunk - trak->start_chunk;
+        data->last = data->pos + entries * sizeof(uint64_t);
+
+        if (entries) {
+            trak->end_offset =
+                            ngx_mp4_get_64value(data->last - sizeof(uint64_t));
+            trak->end_offset += trak->end_chunk_samples_size;
+
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0,
+                           "end chunk offset:%O", trak->end_offset);
+        }
+
+    } else {
+        entries = trak->chunks - trak->start_chunk;
+        trak->end_offset = mp4->mdat_data.buf->file_last;
+    }
+
+    if (entries == 0) {
+        trak->start_offset = mp4->end;
+        trak->end_offset = 0;
+    }
+
+    atom_size = sizeof(ngx_mp4_co64_atom_t) + (data->last - data->pos);
+    trak->size += atom_size;
+
     atom = trak->out[NGX_HTTP_MP4_CO64_ATOM].buf;
     co64_atom = (ngx_mp4_co64_atom_t *) atom->pos;
 
     ngx_mp4_set_32value(co64_atom->size, atom_size);
-    ngx_mp4_set_32value(co64_atom->entries, trak->chunks - trak->start_chunk);
+    ngx_mp4_set_32value(co64_atom->entries, entries);
 
     return NGX_OK;
 }