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_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_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_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 const char *
ngx_rtmp_control_redirect_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s,
    ngx_rtmp_core_srv_conf_t *cscf,
    ngx_rtmp_core_app_conf_t **cacf)
{
    ngx_str_t                 name;
    ngx_rtmp_play_t           vplay;
    ngx_rtmp_publish_t        vpublish;
    ngx_rtmp_live_ctx_t      *lctx;
    ngx_rtmp_control_ctx_t   *ctx;
    ngx_rtmp_close_stream_t   vc;

    if (ngx_http_arg(r, (u_char *) "newname", sizeof("newname") - 1, &name)
        != NGX_OK)
    {
        return "newname not specified";
    }

    if (name.len >= NGX_RTMP_MAX_NAME) {
        name.len = NGX_RTMP_MAX_NAME - 1;
    }

    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);
    ctx->count++;

    ngx_memzero(&vc, sizeof(ngx_rtmp_close_stream_t));

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

    lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);

    if (lctx && lctx->publishing) {
        /* publish */

        ngx_memzero(&vpublish, sizeof(ngx_rtmp_publish_t));

        ngx_memcpy(vpublish.name, name.data, name.len);

        ngx_rtmp_cmd_fill_args(vpublish.name, vpublish.args);

        if (ngx_rtmp_publish(s, &vpublish) != NGX_OK) {
            return "publish failed";
        }

    } else {
        /* play */

        ngx_memzero(&vplay, sizeof(ngx_rtmp_play_t));

        ngx_memcpy(vplay.name, name.data, name.len);

        ngx_rtmp_cmd_fill_args(vplay.name, vplay.args);

        if (ngx_rtmp_play(s, &vplay) != NGX_OK) {
            return "play failed";
        }
    }

    return NGX_CONF_OK;
}