static ngx_int_t
ngx_rtmp_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)
{
    ngx_rtmp_record_app_conf_t     *racf;
    ngx_rtmp_record_ctx_t          *ctx;

    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module);

    if (racf == NULL || racf->flags & NGX_RTMP_RECORD_OFF) {
        goto next;
    }

    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);

    if (ctx == NULL) {
        ctx = ngx_pcalloc(s->connection->pool, 
                sizeof(ngx_rtmp_record_ctx_t));
        if (ctx == NULL) {
            return NGX_ERROR;
        }
        ctx->file.fd = NGX_INVALID_FILE;
        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_record_module);
    }

    ngx_memcpy(ctx->name, v->name, sizeof(ctx->name));
    ngx_memcpy(ctx->args, v->args, sizeof(ctx->args));

    if (ngx_rtmp_record_open(s) != NGX_OK) {
        return NGX_ERROR;
    }

next:
    return next_publish(s, v);
}
static const char *
ngx_rtmp_control_record_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_int_t                    rc;
    ngx_str_t                    rec;
    ngx_uint_t                   rn;
    ngx_rtmp_control_ctx_t      *ctx;
    ngx_rtmp_record_app_conf_t  *racf;

    *cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);
    racf = (*cacf)->app_conf[ngx_rtmp_record_module.ctx_index];

    if (ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec) != NGX_OK) {
        rec.len = 0;
    }

    rn = ngx_rtmp_record_find(racf, &rec);
    if (rn == NGX_CONF_UNSET_UINT) {
        return "Recorder not found";
    }

    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);

    if (ctx->method.len == sizeof("start") - 1 &&
        ngx_strncmp(ctx->method.data, "start", ctx->method.len) == 0)
    {
        rc = ngx_rtmp_record_open(s, rn, &ctx->path);

    } else if (ctx->method.len == sizeof("stop") - 1 &&
               ngx_strncmp(ctx->method.data, "stop", ctx->method.len) == 0)
    {
        rc = ngx_rtmp_record_close(s, rn, &ctx->path);

    } else {
        return "Undefined method";
    }

    if (rc == NGX_ERROR) {
        return "Recorder error";
    }

    return NGX_CONF_OK;
}
static ngx_int_t
ngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h, 
        ngx_chain_t *in)
{
    ngx_rtmp_record_ctx_t          *ctx;
    ngx_rtmp_record_app_conf_t     *racf;
    ngx_time_t                      next;
    ngx_rtmp_header_t               ch;
    ngx_rtmp_codec_ctx_t           *codec_ctx;
    ngx_int_t                       keyframe;

    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module);
    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);

    if (racf == NULL || ctx == NULL || racf->flags & NGX_RTMP_RECORD_OFF) {
        return NGX_OK;
    }

    keyframe = (ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME);

    if (keyframe && racf->interval != (ngx_msec_t)NGX_CONF_UNSET) {
        next = ctx->last;
        next.msec += racf->interval;
        next.sec += (next.msec / 1000);
        next.msec %= 1000;
        if (ngx_cached_time->sec > next.sec
                || (ngx_cached_time->sec == next.sec
                    && ngx_cached_time->msec > next.msec))
        {
            ngx_rtmp_record_close(s);
            if (ngx_rtmp_record_open(s) != NGX_OK) {
                ngx_log_error(NGX_LOG_CRIT, s->connection->log, 0,
                        "record: failed");
            }
        }
    }

    if (ctx->file.fd == NGX_INVALID_FILE) {
        return NGX_OK;
    }

    /* filter frames */
    if (h->type == NGX_RTMP_MSG_AUDIO &&
       (racf->flags & NGX_RTMP_RECORD_AUDIO) == 0)
    {
        return NGX_OK;
    }

    if (h->type == NGX_RTMP_MSG_VIDEO &&
       (racf->flags & NGX_RTMP_RECORD_VIDEO) == 0 &&
       ((racf->flags & NGX_RTMP_RECORD_KEYFRAMES) == 0 || !keyframe))
    {
        return NGX_OK;
    }

    if (ctx->file.offset == 0) {
        ctx->epoch = h->timestamp;

        if (ngx_rtmp_record_write_header(&ctx->file) != NGX_OK) {
            ngx_rtmp_record_close(s);
            return NGX_OK;
        }

        codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);
        if (codec_ctx) {
            ch = *h;

#if 0
            /* metadata */
            if (codec_ctx->meta) {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 
                        "record: writing metadata");
                ch.type = NGX_RTMP_MSG_AMF_META;
                ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->meta);
                if (ngx_rtmp_record_write_frame(s, &ch, codec_ctx->meta)
                        != NGX_OK) 
                {
                    return NGX_OK;
                }
            }
