[
  {
    "path": "AUTHORS",
    "content": "Project author:\n\n  Roman Arutyunyan\n    Moscow, Russia\n\n  Contacts:\n    arut@qip.ru\n    arutyunyan.roman@gmail.com\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) 2012-2014, Roman Arutyunyan\nAll rights reserved.\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND\nON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n"
  },
  {
    "path": "README.md",
    "content": "# NGINX-based Media Streaming Server\n## nginx-rtmp-module\n\n\n### Project blog\n\n  http://nginx-rtmp.blogspot.com\n\n### Wiki manual\n\n  https://github.com/arut/nginx-rtmp-module/wiki/Directives\n\n### Google group\n\n  https://groups.google.com/group/nginx-rtmp\n\n  https://groups.google.com/group/nginx-rtmp-ru (Russian)\n\n### Donation page (Paypal etc)\n\n  http://arut.github.com/nginx-rtmp-module/\n\n### Features\n\n* RTMP/HLS/MPEG-DASH live streaming\n\n* RTMP Video on demand FLV/MP4,\n  playing from local filesystem or HTTP\n\n* Stream relay support for distributed\n  streaming: push & pull models\n\n* Recording streams in multiple FLVs\n\n* H264/AAC support\n\n* Online transcoding with FFmpeg\n\n* HTTP callbacks (publish/play/record/update etc)\n\n* Running external programs on certain events (exec)\n\n* HTTP control module for recording audio/video and dropping clients\n\n* Advanced buffering techniques\n  to keep memory allocations at a minimum\n  level for faster streaming and low\n  memory footprint\n\n* Proved to work with Wirecast, FMS, Wowza,\n  JWPlayer, FlowPlayer, StrobeMediaPlayback,\n  ffmpeg, avconv, rtmpdump, flvstreamer\n  and many more\n\n* Statistics in XML/XSL in machine- & human-\n  readable form\n\n* Linux/FreeBSD/MacOS/Windows\n\n### Build\n\ncd to NGINX source directory & run this:\n\n    ./configure --add-module=/path/to/nginx-rtmp-module\n    make\n    make install\n\nSeveral versions of nginx (1.3.14 - 1.5.0) require http_ssl_module to be\nadded as well:\n\n    ./configure --add-module=/path/to/nginx-rtmp-module --with-http_ssl_module\n\nFor building debug version of nginx add `--with-debug`\n\n    ./configure --add-module=/path/to-nginx/rtmp-module --with-debug\n\n[Read more about debug log](https://github.com/arut/nginx-rtmp-module/wiki/Debug-log)\n\n### Windows limitations\n\nWindows support is limited. These features are not supported\n\n* execs\n* static pulls\n* auto_push\n\n### RTMP URL format\n\n    rtmp://rtmp.example.com/app[/name]\n\napp -  should match one of application {}\n         blocks in config\n\nname - interpreted by each application\n         can be empty\n\n\n### Multi-worker live streaming\n\nModule supports multi-worker live\nstreaming through automatic stream pushing\nto nginx workers. This option is toggled with\nrtmp_auto_push directive.\n\n\n### Example nginx.conf\n\n    rtmp {\n\n        server {\n\n            listen 1935;\n\n            chunk_size 4000;\n\n            # TV mode: one publisher, many subscribers\n            application mytv {\n\n                # enable live streaming\n                live on;\n\n                # record first 1K of stream\n                record all;\n                record_path /tmp/av;\n                record_max_size 1K;\n\n                # append current timestamp to each flv\n                record_unique on;\n\n                # publish only from localhost\n                allow publish 127.0.0.1;\n                deny publish all;\n\n                #allow play all;\n            }\n\n            # Transcoding (ffmpeg needed)\n            application big {\n                live on;\n\n                # On every pusblished stream run this command (ffmpeg)\n                # with substitutions: $app/${app}, $name/${name} for application & stream name.\n                #\n                # This ffmpeg call receives stream from this application &\n                # reduces the resolution down to 32x32. The stream is the published to\n                # 'small' application (see below) under the same name.\n                #\n                # ffmpeg can do anything with the stream like video/audio\n                # transcoding, resizing, altering container/codec params etc\n                #\n                # Multiple exec lines can be specified.\n\n                exec ffmpeg -re -i rtmp://localhost:1935/$app/$name -vcodec flv -acodec copy -s 32x32\n                            -f flv rtmp://localhost:1935/small/${name};\n            }\n\n            application small {\n                live on;\n                # Video with reduced resolution comes here from ffmpeg\n            }\n\n            application webcam {\n                live on;\n\n                # Stream from local webcam\n                exec_static ffmpeg -f video4linux2 -i /dev/video0 -c:v libx264 -an\n                                   -f flv rtmp://localhost:1935/webcam/mystream;\n            }\n\n            application mypush {\n                live on;\n\n                # Every stream published here\n                # is automatically pushed to\n                # these two machines\n                push rtmp1.example.com;\n                push rtmp2.example.com:1934;\n            }\n\n            application mypull {\n                live on;\n\n                # Pull all streams from remote machine\n                # and play locally\n                pull rtmp://rtmp3.example.com pageUrl=www.example.com/index.html;\n            }\n\n            application mystaticpull {\n                live on;\n\n                # Static pull is started at nginx start\n                pull rtmp://rtmp4.example.com pageUrl=www.example.com/index.html name=mystream static;\n            }\n\n            # video on demand\n            application vod {\n                play /var/flvs;\n            }\n\n            application vod2 {\n                play /var/mp4s;\n            }\n\n            # Many publishers, many subscribers\n            # no checks, no recording\n            application videochat {\n\n                live on;\n\n                # The following notifications receive all\n                # the session variables as well as\n                # particular call arguments in HTTP POST\n                # request\n\n                # Make HTTP request & use HTTP retcode\n                # to decide whether to allow publishing\n                # from this connection or not\n                on_publish http://localhost:8080/publish;\n\n                # Same with playing\n                on_play http://localhost:8080/play;\n\n                # Publish/play end (repeats on disconnect)\n                on_done http://localhost:8080/done;\n\n                # All above mentioned notifications receive\n                # standard connect() arguments as well as\n                # play/publish ones. If any arguments are sent\n                # with GET-style syntax to play & publish\n                # these are also included.\n                # Example URL:\n                #   rtmp://localhost/myapp/mystream?a=b&c=d\n\n                # record 10 video keyframes (no audio) every 2 minutes\n                record keyframes;\n                record_path /tmp/vc;\n                record_max_frames 10;\n                record_interval 2m;\n\n                # Async notify about an flv recorded\n                on_record_done http://localhost:8080/record_done;\n\n            }\n\n\n            # HLS\n\n            # For HLS to work please create a directory in tmpfs (/tmp/hls here)\n            # for the fragments. The directory contents is served via HTTP (see\n            # http{} section in config)\n            #\n            # Incoming stream must be in H264/AAC. For iPhones use baseline H264\n            # profile (see ffmpeg example).\n            # This example creates RTMP stream from movie ready for HLS:\n            #\n            # ffmpeg -loglevel verbose -re -i movie.avi  -vcodec libx264\n            #    -vprofile baseline -acodec libmp3lame -ar 44100 -ac 1\n            #    -f flv rtmp://localhost:1935/hls/movie\n            #\n            # If you need to transcode live stream use 'exec' feature.\n            #\n            application hls {\n                live on;\n                hls on;\n                hls_path /tmp/hls;\n            }\n\n            # MPEG-DASH is similar to HLS\n\n            application dash {\n                live on;\n                dash on;\n                dash_path /tmp/dash;\n            }\n        }\n    }\n\n    # HTTP can be used for accessing RTMP stats\n    http {\n\n        server {\n\n            listen      8080;\n\n            # This URL provides RTMP statistics in XML\n            location /stat {\n                rtmp_stat all;\n\n                # Use this stylesheet to view XML as web page\n                # in browser\n                rtmp_stat_stylesheet stat.xsl;\n            }\n\n            location /stat.xsl {\n                # XML stylesheet to view RTMP stats.\n                # Copy stat.xsl wherever you want\n                # and put the full directory path here\n                root /path/to/stat.xsl/;\n            }\n\n            location /hls {\n                # Serve HLS fragments\n                types {\n                    application/vnd.apple.mpegurl m3u8;\n                    video/mp2t ts;\n                }\n                root /tmp;\n                add_header Cache-Control no-cache;\n            }\n\n            location /dash {\n                # Serve DASH fragments\n                root /tmp;\n                add_header Cache-Control no-cache;\n            }\n        }\n    }\n\n\n### Multi-worker streaming example\n\n    rtmp_auto_push on;\n\n    rtmp {\n        server {\n            listen 1935;\n\n            application mytv {\n                live on;\n            }\n        }\n    }\n"
  },
  {
    "path": "config",
    "content": "ngx_addon_name=\"ngx_rtmp_module\"\n\nRTMP_CORE_MODULES=\"                                         \\\n                ngx_rtmp_module                             \\\n                ngx_rtmp_core_module                        \\\n                ngx_rtmp_cmd_module                         \\\n                ngx_rtmp_codec_module                       \\\n                ngx_rtmp_access_module                      \\\n                ngx_rtmp_record_module                      \\\n                ngx_rtmp_live_module                        \\\n                ngx_rtmp_play_module                        \\\n                ngx_rtmp_flv_module                         \\\n                ngx_rtmp_mp4_module                         \\\n                ngx_rtmp_netcall_module                     \\\n                ngx_rtmp_relay_module                       \\\n                ngx_rtmp_exec_module                        \\\n                ngx_rtmp_auto_push_module                   \\\n                ngx_rtmp_auto_push_index_module             \\\n                ngx_rtmp_notify_module                      \\\n                ngx_rtmp_log_module                         \\\n                ngx_rtmp_limit_module                       \\\n                ngx_rtmp_hls_module                         \\\n                ngx_rtmp_dash_module                        \\\n                \"\n\n\nRTMP_HTTP_MODULES=\"                                         \\\n                ngx_rtmp_stat_module                        \\\n                ngx_rtmp_control_module                     \\\n                \"\n\n\nRTMP_DEPS=\"                                                 \\\n                $ngx_addon_dir/ngx_rtmp_amf.h               \\\n                $ngx_addon_dir/ngx_rtmp_bandwidth.h         \\\n                $ngx_addon_dir/ngx_rtmp_cmd_module.h        \\\n                $ngx_addon_dir/ngx_rtmp_codec_module.h      \\\n                $ngx_addon_dir/ngx_rtmp_eval.h              \\\n                $ngx_addon_dir/ngx_rtmp.h                   \\\n                $ngx_addon_dir/ngx_rtmp_version.h           \\\n                $ngx_addon_dir/ngx_rtmp_live_module.h       \\\n                $ngx_addon_dir/ngx_rtmp_netcall_module.h    \\\n                $ngx_addon_dir/ngx_rtmp_play_module.h       \\\n                $ngx_addon_dir/ngx_rtmp_record_module.h     \\\n                $ngx_addon_dir/ngx_rtmp_relay_module.h      \\\n                $ngx_addon_dir/ngx_rtmp_streams.h           \\\n                $ngx_addon_dir/ngx_rtmp_bitop.h             \\\n                $ngx_addon_dir/ngx_rtmp_proxy_protocol.h    \\\n                $ngx_addon_dir/hls/ngx_rtmp_mpegts.h        \\\n                $ngx_addon_dir/dash/ngx_rtmp_mp4.h          \\\n                \"\n\n\nRTMP_CORE_SRCS=\"                                            \\\n                $ngx_addon_dir/ngx_rtmp.c                   \\\n                $ngx_addon_dir/ngx_rtmp_init.c              \\\n                $ngx_addon_dir/ngx_rtmp_handshake.c         \\\n                $ngx_addon_dir/ngx_rtmp_handler.c           \\\n                $ngx_addon_dir/ngx_rtmp_amf.c               \\\n                $ngx_addon_dir/ngx_rtmp_send.c              \\\n                $ngx_addon_dir/ngx_rtmp_shared.c            \\\n                $ngx_addon_dir/ngx_rtmp_eval.c              \\\n                $ngx_addon_dir/ngx_rtmp_receive.c           \\\n                $ngx_addon_dir/ngx_rtmp_core_module.c       \\\n                $ngx_addon_dir/ngx_rtmp_cmd_module.c        \\\n                $ngx_addon_dir/ngx_rtmp_codec_module.c      \\\n                $ngx_addon_dir/ngx_rtmp_access_module.c     \\\n                $ngx_addon_dir/ngx_rtmp_record_module.c     \\\n                $ngx_addon_dir/ngx_rtmp_live_module.c       \\\n                $ngx_addon_dir/ngx_rtmp_play_module.c       \\\n                $ngx_addon_dir/ngx_rtmp_flv_module.c        \\\n                $ngx_addon_dir/ngx_rtmp_mp4_module.c        \\\n                $ngx_addon_dir/ngx_rtmp_netcall_module.c    \\\n                $ngx_addon_dir/ngx_rtmp_relay_module.c      \\\n                $ngx_addon_dir/ngx_rtmp_bandwidth.c         \\\n                $ngx_addon_dir/ngx_rtmp_exec_module.c       \\\n                $ngx_addon_dir/ngx_rtmp_auto_push_module.c  \\\n                $ngx_addon_dir/ngx_rtmp_notify_module.c     \\\n                $ngx_addon_dir/ngx_rtmp_log_module.c        \\\n                $ngx_addon_dir/ngx_rtmp_limit_module.c      \\\n                $ngx_addon_dir/ngx_rtmp_bitop.c             \\\n                $ngx_addon_dir/ngx_rtmp_proxy_protocol.c    \\\n                $ngx_addon_dir/hls/ngx_rtmp_hls_module.c    \\\n                $ngx_addon_dir/dash/ngx_rtmp_dash_module.c  \\\n                $ngx_addon_dir/hls/ngx_rtmp_mpegts.c        \\\n                $ngx_addon_dir/dash/ngx_rtmp_mp4.c          \\\n                \"\n\n\nRTMP_HTTP_SRCS=\"                                            \\\n                $ngx_addon_dir/ngx_rtmp_stat_module.c       \\\n                $ngx_addon_dir/ngx_rtmp_control_module.c    \\\n                \"\n\nif [ -f auto/module ] ; then\n    ngx_module_incs=$ngx_addon_dir\n    ngx_module_deps=$RTMP_DEPS\n\n    if [ $ngx_module_link = DYNAMIC ] ; then\n        ngx_module_name=\"$RTMP_CORE_MODULES $RTMP_HTTP_MODULES\"\n        ngx_module_srcs=\"$RTMP_CORE_SRCS $RTMP_HTTP_SRCS\"\n\n        . auto/module\n\n    else\n        ngx_module_type=CORE\n        ngx_module_name=$RTMP_CORE_MODULES\n        ngx_module_srcs=$RTMP_CORE_SRCS\n\n        . auto/module\n\n\n        ngx_module_type=HTTP\n        ngx_module_name=$RTMP_HTTP_MODULES\n        ngx_module_incs=\n        ngx_module_deps=\n        ngx_module_srcs=$RTMP_HTTP_SRCS\n\n        . auto/module\n    fi\n\nelse\n    CORE_MODULES=\"$CORE_MODULES $RTMP_CORE_MODULES\"\n    HTTP_MODULES=\"$HTTP_MODULES $RTMP_HTTP_MODULES\"\n\n    NGX_ADDON_DEPS=\"$NGX_ADDON_DEPS $RTMP_DEPS\"\n    NGX_ADDON_SRCS=\"$NGX_ADDON_SRCS $RTMP_CORE_SRCS $RTMP_HTTP_SRCS\"\n\n    CFLAGS=\"$CFLAGS -I$ngx_addon_dir\"\nfi\n\nUSE_OPENSSL=YES\n\n"
  },
  {
    "path": "dash/ngx_rtmp_dash_module.c",
    "content": "\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_rtmp.h>\n#include <ngx_rtmp_codec_module.h>\n#include \"ngx_rtmp_live_module.h\"\n#include \"ngx_rtmp_mp4.h\"\n\n\nstatic ngx_rtmp_publish_pt              next_publish;\nstatic ngx_rtmp_close_stream_pt         next_close_stream;\nstatic ngx_rtmp_stream_begin_pt         next_stream_begin;\nstatic ngx_rtmp_stream_eof_pt           next_stream_eof;\n\n\nstatic ngx_int_t ngx_rtmp_dash_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_dash_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\nstatic ngx_int_t ngx_rtmp_dash_write_init_segments(ngx_rtmp_session_t *s);\n\n\n#define NGX_RTMP_DASH_BUFSIZE           (1024*1024)\n#define NGX_RTMP_DASH_MAX_MDAT          (10*1024*1024)\n#define NGX_RTMP_DASH_MAX_SAMPLES       1024\n#define NGX_RTMP_DASH_DIR_ACCESS        0744\n\n\ntypedef struct {\n    uint32_t                            timestamp;\n    uint32_t                            duration;\n} ngx_rtmp_dash_frag_t;\n\n\ntypedef struct {\n    ngx_uint_t                          id;\n    ngx_uint_t                          opened;\n    ngx_uint_t                          mdat_size;\n    ngx_uint_t                          sample_count;\n    ngx_uint_t                          sample_mask;\n    ngx_fd_t                            fd;\n    char                                type;\n    uint32_t                            earliest_pres_time;\n    uint32_t                            latest_pres_time;\n    ngx_rtmp_mp4_sample_t               samples[NGX_RTMP_DASH_MAX_SAMPLES];\n} ngx_rtmp_dash_track_t;\n\n\ntypedef struct {\n    ngx_str_t                           playlist;\n    ngx_str_t                           playlist_bak;\n    ngx_str_t                           name;\n    ngx_str_t                           stream;\n    time_t                              start_time;\n\n    ngx_uint_t                          nfrags;\n    ngx_uint_t                          frag;\n    ngx_rtmp_dash_frag_t               *frags; /* circular 2 * winfrags + 1 */\n\n    unsigned                            opened:1;\n    unsigned                            has_video:1;\n    unsigned                            has_audio:1;\n\n    ngx_file_t                          video_file;\n    ngx_file_t                          audio_file;\n\n    ngx_uint_t                          id;\n\n    ngx_rtmp_dash_track_t               audio;\n    ngx_rtmp_dash_track_t               video;\n} ngx_rtmp_dash_ctx_t;\n\n\ntypedef struct {\n    ngx_str_t                           path;\n    ngx_msec_t                          playlen;\n} ngx_rtmp_dash_cleanup_t;\n\n\ntypedef struct {\n    ngx_flag_t                          dash;\n    ngx_msec_t                          fraglen;\n    ngx_msec_t                          playlen;\n    ngx_flag_t                          nested;\n    ngx_str_t                           path;\n    ngx_uint_t                          winfrags;\n    ngx_flag_t                          cleanup;\n    ngx_path_t                         *slot;\n} ngx_rtmp_dash_app_conf_t;\n\n\nstatic ngx_command_t ngx_rtmp_dash_commands[] = {\n\n    { ngx_string(\"dash\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_dash_app_conf_t, dash),\n      NULL },\n\n    { ngx_string(\"dash_fragment\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_dash_app_conf_t, fraglen),\n      NULL },\n\n    { ngx_string(\"dash_path\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_dash_app_conf_t, path),\n      NULL },\n\n    { ngx_string(\"dash_playlist_length\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_dash_app_conf_t, playlen),\n      NULL },\n\n    { ngx_string(\"dash_cleanup\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_dash_app_conf_t, cleanup),\n      NULL },\n\n    { ngx_string(\"dash_nested\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_dash_app_conf_t, nested),\n      NULL },\n\n    ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_dash_module_ctx = {\n    NULL,                               /* preconfiguration */\n    ngx_rtmp_dash_postconfiguration,    /* postconfiguration */\n\n    NULL,                               /* create main configuration */\n    NULL,                               /* init main configuration */\n\n    NULL,                               /* create server configuration */\n    NULL,                               /* merge server configuration */\n\n    ngx_rtmp_dash_create_app_conf,      /* create location configuration */\n    ngx_rtmp_dash_merge_app_conf,       /* merge location configuration */\n};\n\n\nngx_module_t  ngx_rtmp_dash_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_dash_module_ctx,          /* module context */\n    ngx_rtmp_dash_commands,             /* module directives */\n    NGX_RTMP_MODULE,                    /* module type */\n    NULL,                               /* init master */\n    NULL,                               /* init module */\n    NULL,                               /* init process */\n    NULL,                               /* init thread */\n    NULL,                               /* exit thread */\n    NULL,                               /* exit process */\n    NULL,                               /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic ngx_rtmp_dash_frag_t *\nngx_rtmp_dash_get_frag(ngx_rtmp_session_t *s, ngx_int_t n)\n{\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n\n    return &ctx->frags[(ctx->frag + n) % (dacf->winfrags * 2 + 1)];\n}\n\n\nstatic void\nngx_rtmp_dash_next_frag(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n\n    if (ctx->nfrags == dacf->winfrags) {\n        ctx->frag++;\n    } else {\n        ctx->nfrags++;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_rename_file(u_char *src, u_char *dst)\n{\n    /* rename file with overwrite */\n\n#if (NGX_WIN32)\n    return MoveFileEx((LPCTSTR) src, (LPCTSTR) dst, MOVEFILE_REPLACE_EXISTING);\n#else\n    return ngx_rename_file(src, dst);\n#endif\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_write_playlist(ngx_rtmp_session_t *s)\n{\n    char                      *sep;\n    u_char                    *p, *last;\n    ssize_t                    n;\n    ngx_fd_t                   fd;\n    struct tm                  tm;\n    ngx_str_t                  noname, *name;\n    ngx_uint_t                 i;\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_codec_ctx_t      *codec_ctx;\n    ngx_rtmp_dash_frag_t      *f;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    static u_char              buffer[NGX_RTMP_DASH_BUFSIZE];\n    static u_char              start_time[sizeof(\"1970-09-28T12:00:00Z\")];\n    static u_char              pub_time[sizeof(\"1970-09-28T12:00:00Z\")];\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (dacf == NULL || ctx == NULL || codec_ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (ctx->id == 0) {\n        ngx_rtmp_dash_write_init_segments(s);\n    }\n\n    fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY,\n                       NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);\n\n    if (fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: open failed: '%V'\", &ctx->playlist_bak);\n        return NGX_ERROR;\n    }\n\n#define NGX_RTMP_DASH_MANIFEST_HEADER                                          \\\n    \"<?xml version=\\\"1.0\\\"?>\\n\"                                                \\\n    \"<MPD\\n\"                                                                   \\\n    \"    type=\\\"dynamic\\\"\\n\"                                                   \\\n    \"    xmlns=\\\"urn:mpeg:dash:schema:mpd:2011\\\"\\n\"                            \\\n    \"    availabilityStartTime=\\\"%s\\\"\\n\"                                       \\\n    \"    publishTime=\\\"%s\\\"\\n\"                                                 \\\n    \"    minimumUpdatePeriod=\\\"PT%uiS\\\"\\n\"                                     \\\n    \"    minBufferTime=\\\"PT%uiS\\\"\\n\"                                           \\\n    \"    timeShiftBufferDepth=\\\"PT%uiS\\\"\\n\"                                    \\\n    \"    profiles=\\\"urn:hbbtv:dash:profile:isoff-live:2012,\"                   \\\n                   \"urn:mpeg:dash:profile:isoff-live:2011\\\"\\n\"                 \\\n    \"    xmlns:xsi=\\\"http://www.w3.org/2011/XMLSchema-instance\\\"\\n\"            \\\n    \"    xsi:schemaLocation=\\\"urn:mpeg:DASH:schema:MPD:2011 DASH-MPD.xsd\\\">\\n\" \\\n    \"  <Period start=\\\"PT0S\\\" id=\\\"dash\\\">\\n\"\n\n\n#define NGX_RTMP_DASH_MANIFEST_VIDEO                                           \\\n    \"    <AdaptationSet\\n\"                                                     \\\n    \"        id=\\\"1\\\"\\n\"                                                       \\\n    \"        segmentAlignment=\\\"true\\\"\\n\"                                      \\\n    \"        maxWidth=\\\"%ui\\\"\\n\"                                               \\\n    \"        maxHeight=\\\"%ui\\\"\\n\"                                              \\\n    \"        maxFrameRate=\\\"%ui\\\">\\n\"                                          \\\n    \"      <Representation\\n\"                                                  \\\n    \"          id=\\\"%V_H264\\\"\\n\"                                               \\\n    \"          mimeType=\\\"video/mp4\\\"\\n\"                                       \\\n    \"          codecs=\\\"avc1.%02uxi%02uxi%02uxi\\\"\\n\"                           \\\n    \"          width=\\\"%ui\\\"\\n\"                                                \\\n    \"          height=\\\"%ui\\\"\\n\"                                               \\\n    \"          frameRate=\\\"%ui\\\"\\n\"                                            \\\n    \"          startWithSAP=\\\"1\\\"\\n\"                                           \\\n    \"          bandwidth=\\\"%ui\\\">\\n\"                                           \\\n    \"        <SegmentTemplate\\n\"                                               \\\n    \"            timescale=\\\"1000\\\"\\n\"                                         \\\n    \"            media=\\\"%V%s$Time$.m4v\\\"\\n\"                                   \\\n    \"            initialization=\\\"%V%sinit.m4v\\\">\\n\"                           \\\n    \"          <SegmentTimeline>\\n\"\n\n\n#define NGX_RTMP_DASH_MANIFEST_VIDEO_FOOTER                                    \\\n    \"          </SegmentTimeline>\\n\"                                           \\\n    \"        </SegmentTemplate>\\n\"                                             \\\n    \"      </Representation>\\n\"                                                \\\n    \"    </AdaptationSet>\\n\"\n\n\n#define NGX_RTMP_DASH_MANIFEST_TIME                                            \\\n    \"             <S t=\\\"%uD\\\" d=\\\"%uD\\\"/>\\n\"\n\n\n#define NGX_RTMP_DASH_MANIFEST_AUDIO                                           \\\n    \"    <AdaptationSet\\n\"                                                     \\\n    \"        id=\\\"2\\\"\\n\"                                                       \\\n    \"        segmentAlignment=\\\"true\\\">\\n\"                                     \\\n    \"      <AudioChannelConfiguration\\n\"                                       \\\n    \"          schemeIdUri=\\\"urn:mpeg:dash:\"                                   \\\n                                \"23003:3:audio_channel_configuration:2011\\\"\\n\" \\\n    \"          value=\\\"1\\\"/>\\n\"                                                \\\n    \"      <Representation\\n\"                                                  \\\n    \"          id=\\\"%V_AAC\\\"\\n\"                                                \\\n    \"          mimeType=\\\"audio/mp4\\\"\\n\"                                       \\\n    \"          codecs=\\\"mp4a.%s\\\"\\n\"                                           \\\n    \"          audioSamplingRate=\\\"%ui\\\"\\n\"                                    \\\n    \"          startWithSAP=\\\"1\\\"\\n\"                                           \\\n    \"          bandwidth=\\\"%ui\\\">\\n\"                                           \\\n    \"        <SegmentTemplate\\n\"                                               \\\n    \"            timescale=\\\"1000\\\"\\n\"                                         \\\n    \"            media=\\\"%V%s$Time$.m4a\\\"\\n\"                                   \\\n    \"            initialization=\\\"%V%sinit.m4a\\\">\\n\"                           \\\n    \"          <SegmentTimeline>\\n\"\n\n\n#define NGX_RTMP_DASH_MANIFEST_AUDIO_FOOTER                                    \\\n    \"          </SegmentTimeline>\\n\"                                           \\\n    \"        </SegmentTemplate>\\n\"                                             \\\n    \"      </Representation>\\n\"                                                \\\n    \"    </AdaptationSet>\\n\"\n\n\n#define NGX_RTMP_DASH_MANIFEST_FOOTER                                          \\\n    \"  </Period>\\n\"                                                            \\\n    \"</MPD>\\n\"\n\n    ngx_libc_gmtime(ctx->start_time, &tm);\n\n    ngx_sprintf(start_time, \"%4d-%02d-%02dT%02d:%02d:%02dZ%Z\",\n                tm.tm_year + 1900, tm.tm_mon + 1,\n                tm.tm_mday, tm.tm_hour,\n                tm.tm_min, tm.tm_sec);\n\n    ngx_libc_gmtime(ngx_time(), &tm);\n\n    ngx_sprintf(pub_time, \"%4d-%02d-%02dT%02d:%02d:%02dZ%Z\",\n                tm.tm_year + 1900, tm.tm_mon + 1,\n                tm.tm_mday, tm.tm_hour,\n                tm.tm_min, tm.tm_sec);\n\n    last = buffer + sizeof(buffer);\n\n    p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_HEADER,\n                     start_time,\n                     pub_time,\n                     (ngx_uint_t) (dacf->fraglen / 1000),\n                     (ngx_uint_t) (dacf->fraglen / 1000),\n                     (ngx_uint_t) (dacf->fraglen / 250 + 1));\n\n    /*\n     * timeShiftBufferDepth formula:\n     *     2 * minBufferTime + max_fragment_length + 1\n     */\n\n    n = ngx_write_fd(fd, buffer, p - buffer);\n\n    ngx_str_null(&noname);\n\n    name = (dacf->nested ? &noname : &ctx->name);\n    sep = (dacf->nested ? \"\" : \"-\");\n\n    if (ctx->has_video) {\n        p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_VIDEO,\n                         codec_ctx->width,\n                         codec_ctx->height,\n                         codec_ctx->frame_rate,\n                         &ctx->name,\n                         codec_ctx->avc_profile,\n                         codec_ctx->avc_compat,\n                         codec_ctx->avc_level,\n                         codec_ctx->width,\n                         codec_ctx->height,\n                         codec_ctx->frame_rate,\n                         (ngx_uint_t) (codec_ctx->video_data_rate * 1000),\n                         name, sep,\n                         name, sep);\n\n        for (i = 0; i < ctx->nfrags; i++) {\n            f = ngx_rtmp_dash_get_frag(s, i);\n            p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_TIME,\n                             f->timestamp, f->duration);\n        }\n\n        p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_VIDEO_FOOTER);\n\n        n = ngx_write_fd(fd, buffer, p - buffer);\n    }\n\n    if (ctx->has_audio) {\n        p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_AUDIO,\n                         &ctx->name,\n                         codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC ?\n                         (codec_ctx->aac_sbr ? \"40.5\" : \"40.2\") : \"6b\",\n                         codec_ctx->sample_rate,\n                         (ngx_uint_t) (codec_ctx->audio_data_rate * 1000),\n                         name, sep,\n                         name, sep);\n\n        for (i = 0; i < ctx->nfrags; i++) {\n            f = ngx_rtmp_dash_get_frag(s, i);\n            p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_TIME,\n                             f->timestamp, f->duration);\n        }\n\n        p = ngx_slprintf(p, last, NGX_RTMP_DASH_MANIFEST_AUDIO_FOOTER);\n\n        n = ngx_write_fd(fd, buffer, p - buffer);\n    }\n\n    p = ngx_slprintf(buffer, last, NGX_RTMP_DASH_MANIFEST_FOOTER);\n    n = ngx_write_fd(fd, buffer, p - buffer);\n\n    if (n < 0) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: write failed: '%V'\", &ctx->playlist_bak);\n        ngx_close_file(fd);\n        return NGX_ERROR;\n    }\n\n    ngx_close_file(fd);\n\n    if (ngx_rtmp_dash_rename_file(ctx->playlist_bak.data, ctx->playlist.data)\n        == NGX_FILE_ERROR)\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: rename failed: '%V'->'%V'\",\n                      &ctx->playlist_bak, &ctx->playlist);\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_write_init_segments(ngx_rtmp_session_t *s)\n{\n    ngx_fd_t               fd;\n    ngx_int_t              rc;\n    ngx_buf_t              b;\n    ngx_rtmp_dash_ctx_t   *ctx;\n    ngx_rtmp_codec_ctx_t  *codec_ctx;\n\n    static u_char          buffer[NGX_RTMP_DASH_BUFSIZE];\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (ctx == NULL || codec_ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    /* init video */\n\n    *ngx_sprintf(ctx->stream.data + ctx->stream.len, \"init.m4v\") = 0;\n\n    fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, NGX_FILE_TRUNCATE,\n                       NGX_FILE_DEFAULT_ACCESS);\n\n    if (fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: error creating video init file\");\n        return NGX_ERROR;\n    }\n\n    b.start = buffer;\n    b.end = b.start + sizeof(buffer);\n    b.pos = b.last = b.start;\n\n    ngx_rtmp_mp4_write_ftyp(&b);\n    ngx_rtmp_mp4_write_moov(s, &b, NGX_RTMP_MP4_VIDEO_TRACK);\n\n    rc = ngx_write_fd(fd, b.start, (size_t) (b.last - b.start));\n    if (rc == NGX_ERROR) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: writing video init failed\");\n    }\n\n    ngx_close_file(fd);\n\n    /* init audio */\n\n    *ngx_sprintf(ctx->stream.data + ctx->stream.len, \"init.m4a\") = 0;\n\n    fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR, NGX_FILE_TRUNCATE,\n                       NGX_FILE_DEFAULT_ACCESS);\n\n    if (fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: error creating dash audio init file\");\n        return NGX_ERROR;\n    }\n\n    b.pos = b.last = b.start;\n\n    ngx_rtmp_mp4_write_ftyp(&b);\n    ngx_rtmp_mp4_write_moov(s, &b, NGX_RTMP_MP4_AUDIO_TRACK);\n\n    rc = ngx_write_fd(fd, b.start, (size_t) (b.last - b.start));\n    if (rc == NGX_ERROR) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: writing audio init failed\");\n    }\n\n    ngx_close_file(fd);\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_dash_close_fragment(ngx_rtmp_session_t *s, ngx_rtmp_dash_track_t *t)\n{\n    u_char                    *pos, *pos1;\n    size_t                     left;\n    ssize_t                    n;\n    ngx_fd_t                   fd;\n    ngx_buf_t                  b;\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_dash_frag_t      *f;\n\n    static u_char              buffer[NGX_RTMP_DASH_BUFSIZE];\n\n    if (!t->opened) {\n        return;\n    }\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"dash: close fragment id=%ui, type=%c, pts=%uD\",\n                   t->id, t->type, t->earliest_pres_time);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n\n    b.start = buffer;\n    b.end = buffer + sizeof(buffer);\n    b.pos = b.last = b.start;\n\n    ngx_rtmp_mp4_write_styp(&b);\n\n    pos = b.last;\n    b.last += 44; /* leave room for sidx */\n\n    ngx_rtmp_mp4_write_moof(&b, t->earliest_pres_time, t->sample_count,\n                            t->samples, t->sample_mask, t->id);\n    pos1 = b.last;\n    b.last = pos;\n\n    ngx_rtmp_mp4_write_sidx(&b, t->mdat_size + 8 + (pos1 - (pos + 44)),\n                            t->earliest_pres_time, t->latest_pres_time);\n    b.last = pos1;\n    ngx_rtmp_mp4_write_mdat(&b, t->mdat_size + 8);\n\n    /* move the data down to make room for the headers */\n\n    f = ngx_rtmp_dash_get_frag(s, ctx->nfrags);\n\n    *ngx_sprintf(ctx->stream.data + ctx->stream.len, \"%uD.m4%c\",\n                 f->timestamp, t->type) = 0;\n\n    fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR,\n                       NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);\n\n    if (fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: error creating dash temp video file\");\n        goto done;\n    }\n\n    if (ngx_write_fd(fd, b.pos, (size_t) (b.last - b.pos)) == NGX_ERROR) {\n        goto done;\n    }\n\n    left = (size_t) t->mdat_size;\n\n#if (NGX_WIN32)\n    if (SetFilePointer(t->fd, 0, 0, FILE_BEGIN) == INVALID_SET_FILE_POINTER) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"dash: SetFilePointer error\");\n        goto done;\n    }\n#else\n    if (lseek(t->fd, 0, SEEK_SET) == -1) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: lseek error\");\n        goto done;\n    }\n#endif\n\n    while (left > 0) {\n\n        n = ngx_read_fd(t->fd, buffer, ngx_min(sizeof(buffer), left));\n        if (n == NGX_ERROR) {\n            break;\n        }\n\n        n = ngx_write_fd(fd, buffer, (size_t) n);\n        if (n == NGX_ERROR) {\n            break;\n        }\n\n        left -= n;\n    }\n\ndone:\n\n    if (fd != NGX_INVALID_FILE) {\n        ngx_close_file(fd);\n    }\n\n    ngx_close_file(t->fd);\n\n    t->fd = NGX_INVALID_FILE;\n    t->opened = 0;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_close_fragments(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_dash_ctx_t  *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n    if (ctx == NULL || !ctx->opened) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"dash: close fragments\");\n\n    ngx_rtmp_dash_close_fragment(s, &ctx->video);\n    ngx_rtmp_dash_close_fragment(s, &ctx->audio);\n\n    ngx_rtmp_dash_next_frag(s);\n\n    ngx_rtmp_dash_write_playlist(s);\n\n    ctx->id++;\n    ctx->opened = 0;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_open_fragment(ngx_rtmp_session_t *s, ngx_rtmp_dash_track_t *t,\n    ngx_uint_t id, char type)\n{\n    ngx_rtmp_dash_ctx_t   *ctx;\n\n    if (t->opened) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"dash: open fragment id=%ui, type='%c'\", id, type);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n\n    *ngx_sprintf(ctx->stream.data + ctx->stream.len, \"raw.m4%c\", type) = 0;\n\n    t->fd = ngx_open_file(ctx->stream.data, NGX_FILE_RDWR,\n                          NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);\n\n    if (t->fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: error creating fragment file\");\n        return NGX_ERROR;\n    }\n\n    t->id = id;\n    t->type = type;\n    t->sample_count = 0;\n    t->earliest_pres_time = 0;\n    t->latest_pres_time = 0;\n    t->mdat_size = 0;\n    t->opened = 1;\n\n    if (type == 'v') {\n        t->sample_mask = NGX_RTMP_MP4_SAMPLE_SIZE|\n                         NGX_RTMP_MP4_SAMPLE_DURATION|\n                         NGX_RTMP_MP4_SAMPLE_DELAY|\n                         NGX_RTMP_MP4_SAMPLE_KEY;\n    } else {\n        t->sample_mask = NGX_RTMP_MP4_SAMPLE_SIZE|\n                         NGX_RTMP_MP4_SAMPLE_DURATION;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_open_fragments(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_dash_ctx_t  *ctx;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"dash: open fragments\");\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n\n    if (ctx->opened) {\n        return NGX_OK;\n    }\n\n    ngx_rtmp_dash_open_fragment(s, &ctx->video, ctx->id, 'v');\n\n    ngx_rtmp_dash_open_fragment(s, &ctx->audio, ctx->id, 'a');\n\n    ctx->opened = 1;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_ensure_directory(ngx_rtmp_session_t *s)\n{\n    size_t                     len;\n    ngx_file_info_t            fi;\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    static u_char              path[NGX_MAX_PATH + 1];\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n\n    *ngx_snprintf(path, sizeof(path) - 1, \"%V\", &dacf->path) = 0;\n\n    if (ngx_file_info(path, &fi) == NGX_FILE_ERROR) {\n\n        if (ngx_errno != NGX_ENOENT) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"dash: \" ngx_file_info_n \" failed on '%V'\",\n                          &dacf->path);\n            return NGX_ERROR;\n        }\n\n        /* ENOENT */\n\n        if (ngx_create_dir(path, NGX_RTMP_DASH_DIR_ACCESS) == NGX_FILE_ERROR) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"dash: \" ngx_create_dir_n \" failed on '%V'\",\n                          &dacf->path);\n            return NGX_ERROR;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"dash: directory '%V' created\", &dacf->path);\n\n    } else {\n\n        if (!ngx_is_dir(&fi)) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"dash: '%V' exists and is not a directory\",\n                          &dacf->path);\n            return  NGX_ERROR;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"dash: directory '%V' exists\", &dacf->path);\n    }\n\n    if (!dacf->nested) {\n        return NGX_OK;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n\n    len = dacf->path.len;\n    if (dacf->path.data[len - 1] == '/') {\n        len--;\n    }\n\n    *ngx_snprintf(path, sizeof(path) - 1, \"%*s/%V\", len, dacf->path.data,\n                  &ctx->name) = 0;\n\n    if (ngx_file_info(path, &fi) != NGX_FILE_ERROR) {\n\n        if (ngx_is_dir(&fi)) {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"dash: directory '%s' exists\", path);\n            return NGX_OK;\n        }\n\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"dash: '%s' exists and is not a directory\", path);\n\n        return  NGX_ERROR;\n    }\n\n    if (ngx_errno != NGX_ENOENT) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: \" ngx_file_info_n \" failed on '%s'\", path);\n        return NGX_ERROR;\n    }\n\n    /* NGX_ENOENT */\n\n    if (ngx_create_dir(path, NGX_RTMP_DASH_DIR_ACCESS) == NGX_FILE_ERROR) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: \" ngx_create_dir_n \" failed on '%s'\", path);\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"dash: directory '%s' created\", path);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    u_char                    *p;\n    size_t                     len;\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_dash_frag_t      *f;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n    if (dacf == NULL || !dacf->dash || dacf->path.len == 0) {\n        goto next;\n    }\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"dash: publish: name='%s' type='%s'\", v->name, v->type);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n\n    if (ctx == NULL) {\n        ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_dash_ctx_t));\n        if (ctx == NULL) {\n            goto next;\n        }\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_dash_module);\n\n    } else {\n        if (ctx->opened) {\n            goto next;\n        }\n\n        f = ctx->frags;\n        ngx_memzero(ctx, sizeof(ngx_rtmp_dash_ctx_t));\n        ctx->frags = f;\n    }\n\n    if (ctx->frags == NULL) {\n        ctx->frags = ngx_pcalloc(s->connection->pool,\n                                 sizeof(ngx_rtmp_dash_frag_t) *\n                                 (dacf->winfrags * 2 + 1));\n        if (ctx->frags == NULL) {\n            return NGX_ERROR;\n        }\n    }\n\n    ctx->id = 0;\n\n    if (ngx_strstr(v->name, \"..\")) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"dash: bad stream name: '%s'\", v->name);\n        return NGX_ERROR;\n    }\n\n    ctx->name.len = ngx_strlen(v->name);\n    ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len + 1);\n\n    if (ctx->name.data == NULL) {\n        return NGX_ERROR;\n    }\n\n    *ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0;\n\n    len = dacf->path.len + 1 + ctx->name.len + sizeof(\".mpd\");\n    if (dacf->nested) {\n        len += sizeof(\"/index\") - 1;\n    }\n\n    ctx->playlist.data = ngx_palloc(s->connection->pool, len);\n    p = ngx_cpymem(ctx->playlist.data, dacf->path.data, dacf->path.len);\n\n    if (p[-1] != '/') {\n        *p++ = '/';\n    }\n\n    p = ngx_cpymem(p, ctx->name.data, ctx->name.len);\n\n    /*\n     * ctx->stream holds initial part of stream file path\n     * however the space for the whole stream path\n     * is allocated\n     */\n\n    ctx->stream.len = p - ctx->playlist.data + 1;\n    ctx->stream.data = ngx_palloc(s->connection->pool,\n                                  ctx->stream.len + NGX_INT32_LEN +\n                                  sizeof(\".m4x\"));\n\n    ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1);\n    ctx->stream.data[ctx->stream.len - 1] = (dacf->nested ? '/' : '-');\n\n    if (dacf->nested) {\n        p = ngx_cpymem(p, \"/index.mpd\", sizeof(\"/index.mpd\") - 1);\n    } else {\n        p = ngx_cpymem(p, \".mpd\", sizeof(\".mpd\") - 1);\n    }\n\n    ctx->playlist.len = p - ctx->playlist.data;\n\n    *p = 0;\n\n    /* playlist bak (new playlist) path */\n\n    ctx->playlist_bak.data = ngx_palloc(s->connection->pool,\n                                        ctx->playlist.len + sizeof(\".bak\"));\n    p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data,\n                   ctx->playlist.len);\n    p = ngx_cpymem(p, \".bak\", sizeof(\".bak\") - 1);\n\n    ctx->playlist_bak.len = p - ctx->playlist_bak.data;\n\n    *p = 0;\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"dash: playlist='%V' playlist_bak='%V' stream_pattern='%V'\",\n                   &ctx->playlist, &ctx->playlist_bak, &ctx->stream);\n\n    ctx->start_time = ngx_time();\n\n    if (ngx_rtmp_dash_ensure_directory(s) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)\n{\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n\n    if (dacf == NULL || !dacf->dash || ctx == NULL) {\n        goto next;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"dash: delete stream\");\n\n    ngx_rtmp_dash_close_fragments(s);\n\nnext:\n    return next_close_stream(s, v);\n}\n\n\nstatic void\nngx_rtmp_dash_update_fragments(ngx_rtmp_session_t *s, ngx_int_t boundary,\n    uint32_t timestamp)\n{\n    int32_t                    d;\n    ngx_int_t                  hit;\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_dash_frag_t      *f;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n    f = ngx_rtmp_dash_get_frag(s, ctx->nfrags);\n\n    d = (int32_t) (timestamp - f->timestamp);\n\n    if (d >= 0) {\n\n        f->duration = timestamp - f->timestamp;\n        hit = (f->duration >= dacf->fraglen);\n\n        /* keep fragment lengths within 2x factor for dash.js  */\n        if (f->duration >= dacf->fraglen * 2) {\n            boundary = 1;\n        }\n\n    } else {\n\n        /* sometimes clients generate slightly unordered frames */\n\n        hit = (-d > 1000);\n    }\n\n    if (ctx->has_video && !hit) {\n        boundary = 0;\n    }\n\n    if (!ctx->has_video && ctx->has_audio) {\n        boundary = hit;\n    }\n\n    if (ctx->audio.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) {\n        boundary = 1;\n    }\n\n    if (ctx->video.mdat_size >= NGX_RTMP_DASH_MAX_MDAT) {\n        boundary = 1;\n    }\n\n    if (!ctx->opened) {\n        boundary = 1;\n    }\n\n    if (boundary) {\n        ngx_rtmp_dash_close_fragments(s);\n        ngx_rtmp_dash_open_fragments(s);\n\n        f = ngx_rtmp_dash_get_frag(s, ctx->nfrags);\n        f->timestamp = timestamp;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_append(ngx_rtmp_session_t *s, ngx_chain_t *in,\n    ngx_rtmp_dash_track_t *t, ngx_int_t key, uint32_t timestamp, uint32_t delay)\n{\n    u_char                 *p;\n    size_t                  size, bsize;\n    ngx_rtmp_mp4_sample_t  *smpl;\n\n    static u_char           buffer[NGX_RTMP_DASH_BUFSIZE];\n\n    p = buffer;\n    size = 0;\n\n    for (; in && size < sizeof(buffer); in = in->next) {\n\n        bsize = (size_t) (in->buf->last - in->buf->pos);\n        if (size + bsize > sizeof(buffer)) {\n            bsize = (size_t) (sizeof(buffer) - size);\n        }\n\n        p = ngx_cpymem(p, in->buf->pos, bsize);\n        size += bsize;\n    }\n\n    ngx_rtmp_dash_update_fragments(s, key, timestamp);\n\n    if (t->sample_count == 0) {\n        t->earliest_pres_time = timestamp;\n    }\n\n    t->latest_pres_time = timestamp;\n\n    if (t->sample_count < NGX_RTMP_DASH_MAX_SAMPLES) {\n\n        if (ngx_write_fd(t->fd, buffer, size) == NGX_ERROR) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"dash: \" ngx_write_fd_n \" failed\");\n            return NGX_ERROR;\n        }\n\n        smpl = &t->samples[t->sample_count];\n\n        smpl->delay = delay;\n        smpl->size = (uint32_t) size;\n        smpl->duration = 0;\n        smpl->timestamp = timestamp;\n        smpl->key = (key ? 1 : 0);\n\n        if (t->sample_count > 0) {\n            smpl = &t->samples[t->sample_count - 1];\n            smpl->duration = timestamp - smpl->timestamp;\n        }\n\n        t->sample_count++;\n        t->mdat_size += (ngx_uint_t) size;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n    ngx_chain_t *in)\n{\n    u_char                     htype;\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_codec_ctx_t      *codec_ctx;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (dacf == NULL || !dacf->dash || ctx == NULL ||\n        codec_ctx == NULL || h->mlen < 2)\n    {\n        return NGX_OK;\n    }\n\n    /* Only AAC is supported */\n\n    if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC ||\n        codec_ctx->aac_header == NULL)\n    {\n        return NGX_OK;\n    }\n\n    if (in->buf->last - in->buf->pos < 2) {\n        return NGX_ERROR;\n    }\n\n    /* skip AAC config */\n\n    htype = in->buf->pos[1];\n    if (htype != 1) {\n        return NGX_OK;\n    }\n\n    ctx->has_audio = 1;\n\n    /* skip RTMP & AAC headers */\n\n    in->buf->pos += 2;\n\n    return ngx_rtmp_dash_append(s, in, &ctx->audio, 0, h->timestamp, 0);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n    ngx_chain_t *in)\n{\n    u_char                    *p;\n    uint8_t                    ftype, htype;\n    uint32_t                   delay;\n    ngx_rtmp_dash_ctx_t       *ctx;\n    ngx_rtmp_codec_ctx_t      *codec_ctx;\n    ngx_rtmp_dash_app_conf_t  *dacf;\n\n    dacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_dash_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_dash_module);\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (dacf == NULL || !dacf->dash || ctx == NULL || codec_ctx == NULL ||\n        codec_ctx->avc_header == NULL || h->mlen < 5)\n    {\n        return NGX_OK;\n    }\n\n    /* Only H264 is supported */\n\n    if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) {\n        return NGX_OK;\n    }\n\n    if (in->buf->last - in->buf->pos < 5) {\n        return NGX_ERROR;\n    }\n\n    ftype = (in->buf->pos[0] & 0xf0) >> 4;\n\n    /* skip AVC config */\n\n    htype = in->buf->pos[1];\n    if (htype != 1) {\n        return NGX_OK;\n    }\n\n    p = (u_char *) &delay;\n\n    p[0] = in->buf->pos[4];\n    p[1] = in->buf->pos[3];\n    p[2] = in->buf->pos[2];\n    p[3] = 0;\n\n    ctx->has_video = 1;\n\n    /* skip RTMP & H264 headers */\n\n    in->buf->pos += 5;\n\n    return ngx_rtmp_dash_append(s, in, &ctx->video, ftype == 1, h->timestamp,\n                                delay);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)\n{\n    return next_stream_begin(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)\n{\n    ngx_rtmp_dash_close_fragments(s);\n\n    return next_stream_eof(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)\n{\n    time_t           mtime, max_age;\n    u_char          *p;\n    u_char           path[NGX_MAX_PATH + 1], mpd_path[NGX_MAX_PATH + 1];\n    ngx_dir_t        dir;\n    ngx_err_t        err;\n    ngx_str_t        name, spath, mpd;\n    ngx_int_t        nentries, nerased;\n    ngx_file_info_t  fi;\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                   \"dash: cleanup path='%V' playlen=%M\", ppath, playlen);\n\n    if (ngx_open_dir(ppath, &dir) != NGX_OK) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno,\n                       \"dash: cleanup open dir failed '%V'\", ppath);\n        return NGX_ERROR;\n    }\n\n    nentries = 0;\n    nerased = 0;\n\n    for ( ;; ) {\n        ngx_set_errno(0);\n\n        if (ngx_read_dir(&dir) == NGX_ERROR) {\n            err = ngx_errno;\n\n            if (ngx_close_dir(&dir) == NGX_ERROR) {\n                ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,\n                              \"dash: cleanup \" ngx_close_dir_n \" \\\"%V\\\" failed\",\n                              ppath);\n            }\n\n            if (err == NGX_ENOMOREFILES) {\n                return nentries - nerased;\n            }\n\n            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err,\n                          \"dash: cleanup \" ngx_read_dir_n\n                          \" '%V' failed\", ppath);\n            return NGX_ERROR;\n        }\n\n        name.data = ngx_de_name(&dir);\n        if (name.data[0] == '.') {\n            continue;\n        }\n\n        name.len = ngx_de_namelen(&dir);\n\n        p = ngx_snprintf(path, sizeof(path) - 1, \"%V/%V\", ppath, &name);\n        *p = 0;\n\n        spath.data = path;\n        spath.len = p - path;\n\n        nentries++;\n\n        if (!dir.valid_info && ngx_de_info(path, &dir) == NGX_FILE_ERROR) {\n            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,\n                          \"dash: cleanup \" ngx_de_info_n \" \\\"%V\\\" failed\",\n                          &spath);\n\n            continue;\n        }\n\n        if (ngx_de_is_dir(&dir)) {\n\n            if (ngx_rtmp_dash_cleanup_dir(&spath, playlen) == 0) {\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                               \"dash: cleanup dir '%V'\", &name);\n\n                /*\n                 * null-termination gets spoiled in win32\n                 * version of ngx_open_dir\n                 */\n\n                *p = 0;\n\n                if (ngx_delete_dir(path) == NGX_FILE_ERROR) {\n                    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,\n                                  \"dash: cleanup \" ngx_delete_dir_n\n                                  \" failed on '%V'\", &spath);\n                } else {\n                    nerased++;\n                }\n            }\n\n            continue;\n        }\n\n        if (!ngx_de_is_file(&dir)) {\n            continue;\n        }\n\n        if (name.len >= 8 && name.data[name.len - 8] == 'i' &&\n                             name.data[name.len - 7] == 'n' &&\n                             name.data[name.len - 6] == 'i' &&\n                             name.data[name.len - 5] == 't' &&\n                             name.data[name.len - 4] == '.' &&\n                             name.data[name.len - 3] == 'm' &&\n                             name.data[name.len - 2] == '4')\n        {\n            if (name.len == 8) {\n                ngx_str_set(&mpd, \"index\");\n            } else {\n                mpd.data = name.data;\n                mpd.len = name.len - 9;\n            }\n\n            p = ngx_snprintf(mpd_path, sizeof(mpd_path) - 1, \"%V/%V.mpd\",\n                             ppath, &mpd);\n            *p = 0;\n\n            if (ngx_file_info(mpd_path, &fi) != NGX_FILE_ERROR) {\n                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                               \"dash: cleanup '%V' delayed, mpd exists '%s'\",\n                               &name, mpd_path);\n                continue;\n            }\n\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                           \"dash: cleanup '%V' allowed, mpd missing '%s'\",\n                           &name, mpd_path);\n\n            max_age = 0;\n\n        } else if (name.len >= 4 && name.data[name.len - 4] == '.' &&\n                                    name.data[name.len - 3] == 'm' &&\n                                    name.data[name.len - 2] == '4' &&\n                                    name.data[name.len - 1] == 'v')\n        {\n            max_age = playlen / 500;\n\n        } else if (name.len >= 4 && name.data[name.len - 4] == '.' &&\n                                    name.data[name.len - 3] == 'm' &&\n                                    name.data[name.len - 2] == '4' &&\n                                    name.data[name.len - 1] == 'a')\n        {\n            max_age = playlen / 500;\n\n        } else if (name.len >= 4 && name.data[name.len - 4] == '.' &&\n                                    name.data[name.len - 3] == 'm' &&\n                                    name.data[name.len - 2] == 'p' &&\n                                    name.data[name.len - 1] == 'd')\n        {\n            max_age = playlen / 500;\n\n        } else if (name.len >= 4 && name.data[name.len - 4] == '.' &&\n                                    name.data[name.len - 3] == 'r' &&\n                                    name.data[name.len - 2] == 'a' &&\n                                    name.data[name.len - 1] == 'w')\n        {\n            max_age = playlen / 1000;\n\n        } else {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                           \"dash: cleanup skip unknown file type '%V'\", &name);\n            continue;\n        }\n\n        mtime = ngx_de_mtime(&dir);\n        if (mtime + max_age > ngx_cached_time->sec) {\n            continue;\n        }\n\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                       \"dash: cleanup '%V' mtime=%T age=%T\",\n                       &name, mtime, ngx_cached_time->sec - mtime);\n\n        if (ngx_delete_file(path) == NGX_FILE_ERROR) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,\n                          \"dash: cleanup \" ngx_delete_file_n \" failed on '%V'\",\n                          &spath);\n            continue;\n        }\n\n        nerased++;\n    }\n}\n\n\n#if (nginx_version >= 1011005)\nstatic ngx_msec_t\n#else\nstatic time_t\n#endif\nngx_rtmp_dash_cleanup(void *data)\n{\n    ngx_rtmp_dash_cleanup_t *cleanup = data;\n\n    ngx_rtmp_dash_cleanup_dir(&cleanup->path, cleanup->playlen);\n\n#if (nginx_version >= 1011005)\n    return cleanup->playlen * 2;\n#else\n    return cleanup->playlen / 500;\n#endif\n}\n\n\nstatic void *\nngx_rtmp_dash_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_dash_app_conf_t *conf;\n\n    conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_dash_app_conf_t));\n    if (conf == NULL) {\n        return NULL;\n    }\n\n    conf->dash = NGX_CONF_UNSET;\n    conf->fraglen = NGX_CONF_UNSET_MSEC;\n    conf->playlen = NGX_CONF_UNSET_MSEC;\n    conf->cleanup = NGX_CONF_UNSET;\n    conf->nested = NGX_CONF_UNSET;\n\n    return conf;\n}\n\n\nstatic char *\nngx_rtmp_dash_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_dash_app_conf_t    *prev = parent;\n    ngx_rtmp_dash_app_conf_t    *conf = child;\n    ngx_rtmp_dash_cleanup_t     *cleanup;\n\n    ngx_conf_merge_value(conf->dash, prev->dash, 0);\n    ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000);\n    ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000);\n    ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1);\n    ngx_conf_merge_value(conf->nested, prev->nested, 0);\n\n    if (conf->fraglen) {\n        conf->winfrags = conf->playlen / conf->fraglen;\n    }\n\n    /* schedule cleanup */\n\n    if (conf->dash && conf->path.len && conf->cleanup) {\n        if (conf->path.data[conf->path.len - 1] == '/') {\n            conf->path.len--;\n        }\n\n        cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));\n        if (cleanup == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        cleanup->path = conf->path;\n        cleanup->playlen = conf->playlen;\n\n        conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot));\n        if (conf->slot == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        conf->slot->manager = ngx_rtmp_dash_cleanup;\n        conf->slot->name = conf->path;\n        conf->slot->data = cleanup;\n        conf->slot->conf_file = cf->conf_file->file.name.data;\n        conf->slot->line = cf->conf_file->line;\n\n        if (ngx_add_path(cf, &conf->slot) != NGX_OK) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    ngx_conf_merge_str_value(conf->path, prev->path, \"\");\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_dash_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_handler_pt        *h;\n    ngx_rtmp_core_main_conf_t  *cmcf;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);\n    *h = ngx_rtmp_dash_video;\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);\n    *h = ngx_rtmp_dash_audio;\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_dash_publish;\n\n    next_close_stream = ngx_rtmp_close_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_dash_close_stream;\n\n    next_stream_begin = ngx_rtmp_stream_begin;\n    ngx_rtmp_stream_begin = ngx_rtmp_dash_stream_begin;\n\n    next_stream_eof = ngx_rtmp_stream_eof;\n    ngx_rtmp_stream_eof = ngx_rtmp_dash_stream_eof;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "dash/ngx_rtmp_mp4.c",
    "content": "\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_mp4.h\"\n#include <ngx_rtmp_codec_module.h>\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_field_32(ngx_buf_t *b, uint32_t n)\n{\n    u_char  bytes[4];\n\n    bytes[0] = ((uint32_t) n >> 24) & 0xFF;\n    bytes[1] = ((uint32_t) n >> 16) & 0xFF;\n    bytes[2] = ((uint32_t) n >> 8) & 0xFF;\n    bytes[3] = (uint32_t) n & 0xFF;\n\n    if (b->last + sizeof(bytes) > b->end) {\n        return NGX_ERROR;\n    }\n\n    b->last = ngx_cpymem(b->last, bytes, sizeof(bytes));\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_field_24(ngx_buf_t *b, uint32_t n)\n{\n    u_char  bytes[3];\n\n    bytes[0] = ((uint32_t) n >> 16) & 0xFF;\n    bytes[1] = ((uint32_t) n >> 8) & 0xFF;\n    bytes[2] = (uint32_t) n & 0xFF;\n\n    if (b->last + sizeof(bytes) > b->end) {\n        return NGX_ERROR;\n    }\n\n    b->last = ngx_cpymem(b->last, bytes, sizeof(bytes));\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_field_16(ngx_buf_t *b, uint16_t n)\n{\n    u_char  bytes[2];\n\n    bytes[0] = ((uint32_t) n >> 8) & 0xFF;\n    bytes[1] = (uint32_t) n & 0xFF;\n\n    if (b->last + sizeof(bytes) > b->end) {\n        return NGX_ERROR;\n    }\n\n    b->last = ngx_cpymem(b->last, bytes, sizeof(bytes));\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_field_8(ngx_buf_t *b, uint8_t n)\n{\n    u_char  bytes[1];\n\n    bytes[0] = n & 0xFF;\n\n    if (b->last + sizeof(bytes) > b->end) {\n        return NGX_ERROR;\n    }\n\n    b->last = ngx_cpymem(b->last, bytes, sizeof(bytes));\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_put_descr(ngx_buf_t *b, int tag, size_t size)\n{\n    ngx_rtmp_mp4_field_8(b, (uint8_t) tag);\n    ngx_rtmp_mp4_field_8(b, size & 0x7F);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_data(ngx_buf_t *b, void *data, size_t n)\n{\n    if (b->last + n > b->end) {\n        return NGX_ERROR;\n    }\n\n    b->last = ngx_cpymem(b->last, (u_char *) data, n);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_box(ngx_buf_t *b, const char box[4])\n{\n    if (b->last + 4 > b->end) {\n        return NGX_ERROR;\n    }\n\n    b->last = ngx_cpymem(b->last, (u_char *) box, 4);\n\n    return NGX_OK;\n}\n\n\nstatic u_char *\nngx_rtmp_mp4_start_box(ngx_buf_t *b, const char box[4])\n{\n    u_char  *p;\n\n    p = b->last;\n\n    if (ngx_rtmp_mp4_field_32(b, 0) != NGX_OK) {\n        return NULL;\n    }\n\n    if (ngx_rtmp_mp4_box(b, box) != NGX_OK) {\n        return NULL;\n    }\n\n    return p;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_update_box_size(ngx_buf_t *b, u_char *p)\n{\n    u_char  *curpos;\n\n    if (p == NULL) {\n        return NGX_ERROR;\n    }\n\n    curpos = b->last;\n\n    b->last = p;\n\n    ngx_rtmp_mp4_field_32(b, (uint32_t) (curpos - p));\n\n    b->last = curpos;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_matrix(ngx_buf_t *buf, uint32_t a, uint32_t b, uint32_t c,\n    uint32_t d, uint32_t tx, uint32_t ty)\n{\n\n/*\n * transformation matrix\n * |a  b  u|\n * |c  d  v|\n * |tx ty w|\n */\n\n    ngx_rtmp_mp4_field_32(buf, a << 16);  /* 16.16 format */\n    ngx_rtmp_mp4_field_32(buf, b << 16);  /* 16.16 format */\n    ngx_rtmp_mp4_field_32(buf, 0);        /* u in 2.30 format */\n    ngx_rtmp_mp4_field_32(buf, c << 16);  /* 16.16 format */\n    ngx_rtmp_mp4_field_32(buf, d << 16);  /* 16.16 format */\n    ngx_rtmp_mp4_field_32(buf, 0);        /* v in 2.30 format */\n    ngx_rtmp_mp4_field_32(buf, tx << 16); /* 16.16 format */\n    ngx_rtmp_mp4_field_32(buf, ty << 16); /* 16.16 format */\n    ngx_rtmp_mp4_field_32(buf, 1 << 30);  /* w in 2.30 format */\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_mp4_write_ftyp(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"ftyp\");\n\n    /* major brand */\n    ngx_rtmp_mp4_box(b, \"iso6\");\n\n    /* minor version */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    /* compatible brands */\n    ngx_rtmp_mp4_box(b, \"isom\");\n    ngx_rtmp_mp4_box(b, \"iso6\");\n    ngx_rtmp_mp4_box(b, \"dash\");\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_mp4_write_styp(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"styp\");\n\n    /* major brand */\n    ngx_rtmp_mp4_box(b, \"iso6\");\n\n    /* minor version */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    /* compatible brands */\n    ngx_rtmp_mp4_box(b, \"isom\");\n    ngx_rtmp_mp4_box(b, \"iso6\");\n    ngx_rtmp_mp4_box(b, \"dash\");\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_mvhd(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"mvhd\");\n\n    /* version */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* creation time */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* modification time */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* timescale */\n    ngx_rtmp_mp4_field_32(b, 1000);\n\n    /* duration */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0x00010000);\n    ngx_rtmp_mp4_field_16(b, 0x0100);\n    ngx_rtmp_mp4_field_16(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    ngx_rtmp_mp4_write_matrix(b, 1, 0, 0, 1, 0, 0);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* next track id */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_tkhd(ngx_rtmp_session_t *s, ngx_buf_t *b,\n    ngx_rtmp_mp4_track_type_t ttype)\n{\n    u_char                *pos;\n    ngx_rtmp_codec_ctx_t  *codec_ctx;\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    pos = ngx_rtmp_mp4_start_box(b, \"tkhd\");\n\n    /* version */\n    ngx_rtmp_mp4_field_8(b, 0);\n\n    /* flags: TrackEnabled */\n    ngx_rtmp_mp4_field_24(b, 0x0000000f);\n\n    /* creation time */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* modification time */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* track id */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* duration */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_16(b, ttype == NGX_RTMP_MP4_VIDEO_TRACK ?  0 : 0x0100);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_16(b, 0);\n\n    ngx_rtmp_mp4_write_matrix(b, 1, 0, 0, 1, 0, 0);\n\n    if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {\n        ngx_rtmp_mp4_field_32(b, (uint32_t) codec_ctx->width << 16);\n        ngx_rtmp_mp4_field_32(b, (uint32_t) codec_ctx->height << 16);\n    } else {\n        ngx_rtmp_mp4_field_32(b, 0);\n        ngx_rtmp_mp4_field_32(b, 0);\n    }\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_mdhd(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"mdhd\");\n\n    /* version */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* creation time */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* modification time */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* time scale*/\n    ngx_rtmp_mp4_field_32(b, 1000);\n\n    /* duration */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* lanuguage */\n    ngx_rtmp_mp4_field_16(b, 0x15C7);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_16(b, 0);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_hdlr(ngx_buf_t *b, ngx_rtmp_mp4_track_type_t ttype)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"hdlr\");\n\n    /* version and flags */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* pre defined */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {\n        ngx_rtmp_mp4_box(b, \"vide\");\n    } else {\n        ngx_rtmp_mp4_box(b, \"soun\");\n    }\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {\n        /* video handler string, NULL-terminated */\n        ngx_rtmp_mp4_data(b, \"VideoHandler\", sizeof(\"VideoHandler\"));\n    } else {\n        /* sound handler string, NULL-terminated */\n        ngx_rtmp_mp4_data(b, \"SoundHandler\", sizeof(\"SoundHandler\"));\n    }\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_vmhd(ngx_buf_t *b)\n{\n    /* size is always 20, apparently */\n    ngx_rtmp_mp4_field_32(b, 20);\n\n    ngx_rtmp_mp4_box(b, \"vmhd\");\n\n    /* version and flags */\n    ngx_rtmp_mp4_field_32(b, 0x01);\n\n    /* reserved (graphics mode=copy) */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_smhd(ngx_buf_t *b)\n{\n    /* size is always 16, apparently */\n    ngx_rtmp_mp4_field_32(b, 16);\n\n    ngx_rtmp_mp4_box(b, \"smhd\");\n\n    /* version and flags */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* reserved (balance normally=0) */\n    ngx_rtmp_mp4_field_16(b, 0);\n    ngx_rtmp_mp4_field_16(b, 0);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_dref(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"dref\");\n\n    /* version and flags */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* entry count */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    /* url size */\n    ngx_rtmp_mp4_field_32(b, 0xc);\n\n    ngx_rtmp_mp4_box(b, \"url \");\n\n    /* version and flags */\n    ngx_rtmp_mp4_field_32(b, 0x00000001);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_dinf(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"dinf\");\n\n    ngx_rtmp_mp4_write_dref(b);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_avcc(ngx_rtmp_session_t *s, ngx_buf_t *b)\n{\n    u_char                *pos, *p;\n    ngx_chain_t           *in;\n    ngx_rtmp_codec_ctx_t  *codec_ctx;\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (codec_ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    in = codec_ctx->avc_header;\n    if (in == NULL) {\n        return NGX_ERROR;\n    }\n\n    pos = ngx_rtmp_mp4_start_box(b, \"avcC\");\n\n    /* assume config fits one chunk (highly probable) */\n\n    /*\n     * Skip:\n     * - flv fmt\n     * - H264 CONF/PICT (0x00)\n     * - 0\n     * - 0\n     * - 0\n     */\n\n    p = in->buf->pos + 5;\n\n    if (p < in->buf->last) {\n        ngx_rtmp_mp4_data(b, p, (size_t) (in->buf->last - p));\n    } else {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"dash: invalid avcc received\");\n    }\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_video(ngx_rtmp_session_t *s, ngx_buf_t *b)\n{\n    u_char                *pos;\n    ngx_rtmp_codec_ctx_t  *codec_ctx;\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    pos = ngx_rtmp_mp4_start_box(b, \"avc1\");\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_16(b, 0);\n\n    /* data reference index */\n    ngx_rtmp_mp4_field_16(b, 1);\n\n    /* codec stream version & revision */\n    ngx_rtmp_mp4_field_16(b, 0);\n    ngx_rtmp_mp4_field_16(b, 0);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* width & height */\n    ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->width);\n    ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->height);\n\n    /* horizontal & vertical resolutions 72 dpi */\n    ngx_rtmp_mp4_field_32(b, 0x00480000);\n    ngx_rtmp_mp4_field_32(b, 0x00480000);\n\n    /* data size */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* frame count */\n    ngx_rtmp_mp4_field_16(b, 1);\n\n    /* compressor name */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_16(b, 0x18);\n    ngx_rtmp_mp4_field_16(b, 0xffff);\n\n    ngx_rtmp_mp4_write_avcc(s, b);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_esds(ngx_rtmp_session_t *s, ngx_buf_t *b)\n{\n    size_t                 dsi_len;\n    u_char                *pos, *dsi;\n    ngx_buf_t             *db;\n    ngx_rtmp_codec_ctx_t  *codec_ctx;\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (codec_ctx == NULL || codec_ctx->aac_header == NULL) {\n        return NGX_ERROR;\n    }\n\n    db = codec_ctx->aac_header->buf;\n    if (db == NULL) {\n        return NGX_ERROR;\n    }\n\n    dsi = db->pos + 2;\n    if (dsi > db->last) {\n        return NGX_ERROR;\n    }\n\n    dsi_len = db->last - dsi;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"esds\");\n\n    /* version */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n\n    /* ES Descriptor */\n\n    ngx_rtmp_mp4_put_descr(b, 0x03, 23 + dsi_len);\n\n    /* ES_ID */\n    ngx_rtmp_mp4_field_16(b, 1);\n\n    /* flags */\n    ngx_rtmp_mp4_field_8(b, 0);\n\n\n    /* DecoderConfig Descriptor */\n\n    ngx_rtmp_mp4_put_descr(b, 0x04, 15 + dsi_len);\n\n    /* objectTypeIndication: Audio ISO/IEC 14496-3 (AAC) */\n    ngx_rtmp_mp4_field_8(b, 0x40);\n\n    /* streamType: AudioStream */\n    ngx_rtmp_mp4_field_8(b, 0x15);\n\n    /* bufferSizeDB */\n    ngx_rtmp_mp4_field_24(b, 0);\n\n    /* maxBitrate */\n    ngx_rtmp_mp4_field_32(b, 0x0001F151);\n\n    /* avgBitrate */\n    ngx_rtmp_mp4_field_32(b, 0x0001F14D);\n\n\n    /* DecoderSpecificInfo Descriptor */\n\n    ngx_rtmp_mp4_put_descr(b, 0x05, dsi_len);\n    ngx_rtmp_mp4_data(b, dsi, dsi_len);\n\n\n    /* SL Descriptor */\n\n    ngx_rtmp_mp4_put_descr(b, 0x06, 1);\n    ngx_rtmp_mp4_field_8(b, 0x02);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_audio(ngx_rtmp_session_t *s, ngx_buf_t *b)\n{\n    u_char                *pos;\n    ngx_rtmp_codec_ctx_t  *codec_ctx;\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    pos = ngx_rtmp_mp4_start_box(b, \"mp4a\");\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_16(b, 0);\n\n    /* data reference index */\n    ngx_rtmp_mp4_field_16(b, 1);\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* channel count */\n    ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->audio_channels);\n\n    /* sample size */\n    ngx_rtmp_mp4_field_16(b, (uint16_t) (codec_ctx->sample_size * 8));\n\n    /* reserved */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* time scale */\n    ngx_rtmp_mp4_field_16(b, 1000);\n\n    /* sample rate */\n    ngx_rtmp_mp4_field_16(b, (uint16_t) codec_ctx->sample_rate);\n\n    ngx_rtmp_mp4_write_esds(s, b);\n#if 0\n    /* tag size*/\n    ngx_rtmp_mp4_field_32(b, 8);\n\n    /* null tag */\n    ngx_rtmp_mp4_field_32(b, 0);\n#endif\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_stsd(ngx_rtmp_session_t *s, ngx_buf_t *b,\n    ngx_rtmp_mp4_track_type_t ttype)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"stsd\");\n\n    /* version & flags */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* entry count */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {\n        ngx_rtmp_mp4_write_video(s, b);\n    } else {\n        ngx_rtmp_mp4_write_audio(s, b);\n    }\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_stts(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"stts\");\n\n    ngx_rtmp_mp4_field_32(b, 0); /* version */\n    ngx_rtmp_mp4_field_32(b, 0); /* entry count */\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_stsc(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"stsc\");\n\n    ngx_rtmp_mp4_field_32(b, 0); /* version */\n    ngx_rtmp_mp4_field_32(b, 0); /* entry count */\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_stsz(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"stsz\");\n\n    ngx_rtmp_mp4_field_32(b, 0); /* version */\n    ngx_rtmp_mp4_field_32(b, 0); /* entry count */\n    ngx_rtmp_mp4_field_32(b, 0); /* moar zeros */\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_stco(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"stco\");\n\n    ngx_rtmp_mp4_field_32(b, 0); /* version */\n    ngx_rtmp_mp4_field_32(b, 0); /* entry count */\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_stbl(ngx_rtmp_session_t *s, ngx_buf_t *b,\n    ngx_rtmp_mp4_track_type_t ttype)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"stbl\");\n\n    ngx_rtmp_mp4_write_stsd(s, b, ttype);\n    ngx_rtmp_mp4_write_stts(b);\n    ngx_rtmp_mp4_write_stsc(b);\n    ngx_rtmp_mp4_write_stsz(b);\n    ngx_rtmp_mp4_write_stco(b);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_minf(ngx_rtmp_session_t *s, ngx_buf_t *b,\n    ngx_rtmp_mp4_track_type_t ttype)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"minf\");\n\n    if (ttype == NGX_RTMP_MP4_VIDEO_TRACK) {\n        ngx_rtmp_mp4_write_vmhd(b);\n    } else {\n        ngx_rtmp_mp4_write_smhd(b);\n    }\n\n    ngx_rtmp_mp4_write_dinf(b);\n    ngx_rtmp_mp4_write_stbl(s, b, ttype);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_mdia(ngx_rtmp_session_t *s, ngx_buf_t *b,\n    ngx_rtmp_mp4_track_type_t ttype)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"mdia\");\n\n    ngx_rtmp_mp4_write_mdhd(b);\n    ngx_rtmp_mp4_write_hdlr(b, ttype);\n    ngx_rtmp_mp4_write_minf(s, b, ttype);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_trak(ngx_rtmp_session_t *s, ngx_buf_t *b,\n    ngx_rtmp_mp4_track_type_t ttype)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"trak\");\n\n    ngx_rtmp_mp4_write_tkhd(s, b, ttype);\n    ngx_rtmp_mp4_write_mdia(s, b, ttype);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_mvex(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"mvex\");\n\n    ngx_rtmp_mp4_field_32(b, 0x20);\n\n    ngx_rtmp_mp4_box(b, \"trex\");\n\n    /* version & flags */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* track id */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    /* default sample description index */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    /* default sample duration */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* default sample size, 1024 for AAC */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* default sample flags, key on */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b,\n    ngx_rtmp_mp4_track_type_t ttype)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"moov\");\n\n    ngx_rtmp_mp4_write_mvhd(b);\n    ngx_rtmp_mp4_write_mvex(b);\n    ngx_rtmp_mp4_write_trak(s, b, ttype);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_tfhd(ngx_buf_t *b)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"tfhd\");\n\n    /* version & flags */\n    ngx_rtmp_mp4_field_32(b, 0x00020000);\n\n    /* track id */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_tfdt(ngx_buf_t *b, uint32_t earliest_pres_time)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"tfdt\");\n\n    /* version == 1 aka 64 bit integer */\n    ngx_rtmp_mp4_field_32(b, 0x00000000);\n    ngx_rtmp_mp4_field_32(b, earliest_pres_time);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_trun(ngx_buf_t *b, uint32_t sample_count,\n    ngx_rtmp_mp4_sample_t *samples, ngx_uint_t sample_mask, u_char *moof_pos)\n{\n    u_char    *pos;\n    uint32_t   i, offset, nitems, flags;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"trun\");\n\n    nitems = 0;\n\n    /* data offset present */\n    flags = 0x01;\n\n    if (sample_mask & NGX_RTMP_MP4_SAMPLE_DURATION) {\n        nitems++;\n        flags |= 0x000100;\n    }\n\n    if (sample_mask & NGX_RTMP_MP4_SAMPLE_SIZE) {\n        nitems++;\n        flags |= 0x000200;\n    }\n\n    if (sample_mask & NGX_RTMP_MP4_SAMPLE_KEY) {\n        nitems++;\n        flags |= 0x000400;\n    }\n\n    if (sample_mask & NGX_RTMP_MP4_SAMPLE_DELAY) {\n        nitems++;\n        flags |= 0x000800;\n    }\n\n    offset = (pos - moof_pos) + 20 + (sample_count * nitems * 4) + 8;\n\n    ngx_rtmp_mp4_field_32(b, flags);\n    ngx_rtmp_mp4_field_32(b, sample_count);\n    ngx_rtmp_mp4_field_32(b, offset);\n\n    for (i = 0; i < sample_count; i++, samples++) {\n\n        if (sample_mask & NGX_RTMP_MP4_SAMPLE_DURATION) {\n            ngx_rtmp_mp4_field_32(b, samples->duration);\n        }\n\n        if (sample_mask & NGX_RTMP_MP4_SAMPLE_SIZE) {\n            ngx_rtmp_mp4_field_32(b, samples->size);\n        }\n\n        if (sample_mask & NGX_RTMP_MP4_SAMPLE_KEY) {\n            ngx_rtmp_mp4_field_32(b, samples->key ? 0x00000000 : 0x00010000);\n        }\n\n        if (sample_mask & NGX_RTMP_MP4_SAMPLE_DELAY) {\n            ngx_rtmp_mp4_field_32(b, samples->delay);\n        }\n    }\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_traf(ngx_buf_t *b, uint32_t earliest_pres_time,\n    uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,\n    ngx_uint_t sample_mask, u_char *moof_pos)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"traf\");\n\n    ngx_rtmp_mp4_write_tfhd(b);\n    ngx_rtmp_mp4_write_tfdt(b, earliest_pres_time);\n    ngx_rtmp_mp4_write_trun(b, sample_count, samples, sample_mask, moof_pos);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_write_mfhd(ngx_buf_t *b, uint32_t index)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"mfhd\");\n\n    /* don't know what this is */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* fragment index. */\n    ngx_rtmp_mp4_field_32(b, index);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_mp4_write_sidx(ngx_buf_t *b, ngx_uint_t reference_size,\n    uint32_t earliest_pres_time, uint32_t latest_pres_time)\n{\n    u_char    *pos;\n    uint32_t   duration;\n\n    duration = latest_pres_time - earliest_pres_time;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"sidx\");\n\n    /* version */\n    ngx_rtmp_mp4_field_32(b, 0);\n\n    /* reference id */\n    ngx_rtmp_mp4_field_32(b, 1);\n\n    /* timescale */\n    ngx_rtmp_mp4_field_32(b, 1000);\n\n    /* earliest presentation time */\n    ngx_rtmp_mp4_field_32(b, earliest_pres_time);\n\n    /* first offset */\n    ngx_rtmp_mp4_field_32(b, duration); /*TODO*/\n\n    /* reserved */\n    ngx_rtmp_mp4_field_16(b, 0);\n\n    /* reference count = 1 */\n    ngx_rtmp_mp4_field_16(b, 1);\n\n    /* 1st bit is reference type, the rest is reference size */\n    ngx_rtmp_mp4_field_32(b, reference_size);\n\n    /* subsegment duration */\n    ngx_rtmp_mp4_field_32(b, duration);\n\n    /* first bit is startsWithSAP (=1), next 3 bits are SAP type (=001) */\n    ngx_rtmp_mp4_field_8(b, 0x90);\n\n    /* SAP delta time */\n    ngx_rtmp_mp4_field_24(b, 0);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time,\n    uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,\n    ngx_uint_t sample_mask, uint32_t index)\n{\n    u_char  *pos;\n\n    pos = ngx_rtmp_mp4_start_box(b, \"moof\");\n\n    ngx_rtmp_mp4_write_mfhd(b, index);\n    ngx_rtmp_mp4_write_traf(b, earliest_pres_time, sample_count, samples,\n                            sample_mask, pos);\n\n    ngx_rtmp_mp4_update_box_size(b, pos);\n\n    return NGX_OK;\n}\n\n\nngx_uint_t\nngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size)\n{\n    ngx_rtmp_mp4_field_32(b, size);\n\n    ngx_rtmp_mp4_box(b, \"mdat\");\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "dash/ngx_rtmp_mp4.h",
    "content": "\n\n#ifndef _NGX_RTMP_MP4_H_INCLUDED_\n#define _NGX_RTMP_MP4_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_rtmp.h>\n\n\n#define NGX_RTMP_MP4_SAMPLE_SIZE        0x01\n#define NGX_RTMP_MP4_SAMPLE_DURATION    0x02\n#define NGX_RTMP_MP4_SAMPLE_DELAY       0x04\n#define NGX_RTMP_MP4_SAMPLE_KEY         0x08\n\n\ntypedef struct {\n    uint32_t        size;\n    uint32_t        duration;\n    uint32_t        delay;\n    uint32_t        timestamp;\n    unsigned        key:1;\n} ngx_rtmp_mp4_sample_t;\n\n\ntypedef enum {\n    NGX_RTMP_MP4_FILETYPE_INIT,\n    NGX_RTMP_MP4_FILETYPE_SEG\n} ngx_rtmp_mp4_file_type_t;\n\n\ntypedef enum {\n    NGX_RTMP_MP4_VIDEO_TRACK,\n    NGX_RTMP_MP4_AUDIO_TRACK\n} ngx_rtmp_mp4_track_type_t;\n\n\nngx_int_t ngx_rtmp_mp4_write_ftyp(ngx_buf_t *b);\nngx_int_t ngx_rtmp_mp4_write_styp(ngx_buf_t *b);\nngx_int_t ngx_rtmp_mp4_write_moov(ngx_rtmp_session_t *s, ngx_buf_t *b,\n    ngx_rtmp_mp4_track_type_t ttype);\nngx_int_t ngx_rtmp_mp4_write_moof(ngx_buf_t *b, uint32_t earliest_pres_time,\n    uint32_t sample_count, ngx_rtmp_mp4_sample_t *samples,\n    ngx_uint_t sample_mask, uint32_t index);\nngx_int_t ngx_rtmp_mp4_write_sidx(ngx_buf_t *b,\n    ngx_uint_t reference_size, uint32_t earliest_pres_time,\n    uint32_t latest_pres_time);\nngx_uint_t ngx_rtmp_mp4_write_mdat(ngx_buf_t *b, ngx_uint_t size);\n\n\n#endif /* _NGX_RTMP_MP4_H_INCLUDED_ */\n"
  },
  {
    "path": "doc/README.md",
    "content": "Documentation is available here:\nhttps://github.com/arut/nginx-rtmp-module/wiki\n"
  },
  {
    "path": "hls/ngx_rtmp_hls_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_rtmp.h>\n#include <ngx_rtmp_cmd_module.h>\n#include <ngx_rtmp_codec_module.h>\n#include \"ngx_rtmp_mpegts.h\"\n\n\nstatic ngx_rtmp_publish_pt              next_publish;\nstatic ngx_rtmp_close_stream_pt         next_close_stream;\nstatic ngx_rtmp_stream_begin_pt         next_stream_begin;\nstatic ngx_rtmp_stream_eof_pt           next_stream_eof;\n\n\nstatic char * ngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic ngx_int_t ngx_rtmp_hls_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_hls_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\nstatic ngx_int_t ngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s);\nstatic ngx_int_t ngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s,\n       ngx_str_t *path);\n\n\n#define NGX_RTMP_HLS_BUFSIZE            (1024*1024)\n#define NGX_RTMP_HLS_DIR_ACCESS         0744\n\n\ntypedef struct {\n    uint64_t                            id;\n    uint64_t                            key_id;\n    double                              duration;\n    unsigned                            active:1;\n    unsigned                            discont:1; /* before */\n} ngx_rtmp_hls_frag_t;\n\n\ntypedef struct {\n    ngx_str_t                           suffix;\n    ngx_array_t                         args;\n} ngx_rtmp_hls_variant_t;\n\n\ntypedef struct {\n    unsigned                            opened:1;\n\n    ngx_rtmp_mpegts_file_t              file;\n\n    ngx_str_t                           playlist;\n    ngx_str_t                           playlist_bak;\n    ngx_str_t                           var_playlist;\n    ngx_str_t                           var_playlist_bak;\n    ngx_str_t                           stream;\n    ngx_str_t                           keyfile;\n    ngx_str_t                           name;\n    u_char                              key[16];\n\n    uint64_t                            frag;\n    uint64_t                            frag_ts;\n    uint64_t                            key_id;\n    ngx_uint_t                          nfrags;\n    ngx_rtmp_hls_frag_t                *frags; /* circular 2 * winfrags + 1 */\n\n    ngx_uint_t                          audio_cc;\n    ngx_uint_t                          video_cc;\n    ngx_uint_t                          key_frags;\n\n    uint64_t                            aframe_base;\n    uint64_t                            aframe_num;\n\n    ngx_buf_t                          *aframe;\n    uint64_t                            aframe_pts;\n\n    ngx_rtmp_hls_variant_t             *var;\n} ngx_rtmp_hls_ctx_t;\n\n\ntypedef struct {\n    ngx_str_t                           path;\n    ngx_msec_t                          playlen;\n    ngx_uint_t                          frags_per_key;\n} ngx_rtmp_hls_cleanup_t;\n\n\ntypedef struct {\n    ngx_flag_t                          hls;\n    ngx_msec_t                          fraglen;\n    ngx_msec_t                          max_fraglen;\n    ngx_msec_t                          muxdelay;\n    ngx_msec_t                          sync;\n    ngx_msec_t                          playlen;\n    ngx_uint_t                          winfrags;\n    ngx_flag_t                          continuous;\n    ngx_flag_t                          nested;\n    ngx_str_t                           path;\n    ngx_uint_t                          naming;\n    ngx_uint_t                          slicing;\n    ngx_uint_t                          type;\n    ngx_path_t                         *slot;\n    ngx_msec_t                          max_audio_delay;\n    size_t                              audio_buffer_size;\n    ngx_flag_t                          cleanup;\n    ngx_array_t                        *variant;\n    ngx_str_t                           base_url;\n    ngx_int_t                           granularity;\n    ngx_flag_t                          keys;\n    ngx_str_t                           key_path;\n    ngx_str_t                           key_url;\n    ngx_uint_t                          frags_per_key;\n} ngx_rtmp_hls_app_conf_t;\n\n\n#define NGX_RTMP_HLS_NAMING_SEQUENTIAL  1\n#define NGX_RTMP_HLS_NAMING_TIMESTAMP   2\n#define NGX_RTMP_HLS_NAMING_SYSTEM      3\n\n\n#define NGX_RTMP_HLS_SLICING_PLAIN      1\n#define NGX_RTMP_HLS_SLICING_ALIGNED    2\n\n\n#define NGX_RTMP_HLS_TYPE_LIVE          1\n#define NGX_RTMP_HLS_TYPE_EVENT         2\n\n\nstatic ngx_conf_enum_t                  ngx_rtmp_hls_naming_slots[] = {\n    { ngx_string(\"sequential\"),         NGX_RTMP_HLS_NAMING_SEQUENTIAL },\n    { ngx_string(\"timestamp\"),          NGX_RTMP_HLS_NAMING_TIMESTAMP  },\n    { ngx_string(\"system\"),             NGX_RTMP_HLS_NAMING_SYSTEM     },\n    { ngx_null_string,                  0 }\n};\n\n\nstatic ngx_conf_enum_t                  ngx_rtmp_hls_slicing_slots[] = {\n    { ngx_string(\"plain\"),              NGX_RTMP_HLS_SLICING_PLAIN },\n    { ngx_string(\"aligned\"),            NGX_RTMP_HLS_SLICING_ALIGNED  },\n    { ngx_null_string,                  0 }\n};\n\n\nstatic ngx_conf_enum_t                  ngx_rtmp_hls_type_slots[] = {\n    { ngx_string(\"live\"),               NGX_RTMP_HLS_TYPE_LIVE  },\n    { ngx_string(\"event\"),              NGX_RTMP_HLS_TYPE_EVENT },\n    { ngx_null_string,                  0 }\n};\n\n\nstatic ngx_command_t ngx_rtmp_hls_commands[] = {\n\n    { ngx_string(\"hls\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, hls),\n      NULL },\n\n    { ngx_string(\"hls_fragment\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, fraglen),\n      NULL },\n\n    { ngx_string(\"hls_max_fragment\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, max_fraglen),\n      NULL },\n\n    { ngx_string(\"hls_path\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, path),\n      NULL },\n\n    { ngx_string(\"hls_playlist_length\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, playlen),\n      NULL },\n\n    { ngx_string(\"hls_muxdelay\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, muxdelay),\n      NULL },\n\n    { ngx_string(\"hls_sync\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, sync),\n      NULL },\n\n    { ngx_string(\"hls_continuous\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, continuous),\n      NULL },\n\n    { ngx_string(\"hls_nested\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, nested),\n      NULL },\n\n    { ngx_string(\"hls_fragment_naming\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_enum_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, naming),\n      &ngx_rtmp_hls_naming_slots },\n\n    { ngx_string(\"hls_fragment_slicing\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_enum_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, slicing),\n      &ngx_rtmp_hls_slicing_slots },\n\n    { ngx_string(\"hls_type\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_enum_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, type),\n      &ngx_rtmp_hls_type_slots },\n\n    { ngx_string(\"hls_max_audio_delay\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, max_audio_delay),\n      NULL },\n\n    { ngx_string(\"hls_audio_buffer_size\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_size_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, audio_buffer_size),\n      NULL },\n\n    { ngx_string(\"hls_cleanup\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, cleanup),\n      NULL },\n\n    { ngx_string(\"hls_variant\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_hls_variant,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"hls_base_url\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, base_url),\n      NULL },\n\n    { ngx_string(\"hls_fragment_naming_granularity\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_num_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, granularity),\n      NULL },\n\n    { ngx_string(\"hls_keys\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, keys),\n      NULL },\n\n    { ngx_string(\"hls_key_path\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, key_path),\n      NULL },\n\n    { ngx_string(\"hls_key_url\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, key_url),\n      NULL },\n\n    { ngx_string(\"hls_fragments_per_key\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_num_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_hls_app_conf_t, frags_per_key),\n      NULL },\n\n    ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_hls_module_ctx = {\n    NULL,                               /* preconfiguration */\n    ngx_rtmp_hls_postconfiguration,     /* postconfiguration */\n\n    NULL,                               /* create main configuration */\n    NULL,                               /* init main configuration */\n\n    NULL,                               /* create server configuration */\n    NULL,                               /* merge server configuration */\n\n    ngx_rtmp_hls_create_app_conf,       /* create location configuration */\n    ngx_rtmp_hls_merge_app_conf,        /* merge location configuration */\n};\n\n\nngx_module_t  ngx_rtmp_hls_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_hls_module_ctx,           /* module context */\n    ngx_rtmp_hls_commands,              /* module directives */\n    NGX_RTMP_MODULE,                    /* module type */\n    NULL,                               /* init master */\n    NULL,                               /* init module */\n    NULL,                               /* init process */\n    NULL,                               /* init thread */\n    NULL,                               /* exit thread */\n    NULL,                               /* exit process */\n    NULL,                               /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic ngx_rtmp_hls_frag_t *\nngx_rtmp_hls_get_frag(ngx_rtmp_session_t *s, ngx_int_t n)\n{\n    ngx_rtmp_hls_ctx_t         *ctx;\n    ngx_rtmp_hls_app_conf_t    *hacf;\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    return &ctx->frags[(ctx->frag + n) % (hacf->winfrags * 2 + 1)];\n}\n\n\nstatic void\nngx_rtmp_hls_next_frag(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_hls_ctx_t         *ctx;\n    ngx_rtmp_hls_app_conf_t    *hacf;\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    if (ctx->nfrags == hacf->winfrags) {\n        ctx->frag++;\n    } else {\n        ctx->nfrags++;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_rename_file(u_char *src, u_char *dst)\n{\n    /* rename file with overwrite */\n\n#if (NGX_WIN32)\n    return MoveFileEx((LPCTSTR) src, (LPCTSTR) dst, MOVEFILE_REPLACE_EXISTING);\n#else\n    return ngx_rename_file(src, dst);\n#endif\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_write_variant_playlist(ngx_rtmp_session_t *s)\n{\n    static u_char             buffer[1024];\n\n    u_char                   *p, *last;\n    ssize_t                   rc;\n    ngx_fd_t                  fd;\n    ngx_str_t                *arg;\n    ngx_uint_t                n, k;\n    ngx_rtmp_hls_ctx_t       *ctx;\n    ngx_rtmp_hls_variant_t   *var;\n    ngx_rtmp_hls_app_conf_t  *hacf;\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    fd = ngx_open_file(ctx->var_playlist_bak.data, NGX_FILE_WRONLY,\n                       NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);\n\n    if (fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"hls: \" ngx_open_file_n \" failed: '%V'\",\n                      &ctx->var_playlist_bak);\n\n        return NGX_ERROR;\n    }\n\n#define NGX_RTMP_HLS_VAR_HEADER \"#EXTM3U\\n#EXT-X-VERSION:3\\n\"\n\n    rc = ngx_write_fd(fd, NGX_RTMP_HLS_VAR_HEADER,\n                      sizeof(NGX_RTMP_HLS_VAR_HEADER) - 1);\n    if (rc < 0) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"hls: \" ngx_write_fd_n \" failed: '%V'\",\n                      &ctx->var_playlist_bak);\n        ngx_close_file(fd);\n        return NGX_ERROR;\n    }\n\n    var = hacf->variant->elts;\n    for (n = 0; n < hacf->variant->nelts; n++, var++)\n    {\n        p = buffer;\n        last = buffer + sizeof(buffer);\n\n        p = ngx_slprintf(p, last, \"#EXT-X-STREAM-INF:PROGRAM-ID=1\");\n\n        arg = var->args.elts;\n        for (k = 0; k < var->args.nelts; k++, arg++) {\n            p = ngx_slprintf(p, last, \",%V\", arg);\n        }\n\n        if (p < last) {\n            *p++ = '\\n';\n        }\n\n        p = ngx_slprintf(p, last, \"%V%*s%V\",\n                         &hacf->base_url,\n                         ctx->name.len - ctx->var->suffix.len, ctx->name.data,\n                         &var->suffix);\n        if (hacf->nested) {\n            p = ngx_slprintf(p, last, \"%s\", \"/index\");\n        }\n\n        p = ngx_slprintf(p, last, \"%s\", \".m3u8\\n\");\n\n        rc = ngx_write_fd(fd, buffer, p - buffer);\n        if (rc < 0) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"hls: \" ngx_write_fd_n \" failed '%V'\",\n                          &ctx->var_playlist_bak);\n            ngx_close_file(fd);\n            return NGX_ERROR;\n        }\n    }\n\n    ngx_close_file(fd);\n\n    if (ngx_rtmp_hls_rename_file(ctx->var_playlist_bak.data,\n                                 ctx->var_playlist.data)\n        == NGX_FILE_ERROR)\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"hls: rename failed: '%V'->'%V'\",\n                      &ctx->var_playlist_bak, &ctx->var_playlist);\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_write_playlist(ngx_rtmp_session_t *s)\n{\n    static u_char                   buffer[1024];\n    ngx_fd_t                        fd;\n    u_char                         *p, *end;\n    ngx_rtmp_hls_ctx_t             *ctx;\n    ssize_t                         n;\n    ngx_rtmp_hls_app_conf_t        *hacf;\n    ngx_rtmp_hls_frag_t            *f;\n    ngx_uint_t                      i, max_frag;\n    ngx_str_t                       name_part, key_name_part;\n    uint64_t                        prev_key_id;\n    const char                     *sep, *key_sep;\n\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    fd = ngx_open_file(ctx->playlist_bak.data, NGX_FILE_WRONLY,\n                       NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);\n\n    if (fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"hls: \" ngx_open_file_n \" failed: '%V'\",\n                      &ctx->playlist_bak);\n        return NGX_ERROR;\n    }\n\n    max_frag = hacf->fraglen / 1000;\n\n    for (i = 0; i < ctx->nfrags; i++) {\n        f = ngx_rtmp_hls_get_frag(s, i);\n        if (f->duration > max_frag) {\n            max_frag = (ngx_uint_t) (f->duration + .5);\n        }\n    }\n\n    p = buffer;\n    end = p + sizeof(buffer);\n\n    p = ngx_slprintf(p, end,\n                     \"#EXTM3U\\n\"\n                     \"#EXT-X-VERSION:3\\n\"\n                     \"#EXT-X-MEDIA-SEQUENCE:%uL\\n\"\n                     \"#EXT-X-TARGETDURATION:%ui\\n\",\n                     ctx->frag, max_frag);\n\n    if (hacf->type == NGX_RTMP_HLS_TYPE_EVENT) {\n        p = ngx_slprintf(p, end, \"#EXT-X-PLAYLIST-TYPE: EVENT\\n\");\n    }\n\n    n = ngx_write_fd(fd, buffer, p - buffer);\n    if (n < 0) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"hls: \" ngx_write_fd_n \" failed: '%V'\",\n                      &ctx->playlist_bak);\n        ngx_close_file(fd);\n        return NGX_ERROR;\n    }\n\n    sep = hacf->nested ? (hacf->base_url.len ? \"/\" : \"\") : \"-\";\n    key_sep = hacf->nested ? (hacf->key_url.len ? \"/\" : \"\") : \"-\";\n\n    name_part.len = 0;\n    if (!hacf->nested || hacf->base_url.len) {\n        name_part = ctx->name;\n    }\n\n    key_name_part.len = 0;\n    if (!hacf->nested || hacf->key_url.len) {\n        key_name_part = ctx->name;\n    }\n\n    prev_key_id = 0;\n\n    for (i = 0; i < ctx->nfrags; i++) {\n        f = ngx_rtmp_hls_get_frag(s, i);\n\n        p = buffer;\n        end = p + sizeof(buffer);\n\n        if (f->discont) {\n            p = ngx_slprintf(p, end, \"#EXT-X-DISCONTINUITY\\n\");\n        }\n\n        if (hacf->keys && (i == 0 || f->key_id != prev_key_id)) {\n            p = ngx_slprintf(p, end, \"#EXT-X-KEY:METHOD=AES-128,\"\n                             \"URI=\\\"%V%V%s%uL.key\\\",IV=0x%032XL\\n\",\n                             &hacf->key_url, &key_name_part,\n                             key_sep, f->key_id, f->key_id);\n        }\n\n        prev_key_id = f->key_id;\n\n        p = ngx_slprintf(p, end,\n                         \"#EXTINF:%.3f,\\n\"\n                         \"%V%V%s%uL.ts\\n\",\n                         f->duration, &hacf->base_url, &name_part, sep, f->id);\n\n        ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"hls: fragment frag=%uL, n=%ui/%ui, duration=%.3f, \"\n                       \"discont=%i\",\n                       ctx->frag, i + 1, ctx->nfrags, f->duration, f->discont);\n\n        n = ngx_write_fd(fd, buffer, p - buffer);\n        if (n < 0) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"hls: \" ngx_write_fd_n \" failed '%V'\",\n                          &ctx->playlist_bak);\n            ngx_close_file(fd);\n            return NGX_ERROR;\n        }\n    }\n\n    ngx_close_file(fd);\n\n    if (ngx_rtmp_hls_rename_file(ctx->playlist_bak.data, ctx->playlist.data)\n        == NGX_FILE_ERROR)\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"hls: rename failed: '%V'->'%V'\",\n                      &ctx->playlist_bak, &ctx->playlist);\n        return NGX_ERROR;\n    }\n\n    if (ctx->var) {\n        return ngx_rtmp_hls_write_variant_playlist(s);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_copy(ngx_rtmp_session_t *s, void *dst, u_char **src, size_t n,\n    ngx_chain_t **in)\n{\n    u_char  *last;\n    size_t   pn;\n\n    if (*in == NULL) {\n        return NGX_ERROR;\n    }\n\n    for ( ;; ) {\n        last = (*in)->buf->last;\n\n        if ((size_t)(last - *src) >= n) {\n            if (dst) {\n                ngx_memcpy(dst, *src, n);\n            }\n\n            *src += n;\n\n            while (*in && *src == (*in)->buf->last) {\n                *in = (*in)->next;\n                if (*in) {\n                    *src = (*in)->buf->pos;\n                }\n            }\n\n            return NGX_OK;\n        }\n\n        pn = last - *src;\n\n        if (dst) {\n            ngx_memcpy(dst, *src, pn);\n            dst = (u_char *)dst + pn;\n        }\n\n        n -= pn;\n        *in = (*in)->next;\n\n        if (*in == NULL) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"hls: failed to read %uz byte(s)\", n);\n            return NGX_ERROR;\n        }\n\n        *src = (*in)->buf->pos;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_append_aud(ngx_rtmp_session_t *s, ngx_buf_t *out)\n{\n    static u_char   aud_nal[] = { 0x00, 0x00, 0x00, 0x01, 0x09, 0xf0 };\n\n    if (out->last + sizeof(aud_nal) > out->end) {\n        return NGX_ERROR;\n    }\n\n    out->last = ngx_cpymem(out->last, aud_nal, sizeof(aud_nal));\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_append_sps_pps(ngx_rtmp_session_t *s, ngx_buf_t *out)\n{\n    ngx_rtmp_codec_ctx_t           *codec_ctx;\n    u_char                         *p;\n    ngx_chain_t                    *in;\n    ngx_rtmp_hls_ctx_t             *ctx;\n    int8_t                          nnals;\n    uint16_t                        len, rlen;\n    ngx_int_t                       n;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (ctx == NULL || codec_ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    in = codec_ctx->avc_header;\n    if (in == NULL) {\n        return NGX_ERROR;\n    }\n\n    p = in->buf->pos;\n\n    /*\n     * Skip bytes:\n     * - flv fmt\n     * - H264 CONF/PICT (0x00)\n     * - 0\n     * - 0\n     * - 0\n     * - version\n     * - profile\n     * - compatibility\n     * - level\n     * - nal bytes\n     */\n\n    if (ngx_rtmp_hls_copy(s, NULL, &p, 10, &in) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    /* number of SPS NALs */\n    if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    nnals &= 0x1f; /* 5lsb */\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: SPS number: %uz\", nnals);\n\n    /* SPS */\n    for (n = 0; ; ++n) {\n        for (; nnals; --nnals) {\n\n            /* NAL length */\n            if (ngx_rtmp_hls_copy(s, &rlen, &p, 2, &in) != NGX_OK) {\n                return NGX_ERROR;\n            }\n\n            ngx_rtmp_rmemcpy(&len, &rlen, 2);\n\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"hls: header NAL length: %uz\", (size_t) len);\n\n            /* AnnexB prefix */\n            if (out->end - out->last < 4) {\n                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                              \"hls: too small buffer for header NAL size\");\n                return NGX_ERROR;\n            }\n\n            *out->last++ = 0;\n            *out->last++ = 0;\n            *out->last++ = 0;\n            *out->last++ = 1;\n\n            /* NAL body */\n            if (out->end - out->last < len) {\n                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                              \"hls: too small buffer for header NAL\");\n                return NGX_ERROR;\n            }\n\n            if (ngx_rtmp_hls_copy(s, out->last, &p, len, &in) != NGX_OK) {\n                return NGX_ERROR;\n            }\n\n            out->last += len;\n        }\n\n        if (n == 1) {\n            break;\n        }\n\n        /* number of PPS NALs */\n        if (ngx_rtmp_hls_copy(s, &nnals, &p, 1, &in) != NGX_OK) {\n            return NGX_ERROR;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"hls: PPS number: %uz\", nnals);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic uint64_t\nngx_rtmp_hls_get_fragment_id(ngx_rtmp_session_t *s, uint64_t ts)\n{\n    ngx_rtmp_hls_ctx_t         *ctx;\n    ngx_rtmp_hls_app_conf_t    *hacf;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n\n    switch (hacf->naming) {\n\n    case NGX_RTMP_HLS_NAMING_TIMESTAMP:\n        return ts;\n\n    case NGX_RTMP_HLS_NAMING_SYSTEM:\n        return (uint64_t) ngx_cached_time->sec * 1000 + ngx_cached_time->msec;\n\n    default: /* NGX_RTMP_HLS_NAMING_SEQUENTIAL */\n        return ctx->frag + ctx->nfrags;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_close_fragment(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_hls_ctx_t         *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n    if (ctx == NULL || !ctx->opened) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: close fragment n=%uL\", ctx->frag);\n\n    ngx_rtmp_mpegts_close_file(&ctx->file);\n\n    ctx->opened = 0;\n\n    ngx_rtmp_hls_next_frag(s);\n\n    ngx_rtmp_hls_write_playlist(s);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_open_fragment(ngx_rtmp_session_t *s, uint64_t ts,\n    ngx_int_t discont)\n{\n    uint64_t                  id;\n    ngx_fd_t                  fd;\n    ngx_uint_t                g;\n    ngx_rtmp_hls_ctx_t       *ctx;\n    ngx_rtmp_hls_frag_t      *f;\n    ngx_rtmp_hls_app_conf_t  *hacf;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    if (ctx->opened) {\n        return NGX_OK;\n    }\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n\n    if (ngx_rtmp_hls_ensure_directory(s, &hacf->path) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    if (hacf->keys &&\n        ngx_rtmp_hls_ensure_directory(s, &hacf->key_path) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    id = ngx_rtmp_hls_get_fragment_id(s, ts);\n\n    if (hacf->granularity) {\n        g = (ngx_uint_t) hacf->granularity;\n        id = (uint64_t) (id / g) * g;\n    }\n\n    ngx_sprintf(ctx->stream.data + ctx->stream.len, \"%uL.ts%Z\", id);\n\n    if (hacf->keys) {\n        if (ctx->key_frags == 0) {\n\n            ctx->key_frags = hacf->frags_per_key - 1;\n            ctx->key_id = id;\n\n            if (RAND_bytes(ctx->key, 16) < 0) {\n                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                              \"hls: failed to create key\");\n                return NGX_ERROR;\n            }\n\n            ngx_sprintf(ctx->keyfile.data + ctx->keyfile.len, \"%uL.key%Z\", id);\n\n            fd = ngx_open_file(ctx->keyfile.data, NGX_FILE_WRONLY,\n                               NGX_FILE_TRUNCATE, NGX_FILE_DEFAULT_ACCESS);\n\n            if (fd == NGX_INVALID_FILE) {\n                ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                              \"hls: failed to open key file '%s'\",\n                              ctx->keyfile.data);\n                return NGX_ERROR;\n            }\n\n            if (ngx_write_fd(fd, ctx->key, 16) != 16) {\n                ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                              \"hls: failed to write key file '%s'\",\n                              ctx->keyfile.data);\n                ngx_close_file(fd);\n                return NGX_ERROR;\n            }\n\n            ngx_close_file(fd);\n\n        } else {\n            if (hacf->frags_per_key) {\n                ctx->key_frags--;\n            }\n\n            if (ngx_set_file_time(ctx->keyfile.data, 0, ngx_cached_time->sec)\n                != NGX_OK)\n            {\n                ngx_log_error(NGX_LOG_ALERT, s->connection->log, ngx_errno,\n                              ngx_set_file_time_n \" '%s' failed\",\n                              ctx->keyfile.data);\n            }\n        }\n    }\n\n    ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: open fragment file='%s', keyfile='%s', \"\n                   \"frag=%uL, n=%ui, time=%uL, discont=%i\",\n                   ctx->stream.data,\n                   ctx->keyfile.data ? ctx->keyfile.data : (u_char *) \"\",\n                   ctx->frag, ctx->nfrags, ts, discont);\n\n    if (hacf->keys &&\n        ngx_rtmp_mpegts_init_encryption(&ctx->file, ctx->key, 16, ctx->key_id)\n        != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"hls: failed to initialize hls encryption\");\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_mpegts_open_file(&ctx->file, ctx->stream.data,\n                                  s->connection->log)\n        != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    ctx->opened = 1;\n\n    f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);\n\n    ngx_memzero(f, sizeof(*f));\n\n    f->active = 1;\n    f->discont = discont;\n    f->id = id;\n    f->key_id = ctx->key_id;\n\n    ctx->frag_ts = ts;\n\n    /* start fragment with audio to make iPhone happy */\n\n    ngx_rtmp_hls_flush_audio(s);\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_hls_restore_stream(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_hls_ctx_t             *ctx;\n    ngx_file_t                      file;\n    ssize_t                         ret;\n    off_t                           offset;\n    u_char                         *p, *last, *end, *next, *pa, *pp, c;\n    ngx_rtmp_hls_frag_t            *f;\n    double                          duration;\n    ngx_int_t                       discont;\n    uint64_t                        mag, key_id, base;\n    static u_char                   buffer[4096];\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    ngx_memzero(&file, sizeof(file));\n\n    file.log = s->connection->log;\n\n    ngx_str_set(&file.name, \"m3u8\");\n\n    file.fd = ngx_open_file(ctx->playlist.data, NGX_FILE_RDONLY, NGX_FILE_OPEN,\n                            0);\n    if (file.fd == NGX_INVALID_FILE) {\n        return;\n    }\n\n    offset = 0;\n    ctx->nfrags = 0;\n    f = NULL;\n    duration = 0;\n    discont = 0;\n    key_id = 0;\n\n    for ( ;; ) {\n\n        ret = ngx_read_file(&file, buffer, sizeof(buffer), offset);\n        if (ret <= 0) {\n            goto done;\n        }\n\n        p = buffer;\n        end = buffer + ret;\n\n        for ( ;; ) {\n            last = ngx_strlchr(p, end, '\\n');\n\n            if (last == NULL) {\n                if (p == buffer) {\n                    goto done;\n                }\n                break;\n            }\n\n            next = last + 1;\n            offset += (next - p);\n\n            if (p != last && last[-1] == '\\r') {\n                last--;\n            }\n\n\n#define NGX_RTMP_MSEQ           \"#EXT-X-MEDIA-SEQUENCE:\"\n#define NGX_RTMP_MSEQ_LEN       (sizeof(NGX_RTMP_MSEQ) - 1)\n\n\n            if (ngx_memcmp(p, NGX_RTMP_MSEQ, NGX_RTMP_MSEQ_LEN) == 0) {\n\n                ctx->frag = (uint64_t) strtod((const char *)\n                                              &p[NGX_RTMP_MSEQ_LEN], NULL);\n\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"hls: restore sequence frag=%uL\", ctx->frag);\n            }\n\n\n#define NGX_RTMP_XKEY           \"#EXT-X-KEY:\"\n#define NGX_RTMP_XKEY_LEN       (sizeof(NGX_RTMP_XKEY) - 1)\n\n            if (ngx_memcmp(p, NGX_RTMP_XKEY, NGX_RTMP_XKEY_LEN) == 0) {\n\n                /* recover key id from initialization vector */\n\n                key_id = 0;\n                base = 1;\n                pp = last - 1;\n\n                for ( ;; ) {\n                    if (pp < p) {\n                        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                                \"hls: failed to read key id\");\n                        break;\n                    }\n\n                    c = *pp;\n                    if (c == 'x') {\n                        break;\n                    }\n\n                    if (c >= '0' && c <= '9') {\n                        c -= '0';\n                        goto next;\n                    }\n\n                    c |= 0x20;\n\n                    if (c >= 'a' && c <= 'f') {\n                        c -= 'a' - 10;\n                        goto next;\n                    }\n\n                    ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                                  \"hls: bad character in key id\");\n                    break;\n\n                next:\n\n                    key_id += base * c;\n                    base *= 0x10;\n                    pp--;\n                }\n            }\n\n\n#define NGX_RTMP_EXTINF         \"#EXTINF:\"\n#define NGX_RTMP_EXTINF_LEN     (sizeof(NGX_RTMP_EXTINF) - 1)\n\n\n            if (ngx_memcmp(p, NGX_RTMP_EXTINF, NGX_RTMP_EXTINF_LEN) == 0) {\n\n                duration = strtod((const char *) &p[NGX_RTMP_EXTINF_LEN], NULL);\n\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"hls: restore durarion=%.3f\", duration);\n            }\n\n\n#define NGX_RTMP_DISCONT        \"#EXT-X-DISCONTINUITY\"\n#define NGX_RTMP_DISCONT_LEN    (sizeof(NGX_RTMP_DISCONT) - 1)\n\n\n            if (ngx_memcmp(p, NGX_RTMP_DISCONT, NGX_RTMP_DISCONT_LEN) == 0) {\n\n                discont = 1;\n\n                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"hls: discontinuity\");\n            }\n\n            /* find '.ts\\r' */\n\n            if (p + 4 <= last &&\n                last[-3] == '.' && last[-2] == 't' && last[-1] == 's')\n            {\n                f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);\n\n                ngx_memzero(f, sizeof(*f));\n\n                f->duration = duration;\n                f->discont = discont;\n                f->active = 1;\n                f->id = 0;\n\n                discont = 0;\n\n                mag = 1;\n                for (pa = last - 4; pa >= p; pa--) {\n                    if (*pa < '0' || *pa > '9') {\n                        break;\n                    }\n                    f->id += (*pa - '0') * mag;\n                    mag *= 10;\n                }\n\n                f->key_id = key_id;\n\n                ngx_rtmp_hls_next_frag(s);\n\n                ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"hls: restore fragment '%*s' id=%uL, \"\n                               \"duration=%.3f, frag=%uL, nfrags=%ui\",\n                               (size_t) (last - p), p, f->id, f->duration,\n                               ctx->frag, ctx->nfrags);\n            }\n\n            p = next;\n        }\n    }\n\ndone:\n    ngx_close_file(file.fd);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_ensure_directory(ngx_rtmp_session_t *s, ngx_str_t *path)\n{\n    size_t                    len;\n    ngx_file_info_t           fi;\n    ngx_rtmp_hls_ctx_t       *ctx;\n    ngx_rtmp_hls_app_conf_t  *hacf;\n\n    static u_char  zpath[NGX_MAX_PATH + 1];\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n\n    if (path->len + 1 > sizeof(zpath)) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, \"hls: too long path\");\n        return NGX_ERROR;\n    }\n\n    ngx_snprintf(zpath, sizeof(zpath), \"%V%Z\", path);\n\n    if (ngx_file_info(zpath, &fi) == NGX_FILE_ERROR) {\n\n        if (ngx_errno != NGX_ENOENT) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"hls: \" ngx_file_info_n \" failed on '%V'\", path);\n            return NGX_ERROR;\n        }\n\n        /* ENOENT */\n\n        if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"hls: \" ngx_create_dir_n \" failed on '%V'\", path);\n            return NGX_ERROR;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"hls: directory '%V' created\", path);\n\n    } else {\n\n        if (!ngx_is_dir(&fi)) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"hls: '%V' exists and is not a directory\", path);\n            return  NGX_ERROR;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"hls: directory '%V' exists\", path);\n    }\n\n    if (!hacf->nested) {\n        return NGX_OK;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    len = path->len;\n    if (path->data[len - 1] == '/') {\n        len--;\n    }\n\n    if (len + 1 + ctx->name.len + 1 > sizeof(zpath)) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0, \"hls: too long path\");\n        return NGX_ERROR;\n    }\n\n    ngx_snprintf(zpath, sizeof(zpath) - 1, \"%*s/%V%Z\", len, path->data,\n                 &ctx->name);\n\n    if (ngx_file_info(zpath, &fi) != NGX_FILE_ERROR) {\n\n        if (ngx_is_dir(&fi)) {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"hls: directory '%s' exists\", zpath);\n            return NGX_OK;\n        }\n\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"hls: '%s' exists and is not a directory\", zpath);\n\n        return  NGX_ERROR;\n    }\n\n    if (ngx_errno != NGX_ENOENT) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"hls: \" ngx_file_info_n \" failed on '%s'\", zpath);\n        return NGX_ERROR;\n    }\n\n    /* NGX_ENOENT */\n\n    if (ngx_create_dir(zpath, NGX_RTMP_HLS_DIR_ACCESS) == NGX_FILE_ERROR) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"hls: \" ngx_create_dir_n \" failed on '%s'\", zpath);\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: directory '%s' created\", zpath);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    ngx_rtmp_hls_app_conf_t        *hacf;\n    ngx_rtmp_hls_ctx_t             *ctx;\n    u_char                         *p, *pp;\n    ngx_rtmp_hls_frag_t            *f;\n    ngx_buf_t                      *b;\n    size_t                          len;\n    ngx_rtmp_hls_variant_t         *var;\n    ngx_uint_t                      n;\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n    if (hacf == NULL || !hacf->hls || hacf->path.len == 0) {\n        goto next;\n    }\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: publish: name='%s' type='%s'\",\n                   v->name, v->type);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    if (ctx == NULL) {\n\n        ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_hls_ctx_t));\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_hls_module);\n\n    } else {\n\n        f = ctx->frags;\n        b = ctx->aframe;\n\n        ngx_memzero(ctx, sizeof(ngx_rtmp_hls_ctx_t));\n\n        ctx->frags = f;\n        ctx->aframe = b;\n\n        if (b) {\n            b->pos = b->last = b->start;\n        }\n    }\n\n    if (ctx->frags == NULL) {\n        ctx->frags = ngx_pcalloc(s->connection->pool,\n                                 sizeof(ngx_rtmp_hls_frag_t) *\n                                 (hacf->winfrags * 2 + 1));\n        if (ctx->frags == NULL) {\n            return NGX_ERROR;\n        }\n    }\n\n    if (ngx_strstr(v->name, \"..\")) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"hls: bad stream name: '%s'\", v->name);\n        return NGX_ERROR;\n    }\n\n    ctx->name.len = ngx_strlen(v->name);\n    ctx->name.data = ngx_palloc(s->connection->pool, ctx->name.len + 1);\n\n    if (ctx->name.data == NULL) {\n        return NGX_ERROR;\n    }\n\n    *ngx_cpymem(ctx->name.data, v->name, ctx->name.len) = 0;\n\n    len = hacf->path.len + 1 + ctx->name.len + sizeof(\".m3u8\");\n    if (hacf->nested) {\n        len += sizeof(\"/index\") - 1;\n    }\n\n    ctx->playlist.data = ngx_palloc(s->connection->pool, len);\n    p = ngx_cpymem(ctx->playlist.data, hacf->path.data, hacf->path.len);\n\n    if (p[-1] != '/') {\n        *p++ = '/';\n    }\n\n    p = ngx_cpymem(p, ctx->name.data, ctx->name.len);\n\n    /*\n     * ctx->stream holds initial part of stream file path\n     * however the space for the whole stream path\n     * is allocated\n     */\n\n    ctx->stream.len = p - ctx->playlist.data + 1;\n    ctx->stream.data = ngx_palloc(s->connection->pool,\n                                  ctx->stream.len + NGX_INT64_LEN +\n                                  sizeof(\".ts\"));\n\n    ngx_memcpy(ctx->stream.data, ctx->playlist.data, ctx->stream.len - 1);\n    ctx->stream.data[ctx->stream.len - 1] = (hacf->nested ? '/' : '-');\n\n    /* varint playlist path */\n\n    if (hacf->variant) {\n        var = hacf->variant->elts;\n        for (n = 0; n < hacf->variant->nelts; n++, var++) {\n            if (ctx->name.len > var->suffix.len &&\n                ngx_memcmp(var->suffix.data,\n                           ctx->name.data + ctx->name.len - var->suffix.len,\n                           var->suffix.len)\n                == 0)\n            {\n                ctx->var = var;\n\n                len = (size_t) (p - ctx->playlist.data);\n\n                ctx->var_playlist.len = len - var->suffix.len + sizeof(\".m3u8\")\n                                        - 1;\n                ctx->var_playlist.data = ngx_palloc(s->connection->pool,\n                                                    ctx->var_playlist.len + 1);\n\n                pp = ngx_cpymem(ctx->var_playlist.data, ctx->playlist.data,\n                               len - var->suffix.len);\n                pp = ngx_cpymem(pp, \".m3u8\", sizeof(\".m3u8\") - 1);\n                *pp = 0;\n\n                ctx->var_playlist_bak.len = ctx->var_playlist.len +\n                                            sizeof(\".bak\") - 1;\n                ctx->var_playlist_bak.data = ngx_palloc(s->connection->pool,\n                                                 ctx->var_playlist_bak.len + 1);\n\n                pp = ngx_cpymem(ctx->var_playlist_bak.data,\n                                ctx->var_playlist.data,\n                                ctx->var_playlist.len);\n                pp = ngx_cpymem(pp, \".bak\", sizeof(\".bak\") - 1);\n                *pp = 0;\n\n                break;\n            }\n        }\n    }\n\n\n    /* playlist path */\n\n    if (hacf->nested) {\n        p = ngx_cpymem(p, \"/index.m3u8\", sizeof(\"/index.m3u8\") - 1);\n    } else {\n        p = ngx_cpymem(p, \".m3u8\", sizeof(\".m3u8\") - 1);\n    }\n\n    ctx->playlist.len = p - ctx->playlist.data;\n\n    *p = 0;\n\n    /* playlist bak (new playlist) path */\n\n    ctx->playlist_bak.data = ngx_palloc(s->connection->pool,\n                                        ctx->playlist.len + sizeof(\".bak\"));\n    p = ngx_cpymem(ctx->playlist_bak.data, ctx->playlist.data,\n                   ctx->playlist.len);\n    p = ngx_cpymem(p, \".bak\", sizeof(\".bak\") - 1);\n\n    ctx->playlist_bak.len = p - ctx->playlist_bak.data;\n\n    *p = 0;\n\n    /* key path */\n\n    if (hacf->keys) {\n        len = hacf->key_path.len + 1 + ctx->name.len + 1 + NGX_INT64_LEN\n              + sizeof(\".key\");\n\n        ctx->keyfile.data = ngx_palloc(s->connection->pool, len);\n        if (ctx->keyfile.data == NULL) {\n            return NGX_ERROR;\n        }\n\n        p = ngx_cpymem(ctx->keyfile.data, hacf->key_path.data,\n                       hacf->key_path.len);\n\n        if (p[-1] != '/') {\n            *p++ = '/';\n        }\n\n        p = ngx_cpymem(p, ctx->name.data, ctx->name.len);\n        *p++ = (hacf->nested ? '/' : '-');\n\n        ctx->keyfile.len = p - ctx->keyfile.data;\n    }\n\n    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: playlist='%V' playlist_bak='%V' \"\n                   \"stream_pattern='%V' keyfile_pattern='%V'\",\n                   &ctx->playlist, &ctx->playlist_bak,\n                   &ctx->stream, &ctx->keyfile);\n\n    if (hacf->continuous) {\n        ngx_rtmp_hls_restore_stream(s);\n    }\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)\n{\n    ngx_rtmp_hls_app_conf_t        *hacf;\n    ngx_rtmp_hls_ctx_t             *ctx;\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    if (hacf == NULL || !hacf->hls || ctx == NULL) {\n        goto next;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: close stream\");\n\n    ngx_rtmp_hls_close_fragment(s);\n\nnext:\n    return next_close_stream(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_parse_aac_header(ngx_rtmp_session_t *s, ngx_uint_t *objtype,\n    ngx_uint_t *srindex, ngx_uint_t *chconf)\n{\n    ngx_rtmp_codec_ctx_t   *codec_ctx;\n    ngx_chain_t            *cl;\n    u_char                 *p, b0, b1;\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    cl = codec_ctx->aac_header;\n\n    p = cl->buf->pos;\n\n    if (ngx_rtmp_hls_copy(s, NULL, &p, 2, &cl) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_hls_copy(s, &b0, &p, 1, &cl) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_hls_copy(s, &b1, &p, 1, &cl) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    *objtype = b0 >> 3;\n    if (*objtype == 0 || *objtype == 0x1f) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"hls: unsupported adts object type:%ui\", *objtype);\n        return NGX_ERROR;\n    }\n\n    if (*objtype > 4) {\n\n        /*\n         * Mark all extended profiles as LC\n         * to make Android as happy as possible.\n         */\n\n        *objtype = 2;\n    }\n\n    *srindex = ((b0 << 1) & 0x0f) | ((b1 & 0x80) >> 7);\n    if (*srindex == 0x0f) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"hls: unsupported adts sample rate:%ui\", *srindex);\n        return NGX_ERROR;\n    }\n\n    *chconf = (b1 >> 3) & 0x0f;\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: aac object_type:%ui, sample_rate_index:%ui, \"\n                   \"channel_config:%ui\", *objtype, *srindex, *chconf);\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_hls_update_fragment(ngx_rtmp_session_t *s, uint64_t ts,\n    ngx_int_t boundary, ngx_uint_t flush_rate)\n{\n    ngx_rtmp_hls_ctx_t         *ctx;\n    ngx_rtmp_hls_app_conf_t    *hacf;\n    ngx_rtmp_hls_frag_t        *f;\n    ngx_msec_t                  ts_frag_len;\n    ngx_int_t                   same_frag, force,discont;\n    ngx_buf_t                  *b;\n    int64_t                     d;\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n    f = NULL;\n    force = 0;\n    discont = 1;\n\n    if (ctx->opened) {\n        f = ngx_rtmp_hls_get_frag(s, ctx->nfrags);\n        d = (int64_t) (ts - ctx->frag_ts);\n\n        if (d > (int64_t) hacf->max_fraglen * 90 || d < -90000) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"hls: force fragment split: %.3f sec, \", d / 90000.);\n            force = 1;\n\n        } else {\n            f->duration = (ts - ctx->frag_ts) / 90000.;\n            discont = 0;\n        }\n    }\n\n    switch (hacf->slicing) {\n        case NGX_RTMP_HLS_SLICING_PLAIN:\n            if (f && f->duration < hacf->fraglen / 1000.) {\n                boundary = 0;\n            }\n            break;\n\n        case NGX_RTMP_HLS_SLICING_ALIGNED:\n\n            ts_frag_len = hacf->fraglen * 90;\n            same_frag = ctx->frag_ts / ts_frag_len == ts / ts_frag_len;\n\n            if (f && same_frag) {\n                boundary = 0;\n            }\n\n            if (f == NULL && (ctx->frag_ts == 0 || same_frag)) {\n                ctx->frag_ts = ts;\n                boundary = 0;\n            }\n\n            break;\n    }\n\n    if (boundary || force) {\n        ngx_rtmp_hls_close_fragment(s);\n        ngx_rtmp_hls_open_fragment(s, ts, discont);\n    }\n\n    b = ctx->aframe;\n    if (ctx->opened && b && b->last > b->pos &&\n        ctx->aframe_pts + (uint64_t) hacf->max_audio_delay * 90 / flush_rate\n        < ts)\n    {\n        ngx_rtmp_hls_flush_audio(s);\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_flush_audio(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_hls_ctx_t             *ctx;\n    ngx_rtmp_mpegts_frame_t         frame;\n    ngx_int_t                       rc;\n    ngx_buf_t                      *b;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    if (ctx == NULL || !ctx->opened) {\n        return NGX_OK;\n    }\n\n    b = ctx->aframe;\n\n    if (b == NULL || b->pos == b->last) {\n        return NGX_OK;\n    }\n\n    ngx_memzero(&frame, sizeof(frame));\n\n    frame.dts = ctx->aframe_pts;\n    frame.pts = frame.dts;\n    frame.cc = ctx->audio_cc;\n    frame.pid = 0x101;\n    frame.sid = 0xc0;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: flush audio pts=%uL\", frame.pts);\n\n    rc = ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, b);\n\n    if (rc != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"hls: audio flush failed\");\n    }\n\n    ctx->audio_cc = frame.cc;\n    b->pos = b->last = b->start;\n\n    return rc;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_audio(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n    ngx_chain_t *in)\n{\n    ngx_rtmp_hls_app_conf_t        *hacf;\n    ngx_rtmp_hls_ctx_t             *ctx;\n    ngx_rtmp_codec_ctx_t           *codec_ctx;\n    uint64_t                        pts, est_pts;\n    int64_t                         dpts;\n    size_t                          bsize;\n    ngx_buf_t                      *b;\n    u_char                         *p;\n    ngx_uint_t                      objtype, srindex, chconf, size;\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (hacf == NULL || !hacf->hls || ctx == NULL ||\n        codec_ctx == NULL  || h->mlen < 2)\n    {\n        return NGX_OK;\n    }\n\n    if (codec_ctx->audio_codec_id != NGX_RTMP_AUDIO_AAC ||\n        codec_ctx->aac_header == NULL || ngx_rtmp_is_codec_header(in))\n    {\n        return NGX_OK;\n    }\n\n    b = ctx->aframe;\n\n    if (b == NULL) {\n\n        b = ngx_pcalloc(s->connection->pool, sizeof(ngx_buf_t));\n        if (b == NULL) {\n            return NGX_ERROR;\n        }\n\n        ctx->aframe = b;\n\n        b->start = ngx_palloc(s->connection->pool, hacf->audio_buffer_size);\n        if (b->start == NULL) {\n            return NGX_ERROR;\n        }\n\n        b->end = b->start + hacf->audio_buffer_size;\n        b->pos = b->last = b->start;\n    }\n\n    size = h->mlen - 2 + 7;\n    pts = (uint64_t) h->timestamp * 90;\n\n    if (b->start + size > b->end) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"hls: too big audio frame\");\n        return NGX_OK;\n    }\n\n    /*\n     * start new fragment here if\n     * there's no video at all, otherwise\n     * do it in video handler\n     */\n\n    ngx_rtmp_hls_update_fragment(s, pts, codec_ctx->avc_header == NULL, 2);\n\n    if (b->last + size > b->end) {\n        ngx_rtmp_hls_flush_audio(s);\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: audio pts=%uL\", pts);\n\n    if (b->last + 7 > b->end) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"hls: not enough buffer for audio header\");\n        return NGX_OK;\n    }\n\n    p = b->last;\n    b->last += 5;\n\n    /* copy payload */\n\n    for (; in && b->last < b->end; in = in->next) {\n\n        bsize = in->buf->last - in->buf->pos;\n        if (b->last + bsize > b->end) {\n            bsize = b->end - b->last;\n        }\n\n        b->last = ngx_cpymem(b->last, in->buf->pos, bsize);\n    }\n\n    /* make up ADTS header */\n\n    if (ngx_rtmp_hls_parse_aac_header(s, &objtype, &srindex, &chconf)\n        != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"hls: aac header error\");\n        return NGX_OK;\n    }\n\n    /* we have 5 free bytes + 2 bytes of RTMP frame header */\n\n    p[0] = 0xff;\n    p[1] = 0xf1;\n    p[2] = (u_char) (((objtype - 1) << 6) | (srindex << 2) |\n                     ((chconf & 0x04) >> 2));\n    p[3] = (u_char) (((chconf & 0x03) << 6) | ((size >> 11) & 0x03));\n    p[4] = (u_char) (size >> 3);\n    p[5] = (u_char) ((size << 5) | 0x1f);\n    p[6] = 0xfc;\n\n    if (p != b->start) {\n        ctx->aframe_num++;\n        return NGX_OK;\n    }\n\n    ctx->aframe_pts = pts;\n\n    if (!hacf->sync || codec_ctx->sample_rate == 0) {\n        return NGX_OK;\n    }\n\n    /* align audio frames */\n\n    /* TODO: We assume here AAC frame size is 1024\n     *       Need to handle AAC frames with frame size of 960 */\n\n    est_pts = ctx->aframe_base + ctx->aframe_num * 90000 * 1024 /\n                                 codec_ctx->sample_rate;\n    dpts = (int64_t) (est_pts - pts);\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: audio sync dpts=%L (%.5fs)\",\n                   dpts, dpts / 90000.);\n\n    if (dpts <= (int64_t) hacf->sync * 90 &&\n        dpts >= (int64_t) hacf->sync * -90)\n    {\n        ctx->aframe_num++;\n        ctx->aframe_pts = est_pts;\n        return NGX_OK;\n    }\n\n    ctx->aframe_base = pts;\n    ctx->aframe_num  = 1;\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: audio sync gap dpts=%L (%.5fs)\",\n                   dpts, dpts / 90000.);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_video(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n    ngx_chain_t *in)\n{\n    ngx_rtmp_hls_app_conf_t        *hacf;\n    ngx_rtmp_hls_ctx_t             *ctx;\n    ngx_rtmp_codec_ctx_t           *codec_ctx;\n    u_char                         *p;\n    uint8_t                         fmt, ftype, htype, nal_type, src_nal_type;\n    uint32_t                        len, rlen;\n    ngx_buf_t                       out, *b;\n    uint32_t                        cts;\n    ngx_rtmp_mpegts_frame_t         frame;\n    ngx_uint_t                      nal_bytes;\n    ngx_int_t                       aud_sent, sps_pps_sent, boundary;\n    static u_char                   buffer[NGX_RTMP_HLS_BUFSIZE];\n\n    hacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_hls_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_hls_module);\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (hacf == NULL || !hacf->hls || ctx == NULL || codec_ctx == NULL ||\n        codec_ctx->avc_header == NULL || h->mlen < 1)\n    {\n        return NGX_OK;\n    }\n\n    /* Only H264 is supported */\n    if (codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) {\n        return NGX_OK;\n    }\n\n    p = in->buf->pos;\n    if (ngx_rtmp_hls_copy(s, &fmt, &p, 1, &in) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    /* 1: keyframe (IDR)\n     * 2: inter frame\n     * 3: disposable inter frame */\n\n    ftype = (fmt & 0xf0) >> 4;\n\n    /* H264 HDR/PICT */\n\n    if (ngx_rtmp_hls_copy(s, &htype, &p, 1, &in) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    /* proceed only with PICT */\n\n    if (htype != 1) {\n        return NGX_OK;\n    }\n\n    /* 3 bytes: decoder delay */\n\n    if (ngx_rtmp_hls_copy(s, &cts, &p, 3, &in) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    cts = ((cts & 0x00FF0000) >> 16) | ((cts & 0x000000FF) << 16) |\n          (cts & 0x0000FF00);\n\n    ngx_memzero(&out, sizeof(out));\n\n    out.start = buffer;\n    out.end = buffer + sizeof(buffer);\n    out.pos = out.start;\n    out.last = out.pos;\n\n    nal_bytes = codec_ctx->avc_nal_bytes;\n    aud_sent = 0;\n    sps_pps_sent = 0;\n\n    while (in) {\n        if (ngx_rtmp_hls_copy(s, &rlen, &p, nal_bytes, &in) != NGX_OK) {\n            return NGX_OK;\n        }\n\n        len = 0;\n        ngx_rtmp_rmemcpy(&len, &rlen, nal_bytes);\n\n        if (len == 0) {\n            continue;\n        }\n\n        if (ngx_rtmp_hls_copy(s, &src_nal_type, &p, 1, &in) != NGX_OK) {\n            return NGX_OK;\n        }\n\n        nal_type = src_nal_type & 0x1f;\n\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"hls: h264 NAL type=%ui, len=%uD\",\n                       (ngx_uint_t) nal_type, len);\n\n        if (nal_type >= 7 && nal_type <= 9) {\n            if (ngx_rtmp_hls_copy(s, NULL, &p, len - 1, &in) != NGX_OK) {\n                return NGX_ERROR;\n            }\n            continue;\n        }\n\n        if (!aud_sent) {\n            switch (nal_type) {\n                case 1:\n                case 5:\n                case 6:\n                    if (ngx_rtmp_hls_append_aud(s, &out) != NGX_OK) {\n                        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                                      \"hls: error appending AUD NAL\");\n                    }\n                    /* fall through */\n                case 9:\n                    aud_sent = 1;\n                    break;\n            }\n        }\n\n        switch (nal_type) {\n            case 1:\n                sps_pps_sent = 0;\n                break;\n            case 5:\n                if (sps_pps_sent) {\n                    break;\n                }\n                if (ngx_rtmp_hls_append_sps_pps(s, &out) != NGX_OK) {\n                    ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                                  \"hls: error appenging SPS/PPS NALs\");\n                }\n                sps_pps_sent = 1;\n                break;\n        }\n\n        /* AnnexB prefix */\n\n        if (out.end - out.last < 5) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"hls: not enough buffer for AnnexB prefix\");\n            return NGX_OK;\n        }\n\n        /* first AnnexB prefix is long (4 bytes) */\n\n        if (out.last == out.pos) {\n            *out.last++ = 0;\n        }\n\n        *out.last++ = 0;\n        *out.last++ = 0;\n        *out.last++ = 1;\n        *out.last++ = src_nal_type;\n\n        /* NAL body */\n\n        if (out.end - out.last < (ngx_int_t) len) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"hls: not enough buffer for NAL\");\n            return NGX_OK;\n        }\n\n        if (ngx_rtmp_hls_copy(s, out.last, &p, len - 1, &in) != NGX_OK) {\n            return NGX_ERROR;\n        }\n\n        out.last += (len - 1);\n    }\n\n    ngx_memzero(&frame, sizeof(frame));\n\n    frame.cc = ctx->video_cc;\n    frame.dts = (uint64_t) h->timestamp * 90;\n    frame.pts = frame.dts + cts * 90;\n    frame.pid = 0x100;\n    frame.sid = 0xe0;\n    frame.key = (ftype == 1);\n\n    /*\n     * start new fragment if\n     * - we have video key frame AND\n     * - we have audio buffered or have no audio at all or stream is closed\n     */\n\n    b = ctx->aframe;\n    boundary = frame.key && (codec_ctx->aac_header == NULL || !ctx->opened ||\n                             (b && b->last > b->pos));\n\n    ngx_rtmp_hls_update_fragment(s, frame.dts, boundary, 1);\n\n    if (!ctx->opened) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"hls: video pts=%uL, dts=%uL\", frame.pts, frame.dts);\n\n    if (ngx_rtmp_mpegts_write_frame(&ctx->file, &frame, &out) != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"hls: video frame failed\");\n    }\n\n    ctx->video_cc = frame.cc;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)\n{\n    return next_stream_begin(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)\n{\n    ngx_rtmp_hls_flush_audio(s);\n\n    ngx_rtmp_hls_close_fragment(s);\n\n    return next_stream_eof(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_cleanup_dir(ngx_str_t *ppath, ngx_msec_t playlen)\n{\n    ngx_dir_t               dir;\n    time_t                  mtime, max_age;\n    ngx_err_t               err;\n    ngx_str_t               name, spath;\n    u_char                 *p;\n    ngx_int_t               nentries, nerased;\n    u_char                  path[NGX_MAX_PATH + 1];\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                   \"hls: cleanup path='%V' playlen=%M\",\n                   ppath, playlen);\n\n    if (ngx_open_dir(ppath, &dir) != NGX_OK) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, ngx_errno,\n                      \"hls: cleanup open dir failed '%V'\", ppath);\n        return NGX_ERROR;\n    }\n\n    nentries = 0;\n    nerased = 0;\n\n    for ( ;; ) {\n        ngx_set_errno(0);\n\n        if (ngx_read_dir(&dir) == NGX_ERROR) {\n            err = ngx_errno;\n\n            if (ngx_close_dir(&dir) == NGX_ERROR) {\n                ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,\n                              \"hls: cleanup \" ngx_close_dir_n \" \\\"%V\\\" failed\",\n                              ppath);\n            }\n\n            if (err == NGX_ENOMOREFILES) {\n                return nentries - nerased;\n            }\n\n            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, err,\n                          \"hls: cleanup \" ngx_read_dir_n\n                          \" '%V' failed\", ppath);\n            return NGX_ERROR;\n        }\n\n        name.data = ngx_de_name(&dir);\n        if (name.data[0] == '.') {\n            continue;\n        }\n\n        name.len = ngx_de_namelen(&dir);\n\n        p = ngx_snprintf(path, sizeof(path) - 1, \"%V/%V\", ppath, &name);\n        *p = 0;\n\n        spath.data = path;\n        spath.len = p - path;\n\n        nentries++;\n\n        if (!dir.valid_info && ngx_de_info(path, &dir) == NGX_FILE_ERROR) {\n            ngx_log_error(NGX_LOG_CRIT, ngx_cycle->log, ngx_errno,\n                          \"hls: cleanup \" ngx_de_info_n \" \\\"%V\\\" failed\",\n                          &spath);\n\n            continue;\n        }\n\n        if (ngx_de_is_dir(&dir)) {\n\n            if (ngx_rtmp_hls_cleanup_dir(&spath, playlen) == 0) {\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                               \"hls: cleanup dir '%V'\", &name);\n\n                /*\n                 * null-termination gets spoiled in win32\n                 * version of ngx_open_dir\n                 */\n\n                *p = 0;\n\n                if (ngx_delete_dir(path) == NGX_FILE_ERROR) {\n                    ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,\n                                  \"hls: cleanup \" ngx_delete_dir_n\n                                  \" failed on '%V'\", &spath);\n                } else {\n                    nerased++;\n                }\n            }\n\n            continue;\n        }\n\n        if (!ngx_de_is_file(&dir)) {\n            continue;\n        }\n\n        if (name.len >= 3 && name.data[name.len - 3] == '.' &&\n                             name.data[name.len - 2] == 't' &&\n                             name.data[name.len - 1] == 's')\n        {\n            max_age = playlen / 500;\n\n        } else if (name.len >= 5 && name.data[name.len - 5] == '.' &&\n                                    name.data[name.len - 4] == 'm' &&\n                                    name.data[name.len - 3] == '3' &&\n                                    name.data[name.len - 2] == 'u' &&\n                                    name.data[name.len - 1] == '8')\n        {\n            max_age = playlen / 1000;\n\n        } else if (name.len >= 4 && name.data[name.len - 4] == '.' &&\n                                    name.data[name.len - 3] == 'k' &&\n                                    name.data[name.len - 2] == 'e' &&\n                                    name.data[name.len - 1] == 'y')\n        {\n            max_age = playlen / 500;\n\n        } else {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                           \"hls: cleanup skip unknown file type '%V'\", &name);\n            continue;\n        }\n\n        mtime = ngx_de_mtime(&dir);\n        if (mtime + max_age > ngx_cached_time->sec) {\n            continue;\n        }\n\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ngx_cycle->log, 0,\n                       \"hls: cleanup '%V' mtime=%T age=%T\",\n                       &name, mtime, ngx_cached_time->sec - mtime);\n\n        if (ngx_delete_file(path) == NGX_FILE_ERROR) {\n            ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, ngx_errno,\n                          \"hls: cleanup \" ngx_delete_file_n \" failed on '%V'\",\n                          &spath);\n            continue;\n        }\n\n        nerased++;\n    }\n}\n\n\n#if (nginx_version >= 1011005)\nstatic ngx_msec_t\n#else\nstatic time_t\n#endif\nngx_rtmp_hls_cleanup(void *data)\n{\n    ngx_rtmp_hls_cleanup_t *cleanup = data;\n\n    ngx_rtmp_hls_cleanup_dir(&cleanup->path, cleanup->playlen);\n\n#if (nginx_version >= 1011005)\n    return cleanup->playlen * 2;\n#else\n    return cleanup->playlen / 500;\n#endif\n}\n\n\nstatic char *\nngx_rtmp_hls_variant(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_hls_app_conf_t  *hacf = conf;\n\n    ngx_str_t                *value, *arg;\n    ngx_uint_t                n;\n    ngx_rtmp_hls_variant_t   *var;\n\n    value = cf->args->elts;\n\n    if (hacf->variant == NULL) {\n        hacf->variant = ngx_array_create(cf->pool, 1,\n                                         sizeof(ngx_rtmp_hls_variant_t));\n        if (hacf->variant == NULL) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    var = ngx_array_push(hacf->variant);\n    if (var == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    ngx_memzero(var, sizeof(ngx_rtmp_hls_variant_t));\n\n    var->suffix = value[1];\n\n    if (cf->args->nelts == 2) {\n        return NGX_CONF_OK;\n    }\n\n    if (ngx_array_init(&var->args, cf->pool, cf->args->nelts - 2,\n                       sizeof(ngx_str_t))\n        != NGX_OK)\n    {\n        return NGX_CONF_ERROR;\n    }\n\n    arg = ngx_array_push_n(&var->args, cf->args->nelts - 2);\n    if (arg == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    for (n = 2; n < cf->args->nelts; n++) {\n        *arg++ = value[n];\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic void *\nngx_rtmp_hls_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_hls_app_conf_t *conf;\n\n    conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_hls_app_conf_t));\n    if (conf == NULL) {\n        return NULL;\n    }\n\n    conf->hls = NGX_CONF_UNSET;\n    conf->fraglen = NGX_CONF_UNSET_MSEC;\n    conf->max_fraglen = NGX_CONF_UNSET_MSEC;\n    conf->muxdelay = NGX_CONF_UNSET_MSEC;\n    conf->sync = NGX_CONF_UNSET_MSEC;\n    conf->playlen = NGX_CONF_UNSET_MSEC;\n    conf->continuous = NGX_CONF_UNSET;\n    conf->nested = NGX_CONF_UNSET;\n    conf->naming = NGX_CONF_UNSET_UINT;\n    conf->slicing = NGX_CONF_UNSET_UINT;\n    conf->type = NGX_CONF_UNSET_UINT;\n    conf->max_audio_delay = NGX_CONF_UNSET_MSEC;\n    conf->audio_buffer_size = NGX_CONF_UNSET_SIZE;\n    conf->cleanup = NGX_CONF_UNSET;\n    conf->granularity = NGX_CONF_UNSET;\n    conf->keys = NGX_CONF_UNSET;\n    conf->frags_per_key = NGX_CONF_UNSET_UINT;\n\n    return conf;\n}\n\n\nstatic char *\nngx_rtmp_hls_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_hls_app_conf_t    *prev = parent;\n    ngx_rtmp_hls_app_conf_t    *conf = child;\n    ngx_rtmp_hls_cleanup_t     *cleanup;\n\n    ngx_conf_merge_value(conf->hls, prev->hls, 0);\n    ngx_conf_merge_msec_value(conf->fraglen, prev->fraglen, 5000);\n    ngx_conf_merge_msec_value(conf->max_fraglen, prev->max_fraglen,\n                              conf->fraglen * 10);\n    ngx_conf_merge_msec_value(conf->muxdelay, prev->muxdelay, 700);\n    ngx_conf_merge_msec_value(conf->sync, prev->sync, 2);\n    ngx_conf_merge_msec_value(conf->playlen, prev->playlen, 30000);\n    ngx_conf_merge_value(conf->continuous, prev->continuous, 1);\n    ngx_conf_merge_value(conf->nested, prev->nested, 0);\n    ngx_conf_merge_uint_value(conf->naming, prev->naming,\n                              NGX_RTMP_HLS_NAMING_SEQUENTIAL);\n    ngx_conf_merge_uint_value(conf->slicing, prev->slicing,\n                              NGX_RTMP_HLS_SLICING_PLAIN);\n    ngx_conf_merge_uint_value(conf->type, prev->type,\n                              NGX_RTMP_HLS_TYPE_LIVE);\n    ngx_conf_merge_msec_value(conf->max_audio_delay, prev->max_audio_delay,\n                              300);\n    ngx_conf_merge_size_value(conf->audio_buffer_size, prev->audio_buffer_size,\n                              NGX_RTMP_HLS_BUFSIZE);\n    ngx_conf_merge_value(conf->cleanup, prev->cleanup, 1);\n    ngx_conf_merge_str_value(conf->base_url, prev->base_url, \"\");\n    ngx_conf_merge_value(conf->granularity, prev->granularity, 0);\n    ngx_conf_merge_value(conf->keys, prev->keys, 0);\n    ngx_conf_merge_str_value(conf->key_path, prev->key_path, \"\");\n    ngx_conf_merge_str_value(conf->key_url, prev->key_url, \"\");\n    ngx_conf_merge_uint_value(conf->frags_per_key, prev->frags_per_key, 0);\n\n    if (conf->fraglen) {\n        conf->winfrags = conf->playlen / conf->fraglen;\n    }\n\n    /* schedule cleanup */\n\n    if (conf->hls && conf->path.len && conf->cleanup &&\n        conf->type != NGX_RTMP_HLS_TYPE_EVENT)\n    {\n        if (conf->path.data[conf->path.len - 1] == '/') {\n            conf->path.len--;\n        }\n\n        cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));\n        if (cleanup == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        cleanup->path = conf->path;\n        cleanup->playlen = conf->playlen;\n\n        conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot));\n        if (conf->slot == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        conf->slot->manager = ngx_rtmp_hls_cleanup;\n        conf->slot->name = conf->path;\n        conf->slot->data = cleanup;\n        conf->slot->conf_file = cf->conf_file->file.name.data;\n        conf->slot->line = cf->conf_file->line;\n\n        if (ngx_add_path(cf, &conf->slot) != NGX_OK) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    ngx_conf_merge_str_value(conf->path, prev->path, \"\");\n\n    if (conf->keys && conf->cleanup && conf->key_path.len &&\n        ngx_strcmp(conf->key_path.data, conf->path.data) != 0 &&\n        conf->type != NGX_RTMP_HLS_TYPE_EVENT)\n    {\n        if (conf->key_path.data[conf->key_path.len - 1] == '/') {\n            conf->key_path.len--;\n        }\n\n        cleanup = ngx_pcalloc(cf->pool, sizeof(*cleanup));\n        if (cleanup == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        cleanup->path = conf->key_path;\n        cleanup->playlen = conf->playlen;\n\n        conf->slot = ngx_pcalloc(cf->pool, sizeof(*conf->slot));\n        if (conf->slot == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        conf->slot->manager = ngx_rtmp_hls_cleanup;\n        conf->slot->name = conf->key_path;\n        conf->slot->data = cleanup;\n        conf->slot->conf_file = cf->conf_file->file.name.data;\n        conf->slot->line = cf->conf_file->line;\n\n        if (ngx_add_path(cf, &conf->slot) != NGX_OK) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    ngx_conf_merge_str_value(conf->key_path, prev->key_path, \"\");\n\n    if (conf->key_path.len == 0) {\n        conf->key_path = conf->path;\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_hls_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t   *cmcf;\n    ngx_rtmp_handler_pt         *h;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);\n    *h = ngx_rtmp_hls_video;\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);\n    *h = ngx_rtmp_hls_audio;\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_hls_publish;\n\n    next_close_stream = ngx_rtmp_close_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_hls_close_stream;\n\n    next_stream_begin = ngx_rtmp_stream_begin;\n    ngx_rtmp_stream_begin = ngx_rtmp_hls_stream_begin;\n\n    next_stream_eof = ngx_rtmp_stream_eof;\n    ngx_rtmp_stream_eof = ngx_rtmp_hls_stream_eof;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "hls/ngx_rtmp_mpegts.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_mpegts.h\"\n\n\nstatic u_char ngx_rtmp_mpegts_header[] = {\n\n    /* TS */\n    0x47, 0x40, 0x00, 0x10, 0x00,\n    /* PSI */\n    0x00, 0xb0, 0x0d, 0x00, 0x01, 0xc1, 0x00, 0x00,\n    /* PAT */\n    0x00, 0x01, 0xf0, 0x01,\n    /* CRC */\n    0x2e, 0x70, 0x19, 0x05,\n    /* stuffing 167 bytes */\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n\n    /* TS */\n    0x47, 0x50, 0x01, 0x10, 0x00,\n    /* PSI */\n    0x02, 0xb0, 0x17, 0x00, 0x01, 0xc1, 0x00, 0x00,\n    /* PMT */\n    0xe1, 0x00,\n    0xf0, 0x00,\n    0x1b, 0xe1, 0x00, 0xf0, 0x00, /* h264 */\n    0x0f, 0xe1, 0x01, 0xf0, 0x00, /* aac */\n    /*0x03, 0xe1, 0x01, 0xf0, 0x00,*/ /* mp3 */\n    /* CRC */\n    0x2f, 0x44, 0xb9, 0x9b, /* crc for aac */\n    /*0x4e, 0x59, 0x3d, 0x1e,*/ /* crc for mp3 */\n    /* stuffing 157 bytes */\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,\n    0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff\n};\n\n\n/* 700 ms PCR delay */\n#define NGX_RTMP_HLS_DELAY  63000\n\n\nstatic ngx_int_t\nngx_rtmp_mpegts_write_file(ngx_rtmp_mpegts_file_t *file, u_char *in,\n    size_t in_size)\n{\n    u_char   *out;\n    size_t    out_size, n;\n    ssize_t   rc;\n\n    static u_char  buf[1024];\n\n    if (!file->encrypt) {\n        ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,\n                       \"mpegts: write %uz bytes\", in_size);\n\n        rc = ngx_write_fd(file->fd, in, in_size);\n        if (rc < 0) {\n            return NGX_ERROR;\n        }\n\n        return NGX_OK;\n    }\n\n    /* encrypt */\n\n    ngx_log_debug1(NGX_LOG_DEBUG_CORE, file->log, 0,\n                   \"mpegts: write %uz encrypted bytes\", in_size);\n\n    out = buf;\n    out_size = sizeof(buf);\n\n    if (file->size > 0 && file->size + in_size >= 16) {\n        ngx_memcpy(file->buf + file->size, in, 16 - file->size);\n\n        in += 16 - file->size;\n        in_size -= 16 - file->size;\n\n        AES_cbc_encrypt(file->buf, out, 16, &file->key, file->iv, AES_ENCRYPT);\n\n        out += 16;\n        out_size -= 16;\n\n        file->size = 0;\n    }\n\n    for ( ;; ) {\n        n = in_size & ~0x0f;\n\n        if (n > 0) {\n            if (n > out_size) {\n                n = out_size;\n            }\n\n            AES_cbc_encrypt(in, out, n, &file->key, file->iv, AES_ENCRYPT);\n\n            in += n;\n            in_size -= n;\n\n        } else if (out == buf) {\n            break;\n        }\n\n        rc = ngx_write_fd(file->fd, buf, out - buf + n);\n        if (rc < 0) {\n            return NGX_ERROR;\n        }\n\n        out = buf;\n        out_size = sizeof(buf);\n    }\n\n    if (in_size) {\n        ngx_memcpy(file->buf + file->size, in, in_size);\n        file->size += in_size;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mpegts_write_header(ngx_rtmp_mpegts_file_t *file)\n{\n    return ngx_rtmp_mpegts_write_file(file, ngx_rtmp_mpegts_header,\n                                      sizeof(ngx_rtmp_mpegts_header));\n}\n\n\nstatic u_char *\nngx_rtmp_mpegts_write_pcr(u_char *p, uint64_t pcr)\n{\n    *p++ = (u_char) (pcr >> 25);\n    *p++ = (u_char) (pcr >> 17);\n    *p++ = (u_char) (pcr >> 9);\n    *p++ = (u_char) (pcr >> 1);\n    *p++ = (u_char) (pcr << 7 | 0x7e);\n    *p++ = 0;\n\n    return p;\n}\n\n\nstatic u_char *\nngx_rtmp_mpegts_write_pts(u_char *p, ngx_uint_t fb, uint64_t pts)\n{\n    ngx_uint_t val;\n\n    val = fb << 4 | (((pts >> 30) & 0x07) << 1) | 1;\n    *p++ = (u_char) val;\n\n    val = (((pts >> 15) & 0x7fff) << 1) | 1;\n    *p++ = (u_char) (val >> 8);\n    *p++ = (u_char) val;\n\n    val = (((pts) & 0x7fff) << 1) | 1;\n    *p++ = (u_char) (val >> 8);\n    *p++ = (u_char) val;\n\n    return p;\n}\n\n\nngx_int_t\nngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,\n    ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b)\n{\n    ngx_uint_t  pes_size, header_size, body_size, in_size, stuff_size, flags;\n    u_char      packet[188], *p, *base;\n    ngx_int_t   first, rc;\n\n    ngx_log_debug6(NGX_LOG_DEBUG_CORE, file->log, 0,\n                   \"mpegts: pid=%ui, sid=%ui, pts=%uL, \"\n                   \"dts=%uL, key=%ui, size=%ui\",\n                   f->pid, f->sid, f->pts, f->dts,\n                   (ngx_uint_t) f->key, (size_t) (b->last - b->pos));\n\n    first = 1;\n\n    while (b->pos < b->last) {\n        p = packet;\n\n        f->cc++;\n\n        *p++ = 0x47;\n        *p++ = (u_char) (f->pid >> 8);\n\n        if (first) {\n            p[-1] |= 0x40;\n        }\n\n        *p++ = (u_char) f->pid;\n        *p++ = 0x10 | (f->cc & 0x0f); /* payload */\n\n        if (first) {\n\n            if (f->key) {\n                packet[3] |= 0x20; /* adaptation */\n\n                *p++ = 7;    /* size */\n                *p++ = 0x50; /* random access + PCR */\n\n                p = ngx_rtmp_mpegts_write_pcr(p, f->dts - NGX_RTMP_HLS_DELAY);\n            }\n\n            /* PES header */\n\n            *p++ = 0x00;\n            *p++ = 0x00;\n            *p++ = 0x01;\n            *p++ = (u_char) f->sid;\n\n            header_size = 5;\n            flags = 0x80; /* PTS */\n\n            if (f->dts != f->pts) {\n                header_size += 5;\n                flags |= 0x40; /* DTS */\n            }\n\n            pes_size = (b->last - b->pos) + header_size + 3;\n            if (pes_size > 0xffff) {\n                pes_size = 0;\n            }\n\n            *p++ = (u_char) (pes_size >> 8);\n            *p++ = (u_char) pes_size;\n            *p++ = 0x80; /* H222 */\n            *p++ = (u_char) flags;\n            *p++ = (u_char) header_size;\n\n            p = ngx_rtmp_mpegts_write_pts(p, flags >> 6, f->pts +\n                                                         NGX_RTMP_HLS_DELAY);\n\n            if (f->dts != f->pts) {\n                p = ngx_rtmp_mpegts_write_pts(p, 1, f->dts +\n                                                    NGX_RTMP_HLS_DELAY);\n            }\n\n            first = 0;\n        }\n\n        body_size = (ngx_uint_t) (packet + sizeof(packet) - p);\n        in_size = (ngx_uint_t) (b->last - b->pos);\n\n        if (body_size <= in_size) {\n            ngx_memcpy(p, b->pos, body_size);\n            b->pos += body_size;\n\n        } else {\n            stuff_size = (body_size - in_size);\n\n            if (packet[3] & 0x20) {\n\n                /* has adaptation */\n\n                base = &packet[5] + packet[4];\n                p = ngx_movemem(base + stuff_size, base, p - base);\n                ngx_memset(base, 0xff, stuff_size);\n                packet[4] += (u_char) stuff_size;\n\n            } else {\n\n                /* no adaptation */\n\n                packet[3] |= 0x20;\n                p = ngx_movemem(&packet[4] + stuff_size, &packet[4],\n                                p - &packet[4]);\n\n                packet[4] = (u_char) (stuff_size - 1);\n                if (stuff_size >= 2) {\n                    packet[5] = 0;\n                    ngx_memset(&packet[6], 0xff, stuff_size - 2);\n                }\n            }\n\n            ngx_memcpy(p, b->pos, in_size);\n            b->pos = b->last;\n        }\n\n        rc = ngx_rtmp_mpegts_write_file(file, packet, sizeof(packet));\n        if (rc != NGX_OK) {\n            return rc;\n        }\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,\n    u_char *key, size_t key_len, uint64_t iv)\n{\n    if (AES_set_encrypt_key(key, key_len * 8, &file->key)) {\n        return NGX_ERROR;\n    }\n\n    ngx_memzero(file->iv, 8);\n\n    file->iv[8]  = (u_char) (iv >> 56);\n    file->iv[9]  = (u_char) (iv >> 48);\n    file->iv[10] = (u_char) (iv >> 40);\n    file->iv[11] = (u_char) (iv >> 32);\n    file->iv[12] = (u_char) (iv >> 24);\n    file->iv[13] = (u_char) (iv >> 16);\n    file->iv[14] = (u_char) (iv >> 8);\n    file->iv[15] = (u_char) (iv);\n\n    file->encrypt = 1;\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,\n    ngx_log_t *log)\n{\n    file->log = log;\n\n    file->fd = ngx_open_file(path, NGX_FILE_WRONLY, NGX_FILE_TRUNCATE,\n                             NGX_FILE_DEFAULT_ACCESS);\n\n    if (file->fd == NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, log, ngx_errno,\n                      \"hls: error creating fragment file\");\n        return NGX_ERROR;\n    }\n\n    file->size = 0;\n\n    if (ngx_rtmp_mpegts_write_header(file) != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, log, ngx_errno,\n                      \"hls: error writing fragment header\");\n        ngx_close_file(file->fd);\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file)\n{\n    u_char   buf[16];\n    ssize_t  rc;\n\n    if (file->encrypt) {\n        ngx_memset(file->buf + file->size, 16 - file->size, 16 - file->size);\n\n        AES_cbc_encrypt(file->buf, buf, 16, &file->key, file->iv, AES_ENCRYPT);\n\n        rc = ngx_write_fd(file->fd, buf, 16);\n        if (rc < 0) {\n            return NGX_ERROR;\n        }\n    }\n\n    ngx_close_file(file->fd);\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "hls/ngx_rtmp_mpegts.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_MPEGTS_H_INCLUDED_\n#define _NGX_RTMP_MPEGTS_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <openssl/aes.h>\n\n\ntypedef struct {\n    ngx_fd_t    fd;\n    ngx_log_t  *log;\n    unsigned    encrypt:1;\n    unsigned    size:4;\n    u_char      buf[16];\n    u_char      iv[16];\n    AES_KEY     key;\n} ngx_rtmp_mpegts_file_t;\n\n\ntypedef struct {\n    uint64_t    pts;\n    uint64_t    dts;\n    ngx_uint_t  pid;\n    ngx_uint_t  sid;\n    ngx_uint_t  cc;\n    unsigned    key:1;\n} ngx_rtmp_mpegts_frame_t;\n\n\nngx_int_t ngx_rtmp_mpegts_init_encryption(ngx_rtmp_mpegts_file_t *file,\n    u_char *key, size_t key_len, uint64_t iv);\nngx_int_t ngx_rtmp_mpegts_open_file(ngx_rtmp_mpegts_file_t *file, u_char *path,\n    ngx_log_t *log);\nngx_int_t ngx_rtmp_mpegts_close_file(ngx_rtmp_mpegts_file_t *file);\nngx_int_t ngx_rtmp_mpegts_write_frame(ngx_rtmp_mpegts_file_t *file,\n    ngx_rtmp_mpegts_frame_t *f, ngx_buf_t *b);\n\n\n#endif /* _NGX_RTMP_MPEGTS_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_event.h>\n#include <nginx.h>\n#include \"ngx_rtmp.h\"\n\n\nstatic char *ngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);\nstatic ngx_int_t ngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports,\n    ngx_rtmp_listen_t *listen);\nstatic char *ngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports);\nstatic ngx_int_t ngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport,\n    ngx_rtmp_conf_addr_t *addr);\n#if (NGX_HAVE_INET6)\nstatic ngx_int_t ngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,\n    ngx_rtmp_conf_addr_t *addr);\n#endif\nstatic ngx_int_t ngx_rtmp_cmp_conf_addrs(const void *one, const void *two);\nstatic ngx_int_t ngx_rtmp_init_events(ngx_conf_t *cf,\n        ngx_rtmp_core_main_conf_t *cmcf);\nstatic ngx_int_t ngx_rtmp_init_event_handlers(ngx_conf_t *cf,\n        ngx_rtmp_core_main_conf_t *cmcf);\nstatic char * ngx_rtmp_merge_applications(ngx_conf_t *cf,\n        ngx_array_t *applications, void **app_conf, ngx_rtmp_module_t *module,\n        ngx_uint_t ctx_index);\nstatic ngx_int_t ngx_rtmp_init_process(ngx_cycle_t *cycle);\n\n\n#if (nginx_version >= 1007011)\nngx_queue_t                         ngx_rtmp_init_queue;\n#elif (nginx_version >= 1007005)\nngx_thread_volatile ngx_queue_t     ngx_rtmp_init_queue;\n#else\nngx_thread_volatile ngx_event_t    *ngx_rtmp_init_queue;\n#endif\n\n\nngx_uint_t  ngx_rtmp_max_module;\n\n\nstatic ngx_command_t  ngx_rtmp_commands[] = {\n\n    { ngx_string(\"rtmp\"),\n      NGX_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,\n      ngx_rtmp_block,\n      0,\n      0,\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_core_module_t  ngx_rtmp_module_ctx = {\n    ngx_string(\"rtmp\"),\n    NULL,\n    NULL\n};\n\n\nngx_module_t  ngx_rtmp_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_module_ctx,                  /* module context */\n    ngx_rtmp_commands,                     /* module directives */\n    NGX_CORE_MODULE,                       /* module type */\n    NULL,                                  /* init master */\n    NULL,                                  /* init module */\n    ngx_rtmp_init_process,                 /* init process */\n    NULL,                                  /* init thread */\n    NULL,                                  /* exit thread */\n    NULL,                                  /* exit process */\n    NULL,                                  /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic char *\nngx_rtmp_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    char                        *rv;\n    ngx_uint_t                   i, m, mi, s;\n    ngx_conf_t                   pcf;\n    ngx_array_t                  ports;\n    ngx_module_t               **modules;\n    ngx_rtmp_listen_t           *listen;\n    ngx_rtmp_module_t           *module;\n    ngx_rtmp_conf_ctx_t         *ctx;\n    ngx_rtmp_core_srv_conf_t    *cscf, **cscfp;\n    ngx_rtmp_core_main_conf_t   *cmcf;\n\n    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));\n    if (ctx == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    *(ngx_rtmp_conf_ctx_t **) conf = ctx;\n\n    /* count the number of the rtmp modules and set up their indices */\n\n#if (nginx_version >= 1009011)\n\n    ngx_rtmp_max_module = ngx_count_modules(cf->cycle, NGX_RTMP_MODULE);\n\n#else\n\n    ngx_rtmp_max_module = 0;\n    for (m = 0; ngx_modules[m]; m++) {\n        if (ngx_modules[m]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n\n        ngx_modules[m]->ctx_index = ngx_rtmp_max_module++;\n    }\n\n#endif\n\n\n    /* the rtmp main_conf context, it is the same in the all rtmp contexts */\n\n    ctx->main_conf = ngx_pcalloc(cf->pool,\n                                 sizeof(void *) * ngx_rtmp_max_module);\n    if (ctx->main_conf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n\n    /*\n     * the rtmp null srv_conf context, it is used to merge\n     * the server{}s' srv_conf's\n     */\n\n    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);\n    if (ctx->srv_conf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n\n    /*\n     * the rtmp null app_conf context, it is used to merge\n     * the server{}s' app_conf's\n     */\n\n    ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);\n    if (ctx->app_conf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n\n    /*\n     * create the main_conf's, the null srv_conf's, and the null app_conf's\n     * of the all rtmp modules\n     */\n\n#if (nginx_version >= 1009011)\n    modules = cf->cycle->modules;\n#else\n    modules = ngx_modules;\n#endif\n\n    for (m = 0; modules[m]; m++) {\n        if (modules[m]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n\n        module = modules[m]->ctx;\n        mi = modules[m]->ctx_index;\n\n        if (module->create_main_conf) {\n            ctx->main_conf[mi] = module->create_main_conf(cf);\n            if (ctx->main_conf[mi] == NULL) {\n                return NGX_CONF_ERROR;\n            }\n        }\n\n        if (module->create_srv_conf) {\n            ctx->srv_conf[mi] = module->create_srv_conf(cf);\n            if (ctx->srv_conf[mi] == NULL) {\n                return NGX_CONF_ERROR;\n            }\n        }\n\n        if (module->create_app_conf) {\n            ctx->app_conf[mi] = module->create_app_conf(cf);\n            if (ctx->app_conf[mi] == NULL) {\n                return NGX_CONF_ERROR;\n            }\n        }\n    }\n\n    pcf = *cf;\n    cf->ctx = ctx;\n\n    for (m = 0; modules[m]; m++) {\n        if (modules[m]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n\n        module = modules[m]->ctx;\n\n        if (module->preconfiguration) {\n            if (module->preconfiguration(cf) != NGX_OK) {\n                return NGX_CONF_ERROR;\n            }\n        }\n    }\n\n    /* parse inside the rtmp{} block */\n\n    cf->module_type = NGX_RTMP_MODULE;\n    cf->cmd_type = NGX_RTMP_MAIN_CONF;\n    rv = ngx_conf_parse(cf, NULL);\n\n    if (rv != NGX_CONF_OK) {\n        *cf = pcf;\n        return rv;\n    }\n\n\n    /* init rtmp{} main_conf's, merge the server{}s' srv_conf's */\n\n    cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];\n    cscfp = cmcf->servers.elts;\n\n    for (m = 0; modules[m]; m++) {\n        if (modules[m]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n\n        module = modules[m]->ctx;\n        mi = modules[m]->ctx_index;\n\n        /* init rtmp{} main_conf's */\n\n        cf->ctx = ctx;\n\n        if (module->init_main_conf) {\n            rv = module->init_main_conf(cf, ctx->main_conf[mi]);\n            if (rv != NGX_CONF_OK) {\n                *cf = pcf;\n                return rv;\n            }\n        }\n\n        for (s = 0; s < cmcf->servers.nelts; s++) {\n\n            /* merge the server{}s' srv_conf's */\n\n            cf->ctx = cscfp[s]->ctx;\n\n            if (module->merge_srv_conf) {\n                rv = module->merge_srv_conf(cf,\n                                            ctx->srv_conf[mi],\n                                            cscfp[s]->ctx->srv_conf[mi]);\n                if (rv != NGX_CONF_OK) {\n                    *cf = pcf;\n                    return rv;\n                }\n            }\n\n            if (module->merge_app_conf) {\n\n                /* merge the server{}'s app_conf */\n\n                /*ctx->app_conf = cscfp[s]->ctx->loc_conf;*/\n\n                rv = module->merge_app_conf(cf,\n                                            ctx->app_conf[mi],\n                                            cscfp[s]->ctx->app_conf[mi]);\n                if (rv != NGX_CONF_OK) {\n                    *cf = pcf;\n                    return rv;\n                }\n\n                /* merge the applications{}' app_conf's */\n\n                cscf = cscfp[s]->ctx->srv_conf[ngx_rtmp_core_module.ctx_index];\n\n                rv = ngx_rtmp_merge_applications(cf, &cscf->applications,\n                                            cscfp[s]->ctx->app_conf,\n                                            module, mi);\n                if (rv != NGX_CONF_OK) {\n                    *cf = pcf;\n                    return rv;\n                }\n            }\n\n        }\n    }\n\n\n    if (ngx_rtmp_init_events(cf, cmcf) != NGX_OK) {\n        return NGX_CONF_ERROR;\n    }\n\n    for (m = 0; modules[m]; m++) {\n        if (modules[m]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n\n        module = modules[m]->ctx;\n\n        if (module->postconfiguration) {\n            if (module->postconfiguration(cf) != NGX_OK) {\n                return NGX_CONF_ERROR;\n            }\n        }\n    }\n\n    *cf = pcf;\n\n    if (ngx_rtmp_init_event_handlers(cf, cmcf) != NGX_OK) {\n        return NGX_CONF_ERROR;\n    }\n\n    if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_rtmp_conf_port_t))\n        != NGX_OK)\n    {\n        return NGX_CONF_ERROR;\n    }\n\n    listen = cmcf->listen.elts;\n\n    for (i = 0; i < cmcf->listen.nelts; i++) {\n        if (ngx_rtmp_add_ports(cf, &ports, &listen[i]) != NGX_OK) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    return ngx_rtmp_optimize_servers(cf, &ports);\n}\n\n\nstatic char *\nngx_rtmp_merge_applications(ngx_conf_t *cf, ngx_array_t *applications,\n            void **app_conf, ngx_rtmp_module_t *module, ngx_uint_t ctx_index)\n{\n    char                           *rv;\n    ngx_rtmp_conf_ctx_t            *ctx, saved;\n    ngx_rtmp_core_app_conf_t      **cacfp;\n    ngx_uint_t                      n;\n    ngx_rtmp_core_app_conf_t       *cacf;\n\n    if (applications == NULL) {\n        return NGX_CONF_OK;\n    }\n\n    ctx = (ngx_rtmp_conf_ctx_t *) cf->ctx;\n    saved = *ctx;\n\n    cacfp = applications->elts;\n    for (n = 0; n < applications->nelts; ++n, ++cacfp) {\n\n        ctx->app_conf = (*cacfp)->app_conf;\n\n        rv = module->merge_app_conf(cf, app_conf[ctx_index],\n                (*cacfp)->app_conf[ctx_index]);\n        if (rv != NGX_CONF_OK) {\n            return rv;\n        }\n\n        cacf = (*cacfp)->app_conf[ngx_rtmp_core_module.ctx_index];\n        rv = ngx_rtmp_merge_applications(cf, &cacf->applications,\n                                         (*cacfp)->app_conf,\n                                         module, ctx_index);\n        if (rv != NGX_CONF_OK) {\n            return rv;\n        }\n    }\n\n    *ctx = saved;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_init_events(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)\n{\n    size_t                      n;\n\n    for(n = 0; n < NGX_RTMP_MAX_EVENT; ++n) {\n        if (ngx_array_init(&cmcf->events[n], cf->pool, 1,\n                sizeof(ngx_rtmp_handler_pt)) != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n    }\n\n    if (ngx_array_init(&cmcf->amf, cf->pool, 1,\n                sizeof(ngx_rtmp_amf_handler_t)) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_init_event_handlers(ngx_conf_t *cf, ngx_rtmp_core_main_conf_t *cmcf)\n{\n    ngx_hash_init_t             calls_hash;\n    ngx_rtmp_handler_pt        *eh;\n    ngx_rtmp_amf_handler_t     *h;\n    ngx_hash_key_t             *ha;\n    size_t                      n, m;\n\n    static size_t               pm_events[] = {\n        NGX_RTMP_MSG_CHUNK_SIZE,\n        NGX_RTMP_MSG_ABORT,\n        NGX_RTMP_MSG_ACK,\n        NGX_RTMP_MSG_ACK_SIZE,\n        NGX_RTMP_MSG_BANDWIDTH\n    };\n\n    static size_t               amf_events[] = {\n        NGX_RTMP_MSG_AMF_CMD,\n        NGX_RTMP_MSG_AMF_META,\n        NGX_RTMP_MSG_AMF_SHARED,\n        NGX_RTMP_MSG_AMF3_CMD,\n        NGX_RTMP_MSG_AMF3_META,\n        NGX_RTMP_MSG_AMF3_SHARED\n    };\n\n    /* init standard protocol events */\n    for(n = 0; n < sizeof(pm_events) / sizeof(pm_events[0]); ++n) {\n        eh = ngx_array_push(&cmcf->events[pm_events[n]]);\n        *eh = ngx_rtmp_protocol_message_handler;\n    }\n\n    /* init amf events */\n    for(n = 0; n < sizeof(amf_events) / sizeof(amf_events[0]); ++n) {\n        eh = ngx_array_push(&cmcf->events[amf_events[n]]);\n        *eh = ngx_rtmp_amf_message_handler;\n    }\n\n    /* init user protocol events */\n    eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_USER]);\n    *eh = ngx_rtmp_user_message_handler;\n\n    /* aggregate to audio/video map */\n    eh = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AGGREGATE]);\n    *eh = ngx_rtmp_aggregate_message_handler;\n\n    /* init amf callbacks */\n    ngx_array_init(&cmcf->amf_arrays, cf->pool, 1, sizeof(ngx_hash_key_t));\n\n    h = cmcf->amf.elts;\n    for(n = 0; n < cmcf->amf.nelts; ++n, ++h) {\n        ha = cmcf->amf_arrays.elts;\n        for(m = 0; m < cmcf->amf_arrays.nelts; ++m, ++ha) {\n            if (h->name.len == ha->key.len\n                    && !ngx_strncmp(h->name.data, ha->key.data, ha->key.len))\n            {\n                break;\n            }\n        }\n        if (m == cmcf->amf_arrays.nelts) {\n            ha = ngx_array_push(&cmcf->amf_arrays);\n            ha->key = h->name;\n            ha->key_hash = ngx_hash_key_lc(ha->key.data, ha->key.len);\n            ha->value = ngx_array_create(cf->pool, 1,\n                    sizeof(ngx_rtmp_handler_pt));\n            if (ha->value == NULL) {\n                return NGX_ERROR;\n            }\n        }\n\n        eh = ngx_array_push((ngx_array_t*)ha->value);\n        *eh = h->handler;\n    }\n\n    calls_hash.hash = &cmcf->amf_hash;\n    calls_hash.key = ngx_hash_key_lc;\n    calls_hash.max_size = 512;\n    calls_hash.bucket_size = ngx_cacheline_size;\n    calls_hash.name = \"amf_hash\";\n    calls_hash.pool = cf->pool;\n    calls_hash.temp_pool = NULL;\n\n    if (ngx_hash_init(&calls_hash, cmcf->amf_arrays.elts, cmcf->amf_arrays.nelts)\n            != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_add_ports(ngx_conf_t *cf, ngx_array_t *ports,\n    ngx_rtmp_listen_t *listen)\n{\n    in_port_t              p;\n    ngx_uint_t             i;\n    struct sockaddr       *sa;\n    struct sockaddr_in    *sin;\n    ngx_rtmp_conf_port_t  *port;\n    ngx_rtmp_conf_addr_t  *addr;\n#if (NGX_HAVE_INET6)\n    struct sockaddr_in6   *sin6;\n#endif\n\n    sa = (struct sockaddr *) &listen->sockaddr;\n\n    switch (sa->sa_family) {\n\n#if (NGX_HAVE_INET6)\n    case AF_INET6:\n        sin6 = (struct sockaddr_in6 *) sa;\n        p = sin6->sin6_port;\n        break;\n#endif\n\n    default: /* AF_INET */\n        sin = (struct sockaddr_in *) sa;\n        p = sin->sin_port;\n        break;\n    }\n\n    port = ports->elts;\n    for (i = 0; i < ports->nelts; i++) {\n        if (p == port[i].port && sa->sa_family == port[i].family) {\n\n            /* a port is already in the port list */\n\n            port = &port[i];\n            goto found;\n        }\n    }\n\n    /* add a port to the port list */\n\n    port = ngx_array_push(ports);\n    if (port == NULL) {\n        return NGX_ERROR;\n    }\n\n    port->family = sa->sa_family;\n    port->port = p;\n\n    if (ngx_array_init(&port->addrs, cf->temp_pool, 2,\n                       sizeof(ngx_rtmp_conf_addr_t))\n        != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\nfound:\n\n    addr = ngx_array_push(&port->addrs);\n    if (addr == NULL) {\n        return NGX_ERROR;\n    }\n\n    addr->sockaddr = (struct sockaddr *) &listen->sockaddr;\n    addr->socklen = listen->socklen;\n    addr->ctx = listen->ctx;\n    addr->bind = listen->bind;\n    addr->wildcard = listen->wildcard;\n    addr->so_keepalive = listen->so_keepalive;\n    addr->proxy_protocol = listen->proxy_protocol;\n#if (NGX_HAVE_KEEPALIVE_TUNABLE)\n    addr->tcp_keepidle = listen->tcp_keepidle;\n    addr->tcp_keepintvl = listen->tcp_keepintvl;\n    addr->tcp_keepcnt = listen->tcp_keepcnt;\n#endif\n#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)\n    addr->ipv6only = listen->ipv6only;\n#endif\n\n    return NGX_OK;\n}\n\n\nstatic char *\nngx_rtmp_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports)\n{\n    ngx_uint_t             i, p, last, bind_wildcard;\n    ngx_listening_t       *ls;\n    ngx_rtmp_port_t       *mport;\n    ngx_rtmp_conf_port_t  *port;\n    ngx_rtmp_conf_addr_t  *addr;\n\n    port = ports->elts;\n    for (p = 0; p < ports->nelts; p++) {\n\n        ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts,\n                 sizeof(ngx_rtmp_conf_addr_t), ngx_rtmp_cmp_conf_addrs);\n\n        addr = port[p].addrs.elts;\n        last = port[p].addrs.nelts;\n\n        /*\n         * if there is the binding to the \"*:port\" then we need to bind()\n         * to the \"*:port\" only and ignore the other bindings\n         */\n\n        if (addr[last - 1].wildcard) {\n            addr[last - 1].bind = 1;\n            bind_wildcard = 1;\n\n        } else {\n            bind_wildcard = 0;\n        }\n\n        i = 0;\n\n        while (i < last) {\n\n            if (bind_wildcard && !addr[i].bind) {\n                i++;\n                continue;\n            }\n\n            ls = ngx_create_listening(cf, addr[i].sockaddr, addr[i].socklen);\n            if (ls == NULL) {\n                return NGX_CONF_ERROR;\n            }\n\n            ls->addr_ntop = 1;\n            ls->handler = ngx_rtmp_init_connection;\n            ls->pool_size = 4096;\n\n            /* TODO: error_log directive */\n            ls->logp = &cf->cycle->new_log;\n            ls->log.data = &ls->addr_text;\n            ls->log.handler = ngx_accept_log_error;\n\n            ls->keepalive = addr[i].so_keepalive;\n#if (NGX_HAVE_KEEPALIVE_TUNABLE)\n            ls->keepidle = addr[i].tcp_keepidle;\n            ls->keepintvl = addr[i].tcp_keepintvl;\n            ls->keepcnt = addr[i].tcp_keepcnt;\n#endif\n\n#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)\n            ls->ipv6only = addr[i].ipv6only;\n#endif\n\n            ls->wildcard = addr[i].wildcard;\n\n            mport = ngx_palloc(cf->pool, sizeof(ngx_rtmp_port_t));\n            if (mport == NULL) {\n                return NGX_CONF_ERROR;\n            }\n\n            ls->servers = mport;\n\n            if (i == last - 1) {\n                mport->naddrs = last;\n\n            } else {\n                mport->naddrs = 1;\n                i = 0;\n            }\n\n            switch (ls->sockaddr->sa_family) {\n#if (NGX_HAVE_INET6)\n            case AF_INET6:\n                if (ngx_rtmp_add_addrs6(cf, mport, addr) != NGX_OK) {\n                    return NGX_CONF_ERROR;\n                }\n                break;\n#endif\n            default: /* AF_INET */\n                if (ngx_rtmp_add_addrs(cf, mport, addr) != NGX_OK) {\n                    return NGX_CONF_ERROR;\n                }\n                break;\n            }\n\n            addr++;\n            last--;\n        }\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_add_addrs(ngx_conf_t *cf, ngx_rtmp_port_t *mport,\n    ngx_rtmp_conf_addr_t *addr)\n{\n    u_char              *p;\n    size_t               len;\n    ngx_uint_t           i;\n    ngx_rtmp_in_addr_t  *addrs;\n    struct sockaddr_in  *sin;\n    u_char               buf[NGX_SOCKADDR_STRLEN];\n\n    mport->addrs = ngx_pcalloc(cf->pool,\n                               mport->naddrs * sizeof(ngx_rtmp_in_addr_t));\n    if (mport->addrs == NULL) {\n        return NGX_ERROR;\n    }\n\n    addrs = mport->addrs;\n\n    for (i = 0; i < mport->naddrs; i++) {\n\n        sin = (struct sockaddr_in *) addr[i].sockaddr;\n        addrs[i].addr = sin->sin_addr.s_addr;\n\n        addrs[i].conf.ctx = addr[i].ctx;\n\n        len = ngx_sock_ntop(addr[i].sockaddr,\n#if (nginx_version >= 1005003)\n                            addr[i].socklen,\n#endif\n                            buf, NGX_SOCKADDR_STRLEN, 1);\n\n        p = ngx_pnalloc(cf->pool, len);\n        if (p == NULL) {\n            return NGX_ERROR;\n        }\n\n        ngx_memcpy(p, buf, len);\n\n        addrs[i].conf.addr_text.len = len;\n        addrs[i].conf.addr_text.data = p;\n        addrs[i].conf.proxy_protocol = addr->proxy_protocol;\n    }\n\n    return NGX_OK;\n}\n\n\n#if (NGX_HAVE_INET6)\n\nstatic ngx_int_t\nngx_rtmp_add_addrs6(ngx_conf_t *cf, ngx_rtmp_port_t *mport,\n    ngx_rtmp_conf_addr_t *addr)\n{\n    u_char               *p;\n    size_t                len;\n    ngx_uint_t            i;\n    ngx_rtmp_in6_addr_t  *addrs6;\n    struct sockaddr_in6  *sin6;\n    u_char                buf[NGX_SOCKADDR_STRLEN];\n\n    mport->addrs = ngx_pcalloc(cf->pool,\n                               mport->naddrs * sizeof(ngx_rtmp_in6_addr_t));\n    if (mport->addrs == NULL) {\n        return NGX_ERROR;\n    }\n\n    addrs6 = mport->addrs;\n\n    for (i = 0; i < mport->naddrs; i++) {\n\n        sin6 = (struct sockaddr_in6 *) addr[i].sockaddr;\n        addrs6[i].addr6 = sin6->sin6_addr;\n\n        addrs6[i].conf.ctx = addr[i].ctx;\n\n        len = ngx_sock_ntop(addr[i].sockaddr,\n#if (nginx_version >= 1005003)\n                            addr[i].socklen,\n#endif\n                            buf, NGX_SOCKADDR_STRLEN, 1);\n\n        p = ngx_pnalloc(cf->pool, len);\n        if (p == NULL) {\n            return NGX_ERROR;\n        }\n\n        ngx_memcpy(p, buf, len);\n\n        addrs6[i].conf.addr_text.len = len;\n        addrs6[i].conf.addr_text.data = p;\n        addrs6[i].conf.proxy_protocol = addr->proxy_protocol;\n    }\n\n    return NGX_OK;\n}\n\n#endif\n\n\nstatic ngx_int_t\nngx_rtmp_cmp_conf_addrs(const void *one, const void *two)\n{\n    ngx_rtmp_conf_addr_t  *first, *second;\n\n    first = (ngx_rtmp_conf_addr_t *) one;\n    second = (ngx_rtmp_conf_addr_t *) two;\n\n    if (first->wildcard) {\n        /* a wildcard must be the last resort, shift it to the end */\n        return 1;\n    }\n\n    if (first->bind && !second->bind) {\n        /* shift explicit bind()ed addresses to the start */\n        return -1;\n    }\n\n    if (!first->bind && second->bind) {\n        /* shift explicit bind()ed addresses to the start */\n        return 1;\n    }\n\n    /* do not sort by default */\n\n    return 0;\n}\n\n\nngx_int_t\nngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt,\n        ngx_rtmp_header_t *h, ngx_chain_t *in)\n{\n    ngx_rtmp_core_main_conf_t      *cmcf;\n    ngx_array_t                    *ch;\n    ngx_rtmp_handler_pt            *hh;\n    size_t                          n;\n\n    cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);\n\n    ch = &cmcf->events[evt];\n    hh = ch->elts;\n    for(n = 0; n < ch->nelts; ++n, ++hh) {\n        if (*hh && (*hh)(s, h, in) != NGX_OK) {\n            return NGX_ERROR;\n        }\n    }\n    return NGX_OK;\n}\n\n\nvoid *\nngx_rtmp_rmemcpy(void *dst, const void* src, size_t n)\n{\n    u_char     *d, *s;\n\n    d = dst;\n    s = (u_char*)src + n - 1;\n\n    while(s >= (u_char*)src) {\n        *d++ = *s--;\n    }\n\n    return dst;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_init_process(ngx_cycle_t *cycle)\n{\n#if (nginx_version >= 1007005)\n    ngx_queue_init(&ngx_rtmp_init_queue);\n#endif\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_H_INCLUDED_\n#define _NGX_RTMP_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_event.h>\n#include <ngx_event_connect.h>\n#include <nginx.h>\n\n#include \"ngx_rtmp_amf.h\"\n#include \"ngx_rtmp_bandwidth.h\"\n\n\n#if (NGX_WIN32)\ntypedef __int8              int8_t;\ntypedef unsigned __int8     uint8_t;\n#endif\n\n\ntypedef struct {\n    void                  **main_conf;\n    void                  **srv_conf;\n    void                  **app_conf;\n} ngx_rtmp_conf_ctx_t;\n\n\ntypedef struct {\n    u_char                  sockaddr[NGX_SOCKADDRLEN];\n    socklen_t               socklen;\n\n    /* server ctx */\n    ngx_rtmp_conf_ctx_t    *ctx;\n\n    unsigned                bind:1;\n    unsigned                wildcard:1;\n#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)\n    unsigned                ipv6only:2;\n#endif\n    unsigned                so_keepalive:2;\n    unsigned                proxy_protocol:1;\n#if (NGX_HAVE_KEEPALIVE_TUNABLE)\n    int                     tcp_keepidle;\n    int                     tcp_keepintvl;\n    int                     tcp_keepcnt;\n#endif\n} ngx_rtmp_listen_t;\n\n\ntypedef struct {\n    ngx_rtmp_conf_ctx_t    *ctx;\n    ngx_str_t               addr_text;\n    unsigned                proxy_protocol:1;\n} ngx_rtmp_addr_conf_t;\n\ntypedef struct {\n    in_addr_t               addr;\n    ngx_rtmp_addr_conf_t    conf;\n} ngx_rtmp_in_addr_t;\n\n\n#if (NGX_HAVE_INET6)\n\ntypedef struct {\n    struct in6_addr         addr6;\n    ngx_rtmp_addr_conf_t    conf;\n} ngx_rtmp_in6_addr_t;\n\n#endif\n\n\ntypedef struct {\n    void                   *addrs;\n    ngx_uint_t              naddrs;\n} ngx_rtmp_port_t;\n\n\ntypedef struct {\n    int                     family;\n    in_port_t               port;\n    ngx_array_t             addrs;       /* array of ngx_rtmp_conf_addr_t */\n} ngx_rtmp_conf_port_t;\n\n\ntypedef struct {\n    struct sockaddr        *sockaddr;\n    socklen_t               socklen;\n\n    ngx_rtmp_conf_ctx_t    *ctx;\n\n    unsigned                bind:1;\n    unsigned                wildcard:1;\n#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)\n    unsigned                ipv6only:2;\n#endif\n    unsigned                so_keepalive:2;\n    unsigned                proxy_protocol:1;\n#if (NGX_HAVE_KEEPALIVE_TUNABLE)\n    int                     tcp_keepidle;\n    int                     tcp_keepintvl;\n    int                     tcp_keepcnt;\n#endif\n} ngx_rtmp_conf_addr_t;\n\n\n#define NGX_RTMP_VERSION                3\n\n#define NGX_LOG_DEBUG_RTMP              NGX_LOG_DEBUG_CORE\n\n#define NGX_RTMP_DEFAULT_CHUNK_SIZE     128\n\n\n/* RTMP message types */\n#define NGX_RTMP_MSG_CHUNK_SIZE         1\n#define NGX_RTMP_MSG_ABORT              2\n#define NGX_RTMP_MSG_ACK                3\n#define NGX_RTMP_MSG_USER               4\n#define NGX_RTMP_MSG_ACK_SIZE           5\n#define NGX_RTMP_MSG_BANDWIDTH          6\n#define NGX_RTMP_MSG_EDGE               7\n#define NGX_RTMP_MSG_AUDIO              8\n#define NGX_RTMP_MSG_VIDEO              9\n#define NGX_RTMP_MSG_AMF3_META          15\n#define NGX_RTMP_MSG_AMF3_SHARED        16\n#define NGX_RTMP_MSG_AMF3_CMD           17\n#define NGX_RTMP_MSG_AMF_META           18\n#define NGX_RTMP_MSG_AMF_SHARED         19\n#define NGX_RTMP_MSG_AMF_CMD            20\n#define NGX_RTMP_MSG_AGGREGATE          22\n#define NGX_RTMP_MSG_MAX                22\n\n#define NGX_RTMP_MAX_CHUNK_SIZE         10485760\n\n#define NGX_RTMP_CONNECT                NGX_RTMP_MSG_MAX + 1\n#define NGX_RTMP_DISCONNECT             NGX_RTMP_MSG_MAX + 2\n#define NGX_RTMP_HANDSHAKE_DONE         NGX_RTMP_MSG_MAX + 3\n#define NGX_RTMP_MAX_EVENT              NGX_RTMP_MSG_MAX + 4\n\n\n/* RMTP control message types */\n#define NGX_RTMP_USER_STREAM_BEGIN      0\n#define NGX_RTMP_USER_STREAM_EOF        1\n#define NGX_RTMP_USER_STREAM_DRY        2\n#define NGX_RTMP_USER_SET_BUFLEN        3\n#define NGX_RTMP_USER_RECORDED          4\n#define NGX_RTMP_USER_PING_REQUEST      6\n#define NGX_RTMP_USER_PING_RESPONSE     7\n#define NGX_RTMP_USER_UNKNOWN           8\n#define NGX_RTMP_USER_BUFFER_END        31\n\n\n/* Chunk header:\n *   max 3  basic header\n * + max 11 message header\n * + max 4  extended header (timestamp) */\n#define NGX_RTMP_MAX_CHUNK_HEADER       18\n\n\ntypedef struct {\n    uint32_t                csid;       /* chunk stream id */\n    uint32_t                timestamp;  /* timestamp (delta) */\n    uint32_t                mlen;       /* message length */\n    uint8_t                 type;       /* message type id */\n    uint32_t                msid;       /* message stream id */\n} ngx_rtmp_header_t;\n\n\ntypedef struct {\n    ngx_rtmp_header_t       hdr;\n    uint32_t                dtime;\n    uint32_t                len;        /* current fragment length */\n    uint8_t                 ext;\n    ngx_chain_t            *in;\n} ngx_rtmp_stream_t;\n\n\n/* disable zero-sized array warning by msvc */\n\n#if (NGX_WIN32)\n#pragma warning(push)\n#pragma warning(disable:4200)\n#endif\n\n\ntypedef struct {\n    uint32_t                signature;  /* \"RTMP\" */ /* <-- FIXME wtf */\n\n    ngx_event_t             close;\n\n    void                  **ctx;\n    void                  **main_conf;\n    void                  **srv_conf;\n    void                  **app_conf;\n\n    ngx_str_t              *addr_text;\n    int                     connected;\n\n#if (nginx_version >= 1007005)\n    ngx_queue_t             posted_dry_events;\n#else\n    ngx_event_t            *posted_dry_events;\n#endif\n\n    /* client buffer time in msec */\n    uint32_t                buflen;\n    uint32_t                ack_size;\n\n    /* connection parameters */\n    ngx_str_t               app;\n    ngx_str_t               args;\n    ngx_str_t               flashver;\n    ngx_str_t               swf_url;\n    ngx_str_t               tc_url;\n    uint32_t                acodecs;\n    uint32_t                vcodecs;\n    ngx_str_t               page_url;\n\n    /* handshake data */\n    ngx_buf_t              *hs_buf;\n    u_char                 *hs_digest;\n    unsigned                hs_old:1;\n    ngx_uint_t              hs_stage;\n\n    /* connection timestamps */\n    ngx_msec_t              epoch;\n    ngx_msec_t              peer_epoch;\n    ngx_msec_t              base_time;\n    uint32_t                current_time;\n\n    /* ping */\n    ngx_event_t             ping_evt;\n    unsigned                ping_active:1;\n    unsigned                ping_reset:1;\n\n    /* auto-pushed? */\n    unsigned                auto_pushed:1;\n    unsigned                relay:1;\n    unsigned                static_relay:1;\n\n    /* input stream 0 (reserved by RTMP spec)\n     * is used as free chain link */\n\n    ngx_rtmp_stream_t      *in_streams;\n    uint32_t                in_csid;\n    ngx_uint_t              in_chunk_size;\n    ngx_pool_t             *in_pool;\n    uint32_t                in_bytes;\n    uint32_t                in_last_ack;\n\n    ngx_pool_t             *in_old_pool;\n    ngx_int_t               in_chunk_size_changing;\n\n    ngx_connection_t       *connection;\n\n    /* circular buffer of RTMP message pointers */\n    ngx_msec_t              timeout;\n    uint32_t                out_bytes;\n    size_t                  out_pos, out_last;\n    ngx_chain_t            *out_chain;\n    u_char                 *out_bpos;\n    unsigned                out_buffer:1;\n    size_t                  out_queue;\n    size_t                  out_cork;\n    ngx_chain_t            *out[0];\n} ngx_rtmp_session_t;\n\n\n#if (NGX_WIN32)\n#pragma warning(pop)\n#endif\n\n\n/* handler result code:\n *  NGX_ERROR - error\n *  NGX_OK    - success, may continue\n *  NGX_DONE  - success, input parsed, reply sent; need no\n *      more calls on this event */\ntypedef ngx_int_t (*ngx_rtmp_handler_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in);\n\n\ntypedef struct {\n    ngx_str_t               name;\n    ngx_rtmp_handler_pt     handler;\n} ngx_rtmp_amf_handler_t;\n\n\ntypedef struct {\n    ngx_array_t             servers;    /* ngx_rtmp_core_srv_conf_t */\n    ngx_array_t             listen;     /* ngx_rtmp_listen_t */\n\n    ngx_array_t             events[NGX_RTMP_MAX_EVENT];\n\n    ngx_hash_t              amf_hash;\n    ngx_array_t             amf_arrays;\n    ngx_array_t             amf;\n} ngx_rtmp_core_main_conf_t;\n\n\n/* global main conf for stats */\nextern ngx_rtmp_core_main_conf_t   *ngx_rtmp_core_main_conf;\n\n\ntypedef struct ngx_rtmp_core_srv_conf_s {\n    ngx_array_t             applications; /* ngx_rtmp_core_app_conf_t */\n\n    ngx_msec_t              timeout;\n    ngx_msec_t              ping;\n    ngx_msec_t              ping_timeout;\n    ngx_flag_t              so_keepalive;\n    ngx_int_t               max_streams;\n\n    ngx_uint_t              ack_window;\n\n    ngx_int_t               chunk_size;\n    ngx_pool_t             *pool;\n    ngx_chain_t            *free;\n    ngx_chain_t            *free_hs;\n    size_t                  max_message;\n    ngx_flag_t              play_time_fix;\n    ngx_flag_t              publish_time_fix;\n    ngx_flag_t              busy;\n    size_t                  out_queue;\n    size_t                  out_cork;\n    ngx_msec_t              buflen;\n\n    ngx_rtmp_conf_ctx_t    *ctx;\n} ngx_rtmp_core_srv_conf_t;\n\n\ntypedef struct {\n    ngx_array_t             applications; /* ngx_rtmp_core_app_conf_t */\n    ngx_str_t               name;\n    void                  **app_conf;\n} ngx_rtmp_core_app_conf_t;\n\n\ntypedef struct {\n    ngx_str_t              *client;\n    ngx_rtmp_session_t     *session;\n} ngx_rtmp_error_log_ctx_t;\n\n\ntypedef struct {\n    ngx_int_t             (*preconfiguration)(ngx_conf_t *cf);\n    ngx_int_t             (*postconfiguration)(ngx_conf_t *cf);\n\n    void                 *(*create_main_conf)(ngx_conf_t *cf);\n    char                 *(*init_main_conf)(ngx_conf_t *cf, void *conf);\n\n    void                 *(*create_srv_conf)(ngx_conf_t *cf);\n    char                 *(*merge_srv_conf)(ngx_conf_t *cf, void *prev,\n                                    void *conf);\n\n    void                 *(*create_app_conf)(ngx_conf_t *cf);\n    char                 *(*merge_app_conf)(ngx_conf_t *cf, void *prev,\n                                    void *conf);\n} ngx_rtmp_module_t;\n\n#define NGX_RTMP_MODULE                 0x504D5452     /* \"RTMP\" */\n\n#define NGX_RTMP_MAIN_CONF              0x02000000\n#define NGX_RTMP_SRV_CONF               0x04000000\n#define NGX_RTMP_APP_CONF               0x08000000\n#define NGX_RTMP_REC_CONF               0x10000000\n\n\n#define NGX_RTMP_MAIN_CONF_OFFSET  offsetof(ngx_rtmp_conf_ctx_t, main_conf)\n#define NGX_RTMP_SRV_CONF_OFFSET   offsetof(ngx_rtmp_conf_ctx_t, srv_conf)\n#define NGX_RTMP_APP_CONF_OFFSET   offsetof(ngx_rtmp_conf_ctx_t, app_conf)\n\n\n#define ngx_rtmp_get_module_ctx(s, module)     (s)->ctx[module.ctx_index]\n#define ngx_rtmp_set_ctx(s, c, module)         s->ctx[module.ctx_index] = c;\n#define ngx_rtmp_delete_ctx(s, module)         s->ctx[module.ctx_index] = NULL;\n\n\n#define ngx_rtmp_get_module_main_conf(s, module)                             \\\n    (s)->main_conf[module.ctx_index]\n#define ngx_rtmp_get_module_srv_conf(s, module)  (s)->srv_conf[module.ctx_index]\n#define ngx_rtmp_get_module_app_conf(s, module)  ((s)->app_conf ? \\\n    (s)->app_conf[module.ctx_index] : NULL)\n\n#define ngx_rtmp_conf_get_module_main_conf(cf, module)                       \\\n    ((ngx_rtmp_conf_ctx_t *) cf->ctx)->main_conf[module.ctx_index]\n#define ngx_rtmp_conf_get_module_srv_conf(cf, module)                        \\\n    ((ngx_rtmp_conf_ctx_t *) cf->ctx)->srv_conf[module.ctx_index]\n#define ngx_rtmp_conf_get_module_app_conf(cf, module)                        \\\n    ((ngx_rtmp_conf_ctx_t *) cf->ctx)->app_conf[module.ctx_index]\n\n\n#ifdef NGX_DEBUG\nchar* ngx_rtmp_message_type(uint8_t type);\nchar* ngx_rtmp_user_message_type(uint16_t evt);\n#endif\n\nvoid ngx_rtmp_init_connection(ngx_connection_t *c);\nngx_rtmp_session_t * ngx_rtmp_init_session(ngx_connection_t *c,\n     ngx_rtmp_addr_conf_t *addr_conf);\nvoid ngx_rtmp_finalize_session(ngx_rtmp_session_t *s);\nvoid ngx_rtmp_handshake(ngx_rtmp_session_t *s);\nvoid ngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async);\nvoid ngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s);\nvoid ngx_rtmp_cycle(ngx_rtmp_session_t *s);\nvoid ngx_rtmp_reset_ping(ngx_rtmp_session_t *s);\nngx_int_t ngx_rtmp_fire_event(ngx_rtmp_session_t *s, ngx_uint_t evt,\n        ngx_rtmp_header_t *h, ngx_chain_t *in);\n\n\nngx_int_t ngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size);\n\n\n/* Bit reverse: we need big-endians in many places  */\nvoid * ngx_rtmp_rmemcpy(void *dst, const void* src, size_t n);\n\n#define ngx_rtmp_rcpymem(dst, src, n) \\\n    (((u_char*)ngx_rtmp_rmemcpy(dst, src, n)) + (n))\n\n\nstatic ngx_inline uint16_t\nngx_rtmp_r16(uint16_t n)\n{\n    return (n << 8) | (n >> 8);\n}\n\n\nstatic ngx_inline uint32_t\nngx_rtmp_r32(uint32_t n)\n{\n    return (n << 24) | ((n << 8) & 0xff0000) | ((n >> 8) & 0xff00) | (n >> 24);\n}\n\n\nstatic ngx_inline uint64_t\nngx_rtmp_r64(uint64_t n)\n{\n    return (uint64_t) ngx_rtmp_r32((uint32_t) n) << 32 |\n                      ngx_rtmp_r32((uint32_t) (n >> 32));\n}\n\n\n/* Receiving messages */\nngx_int_t ngx_rtmp_receive_message(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in);\nngx_int_t ngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in);\nngx_int_t ngx_rtmp_user_message_handler(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in);\nngx_int_t ngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in);\nngx_int_t ngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in);\nngx_int_t ngx_rtmp_amf_shared_object_handler(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in);\n\n\n/* Shared output buffers */\n\n/* Store refcount in negative bytes of shared buffer */\n\n#define NGX_RTMP_REFCOUNT_TYPE              uint32_t\n#define NGX_RTMP_REFCOUNT_BYTES             sizeof(NGX_RTMP_REFCOUNT_TYPE)\n\n#define ngx_rtmp_ref(b)                     \\\n    *((NGX_RTMP_REFCOUNT_TYPE*)(b) - 1)\n\n#define ngx_rtmp_ref_set(b, v)              \\\n    ngx_rtmp_ref(b) = v\n\n#define ngx_rtmp_ref_get(b)                 \\\n    ++ngx_rtmp_ref(b)\n\n#define ngx_rtmp_ref_put(b)                 \\\n    --ngx_rtmp_ref(b)\n\nngx_chain_t * ngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf);\nvoid ngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf,\n        ngx_chain_t *in);\nngx_chain_t * ngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,\n        ngx_chain_t *head, ngx_chain_t *in);\n\n#define ngx_rtmp_acquire_shared_chain(in)   \\\n    ngx_rtmp_ref_get(in);                   \\\n\n\n/* Sending messages */\nvoid ngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_rtmp_header_t *lh, ngx_chain_t *out);\nngx_int_t ngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_chain_t *out,\n        ngx_uint_t priority);\n\n/* Note on priorities:\n * the bigger value the lower the priority.\n * priority=0 is the highest */\n\n\n#define NGX_RTMP_LIMIT_SOFT         0\n#define NGX_RTMP_LIMIT_HARD         1\n#define NGX_RTMP_LIMIT_DYNAMIC      2\n\n/* Protocol control messages */\nngx_chain_t * ngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s,\n        uint32_t chunk_size);\nngx_chain_t * ngx_rtmp_create_abort(ngx_rtmp_session_t *s,\n        uint32_t csid);\nngx_chain_t * ngx_rtmp_create_ack(ngx_rtmp_session_t *s,\n        uint32_t seq);\nngx_chain_t * ngx_rtmp_create_ack_size(ngx_rtmp_session_t *s,\n        uint32_t ack_size);\nngx_chain_t * ngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s,\n        uint32_t ack_size, uint8_t limit_type);\n\nngx_int_t ngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s,\n        uint32_t chunk_size);\nngx_int_t ngx_rtmp_send_abort(ngx_rtmp_session_t *s,\n        uint32_t csid);\nngx_int_t ngx_rtmp_send_ack(ngx_rtmp_session_t *s,\n        uint32_t seq);\nngx_int_t ngx_rtmp_send_ack_size(ngx_rtmp_session_t *s,\n        uint32_t ack_size);\nngx_int_t ngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s,\n        uint32_t ack_size, uint8_t limit_type);\n\n/* User control messages */\nngx_chain_t * ngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s,\n        uint32_t msid);\nngx_chain_t * ngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s,\n        uint32_t msid);\nngx_chain_t * ngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s,\n        uint32_t msid);\nngx_chain_t * ngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s,\n        uint32_t msid, uint32_t buflen_msec);\nngx_chain_t * ngx_rtmp_create_recorded(ngx_rtmp_session_t *s,\n        uint32_t msid);\nngx_chain_t * ngx_rtmp_create_ping_request(ngx_rtmp_session_t *s,\n        uint32_t timestamp);\nngx_chain_t * ngx_rtmp_create_ping_response(ngx_rtmp_session_t *s,\n        uint32_t timestamp);\n\nngx_int_t ngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s,\n        uint32_t msid);\nngx_int_t ngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s,\n        uint32_t msid);\nngx_int_t ngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s,\n        uint32_t msid);\nngx_int_t ngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s,\n        uint32_t msid, uint32_t buflen_msec);\nngx_int_t ngx_rtmp_send_recorded(ngx_rtmp_session_t *s,\n        uint32_t msid);\nngx_int_t ngx_rtmp_send_ping_request(ngx_rtmp_session_t *s,\n        uint32_t timestamp);\nngx_int_t ngx_rtmp_send_ping_response(ngx_rtmp_session_t *s,\n        uint32_t timestamp);\n\n/* AMF sender/receiver */\nngx_int_t ngx_rtmp_append_amf(ngx_rtmp_session_t *s,\n        ngx_chain_t **first, ngx_chain_t **last,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts);\nngx_int_t ngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts);\n\nngx_chain_t * ngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts);\nngx_int_t ngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts);\n\n/* AMF status sender */\nngx_chain_t * ngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code,\n        char* level, char *desc);\nngx_chain_t * ngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code,\n        char* level, ngx_uint_t duration, ngx_uint_t bytes);\nngx_chain_t * ngx_rtmp_create_sample_access(ngx_rtmp_session_t *s);\n\nngx_int_t ngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code,\n        char* level, char *desc);\nngx_int_t ngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code,\n        char* level, ngx_uint_t duration, ngx_uint_t bytes);\nngx_int_t ngx_rtmp_send_sample_access(ngx_rtmp_session_t *s);\n\n\n/* Frame types */\n#define NGX_RTMP_VIDEO_KEY_FRAME            1\n#define NGX_RTMP_VIDEO_INTER_FRAME          2\n#define NGX_RTMP_VIDEO_DISPOSABLE_FRAME     3\n\n\nstatic ngx_inline ngx_int_t\nngx_rtmp_get_video_frame_type(ngx_chain_t *in)\n{\n    return (in->buf->pos[0] & 0xf0) >> 4;\n}\n\n\nstatic ngx_inline ngx_int_t\nngx_rtmp_is_codec_header(ngx_chain_t *in)\n{\n    return in->buf->pos + 1 < in->buf->last && in->buf->pos[1] == 0;\n}\n\n\nextern ngx_rtmp_bandwidth_t                 ngx_rtmp_bw_out;\nextern ngx_rtmp_bandwidth_t                 ngx_rtmp_bw_in;\n\n\nextern ngx_uint_t                           ngx_rtmp_naccepted;\n#if (nginx_version >= 1007011)\nextern ngx_queue_t                          ngx_rtmp_init_queue;\n#elif (nginx_version >= 1007005)\nextern ngx_thread_volatile ngx_queue_t      ngx_rtmp_init_queue;\n#else\nextern ngx_thread_volatile ngx_event_t     *ngx_rtmp_init_queue;\n#endif\n\nextern ngx_uint_t                           ngx_rtmp_max_module;\nextern ngx_module_t                         ngx_rtmp_core_module;\n\n\n#endif /* _NGX_RTMP_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_access_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n\n\nstatic ngx_rtmp_publish_pt          next_publish;\nstatic ngx_rtmp_play_pt             next_play;\n\n\n#define NGX_RTMP_ACCESS_PUBLISH     0x01\n#define NGX_RTMP_ACCESS_PLAY        0x02\n\n\nstatic char * ngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic ngx_int_t ngx_rtmp_access_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_access_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_access_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\n\n\ntypedef struct {\n    in_addr_t               mask;\n    in_addr_t               addr;\n    ngx_uint_t              deny;\n    ngx_uint_t              flags;\n} ngx_rtmp_access_rule_t;\n\n\n#if (NGX_HAVE_INET6)\n\ntypedef struct {\n    struct in6_addr         addr;\n    struct in6_addr         mask;\n    ngx_uint_t              deny;\n    ngx_uint_t              flags;\n} ngx_rtmp_access_rule6_t;\n\n#endif\n\n\ntypedef struct {\n    ngx_array_t             rules;     /* array of ngx_rtmp_access_rule_t */\n#if (NGX_HAVE_INET6)\n    ngx_array_t             rules6;    /* array of ngx_rtmp_access_rule6_t */\n#endif\n} ngx_rtmp_access_app_conf_t;\n\n\nstatic ngx_command_t  ngx_rtmp_access_commands[] = {\n\n    { ngx_string(\"allow\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12,\n      ngx_rtmp_access_rule,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"deny\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12,\n      ngx_rtmp_access_rule,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_access_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_access_postconfiguration,      /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    ngx_rtmp_access_create_app_conf,        /* create app configuration */\n    ngx_rtmp_access_merge_app_conf,         /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_access_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_access_module_ctx,            /* module context */\n    ngx_rtmp_access_commands,               /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void *\nngx_rtmp_access_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_access_app_conf_t      *aacf;\n\n    aacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_access_app_conf_t));\n    if (aacf == NULL) {\n        return NULL;\n    }\n\n    if (ngx_array_init(&aacf->rules, cf->pool, 1,\n                       sizeof(ngx_rtmp_access_rule_t))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n#if (NGX_HAVE_INET6)\n    if (ngx_array_init(&aacf->rules6, cf->pool, 1,\n                       sizeof(ngx_rtmp_access_rule6_t))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n#endif\n\n    return aacf;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_access_merge_rules(ngx_array_t *prev, ngx_array_t *rules)\n{\n    void   *p;\n\n    if (prev->nelts == 0) {\n        return NGX_OK;\n    }\n\n    if (rules->nelts == 0) {\n        *rules = *prev;\n        return NGX_OK;\n    }\n\n    p = ngx_array_push_n(rules, prev->nelts);\n    if (p == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_memcpy(p, prev->elts, prev->size * prev->nelts);\n\n    return NGX_OK;\n}\n\n\nstatic char *\nngx_rtmp_access_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_access_app_conf_t *prev = parent;\n    ngx_rtmp_access_app_conf_t *conf = child;\n\n    if (ngx_rtmp_access_merge_rules(&prev->rules, &conf->rules) != NGX_OK) {\n        return NGX_CONF_ERROR;\n    }\n\n#if (NGX_HAVE_INET6)\n    if (ngx_rtmp_access_merge_rules(&prev->rules6, &conf->rules6) != NGX_OK) {\n        return NGX_CONF_ERROR;\n    }\n#endif\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_access_found(ngx_rtmp_session_t *s, ngx_uint_t deny)\n{\n    if (deny) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"access forbidden by rule\");\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_access_inet(ngx_rtmp_session_t *s, in_addr_t addr, ngx_uint_t flag)\n{\n    ngx_uint_t                  i;\n    ngx_rtmp_access_rule_t     *rule;\n    ngx_rtmp_access_app_conf_t *ascf;\n\n    ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);\n\n    rule = ascf->rules.elts;\n    for (i = 0; i < ascf->rules.nelts; i++) {\n\n        ngx_log_debug3(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,\n                       \"access: %08XD %08XD %08XD\",\n                       addr, rule[i].mask, rule[i].addr);\n\n        if ((addr & rule[i].mask) == rule[i].addr && (flag & rule[i].flags)) {\n            return ngx_rtmp_access_found(s, rule[i].deny);\n        }\n    }\n\n    return NGX_OK;\n}\n\n\n#if (NGX_HAVE_INET6)\n\nstatic ngx_int_t\nngx_rtmp_access_inet6(ngx_rtmp_session_t *s, u_char *p, ngx_uint_t flag)\n{\n    ngx_uint_t                  n;\n    ngx_uint_t                  i;\n    ngx_rtmp_access_rule6_t    *rule6;\n    ngx_rtmp_access_app_conf_t *ascf;\n\n    ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);\n\n    rule6 = ascf->rules6.elts;\n    for (i = 0; i < ascf->rules6.nelts; i++) {\n\n#if (NGX_DEBUG)\n        {\n        size_t  cl, ml, al;\n        u_char  ct[NGX_INET6_ADDRSTRLEN];\n        u_char  mt[NGX_INET6_ADDRSTRLEN];\n        u_char  at[NGX_INET6_ADDRSTRLEN];\n\n        cl = ngx_inet6_ntop(p, ct, NGX_INET6_ADDRSTRLEN);\n        ml = ngx_inet6_ntop(rule6[i].mask.s6_addr, mt, NGX_INET6_ADDRSTRLEN);\n        al = ngx_inet6_ntop(rule6[i].addr.s6_addr, at, NGX_INET6_ADDRSTRLEN);\n\n        ngx_log_debug6(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,\n                       \"access: %*s %*s %*s\", cl, ct, ml, mt, al, at);\n        }\n#endif\n\n        for (n = 0; n < 16; n++) {\n            if ((p[n] & rule6[i].mask.s6_addr[n]) != rule6[i].addr.s6_addr[n]) {\n                goto next;\n            }\n        }\n\n        if (flag & rule6[i].flags) {\n            return ngx_rtmp_access_found(s, rule6[i].deny);\n        }\n\n    next:\n        continue;\n    }\n\n    return NGX_OK;\n}\n\n#endif\n\n\nstatic ngx_int_t\nngx_rtmp_access(ngx_rtmp_session_t *s, ngx_uint_t flag)\n{\n    struct sockaddr_in             *sin;\n    ngx_rtmp_access_app_conf_t     *ascf;\n#if (NGX_HAVE_INET6)\n    u_char                         *p;\n    in_addr_t                       addr;\n    struct sockaddr_in6            *sin6;\n#endif\n\n    ascf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_access_module);\n    if (ascf == NULL) {\n        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,\n                       \"access: NULL app conf\");\n        return NGX_ERROR;\n    }\n\n    /* relay etc */\n    if (s->connection->sockaddr == NULL) {\n        return NGX_OK;\n    }\n\n    switch (s->connection->sockaddr->sa_family) {\n\n    case AF_INET:\n        sin = (struct sockaddr_in *) s->connection->sockaddr;\n        return ngx_rtmp_access_inet(s, sin->sin_addr.s_addr, flag);\n\n#if (NGX_HAVE_INET6)\n\n    case AF_INET6:\n        sin6 = (struct sockaddr_in6 *) s->connection->sockaddr;\n        p = sin6->sin6_addr.s6_addr;\n\n        if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {\n            addr  = p[12] << 24;\n            addr += p[13] << 16;\n            addr += p[14] << 8;\n            addr += p[15];\n            return ngx_rtmp_access_inet(s, htonl(addr), flag);\n        }\n\n        return ngx_rtmp_access_inet6(s, p, flag);\n\n#endif\n    }\n\n    return NGX_OK;\n}\n\n\nstatic char *\nngx_rtmp_access_rule(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_access_app_conf_t         *ascf = conf;\n\n    ngx_int_t                           rc;\n    ngx_uint_t                          all;\n    ngx_str_t                          *value;\n    ngx_cidr_t                          cidr;\n    ngx_rtmp_access_rule_t             *rule;\n#if (NGX_HAVE_INET6)\n    ngx_rtmp_access_rule6_t            *rule6;\n#endif\n    size_t                              n;\n    ngx_uint_t                          flags;\n\n    ngx_memzero(&cidr, sizeof(ngx_cidr_t));\n\n    value = cf->args->elts;\n\n    n = 1;\n    flags = 0;\n\n    if (cf->args->nelts == 2) {\n\n        flags = NGX_RTMP_ACCESS_PUBLISH | NGX_RTMP_ACCESS_PLAY;\n\n    } else {\n\n        for(; n < cf->args->nelts - 1; ++n) {\n\n            if (value[n].len == sizeof(\"publish\") - 1 &&\n                ngx_strcmp(value[1].data, \"publish\") == 0)\n            {\n                flags |= NGX_RTMP_ACCESS_PUBLISH;\n                continue;\n\n            }\n\n            if (value[n].len == sizeof(\"play\") - 1 &&\n                ngx_strcmp(value[1].data, \"play\") == 0)\n            {\n                flags |= NGX_RTMP_ACCESS_PLAY;\n                continue;\n\n            }\n\n            ngx_log_error(NGX_LOG_ERR, cf->log, 0,\n                          \"unexpected access specified: '%V'\", &value[n]);\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    all = (value[n].len == 3 && ngx_strcmp(value[n].data, \"all\") == 0);\n\n    if (!all) {\n\n        rc = ngx_ptocidr(&value[n], &cidr);\n\n        if (rc == NGX_ERROR) {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                               \"invalid parameter \\\"%V\\\"\", &value[1]);\n            return NGX_CONF_ERROR;\n        }\n\n        if (rc == NGX_DONE) {\n            ngx_conf_log_error(NGX_LOG_WARN, cf, 0,\n                               \"low address bits of %V are meaningless\",\n                               &value[1]);\n        }\n    }\n\n    switch (cidr.family) {\n\n#if (NGX_HAVE_INET6)\n    case AF_INET6:\n    case 0: /* all */\n\n        rule6 = ngx_array_push(&ascf->rules6);\n        if (rule6 == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        rule6->mask = cidr.u.in6.mask;\n        rule6->addr = cidr.u.in6.addr;\n        rule6->deny = (value[0].data[0] == 'd') ? 1 : 0;\n        rule6->flags = flags;\n\n        if (!all) {\n            break;\n        }\n\n#endif\n        /* fall through */\n\n    default: /* AF_INET */\n\n        rule = ngx_array_push(&ascf->rules);\n        if (rule == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        rule->mask = cidr.u.in.mask;\n        rule->addr = cidr.u.in.addr;\n        rule->deny = (value[0].data[0] == 'd') ? 1 : 0;\n        rule->flags = flags;\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_access_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PUBLISH) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_access_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    if (ngx_rtmp_access(s, NGX_RTMP_ACCESS_PLAY) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    return next_play(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_access_postconfiguration(ngx_conf_t *cf)\n{\n    /* chain handlers */\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_access_publish;\n\n    next_play = ngx_rtmp_play;\n    ngx_rtmp_play = ngx_rtmp_access_play;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_amf.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_amf.h\"\n#include \"ngx_rtmp.h\"\n#include <string.h>\n\n\nstatic ngx_inline void*\nngx_rtmp_amf_reverse_copy(void *dst, void* src, size_t len)\n{\n    size_t  k;\n\n    if (dst == NULL || src == NULL) {\n        return NULL;\n    }\n\n    for(k = 0; k < len; ++k) {\n        ((u_char*)dst)[k] = ((u_char*)src)[len - 1 - k];\n    }\n\n    return dst;\n}\n\n#define NGX_RTMP_AMF_DEBUG_SIZE 16\n\n#ifdef NGX_DEBUG\nstatic void\nngx_rtmp_amf_debug(const char* op, ngx_log_t *log, u_char *p, size_t n)\n{\n    u_char          hstr[3 * NGX_RTMP_AMF_DEBUG_SIZE + 1];\n    u_char          str[NGX_RTMP_AMF_DEBUG_SIZE + 1];\n    u_char         *hp, *sp;\n    static u_char   hex[] = \"0123456789ABCDEF\";\n    size_t          i;\n\n    hp = hstr;\n    sp = str;\n\n    for(i = 0; i < n && i < NGX_RTMP_AMF_DEBUG_SIZE; ++i) {\n        *hp++ = ' ';\n        if (p) {\n            *hp++ = hex[(*p & 0xf0) >> 4];\n            *hp++ = hex[*p & 0x0f];\n            *sp++ = (*p >= 0x20 && *p <= 0x7e) ?\n                *p : (u_char)'?';\n            ++p;\n        } else {\n            *hp++ = 'X';\n            *hp++ = 'X';\n            *sp++ = '?';\n        }\n    }\n    *hp = *sp = '\\0';\n\n    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, log, 0,\n            \"AMF %s (%d)%s '%s'\", op, n, hstr, str);\n}\n#endif\n\nstatic ngx_int_t\nngx_rtmp_amf_get(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n)\n{\n    size_t          size;\n    ngx_chain_t    *l;\n    size_t          offset;\n    u_char         *pos, *last;\n#ifdef NGX_DEBUG\n    void           *op = p;\n    size_t          on = n;\n#endif\n\n    if (!n)\n        return NGX_OK;\n\n    for(l = ctx->link, offset = ctx->offset; l; l = l->next, offset = 0) {\n\n        pos  = l->buf->pos + offset;\n        last = l->buf->last;\n\n        if (last >= pos + n) {\n            if (p) {\n                p = ngx_cpymem(p, pos, n);\n            }\n            ctx->offset = offset + n;\n            ctx->link = l;\n\n#ifdef NGX_DEBUG\n            ngx_rtmp_amf_debug(\"read\", ctx->log, (u_char*)op, on);\n#endif\n\n            return NGX_OK;\n        }\n\n        size = last - pos;\n\n        if (p) {\n            p = ngx_cpymem(p, pos, size);\n        }\n\n        n -= size;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, ctx->log, 0,\n            \"AMF read eof (%d)\", n);\n\n    return NGX_DONE;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_amf_put(ngx_rtmp_amf_ctx_t *ctx, void *p, size_t n)\n{\n    ngx_buf_t       *b;\n    size_t          size;\n    ngx_chain_t    *l, *ln;\n\n#ifdef NGX_DEBUG\n    ngx_rtmp_amf_debug(\"write\", ctx->log, (u_char*)p, n);\n#endif\n\n    l = ctx->link;\n\n    if (ctx->link && ctx->first == NULL) {\n        ctx->first = ctx->link;\n    }\n\n    while(n) {\n        b = l ? l->buf : NULL;\n\n        if (b == NULL || b->last == b->end) {\n\n            ln = ctx->alloc(ctx->arg);\n            if (ln == NULL) {\n                return NGX_ERROR;\n            }\n\n            if (ctx->first == NULL) {\n                ctx->first = ln;\n            }\n\n            if (l) {\n                l->next = ln;\n            }\n\n            l = ln;\n            ctx->link = l;\n            b = l->buf;\n        }\n\n        size = b->end - b->last;\n\n        if (size >= n) {\n            b->last = ngx_cpymem(b->last, p, n);\n            return NGX_OK;\n        }\n\n        b->last = ngx_cpymem(b->last, p, size);\n        p = (u_char*)p + size;\n        n -= size;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_amf_read_object(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,\n        size_t nelts)\n{\n    uint8_t                 type;\n    uint16_t                len;\n    size_t                  n, namelen, maxlen;\n    ngx_int_t               rc;\n    u_char                  buf[2];\n\n    maxlen = 0;\n    for(n = 0; n < nelts; ++n) {\n        namelen = elts[n].name.len;\n        if (namelen > maxlen)\n            maxlen = namelen;\n    }\n\n    for( ;; ) {\n\n#if !(NGX_WIN32)\n        char    name[maxlen];\n#else\n        char    name[1024];\n        if (maxlen > sizeof(name)) {\n            return NGX_ERROR;\n        }\n#endif\n        /* read key */\n        switch (ngx_rtmp_amf_get(ctx, buf, 2)) {\n        case NGX_DONE:\n            /* Envivio sends unfinalized arrays */\n            return NGX_OK;\n        case NGX_OK:\n            break;\n        default:\n            return NGX_ERROR;\n        }\n\n        ngx_rtmp_amf_reverse_copy(&len, buf, 2);\n\n        if (!len)\n            break;\n\n        if (len <= maxlen) {\n            rc = ngx_rtmp_amf_get(ctx, name, len);\n\n        } else {\n            rc = ngx_rtmp_amf_get(ctx, name, maxlen);\n            if (rc != NGX_OK)\n                return NGX_ERROR;\n            rc = ngx_rtmp_amf_get(ctx, 0, len - maxlen);\n        }\n\n        if (rc != NGX_OK)\n            return NGX_ERROR;\n\n        /* TODO: if we require array to be sorted on name\n         * then we could be able to use binary search */\n        for(n = 0; n < nelts\n                && (len != elts[n].name.len\n                    || ngx_strncmp(name, elts[n].name.data, len));\n                ++n);\n\n        if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK)\n            return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_amf_get(ctx, &type, 1) != NGX_OK\n        || type != NGX_RTMP_AMF_END)\n    {\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_amf_read_array(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,\n        size_t nelts)\n{\n    uint32_t                len;\n    size_t                  n;\n    u_char                  buf[4];\n\n    /* read length */\n    if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK)\n        return NGX_ERROR;\n\n    ngx_rtmp_amf_reverse_copy(&len, buf, 4);\n\n    for (n = 0; n < len; ++n) {\n        if (ngx_rtmp_amf_read(ctx, n < nelts ? &elts[n] : NULL, 1) != NGX_OK)\n            return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_amf_read_variant(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,\n        size_t nelts)\n{\n    uint8_t                 type;\n    ngx_int_t               rc;\n    size_t                  n;\n    ngx_rtmp_amf_elt_t      elt;\n\n    rc = ngx_rtmp_amf_get(ctx, &type, 1);\n    if (rc != NGX_OK) {\n        return rc;\n    }\n\n    ngx_memzero(&elt, sizeof(elt));\n    for (n = 0; n < nelts; ++n, ++elts) {\n        if (type == elts->type) {\n            elt.data = elts->data;\n            elt.len  = elts->len;\n        }\n    }\n\n    elt.type = type | NGX_RTMP_AMF_TYPELESS;\n\n    return ngx_rtmp_amf_read(ctx, &elt, 1);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_amf_is_compatible_type(uint8_t t1, uint8_t t2)\n{\n    return t1 == t2\n        || (t1 == NGX_RTMP_AMF_OBJECT && t2 == NGX_RTMP_AMF_MIXED_ARRAY)\n        || (t2 == NGX_RTMP_AMF_OBJECT && t1 == NGX_RTMP_AMF_MIXED_ARRAY);\n}\n\n\nngx_int_t\nngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_amf_elt_t *elts,\n        size_t nelts)\n{\n    void                       *data;\n    ngx_int_t                   type;\n    uint8_t                     type8;\n    size_t                      n;\n    uint16_t                    len;\n    ngx_int_t                   rc;\n    u_char                      buf[8];\n    uint32_t                    max_index;\n\n    for(n = 0; n < nelts; ++n) {\n\n        if (elts && elts->type & NGX_RTMP_AMF_TYPELESS) {\n            type = elts->type & ~NGX_RTMP_AMF_TYPELESS;\n            data = elts->data;\n\n        } else {\n            switch (ngx_rtmp_amf_get(ctx, &type8, 1)) {\n                case NGX_DONE:\n                    if (elts && elts->type & NGX_RTMP_AMF_OPTIONAL) {\n                        return NGX_OK;\n                    }\n                    /* fall through */\n                case NGX_ERROR:\n                    return NGX_ERROR;\n            }\n            type = type8;\n            data = (elts &&\n                    ngx_rtmp_amf_is_compatible_type(\n                                 (uint8_t) (elts->type & 0xff), (uint8_t) type))\n                ? elts->data\n                : NULL;\n\n            if (elts && (elts->type & NGX_RTMP_AMF_CONTEXT)) {\n                if (data) {\n                    *(ngx_rtmp_amf_ctx_t *) data = *ctx;\n                }\n                data = NULL;\n            }\n        }\n\n        switch (type) {\n            case NGX_RTMP_AMF_NUMBER:\n                if (ngx_rtmp_amf_get(ctx, buf, 8) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                ngx_rtmp_amf_reverse_copy(data, buf, 8);\n                break;\n\n            case NGX_RTMP_AMF_BOOLEAN:\n                if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_STRING:\n                if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                ngx_rtmp_amf_reverse_copy(&len, buf, 2);\n\n                if (data == NULL) {\n                    rc = ngx_rtmp_amf_get(ctx, data, len);\n\n                } else if (elts->len <= len) {\n                    rc = ngx_rtmp_amf_get(ctx, data, elts->len - 1);\n                    if (rc != NGX_OK)\n                        return NGX_ERROR;\n                    ((char*)data)[elts->len - 1] = 0;\n                    rc = ngx_rtmp_amf_get(ctx, NULL, len - elts->len + 1);\n\n                } else {\n                    rc = ngx_rtmp_amf_get(ctx, data, len);\n                    ((char*)data)[len] = 0;\n                }\n\n                if (rc != NGX_OK) {\n                    return NGX_ERROR;\n                }\n\n                break;\n\n            case NGX_RTMP_AMF_NULL:\n            case NGX_RTMP_AMF_ARRAY_NULL:\n                break;\n\n            case NGX_RTMP_AMF_MIXED_ARRAY:\n                if (ngx_rtmp_amf_get(ctx, &max_index, 4) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                /* fall through */\n\n            case NGX_RTMP_AMF_OBJECT:\n                if (ngx_rtmp_amf_read_object(ctx, data,\n                    data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0\n                    ) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_ARRAY:\n                if (ngx_rtmp_amf_read_array(ctx, data,\n                    data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0\n                    ) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_VARIANT_:\n                if (ngx_rtmp_amf_read_variant(ctx, data,\n                    data && elts ? elts->len / sizeof(ngx_rtmp_amf_elt_t) : 0\n                    ) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_INT8:\n                if (ngx_rtmp_amf_get(ctx, data, 1) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_INT16:\n                if (ngx_rtmp_amf_get(ctx, buf, 2) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                ngx_rtmp_amf_reverse_copy(data, buf, 2);\n                break;\n\n            case NGX_RTMP_AMF_INT32:\n                if (ngx_rtmp_amf_get(ctx, buf, 4) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                ngx_rtmp_amf_reverse_copy(data, buf, 4);\n                break;\n\n            case NGX_RTMP_AMF_END:\n                return NGX_OK;\n\n            default:\n                return NGX_ERROR;\n        }\n\n        if (elts) {\n            ++elts;\n        }\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_amf_write_object(ngx_rtmp_amf_ctx_t *ctx,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts)\n{\n    uint16_t                len;\n    size_t                  n;\n    u_char                  buf[2];\n\n    for(n = 0; n < nelts; ++n) {\n\n        len = (uint16_t) elts[n].name.len;\n\n        if (ngx_rtmp_amf_put(ctx,\n                    ngx_rtmp_amf_reverse_copy(buf,\n                        &len, 2), 2) != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n\n        if (ngx_rtmp_amf_put(ctx, elts[n].name.data, len) != NGX_OK) {\n            return NGX_ERROR;\n        }\n\n        if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) {\n            return NGX_ERROR;\n        }\n    }\n\n    if (ngx_rtmp_amf_put(ctx, \"\\0\\0\", 2) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_amf_write_array(ngx_rtmp_amf_ctx_t *ctx,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts)\n{\n    uint32_t                len;\n    size_t                  n;\n    u_char                  buf[4];\n\n    len = nelts;\n    if (ngx_rtmp_amf_put(ctx,\n                ngx_rtmp_amf_reverse_copy(buf,\n                    &len, 4), 4) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    for(n = 0; n < nelts; ++n) {\n        if (ngx_rtmp_amf_write(ctx, &elts[n], 1) != NGX_OK) {\n            return NGX_ERROR;\n        }\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts)\n{\n    size_t                  n;\n    ngx_int_t               type;\n    uint8_t                 type8;\n    void                   *data;\n    uint16_t                len;\n    uint32_t                max_index;\n    u_char                  buf[8];\n\n    for(n = 0; n < nelts; ++n) {\n\n        type = elts[n].type;\n        data = elts[n].data;\n        len  = (uint16_t) elts[n].len;\n\n        if (type & NGX_RTMP_AMF_TYPELESS) {\n            type &= ~NGX_RTMP_AMF_TYPELESS;\n        } else {\n            type8 = (uint8_t)type;\n            if (ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK)\n                return NGX_ERROR;\n        }\n\n        switch(type) {\n            case NGX_RTMP_AMF_NUMBER:\n                if (ngx_rtmp_amf_put(ctx,\n                            ngx_rtmp_amf_reverse_copy(buf,\n                                data, 8), 8) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_BOOLEAN:\n                if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_STRING:\n                if (len == 0 && data) {\n                    len = (uint16_t) ngx_strlen((u_char*) data);\n                }\n\n                if (ngx_rtmp_amf_put(ctx,\n                            ngx_rtmp_amf_reverse_copy(buf,\n                                &len, 2), 2) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n\n                if (ngx_rtmp_amf_put(ctx, data, len) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_NULL:\n            case NGX_RTMP_AMF_ARRAY_NULL:\n                break;\n\n            case NGX_RTMP_AMF_MIXED_ARRAY:\n                max_index = 0;\n                if (ngx_rtmp_amf_put(ctx, &max_index, 4) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                /* fall through */\n\n            case NGX_RTMP_AMF_OBJECT:\n                type8 = NGX_RTMP_AMF_END;\n                if (ngx_rtmp_amf_write_object(ctx, data,\n                        elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK\n                    || ngx_rtmp_amf_put(ctx, &type8, 1) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_ARRAY:\n                if (ngx_rtmp_amf_write_array(ctx, data,\n                        elts[n].len / sizeof(ngx_rtmp_amf_elt_t)) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_INT8:\n                if (ngx_rtmp_amf_put(ctx, data, 1) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_INT16:\n                if (ngx_rtmp_amf_put(ctx,\n                            ngx_rtmp_amf_reverse_copy(buf,\n                                data, 2), 2) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n                break;\n\n            case NGX_RTMP_AMF_INT32:\n                if (ngx_rtmp_amf_put(ctx,\n                            ngx_rtmp_amf_reverse_copy(buf,\n                                data, 4), 4) != NGX_OK)\n                {\n                    return NGX_ERROR;\n                }\n                break;\n\n            default:\n                return NGX_ERROR;\n        }\n    }\n\n    return NGX_OK;\n}\n\n"
  },
  {
    "path": "ngx_rtmp_amf.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_AMF_H_INCLUDED_\n#define _NGX_RTMP_AMF_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n\n\n/* basic types */\n#define NGX_RTMP_AMF_NUMBER             0x00\n#define NGX_RTMP_AMF_BOOLEAN            0x01\n#define NGX_RTMP_AMF_STRING             0x02\n#define NGX_RTMP_AMF_OBJECT             0x03\n#define NGX_RTMP_AMF_NULL               0x05\n#define NGX_RTMP_AMF_ARRAY_NULL         0x06\n#define NGX_RTMP_AMF_MIXED_ARRAY        0x08\n#define NGX_RTMP_AMF_END                0x09\n#define NGX_RTMP_AMF_ARRAY              0x0a\n\n/* extended types */\n#define NGX_RTMP_AMF_INT8               0x0100\n#define NGX_RTMP_AMF_INT16              0x0101\n#define NGX_RTMP_AMF_INT32              0x0102\n#define NGX_RTMP_AMF_VARIANT_           0x0103\n\n/* r/w flags */\n#define NGX_RTMP_AMF_OPTIONAL           0x1000\n#define NGX_RTMP_AMF_TYPELESS           0x2000\n#define NGX_RTMP_AMF_CONTEXT            0x4000\n\n#define NGX_RTMP_AMF_VARIANT            (NGX_RTMP_AMF_VARIANT_\\\n                                        |NGX_RTMP_AMF_TYPELESS)\n\n\ntypedef struct {\n    ngx_int_t                           type;\n    ngx_str_t                           name;\n    void                               *data;\n    size_t                              len;\n} ngx_rtmp_amf_elt_t;\n\n\ntypedef ngx_chain_t * (*ngx_rtmp_amf_alloc_pt)(void *arg);\n\n\ntypedef struct {\n    ngx_chain_t                        *link, *first;\n    size_t                              offset;\n    ngx_rtmp_amf_alloc_pt               alloc;\n    void                               *arg;\n    ngx_log_t                          *log;\n} ngx_rtmp_amf_ctx_t;\n\n\n/* reading AMF */\nngx_int_t ngx_rtmp_amf_read(ngx_rtmp_amf_ctx_t *ctx,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts);\n\n/* writing AMF */\nngx_int_t ngx_rtmp_amf_write(ngx_rtmp_amf_ctx_t *ctx,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts);\n\n\n#endif /* _NGX_RTMP_AMF_H_INCLUDED_ */\n\n"
  },
  {
    "path": "ngx_rtmp_auto_push_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_relay_module.h\"\n\n\nstatic ngx_rtmp_publish_pt          next_publish;\nstatic ngx_rtmp_delete_stream_pt    next_delete_stream;\n\n\nstatic ngx_int_t ngx_rtmp_auto_push_init_process(ngx_cycle_t *cycle);\nstatic void ngx_rtmp_auto_push_exit_process(ngx_cycle_t *cycle);\nstatic void * ngx_rtmp_auto_push_create_conf(ngx_cycle_t *cf);\nstatic char * ngx_rtmp_auto_push_init_conf(ngx_cycle_t *cycle, void *conf);\n#if (NGX_HAVE_UNIX_DOMAIN)\nstatic ngx_int_t ngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s,\n       ngx_rtmp_publish_t *v);\nstatic ngx_int_t ngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s,\n       ngx_rtmp_delete_stream_t *v);\n#endif\n\n\ntypedef struct ngx_rtmp_auto_push_ctx_s ngx_rtmp_auto_push_ctx_t;\n\nstruct ngx_rtmp_auto_push_ctx_s {\n    ngx_int_t                      *slots; /* NGX_MAX_PROCESSES */\n    u_char                          name[NGX_RTMP_MAX_NAME];\n    u_char                          args[NGX_RTMP_MAX_ARGS];\n    ngx_event_t                     push_evt;\n};\n\n\ntypedef struct {\n    ngx_flag_t                      auto_push;\n    ngx_str_t                       socket_dir;\n    ngx_msec_t                      push_reconnect;\n} ngx_rtmp_auto_push_conf_t;\n\n\nstatic ngx_command_t  ngx_rtmp_auto_push_commands[] = {\n\n    { ngx_string(\"rtmp_auto_push\"),\n      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      0,\n      offsetof(ngx_rtmp_auto_push_conf_t, auto_push),\n      NULL },\n\n    { ngx_string(\"rtmp_auto_push_reconnect\"),\n      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      0,\n      offsetof(ngx_rtmp_auto_push_conf_t, push_reconnect),\n      NULL },\n\n    { ngx_string(\"rtmp_socket_dir\"),\n      NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      0,\n      offsetof(ngx_rtmp_auto_push_conf_t, socket_dir),\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_core_module_t  ngx_rtmp_auto_push_module_ctx = {\n    ngx_string(\"rtmp_auto_push\"),\n    ngx_rtmp_auto_push_create_conf,         /* create conf */\n    ngx_rtmp_auto_push_init_conf            /* init conf */\n};\n\n\nngx_module_t  ngx_rtmp_auto_push_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_auto_push_module_ctx,         /* module context */\n    ngx_rtmp_auto_push_commands,            /* module directives */\n    NGX_CORE_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    ngx_rtmp_auto_push_init_process,        /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    ngx_rtmp_auto_push_exit_process,        /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_auto_push_index_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    NULL,                                   /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    NULL,                                   /* create app configuration */\n    NULL                                    /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_auto_push_index_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_auto_push_index_module_ctx,   /* module context */\n    NULL,                                   /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\n#define NGX_RTMP_AUTO_PUSH_SOCKNAME         \"nginx-rtmp\"\n\n\nstatic ngx_int_t\nngx_rtmp_auto_push_init_process(ngx_cycle_t *cycle)\n{\n#if (NGX_HAVE_UNIX_DOMAIN)\n    ngx_rtmp_auto_push_conf_t  *apcf;\n    ngx_listening_t            *ls, *lss;\n    struct sockaddr_un         *saun;\n    int                         reuseaddr;\n    ngx_socket_t                s;\n    size_t                      n;\n    ngx_file_info_t             fi;\n\n    if (ngx_process != NGX_PROCESS_WORKER) {\n        return NGX_OK;\n    }\n\n    apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx,\n                                                    ngx_rtmp_auto_push_module);\n    if (apcf->auto_push == 0) {\n        return NGX_OK;\n    }\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_auto_push_publish;\n\n    next_delete_stream = ngx_rtmp_delete_stream;\n    ngx_rtmp_delete_stream = ngx_rtmp_auto_push_delete_stream;\n\n    reuseaddr = 1;\n    s = (ngx_socket_t) -1;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, cycle->log, 0,\n            \"auto_push: creating sockets\");\n\n    /*TODO: clone all RTMP listenings? */\n    ls = cycle->listening.elts;\n    lss = NULL;\n    for (n = 0; n < cycle->listening.nelts; ++n, ++ls) {\n        if (ls->handler == ngx_rtmp_init_connection) {\n            lss = ls;\n            break;\n        }\n    }\n\n    if (lss == NULL) {\n        return NGX_OK;\n    }\n\n    ls = ngx_array_push(&cycle->listening);\n    if (ls == NULL) {\n        return NGX_ERROR;\n    }\n\n    *ls = *lss;\n\n    /* Disable unix socket client address extraction\n     * from accept call\n     * Nginx generates bad addr_text with this enabled */\n    ls->addr_ntop = 0;\n\n    ls->socklen = sizeof(struct sockaddr_un);\n    saun = ngx_pcalloc(cycle->pool, ls->socklen);\n    ls->sockaddr = (struct sockaddr *) saun;\n    if (ls->sockaddr == NULL) {\n        return NGX_ERROR;\n    }\n    saun->sun_family = AF_UNIX;\n    *ngx_snprintf((u_char *) saun->sun_path, sizeof(saun->sun_path),\n                  \"%V/\" NGX_RTMP_AUTO_PUSH_SOCKNAME \".%i\",\n                  &apcf->socket_dir, ngx_process_slot)\n        = 0;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, cycle->log, 0,\n                   \"auto_push: create socket '%s'\",\n                   saun->sun_path);\n\n    if (ngx_file_info(saun->sun_path, &fi) != ENOENT) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, cycle->log, 0,\n                       \"auto_push: delete existing socket '%s'\",\n                       saun->sun_path);\n        ngx_delete_file(saun->sun_path);\n    }\n\n    ngx_str_set(&ls->addr_text, \"worker_socket\");\n\n    s = ngx_socket(AF_UNIX, SOCK_STREAM, 0);\n    if (s == -1) {\n        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,\n                      ngx_socket_n \" worker_socket failed\");\n        return NGX_ERROR;\n    }\n\n    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,\n                   (const void *) &reuseaddr, sizeof(int))\n        == -1)\n    {\n        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,\n                \"setsockopt(SO_REUSEADDR) worker_socket failed\");\n        goto sock_error;\n    }\n\n    if (!(ngx_event_flags & NGX_USE_AIO_EVENT)) {\n        if (ngx_nonblocking(s) == -1) {\n            ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,\n                          ngx_nonblocking_n \" worker_socket failed\");\n            return NGX_ERROR;\n        }\n    }\n\n    if (bind(s, (struct sockaddr *) saun, sizeof(*saun)) == -1) {\n        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,\n                      ngx_nonblocking_n \" worker_socket bind failed\");\n        goto sock_error;\n    }\n\n    if (listen(s, NGX_LISTEN_BACKLOG) == -1) {\n        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,\n                      \"listen() to worker_socket, backlog %d failed\",\n                      NGX_LISTEN_BACKLOG);\n        goto sock_error;\n    }\n\n    ls->fd = s;\n    ls->listen = 1;\n\n    return NGX_OK;\n\nsock_error:\n    if (s != (ngx_socket_t) -1 && ngx_close_socket(s) == -1) {\n        ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno,\n                ngx_close_socket_n \" worker_socket failed\");\n    }\n    ngx_delete_file(saun->sun_path);\n\n    return NGX_ERROR;\n\n#else  /* NGX_HAVE_UNIX_DOMAIN */\n\n    return NGX_OK;\n\n#endif /* NGX_HAVE_UNIX_DOMAIN */\n}\n\n\nstatic void\nngx_rtmp_auto_push_exit_process(ngx_cycle_t *cycle)\n{\n#if (NGX_HAVE_UNIX_DOMAIN)\n    ngx_rtmp_auto_push_conf_t  *apcf;\n    u_char                      path[NGX_MAX_PATH];\n\n    apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(cycle->conf_ctx,\n                                                    ngx_rtmp_auto_push_module);\n    if (apcf->auto_push == 0) {\n        return;\n    }\n    *ngx_snprintf(path, sizeof(path),\n                  \"%V/\" NGX_RTMP_AUTO_PUSH_SOCKNAME \".%i\",\n                  &apcf->socket_dir, ngx_process_slot)\n         = 0;\n\n    ngx_delete_file(path);\n\n#endif\n}\n\n\nstatic void *\nngx_rtmp_auto_push_create_conf(ngx_cycle_t *cycle)\n{\n    ngx_rtmp_auto_push_conf_t       *apcf;\n\n    apcf = ngx_pcalloc(cycle->pool, sizeof(ngx_rtmp_auto_push_conf_t));\n    if (apcf == NULL) {\n        return NULL;\n    }\n\n    apcf->auto_push = NGX_CONF_UNSET;\n    apcf->push_reconnect = NGX_CONF_UNSET_MSEC;\n\n    return apcf;\n}\n\n\nstatic char *\nngx_rtmp_auto_push_init_conf(ngx_cycle_t *cycle, void *conf)\n{\n    ngx_rtmp_auto_push_conf_t      *apcf = conf;\n\n    ngx_conf_init_value(apcf->auto_push, 0);\n    ngx_conf_init_msec_value(apcf->push_reconnect, 100);\n\n    if (apcf->socket_dir.len == 0) {\n        ngx_str_set(&apcf->socket_dir, \"/tmp\");\n    }\n\n    return NGX_CONF_OK;\n}\n\n\n#if (NGX_HAVE_UNIX_DOMAIN)\nstatic void\nngx_rtmp_auto_push_reconnect(ngx_event_t *ev)\n{\n    ngx_rtmp_session_t             *s = ev->data;\n\n    ngx_rtmp_auto_push_conf_t      *apcf;\n    ngx_rtmp_auto_push_ctx_t       *ctx;\n    ngx_int_t                      *slot;\n    ngx_int_t                       n;\n    ngx_rtmp_relay_target_t         at;\n    u_char                          path[sizeof(\"unix:\") + NGX_MAX_PATH];\n    u_char                          flash_ver[sizeof(\"APSH ,\") +\n                                              NGX_INT_T_LEN * 2];\n    u_char                          play_path[NGX_RTMP_MAX_NAME];\n    ngx_str_t                       name;\n    u_char                         *p;\n    ngx_str_t                      *u;\n    ngx_pid_t                       pid;\n    ngx_int_t                       npushed;\n    ngx_core_conf_t                *ccf;\n    ngx_file_info_t                 fi;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"auto_push: reconnect\");\n\n    apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,\n                                                    ngx_rtmp_auto_push_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);\n    if (ctx == NULL) {\n        return;\n    }\n\n    name.data = ctx->name;\n    name.len = ngx_strlen(name.data);\n\n    ngx_memzero(&at, sizeof(at));\n    ngx_str_set(&at.page_url, \"nginx-auto-push\");\n    at.tag = &ngx_rtmp_auto_push_module;\n\n    if (ctx->args[0]) {\n        at.play_path.data = play_path;\n        at.play_path.len = ngx_snprintf(play_path, sizeof(play_path),\n                                        \"%s?%s\", ctx->name, ctx->args) -\n                           play_path;\n    }\n\n    slot = ctx->slots;\n    npushed = 0;\n\n    for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {\n        if (n == ngx_process_slot) {\n            continue;\n        }\n\n        pid = ngx_processes[n].pid;\n        if (pid == 0 || pid == NGX_INVALID_PID) {\n            continue;\n        }\n\n        if (*slot) {\n            npushed++;\n            continue;\n        }\n\n        at.data = &ngx_processes[n];\n\n        ngx_memzero(&at.url, sizeof(at.url));\n        u = &at.url.url;\n        p = ngx_snprintf(path, sizeof(path) - 1,\n                         \"unix:%V/\" NGX_RTMP_AUTO_PUSH_SOCKNAME \".%i\",\n                         &apcf->socket_dir, n);\n        *p = 0;\n\n        if (ngx_file_info(path + sizeof(\"unix:\") - 1, &fi) != NGX_OK) {\n            ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"auto_push: \" ngx_file_info_n \" failed: \"\n                           \"slot=%i pid=%P socket='%s'\" \"url='%V' name='%s'\",\n                           n, pid, path, u, ctx->name);\n            continue;\n        }\n\n        u->data = path;\n        u->len = p - path;\n        if (ngx_parse_url(s->connection->pool, &at.url) != NGX_OK) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"auto_push: auto-push parse_url failed \"\n                          \"url='%V' name='%s'\",\n                          u, ctx->name);\n            continue;\n        }\n\n        p = ngx_snprintf(flash_ver, sizeof(flash_ver) - 1, \"APSH %i,%i\",\n                         (ngx_int_t) ngx_process_slot, (ngx_int_t) ngx_pid);\n        at.flash_ver.data = flash_ver;\n        at.flash_ver.len = p - flash_ver;\n\n        ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"auto_push: connect slot=%i pid=%P socket='%s' name='%s'\",\n                       n, pid, path, ctx->name);\n\n        if (ngx_rtmp_relay_push(s, &name, &at) == NGX_OK) {\n            *slot = 1;\n            npushed++;\n            continue;\n        }\n\n        ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                      \"auto_push: connect failed: slot=%i pid=%P socket='%s'\"\n                      \"url='%V' name='%s'\",\n                      n, pid, path, u, ctx->name);\n    }\n\n    ccf = (ngx_core_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,\n                                           ngx_core_module);\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"auto_push: pushed=%i total=%i failed=%i\",\n                   npushed, ccf->worker_processes,\n                   ccf->worker_processes - 1 - npushed);\n\n    if (ccf->worker_processes == npushed + 1) {\n        return;\n    }\n\n    /* several workers failed */\n\n    slot = ctx->slots;\n\n    for (n = 0; n < NGX_MAX_PROCESSES; ++n, ++slot) {\n        pid = ngx_processes[n].pid;\n\n        if (n == ngx_process_slot || *slot == 1 ||\n            pid == 0 || pid == NGX_INVALID_PID)\n        {\n            continue;\n        }\n\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"auto_push: connect failed: slot=%i pid=%P name='%s'\",\n                      n, pid, ctx->name);\n    }\n\n    if (!ctx->push_evt.timer_set) {\n        ngx_add_timer(&ctx->push_evt, apcf->push_reconnect);\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_auto_push_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    ngx_rtmp_auto_push_conf_t      *apcf;\n    ngx_rtmp_auto_push_ctx_t       *ctx;\n\n    if (s->auto_pushed || (s->relay && !s->static_relay)) {\n        goto next;\n    }\n\n    apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,\n                                                    ngx_rtmp_auto_push_module);\n    if (apcf->auto_push == 0) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);\n    if (ctx == NULL) {\n        ctx = ngx_palloc(s->connection->pool,\n                         sizeof(ngx_rtmp_auto_push_ctx_t));\n        if (ctx == NULL) {\n            goto next;\n        }\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_auto_push_index_module);\n\n    }\n    ngx_memzero(ctx, sizeof(*ctx));\n\n    ctx->push_evt.data = s;\n    ctx->push_evt.log = s->connection->log;\n    ctx->push_evt.handler = ngx_rtmp_auto_push_reconnect;\n\n    ctx->slots = ngx_pcalloc(s->connection->pool,\n                             sizeof(ngx_int_t) * NGX_MAX_PROCESSES);\n    if (ctx->slots == NULL) {\n        goto next;\n    }\n\n    ngx_memcpy(ctx->name, v->name, sizeof(ctx->name));\n    ngx_memcpy(ctx->args, v->args, sizeof(ctx->args));\n\n    ngx_rtmp_auto_push_reconnect(&ctx->push_evt);\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_auto_push_delete_stream(ngx_rtmp_session_t *s,\n    ngx_rtmp_delete_stream_t *v)\n{\n    ngx_rtmp_auto_push_conf_t      *apcf;\n    ngx_rtmp_auto_push_ctx_t       *ctx, *pctx;\n    ngx_rtmp_relay_ctx_t           *rctx;\n    ngx_int_t                       slot;\n\n    apcf = (ngx_rtmp_auto_push_conf_t *) ngx_get_conf(ngx_cycle->conf_ctx,\n                                                    ngx_rtmp_auto_push_module);\n    if (apcf->auto_push == 0) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_auto_push_index_module);\n    if (ctx) {\n        if (ctx->push_evt.timer_set) {\n            ngx_del_timer(&ctx->push_evt);\n        }\n        goto next;\n    }\n\n    /* skip non-relays & publishers */\n    rctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (rctx == NULL ||\n        rctx->tag != &ngx_rtmp_auto_push_module ||\n        rctx->publish == NULL)\n    {\n        goto next;\n    }\n\n    slot = (ngx_process_t *) rctx->data - &ngx_processes[0];\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"auto_push: disconnect slot=%i app='%V' name='%V'\",\n                   slot, &rctx->app, &rctx->name);\n\n    pctx = ngx_rtmp_get_module_ctx(rctx->publish->session,\n                                   ngx_rtmp_auto_push_index_module);\n    if (pctx == NULL) {\n        goto next;\n    }\n\n    pctx->slots[slot] = 0;\n\n    /* push reconnect */\n    if (!pctx->push_evt.timer_set) {\n        ngx_add_timer(&pctx->push_evt, apcf->push_reconnect);\n    }\n\nnext:\n    return next_delete_stream(s, v);\n}\n#endif /* NGX_HAVE_UNIX_DOMAIN */\n"
  },
  {
    "path": "ngx_rtmp_bandwidth.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_bandwidth.h\"\n\n\nvoid\nngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes)\n{\n    if (ngx_cached_time->sec > bw->intl_end) {\n        bw->bandwidth = ngx_cached_time->sec >\n            bw->intl_end + NGX_RTMP_BANDWIDTH_INTERVAL\n            ? 0\n            : bw->intl_bytes / NGX_RTMP_BANDWIDTH_INTERVAL;\n        bw->intl_bytes = 0;\n        bw->intl_end = ngx_cached_time->sec + NGX_RTMP_BANDWIDTH_INTERVAL;\n    }\n\n    bw->bytes += bytes;\n    bw->intl_bytes += bytes;\n}\n"
  },
  {
    "path": "ngx_rtmp_bandwidth.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_BANDWIDTH_H_INCLUDED_\n#define _NGX_RTMP_BANDWIDTH_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n\n\n/* Bandwidth update interval in seconds */\n#define NGX_RTMP_BANDWIDTH_INTERVAL     10\n\n\ntypedef struct {\n    uint64_t            bytes;\n    uint64_t            bandwidth;      /* bytes/sec */\n\n    time_t              intl_end;\n    uint64_t            intl_bytes;\n} ngx_rtmp_bandwidth_t;\n\n\nvoid ngx_rtmp_update_bandwidth(ngx_rtmp_bandwidth_t *bw, uint32_t bytes);\n\n\n#endif /* _NGX_RTMP_BANDWIDTH_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_bitop.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_bitop.h\"\n\n\nvoid\nngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos, u_char *last)\n{\n    ngx_memzero(br, sizeof(ngx_rtmp_bit_reader_t));\n\n    br->pos = pos;\n    br->last = last;\n}\n\n\nuint64_t\nngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n)\n{\n    uint64_t    v;\n    ngx_uint_t  d;\n\n    v = 0;\n\n    while (n) {\n\n        if (br->pos >= br->last) {\n            br->err = 1;\n            return 0;\n        }\n\n        d = (br->offs + n > 8 ? (ngx_uint_t) (8 - br->offs) : n);\n\n        v <<= d;\n        v += (*br->pos >> (8 - br->offs - d)) & ((u_char) 0xff >> (8 - d));\n\n        br->offs += d;\n        n -= d;\n\n        if (br->offs == 8) {\n            br->pos++;\n            br->offs = 0;\n        }\n    }\n\n    return v;\n}\n\n\nuint64_t\nngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br)\n{\n    ngx_uint_t  n;\n\n    for (n = 0; ngx_rtmp_bit_read(br, 1) == 0 && !br->err; n++);\n\n    return ((uint64_t) 1 << n) + ngx_rtmp_bit_read(br, n) - 1;\n}\n"
  },
  {
    "path": "ngx_rtmp_bitop.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_BITOP_H_INCLUDED_\n#define _NGX_RTMP_BITOP_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n\n\ntypedef struct {\n    u_char      *pos;\n    u_char      *last;\n    ngx_uint_t   offs;\n    ngx_uint_t   err;\n} ngx_rtmp_bit_reader_t;\n\n\nvoid ngx_rtmp_bit_init_reader(ngx_rtmp_bit_reader_t *br, u_char *pos,\n    u_char *last);\nuint64_t ngx_rtmp_bit_read(ngx_rtmp_bit_reader_t *br, ngx_uint_t n);\nuint64_t ngx_rtmp_bit_read_golomb(ngx_rtmp_bit_reader_t *br);\n\n\n#define ngx_rtmp_bit_read_err(br) ((br)->err)\n\n#define ngx_rtmp_bit_read_eof(br) ((br)->pos == (br)->last)\n\n#define ngx_rtmp_bit_read_8(br)                                               \\\n    ((uint8_t) ngx_rtmp_bit_read(br, 8))\n\n#define ngx_rtmp_bit_read_16(br)                                              \\\n    ((uint16_t) ngx_rtmp_bit_read(br, 16))\n\n#define ngx_rtmp_bit_read_32(br)                                              \\\n    ((uint32_t) ngx_rtmp_bit_read(br, 32))\n\n#define ngx_rtmp_bit_read_64(br)                                              \\\n    ((uint64_t) ngx_rtmp_read(br, 64))\n\n\n#endif /* _NGX_RTMP_BITOP_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_cmd_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_streams.h\"\n\n\n#define NGX_RTMP_FMS_VERSION        \"FMS/3,0,1,123\"\n#define NGX_RTMP_CAPABILITIES       31\n\n\nstatic ngx_int_t ngx_rtmp_cmd_connect(ngx_rtmp_session_t *s,\n       ngx_rtmp_connect_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s);\nstatic ngx_int_t ngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s,\n       ngx_rtmp_create_stream_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s,\n       ngx_rtmp_close_stream_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s,\n       ngx_rtmp_delete_stream_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_publish(ngx_rtmp_session_t *s,\n       ngx_rtmp_publish_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_play(ngx_rtmp_session_t *s,\n       ngx_rtmp_play_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_seek(ngx_rtmp_session_t *s,\n       ngx_rtmp_seek_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_pause(ngx_rtmp_session_t *s,\n       ngx_rtmp_pause_t *v);\n\n\nstatic ngx_int_t ngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s,\n       ngx_rtmp_stream_begin_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s,\n       ngx_rtmp_stream_eof_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s,\n       ngx_rtmp_stream_dry_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s,\n       ngx_rtmp_recorded_t *v);\nstatic ngx_int_t ngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s,\n       ngx_rtmp_set_buflen_t *v);\n\n\nngx_rtmp_connect_pt         ngx_rtmp_connect;\nngx_rtmp_disconnect_pt      ngx_rtmp_disconnect;\nngx_rtmp_create_stream_pt   ngx_rtmp_create_stream;\nngx_rtmp_close_stream_pt    ngx_rtmp_close_stream;\nngx_rtmp_delete_stream_pt   ngx_rtmp_delete_stream;\nngx_rtmp_publish_pt         ngx_rtmp_publish;\nngx_rtmp_play_pt            ngx_rtmp_play;\nngx_rtmp_seek_pt            ngx_rtmp_seek;\nngx_rtmp_pause_pt           ngx_rtmp_pause;\n\n\nngx_rtmp_stream_begin_pt    ngx_rtmp_stream_begin;\nngx_rtmp_stream_eof_pt      ngx_rtmp_stream_eof;\nngx_rtmp_stream_dry_pt      ngx_rtmp_stream_dry;\nngx_rtmp_recorded_pt        ngx_rtmp_recorded;\nngx_rtmp_set_buflen_pt      ngx_rtmp_set_buflen;\n\n\nstatic ngx_int_t ngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf);\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_cmd_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_cmd_postconfiguration,         /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    NULL,                                   /* create app configuration */\n    NULL                                    /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_cmd_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_cmd_module_ctx,               /* module context */\n    NULL,                                   /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nvoid\nngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME],\n        u_char args[NGX_RTMP_MAX_ARGS])\n{\n    u_char      *p;\n\n    p = (u_char *)ngx_strchr(name, '?');\n    if (p == NULL) {\n        return;\n    }\n\n    *p++ = 0;\n    ngx_cpystrn(args, p, NGX_RTMP_MAX_ARGS);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_connect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    size_t                      len;\n\n    static ngx_rtmp_connect_t   v;\n\n    static ngx_rtmp_amf_elt_t  in_cmd[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"app\"),\n          v.app, sizeof(v.app) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"flashVer\"),\n          v.flashver, sizeof(v.flashver) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"swfUrl\"),\n          v.swf_url, sizeof(v.swf_url) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"tcUrl\"),\n          v.tc_url, sizeof(v.tc_url) },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"audioCodecs\"),\n          &v.acodecs, sizeof(v.acodecs) },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"videoCodecs\"),\n          &v.vcodecs, sizeof(v.vcodecs) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"pageUrl\"),\n          v.page_url, sizeof(v.page_url) },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"objectEncoding\"),\n          &v.object_encoding, 0},\n    };\n\n    static ngx_rtmp_amf_elt_t  in_elts[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.trans, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          in_cmd, sizeof(in_cmd) },\n    };\n\n    ngx_memzero(&v, sizeof(v));\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    len = ngx_strlen(v.app);\n    if (len > 10 && !ngx_memcmp(v.app + len - 10, \"/_definst_\", 10)) {\n        v.app[len - 10] = 0;\n    } else if (len && v.app[len - 1] == '/') {\n        v.app[len - 1] = 0;\n    }\n\n    ngx_rtmp_cmd_fill_args(v.app, v.args);\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n            \"connect: app='%s' args='%s' flashver='%s' swf_url='%s' \"\n            \"tc_url='%s' page_url='%s' acodecs=%uD vcodecs=%uD \"\n            \"object_encoding=%ui\",\n            v.app, v.args, v.flashver, v.swf_url, v.tc_url, v.page_url,\n            (uint32_t)v.acodecs, (uint32_t)v.vcodecs,\n            (ngx_int_t)v.object_encoding);\n\n    return ngx_rtmp_connect(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_connect(ngx_rtmp_session_t *s, ngx_rtmp_connect_t *v)\n{\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_rtmp_core_app_conf_t  **cacfp;\n    ngx_uint_t                  n;\n    ngx_rtmp_header_t           h;\n    u_char                     *p;\n\n    static double               trans;\n    static double               capabilities = NGX_RTMP_CAPABILITIES;\n    static double               object_encoding = 0;\n\n    static ngx_rtmp_amf_elt_t  out_obj[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"fmsVer\"),\n          NGX_RTMP_FMS_VERSION, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"capabilities\"),\n          &capabilities, 0 },\n    };\n\n    static ngx_rtmp_amf_elt_t  out_inf[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"level\"),\n          \"status\", 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"code\"),\n          \"NetConnection.Connect.Success\", 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"description\"),\n          \"Connection succeeded.\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"objectEncoding\"),\n          &object_encoding, 0 }\n    };\n\n    static ngx_rtmp_amf_elt_t  out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"_result\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &trans, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          out_obj, sizeof(out_obj) },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          out_inf, sizeof(out_inf) },\n    };\n\n    if (s->connected) {\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                \"connect: duplicate connection\");\n        return NGX_ERROR;\n    }\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    trans = v->trans;\n\n    /* fill session parameters */\n    s->connected = 1;\n\n    ngx_memzero(&h, sizeof(h));\n    h.csid = NGX_RTMP_CSID_AMF_INI;\n    h.type = NGX_RTMP_MSG_AMF_CMD;\n\n\n#define NGX_RTMP_SET_STRPAR(name)                                             \\\n    s->name.len = ngx_strlen(v->name);                                        \\\n    s->name.data = ngx_palloc(s->connection->pool, s->name.len);              \\\n    ngx_memcpy(s->name.data, v->name, s->name.len)\n\n    NGX_RTMP_SET_STRPAR(app);\n    NGX_RTMP_SET_STRPAR(args);\n    NGX_RTMP_SET_STRPAR(flashver);\n    NGX_RTMP_SET_STRPAR(swf_url);\n    NGX_RTMP_SET_STRPAR(tc_url);\n    NGX_RTMP_SET_STRPAR(page_url);\n\n#undef NGX_RTMP_SET_STRPAR\n\n    p = ngx_strlchr(s->app.data, s->app.data + s->app.len, '?');\n    if (p) {\n        s->app.len = (p - s->app.data);\n    }\n\n    s->acodecs = (uint32_t) v->acodecs;\n    s->vcodecs = (uint32_t) v->vcodecs;\n\n    /* find application & set app_conf */\n    cacfp = cscf->applications.elts;\n    for(n = 0; n < cscf->applications.nelts; ++n, ++cacfp) {\n        if ((*cacfp)->name.len == s->app.len &&\n            ngx_strncmp((*cacfp)->name.data, s->app.data, s->app.len) == 0)\n        {\n            /* found app! */\n            s->app_conf = (*cacfp)->app_conf;\n            break;\n        }\n    }\n\n    if (s->app_conf == NULL) {\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                      \"connect: application not found: '%V'\", &s->app);\n        return NGX_ERROR;\n    }\n\n    object_encoding = v->object_encoding;\n\n    return ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK ||\n           ngx_rtmp_send_bandwidth(s, cscf->ack_window,\n                                   NGX_RTMP_LIMIT_DYNAMIC) != NGX_OK ||\n           ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK ||\n           ngx_rtmp_send_amf(s, &h, out_elts,\n                             sizeof(out_elts) / sizeof(out_elts[0]))\n           != NGX_OK ? NGX_ERROR : NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_create_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                                ngx_chain_t *in)\n{\n    static ngx_rtmp_create_stream_t     v;\n\n    static ngx_rtmp_amf_elt_t  in_elts[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.trans, sizeof(v.trans) },\n    };\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, \"createStream\");\n\n    return ngx_rtmp_create_stream(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_create_stream(ngx_rtmp_session_t *s, ngx_rtmp_create_stream_t *v)\n{\n    /* support one message stream per connection */\n    static double               stream;\n    static double               trans;\n    ngx_rtmp_header_t           h;\n\n    static ngx_rtmp_amf_elt_t  out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"_result\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &trans, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &stream, sizeof(stream) },\n    };\n\n    trans = v->trans;\n    stream = NGX_RTMP_MSID;\n\n    ngx_memzero(&h, sizeof(h));\n\n    h.csid = NGX_RTMP_CSID_AMF_INI;\n    h.type = NGX_RTMP_MSG_AMF_CMD;\n\n    return ngx_rtmp_send_amf(s, &h, out_elts,\n                             sizeof(out_elts) / sizeof(out_elts[0])) == NGX_OK ?\n           NGX_DONE : NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_close_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                               ngx_chain_t *in)\n{\n    static ngx_rtmp_close_stream_t     v;\n\n    static ngx_rtmp_amf_elt_t  in_elts[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.stream, 0 },\n    };\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                             sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, \"closeStream\");\n\n    return ngx_rtmp_close_stream(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_delete_stream_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                                ngx_chain_t *in)\n{\n    static ngx_rtmp_delete_stream_t     v;\n\n    static ngx_rtmp_amf_elt_t  in_elts[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.stream, 0 },\n    };\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                             sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    return ngx_rtmp_delete_stream(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v)\n{\n    ngx_rtmp_close_stream_t         cv;\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, \"deleteStream\");\n\n    cv.stream = 0;\n\n    return ngx_rtmp_close_stream(s, &cv);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_publish_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    static ngx_rtmp_publish_t       v;\n\n    static ngx_rtmp_amf_elt_t      in_elts[] = {\n\n        /* transaction is always 0 */\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          &v.name, sizeof(v.name) },\n\n        { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          &v.type, sizeof(v.type) },\n    };\n\n    ngx_memzero(&v, sizeof(v));\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                             sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_cmd_fill_args(v.name, v.args);\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"publish: name='%s' args='%s' type=%s silent=%d\",\n                  v.name, v.args, v.type, v.silent);\n\n    return ngx_rtmp_publish(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    return NGX_OK;\n}\n\nstatic ngx_int_t\nngx_rtmp_cmd_play_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    static ngx_rtmp_play_t          v;\n\n    static ngx_rtmp_amf_elt_t       in_elts[] = {\n\n        /* transaction is always 0 */\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          &v.name, sizeof(v.name) },\n\n        { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.start, 0 },\n\n        { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.duration, 0 },\n\n        { NGX_RTMP_AMF_OPTIONAL | NGX_RTMP_AMF_BOOLEAN,\n          ngx_null_string,\n          &v.reset, 0 }\n    };\n\n    ngx_memzero(&v, sizeof(v));\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                             sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_cmd_fill_args(v.name, v.args);\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"play: name='%s' args='%s' start=%i duration=%i \"\n                  \"reset=%i silent=%i\",\n                  v.name, v.args, (ngx_int_t) v.start,\n                  (ngx_int_t) v.duration, (ngx_int_t) v.reset,\n                  (ngx_int_t) v.silent);\n\n    return ngx_rtmp_play(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_play2_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    static ngx_rtmp_play_t          v;\n    static ngx_rtmp_close_stream_t  vc;\n\n    static ngx_rtmp_amf_elt_t       in_obj[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"start\"),\n          &v.start, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"streamName\"),\n          &v.name, sizeof(v.name) },\n    };\n\n    static ngx_rtmp_amf_elt_t       in_elts[] = {\n\n        /* transaction is always 0 */\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          &in_obj, sizeof(in_obj) }\n    };\n\n    ngx_memzero(&v, sizeof(v));\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                             sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_cmd_fill_args(v.name, v.args);\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"play2: name='%s' args='%s' start=%i\",\n                  v.name, v.args, (ngx_int_t) v.start);\n\n    /* continue from current timestamp */\n\n    if (v.start < 0) {\n        v.start = s->current_time;\n    }\n\n    ngx_memzero(&vc, sizeof(vc));\n\n    /* close_stream should be synchronous */\n    ngx_rtmp_close_stream(s, &vc);\n\n    return ngx_rtmp_play(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_pause_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    static ngx_rtmp_pause_t     v;\n\n    static ngx_rtmp_amf_elt_t   in_elts[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_BOOLEAN,\n          ngx_null_string,\n          &v.pause, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.position, 0 },\n    };\n\n    ngx_memzero(&v, sizeof(v));\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"pause: pause=%i position=%i\",\n                    (ngx_int_t) v.pause, (ngx_int_t) v.position);\n\n    return ngx_rtmp_pause(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_disconnect_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                        ngx_chain_t *in)\n{\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0, \"disconnect\");\n\n    return ngx_rtmp_disconnect(s);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_disconnect(ngx_rtmp_session_t *s)\n{\n    return ngx_rtmp_delete_stream(s, NULL);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_seek_init(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    static ngx_rtmp_seek_t         v;\n\n    static ngx_rtmp_amf_elt_t      in_elts[] = {\n\n        /* transaction is always 0 */\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.offset, sizeof(v.offset) },\n    };\n\n    ngx_memzero(&v, sizeof(v));\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                             sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"seek: offset=%i\", (ngx_int_t) v.offset);\n\n    return ngx_rtmp_seek(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_stream_dry(ngx_rtmp_session_t *s, ngx_rtmp_stream_dry_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_recorded(ngx_rtmp_session_t *s,\n                      ngx_rtmp_recorded_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_set_buflen(ngx_rtmp_session_t *s, ngx_rtmp_set_buflen_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic ngx_rtmp_amf_handler_t ngx_rtmp_cmd_map[] = {\n    { ngx_string(\"connect\"),            ngx_rtmp_cmd_connect_init           },\n    { ngx_string(\"createStream\"),       ngx_rtmp_cmd_create_stream_init     },\n    { ngx_string(\"closeStream\"),        ngx_rtmp_cmd_close_stream_init      },\n    { ngx_string(\"deleteStream\"),       ngx_rtmp_cmd_delete_stream_init     },\n    { ngx_string(\"publish\"),            ngx_rtmp_cmd_publish_init           },\n    { ngx_string(\"play\"),               ngx_rtmp_cmd_play_init              },\n    { ngx_string(\"play2\"),              ngx_rtmp_cmd_play2_init             },\n    { ngx_string(\"seek\"),               ngx_rtmp_cmd_seek_init              },\n    { ngx_string(\"pause\"),              ngx_rtmp_cmd_pause_init             },\n    { ngx_string(\"pauseraw\"),           ngx_rtmp_cmd_pause_init             },\n};\n\n\nstatic ngx_int_t\nngx_rtmp_cmd_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t          *cmcf;\n    ngx_rtmp_handler_pt                *h;\n    ngx_rtmp_amf_handler_t             *ch, *bh;\n    size_t                              n, ncalls;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    /* redirect disconnects to deleteStream\n     * to free client modules from registering\n     * disconnect callback */\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);\n    if (h == NULL) {\n        return NGX_ERROR;\n    }\n\n    *h = ngx_rtmp_cmd_disconnect_init;\n\n    /* register AMF callbacks */\n\n    ncalls = sizeof(ngx_rtmp_cmd_map) / sizeof(ngx_rtmp_cmd_map[0]);\n\n    ch = ngx_array_push_n(&cmcf->amf, ncalls);\n    if (ch == NULL) {\n        return NGX_ERROR;\n    }\n\n    bh = ngx_rtmp_cmd_map;\n\n    for(n = 0; n < ncalls; ++n, ++ch, ++bh) {\n        *ch = *bh;\n    }\n\n    ngx_rtmp_connect = ngx_rtmp_cmd_connect;\n    ngx_rtmp_disconnect = ngx_rtmp_cmd_disconnect;\n    ngx_rtmp_create_stream = ngx_rtmp_cmd_create_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_cmd_close_stream;\n    ngx_rtmp_delete_stream = ngx_rtmp_cmd_delete_stream;\n    ngx_rtmp_publish = ngx_rtmp_cmd_publish;\n    ngx_rtmp_play = ngx_rtmp_cmd_play;\n    ngx_rtmp_seek = ngx_rtmp_cmd_seek;\n    ngx_rtmp_pause = ngx_rtmp_cmd_pause;\n\n    ngx_rtmp_stream_begin = ngx_rtmp_cmd_stream_begin;\n    ngx_rtmp_stream_eof = ngx_rtmp_cmd_stream_eof;\n    ngx_rtmp_stream_dry = ngx_rtmp_cmd_stream_dry;\n    ngx_rtmp_recorded = ngx_rtmp_cmd_recorded;\n    ngx_rtmp_set_buflen = ngx_rtmp_cmd_set_buflen;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_cmd_module.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_CMD_H_INCLUDED_\n#define _NGX_RTMP_CMD_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_event.h>\n#include \"ngx_rtmp.h\"\n\n\n#define NGX_RTMP_MAX_NAME           256\n#define NGX_RTMP_MAX_URL            256\n#define NGX_RTMP_MAX_ARGS           NGX_RTMP_MAX_NAME\n\n\n/* Basic RTMP call support */\n\ntypedef struct {\n    double                          trans;\n    u_char                          app[NGX_RTMP_MAX_NAME];\n    u_char                          args[NGX_RTMP_MAX_ARGS];\n    u_char                          flashver[32];\n    u_char                          swf_url[NGX_RTMP_MAX_URL];\n    u_char                          tc_url[NGX_RTMP_MAX_URL];\n    double                          acodecs;\n    double                          vcodecs;\n    u_char                          page_url[NGX_RTMP_MAX_URL];\n    double                          object_encoding;\n} ngx_rtmp_connect_t;\n\n\ntypedef struct {\n    double                          trans;\n    double                          stream;\n} ngx_rtmp_create_stream_t;\n\n\ntypedef struct {\n    double                          stream;\n} ngx_rtmp_delete_stream_t;\n\n\ntypedef struct {\n    double                          stream;\n} ngx_rtmp_close_stream_t;\n\n\ntypedef struct {\n    u_char                          name[NGX_RTMP_MAX_NAME];\n    u_char                          args[NGX_RTMP_MAX_ARGS];\n    u_char                          type[16];\n    int                             silent;\n} ngx_rtmp_publish_t;\n\n\ntypedef struct {\n    u_char                          name[NGX_RTMP_MAX_NAME];\n    u_char                          args[NGX_RTMP_MAX_ARGS];\n    double                          start;\n    double                          duration;\n    int                             reset;\n    int                             silent;\n} ngx_rtmp_play_t;\n\n\ntypedef struct {\n    double                          offset;\n} ngx_rtmp_seek_t;\n\n\ntypedef struct {\n    uint8_t                         pause;\n    double                          position;\n} ngx_rtmp_pause_t;\n\n\ntypedef struct {\n    uint32_t                        msid;\n} ngx_rtmp_msid_t;\n\n\ntypedef ngx_rtmp_msid_t             ngx_rtmp_stream_begin_t;\ntypedef ngx_rtmp_msid_t             ngx_rtmp_stream_eof_t;\ntypedef ngx_rtmp_msid_t             ngx_rtmp_stream_dry_t;\ntypedef ngx_rtmp_msid_t             ngx_rtmp_recorded_t;\n\n\ntypedef struct {\n    uint32_t                        msid;\n    uint32_t                        buflen;\n} ngx_rtmp_set_buflen_t;\n\n\nvoid ngx_rtmp_cmd_fill_args(u_char name[NGX_RTMP_MAX_NAME],\n        u_char args[NGX_RTMP_MAX_ARGS]);\n\n\ntypedef ngx_int_t (*ngx_rtmp_connect_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_connect_t *v);\ntypedef ngx_int_t (*ngx_rtmp_disconnect_pt)(ngx_rtmp_session_t *s);\ntypedef ngx_int_t (*ngx_rtmp_create_stream_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_create_stream_t *v);\ntypedef ngx_int_t (*ngx_rtmp_close_stream_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_close_stream_t *v);\ntypedef ngx_int_t (*ngx_rtmp_delete_stream_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_delete_stream_t *v);\ntypedef ngx_int_t (*ngx_rtmp_publish_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_publish_t *v);\ntypedef ngx_int_t (*ngx_rtmp_play_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_play_t *v);\ntypedef ngx_int_t (*ngx_rtmp_seek_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_seek_t *v);\ntypedef ngx_int_t (*ngx_rtmp_pause_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_pause_t *v);\n\ntypedef ngx_int_t (*ngx_rtmp_stream_begin_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_stream_begin_t *v);\ntypedef ngx_int_t (*ngx_rtmp_stream_eof_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_stream_eof_t *v);\ntypedef ngx_int_t (*ngx_rtmp_stream_dry_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_stream_dry_t *v);\ntypedef ngx_int_t (*ngx_rtmp_recorded_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_recorded_t *v);\ntypedef ngx_int_t (*ngx_rtmp_set_buflen_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_set_buflen_t *v);\n\n\nextern ngx_rtmp_connect_pt          ngx_rtmp_connect;\nextern ngx_rtmp_disconnect_pt       ngx_rtmp_disconnect;\nextern ngx_rtmp_create_stream_pt    ngx_rtmp_create_stream;\nextern ngx_rtmp_close_stream_pt     ngx_rtmp_close_stream;\nextern ngx_rtmp_delete_stream_pt    ngx_rtmp_delete_stream;\nextern ngx_rtmp_publish_pt          ngx_rtmp_publish;\nextern ngx_rtmp_play_pt             ngx_rtmp_play;\nextern ngx_rtmp_seek_pt             ngx_rtmp_seek;\nextern ngx_rtmp_pause_pt            ngx_rtmp_pause;\n\nextern ngx_rtmp_stream_begin_pt     ngx_rtmp_stream_begin;\nextern ngx_rtmp_stream_eof_pt       ngx_rtmp_stream_eof;\nextern ngx_rtmp_stream_dry_pt       ngx_rtmp_stream_dry;\nextern ngx_rtmp_set_buflen_pt       ngx_rtmp_set_buflen;\nextern ngx_rtmp_recorded_pt         ngx_rtmp_recorded;\n\n\n#endif /*_NGX_RTMP_CMD_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_codec_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_codec_module.h\"\n#include \"ngx_rtmp_live_module.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_bitop.h\"\n\n\n#define NGX_RTMP_CODEC_META_OFF     0\n#define NGX_RTMP_CODEC_META_ON      1\n#define NGX_RTMP_CODEC_META_COPY    2\n\n\nstatic void * ngx_rtmp_codec_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\nstatic ngx_int_t ngx_rtmp_codec_postconfiguration(ngx_conf_t *cf);\nstatic ngx_int_t ngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s);\nstatic ngx_int_t ngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s,\n       ngx_rtmp_header_t *h, ngx_chain_t *in);\nstatic ngx_int_t ngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s,\n       uint32_t timestamp);\nstatic void ngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s,\n       ngx_chain_t *in);\nstatic void ngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s,\n       ngx_chain_t *in);\n#if (NGX_DEBUG)\nstatic void ngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type,\n       ngx_chain_t *in);\n#endif\n\n\ntypedef struct {\n    ngx_uint_t                      meta;\n} ngx_rtmp_codec_app_conf_t;\n\n\nstatic ngx_conf_enum_t ngx_rtmp_codec_meta_slots[] = {\n    { ngx_string(\"off\"),            NGX_RTMP_CODEC_META_OFF  },\n    { ngx_string(\"on\"),             NGX_RTMP_CODEC_META_ON   },\n    { ngx_string(\"copy\"),           NGX_RTMP_CODEC_META_COPY },\n    { ngx_null_string,              0 }\n};\n\n\nstatic ngx_command_t  ngx_rtmp_codec_commands[] = {\n\n    { ngx_string(\"meta\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_enum_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_codec_app_conf_t, meta),\n      &ngx_rtmp_codec_meta_slots },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_codec_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_codec_postconfiguration,       /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    ngx_rtmp_codec_create_app_conf,         /* create app configuration */\n    ngx_rtmp_codec_merge_app_conf           /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_codec_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_codec_module_ctx,             /* module context */\n    ngx_rtmp_codec_commands,                /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic const char *\naudio_codecs[] = {\n    \"\",\n    \"ADPCM\",\n    \"MP3\",\n    \"LinearLE\",\n    \"Nellymoser16\",\n    \"Nellymoser8\",\n    \"Nellymoser\",\n    \"G711A\",\n    \"G711U\",\n    \"\",\n    \"AAC\",\n    \"Speex\",\n    \"\",\n    \"\",\n    \"MP3-8K\",\n    \"DeviceSpecific\",\n    \"Uncompressed\"\n};\n\n\nstatic const char *\nvideo_codecs[] = {\n    \"\",\n    \"Jpeg\",\n    \"Sorenson-H263\",\n    \"ScreenVideo\",\n    \"On2-VP6\",\n    \"On2-VP6-Alpha\",\n    \"ScreenVideo2\",\n    \"H264\",\n};\n\n\nu_char *\nngx_rtmp_get_audio_codec_name(ngx_uint_t id)\n{\n    return (u_char *)(id < sizeof(audio_codecs) / sizeof(audio_codecs[0])\n        ? audio_codecs[id]\n        : \"\");\n}\n\n\nu_char *\nngx_rtmp_get_video_codec_name(ngx_uint_t id)\n{\n    return (u_char *)(id < sizeof(video_codecs) / sizeof(video_codecs[0])\n        ? video_codecs[id]\n        : \"\");\n}\n\n\nstatic ngx_uint_t\nngx_rtmp_codec_get_next_version()\n{\n    ngx_uint_t          v;\n    static ngx_uint_t   version;\n\n    do {\n        v = ++version;\n    } while (v == 0);\n\n    return v;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_codec_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_codec_ctx_t               *ctx;\n    ngx_rtmp_core_srv_conf_t           *cscf;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    if (ctx->avc_header) {\n        ngx_rtmp_free_shared_chain(cscf, ctx->avc_header);\n        ctx->avc_header = NULL;\n    }\n\n    if (ctx->aac_header) {\n        ngx_rtmp_free_shared_chain(cscf, ctx->aac_header);\n        ctx->aac_header = NULL;\n    }\n\n    if (ctx->meta) {\n        ngx_rtmp_free_shared_chain(cscf, ctx->meta);\n        ctx->meta = NULL;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_codec_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_core_srv_conf_t           *cscf;\n    ngx_rtmp_codec_ctx_t               *ctx;\n    ngx_chain_t                       **header;\n    uint8_t                             fmt;\n    static ngx_uint_t                   sample_rates[] =\n                                        { 5512, 11025, 22050, 44100 };\n\n    if (h->type != NGX_RTMP_MSG_AUDIO && h->type != NGX_RTMP_MSG_VIDEO) {\n        return NGX_OK;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n    if (ctx == NULL) {\n        ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_codec_ctx_t));\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module);\n    }\n\n    /* save codec */\n    if (in->buf->last - in->buf->pos < 1) {\n        return NGX_OK;\n    }\n\n    fmt =  in->buf->pos[0];\n    if (h->type == NGX_RTMP_MSG_AUDIO) {\n        ctx->audio_codec_id = (fmt & 0xf0) >> 4;\n        ctx->audio_channels = (fmt & 0x01) + 1;\n        ctx->sample_size = (fmt & 0x02) ? 2 : 1;\n\n        if (ctx->sample_rate == 0) {\n            ctx->sample_rate = sample_rates[(fmt & 0x0c) >> 2];\n        }\n    } else {\n        ctx->video_codec_id = (fmt & 0x0f);\n    }\n\n    /* save AVC/AAC header */\n    if (in->buf->last - in->buf->pos < 3) {\n        return NGX_OK;\n    }\n\n    /* no conf */\n    if (!ngx_rtmp_is_codec_header(in)) {\n        return NGX_OK;\n    }\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n    header = NULL;\n\n    if (h->type == NGX_RTMP_MSG_AUDIO) {\n        if (ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC) {\n            header = &ctx->aac_header;\n            ngx_rtmp_codec_parse_aac_header(s, in);\n        }\n    } else {\n        if (ctx->video_codec_id == NGX_RTMP_VIDEO_H264) {\n            header = &ctx->avc_header;\n            ngx_rtmp_codec_parse_avc_header(s, in);\n        }\n    }\n\n    if (header == NULL) {\n        return NGX_OK;\n    }\n\n    if (*header) {\n        ngx_rtmp_free_shared_chain(cscf, *header);\n    }\n\n    *header = ngx_rtmp_append_shared_bufs(cscf, NULL, in);\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_codec_parse_aac_header(ngx_rtmp_session_t *s, ngx_chain_t *in)\n{\n    ngx_uint_t              idx;\n    ngx_rtmp_codec_ctx_t   *ctx;\n    ngx_rtmp_bit_reader_t   br;\n\n    static ngx_uint_t      aac_sample_rates[] =\n        { 96000, 88200, 64000, 48000,\n          44100, 32000, 24000, 22050,\n          16000, 12000, 11025,  8000,\n           7350,     0,     0,     0 };\n\n#if (NGX_DEBUG)\n    ngx_rtmp_codec_dump_header(s, \"aac\", in);\n#endif\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last);\n\n    ngx_rtmp_bit_read(&br, 16);\n\n    ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5);\n    if (ctx->aac_profile == 31) {\n        ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32;\n    }\n\n    idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);\n    if (idx == 15) {\n        ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24);\n    } else {\n        ctx->sample_rate = aac_sample_rates[idx];\n    }\n\n    ctx->aac_chan_conf = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);\n\n    if (ctx->aac_profile == 5 || ctx->aac_profile == 29) {\n        \n        if (ctx->aac_profile == 29) {\n            ctx->aac_ps = 1;\n        }\n\n        ctx->aac_sbr = 1;\n\n        idx = (ngx_uint_t) ngx_rtmp_bit_read(&br, 4);\n        if (idx == 15) {\n            ctx->sample_rate = (ngx_uint_t) ngx_rtmp_bit_read(&br, 24);\n        } else {\n            ctx->sample_rate = aac_sample_rates[idx];\n        }\n\n        ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 5);\n        if (ctx->aac_profile == 31) {\n            ctx->aac_profile = (ngx_uint_t) ngx_rtmp_bit_read(&br, 6) + 32;\n        }\n    }\n\n    /* MPEG-4 Audio Specific Config\n\n       5 bits: object type\n       if (object type == 31)\n         6 bits + 32: object type\n       4 bits: frequency index\n       if (frequency index == 15)\n         24 bits: frequency\n       4 bits: channel configuration\n\n       if (object_type == 5)\n           4 bits: frequency index\n           if (frequency index == 15)\n             24 bits: frequency\n           5 bits: object type\n           if (object type == 31)\n             6 bits + 32: object type\n             \n       var bits: AOT Specific Config\n     */\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"codec: aac header profile=%ui, \"\n                   \"sample_rate=%ui, chan_conf=%ui\",\n                   ctx->aac_profile, ctx->sample_rate, ctx->aac_chan_conf);\n}\n\n\nstatic void\nngx_rtmp_codec_parse_avc_header(ngx_rtmp_session_t *s, ngx_chain_t *in)\n{\n    ngx_uint_t              profile_idc, width, height, crop_left, crop_right,\n                            crop_top, crop_bottom, frame_mbs_only, n, cf_idc,\n                            num_ref_frames;\n    ngx_rtmp_codec_ctx_t   *ctx;\n    ngx_rtmp_bit_reader_t   br;\n\n#if (NGX_DEBUG)\n    ngx_rtmp_codec_dump_header(s, \"avc\", in);\n#endif\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    ngx_rtmp_bit_init_reader(&br, in->buf->pos, in->buf->last);\n\n    ngx_rtmp_bit_read(&br, 48);\n\n    ctx->avc_profile = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);\n    ctx->avc_compat = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);\n    ctx->avc_level = (ngx_uint_t) ngx_rtmp_bit_read_8(&br);\n\n    /* nal bytes */\n    ctx->avc_nal_bytes = (ngx_uint_t) ((ngx_rtmp_bit_read_8(&br) & 0x03) + 1);\n\n    /* nnals */\n    if ((ngx_rtmp_bit_read_8(&br) & 0x1f) == 0) {\n        return;\n    }\n\n    /* nal size */\n    ngx_rtmp_bit_read(&br, 16);\n\n    /* nal type */\n    if (ngx_rtmp_bit_read_8(&br) != 0x67) {\n        return;\n    }\n\n    /* SPS */\n\n    /* profile idc */\n    profile_idc = (ngx_uint_t) ngx_rtmp_bit_read(&br, 8);\n\n    /* flags */\n    ngx_rtmp_bit_read(&br, 8);\n\n    /* level idc */\n    ngx_rtmp_bit_read(&br, 8);\n\n    /* SPS id */\n    ngx_rtmp_bit_read_golomb(&br);\n\n    if (profile_idc == 100 || profile_idc == 110 ||\n        profile_idc == 122 || profile_idc == 244 || profile_idc == 44 ||\n        profile_idc == 83 || profile_idc == 86 || profile_idc == 118)\n    {\n        /* chroma format idc */\n        cf_idc = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n        \n        if (cf_idc == 3) {\n\n            /* separate color plane */\n            ngx_rtmp_bit_read(&br, 1);\n        }\n\n        /* bit depth luma - 8 */\n        ngx_rtmp_bit_read_golomb(&br);\n\n        /* bit depth chroma - 8 */\n        ngx_rtmp_bit_read_golomb(&br);\n\n        /* qpprime y zero transform bypass */\n        ngx_rtmp_bit_read(&br, 1);\n\n        /* seq scaling matrix present */\n        if (ngx_rtmp_bit_read(&br, 1)) {\n\n            for (n = 0; n < (cf_idc != 3 ? 8u : 12u); n++) {\n\n                /* seq scaling list present */\n                if (ngx_rtmp_bit_read(&br, 1)) {\n\n                    /* TODO: scaling_list()\n                    if (n < 6) {\n                    } else {\n                    }\n                    */\n                }\n            }\n        }\n    }\n\n    /* log2 max frame num */\n    ngx_rtmp_bit_read_golomb(&br);\n\n    /* pic order cnt type */\n    switch (ngx_rtmp_bit_read_golomb(&br)) {\n    case 0:\n\n        /* max pic order cnt */\n        ngx_rtmp_bit_read_golomb(&br);\n        break;\n\n    case 1:\n\n        /* delta pic order alwys zero */\n        ngx_rtmp_bit_read(&br, 1);\n\n        /* offset for non-ref pic */\n        ngx_rtmp_bit_read_golomb(&br);\n\n        /* offset for top to bottom field */\n        ngx_rtmp_bit_read_golomb(&br);\n\n        /* num ref frames in pic order */\n        num_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n\n        for (n = 0; n < num_ref_frames; n++) {\n\n            /* offset for ref frame */\n            ngx_rtmp_bit_read_golomb(&br);\n        }\n    }\n\n    /* num ref frames */\n    ctx->avc_ref_frames = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n\n    /* gaps in frame num allowed */\n    ngx_rtmp_bit_read(&br, 1);\n\n    /* pic width in mbs - 1 */\n    width = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n\n    /* pic height in map units - 1 */\n    height = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n\n    /* frame mbs only flag */\n    frame_mbs_only = (ngx_uint_t) ngx_rtmp_bit_read(&br, 1);\n\n    if (!frame_mbs_only) {\n\n        /* mbs adaprive frame field */\n        ngx_rtmp_bit_read(&br, 1);\n    }\n\n    /* direct 8x8 inference flag */\n    ngx_rtmp_bit_read(&br, 1);\n\n    /* frame cropping */\n    if (ngx_rtmp_bit_read(&br, 1)) {\n\n        crop_left = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n        crop_right = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n        crop_top = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n        crop_bottom = (ngx_uint_t) ngx_rtmp_bit_read_golomb(&br);\n\n    } else {\n\n        crop_left = 0;\n        crop_right = 0;\n        crop_top = 0;\n        crop_bottom = 0;\n    }\n\n    ctx->width = (width + 1) * 16 - (crop_left + crop_right) * 2;\n    ctx->height = (2 - frame_mbs_only) * (height + 1) * 16 -\n                  (crop_top + crop_bottom) * 2;\n\n    ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"codec: avc header \"\n                   \"profile=%ui, compat=%ui, level=%ui, \"\n                   \"nal_bytes=%ui, ref_frames=%ui, width=%ui, height=%ui\",\n                   ctx->avc_profile, ctx->avc_compat, ctx->avc_level,\n                   ctx->avc_nal_bytes, ctx->avc_ref_frames,\n                   ctx->width, ctx->height);\n}\n\n\n#if (NGX_DEBUG)\nstatic void\nngx_rtmp_codec_dump_header(ngx_rtmp_session_t *s, const char *type,\n    ngx_chain_t *in)\n{\n    u_char buf[256], *p, *pp;\n    u_char hex[] = \"0123456789abcdef\";\n\n    for (pp = buf, p = in->buf->pos;\n         p < in->buf->last && pp < buf + sizeof(buf) - 1;\n         ++p)\n    {\n        *pp++ = hex[*p >> 4];\n        *pp++ = hex[*p & 0x0f];\n    }\n\n    *pp = 0;\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"codec: %s header %s\", type, buf);\n}\n#endif\n\n\nstatic ngx_int_t\nngx_rtmp_codec_reconstruct_meta(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_codec_ctx_t           *ctx;\n    ngx_rtmp_core_srv_conf_t       *cscf;\n    ngx_int_t                       rc;\n\n    static struct {\n        double                      width;\n        double                      height;\n        double                      duration;\n        double                      frame_rate;\n        double                      video_data_rate;\n        double                      video_codec_id;\n        double                      audio_data_rate;\n        double                      audio_codec_id;\n        u_char                      profile[32];\n        u_char                      level[32];\n    }                               v;\n\n    static ngx_rtmp_amf_elt_t       out_inf[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"Server\"),\n          \"NGINX RTMP (github.com/arut/nginx-rtmp-module)\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"width\"),\n          &v.width, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"height\"),\n          &v.height, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"displayWidth\"),\n          &v.width, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"displayHeight\"),\n          &v.height, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"duration\"),\n          &v.duration, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"framerate\"),\n          &v.frame_rate, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"fps\"),\n          &v.frame_rate, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"videodatarate\"),\n          &v.video_data_rate, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"videocodecid\"),\n          &v.video_codec_id, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"audiodatarate\"),\n          &v.audio_data_rate, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"audiocodecid\"),\n          &v.audio_codec_id, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"profile\"),\n          &v.profile, sizeof(v.profile) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"level\"),\n          &v.level, sizeof(v.level) },\n    };\n\n    static ngx_rtmp_amf_elt_t       out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"onMetaData\", 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          out_inf, sizeof(out_inf) },\n    };\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    if (ctx->meta) {\n        ngx_rtmp_free_shared_chain(cscf, ctx->meta);\n        ctx->meta = NULL;\n    }\n\n    v.width = ctx->width;\n    v.height = ctx->height;\n    v.duration = ctx->duration;\n    v.frame_rate = ctx->frame_rate;\n    v.video_data_rate = ctx->video_data_rate;\n    v.video_codec_id = ctx->video_codec_id;\n    v.audio_data_rate = ctx->audio_data_rate;\n    v.audio_codec_id = ctx->audio_codec_id;\n    ngx_memcpy(v.profile, ctx->profile, sizeof(ctx->profile));\n    ngx_memcpy(v.level, ctx->level, sizeof(ctx->level));\n\n    rc = ngx_rtmp_append_amf(s, &ctx->meta, NULL, out_elts,\n                             sizeof(out_elts) / sizeof(out_elts[0]));\n    if (rc != NGX_OK || ctx->meta == NULL) {\n        return NGX_ERROR;\n    }\n\n    return ngx_rtmp_codec_prepare_meta(s, 0);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_codec_copy_meta(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_codec_ctx_t      *ctx;\n    ngx_rtmp_core_srv_conf_t  *cscf;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    if (ctx->meta) {\n        ngx_rtmp_free_shared_chain(cscf, ctx->meta);\n    }\n\n    ctx->meta = ngx_rtmp_append_shared_bufs(cscf, NULL, in);\n\n    if (ctx->meta == NULL) {\n        return NGX_ERROR;\n    }\n\n    return ngx_rtmp_codec_prepare_meta(s, h->timestamp);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_codec_prepare_meta(ngx_rtmp_session_t *s, uint32_t timestamp)\n{\n    ngx_rtmp_header_t      h;\n    ngx_rtmp_codec_ctx_t  *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    ngx_memzero(&h, sizeof(h));\n    h.csid = NGX_RTMP_CSID_AMF;\n    h.msid = NGX_RTMP_MSID;\n    h.type = NGX_RTMP_MSG_AMF_META;\n    h.timestamp = timestamp;\n    ngx_rtmp_prepare_message(s, &h, NULL, ctx->meta);\n\n    ctx->meta_version = ngx_rtmp_codec_get_next_version();\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_codec_meta_data(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_codec_app_conf_t      *cacf;\n    ngx_rtmp_codec_ctx_t           *ctx;\n    ngx_uint_t                      skip;\n\n    static struct {\n        double                      width;\n        double                      height;\n        double                      duration;\n        double                      frame_rate;\n        double                      video_data_rate;\n        double                      video_codec_id_n;\n        u_char                      video_codec_id_s[32];\n        double                      audio_data_rate;\n        double                      audio_codec_id_n;\n        u_char                      audio_codec_id_s[32];\n        u_char                      profile[32];\n        u_char                      level[32];\n    }                               v;\n\n    static ngx_rtmp_amf_elt_t       in_video_codec_id[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.video_codec_id_n, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          &v.video_codec_id_s, sizeof(v.video_codec_id_s) },\n    };\n\n    static ngx_rtmp_amf_elt_t       in_audio_codec_id[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.audio_codec_id_n, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          &v.audio_codec_id_s, sizeof(v.audio_codec_id_s) },\n    };\n\n    static ngx_rtmp_amf_elt_t       in_inf[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"width\"),\n          &v.width, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"height\"),\n          &v.height, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"duration\"),\n          &v.duration, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"framerate\"),\n          &v.frame_rate, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"fps\"),\n          &v.frame_rate, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"videodatarate\"),\n          &v.video_data_rate, 0 },\n\n        { NGX_RTMP_AMF_VARIANT,\n          ngx_string(\"videocodecid\"),\n          in_video_codec_id, sizeof(in_video_codec_id) },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"audiodatarate\"),\n          &v.audio_data_rate, 0 },\n\n        { NGX_RTMP_AMF_VARIANT,\n          ngx_string(\"audiocodecid\"),\n          in_audio_codec_id, sizeof(in_audio_codec_id) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"profile\"),\n          &v.profile, sizeof(v.profile) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"level\"),\n          &v.level, sizeof(v.level) },\n    };\n\n    static ngx_rtmp_amf_elt_t       in_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          in_inf, sizeof(in_inf) },\n    };\n\n    cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_codec_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n    if (ctx == NULL) {\n        ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_codec_ctx_t));\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_codec_module);\n    }\n\n    ngx_memzero(&v, sizeof(v));\n\n    /* use -1 as a sign of unchanged data;\n     * 0 is a valid value for uncompressed audio */\n    v.audio_codec_id_n = -1;\n\n    /* FFmpeg sends a string in front of actal metadata; ignore it */\n    skip = !(in->buf->last > in->buf->pos\n            && *in->buf->pos == NGX_RTMP_AMF_STRING);\n    if (ngx_rtmp_receive_amf(s, in, in_elts + skip,\n                sizeof(in_elts) / sizeof(in_elts[0]) - skip))\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                \"codec: error parsing data frame\");\n        return NGX_OK;\n    }\n\n    ctx->width = (ngx_uint_t) v.width;\n    ctx->height = (ngx_uint_t) v.height;\n    ctx->duration = (ngx_uint_t) v.duration;\n    ctx->frame_rate = (ngx_uint_t) v.frame_rate;\n    ctx->video_data_rate = (ngx_uint_t) v.video_data_rate;\n    ctx->video_codec_id = (ngx_uint_t) v.video_codec_id_n;\n    ctx->audio_data_rate = (ngx_uint_t) v.audio_data_rate;\n    ctx->audio_codec_id = (v.audio_codec_id_n == -1\n            ? 0 : v.audio_codec_id_n == 0\n            ? NGX_RTMP_AUDIO_UNCOMPRESSED : (ngx_uint_t) v.audio_codec_id_n);\n    ngx_memcpy(ctx->profile, v.profile, sizeof(v.profile));\n    ngx_memcpy(ctx->level, v.level, sizeof(v.level));\n\n    ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"codec: data frame: \"\n            \"width=%ui height=%ui duration=%ui frame_rate=%ui \"\n            \"video=%s (%ui) audio=%s (%ui)\",\n            ctx->width, ctx->height, ctx->duration, ctx->frame_rate,\n            ngx_rtmp_get_video_codec_name(ctx->video_codec_id),\n            ctx->video_codec_id,\n            ngx_rtmp_get_audio_codec_name(ctx->audio_codec_id),\n            ctx->audio_codec_id);\n\n    switch (cacf->meta) {\n        case NGX_RTMP_CODEC_META_ON:\n            return ngx_rtmp_codec_reconstruct_meta(s);\n        case NGX_RTMP_CODEC_META_COPY:\n            return ngx_rtmp_codec_copy_meta(s, h, in);\n    }\n\n    /* NGX_RTMP_CODEC_META_OFF */\n\n    return NGX_OK;\n}\n\n\nstatic void *\nngx_rtmp_codec_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_codec_app_conf_t  *cacf;\n\n    cacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_codec_app_conf_t));\n    if (cacf == NULL) {\n        return NULL;\n    }\n\n    cacf->meta = NGX_CONF_UNSET_UINT;\n\n    return cacf;\n}\n\n\nstatic char *\nngx_rtmp_codec_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_codec_app_conf_t *prev = parent;\n    ngx_rtmp_codec_app_conf_t *conf = child;\n\n    ngx_conf_merge_uint_value(conf->meta, prev->meta, NGX_RTMP_CODEC_META_ON);\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_codec_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t          *cmcf;\n    ngx_rtmp_handler_pt                *h;\n    ngx_rtmp_amf_handler_t             *ch;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);\n    *h = ngx_rtmp_codec_av;\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);\n    *h = ngx_rtmp_codec_av;\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);\n    *h = ngx_rtmp_codec_disconnect;\n\n    /* register metadata handler */\n    ch = ngx_array_push(&cmcf->amf);\n    if (ch == NULL) {\n        return NGX_ERROR;\n    }\n    ngx_str_set(&ch->name, \"@setDataFrame\");\n    ch->handler = ngx_rtmp_codec_meta_data;\n\n    ch = ngx_array_push(&cmcf->amf);\n    if (ch == NULL) {\n        return NGX_ERROR;\n    }\n    ngx_str_set(&ch->name, \"onMetaData\");\n    ch->handler = ngx_rtmp_codec_meta_data;\n\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_codec_module.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_CODEC_H_INCLUDED_\n#define _NGX_RTMP_CODEC_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\n/* Audio codecs */\nenum {\n    /* Uncompressed codec id is actually 0,\n     * but we use another value for consistency */\n    NGX_RTMP_AUDIO_UNCOMPRESSED     = 16,\n    NGX_RTMP_AUDIO_ADPCM            = 1,\n    NGX_RTMP_AUDIO_MP3              = 2,\n    NGX_RTMP_AUDIO_LINEAR_LE        = 3,\n    NGX_RTMP_AUDIO_NELLY16          = 4,\n    NGX_RTMP_AUDIO_NELLY8           = 5,\n    NGX_RTMP_AUDIO_NELLY            = 6,\n    NGX_RTMP_AUDIO_G711A            = 7,\n    NGX_RTMP_AUDIO_G711U            = 8,\n    NGX_RTMP_AUDIO_AAC              = 10,\n    NGX_RTMP_AUDIO_SPEEX            = 11,\n    NGX_RTMP_AUDIO_MP3_8            = 14,\n    NGX_RTMP_AUDIO_DEVSPEC          = 15,\n};\n\n\n/* Video codecs */\nenum {\n    NGX_RTMP_VIDEO_JPEG             = 1,\n    NGX_RTMP_VIDEO_SORENSON_H263    = 2,\n    NGX_RTMP_VIDEO_SCREEN           = 3,\n    NGX_RTMP_VIDEO_ON2_VP6          = 4,\n    NGX_RTMP_VIDEO_ON2_VP6_ALPHA    = 5,\n    NGX_RTMP_VIDEO_SCREEN2          = 6,\n    NGX_RTMP_VIDEO_H264             = 7\n};\n\n\nu_char * ngx_rtmp_get_audio_codec_name(ngx_uint_t id);\nu_char * ngx_rtmp_get_video_codec_name(ngx_uint_t id);\n\n\ntypedef struct {\n    ngx_uint_t                  width;\n    ngx_uint_t                  height;\n    ngx_uint_t                  duration;\n    ngx_uint_t                  frame_rate;\n    ngx_uint_t                  video_data_rate;\n    ngx_uint_t                  video_codec_id;\n    ngx_uint_t                  audio_data_rate;\n    ngx_uint_t                  audio_codec_id;\n    ngx_uint_t                  aac_profile;\n    ngx_uint_t                  aac_chan_conf;\n    ngx_uint_t                  aac_sbr;\n    ngx_uint_t                  aac_ps;\n    ngx_uint_t                  avc_profile;\n    ngx_uint_t                  avc_compat;\n    ngx_uint_t                  avc_level;\n    ngx_uint_t                  avc_nal_bytes;\n    ngx_uint_t                  avc_ref_frames;\n    ngx_uint_t                  sample_rate;    /* 5512, 11025, 22050, 44100 */\n    ngx_uint_t                  sample_size;    /* 1=8bit, 2=16bit */\n    ngx_uint_t                  audio_channels; /* 1, 2 */\n    u_char                      profile[32];\n    u_char                      level[32];\n\n    ngx_chain_t                *avc_header;\n    ngx_chain_t                *aac_header;\n\n    ngx_chain_t                *meta;\n    ngx_uint_t                  meta_version;\n} ngx_rtmp_codec_ctx_t;\n\n\nextern ngx_module_t  ngx_rtmp_codec_module;\n\n\n#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_control_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_http.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_live_module.h\"\n#include \"ngx_rtmp_record_module.h\"\n\n\nstatic char *ngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);\nstatic void * ngx_rtmp_control_create_loc_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf,\n    void *parent, void *child);\n\n\ntypedef const char * (*ngx_rtmp_control_handler_t)(ngx_http_request_t *r,\n    ngx_rtmp_session_t *);\n\n\n#define NGX_RTMP_CONTROL_ALL        0xff\n#define NGX_RTMP_CONTROL_RECORD     0x01\n#define NGX_RTMP_CONTROL_DROP       0x02\n#define NGX_RTMP_CONTROL_REDIRECT   0x04\n\n\nenum {\n    NGX_RTMP_CONTROL_FILTER_CLIENT = 0,\n    NGX_RTMP_CONTROL_FILTER_PUBLISHER,\n    NGX_RTMP_CONTROL_FILTER_SUBSCRIBER\n};\n\n\ntypedef struct {\n    ngx_uint_t                      count;\n    ngx_str_t                       path;\n    ngx_uint_t                      filter;\n    ngx_str_t                       method;\n    ngx_array_t                     sessions; /* ngx_rtmp_session_t * */\n} ngx_rtmp_control_ctx_t;\n\n\ntypedef struct {\n    ngx_uint_t                      control;\n} ngx_rtmp_control_loc_conf_t;\n\n\nstatic ngx_conf_bitmask_t           ngx_rtmp_control_masks[] = {\n    { ngx_string(\"all\"),            NGX_RTMP_CONTROL_ALL       },\n    { ngx_string(\"record\"),         NGX_RTMP_CONTROL_RECORD    },\n    { ngx_string(\"drop\"),           NGX_RTMP_CONTROL_DROP      },\n    { ngx_string(\"redirect\"),       NGX_RTMP_CONTROL_REDIRECT  },\n    { ngx_null_string,              0                          }\n};\n\n\nstatic ngx_command_t  ngx_rtmp_control_commands[] = {\n\n    { ngx_string(\"rtmp_control\"),\n      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_control,\n      NGX_HTTP_LOC_CONF_OFFSET,\n      offsetof(ngx_rtmp_control_loc_conf_t, control),\n      ngx_rtmp_control_masks },\n\n    ngx_null_command\n};\n\n\nstatic ngx_http_module_t  ngx_rtmp_control_module_ctx = {\n    NULL,                               /* preconfiguration */\n    NULL,                               /* postconfiguration */\n\n    NULL,                               /* create main configuration */\n    NULL,                               /* init main configuration */\n\n    NULL,                               /* create server configuration */\n    NULL,                               /* merge server configuration */\n\n    ngx_rtmp_control_create_loc_conf,   /* create location configuration */\n    ngx_rtmp_control_merge_loc_conf,    /* merge location configuration */\n};\n\n\nngx_module_t  ngx_rtmp_control_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_control_module_ctx,       /* module context */\n    ngx_rtmp_control_commands,          /* module directives */\n    NGX_HTTP_MODULE,                    /* module type */\n    NULL,                               /* init master */\n    NULL,                               /* init module */\n    NULL,                               /* init process */\n    NULL,                               /* init thread */\n    NULL,                               /* exit thread */\n    NULL,                               /* exit process */\n    NULL,                               /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic const char *\nngx_rtmp_control_record_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)\n{\n    ngx_int_t                    rc;\n    ngx_str_t                    rec;\n    ngx_uint_t                   rn;\n    ngx_rtmp_control_ctx_t      *ctx;\n    ngx_rtmp_core_app_conf_t    *cacf;\n    ngx_rtmp_record_app_conf_t  *racf;\n\n    cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);\n    racf = cacf->app_conf[ngx_rtmp_record_module.ctx_index];\n\n    if (ngx_http_arg(r, (u_char *) \"rec\", sizeof(\"rec\") - 1, &rec) != NGX_OK) {\n        rec.len = 0;\n    }\n\n    rn = ngx_rtmp_record_find(racf, &rec);\n    if (rn == NGX_CONF_UNSET_UINT) {\n        return \"Recorder not found\";\n    }\n\n    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);\n\n    if (ctx->method.len == sizeof(\"start\") - 1 &&\n        ngx_strncmp(ctx->method.data, \"start\", ctx->method.len) == 0)\n    {\n        rc = ngx_rtmp_record_open(s, rn, &ctx->path);\n\n    } else if (ctx->method.len == sizeof(\"stop\") - 1 &&\n               ngx_strncmp(ctx->method.data, \"stop\", ctx->method.len) == 0)\n    {\n        rc = ngx_rtmp_record_close(s, rn, &ctx->path);\n\n    } else {\n        return \"Undefined method\";\n    }\n\n    if (rc == NGX_ERROR) {\n        return \"Recorder error\";\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic const char *\nngx_rtmp_control_drop_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_control_ctx_t  *ctx;\n\n    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);\n\n    ngx_rtmp_finalize_session(s);\n\n    ++ctx->count;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic const char *\nngx_rtmp_control_redirect_handler(ngx_http_request_t *r, ngx_rtmp_session_t *s)\n{\n    ngx_str_t                 name;\n    ngx_rtmp_play_t           vplay;\n    ngx_rtmp_publish_t        vpublish;\n    ngx_rtmp_live_ctx_t      *lctx;\n    ngx_rtmp_control_ctx_t   *ctx;\n    ngx_rtmp_close_stream_t   vc;\n\n    if (ngx_http_arg(r, (u_char *) \"newname\", sizeof(\"newname\") - 1, &name)\n        != NGX_OK)\n    {\n        return \"newname not specified\";\n    }\n\n    if (name.len >= NGX_RTMP_MAX_NAME) {\n        name.len = NGX_RTMP_MAX_NAME - 1;\n    }\n\n    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);\n    ctx->count++;\n\n    ngx_memzero(&vc, sizeof(ngx_rtmp_close_stream_t));\n\n    /* close_stream should be synchronous */\n    ngx_rtmp_close_stream(s, &vc);\n\n    lctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n\n    if (lctx && lctx->publishing) {\n        /* publish */\n\n        ngx_memzero(&vpublish, sizeof(ngx_rtmp_publish_t));\n\n        ngx_memcpy(vpublish.name, name.data, name.len);\n\n        ngx_rtmp_cmd_fill_args(vpublish.name, vpublish.args);\n\n        if (ngx_rtmp_publish(s, &vpublish) != NGX_OK) {\n            return \"publish failed\";\n        }\n\n    } else {\n        /* play */\n\n        ngx_memzero(&vplay, sizeof(ngx_rtmp_play_t));\n\n        ngx_memcpy(vplay.name, name.data, name.len);\n\n        ngx_rtmp_cmd_fill_args(vplay.name, vplay.args);\n\n        if (ngx_rtmp_play(s, &vplay) != NGX_OK) {\n            return \"play failed\";\n        }\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic const char *\nngx_rtmp_control_walk_session(ngx_http_request_t *r,\n    ngx_rtmp_live_ctx_t *lctx)\n{\n    ngx_str_t                addr, *paddr, clientid;\n    ngx_rtmp_session_t      *s, **ss;\n    ngx_rtmp_control_ctx_t  *ctx;\n\n    s = lctx->session;\n\n    if (s == NULL || s->connection == NULL) {\n        return NGX_CONF_OK;\n    }\n\n    if (ngx_http_arg(r, (u_char *) \"addr\", sizeof(\"addr\") - 1, &addr)\n        == NGX_OK)\n    {\n        paddr = &s->connection->addr_text;\n        if (paddr->len != addr.len ||\n            ngx_strncmp(paddr->data, addr.data, addr.len))\n        {\n            return NGX_CONF_OK;\n        }\n    }\n\n    if (ngx_http_arg(r, (u_char *) \"clientid\", sizeof(\"clientid\") - 1,\n                     &clientid)\n        == NGX_OK)\n    {\n        if (s->connection->number !=\n            (ngx_uint_t) ngx_atoi(clientid.data, clientid.len))\n        {\n            return NGX_CONF_OK;\n        }\n    }\n\n    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);\n\n    switch (ctx->filter) {\n        case NGX_RTMP_CONTROL_FILTER_PUBLISHER:\n            if (!lctx->publishing) {\n                return NGX_CONF_OK;\n            }\n            break;\n\n        case NGX_RTMP_CONTROL_FILTER_SUBSCRIBER:\n            if (lctx->publishing) {\n                return NGX_CONF_OK;\n            }\n            break;\n\n        case NGX_RTMP_CONTROL_FILTER_CLIENT:\n            break;\n    }\n\n    ss = ngx_array_push(&ctx->sessions);\n    if (ss == NULL) {\n        return \"allocation error\";\n    }\n\n    *ss = s;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic const char *\nngx_rtmp_control_walk_stream(ngx_http_request_t *r,\n    ngx_rtmp_live_stream_t *ls)\n{\n    const char           *s;\n    ngx_rtmp_live_ctx_t  *lctx;\n\n    for (lctx = ls->ctx; lctx; lctx = lctx->next) {\n        s = ngx_rtmp_control_walk_session(r, lctx);\n        if (s != NGX_CONF_OK) {\n            return s;\n        }\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic const char *\nngx_rtmp_control_walk_app(ngx_http_request_t *r,\n    ngx_rtmp_core_app_conf_t *cacf)\n{\n    size_t                     len;\n    ngx_str_t                  name;\n    const char                *s;\n    ngx_uint_t                 n;\n    ngx_rtmp_live_stream_t    *ls;\n    ngx_rtmp_live_app_conf_t  *lacf;\n\n    lacf = cacf->app_conf[ngx_rtmp_live_module.ctx_index];\n\n    if (ngx_http_arg(r, (u_char *) \"name\", sizeof(\"name\") - 1, &name) != NGX_OK)\n    {\n        for (n = 0; n < (ngx_uint_t) lacf->nbuckets; ++n) {\n            for (ls = lacf->streams[n]; ls; ls = ls->next) {\n                s = ngx_rtmp_control_walk_stream(r, ls);\n                if (s != NGX_CONF_OK) {\n                    return s;\n                }\n            }\n        }\n\n        return NGX_CONF_OK;\n    }\n\n    for (ls = lacf->streams[ngx_hash_key(name.data, name.len) % lacf->nbuckets];\n         ls; ls = ls->next) \n    {\n        len = ngx_strlen(ls->name);\n        if (name.len != len || ngx_strncmp(name.data, ls->name, name.len)) {\n            continue;\n        }\n\n        s = ngx_rtmp_control_walk_stream(r, ls);\n        if (s != NGX_CONF_OK) {\n            return s;\n        }\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic const char *\nngx_rtmp_control_walk_server(ngx_http_request_t *r,\n    ngx_rtmp_core_srv_conf_t *cscf)\n{\n    ngx_str_t                   app;\n    ngx_uint_t                  n;\n    const char                 *s;\n    ngx_rtmp_core_app_conf_t  **pcacf;\n\n    if (ngx_http_arg(r, (u_char *) \"app\", sizeof(\"app\") - 1, &app) != NGX_OK) {\n        app.len = 0;\n    }\n\n    pcacf = cscf->applications.elts;\n\n    for (n = 0; n < cscf->applications.nelts; ++n, ++pcacf) {\n        if (app.len && ((*pcacf)->name.len != app.len ||\n                        ngx_strncmp((*pcacf)->name.data, app.data, app.len)))\n        {\n            continue;\n        }\n\n        s = ngx_rtmp_control_walk_app(r, *pcacf);\n        if (s != NGX_CONF_OK) {\n            return s;\n        }\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic const char *\nngx_rtmp_control_walk(ngx_http_request_t *r, ngx_rtmp_control_handler_t h)\n{\n    ngx_rtmp_core_main_conf_t  *cmcf = ngx_rtmp_core_main_conf;\n\n    ngx_str_t                   srv;\n    ngx_uint_t                  sn, n;\n    const char                 *msg;\n    ngx_rtmp_session_t        **s;\n    ngx_rtmp_control_ctx_t     *ctx;\n    ngx_rtmp_core_srv_conf_t  **pcscf;\n\n    sn = 0;\n    if (ngx_http_arg(r, (u_char *) \"srv\", sizeof(\"srv\") - 1, &srv) == NGX_OK) {\n        sn = ngx_atoi(srv.data, srv.len);\n    }\n\n    if (sn >= cmcf->servers.nelts) {\n        return \"Server index out of range\";\n    }\n\n    pcscf  = cmcf->servers.elts;\n    pcscf += sn;\n\n    msg = ngx_rtmp_control_walk_server(r, *pcscf);\n    if (msg != NGX_CONF_OK) {\n        return msg;\n    }\n\n    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);\n\n    s = ctx->sessions.elts;\n    for (n = 0; n < ctx->sessions.nelts; n++) {\n        msg = h(r, s[n]);\n        if (msg != NGX_CONF_OK) {\n            return msg;\n        }\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_control_record(ngx_http_request_t *r, ngx_str_t *method)\n{\n    ngx_buf_t               *b;\n    const char              *msg;\n    ngx_chain_t              cl;\n    ngx_rtmp_control_ctx_t  *ctx;\n\n    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);\n    ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;\n\n    msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_record_handler);\n    if (msg != NGX_CONF_OK) {\n        goto error;\n    }\n\n    if (ctx->path.len == 0) {\n        return NGX_HTTP_NO_CONTENT;\n    }\n\n    /* output record path */\n\n    r->headers_out.status = NGX_HTTP_OK;\n    r->headers_out.content_length_n = ctx->path.len;\n\n    b = ngx_create_temp_buf(r->pool, ctx->path.len);\n    if (b == NULL) {\n        goto error;\n    }\n\n    ngx_memzero(&cl, sizeof(cl));\n    cl.buf = b;\n\n    b->last = ngx_cpymem(b->pos, ctx->path.data, ctx->path.len);\n    b->last_buf = 1;\n\n    ngx_http_send_header(r);\n\n    return ngx_http_output_filter(r, &cl);\n\nerror:\n    return NGX_HTTP_INTERNAL_SERVER_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_control_drop(ngx_http_request_t *r, ngx_str_t *method)\n{\n    size_t                   len;\n    u_char                  *p;\n    ngx_buf_t               *b;\n    ngx_chain_t              cl;\n    const char              *msg;\n    ngx_rtmp_control_ctx_t  *ctx;\n\n    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);\n\n    if (ctx->method.len == sizeof(\"publisher\") - 1 &&\n        ngx_memcmp(ctx->method.data, \"publisher\", ctx->method.len) == 0)\n    {\n        ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;\n\n    } else if (ctx->method.len == sizeof(\"subscriber\") - 1 &&\n               ngx_memcmp(ctx->method.data, \"subscriber\", ctx->method.len)\n               == 0)\n    {\n        ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;\n\n    } else if (method->len == sizeof(\"client\") - 1 &&\n               ngx_memcmp(ctx->method.data, \"client\", ctx->method.len) == 0)\n    {\n        ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;\n\n    } else {\n        msg = \"Undefined filter\";\n        goto error;\n    }\n\n    msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_drop_handler);\n    if (msg != NGX_CONF_OK) {\n        goto error;\n    }\n\n    /* output count */\n\n    len = NGX_INT_T_LEN;\n\n    p = ngx_palloc(r->connection->pool, len);\n    if (p == NULL) {\n        return NGX_ERROR;\n    }\n\n    len = (size_t) (ngx_snprintf(p, len, \"%ui\", ctx->count) - p);\n\n    r->headers_out.status = NGX_HTTP_OK;\n    r->headers_out.content_length_n = len;\n\n    b = ngx_calloc_buf(r->pool);\n    if (b == NULL) {\n        goto error;\n    }\n\n    b->start = b->pos = p;\n    b->end = b->last = p + len;\n    b->temporary = 1;\n    b->last_buf = 1;\n\n    ngx_memzero(&cl, sizeof(cl));\n    cl.buf = b;\n\n    ngx_http_send_header(r);\n\n    return ngx_http_output_filter(r, &cl);\n\nerror:\n    return NGX_HTTP_INTERNAL_SERVER_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_control_redirect(ngx_http_request_t *r, ngx_str_t *method)\n{\n    size_t                   len;\n    u_char                  *p;\n    ngx_buf_t               *b;\n    ngx_chain_t              cl;\n    const char              *msg;\n    ngx_rtmp_control_ctx_t  *ctx;\n\n    ctx = ngx_http_get_module_ctx(r, ngx_rtmp_control_module);\n\n    if (ctx->method.len == sizeof(\"publisher\") - 1 &&\n        ngx_memcmp(ctx->method.data, \"publisher\", ctx->method.len) == 0)\n    {\n        ctx->filter = NGX_RTMP_CONTROL_FILTER_PUBLISHER;\n\n    } else if (ctx->method.len == sizeof(\"subscriber\") - 1 &&\n               ngx_memcmp(ctx->method.data, \"subscriber\", ctx->method.len)\n               == 0)\n    {\n        ctx->filter = NGX_RTMP_CONTROL_FILTER_SUBSCRIBER;\n\n    } else if (ctx->method.len == sizeof(\"client\") - 1 &&\n               ngx_memcmp(ctx->method.data, \"client\", ctx->method.len) == 0)\n    {\n        ctx->filter = NGX_RTMP_CONTROL_FILTER_CLIENT;\n\n    } else {\n        msg = \"Undefined filter\";\n        goto error;\n    }\n\n    msg = ngx_rtmp_control_walk(r, ngx_rtmp_control_redirect_handler);\n    if (msg != NGX_CONF_OK) {\n        goto error;\n    }\n\n    /* output count */\n\n    len = NGX_INT_T_LEN;\n\n    p = ngx_palloc(r->connection->pool, len);\n    if (p == NULL) {\n        goto error;\n    }\n\n    len = (size_t) (ngx_snprintf(p, len, \"%ui\", ctx->count) - p);\n\n    r->headers_out.status = NGX_HTTP_OK;\n    r->headers_out.content_length_n = len;\n\n    b = ngx_calloc_buf(r->pool);\n    if (b == NULL) {\n        goto error;\n    }\n\n    b->start = b->pos = p;\n    b->end = b->last = p + len;\n    b->temporary = 1;\n    b->last_buf = 1;\n\n    ngx_memzero(&cl, sizeof(cl));\n    cl.buf = b;\n\n    ngx_http_send_header(r);\n\n    return ngx_http_output_filter(r, &cl);\n\nerror:\n    return NGX_HTTP_INTERNAL_SERVER_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_control_handler(ngx_http_request_t *r)\n{\n    u_char                       *p;\n    ngx_str_t                     section, method;\n    ngx_uint_t                    n;\n    ngx_rtmp_control_ctx_t       *ctx;\n    ngx_rtmp_control_loc_conf_t  *llcf;\n\n    llcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_control_module);\n    if (llcf->control == 0) {\n        return NGX_DECLINED;\n    }\n\n    /* uri format: .../section/method?args */\n\n    ngx_str_null(&section);\n    ngx_str_null(&method);\n\n    for (n = r->uri.len; n; --n) {\n        p = &r->uri.data[n - 1];\n\n        if (*p != '/') {\n            continue;\n        }\n\n        if (method.data) {\n            section.data = p + 1;\n            section.len  = method.data - section.data - 1;\n            break;\n        }\n\n        method.data = p + 1;\n        method.len  = r->uri.data + r->uri.len - method.data;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, r->connection->log, 0,\n                   \"rtmp_control: section='%V' method='%V'\",\n                   &section, &method);\n\n    ctx = ngx_pcalloc(r->pool, sizeof(ngx_rtmp_control_ctx_t));\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_http_set_ctx(r, ctx, ngx_rtmp_control_module);\n\n    if (ngx_array_init(&ctx->sessions, r->pool, 1, sizeof(void *)) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    ctx->method = method;\n\n#define NGX_RTMP_CONTROL_SECTION(flag, secname)                             \\\n    if (llcf->control & NGX_RTMP_CONTROL_##flag &&                          \\\n        section.len == sizeof(#secname) - 1 &&                              \\\n        ngx_strncmp(section.data, #secname, sizeof(#secname) - 1) == 0)     \\\n    {                                                                       \\\n        return ngx_rtmp_control_##secname(r, &method);                      \\\n    }\n\n    NGX_RTMP_CONTROL_SECTION(RECORD, record);\n    NGX_RTMP_CONTROL_SECTION(DROP, drop);\n    NGX_RTMP_CONTROL_SECTION(REDIRECT, redirect);\n\n#undef NGX_RTMP_CONTROL_SECTION\n\n    return NGX_DECLINED;\n}\n\n\nstatic void *\nngx_rtmp_control_create_loc_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_control_loc_conf_t  *conf;\n\n    conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_control_loc_conf_t));\n    if (conf == NULL) {\n        return NULL;\n    }\n\n    conf->control = 0;\n\n    return conf;\n}\n\n\nstatic char *\nngx_rtmp_control_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_control_loc_conf_t  *prev = parent;\n    ngx_rtmp_control_loc_conf_t  *conf = child;\n\n    ngx_conf_merge_bitmask_value(conf->control, prev->control, 0);\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_rtmp_control(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_http_core_loc_conf_t  *clcf;\n\n    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);\n    clcf->handler = ngx_rtmp_control_handler;\n\n    return ngx_conf_set_bitmask_slot(cf, cmd, conf);\n}\n"
  },
  {
    "path": "ngx_rtmp_core_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_event.h>\n#include <nginx.h>\n#include \"ngx_rtmp.h\"\n\n\nstatic void *ngx_rtmp_core_create_main_conf(ngx_conf_t *cf);\nstatic void *ngx_rtmp_core_create_srv_conf(ngx_conf_t *cf);\nstatic char *ngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent,\n    void *child);\nstatic void *ngx_rtmp_core_create_app_conf(ngx_conf_t *cf);\nstatic char *ngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent,\n    void *child);\nstatic char *ngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd,\n    void *conf);\nstatic char *ngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,\n    void *conf);\nstatic char *ngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd,\n    void *conf);\n\n\nngx_rtmp_core_main_conf_t      *ngx_rtmp_core_main_conf;\n\n\nstatic ngx_conf_deprecated_t  ngx_conf_deprecated_so_keepalive = {\n    ngx_conf_deprecated, \"so_keepalive\",\n    \"so_keepalive\\\" parameter of the \\\"listen\"\n};\n\n\nstatic ngx_command_t  ngx_rtmp_core_commands[] = {\n\n    { ngx_string(\"server\"),\n      NGX_RTMP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS,\n      ngx_rtmp_core_server,\n      0,\n      0,\n      NULL },\n\n    { ngx_string(\"listen\"),\n      NGX_RTMP_SRV_CONF|NGX_CONF_TAKE12,\n      ngx_rtmp_core_listen,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"application\"),\n      NGX_RTMP_SRV_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,\n      ngx_rtmp_core_application,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"so_keepalive\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_FLAG,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, so_keepalive),\n      &ngx_conf_deprecated_so_keepalive },\n\n    { ngx_string(\"timeout\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, timeout),\n      NULL },\n\n    { ngx_string(\"ping\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, ping),\n      NULL },\n\n    { ngx_string(\"ping_timeout\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, ping_timeout),\n      NULL },\n\n    { ngx_string(\"max_streams\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_num_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, max_streams),\n      NULL },\n\n    { ngx_string(\"ack_window\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_num_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, ack_window),\n      NULL },\n\n    { ngx_string(\"chunk_size\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_num_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, chunk_size),\n      NULL },\n\n    { ngx_string(\"max_message\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_size_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, max_message),\n      NULL },\n\n    { ngx_string(\"out_queue\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_size_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, out_queue),\n      NULL },\n\n    { ngx_string(\"out_cork\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_size_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, out_cork),\n      NULL },\n\n    { ngx_string(\"busy\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, busy),\n      NULL },\n\n    /* time fixes are needed for flash clients */\n    { ngx_string(\"play_time_fix\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, play_time_fix),\n      NULL },\n\n    { ngx_string(\"publish_time_fix\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, publish_time_fix),\n      NULL },\n\n    { ngx_string(\"buflen\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_core_srv_conf_t, buflen),\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_core_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    NULL,                                   /* postconfiguration */\n    ngx_rtmp_core_create_main_conf,         /* create main configuration */\n    NULL,                                   /* init main configuration */\n    ngx_rtmp_core_create_srv_conf,          /* create server configuration */\n    ngx_rtmp_core_merge_srv_conf,           /* merge server configuration */\n    ngx_rtmp_core_create_app_conf,          /* create app configuration */\n    ngx_rtmp_core_merge_app_conf            /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_core_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_core_module_ctx,             /* module context */\n    ngx_rtmp_core_commands,                /* module directives */\n    NGX_RTMP_MODULE,                       /* module type */\n    NULL,                                  /* init master */\n    NULL,                                  /* init module */\n    NULL,                                  /* init process */\n    NULL,                                  /* init thread */\n    NULL,                                  /* exit thread */\n    NULL,                                  /* exit process */\n    NULL,                                  /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void *\nngx_rtmp_core_create_main_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t  *cmcf;\n\n    cmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_main_conf_t));\n    if (cmcf == NULL) {\n        return NULL;\n    }\n\n    ngx_rtmp_core_main_conf = cmcf;\n\n    if (ngx_array_init(&cmcf->servers, cf->pool, 4,\n                       sizeof(ngx_rtmp_core_srv_conf_t *))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n    if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_rtmp_listen_t))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n    return cmcf;\n}\n\n\nstatic void *\nngx_rtmp_core_create_srv_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_srv_conf_t   *conf;\n\n    conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_srv_conf_t));\n    if (conf == NULL) {\n        return NULL;\n    }\n\n    if (ngx_array_init(&conf->applications, cf->pool, 4,\n                       sizeof(ngx_rtmp_core_app_conf_t *))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n    conf->timeout = NGX_CONF_UNSET_MSEC;\n    conf->ping = NGX_CONF_UNSET_MSEC;\n    conf->ping_timeout = NGX_CONF_UNSET_MSEC;\n    conf->so_keepalive = NGX_CONF_UNSET;\n    conf->max_streams = NGX_CONF_UNSET;\n    conf->chunk_size = NGX_CONF_UNSET;\n    conf->ack_window = NGX_CONF_UNSET_UINT;\n    conf->max_message = NGX_CONF_UNSET_SIZE;\n    conf->out_queue = NGX_CONF_UNSET_SIZE;\n    conf->out_cork = NGX_CONF_UNSET_SIZE;\n    conf->play_time_fix = NGX_CONF_UNSET;\n    conf->publish_time_fix = NGX_CONF_UNSET;\n    conf->buflen = NGX_CONF_UNSET_MSEC;\n    conf->busy = NGX_CONF_UNSET;\n\n    return conf;\n}\n\n\nstatic char *\nngx_rtmp_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_core_srv_conf_t *prev = parent;\n    ngx_rtmp_core_srv_conf_t *conf = child;\n\n    ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);\n    ngx_conf_merge_msec_value(conf->ping, prev->ping, 60000);\n    ngx_conf_merge_msec_value(conf->ping_timeout, prev->ping_timeout, 30000);\n\n    ngx_conf_merge_value(conf->so_keepalive, prev->so_keepalive, 0);\n    ngx_conf_merge_value(conf->max_streams, prev->max_streams, 32);\n    ngx_conf_merge_value(conf->chunk_size, prev->chunk_size, 4096);\n    ngx_conf_merge_uint_value(conf->ack_window, prev->ack_window, 5000000);\n    ngx_conf_merge_size_value(conf->max_message, prev->max_message,\n            1 * 1024 * 1024);\n    ngx_conf_merge_size_value(conf->out_queue, prev->out_queue, 256);\n    ngx_conf_merge_size_value(conf->out_cork, prev->out_cork,\n            conf->out_queue / 8);\n    ngx_conf_merge_value(conf->play_time_fix, prev->play_time_fix, 1);\n    ngx_conf_merge_value(conf->publish_time_fix, prev->publish_time_fix, 1);\n    ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 1000);\n    ngx_conf_merge_value(conf->busy, prev->busy, 0);\n\n    if (prev->pool == NULL) {\n        prev->pool = ngx_create_pool(4096, &cf->cycle->new_log);\n        if (prev->pool == NULL) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    conf->pool = prev->pool;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic void *\nngx_rtmp_core_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_app_conf_t   *conf;\n\n    conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_core_app_conf_t));\n    if (conf == NULL) {\n        return NULL;\n    }\n\n    if (ngx_array_init(&conf->applications, cf->pool, 1,\n                       sizeof(ngx_rtmp_core_app_conf_t *))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n    return conf;\n}\n\n\nstatic char *\nngx_rtmp_core_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_core_app_conf_t *prev = parent;\n    ngx_rtmp_core_app_conf_t *conf = child;\n\n    (void)prev;\n    (void)conf;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_rtmp_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    char                       *rv;\n    void                       *mconf;\n    ngx_uint_t                  m;\n    ngx_conf_t                  pcf;\n    ngx_module_t              **modules;\n    ngx_rtmp_module_t          *module;\n    ngx_rtmp_conf_ctx_t        *ctx, *rtmp_ctx;\n    ngx_rtmp_core_srv_conf_t   *cscf, **cscfp;\n    ngx_rtmp_core_main_conf_t  *cmcf;\n\n    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));\n    if (ctx == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    rtmp_ctx = cf->ctx;\n    ctx->main_conf = rtmp_ctx->main_conf;\n\n    /* the server{}'s srv_conf */\n\n    ctx->srv_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);\n    if (ctx->srv_conf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);\n    if (ctx->app_conf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n#if (nginx_version >= 1009011)\n    modules = cf->cycle->modules;\n#else\n    modules = ngx_modules;\n#endif\n\n    for (m = 0; modules[m]; m++) {\n        if (modules[m]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n\n        module = modules[m]->ctx;\n\n        if (module->create_srv_conf) {\n            mconf = module->create_srv_conf(cf);\n            if (mconf == NULL) {\n                return NGX_CONF_ERROR;\n            }\n\n            ctx->srv_conf[modules[m]->ctx_index] = mconf;\n        }\n\n        if (module->create_app_conf) {\n            mconf = module->create_app_conf(cf);\n            if (mconf == NULL) {\n                return NGX_CONF_ERROR;\n            }\n\n            ctx->app_conf[modules[m]->ctx_index] = mconf;\n        }\n    }\n\n    /* the server configuration context */\n\n    cscf = ctx->srv_conf[ngx_rtmp_core_module.ctx_index];\n    cscf->ctx = ctx;\n\n    cmcf = ctx->main_conf[ngx_rtmp_core_module.ctx_index];\n\n    cscfp = ngx_array_push(&cmcf->servers);\n    if (cscfp == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    *cscfp = cscf;\n\n\n    /* parse inside server{} */\n\n    pcf = *cf;\n    cf->ctx = ctx;\n    cf->cmd_type = NGX_RTMP_SRV_CONF;\n\n    rv = ngx_conf_parse(cf, NULL);\n\n    *cf = pcf;\n\n    return rv;\n}\n\n\nstatic char *\nngx_rtmp_core_application(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    char                       *rv;\n    ngx_int_t                   i;\n    ngx_str_t                  *value;\n    ngx_conf_t                  save;\n    ngx_module_t              **modules;\n    ngx_rtmp_module_t          *module;\n    ngx_rtmp_conf_ctx_t        *ctx, *pctx;\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_rtmp_core_app_conf_t   *cacf, **cacfp;\n\n    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));\n    if (ctx == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    pctx = cf->ctx;\n    ctx->main_conf = pctx->main_conf;\n    ctx->srv_conf = pctx->srv_conf;\n\n    ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);\n    if (ctx->app_conf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n#if (nginx_version >= 1009011)\n    modules = cf->cycle->modules;\n#else\n    modules = ngx_modules;\n#endif\n\n    for (i = 0; modules[i]; i++) {\n        if (modules[i]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n\n        module = modules[i]->ctx;\n\n        if (module->create_app_conf) {\n            ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf);\n            if (ctx->app_conf[modules[i]->ctx_index] == NULL) {\n                return NGX_CONF_ERROR;\n            }\n        }\n    }\n\n    cacf = ctx->app_conf[ngx_rtmp_core_module.ctx_index];\n    cacf->app_conf = ctx->app_conf;\n\n    value = cf->args->elts;\n\n    cacf->name = value[1];\n    cscf = pctx->srv_conf[ngx_rtmp_core_module.ctx_index];\n\n    cacfp = ngx_array_push(&cscf->applications);\n    if (cacfp == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    *cacfp = cacf;\n\n    save = *cf;\n    cf->ctx = ctx;\n    cf->cmd_type = NGX_RTMP_APP_CONF;\n\n    rv = ngx_conf_parse(cf, NULL);\n\n    *cf= save;\n\n    return rv;\n}\n\n\nstatic char *\nngx_rtmp_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    size_t                      len, off;\n    in_port_t                   port;\n    ngx_str_t                  *value;\n    ngx_url_t                   u;\n    ngx_uint_t                  i, m;\n    ngx_module_t              **modules;\n    struct sockaddr            *sa;\n    ngx_rtmp_listen_t          *ls;\n    struct sockaddr_in         *sin;\n    ngx_rtmp_core_main_conf_t  *cmcf;\n#if (NGX_HAVE_INET6)\n    struct sockaddr_in6        *sin6;\n#endif\n\n    value = cf->args->elts;\n\n    ngx_memzero(&u, sizeof(ngx_url_t));\n\n    u.url = value[1];\n    u.listen = 1;\n\n    if (ngx_parse_url(cf->pool, &u) != NGX_OK) {\n        if (u.err) {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                               \"%s in \\\"%V\\\" of the \\\"listen\\\" directive\",\n                               u.err, &u.url);\n        }\n\n        return NGX_CONF_ERROR;\n    }\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    ls = cmcf->listen.elts;\n\n    for (i = 0; i < cmcf->listen.nelts; i++) {\n\n        sa = (struct sockaddr *) ls[i].sockaddr;\n\n        if (sa->sa_family != u.family) {\n            continue;\n        }\n\n        switch (sa->sa_family) {\n\n#if (NGX_HAVE_INET6)\n        case AF_INET6:\n            off = offsetof(struct sockaddr_in6, sin6_addr);\n            len = 16;\n            sin6 = (struct sockaddr_in6 *) sa;\n            port = sin6->sin6_port;\n            break;\n#endif\n\n        default: /* AF_INET */\n            off = offsetof(struct sockaddr_in, sin_addr);\n            len = 4;\n            sin = (struct sockaddr_in *) sa;\n            port = sin->sin_port;\n            break;\n        }\n\n        if (ngx_memcmp(ls[i].sockaddr + off, (u_char *) &u.sockaddr + off, len)\n            != 0)\n        {\n            continue;\n        }\n\n        if (port != u.port) {\n            continue;\n        }\n\n        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                           \"duplicate \\\"%V\\\" address and port pair\", &u.url);\n        return NGX_CONF_ERROR;\n    }\n\n    ls = ngx_array_push(&cmcf->listen);\n    if (ls == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    ngx_memzero(ls, sizeof(ngx_rtmp_listen_t));\n\n    ngx_memcpy(ls->sockaddr, (u_char *) &u.sockaddr, u.socklen);\n\n    ls->socklen = u.socklen;\n    ls->wildcard = u.wildcard;\n    ls->ctx = cf->ctx;\n\n#if (nginx_version >= 1009011)\n    modules = cf->cycle->modules;\n#else\n    modules = ngx_modules;\n#endif\n\n    for (m = 0; modules[m]; m++) {\n        if (modules[m]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n    }\n\n    for (i = 2; i < cf->args->nelts; i++) {\n\n        if (ngx_strcmp(value[i].data, \"bind\") == 0) {\n            ls->bind = 1;\n            continue;\n        }\n\n        if (ngx_strncmp(value[i].data, \"ipv6only=o\", 10) == 0) {\n#if (NGX_HAVE_INET6 && defined IPV6_V6ONLY)\n            struct sockaddr  *sa;\n            u_char            buf[NGX_SOCKADDR_STRLEN];\n\n            sa = (struct sockaddr *) ls->sockaddr;\n\n            if (sa->sa_family == AF_INET6) {\n\n                if (ngx_strcmp(&value[i].data[10], \"n\") == 0) {\n                    ls->ipv6only = 1;\n\n                } else if (ngx_strcmp(&value[i].data[10], \"ff\") == 0) {\n                    ls->ipv6only = 0;\n\n                } else {\n                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                       \"invalid ipv6only flags \\\"%s\\\"\",\n                                       &value[i].data[9]);\n                    return NGX_CONF_ERROR;\n                }\n\n                ls->bind = 1;\n\n            } else {\n                len = ngx_sock_ntop(sa,\n#if (nginx_version >= 1005003)\n                                    ls->socklen,\n#endif\n                                    buf, NGX_SOCKADDR_STRLEN, 1);\n\n                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                   \"ipv6only is not supported \"\n                                   \"on addr \\\"%*s\\\", ignored\", len, buf);\n            }\n\n            continue;\n#else\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                               \"bind ipv6only is not supported \"\n                               \"on this platform\");\n            return NGX_CONF_ERROR;\n#endif\n        }\n\n        if (ngx_strncmp(value[i].data, \"so_keepalive=\", 13) == 0) {\n\n            if (ngx_strcmp(&value[i].data[13], \"on\") == 0) {\n                ls->so_keepalive = 1;\n\n            } else if (ngx_strcmp(&value[i].data[13], \"off\") == 0) {\n                ls->so_keepalive = 2;\n\n            } else {\n\n#if (NGX_HAVE_KEEPALIVE_TUNABLE)\n                u_char     *p, *end;\n                ngx_str_t   s;\n\n                end = value[i].data + value[i].len;\n                s.data = value[i].data + 13;\n\n                p = ngx_strlchr(s.data, end, ':');\n                if (p == NULL) {\n                    p = end;\n                }\n\n                if (p > s.data) {\n                    s.len = p - s.data;\n\n                    ls->tcp_keepidle = ngx_parse_time(&s, 1);\n                    if (ls->tcp_keepidle == (time_t) NGX_ERROR) {\n                        goto invalid_so_keepalive;\n                    }\n                }\n\n                s.data = (p < end) ? (p + 1) : end;\n\n                p = ngx_strlchr(s.data, end, ':');\n                if (p == NULL) {\n                    p = end;\n                }\n\n                if (p > s.data) {\n                    s.len = p - s.data;\n\n                    ls->tcp_keepintvl = ngx_parse_time(&s, 1);\n                    if (ls->tcp_keepintvl == (time_t) NGX_ERROR) {\n                        goto invalid_so_keepalive;\n                    }\n                }\n\n                s.data = (p < end) ? (p + 1) : end;\n\n                if (s.data < end) {\n                    s.len = end - s.data;\n\n                    ls->tcp_keepcnt = ngx_atoi(s.data, s.len);\n                    if (ls->tcp_keepcnt == NGX_ERROR) {\n                        goto invalid_so_keepalive;\n                    }\n                }\n\n                if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0\n                    && ls->tcp_keepcnt == 0)\n                {\n                    goto invalid_so_keepalive;\n                }\n\n                ls->so_keepalive = 1;\n\n#else\n\n                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                   \"the \\\"so_keepalive\\\" parameter accepts \"\n                                   \"only \\\"on\\\" or \\\"off\\\" on this platform\");\n                return NGX_CONF_ERROR;\n\n#endif\n            }\n\n            ls->bind = 1;\n\n            continue;\n\n#if (NGX_HAVE_KEEPALIVE_TUNABLE)\n        invalid_so_keepalive:\n\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                               \"invalid so_keepalive value: \\\"%s\\\"\",\n                               &value[i].data[13]);\n            return NGX_CONF_ERROR;\n#endif\n        }\n\n        if (ngx_strcmp(value[i].data, \"proxy_protocol\") == 0) {\n            ls->proxy_protocol = 1;\n            continue;\n        }\n\n        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                           \"the invalid \\\"%V\\\" parameter\", &value[i]);\n        return NGX_CONF_ERROR;\n    }\n\n    return NGX_CONF_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_eval.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_eval.h\"\n\n\n#define NGX_RTMP_EVAL_BUFLEN    16\n\n\nstatic void\nngx_rtmp_eval_session_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)\n{\n    *ret = *(ngx_str_t *) ((u_char *) ctx + e->offset);\n}\n\n\nstatic void\nngx_rtmp_eval_connection_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)\n{\n    ngx_rtmp_session_t  *s = ctx;\n\n    *ret = *(ngx_str_t *) ((u_char *) s->connection + e->offset);\n}\n\n\nngx_rtmp_eval_t ngx_rtmp_eval_session[] = {\n\n    { ngx_string(\"app\"),\n      ngx_rtmp_eval_session_str,\n      offsetof(ngx_rtmp_session_t, app) },\n\n    { ngx_string(\"flashver\"),\n      ngx_rtmp_eval_session_str,\n      offsetof(ngx_rtmp_session_t, flashver) },\n\n    { ngx_string(\"swfurl\"),\n      ngx_rtmp_eval_session_str,\n      offsetof(ngx_rtmp_session_t, swf_url) },\n\n    { ngx_string(\"tcurl\"),\n      ngx_rtmp_eval_session_str,\n      offsetof(ngx_rtmp_session_t, tc_url) },\n\n    { ngx_string(\"pageurl\"),\n      ngx_rtmp_eval_session_str,\n      offsetof(ngx_rtmp_session_t, page_url) },\n\n    { ngx_string(\"addr\"),\n      ngx_rtmp_eval_connection_str,\n      offsetof(ngx_connection_t, addr_text) },\n\n    ngx_rtmp_null_eval\n};\n\n\nstatic void\nngx_rtmp_eval_append(ngx_buf_t *b, void *data, size_t len, ngx_log_t *log)\n{\n    size_t  buf_len;\n\n    if (b->last + len > b->end) {\n        buf_len = 2 * (b->last - b->pos) + len;\n\n        b->start = ngx_alloc(buf_len, log);\n        if (b->start == NULL) {\n            return;\n        }\n\n        b->last = ngx_cpymem(b->start, b->pos, b->last - b->pos);\n        b->pos = b->start;\n        b->end = b->start + buf_len;\n    }\n\n    b->last = ngx_cpymem(b->last, data, len);\n}\n\n\nstatic void\nngx_rtmp_eval_append_var(void *ctx, ngx_buf_t *b, ngx_rtmp_eval_t **e,\n    ngx_str_t *name, ngx_log_t *log)\n{\n    ngx_str_t           v;\n    ngx_rtmp_eval_t    *ee;\n\n    for (; *e; ++e) {\n        for (ee = *e; ee->handler; ++ee) {\n            if (ee->name.len == name->len &&\n                ngx_memcmp(ee->name.data, name->data, name->len) == 0)\n            {\n                ee->handler(ctx, ee, &v);\n                ngx_rtmp_eval_append(b, v.data, v.len, log);\n            }\n        }\n    }\n}\n\n\nngx_int_t\nngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e, ngx_str_t *out,\n    ngx_log_t *log)\n{\n    u_char      c, *p;\n    ngx_str_t   name;\n    ngx_buf_t   b;\n    ngx_uint_t  n;\n\n    enum {\n        NORMAL,\n        ESCAPE,\n        NAME,\n        SNAME\n    } state = NORMAL;\n\n    b.pos = b.last = b.start = ngx_alloc(NGX_RTMP_EVAL_BUFLEN, log);\n    if (b.pos == NULL) {\n        return NGX_ERROR;\n    }\n\n    b.end = b.pos + NGX_RTMP_EVAL_BUFLEN;\n    name.data = NULL;\n\n    for (n = 0; n < in->len; ++n) {\n        p = &in->data[n];\n        c = *p;\n\n        switch (state) {\n            case SNAME:\n                if (c != '}') {\n                    continue;\n                }\n\n                name.len = p - name.data;\n                ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);\n\n                state = NORMAL;\n\n                continue;\n\n            case NAME:\n                if (c == '{' && name.data == p) {\n                    ++name.data;\n                    state = SNAME;\n                    continue;\n                }\n                if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {\n                    continue;\n                }\n\n                name.len = p - name.data;\n                ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);\n                /* fall through */\n\n            case NORMAL:\n                switch (c) {\n                    case '$':\n                        name.data = p + 1;\n                        state = NAME;\n                        continue;\n                    case '\\\\':\n                        state = ESCAPE;\n                        continue;\n                }\n\n                /* fall through */\n\n            case ESCAPE:\n                ngx_rtmp_eval_append(&b, &c, 1, log);\n                state = NORMAL;\n                break;\n\n        }\n    }\n\n    if (state == NAME) {\n        p = &in->data[n];\n        name.len = p - name.data;\n        ngx_rtmp_eval_append_var(ctx, &b, e, &name, log);\n    }\n\n    c = 0;\n    ngx_rtmp_eval_append(&b, &c, 1, log);\n\n    out->data = b.pos;\n    out->len  = b.last - b.pos - 1;\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_eval_streams(ngx_str_t *in)\n{\n#if !(NGX_WIN32)\n    ngx_int_t   mode, create, v, close_src;\n    ngx_fd_t    dst, src;\n    u_char     *path;\n\n    path = in->data;\n\n    while (*path >= '0' && *path <= '9') {\n        path++;\n    }\n\n    switch ((char) *path) {\n\n        case '>':\n\n            v = (path == in->data ? 1 : ngx_atoi(in->data, path - in->data));\n            if (v == NGX_ERROR) {\n                return NGX_ERROR;\n            }\n\n            dst = (ngx_fd_t) v;\n            mode = NGX_FILE_WRONLY;\n            create = NGX_FILE_TRUNCATE;\n            path++;\n\n            if (*path == (u_char) '>') {\n                mode = NGX_FILE_APPEND;\n                create = NGX_FILE_CREATE_OR_OPEN;\n                path++;\n            }\n\n            break;\n\n        case '<':\n\n            v = (path == in->data ? 0 : ngx_atoi(in->data, path - in->data));\n            if (v == NGX_ERROR) {\n                return NGX_ERROR;\n            }\n\n            dst = (ngx_fd_t) v;\n            mode = NGX_FILE_RDONLY;\n            create = NGX_FILE_OPEN;\n            path++;\n\n            break;\n\n        default:\n\n            return NGX_DONE;\n    }\n\n    if (*path == (u_char) '&') {\n\n        path++;\n        v = ngx_atoi(path, in->data + in->len - path);\n        if (v == NGX_ERROR) {\n            return NGX_ERROR;\n        }\n        src = (ngx_fd_t) v;\n        close_src = 0;\n\n    } else {\n\n        src = ngx_open_file(path, mode, create, NGX_FILE_DEFAULT_ACCESS);\n        if (src == NGX_INVALID_FILE) {\n            return NGX_ERROR;\n        }\n        close_src = 1;\n\n    }\n\n    if (src == dst) {\n        return NGX_OK;\n    }\n\n    dup2(src, dst);\n\n    if (close_src) {\n        ngx_close_file(src);\n    }\n    return NGX_OK;\n\n#else\n    return NGX_DONE;\n#endif\n}\n"
  },
  {
    "path": "ngx_rtmp_eval.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_EVAL_H_INCLUDED_\n#define _NGX_RTMP_EVAL_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\ntypedef struct ngx_rtmp_eval_s ngx_rtmp_eval_t;\n\n\ntypedef void (* ngx_rtmp_eval_pt)(void *ctx, ngx_rtmp_eval_t *e,\n                                  ngx_str_t *ret);\n\n\nstruct ngx_rtmp_eval_s {\n    ngx_str_t               name;\n    ngx_rtmp_eval_pt        handler;\n    ngx_uint_t              offset;\n};\n\n\n#define ngx_rtmp_null_eval  { ngx_null_string, NULL, 0 }\n\n\n/* standard session eval variables */\nextern ngx_rtmp_eval_t      ngx_rtmp_eval_session[];\n\n\nngx_int_t ngx_rtmp_eval(void *ctx, ngx_str_t *in, ngx_rtmp_eval_t **e,\n    ngx_str_t *out, ngx_log_t *log);\n\n\nngx_int_t ngx_rtmp_eval_streams(ngx_str_t *in);\n\n\n#endif /* _NGX_RTMP_EVAL_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_exec_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_record_module.h\"\n#include \"ngx_rtmp_eval.h\"\n#include <stdlib.h>\n\n#ifdef NGX_LINUX\n#include <unistd.h>\n#endif\n\n\n#if !(NGX_WIN32)\nstatic ngx_rtmp_publish_pt              next_publish;\nstatic ngx_rtmp_play_pt                 next_play;\nstatic ngx_rtmp_close_stream_pt         next_close_stream;\nstatic ngx_rtmp_record_done_pt          next_record_done;\n#endif\n\n\nstatic ngx_int_t ngx_rtmp_exec_init_process(ngx_cycle_t *cycle);\nstatic ngx_int_t ngx_rtmp_exec_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_exec_create_main_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_exec_init_main_conf(ngx_conf_t *cf, void *conf);\nstatic void * ngx_rtmp_exec_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_exec_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\n/*static char * ngx_rtmp_exec_block(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);*/\nstatic char * ngx_rtmp_exec_conf(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic char *ngx_rtmp_exec_kill_signal(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\n\n\n#define NGX_RTMP_EXEC_RESPAWN           0x01\n#define NGX_RTMP_EXEC_KILL              0x02\n\n\n#define NGX_RTMP_EXEC_PUBLISHING        0x01\n#define NGX_RTMP_EXEC_PLAYING           0x02\n\n\nenum {\n    NGX_RTMP_EXEC_PUSH,\n    NGX_RTMP_EXEC_PULL,\n\n    NGX_RTMP_EXEC_PUBLISH,\n    NGX_RTMP_EXEC_PUBLISH_DONE,\n    NGX_RTMP_EXEC_PLAY,\n    NGX_RTMP_EXEC_PLAY_DONE,\n    NGX_RTMP_EXEC_RECORD_DONE,\n\n    NGX_RTMP_EXEC_MAX,\n\n    NGX_RTMP_EXEC_STATIC\n};\n\n\ntypedef struct {\n    ngx_str_t                           id;\n    ngx_uint_t                          type;\n    ngx_str_t                           cmd;\n    ngx_array_t                         args;       /* ngx_str_t */\n    ngx_array_t                         names;\n} ngx_rtmp_exec_conf_t;\n\n\ntypedef struct {\n    ngx_rtmp_exec_conf_t               *conf;\n    ngx_log_t                          *log;\n    ngx_rtmp_eval_t                   **eval;\n    void                               *eval_ctx;\n    unsigned                            active:1;\n    unsigned                            managed:1;\n    ngx_pid_t                           pid;\n    ngx_pid_t                          *save_pid;\n    int                                 pipefd;\n    ngx_connection_t                    dummy_conn;  /*needed by ngx_xxx_event*/\n    ngx_event_t                         read_evt, write_evt;\n    ngx_event_t                         respawn_evt;\n    ngx_msec_t                          respawn_timeout;\n    ngx_int_t                           kill_signal;\n} ngx_rtmp_exec_t;\n\n\ntypedef struct {\n    ngx_array_t                         static_conf; /* ngx_rtmp_exec_conf_t */\n    ngx_array_t                         static_exec; /* ngx_rtmp_exec_t */\n    ngx_msec_t                          respawn_timeout;\n    ngx_int_t                           kill_signal;\n    ngx_log_t                          *log;\n} ngx_rtmp_exec_main_conf_t;\n\n\ntypedef struct ngx_rtmp_exec_pull_ctx_s  ngx_rtmp_exec_pull_ctx_t;\n\nstruct ngx_rtmp_exec_pull_ctx_s {\n    ngx_pool_t                         *pool;\n    ngx_uint_t                          counter;\n    ngx_str_t                           name;\n    ngx_str_t                           app;\n    ngx_array_t                         pull_exec;   /* ngx_rtmp_exec_t */\n    ngx_rtmp_exec_pull_ctx_t           *next;\n};\n\n\ntypedef struct {\n    ngx_int_t                           active;\n    ngx_array_t                         conf[NGX_RTMP_EXEC_MAX];\n                                                     /* ngx_rtmp_exec_conf_t */\n    ngx_flag_t                          respawn;\n    ngx_flag_t                          options;\n    ngx_uint_t                          nbuckets;\n    ngx_rtmp_exec_pull_ctx_t          **pull;\n} ngx_rtmp_exec_app_conf_t;\n\n\ntypedef struct {\n    ngx_uint_t                          flags;\n    ngx_str_t                           path;     /* /tmp/rec/myfile-123.flv */\n    ngx_str_t                           filename; /* myfile-123.flv */\n    ngx_str_t                           basename; /* myfile-123 */\n    ngx_str_t                           dirname;  /* /tmp/rec */\n    ngx_str_t                           recorder;\n    u_char                              name[NGX_RTMP_MAX_NAME];\n    u_char                              args[NGX_RTMP_MAX_ARGS];\n    ngx_array_t                         push_exec;   /* ngx_rtmp_exec_t */\n    ngx_rtmp_exec_pull_ctx_t           *pull;\n} ngx_rtmp_exec_ctx_t;\n\n\n#if !(NGX_WIN32)\nstatic void ngx_rtmp_exec_respawn(ngx_event_t *ev);\nstatic ngx_int_t ngx_rtmp_exec_kill(ngx_rtmp_exec_t *e, ngx_int_t kill_signal);\nstatic ngx_int_t ngx_rtmp_exec_run(ngx_rtmp_exec_t *e);\n#endif\n\n\nstatic ngx_command_t  ngx_rtmp_exec_commands[] = {\n/*\n    { ngx_string(\"exec_block\"),\n      NGX_RTMP_APP_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS|NGX_CONF_TAKE1,\n      ngx_rtmp_exec_block,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n*/\n    { ngx_string(\"exec\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, conf) +\n      NGX_RTMP_EXEC_PUSH * sizeof(ngx_array_t),\n      NULL },\n\n    { ngx_string(\"exec_push\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, conf) +\n      NGX_RTMP_EXEC_PUSH * sizeof(ngx_array_t),\n      NULL },\n\n    { ngx_string(\"exec_pull\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, conf) +\n      NGX_RTMP_EXEC_PULL * sizeof(ngx_array_t),\n      NULL },\n\n    { ngx_string(\"exec_publish\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, conf) +\n      NGX_RTMP_EXEC_PUBLISH * sizeof(ngx_array_t),\n      NULL },\n\n    { ngx_string(\"exec_publish_done\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, conf) +\n      NGX_RTMP_EXEC_PUBLISH_DONE * sizeof(ngx_array_t),\n      NULL },\n\n    { ngx_string(\"exec_play\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, conf) +\n      NGX_RTMP_EXEC_PLAY * sizeof(ngx_array_t),\n      NULL },\n\n    { ngx_string(\"exec_play_done\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, conf) +\n      NGX_RTMP_EXEC_PLAY_DONE * sizeof(ngx_array_t),\n      NULL },\n\n    { ngx_string(\"exec_record_done\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF|\n                         NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, conf) +\n      NGX_RTMP_EXEC_RECORD_DONE * sizeof(ngx_array_t),\n      NULL },\n\n    { ngx_string(\"exec_static\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_exec_conf,\n      NGX_RTMP_MAIN_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_main_conf_t, static_conf),\n      NULL },\n\n    { ngx_string(\"respawn\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, respawn),\n      NULL },\n\n    { ngx_string(\"respawn_timeout\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_MAIN_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_main_conf_t, respawn_timeout),\n      NULL },\n\n    { ngx_string(\"exec_kill_signal\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_exec_kill_signal,\n      NGX_RTMP_MAIN_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"exec_options\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_exec_app_conf_t, options),\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_exec_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_exec_postconfiguration,        /* postconfiguration */\n    ngx_rtmp_exec_create_main_conf,         /* create main configuration */\n    ngx_rtmp_exec_init_main_conf,           /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    ngx_rtmp_exec_create_app_conf,          /* create app configuration */\n    ngx_rtmp_exec_merge_app_conf            /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_exec_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_exec_module_ctx,              /* module context */\n    ngx_rtmp_exec_commands,                 /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    ngx_rtmp_exec_init_process,             /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void\nngx_rtmp_exec_eval_ctx_cstr(void *sctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)\n{\n    ngx_rtmp_session_t  *s = sctx;\n\n    ngx_rtmp_exec_ctx_t  *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n    if (ctx == NULL) {\n        ret->len = 0;\n        return;\n    }\n\n    ret->data = (u_char *) ctx + e->offset;\n    ret->len = ngx_strlen(ret->data);\n}\n\n\nstatic void\nngx_rtmp_exec_eval_ctx_str(void *sctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)\n{\n    ngx_rtmp_session_t  *s = sctx;\n\n    ngx_rtmp_exec_ctx_t  *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n    if (ctx == NULL) {\n        ret->len = 0;\n        return;\n    }\n\n    *ret = * (ngx_str_t *) ((u_char *) ctx + e->offset);\n}\n\n\nstatic void\nngx_rtmp_exec_eval_pctx_str(void *ctx, ngx_rtmp_eval_t *e, ngx_str_t *ret)\n{\n    *ret = *(ngx_str_t *) ((u_char *) ctx + e->offset);\n}\n\n\nstatic ngx_rtmp_eval_t ngx_rtmp_exec_push_specific_eval[] = {\n\n    { ngx_string(\"name\"),\n      ngx_rtmp_exec_eval_ctx_cstr,\n      offsetof(ngx_rtmp_exec_ctx_t, name) },\n\n    { ngx_string(\"args\"),\n      ngx_rtmp_exec_eval_ctx_cstr,\n      offsetof(ngx_rtmp_exec_ctx_t, args) },\n\n    ngx_rtmp_null_eval\n};\n\n\nstatic ngx_rtmp_eval_t * ngx_rtmp_exec_push_eval[] = {\n    ngx_rtmp_eval_session,\n    ngx_rtmp_exec_push_specific_eval,\n    NULL\n};\n\n\nstatic ngx_rtmp_eval_t ngx_rtmp_exec_pull_specific_eval[] = {\n\n    { ngx_string(\"name\"),\n      ngx_rtmp_exec_eval_pctx_str,\n      offsetof(ngx_rtmp_exec_pull_ctx_t, name) },\n\n    { ngx_string(\"app\"),\n      ngx_rtmp_exec_eval_pctx_str,\n      offsetof(ngx_rtmp_exec_pull_ctx_t, app) },\n\n    ngx_rtmp_null_eval\n};\n\n\nstatic ngx_rtmp_eval_t * ngx_rtmp_exec_pull_eval[] = {\n    ngx_rtmp_exec_pull_specific_eval,\n    NULL\n};\n\n\nstatic ngx_rtmp_eval_t ngx_rtmp_exec_event_specific_eval[] = {\n\n    { ngx_string(\"name\"),\n      ngx_rtmp_exec_eval_ctx_cstr,\n      offsetof(ngx_rtmp_exec_ctx_t, name) },\n\n    { ngx_string(\"args\"),\n      ngx_rtmp_exec_eval_ctx_cstr,\n      offsetof(ngx_rtmp_exec_ctx_t, args) },\n\n    { ngx_string(\"path\"),\n      ngx_rtmp_exec_eval_ctx_str,\n      offsetof(ngx_rtmp_exec_ctx_t, path) },\n\n    { ngx_string(\"filename\"),\n      ngx_rtmp_exec_eval_ctx_str,\n      offsetof(ngx_rtmp_exec_ctx_t, filename) },\n\n    { ngx_string(\"basename\"),\n      ngx_rtmp_exec_eval_ctx_str,\n      offsetof(ngx_rtmp_exec_ctx_t, basename) },\n\n    { ngx_string(\"dirname\"),\n      ngx_rtmp_exec_eval_ctx_str,\n      offsetof(ngx_rtmp_exec_ctx_t, dirname) },\n\n    { ngx_string(\"recorder\"),\n      ngx_rtmp_exec_eval_ctx_str,\n      offsetof(ngx_rtmp_exec_ctx_t, recorder) },\n\n    ngx_rtmp_null_eval\n};\n\n\nstatic ngx_rtmp_eval_t * ngx_rtmp_exec_event_eval[] = {\n    ngx_rtmp_eval_session,\n    ngx_rtmp_exec_event_specific_eval,\n    NULL\n};\n\n\nstatic void *\nngx_rtmp_exec_create_main_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_exec_main_conf_t     *emcf;\n\n    emcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_main_conf_t));\n    if (emcf == NULL) {\n        return NULL;\n    }\n\n    emcf->respawn_timeout = NGX_CONF_UNSET_MSEC;\n    emcf->kill_signal = NGX_CONF_UNSET;\n\n    if (ngx_array_init(&emcf->static_conf, cf->pool, 1,\n                       sizeof(ngx_rtmp_exec_conf_t)) != NGX_OK)\n    {\n        return NULL;\n    }\n\n    return emcf;\n}\n\n\nstatic char *\nngx_rtmp_exec_init_main_conf(ngx_conf_t *cf, void *conf)\n{\n    ngx_rtmp_exec_main_conf_t  *emcf = conf;\n    ngx_rtmp_exec_conf_t       *ec;\n    ngx_rtmp_exec_t            *e;\n    ngx_uint_t                  n;\n\n    if (emcf->respawn_timeout == NGX_CONF_UNSET_MSEC) {\n        emcf->respawn_timeout = 5000;\n    }\n\n#if !(NGX_WIN32)\n    if (emcf->kill_signal == NGX_CONF_UNSET) {\n        emcf->kill_signal = SIGKILL;\n    }\n#endif\n\n    if (ngx_array_init(&emcf->static_exec, cf->pool,\n                       emcf->static_conf.nelts,\n                       sizeof(ngx_rtmp_exec_t)) != NGX_OK)\n    {\n        return NGX_CONF_ERROR;\n    }\n\n    e = ngx_array_push_n(&emcf->static_exec, emcf->static_conf.nelts);\n    if (e == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    emcf->log = &cf->cycle->new_log;\n\n    ec = emcf->static_conf.elts;\n\n    for (n = 0; n < emcf->static_conf.nelts; n++, e++, ec++) {\n        ngx_memzero(e, sizeof(*e));\n        e->conf = ec;\n        e->managed = 1;\n        e->log = emcf->log;\n        e->respawn_timeout = emcf->respawn_timeout;\n        e->kill_signal = emcf->kill_signal;\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic void *\nngx_rtmp_exec_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_exec_app_conf_t      *eacf;\n\n    eacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_app_conf_t));\n    if (eacf == NULL) {\n        return NULL;\n    }\n\n    eacf->respawn = NGX_CONF_UNSET;\n    eacf->options = NGX_CONF_UNSET;\n    eacf->nbuckets = NGX_CONF_UNSET_UINT;\n\n    return eacf;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_merge_confs(ngx_array_t *conf, ngx_array_t *prev)\n{\n    size_t                 n;\n    ngx_rtmp_exec_conf_t  *ec, *pec;\n\n    if (prev->nelts == 0) {\n        return NGX_OK;\n    }\n\n    if (conf->nelts == 0) {\n        *conf = *prev;\n        return NGX_OK;\n    }\n\n    ec = ngx_array_push_n(conf, prev->nelts);\n    if (ec == NULL) {\n        return NGX_ERROR;\n    }\n\n    pec = prev->elts;\n    for (n = 0; n < prev->nelts; n++, ec++, pec++) {\n        *ec = *pec;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic char *\nngx_rtmp_exec_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_exec_app_conf_t   *prev = parent;\n    ngx_rtmp_exec_app_conf_t   *conf = child;\n\n    ngx_uint_t  n;\n\n    ngx_conf_merge_value(conf->respawn, prev->respawn, 1);\n    ngx_conf_merge_uint_value(conf->nbuckets, prev->nbuckets, 1024);\n\n    for (n = 0; n < NGX_RTMP_EXEC_MAX; n++) {\n        if (ngx_rtmp_exec_merge_confs(&conf->conf[n], &prev->conf[n]) != NGX_OK)\n        {\n            return NGX_CONF_ERROR;\n        }\n\n        if (conf->conf[n].nelts) {\n            conf->active = 1;\n            prev->active = 1;\n        }\n    }\n\n    if (conf->conf[NGX_RTMP_EXEC_PULL].nelts > 0) {\n        conf->pull = ngx_pcalloc(cf->pool, sizeof(void *) * conf->nbuckets);\n        if (conf->pull == NULL) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_init_process(ngx_cycle_t *cycle)\n{\n#if !(NGX_WIN32)\n    ngx_rtmp_core_main_conf_t  *cmcf = ngx_rtmp_core_main_conf;\n    ngx_rtmp_core_srv_conf_t  **cscf;\n    ngx_rtmp_conf_ctx_t        *cctx;\n    ngx_rtmp_exec_main_conf_t  *emcf;\n    ngx_rtmp_exec_t            *e;\n    ngx_uint_t                  n;\n\n    if (cmcf == NULL || cmcf->servers.nelts == 0) {\n        return NGX_OK;\n    }\n\n    /* execs are always started by the first worker */\n    if (ngx_process_slot) {\n        return NGX_OK;\n    }\n\n    cscf = cmcf->servers.elts;\n    cctx = (*cscf)->ctx;\n    emcf = cctx->main_conf[ngx_rtmp_exec_module.ctx_index];\n\n    /* FreeBSD note:\n     * When worker is restarted, child process (ffmpeg) will\n     * not be terminated if it's connected to another\n     * (still alive) worker. That leads to starting\n     * another instance of exec_static process.\n     * Need to kill previously started processes.\n     *\n     * On Linux \"prctl\" syscall is used to kill child\n     * when nginx worker is terminated.\n     */\n\n    e = emcf->static_exec.elts;\n    for (n = 0; n < emcf->static_exec.nelts; ++n, ++e) {\n        e->respawn_evt.data = e;\n        e->respawn_evt.log = e->log;\n        e->respawn_evt.handler = ngx_rtmp_exec_respawn;\n        ngx_post_event((&e->respawn_evt), &ngx_rtmp_init_queue);\n    }\n#endif\n\n    return NGX_OK;\n}\n\n\n#if !(NGX_WIN32)\nstatic void\nngx_rtmp_exec_respawn(ngx_event_t *ev)\n{\n    ngx_rtmp_exec_run((ngx_rtmp_exec_t *) ev->data);\n}\n\n\nstatic void\nngx_rtmp_exec_child_dead(ngx_event_t *ev)\n{\n    ngx_connection_t   *dummy_conn = ev->data;\n    ngx_rtmp_exec_t    *e;\n\n    e = dummy_conn->data;\n\n    ngx_log_error(NGX_LOG_INFO, e->log, 0,\n                  \"exec: child %ui exited; %s\", (ngx_int_t) e->pid,\n                  e->respawn_timeout == NGX_CONF_UNSET_MSEC ? \"respawning\" :\n                                                               \"ignoring\");\n\n    ngx_rtmp_exec_kill(e, 0);\n\n    if (e->respawn_timeout == NGX_CONF_UNSET_MSEC) {\n        return;\n    }\n\n    if (e->respawn_timeout == 0) {\n        ngx_rtmp_exec_run(e);\n        return;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0,\n                   \"exec: shedule respawn %Mmsec\", e->respawn_timeout);\n\n    e->respawn_evt.data = e;\n    e->respawn_evt.log = e->log;\n    e->respawn_evt.handler = ngx_rtmp_exec_respawn;\n\n    ngx_add_timer(&e->respawn_evt, e->respawn_timeout);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_kill(ngx_rtmp_exec_t *e, ngx_int_t kill_signal)\n{\n    if (e->respawn_evt.timer_set) {\n        ngx_del_timer(&e->respawn_evt);\n    }\n\n    if (e->read_evt.active) {\n        ngx_del_event(&e->read_evt, NGX_READ_EVENT, 0);\n    }\n\n    if (e->active == 0) {\n        return NGX_OK;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, e->log, 0,\n                  \"exec: terminating child %ui\", (ngx_int_t) e->pid);\n\n    e->active = 0;\n    close(e->pipefd);\n    if (e->save_pid) {\n        *e->save_pid = NGX_INVALID_PID;\n    }\n\n    if (kill_signal == 0) {\n        return NGX_OK;\n    }\n\n    if (kill(e->pid, kill_signal) == -1) {\n        ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno,\n                      \"exec: kill failed pid=%i\", (ngx_int_t) e->pid);\n    } else {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0,\n                       \"exec: killed pid=%i\", (ngx_int_t) e->pid);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_run(ngx_rtmp_exec_t *e)\n{\n    int                     fd, ret, maxfd, pipefd[2];\n    char                  **args, **arg_out;\n    ngx_pid_t               pid;\n    ngx_str_t              *arg_in, a;\n    ngx_uint_t              n;\n    ngx_rtmp_exec_conf_t   *ec;\n\n    ec = e->conf;\n\n    ngx_log_error(NGX_LOG_INFO, e->log, 0,\n                  \"exec: starting %s child '%V'\",\n                  e->managed ? \"managed\" : \"unmanaged\", &ec->cmd);\n\n    pipefd[0] = -1;\n    pipefd[1] = -1;\n\n    if (e->managed) {\n\n        if (e->active) {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, e->log, 0,\n                           \"exec: already active '%V'\", &ec->cmd);\n            return NGX_OK;\n        }\n\n        if (pipe(pipefd) == -1) {\n            ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno,\n                          \"exec: pipe failed\");\n            return NGX_ERROR;\n        }\n\n        /* make pipe write end survive through exec */\n\n        ret = fcntl(pipefd[1], F_GETFD);\n\n        if (ret != -1) {\n            ret &= ~FD_CLOEXEC;\n            ret = fcntl(pipefd[1], F_SETFD, ret);\n        }\n\n        if (ret == -1) {\n\n            close(pipefd[0]);\n            close(pipefd[1]);\n\n            ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno,\n                          \"exec: fcntl failed\");\n\n            return NGX_ERROR;\n        }\n    }\n\n    pid = fork();\n\n    switch (pid) {\n\n        case -1:\n\n            /* failure */\n\n            if (pipefd[0] != -1) {\n                close(pipefd[0]);\n            }\n\n            if (pipefd[1] != -1) {\n                close(pipefd[1]);\n            }\n\n            ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno,\n                          \"exec: fork failed\");\n\n            return NGX_ERROR;\n\n        case 0:\n\n            /* child */\n\n#if (NGX_LINUX)\n            if (e->managed) {\n                prctl(PR_SET_PDEATHSIG, e->kill_signal, 0, 0, 0);\n            }\n#endif\n\n            /* close all descriptors but pipe write end */\n\n            maxfd = sysconf(_SC_OPEN_MAX);\n            for (fd = 0; fd < maxfd; ++fd) {\n                if (fd == pipefd[1]) {\n                    continue;\n                }\n\n                close(fd);\n            }\n\n            fd = open(\"/dev/null\", O_RDWR);\n\n            dup2(fd, STDIN_FILENO);\n            dup2(fd, STDOUT_FILENO);\n            dup2(fd, STDERR_FILENO);\n\n            args = ngx_alloc((ec->args.nelts + 2) * sizeof(char *), e->log);\n            if (args == NULL) {\n                exit(1);\n            }\n\n            arg_in = ec->args.elts;\n            arg_out = args;\n            *arg_out++ = (char *) ec->cmd.data;\n\n            for (n = 0; n < ec->args.nelts; n++, ++arg_in) {\n\n                if (e->eval == NULL) {\n                    a = *arg_in;\n                } else {\n                    ngx_rtmp_eval(e->eval_ctx, arg_in, e->eval, &a, e->log);\n                }\n\n                if (ngx_rtmp_eval_streams(&a) != NGX_DONE) {\n                    continue;\n                }\n\n                *arg_out++ = (char *) a.data;\n            }\n\n            *arg_out = NULL;\n\n#if (NGX_DEBUG)\n            {\n                char    **p;\n\n                for (p = args; *p; p++) {\n                    ngx_write_fd(STDERR_FILENO, \"'\", 1);\n                    ngx_write_fd(STDERR_FILENO, *p, strlen(*p));\n                    ngx_write_fd(STDERR_FILENO, \"' \", 2);\n                }\n\n                ngx_write_fd(STDERR_FILENO, \"\\n\", 1);\n            }\n#endif\n\n            if (execvp((char *) ec->cmd.data, args) == -1) {\n                char    *msg;\n\n                msg = strerror(errno);\n\n                ngx_write_fd(STDERR_FILENO, \"execvp error: \", 14);\n                ngx_write_fd(STDERR_FILENO, msg, strlen(msg));\n                ngx_write_fd(STDERR_FILENO, \"\\n\", 1);\n\n                exit(1);\n            }\n\n            break;\n\n        default:\n\n            /* parent */\n\n            if (pipefd[1] != -1) {\n                close(pipefd[1]);\n            }\n\n            if (pipefd[0] != -1) {\n\n                e->active = 1;\n                e->pid = pid;\n                e->pipefd = pipefd[0];\n\n                if (e->save_pid) {\n                    *e->save_pid = pid;\n                }\n\n                e->dummy_conn.fd = e->pipefd;\n                e->dummy_conn.data = e;\n                e->dummy_conn.read  = &e->read_evt;\n                e->dummy_conn.write = &e->write_evt;\n                e->read_evt.data  = &e->dummy_conn;\n                e->write_evt.data = &e->dummy_conn;\n\n                e->read_evt.log = e->log;\n                e->read_evt.handler = ngx_rtmp_exec_child_dead;\n\n                if (ngx_add_event(&e->read_evt, NGX_READ_EVENT, 0) != NGX_OK) {\n                    ngx_log_error(NGX_LOG_INFO, e->log, ngx_errno,\n                                  \"exec: failed to add child control event\");\n                }\n            }\n\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, e->log, 0,\n                           \"exec: child '%V' started pid=%i\",\n                           &ec->cmd, (ngx_int_t) pid);\n            break;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_init_ctx(ngx_rtmp_session_t *s, u_char name[NGX_RTMP_MAX_NAME],\n    u_char args[NGX_RTMP_MAX_ARGS], ngx_uint_t flags)\n{\n    ngx_uint_t                  n;\n    ngx_array_t                *push_conf;\n    ngx_rtmp_exec_t            *e;\n    ngx_rtmp_exec_ctx_t        *ctx;\n    ngx_rtmp_exec_conf_t       *ec;\n    ngx_rtmp_exec_app_conf_t   *eacf;\n    ngx_rtmp_exec_main_conf_t  *emcf;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n\n    if (ctx != NULL) {\n        goto done;\n    }\n\n    ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_exec_ctx_t));\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_exec_module);\n\n    eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module);\n\n    emcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_exec_module);\n\n    push_conf = &eacf->conf[NGX_RTMP_EXEC_PUSH];\n\n    if (push_conf->nelts > 0) {\n\n        if (ngx_array_init(&ctx->push_exec, s->connection->pool,\n                           push_conf->nelts,\n                           sizeof(ngx_rtmp_exec_t)) != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n\n        e = ngx_array_push_n(&ctx->push_exec, push_conf->nelts);\n\n        if (e == NULL) {\n            return NGX_ERROR;\n        }\n\n        ec = push_conf->elts;\n\n        for (n = 0; n < push_conf->nelts; n++, e++, ec++) {\n            ngx_memzero(e, sizeof(*e));\n            e->conf = ec;\n            e->managed = 1;\n            e->log = s->connection->log;\n            e->eval = ngx_rtmp_exec_push_eval;\n            e->eval_ctx = s;\n            e->kill_signal = emcf->kill_signal;\n            e->respawn_timeout = (eacf->respawn ? emcf->respawn_timeout :\n                                  NGX_CONF_UNSET_MSEC);\n        }\n    }\n\ndone:\n\n    ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME);\n    ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS);\n\n    ctx->flags |= flags;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_init_pull_ctx(ngx_rtmp_session_t *s,\n    u_char name[NGX_RTMP_MAX_NAME])\n{\n    size_t                      len;\n    ngx_uint_t                  n;\n    ngx_pool_t                 *pool;\n    ngx_array_t                *pull_conf;\n    ngx_rtmp_exec_t            *e;\n    ngx_rtmp_exec_ctx_t        *ctx;\n    ngx_rtmp_exec_conf_t       *ec;\n    ngx_rtmp_exec_pull_ctx_t   *pctx, **ppctx;\n    ngx_rtmp_exec_app_conf_t   *eacf;\n    ngx_rtmp_exec_main_conf_t  *emcf;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n    if (ctx->pull != NULL) {\n        return NGX_OK;\n    }\n\n    eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module);\n\n    pull_conf = &eacf->conf[NGX_RTMP_EXEC_PULL];\n\n    if (pull_conf->nelts == 0) {\n        return NGX_OK;\n    }\n\n    emcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_exec_module);\n\n    len = ngx_strlen(name);\n\n    ppctx = &eacf->pull[ngx_hash_key(name, len) % eacf->nbuckets];\n\n    for (; *ppctx; ppctx = &(*ppctx)->next) {\n        pctx = *ppctx;\n\n        if (pctx->name.len == len &&\n            ngx_strncmp(name, pctx->name.data, len) == 0)\n        {\n            goto done;\n        }\n    }\n\n    pool = ngx_create_pool(4096, emcf->log);\n    if (pool == NULL) {\n        return NGX_ERROR;\n    }\n\n    pctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_exec_pull_ctx_t));\n    if (pctx == NULL) {\n        goto error;\n    }\n\n    pctx->pool = pool;\n    pctx->name.len = len;\n    pctx->name.data = ngx_palloc(pool, len);\n\n    if (pctx->name.data == NULL) {\n        goto error;\n    }\n\n    ngx_memcpy(pctx->name.data, name, len);\n\n    pctx->app.len = s->app.len;\n    pctx->app.data = ngx_palloc(pool, s->app.len);\n\n    if (pctx->app.data == NULL) {\n        goto error;\n    }\n\n    ngx_memcpy(pctx->app.data, s->app.data, s->app.len);\n\n    if (ngx_array_init(&pctx->pull_exec, pool, pull_conf->nelts,\n                       sizeof(ngx_rtmp_exec_t)) != NGX_OK)\n    {\n        goto error;\n    }\n\n    e = ngx_array_push_n(&pctx->pull_exec, pull_conf->nelts);\n    if (e == NULL) {\n        goto error;\n    }\n\n    ec = pull_conf->elts;\n    for (n = 0; n < pull_conf->nelts; n++, e++, ec++) {\n        ngx_memzero(e, sizeof(*e));\n        e->conf = ec;\n        e->managed = 1;\n        e->log = emcf->log;\n        e->eval = ngx_rtmp_exec_pull_eval;\n        e->eval_ctx = pctx;\n        e->kill_signal = emcf->kill_signal;\n        e->respawn_timeout = (eacf->respawn ? emcf->respawn_timeout :\n                                              NGX_CONF_UNSET_MSEC);\n    }\n\n    *ppctx = pctx;\n\ndone:\n\n    ctx->pull = pctx;\n    ctx->pull->counter++;\n\n    return NGX_OK;\n\nerror:\n\n    ngx_destroy_pool(pool);\n\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_filter(ngx_rtmp_session_t *s, ngx_rtmp_exec_conf_t *ec)\n{\n    size_t                len;\n    ngx_str_t            *v;\n    ngx_uint_t            n;\n    ngx_rtmp_exec_ctx_t  *ctx;\n\n    if (ec->names.nelts == 0) {\n        return NGX_OK;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n\n    len = ngx_strlen(ctx->name);\n\n    v = ec->names.elts;\n    for (n = 0; n < ec->names.nelts; n++, s++) {\n        if (v->len == len && ngx_strncmp(v->data, ctx->name, len) == 0) {\n            return NGX_OK;\n        }\n    }\n\n    return NGX_DECLINED;\n}\n\n\nstatic void\nngx_rtmp_exec_unmanaged(ngx_rtmp_session_t *s, ngx_array_t *e, const char *op)\n{\n    ngx_uint_t             n;\n    ngx_rtmp_exec_t        en;\n    ngx_rtmp_exec_conf_t  *ec;\n\n    if (e->nelts == 0) {\n        return;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"exec: %s %uz unmanaged command(s)\", op, e->nelts);\n\n    ec = e->elts;\n    for (n = 0; n < e->nelts; n++, ec++) {\n        if (ngx_rtmp_exec_filter(s, ec) != NGX_OK) {\n            continue;\n        }\n\n        ngx_memzero(&en, sizeof(ngx_rtmp_exec_t));\n\n        en.conf = ec;\n        en.eval = ngx_rtmp_exec_event_eval;\n        en.eval_ctx = s;\n        en.log = s->connection->log;\n\n        ngx_rtmp_exec_run(&en);\n    }\n}\n\n\nstatic void\nngx_rtmp_exec_managed(ngx_rtmp_session_t *s, ngx_array_t *e, const char *op)\n{\n    ngx_uint_t        n;\n    ngx_rtmp_exec_t  *en;\n\n    if (e->nelts == 0) {\n        return;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"exec: %s %uz managed command(s)\", op, e->nelts);\n\n    en = e->elts;\n    for (n = 0; n < e->nelts; n++, en++) {\n        if (ngx_rtmp_exec_filter(s, en->conf) == NGX_OK) {\n            ngx_rtmp_exec_run(en);\n        }\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    ngx_rtmp_exec_ctx_t       *ctx;\n    ngx_rtmp_exec_app_conf_t  *eacf;\n\n    eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module);\n\n    if (eacf == NULL || !eacf->active) {\n        goto next;\n    }\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    if (ngx_rtmp_exec_init_ctx(s, v->name, v->args, NGX_RTMP_EXEC_PUBLISHING)\n        != NGX_OK)\n    {\n        goto next;\n    }\n\n    ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PUBLISH], \"publish\");\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n\n    ngx_rtmp_exec_managed(s, &ctx->push_exec, \"push\");\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    ngx_rtmp_exec_ctx_t       *ctx;\n    ngx_rtmp_exec_pull_ctx_t  *pctx;\n    ngx_rtmp_exec_app_conf_t  *eacf;\n\n    eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module);\n\n    if (eacf == NULL || !eacf->active) {\n        goto next;\n    }\n\n    if (ngx_rtmp_exec_init_ctx(s, v->name, v->args, NGX_RTMP_EXEC_PLAYING)\n        != NGX_OK)\n    {\n        goto next;\n    }\n\n    ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PLAY], \"play\");\n\n    if (ngx_rtmp_exec_init_pull_ctx(s, v->name) != NGX_OK) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n    pctx = ctx->pull;\n\n    if (pctx && pctx->counter == 1) {\n        ngx_rtmp_exec_managed(s, &pctx->pull_exec, \"pull\");\n    }\n\nnext:\n    return next_play(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)\n{\n    size_t                     n;\n    ngx_rtmp_exec_t           *e;\n    ngx_rtmp_exec_ctx_t       *ctx;\n    ngx_rtmp_exec_pull_ctx_t  *pctx, **ppctx;\n    ngx_rtmp_exec_app_conf_t  *eacf;\n\n    eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module);\n    if (eacf == NULL) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n    if (ctx == NULL) {\n        goto next;\n    }\n\n    if (ctx->flags & NGX_RTMP_EXEC_PUBLISHING) {\n        ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PUBLISH_DONE],\n                                \"publish_done\");\n    }\n\n    if (ctx->flags & NGX_RTMP_EXEC_PLAYING) {\n        ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_PLAY_DONE],\n                                \"play_done\");\n    }\n\n    ctx->flags = 0;\n\n    if (ctx->push_exec.nelts > 0) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"exec: delete %uz push command(s)\",\n                       ctx->push_exec.nelts);\n\n        e = ctx->push_exec.elts;\n        for (n = 0; n < ctx->push_exec.nelts; n++, e++) {\n            ngx_rtmp_exec_kill(e, e->kill_signal);\n        }\n    }\n\n    pctx = ctx->pull;\n\n    if (pctx && --pctx->counter == 0) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"exec: delete %uz pull command(s)\",\n                       pctx->pull_exec.nelts);\n\n        e = pctx->pull_exec.elts;\n        for (n = 0; n < pctx->pull_exec.nelts; n++, e++) {\n            ngx_rtmp_exec_kill(e, e->kill_signal);\n        }\n\n        ppctx = &eacf->pull[ngx_hash_key(pctx->name.data, pctx->name.len) %\n                            eacf->nbuckets];\n\n        for (; *ppctx; ppctx = &(*ppctx)->next) {\n            if (pctx == *ppctx) {\n                *ppctx = pctx->next;\n                break;\n            }\n        }\n\n        ngx_destroy_pool(pctx->pool);\n    }\n\n    ctx->pull = NULL;\n\nnext:\n    return next_close_stream(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v)\n{\n    u_char                     c;\n    ngx_uint_t                 ext, dir;\n    ngx_rtmp_exec_ctx_t       *ctx;\n    ngx_rtmp_exec_app_conf_t  *eacf;\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    eacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_exec_module);\n    if (eacf == NULL || !eacf->active) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_exec_module);\n    if (ctx == NULL) {\n        goto next;\n    }\n\n    ctx->recorder = v->recorder;\n    ctx->path = v->path;\n\n    ctx->dirname.data = ctx->path.data;\n    ctx->dirname.len = 0;\n\n    for (dir = ctx->path.len; dir > 0; dir--) {\n        c = ctx->path.data[dir - 1];\n        if (c == '/' || c == '\\\\') {\n            ctx->dirname.len = dir - 1;\n            break;\n        }\n    }\n\n    ctx->filename.data = ctx->path.data + dir;\n    ctx->filename.len = ctx->path.len - dir;\n\n    ctx->basename = ctx->filename;\n\n    for (ext = ctx->filename.len; ext > 0; ext--) {\n        if (ctx->filename.data[ext - 1] == '.') {\n            ctx->basename.len = ext - 1;\n            break;\n        }\n    }\n\n    ngx_rtmp_exec_unmanaged(s, &eacf->conf[NGX_RTMP_EXEC_RECORD_DONE],\n                            \"record_done\");\n\n    ngx_str_null(&v->recorder);\n    ngx_str_null(&v->path);\n\nnext:\n    return next_record_done(s, v);\n}\n#endif /* NGX_WIN32 */\n\n\nstatic char *\nngx_rtmp_exec_conf(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    char  *p = conf;\n\n    size_t                     n, nargs;\n    ngx_str_t                 *s, *value, v;\n    ngx_array_t               *confs;\n    ngx_rtmp_exec_conf_t      *ec;\n    ngx_rtmp_exec_app_conf_t  *eacf;\n\n    confs = (ngx_array_t *) (p + cmd->offset);\n\n    eacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_exec_module);\n\n    if (confs->nalloc == 0 && ngx_array_init(confs, cf->pool, 1,\n                                             sizeof(ngx_rtmp_exec_conf_t))\n                              != NGX_OK)\n    {\n        return NGX_CONF_ERROR;\n    }\n\n    value = cf->args->elts;\n\n    ec = ngx_array_push(confs);\n    if (ec == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    ngx_memzero(ec, sizeof(ngx_rtmp_exec_conf_t));\n\n    /* type is undefined for explicit execs */\n\n    ec->type = NGX_CONF_UNSET_UINT;\n    ec->cmd = value[1];\n\n    if (ngx_array_init(&ec->names, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) {\n        return NGX_CONF_ERROR;\n    }\n\n    if (cf->args->nelts == 2) {\n        return NGX_CONF_OK;\n    }\n\n    nargs = cf->args->nelts - 2;\n    if (ngx_array_init(&ec->args, cf->pool, nargs, sizeof(ngx_str_t)) != NGX_OK)\n    {\n        return NGX_CONF_ERROR;\n    }\n\n    for (n = 2; n < cf->args->nelts; n++) {\n\n        v = value[n];\n\n        if (eacf->options == 1) {\n\n            if (v.len >= 5 && ngx_strncmp(v.data, \"name=\", 5) == 0) {\n\n                s = ngx_array_push(&ec->names);\n                if (s == NULL) {\n                    return NGX_CONF_ERROR;\n                }\n\n                v.data += 5;\n                v.len -= 5;\n\n                *s = v;\n\n                continue;\n            }\n        }\n\n        s = ngx_array_push(&ec->args);\n        if (s == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        *s = v;\n    }\n\n    return NGX_CONF_OK;\n}\n\n/*\nstatic char *\nngx_rtmp_exec_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    char                       *rv;\n    ngx_str_t                  *value;\n    ngx_conf_t                  save;\n    ngx_array_t                *confs;\n    ngx_rtmp_conf_ctx_t        *ctx, *pctx;\n    ngx_rtmp_exec_conf_t       *ec, *eec;\n    ngx_rtmp_exec_app_conf_t   *eacf;\n    ngx_rtmp_exec_main_conf_t  *emcf;\n\n    value = cf->args->elts;\n\n    eacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_exec_module);\n\n    emcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_exec_module);\n\n    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));\n    if (ctx == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    pctx = cf->ctx;\n\n    ctx->main_conf = pctx->main_conf;\n    ctx->srv_conf  = pctx->srv_conf;\n\n    ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);\n    if (ctx->app_conf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    ec = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_exec_conf_t));\n    if (ec == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    ec->id = value[1];\n    ec->type = NGX_CONF_UNSET_UINT;\n\n    ctx->app_conf[ngx_rtmp_exec_module.ctx_index] = ec;\n\n    save = *cf;\n\n    cf->ctx = ctx;\n    cf->cmd_type = NGX_RTMP_EXEC_CONF;\n\n    rv = ngx_conf_parse(cf, NULL);\n    *cf= save;\n\n    switch (ec->type) {\n\n        case NGX_RTMP_EXEC_STATIC:\n            confs = &emcf->static_conf;\n            break;\n\n        case NGX_CONF_UNSET_UINT:\n            return \"unspecified exec type\";\n\n        default:\n            confs = &eacf->conf[ec->type];\n    }\n\n    if (confs->nalloc == 0 && ngx_array_init(confs, cf->pool, 1,\n                                             sizeof(ngx_rtmp_exec_conf_t))\n                             != NGX_OK)\n    {\n        return NGX_CONF_ERROR;\n    }\n\n    eec = ngx_array_push(confs);\n    if (eec == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    *eec = *ec;\n\n    return rv;\n}\n*/\n\nstatic char *\nngx_rtmp_exec_kill_signal(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_exec_main_conf_t  *emcf = conf;\n\n    ngx_str_t  *value;\n\n    value = cf->args->elts;\n    value++;\n\n    emcf->kill_signal = ngx_atoi(value->data, value->len);\n    if (emcf->kill_signal != NGX_ERROR) {\n        return NGX_CONF_OK;\n    }\n\n#define NGX_RMTP_EXEC_SIGNAL(name)                                          \\\n    if (value->len == sizeof(#name) - 1 &&                                  \\\n        ngx_strncasecmp(value->data, (u_char *) #name, value->len) == 0)    \\\n    {                                                                       \\\n        emcf->kill_signal = SIG##name;                                      \\\n        return NGX_CONF_OK;                                                 \\\n    }\n\n    /* POSIX.1-1990 signals */\n\n#if !(NGX_WIN32)\n    NGX_RMTP_EXEC_SIGNAL(HUP);\n    NGX_RMTP_EXEC_SIGNAL(INT);\n    NGX_RMTP_EXEC_SIGNAL(QUIT);\n    NGX_RMTP_EXEC_SIGNAL(ILL);\n    NGX_RMTP_EXEC_SIGNAL(ABRT);\n    NGX_RMTP_EXEC_SIGNAL(FPE);\n    NGX_RMTP_EXEC_SIGNAL(KILL);\n    NGX_RMTP_EXEC_SIGNAL(SEGV);\n    NGX_RMTP_EXEC_SIGNAL(PIPE);\n    NGX_RMTP_EXEC_SIGNAL(ALRM);\n    NGX_RMTP_EXEC_SIGNAL(TERM);\n    NGX_RMTP_EXEC_SIGNAL(USR1);\n    NGX_RMTP_EXEC_SIGNAL(USR2);\n    NGX_RMTP_EXEC_SIGNAL(CHLD);\n    NGX_RMTP_EXEC_SIGNAL(CONT);\n    NGX_RMTP_EXEC_SIGNAL(STOP);\n    NGX_RMTP_EXEC_SIGNAL(TSTP);\n    NGX_RMTP_EXEC_SIGNAL(TTIN);\n    NGX_RMTP_EXEC_SIGNAL(TTOU);\n#endif\n\n#undef NGX_RMTP_EXEC_SIGNAL\n\n    return \"unknown signal\";\n}\n\n\nstatic ngx_int_t\nngx_rtmp_exec_postconfiguration(ngx_conf_t *cf)\n{\n#if !(NGX_WIN32)\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_exec_publish;\n\n    next_play = ngx_rtmp_play;\n    ngx_rtmp_play = ngx_rtmp_exec_play;\n\n    next_close_stream = ngx_rtmp_close_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_exec_close_stream;\n\n    next_record_done = ngx_rtmp_record_done;\n    ngx_rtmp_record_done = ngx_rtmp_exec_record_done;\n\n#endif /* NGX_WIN32 */\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_flv_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_play_module.h\"\n#include \"ngx_rtmp_codec_module.h\"\n#include \"ngx_rtmp_streams.h\"\n\n\nstatic ngx_int_t ngx_rtmp_flv_postconfiguration(ngx_conf_t *cf);\nstatic void ngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f);\nstatic ngx_int_t ngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s,\n       ngx_file_t *f, ngx_int_t timestamp);\nstatic ngx_int_t ngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f,\n       ngx_int_t aindex, ngx_int_t vindex);\nstatic ngx_int_t ngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f);\nstatic ngx_int_t ngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f,\n       ngx_uint_t offset);\nstatic ngx_int_t ngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f);\nstatic ngx_int_t ngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f,\n                                   ngx_uint_t *ts);\n\n\ntypedef struct {\n    ngx_uint_t                          nelts;\n    ngx_uint_t                          offset;\n} ngx_rtmp_flv_index_t;\n\n\ntypedef struct {\n    ngx_int_t                           offset;\n    ngx_int_t                           start_timestamp;\n    ngx_event_t                         write_evt;\n    uint32_t                            last_audio;\n    uint32_t                            last_video;\n    ngx_uint_t                          msg_mask;\n    uint32_t                            epoch;\n\n    unsigned                            meta_read:1;\n    ngx_rtmp_flv_index_t                filepositions;\n    ngx_rtmp_flv_index_t                times;\n} ngx_rtmp_flv_ctx_t;\n\n\n#define NGX_RTMP_FLV_BUFFER             (1024*1024)\n#define NGX_RTMP_FLV_BUFLEN_ADDON       1000\n#define NGX_RTMP_FLV_TAG_HEADER         11\n#define NGX_RTMP_FLV_DATA_OFFSET        13\n\n\nstatic u_char                           ngx_rtmp_flv_buffer[\n                                        NGX_RTMP_FLV_BUFFER];\nstatic u_char                           ngx_rtmp_flv_header[\n                                        NGX_RTMP_FLV_TAG_HEADER];\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_flv_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_flv_postconfiguration,         /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    NULL,                                   /* create app configuration */\n    NULL                                    /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_flv_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_flv_module_ctx,               /* module context */\n    NULL,                                   /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic ngx_int_t\nngx_rtmp_flv_fill_index(ngx_rtmp_amf_ctx_t *ctx, ngx_rtmp_flv_index_t *idx)\n{\n    uint32_t                        nelts;\n    ngx_buf_t                      *b;\n\n    /* we have AMF array pointed by context;\n     * need to extract its size (4 bytes) &\n     * save offset of actual array data */\n\n    b = ctx->link->buf;\n\n    if (b->last - b->pos < (ngx_int_t) ctx->offset + 4) {\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_rmemcpy(&nelts, b->pos + ctx->offset, 4);\n\n    idx->nelts = nelts;\n    idx->offset = ctx->offset + 4;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_flv_init_index(ngx_rtmp_session_t *s, ngx_chain_t *in)\n{\n    ngx_rtmp_flv_ctx_t             *ctx;\n\n    static ngx_rtmp_amf_ctx_t       filepositions_ctx;\n    static ngx_rtmp_amf_ctx_t       times_ctx;\n\n    static ngx_rtmp_amf_elt_t       in_keyframes[] = {\n\n        { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,\n          ngx_string(\"filepositions\"),\n          &filepositions_ctx, 0 },\n\n        { NGX_RTMP_AMF_ARRAY | NGX_RTMP_AMF_CONTEXT,\n          ngx_string(\"times\"),\n          &times_ctx, 0 }\n    };\n\n    static ngx_rtmp_amf_elt_t       in_inf[] = {\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_string(\"keyframes\"),\n          in_keyframes, sizeof(in_keyframes) }\n    };\n\n    static ngx_rtmp_amf_elt_t       in_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          in_inf, sizeof(in_inf) },\n    };\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);\n\n    if (ctx == NULL || in == NULL) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: init index\");\n\n    ngx_memzero(&filepositions_ctx, sizeof(filepositions_ctx));\n    ngx_memzero(&times_ctx, sizeof(times_ctx));\n\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                             sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: init index error\");\n        return NGX_OK;\n    }\n\n    if (filepositions_ctx.link && ngx_rtmp_flv_fill_index(&filepositions_ctx,\n                                                          &ctx->filepositions)\n        != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: failed to init filepositions\");\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: filepositions nelts=%ui offset=%ui\",\n                   ctx->filepositions.nelts, ctx->filepositions.offset);\n\n    if (times_ctx.link && ngx_rtmp_flv_fill_index(&times_ctx,\n                                                  &ctx->times)\n        != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: failed to init times\");\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: times nelts=%ui offset=%ui\",\n                   ctx->times.nelts, ctx->times.offset);\n\n    return  NGX_OK;\n}\n\n\nstatic double\nngx_rtmp_flv_index_value(void *src)\n{\n    double      v;\n\n    ngx_rtmp_rmemcpy(&v, src, 8);\n\n    return v;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_flv_timestamp_to_offset(ngx_rtmp_session_t *s, ngx_file_t *f,\n    ngx_int_t timestamp)\n{\n    ngx_rtmp_flv_ctx_t             *ctx;\n    ssize_t                         n, size;\n    ngx_uint_t                      offset, index, ret, nelts;\n    double                          v;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);\n\n    if (ctx == NULL) {\n        goto rewind;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: lookup index start timestamp=%i\",\n                   timestamp);\n\n    if (ctx->meta_read == 0) {\n        ngx_rtmp_flv_read_meta(s, f);\n        ctx->meta_read = 1;\n    }\n\n    if (timestamp <= 0 || ctx->filepositions.nelts == 0\n                       || ctx->times.nelts == 0)\n    {\n        goto rewind;\n    }\n\n    /* read index table from file given offset */\n    offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +\n             ctx->times.offset;\n\n    /* index should fit in the buffer */\n    nelts = ngx_min(ctx->times.nelts, sizeof(ngx_rtmp_flv_buffer) / 9);\n    size = nelts * 9;\n\n    n = ngx_read_file(f, ngx_rtmp_flv_buffer, size, offset);\n\n    if (n != size) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: could not read times index\");\n        goto rewind;\n    }\n\n    /*TODO: implement binary search */\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: lookup times nelts=%ui\", nelts);\n\n    for (index = 0; index < nelts - 1; ++index) {\n        v = ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer +\n                                     index * 9 + 1) * 1000;\n\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                      \"flv: lookup times index=%ui value=%ui\",\n                      index, (ngx_uint_t) v);\n\n        if (timestamp < v) {\n            break;\n        }\n    }\n\n    if (index >= ctx->filepositions.nelts) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: index out of bounds: %ui>=%ui\",\n                     index, ctx->filepositions.nelts);\n        goto rewind;\n    }\n\n    /* take value from filepositions */\n    offset = NGX_RTMP_FLV_DATA_OFFSET + NGX_RTMP_FLV_TAG_HEADER +\n             ctx->filepositions.offset + index * 9;\n\n    n = ngx_read_file(f, ngx_rtmp_flv_buffer, 8, offset + 1);\n\n    if (n != 8) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: could not read filepositions index\");\n        goto rewind;\n    }\n\n    ret = (ngx_uint_t) ngx_rtmp_flv_index_value(ngx_rtmp_flv_buffer);\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: lookup index timestamp=%i offset=%ui\",\n                   timestamp, ret);\n\n    return ret;\n\nrewind:\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: lookup index timestamp=%i offset=begin\",\n                   timestamp);\n\n    return NGX_RTMP_FLV_DATA_OFFSET;\n}\n\n\nstatic void\nngx_rtmp_flv_read_meta(ngx_rtmp_session_t *s, ngx_file_t *f)\n{\n    ngx_rtmp_flv_ctx_t             *ctx;\n    ssize_t                         n;\n    ngx_rtmp_header_t               h;\n    ngx_chain_t                    *out, in;\n    ngx_buf_t                       in_buf;\n    ngx_rtmp_core_srv_conf_t       *cscf;\n    uint32_t                        size;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);\n\n    if (ctx == NULL) {\n        return;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: read meta\");\n\n    /* read tag header */\n    n = ngx_read_file(f, ngx_rtmp_flv_header, sizeof(ngx_rtmp_flv_header),\n                      NGX_RTMP_FLV_DATA_OFFSET);\n\n    if (n != sizeof(ngx_rtmp_flv_header)) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: could not read metadata tag header\");\n        return;\n    }\n\n    if (ngx_rtmp_flv_header[0] != NGX_RTMP_MSG_AMF_META) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                      \"flv: first tag is not metadata, giving up\");\n        return;\n    }\n\n    ngx_memzero(&h, sizeof(h));\n\n    h.type = NGX_RTMP_MSG_AMF_META;\n    h.msid = NGX_RTMP_MSID;\n    h.csid = NGX_RTMP_CSID_AMF;\n\n    size = 0;\n    ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: metadata size=%D\", size);\n\n    if (size > sizeof(ngx_rtmp_flv_buffer)) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: too big metadata\");\n        return;\n    }\n\n    /* read metadata */\n    n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,\n                      sizeof(ngx_rtmp_flv_header) +\n                      NGX_RTMP_FLV_DATA_OFFSET);\n\n    if (n != (ssize_t) size) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"flv: could not read metadata\");\n        return;\n    }\n\n    /* prepare input chain */\n    ngx_memzero(&in, sizeof(in));\n    ngx_memzero(&in_buf, sizeof(in_buf));\n\n    in.buf = &in_buf;\n    in_buf.pos  = ngx_rtmp_flv_buffer;\n    in_buf.last = ngx_rtmp_flv_buffer + size;\n\n    ngx_rtmp_flv_init_index(s, &in);\n\n    /* output chain */\n    out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);\n\n    ngx_rtmp_prepare_message(s, &h, NULL, out);\n    ngx_rtmp_send_message(s, out, 0);\n    ngx_rtmp_free_shared_chain(cscf, out);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_flv_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts)\n{\n    ngx_rtmp_flv_ctx_t             *ctx;\n    uint32_t                        last_timestamp;\n    ngx_rtmp_header_t               h, lh;\n    ngx_rtmp_core_srv_conf_t       *cscf;\n    ngx_chain_t                    *out, in;\n    ngx_buf_t                       in_buf;\n    ngx_int_t                       rc;\n    ssize_t                         n;\n    uint32_t                        buflen, end_timestamp, size;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (ctx->offset == -1) {\n        ctx->offset = ngx_rtmp_flv_timestamp_to_offset(s, f,\n                                                       ctx->start_timestamp);\n        ctx->start_timestamp = -1; /* set later from actual timestamp */\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: read tag at offset=%i\", ctx->offset);\n\n    /* read tag header */\n    n = ngx_read_file(f, ngx_rtmp_flv_header,\n                      sizeof(ngx_rtmp_flv_header), ctx->offset);\n\n    if (n != sizeof(ngx_rtmp_flv_header)) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: could not read flv tag header\");\n        return NGX_DONE;\n    }\n\n    /* parse header fields */\n    ngx_memzero(&h, sizeof(h));\n\n    h.msid = NGX_RTMP_MSID;\n    h.type = ngx_rtmp_flv_header[0];\n\n    size = 0;\n\n    ngx_rtmp_rmemcpy(&size, ngx_rtmp_flv_header + 1, 3);\n    ngx_rtmp_rmemcpy(&h.timestamp, ngx_rtmp_flv_header + 4, 3);\n\n    ((u_char *) &h.timestamp)[3] = ngx_rtmp_flv_header[7];\n\n    ctx->offset += (sizeof(ngx_rtmp_flv_header) + size + 4);\n\n    last_timestamp = 0;\n\n    switch (h.type) {\n\n        case NGX_RTMP_MSG_AUDIO:\n            h.csid = NGX_RTMP_CSID_AUDIO;\n            last_timestamp = ctx->last_audio;\n            ctx->last_audio = h.timestamp;\n            break;\n\n        case NGX_RTMP_MSG_VIDEO:\n            h.csid = NGX_RTMP_CSID_VIDEO;\n            last_timestamp = ctx->last_video;\n            ctx->last_video = h.timestamp;\n            break;\n\n        default:\n            return NGX_OK;\n    }\n\n    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: read tag type=%i size=%uD timestamp=%uD \"\n                  \"last_timestamp=%uD\",\n                  (ngx_int_t) h.type,size, h.timestamp, last_timestamp);\n\n    lh = h;\n    lh.timestamp = last_timestamp;\n\n    if (size > sizeof(ngx_rtmp_flv_buffer)) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: too big message: %D>%uz\", size,\n                      sizeof(ngx_rtmp_flv_buffer));\n        goto next;\n    }\n\n    /* read tag body */\n    n = ngx_read_file(f, ngx_rtmp_flv_buffer, size,\n                      ctx->offset - size - 4);\n\n    if (n != (ssize_t) size) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"flv: could not read flv tag\");\n        return NGX_ERROR;\n    }\n\n    /* prepare input chain */\n    ngx_memzero(&in, sizeof(in));\n    ngx_memzero(&in_buf, sizeof(in_buf));\n\n    in.buf = &in_buf;\n    in_buf.pos  = ngx_rtmp_flv_buffer;\n    in_buf.last = ngx_rtmp_flv_buffer + size;\n\n    /* output chain */\n    out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);\n\n    ngx_rtmp_prepare_message(s, &h, ctx->msg_mask & (1 << h.type) ?\n                             &lh : NULL, out);\n    rc = ngx_rtmp_send_message(s, out, 0);\n    ngx_rtmp_free_shared_chain(cscf, out);\n\n    if (rc == NGX_AGAIN) {\n        return NGX_AGAIN;\n    }\n\n    if (rc != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    ctx->msg_mask |= (1 << h.type);\n\nnext:\n    if (ctx->start_timestamp == -1) {\n        ctx->start_timestamp = h.timestamp;\n        ctx->epoch = ngx_current_msec;\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                      \"flv: start_timestamp=%i\", ctx->start_timestamp);\n        return NGX_OK;\n    }\n\n    buflen = s->buflen + NGX_RTMP_FLV_BUFLEN_ADDON;\n\n    end_timestamp = (ngx_current_msec - ctx->epoch) +\n                     ctx->start_timestamp + buflen;\n\n    ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n           \"flv: %s wait=%D timestamp=%D end_timestamp=%D bufen=%i\",\n            h.timestamp > end_timestamp ? \"schedule\" : \"advance\",\n            h.timestamp > end_timestamp ? h.timestamp - end_timestamp : 0,\n            h.timestamp, end_timestamp, (ngx_int_t) buflen);\n\n    s->current_time = h.timestamp;\n\n    /* too much data sent; schedule timeout */\n    if (h.timestamp > end_timestamp) {\n        return h.timestamp - end_timestamp;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_flv_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex,\n                  ngx_int_t vindex)\n{\n    ngx_rtmp_flv_ctx_t             *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);\n\n    if (ctx == NULL) {\n        ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_flv_ctx_t));\n\n        if (ctx == NULL) {\n            return NGX_ERROR;\n        }\n\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_flv_module);\n    }\n\n    ngx_memzero(ctx, sizeof(*ctx));\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_flv_start(ngx_rtmp_session_t *s, ngx_file_t *f)\n{\n    ngx_rtmp_flv_ctx_t             *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);\n\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: start\");\n\n    ctx->offset = -1;\n    ctx->msg_mask = 0;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_flv_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp)\n{\n    ngx_rtmp_flv_ctx_t             *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);\n\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: seek timestamp=%ui\", timestamp);\n\n    ctx->start_timestamp = timestamp;\n    ctx->epoch = ngx_current_msec;\n    ctx->offset = -1;\n    ctx->msg_mask = 0;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_flv_stop(ngx_rtmp_session_t *s, ngx_file_t *f)\n{\n    ngx_rtmp_flv_ctx_t             *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_flv_module);\n\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                  \"flv: stop\");\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_flv_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_play_main_conf_t      *pmcf;\n    ngx_rtmp_play_fmt_t           **pfmt, *fmt;\n\n    pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);\n\n    pfmt = ngx_array_push(&pmcf->fmts);\n\n    if (pfmt == NULL) {\n        return NGX_ERROR;\n    }\n\n    fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t));\n\n    if (fmt == NULL) {\n        return NGX_ERROR;\n    }\n\n    *pfmt = fmt;\n\n    ngx_str_set(&fmt->name, \"flv-format\");\n\n    ngx_str_null(&fmt->pfx); /* default fmt */\n    ngx_str_set(&fmt->sfx, \".flv\");\n\n    fmt->init  = ngx_rtmp_flv_init;\n    fmt->start = ngx_rtmp_flv_start;\n    fmt->seek  = ngx_rtmp_flv_seek;\n    fmt->stop  = ngx_rtmp_flv_stop;\n    fmt->send  = ngx_rtmp_flv_send;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_handler.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_amf.h\"\n\n\nstatic void ngx_rtmp_recv(ngx_event_t *rev);\nstatic void ngx_rtmp_send(ngx_event_t *rev);\nstatic void ngx_rtmp_ping(ngx_event_t *rev);\nstatic ngx_int_t ngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s);\n\n\nngx_uint_t                  ngx_rtmp_naccepted;\n\n\nngx_rtmp_bandwidth_t        ngx_rtmp_bw_out;\nngx_rtmp_bandwidth_t        ngx_rtmp_bw_in;\n\n\n#ifdef NGX_DEBUG\nchar*\nngx_rtmp_message_type(uint8_t type)\n{\n    static char*    types[] = {\n        \"?\",\n        \"chunk_size\",\n        \"abort\",\n        \"ack\",\n        \"user\",\n        \"ack_size\",\n        \"bandwidth\",\n        \"edge\",\n        \"audio\",\n        \"video\",\n        \"?\",\n        \"?\",\n        \"?\",\n        \"?\",\n        \"?\",\n        \"amf3_meta\",\n        \"amf3_shared\",\n        \"amf3_cmd\",\n        \"amf_meta\",\n        \"amf_shared\",\n        \"amf_cmd\",\n        \"?\",\n        \"aggregate\"\n    };\n\n    return type < sizeof(types) / sizeof(types[0])\n        ? types[type]\n        : \"?\";\n}\n\n\nchar*\nngx_rtmp_user_message_type(uint16_t evt)\n{\n    static char*    evts[] = {\n        \"stream_begin\",\n        \"stream_eof\",\n        \"stream dry\",\n        \"set_buflen\",\n        \"recorded\",\n        \"\",\n        \"ping_request\",\n        \"ping_response\",\n    };\n\n    return evt < sizeof(evts) / sizeof(evts[0])\n        ? evts[evt]\n        : \"?\";\n}\n#endif\n\n\nvoid\nngx_rtmp_cycle(ngx_rtmp_session_t *s)\n{\n    ngx_connection_t           *c;\n\n    c = s->connection;\n    c->read->handler =  ngx_rtmp_recv;\n    c->write->handler = ngx_rtmp_send;\n\n    s->ping_evt.data = c;\n    s->ping_evt.log = c->log;\n    s->ping_evt.handler = ngx_rtmp_ping;\n    ngx_rtmp_reset_ping(s);\n\n    ngx_rtmp_recv(c->read);\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_alloc_in_buf(ngx_rtmp_session_t *s)\n{\n    ngx_chain_t        *cl;\n    ngx_buf_t          *b;\n    size_t              size;\n\n    if ((cl = ngx_alloc_chain_link(s->in_pool)) == NULL\n       || (cl->buf = ngx_calloc_buf(s->in_pool)) == NULL)\n    {\n        return NULL;\n    }\n\n    cl->next = NULL;\n    b = cl->buf;\n    size = s->in_chunk_size + NGX_RTMP_MAX_CHUNK_HEADER;\n\n    b->start = b->last = b->pos = ngx_palloc(s->in_pool, size);\n    if (b->start == NULL) {\n        return NULL;\n    }\n    b->end = b->start + size;\n\n    return cl;\n}\n\n\nvoid\nngx_rtmp_reset_ping(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_core_srv_conf_t   *cscf;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n    if (cscf->ping == 0) {\n        return;\n    }\n\n    s->ping_active = 0;\n    s->ping_reset = 0;\n    ngx_add_timer(&s->ping_evt, cscf->ping);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"ping: wait %Mms\", cscf->ping);\n}\n\n\nstatic void\nngx_rtmp_ping(ngx_event_t *pev)\n{\n    ngx_connection_t           *c;\n    ngx_rtmp_session_t         *s;\n    ngx_rtmp_core_srv_conf_t   *cscf;\n\n    c = pev->data;\n    s = c->data;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    /* i/o event has happened; no need to ping */\n    if (s->ping_reset) {\n        ngx_rtmp_reset_ping(s);\n        return;\n    }\n\n    if (s->ping_active) {\n        ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                \"ping: unresponded\");\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    if (cscf->busy) {\n        ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                \"ping: not busy between pings\");\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"ping: schedule %Mms\", cscf->ping_timeout);\n\n    if (ngx_rtmp_send_ping_request(s, (uint32_t)ngx_current_msec) != NGX_OK) {\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    s->ping_active = 1;\n    ngx_add_timer(pev, cscf->ping_timeout);\n}\n\n\nstatic void\nngx_rtmp_recv(ngx_event_t *rev)\n{\n    ngx_int_t                   n;\n    ngx_connection_t           *c;\n    ngx_rtmp_session_t         *s;\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_rtmp_header_t          *h;\n    ngx_rtmp_stream_t          *st, *st0;\n    ngx_chain_t                *in, *head;\n    ngx_buf_t                  *b;\n    u_char                     *p, *pp, *old_pos;\n    size_t                      size, fsize, old_size;\n    uint8_t                     fmt, ext;\n    uint32_t                    csid, timestamp;\n\n    c = rev->data;\n    s = c->data;\n    b = NULL;\n    old_pos = NULL;\n    old_size = 0;\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    if (c->destroyed) {\n        return;\n    }\n\n    for( ;; ) {\n\n        st = &s->in_streams[s->in_csid];\n\n        /* allocate new buffer */\n        if (st->in == NULL) {\n            st->in = ngx_rtmp_alloc_in_buf(s);\n            if (st->in == NULL) {\n                ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                        \"in buf alloc failed\");\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n        }\n\n        h  = &st->hdr;\n        in = st->in;\n        b  = in->buf;\n\n        if (old_size) {\n\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,\n                    \"reusing formerly read data: %d\", old_size);\n\n            b->pos = b->start;\n\n            size = ngx_min((size_t) (b->end - b->start), old_size);\n            b->last = ngx_movemem(b->pos, old_pos, size);\n\n            if (s->in_chunk_size_changing) {\n                ngx_rtmp_finalize_set_chunk_size(s);\n            }\n\n        } else {\n\n            if (old_pos) {\n                b->pos = b->last = b->start;\n            }\n\n            n = c->recv(c, b->last, b->end - b->last);\n\n            if (n == NGX_ERROR || n == 0) {\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n\n            if (n == NGX_AGAIN) {\n                if (ngx_handle_read_event(c->read, 0) != NGX_OK) {\n                    ngx_rtmp_finalize_session(s);\n                }\n                return;\n            }\n\n            s->ping_reset = 1;\n            ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_in, n);\n            b->last += n;\n            s->in_bytes += n;\n\n            if (s->in_bytes >= 0xf0000000) {\n                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0,\n                               \"resetting byte counter\");\n                s->in_bytes = 0;\n                s->in_last_ack = 0;\n            }\n\n            if (s->ack_size && s->in_bytes - s->in_last_ack >= s->ack_size) {\n\n                s->in_last_ack = s->in_bytes;\n\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,\n                        \"sending RTMP ACK(%uD)\", s->in_bytes);\n\n                if (ngx_rtmp_send_ack(s, s->in_bytes)) {\n                    ngx_rtmp_finalize_session(s);\n                    return;\n                }\n            }\n        }\n\n        old_pos = NULL;\n        old_size = 0;\n\n        /* parse headers */\n        if (b->pos == b->start) {\n            p = b->pos;\n\n            /* chunk basic header */\n            fmt  = (*p >> 6) & 0x03;\n            csid = *p++ & 0x3f;\n\n            if (csid == 0) {\n                if (b->last - p < 1)\n                    continue;\n                csid = 64;\n                csid += *(uint8_t*)p++;\n\n            } else if (csid == 1) {\n                if (b->last - p < 2)\n                    continue;\n                csid = 64;\n                csid += *(uint8_t*)p++;\n                csid += (uint32_t)256 * (*(uint8_t*)p++);\n            }\n\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, c->log, 0,\n                    \"RTMP bheader fmt=%d csid=%D\",\n                    (int)fmt, csid);\n\n            if (csid >= (uint32_t)cscf->max_streams) {\n                ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                    \"RTMP in chunk stream too big: %D >= %D\",\n                    csid, cscf->max_streams);\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n\n            /* link orphan */\n            if (s->in_csid == 0) {\n\n                /* unlink from stream #0 */\n                st->in = st->in->next;\n\n                /* link to new stream */\n                s->in_csid = csid;\n                st = &s->in_streams[csid];\n                if (st->in == NULL) {\n                    in->next = in;\n                } else {\n                    in->next = st->in->next;\n                    st->in->next = in;\n                }\n                st->in = in;\n                h = &st->hdr;\n                h->csid = csid;\n            }\n\n            ext = st->ext;\n            timestamp = st->dtime;\n            if (fmt <= 2 ) {\n                if (b->last - p < 3)\n                    continue;\n                /* timestamp:\n                 *  big-endian 3b -> little-endian 4b */\n                pp = (u_char*)&timestamp;\n                pp[2] = *p++;\n                pp[1] = *p++;\n                pp[0] = *p++;\n                pp[3] = 0;\n\n                ext = (timestamp == 0x00ffffff);\n\n                if (fmt <= 1) {\n                    if (b->last - p < 4)\n                        continue;\n                    /* size:\n                     *  big-endian 3b -> little-endian 4b\n                     * type:\n                     *  1b -> 1b*/\n                    pp = (u_char*)&h->mlen;\n                    pp[2] = *p++;\n                    pp[1] = *p++;\n                    pp[0] = *p++;\n                    pp[3] = 0;\n                    h->type = *(uint8_t*)p++;\n\n                    if (fmt == 0) {\n                        if (b->last - p < 4)\n                            continue;\n                        /* stream:\n                         *  little-endian 4b -> little-endian 4b */\n                        pp = (u_char*)&h->msid;\n                        pp[0] = *p++;\n                        pp[1] = *p++;\n                        pp[2] = *p++;\n                        pp[3] = *p++;\n                    }\n                }\n            }\n\n            /* extended header */\n            if (ext) {\n                if (b->last - p < 4)\n                    continue;\n                pp = (u_char*)&timestamp;\n                pp[3] = *p++;\n                pp[2] = *p++;\n                pp[1] = *p++;\n                pp[0] = *p++;\n            }\n\n            if (st->len == 0) {\n                /* Messages with type=3 should\n                 * never have ext timestamp field\n                 * according to standard.\n                 * However that's not always the case\n                 * in real life */\n                st->ext = (ext && cscf->publish_time_fix);\n                if (fmt) {\n                    st->dtime = timestamp;\n                } else {\n                    h->timestamp = timestamp;\n                    st->dtime = 0;\n                }\n            }\n\n            ngx_log_debug8(NGX_LOG_DEBUG_RTMP, c->log, 0,\n                    \"RTMP mheader fmt=%d %s (%d) \"\n                    \"time=%uD+%uD mlen=%D len=%D msid=%D\",\n                    (int)fmt, ngx_rtmp_message_type(h->type), (int)h->type,\n                    h->timestamp, st->dtime, h->mlen, st->len, h->msid);\n\n            /* header done */\n            b->pos = p;\n\n            if (h->mlen > cscf->max_message) {\n                ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                        \"too big message: %uz\", cscf->max_message);\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n        }\n\n        size = b->last - b->pos;\n        fsize = h->mlen - st->len;\n\n        if (size < ngx_min(fsize, s->in_chunk_size))\n            continue;\n\n        /* buffer is ready */\n\n        if (fsize > s->in_chunk_size) {\n            /* collect fragmented chunks */\n            st->len += s->in_chunk_size;\n            b->last = b->pos + s->in_chunk_size;\n            old_pos = b->last;\n            old_size = size - s->in_chunk_size;\n\n        } else {\n            /* handle! */\n            head = st->in->next;\n            st->in->next = NULL;\n            b->last = b->pos + fsize;\n            old_pos = b->last;\n            old_size = size - fsize;\n            st->len = 0;\n            h->timestamp += st->dtime;\n\n            if (ngx_rtmp_receive_message(s, h, head) != NGX_OK) {\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n\n            if (s->in_chunk_size_changing) {\n                /* copy old data to a new buffer */\n                if (!old_size) {\n                    ngx_rtmp_finalize_set_chunk_size(s);\n                }\n\n            } else {\n                /* add used bufs to stream #0 */\n                st0 = &s->in_streams[0];\n                st->in->next = st0->in;\n                st0->in = head;\n                st->in = NULL;\n            }\n        }\n\n        s->in_csid = 0;\n    }\n}\n\n\nstatic void\nngx_rtmp_send(ngx_event_t *wev)\n{\n    ngx_connection_t           *c;\n    ngx_rtmp_session_t         *s;\n    ngx_int_t                   n;\n    ngx_rtmp_core_srv_conf_t   *cscf;\n\n    c = wev->data;\n    s = c->data;\n\n    if (c->destroyed) {\n        return;\n    }\n\n    if (wev->timedout) {\n        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,\n                \"client timed out\");\n        c->timedout = 1;\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    if (wev->timer_set) {\n        ngx_del_timer(wev);\n    }\n\n    if (s->out_chain == NULL && s->out_pos != s->out_last) {\n        s->out_chain = s->out[s->out_pos];\n        s->out_bpos = s->out_chain->buf->pos;\n    }\n\n    while (s->out_chain) {\n        n = c->send(c, s->out_bpos, s->out_chain->buf->last - s->out_bpos);\n\n        if (n == NGX_AGAIN || n == 0) {\n            ngx_add_timer(c->write, s->timeout);\n            if (ngx_handle_write_event(c->write, 0) != NGX_OK) {\n                ngx_rtmp_finalize_session(s);\n            }\n            return;\n        }\n\n        if (n < 0) {\n            ngx_rtmp_finalize_session(s);\n            return;\n        }\n\n        s->out_bytes += n;\n        s->ping_reset = 1;\n        ngx_rtmp_update_bandwidth(&ngx_rtmp_bw_out, n);\n        s->out_bpos += n;\n        if (s->out_bpos == s->out_chain->buf->last) {\n            s->out_chain = s->out_chain->next;\n            if (s->out_chain == NULL) {\n                cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n                ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos]);\n                ++s->out_pos;\n                s->out_pos %= s->out_queue;\n                if (s->out_pos == s->out_last) {\n                    break;\n                }\n                s->out_chain = s->out[s->out_pos];\n            }\n            s->out_bpos = s->out_chain->buf->pos;\n        }\n    }\n\n    if (wev->active) {\n        ngx_del_event(wev, NGX_WRITE_EVENT, 0);\n    }\n\n    ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &s->posted_dry_events);\n}\n\n\nvoid\nngx_rtmp_prepare_message(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_rtmp_header_t *lh, ngx_chain_t *out)\n{\n    ngx_chain_t                *l;\n    u_char                     *p, *pp;\n    ngx_int_t                   hsize, thsize;\n#if (NGX_DEBUG)\n    ngx_int_t                   nbufs;\n#endif\n    uint32_t                    mlen, timestamp, ext_timestamp;\n    static uint8_t              hdrsize[] = { 12, 8, 4, 1 };\n    u_char                      th[7];\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    uint8_t                     fmt;\n    ngx_connection_t           *c;\n\n    c = s->connection;\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    if (h->csid >= (uint32_t)cscf->max_streams) {\n        ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                \"RTMP out chunk stream too big: %D >= %D\",\n                h->csid, cscf->max_streams);\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    /* detect packet size */\n    mlen = 0;\n#if (NGX_DEBUG)\n    nbufs = 0;\n#endif\n    for(l = out; l; l = l->next) {\n        mlen += (l->buf->last - l->buf->pos);\n#if (NGX_DEBUG)\n        ++nbufs;\n#endif\n    }\n\n    fmt = 0;\n    if (lh && lh->csid && h->msid == lh->msid) {\n        ++fmt;\n        if (h->type == lh->type && mlen && mlen == lh->mlen) {\n            ++fmt;\n            if (h->timestamp == lh->timestamp) {\n                ++fmt;\n            }\n        }\n        timestamp = h->timestamp - lh->timestamp;\n    } else {\n        timestamp = h->timestamp;\n    }\n\n    /*if (lh) {\n        *lh = *h;\n        lh->mlen = mlen;\n    }*/\n\n    hsize = hdrsize[fmt];\n\n    ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"RTMP prep %s (%d) fmt=%d csid=%uD timestamp=%uD \"\n            \"mlen=%uD msid=%uD nbufs=%d\",\n            ngx_rtmp_message_type(h->type), (int)h->type, (int)fmt,\n            h->csid, timestamp, mlen, h->msid, nbufs);\n\n    ext_timestamp = 0;\n    if (timestamp >= 0x00ffffff) {\n        ext_timestamp = timestamp;\n        timestamp = 0x00ffffff;\n        hsize += 4;\n    }\n\n    if (h->csid >= 64) {\n        ++hsize;\n        if (h->csid >= 320) {\n            ++hsize;\n        }\n    }\n\n    /* fill initial header */\n    out->buf->pos -= hsize;\n    p = out->buf->pos;\n\n    /* basic header */\n    *p = (fmt << 6);\n    if (h->csid >= 2 && h->csid <= 63) {\n        *p++ |= (((uint8_t)h->csid) & 0x3f);\n    } else if (h->csid >= 64 && h->csid < 320) {\n        ++p;\n        *p++ = (uint8_t)(h->csid - 64);\n    } else {\n        *p++ |= 1;\n        *p++ = (uint8_t)(h->csid - 64);\n        *p++ = (uint8_t)((h->csid - 64) >> 8);\n    }\n\n    /* create fmt3 header for successive fragments */\n    thsize = p - out->buf->pos;\n    ngx_memcpy(th, out->buf->pos, thsize);\n    th[0] |= 0xc0;\n\n    /* message header */\n    if (fmt <= 2) {\n        pp = (u_char*)&timestamp;\n        *p++ = pp[2];\n        *p++ = pp[1];\n        *p++ = pp[0];\n        if (fmt <= 1) {\n            pp = (u_char*)&mlen;\n            *p++ = pp[2];\n            *p++ = pp[1];\n            *p++ = pp[0];\n            *p++ = h->type;\n            if (fmt == 0) {\n                pp = (u_char*)&h->msid;\n                *p++ = pp[0];\n                *p++ = pp[1];\n                *p++ = pp[2];\n                *p++ = pp[3];\n            }\n        }\n    }\n\n    /* extended header */\n    if (ext_timestamp) {\n        pp = (u_char*)&ext_timestamp;\n        *p++ = pp[3];\n        *p++ = pp[2];\n        *p++ = pp[1];\n        *p++ = pp[0];\n\n        /* This CONTRADICTS the standard\n         * but that's the way flash client\n         * wants data to be encoded;\n         * ffmpeg complains */\n        if (cscf->play_time_fix) {\n            ngx_memcpy(&th[thsize], p - 4, 4);\n            thsize += 4;\n        }\n    }\n\n    /* append headers to successive fragments */\n    for(out = out->next; out; out = out->next) {\n        out->buf->pos -= thsize;\n        ngx_memcpy(out->buf->pos, th, thsize);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_message(ngx_rtmp_session_t *s, ngx_chain_t *out,\n        ngx_uint_t priority)\n{\n    ngx_uint_t                      nmsg;\n\n    nmsg = (s->out_last - s->out_pos) % s->out_queue + 1;\n\n    if (priority > 3) {\n        priority = 3;\n    }\n\n    /* drop packet?\n     * Note we always leave 1 slot free */\n    if (nmsg + priority * s->out_queue / 4 >= s->out_queue) {\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"RTMP drop message bufs=%ui, priority=%ui\",\n                nmsg, priority);\n        return NGX_AGAIN;\n    }\n\n    s->out[s->out_last++] = out;\n    s->out_last %= s->out_queue;\n\n    ngx_rtmp_acquire_shared_chain(out);\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"RTMP send nmsg=%ui, priority=%ui #%ui\",\n            nmsg, priority, s->out_last);\n\n    if (priority && s->out_buffer && nmsg < s->out_cork) {\n        return NGX_OK;\n    }\n\n    if (!s->connection->write->active) {\n        ngx_rtmp_send(s->connection->write);\n        /*return ngx_add_event(s->connection->write, NGX_WRITE_EVENT, NGX_CLEAR_EVENT);*/\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_receive_message(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in)\n{\n    ngx_rtmp_core_main_conf_t  *cmcf;\n    ngx_array_t                *evhs;\n    size_t                      n;\n    ngx_rtmp_handler_pt        *evh;\n\n    cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);\n\n#ifdef NGX_DEBUG\n    {\n        int             nbufs;\n        ngx_chain_t    *ch;\n\n        for(nbufs = 1, ch = in;\n                ch->next;\n                ch = ch->next, ++nbufs);\n\n        ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"RTMP recv %s (%d) csid=%D timestamp=%D \"\n                \"mlen=%D msid=%D nbufs=%d\",\n                ngx_rtmp_message_type(h->type), (int)h->type,\n                h->csid, h->timestamp, h->mlen, h->msid, nbufs);\n    }\n#endif\n\n    if (h->type > NGX_RTMP_MSG_MAX) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"unexpected RTMP message type: %d\", (int)h->type);\n        return NGX_OK;\n    }\n\n    evhs = &cmcf->events[h->type];\n    evh = evhs->elts;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"nhandlers: %d\", evhs->nelts);\n\n    for(n = 0; n < evhs->nelts; ++n, ++evh) {\n        if (!evh) {\n            continue;\n        }\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"calling handler %d\", n);\n\n        switch ((*evh)(s, h, in)) {\n            case NGX_ERROR:\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                        \"handler %d failed\", n);\n                return NGX_ERROR;\n            case NGX_DONE:\n                return NGX_OK;\n        }\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_set_chunk_size(ngx_rtmp_session_t *s, ngx_uint_t size)\n{\n    ngx_rtmp_core_srv_conf_t           *cscf;\n    ngx_chain_t                        *li, *fli, *lo, *flo;\n    ngx_buf_t                          *bi, *bo;\n    ngx_int_t                           n;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n        \"setting chunk_size=%ui\", size);\n\n    if (size > NGX_RTMP_MAX_CHUNK_SIZE) {\n        ngx_log_error(NGX_LOG_ALERT, s->connection->log, 0,\n                      \"too big RTMP chunk size:%ui\", size);\n        return NGX_ERROR;\n    }\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    s->in_old_pool = s->in_pool;\n    s->in_chunk_size = size;\n    s->in_pool = ngx_create_pool(4096, s->connection->log);\n\n    /* copy existing chunk data */\n    if (s->in_old_pool) {\n        s->in_chunk_size_changing = 1;\n        s->in_streams[0].in = NULL;\n\n        for(n = 1; n < cscf->max_streams; ++n) {\n            /* stream buffer is circular\n             * for all streams except for the current one\n             * (which caused this chunk size change);\n             * we can simply ignore it */\n            li = s->in_streams[n].in;\n            if (li == NULL || li->next == NULL) {\n                s->in_streams[n].in = NULL;\n                continue;\n            }\n            /* move from last to the first */\n            li = li->next;\n            fli = li;\n            lo = ngx_rtmp_alloc_in_buf(s);\n            if (lo == NULL) {\n                return NGX_ERROR;\n            }\n            flo = lo;\n            for ( ;; ) {\n                bi = li->buf;\n                bo = lo->buf;\n\n                if (bo->end - bo->last >= bi->last - bi->pos) {\n                    bo->last = ngx_cpymem(bo->last, bi->pos,\n                            bi->last - bi->pos);\n                    li = li->next;\n                    if (li == fli)  {\n                        lo->next = flo;\n                        s->in_streams[n].in = lo;\n                        break;\n                    }\n                    continue;\n                }\n\n                bi->pos += (ngx_cpymem(bo->last, bi->pos,\n                            bo->end - bo->last) - bo->last);\n                lo->next = ngx_rtmp_alloc_in_buf(s);\n                lo = lo->next;\n                if (lo == NULL) {\n                    return NGX_ERROR;\n                }\n            }\n        }\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_finalize_set_chunk_size(ngx_rtmp_session_t *s)\n{\n    if (s->in_chunk_size_changing && s->in_old_pool) {\n        ngx_destroy_pool(s->in_old_pool);\n        s->in_old_pool = NULL;\n        s->in_chunk_size_changing = 0;\n    }\n    return NGX_OK;\n}\n\n\n"
  },
  {
    "path": "ngx_rtmp_handshake.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n#include <openssl/hmac.h>\n#include <openssl/sha.h>\n\n\nstatic void ngx_rtmp_handshake_send(ngx_event_t *wev);\nstatic void ngx_rtmp_handshake_recv(ngx_event_t *rev);\nstatic void ngx_rtmp_handshake_done(ngx_rtmp_session_t *s);\n\n\n/* RTMP handshake :\n *\n *          =peer1=                      =peer2=\n * challenge ----> (.....[digest1]......) ----> 1537 bytes\n * response  <---- (...........[digest2]) <---- 1536 bytes\n *\n *\n * - both packets contain random bytes except for digests\n * - digest1 position is calculated on random packet bytes\n * - digest2 is always at the end of the packet\n *\n * digest1: HMAC_SHA256(packet, peer1_partial_key)\n * digest2: HMAC_SHA256(packet, HMAC_SHA256(digest1, peer2_full_key))\n */\n\n\n/* Handshake keys */\nstatic u_char\nngx_rtmp_server_key[] = {\n    'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',\n    'F', 'l', 'a', 's', 'h', ' ', 'M', 'e', 'd', 'i', 'a', ' ',\n    'S', 'e', 'r', 'v', 'e', 'r', ' ',\n    '0', '0', '1',\n\n    0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,\n    0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,\n    0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE\n};\n\n\nstatic u_char\nngx_rtmp_client_key[] = {\n    'G', 'e', 'n', 'u', 'i', 'n', 'e', ' ', 'A', 'd', 'o', 'b', 'e', ' ',\n    'F', 'l', 'a', 's', 'h', ' ', 'P', 'l', 'a', 'y', 'e', 'r', ' ',\n    '0', '0', '1',\n\n    0xF0, 0xEE, 0xC2, 0x4A, 0x80, 0x68, 0xBE, 0xE8, 0x2E, 0x00, 0xD0, 0xD1,\n    0x02, 0x9E, 0x7E, 0x57, 0x6E, 0xEC, 0x5D, 0x2D, 0x29, 0x80, 0x6F, 0xAB,\n    0x93, 0xB8, 0xE6, 0x36, 0xCF, 0xEB, 0x31, 0xAE\n};\n\n\nstatic const u_char\nngx_rtmp_server_version[4] = {\n    0x0D, 0x0E, 0x0A, 0x0D\n};\n\n\nstatic const u_char\nngx_rtmp_client_version[4] = {\n    0x0C, 0x00, 0x0D, 0x0E\n};\n\n\n#define NGX_RTMP_HANDSHAKE_KEYLEN                   SHA256_DIGEST_LENGTH\n#define NGX_RTMP_HANDSHAKE_BUFSIZE                  1537\n\n\n#define NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE    1\n#define NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE    2\n#define NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE     3\n#define NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE     4\n#define NGX_RTMP_HANDSHAKE_SERVER_DONE              5\n\n\n#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE    6\n#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE    7\n#define NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE     8\n#define NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE     9\n#define NGX_RTMP_HANDSHAKE_CLIENT_DONE              10\n\n\nstatic ngx_str_t            ngx_rtmp_server_full_key\n    = { sizeof(ngx_rtmp_server_key), ngx_rtmp_server_key };\nstatic ngx_str_t            ngx_rtmp_server_partial_key\n    = { 36, ngx_rtmp_server_key };\n\nstatic ngx_str_t            ngx_rtmp_client_full_key\n    = { sizeof(ngx_rtmp_client_key), ngx_rtmp_client_key };\nstatic ngx_str_t            ngx_rtmp_client_partial_key\n    = { 30, ngx_rtmp_client_key };\n\n\nstatic ngx_int_t\nngx_rtmp_make_digest(ngx_str_t *key, ngx_buf_t *src,\n        u_char *skip, u_char *dst, ngx_log_t *log)\n{\n    static HMAC_CTX        *hmac;\n    unsigned int            len;\n\n    if (hmac == NULL) {\n#if OPENSSL_VERSION_NUMBER < 0x10100000L\n        static HMAC_CTX  shmac;\n        hmac = &shmac;\n        HMAC_CTX_init(hmac);\n#else\n        hmac = HMAC_CTX_new();\n        if (hmac == NULL) {\n            return NGX_ERROR;\n        }\n#endif\n    }\n\n    HMAC_Init_ex(hmac, key->data, key->len, EVP_sha256(), NULL);\n\n    if (skip && src->pos <= skip && skip <= src->last) {\n        if (skip != src->pos) {\n            HMAC_Update(hmac, src->pos, skip - src->pos);\n        }\n        if (src->last != skip + NGX_RTMP_HANDSHAKE_KEYLEN) {\n            HMAC_Update(hmac, skip + NGX_RTMP_HANDSHAKE_KEYLEN,\n                    src->last - skip - NGX_RTMP_HANDSHAKE_KEYLEN);\n        }\n    } else {\n        HMAC_Update(hmac, src->pos, src->last - src->pos);\n    }\n\n    HMAC_Final(hmac, dst, &len);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_find_digest(ngx_buf_t *b, ngx_str_t *key, size_t base, ngx_log_t *log)\n{\n    size_t                  n, offs;\n    u_char                  digest[NGX_RTMP_HANDSHAKE_KEYLEN];\n    u_char                 *p;\n\n    offs = 0;\n    for (n = 0; n < 4; ++n) {\n        offs += b->pos[base + n];\n    }\n    offs = (offs % 728) + base + 4;\n    p = b->pos + offs;\n\n    if (ngx_rtmp_make_digest(key, b, p, digest, log) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    if (ngx_memcmp(digest, p, NGX_RTMP_HANDSHAKE_KEYLEN) == 0) {\n        return offs;\n    }\n\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_write_digest(ngx_buf_t *b, ngx_str_t *key, size_t base,\n        ngx_log_t *log)\n{\n    size_t                  n, offs;\n    u_char                 *p;\n\n    offs = 0;\n    for (n = 8; n < 12; ++n) {\n        offs += b->pos[base + n];\n    }\n    offs = (offs % 728) + base + 12;\n    p = b->pos + offs;\n\n    if (ngx_rtmp_make_digest(key, b, p, p, log) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_fill_random_buffer(ngx_buf_t *b)\n{\n    for (; b->last != b->end; ++b->last) {\n        *b->last = (u_char) rand();\n    }\n}\n\n\nstatic ngx_buf_t *\nngx_rtmp_alloc_handshake_buffer(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_chain_t                *cl;\n    ngx_buf_t                  *b;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"handshake: allocating buffer\");\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    if (cscf->free_hs) {\n        cl = cscf->free_hs;\n        b = cl->buf;\n        cscf->free_hs = cl->next;\n        ngx_free_chain(cscf->pool, cl);\n\n    } else {\n        b = ngx_pcalloc(cscf->pool, sizeof(ngx_buf_t));\n        if (b == NULL) {\n            return NULL;\n        }\n        b->memory = 1;\n        b->start = ngx_pcalloc(cscf->pool, NGX_RTMP_HANDSHAKE_BUFSIZE);\n        if (b->start == NULL) {\n            return NULL;\n        }\n        b->end = b->start + NGX_RTMP_HANDSHAKE_BUFSIZE;\n    }\n\n    b->pos = b->last = b->start;\n\n    return b;\n}\n\n\nvoid\nngx_rtmp_free_handshake_buffers(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_chain_t                *cl;\n\n    if (s->hs_buf == NULL) {\n        return;\n    }\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n    cl = ngx_alloc_chain_link(cscf->pool);\n    if (cl == NULL) {\n        return;\n    }\n    cl->buf = s->hs_buf;\n    cl->next = cscf->free_hs;\n    cscf->free_hs = cl;\n    s->hs_buf = NULL;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_handshake_create_challenge(ngx_rtmp_session_t *s,\n        const u_char version[4], ngx_str_t *key)\n{\n    ngx_buf_t          *b;\n\n    b = s->hs_buf;\n    b->last = b->pos = b->start;\n    *b->last++ = '\\x03';\n    b->last = ngx_rtmp_rcpymem(b->last, &s->epoch, 4);\n    b->last = ngx_cpymem(b->last, version, 4);\n    ngx_rtmp_fill_random_buffer(b);\n    ++b->pos;\n    if (ngx_rtmp_write_digest(b, key, 0, s->connection->log) != NGX_OK) {\n        return NGX_ERROR;\n    }\n    --b->pos;\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_handshake_parse_challenge(ngx_rtmp_session_t *s,\n        ngx_str_t *peer_key, ngx_str_t *key)\n{\n    ngx_buf_t              *b;\n    u_char                 *p;\n    ngx_int_t               offs;\n\n    b = s->hs_buf;\n    if (*b->pos != '\\x03') {\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                \"handshake: unexpected RTMP version: %i\",\n                (ngx_int_t)*b->pos);\n        return NGX_ERROR;\n    }\n    ++b->pos;\n    s->peer_epoch = 0;\n    ngx_rtmp_rmemcpy(&s->peer_epoch, b->pos, 4);\n\n    p = b->pos + 4;\n    ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"handshake: peer version=%i.%i.%i.%i epoch=%uD\",\n            (ngx_int_t)p[3], (ngx_int_t)p[2],\n            (ngx_int_t)p[1], (ngx_int_t)p[0],\n            (uint32_t)s->peer_epoch);\n    if (*(uint32_t *)p == 0) {\n        s->hs_old = 1;\n        return NGX_OK;\n    }\n\n    offs = ngx_rtmp_find_digest(b, peer_key, 772, s->connection->log);\n    if (offs == NGX_ERROR) {\n        offs = ngx_rtmp_find_digest(b, peer_key, 8, s->connection->log);\n    }\n    if (offs == NGX_ERROR) {\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                \"handshake: digest not found\");\n        s->hs_old = 1;\n        return NGX_OK;\n    }\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"handshake: digest found at pos=%i\", offs);\n    b->pos += offs;\n    b->last = b->pos + NGX_RTMP_HANDSHAKE_KEYLEN;\n    s->hs_digest = ngx_palloc(s->connection->pool, NGX_RTMP_HANDSHAKE_KEYLEN);\n    if (ngx_rtmp_make_digest(key, b, NULL, s->hs_digest, s->connection->log)\n            != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_handshake_create_response(ngx_rtmp_session_t *s)\n{\n    ngx_buf_t          *b;\n    u_char             *p;\n    ngx_str_t           key;\n\n    b = s->hs_buf;\n    b->pos = b->last = b->start + 1;\n    ngx_rtmp_fill_random_buffer(b);\n    if (s->hs_digest) {\n        p = b->last - NGX_RTMP_HANDSHAKE_KEYLEN;\n        key.data = s->hs_digest;\n        key.len = NGX_RTMP_HANDSHAKE_KEYLEN;\n        if (ngx_rtmp_make_digest(&key, b, p, p, s->connection->log) != NGX_OK) {\n            return NGX_ERROR;\n        }\n    }\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_handshake_done(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_free_handshake_buffers(s);\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"handshake: done\");\n\n    if (ngx_rtmp_fire_event(s, NGX_RTMP_HANDSHAKE_DONE,\n                NULL, NULL) != NGX_OK)\n    {\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    ngx_rtmp_cycle(s);\n}\n\n\nstatic void\nngx_rtmp_handshake_recv(ngx_event_t *rev)\n{\n    ssize_t                     n;\n    ngx_connection_t           *c;\n    ngx_rtmp_session_t         *s;\n    ngx_buf_t                  *b;\n\n    c = rev->data;\n    s = c->data;\n\n    if (c->destroyed) {\n        return;\n    }\n\n    if (rev->timedout) {\n        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,\n                \"handshake: recv: client timed out\");\n        c->timedout = 1;\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    if (rev->timer_set) {\n        ngx_del_timer(rev);\n    }\n\n    b = s->hs_buf;\n\n    while (b->last != b->end) {\n        n = c->recv(c, b->last, b->end - b->last);\n\n        if (n == NGX_ERROR || n == 0) {\n            ngx_rtmp_finalize_session(s);\n            return;\n        }\n\n        if (n == NGX_AGAIN) {\n            ngx_add_timer(rev, s->timeout);\n            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {\n                ngx_rtmp_finalize_session(s);\n            }\n            return;\n        }\n\n        b->last += n;\n    }\n\n    if (rev->active) {\n        ngx_del_event(rev, NGX_READ_EVENT, 0);\n    }\n\n    ++s->hs_stage;\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"handshake: stage %ui\", s->hs_stage);\n\n    switch (s->hs_stage) {\n        case NGX_RTMP_HANDSHAKE_SERVER_SEND_CHALLENGE:\n            if (ngx_rtmp_handshake_parse_challenge(s,\n                    &ngx_rtmp_client_partial_key,\n                    &ngx_rtmp_server_full_key) != NGX_OK)\n            {\n                ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                        \"handshake: error parsing challenge\");\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n            if (s->hs_old) {\n                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                        \"handshake: old-style challenge\");\n                s->hs_buf->pos = s->hs_buf->start;\n                s->hs_buf->last = s->hs_buf->end;\n            } else if (ngx_rtmp_handshake_create_challenge(s,\n                        ngx_rtmp_server_version,\n                        &ngx_rtmp_server_partial_key) != NGX_OK)\n            {\n                ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                        \"handshake: error creating challenge\");\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n            ngx_rtmp_handshake_send(c->write);\n            break;\n\n        case NGX_RTMP_HANDSHAKE_SERVER_DONE:\n            ngx_rtmp_handshake_done(s);\n            break;\n\n        case NGX_RTMP_HANDSHAKE_CLIENT_RECV_RESPONSE:\n            if (ngx_rtmp_handshake_parse_challenge(s,\n                    &ngx_rtmp_server_partial_key,\n                    &ngx_rtmp_client_full_key) != NGX_OK)\n            {\n                ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                        \"handshake: error parsing challenge\");\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;\n            ngx_rtmp_handshake_recv(c->read);\n            break;\n\n        case NGX_RTMP_HANDSHAKE_CLIENT_SEND_RESPONSE:\n            if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {\n                ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                        \"handshake: response error\");\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n            ngx_rtmp_handshake_send(c->write);\n            break;\n    }\n}\n\n\nstatic void\nngx_rtmp_handshake_send(ngx_event_t *wev)\n{\n    ngx_int_t                   n;\n    ngx_connection_t           *c;\n    ngx_rtmp_session_t         *s;\n    ngx_buf_t                  *b;\n\n    c = wev->data;\n    s = c->data;\n\n    if (c->destroyed) {\n        return;\n    }\n\n    if (wev->timedout) {\n        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,\n                \"handshake: send: client timed out\");\n        c->timedout = 1;\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    if (wev->timer_set) {\n        ngx_del_timer(wev);\n    }\n\n    b = s->hs_buf;\n\n    while(b->pos != b->last) {\n        n = c->send(c, b->pos, b->last - b->pos);\n\n        if (n == NGX_ERROR) {\n            ngx_rtmp_finalize_session(s);\n            return;\n        }\n\n        if (n == NGX_AGAIN || n == 0) {\n            ngx_add_timer(c->write, s->timeout);\n            if (ngx_handle_write_event(c->write, 0) != NGX_OK) {\n                ngx_rtmp_finalize_session(s);\n            }\n            return;\n        }\n\n        b->pos += n;\n    }\n\n    if (wev->active) {\n        ngx_del_event(wev, NGX_WRITE_EVENT, 0);\n    }\n\n    ++s->hs_stage;\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"handshake: stage %ui\", s->hs_stage);\n\n    switch (s->hs_stage) {\n        case NGX_RTMP_HANDSHAKE_SERVER_SEND_RESPONSE:\n            if (s->hs_old) {\n                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                        \"handshake: old-style response\");\n                s->hs_buf->pos = s->hs_buf->start + 1;\n                s->hs_buf->last = s->hs_buf->end;\n            } else if (ngx_rtmp_handshake_create_response(s) != NGX_OK) {\n                ngx_log_error(NGX_LOG_INFO, c->log, 0,\n                        \"handshake: response error\");\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n            ngx_rtmp_handshake_send(wev);\n            break;\n\n        case NGX_RTMP_HANDSHAKE_SERVER_RECV_RESPONSE:\n            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start + 1;\n            ngx_rtmp_handshake_recv(c->read);\n            break;\n\n        case NGX_RTMP_HANDSHAKE_CLIENT_RECV_CHALLENGE:\n            s->hs_buf->pos = s->hs_buf->last = s->hs_buf->start;\n            ngx_rtmp_handshake_recv(c->read);\n            break;\n\n        case NGX_RTMP_HANDSHAKE_CLIENT_DONE:\n            ngx_rtmp_handshake_done(s);\n            break;\n    }\n}\n\n\nvoid\nngx_rtmp_handshake(ngx_rtmp_session_t *s)\n{\n    ngx_connection_t           *c;\n\n    c = s->connection;\n    c->read->handler =  ngx_rtmp_handshake_recv;\n    c->write->handler = ngx_rtmp_handshake_send;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"handshake: start server handshake\");\n\n    s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);\n    s->hs_stage = NGX_RTMP_HANDSHAKE_SERVER_RECV_CHALLENGE;\n\n    ngx_rtmp_handshake_recv(c->read);\n}\n\n\nvoid\nngx_rtmp_client_handshake(ngx_rtmp_session_t *s, unsigned async)\n{\n    ngx_connection_t           *c;\n\n    c = s->connection;\n    c->read->handler =  ngx_rtmp_handshake_recv;\n    c->write->handler = ngx_rtmp_handshake_send;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"handshake: start client handshake\");\n\n    s->hs_buf = ngx_rtmp_alloc_handshake_buffer(s);\n    s->hs_stage = NGX_RTMP_HANDSHAKE_CLIENT_SEND_CHALLENGE;\n\n    if (ngx_rtmp_handshake_create_challenge(s,\n                ngx_rtmp_client_version,\n                &ngx_rtmp_client_partial_key) != NGX_OK)\n    {\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    if (async) {\n        ngx_add_timer(c->write, s->timeout);\n        if (ngx_handle_write_event(c->write, 0) != NGX_OK) {\n            ngx_rtmp_finalize_session(s);\n        }\n        return;\n    }\n\n    ngx_rtmp_handshake_send(c->write);\n}\n\n"
  },
  {
    "path": "ngx_rtmp_init.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_proxy_protocol.h\"\n\n\nstatic void ngx_rtmp_close_connection(ngx_connection_t *c);\nstatic u_char * ngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len);\n\n\nvoid\nngx_rtmp_init_connection(ngx_connection_t *c)\n{\n    ngx_uint_t             i;\n    ngx_rtmp_port_t       *port;\n    struct sockaddr       *sa;\n    struct sockaddr_in    *sin;\n    ngx_rtmp_in_addr_t    *addr;\n    ngx_rtmp_session_t    *s;\n    ngx_rtmp_addr_conf_t  *addr_conf;\n    ngx_int_t              unix_socket;\n#if (NGX_HAVE_INET6)\n    struct sockaddr_in6   *sin6;\n    ngx_rtmp_in6_addr_t   *addr6;\n#endif\n\n    ++ngx_rtmp_naccepted;\n\n    /* find the server configuration for the address:port */\n\n    /* AF_INET only */\n\n    port = c->listening->servers;\n    unix_socket = 0;\n\n    if (port->naddrs > 1) {\n\n        /*\n         * There are several addresses on this port and one of them\n         * is the \"*:port\" wildcard so getsockname() is needed to determine\n         * the server address.\n         *\n         * AcceptEx() already gave this address.\n         */\n\n        if (ngx_connection_local_sockaddr(c, NULL, 0) != NGX_OK) {\n            ngx_rtmp_close_connection(c);\n            return;\n        }\n\n        sa = c->local_sockaddr;\n\n        switch (sa->sa_family) {\n\n#if (NGX_HAVE_INET6)\n        case AF_INET6:\n            sin6 = (struct sockaddr_in6 *) sa;\n\n            addr6 = port->addrs;\n\n            /* the last address is \"*\" */\n\n            for (i = 0; i < port->naddrs - 1; i++) {\n                if (ngx_memcmp(&addr6[i].addr6, &sin6->sin6_addr, 16) == 0) {\n                    break;\n                }\n            }\n\n            addr_conf = &addr6[i].conf;\n\n            break;\n#endif\n\n        case AF_UNIX:\n            unix_socket = 1;\n            /* fall through */\n\n        default: /* AF_INET */\n            sin = (struct sockaddr_in *) sa;\n\n            addr = port->addrs;\n\n            /* the last address is \"*\" */\n\n            for (i = 0; i < port->naddrs - 1; i++) {\n                if (addr[i].addr == sin->sin_addr.s_addr) {\n                    break;\n                }\n            }\n\n            addr_conf = &addr[i].conf;\n\n            break;\n        }\n\n    } else {\n        switch (c->local_sockaddr->sa_family) {\n\n#if (NGX_HAVE_INET6)\n        case AF_INET6:\n            addr6 = port->addrs;\n            addr_conf = &addr6[0].conf;\n            break;\n#endif\n\n        case AF_UNIX:\n            unix_socket = 1;\n            /* fall through */\n\n        default: /* AF_INET */\n            addr = port->addrs;\n            addr_conf = &addr[0].conf;\n            break;\n        }\n    }\n\n    ngx_log_error(NGX_LOG_INFO, c->log, 0, \"*%ui client connected '%V'\",\n                  c->number, &c->addr_text);\n\n    s = ngx_rtmp_init_session(c, addr_conf);\n    if (s == NULL) {\n        return;\n    }\n\n    /* only auto-pushed connections are\n     * done through unix socket */\n\n    s->auto_pushed = unix_socket;\n\n    if (addr_conf->proxy_protocol) {\n        ngx_rtmp_proxy_protocol(s);\n\n    } else {\n        ngx_rtmp_handshake(s);\n    }\n}\n\n\nngx_rtmp_session_t *\nngx_rtmp_init_session(ngx_connection_t *c, ngx_rtmp_addr_conf_t *addr_conf)\n{\n    ngx_rtmp_session_t             *s;\n    ngx_rtmp_core_srv_conf_t       *cscf;\n    ngx_rtmp_error_log_ctx_t       *ctx;\n\n    s = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_session_t) +\n            sizeof(ngx_chain_t *) * ((ngx_rtmp_core_srv_conf_t *)\n                addr_conf->ctx-> srv_conf[ngx_rtmp_core_module\n                    .ctx_index])->out_queue);\n    if (s == NULL) {\n        ngx_rtmp_close_connection(c);\n        return NULL;\n    }\n\n    s->main_conf = addr_conf->ctx->main_conf;\n    s->srv_conf = addr_conf->ctx->srv_conf;\n\n    s->addr_text = &addr_conf->addr_text;\n\n    c->data = s;\n    s->connection = c;\n\n    ctx = ngx_palloc(c->pool, sizeof(ngx_rtmp_error_log_ctx_t));\n    if (ctx == NULL) {\n        ngx_rtmp_close_connection(c);\n        return NULL;\n    }\n\n    ctx->client = &c->addr_text;\n    ctx->session = s;\n\n    c->log->connection = c->number;\n    c->log->handler = ngx_rtmp_log_error;\n    c->log->data = ctx;\n    c->log->action = NULL;\n\n    c->log_error = NGX_ERROR_INFO;\n\n    s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_rtmp_max_module);\n    if (s->ctx == NULL) {\n        ngx_rtmp_close_connection(c);\n        return NULL;\n    }\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    s->out_queue = cscf->out_queue;\n    s->out_cork = cscf->out_cork;\n    s->in_streams = ngx_pcalloc(c->pool, sizeof(ngx_rtmp_stream_t)\n            * cscf->max_streams);\n    if (s->in_streams == NULL) {\n        ngx_rtmp_close_connection(c);\n        return NULL;\n    }\n\n#if (nginx_version >= 1007005)\n    ngx_queue_init(&s->posted_dry_events);\n#endif\n\n    s->epoch = ngx_current_msec;\n    s->timeout = cscf->timeout;\n    s->buflen = cscf->buflen;\n    ngx_rtmp_set_chunk_size(s, NGX_RTMP_DEFAULT_CHUNK_SIZE);\n\n\n    if (ngx_rtmp_fire_event(s, NGX_RTMP_CONNECT, NULL, NULL) != NGX_OK) {\n        ngx_rtmp_finalize_session(s);\n        return NULL;\n    }\n\n    return s;\n}\n\n\nstatic u_char *\nngx_rtmp_log_error(ngx_log_t *log, u_char *buf, size_t len)\n{\n    u_char                     *p;\n    ngx_rtmp_session_t         *s;\n    ngx_rtmp_error_log_ctx_t   *ctx;\n\n    if (log->action) {\n        p = ngx_snprintf(buf, len, \" while %s\", log->action);\n        len -= p - buf;\n        buf = p;\n    }\n\n    ctx = log->data;\n\n    p = ngx_snprintf(buf, len, \", client: %V\", ctx->client);\n    len -= p - buf;\n    buf = p;\n\n    s = ctx->session;\n\n    if (s == NULL) {\n        return p;\n    }\n\n    p = ngx_snprintf(buf, len, \", server: %V\", s->addr_text);\n    len -= p - buf;\n    buf = p;\n\n    return p;\n}\n\n\nstatic void\nngx_rtmp_close_connection(ngx_connection_t *c)\n{\n    ngx_pool_t                         *pool;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, \"close connection\");\n\n#if (NGX_SSL)\n\n    if (c->ssl) {\n        if (ngx_ssl_shutdown(c) == NGX_AGAIN) {\n            c->ssl->handler = ngx_rtmp_close_connection;\n            return;\n        }\n    }\n\n#endif\n\n#if (NGX_STAT_STUB)\n    (void) ngx_atomic_fetch_add(ngx_stat_active, -1);\n#endif\n\n    pool = c->pool;\n    ngx_close_connection(c);\n    ngx_destroy_pool(pool);\n}\n\n\nstatic void\nngx_rtmp_close_session_handler(ngx_event_t *e)\n{\n    ngx_rtmp_session_t                 *s;\n    ngx_connection_t                   *c;\n    ngx_rtmp_core_srv_conf_t           *cscf;\n\n    s = e->data;\n    c = s->connection;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, \"close session\");\n\n    ngx_rtmp_fire_event(s, NGX_RTMP_DISCONNECT, NULL, NULL);\n\n    if (s->ping_evt.timer_set) {\n        ngx_del_timer(&s->ping_evt);\n    }\n\n    if (s->in_old_pool) {\n        ngx_destroy_pool(s->in_old_pool);\n    }\n\n    if (s->in_pool) {\n        ngx_destroy_pool(s->in_pool);\n    }\n\n    ngx_rtmp_free_handshake_buffers(s);\n\n    while (s->out_pos != s->out_last) {\n        ngx_rtmp_free_shared_chain(cscf, s->out[s->out_pos++]);\n        s->out_pos %= s->out_queue;\n    }\n\n    ngx_rtmp_close_connection(c);\n}\n\n\nvoid\nngx_rtmp_finalize_session(ngx_rtmp_session_t *s)\n{\n    ngx_event_t        *e;\n    ngx_connection_t   *c;\n\n    c = s->connection;\n    if (c->destroyed) {\n        return;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, c->log, 0, \"finalize session\");\n\n    c->destroyed = 1;\n    e = &s->close;\n    e->data = s;\n    e->handler = ngx_rtmp_close_session_handler;\n    e->log = c->log;\n\n    ngx_post_event(e, &ngx_posted_events);\n}\n\n"
  },
  {
    "path": "ngx_rtmp_limit_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\ntypedef struct {\n    ngx_int_t       max_conn;\n    ngx_shm_zone_t *shm_zone;\n} ngx_rtmp_limit_main_conf_t;\n\n\nstatic ngx_str_t    shm_name = ngx_string(\"rtmp_limit\");\n\n\nstatic ngx_int_t ngx_rtmp_limit_postconfiguration(ngx_conf_t *cf);\nstatic void *ngx_rtmp_limit_create_main_conf(ngx_conf_t *cf);\n\n\nstatic ngx_command_t  ngx_rtmp_limit_commands[] = {\n\n    { ngx_string(\"max_connections\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_num_slot,\n      NGX_RTMP_MAIN_CONF_OFFSET,\n      offsetof(ngx_rtmp_limit_main_conf_t, max_conn),\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_limit_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_limit_postconfiguration,       /* postconfiguration */\n    ngx_rtmp_limit_create_main_conf,        /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    NULL,                                   /* create app configuration */\n    NULL                                    /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_limit_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_limit_module_ctx,             /* module context */\n    ngx_rtmp_limit_commands,                /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void *\nngx_rtmp_limit_create_main_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_limit_main_conf_t      *lmcf;\n\n    lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_limit_main_conf_t));\n    if (lmcf == NULL) {\n        return NULL;\n    }\n\n    lmcf->max_conn = NGX_CONF_UNSET;\n\n    return lmcf;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_limit_connect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n    ngx_chain_t *in)\n{\n    ngx_rtmp_limit_main_conf_t *lmcf;\n    ngx_slab_pool_t            *shpool;\n    ngx_shm_zone_t             *shm_zone;\n    uint32_t                   *nconn, n;\n    ngx_int_t                   rc;\n\n    lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module);\n    if (lmcf->max_conn == NGX_CONF_UNSET) {\n        return NGX_OK;\n    }\n\n    shm_zone = lmcf->shm_zone;\n    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;\n    nconn = shm_zone->data;\n\n    ngx_shmtx_lock(&shpool->mutex);\n    n = ++*nconn;\n    ngx_shmtx_unlock(&shpool->mutex);\n\n    rc = n > (ngx_uint_t) lmcf->max_conn ? NGX_ERROR : NGX_OK;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"limit: inc conection counter: %uD\", n);\n\n    if (rc != NGX_OK) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"limit: too many connections: %uD > %i\",\n                      n, lmcf->max_conn);\n    }\n\n    return rc;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_limit_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n    ngx_chain_t *in)\n{\n    ngx_rtmp_limit_main_conf_t *lmcf;\n    ngx_slab_pool_t            *shpool;\n    ngx_shm_zone_t             *shm_zone;\n    uint32_t                   *nconn, n;\n\n    lmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_limit_module);\n    if (lmcf->max_conn == NGX_CONF_UNSET) {\n        return NGX_OK;\n    }\n\n    shm_zone = lmcf->shm_zone;\n    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;\n    nconn = shm_zone->data;\n\n    ngx_shmtx_lock(&shpool->mutex);\n    n = --*nconn;\n    ngx_shmtx_unlock(&shpool->mutex);\n\n    (void) n;\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"limit: dec conection counter: %uD\", n);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_limit_shm_init(ngx_shm_zone_t *shm_zone, void *data)\n{\n    ngx_slab_pool_t    *shpool;\n    uint32_t           *nconn;\n\n    if (data) {\n        shm_zone->data = data;\n        return NGX_OK;\n    }\n\n    shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;\n\n    nconn = ngx_slab_alloc(shpool, 4);\n    if (nconn == NULL) {\n        return NGX_ERROR;\n    }\n\n    *nconn = 0;\n\n    shm_zone->data = nconn;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_limit_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t  *cmcf;\n    ngx_rtmp_limit_main_conf_t *lmcf;\n    ngx_rtmp_handler_pt        *h;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_CONNECT]);\n    *h = ngx_rtmp_limit_connect;\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);\n    *h = ngx_rtmp_limit_disconnect;\n\n    lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_limit_module);\n    if (lmcf->max_conn == NGX_CONF_UNSET) {\n        return NGX_OK;\n    }\n\n    lmcf->shm_zone = ngx_shared_memory_add(cf, &shm_name, ngx_pagesize * 2,\n                                           &ngx_rtmp_limit_module);\n    if (lmcf->shm_zone == NULL) {\n        return NGX_ERROR;\n    }\n\n    lmcf->shm_zone->init = ngx_rtmp_limit_shm_init;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_live_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_live_module.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_codec_module.h\"\n\n\nstatic ngx_rtmp_publish_pt              next_publish;\nstatic ngx_rtmp_play_pt                 next_play;\nstatic ngx_rtmp_close_stream_pt         next_close_stream;\nstatic ngx_rtmp_pause_pt                next_pause;\nstatic ngx_rtmp_stream_begin_pt         next_stream_begin;\nstatic ngx_rtmp_stream_eof_pt           next_stream_eof;\n\n\nstatic ngx_int_t ngx_rtmp_live_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_live_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_live_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\nstatic char *ngx_rtmp_live_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic void ngx_rtmp_live_start(ngx_rtmp_session_t *s);\nstatic void ngx_rtmp_live_stop(ngx_rtmp_session_t *s);\n\n\nstatic ngx_command_t  ngx_rtmp_live_commands[] = {\n\n    { ngx_string(\"live\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, live),\n      NULL },\n\n    { ngx_string(\"stream_buckets\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, nbuckets),\n      NULL },\n\n    { ngx_string(\"buffer\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, buflen),\n      NULL },\n\n    { ngx_string(\"sync\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_live_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, sync),\n      NULL },\n\n    { ngx_string(\"interleave\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, interleave),\n      NULL },\n\n    { ngx_string(\"wait_key\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, wait_key),\n      NULL },\n\n    { ngx_string(\"wait_video\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, wait_video),\n      NULL },\n\n    { ngx_string(\"publish_notify\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, publish_notify),\n      NULL },\n\n    { ngx_string(\"play_restart\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, play_restart),\n      NULL },\n\n    { ngx_string(\"idle_streams\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, idle_streams),\n      NULL },\n\n    { ngx_string(\"drop_idle_publisher\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_live_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_live_app_conf_t, idle_timeout),\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_live_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_live_postconfiguration,        /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    ngx_rtmp_live_create_app_conf,          /* create app configuration */\n    ngx_rtmp_live_merge_app_conf            /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_live_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_live_module_ctx,              /* module context */\n    ngx_rtmp_live_commands,                 /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void *\nngx_rtmp_live_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_live_app_conf_t      *lacf;\n\n    lacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_live_app_conf_t));\n    if (lacf == NULL) {\n        return NULL;\n    }\n\n    lacf->live = NGX_CONF_UNSET;\n    lacf->nbuckets = NGX_CONF_UNSET;\n    lacf->buflen = NGX_CONF_UNSET_MSEC;\n    lacf->sync = NGX_CONF_UNSET_MSEC;\n    lacf->idle_timeout = NGX_CONF_UNSET_MSEC;\n    lacf->interleave = NGX_CONF_UNSET;\n    lacf->wait_key = NGX_CONF_UNSET;\n    lacf->wait_video = NGX_CONF_UNSET;\n    lacf->publish_notify = NGX_CONF_UNSET;\n    lacf->play_restart = NGX_CONF_UNSET;\n    lacf->idle_streams = NGX_CONF_UNSET;\n\n    return lacf;\n}\n\n\nstatic char *\nngx_rtmp_live_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_live_app_conf_t *prev = parent;\n    ngx_rtmp_live_app_conf_t *conf = child;\n\n    ngx_conf_merge_value(conf->live, prev->live, 0);\n    ngx_conf_merge_value(conf->nbuckets, prev->nbuckets, 1024);\n    ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 0);\n    ngx_conf_merge_msec_value(conf->sync, prev->sync, 300);\n    ngx_conf_merge_msec_value(conf->idle_timeout, prev->idle_timeout, 0);\n    ngx_conf_merge_value(conf->interleave, prev->interleave, 0);\n    ngx_conf_merge_value(conf->wait_key, prev->wait_key, 1);\n    ngx_conf_merge_value(conf->wait_video, prev->wait_video, 0);\n    ngx_conf_merge_value(conf->publish_notify, prev->publish_notify, 0);\n    ngx_conf_merge_value(conf->play_restart, prev->play_restart, 0);\n    ngx_conf_merge_value(conf->idle_streams, prev->idle_streams, 1);\n\n    conf->pool = ngx_create_pool(4096, &cf->cycle->new_log);\n    if (conf->pool == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    conf->streams = ngx_pcalloc(cf->pool,\n            sizeof(ngx_rtmp_live_stream_t *) * conf->nbuckets);\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_rtmp_live_set_msec_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    char                       *p = conf;\n    ngx_str_t                  *value;\n    ngx_msec_t                 *msp;\n\n    msp = (ngx_msec_t *) (p + cmd->offset);\n\n    value = cf->args->elts;\n\n    if (value[1].len == sizeof(\"off\") - 1 &&\n        ngx_strncasecmp(value[1].data, (u_char *) \"off\", value[1].len) == 0)\n    {\n        *msp = 0;\n        return NGX_CONF_OK;\n    }\n\n    return ngx_conf_set_msec_slot(cf, cmd, conf);\n}\n\n\nstatic ngx_rtmp_live_stream_t **\nngx_rtmp_live_get_stream(ngx_rtmp_session_t *s, u_char *name, int create)\n{\n    ngx_rtmp_live_app_conf_t   *lacf;\n    ngx_rtmp_live_stream_t    **stream;\n    size_t                      len;\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n    if (lacf == NULL) {\n        return NULL;\n    }\n\n    len = ngx_strlen(name);\n    stream = &lacf->streams[ngx_hash_key(name, len) % lacf->nbuckets];\n\n    for (; *stream; stream = &(*stream)->next) {\n        if (ngx_strcmp(name, (*stream)->name) == 0) {\n            return stream;\n        }\n    }\n\n    if (!create) {\n        return NULL;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"live: create stream '%s'\", name);\n\n    if (lacf->free_streams) {\n        *stream = lacf->free_streams;\n        lacf->free_streams = lacf->free_streams->next;\n    } else {\n        *stream = ngx_palloc(lacf->pool, sizeof(ngx_rtmp_live_stream_t));\n    }\n    ngx_memzero(*stream, sizeof(ngx_rtmp_live_stream_t));\n    ngx_memcpy((*stream)->name, name,\n            ngx_min(sizeof((*stream)->name) - 1, len));\n    (*stream)->epoch = ngx_current_msec;\n\n    return stream;\n}\n\n\nstatic void\nngx_rtmp_live_idle(ngx_event_t *pev)\n{\n    ngx_connection_t           *c;\n    ngx_rtmp_session_t         *s;\n\n    c = pev->data;\n    s = c->data;\n\n    ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                  \"live: drop idle publisher\");\n\n    ngx_rtmp_finalize_session(s);\n}\n\n\nstatic void\nngx_rtmp_live_set_status(ngx_rtmp_session_t *s, ngx_chain_t *control,\n                         ngx_chain_t **status, size_t nstatus,\n                         unsigned active)\n{\n    ngx_rtmp_live_app_conf_t   *lacf;\n    ngx_rtmp_live_ctx_t        *ctx, *pctx;\n    ngx_chain_t               **cl;\n    ngx_event_t                *e;\n    size_t                      n;\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: set active=%ui\", active);\n\n    if (ctx->active == active) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"live: unchanged active=%ui\", active);\n        return;\n    }\n\n    ctx->active = active;\n\n    if (ctx->publishing) {\n\n        /* publisher */\n\n        if (lacf->idle_timeout) {\n            e = &ctx->idle_evt;\n\n            if (active && !ctx->idle_evt.timer_set) {\n                e->data = s->connection;\n                e->log = s->connection->log;\n                e->handler = ngx_rtmp_live_idle;\n\n                ngx_add_timer(e, lacf->idle_timeout);\n\n            } else if (!active && ctx->idle_evt.timer_set) {\n                ngx_del_timer(e);\n            }\n        }\n\n        ctx->stream->active = active;\n\n        for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {\n            if (pctx->publishing == 0) {\n                ngx_rtmp_live_set_status(pctx->session, control, status,\n                                         nstatus, active);\n            }\n        }\n\n        return;\n    }\n\n    /* subscriber */\n\n    if (control && ngx_rtmp_send_message(s, control, 0) != NGX_OK) {\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    if (!ctx->silent) {\n        cl = status;\n\n        for (n = 0; n < nstatus; ++n, ++cl) {\n            if (*cl && ngx_rtmp_send_message(s, *cl, 0) != NGX_OK) {\n                ngx_rtmp_finalize_session(s);\n                return;\n            }\n        }\n    }\n\n    ctx->cs[0].active = 0;\n    ctx->cs[0].dropped = 0;\n\n    ctx->cs[1].active = 0;\n    ctx->cs[1].dropped = 0;\n}\n\n\nstatic void\nngx_rtmp_live_start(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_rtmp_live_app_conf_t   *lacf;\n    ngx_chain_t                *control;\n    ngx_chain_t                *status[3];\n    size_t                      n, nstatus;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n\n    control = ngx_rtmp_create_stream_begin(s, NGX_RTMP_MSID);\n\n    nstatus = 0;\n\n    if (lacf->play_restart) {\n        status[nstatus++] = ngx_rtmp_create_status(s, \"NetStream.Play.Start\",\n                                                   \"status\", \"Start live\");\n        status[nstatus++] = ngx_rtmp_create_sample_access(s);\n    }\n\n    if (lacf->publish_notify) {\n        status[nstatus++] = ngx_rtmp_create_status(s,\n                                                 \"NetStream.Play.PublishNotify\",\n                                                 \"status\", \"Start publishing\");\n    }\n\n    ngx_rtmp_live_set_status(s, control, status, nstatus, 1);\n\n    if (control) {\n        ngx_rtmp_free_shared_chain(cscf, control);\n    }\n\n    for (n = 0; n < nstatus; ++n) {\n        ngx_rtmp_free_shared_chain(cscf, status[n]);\n    }\n}\n\n\nstatic void\nngx_rtmp_live_stop(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_rtmp_live_app_conf_t   *lacf;\n    ngx_chain_t                *control;\n    ngx_chain_t                *status[3];\n    size_t                      n, nstatus;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n\n    control = ngx_rtmp_create_stream_eof(s, NGX_RTMP_MSID);\n\n    nstatus = 0;\n\n    if (lacf->play_restart) {\n        status[nstatus++] = ngx_rtmp_create_status(s, \"NetStream.Play.Stop\",\n                                                   \"status\", \"Stop live\");\n    }\n\n    if (lacf->publish_notify) {\n        status[nstatus++] = ngx_rtmp_create_status(s,\n                                               \"NetStream.Play.UnpublishNotify\",\n                                               \"status\", \"Stop publishing\");\n    }\n\n    ngx_rtmp_live_set_status(s, control, status, nstatus, 0);\n\n    if (control) {\n        ngx_rtmp_free_shared_chain(cscf, control);\n    }\n\n    for (n = 0; n < nstatus; ++n) {\n        ngx_rtmp_free_shared_chain(cscf, status[n]);\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_live_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)\n{\n    ngx_rtmp_live_ctx_t    *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n\n    if (ctx == NULL || ctx->stream == NULL || !ctx->publishing) {\n        goto next;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: stream_begin\");\n\n    ngx_rtmp_live_start(s);\n\nnext:\n    return next_stream_begin(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_live_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_eof_t *v)\n{\n    ngx_rtmp_live_ctx_t    *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n\n    if (ctx == NULL || ctx->stream == NULL || !ctx->publishing) {\n        goto next;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: stream_eof\");\n\n    ngx_rtmp_live_stop(s);\n\nnext:\n    return next_stream_eof(s, v);\n}\n\n\nstatic void\nngx_rtmp_live_join(ngx_rtmp_session_t *s, u_char *name, unsigned publisher)\n{\n    ngx_rtmp_live_ctx_t            *ctx;\n    ngx_rtmp_live_stream_t        **stream;\n    ngx_rtmp_live_app_conf_t       *lacf;\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n    if (lacf == NULL) {\n        return;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n    if (ctx && ctx->stream) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"live: already joined\");\n        return;\n    }\n\n    if (ctx == NULL) {\n        ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_live_ctx_t));\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_live_module);\n    }\n\n    ngx_memzero(ctx, sizeof(*ctx));\n\n    ctx->session = s;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: join '%s'\", name);\n\n    stream = ngx_rtmp_live_get_stream(s, name, publisher || lacf->idle_streams);\n\n    if (stream == NULL ||\n        !(publisher || (*stream)->publishing || lacf->idle_streams))\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"live: stream not found\");\n\n        ngx_rtmp_send_status(s, \"NetStream.Play.StreamNotFound\", \"error\",\n                             \"No such stream\");\n\n        ngx_rtmp_finalize_session(s);\n\n        return;\n    }\n\n    if (publisher) {\n        if ((*stream)->publishing) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"live: already publishing\");\n\n            ngx_rtmp_send_status(s, \"NetStream.Publish.BadName\", \"error\",\n                                 \"Already publishing\");\n\n            return;\n        }\n\n        (*stream)->publishing = 1;\n    }\n\n    ctx->stream = *stream;\n    ctx->publishing = publisher;\n    ctx->next = (*stream)->ctx;\n\n    (*stream)->ctx = ctx;\n\n    if (lacf->buflen) {\n        s->out_buffer = 1;\n    }\n\n    ctx->cs[0].csid = NGX_RTMP_CSID_VIDEO;\n    ctx->cs[1].csid = NGX_RTMP_CSID_AUDIO;\n\n    if (!ctx->publishing && ctx->stream->active) {\n        ngx_rtmp_live_start(s);\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_live_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)\n{\n    ngx_rtmp_session_t             *ss;\n    ngx_rtmp_live_ctx_t            *ctx, **cctx, *pctx;\n    ngx_rtmp_live_stream_t        **stream;\n    ngx_rtmp_live_app_conf_t       *lacf;\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n    if (lacf == NULL) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n    if (ctx == NULL) {\n        goto next;\n    }\n\n    if (ctx->stream == NULL) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"live: not joined\");\n        goto next;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: leave '%s'\", ctx->stream->name);\n\n    if (ctx->stream->publishing && ctx->publishing) {\n        ctx->stream->publishing = 0;\n    }\n\n    for (cctx = &ctx->stream->ctx; *cctx; cctx = &(*cctx)->next) {\n        if (*cctx == ctx) {\n            *cctx = ctx->next;\n            break;\n        }\n    }\n\n    if (ctx->publishing || ctx->stream->active) {\n        ngx_rtmp_live_stop(s);\n    }\n\n    if (ctx->publishing) {\n        ngx_rtmp_send_status(s, \"NetStream.Unpublish.Success\",\n                             \"status\", \"Stop publishing\");\n        if (!lacf->idle_streams) {\n            for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {\n                if (pctx->publishing == 0) {\n                    ss = pctx->session;\n                    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                                   \"live: no publisher\");\n                    ngx_rtmp_finalize_session(ss);\n                }\n            }\n        }\n    }\n\n    if (ctx->stream->ctx) {\n        ctx->stream = NULL;\n        goto next;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: delete empty stream '%s'\",\n                   ctx->stream->name);\n\n    stream = ngx_rtmp_live_get_stream(s, ctx->stream->name, 0);\n    if (stream == NULL) {\n        goto next;\n    }\n    *stream = (*stream)->next;\n\n    ctx->stream->next = lacf->free_streams;\n    lacf->free_streams = ctx->stream;\n    ctx->stream = NULL;\n\n    if (!ctx->silent && !ctx->publishing && !lacf->play_restart) {\n        ngx_rtmp_send_status(s, \"NetStream.Play.Stop\", \"status\", \"Stop live\");\n    }\n\nnext:\n    return next_close_stream(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_live_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v)\n{\n    ngx_rtmp_live_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n\n    if (ctx == NULL || ctx->stream == NULL) {\n        goto next;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: pause=%i timestamp=%f\",\n                   (ngx_int_t) v->pause, v->position);\n\n    if (v->pause) {\n        if (ngx_rtmp_send_status(s, \"NetStream.Pause.Notify\", \"status\",\n                                 \"Paused live\")\n            != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n\n        ctx->paused = 1;\n\n        ngx_rtmp_live_stop(s);\n\n    } else {\n        if (ngx_rtmp_send_status(s, \"NetStream.Unpause.Notify\", \"status\",\n                                 \"Unpaused live\")\n            != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n\n        ctx->paused = 0;\n\n        ngx_rtmp_live_start(s);\n    }\n\nnext:\n    return next_pause(s, v);\n}\n\nstatic ngx_int_t\nngx_rtmp_live_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                 ngx_chain_t *in)\n{\n    ngx_rtmp_live_ctx_t            *ctx, *pctx;\n    ngx_rtmp_codec_ctx_t           *codec_ctx;\n    ngx_chain_t                    *header, *coheader, *meta,\n                                   *apkt, *aapkt, *acopkt, *rpkt;\n    ngx_rtmp_core_srv_conf_t       *cscf;\n    ngx_rtmp_live_app_conf_t       *lacf;\n    ngx_rtmp_session_t             *ss;\n    ngx_rtmp_header_t               ch, lh, clh;\n    ngx_int_t                       rc, mandatory, dummy_audio;\n    ngx_uint_t                      prio;\n    ngx_uint_t                      peers;\n    ngx_uint_t                      meta_version;\n    ngx_uint_t                      csidx;\n    uint32_t                        delta;\n    ngx_rtmp_live_chunk_stream_t   *cs;\n#ifdef NGX_DEBUG\n    const char                     *type_s;\n\n    type_s = (h->type == NGX_RTMP_MSG_VIDEO ? \"video\" : \"audio\");\n#endif\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n    if (lacf == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (!lacf->live || in == NULL  || in->buf == NULL) {\n        return NGX_OK;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n    if (ctx == NULL || ctx->stream == NULL) {\n        return NGX_OK;\n    }\n\n    if (ctx->publishing == 0) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"live: %s from non-publisher\", type_s);\n        return NGX_OK;\n    }\n\n    if (!ctx->stream->active) {\n        ngx_rtmp_live_start(s);\n    }\n\n    if (ctx->idle_evt.timer_set) {\n        ngx_add_timer(&ctx->idle_evt, lacf->idle_timeout);\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: %s packet timestamp=%uD\",\n                   type_s, h->timestamp);\n\n    s->current_time = h->timestamp;\n\n    peers = 0;\n    apkt = NULL;\n    aapkt = NULL;\n    acopkt = NULL;\n    header = NULL;\n    coheader = NULL;\n    meta = NULL;\n    meta_version = 0;\n    mandatory = 0;\n\n    prio = (h->type == NGX_RTMP_MSG_VIDEO ?\n            ngx_rtmp_get_video_frame_type(in) : 0);\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    csidx = !(lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO);\n\n    cs  = &ctx->cs[csidx];\n\n    ngx_memzero(&ch, sizeof(ch));\n\n    ch.timestamp = h->timestamp;\n    ch.msid = NGX_RTMP_MSID;\n    ch.csid = cs->csid;\n    ch.type = h->type;\n\n    lh = ch;\n\n    if (cs->active) {\n        lh.timestamp = cs->timestamp;\n    }\n\n    clh = lh;\n    clh.type = (h->type == NGX_RTMP_MSG_AUDIO ? NGX_RTMP_MSG_VIDEO :\n                                                NGX_RTMP_MSG_AUDIO);\n\n    cs->active = 1;\n    cs->timestamp = ch.timestamp;\n\n    delta = ch.timestamp - lh.timestamp;\n/*\n    if (delta >> 31) {\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"live: clipping non-monotonical timestamp %uD->%uD\",\n                       lh.timestamp, ch.timestamp);\n\n        delta = 0;\n\n        ch.timestamp = lh.timestamp;\n    }\n*/\n    rpkt = ngx_rtmp_append_shared_bufs(cscf, NULL, in);\n\n    ngx_rtmp_prepare_message(s, &ch, &lh, rpkt);\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n\n    if (codec_ctx) {\n\n        if (h->type == NGX_RTMP_MSG_AUDIO) {\n            header = codec_ctx->aac_header;\n\n            if (lacf->interleave) {\n                coheader = codec_ctx->avc_header;\n            }\n\n            if (codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC &&\n                ngx_rtmp_is_codec_header(in))\n            {\n                prio = 0;\n                mandatory = 1;\n            }\n\n        } else {\n            header = codec_ctx->avc_header;\n\n            if (lacf->interleave) {\n                coheader = codec_ctx->aac_header;\n            }\n\n            if (codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 &&\n                ngx_rtmp_is_codec_header(in))\n            {\n                prio = 0;\n                mandatory = 1;\n            }\n        }\n\n        if (codec_ctx->meta) {\n            meta = codec_ctx->meta;\n            meta_version = codec_ctx->meta_version;\n        }\n    }\n\n    /* broadcast to all subscribers */\n\n    for (pctx = ctx->stream->ctx; pctx; pctx = pctx->next) {\n        if (pctx == ctx || pctx->paused) {\n            continue;\n        }\n\n        ss = pctx->session;\n        cs = &pctx->cs[csidx];\n\n        /* send metadata */\n\n        if (meta && meta_version != pctx->meta_version) {\n            ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                           \"live: meta\");\n\n            if (ngx_rtmp_send_message(ss, meta, 0) == NGX_OK) {\n                pctx->meta_version = meta_version;\n            }\n        }\n\n        /* sync stream */\n\n        if (cs->active && (lacf->sync && cs->dropped > lacf->sync)) {\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                           \"live: sync %s dropped=%uD\", type_s, cs->dropped);\n\n            cs->active = 0;\n            cs->dropped = 0;\n        }\n\n        /* absolute packet */\n\n        if (!cs->active) {\n\n            if (mandatory) {\n                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                               \"live: skipping header\");\n                continue;\n            }\n\n            if (lacf->wait_video && h->type == NGX_RTMP_MSG_AUDIO &&\n                !pctx->cs[0].active)\n            {\n                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                               \"live: waiting for video\");\n                continue;\n            }\n\n            if (lacf->wait_key && prio != NGX_RTMP_VIDEO_KEY_FRAME &&\n               (lacf->interleave || h->type == NGX_RTMP_MSG_VIDEO))\n            {\n                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                               \"live: skip non-key\");\n                continue;\n            }\n\n            dummy_audio = 0;\n            if (lacf->wait_video && h->type == NGX_RTMP_MSG_VIDEO &&\n                !pctx->cs[1].active)\n            {\n                dummy_audio = 1;\n                if (aapkt == NULL) {\n                    aapkt = ngx_rtmp_alloc_shared_buf(cscf);\n                    ngx_rtmp_prepare_message(s, &clh, NULL, aapkt);\n                }\n            }\n\n            if (header || coheader) {\n\n                /* send absolute codec header */\n\n                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                               \"live: abs %s header timestamp=%uD\",\n                               type_s, lh.timestamp);\n\n                if (header) {\n                    if (apkt == NULL) {\n                        apkt = ngx_rtmp_append_shared_bufs(cscf, NULL, header);\n                        ngx_rtmp_prepare_message(s, &lh, NULL, apkt);\n                    }\n\n                    rc = ngx_rtmp_send_message(ss, apkt, 0);\n                    if (rc != NGX_OK) {\n                        continue;\n                    }\n                }\n\n                if (coheader) {\n                    if (acopkt == NULL) {\n                        acopkt = ngx_rtmp_append_shared_bufs(cscf, NULL, coheader);\n                        ngx_rtmp_prepare_message(s, &clh, NULL, acopkt);\n                    }\n\n                    rc = ngx_rtmp_send_message(ss, acopkt, 0);\n                    if (rc != NGX_OK) {\n                        continue;\n                    }\n\n                } else if (dummy_audio) {\n                    ngx_rtmp_send_message(ss, aapkt, 0);\n                }\n\n                cs->timestamp = lh.timestamp;\n                cs->active = 1;\n                ss->current_time = cs->timestamp;\n\n            } else {\n\n                /* send absolute packet */\n\n                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                               \"live: abs %s packet timestamp=%uD\",\n                               type_s, ch.timestamp);\n\n                if (apkt == NULL) {\n                    apkt = ngx_rtmp_append_shared_bufs(cscf, NULL, in);\n                    ngx_rtmp_prepare_message(s, &ch, NULL, apkt);\n                }\n\n                rc = ngx_rtmp_send_message(ss, apkt, prio);\n                if (rc != NGX_OK) {\n                    continue;\n                }\n\n                cs->timestamp = ch.timestamp;\n                cs->active = 1;\n                ss->current_time = cs->timestamp;\n\n                ++peers;\n\n                if (dummy_audio) {\n                    ngx_rtmp_send_message(ss, aapkt, 0);\n                }\n\n                continue;\n            }\n        }\n\n        /* send relative packet */\n\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                       \"live: rel %s packet delta=%uD\",\n                       type_s, delta);\n\n        if (ngx_rtmp_send_message(ss, rpkt, prio) != NGX_OK) {\n            ++pctx->ndropped;\n\n            cs->dropped += delta;\n\n            if (mandatory) {\n                ngx_log_debug0(NGX_LOG_DEBUG_RTMP, ss->connection->log, 0,\n                               \"live: mandatory packet failed\");\n                ngx_rtmp_finalize_session(ss);\n            }\n\n            continue;\n        }\n\n        cs->timestamp += delta;\n        ++peers;\n        ss->current_time = cs->timestamp;\n    }\n\n    if (rpkt) {\n        ngx_rtmp_free_shared_chain(cscf, rpkt);\n    }\n\n    if (apkt) {\n        ngx_rtmp_free_shared_chain(cscf, apkt);\n    }\n\n    if (aapkt) {\n        ngx_rtmp_free_shared_chain(cscf, aapkt);\n    }\n\n    if (acopkt) {\n        ngx_rtmp_free_shared_chain(cscf, acopkt);\n    }\n\n    ngx_rtmp_update_bandwidth(&ctx->stream->bw_in, h->mlen);\n    ngx_rtmp_update_bandwidth(&ctx->stream->bw_out, h->mlen * peers);\n\n    ngx_rtmp_update_bandwidth(h->type == NGX_RTMP_MSG_AUDIO ?\n                              &ctx->stream->bw_in_audio :\n                              &ctx->stream->bw_in_video,\n                              h->mlen);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_live_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    ngx_rtmp_live_app_conf_t       *lacf;\n    ngx_rtmp_live_ctx_t            *ctx;\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n\n    if (lacf == NULL || !lacf->live) {\n        goto next;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: publish: name='%s' type='%s'\",\n                   v->name, v->type);\n\n    /* join stream as publisher */\n\n    ngx_rtmp_live_join(s, v->name, 1);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n    if (ctx == NULL || !ctx->publishing) {\n        goto next;\n    }\n\n    ctx->silent = v->silent;\n\n    if (!ctx->silent) {\n        ngx_rtmp_send_status(s, \"NetStream.Publish.Start\",\n                             \"status\", \"Start publishing\");\n    }\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_live_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    ngx_rtmp_live_app_conf_t       *lacf;\n    ngx_rtmp_live_ctx_t            *ctx;\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_live_module);\n\n    if (lacf == NULL || !lacf->live) {\n        goto next;\n    }\n\n    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"live: play: name='%s' start=%uD duration=%uD reset=%d\",\n                   v->name, (uint32_t) v->start,\n                   (uint32_t) v->duration, (uint32_t) v->reset);\n\n    /* join stream as subscriber */\n\n    ngx_rtmp_live_join(s, v->name, 0);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_live_module);\n    if (ctx == NULL) {\n        goto next;\n    }\n\n    ctx->silent = v->silent;\n\n    if (!ctx->silent && !lacf->play_restart) {\n        ngx_rtmp_send_status(s, \"NetStream.Play.Start\",\n                             \"status\", \"Start live\");\n        ngx_rtmp_send_sample_access(s);\n    }\n\nnext:\n    return next_play(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_live_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t          *cmcf;\n    ngx_rtmp_handler_pt                *h;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    /* register raw event handlers */\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);\n    *h = ngx_rtmp_live_av;\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);\n    *h = ngx_rtmp_live_av;\n\n    /* chain handlers */\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_live_publish;\n\n    next_play = ngx_rtmp_play;\n    ngx_rtmp_play = ngx_rtmp_live_play;\n\n    next_close_stream = ngx_rtmp_close_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_live_close_stream;\n\n    next_pause = ngx_rtmp_pause;\n    ngx_rtmp_pause = ngx_rtmp_live_pause;\n\n    next_stream_begin = ngx_rtmp_stream_begin;\n    ngx_rtmp_stream_begin = ngx_rtmp_live_stream_begin;\n\n    next_stream_eof = ngx_rtmp_stream_eof;\n    ngx_rtmp_stream_eof = ngx_rtmp_live_stream_eof;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_live_module.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_LIVE_H_INCLUDED_\n#define _NGX_RTMP_LIVE_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_bandwidth.h\"\n#include \"ngx_rtmp_streams.h\"\n\n\ntypedef struct ngx_rtmp_live_ctx_s ngx_rtmp_live_ctx_t;\ntypedef struct ngx_rtmp_live_stream_s ngx_rtmp_live_stream_t;\n\n\ntypedef struct {\n    unsigned                            active:1;\n    uint32_t                            timestamp;\n    uint32_t                            csid;\n    uint32_t                            dropped;\n} ngx_rtmp_live_chunk_stream_t;\n\n\nstruct ngx_rtmp_live_ctx_s {\n    ngx_rtmp_session_t                 *session;\n    ngx_rtmp_live_stream_t             *stream;\n    ngx_rtmp_live_ctx_t                *next;\n    ngx_uint_t                          ndropped;\n    ngx_rtmp_live_chunk_stream_t        cs[2];\n    ngx_uint_t                          meta_version;\n    ngx_event_t                         idle_evt;\n    unsigned                            active:1;\n    unsigned                            publishing:1;\n    unsigned                            silent:1;\n    unsigned                            paused:1;\n};\n\n\nstruct ngx_rtmp_live_stream_s {\n    u_char                              name[NGX_RTMP_MAX_NAME];\n    ngx_rtmp_live_stream_t             *next;\n    ngx_rtmp_live_ctx_t                *ctx;\n    ngx_rtmp_bandwidth_t                bw_in;\n    ngx_rtmp_bandwidth_t                bw_in_audio;\n    ngx_rtmp_bandwidth_t                bw_in_video;\n    ngx_rtmp_bandwidth_t                bw_out;\n    ngx_msec_t                          epoch;\n    unsigned                            active:1;\n    unsigned                            publishing:1;\n};\n\n\ntypedef struct {\n    ngx_int_t                           nbuckets;\n    ngx_rtmp_live_stream_t            **streams;\n    ngx_flag_t                          live;\n    ngx_flag_t                          meta;\n    ngx_msec_t                          sync;\n    ngx_msec_t                          idle_timeout;\n    ngx_flag_t                          atc;\n    ngx_flag_t                          interleave;\n    ngx_flag_t                          wait_key;\n    ngx_flag_t                          wait_video;\n    ngx_flag_t                          publish_notify;\n    ngx_flag_t                          play_restart;\n    ngx_flag_t                          idle_streams;\n    ngx_msec_t                          buflen;\n    ngx_pool_t                         *pool;\n    ngx_rtmp_live_stream_t             *free_streams;\n} ngx_rtmp_live_app_conf_t;\n\n\nextern ngx_module_t  ngx_rtmp_live_module;\n\n\n#endif /* _NGX_RTMP_LIVE_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_log_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_cmd_module.h\"\n\n\nstatic ngx_rtmp_publish_pt  next_publish;\nstatic ngx_rtmp_play_pt     next_play;\n\n\nstatic ngx_int_t ngx_rtmp_log_postconfiguration(ngx_conf_t *cf);\nstatic void *ngx_rtmp_log_create_main_conf(ngx_conf_t *cf);\nstatic void * ngx_rtmp_log_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_log_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\nstatic char * ngx_rtmp_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic char * ngx_rtmp_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic char * ngx_rtmp_log_compile_format(ngx_conf_t *cf, ngx_array_t *ops,\n       ngx_array_t *args, ngx_uint_t s);\n\n\ntypedef struct ngx_rtmp_log_op_s ngx_rtmp_log_op_t;\n\n\ntypedef size_t (*ngx_rtmp_log_op_getlen_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_log_op_t *op);\ntypedef u_char * (*ngx_rtmp_log_op_getdata_pt)(ngx_rtmp_session_t *s,\n        u_char *buf, ngx_rtmp_log_op_t *log);\n\n\nstruct ngx_rtmp_log_op_s {\n    ngx_rtmp_log_op_getlen_pt   getlen;\n    ngx_rtmp_log_op_getdata_pt  getdata;\n    ngx_str_t                   value;\n    ngx_uint_t                  offset;\n};\n\n\ntypedef struct {\n    ngx_str_t                   name;\n    ngx_rtmp_log_op_getlen_pt   getlen;\n    ngx_rtmp_log_op_getdata_pt  getdata;\n    ngx_uint_t                  offset;\n} ngx_rtmp_log_var_t;\n\n\ntypedef struct {\n    ngx_str_t                   name;\n    ngx_array_t                *ops; /* ngx_rtmp_log_op_t */\n} ngx_rtmp_log_fmt_t;\n\n\ntypedef struct {\n    ngx_open_file_t            *file;\n    time_t                      disk_full_time;\n    time_t                      error_log_time;\n    ngx_rtmp_log_fmt_t *format;\n} ngx_rtmp_log_t;\n\n\ntypedef struct {\n    ngx_array_t                *logs; /* ngx_rtmp_log_t */\n    ngx_uint_t                  off;\n} ngx_rtmp_log_app_conf_t;\n\n\ntypedef struct {\n    ngx_array_t                 formats; /* ngx_rtmp_log_fmt_t */\n    ngx_uint_t                  combined_used;\n} ngx_rtmp_log_main_conf_t;\n\n\ntypedef struct {\n    unsigned                    play:1;\n    unsigned                    publish:1;\n    u_char                      name[NGX_RTMP_MAX_NAME];\n    u_char                      args[NGX_RTMP_MAX_ARGS];\n} ngx_rtmp_log_ctx_t;\n\n\nstatic ngx_str_t ngx_rtmp_access_log = ngx_string(NGX_HTTP_LOG_PATH);\n\n\nstatic ngx_command_t  ngx_rtmp_log_commands[] = {\n\n    { ngx_string(\"access_log\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE12,\n      ngx_rtmp_log_set_log,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"log_format\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_2MORE,\n      ngx_rtmp_log_set_format,\n      NGX_RTMP_MAIN_CONF_OFFSET,\n      0,\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_log_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_log_postconfiguration,         /* postconfiguration */\n    ngx_rtmp_log_create_main_conf,          /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    ngx_rtmp_log_create_app_conf,           /* create app configuration */\n    ngx_rtmp_log_merge_app_conf             /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_log_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_log_module_ctx,               /* module context */\n    ngx_rtmp_log_commands,                  /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic ngx_str_t ngx_rtmp_combined_fmt =\n    ngx_string(\"$remote_addr [$time_local] $command \"\n               \"\\\"$app\\\" \\\"$name\\\" \\\"$args\\\" - \"\n               \"$bytes_received $bytes_sent \"\n               \"\\\"$pageurl\\\" \\\"$flashver\\\" ($session_readable_time)\");\n\n\nstatic size_t\nngx_rtmp_log_var_default_getlen(ngx_rtmp_session_t *s, ngx_rtmp_log_op_t *op)\n{\n    return op->value.len;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_default_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    return ngx_cpymem(buf, op->value.data, op->value.len);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_connection_getlen(ngx_rtmp_session_t *s, ngx_rtmp_log_op_t *op)\n{\n    return NGX_INT_T_LEN;\n}\n\nstatic u_char *\nngx_rtmp_log_var_connection_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    return ngx_sprintf(buf, \"%ui\", (ngx_uint_t) s->connection->number);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_remote_addr_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return s->connection->addr_text.len;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_remote_addr_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    return ngx_cpymem(buf, s->connection->addr_text.data,\n                           s->connection->addr_text.len);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_msec_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return NGX_TIME_T_LEN + 4;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_msec_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    ngx_time_t  *tp;\n\n    tp = ngx_timeofday();\n    \n    return ngx_sprintf(buf, \"%T.%03M\", tp->sec, tp->msec);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_session_string_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return ((ngx_str_t *) ((u_char *) s + op->offset))->len;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_session_string_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    ngx_str_t  *str;\n\n    str = (ngx_str_t *) ((u_char *) s + op->offset);\n\n    return ngx_cpymem(buf, str->data, str->len);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_command_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return sizeof(\"PLAY+PUBLISH\") - 1;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_command_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    ngx_rtmp_log_ctx_t *ctx;\n    ngx_str_t          *cmd;\n    ngx_uint_t          n;\n\n    static ngx_str_t    commands[] = {\n        ngx_string(\"NONE\"),\n        ngx_string(\"PLAY\"),\n        ngx_string(\"PUBLISH\"),\n        ngx_string(\"PLAY+PUBLISH\")\n    };\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module);\n\n    n = ctx ? (ctx->play + ctx->publish * 2) : 0;\n\n    cmd = &commands[n];\n\n    return ngx_cpymem(buf, cmd->data, cmd->len);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_context_cstring_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return ngx_max(NGX_RTMP_MAX_NAME, NGX_RTMP_MAX_ARGS);\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_context_cstring_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    ngx_rtmp_log_ctx_t *ctx;\n    u_char             *p;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module);\n    if (ctx == NULL) {\n        return buf;\n    }\n\n    p = (u_char *) ctx + op->offset;\n    while (*p) {\n        *buf++ = *p++;\n    }\n\n    return buf;\n}\n\n\nstatic size_t\nngx_rtmp_log_var_session_uint32_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return NGX_INT32_LEN;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_session_uint32_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    uint32_t   *v;\n\n    v = (uint32_t *) ((uint8_t *) s + op->offset);\n\n    return ngx_sprintf(buf, \"%uD\", *v);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_time_local_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return ngx_cached_http_log_time.len;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_time_local_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    return ngx_cpymem(buf, ngx_cached_http_log_time.data,\n                      ngx_cached_http_log_time.len);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_session_time_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return NGX_INT64_LEN;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_session_time_getdata(ngx_rtmp_session_t *s, u_char *buf,\n    ngx_rtmp_log_op_t *op)\n{\n    return ngx_sprintf(buf, \"%L\",\n                       (int64_t) (ngx_current_msec - s->epoch) / 1000);\n}\n\n\nstatic size_t\nngx_rtmp_log_var_session_readable_time_getlen(ngx_rtmp_session_t *s,\n    ngx_rtmp_log_op_t *op)\n{\n    return NGX_INT_T_LEN + sizeof(\"d 23h 59m 59s\") - 1;\n}\n\n\nstatic u_char *\nngx_rtmp_log_var_session_readable_time_getdata(ngx_rtmp_session_t *s,\n    u_char *buf, ngx_rtmp_log_op_t *op)\n{\n    int64_t     v;\n    ngx_uint_t  days, hours, minutes, seconds;\n\n    v = (ngx_current_msec - s->epoch) / 1000;\n\n    days = (ngx_uint_t) (v / (60 * 60 * 24));\n    hours = (ngx_uint_t) (v / (60 * 60) % 24);\n    minutes = (ngx_uint_t) (v / 60 % 60);\n    seconds = (ngx_uint_t) (v % 60);\n\n    if (days) {\n        buf = ngx_sprintf(buf, \"%uid \", days);\n    }\n\n    if (days || hours) {\n        buf = ngx_sprintf(buf, \"%uih \", hours);\n    }\n\n    if (days || hours || minutes) {\n        buf = ngx_sprintf(buf, \"%uim \", minutes);\n    }\n\n    buf = ngx_sprintf(buf, \"%uis\", seconds);\n\n    return buf;\n}\n\n\nstatic ngx_rtmp_log_var_t ngx_rtmp_log_vars[] = {\n    { ngx_string(\"connection\"),\n      ngx_rtmp_log_var_connection_getlen,\n      ngx_rtmp_log_var_connection_getdata,\n      0 },\n\n    { ngx_string(\"remote_addr\"),\n      ngx_rtmp_log_var_remote_addr_getlen,\n      ngx_rtmp_log_var_remote_addr_getdata,\n      0 },\n\n    { ngx_string(\"app\"),\n      ngx_rtmp_log_var_session_string_getlen,\n      ngx_rtmp_log_var_session_string_getdata,\n      offsetof(ngx_rtmp_session_t, app) },\n\n    { ngx_string(\"flashver\"),\n      ngx_rtmp_log_var_session_string_getlen,\n      ngx_rtmp_log_var_session_string_getdata,\n      offsetof(ngx_rtmp_session_t, flashver) },\n\n    { ngx_string(\"swfurl\"),\n      ngx_rtmp_log_var_session_string_getlen,\n      ngx_rtmp_log_var_session_string_getdata,\n      offsetof(ngx_rtmp_session_t, swf_url) },\n\n    { ngx_string(\"tcurl\"),\n      ngx_rtmp_log_var_session_string_getlen,\n      ngx_rtmp_log_var_session_string_getdata,\n      offsetof(ngx_rtmp_session_t, tc_url) },\n\n    { ngx_string(\"pageurl\"),\n      ngx_rtmp_log_var_session_string_getlen,\n      ngx_rtmp_log_var_session_string_getdata,\n      offsetof(ngx_rtmp_session_t, page_url) },\n\n    { ngx_string(\"command\"),\n      ngx_rtmp_log_var_command_getlen,\n      ngx_rtmp_log_var_command_getdata,\n      0 },\n\n    { ngx_string(\"name\"),\n      ngx_rtmp_log_var_context_cstring_getlen,\n      ngx_rtmp_log_var_context_cstring_getdata,\n      offsetof(ngx_rtmp_log_ctx_t, name) },\n\n    { ngx_string(\"args\"),\n      ngx_rtmp_log_var_context_cstring_getlen,\n      ngx_rtmp_log_var_context_cstring_getdata,\n      offsetof(ngx_rtmp_log_ctx_t, args) },\n\n    { ngx_string(\"bytes_sent\"),\n      ngx_rtmp_log_var_session_uint32_getlen,\n      ngx_rtmp_log_var_session_uint32_getdata,\n      offsetof(ngx_rtmp_session_t, out_bytes) },\n\n    { ngx_string(\"bytes_received\"),\n      ngx_rtmp_log_var_session_uint32_getlen,\n      ngx_rtmp_log_var_session_uint32_getdata,\n      offsetof(ngx_rtmp_session_t, in_bytes) },\n\n    { ngx_string(\"time_local\"),\n      ngx_rtmp_log_var_time_local_getlen,\n      ngx_rtmp_log_var_time_local_getdata,\n      0 },\n\n    { ngx_string(\"msec\"),\n      ngx_rtmp_log_var_msec_getlen,\n      ngx_rtmp_log_var_msec_getdata,\n      0 },\n\n    { ngx_string(\"session_time\"),\n      ngx_rtmp_log_var_session_time_getlen,\n      ngx_rtmp_log_var_session_time_getdata,\n      0 },\n\n    { ngx_string(\"session_readable_time\"),\n      ngx_rtmp_log_var_session_readable_time_getlen,\n      ngx_rtmp_log_var_session_readable_time_getdata,\n      0 },\n\n    { ngx_null_string, NULL, NULL, 0 }\n};\n\n\nstatic void *\nngx_rtmp_log_create_main_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_log_main_conf_t   *lmcf;\n    ngx_rtmp_log_fmt_t         *fmt;\n\n    lmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_log_main_conf_t));\n    if (lmcf == NULL) {\n        return NULL;\n    }\n\n    if (ngx_array_init(&lmcf->formats, cf->pool, 4, sizeof(ngx_rtmp_log_fmt_t))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n    fmt = ngx_array_push(&lmcf->formats);\n    if (fmt == NULL) {\n        return NULL;\n    }\n\n    ngx_str_set(&fmt->name, \"combined\");\n\n    fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_rtmp_log_op_t));\n    if (fmt->ops == NULL) {\n        return NULL;\n    }\n\n    return lmcf;\n\n}\n\n\nstatic void *\nngx_rtmp_log_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_log_app_conf_t *lacf;\n\n    lacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_log_app_conf_t));\n    if (lacf == NULL) {\n        return NULL;\n    }\n\n    return lacf;\n}\n\n\nstatic char *\nngx_rtmp_log_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_log_app_conf_t    *prev = parent;\n    ngx_rtmp_log_app_conf_t    *conf = child;\n    ngx_rtmp_log_main_conf_t   *lmcf;\n    ngx_rtmp_log_fmt_t         *fmt;\n    ngx_rtmp_log_t             *log;\n\n    if (conf->logs || conf->off) {\n        return NGX_OK;\n    }\n\n    conf->logs = prev->logs;\n    conf->off = prev->off;\n\n    if (conf->logs || conf->off) {\n        return NGX_OK;\n    }\n\n    conf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_rtmp_log_t));\n    if (conf->logs == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    log = ngx_array_push(conf->logs);\n    if (log == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    log->file = ngx_conf_open_file(cf->cycle, &ngx_rtmp_access_log);\n    if (log->file == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    log->disk_full_time = 0;\n    log->error_log_time = 0;\n\n    lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module);\n    fmt = lmcf->formats.elts;\n\n    log->format = &fmt[0];\n    lmcf->combined_used = 1;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_rtmp_log_set_log(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_log_app_conf_t    *lacf = conf;\n\n    ngx_rtmp_log_main_conf_t   *lmcf;\n    ngx_rtmp_log_fmt_t         *fmt;\n    ngx_rtmp_log_t             *log;\n    ngx_str_t                  *value, name;\n    ngx_uint_t                  n;\n\n    value = cf->args->elts;\n\n    if (ngx_strcmp(value[1].data, \"off\") == 0) {\n        lacf->off = 1;\n        return NGX_CONF_OK;\n    }\n\n    if (lacf->logs == NULL) {\n        lacf->logs = ngx_array_create(cf->pool, 2, sizeof(ngx_rtmp_log_t));\n        if (lacf->logs == NULL) {\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    log = ngx_array_push(lacf->logs);\n    if (log == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    ngx_memzero(log, sizeof(*log));\n\n    lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module);\n\n    log->file = ngx_conf_open_file(cf->cycle, &value[1]);\n    if (log->file == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    if (cf->args->nelts == 2) {\n        ngx_str_set(&name, \"combined\");\n        lmcf->combined_used = 1;\n\n    } else {\n        name = value[2];\n        if (ngx_strcmp(name.data, \"combined\") == 0) {\n            lmcf->combined_used = 1;\n        }\n    }\n\n    fmt = lmcf->formats.elts;\n    for (n = 0; n < lmcf->formats.nelts; ++n, ++fmt) {\n        if (fmt->name.len == name.len &&\n            ngx_strncasecmp(fmt->name.data, name.data, name.len) == 0)\n        {\n            log->format = fmt;\n            break;\n        }\n    }\n\n    if (log->format == NULL) {\n        ngx_conf_log_error(NGX_LOG_WARN, cf, 0, \"unknown log format \\\"%V\\\"\",\n                           &name);\n        return NGX_CONF_ERROR;\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_rtmp_log_set_format(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_log_main_conf_t   *lmcf = conf;\n    ngx_rtmp_log_fmt_t         *fmt;\n    ngx_str_t                  *value;\n    ngx_uint_t                  i;\n\n    value = cf->args->elts;\n\n    if (cf->cmd_type != NGX_RTMP_MAIN_CONF) {\n        ngx_conf_log_error(NGX_LOG_WARN, cf, 0,\n                           \"\\\"log_format\\\" directive can only be used on \"\n                           \"\\\"rtmp\\\" level\");\n    }\n\n    fmt = lmcf->formats.elts;\n    for (i = 0; i < lmcf->formats.nelts; i++) {\n        if (fmt[i].name.len == value[1].len &&\n            ngx_strcmp(fmt[i].name.data, value[1].data) == 0)\n        {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                               \"duplicate \\\"log_format\\\" name \\\"%V\\\"\",\n                               &value[1]);\n            return NGX_CONF_ERROR;\n        }\n    }\n\n    fmt = ngx_array_push(&lmcf->formats);\n    if (fmt == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    fmt->name = value[1];\n\n    fmt->ops = ngx_array_create(cf->pool, 16, sizeof(ngx_rtmp_log_op_t));\n    if (fmt->ops == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    return ngx_rtmp_log_compile_format(cf, fmt->ops, cf->args, 2);\n}\n\n\nstatic char *\nngx_rtmp_log_compile_format(ngx_conf_t *cf, ngx_array_t *ops, ngx_array_t *args,\n                            ngx_uint_t s)\n{\n    size_t              i, len;\n    u_char             *data, *d, c;\n    ngx_uint_t          bracket;\n    ngx_str_t          *value, var;\n    ngx_rtmp_log_op_t  *op;\n    ngx_rtmp_log_var_t *v;\n\n    value = args->elts;\n\n    for (; s < args->nelts; ++s) {\n        i = 0;\n\n        len = value[s].len;\n        d = value[s].data;\n\n        while (i < len) {\n\n            op = ngx_array_push(ops);\n            if (op == NULL) {\n                return NGX_CONF_ERROR;\n            }\n\n            ngx_memzero(op, sizeof(*op));\n\n            data = &d[i];\n\n            if (d[i] == '$') {\n                if (++i == len) {\n                    goto invalid;\n                }\n\n                if (d[i] == '{') {\n                    bracket = 1;\n                    if (++i == len) {\n                        goto invalid;\n                    }\n                } else {\n                    bracket = 0;\n                }\n\n                var.data = &d[i];\n\n                for (var.len = 0; i < len; ++i, ++var.len) {\n                    c = d[i];\n\n                    if (c == '}' && bracket) {\n                        ++i;\n                        bracket = 0;\n                        break;\n                    }\n\n                    if ((c >= 'A' && c <= 'Z') ||\n                        (c >= 'a' && c <= 'z') ||\n                        (c >= '0' && c <= '9') ||\n                        (c == '_'))\n                    {\n                        continue;\n                    }\n\n                    break;\n                }\n\n                if (bracket) {\n                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                       \"missing closing bracket in \\\"%V\\\"\",\n                                       &var);\n                    return NGX_CONF_ERROR;\n                }\n\n                if (var.len == 0) {\n                    goto invalid;\n                }\n\n                for (v = ngx_rtmp_log_vars; v->name.len; ++v) {\n                    if (v->name.len == var.len &&\n                        ngx_strncmp(v->name.data, var.data, var.len) == 0)\n                    {\n                        op->getlen = v->getlen;\n                        op->getdata = v->getdata;\n                        op->offset = v->offset;\n                        break;\n                    }\n                }\n\n                if (v->name.len == 0) {\n                    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                                       \"unknown variable \\\"%V\\\"\", &var);\n                    return NGX_CONF_ERROR;\n                }\n\n                continue;\n            }\n\n            ++i;\n\n            while (i < len && d[i] != '$') {\n                ++i;\n            }\n\n            op->getlen = ngx_rtmp_log_var_default_getlen;\n            op->getdata = ngx_rtmp_log_var_default_getdata;\n\n            op->value.len = &d[i] - data;\n\n            op->value.data = ngx_pnalloc(cf->pool, op->value.len);\n            if (op->value.data == NULL) {\n                return NGX_CONF_ERROR;\n            }\n\n            ngx_memcpy(op->value.data, data, op->value.len);\n        }\n    }\n\n    return NGX_CONF_OK;\n\ninvalid:\n\n    ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, \"invalid parameter \\\"%s\\\"\", data);\n\n    return NGX_CONF_ERROR;\n}\n\n\nstatic ngx_rtmp_log_ctx_t *\nngx_rtmp_log_set_names(ngx_rtmp_session_t *s, u_char *name, u_char *args)\n{\n    ngx_rtmp_log_ctx_t *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_log_module);\n    if (ctx == NULL) {\n        ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_log_ctx_t));\n        if (ctx == NULL) {\n            return NULL;\n        }\n\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_log_module);\n    }\n\n    ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME);\n    ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS);\n\n    return ctx;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_log_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    ngx_rtmp_log_ctx_t *ctx;\n\n    if (s->auto_pushed || s->relay) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_log_set_names(s, v->name, v->args);\n    if (ctx == NULL) {\n        goto next;\n    }\n\n    ctx->publish = 1;\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_log_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    ngx_rtmp_log_ctx_t *ctx;\n\n    if (s->auto_pushed || s->relay) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_log_set_names(s, v->name, v->args);\n    if (ctx == NULL) {\n        goto next;\n    }\n\n    ctx->play = 1;\n\nnext:\n    return next_play(s, v);\n}\n\n\nstatic void\nngx_rtmp_log_write(ngx_rtmp_session_t *s, ngx_rtmp_log_t *log, u_char *buf,\n                   size_t len)\n{\n    u_char *name;\n    time_t  now;\n    ssize_t n;\n    int     err;\n\n    err = 0;\n    name = log->file->name.data;\n    n = ngx_write_fd(log->file->fd, buf, len);\n\n    if (n == (ssize_t) len) {\n        return;\n    }\n\n    now = ngx_time();\n\n    if (n == -1) {\n        err = ngx_errno;\n\n        if (err == NGX_ENOSPC) {\n            log->disk_full_time = now;\n        }\n\n        if (now - log->error_log_time > 59) {\n            ngx_log_error(NGX_LOG_ALERT, s->connection->log, err,\n                          ngx_write_fd_n \" to \\\"%s\\\" failed\", name);\n            log->error_log_time = now;\n        }\n    }\n\n    if (now - log->error_log_time > 59) {\n        ngx_log_error(NGX_LOG_ALERT, s->connection->log, err,\n                      ngx_write_fd_n \" to \\\"%s\\\" was incomplete: %z of %uz\",\n                      name, n, len);\n        log->error_log_time = now;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_log_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                        ngx_chain_t *in)\n{\n    ngx_rtmp_log_app_conf_t    *lacf;\n    ngx_rtmp_log_t             *log;\n    ngx_rtmp_log_op_t          *op;\n    ngx_uint_t                  n, i;\n    u_char                     *line, *p;\n    size_t                      len;\n\n    if (s->auto_pushed || s->relay) {\n        return NGX_OK;\n    }\n\n    lacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_log_module);\n    if (lacf == NULL || lacf->off || lacf->logs == NULL) {\n        return NGX_OK;\n    }\n\n    log = lacf->logs->elts;\n    for (i = 0; i < lacf->logs->nelts; ++i, ++log) {\n\n        if (ngx_time() == log->disk_full_time) {\n            /* FreeBSD full disk protection;\n             * nginx http logger does the same */\n            continue;\n        }\n\n        len = 0;\n        op = log->format->ops->elts;\n        for (n = 0; n < log->format->ops->nelts; ++n, ++op) {\n            len += op->getlen(s, op);\n        }\n\n        len += NGX_LINEFEED_SIZE;\n\n        line = ngx_palloc(s->connection->pool, len);\n        if (line == NULL) {\n            return NGX_OK;\n        }\n\n        p = line;\n        op = log->format->ops->elts;\n        for (n = 0; n < log->format->ops->nelts; ++n, ++op) {\n            p = op->getdata(s, p, op);\n        }\n\n        ngx_linefeed(p);\n\n        ngx_rtmp_log_write(s, log, line, p - line);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_log_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t  *cmcf;\n    ngx_rtmp_handler_pt        *h;\n    ngx_rtmp_log_main_conf_t   *lmcf;\n    ngx_array_t                 a;\n    ngx_rtmp_log_fmt_t         *fmt;\n    ngx_str_t                  *value;\n\n    lmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_log_module);\n    if (lmcf->combined_used) {\n        if (ngx_array_init(&a, cf->pool, 1, sizeof(ngx_str_t)) != NGX_OK) {\n            return NGX_ERROR;\n        }\n\n        value = ngx_array_push(&a);\n        if (value == NULL) {\n            return NGX_ERROR;\n        }\n\n        *value = ngx_rtmp_combined_fmt;\n        fmt = lmcf->formats.elts;\n\n        if (ngx_rtmp_log_compile_format(cf, fmt->ops, &a, 0)\n            != NGX_CONF_OK)\n        {\n            return NGX_ERROR;\n        }\n    }\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);\n    *h = ngx_rtmp_log_disconnect;\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_log_publish;\n\n    next_play = ngx_rtmp_play;\n    ngx_rtmp_play = ngx_rtmp_log_play;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_mp4_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_play_module.h\"\n#include \"ngx_rtmp_codec_module.h\"\n#include \"ngx_rtmp_streams.h\"\n\n\nstatic ngx_int_t ngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf);\nstatic ngx_int_t ngx_rtmp_mp4_init(ngx_rtmp_session_t *s,  ngx_file_t *f,\n       ngx_int_t aindex, ngx_int_t vindex);\nstatic ngx_int_t ngx_rtmp_mp4_done(ngx_rtmp_session_t *s,  ngx_file_t *f);\nstatic ngx_int_t ngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f);\nstatic ngx_int_t ngx_rtmp_mp4_seek(ngx_rtmp_session_t *s,  ngx_file_t *f,\n                                   ngx_uint_t offset);\nstatic ngx_int_t ngx_rtmp_mp4_stop(ngx_rtmp_session_t *s,  ngx_file_t *f);\nstatic ngx_int_t ngx_rtmp_mp4_send(ngx_rtmp_session_t *s,  ngx_file_t *f,\n                                   ngx_uint_t *ts);\nstatic ngx_int_t ngx_rtmp_mp4_reset(ngx_rtmp_session_t *s);\n\n\n#define NGX_RTMP_MP4_MAX_FRAMES         8\n\n\n#pragma pack(push,4)\n\n\n/* disable zero-sized array warning by msvc */\n\n#if (NGX_WIN32)\n#pragma warning(push)\n#pragma warning(disable:4200)\n#endif\n\n\ntypedef struct {\n    uint32_t                            first_chunk;\n    uint32_t                            samples_per_chunk;\n    uint32_t                            sample_descrption_index;\n} ngx_rtmp_mp4_chunk_entry_t;\n\n\ntypedef struct {\n    uint32_t                            version_flags;\n    uint32_t                            entry_count;\n    ngx_rtmp_mp4_chunk_entry_t          entries[0];\n} ngx_rtmp_mp4_chunks_t;\n\n\ntypedef struct {\n    uint32_t                            sample_count;\n    uint32_t                            sample_delta;\n} ngx_rtmp_mp4_time_entry_t;\n\n\ntypedef struct {\n    uint32_t                            version_flags;\n    uint32_t                            entry_count;\n    ngx_rtmp_mp4_time_entry_t           entries[0];\n} ngx_rtmp_mp4_times_t;\n\n\ntypedef struct {\n    uint32_t                            sample_count;\n    uint32_t                            sample_offset;\n} ngx_rtmp_mp4_delay_entry_t;\n\n\ntypedef struct {\n    uint32_t                            version_flags;\n    uint32_t                            entry_count;\n    ngx_rtmp_mp4_delay_entry_t          entries[0];\n} ngx_rtmp_mp4_delays_t;\n\n\ntypedef struct {\n    uint32_t                            version_flags;\n    uint32_t                            entry_count;\n    uint32_t                            entries[0];\n} ngx_rtmp_mp4_keys_t;\n\n\ntypedef struct {\n    uint32_t                            version_flags;\n    uint32_t                            sample_size;\n    uint32_t                            sample_count;\n    uint32_t                            entries[0];\n} ngx_rtmp_mp4_sizes_t;\n\n\ntypedef struct {\n    uint32_t                            version_flags;\n    uint32_t                            field_size;\n    uint32_t                            sample_count;\n    uint32_t                            entries[0];\n} ngx_rtmp_mp4_sizes2_t;\n\n\ntypedef struct {\n    uint32_t                            version_flags;\n    uint32_t                            entry_count;\n    uint32_t                            entries[0];\n} ngx_rtmp_mp4_offsets_t;\n\n\ntypedef struct {\n    uint32_t                            version_flags;\n    uint32_t                            entry_count;\n    uint64_t                            entries[0];\n} ngx_rtmp_mp4_offsets64_t;\n\n\n#if (NGX_WIN32)\n#pragma warning(pop)\n#endif\n\n\n#pragma pack(pop)\n\n\ntypedef struct {\n    uint32_t                            timestamp;\n    uint32_t                            last_timestamp;\n    off_t                               offset;\n    size_t                              size;\n    ngx_int_t                           key;\n    uint32_t                            delay;\n\n    unsigned                            not_first:1;\n    unsigned                            valid:1;\n\n    ngx_uint_t                          pos;\n\n    ngx_uint_t                          key_pos;\n\n    ngx_uint_t                          chunk;\n    ngx_uint_t                          chunk_pos;\n    ngx_uint_t                          chunk_count;\n\n    ngx_uint_t                          time_pos;\n    ngx_uint_t                          time_count;\n\n    ngx_uint_t                          delay_pos;\n    ngx_uint_t                          delay_count;\n\n    ngx_uint_t                          size_pos;\n} ngx_rtmp_mp4_cursor_t;\n\n\ntypedef struct {\n    ngx_uint_t                          id;\n\n    ngx_int_t                           type;\n    ngx_int_t                           codec;\n    uint32_t                            csid;\n    u_char                              fhdr;\n    ngx_int_t                           time_scale;\n    uint64_t                            duration;\n\n    u_char                             *header;\n    size_t                              header_size;\n    unsigned                            header_sent:1;\n\n    ngx_rtmp_mp4_times_t               *times;\n    ngx_rtmp_mp4_delays_t              *delays;\n    ngx_rtmp_mp4_keys_t                *keys;\n    ngx_rtmp_mp4_chunks_t              *chunks;\n    ngx_rtmp_mp4_sizes_t               *sizes;\n    ngx_rtmp_mp4_sizes2_t              *sizes2;\n    ngx_rtmp_mp4_offsets_t             *offsets;\n    ngx_rtmp_mp4_offsets64_t           *offsets64;\n    ngx_rtmp_mp4_cursor_t               cursor;\n} ngx_rtmp_mp4_track_t;\n\n\ntypedef struct {\n    void                               *mmaped;\n    size_t                              mmaped_size;\n    ngx_fd_t                            extra;\n\n    unsigned                            meta_sent:1;\n\n    ngx_rtmp_mp4_track_t                tracks[2];\n    ngx_rtmp_mp4_track_t               *track;\n    ngx_uint_t                          ntracks;\n\n    ngx_uint_t                          width;\n    ngx_uint_t                          height;\n    ngx_uint_t                          nchannels;\n    ngx_uint_t                          sample_size;\n    ngx_uint_t                          sample_rate;\n\n    ngx_int_t                           atracks, vtracks;\n    ngx_int_t                           aindex, vindex;\n\n    uint32_t                            start_timestamp, epoch;\n} ngx_rtmp_mp4_ctx_t;\n\n\n#define ngx_rtmp_mp4_make_tag(a, b, c, d)  \\\n    ((uint32_t)d << 24 | (uint32_t)c << 16 | (uint32_t)b << 8 | (uint32_t)a)\n\n\nstatic ngx_inline uint32_t\nngx_rtmp_mp4_to_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint64_t ts)\n{\n    return (uint32_t) (ts * 1000 / t->time_scale);\n}\n\n\nstatic ngx_inline uint32_t\nngx_rtmp_mp4_from_rtmp_timestamp(ngx_rtmp_mp4_track_t *t, uint32_t ts)\n{\n    return (uint64_t) ts * t->time_scale / 1000;\n}\n\n\n#define NGX_RTMP_MP4_BUFLEN_ADDON       1000\n\n\nstatic u_char                           ngx_rtmp_mp4_buffer[1024*1024];\n\n\n#if (NGX_WIN32)\nstatic void *\nngx_rtmp_mp4_mmap(ngx_fd_t fd, size_t size, off_t offset, ngx_fd_t *extra)\n{\n    void           *data;\n\n    *extra = CreateFileMapping(fd, NULL, PAGE_READONLY,\n                               (DWORD) ((uint64_t) size >> 32),\n                               (DWORD) (size & 0xffffffff),\n                               NULL);\n    if (*extra == NULL) {\n        return NULL;\n    }\n\n    data = MapViewOfFile(*extra, FILE_MAP_READ,\n                         (DWORD) ((uint64_t) offset >> 32),\n                         (DWORD) (offset & 0xffffffff),\n                         size);\n\n    if (data == NULL) {\n        CloseHandle(*extra);\n    }\n\n    /*\n     * non-NULL result means map view handle is open\n     * and should be closed later\n     */\n\n    return data;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_munmap(void *data, size_t size, ngx_fd_t *extra)\n{\n    ngx_int_t  rc;\n\n    rc = NGX_OK;\n\n    if (UnmapViewOfFile(data) == 0) {\n        rc = NGX_ERROR;\n    }\n\n    if (CloseHandle(*extra) == 0) {\n        rc = NGX_ERROR;\n    }\n\n    return rc;\n}\n\n#else\n\nstatic void *\nngx_rtmp_mp4_mmap(ngx_fd_t fd, size_t size, off_t offset, ngx_fd_t *extra)\n{\n    void  *data;\n\n    data = mmap(NULL, size, PROT_READ, MAP_SHARED, fd, offset);\n\n    /* valid address is never NULL since there's no MAP_FIXED */\n\n    return data == MAP_FAILED ? NULL : data;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_munmap(void *data, size_t size, ngx_fd_t *extra)\n{\n    return munmap(data, size);\n}\n\n#endif\n\n\nstatic ngx_int_t ngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_mdhd(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_hdlr(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_stsd(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_stsc(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_stts(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_ctts(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_stss(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_stsz(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_stz2(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_stco(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_co64(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_avc1(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_avcC(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_mp4a(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_mp4v(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_esds(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_mp3(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_nmos(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_spex(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\n\n\ntypedef ngx_int_t (*ngx_rtmp_mp4_box_pt)(ngx_rtmp_session_t *s, u_char *pos,\n                                         u_char *last);\n\ntypedef struct {\n    uint32_t                            tag;\n    ngx_rtmp_mp4_box_pt                 handler;\n} ngx_rtmp_mp4_box_t;\n\n\nstatic ngx_rtmp_mp4_box_t                       ngx_rtmp_mp4_boxes[] = {\n    { ngx_rtmp_mp4_make_tag('t','r','a','k'),   ngx_rtmp_mp4_parse_trak   },\n    { ngx_rtmp_mp4_make_tag('m','d','i','a'),   ngx_rtmp_mp4_parse        },\n    { ngx_rtmp_mp4_make_tag('m','d','h','d'),   ngx_rtmp_mp4_parse_mdhd   },\n    { ngx_rtmp_mp4_make_tag('h','d','l','r'),   ngx_rtmp_mp4_parse_hdlr   },\n    { ngx_rtmp_mp4_make_tag('m','i','n','f'),   ngx_rtmp_mp4_parse        },\n    { ngx_rtmp_mp4_make_tag('s','t','b','l'),   ngx_rtmp_mp4_parse        },\n    { ngx_rtmp_mp4_make_tag('s','t','s','d'),   ngx_rtmp_mp4_parse_stsd   },\n    { ngx_rtmp_mp4_make_tag('s','t','s','c'),   ngx_rtmp_mp4_parse_stsc   },\n    { ngx_rtmp_mp4_make_tag('s','t','t','s'),   ngx_rtmp_mp4_parse_stts   },\n    { ngx_rtmp_mp4_make_tag('c','t','t','s'),   ngx_rtmp_mp4_parse_ctts   },\n    { ngx_rtmp_mp4_make_tag('s','t','s','s'),   ngx_rtmp_mp4_parse_stss   },\n    { ngx_rtmp_mp4_make_tag('s','t','s','z'),   ngx_rtmp_mp4_parse_stsz   },\n    { ngx_rtmp_mp4_make_tag('s','t','z','2'),   ngx_rtmp_mp4_parse_stz2   },\n    { ngx_rtmp_mp4_make_tag('s','t','c','o'),   ngx_rtmp_mp4_parse_stco   },\n    { ngx_rtmp_mp4_make_tag('c','o','6','4'),   ngx_rtmp_mp4_parse_co64   },\n    { ngx_rtmp_mp4_make_tag('a','v','c','1'),   ngx_rtmp_mp4_parse_avc1   },\n    { ngx_rtmp_mp4_make_tag('a','v','c','C'),   ngx_rtmp_mp4_parse_avcC   },\n    { ngx_rtmp_mp4_make_tag('m','p','4','a'),   ngx_rtmp_mp4_parse_mp4a   },\n    { ngx_rtmp_mp4_make_tag('m','p','4','v'),   ngx_rtmp_mp4_parse_mp4v   },\n    { ngx_rtmp_mp4_make_tag('e','s','d','s'),   ngx_rtmp_mp4_parse_esds   },\n    { ngx_rtmp_mp4_make_tag('.','m','p','3'),   ngx_rtmp_mp4_parse_mp3    },\n    { ngx_rtmp_mp4_make_tag('n','m','o','s'),   ngx_rtmp_mp4_parse_nmos   },\n    { ngx_rtmp_mp4_make_tag('s','p','e','x'),   ngx_rtmp_mp4_parse_spex   },\n    { ngx_rtmp_mp4_make_tag('w','a','v','e'),   ngx_rtmp_mp4_parse        }\n};\n\n\nstatic ngx_int_t ngx_rtmp_mp4_parse_descr(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_es(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_dc(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\nstatic ngx_int_t ngx_rtmp_mp4_parse_ds(ngx_rtmp_session_t *s, u_char *pos,\n       u_char *last);\n\n\ntypedef ngx_int_t (*ngx_rtmp_mp4_descriptor_pt)(ngx_rtmp_session_t *s,\n                                                u_char *pos, u_char *last);\n\ntypedef struct {\n    uint8_t                             tag;\n    ngx_rtmp_mp4_descriptor_pt          handler;\n} ngx_rtmp_mp4_descriptor_t;\n\n\nstatic ngx_rtmp_mp4_descriptor_t        ngx_rtmp_mp4_descriptors[] = {\n    { 0x03,   ngx_rtmp_mp4_parse_es   },    /* MPEG ES Descriptor */\n    { 0x04,   ngx_rtmp_mp4_parse_dc   },    /* MPEG DecoderConfig Descriptor */\n    { 0x05,   ngx_rtmp_mp4_parse_ds   }     /* MPEG DecoderSpec Descriptor */\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_mp4_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_mp4_postconfiguration,         /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    NULL,                                   /* create app configuration */\n    NULL                                    /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_mp4_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_mp4_module_ctx,               /* module context */\n    NULL,                                   /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_trak(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx->track) {\n        return NGX_OK;\n    }\n\n    ctx->track = (ctx->ntracks == sizeof(ctx->tracks) / sizeof(ctx->tracks[0]))\n                 ? NULL : &ctx->tracks[ctx->ntracks];\n\n    if (ctx->track) {\n        ngx_memzero(ctx->track, sizeof(*ctx->track));\n        ctx->track->id = ctx->ntracks;\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: trying track %ui\", ctx->ntracks);\n    }\n\n    if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    if (ctx->track && ctx->track->type &&\n        (ctx->ntracks == 0 ||\n         ctx->tracks[0].type != ctx->tracks[ctx->ntracks].type))\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: adding track %ui\", ctx->ntracks);\n\n        if (ctx->track->type == NGX_RTMP_MSG_AUDIO) {\n            if (ctx->atracks++ != ctx->aindex) {\n                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"mp4: skipping audio track %ui!=%ui\",\n                               ctx->atracks - 1, ctx->aindex);\n                ctx->track = NULL;\n                return NGX_OK;\n            }\n\n        } else {\n            if (ctx->vtracks++ != ctx->vindex) {\n                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"mp4: skipping video track %i!=%i\",\n                               ctx->vtracks - 1, ctx->vindex);\n                ctx->track = NULL;\n                return NGX_OK;\n            }\n        }\n\n        ++ctx->ntracks;\n\n    } else {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: ignoring track %ui\", ctx->ntracks);\n    }\n\n    ctx->track = NULL;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_mdhd(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n    uint8_t                     version;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx->track == NULL) {\n        return NGX_OK;\n    }\n\n    t = ctx->track;\n\n    if (pos + 1 > last) {\n        return NGX_ERROR;\n    }\n\n    version = *(uint8_t *) pos;\n\n    switch (version) {\n        case 0:\n            if (pos + 20 > last) {\n                return NGX_ERROR;\n            }\n\n            pos += 12;\n            t->time_scale = ngx_rtmp_r32(*(uint32_t *) pos);\n            pos += 4;\n            t->duration = ngx_rtmp_r32(*(uint32_t *) pos);\n            break;\n\n        case 1:\n            if (pos + 28 > last) {\n                return NGX_ERROR;\n            }\n\n            pos += 20;\n            t->time_scale = ngx_rtmp_r32(*(uint32_t *) pos);\n            pos += 4;\n            t->duration = ngx_rtmp_r64(*(uint64_t *) pos);\n            break;\n\n        default:\n            return NGX_ERROR;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: duration time_scale=%ui duration=%uL\",\n                   t->time_scale, t->duration);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_hdlr(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    uint32_t                    type;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx->track == NULL) {\n        return NGX_OK;\n    }\n\n    if (pos + 12 > last) {\n        return NGX_ERROR;\n    }\n\n    type = *(uint32_t *)(pos + 8);\n\n    if (type == ngx_rtmp_mp4_make_tag('v','i','d','e')) {\n        ctx->track->type = NGX_RTMP_MSG_VIDEO;\n        ctx->track->csid = NGX_RTMP_CSID_VIDEO;\n\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: video track\");\n\n    } else if (type == ngx_rtmp_mp4_make_tag('s','o','u','n')) {\n        ctx->track->type = NGX_RTMP_MSG_AUDIO;\n        ctx->track->csid = NGX_RTMP_CSID_AUDIO;\n\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: audio track\");\n    } else {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: unknown track\");\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_video(ngx_rtmp_session_t *s, u_char *pos, u_char *last,\n                         ngx_int_t codec)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx->track == NULL) {\n        return NGX_OK;\n    }\n\n    ctx->track->codec = codec;\n\n    if (pos + 78 > last) {\n        return NGX_ERROR;\n    }\n\n    pos += 24;\n\n    ctx->width = ngx_rtmp_r16(*(uint16_t *) pos);\n\n    pos += 2;\n\n    ctx->height = ngx_rtmp_r16(*(uint16_t *) pos);\n\n    pos += 52;\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: video settings codec=%i, width=%ui, height=%ui\",\n                   codec, ctx->width, ctx->height);\n\n    if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    ctx->track->fhdr = (u_char) ctx->track->codec;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_audio(ngx_rtmp_session_t *s, u_char *pos, u_char *last,\n                         ngx_int_t codec)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    u_char                     *p;\n    ngx_uint_t                  version;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx->track == NULL) {\n        return NGX_OK;\n    }\n\n    ctx->track->codec = codec;\n\n    if (pos + 28 > last) {\n        return NGX_ERROR;\n    }\n\n    pos += 8;\n\n    version = ngx_rtmp_r16(*(uint16_t *) pos);\n\n    pos += 8;\n\n    ctx->nchannels = ngx_rtmp_r16(*(uint16_t *) pos);\n\n    pos += 2;\n\n    ctx->sample_size = ngx_rtmp_r16(*(uint16_t *) pos);\n\n    pos += 6;\n\n    ctx->sample_rate = ngx_rtmp_r16(*(uint16_t *) pos);\n\n    pos += 4;\n\n    p = &ctx->track->fhdr;\n\n    *p = 0;\n\n    if (ctx->nchannels == 2) {\n        *p |= 0x01;\n    }\n\n    if (ctx->sample_size == 16) {\n        *p |= 0x02;\n    }\n\n    switch (ctx->sample_rate) {\n        case 5512:\n            break;\n\n        case 11025:\n            *p |= 0x04;\n            break;\n\n        case 22050:\n            *p |= 0x08;\n            break;\n\n        default:  /*44100 etc */\n            *p |= 0x0c;\n            break;\n    }\n\n    ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: audio settings version=%ui, codec=%i, nchannels==%ui, \"\n                   \"sample_size=%ui, sample_rate=%ui\",\n                   version, codec, ctx->nchannels, ctx->sample_size,\n                   ctx->sample_rate);\n\n    switch (version) {\n        case 1:\n            pos += 16;\n            break;\n\n        case 2:\n            pos += 36;\n    }\n\n    if (pos > last) {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_mp4_parse(s, pos, last) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    *p |= (ctx->track->codec << 4);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_avc1(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    return ngx_rtmp_mp4_parse_video(s, pos, last, NGX_RTMP_VIDEO_H264);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_mp4v(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    return ngx_rtmp_mp4_parse_video(s, pos, last, NGX_RTMP_VIDEO_H264);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_avcC(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n\n    if (pos == last) {\n        return NGX_OK;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx->track == NULL || ctx->track->codec != NGX_RTMP_VIDEO_H264) {\n        return NGX_OK;\n    }\n\n    ctx->track->header = pos;\n    ctx->track->header_size = (size_t) (last - pos);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: video h264 header size=%uz\",\n                   ctx->track->header_size);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_mp4a(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_MP3);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_ds(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t     *ctx;\n    ngx_rtmp_mp4_track_t   *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->header = pos;\n    t->header_size = (size_t) (last - pos);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: decoder header size=%uz\", t->header_size);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_dc(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    uint8_t                 id;\n    ngx_rtmp_mp4_ctx_t     *ctx;\n    ngx_int_t              *pc;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx->track == NULL) {\n        return NGX_OK;\n    }\n\n    if (pos + 13 > last) {\n        return NGX_ERROR;\n    }\n\n    id = * (uint8_t *) pos;\n    pos += 13;\n    pc = &ctx->track->codec;\n\n    switch (id) {\n        case 0x21:\n            *pc = NGX_RTMP_VIDEO_H264;\n            break;\n\n        case 0x40:\n        case 0x66:\n        case 0x67:\n        case 0x68:\n            *pc = NGX_RTMP_AUDIO_AAC;\n            break;\n\n        case 0x69:\n        case 0x6b:\n            *pc = NGX_RTMP_AUDIO_MP3;\n            break;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: decoder descriptor id=%i codec=%i\",\n                   (ngx_int_t) id, *pc);\n\n    return ngx_rtmp_mp4_parse_descr(s, pos, last);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_es(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    uint16_t    id;\n    uint8_t     flags;\n\n    if (pos + 3 > last) {\n        return NGX_ERROR;\n    }\n\n    id = ngx_rtmp_r16(*(uint16_t *) pos);\n    pos += 2;\n\n    flags = *(uint8_t *) pos;\n    ++pos;\n\n    if (flags & 0x80) { /* streamDependenceFlag */\n        pos += 2;\n    }\n\n    if (flags & 0x40) { /* URL_FLag */\n        return NGX_OK;\n    }\n\n    if (flags & 0x20) { /* OCRstreamFlag */\n        pos += 2;\n    }\n\n    if (pos > last) {\n        return NGX_ERROR;\n    }\n\n    (void) id;\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: es descriptor es id=%i flags=%i\",\n                   (ngx_int_t) id, (ngx_int_t) flags);\n\n    return ngx_rtmp_mp4_parse_descr(s, pos, last);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_descr(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    uint8_t                     tag, v;\n    uint32_t                    size;\n    ngx_uint_t                  n, ndesc;\n    ngx_rtmp_mp4_descriptor_t   *ds;\n\n    ndesc = sizeof(ngx_rtmp_mp4_descriptors)\n          / sizeof(ngx_rtmp_mp4_descriptors[0]);\n\n    while (pos < last) {\n        tag = *(uint8_t *) pos++;\n\n        for (size = 0, n = 0; n < 4; ++n) {\n            if (pos == last) {\n                return NGX_ERROR;\n            }\n\n            v = *(uint8_t *) pos++;\n\n            size = (size << 7) | (v & 0x7f);\n\n            if (!(v & 0x80)) {\n                break;\n            }\n        }\n\n        if (pos + size > last) {\n            return NGX_ERROR;\n        }\n\n        ds = ngx_rtmp_mp4_descriptors;;\n\n        for (n = 0; n < ndesc; ++n, ++ds) {\n            if (tag == ds->tag) {\n                break;\n            }\n        }\n\n        if (n == ndesc) {\n            ds = NULL;\n        }\n\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"mp4: descriptor%s tag=%i size=%uD\",\n                ds ? \"\" : \" unhandled\", (ngx_int_t) tag, size);\n\n        if (ds && ds->handler(s, pos, pos + size) != NGX_OK) {\n            return NGX_ERROR;\n        }\n\n        pos += size;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_esds(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    if (pos + 4 > last) {\n        return NGX_ERROR;\n    }\n\n    pos += 4; /* version */\n\n    return ngx_rtmp_mp4_parse_descr(s, pos, last);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_mp3(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_MP3);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_nmos(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_NELLY);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_spex(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    return ngx_rtmp_mp4_parse_audio(s, pos, last, NGX_RTMP_AUDIO_SPEEX);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_stsd(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    if (pos + 8 > last) {\n        return NGX_ERROR;\n    }\n\n    pos += 8;\n\n    ngx_rtmp_mp4_parse(s, pos, last);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_stsc(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->chunks = (ngx_rtmp_mp4_chunks_t *) pos;\n\n    if (pos + sizeof(*t->chunks) + ngx_rtmp_r32(t->chunks->entry_count) *\n                                   sizeof(t->chunks->entries[0])\n        <= last)\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: chunks entries=%uD\",\n                       ngx_rtmp_r32(t->chunks->entry_count));\n        return NGX_OK;\n    }\n\n    t->chunks = NULL;\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_stts(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->times = (ngx_rtmp_mp4_times_t *) pos;\n\n    if (pos + sizeof(*t->times) + ngx_rtmp_r32(t->times->entry_count) *\n                                  sizeof(t->times->entries[0])\n        <= last)\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: times entries=%uD\",\n                       ngx_rtmp_r32(t->times->entry_count));\n        return NGX_OK;\n    }\n\n    t->times = NULL;\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_ctts(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->delays = (ngx_rtmp_mp4_delays_t *) pos;\n\n    if (pos + sizeof(*t->delays) + ngx_rtmp_r32(t->delays->entry_count) *\n                                   sizeof(t->delays->entries[0])\n        <= last)\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: delays entries=%uD\",\n                       ngx_rtmp_r32(t->delays->entry_count));\n        return NGX_OK;\n    }\n\n    t->delays = NULL;\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_stss(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->keys = (ngx_rtmp_mp4_keys_t *) pos;\n\n    if (pos + sizeof(*t->keys) + ngx_rtmp_r32(t->keys->entry_count) *\n                                  sizeof(t->keys->entries[0])\n        <= last)\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: keys entries=%uD\",\n                       ngx_rtmp_r32(t->keys->entry_count));\n        return NGX_OK;\n    }\n\n    t->keys = NULL;\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_stsz(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->sizes = (ngx_rtmp_mp4_sizes_t *) pos;\n\n    if (pos + sizeof(*t->sizes) <= last && t->sizes->sample_size) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: sizes size=%uD\",\n                       ngx_rtmp_r32(t->sizes->sample_size));\n        return NGX_OK;\n    }\n\n    if (pos + sizeof(*t->sizes) + ngx_rtmp_r32(t->sizes->sample_count) *\n                                  sizeof(t->sizes->entries[0])\n        <= last)\n\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: sizes entries=%uD\",\n                       ngx_rtmp_r32(t->sizes->sample_count));\n        return NGX_OK;\n    }\n\n    t->sizes = NULL;\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_stz2(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->sizes2 = (ngx_rtmp_mp4_sizes2_t *) pos;\n\n    if (pos + sizeof(*t->sizes) + ngx_rtmp_r32(t->sizes2->sample_count) *\n                                  ngx_rtmp_r32(t->sizes2->field_size) / 8\n        <= last)\n    {\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: sizes2 field_size=%uD entries=%uD\",\n                       ngx_rtmp_r32(t->sizes2->field_size),\n                       ngx_rtmp_r32(t->sizes2->sample_count));\n        return NGX_OK;\n    }\n\n    t->sizes2 = NULL;\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_stco(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->offsets = (ngx_rtmp_mp4_offsets_t *) pos;\n\n    if (pos + sizeof(*t->offsets) + ngx_rtmp_r32(t->offsets->entry_count) *\n                                    sizeof(t->offsets->entries[0])\n        <= last)\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: offsets entries=%uD\",\n                       ngx_rtmp_r32(t->offsets->entry_count));\n        return NGX_OK;\n    }\n\n    t->offsets = NULL;\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse_co64(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    ngx_rtmp_mp4_track_t       *t;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    t = ctx->track;\n\n    if (t == NULL) {\n        return NGX_OK;\n    }\n\n    t->offsets64 = (ngx_rtmp_mp4_offsets64_t *) pos;\n\n    if (pos + sizeof(*t->offsets64) + ngx_rtmp_r32(t->offsets64->entry_count) *\n                                      sizeof(t->offsets64->entries[0])\n        <= last)\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: offsets64 entries=%uD\",\n                       ngx_rtmp_r32(t->offsets64->entry_count));\n        return NGX_OK;\n    }\n\n    t->offsets64 = NULL;\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_parse(ngx_rtmp_session_t *s, u_char *pos, u_char *last)\n{\n    uint32_t                   *hdr, tag;\n    size_t                      size, nboxes;\n    ngx_uint_t                  n;\n    ngx_rtmp_mp4_box_t         *b;\n\n    while (pos != last) {\n        if (pos + 8 > last) {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: too small box: size=%i\", last - pos);\n            return NGX_ERROR;\n        }\n\n        hdr = (uint32_t *) pos;\n        size = ngx_rtmp_r32(hdr[0]);\n        tag  = hdr[1];\n\n        if (pos + size > last) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"mp4: too big box '%*s': size=%uz\",\n                          4, &tag, size);\n            return NGX_ERROR;\n        }\n\n        b = ngx_rtmp_mp4_boxes;\n        nboxes = sizeof(ngx_rtmp_mp4_boxes) / sizeof(ngx_rtmp_mp4_boxes[0]);\n\n        for (n = 0; n < nboxes && b->tag != tag; ++n, ++b);\n\n        if (n == nboxes) {\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: box unhandled '%*s'\", 4, &tag);\n        } else {\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: box '%*s'\", 4, &tag);\n            b->handler(s, pos + 8, pos + size);\n        }\n\n        pos += size;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_next_time(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t      *cr;\n    ngx_rtmp_mp4_time_entry_t  *te;\n\n    if (t->times == NULL) {\n        return NGX_ERROR;\n    }\n\n    cr = &t->cursor;\n\n    if (cr->time_pos >= ngx_rtmp_r32(t->times->entry_count)) {\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui time[%ui/%uD] overflow\",\n                       t->id, cr->time_pos,\n                       ngx_rtmp_r32(t->times->entry_count));\n\n        return NGX_ERROR;\n    }\n\n    te = &t->times->entries[cr->time_pos];\n\n    cr->last_timestamp = cr->timestamp;\n    cr->timestamp += ngx_rtmp_r32(te->sample_delta);\n\n    cr->not_first = 1;\n\n    ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: track#%ui time[%ui] [%ui/%uD][%ui/%uD]=%uD t=%uD\",\n                   t->id, cr->pos, cr->time_pos,\n                   ngx_rtmp_r32(t->times->entry_count),\n                   cr->time_count, ngx_rtmp_r32(te->sample_count),\n                   ngx_rtmp_r32(te->sample_delta),\n                   cr->timestamp);\n\n    cr->time_count++;\n    cr->pos++;\n\n    if (cr->time_count >= ngx_rtmp_r32(te->sample_count)) {\n        cr->time_pos++;\n        cr->time_count = 0;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_seek_time(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t,\n                       uint32_t timestamp)\n{\n    ngx_rtmp_mp4_cursor_t      *cr;\n    ngx_rtmp_mp4_time_entry_t  *te;\n    uint32_t                    dt;\n\n    if (t->times == NULL) {\n        return NGX_ERROR;\n    }\n\n    cr = &t->cursor;\n\n    te = t->times->entries;\n\n    while (cr->time_pos < ngx_rtmp_r32(t->times->entry_count)) {\n        dt = ngx_rtmp_r32(te->sample_delta) * ngx_rtmp_r32(te->sample_count);\n\n        if (cr->timestamp + dt >= timestamp) {\n            if (te->sample_delta == 0) {\n                return NGX_ERROR;\n            }\n\n            cr->time_count = (timestamp - cr->timestamp) /\n                             ngx_rtmp_r32(te->sample_delta);\n            cr->timestamp += ngx_rtmp_r32(te->sample_delta) * cr->time_count;\n            cr->pos += cr->time_count;\n\n            break;\n        }\n\n        cr->timestamp += dt;\n        cr->pos += ngx_rtmp_r32(te->sample_count);\n        cr->time_pos++;\n        te++;\n    }\n\n    if (cr->time_pos >= ngx_rtmp_r32(t->times->entry_count)) {\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui seek time[%ui/%uD] overflow\",\n                       t->id, cr->time_pos,\n                       ngx_rtmp_r32(t->times->entry_count));\n\n        return  NGX_ERROR;\n    }\n\n    ngx_log_debug8(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: track#%ui seek time[%ui] [%ui/%uD][%ui/%uD]=%uD \"\n                   \"t=%uD\",\n                   t->id, cr->pos, cr->time_pos,\n                   ngx_rtmp_r32(t->times->entry_count),\n                   cr->time_count,\n                   ngx_rtmp_r32(te->sample_count),\n                   ngx_rtmp_r32(te->sample_delta),\n                   cr->timestamp);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_update_offset(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t          *cr;\n    ngx_uint_t                      chunk;\n\n    cr = &t->cursor;\n\n    if (cr->chunk < 1) {\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui offset[%ui] underflow\",\n                       t->id, cr->chunk);\n        return NGX_ERROR;\n    }\n\n    chunk = cr->chunk - 1;\n\n    if (t->offsets) {\n        if (chunk >= ngx_rtmp_r32(t->offsets->entry_count)) {\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui offset[%ui/%uD] overflow\",\n                           t->id, cr->chunk,\n                           ngx_rtmp_r32(t->offsets->entry_count));\n\n            return NGX_ERROR;\n        }\n\n        cr->offset = (off_t) ngx_rtmp_r32(t->offsets->entries[chunk]);\n        cr->size = 0;\n\n        ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui offset[%ui/%uD]=%O\",\n                       t->id, cr->chunk,\n                       ngx_rtmp_r32(t->offsets->entry_count),\n                       cr->offset);\n\n        return NGX_OK;\n    }\n\n    if (t->offsets64) {\n        if (chunk >= ngx_rtmp_r32(t->offsets64->entry_count)) {\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui offset64[%ui/%uD] overflow\",\n                           t->id, cr->chunk,\n                           ngx_rtmp_r32(t->offsets->entry_count));\n\n            return NGX_ERROR;\n        }\n\n        cr->offset = (off_t) ngx_rtmp_r64(t->offsets64->entries[chunk]);\n        cr->size = 0;\n\n        ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui offset64[%ui/%uD]=%O\",\n                       t->id, cr->chunk,\n                       ngx_rtmp_r32(t->offsets->entry_count),\n                       cr->offset);\n\n        return NGX_OK;\n    }\n\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_next_chunk(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t          *cr;\n    ngx_rtmp_mp4_chunk_entry_t     *ce, *nce;\n    ngx_int_t                       new_chunk;\n\n    if (t->chunks == NULL) {\n        return NGX_OK;\n    }\n\n    cr = &t->cursor;\n\n    if (cr->chunk_pos >= ngx_rtmp_r32(t->chunks->entry_count)) {\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui chunk[%ui/%uD] overflow\",\n                       t->id, cr->chunk_pos,\n                       ngx_rtmp_r32(t->chunks->entry_count));\n\n        return NGX_ERROR;\n    }\n\n    ce = &t->chunks->entries[cr->chunk_pos];\n\n    cr->chunk_count++;\n\n    if (cr->chunk_count >= ngx_rtmp_r32(ce->samples_per_chunk)) {\n        cr->chunk_count = 0;\n        cr->chunk++;\n\n        if (cr->chunk_pos + 1 < ngx_rtmp_r32(t->chunks->entry_count)) {\n            nce = ce + 1;\n            if (cr->chunk >= ngx_rtmp_r32(nce->first_chunk)) {\n                cr->chunk_pos++;\n                ce = nce;\n            }\n        }\n\n        new_chunk = 1;\n\n    } else {\n        new_chunk = 0;\n    }\n\n    ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: track#%ui chunk[%ui/%uD][%uD..%ui][%ui/%uD]\",\n                   t->id, cr->chunk_pos,\n                   ngx_rtmp_r32(t->chunks->entry_count),\n                   ngx_rtmp_r32(ce->first_chunk),\n                   cr->chunk, cr->chunk_count,\n                   ngx_rtmp_r32(ce->samples_per_chunk));\n\n\n    if (new_chunk) {\n        return ngx_rtmp_mp4_update_offset(s, t);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_seek_chunk(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t          *cr;\n    ngx_rtmp_mp4_chunk_entry_t     *ce, *nce;\n    ngx_uint_t                      pos, dpos, dchunk;\n\n    cr = &t->cursor;\n\n    if (t->chunks == NULL || t->chunks->entry_count == 0) {\n        cr->chunk = 1;\n        return NGX_OK;\n    }\n\n    ce = t->chunks->entries;\n    pos = 0;\n\n    while (cr->chunk_pos + 1 < ngx_rtmp_r32(t->chunks->entry_count)) {\n        nce = ce + 1;\n\n        dpos = (ngx_rtmp_r32(nce->first_chunk) -\n                ngx_rtmp_r32(ce->first_chunk)) *\n                ngx_rtmp_r32(ce->samples_per_chunk);\n\n        if (pos + dpos > cr->pos) {\n            break;\n        }\n\n        pos += dpos;\n        ce++;\n        cr->chunk_pos++;\n    }\n\n    if (ce->samples_per_chunk == 0) {\n        return NGX_ERROR;\n    }\n\n    dchunk = (cr->pos - pos) / ngx_rtmp_r32(ce->samples_per_chunk);\n\n    cr->chunk = ngx_rtmp_r32(ce->first_chunk) + dchunk;\n    cr->chunk_pos = (ngx_uint_t) (ce - t->chunks->entries);\n    cr->chunk_count = (ngx_uint_t) (cr->pos - pos - dchunk *\n                                    ngx_rtmp_r32(ce->samples_per_chunk));\n\n    ngx_log_debug7(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: track#%ui seek chunk[%ui/%uD][%uD..%ui][%ui/%uD]\",\n                   t->id, cr->chunk_pos,\n                   ngx_rtmp_r32(t->chunks->entry_count),\n                   ngx_rtmp_r32(ce->first_chunk),\n                   cr->chunk, cr->chunk_count,\n                   ngx_rtmp_r32(ce->samples_per_chunk));\n\n    return ngx_rtmp_mp4_update_offset(s, t);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_next_size(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t          *cr;\n\n    cr = &t->cursor;\n\n    cr->offset += cr->size;\n\n    if (t->sizes) {\n        if (t->sizes->sample_size) {\n            cr->size = ngx_rtmp_r32(t->sizes->sample_size);\n\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui size fix=%uz\",\n                           t->id, cr->size);\n\n            return NGX_OK;\n        }\n\n        cr->size_pos++;\n\n        if (cr->size_pos >= ngx_rtmp_r32(t->sizes->sample_count)) {\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui size[%ui/%uD] overflow\",\n                           t->id, cr->size_pos,\n                           ngx_rtmp_r32(t->sizes->sample_count));\n\n            return NGX_ERROR;\n        }\n\n        cr->size = ngx_rtmp_r32(t->sizes->entries[cr->size_pos]);\n\n        ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui size[%ui/%uD]=%uz\",\n                       t->id, cr->size_pos,\n                       ngx_rtmp_r32(t->sizes->sample_count),\n                       cr->size);\n\n        return NGX_OK;\n    }\n\n    if (t->sizes2) {\n        if (cr->size_pos >= ngx_rtmp_r32(t->sizes2->sample_count)) {\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui size[%ui/%uD] overflow\",\n                           t->id, cr->size_pos,\n                           ngx_rtmp_r32(t->sizes2->sample_count));\n\n            return NGX_ERROR;\n        }\n\n        /*TODO*/\n\n        return NGX_OK;\n    }\n\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_seek_size(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t      *cr;\n    ngx_uint_t                  pos;\n\n    cr = &t->cursor;\n\n    if (cr->chunk_count > cr->pos) {\n        return NGX_ERROR;\n    }\n\n    if (t->sizes) {\n        if (t->sizes->sample_size) {\n            cr->size = ngx_rtmp_r32(t->sizes->sample_size);\n\n            cr->offset += cr->size * cr->chunk_count;\n\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui seek size fix=%uz\",\n                           t->id, cr->size);\n\n            return NGX_OK;\n        }\n\n        if (cr->pos >= ngx_rtmp_r32(t->sizes->sample_count)) {\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui seek size[%ui/%uD] overflow\",\n                           t->id, cr->pos,\n                           ngx_rtmp_r32(t->sizes->sample_count));\n\n            return NGX_ERROR;\n        }\n\n        for (pos = 1; pos <= cr->chunk_count; ++pos) {\n            cr->offset += ngx_rtmp_r32(t->sizes->entries[cr->pos - pos]);\n        }\n\n        cr->size_pos = cr->pos;\n        cr->size = ngx_rtmp_r32(t->sizes->entries[cr->size_pos]);\n\n        ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui seek size[%ui/%uD]=%uz\",\n                       t->id, cr->size_pos,\n                       ngx_rtmp_r32(t->sizes->sample_count),\n                       cr->size);\n\n        return NGX_OK;\n    }\n\n    if (t->sizes2) {\n        if (cr->size_pos >= ngx_rtmp_r32(t->sizes2->sample_count)) {\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui seek size2[%ui/%uD] overflow\",\n                           t->id, cr->size_pos,\n                           ngx_rtmp_r32(t->sizes->sample_count));\n\n            return NGX_ERROR;\n        }\n\n        cr->size_pos = cr->pos;\n\n        /* TODO */\n        return NGX_OK;\n    }\n\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_next_key(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t          *cr;\n    uint32_t                       *ke;\n\n    cr = &t->cursor;\n\n    if (t->keys == NULL) {\n        return NGX_OK;\n    }\n\n    if (cr->key) {\n        cr->key_pos++;\n    }\n\n    if (cr->key_pos >= ngx_rtmp_r32(t->keys->entry_count)) {\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"mp4: track#%ui key[%ui/%uD] overflow\",\n                t->id, cr->key_pos,\n                ngx_rtmp_r32(t->keys->entry_count));\n\n        cr->key = 0;\n\n        return NGX_OK;\n    }\n\n    ke = &t->keys->entries[cr->key_pos];\n    cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke));\n\n    ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: track#%ui key[%ui/%uD][%ui/%uD]=%s\",\n                   t->id, cr->key_pos,\n                   ngx_rtmp_r32(t->keys->entry_count),\n                   cr->pos, ngx_rtmp_r32(*ke),\n                   cr->key ? \"match\" : \"miss\");\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_seek_key(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t      *cr;\n    uint32_t                   *ke;\n    ngx_int_t                   dpos;\n\n    cr = &t->cursor;\n\n    if (t->keys == NULL) {\n        return NGX_OK;\n    }\n\n    while (cr->key_pos < ngx_rtmp_r32(t->keys->entry_count)) {\n        if (ngx_rtmp_r32(t->keys->entries[cr->key_pos]) > cr->pos) {\n            break;\n        }\n\n        cr->key_pos++;\n    }\n\n    if (cr->key_pos >= ngx_rtmp_r32(t->keys->entry_count)) {\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"mp4: track#%ui seek key[%ui/%uD] overflow\",\n                t->id, cr->key_pos,\n                ngx_rtmp_r32(t->keys->entry_count));\n        return NGX_OK;\n    }\n\n    ke = &t->keys->entries[cr->key_pos];\n    /*cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke));*/\n\n    /* distance to the next keyframe */\n    dpos = ngx_rtmp_r32(*ke) - cr->pos - 1;\n    cr->key = 1;\n\n    /* TODO: range version needed */\n    for (; dpos > 0; --dpos) {\n        ngx_rtmp_mp4_next_time(s, t);\n    }\n\n/*    cr->key = (cr->pos + 1 == ngx_rtmp_r32(*ke));*/\n\n    ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: track#%ui seek key[%ui/%uD][%ui/%uD]=%s\",\n                   t->id, cr->key_pos,\n                   ngx_rtmp_r32(t->keys->entry_count),\n                   cr->pos, ngx_rtmp_r32(*ke),\n                   cr->key ? \"match\" : \"miss\");\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_next_delay(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t          *cr;\n    ngx_rtmp_mp4_delay_entry_t     *de;\n\n    cr = &t->cursor;\n\n    if (t->delays == NULL) {\n        return NGX_OK;\n    }\n\n    if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) {\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"mp4: track#%ui delay[%ui/%uD] overflow\",\n                t->id, cr->delay_pos,\n                ngx_rtmp_r32(t->delays->entry_count));\n\n        return NGX_OK;\n    }\n\n    cr->delay_count++;\n    de = &t->delays->entries[cr->delay_pos];\n\n    if (cr->delay_count >= ngx_rtmp_r32(de->sample_count)) {\n        cr->delay_pos++;\n        de++;\n        cr->delay_count = 0;\n    }\n\n    if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) {\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"mp4: track#%ui delay[%ui/%uD] overflow\",\n                t->id, cr->delay_pos,\n                ngx_rtmp_r32(t->delays->entry_count));\n\n        return NGX_OK;\n    }\n\n    cr->delay = ngx_rtmp_r32(de->sample_offset);\n\n    ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: track#%ui delay[%ui/%uD][%ui/%uD]=%ui\",\n                   t->id, cr->delay_pos,\n                   ngx_rtmp_r32(t->delays->entry_count),\n                   cr->delay_count,\n                   ngx_rtmp_r32(de->sample_count), cr->delay);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_seek_delay(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    ngx_rtmp_mp4_cursor_t      *cr;\n    ngx_rtmp_mp4_delay_entry_t *de;\n    uint32_t                    pos, dpos;\n\n    cr = &t->cursor;\n\n    if (t->delays == NULL) {\n        return NGX_OK;\n    }\n\n    pos = 0;\n    de = t->delays->entries;\n\n    while (cr->delay_pos < ngx_rtmp_r32(t->delays->entry_count)) {\n        dpos = ngx_rtmp_r32(de->sample_count);\n\n        if (pos + dpos > cr->pos) {\n            cr->delay_count = cr->pos - pos;\n            cr->delay = ngx_rtmp_r32(de->sample_offset);\n            break;\n        }\n\n        cr->delay_pos++;\n        pos += dpos;\n        de++;\n    }\n\n    if (cr->delay_pos >= ngx_rtmp_r32(t->delays->entry_count)) {\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"mp4: track#%ui seek delay[%ui/%uD] overflow\",\n                t->id, cr->delay_pos,\n                ngx_rtmp_r32(t->delays->entry_count));\n\n        return NGX_OK;\n    }\n\n    ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: track#%ui seek delay[%ui/%uD][%ui/%uD]=%ui\",\n                   t->id, cr->delay_pos,\n                   ngx_rtmp_r32(t->delays->entry_count),\n                   cr->delay_count,\n                   ngx_rtmp_r32(de->sample_count), cr->delay);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_next(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t)\n{\n    if (ngx_rtmp_mp4_next_time(s, t)  != NGX_OK ||\n        ngx_rtmp_mp4_next_key(s, t)   != NGX_OK ||\n        ngx_rtmp_mp4_next_chunk(s, t) != NGX_OK ||\n        ngx_rtmp_mp4_next_size(s, t)  != NGX_OK ||\n        ngx_rtmp_mp4_next_delay(s, t) != NGX_OK)\n    {\n        t->cursor.valid = 0;\n        return NGX_ERROR;\n    }\n\n    t->cursor.valid = 1;\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_send_meta(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_mp4_ctx_t             *ctx;\n    ngx_rtmp_core_srv_conf_t       *cscf;\n    ngx_int_t                       rc;\n    ngx_uint_t                      n;\n    ngx_rtmp_header_t               h;\n    ngx_chain_t                    *out;\n    ngx_rtmp_mp4_track_t           *t;\n    double                          d;\n\n    static struct {\n        double                      width;\n        double                      height;\n        double                      duration;\n        double                      video_codec_id;\n        double                      audio_codec_id;\n        double                      audio_sample_rate;\n    }                               v;\n\n    static ngx_rtmp_amf_elt_t       out_inf[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"width\"),\n          &v.width, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"height\"),\n          &v.height, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"displayWidth\"),\n          &v.width, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"displayHeight\"),\n          &v.height, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"duration\"),\n          &v.duration, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"videocodecid\"),\n          &v.video_codec_id, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"audiocodecid\"),\n          &v.audio_codec_id, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"audiosamplerate\"),\n          &v.audio_sample_rate, 0 },\n    };\n\n    static ngx_rtmp_amf_elt_t       out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"onMetaData\", 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          out_inf, sizeof(out_inf) },\n    };\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    ngx_memzero(&v, sizeof(v));\n\n    v.width  = ctx->width;\n    v.height = ctx->height;\n    v.audio_sample_rate = ctx->sample_rate;\n\n    t = &ctx->tracks[0];\n    for (n = 0; n < ctx->ntracks; ++n, ++t) {\n        d = ngx_rtmp_mp4_to_rtmp_timestamp(t, t->duration) / 1000.;\n\n        if (v.duration < d) {\n            v.duration = d;\n        }\n\n        switch (t->type) {\n            case NGX_RTMP_MSG_AUDIO:\n                v.audio_codec_id = t->codec;\n                break;\n            case NGX_RTMP_MSG_VIDEO:\n                v.video_codec_id = t->codec;\n                break;\n        }\n    }\n\n    out = NULL;\n    rc = ngx_rtmp_append_amf(s, &out, NULL, out_elts,\n                             sizeof(out_elts) / sizeof(out_elts[0]));\n    if (rc != NGX_OK || out == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_memzero(&h, sizeof(h));\n\n    h.csid = NGX_RTMP_CSID_AMF;\n    h.msid = NGX_RTMP_MSID;\n    h.type = NGX_RTMP_MSG_AMF_META;\n\n    ngx_rtmp_prepare_message(s, &h, NULL, out);\n    rc = ngx_rtmp_send_message(s, out, 0);\n    ngx_rtmp_free_shared_chain(cscf, out);\n\n    return rc;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_seek_track(ngx_rtmp_session_t *s, ngx_rtmp_mp4_track_t *t,\n                        ngx_int_t timestamp)\n{\n    ngx_rtmp_mp4_cursor_t          *cr;\n\n    cr = &t->cursor;\n    ngx_memzero(cr, sizeof(*cr));\n\n    if (ngx_rtmp_mp4_seek_time(s, t, ngx_rtmp_mp4_from_rtmp_timestamp(\n                          t, timestamp)) != NGX_OK ||\n        ngx_rtmp_mp4_seek_key(s, t)   != NGX_OK ||\n        ngx_rtmp_mp4_seek_chunk(s, t) != NGX_OK ||\n        ngx_rtmp_mp4_seek_size(s, t)  != NGX_OK ||\n        ngx_rtmp_mp4_seek_delay(s, t) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    cr->valid = 1;\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_send(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t *ts)\n{\n    ngx_rtmp_mp4_ctx_t             *ctx;\n    ngx_buf_t                       in_buf;\n    ngx_rtmp_header_t               h, lh;\n    ngx_rtmp_core_srv_conf_t       *cscf;\n    ngx_chain_t                    *out, in;\n    ngx_rtmp_mp4_track_t           *t, *cur_t;\n    ngx_rtmp_mp4_cursor_t          *cr, *cur_cr;\n    uint32_t                        buflen, end_timestamp,\n                                    timestamp, last_timestamp, rdelay,\n                                    cur_timestamp;\n    ssize_t                         ret;\n    u_char                          fhdr[5];\n    size_t                          fhdr_size;\n    ngx_int_t                       rc;\n    ngx_uint_t                      n, counter;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    ctx  = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (!ctx->meta_sent) {\n        rc = ngx_rtmp_mp4_send_meta(s);\n\n        if (rc == NGX_OK) {\n            ctx->meta_sent = 1;\n        }\n\n        return rc;\n    }\n\n    buflen = s->buflen + NGX_RTMP_MP4_BUFLEN_ADDON;\n\n    counter = 0;\n    last_timestamp = 0;\n    end_timestamp = ctx->start_timestamp +\n                    (ngx_current_msec - ctx->epoch) + buflen;\n\n    for ( ;; ) {\n        counter++;\n        if (counter > NGX_RTMP_MP4_MAX_FRAMES) {\n            return NGX_OK;\n        }\n\n        timestamp = 0;\n        t = NULL;\n\n        for (n = 0; n < ctx->ntracks; n++) {\n            cur_t = &ctx->tracks[n];\n            cur_cr = &cur_t->cursor;\n\n            if (!cur_cr->valid) {\n                continue;\n            }\n\n            cur_timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(cur_t,\n                                                           cur_cr->timestamp);\n\n            if (t == NULL || cur_timestamp < timestamp) {\n                timestamp = cur_timestamp;\n                t = cur_t;\n            }\n        }\n\n        if (t == NULL) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                    \"mp4: no track\");\n            return NGX_DONE;\n        }\n\n        if (timestamp > end_timestamp) {\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                    \"mp4: track#%ui ahead %uD > %uD\",\n                    t->id, timestamp, end_timestamp);\n\n            if (ts) {\n                *ts = last_timestamp;\n            }\n\n            return (uint32_t) (timestamp - end_timestamp);\n        }\n\n        cr = &t->cursor;\n\n        last_timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(t, cr->last_timestamp);\n\n        ngx_memzero(&h, sizeof(h));\n\n        h.msid = NGX_RTMP_MSID;\n        h.type = (uint8_t) t->type;\n        h.csid = t->csid;\n\n        lh = h;\n\n        h.timestamp  = timestamp;\n        lh.timestamp = last_timestamp;\n\n        ngx_memzero(&in, sizeof(in));\n        ngx_memzero(&in_buf, sizeof(in_buf));\n\n        if (t->header && !t->header_sent) {\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: track#%ui sending header of size=%uz\",\n                           t->id, t->header_size);\n\n            fhdr[0] = t->fhdr;\n            fhdr[1] = 0;\n\n            if (t->type == NGX_RTMP_MSG_VIDEO) {\n                fhdr[0] |= 0x10;\n                fhdr[2] = fhdr[3] = fhdr[4] = 0;\n                fhdr_size = 5;\n            } else {\n                fhdr_size = 2;\n            }\n\n            in.buf = &in_buf;\n            in_buf.pos  = fhdr;\n            in_buf.last = fhdr + fhdr_size;\n\n            out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);\n\n            in.buf = &in_buf;\n            in_buf.pos  = t->header;\n            in_buf.last = t->header + t->header_size;\n\n            ngx_rtmp_append_shared_bufs(cscf, out, &in);\n\n            ngx_rtmp_prepare_message(s, &h, NULL, out);\n            rc = ngx_rtmp_send_message(s, out, 0);\n            ngx_rtmp_free_shared_chain(cscf, out);\n\n            if (rc == NGX_AGAIN) {\n                return NGX_AGAIN;\n            }\n\n            t->header_sent = 1;\n        }\n\n        ngx_log_debug5(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui read frame offset=%O, size=%uz, \"\n                       \"timestamp=%uD, last_timestamp=%uD\",\n                       t->id, cr->offset, cr->size, timestamp,\n                       last_timestamp);\n\n        ngx_rtmp_mp4_buffer[0] = t->fhdr;\n        fhdr_size = 1;\n\n        if (t->type == NGX_RTMP_MSG_VIDEO) {\n            if (cr->key) {\n                ngx_rtmp_mp4_buffer[0] |= 0x10;\n            } else if (cr->delay) {\n                ngx_rtmp_mp4_buffer[0] |= 0x20;\n            } else {\n                ngx_rtmp_mp4_buffer[0] |= 0x30;\n            }\n\n            if (t->header) {\n                fhdr_size = 5;\n\n                rdelay = ngx_rtmp_mp4_to_rtmp_timestamp(t, cr->delay);\n\n                ngx_rtmp_mp4_buffer[1] = 1;\n                ngx_rtmp_mp4_buffer[2] = (rdelay >> 16) & 0xff;\n                ngx_rtmp_mp4_buffer[3] = (rdelay >> 8)  & 0xff;\n                ngx_rtmp_mp4_buffer[4] = rdelay & 0xff;\n            }\n\n        } else { /* NGX_RTMP_MSG_AUDIO */\n            if (t->header) {\n                fhdr_size = 2;\n                ngx_rtmp_mp4_buffer[1] = 1;\n            }\n        }\n\n        if (cr->size + fhdr_size > sizeof(ngx_rtmp_mp4_buffer)) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"mp4: track#%ui too big frame: %D>%uz\",\n                          t->id, cr->size, sizeof(ngx_rtmp_mp4_buffer));\n            goto next;\n        }\n\n        ret = ngx_read_file(f, ngx_rtmp_mp4_buffer + fhdr_size,\n                            cr->size, cr->offset);\n\n        if (ret != (ssize_t) cr->size) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                          \"mp4: track#%ui could not read frame\", t->id);\n            goto next;\n        }\n\n        in.buf = &in_buf;\n        in_buf.pos  = ngx_rtmp_mp4_buffer;\n        in_buf.last = ngx_rtmp_mp4_buffer + cr->size + fhdr_size;\n\n        out = ngx_rtmp_append_shared_bufs(cscf, NULL, &in);\n\n        ngx_rtmp_prepare_message(s, &h, cr->not_first ? &lh : NULL, out);\n        rc = ngx_rtmp_send_message(s, out, 0);\n        ngx_rtmp_free_shared_chain(cscf, out);\n\n        if (rc == NGX_AGAIN) {\n            return NGX_AGAIN;\n        }\n\n        s->current_time = timestamp;\n\nnext:\n        if (ngx_rtmp_mp4_next(s, t) != NGX_OK) {\n            return NGX_DONE;\n        }\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_init(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_int_t aindex,\n                  ngx_int_t vindex)\n{\n    ngx_rtmp_mp4_ctx_t         *ctx;\n    uint32_t                    hdr[2];\n    ssize_t                     n;\n    size_t                      offset, page_offset, size, shift;\n    uint64_t                    extended_size;\n    ngx_file_info_t             fi;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx == NULL) {\n        ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_mp4_ctx_t));\n\n        if (ctx == NULL) {\n            return NGX_ERROR;\n        }\n\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_mp4_module);\n    }\n\n    ngx_memzero(ctx, sizeof(*ctx));\n\n    ctx->aindex = aindex;\n    ctx->vindex = vindex;\n\n    offset = 0;\n    size   = 0;\n\n    for ( ;; ) {\n        n = ngx_read_file(f, (u_char *) &hdr, sizeof(hdr), offset);\n\n        if (n != sizeof(hdr)) {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                          \"mp4: error reading file at offset=%uz \"\n                          \"while searching for moov box\", offset);\n            return NGX_ERROR;\n        }\n\n        size = (size_t) ngx_rtmp_r32(hdr[0]);\n        shift = sizeof(hdr);\n\n        if (size == 1) {\n            n = ngx_read_file(f, (u_char *) &extended_size,\n                              sizeof(extended_size), offset + sizeof(hdr));\n\n            if (n != sizeof(extended_size)) {\n                ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                              \"mp4: error reading file at offset=%uz \"\n                              \"while searching for moov box\", offset + 8);\n                return NGX_ERROR;\n            }\n\n            size = (size_t) ngx_rtmp_r64(extended_size);\n            shift += sizeof(extended_size);\n\n        } else if (size == 0) {\n            if (ngx_fd_info(f->fd, &fi) == NGX_FILE_ERROR) {\n                ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                              \"mp4: \" ngx_fd_info_n \" failed\");\n                return NGX_ERROR;\n            }\n            size = ngx_file_size(&fi) - offset;\n        }\n\n        if (hdr[1] == ngx_rtmp_mp4_make_tag('m','o','o','v')) {\n            ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"mp4: found moov box\");\n            break;\n        }\n\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: skipping box '%*s'\", 4, hdr + 1);\n\n        offset += size;\n    }\n\n    if (size < shift) {\n        return NGX_ERROR;\n    }\n\n    size   -= shift;\n    offset += shift;\n\n    page_offset = offset & (ngx_pagesize - 1);\n    ctx->mmaped_size = page_offset + size;\n\n    ctx->mmaped = ngx_rtmp_mp4_mmap(f->fd, ctx->mmaped_size,\n                                    offset - page_offset, &ctx->extra);\n    if (ctx->mmaped == NULL) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"mp4: mmap failed at offset=%ui, size=%uz\",\n                      offset, size);\n        return NGX_ERROR;\n    }\n\n    return ngx_rtmp_mp4_parse(s, (u_char *) ctx->mmaped + page_offset,\n                                 (u_char *) ctx->mmaped + page_offset + size);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_done(ngx_rtmp_session_t *s, ngx_file_t *f)\n{\n    ngx_rtmp_mp4_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx == NULL || ctx->mmaped == NULL) {\n        return NGX_OK;\n    }\n\n    if (ngx_rtmp_mp4_munmap(ctx->mmaped, ctx->mmaped_size, &ctx->extra)\n        != NGX_OK)\n    {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                      \"mp4: munmap failed\");\n        return NGX_ERROR;\n    }\n\n    ctx->mmaped = NULL;\n    ctx->mmaped_size = 0;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_seek(ngx_rtmp_session_t *s, ngx_file_t *f, ngx_uint_t timestamp)\n{\n    ngx_rtmp_mp4_ctx_t     *ctx;\n    ngx_rtmp_mp4_track_t   *t;\n    ngx_uint_t              n;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: seek timestamp=%ui\", timestamp);\n\n    for (n = 0; n < ctx->ntracks; ++n) {\n        t = &ctx->tracks[n];\n\n        if (t->type != NGX_RTMP_MSG_VIDEO) {\n            continue;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui seek video\", n);\n\n        ngx_rtmp_mp4_seek_track(s, t, timestamp);\n\n        timestamp = ngx_rtmp_mp4_to_rtmp_timestamp(t, t->cursor.timestamp);\n\n        break;\n    }\n\n    for (n = 0; n < ctx->ntracks; ++n) {\n        t = &ctx->tracks[n];\n\n        if (t->type == NGX_RTMP_MSG_VIDEO) {\n            continue;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"mp4: track#%ui seek\", n);\n\n        ngx_rtmp_mp4_seek_track(s, &ctx->tracks[n], timestamp);\n    }\n\n    ctx->start_timestamp = timestamp;\n    ctx->epoch = ngx_current_msec;\n\n    return ngx_rtmp_mp4_reset(s);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_start(ngx_rtmp_session_t *s, ngx_file_t *f)\n{\n    ngx_rtmp_mp4_ctx_t     *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: start timestamp=%uD\", ctx->start_timestamp);\n\n    ctx->epoch = ngx_current_msec;\n\n    return NGX_OK;/*ngx_rtmp_mp4_reset(s);*/\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_reset(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_mp4_ctx_t     *ctx;\n    ngx_rtmp_mp4_cursor_t  *cr;\n    ngx_rtmp_mp4_track_t   *t;\n    ngx_uint_t              n;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    t = &ctx->tracks[0];\n    for (n = 0; n < ctx->ntracks; ++n, ++t) {\n        cr = &t->cursor;\n        cr->not_first = 0;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_stop(ngx_rtmp_session_t *s, ngx_file_t *f)\n{\n    ngx_rtmp_mp4_ctx_t     *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_mp4_module);\n\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    ctx->start_timestamp += (ngx_current_msec - ctx->epoch);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"mp4: stop timestamp=%uD\", ctx->start_timestamp);\n\n    return NGX_OK;/*ngx_rtmp_mp4_reset(s);*/\n}\n\n\nstatic ngx_int_t\nngx_rtmp_mp4_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_play_main_conf_t      *pmcf;\n    ngx_rtmp_play_fmt_t           **pfmt, *fmt;\n\n    pmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_play_module);\n\n    pfmt = ngx_array_push(&pmcf->fmts);\n\n    if (pfmt == NULL) {\n        return NGX_ERROR;\n    }\n\n    fmt = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_fmt_t));\n\n    if (fmt == NULL) {\n        return NGX_ERROR;\n    }\n\n    *pfmt = fmt;\n\n    ngx_str_set(&fmt->name, \"mp4-format\");\n\n    ngx_str_set(&fmt->pfx, \"mp4:\");\n    ngx_str_set(&fmt->sfx, \".mp4\");\n\n    fmt->init  = ngx_rtmp_mp4_init;\n    fmt->done  = ngx_rtmp_mp4_done;\n    fmt->seek  = ngx_rtmp_mp4_seek;\n    fmt->start = ngx_rtmp_mp4_start;\n    fmt->stop  = ngx_rtmp_mp4_stop;\n    fmt->send  = ngx_rtmp_mp4_send;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_netcall_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_netcall_module.h\"\n\n\nstatic ngx_int_t ngx_rtmp_netcall_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_netcall_create_srv_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_netcall_merge_srv_conf(ngx_conf_t *cf,\n       void *parent, void *child);\n\nstatic void ngx_rtmp_netcall_close(ngx_connection_t *cc);\nstatic void ngx_rtmp_netcall_detach(ngx_connection_t *cc);\n\nstatic void ngx_rtmp_netcall_recv(ngx_event_t *rev);\nstatic void ngx_rtmp_netcall_send(ngx_event_t *wev);\n\n\ntypedef struct {\n    ngx_msec_t                                  timeout;\n    size_t                                      bufsize;\n    ngx_log_t                                  *log;\n} ngx_rtmp_netcall_srv_conf_t;\n\n\ntypedef struct ngx_rtmp_netcall_session_s {\n    ngx_rtmp_session_t                         *session;\n    ngx_peer_connection_t                      *pc;\n    ngx_url_t                                  *url;\n    struct ngx_rtmp_netcall_session_s          *next;\n    void                                       *arg;\n    ngx_rtmp_netcall_handle_pt                  handle;\n    ngx_rtmp_netcall_filter_pt                  filter;\n    ngx_rtmp_netcall_sink_pt                    sink;\n    ngx_chain_t                                *in;\n    ngx_chain_t                                *inlast;\n    ngx_chain_t                                *out;\n    ngx_msec_t                                  timeout;\n    unsigned                                    detached:1;\n    size_t                                      bufsize;\n} ngx_rtmp_netcall_session_t;\n\n\ntypedef struct {\n    ngx_rtmp_netcall_session_t                 *cs;\n} ngx_rtmp_netcall_ctx_t;\n\n\nstatic ngx_command_t  ngx_rtmp_netcall_commands[] = {\n\n    { ngx_string(\"netcall_timeout\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_netcall_srv_conf_t, timeout),\n      NULL },\n\n    { ngx_string(\"netcall_buffer\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_size_slot,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      offsetof(ngx_rtmp_netcall_srv_conf_t, bufsize),\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_netcall_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_netcall_postconfiguration,     /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    ngx_rtmp_netcall_create_srv_conf,       /* create server configuration */\n    ngx_rtmp_netcall_merge_srv_conf,        /* merge server configuration */\n    NULL,                                   /* create app configuration */\n    NULL                                    /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_netcall_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_netcall_module_ctx,           /* module context */\n    ngx_rtmp_netcall_commands,              /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void *\nngx_rtmp_netcall_create_srv_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_netcall_srv_conf_t     *nscf;\n\n    nscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_netcall_srv_conf_t));\n    if (nscf == NULL) {\n        return NULL;\n    }\n\n    nscf->timeout = NGX_CONF_UNSET_MSEC;\n    nscf->bufsize = NGX_CONF_UNSET_SIZE;\n\n    nscf->log = &cf->cycle->new_log;\n\n    return nscf;\n}\n\n\nstatic char *\nngx_rtmp_netcall_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_netcall_srv_conf_t *prev = parent;\n    ngx_rtmp_netcall_srv_conf_t *conf = child;\n\n    ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 10000);\n    ngx_conf_merge_size_value(conf->bufsize, prev->bufsize, 1024);\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_netcall_disconnect(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_netcall_ctx_t         *ctx;\n    ngx_rtmp_netcall_session_t     *cs;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);\n\n    if (ctx) {\n        for (cs = ctx->cs; cs; cs = cs->next) {\n            ngx_rtmp_netcall_detach(cs->pc->connection);\n        }\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_netcall_get_peer(ngx_peer_connection_t *pc, void *data)\n{\n    ngx_rtmp_netcall_session_t   *cs = data;\n\n    pc->sockaddr =(struct sockaddr *)&cs->url->sockaddr;\n    pc->socklen = cs->url->socklen;\n    pc->name = &cs->url->host;\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_netcall_free_peer(ngx_peer_connection_t *pc, void *data,\n            ngx_uint_t state)\n{\n}\n\n\nngx_int_t\nngx_rtmp_netcall_create(ngx_rtmp_session_t *s, ngx_rtmp_netcall_init_t *ci)\n{\n    ngx_rtmp_netcall_ctx_t         *ctx;\n    ngx_peer_connection_t          *pc;\n    ngx_rtmp_netcall_session_t     *cs;\n    ngx_rtmp_netcall_srv_conf_t    *nscf;\n    ngx_connection_t               *c, *cc;\n    ngx_pool_t                     *pool;\n    ngx_int_t                       rc;\n\n    pool = NULL;\n    c = s->connection;\n\n    nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_netcall_module);\n    if (nscf == NULL) {\n        goto error;\n    }\n\n    /* get module context */\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);\n    if (ctx == NULL) {\n        ctx = ngx_pcalloc(c->pool,\n                sizeof(ngx_rtmp_netcall_ctx_t));\n        if (ctx == NULL) {\n            return NGX_ERROR;\n        }\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_netcall_module);\n    }\n\n    /* Create netcall pool, connection, session.\n     * Note we use shared (app-wide) log because\n     * s->connection->log might be unavailable\n     * in detached netcall when it's being closed */\n    pool = ngx_create_pool(4096, nscf->log);\n    if (pool == NULL) {\n        goto error;\n    }\n\n    pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t));\n    if (pc == NULL) {\n        goto error;\n    }\n\n    cs = ngx_pcalloc(pool, sizeof(ngx_rtmp_netcall_session_t));\n    if (cs == NULL) {\n        goto error;\n    }\n\n    /* copy arg to connection pool */\n    if (ci->argsize) {\n        cs->arg = ngx_pcalloc(pool, ci->argsize);\n        if (cs->arg == NULL) {\n            goto error;\n        }\n        ngx_memcpy(cs->arg, ci->arg, ci->argsize);\n    }\n\n    cs->timeout = nscf->timeout;\n    cs->bufsize = nscf->bufsize;\n    cs->url = ci->url;\n    cs->session = s;\n    cs->filter = ci->filter;\n    cs->sink = ci->sink;\n    cs->handle = ci->handle;\n    if (cs->handle == NULL) {\n        cs->detached = 1;\n    }\n\n    pc->log = nscf->log;\n    pc->get = ngx_rtmp_netcall_get_peer;\n    pc->free = ngx_rtmp_netcall_free_peer;\n    pc->data = cs;\n\n    /* connect */\n    rc = ngx_event_connect_peer(pc);\n    if (rc != NGX_OK && rc != NGX_AGAIN ) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"netcall: connection failed\");\n        goto error;\n    }\n\n    cc = pc->connection;\n    cc->data = cs;\n    cc->pool = pool;\n    cs->pc = pc;\n\n    cs->out = ci->create(s, ci->arg, pool);\n    if (cs->out == NULL) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"netcall: creation failed\");\n        ngx_close_connection(pc->connection);\n        goto error;\n    }\n\n    cc->write->handler = ngx_rtmp_netcall_send;\n    cc->read->handler = ngx_rtmp_netcall_recv;\n\n    if (!cs->detached) {\n        cs->next = ctx->cs;\n        ctx->cs = cs;\n    }\n\n    ngx_rtmp_netcall_send(cc->write);\n\n    return c->destroyed ? NGX_ERROR : NGX_OK;\n\nerror:\n    if (pool) {\n        ngx_destroy_pool(pool);\n    }\n\n    return NGX_ERROR;\n}\n\n\nstatic void\nngx_rtmp_netcall_close(ngx_connection_t *cc)\n{\n    ngx_rtmp_netcall_session_t         *cs, **css;\n    ngx_pool_t                         *pool;\n    ngx_rtmp_session_t                 *s;\n    ngx_rtmp_netcall_ctx_t             *ctx;\n    ngx_buf_t                          *b;\n\n    cs = cc->data;\n\n    if (cc->destroyed) {\n        return;\n    }\n\n    cc->destroyed = 1;\n\n    if (!cs->detached) {\n        s = cs->session;\n        ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_netcall_module);\n\n        if (cs->in && cs->sink) {\n            cs->sink(cs->session, cs->in);\n\n            b = cs->in->buf;\n            b->pos = b->last = b->start;\n\n        }\n\n        for(css = &ctx->cs; *css; css = &((*css)->next)) {\n            if (*css == cs) {\n                *css = cs->next;\n                break;\n            }\n        }\n\n        if (cs->handle && cs->handle(s, cs->arg, cs->in) != NGX_OK) {\n            ngx_rtmp_finalize_session(s);\n        }\n    }\n\n    pool = cc->pool;\n    ngx_close_connection(cc);\n    ngx_destroy_pool(pool);\n}\n\n\nstatic void\nngx_rtmp_netcall_detach(ngx_connection_t *cc)\n{\n    ngx_rtmp_netcall_session_t         *cs;\n\n    cs = cc->data;\n    cs->detached = 1;\n}\n\n\nstatic void\nngx_rtmp_netcall_recv(ngx_event_t *rev)\n{\n    ngx_rtmp_netcall_session_t         *cs;\n    ngx_connection_t                   *cc;\n    ngx_chain_t                        *cl;\n    ngx_int_t                           n;\n    ngx_buf_t                          *b;\n\n    cc = rev->data;\n    cs = cc->data;\n\n    if (cc->destroyed) {\n        return;\n    }\n\n    if (rev->timedout) {\n        cc->timedout = 1;\n        ngx_rtmp_netcall_close(cc);\n        return;\n    }\n\n    if (rev->timer_set) {\n        ngx_del_timer(rev);\n    }\n\n    for ( ;; ) {\n\n        if (cs->inlast == NULL ||\n            cs->inlast->buf->last == cs->inlast->buf->end)\n        {\n            if (cs->in && cs->sink) {\n                if (!cs->detached) {\n                    if (cs->sink(cs->session, cs->in) != NGX_OK) {\n                        ngx_rtmp_netcall_close(cc);\n                        return;\n                    }\n                }\n\n                b = cs->in->buf;\n                b->pos = b->last = b->start;\n\n            } else {\n                cl = ngx_alloc_chain_link(cc->pool);\n                if (cl == NULL) {\n                    ngx_rtmp_netcall_close(cc);\n                    return;\n                }\n\n                cl->next = NULL;\n\n                cl->buf = ngx_create_temp_buf(cc->pool, cs->bufsize);\n                if (cl->buf == NULL) {\n                    ngx_rtmp_netcall_close(cc);\n                    return;\n                }\n\n                if (cs->in == NULL) {\n                    cs->in = cl;\n                } else {\n                    cs->inlast->next = cl;\n                }\n\n                cs->inlast = cl;\n            }\n        }\n\n        b = cs->inlast->buf;\n\n        n = cc->recv(cc, b->last, b->end - b->last);\n\n        if (n == NGX_ERROR || n == 0) {\n            ngx_rtmp_netcall_close(cc);\n            return;\n        }\n\n        if (n == NGX_AGAIN) {\n            if (cs->filter && cs->in\n                && cs->filter(cs->in) != NGX_AGAIN)\n            {\n                ngx_rtmp_netcall_close(cc);\n                return;\n            }\n\n            ngx_add_timer(rev, cs->timeout);\n            if (ngx_handle_read_event(rev, 0) != NGX_OK) {\n                ngx_rtmp_netcall_close(cc);\n            }\n            return;\n        }\n\n        b->last += n;\n    }\n}\n\n\nstatic void\nngx_rtmp_netcall_send(ngx_event_t *wev)\n{\n    ngx_rtmp_netcall_session_t         *cs;\n    ngx_connection_t                   *cc;\n    ngx_chain_t                        *cl;\n\n    cc = wev->data;\n    cs = cc->data;\n\n    if (cc->destroyed) {\n        return;\n    }\n\n    if (wev->timedout) {\n        ngx_log_error(NGX_LOG_INFO, cc->log, NGX_ETIMEDOUT,\n                \"netcall: client send timed out\");\n        cc->timedout = 1;\n        ngx_rtmp_netcall_close(cc);\n        return;\n    }\n\n    if (wev->timer_set) {\n        ngx_del_timer(wev);\n    }\n\n    cl = cc->send_chain(cc, cs->out, 0);\n\n    if (cl == NGX_CHAIN_ERROR) {\n        ngx_rtmp_netcall_close(cc);\n        return;\n    }\n\n    cs->out = cl;\n\n    /* more data to send? */\n    if (cl) {\n        ngx_add_timer(wev, cs->timeout);\n        if (ngx_handle_write_event(wev, 0) != NGX_OK) {\n            ngx_rtmp_netcall_close(cc);\n        }\n        return;\n    }\n\n    /* we've sent everything we had.\n     * now receive reply */\n    ngx_del_event(wev, NGX_WRITE_EVENT, 0);\n\n    ngx_rtmp_netcall_recv(cc->read);\n}\n\n\nngx_chain_t *\nngx_rtmp_netcall_http_format_request(ngx_int_t method, ngx_str_t *host,\n                                     ngx_str_t *uri, ngx_chain_t *args,\n                                     ngx_chain_t *body, ngx_pool_t *pool,\n                                     ngx_str_t *content_type)\n{\n    ngx_chain_t                    *al, *bl, *ret;\n    ngx_buf_t                      *b;\n    size_t                          content_length;\n    static const char              *methods[2] = { \"GET\", \"POST\" };\n    static const char               rq_tmpl[] = \" HTTP/1.0\\r\\n\"\n                                                \"Host: %V\\r\\n\"\n                                                \"Content-Type: %V\\r\\n\"\n                                                \"Connection: Close\\r\\n\"\n                                                \"Content-Length: %uz\\r\\n\"\n                                                \"\\r\\n\";\n\n    content_length = 0;\n    for (al = body; al; al = al->next) {\n        b = al->buf;\n        content_length += (b->last - b->pos);\n    }\n\n    /* create first buffer */\n\n    al = ngx_alloc_chain_link(pool);\n    if (al == NULL) {\n        return NULL;\n    }\n\n    b = ngx_create_temp_buf(pool, sizeof(\"POST\") + /* longest method + 1 */\n                                  uri->len);\n    if (b == NULL) {\n        return NULL;\n    }\n\n    b->last = ngx_snprintf(b->last, b->end - b->last, \"%s %V\",\n                           methods[method], uri);\n\n    al->buf = b;\n\n    ret = al;\n\n    if (args) {\n        *b->last++ = '?';\n        al->next = args;\n        for (al = args; al->next; al = al->next);\n    }\n\n    /* create second buffer */\n\n    bl = ngx_alloc_chain_link(pool);\n    if (bl == NULL) {\n        return NULL;\n    }\n\n    b = ngx_create_temp_buf(pool, sizeof(rq_tmpl) + host->len +\n                            content_type->len + NGX_SIZE_T_LEN);\n    if (b == NULL) {\n        return NULL;\n    }\n\n    bl->buf = b;\n\n    b->last = ngx_snprintf(b->last, b->end - b->last, rq_tmpl,\n                           host, content_type, content_length);\n\n    al->next = bl;\n    bl->next = body;\n\n    return ret;\n}\n\n\nngx_chain_t *\nngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s, ngx_pool_t *pool)\n{\n    ngx_chain_t                    *cl;\n    ngx_buf_t                      *b;\n    ngx_str_t                      *addr_text;\n\n    addr_text = &s->connection->addr_text;\n\n    cl = ngx_alloc_chain_link(pool);\n    if (cl == NULL) {\n        return NULL;\n    }\n\n    b = ngx_create_temp_buf(pool,\n            sizeof(\"app=\") - 1 + s->app.len * 3 +\n            sizeof(\"&flashver=\") - 1 + s->flashver.len * 3 +\n            sizeof(\"&swfurl=\") - 1 + s->swf_url.len * 3 +\n            sizeof(\"&tcurl=\") - 1 + s->tc_url.len * 3 +\n            sizeof(\"&pageurl=\") - 1 + s->page_url.len * 3 +\n            sizeof(\"&addr=\") - 1 + addr_text->len * 3 +\n            sizeof(\"&clientid=\") - 1 + NGX_INT_T_LEN\n        );\n\n    if (b == NULL) {\n        return NULL;\n    }\n\n    cl->buf = b;\n    cl->next = NULL;\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"app=\", sizeof(\"app=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, s->app.data, s->app.len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&flashver=\",\n                         sizeof(\"&flashver=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, s->flashver.data,\n                                       s->flashver.len, NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&swfurl=\",\n                         sizeof(\"&swfurl=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, s->swf_url.data,\n                                       s->swf_url.len, NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&tcurl=\",\n                         sizeof(\"&tcurl=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, s->tc_url.data,\n                                       s->tc_url.len, NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&pageurl=\",\n                         sizeof(\"&pageurl=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, s->page_url.data,\n                                       s->page_url.len, NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&addr=\", sizeof(\"&addr=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, addr_text->data,\n                                       addr_text->len, NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&clientid=\",\n                         sizeof(\"&clientid=\") - 1);\n    b->last = ngx_sprintf(b->last, \"%ui\", (ngx_uint_t) s->connection->number);\n\n    return cl;\n}\n\n\nngx_chain_t *\nngx_rtmp_netcall_http_skip_header(ngx_chain_t *in)\n{\n    ngx_buf_t       *b;\n\n    /* find \\n[\\r]\\n */\n    enum {\n        normal,\n        lf,\n        lfcr\n    } state = normal;\n\n    if (in == NULL) {\n        return NULL;\n    }\n\n    b = in->buf;\n\n    for ( ;; ) {\n\n        while (b->pos == b->last) {\n            in = in->next;\n            if (in == NULL) {\n                return NULL;\n            }\n            b = in->buf;\n        }\n\n        switch (*b->pos++) {\n            case '\\r':\n                state = (state == lf) ? lfcr : normal;\n                break;\n\n            case '\\n':\n                if (state != normal) {\n                    return in;\n                }\n                state = lf;\n                break;\n\n           default:\n                state = normal;\n        }\n    }\n}\n\n\nngx_chain_t *\nngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s, ngx_pool_t *pool,\n        ngx_str_t *key, ngx_str_t *value, ngx_uint_t flags, ngx_uint_t sec)\n{\n    ngx_chain_t                    *cl;\n    ngx_buf_t                      *b;\n\n    cl = ngx_alloc_chain_link(pool);\n    if (cl == NULL) {\n        return NULL;\n    }\n\n    b = ngx_create_temp_buf(pool, sizeof(\"set \") - 1 + key->len +\n                            (1 + NGX_INT_T_LEN) * 3 +\n                            (sizeof(\"\\r\\n\") - 1) * 2 + value->len);\n\n    if (b == NULL) {\n        return NULL;\n    }\n\n    cl->next = NULL;\n    cl->buf = b;\n\n    b->last = ngx_sprintf(b->pos, \"set %V %ui %ui %ui\\r\\n%V\\r\\n\",\n                          key, flags, sec, (ngx_uint_t) value->len, value);\n\n    return cl;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_netcall_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t          *cmcf;\n    ngx_rtmp_handler_pt                *h;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_DISCONNECT]);\n    *h = ngx_rtmp_netcall_disconnect;\n\n    return NGX_OK;\n}\n\n"
  },
  {
    "path": "ngx_rtmp_netcall_module.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_NETCALL_H_INCLUDED_\n#define _NGX_RTMP_NETCALL_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\ntypedef ngx_chain_t * (*ngx_rtmp_netcall_create_pt)(ngx_rtmp_session_t *s,\n        void *arg, ngx_pool_t *pool);\ntypedef ngx_int_t (*ngx_rtmp_netcall_filter_pt)(ngx_chain_t *in);\ntypedef ngx_int_t (*ngx_rtmp_netcall_sink_pt)(ngx_rtmp_session_t *s,\n        ngx_chain_t *in);\ntypedef ngx_int_t (*ngx_rtmp_netcall_handle_pt)(ngx_rtmp_session_t *s,\n        void *arg, ngx_chain_t *in);\n\n#define NGX_RTMP_NETCALL_HTTP_GET   0\n#define NGX_RTMP_NETCALL_HTTP_POST  1\n\n\n/* If handle is NULL then netcall is created detached\n * which means it's completely independent of RTMP\n * session and its result is never visible to anyone.\n *\n * WARNING: It's not recommended to create non-detached\n * netcalls from disconect handlers. Netcall disconnect\n * handler which detaches active netcalls is executed\n * BEFORE your handler. It leads to a crash\n * after netcall connection is closed */\ntypedef struct {\n    ngx_url_t                      *url;\n    ngx_rtmp_netcall_create_pt      create;\n    ngx_rtmp_netcall_filter_pt      filter;\n    ngx_rtmp_netcall_sink_pt        sink;\n    ngx_rtmp_netcall_handle_pt      handle;\n    void                           *arg;\n    size_t                          argsize;\n} ngx_rtmp_netcall_init_t;\n\n\nngx_int_t ngx_rtmp_netcall_create(ngx_rtmp_session_t *s,\n        ngx_rtmp_netcall_init_t *ci);\n\n\n/* HTTP handling */\nngx_chain_t * ngx_rtmp_netcall_http_format_session(ngx_rtmp_session_t *s,\n        ngx_pool_t *pool);\nngx_chain_t * ngx_rtmp_netcall_http_format_request(ngx_int_t method,\n        ngx_str_t *host, ngx_str_t *uri, ngx_chain_t *args, ngx_chain_t *body,\n        ngx_pool_t *pool, ngx_str_t *content_type);\nngx_chain_t * ngx_rtmp_netcall_http_skip_header(ngx_chain_t *in);\n\n\n/* Memcache handling */\nngx_chain_t * ngx_rtmp_netcall_memcache_set(ngx_rtmp_session_t *s,\n        ngx_pool_t *pool, ngx_str_t *key, ngx_str_t *value,\n        ngx_uint_t flags, ngx_uint_t sec);\n\n\n#endif /* _NGX_RTMP_NETCALL_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_notify_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_md5.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_netcall_module.h\"\n#include \"ngx_rtmp_record_module.h\"\n#include \"ngx_rtmp_relay_module.h\"\n\n\nstatic ngx_rtmp_connect_pt                      next_connect;\nstatic ngx_rtmp_disconnect_pt                   next_disconnect;\nstatic ngx_rtmp_publish_pt                      next_publish;\nstatic ngx_rtmp_play_pt                         next_play;\nstatic ngx_rtmp_close_stream_pt                 next_close_stream;\nstatic ngx_rtmp_record_done_pt                  next_record_done;\n\n\nstatic char *ngx_rtmp_notify_on_srv_event(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic char *ngx_rtmp_notify_on_app_event(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic char *ngx_rtmp_notify_method(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic ngx_int_t ngx_rtmp_notify_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_notify_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\nstatic void *ngx_rtmp_notify_create_srv_conf(ngx_conf_t *cf);\nstatic char *ngx_rtmp_notify_merge_srv_conf(ngx_conf_t *cf, void *parent,\n       void *child);\nstatic ngx_int_t ngx_rtmp_notify_done(ngx_rtmp_session_t *s, char *cbname,\n       ngx_uint_t url_idx);\n\n\nngx_str_t   ngx_rtmp_notify_urlencoded =\n            ngx_string(\"application/x-www-form-urlencoded\");\n\n\n#define NGX_RTMP_NOTIFY_PUBLISHING              0x01\n#define NGX_RTMP_NOTIFY_PLAYING                 0x02\n\n\nenum {\n    NGX_RTMP_NOTIFY_PLAY,\n    NGX_RTMP_NOTIFY_PUBLISH,\n    NGX_RTMP_NOTIFY_PLAY_DONE,\n    NGX_RTMP_NOTIFY_PUBLISH_DONE,\n    NGX_RTMP_NOTIFY_DONE,\n    NGX_RTMP_NOTIFY_RECORD_DONE,\n    NGX_RTMP_NOTIFY_UPDATE,\n    NGX_RTMP_NOTIFY_APP_MAX\n};\n\n\nenum {\n    NGX_RTMP_NOTIFY_CONNECT,\n    NGX_RTMP_NOTIFY_DISCONNECT,\n    NGX_RTMP_NOTIFY_SRV_MAX\n};\n\n\ntypedef struct {\n    ngx_url_t                                  *url[NGX_RTMP_NOTIFY_APP_MAX];\n    ngx_flag_t                                  active;\n    ngx_uint_t                                  method;\n    ngx_msec_t                                  update_timeout;\n    ngx_flag_t                                  update_strict;\n    ngx_flag_t                                  relay_redirect;\n} ngx_rtmp_notify_app_conf_t;\n\n\ntypedef struct {\n    ngx_url_t                                  *url[NGX_RTMP_NOTIFY_SRV_MAX];\n    ngx_uint_t                                  method;\n} ngx_rtmp_notify_srv_conf_t;\n\n\ntypedef struct {\n    ngx_uint_t                                  flags;\n    u_char                                      name[NGX_RTMP_MAX_NAME];\n    u_char                                      args[NGX_RTMP_MAX_ARGS];\n    ngx_event_t                                 update_evt;\n    time_t                                      start;\n} ngx_rtmp_notify_ctx_t;\n\n\ntypedef struct {\n    u_char                                     *cbname;\n    ngx_uint_t                                  url_idx;\n} ngx_rtmp_notify_done_t;\n\n\nstatic ngx_command_t  ngx_rtmp_notify_commands[] = {\n\n    { ngx_string(\"on_connect\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_srv_event,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"on_disconnect\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_srv_event,\n      NGX_RTMP_SRV_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"on_publish\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_app_event,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"on_play\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_app_event,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"on_publish_done\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_app_event,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"on_play_done\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_app_event,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"on_done\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_app_event,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"on_record_done\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_RTMP_REC_CONF|\n                         NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_app_event,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"on_update\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_on_app_event,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"notify_method\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_rtmp_notify_method,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"notify_update_timeout\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_notify_app_conf_t, update_timeout),\n      NULL },\n\n    { ngx_string(\"notify_update_strict\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_notify_app_conf_t, update_strict),\n      NULL },\n\n    { ngx_string(\"notify_relay_redirect\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_notify_app_conf_t, relay_redirect),\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_notify_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_notify_postconfiguration,      /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    ngx_rtmp_notify_create_srv_conf,        /* create server configuration */\n    ngx_rtmp_notify_merge_srv_conf,         /* merge server configuration */\n    ngx_rtmp_notify_create_app_conf,        /* create app configuration */\n    ngx_rtmp_notify_merge_app_conf          /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_notify_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_notify_module_ctx,            /* module context */\n    ngx_rtmp_notify_commands,               /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void *\nngx_rtmp_notify_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_notify_app_conf_t     *nacf;\n    ngx_uint_t                      n;\n\n    nacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_notify_app_conf_t));\n    if (nacf == NULL) {\n        return NULL;\n    }\n\n    for (n = 0; n < NGX_RTMP_NOTIFY_APP_MAX; ++n) {\n        nacf->url[n] = NGX_CONF_UNSET_PTR;\n    }\n\n    nacf->method = NGX_CONF_UNSET_UINT;\n    nacf->update_timeout = NGX_CONF_UNSET_MSEC;\n    nacf->update_strict = NGX_CONF_UNSET;\n    nacf->relay_redirect = NGX_CONF_UNSET;\n\n    return nacf;\n}\n\n\nstatic char *\nngx_rtmp_notify_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_notify_app_conf_t *prev = parent;\n    ngx_rtmp_notify_app_conf_t *conf = child;\n    ngx_uint_t                  n;\n\n    for (n = 0; n < NGX_RTMP_NOTIFY_APP_MAX; ++n) {\n        ngx_conf_merge_ptr_value(conf->url[n], prev->url[n], NULL);\n        if (conf->url[n]) {\n            conf->active = 1;\n        }\n    }\n\n    if (conf->active) {\n        prev->active = 1;\n    }\n\n    ngx_conf_merge_uint_value(conf->method, prev->method,\n                              NGX_RTMP_NETCALL_HTTP_POST);\n    ngx_conf_merge_msec_value(conf->update_timeout, prev->update_timeout,\n                              30000);\n    ngx_conf_merge_value(conf->update_strict, prev->update_strict, 0);\n    ngx_conf_merge_value(conf->relay_redirect, prev->relay_redirect, 0);\n\n    return NGX_CONF_OK;\n}\n\n\nstatic void *\nngx_rtmp_notify_create_srv_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_notify_srv_conf_t     *nscf;\n    ngx_uint_t                      n;\n\n    nscf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_notify_srv_conf_t));\n    if (nscf == NULL) {\n        return NULL;\n    }\n\n    for (n = 0; n < NGX_RTMP_NOTIFY_SRV_MAX; ++n) {\n        nscf->url[n] = NGX_CONF_UNSET_PTR;\n    }\n\n    nscf->method = NGX_CONF_UNSET_UINT;\n\n    return nscf;\n}\n\n\nstatic char *\nngx_rtmp_notify_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_notify_srv_conf_t *prev = parent;\n    ngx_rtmp_notify_srv_conf_t *conf = child;\n    ngx_uint_t                  n;\n\n    for (n = 0; n < NGX_RTMP_NOTIFY_SRV_MAX; ++n) {\n        ngx_conf_merge_ptr_value(conf->url[n], prev->url[n], NULL);\n    }\n\n    ngx_conf_merge_uint_value(conf->method, prev->method,\n                              NGX_RTMP_NETCALL_HTTP_POST);\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_notify_create_request(ngx_rtmp_session_t *s, ngx_pool_t *pool,\n                                   ngx_uint_t url_idx, ngx_chain_t *args)\n{\n    ngx_rtmp_notify_app_conf_t *nacf;\n    ngx_chain_t                *al, *bl, *cl;\n    ngx_url_t                  *url;\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n\n    url = nacf->url[url_idx];\n\n    al = ngx_rtmp_netcall_http_format_session(s, pool);\n    if (al == NULL) {\n        return NULL;\n    }\n\n    al->next = args;\n\n    bl = NULL;\n\n    if (nacf->method == NGX_RTMP_NETCALL_HTTP_POST) {\n        cl = al;\n        al = bl;\n        bl = cl;\n    }\n\n    return ngx_rtmp_netcall_http_format_request(nacf->method, &url->host,\n                                                &url->uri, al, bl, pool,\n                                                &ngx_rtmp_notify_urlencoded);\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_notify_connect_create(ngx_rtmp_session_t *s, void *arg,\n        ngx_pool_t *pool)\n{\n    ngx_rtmp_connect_t             *v = arg;\n\n    ngx_rtmp_notify_srv_conf_t     *nscf;\n    ngx_url_t                      *url;\n    ngx_chain_t                    *al, *bl;\n    ngx_buf_t                      *b;\n    ngx_str_t                      *addr_text;\n    size_t                          app_len, args_len, flashver_len,\n                                    swf_url_len, tc_url_len, page_url_len;\n\n    nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module);\n\n    al = ngx_alloc_chain_link(pool);\n    if (al == NULL) {\n        return NULL;\n    }\n\n    /* these values are still missing in session\n     * so we have to construct the request from\n     * connection struct */\n\n    app_len = ngx_strlen(v->app);\n    args_len = ngx_strlen(v->args);\n    flashver_len = ngx_strlen(v->flashver);\n    swf_url_len = ngx_strlen(v->swf_url);\n    tc_url_len = ngx_strlen(v->tc_url);\n    page_url_len = ngx_strlen(v->page_url);\n\n    addr_text = &s->connection->addr_text;\n\n    b = ngx_create_temp_buf(pool,\n            sizeof(\"call=connect\") - 1 +\n            sizeof(\"&app=\") - 1 + app_len * 3 +\n            sizeof(\"&flashver=\") - 1 + flashver_len * 3 +\n            sizeof(\"&swfurl=\") - 1 + swf_url_len * 3 +\n            sizeof(\"&tcurl=\") - 1 + tc_url_len * 3 +\n            sizeof(\"&pageurl=\") - 1 + page_url_len * 3 +\n            sizeof(\"&addr=\") - 1 + addr_text->len * 3 +\n            sizeof(\"&epoch=\") - 1 + NGX_INT32_LEN +\n            1 + args_len\n        );\n\n    if (b == NULL) {\n        return NULL;\n    }\n\n    al->buf = b;\n    al->next = NULL;\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"app=\", sizeof(\"app=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->app, app_len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&flashver=\",\n                         sizeof(\"&flashver=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->flashver, flashver_len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&swfurl=\",\n                         sizeof(\"&swfurl=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->swf_url, swf_url_len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&tcurl=\",\n                         sizeof(\"&tcurl=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->tc_url, tc_url_len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&pageurl=\",\n                         sizeof(\"&pageurl=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->page_url, page_url_len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&addr=\", sizeof(\"&addr=\") -1);\n    b->last = (u_char*) ngx_escape_uri(b->last, addr_text->data,\n                                       addr_text->len, NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&epoch=\", sizeof(\"&epoch=\") -1);\n    b->last = ngx_sprintf(b->last, \"%uD\", (uint32_t) s->epoch);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&call=connect\",\n                         sizeof(\"&call=connect\") - 1);\n\n    if (args_len) {\n        *b->last++ = '&';\n        b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len);\n    }\n\n    url = nscf->url[NGX_RTMP_NOTIFY_CONNECT];\n\n    bl = NULL;\n\n    if (nscf->method == NGX_RTMP_NETCALL_HTTP_POST) {\n        bl = al;\n        al = NULL;\n    }\n\n    return ngx_rtmp_netcall_http_format_request(nscf->method, &url->host,\n                                                &url->uri, al, bl, pool,\n                                                &ngx_rtmp_notify_urlencoded);\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_notify_disconnect_create(ngx_rtmp_session_t *s, void *arg,\n        ngx_pool_t *pool)\n{\n    ngx_rtmp_notify_srv_conf_t     *nscf;\n    ngx_url_t                      *url;\n    ngx_chain_t                    *al, *bl, *pl;\n    ngx_buf_t                      *b;\n\n    nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module);\n\n    pl = ngx_alloc_chain_link(pool);\n    if (pl == NULL) {\n        return NULL;\n    }\n\n    b = ngx_create_temp_buf(pool,\n                            sizeof(\"&call=disconnect\") +\n                            sizeof(\"&app=\") + s->app.len * 3 +\n                            1 + s->args.len);\n    if (b == NULL) {\n        return NULL;\n    }\n\n    pl->buf = b;\n    pl->next = NULL;\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&call=disconnect\",\n                         sizeof(\"&call=disconnect\") - 1);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&app=\", sizeof(\"&app=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, s->app.data, s->app.len,\n                                       NGX_ESCAPE_ARGS);\n\n    if (s->args.len) {\n        *b->last++ = '&';\n        b->last = (u_char *) ngx_cpymem(b->last, s->args.data, s->args.len);\n    }\n\n    url = nscf->url[NGX_RTMP_NOTIFY_DISCONNECT];\n\n    al = ngx_rtmp_netcall_http_format_session(s, pool);\n    if (al == NULL) {\n        return NULL;\n    }\n\n    al->next = pl;\n\n    bl = NULL;\n\n    if (nscf->method == NGX_RTMP_NETCALL_HTTP_POST) {\n        bl = al;\n        al = NULL;\n    }\n\n    return ngx_rtmp_netcall_http_format_request(nscf->method, &url->host,\n                                                &url->uri, al, bl, pool,\n                                                &ngx_rtmp_notify_urlencoded);\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_notify_publish_create(ngx_rtmp_session_t *s, void *arg,\n        ngx_pool_t *pool)\n{\n    ngx_rtmp_publish_t             *v = arg;\n\n    ngx_chain_t                    *pl;\n    ngx_buf_t                      *b;\n    size_t                          name_len, type_len, args_len;\n\n    pl = ngx_alloc_chain_link(pool);\n    if (pl == NULL) {\n        return NULL;\n    }\n\n    name_len = ngx_strlen(v->name);\n    type_len = ngx_strlen(v->type);\n    args_len = ngx_strlen(v->args);\n\n    b = ngx_create_temp_buf(pool,\n                            sizeof(\"&call=publish\") +\n                            sizeof(\"&name=\") + name_len * 3 +\n                            sizeof(\"&type=\") + type_len * 3 +\n                            1 + args_len);\n    if (b == NULL) {\n        return NULL;\n    }\n\n    pl->buf = b;\n    pl->next = NULL;\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&call=publish\",\n                         sizeof(\"&call=publish\") - 1);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&name=\", sizeof(\"&name=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->name, name_len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&type=\", sizeof(\"&type=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->type, type_len,\n                                       NGX_ESCAPE_ARGS);\n\n    if (args_len) {\n        *b->last++ = '&';\n        b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len);\n    }\n\n    return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_PUBLISH, pl);\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_notify_play_create(ngx_rtmp_session_t *s, void *arg,\n        ngx_pool_t *pool)\n{\n    ngx_rtmp_play_t                *v = arg;\n\n    ngx_chain_t                    *pl;\n    ngx_buf_t                      *b;\n    size_t                          name_len, args_len;\n\n    pl = ngx_alloc_chain_link(pool);\n    if (pl == NULL) {\n        return NULL;\n    }\n\n    name_len = ngx_strlen(v->name);\n    args_len = ngx_strlen(v->args);\n\n    b = ngx_create_temp_buf(pool,\n                            sizeof(\"&call=play\") +\n                            sizeof(\"&name=\") + name_len * 3 +\n                            sizeof(\"&start=&duration=&reset=\") +\n                            NGX_INT32_LEN * 3 + 1 + args_len);\n    if (b == NULL) {\n        return NULL;\n    }\n\n    pl->buf = b;\n    pl->next = NULL;\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&call=play\",\n                         sizeof(\"&call=play\") - 1);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&name=\", sizeof(\"&name=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->name, name_len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_snprintf(b->last, b->end - b->last,\n                           \"&start=%uD&duration=%uD&reset=%d\",\n                           (uint32_t) v->start, (uint32_t) v->duration,\n                           v->reset & 1);\n\n    if (args_len) {\n        *b->last++ = '&';\n        b->last = (u_char *) ngx_cpymem(b->last, v->args, args_len);\n    }\n\n    return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_PLAY, pl);\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_notify_done_create(ngx_rtmp_session_t *s, void *arg,\n        ngx_pool_t *pool)\n{\n    ngx_rtmp_notify_done_t         *ds = arg;\n\n    ngx_chain_t                    *pl;\n    ngx_buf_t                      *b;\n    size_t                          cbname_len, name_len, args_len;\n    ngx_rtmp_notify_ctx_t          *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module);\n\n    pl = ngx_alloc_chain_link(pool);\n    if (pl == NULL) {\n        return NULL;\n    }\n\n    cbname_len = ngx_strlen(ds->cbname);\n    name_len = ctx ? ngx_strlen(ctx->name) : 0;\n    args_len = ctx ? ngx_strlen(ctx->args) : 0;\n\n    b = ngx_create_temp_buf(pool,\n                            sizeof(\"&call=\") + cbname_len +\n                            sizeof(\"&name=\") + name_len * 3 +\n                            1 + args_len);\n    if (b == NULL) {\n        return NULL;\n    }\n\n    pl->buf = b;\n    pl->next = NULL;\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&call=\", sizeof(\"&call=\") - 1);\n    b->last = ngx_cpymem(b->last, ds->cbname, cbname_len);\n\n    if (name_len) {\n        b->last = ngx_cpymem(b->last, (u_char*) \"&name=\", sizeof(\"&name=\") - 1);\n        b->last = (u_char*) ngx_escape_uri(b->last, ctx->name, name_len,\n                                           NGX_ESCAPE_ARGS);\n    }\n\n    if (args_len) {\n        *b->last++ = '&';\n        b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len);\n    }\n\n    return ngx_rtmp_notify_create_request(s, pool, ds->url_idx, pl);\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_notify_update_create(ngx_rtmp_session_t *s, void *arg,\n        ngx_pool_t *pool)\n{\n    ngx_chain_t                    *pl;\n    ngx_buf_t                      *b;\n    size_t                          name_len, args_len;\n    ngx_rtmp_notify_ctx_t          *ctx;\n    ngx_str_t                       sfx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module);\n\n    pl = ngx_alloc_chain_link(pool);\n    if (pl == NULL) {\n        return NULL;\n    }\n\n    if (ctx->flags & NGX_RTMP_NOTIFY_PUBLISHING) {\n        ngx_str_set(&sfx, \"_publish\");\n    } else if (ctx->flags & NGX_RTMP_NOTIFY_PLAYING) {\n        ngx_str_set(&sfx, \"_play\");\n    } else {\n        ngx_str_null(&sfx);\n    }\n\n    name_len = ctx ? ngx_strlen(ctx->name) : 0;\n    args_len = ctx ? ngx_strlen(ctx->args) : 0;\n\n    b = ngx_create_temp_buf(pool,\n                            sizeof(\"&call=update\") + sfx.len +\n                            sizeof(\"&time=\") + NGX_TIME_T_LEN +\n                            sizeof(\"&timestamp=\") + NGX_INT32_LEN +\n                            sizeof(\"&name=\") + name_len * 3 +\n                            1 + args_len);\n    if (b == NULL) {\n        return NULL;\n    }\n\n    pl->buf = b;\n    pl->next = NULL;\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&call=update\",\n                         sizeof(\"&call=update\") - 1);\n    b->last = ngx_cpymem(b->last, sfx.data, sfx.len);\n\n    b->last = ngx_cpymem(b->last, (u_char *) \"&time=\",\n                         sizeof(\"&time=\") - 1);\n    b->last = ngx_sprintf(b->last, \"%T\", ngx_cached_time->sec - ctx->start);\n\n    b->last = ngx_cpymem(b->last, (u_char *) \"&timestamp=\",\n                         sizeof(\"&timestamp=\") - 1);\n    b->last = ngx_sprintf(b->last, \"%D\", s->current_time);\n\n    if (name_len) {\n        b->last = ngx_cpymem(b->last, (u_char*) \"&name=\", sizeof(\"&name=\") - 1);\n        b->last = (u_char*) ngx_escape_uri(b->last, ctx->name, name_len,\n                                           NGX_ESCAPE_ARGS);\n    }\n\n    if (args_len) {\n        *b->last++ = '&';\n        b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len);\n    }\n\n    return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_UPDATE, pl);\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_notify_record_done_create(ngx_rtmp_session_t *s, void *arg,\n                                   ngx_pool_t *pool)\n{\n    ngx_rtmp_record_done_t         *v = arg;\n\n    ngx_rtmp_notify_ctx_t          *ctx;\n    ngx_chain_t                    *pl;\n    ngx_buf_t                      *b;\n    size_t                          name_len, args_len;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module);\n\n    pl = ngx_alloc_chain_link(pool);\n    if (pl == NULL) {\n        return NULL;\n    }\n\n    name_len  = ngx_strlen(ctx->name);\n    args_len  = ngx_strlen(ctx->args);\n\n    b = ngx_create_temp_buf(pool,\n                            sizeof(\"&call=record_done\") +\n                            sizeof(\"&recorder=\") + v->recorder.len +\n                            sizeof(\"&name=\") + name_len * 3 +\n                            sizeof(\"&path=\") + v->path.len * 3 +\n                            1 + args_len);\n    if (b == NULL) {\n        return NULL;\n    }\n\n    pl->buf = b;\n    pl->next = NULL;\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&call=record_done\",\n                         sizeof(\"&call=record_done\") - 1);\n\n    b->last = ngx_cpymem(b->last, (u_char *) \"&recorder=\",\n                         sizeof(\"&recorder=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->recorder.data,\n                                       v->recorder.len, NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&name=\", sizeof(\"&name=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, ctx->name, name_len,\n                                       NGX_ESCAPE_ARGS);\n\n    b->last = ngx_cpymem(b->last, (u_char*) \"&path=\", sizeof(\"&path=\") - 1);\n    b->last = (u_char*) ngx_escape_uri(b->last, v->path.data, v->path.len,\n                                       NGX_ESCAPE_ARGS);\n\n    if (args_len) {\n        *b->last++ = '&';\n        b->last = (u_char *) ngx_cpymem(b->last, ctx->args, args_len);\n    }\n\n    return ngx_rtmp_notify_create_request(s, pool, NGX_RTMP_NOTIFY_RECORD_DONE,\n                                          pl);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_parse_http_retcode(ngx_rtmp_session_t *s,\n        ngx_chain_t *in)\n{\n    ngx_buf_t      *b;\n    ngx_int_t       n;\n    u_char          c;\n\n    /* find 10th character */\n\n    n = 9;\n    while (in) {\n        b = in->buf;\n        if (b->last - b->pos > n) {\n            c = b->pos[n];\n            if (c >= (u_char)'0' && c <= (u_char)'9') {\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                    \"notify: HTTP retcode: %dxx\", (int)(c - '0'));\n                switch (c) {\n                    case (u_char) '2':\n                        return NGX_OK;\n                    case (u_char) '3':\n                        return NGX_AGAIN;\n                    default:\n                        return NGX_ERROR;\n                }\n            }\n\n            ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                    \"notify: invalid HTTP retcode: %d..\", (int)c);\n\n            return NGX_ERROR;\n        }\n        n -= (b->last - b->pos);\n        in = in->next;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n            \"notify: empty or broken HTTP response\");\n\n    /*\n     * not enough data;\n     * it can happen in case of empty or broken reply\n     */\n\n    return NGX_ERROR;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_parse_http_header(ngx_rtmp_session_t *s,\n        ngx_chain_t *in, ngx_str_t *name, u_char *data, size_t len)\n{\n    ngx_buf_t      *b;\n    ngx_int_t       matched;\n    u_char         *p, c;\n    ngx_uint_t      n;\n\n    enum {\n        parse_name,\n        parse_space,\n        parse_value,\n        parse_value_newline\n    } state = parse_name;\n\n    n = 0;\n    matched = 0;\n\n    while (in) {\n        b = in->buf;\n\n        for (p = b->pos; p != b->last; ++p) {\n            c = *p;\n\n            if (c == '\\r') {\n                continue;\n            }\n\n            switch (state) {\n                case parse_value_newline:\n                    if (c == ' ' || c == '\\t') {\n                        state = parse_space;\n                        break;\n                    }\n\n                    if (matched) {\n                        return n;\n                    }\n\n                    if (c == '\\n') {\n                        return NGX_OK;\n                    }\n\n                    n = 0;\n                    state = parse_name;\n                    /* fall through */\n\n                case parse_name:\n                    switch (c) {\n                        case ':':\n                            matched = (n == name->len);\n                            n = 0;\n                            state = parse_space;\n                            break;\n                        case '\\n':\n                            n = 0;\n                            break;\n                        default:\n                            if (n < name->len &&\n                                ngx_tolower(c) == ngx_tolower(name->data[n]))\n                            {\n                                ++n;\n                                break;\n                            }\n                            n = name->len + 1;\n                    }\n                    break;\n\n                case parse_space:\n                    if (c == ' ' || c == '\\t') {\n                        break;\n                    }\n                    state = parse_value;\n                    /* fall through */\n\n                case parse_value:\n                    if (c == '\\n') {\n                        state = parse_value_newline;\n                        break;\n                    }\n\n                    if (matched && n + 1 < len) {\n                        data[n++] = c;\n                    }\n\n                    break;\n            }\n        }\n\n        in = in->next;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_notify_clear_flag(ngx_rtmp_session_t *s, ngx_uint_t flag)\n{\n    ngx_rtmp_notify_ctx_t  *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module);\n\n    ctx->flags &= ~flag;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_connect_handle(ngx_rtmp_session_t *s,\n        void *arg, ngx_chain_t *in)\n{\n    ngx_rtmp_connect_t *v = arg;\n    ngx_int_t           rc;\n    u_char              app[NGX_RTMP_MAX_NAME];\n\n    static ngx_str_t    location = ngx_string(\"location\");\n\n    rc = ngx_rtmp_notify_parse_http_retcode(s, in);\n    if (rc == NGX_ERROR) {\n        return NGX_ERROR;\n    }\n\n    if (rc == NGX_AGAIN) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"notify: connect redirect received\");\n\n        rc = ngx_rtmp_notify_parse_http_header(s, in, &location, app,\n                                               sizeof(app) - 1);\n        if (rc > 0) {\n            *ngx_cpymem(v->app, app, rc) = 0;\n            ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                          \"notify: connect redirect to '%s'\", v->app);\n        }\n    }\n\n    return next_connect(s, v);\n}\n\n\nstatic void\nngx_rtmp_notify_set_name(u_char *dst, size_t dst_len, u_char *src,\n    size_t src_len)\n{\n    u_char     result[16], *p;\n    ngx_md5_t  md5;\n\n    ngx_md5_init(&md5);\n    ngx_md5_update(&md5, src, src_len);\n    ngx_md5_final(result, &md5);\n\n    p = ngx_hex_dump(dst, result, ngx_min((dst_len - 1) / 2, 16));\n    *p = '\\0';\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_publish_handle(ngx_rtmp_session_t *s,\n        void *arg, ngx_chain_t *in)\n{\n    ngx_rtmp_publish_t         *v = arg;\n    ngx_int_t                   rc;\n    ngx_str_t                   local_name;\n    ngx_rtmp_relay_target_t     target;\n    ngx_url_t                  *u;\n    ngx_rtmp_notify_app_conf_t *nacf;\n    u_char                      name[NGX_RTMP_MAX_NAME];\n\n    static ngx_str_t    location = ngx_string(\"location\");\n\n    rc = ngx_rtmp_notify_parse_http_retcode(s, in);\n    if (rc == NGX_ERROR) {\n        ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PUBLISHING);\n        return NGX_ERROR;\n    }\n\n    if (rc != NGX_AGAIN) {\n        goto next;\n    }\n\n    /* HTTP 3xx */\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"notify: publish redirect received\");\n\n    rc = ngx_rtmp_notify_parse_http_header(s, in, &location, name,\n                                           sizeof(name) - 1);\n    if (rc <= 0) {\n        goto next;\n    }\n\n    if (ngx_strncasecmp(name, (u_char *) \"rtmp://\", 7)) {\n        *ngx_cpymem(v->name, name, rc) = 0;\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                      \"notify: publish redirect to '%s'\", v->name);\n        goto next;\n    }\n\n    /* push */\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n    if (nacf->relay_redirect) {\n        ngx_rtmp_notify_set_name(v->name, NGX_RTMP_MAX_NAME, name, (size_t) rc);\n    }\n\n    ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                  \"notify: push '%s' to '%*s'\", v->name, rc, name);\n\n    local_name.data = v->name;\n    local_name.len = ngx_strlen(v->name);\n\n    ngx_memzero(&target, sizeof(target));\n\n    u = &target.url;\n    u->url = local_name;\n    u->url.data = name + 7;\n    u->url.len = rc - 7;\n    u->default_port = 1935;\n    u->uri_part = 1;\n    u->no_resolve = 1; /* want ip here */\n\n    if (ngx_parse_url(s->connection->pool, u) != NGX_OK) {\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                      \"notify: push failed '%V'\", &local_name);\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_relay_push(s, &local_name, &target);\n\nnext:\n\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_play_handle(ngx_rtmp_session_t *s,\n        void *arg, ngx_chain_t *in)\n{\n    ngx_rtmp_play_t            *v = arg;\n    ngx_int_t                   rc;\n    ngx_str_t                   local_name;\n    ngx_rtmp_relay_target_t     target;\n    ngx_url_t                  *u;\n    ngx_rtmp_notify_app_conf_t *nacf;\n    u_char                      name[NGX_RTMP_MAX_NAME];\n\n    static ngx_str_t            location = ngx_string(\"location\");\n\n    rc = ngx_rtmp_notify_parse_http_retcode(s, in);\n    if (rc == NGX_ERROR) {\n        ngx_rtmp_notify_clear_flag(s, NGX_RTMP_NOTIFY_PLAYING);\n        return NGX_ERROR;\n    }\n\n    if (rc != NGX_AGAIN) {\n        goto next;\n    }\n\n    /* HTTP 3xx */\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"notify: play redirect received\");\n\n    rc = ngx_rtmp_notify_parse_http_header(s, in, &location, name,\n                                           sizeof(name) - 1);\n    if (rc <= 0) {\n        goto next;\n    }\n\n    if (ngx_strncasecmp(name, (u_char *) \"rtmp://\", 7)) {\n        *ngx_cpymem(v->name, name, rc) = 0;\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                      \"notify: play redirect to '%s'\", v->name);\n        goto next;\n    }\n\n    /* pull */\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n    if (nacf->relay_redirect) {\n        ngx_rtmp_notify_set_name(v->name, NGX_RTMP_MAX_NAME, name, (size_t) rc);\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"notify: pull '%s' from '%*s'\", v->name, rc, name);\n\n    local_name.data = v->name;\n    local_name.len = ngx_strlen(v->name);\n\n    ngx_memzero(&target, sizeof(target));\n\n    u = &target.url;\n    u->url = local_name;\n    u->url.data = name + 7;\n    u->url.len = rc - 7;\n    u->default_port = 1935;\n    u->uri_part = 1;\n    u->no_resolve = 1; /* want ip here */\n\n    if (ngx_parse_url(s->connection->pool, u) != NGX_OK) {\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                      \"notify: pull failed '%V'\", &local_name);\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_relay_pull(s, &local_name, &target);\n\nnext:\n\n    return next_play(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_update_handle(ngx_rtmp_session_t *s,\n        void *arg, ngx_chain_t *in)\n{\n    ngx_rtmp_notify_app_conf_t *nacf;\n    ngx_rtmp_notify_ctx_t      *ctx;\n    ngx_int_t                   rc;\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n\n    rc = ngx_rtmp_notify_parse_http_retcode(s, in);\n\n    if ((!nacf->update_strict && rc == NGX_ERROR) ||\n         (nacf->update_strict && rc != NGX_OK))\n    {\n        ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                      \"notify: update failed\");\n\n        return NGX_ERROR;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"notify: schedule update %Mms\",\n                   nacf->update_timeout);\n\n    ngx_add_timer(&ctx->update_evt, nacf->update_timeout);\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_notify_update(ngx_event_t *e)\n{\n    ngx_connection_t           *c;\n    ngx_rtmp_session_t         *s;\n    ngx_rtmp_notify_app_conf_t *nacf;\n    ngx_rtmp_netcall_init_t     ci;\n    ngx_url_t                  *url;\n\n    c = e->data;\n    s = c->data;\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n\n    url = nacf->url[NGX_RTMP_NOTIFY_UPDATE];\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"notify: update '%V'\", &url->url);\n\n    ngx_memzero(&ci, sizeof(ci));\n\n    ci.url = url;\n    ci.create = ngx_rtmp_notify_update_create;\n    ci.handle = ngx_rtmp_notify_update_handle;\n\n    if (ngx_rtmp_netcall_create(s, &ci) == NGX_OK) {\n        return;\n    }\n\n    /* schedule next update on connection error */\n\n    ngx_rtmp_notify_update_handle(s, NULL, NULL);\n}\n\n\nstatic void\nngx_rtmp_notify_init(ngx_rtmp_session_t *s,\n        u_char name[NGX_RTMP_MAX_NAME], u_char args[NGX_RTMP_MAX_ARGS],\n        ngx_uint_t flags)\n{\n    ngx_rtmp_notify_ctx_t          *ctx;\n    ngx_rtmp_notify_app_conf_t     *nacf;\n    ngx_event_t                    *e;\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n    if (!nacf->active) {\n        return;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module);\n\n    if (ctx == NULL) {\n        ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_notify_ctx_t));\n        if (ctx == NULL) {\n            return;\n        }\n\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_notify_module);\n    }\n\n    ngx_memcpy(ctx->name, name, NGX_RTMP_MAX_NAME);\n    ngx_memcpy(ctx->args, args, NGX_RTMP_MAX_ARGS);\n\n    ctx->flags |= flags;\n\n    if (nacf->url[NGX_RTMP_NOTIFY_UPDATE] == NULL ||\n        nacf->update_timeout == 0)\n    {\n        return;\n    }\n\n    if (ctx->update_evt.timer_set) {\n        return;\n    }\n\n    ctx->start = ngx_cached_time->sec;\n\n    e = &ctx->update_evt;\n\n    e->data = s->connection;\n    e->log = s->connection->log;\n    e->handler = ngx_rtmp_notify_update;\n\n    ngx_add_timer(e, nacf->update_timeout);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"notify: schedule initial update %Mms\",\n                   nacf->update_timeout);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_connect(ngx_rtmp_session_t *s, ngx_rtmp_connect_t *v)\n{\n    ngx_rtmp_notify_srv_conf_t     *nscf;\n    ngx_rtmp_netcall_init_t         ci;\n    ngx_url_t                      *url;\n\n    if (s->auto_pushed || s->relay) {\n        goto next;\n    }\n\n    nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module);\n\n    url = nscf->url[NGX_RTMP_NOTIFY_CONNECT];\n    if (url == NULL) {\n        goto next;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"notify: connect '%V'\", &url->url);\n\n    ngx_memzero(&ci, sizeof(ci));\n\n    ci.url = url;\n    ci.create = ngx_rtmp_notify_connect_create;\n    ci.handle = ngx_rtmp_notify_connect_handle;\n    ci.arg = v;\n    ci.argsize = sizeof(*v);\n\n    return ngx_rtmp_netcall_create(s, &ci);\n\nnext:\n    return next_connect(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_disconnect(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_notify_srv_conf_t     *nscf;\n    ngx_rtmp_netcall_init_t         ci;\n    ngx_url_t                      *url;\n\n    if (s->auto_pushed || s->relay) {\n        goto next;\n    }\n\n    nscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_notify_module);\n\n    url = nscf->url[NGX_RTMP_NOTIFY_DISCONNECT];\n    if (url == NULL) {\n        goto next;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"notify: disconnect '%V'\", &url->url);\n\n    ngx_memzero(&ci, sizeof(ci));\n\n    ci.url = url;\n    ci.create = ngx_rtmp_notify_disconnect_create;\n\n    ngx_rtmp_netcall_create(s, &ci);\n\nnext:\n    return next_disconnect(s);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    ngx_rtmp_notify_app_conf_t     *nacf;\n    ngx_rtmp_netcall_init_t         ci;\n    ngx_url_t                      *url;\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n    if (nacf == NULL) {\n        goto next;\n    }\n\n    url = nacf->url[NGX_RTMP_NOTIFY_PUBLISH];\n\n    ngx_rtmp_notify_init(s, v->name, v->args, NGX_RTMP_NOTIFY_PUBLISHING);\n\n    if (url == NULL) {\n        goto next;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"notify: publish '%V'\", &url->url);\n\n    ngx_memzero(&ci, sizeof(ci));\n\n    ci.url = url;\n    ci.create = ngx_rtmp_notify_publish_create;\n    ci.handle = ngx_rtmp_notify_publish_handle;\n    ci.arg = v;\n    ci.argsize = sizeof(*v);\n\n    return ngx_rtmp_netcall_create(s, &ci);\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    ngx_rtmp_notify_app_conf_t     *nacf;\n    ngx_rtmp_netcall_init_t         ci;\n    ngx_url_t                      *url;\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n    if (nacf == NULL) {\n        goto next;\n    }\n\n    url = nacf->url[NGX_RTMP_NOTIFY_PLAY];\n\n    ngx_rtmp_notify_init(s, v->name, v->args, NGX_RTMP_NOTIFY_PLAYING);\n\n    if (url == NULL) {\n        goto next;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"notify: play '%V'\", &url->url);\n\n    ngx_memzero(&ci, sizeof(ci));\n\n    ci.url = url;\n    ci.create = ngx_rtmp_notify_play_create;\n    ci.handle = ngx_rtmp_notify_play_handle;\n    ci.arg = v;\n    ci.argsize = sizeof(*v);\n\n    return ngx_rtmp_netcall_create(s, &ci);\n\nnext:\n    return next_play(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_close_stream(ngx_rtmp_session_t *s,\n                             ngx_rtmp_close_stream_t *v)\n{\n    ngx_rtmp_notify_ctx_t          *ctx;\n    ngx_rtmp_notify_app_conf_t     *nacf;\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_notify_module);\n\n    if (ctx == NULL) {\n        goto next;\n    }\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n\n    if (nacf == NULL) {\n        goto next;\n    }\n\n    if (ctx->flags & NGX_RTMP_NOTIFY_PUBLISHING) {\n        ngx_rtmp_notify_done(s, \"publish_done\", NGX_RTMP_NOTIFY_PUBLISH_DONE);\n    }\n\n    if (ctx->flags & NGX_RTMP_NOTIFY_PLAYING) {\n        ngx_rtmp_notify_done(s, \"play_done\", NGX_RTMP_NOTIFY_PLAY_DONE);\n    }\n\n    if (ctx->flags) {\n        ngx_rtmp_notify_done(s, \"done\", NGX_RTMP_NOTIFY_DONE);\n    }\n\n    if (ctx->update_evt.timer_set) {\n        ngx_del_timer(&ctx->update_evt);\n    }\n\n    ctx->flags = 0;\n\nnext:\n    return next_close_stream(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_record_done(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v)\n{\n    ngx_rtmp_netcall_init_t         ci;\n    ngx_rtmp_notify_app_conf_t     *nacf;\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n    if (nacf == NULL || nacf->url[NGX_RTMP_NOTIFY_RECORD_DONE] == NULL) {\n        goto next;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"notify: record_done recorder=%V path='%V' url='%V'\",\n                  &v->recorder, &v->path,\n                  &nacf->url[NGX_RTMP_NOTIFY_RECORD_DONE]->url);\n\n    ngx_memzero(&ci, sizeof(ci));\n\n    ci.url    = nacf->url[NGX_RTMP_NOTIFY_RECORD_DONE];\n    ci.create = ngx_rtmp_notify_record_done_create;\n    ci.arg    = v;\n\n    ngx_rtmp_netcall_create(s, &ci);\n\nnext:\n    return next_record_done(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_done(ngx_rtmp_session_t *s, char *cbname, ngx_uint_t url_idx)\n{\n    ngx_rtmp_netcall_init_t         ci;\n    ngx_rtmp_notify_done_t          ds;\n    ngx_rtmp_notify_app_conf_t     *nacf;\n    ngx_url_t                      *url;\n\n    nacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_notify_module);\n\n    url = nacf->url[url_idx];\n    if (url == NULL) {\n        return NGX_OK;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"notify: %s '%V'\", cbname, &url->url);\n\n    ds.cbname = (u_char *) cbname;\n    ds.url_idx = url_idx;\n\n    ngx_memzero(&ci, sizeof(ci));\n\n    ci.url = url;\n    ci.arg = &ds;\n    ci.create = ngx_rtmp_notify_done_create;\n\n    return ngx_rtmp_netcall_create(s, &ci);\n}\n\n\nstatic ngx_url_t *\nngx_rtmp_notify_parse_url(ngx_conf_t *cf, ngx_str_t *url)\n{\n    ngx_url_t  *u;\n    size_t      add;\n\n    add = 0;\n\n    u = ngx_pcalloc(cf->pool, sizeof(ngx_url_t));\n    if (u == NULL) {\n        return NULL;\n    }\n\n    if (ngx_strncasecmp(url->data, (u_char *) \"http://\", 7) == 0) {\n        add = 7;\n    }\n\n    u->url.len = url->len - add;\n    u->url.data = url->data + add;\n    u->default_port = 80;\n    u->uri_part = 1;\n\n    if (ngx_parse_url(cf->pool, u) != NGX_OK) {\n        if (u->err) {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                    \"%s in url \\\"%V\\\"\", u->err, &u->url);\n        }\n        return NULL;\n    }\n\n    return u;\n}\n\n\nstatic char *\nngx_rtmp_notify_on_srv_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_notify_srv_conf_t     *nscf = conf;\n\n    ngx_str_t                      *name, *value;\n    ngx_url_t                      *u;\n    ngx_uint_t                      n;\n\n    value = cf->args->elts;\n\n    u = ngx_rtmp_notify_parse_url(cf, &value[1]);\n    if (u == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    name = &value[0];\n\n    n = 0;\n\n    switch (name->len) {\n        case sizeof(\"on_connect\") - 1:\n            n = NGX_RTMP_NOTIFY_CONNECT;\n            break;\n\n        case sizeof(\"on_disconnect\") - 1:\n            n = NGX_RTMP_NOTIFY_DISCONNECT;\n            break;\n    }\n\n    nscf->url[n] = u;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_rtmp_notify_on_app_event(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_notify_app_conf_t     *nacf = conf;\n\n    ngx_str_t                      *name, *value;\n    ngx_url_t                      *u;\n    ngx_uint_t                      n;\n\n    value = cf->args->elts;\n\n    u = ngx_rtmp_notify_parse_url(cf, &value[1]);\n    if (u == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    name = &value[0];\n\n    n = 0;\n\n    switch (name->len) {\n        case sizeof(\"on_done\") - 1: /* and on_play */\n            if (name->data[3] == 'd') {\n                n = NGX_RTMP_NOTIFY_DONE;\n            } else {\n                n = NGX_RTMP_NOTIFY_PLAY;\n            }\n            break;\n\n        case sizeof(\"on_update\") - 1:\n            n = NGX_RTMP_NOTIFY_UPDATE;\n            break;\n\n        case sizeof(\"on_publish\") - 1:\n            n = NGX_RTMP_NOTIFY_PUBLISH;\n            break;\n\n        case sizeof(\"on_play_done\") - 1:\n            n = NGX_RTMP_NOTIFY_PLAY_DONE;\n            break;\n\n        case sizeof(\"on_record_done\") - 1:\n            n = NGX_RTMP_NOTIFY_RECORD_DONE;\n            break;\n\n        case sizeof(\"on_publish_done\") - 1:\n            n = NGX_RTMP_NOTIFY_PUBLISH_DONE;\n            break;\n    }\n\n    nacf->url[n] = u;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_rtmp_notify_method(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_notify_app_conf_t     *nacf = conf;\n\n    ngx_rtmp_notify_srv_conf_t     *nscf;\n    ngx_str_t                      *value;\n\n    value = cf->args->elts;\n    value++;\n\n    if (value->len == sizeof(\"get\") - 1 &&\n        ngx_strncasecmp(value->data, (u_char *) \"get\", value->len) == 0)\n    {\n        nacf->method = NGX_RTMP_NETCALL_HTTP_GET;\n\n    } else if (value->len == sizeof(\"post\") - 1 &&\n               ngx_strncasecmp(value->data, (u_char *) \"post\", value->len) == 0)\n    {\n        nacf->method = NGX_RTMP_NETCALL_HTTP_POST;\n\n    } else {\n        return \"got unexpected method\";\n    }\n\n    nscf = ngx_rtmp_conf_get_module_srv_conf(cf, ngx_rtmp_notify_module);\n    nscf->method = nacf->method;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_notify_postconfiguration(ngx_conf_t *cf)\n{\n    next_connect = ngx_rtmp_connect;\n    ngx_rtmp_connect = ngx_rtmp_notify_connect;\n\n    next_disconnect = ngx_rtmp_disconnect;\n    ngx_rtmp_disconnect = ngx_rtmp_notify_disconnect;\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_notify_publish;\n\n    next_play = ngx_rtmp_play;\n    ngx_rtmp_play = ngx_rtmp_notify_play;\n\n    next_close_stream = ngx_rtmp_close_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_notify_close_stream;\n\n    next_record_done = ngx_rtmp_record_done;\n    ngx_rtmp_record_done = ngx_rtmp_notify_record_done;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_play_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <nginx.h>\n#include \"ngx_rtmp_play_module.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_netcall_module.h\"\n#include \"ngx_rtmp_streams.h\"\n\n\nstatic ngx_rtmp_play_pt                 next_play;\nstatic ngx_rtmp_close_stream_pt         next_close_stream;\nstatic ngx_rtmp_seek_pt                 next_seek;\nstatic ngx_rtmp_pause_pt                next_pause;\n\n\nstatic char *ngx_rtmp_play_url(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic void *ngx_rtmp_play_create_main_conf(ngx_conf_t *cf);\nstatic ngx_int_t ngx_rtmp_play_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_play_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_play_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\n\nstatic ngx_int_t ngx_rtmp_play_do_init(ngx_rtmp_session_t *s);\nstatic ngx_int_t ngx_rtmp_play_do_done(ngx_rtmp_session_t *s);\nstatic ngx_int_t ngx_rtmp_play_do_start(ngx_rtmp_session_t *s);\nstatic ngx_int_t ngx_rtmp_play_do_stop(ngx_rtmp_session_t *s);\nstatic ngx_int_t ngx_rtmp_play_do_seek(ngx_rtmp_session_t *s,\n                                       ngx_uint_t timestamp);\n\nstatic ngx_int_t ngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v);\nstatic ngx_int_t ngx_rtmp_play_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v);\nstatic ngx_int_t ngx_rtmp_play_pause(ngx_rtmp_session_t *s,\n                                     ngx_rtmp_pause_t *v);\nstatic void ngx_rtmp_play_send(ngx_event_t *e);\nstatic ngx_int_t ngx_rtmp_play_open(ngx_rtmp_session_t *s, double start);\nstatic ngx_int_t ngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s,\n       void *arg, ngx_chain_t *in);\nstatic ngx_chain_t * ngx_rtmp_play_remote_create(ngx_rtmp_session_t *s,\n       void *arg, ngx_pool_t *pool);\nstatic ngx_int_t ngx_rtmp_play_open_remote(ngx_rtmp_session_t *s,\n       ngx_rtmp_play_t *v);\nstatic ngx_int_t ngx_rtmp_play_next_entry(ngx_rtmp_session_t *s,\n       ngx_rtmp_play_t *v);\nstatic ngx_rtmp_play_entry_t * ngx_rtmp_play_get_current_entry(\n       ngx_rtmp_session_t *s);\nstatic void ngx_rtmp_play_cleanup_local_file(ngx_rtmp_session_t *s);\nstatic void ngx_rtmp_play_copy_local_file(ngx_rtmp_session_t *s, u_char *name);\nstatic u_char * ngx_rtmp_play_get_local_file_path(ngx_rtmp_session_t *s);\n\n\nstatic ngx_command_t  ngx_rtmp_play_commands[] = {\n\n    { ngx_string(\"play\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_play_url,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"play_temp_path\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_play_app_conf_t, temp_path),\n      NULL },\n\n    { ngx_string(\"play_local_path\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_play_app_conf_t, local_path),\n      NULL },\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_play_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_play_postconfiguration,        /* postconfiguration */\n    ngx_rtmp_play_create_main_conf,         /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    ngx_rtmp_play_create_app_conf,          /* create app configuration */\n    ngx_rtmp_play_merge_app_conf            /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_play_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_play_module_ctx,              /* module context */\n    ngx_rtmp_play_commands,                 /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\n#define NGX_RTMP_PLAY_TMP_FILE              \"nginx-rtmp-vod.\"\n\n\nstatic void *\nngx_rtmp_play_create_main_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_play_main_conf_t      *pmcf;\n\n    pmcf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_main_conf_t));\n    if (pmcf == NULL) {\n        return NULL;\n    }\n\n    if (ngx_array_init(&pmcf->fmts, cf->pool, 1,\n                       sizeof(ngx_rtmp_play_fmt_t *))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n    return pmcf;\n}\n\n\nstatic void *\nngx_rtmp_play_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_play_app_conf_t      *pacf;\n\n    pacf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_app_conf_t));\n    if (pacf == NULL) {\n        return NULL;\n    }\n\n    pacf->nbuckets = 1024;\n\n    return pacf;\n}\n\n\nstatic char *\nngx_rtmp_play_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_play_app_conf_t *prev = parent;\n    ngx_rtmp_play_app_conf_t *conf = child;\n    ngx_rtmp_play_entry_t   **ppe;\n\n    ngx_conf_merge_str_value(conf->temp_path, prev->temp_path, \"/tmp\");\n    ngx_conf_merge_str_value(conf->local_path, prev->local_path, \"\");\n\n    if (prev->entries.nelts == 0) {\n        goto done;\n    }\n\n    if (conf->entries.nelts == 0) {\n        conf->entries = prev->entries;\n        goto done;\n    }\n\n    ppe = ngx_array_push_n(&conf->entries, prev->entries.nelts);\n    if (ppe == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    ngx_memcpy(ppe, prev->entries.elts, prev->entries.nelts * sizeof(void *));\n\ndone:\n\n    if (conf->entries.nelts == 0) {\n        return NGX_CONF_OK;\n    }\n\n    conf->ctx = ngx_pcalloc(cf->pool, sizeof(void *) * conf->nbuckets);\n    if (conf->ctx == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_join(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_ctx_t        *ctx, **pctx;\n    ngx_rtmp_play_app_conf_t   *pacf;\n    ngx_uint_t                  h;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: join\");\n\n    pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n    if (ctx == NULL || ctx->joined) {\n        return NGX_ERROR;\n    }\n\n    h = ngx_hash_key(ctx->name, ngx_strlen(ctx->name));\n    pctx = &pacf->ctx[h % pacf->nbuckets];\n\n    while (*pctx) {\n        if (!ngx_strncmp((*pctx)->name, ctx->name, NGX_RTMP_MAX_NAME)) {\n            break;\n        }\n        pctx = &(*pctx)->next;\n    }\n\n    ctx->next = *pctx;\n    *pctx = ctx;\n    ctx->joined = 1;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_leave(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_ctx_t        *ctx, **pctx;\n    ngx_rtmp_play_app_conf_t   *pacf;\n    ngx_uint_t                  h;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: leave\");\n\n    pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n    if (ctx == NULL || !ctx->joined) {\n        return NGX_ERROR;\n    }\n\n    h = ngx_hash_key(ctx->name, ngx_strlen(ctx->name));\n    pctx = &pacf->ctx[h % pacf->nbuckets];\n\n    while (*pctx && *pctx != ctx) {\n        pctx = &(*pctx)->next;\n    }\n\n    if (*pctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    *pctx = (*pctx)->next;\n    ctx->joined = 0;\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_play_send(ngx_event_t *e)\n{\n    ngx_rtmp_session_t     *s = e->data;\n    ngx_rtmp_play_ctx_t    *ctx;\n    ngx_int_t               rc;\n    ngx_uint_t              ts;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx == NULL || ctx->fmt == NULL || ctx->fmt->send == NULL) {\n        return;\n    }\n\n    ts = 0;\n\n    rc = ctx->fmt->send(s, &ctx->file, &ts);\n\n    if (rc > 0) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"play: send schedule %i\", rc);\n\n        ngx_add_timer(e, rc);\n        return;\n    }\n\n    if (rc == NGX_AGAIN) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"play: send buffer full\");\n\n        ngx_post_event(e, &s->posted_dry_events);\n        return;\n    }\n\n    if (rc == NGX_OK) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"play: send restart\");\n\n        ngx_post_event(e, &ngx_posted_events);\n        return;\n    }\n\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: send done\");\n\n    ngx_rtmp_send_stream_eof(s, NGX_RTMP_MSID);\n\n    ngx_rtmp_send_play_status(s, \"NetStream.Play.Complete\", \"status\", ts, 0);\n\n    ngx_rtmp_send_status(s, \"NetStream.Play.Stop\", \"status\", \"Stopped\");\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_do_init(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (ctx->fmt && ctx->fmt->init &&\n        ctx->fmt->init(s, &ctx->file, ctx->aindex, ctx->vindex) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_do_done(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (ctx->fmt && ctx->fmt->done &&\n        ctx->fmt->done(s, &ctx->file) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_do_start(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: start\");\n\n    if (ctx->fmt && ctx->fmt->start &&\n        ctx->fmt->start(s, &ctx->file) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_post_event((&ctx->send_evt), &ngx_posted_events);\n\n    ctx->playing = 1;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_do_seek(ngx_rtmp_session_t *s, ngx_uint_t timestamp)\n{\n    ngx_rtmp_play_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: seek timestamp=%ui\", timestamp);\n\n    if (ctx->fmt && ctx->fmt->seek &&\n        ctx->fmt->seek(s, &ctx->file, timestamp) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    if (ctx->playing) {\n        ngx_post_event((&ctx->send_evt), &ngx_posted_events);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_do_stop(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: stop\");\n\n    if (ctx->send_evt.timer_set) {\n        ngx_del_timer(&ctx->send_evt);\n    }\n\n#if (nginx_version >= 1007005)\n    if (ctx->send_evt.posted)\n#else\n    if (ctx->send_evt.prev)\n#endif\n    {\n        ngx_delete_posted_event((&ctx->send_evt));\n    }\n\n    if (ctx->fmt && ctx->fmt->stop &&\n        ctx->fmt->stop(s, &ctx->file) != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    ctx->playing = 0;\n\n    return NGX_OK;\n}\n\n\n/* This function returns pointer to a static buffer */\n\nstatic u_char *\nngx_rtmp_play_get_local_file_path(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_app_conf_t       *pacf;\n    ngx_rtmp_play_ctx_t            *ctx;\n    u_char                         *p;\n    static u_char                   path[NGX_MAX_PATH + 1];\n\n    pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    p = ngx_snprintf(path, NGX_MAX_PATH, \"%V/\" NGX_RTMP_PLAY_TMP_FILE \"%ui\",\n                     &pacf->temp_path, ctx->file_id);\n    *p = 0;\n\n    return path;\n}\n\n\nstatic void\nngx_rtmp_play_copy_local_file(ngx_rtmp_session_t *s, u_char *name)\n{\n    ngx_rtmp_play_app_conf_t   *pacf;\n    ngx_rtmp_play_ctx_t        *ctx;\n    u_char                     *path, *p;\n    static u_char               dpath[NGX_MAX_PATH + 1];\n\n    pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);\n    if (pacf == NULL) {\n        return;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n    if (ctx == NULL || ctx->file_id == 0) {\n        return;\n    }\n\n    path = ngx_rtmp_play_get_local_file_path(s);\n\n    p = ngx_snprintf(dpath, NGX_MAX_PATH, \"%V/%s%V\", &pacf->local_path,\n                     name + ctx->pfx_size, &ctx->sfx);\n    *p = 0;\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: copy local file '%s' to '%s'\", path, dpath);\n\n    if (ngx_rename_file(path, dpath) == 0) {\n        ctx->file_id = 0;\n        return;\n    }\n\n    ngx_log_error(NGX_LOG_ERR, s->connection->log, ngx_errno,\n                  \"play: error copying local file '%s' to '%s'\",\n                  path, dpath);\n\n    ngx_rtmp_play_cleanup_local_file(s);\n}\n\n\nstatic void\nngx_rtmp_play_cleanup_local_file(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_ctx_t        *ctx;\n    u_char                     *path;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n    if (ctx == NULL || ctx->file_id == 0) {\n        return;\n    }\n\n    path = ngx_rtmp_play_get_local_file_path(s);\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: deleting local file '%s'\", path);\n\n    ctx->file_id = 0;\n\n    ngx_delete_file(path);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)\n{\n    ngx_rtmp_play_ctx_t        *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n    if (ctx == NULL) {\n        goto next;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: close_stream\");\n\n    ngx_rtmp_play_do_stop(s);\n\n    ngx_rtmp_play_do_done(s);\n\n    if (ctx->file.fd != NGX_INVALID_FILE) {\n        ngx_close_file(ctx->file.fd);\n        ctx->file.fd = NGX_INVALID_FILE;\n\n        ngx_rtmp_send_stream_eof(s, NGX_RTMP_MSID);\n\n        ngx_rtmp_send_status(s, \"NetStream.Play.Stop\", \"status\",\n                             \"Stop video on demand\");\n    }\n\n    if (ctx->file_id) {\n        ngx_rtmp_play_cleanup_local_file(s);\n    }\n\n    ngx_rtmp_play_leave(s);\n\nnext:\n    return next_close_stream(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_seek(ngx_rtmp_session_t *s, ngx_rtmp_seek_t *v)\n{\n    ngx_rtmp_play_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n    if (ctx == NULL || ctx->file.fd == NGX_INVALID_FILE) {\n        goto next;\n    }\n\n    if (!ctx->opened) {\n        ctx->post_seek = (ngx_uint_t) v->offset;\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"play: post seek=%ui\", ctx->post_seek);\n        goto next;\n    }\n\n    if (ngx_rtmp_send_stream_eof(s, NGX_RTMP_MSID) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_play_do_seek(s, (ngx_uint_t) v->offset);\n\n    if (ngx_rtmp_send_status(s, \"NetStream.Seek.Notify\", \"status\", \"Seeking\")\n        != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_send_stream_begin(s, NGX_RTMP_MSID) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\nnext:\n    return next_seek(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_pause(ngx_rtmp_session_t *s, ngx_rtmp_pause_t *v)\n{\n    ngx_rtmp_play_ctx_t            *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx == NULL || ctx->file.fd == NGX_INVALID_FILE) {\n        goto next;\n    }\n\n    if (!ctx->opened) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"play: pause ignored\");\n        goto next;\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: pause=%i timestamp=%f\",\n                   (ngx_int_t) v->pause, v->position);\n\n    if (v->pause) {\n        if (ngx_rtmp_send_status(s, \"NetStream.Pause.Notify\", \"status\",\n                                 \"Paused video on demand\")\n            != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n\n        ngx_rtmp_play_do_stop(s);\n\n    } else {\n        if (ngx_rtmp_send_status(s, \"NetStream.Unpause.Notify\", \"status\",\n                                 \"Unpaused video on demand\")\n            != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n\n        ngx_rtmp_play_do_start(s); /*TODO: v->position? */\n    }\n\nnext:\n    return next_pause(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_parse_index(char type, u_char *args)\n{\n    u_char             *p, c;\n    static u_char       name[] = \"xindex=\";\n\n    name[0] = (u_char) type;\n\n    for ( ;; ) {\n        p = (u_char *) ngx_strstr(args, name);\n        if (p == NULL) {\n            return 0;\n        }\n\n        if (p != args) {\n            c = *(p - 1);\n            if (c != '?' && c != '&') {\n                args = p + 1;\n                continue;\n            }\n        }\n\n        return atoi((char *) p + (sizeof(name) - 1));\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    ngx_rtmp_play_main_conf_t      *pmcf;\n    ngx_rtmp_play_app_conf_t       *pacf;\n    ngx_rtmp_play_ctx_t            *ctx;\n    u_char                         *p;\n    ngx_rtmp_play_fmt_t            *fmt, **pfmt;\n    ngx_str_t                      *pfx, *sfx;\n    ngx_str_t                       name;\n    ngx_uint_t                      n;\n\n    pmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_play_module);\n\n    pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);\n\n    if (pacf == NULL || pacf->entries.nelts == 0) {\n        goto next;\n    }\n\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                  \"play: play name='%s' timestamp=%i\",\n                  v->name, (ngx_int_t) v->start);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx && ctx->file.fd != NGX_INVALID_FILE) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                     \"play: already playing\");\n        goto next;\n    }\n\n    /* check for double-dot in v->name;\n     * we should not move out of play directory */\n    for (p = v->name; *p; ++p) {\n        if (ngx_path_separator(p[0]) &&\n            p[1] == '.' && p[2] == '.' &&\n            ngx_path_separator(p[3]))\n        {\n            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                         \"play: bad name '%s'\", v->name);\n            return NGX_ERROR;\n        }\n    }\n\n    if (ctx == NULL) {\n        ctx = ngx_palloc(s->connection->pool, sizeof(ngx_rtmp_play_ctx_t));\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_play_module);\n    }\n\n    ngx_memzero(ctx, sizeof(*ctx));\n\n    ctx->session = s;\n    ctx->aindex = ngx_rtmp_play_parse_index('a', v->args);\n    ctx->vindex = ngx_rtmp_play_parse_index('v', v->args);\n\n    ctx->file.log = s->connection->log;\n\n    ngx_memcpy(ctx->name, v->name, NGX_RTMP_MAX_NAME);\n\n    name.len = ngx_strlen(v->name);\n    name.data = v->name;\n\n    pfmt = pmcf->fmts.elts;\n\n    for (n = 0; n < pmcf->fmts.nelts; ++n, ++pfmt) {\n        fmt = *pfmt;\n\n        pfx = &fmt->pfx;\n        sfx = &fmt->sfx;\n\n        if (pfx->len == 0 && ctx->fmt == NULL) {\n            ctx->fmt = fmt;\n        }\n\n        if (pfx->len && name.len >= pfx->len &&\n            ngx_strncasecmp(pfx->data, name.data, pfx->len) == 0)\n        {\n            ctx->pfx_size = pfx->len;\n            ctx->fmt = fmt;\n\n            break;\n        }\n\n        if (name.len >= sfx->len &&\n            ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len,\n                            sfx->len) == 0)\n        {\n            ctx->fmt = fmt;\n        }\n    }\n\n    if (ctx->fmt == NULL) {\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                      \"play: fmt not found\");\n        goto next;\n    }\n\n    ctx->file.fd = NGX_INVALID_FILE;\n    ctx->nentry = NGX_CONF_UNSET_UINT;\n    ctx->post_seek = NGX_CONF_UNSET_UINT;\n\n    sfx = &ctx->fmt->sfx;\n\n    if (name.len < sfx->len ||\n        ngx_strncasecmp(sfx->data, name.data + name.len - sfx->len,\n                        sfx->len))\n    {\n        ctx->sfx = *sfx;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: fmt=%V\", &ctx->fmt->name);\n\n    return ngx_rtmp_play_next_entry(s, v);\n\nnext:\n    return next_play(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_next_entry(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    ngx_rtmp_play_app_conf_t   *pacf;\n    ngx_rtmp_play_ctx_t        *ctx;\n    ngx_rtmp_play_entry_t      *pe;\n    u_char                     *p;\n    static u_char               path[NGX_MAX_PATH + 1];\n\n    pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    for ( ;; ) {\n\n        if (ctx->file.fd != NGX_INVALID_FILE) {\n            ngx_close_file(ctx->file.fd);\n            ctx->file.fd = NGX_INVALID_FILE;\n        }\n\n        if (ctx->file_id) {\n            ngx_rtmp_play_cleanup_local_file(s);\n        }\n\n        ctx->nentry = (ctx->nentry == NGX_CONF_UNSET_UINT ?\n                       0 : ctx->nentry + 1);\n\n        if (ctx->nentry >= pacf->entries.nelts) {\n            ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"play: all entries failed\");\n\n            ngx_rtmp_send_status(s, \"NetStream.Play.StreamNotFound\", \"error\",\n                                 \"Video on demand stream not found\");\n            break;\n        }\n\n        pe = ngx_rtmp_play_get_current_entry(s);\n\n        ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"play: trying %s entry %ui/%uz '%V'\",\n                       pe->url ? \"remote\" : \"local\",\n                       ctx->nentry + 1, pacf->entries.nelts,\n                       pe->url ? &pe->url->url : pe->root);\n\n        /* open remote */\n\n        if (pe->url) {\n            return ngx_rtmp_play_open_remote(s, v);\n        }\n\n        /* open local */\n\n        p = ngx_snprintf(path, NGX_MAX_PATH, \"%V/%s%V\",\n                         pe->root, v->name + ctx->pfx_size, &ctx->sfx);\n        *p = 0;\n\n        ctx->file.fd = ngx_open_file(path, NGX_FILE_RDONLY, NGX_FILE_OPEN,\n                                     NGX_FILE_DEFAULT_ACCESS);\n\n        if (ctx->file.fd == NGX_INVALID_FILE) {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, ngx_errno,\n                           \"play: error opening file '%s'\", path);\n            continue;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"play: open local file '%s'\", path);\n\n        if (ngx_rtmp_play_open(s, v->start) != NGX_OK) {\n            return NGX_ERROR;\n        }\n\n        break;\n    }\n\n    return next_play(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_open(ngx_rtmp_session_t *s, double start)\n{\n    ngx_rtmp_play_ctx_t    *ctx;\n    ngx_event_t            *e;\n    ngx_uint_t              timestamp;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx->file.fd == NGX_INVALID_FILE) {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_send_stream_begin(s, NGX_RTMP_MSID) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_send_status(s, \"NetStream.Play.Start\", \"status\",\n                             \"Start video on demand\")\n        != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_play_join(s) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    e = &ctx->send_evt;\n    e->data = s;\n    e->handler = ngx_rtmp_play_send;\n    e->log = s->connection->log;\n\n    ngx_rtmp_send_recorded(s, 1);\n\n    if (ngx_rtmp_send_sample_access(s) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_play_do_init(s) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    timestamp = ctx->post_seek != NGX_CONF_UNSET_UINT ? ctx->post_seek :\n                (start < 0 ? 0 : (ngx_uint_t) start);\n\n    if (ngx_rtmp_play_do_seek(s, timestamp) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    if (ngx_rtmp_play_do_start(s) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    ctx->opened = 1;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_play_remote_create(ngx_rtmp_session_t *s, void *arg, ngx_pool_t *pool)\n{\n    ngx_rtmp_play_t                *v = arg;\n\n    ngx_rtmp_play_ctx_t            *ctx;\n    ngx_rtmp_play_entry_t          *pe;\n    ngx_str_t                      *addr_text, uri;\n    u_char                         *p, *name;\n    size_t                          args_len, name_len, len;\n    static ngx_str_t                text_plain = ngx_string(\"text/plain\");\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    pe = ngx_rtmp_play_get_current_entry(s);\n\n    name = v->name + ctx->pfx_size;\n\n    name_len = ngx_strlen(name);\n    args_len = ngx_strlen(v->args);\n    addr_text = &s->connection->addr_text;\n\n    len = pe->url->uri.len + 1 +\n          name_len + ctx->sfx.len +\n          sizeof(\"?addr=\") + addr_text->len * 3 +\n          1 + args_len;\n\n    uri.data = ngx_palloc(pool, len);\n    if (uri.data == NULL) {\n        return NULL;\n    }\n\n    p = uri.data;\n\n    p = ngx_cpymem(p, pe->url->uri.data, pe->url->uri.len);\n\n    if (p == uri.data || p[-1] != '/') {\n        *p++ = '/';\n    }\n\n    p = ngx_cpymem(p, name, name_len);\n    p = ngx_cpymem(p, ctx->sfx.data, ctx->sfx.len);\n    p = ngx_cpymem(p, (u_char*)\"?addr=\", sizeof(\"&addr=\") -1);\n    p = (u_char*)ngx_escape_uri(p, addr_text->data, addr_text->len,\n                                NGX_ESCAPE_ARGS);\n    if (args_len) {\n        *p++ = '&';\n        p = (u_char *) ngx_cpymem(p, v->args, args_len);\n    }\n\n    uri.len = p - uri.data;\n\n    return ngx_rtmp_netcall_http_format_request(NGX_RTMP_NETCALL_HTTP_GET,\n                                                &pe->url->host, &uri,\n                                                NULL, NULL, pool, &text_plain);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_remote_handle(ngx_rtmp_session_t *s, void *arg, ngx_chain_t *in)\n{\n    ngx_rtmp_play_t        *v = arg;\n\n    ngx_rtmp_play_ctx_t    *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    if (ctx->nbody == 0) {\n        return ngx_rtmp_play_next_entry(s, v);\n    }\n\n    if (ctx->file_id) {\n        ngx_rtmp_play_copy_local_file(s, v->name);\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: open remote file\");\n\n    if (ngx_rtmp_play_open(s, v->start) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    return next_play(s, (ngx_rtmp_play_t *)arg);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_remote_sink(ngx_rtmp_session_t *s, ngx_chain_t *in)\n{\n    ngx_rtmp_play_ctx_t    *ctx;\n    ngx_buf_t              *b;\n    ngx_int_t               rc;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    /* skip HTTP header */\n    while (in && ctx->ncrs != 2) {\n        b = in->buf;\n\n        for (; b->pos != b->last && ctx->ncrs != 2; ++b->pos) {\n            switch (*b->pos) {\n                case '\\n':\n                    ++ctx->ncrs;\n                case '\\r':\n                    break;\n                default:\n                    ctx->ncrs = 0;\n            }\n            /* 10th header byte is HTTP response header */\n            if (++ctx->nheader == 10 && *b->pos != (u_char) '2') {\n                ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                              \"play: remote HTTP response code: %cxx\",\n                              *b->pos);\n                return NGX_ERROR;\n            }\n        }\n\n        if (b->pos == b->last) {\n            in = in->next;\n        }\n    }\n\n    /* write to temp file */\n    for (; in; in = in->next) {\n        b = in->buf;\n\n        if (b->pos == b->last) {\n            continue;\n        }\n\n        rc = ngx_write_fd(ctx->file.fd, b->pos, b->last - b->pos);\n\n        if (rc == NGX_ERROR) {\n            ngx_log_error(NGX_LOG_INFO, s->connection->log, ngx_errno,\n                          \"play: error writing to temp file\");\n            return NGX_ERROR;\n        }\n\n        ctx->nbody += rc;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_rtmp_play_entry_t *\nngx_rtmp_play_get_current_entry(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_app_conf_t   *pacf;\n    ngx_rtmp_play_ctx_t        *ctx;\n    ngx_rtmp_play_entry_t     **ppe;\n\n    pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    ppe = pacf->entries.elts;\n\n    return ppe[ctx->nentry];\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_open_remote(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    ngx_rtmp_play_app_conf_t       *pacf;\n    ngx_rtmp_play_ctx_t            *ctx;\n    ngx_rtmp_play_entry_t          *pe;\n    ngx_rtmp_netcall_init_t         ci;\n    u_char                         *path;\n    ngx_err_t                       err;\n    static ngx_uint_t               file_id;\n\n    pacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_play_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_play_module);\n\n    ctx->ncrs = 0;\n    ctx->nheader = 0;\n    ctx->nbody = 0;\n\n    for ( ;; ) {\n        ctx->file_id = ++file_id;\n\n        /* no zero after overflow */\n        if (ctx->file_id == 0) {\n            continue;\n        }\n\n        path = ngx_rtmp_play_get_local_file_path(s);\n\n        ctx->file.fd = ngx_open_tempfile(path, pacf->local_path.len, 0);\n\n        if (pacf->local_path.len == 0) {\n            ctx->file_id = 0;\n        }\n\n        if (ctx->file.fd != NGX_INVALID_FILE) {\n            break;\n        }\n\n        err = ngx_errno;\n\n        if (err != NGX_EEXIST) {\n            ctx->file_id = 0;\n\n            ngx_log_error(NGX_LOG_INFO, s->connection->log, err,\n                          \"play: failed to create temp file\");\n\n            return NGX_ERROR;\n        }\n    }\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"play: temp file '%s' file_id=%ui\",\n                   path, ctx->file_id);\n\n    pe = ngx_rtmp_play_get_current_entry(s);\n\n    ngx_memzero(&ci, sizeof(ci));\n\n    ci.url = pe->url;\n    ci.create = ngx_rtmp_play_remote_create;\n    ci.sink   = ngx_rtmp_play_remote_sink;\n    ci.handle = ngx_rtmp_play_remote_handle;\n    ci.arg = v;\n    ci.argsize = sizeof(*v);\n\n    return ngx_rtmp_netcall_create(s, &ci);\n}\n\n\nstatic char *\nngx_rtmp_play_url(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_rtmp_play_app_conf_t       *pacf = conf;\n\n    ngx_rtmp_play_entry_t          *pe, **ppe;\n    ngx_str_t                       url;\n    ngx_url_t                      *u;\n    size_t                          add, n;\n    ngx_str_t                      *value;\n\n    if (pacf->entries.nalloc == 0 &&\n        ngx_array_init(&pacf->entries, cf->pool, 1, sizeof(void *)) != NGX_OK)\n    {\n        return NGX_CONF_ERROR;\n    }\n\n    value = cf->args->elts;\n\n    for (n = 1; n < cf->args->nelts; ++n) {\n\n        ppe = ngx_array_push(&pacf->entries);\n        if (ppe == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        pe = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_play_entry_t));\n        if (pe == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        *ppe = pe;\n\n        if (ngx_strncasecmp(value[n].data, (u_char *) \"http://\", 7)) {\n\n            /* local file */\n\n            pe->root = ngx_palloc(cf->pool, sizeof(ngx_str_t));\n            if (pe->root == NULL) {\n                return NGX_CONF_ERROR;\n            }\n\n            *pe->root = value[n];\n\n            continue;\n        }\n\n        /* http case */\n\n        url = value[n];\n\n        add = sizeof(\"http://\") - 1;\n\n        url.data += add;\n        url.len  -= add;\n\n        u = ngx_pcalloc(cf->pool, sizeof(ngx_url_t));\n        if (u == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        u->url.len = url.len;\n        u->url.data = url.data;\n        u->default_port = 80;\n        u->uri_part = 1;\n\n        if (ngx_parse_url(cf->pool, u) != NGX_OK) {\n            if (u->err) {\n                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                        \"%s in url \\\"%V\\\"\", u->err, &u->url);\n            }\n            return NGX_CONF_ERROR;\n        }\n\n        pe->url = u;\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_play_postconfiguration(ngx_conf_t *cf)\n{\n    next_play = ngx_rtmp_play;\n    ngx_rtmp_play = ngx_rtmp_play_play;\n\n    next_close_stream = ngx_rtmp_close_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_play_close_stream;\n\n    next_seek = ngx_rtmp_seek;\n    ngx_rtmp_seek = ngx_rtmp_play_seek;\n\n    next_pause = ngx_rtmp_pause;\n    ngx_rtmp_pause = ngx_rtmp_play_pause;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_play_module.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_PLAY_H_INCLUDED_\n#define _NGX_RTMP_PLAY_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n\n\ntypedef ngx_int_t (*ngx_rtmp_play_init_pt)  (ngx_rtmp_session_t *s,\n        ngx_file_t *f, ngx_int_t aindex, ngx_int_t vindex);\ntypedef ngx_int_t (*ngx_rtmp_play_done_pt)  (ngx_rtmp_session_t *s,\n        ngx_file_t *f);\ntypedef ngx_int_t (*ngx_rtmp_play_start_pt) (ngx_rtmp_session_t *s,\n        ngx_file_t *f);\ntypedef ngx_int_t (*ngx_rtmp_play_seek_pt)  (ngx_rtmp_session_t *s,\n        ngx_file_t *f, ngx_uint_t offs);\ntypedef ngx_int_t (*ngx_rtmp_play_stop_pt)  (ngx_rtmp_session_t *s,\n        ngx_file_t *f);\ntypedef ngx_int_t (*ngx_rtmp_play_send_pt)  (ngx_rtmp_session_t *s,\n        ngx_file_t *f, ngx_uint_t *ts);\n\n\ntypedef struct {\n    ngx_str_t               name;\n    ngx_str_t               pfx;\n    ngx_str_t               sfx;\n\n    ngx_rtmp_play_init_pt   init;\n    ngx_rtmp_play_done_pt   done;\n    ngx_rtmp_play_start_pt  start;\n    ngx_rtmp_play_seek_pt   seek;\n    ngx_rtmp_play_stop_pt   stop;\n    ngx_rtmp_play_send_pt   send;\n} ngx_rtmp_play_fmt_t;\n\n\ntypedef struct ngx_rtmp_play_ctx_s ngx_rtmp_play_ctx_t;\n\n\nstruct ngx_rtmp_play_ctx_s {\n    ngx_rtmp_session_t     *session;\n    ngx_file_t              file;\n    ngx_rtmp_play_fmt_t    *fmt;\n    ngx_event_t             send_evt;\n    unsigned                playing:1;\n    unsigned                opened:1;\n    unsigned                joined:1;\n    ngx_uint_t              ncrs;\n    ngx_uint_t              nheader;\n    ngx_uint_t              nbody;\n    size_t                  pfx_size;\n    ngx_str_t               sfx;\n    ngx_uint_t              file_id;\n    ngx_int_t               aindex, vindex;\n    ngx_uint_t              nentry;\n    ngx_uint_t              post_seek;\n    u_char                  name[NGX_RTMP_MAX_NAME];\n    ngx_rtmp_play_ctx_t    *next;\n};\n\n\ntypedef struct {\n    ngx_str_t              *root;\n    ngx_url_t              *url;\n} ngx_rtmp_play_entry_t;\n\n\ntypedef struct {\n    ngx_str_t               temp_path;\n    ngx_str_t               local_path;\n    ngx_array_t             entries; /* ngx_rtmp_play_entry_t * */\n    ngx_uint_t              nbuckets;\n    ngx_rtmp_play_ctx_t   **ctx;\n} ngx_rtmp_play_app_conf_t;\n\n\ntypedef struct {\n    ngx_array_t             fmts; /* ngx_rtmp_play_fmt_t * */\n} ngx_rtmp_play_main_conf_t;\n\n\nextern ngx_module_t         ngx_rtmp_play_module;\n\n\n#endif /* _NGX_RTMP_PLAY_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_proxy_protocol.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <nginx.h>\n#include \"ngx_rtmp_proxy_protocol.h\"\n\n\nstatic void ngx_rtmp_proxy_protocol_recv(ngx_event_t *rev);\n\n\nvoid\nngx_rtmp_proxy_protocol(ngx_rtmp_session_t *s)\n{\n    ngx_event_t       *rev;\n    ngx_connection_t  *c;\n\n    c = s->connection;\n    rev = c->read;\n    rev->handler =  ngx_rtmp_proxy_protocol_recv;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"proxy_protocol: start\");\n\n    if (rev->ready) {\n        /* the deferred accept(), rtsig, aio, iocp */\n\n        if (ngx_use_accept_mutex) {\n            ngx_post_event(rev, &ngx_posted_events);\n            return;\n        }\n\n        rev->handler(rev);\n        return;\n    }\n\n    ngx_add_timer(rev, s->timeout);\n\n    if (ngx_handle_read_event(rev, 0) != NGX_OK) {\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n}\n\n\nstatic void\nngx_rtmp_proxy_protocol_recv(ngx_event_t *rev)\n{\n    u_char               buf[107], *p, *pp, *text;\n    size_t               len;\n    ssize_t              n;\n    ngx_err_t            err;\n    ngx_int_t            i;\n    ngx_addr_t           addr;\n    ngx_connection_t    *c;\n    ngx_rtmp_session_t  *s;\n\n    c = rev->data;\n    s = c->data;\n\n    if (c->destroyed) {\n        return;\n    }\n\n    if (rev->timedout) {\n        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,\n                \"proxy_protocol: recv: client timed out\");\n        c->timedout = 1;\n        ngx_rtmp_finalize_session(s);\n        return;\n    }\n\n    if (rev->timer_set) {\n        ngx_del_timer(rev);\n    }\n\n    n = recv(c->fd, (char *) buf, sizeof(buf), MSG_PEEK);\n\n    err = ngx_socket_errno;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0, \"recv(): %d\", n);\n\n    if (n == -1) {\n\n        if (err == NGX_EAGAIN) {\n            ngx_add_timer(rev, s->timeout);\n\n            if (ngx_handle_read_event(c->read, 0) != NGX_OK) {\n                ngx_rtmp_finalize_session(s);\n            }\n\n            return;\n        }\n\n        ngx_rtmp_finalize_session(s);\n\n        return;\n    }\n\n    p = buf;\n\n    if (n <= 8 && ngx_strncmp(p, \"PROXY \", 6) != 0) {\n        goto bad_header;\n    }\n\n    n -= 6;\n    p += 6;\n\n    ngx_memzero(&addr, sizeof(ngx_addr_t));\n\n    if (n >= 7 && ngx_strncmp(p, \"UNKNOWN\", 7) == 0) {\n        n -= 7;\n        p += 7;\n        goto skip;\n    }\n\n    if (n < 5 || ngx_strncmp(p, \"TCP\", 3) != 0\n        || (p[3] != '4' && p[3] != '6') || p[4] != ' ')\n    {\n        goto bad_header;\n    }\n\n    n -= 5;\n    p += 5;\n\n    pp = ngx_strlchr(p, p + n, ' ');\n\n    if (pp == NULL) {\n        goto bad_header;\n    }\n\n    if (ngx_parse_addr(s->connection->pool, &addr, p, pp - p) != NGX_OK) {\n        goto bad_header;\n    }\n\n    n -= pp - p;\n    p = pp;\n\nskip:\n\n    for (i = 0; i + 1 < n; i++) {\n        if (p[i] == CR && p[i + 1] == LF) {\n            break;\n        }\n    }\n\n    if (i + 1 >= n) {\n        goto bad_header;\n    }\n\n    n = p - buf + i + 2;\n\n    if (c->recv(c, buf, n) != n) {\n        goto failed;\n    }\n\n    if (addr.socklen) {\n        text = ngx_palloc(s->connection->pool, NGX_SOCKADDR_STRLEN);\n\n        if (text == NULL) {\n            goto failed;\n        }\n\n        len = ngx_sock_ntop(addr.sockaddr,\n#if (nginx_version >= 1005003)\n                            addr.socklen,\n#endif\n                            text, NGX_SOCKADDR_STRLEN, 0);\n        if (len == 0) {\n            goto failed;\n        }\n\n        c->sockaddr = addr.sockaddr;\n        c->socklen = addr.socklen;\n        c->addr_text.data = text;\n        c->addr_text.len = len;\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, c->log, 0,\n                       \"proxy_protocol: remote_addr:'%V'\", &c->addr_text);\n    }\n\n    ngx_rtmp_handshake(s);\n\n    return;\n\nbad_header:\n\n    ngx_log_error(NGX_LOG_INFO, c->log, 0, \"proxy_protocol: bad header\");\n\nfailed:\n\n    ngx_rtmp_finalize_session(s);\n}\n"
  },
  {
    "path": "ngx_rtmp_proxy_protocol.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_\n#define _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\nvoid ngx_rtmp_proxy_protocol(ngx_rtmp_session_t *c);\n\n\n#endif /* _NGX_RTMP_PROXY_PROTOCOL_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_receive.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_amf.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n#include <string.h>\n\n\nngx_int_t\nngx_rtmp_protocol_message_handler(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in)\n{\n    ngx_buf_t              *b;\n    u_char                 *p;\n    uint32_t                val;\n    uint8_t                 limit;\n\n    b = in->buf;\n\n    if (b->last - b->pos < 4) {\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"too small buffer for %d message: %d\",\n                (int)h->type, b->last - b->pos);\n        return NGX_OK;\n    }\n\n    p = (u_char*)&val;\n    p[0] = b->pos[3];\n    p[1] = b->pos[2];\n    p[2] = b->pos[1];\n    p[3] = b->pos[0];\n\n    switch(h->type) {\n        case NGX_RTMP_MSG_CHUNK_SIZE:\n            /* set chunk size =val */\n            ngx_rtmp_set_chunk_size(s, val);\n            break;\n\n        case NGX_RTMP_MSG_ABORT:\n            /* abort chunk stream =val */\n            break;\n\n        case NGX_RTMP_MSG_ACK:\n            /* receive ack with sequence number =val */\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"receive ack seq=%uD\", val);\n            break;\n\n        case NGX_RTMP_MSG_ACK_SIZE:\n            /* receive window size =val */\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"receive ack_size=%uD\", val);\n            s->ack_size = val;\n            break;\n\n        case NGX_RTMP_MSG_BANDWIDTH:\n            if (b->last - b->pos >= 5) {\n                limit = *(uint8_t*)&b->pos[4];\n\n                (void)val;\n                (void)limit;\n\n                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                    \"receive bandwidth=%uD limit=%d\",\n                    val, (int)limit);\n\n                /* receive window size =val\n                 * && limit */\n            }\n            break;\n\n        default:\n            return NGX_ERROR;\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_user_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                              ngx_chain_t *in)\n{\n    ngx_buf_t              *b;\n    u_char                 *p;\n    uint16_t                evt;\n    uint32_t                val;\n\n    b = in->buf;\n\n    if (b->last - b->pos < 6) {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"too small buffer for user message: %d\",\n                       b->last - b->pos);\n        return NGX_OK;\n    }\n\n    p = (u_char*)&evt;\n\n    p[0] = b->pos[1];\n    p[1] = b->pos[0];\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"RTMP recv user evt %s (%i)\",\n                   ngx_rtmp_user_message_type(evt), (ngx_int_t) evt);\n\n    p = (u_char *) &val;\n\n    p[0] = b->pos[5];\n    p[1] = b->pos[4];\n    p[2] = b->pos[3];\n    p[3] = b->pos[2];\n\n    switch(evt) {\n        case NGX_RTMP_USER_STREAM_BEGIN:\n            {\n                ngx_rtmp_stream_begin_t     v;\n\n                v.msid = val;\n\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"receive: stream_begin msid=%uD\", v.msid);\n\n                return ngx_rtmp_stream_begin(s, &v);\n            }\n\n        case NGX_RTMP_USER_STREAM_EOF:\n            {\n                ngx_rtmp_stream_eof_t       v;\n\n                v.msid = val;\n\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"receive: stream_eof msid=%uD\", v.msid);\n\n                return ngx_rtmp_stream_eof(s, &v);\n            }\n\n        case NGX_RTMP_USER_STREAM_DRY:\n            {\n                ngx_rtmp_stream_dry_t       v;\n\n                v.msid = val;\n\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"receive: stream_dry msid=%uD\", v.msid);\n\n                return ngx_rtmp_stream_dry(s, &v);\n            }\n\n        case NGX_RTMP_USER_SET_BUFLEN:\n            {\n                ngx_rtmp_set_buflen_t       v;\n\n                v.msid = val;\n\n                if (b->last - b->pos < 10) {\n                    return NGX_OK;\n                }\n\n                p = (u_char *) &v.buflen;\n\n                p[0] = b->pos[9];\n                p[1] = b->pos[8];\n                p[2] = b->pos[7];\n                p[3] = b->pos[6];\n\n                ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"receive: set_buflen msid=%uD buflen=%uD\",\n                               v.msid, v.buflen);\n\n                /*TODO: move this to play module */\n                s->buflen = v.buflen;\n\n                return ngx_rtmp_set_buflen(s, &v);\n            }\n\n        case NGX_RTMP_USER_RECORDED:\n            {\n                ngx_rtmp_recorded_t       v;\n\n                v.msid = val;\n\n                ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                               \"receive: recorded msid=%uD\", v.msid);\n\n                return ngx_rtmp_recorded(s, &v);\n            }\n\n        case NGX_RTMP_USER_PING_REQUEST:\n            return ngx_rtmp_send_ping_response(s, val);\n\n        case NGX_RTMP_USER_PING_RESPONSE:\n\n            /* val = incoming timestamp */\n\n            ngx_rtmp_reset_ping(s);\n\n            return NGX_OK;\n\n        default:\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"unexpected user event: %i\", (ngx_int_t) evt);\n\n            return NGX_OK;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_fetch(ngx_chain_t **in, u_char *ret)\n{\n    while (*in && (*in)->buf->pos >= (*in)->buf->last) {\n        *in = (*in)->next;\n    }\n\n    if (*in == NULL) {\n        return NGX_DONE;\n    }\n\n    *ret = *(*in)->buf->pos++;\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_fetch_uint8(ngx_chain_t **in, uint8_t *ret)\n{\n    return ngx_rtmp_fetch(in, (u_char *) ret);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_fetch_uint32(ngx_chain_t **in, uint32_t *ret, ngx_int_t n)\n{\n    u_char     *r = (u_char *) ret;\n    ngx_int_t   rc;\n\n    *ret = 0;\n\n    while (--n >= 0) {\n        rc = ngx_rtmp_fetch(in, &r[n]);\n        if (rc != NGX_OK) {\n            return rc;\n        }\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_aggregate_message_handler(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                                   ngx_chain_t *in)\n{\n    uint32_t            base_time, timestamp, prev_size;\n    size_t              len;\n    ngx_int_t           first;\n    u_char             *last;\n    ngx_int_t           rc;\n    ngx_buf_t          *b;\n    ngx_chain_t        *cl, *next;\n    ngx_rtmp_header_t   ch;\n\n    ch = *h;\n\n    first = 1;\n    base_time = 0;\n\n    while (in) {\n        if (ngx_rtmp_fetch_uint8(&in, &ch.type) != NGX_OK) {\n            return NGX_OK;\n        }\n\n        if (ngx_rtmp_fetch_uint32(&in, &ch.mlen, 3) != NGX_OK) {\n            return NGX_ERROR;\n        }\n\n        if (ngx_rtmp_fetch_uint32(&in, &timestamp, 3) != NGX_OK) {\n            return NGX_ERROR;\n        }\n\n        if (ngx_rtmp_fetch_uint8(&in, (uint8_t *) &timestamp + 3) != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n\n        if (ngx_rtmp_fetch_uint32(&in, &ch.msid, 3) != NGX_OK)\n        {\n            return NGX_ERROR;\n        }\n\n        if (first) {\n            base_time = timestamp;\n            first = 0;\n        }\n\n        ngx_log_debug6(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"RTMP aggregate %s (%d) len=%uD time=%uD (+%D) msid=%uD\",\n                       ngx_rtmp_message_type(ch.type),\n                       (ngx_int_t) ch.type, ch.mlen, ch.timestamp,\n                       timestamp - base_time, ch.msid);\n\n        /* limit chain */\n\n        len = 0;\n        cl = in;\n        while (cl) {\n            b = cl->buf;\n            len += (b->last - b->pos);\n            if (len > ch.mlen) {\n                break;\n            }\n            cl = cl->next;\n        }\n\n        if (cl == NULL) {\n            ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n                          \"RTMP error parsing aggregate\");\n            return NGX_ERROR;\n        }\n\n        next = cl->next;\n        cl->next = NULL;\n        b = cl->buf;\n        last = b->last;\n        b->last -= (len - ch.mlen);\n\n        /* handle aggregated message */\n\n        ch.timestamp = h->timestamp + timestamp - base_time;\n\n        rc = ngx_rtmp_receive_message(s, &ch, in);\n\n        /* restore chain before checking the result */\n\n        in = cl;\n        in->next = next;\n        b->pos = b->last;\n        b->last = last;\n\n        if (rc != NGX_OK) {\n            return rc;\n        }\n\n        /* read 32-bit previous tag size */\n\n        if (ngx_rtmp_fetch_uint32(&in, &prev_size, 4) != NGX_OK) {\n            return NGX_OK;\n        }\n\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"RTMP aggregate prev_size=%uD\", prev_size);\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_amf_message_handler(ngx_rtmp_session_t *s,\n        ngx_rtmp_header_t *h, ngx_chain_t *in)\n{\n    ngx_rtmp_amf_ctx_t          act;\n    ngx_rtmp_core_main_conf_t  *cmcf;\n    ngx_array_t                *ch;\n    ngx_rtmp_handler_pt        *ph;\n    size_t                      len, n;\n\n    static u_char               func[128];\n\n    static ngx_rtmp_amf_elt_t   elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          func,   sizeof(func) },\n    };\n\n    /* AMF command names come with string type, but shared object names\n     * come without type */\n    if (h->type == NGX_RTMP_MSG_AMF_SHARED ||\n        h->type == NGX_RTMP_MSG_AMF3_SHARED)\n    {\n        elts[0].type |= NGX_RTMP_AMF_TYPELESS;\n    } else {\n        elts[0].type &= ~NGX_RTMP_AMF_TYPELESS;\n    }\n\n    if ((h->type == NGX_RTMP_MSG_AMF3_SHARED ||\n         h->type == NGX_RTMP_MSG_AMF3_META ||\n         h->type == NGX_RTMP_MSG_AMF3_CMD)\n         && in->buf->last > in->buf->pos)\n    {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"AMF3 prefix: %ui\", (ngx_int_t)*in->buf->pos);\n        ++in->buf->pos;\n    }\n\n    cmcf = ngx_rtmp_get_module_main_conf(s, ngx_rtmp_core_module);\n\n    /* read AMF func name & transaction id */\n    ngx_memzero(&act, sizeof(act));\n    act.link = in;\n    act.log = s->connection->log;\n    memset(func, 0, sizeof(func));\n\n    if (ngx_rtmp_amf_read(&act, elts,\n                sizeof(elts) / sizeof(elts[0])) != NGX_OK)\n    {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"AMF cmd failed\");\n        return NGX_ERROR;\n    }\n\n    /* skip name */\n    in = act.link;\n    in->buf->pos += act.offset;\n\n    len = ngx_strlen(func);\n\n    ch = ngx_hash_find(&cmcf->amf_hash,\n            ngx_hash_strlow(func, func, len), func, len);\n\n    if (ch && ch->nelts) {\n        ph = ch->elts;\n        for (n = 0; n < ch->nelts; ++n, ++ph) {\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                \"AMF func '%s' passed to handler %d/%d\",\n                func, n, ch->nelts);\n            switch ((*ph)(s, h, in)) {\n                case NGX_ERROR:\n                    return NGX_ERROR;\n                case NGX_DONE:\n                    return NGX_OK;\n            }\n        }\n    } else {\n        ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"AMF cmd '%s' no handler\", func);\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_receive_amf(ngx_rtmp_session_t *s, ngx_chain_t *in,\n        ngx_rtmp_amf_elt_t *elts, size_t nelts)\n{\n    ngx_rtmp_amf_ctx_t     act;\n\n    ngx_memzero(&act, sizeof(act));\n    act.link = in;\n    act.log = s->connection->log;\n\n    return ngx_rtmp_amf_read(&act, elts, nelts);\n}\n"
  },
  {
    "path": "ngx_rtmp_record_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n#include \"ngx_rtmp_netcall_module.h\"\n#include \"ngx_rtmp_codec_module.h\"\n#include \"ngx_rtmp_record_module.h\"\n\n\nngx_rtmp_record_done_pt             ngx_rtmp_record_done;\n\n\nstatic ngx_rtmp_publish_pt          next_publish;\nstatic ngx_rtmp_close_stream_pt     next_close_stream;\nstatic ngx_rtmp_stream_begin_pt     next_stream_begin;\nstatic ngx_rtmp_stream_eof_pt       next_stream_eof;\n\n\nstatic char *ngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic ngx_int_t ngx_rtmp_record_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_record_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_record_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\nstatic ngx_int_t ngx_rtmp_record_write_frame(ngx_rtmp_session_t *s,\n       ngx_rtmp_record_rec_ctx_t *rctx,\n       ngx_rtmp_header_t *h, ngx_chain_t *in, ngx_int_t inc_nframes);\nstatic ngx_int_t ngx_rtmp_record_av(ngx_rtmp_session_t *s,\n       ngx_rtmp_header_t *h, ngx_chain_t *in);\nstatic ngx_int_t ngx_rtmp_record_node_av(ngx_rtmp_session_t *s,\n       ngx_rtmp_record_rec_ctx_t *rctx, ngx_rtmp_header_t *h, ngx_chain_t *in);\nstatic ngx_int_t ngx_rtmp_record_node_open(ngx_rtmp_session_t *s,\n       ngx_rtmp_record_rec_ctx_t *rctx);\nstatic ngx_int_t ngx_rtmp_record_node_close(ngx_rtmp_session_t *s,\n       ngx_rtmp_record_rec_ctx_t *rctx);\nstatic void  ngx_rtmp_record_make_path(ngx_rtmp_session_t *s,\n       ngx_rtmp_record_rec_ctx_t *rctx, ngx_str_t *path);\nstatic ngx_int_t ngx_rtmp_record_init(ngx_rtmp_session_t *s);\n\n\nstatic ngx_conf_bitmask_t  ngx_rtmp_record_mask[] = {\n    { ngx_string(\"off\"),                NGX_RTMP_RECORD_OFF         },\n    { ngx_string(\"all\"),                NGX_RTMP_RECORD_AUDIO       |\n                                        NGX_RTMP_RECORD_VIDEO       },\n    { ngx_string(\"audio\"),              NGX_RTMP_RECORD_AUDIO       },\n    { ngx_string(\"video\"),              NGX_RTMP_RECORD_VIDEO       },\n    { ngx_string(\"keyframes\"),          NGX_RTMP_RECORD_KEYFRAMES   },\n    { ngx_string(\"manual\"),             NGX_RTMP_RECORD_MANUAL      },\n    { ngx_null_string,                  0                           }\n};\n\n\nstatic ngx_command_t  ngx_rtmp_record_commands[] = {\n\n    { ngx_string(\"record\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_1MORE,\n      ngx_conf_set_bitmask_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, flags),\n      ngx_rtmp_record_mask },\n\n    { ngx_string(\"record_path\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, path),\n      NULL },\n\n    { ngx_string(\"record_suffix\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_str_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, suffix),\n      NULL },\n\n    { ngx_string(\"record_unique\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, unique),\n      NULL },\n\n    { ngx_string(\"record_append\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, append),\n      NULL },\n\n    { ngx_string(\"record_lock\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, lock_file),\n      NULL },\n\n    { ngx_string(\"record_max_size\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_size_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, max_size),\n      NULL },\n\n    { ngx_string(\"record_max_frames\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_size_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, max_frames),\n      NULL },\n\n    { ngx_string(\"record_interval\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, interval),\n      NULL },\n\n    { ngx_string(\"record_notify\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|\n                         NGX_RTMP_REC_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_record_app_conf_t, notify),\n      NULL },\n\n    { ngx_string(\"recorder\"),\n      NGX_RTMP_APP_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE1,\n      ngx_rtmp_record_recorder,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_record_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_record_postconfiguration,      /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    ngx_rtmp_record_create_app_conf,        /* create app configuration */\n    ngx_rtmp_record_merge_app_conf          /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_record_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_record_module_ctx,            /* module context */\n    ngx_rtmp_record_commands,               /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    NULL,                                   /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void *\nngx_rtmp_record_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_record_app_conf_t      *racf;\n\n    racf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_record_app_conf_t));\n\n    if (racf == NULL) {\n        return NULL;\n    }\n\n    racf->max_size = NGX_CONF_UNSET_SIZE;\n    racf->max_frames = NGX_CONF_UNSET_SIZE;\n    racf->interval = NGX_CONF_UNSET_MSEC;\n    racf->unique = NGX_CONF_UNSET;\n    racf->append = NGX_CONF_UNSET;\n    racf->lock_file = NGX_CONF_UNSET;\n    racf->notify = NGX_CONF_UNSET;\n    racf->url = NGX_CONF_UNSET_PTR;\n\n    if (ngx_array_init(&racf->rec, cf->pool, 1, sizeof(void *)) != NGX_OK) {\n        return NULL;\n    }\n\n    return racf;\n}\n\n\nstatic char *\nngx_rtmp_record_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_record_app_conf_t     *prev = parent;\n    ngx_rtmp_record_app_conf_t     *conf = child;\n    ngx_rtmp_record_app_conf_t    **rracf;\n\n    ngx_conf_merge_str_value(conf->path, prev->path, \"\");\n    ngx_conf_merge_str_value(conf->suffix, prev->suffix, \".flv\");\n    ngx_conf_merge_size_value(conf->max_size, prev->max_size, 0);\n    ngx_conf_merge_size_value(conf->max_frames, prev->max_frames, 0);\n    ngx_conf_merge_value(conf->unique, prev->unique, 0);\n    ngx_conf_merge_value(conf->append, prev->append, 0);\n    ngx_conf_merge_value(conf->lock_file, prev->lock_file, 0);\n    ngx_conf_merge_value(conf->notify, prev->notify, 0);\n    ngx_conf_merge_msec_value(conf->interval, prev->interval,\n                              (ngx_msec_t) NGX_CONF_UNSET);\n    ngx_conf_merge_bitmask_value(conf->flags, prev->flags, 0);\n    ngx_conf_merge_ptr_value(conf->url, prev->url, NULL);\n\n    if (conf->flags) {\n        rracf = ngx_array_push(&conf->rec);\n        if (rracf == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        *rracf = conf;\n    }\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_write_header(ngx_file_t *file)\n{\n    static u_char       flv_header[] = {\n        0x46, /* 'F' */\n        0x4c, /* 'L' */\n        0x56, /* 'V' */\n        0x01, /* version = 1 */\n        0x05, /* 00000 1 0 1 = has audio & video */\n        0x00,\n        0x00,\n        0x00,\n        0x09, /* header size */\n        0x00,\n        0x00,\n        0x00,\n        0x00  /* PreviousTagSize0 (not actually a header) */\n    };\n\n    return ngx_write_file(file, flv_header, sizeof(flv_header), 0) == NGX_ERROR\n           ? NGX_ERROR\n           : NGX_OK;\n}\n\n\nstatic ngx_rtmp_record_rec_ctx_t *\nngx_rtmp_record_get_node_ctx(ngx_rtmp_session_t *s, ngx_uint_t n)\n{\n    ngx_rtmp_record_ctx_t          *ctx;\n    ngx_rtmp_record_rec_ctx_t      *rctx;\n\n    if (ngx_rtmp_record_init(s) != NGX_OK) {\n        return NULL;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);\n\n    if (n >= ctx->rec.nelts) {\n        return NULL;\n    }\n\n    rctx = ctx->rec.elts;\n\n    return &rctx[n];\n}\n\n\nngx_int_t\nngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n, ngx_str_t *path)\n{\n    ngx_rtmp_record_rec_ctx_t      *rctx;\n    ngx_int_t                       rc;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: #%ui manual open\", n);\n\n    rctx = ngx_rtmp_record_get_node_ctx(s, n);\n\n    if (rctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    rc = ngx_rtmp_record_node_open(s, rctx);\n    if (rc != NGX_OK) {\n        return rc;\n    }\n\n    if (path) {\n        ngx_rtmp_record_make_path(s, rctx, path);\n    }\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n, ngx_str_t *path)\n{\n    ngx_rtmp_record_rec_ctx_t      *rctx;\n    ngx_int_t                       rc;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: #%ui manual close\", n);\n\n    rctx = ngx_rtmp_record_get_node_ctx(s, n);\n\n    if (rctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    rc = ngx_rtmp_record_node_close(s, rctx);\n    if (rc != NGX_OK) {\n        return rc;\n    }\n\n    if (path) {\n        ngx_rtmp_record_make_path(s, rctx, path);\n    }\n\n    return NGX_OK;\n}\n\n\nngx_uint_t\nngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf, ngx_str_t *id)\n{\n    ngx_rtmp_record_app_conf_t    **pracf, *rracf;\n    ngx_uint_t                      n;\n\n    pracf = racf->rec.elts;\n\n    for (n = 0; n < racf->rec.nelts; ++n, ++pracf) {\n        rracf = *pracf;\n\n        if (rracf->id.len == id->len &&\n            ngx_strncmp(rracf->id.data, id->data, id->len) == 0)\n        {\n            return n;\n        }\n    }\n\n    return NGX_CONF_UNSET_UINT;\n}\n\n\n/* This funcion returns pointer to a static buffer */\nstatic void\nngx_rtmp_record_make_path(ngx_rtmp_session_t *s,\n                          ngx_rtmp_record_rec_ctx_t *rctx, ngx_str_t *path)\n{\n    ngx_rtmp_record_ctx_t          *ctx;\n    ngx_rtmp_record_app_conf_t     *rracf;\n    u_char                         *p, *l;\n    struct tm                       tm;\n\n    static u_char                   buf[NGX_TIME_T_LEN + 1];\n    static u_char                   pbuf[NGX_MAX_PATH + 1];\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);\n\n    rracf = rctx->conf;\n\n    /* create file path */\n    p = pbuf;\n    l = pbuf + sizeof(pbuf) - 1;\n\n    p = ngx_cpymem(p, rracf->path.data,\n                ngx_min(rracf->path.len, (size_t)(l - p - 1)));\n    *p++ = '/';\n    p = (u_char *)ngx_escape_uri(p, ctx->name, ngx_min(ngx_strlen(ctx->name),\n                (size_t)(l - p)), NGX_ESCAPE_URI_COMPONENT);\n\n    /* append timestamp */\n    if (rracf->unique) {\n        p = ngx_cpymem(p, buf, ngx_min(ngx_sprintf(buf, \"-%T\",\n                       rctx->timestamp) - buf, l - p));\n    }\n\n    if (ngx_strchr(rracf->suffix.data, '%')) {\n        ngx_libc_localtime(rctx->timestamp, &tm);\n        p += strftime((char *) p, l - p, (char *) rracf->suffix.data, &tm);\n    } else {\n        p = ngx_cpymem(p, rracf->suffix.data,\n                ngx_min(rracf->suffix.len, (size_t)(l - p)));\n    }\n\n    *p = 0;\n    path->data = pbuf;\n    path->len  = p - pbuf;\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: %V path: '%V'\", &rracf->id, path);\n}\n\n\nstatic void\nngx_rtmp_record_notify_error(ngx_rtmp_session_t *s,\n                             ngx_rtmp_record_rec_ctx_t *rctx)\n{\n    ngx_rtmp_record_app_conf_t *rracf = rctx->conf;\n\n    rctx->failed = 1;\n\n    if (!rracf->notify) {\n        return;\n    }\n\n    ngx_rtmp_send_status(s, \"NetStream.Record.Failed\", \"error\",\n                         rracf->id.data ? (char *) rracf->id.data : \"\");\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_node_open(ngx_rtmp_session_t *s,\n                          ngx_rtmp_record_rec_ctx_t *rctx)\n{\n    ngx_rtmp_record_app_conf_t *rracf;\n    ngx_err_t                   err;\n    ngx_str_t                   path;\n    ngx_int_t                   mode, create_mode;\n    u_char                      buf[8], *p;\n    off_t                       file_size;\n    uint32_t                    tag_size, mlen, timestamp;\n\n    rracf = rctx->conf;\n    tag_size = 0;\n\n    if (rctx->file.fd != NGX_INVALID_FILE) {\n        return NGX_AGAIN;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: %V opening\", &rracf->id);\n\n    ngx_memzero(rctx, sizeof(*rctx));\n    rctx->conf = rracf;\n    rctx->last = *ngx_cached_time;\n    rctx->timestamp = ngx_cached_time->sec;\n\n    ngx_rtmp_record_make_path(s, rctx, &path);\n\n    mode = rracf->append ? NGX_FILE_RDWR : NGX_FILE_WRONLY;\n    create_mode = rracf->append ? NGX_FILE_CREATE_OR_OPEN : NGX_FILE_TRUNCATE;\n\n    ngx_memzero(&rctx->file, sizeof(rctx->file));\n    rctx->file.offset = 0;\n    rctx->file.log = s->connection->log;\n    rctx->file.fd = ngx_open_file(path.data, mode, create_mode,\n                                  NGX_FILE_DEFAULT_ACCESS);\n    ngx_str_set(&rctx->file.name, \"recorded\");\n\n    if (rctx->file.fd == NGX_INVALID_FILE) {\n        err = ngx_errno;\n\n        if (err != NGX_ENOENT) {\n            ngx_log_error(NGX_LOG_CRIT, s->connection->log, err,\n                          \"record: %V failed to open file '%V'\",\n                          &rracf->id, &path);\n        }\n\n        ngx_rtmp_record_notify_error(s, rctx);\n\n        return NGX_OK;\n    }\n\n#if !(NGX_WIN32)\n    if (rracf->lock_file) {\n        err = ngx_lock_fd(rctx->file.fd);\n        if (err) {\n            ngx_log_error(NGX_LOG_CRIT, s->connection->log, err,\n                          \"record: %V lock failed\", &rracf->id);\n        }\n    }\n#endif\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: %V opened '%V'\", &rracf->id, &path);\n\n    if (rracf->notify) {\n        ngx_rtmp_send_status(s, \"NetStream.Record.Start\", \"status\",\n                             rracf->id.data ? (char *) rracf->id.data : \"\");\n    }\n\n    if (rracf->append) {\n\n        file_size = 0;\n        timestamp = 0;\n\n#if (NGX_WIN32)\n        {\n            LONG  lo, hi;\n\n            lo = 0;\n            hi = 0;\n            lo = SetFilePointer(rctx->file.fd, lo, &hi, FILE_END);\n            file_size = (lo == INVALID_SET_FILE_POINTER ?\n                         (off_t) -1 : (off_t) hi << 32 | (off_t) lo);\n        }\n#else\n        file_size = lseek(rctx->file.fd, 0, SEEK_END);\n#endif\n        if (file_size == (off_t) -1) {\n            ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno,\n                          \"record: %V seek failed\", &rracf->id);\n            goto done;\n        }\n\n        if (file_size < 4) {\n            goto done;\n        }\n\n        if (ngx_read_file(&rctx->file, buf, 4, file_size - 4) != 4) {\n            ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno,\n                          \"record: %V tag size read failed\", &rracf->id);\n            goto done;\n        }\n\n        p = (u_char *) &tag_size;\n        p[0] = buf[3];\n        p[1] = buf[2];\n        p[2] = buf[1];\n        p[3] = buf[0];\n\n        if (tag_size == 0 || tag_size + 4 > file_size) {\n            file_size = 0;\n            goto done;\n        }\n\n        if (ngx_read_file(&rctx->file, buf, 8, file_size - tag_size - 4) != 8)\n        {\n            ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno,\n                          \"record: %V tag read failed\", &rracf->id);\n            goto done;\n        }\n\n        p = (u_char *) &mlen;\n        p[0] = buf[3];\n        p[1] = buf[2];\n        p[2] = buf[1];\n        p[3] = 0;\n\n        if (tag_size != mlen + 11) {\n            ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno,\n                          \"record: %V tag size mismatch: \"\n                          \"tag_size=%uD, mlen=%uD\", &rracf->id, tag_size, mlen);\n            goto done;\n        }\n\n        p = (u_char *) &timestamp;\n        p[3] = buf[7];\n        p[0] = buf[6];\n        p[1] = buf[5];\n        p[2] = buf[4];\n\ndone:\n        rctx->file.offset = file_size;\n        rctx->time_shift = timestamp;\n\n        ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"record: append offset=%O, time=%uD, tag_size=%uD\",\n                       file_size, timestamp, tag_size);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_init(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_record_app_conf_t     *racf, **rracf;\n    ngx_rtmp_record_rec_ctx_t      *rctx;\n    ngx_rtmp_record_ctx_t          *ctx;\n    ngx_uint_t                      n;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);\n\n    if (ctx) {\n        return NGX_OK;\n    }\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module);\n\n    if (racf == NULL || racf->rec.nelts == 0) {\n        return NGX_OK;\n    }\n\n    ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_record_ctx_t));\n\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_record_module);\n\n    if (ngx_array_init(&ctx->rec, s->connection->pool, racf->rec.nelts,\n                       sizeof(ngx_rtmp_record_rec_ctx_t))\n        != NGX_OK)\n    {\n        return NGX_ERROR;\n    }\n\n    rracf = racf->rec.elts;\n\n    rctx = ngx_array_push_n(&ctx->rec, racf->rec.nelts);\n\n    if (rctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    for (n = 0; n < racf->rec.nelts; ++n, ++rracf, ++rctx) {\n        ngx_memzero(rctx, sizeof(*rctx));\n\n        rctx->conf = *rracf;\n        rctx->file.fd = NGX_INVALID_FILE;\n    }\n\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_record_start(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_record_app_conf_t     *racf;\n    ngx_rtmp_record_rec_ctx_t      *rctx;\n    ngx_rtmp_record_ctx_t          *ctx;\n    ngx_uint_t                      n;\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module);\n    if (racf == NULL || racf->rec.nelts == 0) {\n        return;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);\n    if (ctx == NULL) {\n        return;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: start\");\n\n    rctx = ctx->rec.elts;\n    for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) {\n        if (rctx->conf->flags & (NGX_RTMP_RECORD_OFF|NGX_RTMP_RECORD_MANUAL)) {\n            continue;\n        }\n        ngx_rtmp_record_node_open(s, rctx);\n    }\n}\n\n\nstatic void\nngx_rtmp_record_stop(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_record_app_conf_t     *racf;\n    ngx_rtmp_record_rec_ctx_t      *rctx;\n    ngx_rtmp_record_ctx_t          *ctx;\n    ngx_uint_t                      n;\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module);\n    if (racf == NULL || racf->rec.nelts == 0) {\n        return;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);\n    if (ctx == NULL) {\n        return;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: stop\");\n\n    rctx = ctx->rec.elts;\n    for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) {\n        ngx_rtmp_record_node_close(s, rctx);\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    ngx_rtmp_record_app_conf_t     *racf;\n    ngx_rtmp_record_ctx_t          *ctx;\n    u_char                         *p;\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_record_module);\n\n    if (racf == NULL || racf->rec.nelts == 0) {\n        goto next;\n    }\n\n    if (ngx_rtmp_record_init(s) != NGX_OK) {\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: publish %ui nodes\",\n                   racf->rec.nelts);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);\n\n    ngx_memcpy(ctx->name, v->name, sizeof(ctx->name));\n    ngx_memcpy(ctx->args, v->args, sizeof(ctx->args));\n\n    /* terminate name on /../ */\n    for (p = ctx->name; *p; ++p) {\n        if (ngx_path_separator(p[0]) &&\n            p[1] == '.' && p[2] == '.' &&\n            ngx_path_separator(p[3]))\n        {\n            *p = 0;\n            break;\n        }\n    }\n\n    ngx_rtmp_record_start(s);\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_stream_begin(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)\n{\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: stream_begin\");\n\n    ngx_rtmp_record_start(s);\n\nnext:\n    return next_stream_begin(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_stream_eof(ngx_rtmp_session_t *s, ngx_rtmp_stream_begin_t *v)\n{\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: stream_eof\");\n\n    ngx_rtmp_record_stop(s);\n\nnext:\n    return next_stream_eof(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_node_close(ngx_rtmp_session_t *s,\n                           ngx_rtmp_record_rec_ctx_t *rctx)\n{\n    ngx_rtmp_record_app_conf_t *rracf;\n    ngx_err_t                   err;\n    void                      **app_conf;\n    ngx_int_t                   rc;\n    ngx_rtmp_record_done_t      v;\n    u_char                      av;\n\n    rracf = rctx->conf;\n\n    if (rctx->file.fd == NGX_INVALID_FILE) {\n        return NGX_AGAIN;\n    }\n\n    if (rctx->initialized) {\n        av = 0;\n\n        if (rctx->video) {\n            av |= 0x01;\n        }\n\n        if (rctx->audio) {\n            av |= 0x04;\n        }\n\n        if (ngx_write_file(&rctx->file, &av, 1, 4) == NGX_ERROR) {\n            ngx_log_error(NGX_LOG_CRIT, s->connection->log, ngx_errno,\n                          \"record: %V error writing av mask\", &rracf->id);\n        }\n    }\n\n    if (ngx_close_file(rctx->file.fd) == NGX_FILE_ERROR) {\n        err = ngx_errno;\n        ngx_log_error(NGX_LOG_CRIT, s->connection->log, err,\n                      \"record: %V error closing file\", &rracf->id);\n\n        ngx_rtmp_record_notify_error(s, rctx);\n    }\n\n    rctx->file.fd = NGX_INVALID_FILE;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: %V closed\", &rracf->id);\n\n    if (rracf->notify) {\n        ngx_rtmp_send_status(s, \"NetStream.Record.Stop\", \"status\",\n                             rracf->id.data ? (char *) rracf->id.data : \"\");\n    }\n\n    app_conf = s->app_conf;\n\n    if (rracf->rec_conf) {\n        s->app_conf = rracf->rec_conf;\n    }\n\n    v.recorder = rracf->id;\n    ngx_rtmp_record_make_path(s, rctx, &v.path);\n\n    rc = ngx_rtmp_record_done(s, &v);\n\n    s->app_conf = app_conf;\n\n    return rc;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_close_stream(ngx_rtmp_session_t *s,\n                             ngx_rtmp_close_stream_t *v)\n{\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: close_stream\");\n\n    ngx_rtmp_record_stop(s);\n\nnext:\n    return next_close_stream(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_write_frame(ngx_rtmp_session_t *s,\n                            ngx_rtmp_record_rec_ctx_t *rctx,\n                            ngx_rtmp_header_t *h, ngx_chain_t *in,\n                            ngx_int_t inc_nframes)\n{\n    u_char                      hdr[11], *p, *ph;\n    uint32_t                    timestamp, tag_size;\n    ngx_rtmp_record_app_conf_t *rracf;\n\n    rracf = rctx->conf;\n\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"record: %V frame: mlen=%uD\",\n                   &rracf->id, h->mlen);\n\n    if (h->type == NGX_RTMP_MSG_VIDEO) {\n        rctx->video = 1;\n    } else {\n        rctx->audio = 1;\n    }\n\n    timestamp = h->timestamp - rctx->epoch;\n\n    if ((int32_t) timestamp < 0) {\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                       \"record: %V cut timestamp=%D\", &rracf->id, timestamp);\n\n        timestamp = 0;\n    }\n\n    /* write tag header */\n    ph = hdr;\n\n    *ph++ = (u_char)h->type;\n\n    p = (u_char*)&h->mlen;\n    *ph++ = p[2];\n    *ph++ = p[1];\n    *ph++ = p[0];\n\n    p = (u_char*)&timestamp;\n    *ph++ = p[2];\n    *ph++ = p[1];\n    *ph++ = p[0];\n    *ph++ = p[3];\n\n    *ph++ = 0;\n    *ph++ = 0;\n    *ph++ = 0;\n\n    tag_size = (ph - hdr) + h->mlen;\n\n    if (ngx_write_file(&rctx->file, hdr, ph - hdr, rctx->file.offset)\n        == NGX_ERROR)\n    {\n        ngx_rtmp_record_notify_error(s, rctx);\n\n        ngx_close_file(rctx->file.fd);\n\n        return NGX_ERROR;\n    }\n\n    /* write tag body\n     * FIXME: NGINX\n     * ngx_write_chain seems to fit best\n     * but it suffers from uncontrollable\n     * allocations.\n     * we're left with plain writing */\n    for(; in; in = in->next) {\n        if (in->buf->pos == in->buf->last) {\n            continue;\n        }\n\n        if (ngx_write_file(&rctx->file, in->buf->pos, in->buf->last\n                           - in->buf->pos, rctx->file.offset)\n            == NGX_ERROR)\n        {\n            return NGX_ERROR;\n        }\n    }\n\n    /* write tag size */\n    ph = hdr;\n    p = (u_char*)&tag_size;\n\n    *ph++ = p[3];\n    *ph++ = p[2];\n    *ph++ = p[1];\n    *ph++ = p[0];\n\n    if (ngx_write_file(&rctx->file, hdr, ph - hdr,\n                       rctx->file.offset)\n        == NGX_ERROR)\n    {\n        return NGX_ERROR;\n    }\n\n    rctx->nframes += inc_nframes;\n\n    /* watch max size */\n    if ((rracf->max_size && rctx->file.offset >= (ngx_int_t) rracf->max_size) ||\n        (rracf->max_frames && rctx->nframes >= rracf->max_frames))\n    {\n        ngx_rtmp_record_node_close(s, rctx);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic size_t\nngx_rtmp_record_get_chain_mlen(ngx_chain_t *in)\n{\n    size_t                          ret;\n\n    for (ret = 0; in; in = in->next) {\n        ret += (in->buf->last - in->buf->pos);\n    }\n\n    return ret;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_av(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                   ngx_chain_t *in)\n{\n    ngx_rtmp_record_ctx_t          *ctx;\n    ngx_rtmp_record_rec_ctx_t      *rctx;\n    ngx_uint_t                      n;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_record_module);\n\n    if (ctx == NULL) {\n        return NGX_OK;\n    }\n\n    rctx = ctx->rec.elts;\n\n    for (n = 0; n < ctx->rec.nelts; ++n, ++rctx) {\n        ngx_rtmp_record_node_av(s, rctx, h, in);\n    }\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_node_av(ngx_rtmp_session_t *s, ngx_rtmp_record_rec_ctx_t *rctx,\n                        ngx_rtmp_header_t *h, ngx_chain_t *in)\n{\n    ngx_time_t                      next;\n    ngx_rtmp_header_t               ch;\n    ngx_rtmp_codec_ctx_t           *codec_ctx;\n    ngx_int_t                       keyframe, brkframe;\n    ngx_rtmp_record_app_conf_t     *rracf;\n\n    rracf = rctx->conf;\n\n    if (rracf->flags & NGX_RTMP_RECORD_OFF) {\n        ngx_rtmp_record_node_close(s, rctx);\n        return NGX_OK;\n    }\n\n    keyframe = (h->type == NGX_RTMP_MSG_VIDEO)\n             ? (ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME)\n             : 0;\n\n    brkframe = (h->type == NGX_RTMP_MSG_VIDEO)\n             ? keyframe\n             : (rracf->flags & NGX_RTMP_RECORD_VIDEO) == 0;\n\n    if (brkframe && (rracf->flags & NGX_RTMP_RECORD_MANUAL) == 0) {\n\n        if (rracf->interval != (ngx_msec_t) NGX_CONF_UNSET) {\n\n            next = rctx->last;\n            next.msec += rracf->interval;\n            next.sec  += (next.msec / 1000);\n            next.msec %= 1000;\n\n            if (ngx_cached_time->sec  > next.sec ||\n               (ngx_cached_time->sec == next.sec &&\n                ngx_cached_time->msec > next.msec))\n            {\n                ngx_rtmp_record_node_close(s, rctx);\n                ngx_rtmp_record_node_open(s, rctx);\n            }\n\n        } else if (!rctx->failed) {\n            ngx_rtmp_record_node_open(s, rctx);\n        }\n    }\n\n    if ((rracf->flags & NGX_RTMP_RECORD_MANUAL) &&\n        !brkframe && rctx->nframes == 0)\n    {\n        return NGX_OK;\n    }\n\n    if (rctx->file.fd == NGX_INVALID_FILE) {\n        return NGX_OK;\n    }\n\n    if (h->type == NGX_RTMP_MSG_AUDIO &&\n       (rracf->flags & NGX_RTMP_RECORD_AUDIO) == 0)\n    {\n        return NGX_OK;\n    }\n\n    if (h->type == NGX_RTMP_MSG_VIDEO &&\n       (rracf->flags & NGX_RTMP_RECORD_VIDEO) == 0 &&\n       ((rracf->flags & NGX_RTMP_RECORD_KEYFRAMES) == 0 || !keyframe))\n    {\n        return NGX_OK;\n    }\n\n    if (!rctx->initialized) {\n\n        rctx->initialized = 1;\n        rctx->epoch = h->timestamp - rctx->time_shift;\n\n        if (rctx->file.offset == 0 &&\n            ngx_rtmp_record_write_header(&rctx->file) != NGX_OK)\n        {\n            ngx_rtmp_record_node_close(s, rctx);\n            return NGX_OK;\n        }\n    }\n\n    codec_ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n    if (codec_ctx) {\n        ch = *h;\n\n        /* AAC header */\n        if (!rctx->aac_header_sent && codec_ctx->aac_header &&\n           (rracf->flags & NGX_RTMP_RECORD_AUDIO))\n        {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"record: %V writing AAC header\", &rracf->id);\n\n            ch.type = NGX_RTMP_MSG_AUDIO;\n            ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->aac_header);\n\n            if (ngx_rtmp_record_write_frame(s, rctx, &ch,\n                                            codec_ctx->aac_header, 0)\n                != NGX_OK)\n            {\n                return NGX_OK;\n            }\n\n            rctx->aac_header_sent = 1;\n        }\n\n        /* AVC header */\n        if (!rctx->avc_header_sent && codec_ctx->avc_header &&\n           (rracf->flags & (NGX_RTMP_RECORD_VIDEO|\n                            NGX_RTMP_RECORD_KEYFRAMES)))\n        {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"record: %V writing AVC header\", &rracf->id);\n\n            ch.type = NGX_RTMP_MSG_VIDEO;\n            ch.mlen = ngx_rtmp_record_get_chain_mlen(codec_ctx->avc_header);\n\n            if (ngx_rtmp_record_write_frame(s, rctx, &ch,\n                                            codec_ctx->avc_header, 0)\n                != NGX_OK)\n            {\n                return NGX_OK;\n            }\n\n            rctx->avc_header_sent = 1;\n        }\n    }\n\n    if (h->type == NGX_RTMP_MSG_VIDEO) {\n        if (codec_ctx && codec_ctx->video_codec_id == NGX_RTMP_VIDEO_H264 &&\n            !rctx->avc_header_sent)\n        {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"record: %V skipping until H264 header\", &rracf->id);\n            return NGX_OK;\n        }\n\n        if (ngx_rtmp_get_video_frame_type(in) == NGX_RTMP_VIDEO_KEY_FRAME &&\n            ((codec_ctx && codec_ctx->video_codec_id != NGX_RTMP_VIDEO_H264) ||\n             !ngx_rtmp_is_codec_header(in)))\n        {\n            rctx->video_key_sent = 1;\n        }\n\n        if (!rctx->video_key_sent) {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"record: %V skipping until keyframe\", &rracf->id);\n            return NGX_OK;\n        }\n\n    } else {\n        if (codec_ctx && codec_ctx->audio_codec_id == NGX_RTMP_AUDIO_AAC &&\n            !rctx->aac_header_sent)\n        {\n            ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                           \"record: %V skipping until AAC header\", &rracf->id);\n            return NGX_OK;\n        }\n    }\n\n    return ngx_rtmp_record_write_frame(s, rctx, h, in, 1);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_done_init(ngx_rtmp_session_t *s, ngx_rtmp_record_done_t *v)\n{\n    return NGX_OK;\n}\n\n\nstatic char *\nngx_rtmp_record_recorder(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    char                       *rv;\n    ngx_int_t                   i;\n    ngx_str_t                  *value;\n    ngx_conf_t                  save;\n    ngx_module_t              **modules;\n    ngx_rtmp_module_t          *module;\n    ngx_rtmp_core_app_conf_t   *cacf, **pcacf, *rcacf;\n    ngx_rtmp_record_app_conf_t *racf, **pracf, *rracf;\n    ngx_rtmp_conf_ctx_t        *ctx, *pctx;\n\n    value = cf->args->elts;\n\n    cacf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_core_module);\n\n    racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_record_module);\n\n    ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_conf_ctx_t));\n    if (ctx == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    pctx = cf->ctx;\n\n    ctx->main_conf = pctx->main_conf;\n    ctx->srv_conf  = pctx->srv_conf;\n\n    ctx->app_conf = ngx_pcalloc(cf->pool, sizeof(void *) * ngx_rtmp_max_module);\n    if (ctx->app_conf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n#if (nginx_version >= 1009011)\n    modules = cf->cycle->modules;\n#else\n    modules = ngx_modules;\n#endif\n\n    for (i = 0; modules[i]; i++) {\n        if (modules[i]->type != NGX_RTMP_MODULE) {\n            continue;\n        }\n\n        module = modules[i]->ctx;\n\n        if (module->create_app_conf) {\n            ctx->app_conf[modules[i]->ctx_index] = module->create_app_conf(cf);\n            if (ctx->app_conf[modules[i]->ctx_index] == NULL) {\n                return NGX_CONF_ERROR;\n            }\n        }\n    }\n\n    /* add to sub-applications */\n    rcacf = ctx->app_conf[ngx_rtmp_core_module.ctx_index];\n    rcacf->app_conf = ctx->app_conf;\n    pcacf = ngx_array_push(&cacf->applications);\n    if (pcacf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n    *pcacf = rcacf;\n\n    /* add to recorders */\n    rracf = ctx->app_conf[ngx_rtmp_record_module.ctx_index];\n    rracf->rec_conf = ctx->app_conf;\n    pracf = ngx_array_push(&racf->rec);\n    if (pracf == NULL) {\n        return NGX_CONF_ERROR;\n    }\n    *pracf = rracf;\n\n    rracf->id = value[1];\n\n\n    save = *cf;\n    cf->ctx = ctx;\n    cf->cmd_type = NGX_RTMP_REC_CONF;\n\n    rv = ngx_conf_parse(cf, NULL);\n    *cf= save;\n\n    return rv;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_record_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t          *cmcf;\n    ngx_rtmp_handler_pt                *h;\n\n    ngx_rtmp_record_done = ngx_rtmp_record_done_init;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_AUDIO]);\n    *h = ngx_rtmp_record_av;\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_MSG_VIDEO]);\n    *h = ngx_rtmp_record_av;\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_record_publish;\n\n    next_close_stream = ngx_rtmp_close_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_record_close_stream;\n\n    next_stream_begin = ngx_rtmp_stream_begin;\n    ngx_rtmp_stream_begin = ngx_rtmp_record_stream_begin;\n\n    next_stream_eof = ngx_rtmp_stream_eof;\n    ngx_rtmp_stream_eof = ngx_rtmp_record_stream_eof;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_record_module.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_RECORD_H_INCLUDED_\n#define _NGX_RTMP_RECORD_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\n#define NGX_RTMP_RECORD_OFF             0x01\n#define NGX_RTMP_RECORD_AUDIO           0x02\n#define NGX_RTMP_RECORD_VIDEO           0x04\n#define NGX_RTMP_RECORD_KEYFRAMES       0x08\n#define NGX_RTMP_RECORD_MANUAL          0x10\n\n\ntypedef struct {\n    ngx_str_t                           id;\n    ngx_uint_t                          flags;\n    ngx_str_t                           path;\n    size_t                              max_size;\n    size_t                              max_frames;\n    ngx_msec_t                          interval;\n    ngx_str_t                           suffix;\n    ngx_flag_t                          unique;\n    ngx_flag_t                          append;\n    ngx_flag_t                          lock_file;\n    ngx_flag_t                          notify;\n    ngx_url_t                          *url;\n\n    void                              **rec_conf;\n    ngx_array_t                         rec; /* ngx_rtmp_record_app_conf_t * */\n} ngx_rtmp_record_app_conf_t;\n\n\ntypedef struct {\n    ngx_rtmp_record_app_conf_t         *conf;\n    ngx_file_t                          file;\n    ngx_uint_t                          nframes;\n    uint32_t                            epoch, time_shift;\n    ngx_time_t                          last;\n    time_t                              timestamp;\n    unsigned                            failed:1;\n    unsigned                            initialized:1;\n    unsigned                            aac_header_sent:1;\n    unsigned                            avc_header_sent:1;\n    unsigned                            video_key_sent:1;\n    unsigned                            audio:1;\n    unsigned                            video:1;\n} ngx_rtmp_record_rec_ctx_t;\n\n\ntypedef struct {\n    ngx_array_t                         rec; /* ngx_rtmp_record_rec_ctx_t */\n    u_char                              name[NGX_RTMP_MAX_NAME];\n    u_char                              args[NGX_RTMP_MAX_ARGS];\n} ngx_rtmp_record_ctx_t;\n\n\nngx_uint_t ngx_rtmp_record_find(ngx_rtmp_record_app_conf_t *racf,\n           ngx_str_t *id);\n\n\n/* Manual recording control,\n * 'n' is record node index in config array.\n * Note: these functions allocate path in static buffer */\n\nngx_int_t ngx_rtmp_record_open(ngx_rtmp_session_t *s, ngx_uint_t n,\n          ngx_str_t *path);\nngx_int_t ngx_rtmp_record_close(ngx_rtmp_session_t *s, ngx_uint_t n,\n          ngx_str_t *path);\n\n\ntypedef struct {\n    ngx_str_t                           recorder;\n    ngx_str_t                           path;\n} ngx_rtmp_record_done_t;\n\n\ntypedef ngx_int_t (*ngx_rtmp_record_done_pt)(ngx_rtmp_session_t *s,\n        ngx_rtmp_record_done_t *v);\n\n\nextern ngx_rtmp_record_done_pt          ngx_rtmp_record_done;\n\n\nextern ngx_module_t                     ngx_rtmp_record_module;\n\n\n#endif /* _NGX_RTMP_RECORD_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_relay_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp_relay_module.h\"\n#include \"ngx_rtmp_cmd_module.h\"\n\n\nstatic ngx_rtmp_publish_pt          next_publish;\nstatic ngx_rtmp_play_pt             next_play;\nstatic ngx_rtmp_delete_stream_pt    next_delete_stream;\nstatic ngx_rtmp_close_stream_pt     next_close_stream;\n\n\nstatic ngx_int_t ngx_rtmp_relay_init_process(ngx_cycle_t *cycle);\nstatic ngx_int_t ngx_rtmp_relay_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_relay_create_app_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_relay_merge_app_conf(ngx_conf_t *cf,\n       void *parent, void *child);\nstatic char * ngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd,\n       void *conf);\nstatic ngx_int_t ngx_rtmp_relay_publish(ngx_rtmp_session_t *s,\n       ngx_rtmp_publish_t *v);\nstatic ngx_rtmp_relay_ctx_t * ngx_rtmp_relay_create_connection(\n       ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name,\n       ngx_rtmp_relay_target_t *target);\n\n\n/*                _____\n * =push=        |     |---publish--->\n * ---publish--->|     |---publish--->\n *     (src)     |     |---publish--->\n *                -----  (next,relay)\n *                      need reconnect\n * =pull=         _____\n * -----play---->|     |\n * -----play---->|     |----play----->\n * -----play---->|     | (src,relay)\n *     (next)     -----\n */\n\n\ntypedef struct {\n    ngx_array_t                 pulls;         /* ngx_rtmp_relay_target_t * */\n    ngx_array_t                 pushes;        /* ngx_rtmp_relay_target_t * */\n    ngx_array_t                 static_pulls;  /* ngx_rtmp_relay_target_t * */\n    ngx_array_t                 static_events; /* ngx_event_t * */\n    ngx_log_t                  *log;\n    ngx_uint_t                  nbuckets;\n    ngx_msec_t                  buflen;\n    ngx_flag_t                  session_relay;\n    ngx_msec_t                  push_reconnect;\n    ngx_msec_t                  pull_reconnect;\n    ngx_rtmp_relay_ctx_t        **ctx;\n} ngx_rtmp_relay_app_conf_t;\n\n\ntypedef struct {\n    ngx_rtmp_conf_ctx_t         cctx;\n    ngx_rtmp_relay_target_t    *target;\n} ngx_rtmp_relay_static_t;\n\n\n#define NGX_RTMP_RELAY_CONNECT_TRANS            1\n#define NGX_RTMP_RELAY_CREATE_STREAM_TRANS      2\n\n\n#define NGX_RTMP_RELAY_CSID_AMF_INI             3\n#define NGX_RTMP_RELAY_CSID_AMF                 5\n#define NGX_RTMP_RELAY_MSID                     1\n\n\n/* default flashVer */\n#define NGX_RTMP_RELAY_FLASHVER                 \"LNX.11,1,102,55\"\n\n\nstatic ngx_command_t  ngx_rtmp_relay_commands[] = {\n\n    { ngx_string(\"push\"),\n      NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_relay_push_pull,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"pull\"),\n      NGX_RTMP_APP_CONF|NGX_CONF_1MORE,\n      ngx_rtmp_relay_push_pull,\n      NGX_RTMP_APP_CONF_OFFSET,\n      0,\n      NULL },\n\n    { ngx_string(\"relay_buffer\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_relay_app_conf_t, buflen),\n      NULL },\n\n    { ngx_string(\"push_reconnect\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_relay_app_conf_t, push_reconnect),\n      NULL },\n\n    { ngx_string(\"pull_reconnect\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_msec_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_relay_app_conf_t, pull_reconnect),\n      NULL },\n\n    { ngx_string(\"session_relay\"),\n      NGX_RTMP_MAIN_CONF|NGX_RTMP_SRV_CONF|NGX_RTMP_APP_CONF|NGX_CONF_TAKE1,\n      ngx_conf_set_flag_slot,\n      NGX_RTMP_APP_CONF_OFFSET,\n      offsetof(ngx_rtmp_relay_app_conf_t, session_relay),\n      NULL },\n\n\n      ngx_null_command\n};\n\n\nstatic ngx_rtmp_module_t  ngx_rtmp_relay_module_ctx = {\n    NULL,                                   /* preconfiguration */\n    ngx_rtmp_relay_postconfiguration,       /* postconfiguration */\n    NULL,                                   /* create main configuration */\n    NULL,                                   /* init main configuration */\n    NULL,                                   /* create server configuration */\n    NULL,                                   /* merge server configuration */\n    ngx_rtmp_relay_create_app_conf,         /* create app configuration */\n    ngx_rtmp_relay_merge_app_conf           /* merge app configuration */\n};\n\n\nngx_module_t  ngx_rtmp_relay_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_relay_module_ctx,             /* module context */\n    ngx_rtmp_relay_commands,                /* module directives */\n    NGX_RTMP_MODULE,                        /* module type */\n    NULL,                                   /* init master */\n    NULL,                                   /* init module */\n    ngx_rtmp_relay_init_process,            /* init process */\n    NULL,                                   /* init thread */\n    NULL,                                   /* exit thread */\n    NULL,                                   /* exit process */\n    NULL,                                   /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\nstatic void *\nngx_rtmp_relay_create_app_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_relay_app_conf_t     *racf;\n\n    racf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_app_conf_t));\n    if (racf == NULL) {\n        return NULL;\n    }\n\n    if (ngx_array_init(&racf->pushes, cf->pool, 1, sizeof(void *)) != NGX_OK) {\n        return NULL;\n    }\n\n    if (ngx_array_init(&racf->pulls, cf->pool, 1, sizeof(void *)) != NGX_OK) {\n        return NULL;\n    }\n\n    if (ngx_array_init(&racf->static_pulls, cf->pool, 1, sizeof(void *))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n    if (ngx_array_init(&racf->static_events, cf->pool, 1, sizeof(void *))\n        != NGX_OK)\n    {\n        return NULL;\n    }\n\n    racf->nbuckets = 1024;\n    racf->log = &cf->cycle->new_log;\n    racf->buflen = NGX_CONF_UNSET_MSEC;\n    racf->session_relay = NGX_CONF_UNSET;\n    racf->push_reconnect = NGX_CONF_UNSET_MSEC;\n    racf->pull_reconnect = NGX_CONF_UNSET_MSEC;\n\n    return racf;\n}\n\n\nstatic char *\nngx_rtmp_relay_merge_app_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_relay_app_conf_t  *prev = parent;\n    ngx_rtmp_relay_app_conf_t  *conf = child;\n\n    conf->ctx = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_ctx_t *)\n            * conf->nbuckets);\n\n    ngx_conf_merge_value(conf->session_relay, prev->session_relay, 0);\n    ngx_conf_merge_msec_value(conf->buflen, prev->buflen, 5000);\n    ngx_conf_merge_msec_value(conf->push_reconnect, prev->push_reconnect,\n            3000);\n    ngx_conf_merge_msec_value(conf->pull_reconnect, prev->pull_reconnect,\n            3000);\n\n    return NGX_CONF_OK;\n}\n\n\nstatic void\nngx_rtmp_relay_static_pull_reconnect(ngx_event_t *ev)\n{\n    ngx_rtmp_relay_static_t    *rs = ev->data;\n\n    ngx_rtmp_relay_ctx_t       *ctx;\n    ngx_rtmp_relay_app_conf_t  *racf;\n\n    racf = ngx_rtmp_get_module_app_conf(&rs->cctx, ngx_rtmp_relay_module);\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0,\n                   \"relay: reconnecting static pull\");\n\n    ctx = ngx_rtmp_relay_create_connection(&rs->cctx, &rs->target->name,\n                                           rs->target);\n    if (ctx) {\n        ctx->session->static_relay = 1;\n        ctx->static_evt = ev;\n        return;\n    }\n\n    ngx_add_timer(ev, racf->pull_reconnect);\n}\n\n\nstatic void\nngx_rtmp_relay_push_reconnect(ngx_event_t *ev)\n{\n    ngx_rtmp_session_t             *s = ev->data;\n\n    ngx_rtmp_relay_app_conf_t      *racf;\n    ngx_rtmp_relay_ctx_t           *ctx, *pctx;\n    ngx_uint_t                      n;\n    ngx_rtmp_relay_target_t        *target, **t;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"relay: push reconnect\");\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL) {\n        return;\n    }\n\n    t = racf->pushes.elts;\n    for (n = 0; n < racf->pushes.nelts; ++n, ++t) {\n        target = *t;\n\n        if (target->name.len && (ctx->name.len != target->name.len ||\n            ngx_memcmp(ctx->name.data, target->name.data, ctx->name.len)))\n        {\n            continue;\n        }\n\n        for (pctx = ctx->play; pctx; pctx = pctx->next) {\n            if (pctx->tag == &ngx_rtmp_relay_module &&\n                pctx->data == target)\n            {\n                break;\n            }\n        }\n\n        if (pctx) {\n            continue;\n        }\n\n        if (ngx_rtmp_relay_push(s, &ctx->name, target) == NGX_OK) {\n            continue;\n        }\n\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                \"relay: push reconnect failed name='%V' app='%V' \"\n                \"playpath='%V' url='%V'\",\n                &ctx->name, &target->app, &target->play_path,\n                &target->url.url);\n\n        if (!ctx->push_evt.timer_set) {\n            ngx_add_timer(&ctx->push_evt, racf->push_reconnect);\n        }\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_get_peer(ngx_peer_connection_t *pc, void *data)\n{\n    return NGX_OK;\n}\n\n\nstatic void\nngx_rtmp_relay_free_peer(ngx_peer_connection_t *pc, void *data,\n            ngx_uint_t state)\n{\n}\n\n\ntypedef ngx_rtmp_relay_ctx_t * (* ngx_rtmp_relay_create_ctx_pt)\n    (ngx_rtmp_session_t *s, ngx_str_t *name, ngx_rtmp_relay_target_t *target);\n\n\nstatic ngx_int_t\nngx_rtmp_relay_copy_str(ngx_pool_t *pool, ngx_str_t *dst, ngx_str_t *src)\n{\n    if (src->len == 0) {\n        return NGX_OK;\n    }\n    dst->len = src->len;\n    dst->data = ngx_palloc(pool, src->len);\n    if (dst->data == NULL) {\n        return NGX_ERROR;\n    }\n    ngx_memcpy(dst->data, src->data, src->len);\n    return NGX_OK;\n}\n\n\nstatic ngx_rtmp_relay_ctx_t *\nngx_rtmp_relay_create_connection(ngx_rtmp_conf_ctx_t *cctx, ngx_str_t* name,\n        ngx_rtmp_relay_target_t *target)\n{\n    ngx_rtmp_relay_app_conf_t      *racf;\n    ngx_rtmp_relay_ctx_t           *rctx;\n    ngx_rtmp_addr_conf_t           *addr_conf;\n    ngx_rtmp_conf_ctx_t            *addr_ctx;\n    ngx_rtmp_session_t             *rs;\n    ngx_peer_connection_t          *pc;\n    ngx_connection_t               *c;\n    ngx_addr_t                     *addr;\n    ngx_pool_t                     *pool;\n    ngx_int_t                       rc;\n    ngx_str_t                       v, *uri;\n    u_char                         *first, *last, *p;\n\n    racf = ngx_rtmp_get_module_app_conf(cctx, ngx_rtmp_relay_module);\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0,\n                   \"relay: create remote context\");\n\n    pool = NULL;\n    pool = ngx_create_pool(4096, racf->log);\n    if (pool == NULL) {\n        return NULL;\n    }\n\n    rctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_relay_ctx_t));\n    if (rctx == NULL) {\n        goto clear;\n    }\n\n    if (name && ngx_rtmp_relay_copy_str(pool, &rctx->name, name) != NGX_OK) {\n        goto clear;\n    }\n\n    if (ngx_rtmp_relay_copy_str(pool, &rctx->url, &target->url.url) != NGX_OK) {\n        goto clear;\n    }\n\n    rctx->tag = target->tag;\n    rctx->data = target->data;\n\n#define NGX_RTMP_RELAY_STR_COPY(to, from)                                     \\\n    if (ngx_rtmp_relay_copy_str(pool, &rctx->to, &target->from) != NGX_OK) {  \\\n        goto clear;                                                           \\\n    }\n\n    NGX_RTMP_RELAY_STR_COPY(app,        app);\n    NGX_RTMP_RELAY_STR_COPY(tc_url,     tc_url);\n    NGX_RTMP_RELAY_STR_COPY(page_url,   page_url);\n    NGX_RTMP_RELAY_STR_COPY(swf_url,    swf_url);\n    NGX_RTMP_RELAY_STR_COPY(flash_ver,  flash_ver);\n    NGX_RTMP_RELAY_STR_COPY(play_path,  play_path);\n\n    rctx->live  = target->live;\n    rctx->start = target->start;\n    rctx->stop  = target->stop;\n\n#undef NGX_RTMP_RELAY_STR_COPY\n\n    if (rctx->app.len == 0 || rctx->play_path.len == 0) {\n        /* parse uri */\n        uri = &target->url.uri;\n        first = uri->data;\n        last  = uri->data + uri->len;\n        if (first != last && *first == '/') {\n            ++first;\n        }\n\n        if (first != last) {\n\n            /* deduce app */\n            p = ngx_strlchr(first, last, '/');\n            if (p == NULL) {\n                p = last;\n            }\n\n            if (rctx->app.len == 0 && first != p) {\n                v.data = first;\n                v.len = p - first;\n                if (ngx_rtmp_relay_copy_str(pool, &rctx->app, &v) != NGX_OK) {\n                    goto clear;\n                }\n            }\n\n            /* deduce play_path */\n            if (p != last) {\n                ++p;\n            }\n\n            if (rctx->play_path.len == 0 && p != last) {\n                v.data = p;\n                v.len = last - p;\n                if (ngx_rtmp_relay_copy_str(pool, &rctx->play_path, &v)\n                        != NGX_OK)\n                {\n                    goto clear;\n                }\n            }\n        }\n    }\n\n    pc = ngx_pcalloc(pool, sizeof(ngx_peer_connection_t));\n    if (pc == NULL) {\n        goto clear;\n    }\n\n    if (target->url.naddrs == 0) {\n        ngx_log_error(NGX_LOG_ERR, racf->log, 0,\n                      \"relay: no address\");\n        goto clear;\n    }\n\n    /* get address */\n    addr = &target->url.addrs[target->counter % target->url.naddrs];\n    target->counter++;\n\n    /* copy log to keep shared log unchanged */\n    rctx->log = *racf->log;\n    pc->log = &rctx->log;\n    pc->get = ngx_rtmp_relay_get_peer;\n    pc->free = ngx_rtmp_relay_free_peer;\n    pc->name = &addr->name;\n    pc->socklen = addr->socklen;\n    pc->sockaddr = (struct sockaddr *)ngx_palloc(pool, pc->socklen);\n    if (pc->sockaddr == NULL) {\n        goto clear;\n    }\n    ngx_memcpy(pc->sockaddr, addr->sockaddr, pc->socklen);\n\n    rc = ngx_event_connect_peer(pc);\n    if (rc != NGX_OK && rc != NGX_AGAIN ) {\n        ngx_log_debug0(NGX_LOG_DEBUG_RTMP, racf->log, 0,\n                \"relay: connection failed\");\n        goto clear;\n    }\n    c = pc->connection;\n    c->pool = pool;\n    c->addr_text = rctx->url;\n\n    addr_conf = ngx_pcalloc(pool, sizeof(ngx_rtmp_addr_conf_t));\n    if (addr_conf == NULL) {\n        goto clear;\n    }\n    addr_ctx = ngx_pcalloc(pool, sizeof(ngx_rtmp_conf_ctx_t));\n    if (addr_ctx == NULL) {\n        goto clear;\n    }\n    addr_conf->ctx = addr_ctx;\n    addr_ctx->main_conf = cctx->main_conf;\n    addr_ctx->srv_conf  = cctx->srv_conf;\n    ngx_str_set(&addr_conf->addr_text, \"ngx-relay\");\n\n    rs = ngx_rtmp_init_session(c, addr_conf);\n    if (rs == NULL) {\n        /* no need to destroy pool */\n        return NULL;\n    }\n    rs->app_conf = cctx->app_conf;\n    rs->relay = 1;\n    rctx->session = rs;\n    ngx_rtmp_set_ctx(rs, rctx, ngx_rtmp_relay_module);\n    ngx_str_set(&rs->flashver, \"ngx-local-relay\");\n\n#if (NGX_STAT_STUB)\n    (void) ngx_atomic_fetch_add(ngx_stat_active, 1);\n#endif\n\n    ngx_rtmp_client_handshake(rs, 1);\n    return rctx;\n\nclear:\n    if (pool) {\n        ngx_destroy_pool(pool);\n    }\n    return NULL;\n}\n\n\nstatic ngx_rtmp_relay_ctx_t *\nngx_rtmp_relay_create_remote_ctx(ngx_rtmp_session_t *s, ngx_str_t* name,\n        ngx_rtmp_relay_target_t *target)\n{\n    ngx_rtmp_conf_ctx_t         cctx;\n\n    cctx.app_conf = s->app_conf;\n    cctx.srv_conf = s->srv_conf;\n    cctx.main_conf = s->main_conf;\n\n    return ngx_rtmp_relay_create_connection(&cctx, name, target);\n}\n\n\nstatic ngx_rtmp_relay_ctx_t *\nngx_rtmp_relay_create_local_ctx(ngx_rtmp_session_t *s, ngx_str_t *name,\n        ngx_rtmp_relay_target_t *target)\n{\n    ngx_rtmp_relay_ctx_t           *ctx;\n\n    ngx_log_debug0(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"relay: create local context\");\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL) {\n        ctx = ngx_pcalloc(s->connection->pool, sizeof(ngx_rtmp_relay_ctx_t));\n        if (ctx == NULL) {\n            return NULL;\n        }\n        ngx_rtmp_set_ctx(s, ctx, ngx_rtmp_relay_module);\n    }\n    ctx->session = s;\n\n    ctx->push_evt.data = s;\n    ctx->push_evt.log = s->connection->log;\n    ctx->push_evt.handler = ngx_rtmp_relay_push_reconnect;\n\n    if (ctx->publish) {\n        return NULL;\n    }\n\n    if (ngx_rtmp_relay_copy_str(s->connection->pool, &ctx->name, name)\n            != NGX_OK)\n    {\n        return NULL;\n    }\n\n    return ctx;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_create(ngx_rtmp_session_t *s, ngx_str_t *name,\n        ngx_rtmp_relay_target_t *target,\n        ngx_rtmp_relay_create_ctx_pt create_publish_ctx,\n        ngx_rtmp_relay_create_ctx_pt create_play_ctx)\n{\n    ngx_rtmp_relay_app_conf_t      *racf;\n    ngx_rtmp_relay_ctx_t           *publish_ctx, *play_ctx, **cctx;\n    ngx_uint_t                      hash;\n\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);\n    if (racf == NULL) {\n        return NGX_ERROR;\n    }\n\n    play_ctx = create_play_ctx(s, name, target);\n    if (play_ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    hash = ngx_hash_key(name->data, name->len);\n    cctx = &racf->ctx[hash % racf->nbuckets];\n    for (; *cctx; cctx = &(*cctx)->next) {\n        if ((*cctx)->name.len == name->len\n            && !ngx_memcmp(name->data, (*cctx)->name.data,\n                name->len))\n        {\n            break;\n        }\n    }\n\n    if (*cctx) {\n        play_ctx->publish = (*cctx)->publish;\n        play_ctx->next = (*cctx)->play;\n        (*cctx)->play = play_ctx;\n        return NGX_OK;\n    }\n\n    publish_ctx = create_publish_ctx(s, name, target);\n    if (publish_ctx == NULL) {\n        ngx_rtmp_finalize_session(play_ctx->session);\n        return NGX_ERROR;\n    }\n\n    publish_ctx->publish = publish_ctx;\n    publish_ctx->play = play_ctx;\n    play_ctx->publish = publish_ctx;\n    *cctx = publish_ctx;\n\n    return NGX_OK;\n}\n\n\nngx_int_t\nngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name,\n        ngx_rtmp_relay_target_t *target)\n{\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n            \"relay: create pull name='%V' app='%V' playpath='%V' url='%V'\",\n            name, &target->app, &target->play_path, &target->url.url);\n\n    return ngx_rtmp_relay_create(s, name, target,\n            ngx_rtmp_relay_create_remote_ctx,\n            ngx_rtmp_relay_create_local_ctx);\n}\n\n\nngx_int_t\nngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name,\n        ngx_rtmp_relay_target_t *target)\n{\n    ngx_log_error(NGX_LOG_INFO, s->connection->log, 0,\n            \"relay: create push name='%V' app='%V' playpath='%V' url='%V'\",\n            name, &target->app, &target->play_path, &target->url.url);\n\n    return ngx_rtmp_relay_create(s, name, target,\n            ngx_rtmp_relay_create_local_ctx,\n            ngx_rtmp_relay_create_remote_ctx);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_publish(ngx_rtmp_session_t *s, ngx_rtmp_publish_t *v)\n{\n    ngx_rtmp_relay_app_conf_t      *racf;\n    ngx_rtmp_relay_target_t        *target, **t;\n    ngx_str_t                       name;\n    size_t                          n;\n    ngx_rtmp_relay_ctx_t           *ctx;\n\n    if (s->auto_pushed) {\n        goto next;\n    }\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx && s->relay) {\n        goto next;\n    }\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);\n    if (racf == NULL || racf->pushes.nelts == 0) {\n        goto next;\n    }\n\n    name.len = ngx_strlen(v->name);\n    name.data = v->name;\n\n    t = racf->pushes.elts;\n    for (n = 0; n < racf->pushes.nelts; ++n, ++t) {\n        target = *t;\n\n        if (target->name.len && (name.len != target->name.len ||\n            ngx_memcmp(name.data, target->name.data, name.len)))\n        {\n            continue;\n        }\n\n        if (ngx_rtmp_relay_push(s, &name, target) == NGX_OK) {\n            continue;\n        }\n\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                \"relay: push failed name='%V' app='%V' \"\n                \"playpath='%V' url='%V'\",\n                &name, &target->app, &target->play_path,\n                &target->url.url);\n\n        if (!ctx->push_evt.timer_set) {\n            ngx_add_timer(&ctx->push_evt, racf->push_reconnect);\n        }\n    }\n\nnext:\n    return next_publish(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_play(ngx_rtmp_session_t *s, ngx_rtmp_play_t *v)\n{\n    ngx_rtmp_relay_app_conf_t      *racf;\n    ngx_rtmp_relay_target_t        *target, **t;\n    ngx_str_t                       name;\n    size_t                          n;\n    ngx_rtmp_relay_ctx_t           *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx && s->relay) {\n        goto next;\n    }\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);\n    if (racf == NULL || racf->pulls.nelts == 0) {\n        goto next;\n    }\n\n    name.len = ngx_strlen(v->name);\n    name.data = v->name;\n\n    t = racf->pulls.elts;\n    for (n = 0; n < racf->pulls.nelts; ++n, ++t) {\n        target = *t;\n\n        if (target->name.len && (name.len != target->name.len ||\n            ngx_memcmp(name.data, target->name.data, name.len)))\n        {\n            continue;\n        }\n\n        if (ngx_rtmp_relay_pull(s, &name, target) == NGX_OK) {\n            continue;\n        }\n\n        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,\n                \"relay: pull failed name='%V' app='%V' \"\n                \"playpath='%V' url='%V'\",\n                &name, &target->app, &target->play_path,\n                &target->url.url);\n    }\n\nnext:\n    return next_play(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_play_local(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_play_t             v;\n    ngx_rtmp_relay_ctx_t       *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_memzero(&v, sizeof(ngx_rtmp_play_t));\n    v.silent = 1;\n    *(ngx_cpymem(v.name, ctx->name.data,\n            ngx_min(sizeof(v.name) - 1, ctx->name.len))) = 0;\n\n    return ngx_rtmp_play(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_publish_local(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_publish_t          v;\n    ngx_rtmp_relay_ctx_t       *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    ngx_memzero(&v, sizeof(ngx_rtmp_publish_t));\n    v.silent = 1;\n    *(ngx_cpymem(v.name, ctx->name.data,\n            ngx_min(sizeof(v.name) - 1, ctx->name.len))) = 0;\n\n    return ngx_rtmp_publish(s, &v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_send_connect(ngx_rtmp_session_t *s)\n{\n    static double               trans = NGX_RTMP_RELAY_CONNECT_TRANS;\n    static double               acodecs = 3575;\n    static double               vcodecs = 252;\n\n    static ngx_rtmp_amf_elt_t   out_cmd[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"app\"),\n          NULL, 0 }, /* <-- fill */\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"tcUrl\"),\n          NULL, 0 }, /* <-- fill */\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"pageUrl\"),\n          NULL, 0 }, /* <-- fill */\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"swfUrl\"),\n          NULL, 0 }, /* <-- fill */\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"flashVer\"),\n          NULL, 0 }, /* <-- fill */\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"audioCodecs\"),\n          &acodecs, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"videoCodecs\"),\n          &vcodecs, 0 }\n    };\n\n    static ngx_rtmp_amf_elt_t   out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"connect\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &trans, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          out_cmd, sizeof(out_cmd) }\n    };\n\n    ngx_rtmp_core_app_conf_t   *cacf;\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_rtmp_relay_ctx_t       *ctx;\n    ngx_rtmp_header_t           h;\n    size_t                      len, url_len;\n    u_char                     *p, *url_end;\n\n\n    cacf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_core_module);\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (cacf == NULL || ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    /* app */\n    if (ctx->app.len) {\n        out_cmd[0].data = ctx->app.data;\n        out_cmd[0].len  = ctx->app.len;\n    } else {\n        out_cmd[0].data = cacf->name.data;\n        out_cmd[0].len  = cacf->name.len;\n    }\n\n    /* tcUrl */\n    if (ctx->tc_url.len) {\n        out_cmd[1].data = ctx->tc_url.data;\n        out_cmd[1].len  = ctx->tc_url.len;\n    } else {\n        len = sizeof(\"rtmp://\") - 1 + ctx->url.len +\n            sizeof(\"/\") - 1 + ctx->app.len;\n        p = ngx_palloc(s->connection->pool, len);\n        if (p == NULL) {\n            return NGX_ERROR;\n        }\n        out_cmd[1].data = p;\n        p = ngx_cpymem(p, \"rtmp://\", sizeof(\"rtmp://\") - 1);\n\n        url_len = ctx->url.len;\n        url_end = ngx_strlchr(ctx->url.data, ctx->url.data + ctx->url.len, '/');\n        if (url_end) {\n            url_len = (size_t) (url_end - ctx->url.data);\n        }\n\n        p = ngx_cpymem(p, ctx->url.data, url_len);\n        *p++ = '/';\n        p = ngx_cpymem(p, ctx->app.data, ctx->app.len);\n        out_cmd[1].len = p - (u_char *)out_cmd[1].data;\n    }\n\n    /* pageUrl */\n    out_cmd[2].data = ctx->page_url.data;\n    out_cmd[2].len  = ctx->page_url.len;\n\n    /* swfUrl */\n    out_cmd[3].data = ctx->swf_url.data;\n    out_cmd[3].len  = ctx->swf_url.len;\n\n    /* flashVer */\n    if (ctx->flash_ver.len) {\n        out_cmd[4].data = ctx->flash_ver.data;\n        out_cmd[4].len  = ctx->flash_ver.len;\n    } else {\n        out_cmd[4].data = NGX_RTMP_RELAY_FLASHVER;\n        out_cmd[4].len  = sizeof(NGX_RTMP_RELAY_FLASHVER) - 1;\n    }\n\n    ngx_memzero(&h, sizeof(h));\n    h.csid = NGX_RTMP_RELAY_CSID_AMF_INI;\n    h.type = NGX_RTMP_MSG_AMF_CMD;\n\n    return ngx_rtmp_send_chunk_size(s, cscf->chunk_size) != NGX_OK\n        || ngx_rtmp_send_ack_size(s, cscf->ack_window) != NGX_OK\n        || ngx_rtmp_send_amf(s, &h, out_elts,\n            sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK\n        ? NGX_ERROR\n        : NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_send_create_stream(ngx_rtmp_session_t *s)\n{\n    static double               trans = NGX_RTMP_RELAY_CREATE_STREAM_TRANS;\n\n    static ngx_rtmp_amf_elt_t   out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"createStream\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &trans, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 }\n    };\n\n    ngx_rtmp_header_t           h;\n\n\n    ngx_memzero(&h, sizeof(h));\n    h.csid = NGX_RTMP_RELAY_CSID_AMF_INI;\n    h.type = NGX_RTMP_MSG_AMF_CMD;\n\n    return ngx_rtmp_send_amf(s, &h, out_elts,\n            sizeof(out_elts) / sizeof(out_elts[0]));\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_send_publish(ngx_rtmp_session_t *s)\n{\n    static double               trans;\n\n    static ngx_rtmp_amf_elt_t   out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"publish\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &trans, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          NULL, 0 }, /* <- to fill */\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"live\", 0 }\n    };\n\n    ngx_rtmp_header_t           h;\n    ngx_rtmp_relay_ctx_t       *ctx;\n\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (ctx->play_path.len) {\n        out_elts[3].data = ctx->play_path.data;\n        out_elts[3].len  = ctx->play_path.len;\n    } else {\n        out_elts[3].data = ctx->name.data;\n        out_elts[3].len  = ctx->name.len;\n    }\n\n    ngx_memzero(&h, sizeof(h));\n    h.csid = NGX_RTMP_RELAY_CSID_AMF;\n    h.msid = NGX_RTMP_RELAY_MSID;\n    h.type = NGX_RTMP_MSG_AMF_CMD;\n\n    return ngx_rtmp_send_amf(s, &h, out_elts,\n            sizeof(out_elts) / sizeof(out_elts[0]));\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_send_play(ngx_rtmp_session_t *s)\n{\n    static double               trans;\n    static double               start, duration;\n\n    static ngx_rtmp_amf_elt_t   out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"play\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &trans, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          NULL, 0 }, /* <- fill */\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &start, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &duration, 0 },\n    };\n\n    ngx_rtmp_header_t           h;\n    ngx_rtmp_relay_ctx_t       *ctx;\n    ngx_rtmp_relay_app_conf_t  *racf;\n\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (racf == NULL || ctx == NULL) {\n        return NGX_ERROR;\n    }\n\n    if (ctx->play_path.len) {\n        out_elts[3].data = ctx->play_path.data;\n        out_elts[3].len  = ctx->play_path.len;\n    } else {\n        out_elts[3].data = ctx->name.data;\n        out_elts[3].len  = ctx->name.len;\n    }\n\n    if (ctx->live) {\n        start = -1000;\n        duration = -1000;\n    } else {\n        start    = (ctx->start ? ctx->start : -2000);\n        duration = (ctx->stop  ? ctx->stop - ctx->start : -1000);\n    }\n\n    ngx_memzero(&h, sizeof(h));\n    h.csid = NGX_RTMP_RELAY_CSID_AMF;\n    h.msid = NGX_RTMP_RELAY_MSID;\n    h.type = NGX_RTMP_MSG_AMF_CMD;\n\n    return ngx_rtmp_send_amf(s, &h, out_elts,\n            sizeof(out_elts) / sizeof(out_elts[0])) != NGX_OK\n           || ngx_rtmp_send_set_buflen(s, NGX_RTMP_RELAY_MSID,\n                   racf->buflen) != NGX_OK\n           ? NGX_ERROR\n           : NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_on_result(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_relay_ctx_t       *ctx;\n    static struct {\n        double                  trans;\n        u_char                  level[32];\n        u_char                  code[128];\n        u_char                  desc[1024];\n    } v;\n\n    static ngx_rtmp_amf_elt_t   in_inf[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"level\"),\n          &v.level, sizeof(v.level) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"code\"),\n          &v.code, sizeof(v.code) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"description\"),\n          &v.desc, sizeof(v.desc) },\n    };\n\n    static ngx_rtmp_amf_elt_t   in_elts[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.trans, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          in_inf, sizeof(in_inf) },\n    };\n\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL || !s->relay) {\n        return NGX_OK;\n    }\n\n    ngx_memzero(&v, sizeof(v));\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"relay: _result: level='%s' code='%s' description='%s'\",\n            v.level, v.code, v.desc);\n\n    switch ((ngx_int_t)v.trans) {\n        case NGX_RTMP_RELAY_CONNECT_TRANS:\n            return ngx_rtmp_relay_send_create_stream(s);\n\n        case NGX_RTMP_RELAY_CREATE_STREAM_TRANS:\n            if (ctx->publish != ctx && !s->static_relay) {\n                if (ngx_rtmp_relay_send_publish(s) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                return ngx_rtmp_relay_play_local(s);\n\n            } else {\n                if (ngx_rtmp_relay_send_play(s) != NGX_OK) {\n                    return NGX_ERROR;\n                }\n                return ngx_rtmp_relay_publish_local(s);\n            }\n\n        default:\n            return NGX_OK;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_on_error(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_relay_ctx_t       *ctx;\n    static struct {\n        double                  trans;\n        u_char                  level[32];\n        u_char                  code[128];\n        u_char                  desc[1024];\n    } v;\n\n    static ngx_rtmp_amf_elt_t   in_inf[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"level\"),\n          &v.level, sizeof(v.level) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"code\"),\n          &v.code, sizeof(v.code) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"description\"),\n          &v.desc, sizeof(v.desc) },\n    };\n\n    static ngx_rtmp_amf_elt_t   in_elts[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.trans, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          in_inf, sizeof(in_inf) },\n    };\n\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL || !s->relay) {\n        return NGX_OK;\n    }\n\n    ngx_memzero(&v, sizeof(v));\n    if (ngx_rtmp_receive_amf(s, in, in_elts,\n                sizeof(in_elts) / sizeof(in_elts[0])))\n    {\n        return NGX_ERROR;\n    }\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"relay: _error: level='%s' code='%s' description='%s'\",\n            v.level, v.code, v.desc);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_on_status(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_relay_ctx_t       *ctx;\n    static struct {\n        double                  trans;\n        u_char                  level[32];\n        u_char                  code[128];\n        u_char                  desc[1024];\n    } v;\n\n    static ngx_rtmp_amf_elt_t   in_inf[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"level\"),\n          &v.level, sizeof(v.level) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"code\"),\n          &v.code, sizeof(v.code) },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"description\"),\n          &v.desc, sizeof(v.desc) },\n    };\n\n    static ngx_rtmp_amf_elt_t   in_elts[] = {\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &v.trans, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          in_inf, sizeof(in_inf) },\n    };\n\n    static ngx_rtmp_amf_elt_t   in_elts_meta[] = {\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          in_inf, sizeof(in_inf) },\n    };\n\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL || !s->relay) {\n        return NGX_OK;\n    }\n\n    ngx_memzero(&v, sizeof(v));\n    if (h->type == NGX_RTMP_MSG_AMF_META) {\n        ngx_rtmp_receive_amf(s, in, in_elts_meta,\n                sizeof(in_elts_meta) / sizeof(in_elts_meta[0]));\n    } else {\n        ngx_rtmp_receive_amf(s, in, in_elts,\n                sizeof(in_elts) / sizeof(in_elts[0]));\n    }\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n            \"relay: onStatus: level='%s' code='%s' description='%s'\",\n            v.level, v.code, v.desc);\n\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_handshake_done(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n        ngx_chain_t *in)\n{\n    ngx_rtmp_relay_ctx_t   *ctx;\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL || !s->relay) {\n        return NGX_OK;\n    }\n\n    return ngx_rtmp_relay_send_connect(s);\n}\n\n\nstatic void\nngx_rtmp_relay_close(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_relay_app_conf_t          *racf;\n    ngx_rtmp_relay_ctx_t               *ctx, **cctx;\n    ngx_uint_t                          hash;\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);\n\n    ctx = ngx_rtmp_get_module_ctx(s, ngx_rtmp_relay_module);\n    if (ctx == NULL) {\n        return;\n    }\n\n    if (s->static_relay) {\n        ngx_add_timer(ctx->static_evt, racf->pull_reconnect);\n    }\n\n    if (ctx->publish == NULL) {\n        return;\n    }\n\n    /* play end disconnect? */\n    if (ctx->publish != ctx) {\n        for (cctx = &ctx->publish->play; *cctx; cctx = &(*cctx)->next) {\n            if (*cctx == ctx) {\n                *cctx = ctx->next;\n                break;\n            }\n        }\n\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0,\n                \"relay: play disconnect app='%V' name='%V'\",\n                &ctx->app, &ctx->name);\n\n        /* push reconnect */\n        if (s->relay && ctx->tag == &ngx_rtmp_relay_module &&\n            !ctx->publish->push_evt.timer_set)\n        {\n            ngx_add_timer(&ctx->publish->push_evt, racf->push_reconnect);\n        }\n\n#ifdef NGX_DEBUG\n        {\n            ngx_uint_t  n = 0;\n            for (cctx = &ctx->publish->play; *cctx; cctx = &(*cctx)->next, ++n);\n            ngx_log_debug3(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0,\n                \"relay: play left after disconnect app='%V' name='%V': %ui\",\n                &ctx->app, &ctx->name, n);\n        }\n#endif\n\n        if (ctx->publish->play == NULL && ctx->publish->session->relay) {\n            ngx_log_debug2(NGX_LOG_DEBUG_RTMP,\n                 ctx->publish->session->connection->log, 0,\n                \"relay: publish disconnect empty app='%V' name='%V'\",\n                &ctx->app, &ctx->name);\n            ngx_rtmp_finalize_session(ctx->publish->session);\n        }\n\n        ctx->publish = NULL;\n\n        return;\n    }\n\n    /* publish end disconnect */\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, ctx->session->connection->log, 0,\n            \"relay: publish disconnect app='%V' name='%V'\",\n            &ctx->app, &ctx->name);\n\n    if (ctx->push_evt.timer_set) {\n        ngx_del_timer(&ctx->push_evt);\n    }\n\n    for (cctx = &ctx->play; *cctx; cctx = &(*cctx)->next) {\n        (*cctx)->publish = NULL;\n        ngx_log_debug2(NGX_LOG_DEBUG_RTMP, (*cctx)->session->connection->log,\n            0, \"relay: play disconnect orphan app='%V' name='%V'\",\n            &(*cctx)->app, &(*cctx)->name);\n        ngx_rtmp_finalize_session((*cctx)->session);\n    }\n    ctx->publish = NULL;\n\n    hash = ngx_hash_key(ctx->name.data, ctx->name.len);\n    cctx = &racf->ctx[hash % racf->nbuckets];\n    for (; *cctx && *cctx != ctx; cctx = &(*cctx)->next);\n    if (*cctx) {\n        *cctx = ctx->next;\n    }\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_close_stream(ngx_rtmp_session_t *s, ngx_rtmp_close_stream_t *v)\n{\n    ngx_rtmp_relay_app_conf_t  *racf;\n\n    racf = ngx_rtmp_get_module_app_conf(s, ngx_rtmp_relay_module);\n    if (racf && !racf->session_relay) {\n        ngx_rtmp_relay_close(s);\n    }\n\n    return next_close_stream(s, v);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_delete_stream(ngx_rtmp_session_t *s, ngx_rtmp_delete_stream_t *v)\n{\n    ngx_rtmp_relay_close(s);\n\n    return next_delete_stream(s, v);\n}\n\n\nstatic char *\nngx_rtmp_relay_push_pull(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_str_t                          *value, v, n;\n    ngx_rtmp_relay_app_conf_t          *racf;\n    ngx_rtmp_relay_target_t            *target, **t;\n    ngx_url_t                          *u;\n    ngx_uint_t                          i;\n    ngx_int_t                           is_pull, is_static;\n    ngx_event_t                       **ee, *e;\n    ngx_rtmp_relay_static_t            *rs;\n    u_char                             *p;\n\n    value = cf->args->elts;\n\n    racf = ngx_rtmp_conf_get_module_app_conf(cf, ngx_rtmp_relay_module);\n\n    is_pull = (value[0].data[3] == 'l');\n    is_static = 0;\n\n    target = ngx_pcalloc(cf->pool, sizeof(*target));\n    if (target == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    target->tag = &ngx_rtmp_relay_module;\n    target->data = target;\n\n    u = &target->url;\n    u->default_port = 1935;\n    u->uri_part = 1;\n    u->url = value[1];\n\n    if (ngx_strncasecmp(u->url.data, (u_char *) \"rtmp://\", 7) == 0) {\n        u->url.data += 7;\n        u->url.len  -= 7;\n    }\n\n    if (ngx_parse_url(cf->pool, u) != NGX_OK) {\n        if (u->err) {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                    \"%s in url \\\"%V\\\"\", u->err, &u->url);\n        }\n        return NGX_CONF_ERROR;\n    }\n\n    value += 2;\n    for (i = 2; i < cf->args->nelts; ++i, ++value) {\n        p = ngx_strlchr(value->data, value->data + value->len, '=');\n\n        if (p == NULL) {\n            n = *value;\n            ngx_str_set(&v, \"1\");\n\n        } else {\n            n.data = value->data;\n            n.len  = p - value->data;\n\n            v.data = p + 1;\n            v.len  = value->data + value->len - p - 1;\n        }\n\n#define NGX_RTMP_RELAY_STR_PAR(name, var)                                     \\\n        if (n.len == sizeof(name) - 1                                         \\\n            && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0)          \\\n        {                                                                     \\\n            target->var = v;                                                  \\\n            continue;                                                         \\\n        }\n\n#define NGX_RTMP_RELAY_NUM_PAR(name, var)                                     \\\n        if (n.len == sizeof(name) - 1                                         \\\n            && ngx_strncasecmp(n.data, (u_char *) name, n.len) == 0)          \\\n        {                                                                     \\\n            target->var = ngx_atoi(v.data, v.len);                            \\\n            continue;                                                         \\\n        }\n\n        NGX_RTMP_RELAY_STR_PAR(\"app\",         app);\n        NGX_RTMP_RELAY_STR_PAR(\"name\",        name);\n        NGX_RTMP_RELAY_STR_PAR(\"tcUrl\",       tc_url);\n        NGX_RTMP_RELAY_STR_PAR(\"pageUrl\",     page_url);\n        NGX_RTMP_RELAY_STR_PAR(\"swfUrl\",      swf_url);\n        NGX_RTMP_RELAY_STR_PAR(\"flashVer\",    flash_ver);\n        NGX_RTMP_RELAY_STR_PAR(\"playPath\",    play_path);\n        NGX_RTMP_RELAY_NUM_PAR(\"live\",        live);\n        NGX_RTMP_RELAY_NUM_PAR(\"start\",       start);\n        NGX_RTMP_RELAY_NUM_PAR(\"stop\",        stop);\n\n#undef NGX_RTMP_RELAY_STR_PAR\n#undef NGX_RTMP_RELAY_NUM_PAR\n\n        if (n.len == sizeof(\"static\") - 1 &&\n            ngx_strncasecmp(n.data, (u_char *) \"static\", n.len) == 0 &&\n            ngx_atoi(v.data, v.len))\n        {\n            is_static = 1;\n            continue;\n        }\n\n        return \"unsuppored parameter\";\n    }\n\n    if (is_static) {\n\n        if (!is_pull) {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                               \"static push is not allowed\");\n            return NGX_CONF_ERROR;\n        }\n\n        if (target->name.len == 0) {\n            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,\n                               \"stream name missing in static pull \"\n                               \"declaration\");\n            return NGX_CONF_ERROR;\n        }\n\n        ee = ngx_array_push(&racf->static_events);\n        if (ee == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        e = ngx_pcalloc(cf->pool, sizeof(ngx_event_t));\n        if (e == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        *ee = e;\n\n        rs = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_relay_static_t));\n        if (rs == NULL) {\n            return NGX_CONF_ERROR;\n        }\n\n        rs->target = target;\n\n        e->data = rs;\n        e->log = &cf->cycle->new_log;\n        e->handler = ngx_rtmp_relay_static_pull_reconnect;\n\n        t = ngx_array_push(&racf->static_pulls);\n\n    } else if (is_pull) {\n        t = ngx_array_push(&racf->pulls);\n\n    } else {\n        t = ngx_array_push(&racf->pushes);\n    }\n\n    if (t == NULL) {\n        return NGX_CONF_ERROR;\n    }\n\n    *t = target;\n\n    return NGX_CONF_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_init_process(ngx_cycle_t *cycle)\n{\n#if !(NGX_WIN32)\n    ngx_rtmp_core_main_conf_t  *cmcf = ngx_rtmp_core_main_conf;\n    ngx_rtmp_core_srv_conf_t  **pcscf, *cscf;\n    ngx_rtmp_core_app_conf_t  **pcacf, *cacf;\n    ngx_rtmp_relay_app_conf_t  *racf;\n    ngx_uint_t                  n, m, k;\n    ngx_rtmp_relay_static_t    *rs;\n    ngx_rtmp_listen_t          *lst;\n    ngx_event_t               **pevent, *event;\n\n    if (cmcf == NULL || cmcf->listen.nelts == 0) {\n        return NGX_OK;\n    }\n\n    /* only first worker does static pulling */\n\n    if (ngx_process_slot) {\n        return NGX_OK;\n    }\n\n    lst = cmcf->listen.elts;\n\n    pcscf = cmcf->servers.elts;\n    for (n = 0; n < cmcf->servers.nelts; ++n, ++pcscf) {\n\n        cscf = *pcscf;\n        pcacf = cscf->applications.elts;\n\n        for (m = 0; m < cscf->applications.nelts; ++m, ++pcacf) {\n\n            cacf = *pcacf;\n            racf = cacf->app_conf[ngx_rtmp_relay_module.ctx_index];\n            pevent = racf->static_events.elts;\n\n            for (k = 0; k < racf->static_events.nelts; ++k, ++pevent) {\n                event = *pevent;\n\n                rs = event->data;\n                rs->cctx = *lst->ctx;\n                rs->cctx.app_conf = cacf->app_conf;\n\n                ngx_post_event(event, &ngx_rtmp_init_queue);\n            }\n        }\n    }\n#endif\n    return NGX_OK;\n}\n\n\nstatic ngx_int_t\nngx_rtmp_relay_postconfiguration(ngx_conf_t *cf)\n{\n    ngx_rtmp_core_main_conf_t          *cmcf;\n    ngx_rtmp_handler_pt                *h;\n    ngx_rtmp_amf_handler_t             *ch;\n\n    cmcf = ngx_rtmp_conf_get_module_main_conf(cf, ngx_rtmp_core_module);\n\n\n    h = ngx_array_push(&cmcf->events[NGX_RTMP_HANDSHAKE_DONE]);\n    *h = ngx_rtmp_relay_handshake_done;\n\n\n    next_publish = ngx_rtmp_publish;\n    ngx_rtmp_publish = ngx_rtmp_relay_publish;\n\n    next_play = ngx_rtmp_play;\n    ngx_rtmp_play = ngx_rtmp_relay_play;\n\n    next_delete_stream = ngx_rtmp_delete_stream;\n    ngx_rtmp_delete_stream = ngx_rtmp_relay_delete_stream;\n\n    next_close_stream = ngx_rtmp_close_stream;\n    ngx_rtmp_close_stream = ngx_rtmp_relay_close_stream;\n\n\n    ch = ngx_array_push(&cmcf->amf);\n    ngx_str_set(&ch->name, \"_result\");\n    ch->handler = ngx_rtmp_relay_on_result;\n\n    ch = ngx_array_push(&cmcf->amf);\n    ngx_str_set(&ch->name, \"_error\");\n    ch->handler = ngx_rtmp_relay_on_error;\n\n    ch = ngx_array_push(&cmcf->amf);\n    ngx_str_set(&ch->name, \"onStatus\");\n    ch->handler = ngx_rtmp_relay_on_status;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_relay_module.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_RELAY_H_INCLUDED_\n#define _NGX_RTMP_RELAY_H_INCLUDED_\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\ntypedef struct {\n    ngx_url_t                       url;\n    ngx_str_t                       app;\n    ngx_str_t                       name;\n    ngx_str_t                       tc_url;\n    ngx_str_t                       page_url;\n    ngx_str_t                       swf_url;\n    ngx_str_t                       flash_ver;\n    ngx_str_t                       play_path;\n    ngx_int_t                       live;\n    ngx_int_t                       start;\n    ngx_int_t                       stop;\n\n    void                           *tag;     /* usually module reference */\n    void                           *data;    /* module-specific data */\n    ngx_uint_t                      counter; /* mutable connection counter */\n} ngx_rtmp_relay_target_t;\n\n\ntypedef struct ngx_rtmp_relay_ctx_s ngx_rtmp_relay_ctx_t;\n\nstruct ngx_rtmp_relay_ctx_s {\n    ngx_str_t                       name;\n    ngx_str_t                       url;\n    ngx_log_t                       log;\n    ngx_rtmp_session_t             *session;\n    ngx_rtmp_relay_ctx_t           *publish;\n    ngx_rtmp_relay_ctx_t           *play;\n    ngx_rtmp_relay_ctx_t           *next;\n\n    ngx_str_t                       app;\n    ngx_str_t                       tc_url;\n    ngx_str_t                       page_url;\n    ngx_str_t                       swf_url;\n    ngx_str_t                       flash_ver;\n    ngx_str_t                       play_path;\n    ngx_int_t                       live;\n    ngx_int_t                       start;\n    ngx_int_t                       stop;\n\n    ngx_event_t                     push_evt;\n    ngx_event_t                    *static_evt;\n    void                           *tag;\n    void                           *data;\n};\n\n\nextern ngx_module_t                 ngx_rtmp_relay_module;\n\n\nngx_int_t ngx_rtmp_relay_pull(ngx_rtmp_session_t *s, ngx_str_t *name,\n                              ngx_rtmp_relay_target_t *target);\nngx_int_t ngx_rtmp_relay_push(ngx_rtmp_session_t *s, ngx_str_t *name,\n                              ngx_rtmp_relay_target_t *target);\n\n\n#endif /* _NGX_RTMP_RELAY_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_send.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_amf.h\"\n#include \"ngx_rtmp_streams.h\"\n\n\n#define NGX_RTMP_USER_START(s, tp)                                          \\\n    ngx_rtmp_header_t               __h;                                    \\\n    ngx_chain_t                    *__l;                                    \\\n    ngx_buf_t                      *__b;                                    \\\n    ngx_rtmp_core_srv_conf_t       *__cscf;                                 \\\n                                                                            \\\n    __cscf = ngx_rtmp_get_module_srv_conf(                                  \\\n            s, ngx_rtmp_core_module);                                       \\\n    memset(&__h, 0, sizeof(__h));                                           \\\n    __h.type = tp;                                                          \\\n    __h.csid = 2;                                                           \\\n    __l = ngx_rtmp_alloc_shared_buf(__cscf);                                \\\n    if (__l == NULL) {                                                      \\\n        return NULL;                                                        \\\n    }                                                                       \\\n    __b = __l->buf;\n\n#define NGX_RTMP_UCTL_START(s, type, utype)                                 \\\n    NGX_RTMP_USER_START(s, type);                                           \\\n    *(__b->last++) = (u_char)((utype) >> 8);                                \\\n    *(__b->last++) = (u_char)(utype);\n\n#define NGX_RTMP_USER_OUT1(v)                                               \\\n    *(__b->last++) = ((u_char*)&v)[0];\n\n#define NGX_RTMP_USER_OUT4(v)                                               \\\n    *(__b->last++) = ((u_char*)&v)[3];                                      \\\n    *(__b->last++) = ((u_char*)&v)[2];                                      \\\n    *(__b->last++) = ((u_char*)&v)[1];                                      \\\n    *(__b->last++) = ((u_char*)&v)[0];\n\n#define NGX_RTMP_USER_END(s)                                                \\\n    ngx_rtmp_prepare_message(s, &__h, NULL, __l);                           \\\n    return __l;\n\n\nstatic ngx_int_t\nngx_rtmp_send_shared_packet(ngx_rtmp_session_t *s, ngx_chain_t *cl)\n{\n    ngx_rtmp_core_srv_conf_t       *cscf;\n    ngx_int_t                       rc;\n\n    if (cl == NULL) {\n        return NGX_ERROR;\n    }\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    rc = ngx_rtmp_send_message(s, cl, 0);\n\n    ngx_rtmp_free_shared_chain(cscf, cl);\n\n    return rc;\n}\n\n\n/* Protocol control messages */\n\nngx_chain_t *\nngx_rtmp_create_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"chunk_size=%uD\", chunk_size);\n\n    {\n        NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE);\n\n        NGX_RTMP_USER_OUT4(chunk_size);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_chunk_size(ngx_rtmp_session_t *s, uint32_t chunk_size)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_chunk_size(s, chunk_size));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_abort(ngx_rtmp_session_t *s, uint32_t csid)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: abort csid=%uD\", csid);\n\n    {\n        NGX_RTMP_USER_START(s, NGX_RTMP_MSG_CHUNK_SIZE);\n\n        NGX_RTMP_USER_OUT4(csid);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_abort(ngx_rtmp_session_t *s, uint32_t csid)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_abort(s, csid));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_ack(ngx_rtmp_session_t *s, uint32_t seq)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: ack seq=%uD\", seq);\n\n    {\n        NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK);\n\n        NGX_RTMP_USER_OUT4(seq);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_ack(ngx_rtmp_session_t *s, uint32_t seq)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_ack(s, seq));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: ack_size=%uD\", ack_size);\n\n    {\n        NGX_RTMP_USER_START(s, NGX_RTMP_MSG_ACK_SIZE);\n\n        NGX_RTMP_USER_OUT4(ack_size);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_ack_size(ngx_rtmp_session_t *s, uint32_t ack_size)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_ack_size(s, ack_size));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size,\n                          uint8_t limit_type)\n{\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: bandwidth ack_size=%uD limit=%d\",\n                   ack_size, (int)limit_type);\n\n    {\n        NGX_RTMP_USER_START(s, NGX_RTMP_MSG_BANDWIDTH);\n\n        NGX_RTMP_USER_OUT4(ack_size);\n        NGX_RTMP_USER_OUT1(limit_type);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_bandwidth(ngx_rtmp_session_t *s, uint32_t ack_size,\n                        uint8_t limit_type)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_bandwidth(s, ack_size, limit_type));\n}\n\n\n/* User control messages */\n\nngx_chain_t *\nngx_rtmp_create_stream_begin(ngx_rtmp_session_t *s, uint32_t msid)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: stream_begin msid=%uD\", msid);\n\n    {\n        NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_BEGIN);\n\n        NGX_RTMP_USER_OUT4(msid);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_stream_begin(ngx_rtmp_session_t *s, uint32_t msid)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_stream_begin(s, msid));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_stream_eof(ngx_rtmp_session_t *s, uint32_t msid)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: stream_end msid=%uD\", msid);\n\n    {\n        NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_EOF);\n\n        NGX_RTMP_USER_OUT4(msid);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_stream_eof(ngx_rtmp_session_t *s, uint32_t msid)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_stream_eof(s, msid));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_stream_dry(ngx_rtmp_session_t *s, uint32_t msid)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: stream_dry msid=%uD\", msid);\n\n    {\n        NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_STREAM_DRY);\n\n        NGX_RTMP_USER_OUT4(msid);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_stream_dry(ngx_rtmp_session_t *s, uint32_t msid)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_stream_dry(s, msid));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_set_buflen(ngx_rtmp_session_t *s, uint32_t msid,\n                           uint32_t buflen_msec)\n{\n    ngx_log_debug2(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: set_buflen msid=%uD buflen=%uD\",\n                   msid, buflen_msec);\n\n    {\n        NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_SET_BUFLEN);\n\n        NGX_RTMP_USER_OUT4(msid);\n        NGX_RTMP_USER_OUT4(buflen_msec);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_set_buflen(ngx_rtmp_session_t *s, uint32_t msid,\n        uint32_t buflen_msec)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_set_buflen(s, msid, buflen_msec));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_recorded(ngx_rtmp_session_t *s, uint32_t msid)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: recorded msid=%uD\", msid);\n\n    {\n        NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_RECORDED);\n\n        NGX_RTMP_USER_OUT4(msid);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_recorded(ngx_rtmp_session_t *s, uint32_t msid)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_recorded(s, msid));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: ping_request timestamp=%uD\", timestamp);\n\n    {\n        NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_REQUEST);\n\n        NGX_RTMP_USER_OUT4(timestamp);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_ping_request(ngx_rtmp_session_t *s, uint32_t timestamp)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_ping_request(s, timestamp));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp)\n{\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: ping_response timestamp=%uD\", timestamp);\n\n    {\n        NGX_RTMP_UCTL_START(s, NGX_RTMP_MSG_USER, NGX_RTMP_USER_PING_RESPONSE);\n\n        NGX_RTMP_USER_OUT4(timestamp);\n\n        NGX_RTMP_USER_END(s);\n    }\n}\n\n\nngx_int_t\nngx_rtmp_send_ping_response(ngx_rtmp_session_t *s, uint32_t timestamp)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_ping_response(s, timestamp));\n}\n\n\nstatic ngx_chain_t *\nngx_rtmp_alloc_amf_buf(void *arg)\n{\n    return ngx_rtmp_alloc_shared_buf((ngx_rtmp_core_srv_conf_t *)arg);\n}\n\n\n/* AMF sender */\n\n/* NOTE: this function does not free shared bufs on error */\nngx_int_t\nngx_rtmp_append_amf(ngx_rtmp_session_t *s,\n                    ngx_chain_t **first, ngx_chain_t **last,\n                    ngx_rtmp_amf_elt_t *elts, size_t nelts)\n{\n    ngx_rtmp_amf_ctx_t          act;\n    ngx_rtmp_core_srv_conf_t   *cscf;\n    ngx_int_t                   rc;\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    memset(&act, 0, sizeof(act));\n    act.arg = cscf;\n    act.alloc = ngx_rtmp_alloc_amf_buf;\n    act.log = s->connection->log;\n\n    if (first) {\n        act.first = *first;\n    }\n\n    if (last) {\n        act.link = *last;\n    }\n\n    rc = ngx_rtmp_amf_write(&act, elts, nelts);\n\n    if (first) {\n        *first = act.first;\n    }\n\n    if (last) {\n        *last = act.link;\n    }\n\n    return rc;\n}\n\n\nngx_chain_t *\nngx_rtmp_create_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                    ngx_rtmp_amf_elt_t *elts, size_t nelts)\n{\n    ngx_chain_t                *first;\n    ngx_int_t                   rc;\n    ngx_rtmp_core_srv_conf_t   *cscf;\n\n    ngx_log_debug1(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: amf nelts=%ui\", nelts);\n\n    cscf = ngx_rtmp_get_module_srv_conf(s, ngx_rtmp_core_module);\n\n    first = NULL;\n\n    rc = ngx_rtmp_append_amf(s, &first, NULL, elts, nelts);\n\n    if (rc != NGX_OK && first) {\n        ngx_rtmp_free_shared_chain(cscf, first);\n        first = NULL;\n    }\n\n    if (first) {\n        ngx_rtmp_prepare_message(s, h, NULL, first);\n    }\n\n    return first;\n}\n\n\nngx_int_t\nngx_rtmp_send_amf(ngx_rtmp_session_t *s, ngx_rtmp_header_t *h,\n                  ngx_rtmp_amf_elt_t *elts, size_t nelts)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_amf(s, h, elts, nelts));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_status(ngx_rtmp_session_t *s, char *code, char* level,\n                       char *desc)\n{\n    ngx_rtmp_header_t               h;\n    static double                   trans;\n\n    static ngx_rtmp_amf_elt_t       out_inf[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"level\"),\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"code\"),\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"description\"),\n          NULL, 0 },\n    };\n\n    static ngx_rtmp_amf_elt_t       out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"onStatus\", 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_null_string,\n          &trans, 0 },\n\n        { NGX_RTMP_AMF_NULL,\n          ngx_null_string,\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          out_inf,\n          sizeof(out_inf) },\n    };\n\n    ngx_log_debug3(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: status code='%s' level='%s' desc='%s'\",\n                   code, level, desc);\n\n    out_inf[0].data = level;\n    out_inf[1].data = code;\n    out_inf[2].data = desc;\n\n    memset(&h, 0, sizeof(h));\n\n    h.type = NGX_RTMP_MSG_AMF_CMD;\n    h.csid = NGX_RTMP_CSID_AMF;\n    h.msid = NGX_RTMP_MSID;\n\n    return ngx_rtmp_create_amf(s, &h, out_elts,\n                               sizeof(out_elts) / sizeof(out_elts[0]));\n}\n\n\nngx_int_t\nngx_rtmp_send_status(ngx_rtmp_session_t *s, char *code, char* level, char *desc)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_status(s, code, level, desc));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_play_status(ngx_rtmp_session_t *s, char *code, char* level,\n                            ngx_uint_t duration, ngx_uint_t bytes)\n{\n    ngx_rtmp_header_t               h;\n    static double                   dduration;\n    static double                   dbytes;\n\n    static ngx_rtmp_amf_elt_t       out_inf[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"code\"),\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_string(\"level\"),\n          NULL, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"duration\"),\n          &dduration, 0 },\n\n        { NGX_RTMP_AMF_NUMBER,\n          ngx_string(\"bytes\"),\n          &dbytes, 0 },\n    };\n\n    static ngx_rtmp_amf_elt_t       out_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"onPlayStatus\", 0 },\n\n        { NGX_RTMP_AMF_OBJECT,\n          ngx_null_string,\n          out_inf,\n          sizeof(out_inf) },\n    };\n\n    ngx_log_debug4(NGX_LOG_DEBUG_RTMP, s->connection->log, 0,\n                   \"create: play_status code='%s' level='%s' \"\n                   \"duration=%ui bytes=%ui\",\n                   code, level, duration, bytes);\n\n    out_inf[0].data = code;\n    out_inf[1].data = level;\n\n    dduration = duration;\n    dbytes = bytes;\n\n    memset(&h, 0, sizeof(h));\n\n    h.type = NGX_RTMP_MSG_AMF_META;\n    h.csid = NGX_RTMP_CSID_AMF;\n    h.msid = NGX_RTMP_MSID;\n    h.timestamp = duration;\n\n    return ngx_rtmp_create_amf(s, &h, out_elts,\n                               sizeof(out_elts) / sizeof(out_elts[0]));\n}\n\n\nngx_int_t\nngx_rtmp_send_play_status(ngx_rtmp_session_t *s, char *code, char* level,\n                          ngx_uint_t duration, ngx_uint_t bytes)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_play_status(s, code, level, duration, bytes));\n}\n\n\nngx_chain_t *\nngx_rtmp_create_sample_access(ngx_rtmp_session_t *s)\n{\n    ngx_rtmp_header_t               h;\n\n    static int                      access = 1;\n\n    static ngx_rtmp_amf_elt_t       access_elts[] = {\n\n        { NGX_RTMP_AMF_STRING,\n          ngx_null_string,\n          \"|RtmpSampleAccess\", 0 },\n\n        { NGX_RTMP_AMF_BOOLEAN,\n          ngx_null_string,\n          &access, 0 },\n\n        { NGX_RTMP_AMF_BOOLEAN,\n          ngx_null_string,\n          &access, 0 },\n    };\n\n    memset(&h, 0, sizeof(h));\n\n    h.type = NGX_RTMP_MSG_AMF_META;\n    h.csid = NGX_RTMP_CSID_AMF;\n    h.msid = NGX_RTMP_MSID;\n\n    return ngx_rtmp_create_amf(s, &h, access_elts,\n                               sizeof(access_elts) / sizeof(access_elts[0]));\n}\n\n\nngx_int_t\nngx_rtmp_send_sample_access(ngx_rtmp_session_t *s)\n{\n    return ngx_rtmp_send_shared_packet(s,\n           ngx_rtmp_create_sample_access(s));\n}\n"
  },
  {
    "path": "ngx_rtmp_shared.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include \"ngx_rtmp.h\"\n\n\nngx_chain_t *\nngx_rtmp_alloc_shared_buf(ngx_rtmp_core_srv_conf_t *cscf)\n{\n    u_char                     *p;\n    ngx_chain_t                *out;\n    ngx_buf_t                  *b;\n    size_t                      size;\n\n    if (cscf->free) {\n        out = cscf->free;\n        cscf->free = out->next;\n\n    } else {\n\n        size = cscf->chunk_size + NGX_RTMP_MAX_CHUNK_HEADER;\n\n        p = ngx_pcalloc(cscf->pool, NGX_RTMP_REFCOUNT_BYTES\n                + sizeof(ngx_chain_t)\n                + sizeof(ngx_buf_t)\n                + size);\n        if (p == NULL) {\n            return NULL;\n        }\n\n        p += NGX_RTMP_REFCOUNT_BYTES;\n        out = (ngx_chain_t *)p;\n\n        p += sizeof(ngx_chain_t);\n        out->buf = (ngx_buf_t *)p;\n\n        p += sizeof(ngx_buf_t);\n        out->buf->start = p;\n        out->buf->end = p + size;\n    }\n\n    out->next = NULL;\n    b = out->buf;\n    b->pos = b->last = b->start + NGX_RTMP_MAX_CHUNK_HEADER;\n    b->memory = 1;\n\n    /* buffer has refcount =1 when created! */\n    ngx_rtmp_ref_set(out, 1);\n\n    return out;\n}\n\n\nvoid\nngx_rtmp_free_shared_chain(ngx_rtmp_core_srv_conf_t *cscf, ngx_chain_t *in)\n{\n    ngx_chain_t        *cl;\n\n    if (ngx_rtmp_ref_put(in)) {\n        return;\n    }\n\n    for (cl = in; ; cl = cl->next) {\n        if (cl->next == NULL) {\n            cl->next = cscf->free;\n            cscf->free = in;\n            return;\n        }\n    }\n}\n\n\nngx_chain_t *\nngx_rtmp_append_shared_bufs(ngx_rtmp_core_srv_conf_t *cscf,\n        ngx_chain_t *head, ngx_chain_t *in)\n{\n    ngx_chain_t                    *l, **ll;\n    u_char                         *p;\n    size_t                          size;\n\n    ll = &head;\n    p = in->buf->pos;\n    l = head;\n\n    if (l) {\n        for(; l->next; l = l->next);\n        ll = &l->next;\n    }\n\n    for ( ;; ) {\n\n        if (l == NULL || l->buf->last == l->buf->end) {\n            l = ngx_rtmp_alloc_shared_buf(cscf);\n            if (l == NULL || l->buf == NULL) {\n                break;\n            }\n\n            *ll = l;\n            ll = &l->next;\n        }\n\n        while (l->buf->end - l->buf->last >= in->buf->last - p) {\n            l->buf->last = ngx_cpymem(l->buf->last, p,\n                    in->buf->last - p);\n            in = in->next;\n            if (in == NULL) {\n                goto done;\n            }\n            p = in->buf->pos;\n        }\n\n        size = l->buf->end - l->buf->last;\n        l->buf->last = ngx_cpymem(l->buf->last, p, size);\n        p += size;\n    }\n\ndone:\n    *ll = NULL;\n\n    return head;\n}\n"
  },
  {
    "path": "ngx_rtmp_stat_module.c",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#include <ngx_config.h>\n#include <ngx_core.h>\n#include <ngx_http.h>\n#include <nginx.h>\n#include \"ngx_rtmp.h\"\n#include \"ngx_rtmp_version.h\"\n#include \"ngx_rtmp_live_module.h\"\n#include \"ngx_rtmp_play_module.h\"\n#include \"ngx_rtmp_codec_module.h\"\n\n\nstatic ngx_int_t ngx_rtmp_stat_init_process(ngx_cycle_t *cycle);\nstatic char *ngx_rtmp_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);\nstatic ngx_int_t ngx_rtmp_stat_postconfiguration(ngx_conf_t *cf);\nstatic void * ngx_rtmp_stat_create_loc_conf(ngx_conf_t *cf);\nstatic char * ngx_rtmp_stat_merge_loc_conf(ngx_conf_t *cf,\n        void *parent, void *child);\n\n\nstatic time_t                       start_time;\n\n\n#define NGX_RTMP_STAT_ALL           0xff\n#define NGX_RTMP_STAT_GLOBAL        0x01\n#define NGX_RTMP_STAT_LIVE          0x02\n#define NGX_RTMP_STAT_CLIENTS       0x04\n#define NGX_RTMP_STAT_PLAY          0x08\n\n/*\n * global: stat-{bufs-{total,free,used}, total bytes in/out, bw in/out} - cscf\n*/\n\n\ntypedef struct {\n    ngx_uint_t                      stat;\n    ngx_str_t                       stylesheet;\n} ngx_rtmp_stat_loc_conf_t;\n\n\nstatic ngx_conf_bitmask_t           ngx_rtmp_stat_masks[] = {\n    { ngx_string(\"all\"),            NGX_RTMP_STAT_ALL           },\n    { ngx_string(\"global\"),         NGX_RTMP_STAT_GLOBAL        },\n    { ngx_string(\"live\"),           NGX_RTMP_STAT_LIVE          },\n    { ngx_string(\"clients\"),        NGX_RTMP_STAT_CLIENTS       },\n    { ngx_null_string,              0 }\n};\n\n\nstatic ngx_command_t  ngx_rtmp_stat_commands[] = {\n\n    { ngx_string(\"rtmp_stat\"),\n        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,\n        ngx_rtmp_stat,\n        NGX_HTTP_LOC_CONF_OFFSET,\n        offsetof(ngx_rtmp_stat_loc_conf_t, stat),\n        ngx_rtmp_stat_masks },\n\n    { ngx_string(\"rtmp_stat_stylesheet\"),\n        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,\n        ngx_conf_set_str_slot,\n        NGX_HTTP_LOC_CONF_OFFSET,\n        offsetof(ngx_rtmp_stat_loc_conf_t, stylesheet),\n        NULL },\n\n    ngx_null_command\n};\n\n\nstatic ngx_http_module_t  ngx_rtmp_stat_module_ctx = {\n    NULL,                               /* preconfiguration */\n    ngx_rtmp_stat_postconfiguration,    /* postconfiguration */\n\n    NULL,                               /* create main configuration */\n    NULL,                               /* init main configuration */\n\n    NULL,                               /* create server configuration */\n    NULL,                               /* merge server configuration */\n\n    ngx_rtmp_stat_create_loc_conf,      /* create location configuration */\n    ngx_rtmp_stat_merge_loc_conf,       /* merge location configuration */\n};\n\n\nngx_module_t  ngx_rtmp_stat_module = {\n    NGX_MODULE_V1,\n    &ngx_rtmp_stat_module_ctx,          /* module context */\n    ngx_rtmp_stat_commands,             /* module directives */\n    NGX_HTTP_MODULE,                    /* module type */\n    NULL,                               /* init master */\n    NULL,                               /* init module */\n    ngx_rtmp_stat_init_process,         /* init process */\n    NULL,                               /* init thread */\n    NULL,                               /* exit thread */\n    NULL,                               /* exit process */\n    NULL,                               /* exit master */\n    NGX_MODULE_V1_PADDING\n};\n\n\n#define NGX_RTMP_STAT_BUFSIZE           256\n\n\nstatic ngx_int_t\nngx_rtmp_stat_init_process(ngx_cycle_t *cycle)\n{\n    /*\n     * HTTP process initializer is called\n     * after event module initializer\n     * so we can run posted events here\n     */\n\n    ngx_event_process_posted(cycle, &ngx_rtmp_init_queue);\n\n    return NGX_OK;\n}\n\n\n/* ngx_escape_html does not escape characters out of ASCII range\n * which are bad for xslt */\n\nstatic void *\nngx_rtmp_stat_escape(ngx_http_request_t *r, void *data, size_t len)\n{\n    u_char *p, *np;\n    void   *new_data;\n    size_t  n;\n\n    p = data;\n\n    for (n = 0; n < len; ++n, ++p) {\n        if (*p < 0x20 || *p >= 0x7f) {\n            break;\n        }\n    }\n\n    if (n == len) {\n        return data;\n    }\n\n    new_data = ngx_palloc(r->pool, len);\n    if (new_data == NULL) {\n        return NULL;\n    }\n\n    p  = data;\n    np = new_data;\n\n    for (n = 0; n < len; ++n, ++p, ++np) {\n        *np = (*p < 0x20 || *p >= 0x7f) ? (u_char) ' ' : *p;\n    }\n\n    return new_data;\n}\n\n#if (NGX_WIN32)\n/*\n * Fix broken MSVC memcpy optimization for 4-byte data\n * when this function is inlined\n */\n__declspec(noinline)\n#endif\n\nstatic void\nngx_rtmp_stat_output(ngx_http_request_t *r, ngx_chain_t ***lll,\n        void *data, size_t len, ngx_uint_t escape)\n{\n    ngx_chain_t        *cl;\n    ngx_buf_t          *b;\n    size_t              real_len;\n\n    if (len == 0) {\n        return;\n    }\n\n    if (escape) {\n        data = ngx_rtmp_stat_escape(r, data, len);\n        if (data == NULL) {\n            return;\n        }\n    }\n\n    real_len = escape\n        ? len + ngx_escape_html(NULL, data, len)\n        : len;\n\n    cl = **lll;\n    if (cl && cl->buf->last + real_len > cl->buf->end) {\n        *lll = &cl->next;\n    }\n\n    if (**lll == NULL) {\n        cl = ngx_alloc_chain_link(r->pool);\n        if (cl == NULL) {\n            return;\n        }\n        b = ngx_create_temp_buf(r->pool,\n                ngx_max(NGX_RTMP_STAT_BUFSIZE, real_len));\n        if (b == NULL || b->pos == NULL) {\n            return;\n        }\n        cl->next = NULL;\n        cl->buf = b;\n        **lll = cl;\n    }\n\n    b = (**lll)->buf;\n\n    if (escape) {\n        b->last = (u_char *)ngx_escape_html(b->last, data, len);\n    } else {\n        b->last = ngx_cpymem(b->last, data, len);\n    }\n}\n\n\n/* These shortcuts assume 2 variables exist in current context:\n *   ngx_http_request_t    *r\n *   ngx_chain_t         ***lll */\n\n/* plain data */\n#define NGX_RTMP_STAT(data, len)    ngx_rtmp_stat_output(r, lll, data, len, 0)\n\n/* escaped data */\n#define NGX_RTMP_STAT_E(data, len)  ngx_rtmp_stat_output(r, lll, data, len, 1)\n\n/* literal */\n#define NGX_RTMP_STAT_L(s)          NGX_RTMP_STAT((s), sizeof(s) - 1)\n\n/* ngx_str_t */\n#define NGX_RTMP_STAT_S(s)          NGX_RTMP_STAT((s)->data, (s)->len)\n\n/* escaped ngx_str_t */\n#define NGX_RTMP_STAT_ES(s)         NGX_RTMP_STAT_E((s)->data, (s)->len)\n\n/* C string */\n#define NGX_RTMP_STAT_CS(s)         NGX_RTMP_STAT((s), ngx_strlen(s))\n\n/* escaped C string */\n#define NGX_RTMP_STAT_ECS(s)        NGX_RTMP_STAT_E((s), ngx_strlen(s))\n\n\n#define NGX_RTMP_STAT_BW            0x01\n#define NGX_RTMP_STAT_BYTES         0x02\n#define NGX_RTMP_STAT_BW_BYTES      0x03\n\n\nstatic void\nngx_rtmp_stat_bw(ngx_http_request_t *r, ngx_chain_t ***lll,\n                 ngx_rtmp_bandwidth_t *bw, char *name,\n                 ngx_uint_t flags)\n{\n    u_char  buf[NGX_INT64_LEN + 9];\n\n    ngx_rtmp_update_bandwidth(bw, 0);\n\n    if (flags & NGX_RTMP_STAT_BW) {\n        NGX_RTMP_STAT_L(\"<bw_\");\n        NGX_RTMP_STAT_CS(name);\n        NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), \">%uL</bw_\",\n                                        bw->bandwidth * 8)\n                           - buf);\n        NGX_RTMP_STAT_CS(name);\n        NGX_RTMP_STAT_L(\">\\r\\n\");\n    }\n\n    if (flags & NGX_RTMP_STAT_BYTES) {\n        NGX_RTMP_STAT_L(\"<bytes_\");\n        NGX_RTMP_STAT_CS(name);\n        NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), \">%uL</bytes_\",\n                                        bw->bytes)\n                           - buf);\n        NGX_RTMP_STAT_CS(name);\n        NGX_RTMP_STAT_L(\">\\r\\n\");\n    }\n}\n\n\n#ifdef NGX_RTMP_POOL_DEBUG\nstatic void\nngx_rtmp_stat_get_pool_size(ngx_pool_t *pool, ngx_uint_t *nlarge,\n        ngx_uint_t *size)\n{\n    ngx_pool_large_t       *l;\n    ngx_pool_t             *p, *n;\n\n    *nlarge = 0;\n    for (l = pool->large; l; l = l->next) {\n        ++*nlarge;\n    }\n\n    *size = 0;\n    for (p = pool, n = pool->d.next; /* void */; p = n, n = n->d.next) {\n        *size += (p->d.last - (u_char *)p);\n        if (n == NULL) {\n            break;\n        }\n    }\n}\n\n\nstatic void\nngx_rtmp_stat_dump_pool(ngx_http_request_t *r, ngx_chain_t ***lll,\n        ngx_pool_t *pool)\n{\n    ngx_uint_t  nlarge, size;\n    u_char      buf[NGX_INT_T_LEN];\n\n    size = 0;\n    nlarge = 0;\n    ngx_rtmp_stat_get_pool_size(pool, &nlarge, &size);\n    NGX_RTMP_STAT_L(\"<pool><nlarge>\");\n    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), \"%ui\", nlarge) - buf);\n    NGX_RTMP_STAT_L(\"</nlarge><size>\");\n    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), \"%ui\", size) - buf);\n    NGX_RTMP_STAT_L(\"</size></pool>\\r\\n\");\n}\n#endif\n\n\n\nstatic void\nngx_rtmp_stat_client(ngx_http_request_t *r, ngx_chain_t ***lll,\n    ngx_rtmp_session_t *s)\n{\n    u_char  buf[NGX_INT_T_LEN];\n\n#ifdef NGX_RTMP_POOL_DEBUG\n    ngx_rtmp_stat_dump_pool(r, lll, s->connection->pool);\n#endif\n    NGX_RTMP_STAT_L(\"<id>\");\n    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), \"%ui\",\n                  (ngx_uint_t) s->connection->number) - buf);\n    NGX_RTMP_STAT_L(\"</id>\");\n\n    NGX_RTMP_STAT_L(\"<address>\");\n    NGX_RTMP_STAT_ES(&s->connection->addr_text);\n    NGX_RTMP_STAT_L(\"</address>\");\n\n    NGX_RTMP_STAT_L(\"<time>\");\n    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), \"%i\",\n                  (ngx_int_t) (ngx_current_msec - s->epoch)) - buf);\n    NGX_RTMP_STAT_L(\"</time>\");\n\n    if (s->flashver.len) {\n        NGX_RTMP_STAT_L(\"<flashver>\");\n        NGX_RTMP_STAT_ES(&s->flashver);\n        NGX_RTMP_STAT_L(\"</flashver>\");\n    }\n\n    if (s->page_url.len) {\n        NGX_RTMP_STAT_L(\"<pageurl>\");\n        NGX_RTMP_STAT_ES(&s->page_url);\n        NGX_RTMP_STAT_L(\"</pageurl>\");\n    }\n\n    if (s->swf_url.len) {\n        NGX_RTMP_STAT_L(\"<swfurl>\");\n        NGX_RTMP_STAT_ES(&s->swf_url);\n        NGX_RTMP_STAT_L(\"</swfurl>\");\n    }\n}\n\n\nstatic char *\nngx_rtmp_stat_get_aac_profile(ngx_uint_t p, ngx_uint_t sbr, ngx_uint_t ps) {\n    switch (p) {\n        case 1:\n            return \"Main\";\n        case 2:\n            if (ps) {\n                return \"HEv2\";\n            }\n            if (sbr) {\n                return \"HE\";\n            }\n            return \"LC\";\n        case 3:\n            return \"SSR\";\n        case 4:\n            return \"LTP\";\n        case 5:\n            return \"SBR\";\n        default:\n            return \"\";\n    }\n}\n\n\nstatic char *\nngx_rtmp_stat_get_avc_profile(ngx_uint_t p) {\n    switch (p) {\n        case 66:\n            return \"Baseline\";\n        case 77:\n            return \"Main\";\n        case 100:\n            return \"High\";\n        default:\n            return \"\";\n    }\n}\n\n\nstatic void\nngx_rtmp_stat_live(ngx_http_request_t *r, ngx_chain_t ***lll,\n        ngx_rtmp_live_app_conf_t *lacf)\n{\n    ngx_rtmp_live_stream_t         *stream;\n    ngx_rtmp_codec_ctx_t           *codec;\n    ngx_rtmp_live_ctx_t            *ctx;\n    ngx_rtmp_session_t             *s;\n    ngx_int_t                       n;\n    ngx_uint_t                      nclients, total_nclients;\n    u_char                          buf[NGX_INT_T_LEN];\n    u_char                          bbuf[NGX_INT32_LEN];\n    ngx_rtmp_stat_loc_conf_t       *slcf;\n    u_char                         *cname;\n\n    if (!lacf->live) {\n        return;\n    }\n\n    slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);\n\n    NGX_RTMP_STAT_L(\"<live>\\r\\n\");\n\n    total_nclients = 0;\n    for (n = 0; n < lacf->nbuckets; ++n) {\n        for (stream = lacf->streams[n]; stream; stream = stream->next) {\n            NGX_RTMP_STAT_L(\"<stream>\\r\\n\");\n\n            NGX_RTMP_STAT_L(\"<name>\");\n            NGX_RTMP_STAT_ECS(stream->name);\n            NGX_RTMP_STAT_L(\"</name>\\r\\n\");\n\n            NGX_RTMP_STAT_L(\"<time>\");\n            NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf), \"%i\",\n                          (ngx_int_t) (ngx_current_msec - stream->epoch))\n                          - buf);\n            NGX_RTMP_STAT_L(\"</time>\");\n\n            ngx_rtmp_stat_bw(r, lll, &stream->bw_in, \"in\",\n                             NGX_RTMP_STAT_BW_BYTES);\n            ngx_rtmp_stat_bw(r, lll, &stream->bw_out, \"out\",\n                             NGX_RTMP_STAT_BW_BYTES);\n            ngx_rtmp_stat_bw(r, lll, &stream->bw_in_audio, \"audio\",\n                             NGX_RTMP_STAT_BW);\n            ngx_rtmp_stat_bw(r, lll, &stream->bw_in_video, \"video\",\n                             NGX_RTMP_STAT_BW);\n\n            nclients = 0;\n            codec = NULL;\n            for (ctx = stream->ctx; ctx; ctx = ctx->next, ++nclients) {\n                s = ctx->session;\n                if (slcf->stat & NGX_RTMP_STAT_CLIENTS) {\n                    NGX_RTMP_STAT_L(\"<client>\");\n\n                    ngx_rtmp_stat_client(r, lll, s);\n\n                    NGX_RTMP_STAT_L(\"<dropped>\");\n                    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                                  \"%ui\", ctx->ndropped) - buf);\n                    NGX_RTMP_STAT_L(\"</dropped>\");\n\n                    NGX_RTMP_STAT_L(\"<avsync>\");\n                    if (!lacf->interleave) {\n                        NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf),\n                                      \"%D\", ctx->cs[1].timestamp -\n                                      ctx->cs[0].timestamp) - bbuf);\n                    }\n                    NGX_RTMP_STAT_L(\"</avsync>\");\n\n                    NGX_RTMP_STAT_L(\"<timestamp>\");\n                    NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf),\n                                  \"%D\", s->current_time) - bbuf);\n                    NGX_RTMP_STAT_L(\"</timestamp>\");\n\n                    if (ctx->publishing) {\n                        NGX_RTMP_STAT_L(\"<publishing/>\");\n                    }\n\n                    if (ctx->active) {\n                        NGX_RTMP_STAT_L(\"<active/>\");\n                    }\n\n                    NGX_RTMP_STAT_L(\"</client>\\r\\n\");\n                }\n                if (ctx->publishing) {\n                    codec = ngx_rtmp_get_module_ctx(s, ngx_rtmp_codec_module);\n                }\n            }\n            total_nclients += nclients;\n\n            if (codec) {\n                NGX_RTMP_STAT_L(\"<meta>\");\n\n                NGX_RTMP_STAT_L(\"<video>\");\n                NGX_RTMP_STAT_L(\"<width>\");\n                NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                              \"%ui\", codec->width) - buf);\n                NGX_RTMP_STAT_L(\"</width><height>\");\n                NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                              \"%ui\", codec->height) - buf);\n                NGX_RTMP_STAT_L(\"</height><frame_rate>\");\n                NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                              \"%ui\", codec->frame_rate) - buf);\n                NGX_RTMP_STAT_L(\"</frame_rate>\");\n\n                cname = ngx_rtmp_get_video_codec_name(codec->video_codec_id);\n                if (*cname) {\n                    NGX_RTMP_STAT_L(\"<codec>\");\n                    NGX_RTMP_STAT_ECS(cname);\n                    NGX_RTMP_STAT_L(\"</codec>\");\n                }\n                if (codec->avc_profile) {\n                    NGX_RTMP_STAT_L(\"<profile>\");\n                    NGX_RTMP_STAT_CS(\n                            ngx_rtmp_stat_get_avc_profile(codec->avc_profile));\n                    NGX_RTMP_STAT_L(\"</profile>\");\n                }\n                if (codec->avc_level) {\n                    NGX_RTMP_STAT_L(\"<compat>\");\n                    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                                  \"%ui\", codec->avc_compat) - buf);\n                    NGX_RTMP_STAT_L(\"</compat>\");\n                }\n                if (codec->avc_level) {\n                    NGX_RTMP_STAT_L(\"<level>\");\n                    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                                  \"%.1f\", codec->avc_level / 10.) - buf);\n                    NGX_RTMP_STAT_L(\"</level>\");\n                }\n                NGX_RTMP_STAT_L(\"</video>\");\n\n                NGX_RTMP_STAT_L(\"<audio>\");\n                cname = ngx_rtmp_get_audio_codec_name(codec->audio_codec_id);\n                if (*cname) {\n                    NGX_RTMP_STAT_L(\"<codec>\");\n                    NGX_RTMP_STAT_ECS(cname);\n                    NGX_RTMP_STAT_L(\"</codec>\");\n                }\n                if (codec->aac_profile) {\n                    NGX_RTMP_STAT_L(\"<profile>\");\n                    NGX_RTMP_STAT_CS(\n                            ngx_rtmp_stat_get_aac_profile(codec->aac_profile,\n                                                          codec->aac_sbr,\n                                                          codec->aac_ps));\n                    NGX_RTMP_STAT_L(\"</profile>\");\n                }\n                if (codec->aac_chan_conf) {\n                    NGX_RTMP_STAT_L(\"<channels>\");\n                    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                                  \"%ui\", codec->aac_chan_conf) - buf);\n                    NGX_RTMP_STAT_L(\"</channels>\");\n                } else if (codec->audio_channels) {\n                    NGX_RTMP_STAT_L(\"<channels>\");\n                    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                                  \"%ui\", codec->audio_channels) - buf);\n                    NGX_RTMP_STAT_L(\"</channels>\");\n                }\n                if (codec->sample_rate) {\n                    NGX_RTMP_STAT_L(\"<sample_rate>\");\n                    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                                  \"%ui\", codec->sample_rate) - buf);\n                    NGX_RTMP_STAT_L(\"</sample_rate>\");\n                }\n                NGX_RTMP_STAT_L(\"</audio>\");\n\n                NGX_RTMP_STAT_L(\"</meta>\\r\\n\");\n            }\n\n            NGX_RTMP_STAT_L(\"<nclients>\");\n            NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                          \"%ui\", nclients) - buf);\n            NGX_RTMP_STAT_L(\"</nclients>\\r\\n\");\n\n            if (stream->publishing) {\n                NGX_RTMP_STAT_L(\"<publishing/>\\r\\n\");\n            }\n\n            if (stream->active) {\n                NGX_RTMP_STAT_L(\"<active/>\\r\\n\");\n            }\n\n            NGX_RTMP_STAT_L(\"</stream>\\r\\n\");\n        }\n    }\n\n    NGX_RTMP_STAT_L(\"<nclients>\");\n    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                  \"%ui\", total_nclients) - buf);\n    NGX_RTMP_STAT_L(\"</nclients>\\r\\n\");\n\n    NGX_RTMP_STAT_L(\"</live>\\r\\n\");\n}\n\n\nstatic void\nngx_rtmp_stat_play(ngx_http_request_t *r, ngx_chain_t ***lll,\n        ngx_rtmp_play_app_conf_t *pacf)\n{\n    ngx_rtmp_play_ctx_t            *ctx, *sctx;\n    ngx_rtmp_session_t             *s;\n    ngx_uint_t                      n, nclients, total_nclients;\n    u_char                          buf[NGX_INT_T_LEN];\n    u_char                          bbuf[NGX_INT32_LEN];\n    ngx_rtmp_stat_loc_conf_t       *slcf;\n\n    if (pacf->entries.nelts == 0) {\n        return;\n    }\n\n    slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);\n\n    NGX_RTMP_STAT_L(\"<play>\\r\\n\");\n\n    total_nclients = 0;\n    for (n = 0; n < pacf->nbuckets; ++n) {\n        for (ctx = pacf->ctx[n]; ctx; ) {\n            NGX_RTMP_STAT_L(\"<stream>\\r\\n\");\n\n            NGX_RTMP_STAT_L(\"<name>\");\n            NGX_RTMP_STAT_ECS(ctx->name);\n            NGX_RTMP_STAT_L(\"</name>\\r\\n\");\n\n            nclients = 0;\n            sctx = ctx;\n            for (; ctx; ctx = ctx->next) {\n                if (ngx_strcmp(ctx->name, sctx->name)) {\n                    break;\n                }\n\n                nclients++;\n\n                s = ctx->session;\n                if (slcf->stat & NGX_RTMP_STAT_CLIENTS) {\n                    NGX_RTMP_STAT_L(\"<client>\");\n\n                    ngx_rtmp_stat_client(r, lll, s);\n\n                    NGX_RTMP_STAT_L(\"<timestamp>\");\n                    NGX_RTMP_STAT(bbuf, ngx_snprintf(bbuf, sizeof(bbuf),\n                                  \"%D\", s->current_time) - bbuf);\n                    NGX_RTMP_STAT_L(\"</timestamp>\");\n\n                    NGX_RTMP_STAT_L(\"</client>\\r\\n\");\n                }\n            }\n            total_nclients += nclients;\n\n            NGX_RTMP_STAT_L(\"<active/>\");\n            NGX_RTMP_STAT_L(\"<nclients>\");\n            NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                          \"%ui\", nclients) - buf);\n            NGX_RTMP_STAT_L(\"</nclients>\\r\\n\");\n\n            NGX_RTMP_STAT_L(\"</stream>\\r\\n\");\n        }\n    }\n\n    NGX_RTMP_STAT_L(\"<nclients>\");\n    NGX_RTMP_STAT(buf, ngx_snprintf(buf, sizeof(buf),\n                  \"%ui\", total_nclients) - buf);\n    NGX_RTMP_STAT_L(\"</nclients>\\r\\n\");\n\n    NGX_RTMP_STAT_L(\"</play>\\r\\n\");\n}\n\n\nstatic void\nngx_rtmp_stat_application(ngx_http_request_t *r, ngx_chain_t ***lll,\n        ngx_rtmp_core_app_conf_t *cacf)\n{\n    ngx_rtmp_stat_loc_conf_t       *slcf;\n\n    NGX_RTMP_STAT_L(\"<application>\\r\\n\");\n    NGX_RTMP_STAT_L(\"<name>\");\n    NGX_RTMP_STAT_ES(&cacf->name);\n    NGX_RTMP_STAT_L(\"</name>\\r\\n\");\n\n    slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);\n\n    if (slcf->stat & NGX_RTMP_STAT_LIVE) {\n        ngx_rtmp_stat_live(r, lll,\n                cacf->app_conf[ngx_rtmp_live_module.ctx_index]);\n    }\n\n    if (slcf->stat & NGX_RTMP_STAT_PLAY) {\n        ngx_rtmp_stat_play(r, lll,\n                cacf->app_conf[ngx_rtmp_play_module.ctx_index]);\n    }\n\n    NGX_RTMP_STAT_L(\"</application>\\r\\n\");\n}\n\n\nstatic void\nngx_rtmp_stat_server(ngx_http_request_t *r, ngx_chain_t ***lll,\n        ngx_rtmp_core_srv_conf_t *cscf)\n{\n    ngx_rtmp_core_app_conf_t      **cacf;\n    size_t                          n;\n\n    NGX_RTMP_STAT_L(\"<server>\\r\\n\");\n\n#ifdef NGX_RTMP_POOL_DEBUG\n    ngx_rtmp_stat_dump_pool(r, lll, cscf->pool);\n#endif\n\n    cacf = cscf->applications.elts;\n    for (n = 0; n < cscf->applications.nelts; ++n, ++cacf) {\n        ngx_rtmp_stat_application(r, lll, *cacf);\n    }\n\n    NGX_RTMP_STAT_L(\"</server>\\r\\n\");\n}\n\n\nstatic ngx_int_t\nngx_rtmp_stat_handler(ngx_http_request_t *r)\n{\n    ngx_rtmp_stat_loc_conf_t       *slcf;\n    ngx_rtmp_core_main_conf_t      *cmcf;\n    ngx_rtmp_core_srv_conf_t      **cscf;\n    ngx_chain_t                    *cl, *l, **ll, ***lll;\n    size_t                          n;\n    off_t                           len;\n    static u_char                   tbuf[NGX_TIME_T_LEN];\n    static u_char                   nbuf[NGX_INT_T_LEN];\n\n    slcf = ngx_http_get_module_loc_conf(r, ngx_rtmp_stat_module);\n    if (slcf->stat == 0) {\n        return NGX_DECLINED;\n    }\n\n    cmcf = ngx_rtmp_core_main_conf;\n    if (cmcf == NULL) {\n        goto error;\n    }\n\n    cl = NULL;\n    ll = &cl;\n    lll = &ll;\n\n    NGX_RTMP_STAT_L(\"<?xml version=\\\"1.0\\\" encoding=\\\"utf-8\\\" ?>\\r\\n\");\n    if (slcf->stylesheet.len) {\n        NGX_RTMP_STAT_L(\"<?xml-stylesheet type=\\\"text/xsl\\\" href=\\\"\");\n        NGX_RTMP_STAT_ES(&slcf->stylesheet);\n        NGX_RTMP_STAT_L(\"\\\" ?>\\r\\n\");\n    }\n\n    NGX_RTMP_STAT_L(\"<rtmp>\\r\\n\");\n\n#ifdef NGINX_VERSION\n    NGX_RTMP_STAT_L(\"<nginx_version>\" NGINX_VERSION \"</nginx_version>\\r\\n\");\n#endif\n\n#ifdef NGINX_RTMP_VERSION\n    NGX_RTMP_STAT_L(\"<nginx_rtmp_version>\" NGINX_RTMP_VERSION \"</nginx_rtmp_version>\\r\\n\");\n#endif\n\n#ifdef NGX_COMPILER\n    NGX_RTMP_STAT_L(\"<compiler>\" NGX_COMPILER \"</compiler>\\r\\n\");\n#endif\n    NGX_RTMP_STAT_L(\"<built>\" __DATE__ \" \" __TIME__ \"</built>\\r\\n\");\n\n    NGX_RTMP_STAT_L(\"<pid>\");\n    NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf),\n                  \"%ui\", (ngx_uint_t) ngx_getpid()) - nbuf);\n    NGX_RTMP_STAT_L(\"</pid>\\r\\n\");\n\n    NGX_RTMP_STAT_L(\"<uptime>\");\n    NGX_RTMP_STAT(tbuf, ngx_snprintf(tbuf, sizeof(tbuf),\n                  \"%T\", ngx_cached_time->sec - start_time) - tbuf);\n    NGX_RTMP_STAT_L(\"</uptime>\\r\\n\");\n\n    NGX_RTMP_STAT_L(\"<naccepted>\");\n    NGX_RTMP_STAT(nbuf, ngx_snprintf(nbuf, sizeof(nbuf),\n                  \"%ui\", ngx_rtmp_naccepted) - nbuf);\n    NGX_RTMP_STAT_L(\"</naccepted>\\r\\n\");\n\n    ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_in, \"in\", NGX_RTMP_STAT_BW_BYTES);\n    ngx_rtmp_stat_bw(r, lll, &ngx_rtmp_bw_out, \"out\", NGX_RTMP_STAT_BW_BYTES);\n\n    cscf = cmcf->servers.elts;\n    for (n = 0; n < cmcf->servers.nelts; ++n, ++cscf) {\n        ngx_rtmp_stat_server(r, lll, *cscf);\n    }\n\n    NGX_RTMP_STAT_L(\"</rtmp>\\r\\n\");\n\n    len = 0;\n    for (l = cl; l; l = l->next) {\n        len += (l->buf->last - l->buf->pos);\n    }\n    ngx_str_set(&r->headers_out.content_type, \"text/xml\");\n    r->headers_out.content_length_n = len;\n    r->headers_out.status = NGX_HTTP_OK;\n    ngx_http_send_header(r);\n    (*ll)->buf->last_buf = 1;\n    return ngx_http_output_filter(r, cl);\n\nerror:\n    r->headers_out.status = NGX_HTTP_INTERNAL_SERVER_ERROR;\n    r->headers_out.content_length_n = 0;\n    return ngx_http_send_header(r);\n}\n\n\nstatic void *\nngx_rtmp_stat_create_loc_conf(ngx_conf_t *cf)\n{\n    ngx_rtmp_stat_loc_conf_t       *conf;\n\n    conf = ngx_pcalloc(cf->pool, sizeof(ngx_rtmp_stat_loc_conf_t));\n    if (conf == NULL) {\n        return NULL;\n    }\n\n    conf->stat = 0;\n\n    return conf;\n}\n\n\nstatic char *\nngx_rtmp_stat_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)\n{\n    ngx_rtmp_stat_loc_conf_t       *prev = parent;\n    ngx_rtmp_stat_loc_conf_t       *conf = child;\n\n    ngx_conf_merge_bitmask_value(conf->stat, prev->stat, 0);\n    ngx_conf_merge_str_value(conf->stylesheet, prev->stylesheet, \"\");\n\n    return NGX_CONF_OK;\n}\n\n\nstatic char *\nngx_rtmp_stat(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)\n{\n    ngx_http_core_loc_conf_t  *clcf;\n\n    clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);\n    clcf->handler = ngx_rtmp_stat_handler;\n\n    return ngx_conf_set_bitmask_slot(cf, cmd, conf);\n}\n\n\nstatic ngx_int_t\nngx_rtmp_stat_postconfiguration(ngx_conf_t *cf)\n{\n    start_time = ngx_cached_time->sec;\n\n    return NGX_OK;\n}\n"
  },
  {
    "path": "ngx_rtmp_streams.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_STREAMS_H_INCLUDED_\n#define _NGX_RTMP_STREAMS_H_INCLUDED_\n\n\n#define NGX_RTMP_MSID                   1\n\n#define NGX_RTMP_CSID_AMF_INI           3\n#define NGX_RTMP_CSID_AMF               5\n#define NGX_RTMP_CSID_AUDIO             6\n#define NGX_RTMP_CSID_VIDEO             7\n\n\n#endif /* _NGX_RTMP_STREAMS_H_INCLUDED_ */\n"
  },
  {
    "path": "ngx_rtmp_version.h",
    "content": "\n/*\n * Copyright (C) Roman Arutyunyan\n */\n\n\n#ifndef _NGX_RTMP_VERSION_H_INCLUDED_\n#define _NGX_RTMP_VERSION_H_INCLUDED_\n\n\n#define nginx_rtmp_version  1001004\n#define NGINX_RTMP_VERSION  \"1.1.4\"\n\n\n#endif /* _NGX_RTMP_VERSION_H_INCLUDED_ */\n"
  },
  {
    "path": "stat.xsl",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n\n\n<!--\n   Copyright (C) Roman Arutyunyan\n-->\n\n\n<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n\n\n<xsl:template match=\"/\">\n    <html>\n        <head>\n            <title>RTMP statistics</title>\n        </head>\n        <body>\n            <xsl:apply-templates select=\"rtmp\"/>\n            <hr/>\n            Generated by <a href='https://github.com/arut/nginx-rtmp-module'>\n            nginx-rtmp-module</a>&#160;<xsl:value-of select=\"/rtmp/nginx_rtmp_version\"/>,\n            <a href=\"http://nginx.org\">nginx</a>&#160;<xsl:value-of select=\"/rtmp/nginx_version\"/>,\n            pid <xsl:value-of select=\"/rtmp/pid\"/>,\n            built <xsl:value-of select=\"/rtmp/built\"/>&#160;<xsl:value-of select=\"/rtmp/compiler\"/>\n        </body>\n    </html>\n</xsl:template>\n\n<xsl:template match=\"rtmp\">\n    <table cellspacing=\"1\" cellpadding=\"5\">\n        <tr bgcolor=\"#999999\">\n            <th>RTMP</th>\n            <th>#clients</th>\n            <th colspan=\"4\">Video</th>\n            <th colspan=\"4\">Audio</th>\n            <th>In bytes</th>\n            <th>Out bytes</th>\n            <th>In bits/s</th>\n            <th>Out bits/s</th>\n            <th>State</th>\n            <th>Time</th>\n        </tr>\n        <tr>\n            <td colspan=\"2\">Accepted: <xsl:value-of select=\"naccepted\"/></td>\n            <th bgcolor=\"#999999\">codec</th>\n            <th bgcolor=\"#999999\">bits/s</th>\n            <th bgcolor=\"#999999\">size</th>\n            <th bgcolor=\"#999999\">fps</th>\n            <th bgcolor=\"#999999\">codec</th>\n            <th bgcolor=\"#999999\">bits/s</th>\n            <th bgcolor=\"#999999\">freq</th>\n            <th bgcolor=\"#999999\">chan</th>\n            <td>\n                <xsl:call-template name=\"showsize\">\n                    <xsl:with-param name=\"size\" select=\"bytes_in\"/>\n                </xsl:call-template>\n            </td>\n            <td>\n                <xsl:call-template name=\"showsize\">\n                    <xsl:with-param name=\"size\" select=\"bytes_out\"/>\n                </xsl:call-template>\n            </td>\n            <td>\n                <xsl:call-template name=\"showsize\">\n                    <xsl:with-param name=\"size\" select=\"bw_in\"/>\n                    <xsl:with-param name=\"bits\" select=\"1\"/>\n                    <xsl:with-param name=\"persec\" select=\"1\"/>\n                </xsl:call-template>\n            </td>\n            <td>\n                <xsl:call-template name=\"showsize\">\n                    <xsl:with-param name=\"size\" select=\"bw_out\"/>\n                    <xsl:with-param name=\"bits\" select=\"1\"/>\n                    <xsl:with-param name=\"persec\" select=\"1\"/>\n                </xsl:call-template>\n            </td>\n            <td/>\n            <td>\n                <xsl:call-template name=\"showtime\">\n                    <xsl:with-param name=\"time\" select=\"/rtmp/uptime * 1000\"/>\n                </xsl:call-template>\n            </td>\n        </tr>\n        <xsl:apply-templates select=\"server\"/>\n    </table>\n</xsl:template>\n\n<xsl:template match=\"server\">\n    <xsl:apply-templates select=\"application\"/>\n</xsl:template>\n\n<xsl:template match=\"application\">\n    <tr bgcolor=\"#999999\">\n        <td>\n            <b><xsl:value-of select=\"name\"/></b>\n        </td>\n    </tr>\n    <xsl:apply-templates select=\"live\"/>\n    <xsl:apply-templates select=\"play\"/>\n</xsl:template>\n\n<xsl:template match=\"live\">\n    <tr bgcolor=\"#aaaaaa\">\n        <td>\n            <i>live streams</i>\n        </td>\n        <td align=\"middle\">\n            <xsl:value-of select=\"nclients\"/>\n        </td>\n    </tr>\n    <xsl:apply-templates select=\"stream\"/>\n</xsl:template>\n\n<xsl:template match=\"play\">\n    <tr bgcolor=\"#aaaaaa\">\n        <td>\n            <i>vod streams</i>\n        </td>\n        <td align=\"middle\">\n            <xsl:value-of select=\"nclients\"/>\n        </td>\n    </tr>\n    <xsl:apply-templates select=\"stream\"/>\n</xsl:template>\n\n<xsl:template match=\"stream\">\n    <tr valign=\"top\">\n        <xsl:attribute name=\"bgcolor\">\n            <xsl:choose>\n                <xsl:when test=\"active\">#cccccc</xsl:when>\n                <xsl:otherwise>#dddddd</xsl:otherwise>\n            </xsl:choose>\n        </xsl:attribute>\n        <td>\n            <a href=\"\">\n                <xsl:attribute name=\"onclick\">\n                    var d=document.getElementById('<xsl:value-of select=\"../../name\"/>-<xsl:value-of select=\"name\"/>');\n                    d.style.display=d.style.display=='none'?'':'none';\n                    return false\n                </xsl:attribute>\n                <xsl:value-of select=\"name\"/>\n                <xsl:if test=\"string-length(name) = 0\">\n                    [EMPTY]\n                </xsl:if>\n            </a>\n        </td>\n        <td align=\"middle\"> <xsl:value-of select=\"nclients\"/> </td>\n        <td>\n            <xsl:value-of select=\"meta/video/codec\"/>&#160;<xsl:value-of select=\"meta/video/profile\"/>&#160;<xsl:value-of select=\"meta/video/level\"/>\n        </td>\n        <td>\n            <xsl:call-template name=\"showsize\">\n                <xsl:with-param name=\"size\" select=\"bw_video\"/>\n                <xsl:with-param name=\"bits\" select=\"1\"/>\n                <xsl:with-param name=\"persec\" select=\"1\"/>\n            </xsl:call-template>\n        </td>\n        <td>\n            <xsl:apply-templates select=\"meta/video/width\"/>\n        </td>\n        <td>\n            <xsl:value-of select=\"meta/video/frame_rate\"/>\n        </td>\n        <td>\n            <xsl:value-of select=\"meta/audio/codec\"/>&#160;<xsl:value-of select=\"meta/audio/profile\"/>\n        </td>\n        <td>\n            <xsl:call-template name=\"showsize\">\n                <xsl:with-param name=\"size\" select=\"bw_audio\"/>\n                <xsl:with-param name=\"bits\" select=\"1\"/>\n                <xsl:with-param name=\"persec\" select=\"1\"/>\n            </xsl:call-template>\n        </td>\n        <td>\n            <xsl:apply-templates select=\"meta/audio/sample_rate\"/>\n        </td>\n        <td>\n            <xsl:value-of select=\"meta/audio/channels\"/>\n        </td>\n        <td>\n            <xsl:call-template name=\"showsize\">\n               <xsl:with-param name=\"size\" select=\"bytes_in\"/>\n           </xsl:call-template>\n        </td>\n        <td>\n            <xsl:call-template name=\"showsize\">\n                <xsl:with-param name=\"size\" select=\"bytes_out\"/>\n            </xsl:call-template>\n        </td>\n        <td>\n            <xsl:call-template name=\"showsize\">\n                <xsl:with-param name=\"size\" select=\"bw_in\"/>\n                <xsl:with-param name=\"bits\" select=\"1\"/>\n                <xsl:with-param name=\"persec\" select=\"1\"/>\n            </xsl:call-template>\n        </td>\n        <td>\n            <xsl:call-template name=\"showsize\">\n                <xsl:with-param name=\"size\" select=\"bw_out\"/>\n                <xsl:with-param name=\"bits\" select=\"1\"/>\n                <xsl:with-param name=\"persec\" select=\"1\"/>\n            </xsl:call-template>\n        </td>\n        <td><xsl:call-template name=\"streamstate\"/></td>\n        <td>\n            <xsl:call-template name=\"showtime\">\n               <xsl:with-param name=\"time\" select=\"time\"/>\n            </xsl:call-template>\n        </td>\n    </tr>\n    <tr style=\"display:none\">\n        <xsl:attribute name=\"id\">\n            <xsl:value-of select=\"../../name\"/>-<xsl:value-of select=\"name\"/>\n        </xsl:attribute>\n        <td colspan=\"16\" ngcolor=\"#eeeeee\">\n            <table cellspacing=\"1\" cellpadding=\"5\">\n                <tr>\n                    <th>Id</th>\n                    <th>State</th>\n                    <th>Address</th>\n                    <th>Flash version</th>\n                    <th>Page URL</th>\n                    <th>SWF URL</th>\n                    <th>Dropped</th>\n                    <th>Timestamp</th>\n                    <th>A-V</th>\n                    <th>Time</th>\n                </tr>\n                <xsl:apply-templates select=\"client\"/>\n            </table>\n        </td>\n    </tr>\n</xsl:template>\n\n<xsl:template name=\"showtime\">\n    <xsl:param name=\"time\"/>\n\n    <xsl:if test=\"$time &gt; 0\">\n        <xsl:variable name=\"sec\">\n            <xsl:value-of select=\"floor($time div 1000)\"/>\n        </xsl:variable>\n\n        <xsl:if test=\"$sec &gt;= 86400\">\n            <xsl:value-of select=\"floor($sec div 86400)\"/>d\n        </xsl:if>\n\n        <xsl:if test=\"$sec &gt;= 3600\">\n            <xsl:value-of select=\"(floor($sec div 3600)) mod 24\"/>h\n        </xsl:if>\n\n        <xsl:if test=\"$sec &gt;= 60\">\n            <xsl:value-of select=\"(floor($sec div 60)) mod 60\"/>m\n        </xsl:if>\n\n        <xsl:value-of select=\"$sec mod 60\"/>s\n    </xsl:if>\n</xsl:template>\n\n<xsl:template name=\"showsize\">\n    <xsl:param name=\"size\"/>\n    <xsl:param name=\"bits\" select=\"0\" />\n    <xsl:param name=\"persec\" select=\"0\" />\n    <xsl:variable name=\"sizen\">\n        <xsl:value-of select=\"floor($size div 1024)\"/>\n    </xsl:variable>\n    <xsl:choose>\n        <xsl:when test=\"$sizen &gt;= 1073741824\">\n            <xsl:value-of select=\"format-number($sizen div 1073741824,'#.###')\"/> T</xsl:when>\n\n        <xsl:when test=\"$sizen &gt;= 1048576\">\n            <xsl:value-of select=\"format-number($sizen div 1048576,'#.###')\"/> G</xsl:when>\n\n        <xsl:when test=\"$sizen &gt;= 1024\">\n            <xsl:value-of select=\"format-number($sizen div 1024,'#.##')\"/> M</xsl:when>\n        <xsl:when test=\"$sizen &gt;= 0\">\n            <xsl:value-of select=\"$sizen\"/> K</xsl:when>\n    </xsl:choose>\n    <xsl:if test=\"string-length($size) &gt; 0\">\n        <xsl:choose>\n            <xsl:when test=\"$bits = 1\">b</xsl:when>\n            <xsl:otherwise>B</xsl:otherwise>\n        </xsl:choose>\n        <xsl:if test=\"$persec = 1\">/s</xsl:if>\n    </xsl:if>\n</xsl:template>\n\n<xsl:template name=\"streamstate\">\n    <xsl:choose>\n        <xsl:when test=\"active\">active</xsl:when>\n        <xsl:otherwise>idle</xsl:otherwise>\n    </xsl:choose>\n</xsl:template>\n\n\n<xsl:template name=\"clientstate\">\n    <xsl:choose>\n        <xsl:when test=\"publishing\">publishing</xsl:when>\n        <xsl:otherwise>playing</xsl:otherwise>\n    </xsl:choose>\n</xsl:template>\n\n\n<xsl:template match=\"client\">\n    <tr>\n        <xsl:attribute name=\"bgcolor\">\n            <xsl:choose>\n                <xsl:when test=\"publishing\">#cccccc</xsl:when>\n                <xsl:otherwise>#eeeeee</xsl:otherwise>\n            </xsl:choose>\n        </xsl:attribute>\n        <td><xsl:value-of select=\"id\"/></td>\n        <td><xsl:call-template name=\"clientstate\"/></td>\n        <td>\n            <a target=\"_blank\">\n                <xsl:attribute name=\"href\">\n                    http://apps.db.ripe.net/search/query.html&#63;searchtext=<xsl:value-of select=\"address\"/>\n                </xsl:attribute>\n                <xsl:attribute name=\"title\">whois</xsl:attribute>\n                <xsl:value-of select=\"address\"/>\n            </a>\n        </td>\n        <td><xsl:value-of select=\"flashver\"/></td>\n        <td>\n            <a target=\"_blank\">\n                <xsl:attribute name=\"href\">\n                    <xsl:value-of select=\"pageurl\"/>\n                </xsl:attribute>\n                <xsl:value-of select=\"pageurl\"/>\n            </a>\n        </td>\n        <td><xsl:value-of select=\"swfurl\"/></td>\n        <td><xsl:value-of select=\"dropped\"/></td>\n        <td><xsl:value-of select=\"timestamp\"/></td>\n        <td><xsl:value-of select=\"avsync\"/></td>\n        <td>\n            <xsl:call-template name=\"showtime\">\n               <xsl:with-param name=\"time\" select=\"time\"/>\n            </xsl:call-template>\n        </td>\n    </tr>\n</xsl:template>\n\n<xsl:template match=\"publishing\">\n    publishing\n</xsl:template>\n\n<xsl:template match=\"active\">\n    active\n</xsl:template>\n\n<xsl:template match=\"width\">\n    <xsl:value-of select=\".\"/>x<xsl:value-of select=\"../height\"/>\n</xsl:template>\n\n</xsl:stylesheet>\n"
  },
  {
    "path": "test/README.md",
    "content": "# RTMP tests\n\nnginx.conf is sample config for testing nginx-rtmp.\nPlease update paths in it before using.\n\nRTMP port: 1935, HTTP port: 8080\n\n* http://localhost:8080/ - play myapp/mystream with JWPlayer\n* http://localhost:8080/record.html - capture myapp/mystream from webcam with old JWPlayer\n* http://localhost:8080/rtmp-publisher/player.html - play myapp/mystream with the test flash applet\n* http://localhost:8080/rtmp-publisher/publisher.html - capture myapp/mystream with the test flash applet\n"
  },
  {
    "path": "test/dump.sh",
    "content": "rtmpdump -v -r \"rtmp://localhost/myapp/mystream\"\n"
  },
  {
    "path": "test/ffstream.sh",
    "content": "ffmpeg -loglevel verbose -re -i ~/movie.avi  -f flv rtmp://localhost/myapp/mystream\n"
  },
  {
    "path": "test/nginx.conf",
    "content": "worker_processes  1;\n\nerror_log  logs/error.log debug;\n\nevents {\n    worker_connections  1024;\n}\n\nrtmp {\n    server {\n        listen 1935;\n\n        application myapp {\n            live on;\n\n            #record keyframes;\n            #record_path /tmp;\n            #record_max_size 128K;\n            #record_interval 30s;\n            #record_suffix .this.is.flv;\n\n            #on_publish http://localhost:8080/publish;\n            #on_play http://localhost:8080/play;\n            #on_record_done http://localhost:8080/record_done;\n        }\n    }\n}\n\nhttp {\n    server {\n        listen      8080;\n\n        location /stat {\n            rtmp_stat all;\n            rtmp_stat_stylesheet stat.xsl;\n        }\n\n        location /stat.xsl {\n            root /path/to/nginx-rtmp-module/;\n        }\n\n        location /control {\n            rtmp_control all;\n        }\n\n        #location /publish {\n        #    return 201;\n        #}\n\n        #location /play {\n        #    return 202;\n        #}\n\n        #location /record_done {\n        #    return 203;\n        #}\n\n        location /rtmp-publisher {\n            root /path/to/nginx-rtmp-module/test;\n        }\n\n        location / {\n            root /path/to/nginx-rtmp-module/test/www;\n        }\n    }\n}\n"
  },
  {
    "path": "test/play.sh",
    "content": "ffplay -loglevel verbose \"rtmp://localhost/myapp/mystream\"\n"
  },
  {
    "path": "test/rtmp-publisher/README.md",
    "content": "# RTMP Publisher\n\nSimple RTMP publisher.\n\nEdit the following flashvars in publisher.html & player.html to suite your needs.\n\nstreamer: RTMP endpoint\nfile: live stream name\n\n## Compile\n\nInstall flex sdk http://www.adobe.com/devnet/flex/flex-sdk-download.html\n\n    mxmlc RtmpPublisher.mxml\n    mxmlc RtmpPlayer.mxml\n"
  },
  {
    "path": "test/rtmp-publisher/RtmpPlayer.mxml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<s:Application xmlns:fx=\"http://ns.adobe.com/mxml/2009\"\n\t\t\txmlns:s=\"library://ns.adobe.com/flex/spark\"\n\t\t\txmlns:mx=\"library://ns.adobe.com/flex/mx\"\n\t\t\tminWidth=\"500\" minHeight=\"350\" creationComplete=\"init()\">\n\n\t  <fx:Script>\n\t\t<![CDATA[\n\t\t\timport mx.controls.Alert;\n\t\t\timport mx.core.FlexGlobals;\n\t\t\timport flash.display.StageDisplayState;\n\t\t\timport mx.managers.SystemManager;\n\n\t\t\tprivate var streamer:String;\n\t\t\tprivate var file:String;\n\n\t\t\tprivate function playButtonListener(event:MouseEvent):void {\n\t\t\t\tif(playButton.label == 'Play') {\n\t\t\t\t\tplayButton.label = 'Stop';\n\t\t\t\t\tvideoDisplay.source = streamer + \"/\" + file;\n\t\t\t\t\tvideoDisplay.play();\n\t\t\t\t} else {\n\t\t\t\t\tplayButton.label = 'Play';\n\t\t\t\t\tvideoDisplay.source = \"\";\n\t\t\t\t\t//videoDisplay.close();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprivate function fullscreenButtonListener(event:MouseEvent):void {\n\t\t\t\ttry {\n\t\t\t\t\tswitch (systemManager.stage.displayState) {\n\t\t\t\t\t\tcase StageDisplayState.FULL_SCREEN:\n\t\t\t\t\t\t\tstage.displayState = StageDisplayState.NORMAL;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tstage.displayState = StageDisplayState.FULL_SCREEN;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} catch (err:SecurityError) {\n\t\t\t\t\t// ignore\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprivate function init():void {\n\t\t\t\tvideoDisplay.mx_internal::videoPlayer.bufferTime = 1;\n\n\t\t\t\tstreamer = FlexGlobals.topLevelApplication.parameters.streamer;\n\t\t\t\tif(streamer == null) {\n\t\t\t\t\tAlert.show('Missing flashvars: streamer');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfile = FlexGlobals.topLevelApplication.parameters.file;\n\t\t\t\tif(file == null) {\n\t\t\t\t\tAlert.show('Missing flashvars: file');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tplayButton.addEventListener(MouseEvent.CLICK, playButtonListener);\n\t\t\t\tfullscreenButton.addEventListener(MouseEvent.CLICK, fullscreenButtonListener);\n\t\t\t}\n\t    ]]>\n\t  </fx:Script>\n\t<s:BorderContainer x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">\n\t\t<s:VideoDisplay width=\"100%\" height=\"100%\" id=\"videoDisplay\"></s:VideoDisplay>\n\t\t<s:Button label=\"Play\" id=\"playButton\" horizontalCenter=\"0\" bottom=\"10\"></s:Button>\n\t\t<s:Button label=\"[ ]\" id=\"fullscreenButton\" right=\"10\" bottom=\"10\"></s:Button>\n\t</s:BorderContainer>\n</s:Application>\n"
  },
  {
    "path": "test/rtmp-publisher/RtmpPlayerLight.mxml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<s:Application xmlns:fx=\"http://ns.adobe.com/mxml/2009\"\n\t\t\txmlns:s=\"library://ns.adobe.com/flex/spark\"\n\t\t\txmlns:mx=\"library://ns.adobe.com/flex/mx\"\n\t\t\tminWidth=\"500\" minHeight=\"350\" creationComplete=\"init()\">\n\n\t  <fx:Script>\n\t\t<![CDATA[\n\t\t\timport mx.controls.Alert;\n\t\t\timport mx.core.FlexGlobals;\n\t\t\timport flash.display.StageDisplayState;\n\t\t\timport mx.managers.SystemManager;\n\t\t\timport org.osmf.events.MediaPlayerStateChangeEvent;\n\t\t\timport org.osmf.events.TimeEvent;\n\t\t\timport org.osmf.media.MediaPlayerState;\n\n\t\t\tprivate var streamer:String;\n\t\t\tprivate var file:String;\n\t\t\tprivate var videoEventsDisabled:Boolean;\n\t\t\tprivate var previousVideoTime:Number;\n\n\t\t\tprivate function fullscreenListener(event:MouseEvent):void {\n\t\t\t\ttry {\n\t\t\t\t\tswitch (stage.displayState) {\n\t\t\t\t\t\tcase StageDisplayState.FULL_SCREEN:\n\t\t\t\t\t\t\tstage.displayState = StageDisplayState.NORMAL;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tdefault:\n\t\t\t\t\t\t\tstage.displayState = StageDisplayState.FULL_SCREEN;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} catch (err:SecurityError) {\n\t\t\t\t\tAlert.show('Fullsceen error: ' + err);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprivate function resetVideo():void {\n\t\t\t\tvideoEventsDisabled = true;\n\n\t\t\t\ttry {\n\t\t\t\t\tvideoDisplay.source = \"\";\n\t\t\t\t} catch (any:*) {}\n\n\t\t\t\tsetTimeout(resetVideoSource, 5000);\n\t\t\t}\n\n\t\t\tprivate function resetVideoSource():void {\n\t\t\t\tvideoEventsDisabled = false;\n\t\t\t\tpreviousVideoTime = NaN;\n\t\t\t\tvideoDisplay.source = streamer + \"/\" + file;\n\t\t\t}\n\n\t\t\tprotected function stateChangeListener(event:MediaPlayerStateChangeEvent):void {\n\t\t\t\tif (videoEventsDisabled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (event.state == MediaPlayerState.PLAYBACK_ERROR) {\n\t\t\t\t\tresetVideo();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprotected function timeChangeListener(event:TimeEvent):void {\n\t\t\t\tif (videoEventsDisabled) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (isNaN(event.time) && !isNaN(previousVideoTime)) {\n\t\t\t\t\tresetVideo();\n\t\t\t\t} else {\n\t\t\t\t\tpreviousVideoTime = event.time;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprivate function init():void {\n\t\t\t\tvideoDisplay.mx_internal::videoPlayer.bufferTime = 1;\n\n\t\t\t\tstreamer = FlexGlobals.topLevelApplication.parameters.streamer;\n\t\t\t\tif(streamer == null) {\n\t\t\t\t\tAlert.show('Missing flashvars: streamer');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfile = FlexGlobals.topLevelApplication.parameters.file;\n\t\t\t\tif(file == null) {\n\t\t\t\t\tAlert.show('Missing flashvars: file');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tvideoDisplay.addEventListener(MouseEvent.DOUBLE_CLICK, fullscreenListener);\n\t\t\t\tvideoDisplay.addEventListener(\"MediaPlayerStateChange\", stateChangeListener);\n\t\t\t\tvideoDisplay.addEventListener(\"currentTimeChange\", timeChangeListener);\n\n\t\t\t\tresetVideoSource();\n\t\t\t}\n\t    ]]>\n\t  </fx:Script>\n\t<s:BorderContainer x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">\n\t\t<s:VideoDisplay doubleClickEnabled=\"true\" width=\"100%\" height=\"100%\" id=\"videoDisplay\"></s:VideoDisplay>\n\t</s:BorderContainer>\n</s:Application>\n"
  },
  {
    "path": "test/rtmp-publisher/RtmpPublisher.mxml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<s:Application xmlns:fx=\"http://ns.adobe.com/mxml/2009\"\n\t\t\t   xmlns:s=\"library://ns.adobe.com/flex/spark\"\n\t\t\t   xmlns:mx=\"library://ns.adobe.com/flex/mx\"\n\t\t\t   minWidth=\"500\" minHeight=\"350\" creationComplete=\"init()\">\n\n\t<fx:Script>\n\t\t<![CDATA[\n\t\t\timport mx.controls.Alert;\n\t\t\timport mx.core.FlexGlobals;\n\t\t\timport mx.events.FlexEvent;\n\t\t\timport spark.skins.spark.PanelSkin;\n\n\t\t\tprivate var streamer:String;\n\t\t\tprivate var file:String;\n\t\t\tprivate var camera:Camera;\n\t\t\tprivate var microphone:Microphone;\n\t\t\tprivate var connection:NetConnection;\n\t\t\tprivate var stream:NetStream;\n\t\t\tprivate var h264Settings:H264VideoStreamSettings;\n\n\t\t\tprivate function publishButtonListener(event:MouseEvent):void {\n\t\t\t\tif(publishButton.label == 'Publish') {\n\t\t\t\t\tpublishButton.label = 'Stop';\n\t\t\t\t\tconnection = new NetConnection();\n\t\t\t\t\tconnection.connect(streamer);\n\t\t\t\t\tconnection.addEventListener(NetStatusEvent.NET_STATUS, netStatusHander);\n\t\t\t\t} else {\n\t\t\t\t\tpublishButton.label = 'Publish';\n\t\t\t\t\tstream.close();\n\t\t\t\t\tconnection.close();\n\t\t\t\t\tstream = null;\n\t\t\t\t\tconnection = null;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprivate function netStatusHander(event:NetStatusEvent):void {\n\t\t\t\tswitch(event.info.code) {\n\t\t\t\t\tcase 'NetConnection.Connect.Success':\n\t\t\t\t\t\tstream = new NetStream(connection);\n\t\t\t\t\t\tstream.attachCamera(camera);\n\t\t\t\t\t\tstream.attachAudio(microphone);\n\t\t\t\t\t\th264Settings = new H264VideoStreamSettings();\n\t\t\t\t\t\th264Settings.setProfileLevel(H264Profile.BASELINE, H264Level.LEVEL_3_1);\n\t\t\t\t\t\tstream.videoStreamSettings = h264Settings;\n\t\t\t\t\t\tstream.publish(file, 'live');\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprivate function init():void {\n\t\t\t\tstreamer = FlexGlobals.topLevelApplication.parameters.streamer;\n\t\t\t\tif(streamer == null) {\n\t\t\t\t\tAlert.show('Missing flashvars: streamer');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tfile = FlexGlobals.topLevelApplication.parameters.file;\n\t\t\t\tif(file == null) {\n\t\t\t\t\tAlert.show('Missing flashvars: file');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tpublishButton.addEventListener(MouseEvent.CLICK, publishButtonListener);\n\n\t\t\t\tcamera = Camera.getCamera();\n\t\t\t\tcamera.setMode(640, 480, 30);\n\t\t\t\tcamera.setQuality(131072, 70);\n\n\t\t\t\t//videoDisplay.attachCamera(camera);\n\t\t\t\tvar localCam:Video = new Video(640,480);\n\t\t\t\tlocalCam.attachCamera(camera);\n\t\t\t\tvideoDisplay.addChild(localCam);\n\n\t\t\t\tmicrophone = Microphone.getMicrophone();\n\t\t\t\tmicrophone.setSilenceLevel(0);\n\t\t\t\tmicrophone.codec = \"Speex\";\n\t\t\t\tmicrophone.encodeQuality = 6;\n\t\t\t}\n\t\t]]>\n\t</fx:Script>\n\t<s:BorderContainer x=\"0\" y=\"0\" width=\"100%\" height=\"100%\">\n\t\t<s:VideoDisplay width=\"100%\" height=\"100%\" id=\"videoDisplay\"></s:VideoDisplay>\n\t\t<s:Button label=\"Publish\" id=\"publishButton\" horizontalCenter=\"0\" bottom=\"10\"></s:Button>\n\t</s:BorderContainer>\n</s:Application>\n"
  },
  {
    "path": "test/rtmp-publisher/player.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>RTMP Player</title>\n    <script type=\"text/javascript\" src=\"swfobject.js\"></script>\n    <script type=\"text/javascript\">\n        var flashVars = {\n            streamer: 'rtmp://localhost/myapp',\n            file:'mystream'\n        };\n        var params = {};\n        params.allowfullscreen = \"true\";\n        var attributes = {};\n        swfobject.embedSWF(\"RtmpPlayer.swf\", \"rtmp-publisher\", \"640\", \"480\", \"9.0.0\", null, flashVars, params, attributes);\n    </script>\n</head>\n<body>\n    <div id=\"rtmp-publisher\">\n        <p>Flash not installed</p>\n    </div>\n</body>\n</html>\n"
  },
  {
    "path": "test/rtmp-publisher/publisher.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>RTMP Publisher</title>\n    <script type=\"text/javascript\" src=\"swfobject.js\"></script>\n    <script type=\"text/javascript\">\n        var flashVars = {\n            streamer: 'rtmp://localhost/myapp',\n            file:'mystream'\n        };\n        swfobject.embedSWF(\"RtmpPublisher.swf\", \"rtmp-publisher\", \"640\", \"480\", \"9.0.0\", null, flashVars);\n    </script>\n</head>\n<body>\n    <div id=\"rtmp-publisher\">\n        <p>Flash not installed</p>\n    </div>\n</body>\n</html>\n"
  },
  {
    "path": "test/rtmp-publisher/swfobject.js",
    "content": "/*\tSWFObject v2.2 <http://code.google.com/p/swfobject/> \n\tis released under the MIT License <http://www.opensource.org/licenses/mit-license.php> \n*/\nvar swfobject=function(){var D=\"undefined\",r=\"object\",S=\"Shockwave Flash\",W=\"ShockwaveFlash.ShockwaveFlash\",q=\"application/x-shockwave-flash\",R=\"SWFObjectExprInst\",x=\"onreadystatechange\",O=window,j=document,t=navigator,T=false,U=[h],o=[],N=[],I=[],l,Q,E,B,J=false,a=false,n,G,m=true,M=function(){var aa=typeof j.getElementById!=D&&typeof j.getElementsByTagName!=D&&typeof j.createElement!=D,ah=t.userAgent.toLowerCase(),Y=t.platform.toLowerCase(),ae=Y?/win/.test(Y):/win/.test(ah),ac=Y?/mac/.test(Y):/mac/.test(ah),af=/webkit/.test(ah)?parseFloat(ah.replace(/^.*webkit\\/(\\d+(\\.\\d+)?).*$/,\"$1\")):false,X=!+\"\\v1\",ag=[0,0,0],ab=null;if(typeof t.plugins!=D&&typeof t.plugins[S]==r){ab=t.plugins[S].description;if(ab&&!(typeof t.mimeTypes!=D&&t.mimeTypes[q]&&!t.mimeTypes[q].enabledPlugin)){T=true;X=false;ab=ab.replace(/^.*\\s+(\\S+\\s+\\S+$)/,\"$1\");ag[0]=parseInt(ab.replace(/^(.*)\\..*$/,\"$1\"),10);ag[1]=parseInt(ab.replace(/^.*\\.(.*)\\s.*$/,\"$1\"),10);ag[2]=/[a-zA-Z]/.test(ab)?parseInt(ab.replace(/^.*[a-zA-Z]+(.*)$/,\"$1\"),10):0}}else{if(typeof O.ActiveXObject!=D){try{var ad=new ActiveXObject(W);if(ad){ab=ad.GetVariable(\"$version\");if(ab){X=true;ab=ab.split(\" \")[1].split(\",\");ag=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}}catch(Z){}}}return{w3:aa,pv:ag,wk:af,ie:X,win:ae,mac:ac}}(),k=function(){if(!M.w3){return}if((typeof j.readyState!=D&&j.readyState==\"complete\")||(typeof j.readyState==D&&(j.getElementsByTagName(\"body\")[0]||j.body))){f()}if(!J){if(typeof j.addEventListener!=D){j.addEventListener(\"DOMContentLoaded\",f,false)}if(M.ie&&M.win){j.attachEvent(x,function(){if(j.readyState==\"complete\"){j.detachEvent(x,arguments.callee);f()}});if(O==top){(function(){if(J){return}try{j.documentElement.doScroll(\"left\")}catch(X){setTimeout(arguments.callee,0);return}f()})()}}if(M.wk){(function(){if(J){return}if(!/loaded|complete/.test(j.readyState)){setTimeout(arguments.callee,0);return}f()})()}s(f)}}();function f(){if(J){return}try{var Z=j.getElementsByTagName(\"body\")[0].appendChild(C(\"span\"));Z.parentNode.removeChild(Z)}catch(aa){return}J=true;var X=U.length;for(var Y=0;Y<X;Y++){U[Y]()}}function K(X){if(J){X()}else{U[U.length]=X}}function s(Y){if(typeof O.addEventListener!=D){O.addEventListener(\"load\",Y,false)}else{if(typeof j.addEventListener!=D){j.addEventListener(\"load\",Y,false)}else{if(typeof O.attachEvent!=D){i(O,\"onload\",Y)}else{if(typeof O.onload==\"function\"){var X=O.onload;O.onload=function(){X();Y()}}else{O.onload=Y}}}}}function h(){if(T){V()}else{H()}}function V(){var X=j.getElementsByTagName(\"body\")[0];var aa=C(r);aa.setAttribute(\"type\",q);var Z=X.appendChild(aa);if(Z){var Y=0;(function(){if(typeof Z.GetVariable!=D){var ab=Z.GetVariable(\"$version\");if(ab){ab=ab.split(\" \")[1].split(\",\");M.pv=[parseInt(ab[0],10),parseInt(ab[1],10),parseInt(ab[2],10)]}}else{if(Y<10){Y++;setTimeout(arguments.callee,10);return}}X.removeChild(aa);Z=null;H()})()}else{H()}}function H(){var ag=o.length;if(ag>0){for(var af=0;af<ag;af++){var Y=o[af].id;var ab=o[af].callbackFn;var aa={success:false,id:Y};if(M.pv[0]>0){var ae=c(Y);if(ae){if(F(o[af].swfVersion)&&!(M.wk&&M.wk<312)){w(Y,true);if(ab){aa.success=true;aa.ref=z(Y);ab(aa)}}else{if(o[af].expressInstall&&A()){var ai={};ai.data=o[af].expressInstall;ai.width=ae.getAttribute(\"width\")||\"0\";ai.height=ae.getAttribute(\"height\")||\"0\";if(ae.getAttribute(\"class\")){ai.styleclass=ae.getAttribute(\"class\")}if(ae.getAttribute(\"align\")){ai.align=ae.getAttribute(\"align\")}var ah={};var X=ae.getElementsByTagName(\"param\");var ac=X.length;for(var ad=0;ad<ac;ad++){if(X[ad].getAttribute(\"name\").toLowerCase()!=\"movie\"){ah[X[ad].getAttribute(\"name\")]=X[ad].getAttribute(\"value\")}}P(ai,ah,Y,ab)}else{p(ae);if(ab){ab(aa)}}}}}else{w(Y,true);if(ab){var Z=z(Y);if(Z&&typeof Z.SetVariable!=D){aa.success=true;aa.ref=Z}ab(aa)}}}}}function z(aa){var X=null;var Y=c(aa);if(Y&&Y.nodeName==\"OBJECT\"){if(typeof Y.SetVariable!=D){X=Y}else{var Z=Y.getElementsByTagName(r)[0];if(Z){X=Z}}}return X}function A(){return !a&&F(\"6.0.65\")&&(M.win||M.mac)&&!(M.wk&&M.wk<312)}function P(aa,ab,X,Z){a=true;E=Z||null;B={success:false,id:X};var ae=c(X);if(ae){if(ae.nodeName==\"OBJECT\"){l=g(ae);Q=null}else{l=ae;Q=X}aa.id=R;if(typeof aa.width==D||(!/%$/.test(aa.width)&&parseInt(aa.width,10)<310)){aa.width=\"310\"}if(typeof aa.height==D||(!/%$/.test(aa.height)&&parseInt(aa.height,10)<137)){aa.height=\"137\"}j.title=j.title.slice(0,47)+\" - Flash Player Installation\";var ad=M.ie&&M.win?\"ActiveX\":\"PlugIn\",ac=\"MMredirectURL=\"+O.location.toString().replace(/&/g,\"%26\")+\"&MMplayerType=\"+ad+\"&MMdoctitle=\"+j.title;if(typeof ab.flashvars!=D){ab.flashvars+=\"&\"+ac}else{ab.flashvars=ac}if(M.ie&&M.win&&ae.readyState!=4){var Y=C(\"div\");X+=\"SWFObjectNew\";Y.setAttribute(\"id\",X);ae.parentNode.insertBefore(Y,ae);ae.style.display=\"none\";(function(){if(ae.readyState==4){ae.parentNode.removeChild(ae)}else{setTimeout(arguments.callee,10)}})()}u(aa,ab,X)}}function p(Y){if(M.ie&&M.win&&Y.readyState!=4){var X=C(\"div\");Y.parentNode.insertBefore(X,Y);X.parentNode.replaceChild(g(Y),X);Y.style.display=\"none\";(function(){if(Y.readyState==4){Y.parentNode.removeChild(Y)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.replaceChild(g(Y),Y)}}function g(ab){var aa=C(\"div\");if(M.win&&M.ie){aa.innerHTML=ab.innerHTML}else{var Y=ab.getElementsByTagName(r)[0];if(Y){var ad=Y.childNodes;if(ad){var X=ad.length;for(var Z=0;Z<X;Z++){if(!(ad[Z].nodeType==1&&ad[Z].nodeName==\"PARAM\")&&!(ad[Z].nodeType==8)){aa.appendChild(ad[Z].cloneNode(true))}}}}}return aa}function u(ai,ag,Y){var X,aa=c(Y);if(M.wk&&M.wk<312){return X}if(aa){if(typeof ai.id==D){ai.id=Y}if(M.ie&&M.win){var ah=\"\";for(var ae in ai){if(ai[ae]!=Object.prototype[ae]){if(ae.toLowerCase()==\"data\"){ag.movie=ai[ae]}else{if(ae.toLowerCase()==\"styleclass\"){ah+=' class=\"'+ai[ae]+'\"'}else{if(ae.toLowerCase()!=\"classid\"){ah+=\" \"+ae+'=\"'+ai[ae]+'\"'}}}}}var af=\"\";for(var ad in ag){if(ag[ad]!=Object.prototype[ad]){af+='<param name=\"'+ad+'\" value=\"'+ag[ad]+'\" />'}}aa.outerHTML='<object classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"'+ah+\">\"+af+\"</object>\";N[N.length]=ai.id;X=c(ai.id)}else{var Z=C(r);Z.setAttribute(\"type\",q);for(var ac in ai){if(ai[ac]!=Object.prototype[ac]){if(ac.toLowerCase()==\"styleclass\"){Z.setAttribute(\"class\",ai[ac])}else{if(ac.toLowerCase()!=\"classid\"){Z.setAttribute(ac,ai[ac])}}}}for(var ab in ag){if(ag[ab]!=Object.prototype[ab]&&ab.toLowerCase()!=\"movie\"){e(Z,ab,ag[ab])}}aa.parentNode.replaceChild(Z,aa);X=Z}}return X}function e(Z,X,Y){var aa=C(\"param\");aa.setAttribute(\"name\",X);aa.setAttribute(\"value\",Y);Z.appendChild(aa)}function y(Y){var X=c(Y);if(X&&X.nodeName==\"OBJECT\"){if(M.ie&&M.win){X.style.display=\"none\";(function(){if(X.readyState==4){b(Y)}else{setTimeout(arguments.callee,10)}})()}else{X.parentNode.removeChild(X)}}}function b(Z){var Y=c(Z);if(Y){for(var X in Y){if(typeof Y[X]==\"function\"){Y[X]=null}}Y.parentNode.removeChild(Y)}}function c(Z){var X=null;try{X=j.getElementById(Z)}catch(Y){}return X}function C(X){return j.createElement(X)}function i(Z,X,Y){Z.attachEvent(X,Y);I[I.length]=[Z,X,Y]}function F(Z){var Y=M.pv,X=Z.split(\".\");X[0]=parseInt(X[0],10);X[1]=parseInt(X[1],10)||0;X[2]=parseInt(X[2],10)||0;return(Y[0]>X[0]||(Y[0]==X[0]&&Y[1]>X[1])||(Y[0]==X[0]&&Y[1]==X[1]&&Y[2]>=X[2]))?true:false}function v(ac,Y,ad,ab){if(M.ie&&M.mac){return}var aa=j.getElementsByTagName(\"head\")[0];if(!aa){return}var X=(ad&&typeof ad==\"string\")?ad:\"screen\";if(ab){n=null;G=null}if(!n||G!=X){var Z=C(\"style\");Z.setAttribute(\"type\",\"text/css\");Z.setAttribute(\"media\",X);n=aa.appendChild(Z);if(M.ie&&M.win&&typeof j.styleSheets!=D&&j.styleSheets.length>0){n=j.styleSheets[j.styleSheets.length-1]}G=X}if(M.ie&&M.win){if(n&&typeof n.addRule==r){n.addRule(ac,Y)}}else{if(n&&typeof j.createTextNode!=D){n.appendChild(j.createTextNode(ac+\" {\"+Y+\"}\"))}}}function w(Z,X){if(!m){return}var Y=X?\"visible\":\"hidden\";if(J&&c(Z)){c(Z).style.visibility=Y}else{v(\"#\"+Z,\"visibility:\"+Y)}}function L(Y){var Z=/[\\\\\\\"<>\\.;]/;var X=Z.exec(Y)!=null;return X&&typeof encodeURIComponent!=D?encodeURIComponent(Y):Y}var d=function(){if(M.ie&&M.win){window.attachEvent(\"onunload\",function(){var ac=I.length;for(var ab=0;ab<ac;ab++){I[ab][0].detachEvent(I[ab][1],I[ab][2])}var Z=N.length;for(var aa=0;aa<Z;aa++){y(N[aa])}for(var Y in M){M[Y]=null}M=null;for(var X in swfobject){swfobject[X]=null}swfobject=null})}}();return{registerObject:function(ab,X,aa,Z){if(M.w3&&ab&&X){var Y={};Y.id=ab;Y.swfVersion=X;Y.expressInstall=aa;Y.callbackFn=Z;o[o.length]=Y;w(ab,false)}else{if(Z){Z({success:false,id:ab})}}},getObjectById:function(X){if(M.w3){return z(X)}},embedSWF:function(ab,ah,ae,ag,Y,aa,Z,ad,af,ac){var X={success:false,id:ah};if(M.w3&&!(M.wk&&M.wk<312)&&ab&&ah&&ae&&ag&&Y){w(ah,false);K(function(){ae+=\"\";ag+=\"\";var aj={};if(af&&typeof af===r){for(var al in af){aj[al]=af[al]}}aj.data=ab;aj.width=ae;aj.height=ag;var am={};if(ad&&typeof ad===r){for(var ak in ad){am[ak]=ad[ak]}}if(Z&&typeof Z===r){for(var ai in Z){if(typeof am.flashvars!=D){am.flashvars+=\"&\"+ai+\"=\"+Z[ai]}else{am.flashvars=ai+\"=\"+Z[ai]}}}if(F(Y)){var an=u(aj,am,ah);if(aj.id==ah){w(ah,true)}X.success=true;X.ref=an}else{if(aa&&A()){aj.data=aa;P(aj,am,ah,ac);return}else{w(ah,true)}}if(ac){ac(X)}})}else{if(ac){ac(X)}}},switchOffAutoHideShow:function(){m=false},ua:M,getFlashPlayerVersion:function(){return{major:M.pv[0],minor:M.pv[1],release:M.pv[2]}},hasFlashPlayerVersion:F,createSWF:function(Z,Y,X){if(M.w3){return u(Z,Y,X)}else{return undefined}},showExpressInstall:function(Z,aa,X,Y){if(M.w3&&A()){P(Z,aa,X,Y)}},removeSWF:function(X){if(M.w3){y(X)}},createCSS:function(aa,Z,Y,X){if(M.w3){v(aa,Z,Y,X)}},addDomLoadEvent:K,addLoadEvent:s,getQueryParamValue:function(aa){var Z=j.location.search||j.location.hash;if(Z){if(/\\?/.test(Z)){Z=Z.split(\"?\")[1]}if(aa==null){return L(Z)}var Y=Z.split(\"&\");for(var X=0;X<Y.length;X++){if(Y[X].substring(0,Y[X].indexOf(\"=\"))==aa){return L(Y[X].substring((Y[X].indexOf(\"=\")+1)))}}}return\"\"},expressInstallCallback:function(){if(a){var X=c(R);if(X&&l){X.parentNode.replaceChild(l,X);if(Q){w(Q,true);if(M.ie&&M.win){l.style.display=\"block\"}}if(E){E(B)}}a=false}}}}();"
  },
  {
    "path": "test/www/index.html",
    "content": "<b>Play</b> | <a href=\"record.html\">Record</a>\n<br/>\n<script type=\"text/javascript\" src=\"/jwplayer/jwplayer.js\"></script>\n\n<div id=\"container\">Loading the player ...</div>\n    <script type=\"text/javascript\">\n        jwplayer(\"container\").setup({\n        sources: [\n            {\n                file: \"rtmp://localhost/myapp?carg=1/mystream?sarg=2\"\n            }\n        ],\n        image: \"bg.jpg\",\n        autostart: false,\n        width: 640,\n        height: 480,\n        primary: \"flash\"\n});\n</script>\n"
  },
  {
    "path": "test/www/jwplayer/jwplayer.js",
    "content": "\"undefined\"==typeof jwplayer&&(jwplayer=function(d){if(jwplayer.api)return jwplayer.api.selectPlayer(d)},jwplayer.version=\"6.3.3242\",jwplayer.vid=document.createElement(\"video\"),jwplayer.audio=document.createElement(\"audio\"),jwplayer.source=document.createElement(\"source\"),function(d){function a(b){return function(){return e(b)}}var h=document,g=window,c=navigator,b=d.utils=function(){};b.exists=function(b){switch(typeof b){case \"string\":return 0<b.length;case \"object\":return null!==b;case \"undefined\":return!1}return!0};\nb.styleDimension=function(b){return b+(0<b.toString().indexOf(\"%\")?\"\":\"px\")};b.getAbsolutePath=function(a,e){b.exists(e)||(e=h.location.href);if(b.exists(a)){var c;if(b.exists(a)){c=a.indexOf(\"://\");var g=a.indexOf(\"?\");c=0<c&&(0>g||g>c)}else c=void 0;if(c)return a;c=e.substring(0,e.indexOf(\"://\")+3);var g=e.substring(c.length,e.indexOf(\"/\",c.length+1)),k;0===a.indexOf(\"/\")?k=a.split(\"/\"):(k=e.split(\"?\")[0],k=k.substring(c.length+g.length+1,k.lastIndexOf(\"/\")),k=k.split(\"/\").concat(a.split(\"/\")));\nfor(var f=[],d=0;d<k.length;d++)k[d]&&(b.exists(k[d])&&\".\"!=k[d])&&(\"..\"==k[d]?f.pop():f.push(k[d]));return c+g+\"/\"+f.join(\"/\")}};b.extend=function(){var a=b.extend.arguments;if(1<a.length){for(var e=1;e<a.length;e++)for(var c in a[e])try{b.exists(a[e][c])&&(a[0][c]=a[e][c])}catch(k){}return a[0]}return null};b.log=function(b,a){\"undefined\"!=typeof console&&\"undefined\"!=typeof console.log&&(a?console.log(b,a):console.log(b))};var e=b.userAgentMatch=function(b){return null!==c.userAgent.toLowerCase().match(b)};\nb.isIE=a(/msie/i);b.isFF=a(/firefox/i);b.isChrome=a(/chrome/i);b.isIOS=a(/iP(hone|ad|od)/i);b.isIPod=a(/iP(hone|od)/i);b.isIPad=a(/iPad/i);b.isSafari602=a(/Macintosh.*Mac OS X 10_8.*6\\.0\\.\\d* Safari/i);b.isAndroid=function(b){return b?e(RegExp(\"android.*\"+b,\"i\")):e(/android/i)};b.isMobile=function(){return b.isIOS()||b.isAndroid()};b.saveCookie=function(b,a){h.cookie=\"jwplayer.\"+b+\"\\x3d\"+a+\"; path\\x3d/\"};b.getCookies=function(){for(var b={},a=h.cookie.split(\"; \"),e=0;e<a.length;e++){var c=a[e].split(\"\\x3d\");\n0==c[0].indexOf(\"jwplayer.\")&&(b[c[0].substring(9,c[0].length)]=c[1])}return b};b.typeOf=function(b){var a=typeof b;return\"object\"===a?!b?\"null\":b instanceof Array?\"array\":a:a};b.translateEventResponse=function(a,e){var c=b.extend({},e);a==d.events.JWPLAYER_FULLSCREEN&&!c.fullscreen?(c.fullscreen=\"true\"==c.message?!0:!1,delete c.message):\"object\"==typeof c.data?(c=b.extend(c,c.data),delete c.data):\"object\"==typeof c.metadata&&b.deepReplaceKeyName(c.metadata,[\"__dot__\",\"__spc__\",\"__dsh__\",\"__default__\"],\n[\".\",\" \",\"-\",\"default\"]);var k=[\"position\",\"duration\",\"offset\"],g;for(g in k)c[k[g]]&&(c[k[g]]=Math.round(1E3*c[k[g]])/1E3);return c};b.flashVersion=function(){if(b.isAndroid())return 0;var a=c.plugins,e;try{if(\"undefined\"!==a&&(e=a[\"Shockwave Flash\"]))return parseInt(e.description.replace(/\\D+(\\d+)\\..*/,\"$1\"))}catch(k){}if(\"undefined\"!=typeof g.ActiveXObject)try{if(e=new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash\"))return parseInt(e.GetVariable(\"$version\").split(\" \")[1].split(\",\")[0])}catch(d){}return 0};\nb.getScriptPath=function(b){for(var a=h.getElementsByTagName(\"script\"),e=0;e<a.length;e++){var c=a[e].src;if(c&&0<=c.indexOf(b))return c.substr(0,c.indexOf(b))}return\"\"};b.deepReplaceKeyName=function(b,a,e){switch(d.utils.typeOf(b)){case \"array\":for(var c=0;c<b.length;c++)b[c]=d.utils.deepReplaceKeyName(b[c],a,e);break;case \"object\":for(var k in b){var f;if(a instanceof Array&&e instanceof Array)if(a.length!=e.length)continue;else f=a;else f=[a];for(var g=k,c=0;c<f.length;c++)g=g.replace(RegExp(a[c],\n\"g\"),e[c]);b[g]=d.utils.deepReplaceKeyName(b[k],a,e);k!=g&&delete b[k]}}return b};var k=b.pluginPathType={ABSOLUTE:0,RELATIVE:1,CDN:2};b.getPluginPathType=function(a){if(\"string\"==typeof a){a=a.split(\"?\")[0];var e=a.indexOf(\"://\");if(0<e)return k.ABSOLUTE;var c=a.indexOf(\"/\");a=b.extension(a);return 0>e&&0>c&&(!a||!isNaN(a))?k.CDN:k.RELATIVE}};b.getPluginName=function(a){return a.replace(/^(.*\\/)?([^-]*)-?.*\\.(swf|js)$/,\"$2\")};b.getPluginVersion=function(a){return a.replace(/[^-]*-?([^\\.]*).*$/,\"$1\")};\nb.isYouTube=function(a){return-1<a.indexOf(\"youtube.com\")||-1<a.indexOf(\"youtu.be\")};b.isRtmp=function(a,b){return 0==a.indexOf(\"rtmp\")||\"rtmp\"==b};b.foreach=function(a,b){for(var e in a)a.hasOwnProperty(e)&&b(e)};b.isHTTPS=function(){return 0==g.location.href.indexOf(\"https\")};b.repo=function(){var a=\"http://p.jwpcdn.com/\"+d.version.split(/\\W/).splice(0,2).join(\"/\")+\"/\";try{b.isHTTPS()&&(a=a.replace(\"http://\",\"https://ssl.\"))}catch(e){}return a}}(jwplayer),function(d){var a=\"video/\",h={mp4:a+\"mp4\",\nvorbis:\"audio/ogg\",ogg:a+\"ogg\",webm:a+\"webm\",aac:\"audio/mp4\",mp3:\"audio/mpeg\",hls:\"application/vnd.apple.mpegurl\"},g={mp4:h.mp4,f4v:h.mp4,m4v:h.mp4,mov:h.mp4,m4a:h.aac,f4a:h.aac,aac:h.aac,mp3:h.mp3,ogv:h.ogg,ogg:h.vorbis,oga:h.vorbis,webm:h.webm,m3u8:h.hls,hls:h.hls},a=\"video\",a={flv:a,f4v:a,mov:a,m4a:a,m4v:a,mp4:a,aac:a,f4a:a,mp3:\"sound\",smil:\"rtmp\",m3u8:\"hls\",hls:\"hls\"},c=d.extensionmap={},b;for(b in g)c[b]={html5:g[b]};for(b in a)c[b]||(c[b]={}),c[b].flash=a[b];c.types=h;c.mimeType=function(a){for(var b in h)if(h[b]==\na)return b};c.extType=function(a){return c.mimeType(g[a])}}(jwplayer.utils),function(d){var a=d.loaderstatus={NEW:0,LOADING:1,ERROR:2,COMPLETE:3},h=document;d.scriptloader=function(g){function c(){e=a.ERROR;l.sendEvent(k.ERROR)}function b(){e=a.COMPLETE;l.sendEvent(k.COMPLETE)}var e=a.NEW,k=jwplayer.events,l=new k.eventdispatcher;d.extend(this,l);this.load=function(){var l=d.scriptloader.loaders[g];if(l&&(l.getStatus()==a.NEW||l.getStatus()==a.LOADING))l.addEventListener(k.ERROR,c),l.addEventListener(k.COMPLETE,\nb);else if(d.scriptloader.loaders[g]=this,e==a.NEW){e=a.LOADING;var n=h.createElement(\"script\");n.addEventListener?(n.onload=b,n.onerror=c):n.readyState&&(n.onreadystatechange=function(){(\"loaded\"==n.readyState||\"complete\"==n.readyState)&&b()});h.getElementsByTagName(\"head\")[0].appendChild(n);n.src=g}};this.getStatus=function(){return e}};d.scriptloader.loaders={}}(jwplayer.utils),function(d){d.trim=function(a){return a.replace(/^\\s*/,\"\").replace(/\\s*$/,\"\")};d.pad=function(a,d,g){for(g||(g=\"0\");a.length<\nd;)a=g+a;return a};d.xmlAttribute=function(a,d){for(var g=0;g<a.attributes.length;g++)if(a.attributes[g].name&&a.attributes[g].name.toLowerCase()==d.toLowerCase())return a.attributes[g].value.toString();return\"\"};d.extension=function(a){if(!a||\"rtmp\"==a.substr(0,4))return\"\";a=a.substring(a.lastIndexOf(\"/\")+1,a.length).split(\"?\")[0].split(\"#\")[0];if(-1<a.lastIndexOf(\".\"))return a.substr(a.lastIndexOf(\".\")+1,a.length).toLowerCase()};d.stringToColor=function(a){a=a.replace(/(#|0x)?([0-9A-F]{3,6})$/gi,\n\"$2\");3==a.length&&(a=a.charAt(0)+a.charAt(0)+a.charAt(1)+a.charAt(1)+a.charAt(2)+a.charAt(2));return parseInt(a,16)}}(jwplayer.utils),function(d){d.key=function(a){var h,g,c;this.edition=function(){return c&&c.getTime()<(new Date).getTime()?\"invalid\":h};this.token=function(){return g};d.exists(a)||(a=\"\");try{a=d.tea.decrypt(a,\"36QXq4W@GSBV^teR\");var b=a.split(\"/\");(h=b[0])||(h=\"free\");g=b[1];b[2]&&0<parseInt(b[2])&&(c=new Date,c.setTime(String(b[2])))}catch(e){h=\"invalid\"}}}(jwplayer.utils),function(d){var a=\nd.tea={};a.encrypt=function(c,b){if(0==c.length)return\"\";var e=a.strToLongs(g.encode(c));1>=e.length&&(e[1]=0);for(var k=a.strToLongs(g.encode(b).slice(0,16)),l=e.length,d=e[l-1],n=e[0],q,j=Math.floor(6+52/l),f=0;0<j--;){f+=2654435769;q=f>>>2&3;for(var r=0;r<l;r++)n=e[(r+1)%l],d=(d>>>5^n<<2)+(n>>>3^d<<4)^(f^n)+(k[r&3^q]^d),d=e[r]+=d}e=a.longsToStr(e);return h.encode(e)};a.decrypt=function(c,b){if(0==c.length)return\"\";for(var e=a.strToLongs(h.decode(c)),k=a.strToLongs(g.encode(b).slice(0,16)),d=e.length,\nm=e[d-1],n=e[0],q,j=2654435769*Math.floor(6+52/d);0!=j;){q=j>>>2&3;for(var f=d-1;0<=f;f--)m=e[0<f?f-1:d-1],m=(m>>>5^n<<2)+(n>>>3^m<<4)^(j^n)+(k[f&3^q]^m),n=e[f]-=m;j-=2654435769}e=a.longsToStr(e);e=e.replace(/\\0+$/,\"\");return g.decode(e)};a.strToLongs=function(a){for(var b=Array(Math.ceil(a.length/4)),e=0;e<b.length;e++)b[e]=a.charCodeAt(4*e)+(a.charCodeAt(4*e+1)<<8)+(a.charCodeAt(4*e+2)<<16)+(a.charCodeAt(4*e+3)<<24);return b};a.longsToStr=function(a){for(var b=Array(a.length),e=0;e<a.length;e++)b[e]=\nString.fromCharCode(a[e]&255,a[e]>>>8&255,a[e]>>>16&255,a[e]>>>24&255);return b.join(\"\")};var h={code:\"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\\x3d\",encode:function(a,b){var e,k,d,m,n=[],q=\"\",j,f,r=h.code;f=(\"undefined\"==typeof b?0:b)?g.encode(a):a;j=f.length%3;if(0<j)for(;3>j++;)q+=\"\\x3d\",f+=\"\\x00\";for(j=0;j<f.length;j+=3)e=f.charCodeAt(j),k=f.charCodeAt(j+1),d=f.charCodeAt(j+2),m=e<<16|k<<8|d,e=m>>18&63,k=m>>12&63,d=m>>6&63,m&=63,n[j/3]=r.charAt(e)+r.charAt(k)+r.charAt(d)+\nr.charAt(m);n=n.join(\"\");return n=n.slice(0,n.length-q.length)+q},decode:function(a,b){b=\"undefined\"==typeof b?!1:b;var e,k,d,m,n,q=[],j,f=h.code;j=b?g.decode(a):a;for(var r=0;r<j.length;r+=4)e=f.indexOf(j.charAt(r)),k=f.indexOf(j.charAt(r+1)),m=f.indexOf(j.charAt(r+2)),n=f.indexOf(j.charAt(r+3)),d=e<<18|k<<12|m<<6|n,e=d>>>16&255,k=d>>>8&255,d&=255,q[r/4]=String.fromCharCode(e,k,d),64==n&&(q[r/4]=String.fromCharCode(e,k)),64==m&&(q[r/4]=String.fromCharCode(e));m=q.join(\"\");return b?g.decode(m):m}},\ng={encode:function(a){a=a.replace(/[\\u0080-\\u07ff]/g,function(a){a=a.charCodeAt(0);return String.fromCharCode(192|a>>6,128|a&63)});return a=a.replace(/[\\u0800-\\uffff]/g,function(a){a=a.charCodeAt(0);return String.fromCharCode(224|a>>12,128|a>>6&63,128|a&63)})},decode:function(a){a=a.replace(/[\\u00e0-\\u00ef][\\u0080-\\u00bf][\\u0080-\\u00bf]/g,function(a){a=(a.charCodeAt(0)&15)<<12|(a.charCodeAt(1)&63)<<6|a.charCodeAt(2)&63;return String.fromCharCode(a)});return a=a.replace(/[\\u00c0-\\u00df][\\u0080-\\u00bf]/g,\nfunction(a){a=(a.charCodeAt(0)&31)<<6|a.charCodeAt(1)&63;return String.fromCharCode(a)})}}}(jwplayer.utils),function(d){d.events={COMPLETE:\"COMPLETE\",ERROR:\"ERROR\",API_READY:\"jwplayerAPIReady\",JWPLAYER_READY:\"jwplayerReady\",JWPLAYER_FULLSCREEN:\"jwplayerFullscreen\",JWPLAYER_RESIZE:\"jwplayerResize\",JWPLAYER_ERROR:\"jwplayerError\",JWPLAYER_MEDIA_BEFOREPLAY:\"jwplayerMediaBeforePlay\",JWPLAYER_MEDIA_BEFORECOMPLETE:\"jwplayerMediaBeforeComplete\",JWPLAYER_COMPONENT_SHOW:\"jwplayerComponentShow\",JWPLAYER_COMPONENT_HIDE:\"jwplayerComponentHide\",\nJWPLAYER_MEDIA_BUFFER:\"jwplayerMediaBuffer\",JWPLAYER_MEDIA_BUFFER_FULL:\"jwplayerMediaBufferFull\",JWPLAYER_MEDIA_ERROR:\"jwplayerMediaError\",JWPLAYER_MEDIA_LOADED:\"jwplayerMediaLoaded\",JWPLAYER_MEDIA_COMPLETE:\"jwplayerMediaComplete\",JWPLAYER_MEDIA_SEEK:\"jwplayerMediaSeek\",JWPLAYER_MEDIA_TIME:\"jwplayerMediaTime\",JWPLAYER_MEDIA_VOLUME:\"jwplayerMediaVolume\",JWPLAYER_MEDIA_META:\"jwplayerMediaMeta\",JWPLAYER_MEDIA_MUTE:\"jwplayerMediaMute\",JWPLAYER_MEDIA_LEVELS:\"jwplayerMediaLevels\",JWPLAYER_MEDIA_LEVEL_CHANGED:\"jwplayerMediaLevelChanged\",\nJWPLAYER_CAPTIONS_CHANGED:\"jwplayerCaptionsChanged\",JWPLAYER_CAPTIONS_LIST:\"jwplayerCaptionsList\",JWPLAYER_PLAYER_STATE:\"jwplayerPlayerState\",state:{BUFFERING:\"BUFFERING\",IDLE:\"IDLE\",PAUSED:\"PAUSED\",PLAYING:\"PLAYING\"},JWPLAYER_PLAYLIST_LOADED:\"jwplayerPlaylistLoaded\",JWPLAYER_PLAYLIST_ITEM:\"jwplayerPlaylistItem\",JWPLAYER_PLAYLIST_COMPLETE:\"jwplayerPlaylistComplete\",JWPLAYER_DISPLAY_CLICK:\"jwplayerViewClick\",JWPLAYER_CONTROLS:\"jwplayerViewControls\",JWPLAYER_INSTREAM_CLICK:\"jwplayerInstreamClicked\",\nJWPLAYER_INSTREAM_DESTROYED:\"jwplayerInstreamDestroyed\"}}(jwplayer),function(d){var a=jwplayer.utils;d.eventdispatcher=function(d,g){var c,b;this.resetEventListeners=function(){c={};b=[]};this.resetEventListeners();this.addEventListener=function(b,k,d){try{a.exists(c[b])||(c[b]=[]),\"string\"==a.typeOf(k)&&(k=(new Function(\"return \"+k))()),c[b].push({listener:k,count:d})}catch(g){a.log(\"error\",g)}return!1};this.removeEventListener=function(b,d){if(c[b]){try{for(var g=0;g<c[b].length;g++)if(c[b][g].listener.toString()==\nd.toString()){c[b].splice(g,1);break}}catch(h){a.log(\"error\",h)}return!1}};this.addGlobalListener=function(e,c){try{\"string\"==a.typeOf(e)&&(e=(new Function(\"return \"+e))()),b.push({listener:e,count:c})}catch(d){a.log(\"error\",d)}return!1};this.removeGlobalListener=function(e){if(e){try{for(var c=0;c<b.length;c++)if(b[c].listener.toString()==e.toString()){b.splice(c,1);break}}catch(d){a.log(\"error\",d)}return!1}};this.sendEvent=function(e,k){a.exists(k)||(k={});a.extend(k,{id:d,version:jwplayer.version,\ntype:e});g&&a.log(e,k);if(\"undefined\"!=a.typeOf(c[e]))for(var l=0;l<c[e].length;l++){try{c[e][l].listener(k)}catch(m){a.log(\"There was an error while handling a listener: \"+m.toString(),c[e][l].listener)}c[e][l]&&(1===c[e][l].count?delete c[e][l]:0<c[e][l].count&&(c[e][l].count-=1))}for(l=0;l<b.length;l++){try{b[l].listener(k)}catch(n){a.log(\"There was an error while handling a listener: \"+n.toString(),b[l].listener)}b[l]&&(1===b[l].count?delete b[l]:0<b[l].count&&(b[l].count-=1))}}}}(jwplayer.events),\nfunction(d){var a={},h={};d.plugins=function(){};d.plugins.loadPlugins=function(g,c){h[g]=new d.plugins.pluginloader(new d.plugins.model(a),c);return h[g]};d.plugins.registerPlugin=function(g,c,b,e){var k=d.utils.getPluginName(g);a[k]||(a[k]=new d.plugins.plugin(g));a[k].registerPlugin(g,c,b,e)}}(jwplayer),function(d){d.plugins.model=function(a){this.addPlugin=function(h){var g=d.utils.getPluginName(h);a[g]||(a[g]=new d.plugins.plugin(h));return a[g]};this.getPlugins=function(){return a}}}(jwplayer),\nfunction(d){var a=jwplayer.utils,h=jwplayer.events;d.pluginmodes={FLASH:0,JAVASCRIPT:1,HYBRID:2};d.plugin=function(g){function c(){switch(a.getPluginPathType(g)){case a.pluginPathType.ABSOLUTE:return g;case a.pluginPathType.RELATIVE:return a.getAbsolutePath(g,window.location.href)}}function b(){q=setTimeout(function(){k=a.loaderstatus.COMPLETE;j.sendEvent(h.COMPLETE)},1E3)}function e(){k=a.loaderstatus.ERROR;j.sendEvent(h.ERROR)}var k=a.loaderstatus.NEW,l,m,n,q,j=new h.eventdispatcher;a.extend(this,\nj);this.load=function(){if(k==a.loaderstatus.NEW)if(0<g.lastIndexOf(\".swf\"))l=g,k=a.loaderstatus.COMPLETE,j.sendEvent(h.COMPLETE);else if(a.getPluginPathType(g)==a.pluginPathType.CDN)k=a.loaderstatus.COMPLETE,j.sendEvent(h.COMPLETE);else{k=a.loaderstatus.LOADING;var f=new a.scriptloader(c());f.addEventListener(h.COMPLETE,b);f.addEventListener(h.ERROR,e);f.load()}};this.registerPlugin=function(b,e,c,d){q&&(clearTimeout(q),q=void 0);n=e;c&&d?(l=d,m=c):\"string\"==typeof c?l=c:\"function\"==typeof c?m=c:\n!c&&!d&&(l=b);k=a.loaderstatus.COMPLETE;j.sendEvent(h.COMPLETE)};this.getStatus=function(){return k};this.getPluginName=function(){return a.getPluginName(g)};this.getFlashPath=function(){if(l)switch(a.getPluginPathType(l)){case a.pluginPathType.ABSOLUTE:return l;case a.pluginPathType.RELATIVE:return 0<g.lastIndexOf(\".swf\")?a.getAbsolutePath(l,window.location.href):a.getAbsolutePath(l,c())}return null};this.getJS=function(){return m};this.getTarget=function(){return n};this.getPluginmode=function(){if(\"undefined\"!=\ntypeof l&&\"undefined\"!=typeof m)return d.pluginmodes.HYBRID;if(\"undefined\"!=typeof l)return d.pluginmodes.FLASH;if(\"undefined\"!=typeof m)return d.pluginmodes.JAVASCRIPT};this.getNewInstance=function(a,b,e){return new m(a,b,e)};this.getURL=function(){return g}}}(jwplayer.plugins),function(d){var a=d.utils,h=d.events;d.plugins.pluginloader=function(g,c){function b(){m?j.sendEvent(h.ERROR,{message:n}):l||(l=!0,k=a.loaderstatus.COMPLETE,j.sendEvent(h.COMPLETE))}function e(){q||b();if(!l&&!m){var e=0,\nc=g.getPlugins(),f;for(f in q){var k=a.getPluginName(f),h=c[k],k=h.getJS(),j=h.getTarget(),h=h.getStatus();if(h==a.loaderstatus.LOADING||h==a.loaderstatus.NEW)e++;else if(k&&(!j||parseFloat(j)>parseFloat(d.version)))m=!0,n=\"Incompatible player version\",b()}0==e&&b()}}var k=a.loaderstatus.NEW,l=!1,m=!1,n,q=c,j=new h.eventdispatcher;a.extend(this,j);this.setupPlugins=function(b,e,c){var f={length:0,plugins:{}},d=0,k={},h=g.getPlugins(),l;for(l in e.plugins){var j=a.getPluginName(l),m=h[j],B=m.getFlashPath(),\nn=m.getJS(),q=m.getURL();B&&(f.plugins[B]=a.extend({},e.plugins[l]),f.plugins[B].pluginmode=m.getPluginmode(),f.length++);try{if(n&&e.plugins&&e.plugins[q]){var v=document.createElement(\"div\");v.id=b.id+\"_\"+j;v.style.position=\"absolute\";v.style.top=0;v.style.zIndex=d+10;k[j]=m.getNewInstance(b,a.extend({},e.plugins[q]),v);d++;b.onReady(c(k[j],v,!0));b.onResize(c(k[j],v))}}catch(C){a.log(\"ERROR: Failed to load \"+j+\".\")}}b.plugins=k;return f};this.load=function(){if(!(a.exists(c)&&\"object\"!=a.typeOf(c))){k=\na.loaderstatus.LOADING;for(var b in c)if(a.exists(b)){var d=g.addPlugin(b);d.addEventListener(h.COMPLETE,e);d.addEventListener(h.ERROR,f)}d=g.getPlugins();for(b in d)d[b].load()}e()};var f=this.pluginFailed=function(){m||(m=!0,n=\"File not found\",b())};this.getStatus=function(){return k}}}(jwplayer),function(d){d.playlist=function(a){var h=[];if(\"array\"==d.utils.typeOf(a))for(var g=0;g<a.length;g++)h.push(new d.playlist.item(a[g]));else h.push(new d.playlist.item(a));return h}}(jwplayer),function(d){var a=\nd.item=function(h){var g=jwplayer.utils,c=g.extend({},a.defaults,h);c.tracks=g.exists(h.tracks)?h.tracks:[];0==c.sources.length&&(c.sources=[new d.source(c)]);for(var b=0;b<c.sources.length;b++){var e=c.sources[b][\"default\"];c.sources[b][\"default\"]=e?\"true\"==e.toString():!1;c.sources[b]=new d.source(c.sources[b])}if(c.captions&&!g.exists(h.tracks)){for(h=0;h<c.captions.length;h++)c.tracks.push(c.captions[h]);delete c.captions}for(b=0;b<c.tracks.length;b++)c.tracks[b]=new d.track(c.tracks[b]);return c};\na.defaults={description:\"\",image:\"\",mediaid:\"\",title:\"\",sources:[],tracks:[]}}(jwplayer.playlist),function(d){var a=jwplayer.utils,h={file:void 0,label:void 0,type:void 0,\"default\":void 0};d.source=function(d){var c=a.extend({},h),b;for(b in h)a.exists(d[b])&&(c[b]=d[b],delete d[b]);c.type&&0<c.type.indexOf(\"/\")&&(c.type=a.extensionmap.mimeType(c.type));\"m3u8\"==c.type&&(c.type=\"hls\");\"smil\"==c.type&&(c.type=\"rtmp\");return c}}(jwplayer.playlist),function(d){var a=jwplayer.utils,h={file:void 0,label:void 0,\nkind:\"captions\",\"default\":!1};d.track=function(d){var c=a.extend({},h);d||(d={});for(var b in h)a.exists(d[b])&&(c[b]=d[b],delete d[b]);return c}}(jwplayer.playlist),function(d){var a=d.utils,h=d.events,g=document,c=d.embed=function(b){function e(a){l(n,p+a.message)}function k(){l(n,p+\"No playable sources found\")}function l(b,e){if(m.fallback){var c=b.style;c.backgroundColor=\"#000\";c.color=\"#FFF\";c.width=a.styleDimension(m.width);c.height=a.styleDimension(m.height);c.display=\"table\";c.opacity=1;var c=\ndocument.createElement(\"p\"),d=c.style;d.verticalAlign=\"middle\";d.textAlign=\"center\";d.display=\"table-cell\";d.font=\"15px/20px Arial, Helvetica, sans-serif\";c.innerHTML=e.replace(\":\",\":\\x3cbr\\x3e\");b.innerHTML=\"\";b.appendChild(c)}}var m=new c.config(b.config),n,q,j,f=m.width,r=m.height,p=\"Error loading player: \",s=d.plugins.loadPlugins(b.id,m.plugins);m.fallbackDiv&&(j=m.fallbackDiv,delete m.fallbackDiv);m.id=b.id;q=g.getElementById(b.id);n=g.createElement(\"div\");n.id=q.id;n.style.width=0<f.toString().indexOf(\"%\")?\nf:f+\"px\";n.style.height=0<r.toString().indexOf(\"%\")?r:r+\"px\";q.parentNode.replaceChild(n,q);d.embed.errorScreen=l;s.addEventListener(h.COMPLETE,function(){if(\"array\"==a.typeOf(m.playlist)&&2>m.playlist.length&&(0==m.playlist.length||!m.playlist[0].sources||0==m.playlist[0].sources.length))k();else if(s.getStatus()==a.loaderstatus.COMPLETE){for(var d=0;d<m.modes.length;d++)if(m.modes[d].type&&c[m.modes[d].type]){var f=a.extend({},m),g=new c[m.modes[d].type](n,m.modes[d],f,s,b);if(g.supportsConfig()){g.addEventListener(h.ERROR,\ne);g.embed();d=b;f=f.events;g=void 0;for(g in f)\"function\"==typeof d[g]&&d[g].call(d,f[g]);return b}}m.fallback?(a.log(\"No suitable players found and fallback enabled\"),new c.download(n,m,k)):(a.log(\"No suitable players found and fallback disabled\"),n.parentNode.replaceChild(j,n))}});s.addEventListener(h.ERROR,function(a){l(n,\"Could not load plugins: \"+a.message)});s.load();return b}}(jwplayer),function(d){function a(a){if(a.playlist)for(var e=0;e<a.playlist.length;e++)a.playlist[e]=new c(a.playlist[e]);\nelse{var e={},d;for(d in c.defaults)h(a,e,d);e.sources||(a.levels?(e.sources=a.levels,delete a.levels):(d={},h(a,d,\"file\"),h(a,d,\"type\"),e.sources=d.file?[d]:[]));a.playlist=[new c(e)]}}function h(a,e,c){g.exists(a[c])&&(e[c]=a[c],delete a[c])}var g=d.utils,c=d.playlist.item;(d.embed.config=function(b){var e={fallback:!0,height:270,primary:\"html5\",width:480,base:b.base?b.base:g.getScriptPath(\"jwplayer.js\")};b=g.extend(e,d.defaults,b);var e={type:\"html5\",src:b.base+\"jwplayer.html5.js\"},c={type:\"flash\",\nsrc:b.base+\"jwplayer.flash.swf\"};b.modes=\"flash\"==b.primary?[c,e]:[e,c];b.listbar&&(b.playlistsize=b.listbar.size,b.playlistposition=b.listbar.position);b.flashplayer&&(c.src=b.flashplayer);b.html5player&&(e.src=b.html5player);a(b);return b}).addConfig=function(b,c){a(c);return g.extend(b,c)}}(jwplayer),function(d){var a=d.utils,h=document;d.embed.download=function(d,c,b){function e(a,b){for(var c=h.querySelectorAll(a),e=0;e<c.length;e++)for(var d in b)c[e].style[d]=b[d]}function k(a,b,c){a=h.createElement(a);\nb&&(a.className=\"jwdownload\"+b);c&&c.appendChild(a);return a}var l=a.extend({},c),m,n=l.width?l.width:480,q=l.height?l.height:320,j;c=c.logo?c.logo:{prefix:a.repo(),file:\"logo.png\",margin:10};var f,r;r=l.playlist;var p,s,l=[\"mp4\",\"aac\",\"mp3\"];if(r&&r.length){p=r[0];s=p.sources;for(r=0;r<s.length;r++){var u=s[r],t=u.type?u.type:a.extensionmap.extType(a.extension(u.file));if(u.file)for(r in l)t==l[r]?(m=u.file,j=p.image):a.isYouTube(u.file)&&(f=u.file)}m?(b=m,d&&(m=k(\"a\",\"display\",d),k(\"div\",\"icon\",\nm),k(\"div\",\"logo\",m),b&&m.setAttribute(\"href\",a.getAbsolutePath(b))),b=\"#\"+d.id+\" .jwdownload\",d.style.width=\"\",d.style.height=\"\",e(b+\"display\",{width:a.styleDimension(Math.max(320,n)),height:a.styleDimension(Math.max(180,q)),background:\"black center no-repeat \"+(j?\"url(\"+j+\")\":\"\"),backgroundSize:\"contain\",position:\"relative\",border:\"none\",display:\"block\"}),e(b+\"display div\",{position:\"absolute\",width:\"100%\",height:\"100%\"}),e(b+\"logo\",{top:c.margin+\"px\",right:c.margin+\"px\",background:\"top right no-repeat url(\"+\nc.prefix+c.file+\")\"}),e(b+\"icon\",{background:\"center no-repeat url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAgNJREFUeNrs28lqwkAYB/CZqNVDDj2r6FN41QeIy8Fe+gj6BL275Q08u9FbT8ZdwVfotSBYEPUkxFOoks4EKiJdaDuTjMn3wWBO0V/+sySR8SNSqVRKIR8qaXHkzlqS9jCfzzWcTCYp9hF5o+59sVjsiRzcegSckFzcjT+ruN80TeSlAjCAAXzdJSGPFXRpAAMYwACGZQkSdhG4WCzehMNhqV6vG6vVSrirKVEw66YoSqDb7cqlUilE8JjHd/y1MQefVzqdDmiaJpfLZWHgXMHn8F6vJ1cqlVAkEsGuAn83J4gAd2RZymQygX6/L1erVQt+9ZPWb+CDwcCC2zXGJaewl/DhcHhK3DVj+KfKZrMWvFarcYNLomAv4aPRSFZVlTlcSPA5fDweW/BoNIqFnKV53JvncjkLns/n/cLdS+92O7RYLLgsKfv9/t8XlDn4eDyiw+HA9Jyz2eyt0+kY2+3WFC5hluej0Ha7zQQq9PPwdDq1Et1sNsx/nFBgCqWJ8oAK1aUptNVqcYWewE4nahfU0YQnk4ntUEfGMIU2m01HoLaCKbTRaDgKtaVLk9tBYaBcE/6Artdr4RZ5TB6/dC+9iIe/WgAMYADDpAUJAxjAAAYwgGFZgoS/AtNNTF7Z2bL0BYPBV3Jw5xFwwWcYxgtBP5OkE8i9G7aWGOOCruvauwADALMLMEbKf4SdAAAAAElFTkSuQmCC)\"})):\nf?(c=f,d=k(\"embed\",\"\",d),d.src=\"http://www.youtube.com/v/\"+/v[=\\/](\\w*)|\\/(\\w+)$|^(\\w+)$/i.exec(c).slice(1).join(\"\"),d.type=\"application/x-shockwave-flash\",d.width=n,d.height=q):b()}}}(jwplayer),function(d){var a=d.utils,h=d.events,g={};(d.embed.flash=function(c,b,e,k,l){function m(a,b,c){var e=document.createElement(\"param\");e.setAttribute(\"name\",b);e.setAttribute(\"value\",c);a.appendChild(e)}function n(a,b,c){return function(){try{c&&document.getElementById(l.id+\"_wrapper\").appendChild(b);var e=\ndocument.getElementById(l.id).getPluginConfig(\"display\");\"function\"==typeof a.resize&&a.resize(e.width,e.height);b.style.left=e.x;b.style.top=e.h}catch(d){}}}function q(b){if(!b)return{};var c={},e=[],d;for(d in b){var f=a.getPluginName(d),g=b[d];e.push(d);for(var k in g)c[f+\".\"+k]=g[k]}c.plugins=e.join(\",\");return c}var j=new d.events.eventdispatcher,f=a.flashVersion();a.extend(this,j);this.embed=function(){e.id=l.id;if(10>f)return j.sendEvent(h.ERROR,{message:\"Flash version must be 10.0 or greater\"}),\n!1;var d,p=a.extend({},e);c.id+\"_wrapper\"==c.parentNode.id?document.getElementById(c.id+\"_wrapper\"):(d=document.createElement(\"div\"),d.id=c.id+\"_wrapper\",d.style.position=\"relative\",d.style.width=a.styleDimension(p.width),d.style.height=a.styleDimension(p.height),c.parentNode.replaceChild(d,c),d.appendChild(c));d=k.setupPlugins(l,p,n);0<d.length?a.extend(p,q(d.plugins)):delete p.plugins;\"undefined\"!=typeof p[\"dock.position\"]&&\"false\"==p[\"dock.position\"].toString().toLowerCase()&&(p.dock=p[\"dock.position\"],\ndelete p[\"dock.position\"]);d=p.wmode?p.wmode:p.height&&40>=p.height?\"transparent\":\"opaque\";for(var s=\"height width modes events primary base fallback volume\".split(\" \"),u=0;u<s.length;u++)delete p[s[u]];var s=a.getCookies(),t;for(t in s)\"undefined\"==typeof p[t]&&(p[t]=s[t]);t=window.location.pathname.split(\"/\");t.splice(t.length-1,1);t=t.join(\"/\");p.base=t+\"/\";g[c.id]=p;a.isIE()?(p='\\x3cobject classid\\x3d\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\" \" width\\x3d\"100%\" height\\x3d\"100%\" id\\x3d\"'+c.id+\n'\" name\\x3d\"'+c.id+'\" tabindex\\x3d0\"\"\\x3e',p+='\\x3cparam name\\x3d\"movie\" value\\x3d\"'+b.src+'\"\\x3e',p+='\\x3cparam name\\x3d\"allowfullscreen\" value\\x3d\"true\"\\x3e\\x3cparam name\\x3d\"allowscriptaccess\" value\\x3d\"always\"\\x3e',p+='\\x3cparam name\\x3d\"seamlesstabbing\" value\\x3d\"true\"\\x3e',p+='\\x3cparam name\\x3d\"wmode\" value\\x3d\"'+d+'\"\\x3e',p+='\\x3cparam name\\x3d\"bgcolor\" value\\x3d\"#000000\"\\x3e',p+=\"\\x3c/object\\x3e\",c.outerHTML=p,p=document.getElementById(c.id)):(p=document.createElement(\"object\"),p.setAttribute(\"type\",\n\"application/x-shockwave-flash\"),p.setAttribute(\"data\",b.src),p.setAttribute(\"width\",\"100%\"),p.setAttribute(\"height\",\"100%\"),p.setAttribute(\"bgcolor\",\"#000000\"),p.setAttribute(\"id\",c.id),p.setAttribute(\"name\",c.id),p.setAttribute(\"tabindex\",0),m(p,\"allowfullscreen\",\"true\"),m(p,\"allowscriptaccess\",\"always\"),m(p,\"seamlesstabbing\",\"true\"),m(p,\"wmode\",d),c.parentNode.replaceChild(p,c));l.container=p;l.setPlayer(p,\"flash\")};this.supportsConfig=function(){if(f)if(e){if(\"string\"==a.typeOf(e.playlist))return!0;\ntry{var b=e.playlist[0].sources;if(\"undefined\"==typeof b)return!0;for(var c=0;c<b.length;c++){var d;if(d=b[c].file){var g=b[c].file,k=b[c].type;if(a.isYouTube(g)||a.isRtmp(g,k)||\"hls\"==k)d=!0;else{var h=a.extensionmap[k?k:a.extension(g)];d=!h?!1:!!h.flash}}if(d)return!0}}catch(j){}}else return!0;return!1}}).getVars=function(a){return g[a]}}(jwplayer),function(d){var a=d.utils,h=a.extensionmap,g=d.events;d.embed.html5=function(c,b,e,k,l){function m(a,b,d){return function(){try{var e=document.querySelector(\"#\"+\nc.id+\" .jwmain\");d&&e.appendChild(b);\"function\"==typeof a.resize&&(a.resize(e.clientWidth,e.clientHeight),setTimeout(function(){a.resize(e.clientWidth,e.clientHeight)},400));b.left=e.style.left;b.top=e.style.top}catch(g){}}}function n(a){q.sendEvent(a.type,{message:\"HTML5 player not found\"})}var q=this,j=new g.eventdispatcher;a.extend(q,j);q.embed=function(){if(d.html5){k.setupPlugins(l,e,m);c.innerHTML=\"\";var f=d.utils.extend({},e);delete f.volume;f=new d.html5.player(f);l.container=document.getElementById(l.id);\nl.setPlayer(f,\"html5\")}else f=new a.scriptloader(b.src),f.addEventListener(g.ERROR,n),f.addEventListener(g.COMPLETE,q.embed),f.load()};q.supportsConfig=function(){if(d.vid.canPlayType)try{if(\"string\"==a.typeOf(e.playlist))return!0;for(var b=e.playlist[0].sources,c=0;c<b.length;c++){var g;var k=b[c].file,j=b[c].type;if(null!==navigator.userAgent.match(/BlackBerry/i)||a.isAndroid()&&(\"m3u\"==a.extension(k)||\"m3u8\"==a.extension(k))||a.isRtmp(k,j))g=!1;else{var l=h[j?j:a.extension(k)],m;if(!l||l.flash&&\n!l.html5)m=!1;else{var n=l.html5,q=d.vid;if(n)try{m=q.canPlayType(n)?!0:!1}catch(z){m=!1}else m=!0}g=m}if(g)return!0}}catch(A){}return!1}}}(jwplayer),function(d){var a=d.embed,h=d.utils,g=h.extend(function(c){var b=h.repo(),e=c.config,g=e.plugins,l=e.analytics,m=b+\"jwpsrv.js\",n=b+\"sharing.js\",q=b+\"related.js\",j=b+\"gapro.js\",f=(new d.utils.key(d.key)).edition(),g=g?g:{};\"ads\"==f&&e.advertising&&(e.advertising.client.match(\".js$|.swf$\")?g[e.advertising.client]=e.advertising:g[b+e.advertising.client+\n(\"flash\"==e.primary?\".swf\":\".js\")]=e.advertising);delete e.advertising;e.key=d.key;e.analytics&&(e.analytics.client&&e.analytics.client.match(\".js$|.swf$\"))&&(m=e.analytics.client);delete e.analytics;if(\"free\"==f||!l||!1!==l.enabled)g[m]=l?l:{};delete g.sharing;delete g.related;if(\"premium\"==f||\"ads\"==f)e.sharing&&(e.sharing.client&&e.sharing.client.match(\".js$|.swf$\")&&(n=e.sharing.client),g[n]=e.sharing),e.related&&(e.related.client&&e.related.client.match(\".js$|.swf$\")&&(q=e.related.client),g[q]=\ne.related),e.ga&&(e.ga.client&&e.ga.client.match(\".js$|.swf$\")&&(j=e.ga.client),g[j]=e.ga),e.skin&&(e.skin=e.skin.replace(/^(beelden|bekle|five|glow|modieus|roundster|stormtrooper|vapor)$/i,h.repo()+\"skins/$1.xml\").toLowerCase());e.plugins=g;return new a(c)},a);d.embed=g}(jwplayer),function(d){var a=[],h=d.utils,g=d.events,c=g.state,b=document,e=d.api=function(a){function l(a,b){return function(c){return b(a,c)}}function m(a,b){p[a]||(p[a]=[],q(g.JWPLAYER_PLAYER_STATE,function(b){var c=b.newstate;\nb=b.oldstate;if(c==a){var e=p[c];if(e)for(var d=0;d<e.length;d++)\"function\"==typeof e[d]&&e[d].call(this,{oldstate:b,newstate:c})}}));p[a].push(b);return f}function n(a,b){try{a.jwAddEventListener(b,'function(dat) { jwplayer(\"'+f.id+'\").dispatchEvent(\"'+b+'\", dat); }')}catch(c){h.log(\"Could not add internal listener\")}}function q(a,b){r[a]||(r[a]=[],s&&u&&n(s,a));r[a].push(b);return f}function j(){if(u){for(var a=arguments[0],b=[],c=1;c<arguments.length;c++)b.push(arguments[c]);if(\"undefined\"!=typeof s&&\n\"function\"==typeof s[a])switch(b.length){case 4:return s[a](b[0],b[1],b[2],b[3]);case 3:return s[a](b[0],b[1],b[2]);case 2:return s[a](b[0],b[1]);case 1:return s[a](b[0]);default:return s[a]()}return null}t.push(arguments)}var f=this,r={},p={},s=void 0,u=!1,t=[],w=void 0,x={},y={};f.container=a;f.id=a.id;f.getBuffer=function(){return j(\"jwGetBuffer\")};f.getContainer=function(){return f.container};f.addButton=function(a,b,c,e){try{y[e]=c,j(\"jwDockAddButton\",a,b,\"jwplayer('\"+f.id+\"').callback('\"+e+\n\"')\",e)}catch(d){h.log(\"Could not add dock button\"+d.message)}};f.removeButton=function(a){j(\"jwDockRemoveButton\",a)};f.callback=function(a){if(y[a])y[a]()};f.forceState=function(a){j(\"jwForceState\",a);return f};f.releaseState=function(){return j(\"jwReleaseState\")};f.getDuration=function(){return j(\"jwGetDuration\")};f.getFullscreen=function(){return j(\"jwGetFullscreen\")};f.getStretching=function(){return j(\"jwGetStretching\")};f.getHeight=function(){return j(\"jwGetHeight\")};f.getLockState=function(){return j(\"jwGetLockState\")};\nf.getMeta=function(){return f.getItemMeta()};f.getMute=function(){return j(\"jwGetMute\")};f.getPlaylist=function(){var a=j(\"jwGetPlaylist\");\"flash\"==f.renderingMode&&h.deepReplaceKeyName(a,[\"__dot__\",\"__spc__\",\"__dsh__\",\"__default__\"],[\".\",\" \",\"-\",\"default\"]);return a};f.getPlaylistItem=function(a){h.exists(a)||(a=f.getCurrentItem());return f.getPlaylist()[a]};f.getPosition=function(){return j(\"jwGetPosition\")};f.getRenderingMode=function(){return f.renderingMode};f.getState=function(){return j(\"jwGetState\")};\nf.getVolume=function(){return j(\"jwGetVolume\")};f.getWidth=function(){return j(\"jwGetWidth\")};f.setFullscreen=function(a){h.exists(a)?j(\"jwSetFullscreen\",a):j(\"jwSetFullscreen\",!j(\"jwGetFullscreen\"));return f};f.setStretching=function(a){j(\"jwSetStretching\",a);return f};f.setMute=function(a){h.exists(a)?j(\"jwSetMute\",a):j(\"jwSetMute\",!j(\"jwGetMute\"));return f};f.lock=function(){return f};f.unlock=function(){return f};f.load=function(a){j(\"jwLoad\",a);return f};f.playlistItem=function(a){j(\"jwPlaylistItem\",\nparseInt(a));return f};f.playlistPrev=function(){j(\"jwPlaylistPrev\");return f};f.playlistNext=function(){j(\"jwPlaylistNext\");return f};f.resize=function(a,c){if(\"flash\"!=f.renderingMode)j(\"jwResize\",a,c);else{var e=b.getElementById(f.id+\"_wrapper\");e&&(e.style.width=h.styleDimension(a),e.style.height=h.styleDimension(c))}return f};f.play=function(a){\"undefined\"==typeof a?(a=f.getState(),a==c.PLAYING||a==c.BUFFERING?j(\"jwPause\"):j(\"jwPlay\")):j(\"jwPlay\",a);return f};f.pause=function(a){\"undefined\"==\ntypeof a?(a=f.getState(),a==c.PLAYING||a==c.BUFFERING?j(\"jwPause\"):j(\"jwPlay\")):j(\"jwPause\",a);return f};f.stop=function(){j(\"jwStop\");return f};f.seek=function(a){j(\"jwSeek\",a);return f};f.setVolume=function(a){j(\"jwSetVolume\",a);return f};f.loadInstream=function(a,b){return w=new e.instream(this,s,a,b)};f.getQualityLevels=function(){return j(\"jwGetQualityLevels\")};f.getCurrentQuality=function(){return j(\"jwGetCurrentQuality\")};f.setCurrentQuality=function(a){j(\"jwSetCurrentQuality\",a)};f.getCaptionsList=\nfunction(){return j(\"jwGetCaptionsList\")};f.getCurrentCaptions=function(){return j(\"jwGetCurrentCaptions\")};f.setCurrentCaptions=function(a){j(\"jwSetCurrentCaptions\",a)};f.getControls=function(){return j(\"jwGetControls\")};f.getSafeRegion=function(){return j(\"jwGetSafeRegion\")};f.setControls=function(a){j(\"jwSetControls\",a)};f.destroyPlayer=function(){j(\"jwPlayerDestroy\")};var z={onBufferChange:g.JWPLAYER_MEDIA_BUFFER,onBufferFull:g.JWPLAYER_MEDIA_BUFFER_FULL,onError:g.JWPLAYER_ERROR,onFullscreen:g.JWPLAYER_FULLSCREEN,\nonMeta:g.JWPLAYER_MEDIA_META,onMute:g.JWPLAYER_MEDIA_MUTE,onPlaylist:g.JWPLAYER_PLAYLIST_LOADED,onPlaylistItem:g.JWPLAYER_PLAYLIST_ITEM,onPlaylistComplete:g.JWPLAYER_PLAYLIST_COMPLETE,onReady:g.API_READY,onResize:g.JWPLAYER_RESIZE,onComplete:g.JWPLAYER_MEDIA_COMPLETE,onSeek:g.JWPLAYER_MEDIA_SEEK,onTime:g.JWPLAYER_MEDIA_TIME,onVolume:g.JWPLAYER_MEDIA_VOLUME,onBeforePlay:g.JWPLAYER_MEDIA_BEFOREPLAY,onBeforeComplete:g.JWPLAYER_MEDIA_BEFORECOMPLETE,onDisplayClick:g.JWPLAYER_DISPLAY_CLICK,onControls:g.JWPLAYER_CONTROLS,\nonQualityLevels:g.JWPLAYER_MEDIA_LEVELS,onQualityChange:g.JWPLAYER_MEDIA_LEVEL_CHANGED,onCaptionsList:g.JWPLAYER_CAPTIONS_LIST,onCaptionsChange:g.JWPLAYER_CAPTIONS_CHANGED};h.foreach(z,function(a){f[a]=l(z[a],q)});var A={onBuffer:c.BUFFERING,onPause:c.PAUSED,onPlay:c.PLAYING,onIdle:c.IDLE};h.foreach(A,function(a){f[a]=l(A[a],m)});f.remove=function(){if(!u)throw\"Cannot call remove() before player is ready\";t=[];e.destroyPlayer(this.id)};f.setup=function(a){if(d.embed){var c=b.getElementById(f.id);\nc&&(a.fallbackDiv=c);c=f;t=[];e.destroyPlayer(c.id);c=d(f.id);c.config=a;return new d.embed(c)}return f};f.registerPlugin=function(a,b,c,e){d.plugins.registerPlugin(a,b,c,e)};f.setPlayer=function(a,b){s=a;f.renderingMode=b};f.detachMedia=function(){if(\"html5\"==f.renderingMode)return j(\"jwDetachMedia\")};f.attachMedia=function(a){if(\"html5\"==f.renderingMode)return j(\"jwAttachMedia\",a)};f.dispatchEvent=function(a,b){if(r[a])for(var c=h.translateEventResponse(a,b),e=0;e<r[a].length;e++)if(\"function\"==\ntypeof r[a][e])try{r[a][e].call(this,c)}catch(d){h.log(\"There was an error calling back an event handler\")}};f.dispatchInstreamEvent=function(a){w&&w.dispatchEvent(a,arguments)};f.callInternal=j;f.playerReady=function(a){u=!0;s||f.setPlayer(b.getElementById(a.id));f.container=b.getElementById(f.id);h.foreach(r,function(a){n(s,a)});q(g.JWPLAYER_PLAYLIST_ITEM,function(){x={}});q(g.JWPLAYER_MEDIA_META,function(a){h.extend(x,a.metadata)});for(f.dispatchEvent(g.API_READY);0<t.length;)j.apply(this,t.shift())};\nf.getItemMeta=function(){return x};f.getCurrentItem=function(){return j(\"jwGetPlaylistIndex\")};return f};e.selectPlayer=function(c){var d;h.exists(c)||(c=0);c.nodeType?d=c:\"string\"==typeof c&&(d=b.getElementById(c));return d?(c=e.playerById(d.id))?c:e.addPlayer(new e(d)):\"number\"==typeof c?a[c]:null};e.playerById=function(b){for(var c=0;c<a.length;c++)if(a[c].id==b)return a[c];return null};e.addPlayer=function(b){for(var c=0;c<a.length;c++)if(a[c]==b)return b;a.push(b);return b};e.destroyPlayer=function(c){for(var e=\n-1,d,g=0;g<a.length;g++)a[g].id==c&&(e=g,d=a[g]);0<=e&&(c=d.id,g=b.getElementById(c+(\"flash\"==d.renderingMode?\"_wrapper\":\"\")),h.clearCss&&h.clearCss(\"#\"+c),g&&(\"html5\"==d.renderingMode&&d.destroyPlayer(),d=b.createElement(\"div\"),d.id=c,g.parentNode.replaceChild(d,g)),a.splice(e,1));return null};d.playerReady=function(a){var b=d.api.playerById(a.id);b?b.playerReady(a):d.api.selectPlayer(a.id).playerReady(a)}}(jwplayer),function(d){d.api.instream=function(a,d,g,c){this.play=function(a){d.jwInstreamPlay(a)};\nthis.pause=function(a){d.jwInstreamPause(a)};this.destroy=function(){d.jwInstreamDestroy()};a.callInternal(\"jwLoadInstream\",g,c?c:{})}}(jwplayer),function(d){var a=d.api,h=a.selectPlayer;a.selectPlayer=function(a){return(a=h(a))?a:{registerPlugin:function(a,b,e){d.plugins.registerPlugin(a,b,e)}}}}(jwplayer));"
  },
  {
    "path": "test/www/jwplayer_old/swfobject.js",
    "content": "/* SWFObject v2.1 <http://code.google.com/p/swfobject/>\n\tCopyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis\n\tThis software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>\n*/\nvar swfobject=function(){var b=\"undefined\",Q=\"object\",n=\"Shockwave Flash\",p=\"ShockwaveFlash.ShockwaveFlash\",P=\"application/x-shockwave-flash\",m=\"SWFObjectExprInst\",j=window,K=document,T=navigator,o=[],N=[],i=[],d=[],J,Z=null,M=null,l=null,e=false,A=false;var h=function(){var v=typeof K.getElementById!=b&&typeof K.getElementsByTagName!=b&&typeof K.createElement!=b,AC=[0,0,0],x=null;if(typeof T.plugins!=b&&typeof T.plugins[n]==Q){x=T.plugins[n].description;if(x&&!(typeof T.mimeTypes!=b&&T.mimeTypes[P]&&!T.mimeTypes[P].enabledPlugin)){x=x.replace(/^.*\\s+(\\S+\\s+\\S+$)/,\"$1\");AC[0]=parseInt(x.replace(/^(.*)\\..*$/,\"$1\"),10);AC[1]=parseInt(x.replace(/^.*\\.(.*)\\s.*$/,\"$1\"),10);AC[2]=/r/.test(x)?parseInt(x.replace(/^.*r(.*)$/,\"$1\"),10):0}}else{if(typeof j.ActiveXObject!=b){var y=null,AB=false;try{y=new ActiveXObject(p+\".7\")}catch(t){try{y=new ActiveXObject(p+\".6\");AC=[6,0,21];y.AllowScriptAccess=\"always\"}catch(t){if(AC[0]==6){AB=true}}if(!AB){try{y=new ActiveXObject(p)}catch(t){}}}if(!AB&&y){try{x=y.GetVariable(\"$version\");if(x){x=x.split(\" \")[1].split(\",\");AC=[parseInt(x[0],10),parseInt(x[1],10),parseInt(x[2],10)]}}catch(t){}}}}var AD=T.userAgent.toLowerCase(),r=T.platform.toLowerCase(),AA=/webkit/.test(AD)?parseFloat(AD.replace(/^.*webkit\\/(\\d+(\\.\\d+)?).*$/,\"$1\")):false,q=false,z=r?/win/.test(r):/win/.test(AD),w=r?/mac/.test(r):/mac/.test(AD);/*@cc_on q=true;@if(@_win32)z=true;@elif(@_mac)w=true;@end@*/return{w3cdom:v,pv:AC,webkit:AA,ie:q,win:z,mac:w}}();var L=function(){if(!h.w3cdom){return }f(H);if(h.ie&&h.win){try{K.write(\"<script id=__ie_ondomload defer=true src=//:><\\/script>\");J=C(\"__ie_ondomload\");if(J){I(J,\"onreadystatechange\",S)}}catch(q){}}if(h.webkit&&typeof K.readyState!=b){Z=setInterval(function(){if(/loaded|complete/.test(K.readyState)){E()}},10)}if(typeof K.addEventListener!=b){K.addEventListener(\"DOMContentLoaded\",E,null)}R(E)}();function S(){if(J.readyState==\"complete\"){J.parentNode.removeChild(J);E()}}function E(){if(e){return }if(h.ie&&h.win){var v=a(\"span\");try{var u=K.getElementsByTagName(\"body\")[0].appendChild(v);u.parentNode.removeChild(u)}catch(w){return }}e=true;if(Z){clearInterval(Z);Z=null}var q=o.length;for(var r=0;r<q;r++){o[r]()}}function f(q){if(e){q()}else{o[o.length]=q}}function R(r){if(typeof j.addEventListener!=b){j.addEventListener(\"load\",r,false)}else{if(typeof K.addEventListener!=b){K.addEventListener(\"load\",r,false)}else{if(typeof j.attachEvent!=b){I(j,\"onload\",r)}else{if(typeof j.onload==\"function\"){var q=j.onload;j.onload=function(){q();r()}}else{j.onload=r}}}}}function H(){var t=N.length;for(var q=0;q<t;q++){var u=N[q].id;if(h.pv[0]>0){var r=C(u);if(r){N[q].width=r.getAttribute(\"width\")?r.getAttribute(\"width\"):\"0\";N[q].height=r.getAttribute(\"height\")?r.getAttribute(\"height\"):\"0\";if(c(N[q].swfVersion)){if(h.webkit&&h.webkit<312){Y(r)}W(u,true)}else{if(N[q].expressInstall&&!A&&c(\"6.0.65\")&&(h.win||h.mac)){k(N[q])}else{O(r)}}}}else{W(u,true)}}}function Y(t){var q=t.getElementsByTagName(Q)[0];if(q){var w=a(\"embed\"),y=q.attributes;if(y){var v=y.length;for(var u=0;u<v;u++){if(y[u].nodeName==\"DATA\"){w.setAttribute(\"src\",y[u].nodeValue)}else{w.setAttribute(y[u].nodeName,y[u].nodeValue)}}}var x=q.childNodes;if(x){var z=x.length;for(var r=0;r<z;r++){if(x[r].nodeType==1&&x[r].nodeName==\"PARAM\"){w.setAttribute(x[r].getAttribute(\"name\"),x[r].getAttribute(\"value\"))}}}t.parentNode.replaceChild(w,t)}}function k(w){A=true;var u=C(w.id);if(u){if(w.altContentId){var y=C(w.altContentId);if(y){M=y;l=w.altContentId}}else{M=G(u)}if(!(/%$/.test(w.width))&&parseInt(w.width,10)<310){w.width=\"310\"}if(!(/%$/.test(w.height))&&parseInt(w.height,10)<137){w.height=\"137\"}K.title=K.title.slice(0,47)+\" - Flash Player Installation\";var z=h.ie&&h.win?\"ActiveX\":\"PlugIn\",q=K.title,r=\"MMredirectURL=\"+j.location+\"&MMplayerType=\"+z+\"&MMdoctitle=\"+q,x=w.id;if(h.ie&&h.win&&u.readyState!=4){var t=a(\"div\");x+=\"SWFObjectNew\";t.setAttribute(\"id\",x);u.parentNode.insertBefore(t,u);u.style.display=\"none\";var v=function(){u.parentNode.removeChild(u)};I(j,\"onload\",v)}U({data:w.expressInstall,id:m,width:w.width,height:w.height},{flashvars:r},x)}}function O(t){if(h.ie&&h.win&&t.readyState!=4){var r=a(\"div\");t.parentNode.insertBefore(r,t);r.parentNode.replaceChild(G(t),r);t.style.display=\"none\";var q=function(){t.parentNode.removeChild(t)};I(j,\"onload\",q)}else{t.parentNode.replaceChild(G(t),t)}}function G(v){var u=a(\"div\");if(h.win&&h.ie){u.innerHTML=v.innerHTML}else{var r=v.getElementsByTagName(Q)[0];if(r){var w=r.childNodes;if(w){var q=w.length;for(var t=0;t<q;t++){if(!(w[t].nodeType==1&&w[t].nodeName==\"PARAM\")&&!(w[t].nodeType==8)){u.appendChild(w[t].cloneNode(true))}}}}}return u}function U(AG,AE,t){var q,v=C(t);if(v){if(typeof AG.id==b){AG.id=t}if(h.ie&&h.win){var AF=\"\";for(var AB in AG){if(AG[AB]!=Object.prototype[AB]){if(AB.toLowerCase()==\"data\"){AE.movie=AG[AB]}else{if(AB.toLowerCase()==\"styleclass\"){AF+=' class=\"'+AG[AB]+'\"'}else{if(AB.toLowerCase()!=\"classid\"){AF+=\" \"+AB+'=\"'+AG[AB]+'\"'}}}}}var AD=\"\";for(var AA in AE){if(AE[AA]!=Object.prototype[AA]){AD+='<param name=\"'+AA+'\" value=\"'+AE[AA]+'\" />'}}v.outerHTML='<object classid=\"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"'+AF+\">\"+AD+\"</object>\";i[i.length]=AG.id;q=C(AG.id)}else{if(h.webkit&&h.webkit<312){var AC=a(\"embed\");AC.setAttribute(\"type\",P);for(var z in AG){if(AG[z]!=Object.prototype[z]){if(z.toLowerCase()==\"data\"){AC.setAttribute(\"src\",AG[z])}else{if(z.toLowerCase()==\"styleclass\"){AC.setAttribute(\"class\",AG[z])}else{if(z.toLowerCase()!=\"classid\"){AC.setAttribute(z,AG[z])}}}}}for(var y in AE){if(AE[y]!=Object.prototype[y]){if(y.toLowerCase()!=\"movie\"){AC.setAttribute(y,AE[y])}}}v.parentNode.replaceChild(AC,v);q=AC}else{var u=a(Q);u.setAttribute(\"type\",P);for(var x in AG){if(AG[x]!=Object.prototype[x]){if(x.toLowerCase()==\"styleclass\"){u.setAttribute(\"class\",AG[x])}else{if(x.toLowerCase()!=\"classid\"){u.setAttribute(x,AG[x])}}}}for(var w in AE){if(AE[w]!=Object.prototype[w]&&w.toLowerCase()!=\"movie\"){F(u,w,AE[w])}}v.parentNode.replaceChild(u,v);q=u}}}return q}function F(t,q,r){var u=a(\"param\");u.setAttribute(\"name\",q);u.setAttribute(\"value\",r);t.appendChild(u)}function X(r){var q=C(r);if(q&&(q.nodeName==\"OBJECT\"||q.nodeName==\"EMBED\")){if(h.ie&&h.win){if(q.readyState==4){B(r)}else{j.attachEvent(\"onload\",function(){B(r)})}}else{q.parentNode.removeChild(q)}}}function B(t){var r=C(t);if(r){for(var q in r){if(typeof r[q]==\"function\"){r[q]=null}}r.parentNode.removeChild(r)}}function C(t){var q=null;try{q=K.getElementById(t)}catch(r){}return q}function a(q){return K.createElement(q)}function I(t,q,r){t.attachEvent(q,r);d[d.length]=[t,q,r]}function c(t){var r=h.pv,q=t.split(\".\");q[0]=parseInt(q[0],10);q[1]=parseInt(q[1],10)||0;q[2]=parseInt(q[2],10)||0;return(r[0]>q[0]||(r[0]==q[0]&&r[1]>q[1])||(r[0]==q[0]&&r[1]==q[1]&&r[2]>=q[2]))?true:false}function V(v,r){if(h.ie&&h.mac){return }var u=K.getElementsByTagName(\"head\")[0],t=a(\"style\");t.setAttribute(\"type\",\"text/css\");t.setAttribute(\"media\",\"screen\");if(!(h.ie&&h.win)&&typeof K.createTextNode!=b){t.appendChild(K.createTextNode(v+\" {\"+r+\"}\"))}u.appendChild(t);if(h.ie&&h.win&&typeof K.styleSheets!=b&&K.styleSheets.length>0){var q=K.styleSheets[K.styleSheets.length-1];if(typeof q.addRule==Q){q.addRule(v,r)}}}function W(t,q){var r=q?\"visible\":\"hidden\";if(e&&C(t)){C(t).style.visibility=r}else{V(\"#\"+t,\"visibility:\"+r)}}function g(s){var r=/[\\\\\\\"<>\\.;]/;var q=r.exec(s)!=null;return q?encodeURIComponent(s):s}var D=function(){if(h.ie&&h.win){window.attachEvent(\"onunload\",function(){var w=d.length;for(var v=0;v<w;v++){d[v][0].detachEvent(d[v][1],d[v][2])}var t=i.length;for(var u=0;u<t;u++){X(i[u])}for(var r in h){h[r]=null}h=null;for(var q in swfobject){swfobject[q]=null}swfobject=null})}}();return{registerObject:function(u,q,t){if(!h.w3cdom||!u||!q){return }var r={};r.id=u;r.swfVersion=q;r.expressInstall=t?t:false;N[N.length]=r;W(u,false)},getObjectById:function(v){var q=null;if(h.w3cdom){var t=C(v);if(t){var u=t.getElementsByTagName(Q)[0];if(!u||(u&&typeof t.SetVariable!=b)){q=t}else{if(typeof u.SetVariable!=b){q=u}}}}return q},embedSWF:function(x,AE,AB,AD,q,w,r,z,AC){if(!h.w3cdom||!x||!AE||!AB||!AD||!q){return }AB+=\"\";AD+=\"\";if(c(q)){W(AE,false);var AA={};if(AC&&typeof AC===Q){for(var v in AC){if(AC[v]!=Object.prototype[v]){AA[v]=AC[v]}}}AA.data=x;AA.width=AB;AA.height=AD;var y={};if(z&&typeof z===Q){for(var u in z){if(z[u]!=Object.prototype[u]){y[u]=z[u]}}}if(r&&typeof r===Q){for(var t in r){if(r[t]!=Object.prototype[t]){if(typeof y.flashvars!=b){y.flashvars+=\"&\"+t+\"=\"+r[t]}else{y.flashvars=t+\"=\"+r[t]}}}}f(function(){U(AA,y,AE);if(AA.id==AE){W(AE,true)}})}else{if(w&&!A&&c(\"6.0.65\")&&(h.win||h.mac)){A=true;W(AE,false);f(function(){var AF={};AF.id=AF.altContentId=AE;AF.width=AB;AF.height=AD;AF.expressInstall=w;k(AF)})}}},getFlashPlayerVersion:function(){return{major:h.pv[0],minor:h.pv[1],release:h.pv[2]}},hasFlashPlayerVersion:c,createSWF:function(t,r,q){if(h.w3cdom){return U(t,r,q)}else{return undefined}},removeSWF:function(q){if(h.w3cdom){X(q)}},createCSS:function(r,q){if(h.w3cdom){V(r,q)}},addDomLoadEvent:f,addLoadEvent:R,getQueryParamValue:function(v){var u=K.location.search||K.location.hash;if(v==null){return g(u)}if(u){var t=u.substring(1).split(\"&\");for(var r=0;r<t.length;r++){if(t[r].substring(0,t[r].indexOf(\"=\"))==v){return g(t[r].substring((t[r].indexOf(\"=\")+1)))}}}return\"\"},expressInstallCallback:function(){if(A&&M){var q=C(m);if(q){q.parentNode.replaceChild(M,q);if(l){W(l,true);if(h.ie&&h.win){M.style.display=\"block\"}}M=null;l=null;A=false}}}}}();"
  },
  {
    "path": "test/www/record.html",
    "content": "<a href=\"index.html\">Play</a> | <b>Record</b>\n<br/>\n<script src=\"jwplayer_old/swfobject.js\"></script>\n<script type=\"text/javascript\">\nvar flashvars =\n{\n    'streamer':                     'rtmp://localhost/myapp',\n    'file':                         'mystream',\n    'type':                         'camera',\n    'controlbar':                   'bottom',\n    'stretching':                   'none',\n    'frontcolor':                   '86C29D',  // text & icons                  (green)\n    'backcolor':                    '849BC1',  // playlist background           (blue)\n    'lightcolor':                   'C286BA',  // selected text/track highlight (pink)\n    'screencolor':                  'FFFFFF',  // screen background             (black)\n    'id':                           'playerID',\n    'autostart':                    'true'\n};\n\nvar params =\n{\n    'allowfullscreen':              'true',\n    'allowscriptaccess':            'always',\n    'bgcolor':                      '#FFFFFF'\n};\n\nvar attributes =\n{\n    'id':                           'playerID',\n    'name':                         'playerID'\n};\nswfobject.embedSWF('jwplayer_old/player.swf', 'player', '320', '260', '9.0.124', false, flashvars, params, attributes);\n</script>\n</head>\n<body>\n<div id=\"playercontainer\" class=\"playercontainer\"><a id=\"player\" class=\"player\" href=\"http://get.adobe.com/flashplayer/\">Get the Adobe Flash Player to see this video.</a></div>\n</body>\n</html>\n\n"
  }
]