static ngx_int_t
ngx_rtmp_cmd_fcsubscribe_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    static ngx_rtmp_fcsubscribe_t   v;

    static ngx_rtmp_amf_elt_t       in_elts[] = {

        /* transaction is always 0 */
        { NGX_RTMP_AMF_NUMBER, 
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_STRING,
          ngx_null_string,
          &v.name, sizeof(v.name) },

    };

    ngx_memzero(&v, sizeof(v));

    /* parse input */
    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        return NGX_ERROR;
    }

    return ngx_rtmp_fcsubscribe
        ? ngx_rtmp_fcsubscribe(s, &v)
        : NGX_OK;
}
static ngx_int_t
ngx_rtmp_cmd_close_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    static ngx_rtmp_close_stream_t     v;

    static ngx_rtmp_amf_elt_t  in_elts[] = {

        { NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.stream, 0 },
    };

    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        return NGX_ERROR;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
            "closeStream");

    return ngx_rtmp_close_stream
        ? ngx_rtmp_close_stream(s, &v)
        : NGX_OK;
}
static ngx_int_t
ngx_rtmp_cmd_delete_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    static ngx_rtmp_delete_stream_t     v;

    static ngx_rtmp_amf_elt_t  in_elts[] = {

        { NGX_RTMP_AMF_NUMBER, 
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_NULL,
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.stream, 0 },
    };

    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        return NGX_ERROR;
    }

    return ngx_rtmp_delete_stream
        ? ngx_rtmp_delete_stream(s, &v)
        : NGX_OK;
}
static ngx_int_t
ngx_rtmp_cmd_seek_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    static ngx_rtmp_seek_t         v;

    static ngx_rtmp_amf_elt_t      in_elts[] = {

        /* transaction is always 0 */
        { NGX_RTMP_AMF_NUMBER, 
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_NULL,
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.offset, sizeof(v.offset) },
    };

    ngx_memzero(&v, sizeof(v));

    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                             sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        return NGX_ERROR;
    }

    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                  "seek: offset=%i", (ngx_int_t) v.offset);

    return ngx_rtmp_seek(s, &v);
}
static ngx_int_t
ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                       ngx_chain_t *in)
{
    static ngx_rtmp_play_t          v;

    static ngx_rtmp_amf_elt_t       in_elts[] = {

        /* transaction is always 0 */
        {   NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            NULL, 0
        },

        {   NGX_RTMP_AMF_NULL,
            ngx_null_string,
            NULL, 0
        },

        {   NGX_RTMP_AMF_STRING,
            ngx_null_string,
            &v.name, sizeof(v.name)
        },

        {   NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            &v.start, 0
        },

        {   NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            &v.duration, 0
        },

        {   NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_BOOLEAN,
            ngx_null_string,
            &v.reset, 0
        }
    };

    ngx_memzero(&v, sizeof(v));

    if (ngx_rtmp_receive_amf(s, in, in_elts,
                             sizeof(in_elts) / sizeof(in_elts[0])))
    {
        return NGX_ERROR;
    }

    ngx_rtmp_cmd_fill_args(v.name, v.args);

    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                  "play: name='%s' args='%s' start=%i duration=%i "
                  "reset=%i silent=%i",
                  v.name, v.args, (ngx_int_t) v.start,
                  (ngx_int_t) v.duration, (ngx_int_t) v.reset,
                  (ngx_int_t) v.silent);

    return ngx_rtmp_play(s, &v);
}
static ngx_int_t
ngx_rtmp_live_on_fcunpublish(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                           ngx_chain_t *in)
{

    ngx_rtmp_live_app_conf_t       *lacf;

    static struct {
        double                  trans;
        u_char                  action[128];
        u_char                  stream[1024];
    } v;

    static ngx_rtmp_amf_elt_t   in_elts[] = {

//          Already readed
/*        { NGX_RTMP_AMF_STRING,
          ngx_null_string,
          &v.action, sizeof(v.action) },
*/
        { NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.trans, 0 },

        { NGX_RTMP_AMF_NULL,
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_STRING,
          ngx_null_string,
          &v.stream, sizeof(v.stream) },
    };

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
                       "live: FCUnpublish - no live config!");
        return NGX_ERROR;
    }

    if (!lacf->live || in == NULL  || in->buf == NULL) {
        ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
                       "live: FCUnpublish - no live or no buffer!");
        return NGX_OK;
    }

    ngx_memzero(&v, sizeof(v));
    ngx_rtmp_receive_amf(s, in, in_elts,
            sizeof(in_elts) / sizeof(in_elts[0]));

    ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
            "live: onFCUnpublish: stream='%s'",
            v.stream);

    return ngx_rtmp_send_fcunpublish(s, v.stream);
}
static ngx_int_t
ngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    static ngx_rtmp_play_t          v;

    static ngx_rtmp_amf_elt_t      in_elts[] = {

        /* transaction is always 0 */
        { NGX_RTMP_AMF_NUMBER, 
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_NULL,
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_STRING,
          ngx_null_string,
          &v.name, sizeof(v.name) },

        { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.start, 0 },

        { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.duration, 0 },

        { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_BOOLEAN,
          ngx_null_string,
          &v.reset, 0 }
    };

    ngx_memzero(&v, sizeof(v));

    /* parse input */
    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        return NGX_ERROR;
    }

    ngx_rtmp_cmd_fill_args(v.name, v.args);

    return ngx_rtmp_play
        ? ngx_rtmp_play(s, &v)
        : NGX_OK;
}
static ngx_int_t
ngx_rtmp_cmd_publish_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                          ngx_chain_t *in)
{
    static ngx_rtmp_publish_t       v;

    static ngx_rtmp_amf_elt_t      in_elts[] = {

        /* transaction is always 0 */
        {   NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            NULL, 0
        },

        {   NGX_RTMP_AMF_NULL,
            ngx_null_string,
            NULL, 0
        },

        {   NGX_RTMP_AMF_STRING,
            ngx_null_string,
            &v.name, sizeof(v.name)
        },

        {   NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_STRING,
            ngx_null_string,
            &v.type, sizeof(v.type)
        },
    };

    ngx_memzero(&v, sizeof(v));

    if (ngx_rtmp_receive_amf(s, in, in_elts,
                             sizeof(in_elts) / sizeof(in_elts[0])))
    {
        return NGX_ERROR;
    }

    ngx_rtmp_cmd_fill_args(v.name, v.args);

    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                  "publish: name='%s' args='%s' type=%s silent=%d",
                  v.name, v.args, v.type, v.silent);

    return ngx_rtmp_publish(s, &v);
}
static ngx_int_t
ngx_rtmp_cmd_pause_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                        ngx_chain_t *in)
{
    static ngx_rtmp_pause_t     v;

    static ngx_rtmp_amf_elt_t   in_elts[] = {

        {   NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            NULL, 0
        },

        {   NGX_RTMP_AMF_NULL,
            ngx_null_string,
            NULL, 0
        },

        {   NGX_RTMP_AMF_BOOLEAN,
            ngx_null_string,
            &v.pause, 0
        },

        {   NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            &v.position, 0
        },
    };

    ngx_memzero(&v, sizeof(v));

    if (ngx_rtmp_receive_amf(s, in, in_elts,
                             sizeof(in_elts) / sizeof(in_elts[0])))
    {
        return NGX_ERROR;
    }

    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                   "pause: pause=%i position=%i",
                   (ngx_int_t) v.pause, (ngx_int_t) v.position);

    return ngx_rtmp_pause(s, &v);
}
static ngx_int_t
ngx_rtmp_cmd_create_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                                ngx_chain_t *in)
{
    static ngx_rtmp_create_stream_t     v;

    static ngx_rtmp_amf_elt_t  in_elts[] = {

        { NGX_RTMP_AMF_NUMBER, 
          ngx_null_string,      
          &v.trans, sizeof(v.trans) },
    };

    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        return NGX_ERROR;
    }

    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, "createStream");

    return ngx_rtmp_create_stream(s, &v);
}
static ngx_int_t
ngx_rtmp_cmd_connect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    size_t                      len;

    static ngx_rtmp_connect_t   v;

    static ngx_rtmp_amf_elt_t  in_cmd[] = {

        { NGX_RTMP_AMF_STRING, 
          ngx_string("app"),
          v.app, sizeof(v.app) },

        { NGX_RTMP_AMF_STRING, 
          ngx_string("flashVer"),
          v.flashver, sizeof(v.flashver) },

        { NGX_RTMP_AMF_STRING,
          ngx_string("swfUrl"),
          v.swf_url, sizeof(v.swf_url) },

        { NGX_RTMP_AMF_STRING,
          ngx_string("tcUrl"),
          v.tc_url, sizeof(v.tc_url) },

        { NGX_RTMP_AMF_NUMBER,
          ngx_string("audioCodecs"),
          &v.acodecs, sizeof(v.acodecs) },

        { NGX_RTMP_AMF_NUMBER,
          ngx_string("videoCodecs"),
          &v.vcodecs, sizeof(v.vcodecs) },

        { NGX_RTMP_AMF_STRING,
          ngx_string("pageUrl"),
          v.page_url, sizeof(v.page_url) },

        { NGX_RTMP_AMF_NUMBER,
          ngx_string("objectEncoding"),
          &v.object_encoding, 0},
    };

    static ngx_rtmp_amf_elt_t  in_elts[] = {

        { NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.trans, 0 },

        { NGX_RTMP_AMF_OBJECT,
          ngx_null_string,
          in_cmd, sizeof(in_cmd) },
    };

    ngx_memzero(&v, sizeof(v));
    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        return NGX_ERROR;
    }

    len = ngx_strlen(v.app);
    if (len && v.app[len - 1] == '/') {
        v.app[len - 1] = 0;
    }

    return ngx_rtmp_connect 
        ? ngx_rtmp_connect(s, &v)
        : NGX_OK;
}
static ngx_int_t
ngx_rtmp_relay_on_result(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    ngx_rtmp_relay_ctx_t       *ctx;
    static struct {
        double                  trans;
        u_char                  level[32];
        u_char                  code[128];
        u_char                  desc[1024];
    } v;

    static ngx_rtmp_amf_elt_t   in_inf[] = {

        { NGX_RTMP_AMF_STRING, 
          ngx_string("level"),
          &v.level, sizeof(v.level) },

        { NGX_RTMP_AMF_STRING, 
          ngx_string("code"),
          &v.code, sizeof(v.code) },

        { NGX_RTMP_AMF_STRING,
          ngx_string("description"),
          &v.desc, sizeof(v.desc) },
    };

    static ngx_rtmp_amf_elt_t   in_elts[] = {

        { NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.trans, 0 },

        { NGX_RTMP_AMF_NULL,
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_OBJECT,
          ngx_null_string,
          in_inf, sizeof(in_inf) },
    };


    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
    if (ctx == NULL || !ctx->relay) {
        return NGX_OK;
    }

    ngx_memzero(&v, sizeof(v));
    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        return NGX_ERROR;
    }

    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 
            "relay: _result: level='%s' code='%s' description='%s'",
            v.level, v.code, v.desc);

    switch ((ngx_int_t)v.trans) {
        case NGX_RTMP_RELAY_CONNECT_TRANS:
            return ngx_rtmp_relay_send_create_stream(s);

        case NGX_RTMP_RELAY_CREATE_STREAM_TRANS:
            if (ctx->publish != ctx) {
                if (ngx_rtmp_relay_send_publish(s) != NGX_OK) {
                    return NGX_ERROR;
                }
                return ngx_rtmp_relay_play_local(s);

            } else {
                if (ngx_rtmp_relay_send_play(s) != NGX_OK) {
                    return NGX_ERROR;
                }
                return ngx_rtmp_relay_publish_local(s);
            }

        default:
            return NGX_OK;
    }
}
static ngx_int_t
ngx_rtmp_relay_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
        ngx_chain_t *in)
{
    ngx_rtmp_relay_ctx_t       *ctx;
    static struct {
        double                  trans;
        u_char                  level[32];
        u_char                  code[128];
        u_char                  desc[1024];
    } v;

    static ngx_rtmp_amf_elt_t   in_inf[] = {

        { NGX_RTMP_AMF_STRING, 
          ngx_string("level"),
          &v.level, sizeof(v.level) },

        { NGX_RTMP_AMF_STRING, 
          ngx_string("code"),
          &v.code, sizeof(v.code) },

        { NGX_RTMP_AMF_STRING,
          ngx_string("description"),
          &v.desc, sizeof(v.desc) },
    };

    static ngx_rtmp_amf_elt_t   in_elts[] = {

        { NGX_RTMP_AMF_NUMBER,
          ngx_null_string,
          &v.trans, 0 },

        { NGX_RTMP_AMF_NULL,
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_OBJECT,
          ngx_null_string,
          in_inf, sizeof(in_inf) },
    };

    static ngx_rtmp_amf_elt_t   in_elts_meta[] = {

        { NGX_RTMP_AMF_OBJECT,
          ngx_null_string,
          in_inf, sizeof(in_inf) },
    };


    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);
    if (ctx == NULL || !ctx->relay) {
        return NGX_OK;
    }

    ngx_memzero(&v, sizeof(v));
    if (h->type == NGX_RTMP_MSG_AMF_META) {
        ngx_rtmp_receive_amf(s, in, in_elts_meta, 
                sizeof(in_elts_meta) / sizeof(in_elts_meta[0]));
    } else {
        ngx_rtmp_receive_amf(s, in, in_elts, 
                sizeof(in_elts) / sizeof(in_elts[0]));
    }

    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 
            "relay: onStatus: level='%s' code='%s' description='%s'",
            v.level, v.code, v.desc);

    return NGX_OK;
}
static ngx_int_t
ngx_rtmp_cmd_play2_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                        ngx_chain_t *in)
{
    static ngx_rtmp_play_t          v;
    static ngx_rtmp_close_stream_t  vc;

    static ngx_rtmp_amf_elt_t       in_obj[] = {

        {   NGX_RTMP_AMF_NUMBER,
            ngx_string("start"),
            &v.start, 0
        },

        {   NGX_RTMP_AMF_STRING,
            ngx_string("streamName"),
            &v.name, sizeof(v.name)
        },
    };

    static ngx_rtmp_amf_elt_t       in_elts[] = {

        /* transaction is always 0 */
        {   NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            NULL, 0
        },

        {   NGX_RTMP_AMF_NULL,
            ngx_null_string,
            NULL, 0
        },

        {   NGX_RTMP_AMF_OBJECT,
            ngx_null_string,
            &in_obj, sizeof(in_obj)
        }
    };

    ngx_memzero(&v, sizeof(v));

    if (ngx_rtmp_receive_amf(s, in, in_elts,
                             sizeof(in_elts) / sizeof(in_elts[0])))
    {
        return NGX_ERROR;
    }

    ngx_rtmp_cmd_fill_args(v.name, v.args);

    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                  "play2: name='%s' args='%s' start=%i",
                  v.name, v.args, (ngx_int_t) v.start);

    /* continue from current timestamp */

    if (v.start < 0) {
        v.start = s->current_time;
    }

    ngx_memzero(&vc, sizeof(vc));

    /* close_stream should be synchronous */
    ngx_rtmp_close_stream(s, &vc);

    return ngx_rtmp_play(s, &v);
}
static ngx_int_t
ngx_rtmp_cmd_connect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                          ngx_chain_t *in)
{
    size_t                      len;

    static ngx_rtmp_connect_t   v;

    static ngx_rtmp_amf_elt_t  in_cmd[] = {

        {   NGX_RTMP_AMF_STRING,
            ngx_string("app"),
            v.app, sizeof(v.app)
        },

        {   NGX_RTMP_AMF_STRING,
            ngx_string("flashVer"),
            v.flashver, sizeof(v.flashver)
        },

        {   NGX_RTMP_AMF_STRING,
            ngx_string("swfUrl"),
            v.swf_url, sizeof(v.swf_url)
        },

        {   NGX_RTMP_AMF_STRING,
            ngx_string("tcUrl"),
            v.tc_url, sizeof(v.tc_url)
        },

        {   NGX_RTMP_AMF_NUMBER,
            ngx_string("audioCodecs"),
            &v.acodecs, sizeof(v.acodecs)
        },

        {   NGX_RTMP_AMF_NUMBER,
            ngx_string("videoCodecs"),
            &v.vcodecs, sizeof(v.vcodecs)
        },

        {   NGX_RTMP_AMF_STRING,
            ngx_string("pageUrl"),
            v.page_url, sizeof(v.page_url)
        },

        {   NGX_RTMP_AMF_NUMBER,
            ngx_string("objectEncoding"),
            &v.object_encoding, 0
        },
    };

    static ngx_rtmp_amf_elt_t  in_elts[] = {

        {   NGX_RTMP_AMF_NUMBER,
            ngx_null_string,
            &v.trans, 0
        },

        {   NGX_RTMP_AMF_OBJECT,
            ngx_null_string,
            in_cmd, sizeof(in_cmd)
        },
    };

    ngx_memzero(&v, sizeof(v));
    if (ngx_rtmp_receive_amf(s, in, in_elts,
                             sizeof(in_elts) / sizeof(in_elts[0])))
    {
        return NGX_ERROR;
    }

    len = ngx_strlen(v.app);
    if (len > 10 && !ngx_memcmp(v.app + len - 10, "/_definst_", 10)) {
        v.app[len - 10] = 0;
    } else if (len && v.app[len - 1] == '/') {
        v.app[len - 1] = 0;
    }

    ngx_rtmp_cmd_fill_args(v.app, v.args);

    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,
                  "connect: app='%s' args='%s' flashver='%s' swf_url='%s' "
                  "tc_url='%s' page_url='%s' acodecs=%uD vcodecs=%uD "
                  "object_encoding=%ui",
                  v.app, v.args, v.flashver, v.swf_url, v.tc_url, v.page_url,
                  (uint32_t)v.acodecs, (uint32_t)v.vcodecs,
                  (ngx_int_t)v.object_encoding);

    return ngx_rtmp_connect(s, &v);
}
static ngx_int_t
ngx_rtmp_play_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in)
{
    ngx_rtmp_play_ctx_t            *ctx;

    static ngx_rtmp_amf_ctx_t       filepositions_ctx;
    static ngx_rtmp_amf_ctx_t       times_ctx;

    static ngx_rtmp_amf_elt_t       in_keyframes[] = {

        { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT, 
          ngx_string("filepositions"),
          &filepositions_ctx, 0 },

        { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT, 
          ngx_string("times"),
          &times_ctx, 0 }
    };

    static ngx_rtmp_amf_elt_t       in_inf[] = {

        { NGX_RTMP_AMF_OBJECT, 
          ngx_string("keyframes"),
          in_keyframes, sizeof(in_keyframes) }
    };

    static ngx_rtmp_amf_elt_t       in_elts[] = {

        { NGX_RTMP_AMF_STRING, 
          ngx_null_string,
          NULL, 0 },

        { NGX_RTMP_AMF_OBJECT, 
          ngx_null_string,
          in_inf, sizeof(in_inf) },
    };

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);
    if (ctx == NULL || in == NULL) {
        return NGX_OK;
    }

    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: init index");

    ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx));
    ngx_memzero(&times_ctx, sizeof(times_ctx));

    if (ngx_rtmp_receive_amf(s, in, in_elts, 
                             sizeof(in_elts) / sizeof(in_elts[0]))) 
    {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: init index error");
        return NGX_OK;
    }

    if (filepositions_ctx.link && ngx_rtmp_play_fill_index(&filepositions_ctx, 
                                                           &ctx->filepositions)
        != NGX_OK)
    {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: failed to init filepositions");
        return NGX_ERROR;
    }
    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: filepositions nelts=%ui offset=%ui",
                   ctx->filepositions.nelts, ctx->filepositions.offset);

    if (times_ctx.link && ngx_rtmp_play_fill_index(&times_ctx,
                                                   &ctx->times)
        != NGX_OK)
    {
        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
                     "play: failed to init times");
        return NGX_ERROR;
    }
    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,
                  "play: times nelts=%ui offset=%ui",
                   ctx->times.nelts, ctx->times.offset);

    return  NGX_OK;
}
static ngx_int_t
ngx_rtmp_live_on_fi(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,
                           ngx_chain_t *in)
{
    ngx_rtmp_live_app_conf_t       *lacf;
    ngx_int_t                       res;

    static struct {
        u_char                  time[NGX_TIME_T_LEN + 1];
        u_char                  date[NGX_TIME_T_LEN + 1];
    } v;

    static ngx_rtmp_amf_elt_t   in_dt_elts[] = {

        { NGX_RTMP_AMF_STRING,
          ngx_string("sd"),
          &v.date, sizeof(v.date) },

        { NGX_RTMP_AMF_STRING,
          ngx_string("st"),
          &v.time, sizeof(v.time) },

    };

    static ngx_rtmp_amf_elt_t   in_elts[] = {

        { NGX_RTMP_AMF_MIXED_ARRAY,
          ngx_null_string,
          in_dt_elts, sizeof(in_dt_elts) },
    };


    static ngx_rtmp_amf_elt_t   out_dt_elts[] = {

        { NGX_RTMP_AMF_STRING,
          ngx_string("sd"),
          NULL, 0 },

        { NGX_RTMP_AMF_STRING,
          ngx_string("st"),
          NULL, 0 },

    };

    static ngx_rtmp_amf_elt_t   out_elts[] = {

        { NGX_RTMP_AMF_STRING,
          ngx_null_string,
          "onFi", 0 },

        { NGX_RTMP_AMF_MIXED_ARRAY,
          ngx_null_string,
          out_dt_elts, sizeof(out_dt_elts) },
    };

    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);
    if (lacf == NULL) {
        ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
                       "live: Fi - no live config!");
        return NGX_ERROR;
    }

    if (!lacf->live || in == NULL  || in->buf == NULL) {
        ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
                       "live: Fi - no live or no buffer!");
        return NGX_OK;
    }

    ngx_memzero(&v, sizeof(v));
    res = ngx_rtmp_receive_amf(s, in, in_elts,
        sizeof(in_elts) / sizeof(in_elts[0]));

    if (res == NGX_OK) {

        ngx_log_error(NGX_LOG_DEBUG, s->connection->log, 0,
            "live: onFi: date='%s', time='%s'",
            v.date, v.time);

        out_dt_elts[0].data = v.date;
        out_dt_elts[1].data = v.time;

        // Pass through datetime from publisher
        return ngx_rtmp_live_data(s, h, in, out_elts,
            sizeof(out_elts) / sizeof(out_elts[0]));

    } else {
        // Send our server datetime
        return ngx_rtmp_send_fi(s);
    }
}