#endif
            /* AAC header */
            if (codec_ctx->aac_header && (racf->flags & NGX_RTMP_RECORD_AUDIO)) 
            {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 
                        "record: writing AAC header");
                ch.type = NGX_RTMP_MSG_AUDIO;
                ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->aac_header);
                if (ngx_rtmp_record_write_frame(s, &ch, codec_ctx->aac_header)
                        != NGX_OK) 
                {
                    return NGX_OK;
                }
            }

            /* AVC header */
            if (codec_ctx->avc_header && (racf->flags 
                & (NGX_RTMP_RECORD_VIDEO|NGX_RTMP_RECORD_KEYFRAMES))) 
            {
                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0, 
                        "record: writing AVC header");
                ch.type = NGX_RTMP_MSG_VIDEO;
                ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->avc_header);
                if (ngx_rtmp_record_write_frame(s, &ch, codec_ctx->avc_header)
                        != NGX_OK) 
                {
                    return NGX_OK;
                }
            }
        }
    }

    return ngx_rtmp_record_write_frame(s, h, in);
}
static ngx_int_t
ngx_rtmp_control_record(ngx_http_request_t *r, ngx_str_t *method)
{
    ngx_rtmp_record_app_conf_t     *racf;
    ngx_rtmp_core_main_conf_t      *cmcf;
    ngx_rtmp_core_srv_conf_t      **pcscf, *cscf;
    ngx_rtmp_core_app_conf_t      **pcacf, *cacf;
    ngx_rtmp_live_app_conf_t       *lacf;
    ngx_rtmp_live_stream_t         *ls;
    ngx_rtmp_live_ctx_t            *lctx;
    ngx_rtmp_session_t             *s;
    ngx_chain_t                     cl;
    ngx_uint_t                      sn, rn, n;
    ngx_str_t                       srv, app, rec, name, path;
    ngx_str_t                       msg;
    ngx_buf_t                      *b;
    ngx_int_t                       rc;
    size_t                          len;

    sn = 0;
    if (ngx_http_arg(r, (u_char *) "srv", sizeof("srv") - 1, &srv) == NGX_OK) {
        sn = ngx_atoi(srv.data, srv.len);
    }

    if (ngx_http_arg(r, (u_char *) "app", sizeof("app") - 1, &app) != NGX_OK) {
        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                      "rtmp_control: app not specified");
        ngx_str_set(&msg, "Application not specified");
        goto error;
    }

    ngx_memzero(&rec, sizeof(rec));
    ngx_http_arg(r, (u_char *) "rec", sizeof("rec") - 1, &rec);

    ngx_memzero(&name, sizeof(name));
    ngx_http_arg(r, (u_char *) "name", sizeof("name") - 1, &name);

    cmcf = ngx_rtmp_core_main_conf;
    if (cmcf == NULL) {
        ngx_str_set(&msg, "Missing main RTMP conf");
        goto error;
    }

    /* find server */
    if (sn >= cmcf->servers.nelts) {
        ngx_str_set(&msg, "Server index out of range");
        goto error;
    }

    pcscf = cmcf->servers.elts;
    pcscf += sn;
    cscf = *pcscf;

    /* find application */
    pcacf = cscf->applications.elts;
    cacf = NULL;

    for (n = 0; n < cscf->applications.nelts; ++n, ++pcacf) {
        if ((*pcacf)->name.len == app.len &&
            ngx_strncmp((*pcacf)->name.data, app.data, app.len) == 0)
        {
            cacf = *pcacf;
            break;
        }
    }

    if (cacf == NULL) {
        ngx_str_set(&msg, "Application not found");
        goto error;
    }

    lacf = cacf->app_conf[ngx_rtmp_live_module.ctx_index];
    racf = cacf->app_conf[ngx_rtmp_record_module.ctx_index];

    /* find live stream by name */
    for (ls = lacf->streams[ngx_hash_key(name.data, name.len) % lacf->nbuckets];
         ls; ls = ls->next) 
    {
        len = ngx_strlen(ls->name);

        if (name.len == len && ngx_strncmp(name.data, ls->name, name.len)
                                == 0)
        {
            break;
        }
    }

    if (ls == NULL) {
        ngx_str_set(&msg, "Live stream not found");
        goto error;
    }

    /* find publisher context */
    for (lctx = ls->ctx; lctx; lctx = lctx->next) {
        if (lctx->flags & NGX_RTMP_LIVE_PUBLISHING) {
            break;
        }
    }

    if (lctx == NULL) {
        ngx_str_set(&msg, "No publisher");
        goto error;
    }

    s = lctx->session;

    /* find recorder */
    rn = ngx_rtmp_record_find(racf, &rec);
    if (rn == NGX_CONF_UNSET_UINT) {
        ngx_str_set(&msg, "Recorder not found");
        goto error;
    }

    ngx_memzero(&path, sizeof(path));

    if (method->len == sizeof("start") - 1 &&
        ngx_strncmp(method->data, "start", method->len) == 0)
    {
        rc = ngx_rtmp_record_open(s, rn, &path);

    } else if (method->len == sizeof("stop") - 1 &&
               ngx_strncmp(method->data, "stop", method->len) == 0)
    {
        rc = ngx_rtmp_record_close(s, rn, &path);

    } else {
        ngx_str_set(&msg, "Undefined method");
        goto error;
    }

    if (rc == NGX_ERROR) {
        ngx_str_set(&msg, "Recorder error");
        goto error;
    }

    if (rc == NGX_AGAIN) {
        /* already opened/closed */
        ngx_str_null(&path);
        r->header_only = 1;
    }

    r->headers_out.status = NGX_HTTP_OK;
    r->headers_out.content_length_n = path.len;

    b = ngx_create_temp_buf(r->pool, path.len);
    if (b == NULL) {
        return NGX_ERROR;
    }

    ngx_memzero(&cl, sizeof(cl));
    cl.buf = b;

    b->last = ngx_cpymem(b->pos, path.data, path.len);
    b->last_buf = 1;
    
    ngx_http_send_header(r);

    return ngx_http_output_filter(r, &cl);

error:
    r->headers_out.status = NGX_HTTP_BAD_REQUEST;
    r->headers_out.content_length_n = msg.len;

    b = ngx_calloc_buf(r->pool);
    if (b == NULL) {
        return NGX_ERROR;
    }

    ngx_memzero(&cl, sizeof(cl));
    cl.buf = b;

    b->start = b->pos = msg.data;
    b->end = b->last = msg.data + msg.len;
    b->memory = 1;
    b->last_buf = 1;

    ngx_http_send_header(r);

    return ngx_http_output_filter(r, &cl);
}