PageRenderTime 71ms CodeModel.GetById 9ms app.highlight 54ms RepoModel.GetById 1ms app.codeStats 0ms

/apps/rtmp/src/rtmp_socket.erl

https://github.com/MicronXD/erlyvideo
Erlang | 702 lines | 387 code | 152 blank | 163 comment | 0 complexity | 0d566d4cdb8f0c1abd68f2cc6167ff7f MD5 | raw file
  1%%% @author     Max Lapshin <max@maxidoors.ru> [http://erlyvideo.org]
  2%%% @copyright  2010 Max Lapshin
  3%%% @doc        RTMP socket module.
  4%%% Designed to look like rtmp mode of usual TCP socket. If you have used {packet, http}, you will
  5%%% find many common behaviours.
  6%%% 
  7%%% When working on server side, you should accept Socket::port() with:
  8%%% <pre><code>
  9%%% {ok, RTMP} = rtmp_socket:accept(Socket).
 10%%% receive
 11%%%   {rtmp, RTMP, connected} ->
 12%%%     rtmp_socket:setopts(RTMP, [{active, once}]),
 13%%%     loop(RTMP)
 14%%% end
 15%%% loop(RTMP) ->
 16%%%   receive
 17%%%     {rtmp, RTMP, disconnect, Statistics} -> 
 18%%%       ok;
 19%%%     {rtmp, RTMP, #rtmp_message{} = Message} ->
 20%%%       io:format("Message: ~p~n", [Message]),
 21%%%       loop(RTMP)
 22%%%   end.
 23%%% </code></pre>
 24%%%
 25%%% You are strongly advised to use {active, once} mode, because in any other case it is very easy
 26%%% to crash whole your server with OOM killer.
 27%%% @reference  See <a href="http://erlyvideo.org/rtmp" target="_top">http://erlyvideo.org/rtmp</a> for more information
 28%%% @end
 29%%%
 30%%% This file is part of erlang-rtmp.
 31%%% 
 32%%% erlang-rtmp is free software: you can redistribute it and/or modify
 33%%% it under the terms of the GNU General Public License as published by
 34%%% the Free Software Foundation, either version 3 of the License, or
 35%%% (at your option) any later version.
 36%%%
 37%%% erlang-rtmp is distributed in the hope that it will be useful,
 38%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
 39%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 40%%% GNU General Public License for more details.
 41%%%
 42%%% You should have received a copy of the GNU General Public License
 43%%% along with erlang-rtmp.  If not, see <http://www.gnu.org/licenses/>.
 44%%%
 45%%%---------------------------------------------------------------------------------------
 46-module(rtmp_socket).
 47-author('Max Lapshin <max@maxidoors.ru>').
 48-include("../include/rtmp.hrl").
 49-include("rtmp_private.hrl").
 50-version(1.1).
 51
 52-export([accept/1, connect/1, start_link/1, getopts/2, setopts/2, getstat/2, getstat/1, send/2, get_socket/1]).
 53-export([status/3, status/4, prepare_status/2, prepare_status/3, invoke/2, invoke/4, prepare_invoke/3, notify/4, prepare_notify/3]).
 54
 55-export([start_socket/2, start_server/3, start_server/4, set_socket/2]).
 56  
 57
 58
 59%% gen_fsm callbacks
 60-export([init/1, handle_event/3, handle_sync_event/4, handle_info/3, terminate/3, code_change/4]).
 61
 62-export([wait_for_socket_on_server/2, wait_for_socket_on_client/2, handshake_c1/2, handshake_c3/2, handshake_s1/2, loop/2, loop/3]).
 63
 64%% @spec (Port::integer(), Name::atom(), Callback::atom()) -> {ok, Pid::pid()}
 65%% @doc Starts RTMP listener on port Port, registered under name Name with callback module Callback.
 66%% Callback must export one function: create_client/1
 67%% create_client(RTMPSocket::pid()) -> {ok, Pid::pid()}
 68%%
 69%% This function receives RTMPSocket, that is ready to send messages and after this callback function returns, this socket
 70%% will send rtmp_message as it is defined in overview.
 71%% @end
 72-spec(start_server(Port::integer(), Name::atom(), Callback::atom()) -> {ok, Pid::pid()}).
 73start_server(Port, Name, Callback) ->
 74  rtmp_sup:start_rtmp_listener(Port, Name, Callback, []).
 75
 76start_server(Port, Name, Callback, Args) ->
 77  rtmp_sup:start_rtmp_listener(Port, Name, Callback, Args).
 78
 79
 80%% @spec (Socket::port()) -> {ok, RTMP::pid()}
 81%% @doc Accepts client connection on socket Socket, starts RTMP decoder, passes socket to it
 82%% and returns pid of newly created RTMP socket.
 83%% @end
 84-spec(accept(Socket::port()) -> {ok, RTMPSocket::pid()}).
 85accept(Socket) ->
 86  {ok, Pid} = start_socket(accept, Socket),
 87  setopts(Pid, [{consumer,self()}]),
 88  {ok,Pid}.
 89  
 90%% @spec (Socket::port()) -> {ok, RTMP::pid()}
 91%% @doc Accepts client connection on socket Socket, starts RTMP decoder, passes socket to it
 92%% and returns pid of newly created RTMP socket.
 93%% @end
 94-spec(connect(Socket::port()|string()) -> {ok, RTMPSocket::pid()}).
 95connect(ServerSpec) when is_list(ServerSpec) ->
 96  {_Proto,_Auth,Host,Port,_Path,_Query} = http_uri2:parse(ServerSpec),
 97  {ok, Socket} = gen_tcp:connect(Host, Port, [binary, {active, false}, {packet, raw}]),
 98  connect(Socket);
 99
100connect(Socket) when is_port(Socket) ->
101  {ok, Pid} = start_socket(connect, Socket),
102  setopts(Pid, [{consumer,self()}]),
103  {ok,Pid}.
104
105%% @spec (Type::accept|connect, Socket::port) -> {ok, RTMP::pid()}
106%% @doc Starts RTMP socket with provided consumer and inititiate server or client connection
107%% @end
108-spec(start_socket(Type::connect|accept, Socket::port()) -> {ok, RTMP::pid()}).
109start_socket(Type, Socket) ->
110  {ok, RTMP} = rtmp_sup:start_rtmp_socket(Type),
111  case Socket of
112    _ when is_port(Socket) -> gen_tcp:controlling_process(Socket, RTMP);
113    _ -> ok
114  end,
115  set_socket(RTMP, Socket),
116  {ok, RTMP}.
117
118
119set_socket(RTMP, Socket) ->
120  gen_fsm:send_event(RTMP, {socket, Socket}).
121
122get_socket(RTMP) ->
123  gen_fsm:sync_send_all_state_event(RTMP, get_socket, ?RTMP_TIMEOUT).
124  
125  
126%% @private
127start_link(Type) ->
128  gen_fsm:start_link(?MODULE, [Type], []).
129  
130  
131%% @spec (RTMP::pid(), Options::[{Key, Value}]|{Key, Value}) -> ok
132%% @doc Just the same as {@link inet:setopts/2. inet:setopts/2} this function changes state of 
133%% rtmp socket.<br/>
134%%  Available options:
135%%  <ul><li><code>chunk_size</code> - change outgoing chunk size</li>
136%%  <li><code>window_size</code> - ask remote client to send read acknowlegement after WindowSize bytes</li>
137%%  <li><code>amf_version</code> - change AMF0 to AMF3, but only before first call</li>
138%%  <li><code>consumer</code> - change messages consumer</li>
139%%  <li><code>debug = true|false</code> - dump all important packets or no</li>
140%% </ul>
141%% @end
142-spec(setopts(RTMP::rtmp_socket_pid(), Options::[{Key::atom(), Value::any()}]) -> ok).
143setopts(RTMP, Options) ->
144  gen_fsm:sync_send_all_state_event(RTMP, {setopts, Options}).
145
146%% @spec (RTMP::pid(), Options::[{Key, Value}]|{Key, Value}) -> ok
147%% @doc Just the same as {@link inet:getopts/2. inet:getopts/2} this function gets state of 
148%% rtmp socket.<br/>
149%%  Available options:
150%%  <ul>
151%%  <li><code>chunk_size</code> - get outgoing chunk size</li>
152%%  <li><code>window_size</code> - get remote client read acknowlegement window size bytes</li>
153%%  <li><code>amf_version</code> - get AMF0 or AMF3</li>
154%%  <li><code>consumer</code> - get messages consumer</li>
155%%  <li><code>address</code> - get remote client IP and port</li>
156%% </ul>
157%% @end
158-spec(getopts(RTMP::rtmp_socket_pid(), Options::[Key::atom()]) -> ok).
159getopts(RTMP, Options) ->
160  gen_fsm:sync_send_all_state_event(RTMP, {getopts, Options}, ?RTMP_TIMEOUT).
161
162%% @spec (RTMP::pid(), Stats::[Key]) -> Values::[{Key,Value}]
163%% @doc Just the same as {@link inet:getstats/2. inet:getstats/2} this function gets statistics of 
164%% rtmp socket.<br/>
165%%  Available options:
166%%  <ul>
167%%  <li><code>recv_oct</code> - number of bytes received to the socket.</li>
168%%  <li><code>send_oct</code> - number of bytes sent from the socket</li>
169%% </ul>
170%% @end
171-spec(getstat(RTMP::rtmp_socket_pid(), Options::[Key::atom()]) -> ok).
172getstat(RTMP, Options) ->
173  gen_fsm:sync_send_all_state_event(RTMP, {getstat, Options}, ?RTMP_TIMEOUT).
174
175%% @spec (RTMP::pid()) -> Values::[{Key,Value}]
176%% @doc Just the same as {@link inet:getstats/1. inet:getstats/1} this function gets statistics of 
177%% rtmp socket.<br/>
178-spec(getstat(RTMP::rtmp_socket_pid()) -> ok).
179getstat(RTMP) ->
180  gen_fsm:sync_send_all_state_event(RTMP, getstat, ?RTMP_TIMEOUT).
181
182  
183%% @spec (RTMP::pid(), Message::rtmp_message()) -> ok
184%% @doc Sends message to client.
185%% @end
186-spec(send(RTMP::rtmp_socket_pid(), Message::rtmp_message()) -> ok).
187send(RTMP, Message) ->
188  % io:format("Message ~p ~p ~p~n", [Message#rtmp_message.type, Message#rtmp_message.timestamp, Message#rtmp_message.stream_id]),
189  % case process_info(RTMP, message_queue_len) of
190  %   {message_queue_len, Length} when Length < 20 -> RTMP ! Message;
191  %   {message_queue_len, Length} -> gen_fsm:sync_send_event(RTMP, Message, ?RTMP_TIMEOUT);
192  %   _ -> ok
193  % end,
194  RTMP ! Message,
195  ok.
196  
197
198
199notify(RTMP, StreamId, Name, Args) ->
200  send(RTMP, prepare_notify(StreamId, Name, Args)).
201
202prepare_notify(StreamId, Name, Args) ->
203  Arg = {object, lists:ukeymerge(1, [{level, <<"status">>}], lists:keysort(1, Args))},
204  #rtmp_message{type = metadata, channel_id = rtmp_lib:channel_id(audio, StreamId), stream_id = StreamId, body = [Name, Arg], timestamp = same}.
205
206
207prepare_status(StreamId, Code) when is_list(Code) ->
208  prepare_status(StreamId, list_to_binary(Code), <<"-">>);
209
210prepare_status(StreamId, Code) when is_binary(Code) ->
211  prepare_status(StreamId, Code, <<"-">>).
212
213
214-spec(prepare_status(StreamId::non_neg_integer(), Code::any_string(), Description::any_string()) -> rtmp_message()).
215prepare_status(StreamId, Code, Description) ->
216  Arg = {object, [
217    {code, Code}, 
218    {level, <<"status">>}, 
219    {description, Description}
220  ]},
221  prepare_invoke(StreamId, onStatus, [Arg]).
222
223
224status(RTMP, StreamId, Code, Description) ->
225  send(RTMP, prepare_status(StreamId, Code, Description)).
226
227-spec(status(RTMP::rtmp_socket_pid(), StreamId::integer(), Code::any_string()) -> ok).
228status(RTMP, StreamId, Code) when is_list(Code) ->
229  status(RTMP, StreamId, list_to_binary(Code), <<"-">>);
230
231status(RTMP, StreamId, Code) when is_binary(Code) ->
232  status(RTMP, StreamId, Code, <<"-">>).
233
234  
235prepare_invoke(StreamId, Command, Args) when is_integer(StreamId) ->
236  AMF = #rtmp_funcall{
237      command = Command,
238        type = invoke,
239        id = 0,
240        stream_id = StreamId,
241        args = [null | Args ]},
242  #rtmp_message{stream_id = StreamId, type = invoke, body = AMF}.
243
244
245invoke(RTMP, StreamId, Command, Args) ->
246  send(RTMP, prepare_invoke(StreamId, Command, Args)).
247
248invoke(RTMP, #rtmp_funcall{stream_id = StreamId} = AMF) ->
249  send(RTMP, #rtmp_message{stream_id = StreamId, type = invoke, body = AMF}).
250
251
252  
253%% @private  
254init([accept]) ->
255  {ok, wait_for_socket_on_server, #rtmp_socket{channels = {}, out_channels = {}, active = false}, ?RTMP_TIMEOUT};
256
257init([connect]) ->
258  {ok, wait_for_socket_on_client, #rtmp_socket{channels = {}, out_channels = {}, active = false}, ?RTMP_TIMEOUT}.
259
260
261
262%% @private 
263
264wait_for_socket_on_server(timeout, State) ->
265  {stop, normal, State};
266
267wait_for_socket_on_server({socket, Socket}, #rtmp_socket{} = State) when is_port(Socket) ->
268  case inet:setopts(Socket, [{active, once}, {packet, raw}, binary]) of
269    ok ->
270      {ok, {IP, Port}} = inet:peername(Socket),
271      {next_state, handshake_c1, State#rtmp_socket{socket = Socket, address = IP, port = Port}, ?RTMP_TIMEOUT};
272    {error, _Error} ->
273      {stop, normal, State}
274  end;
275      
276wait_for_socket_on_server({socket, Socket}, #rtmp_socket{} = State) when is_pid(Socket) ->
277  erlang:monitor(process, Socket),
278  {next_state, handshake_c1, State#rtmp_socket{socket = Socket}, ?RTMP_TIMEOUT}.
279
280
281%% @private  
282-spec(wait_for_socket_on_client(Message::any(), Socket::rtmp_socket()) -> no_return()).
283
284wait_for_socket_on_client(timeout, State) ->
285  {stop, normal, State};
286
287wait_for_socket_on_client({socket, Socket}, #rtmp_socket{} = State) ->
288  inet:setopts(Socket, [{active, once}, {packet, raw}, binary]),
289  {ok, {IP, Port}} = inet:peername(Socket),
290  State1 = State#rtmp_socket{socket = Socket, address = IP, port = Port},
291  send_data(State1, rtmp_handshake:c1()),
292  {next_state, handshake_s1, State1, ?RTMP_TIMEOUT}.
293
294%% @private  
295handshake_c1(timeout, State) ->
296  {stop, normal, State}.
297
298%% @private  
299handshake_c3(timeout, State) ->
300  {stop, normal, State}.
301
302%% @private  
303handshake_s1(timeout, State) ->
304  {stop, normal, State}.
305  
306%% @private  
307loop(timeout, #rtmp_socket{pinged = false} = State) ->
308  State1 = send_data(State, #rtmp_message{type = ping}),
309  {next_state, loop, State1#rtmp_socket{pinged = true}, ?RTMP_TIMEOUT};
310  
311loop(timeout, #rtmp_socket{consumer = Consumer} = State) ->
312  Consumer ! {rtmp, self(), timeout},
313  {stop, normal, State}.
314
315
316%% @private
317loop(#rtmp_message{} = Message, _From, State) ->
318  State1 = send_data(State, Message),
319  {reply, ok, loop, State1, ?RTMP_TIMEOUT}.
320
321
322-type(rtmp_option() ::active|amf_version|chunk_size|window_size|client_buffer|address).
323% -type(rtmp_option_value() ::{rtmp_option(), any()}).
324-spec(get_options(State::rtmp_socket(), Key::rtmp_option()|[rtmp_option()]) -> term()).
325
326get_options(State, active) ->
327  {active, State#rtmp_socket.active};
328
329get_options(State, amf_version) ->
330  {amf_version, State#rtmp_socket.amf_version};
331
332get_options(State, chunk_size) ->
333  {chunk_size, State#rtmp_socket.server_chunk_size};
334
335get_options(State, window_size) ->
336  {window_size, State#rtmp_socket.window_size};
337
338get_options(State, client_buffer) ->
339  {client_buffer, State#rtmp_socket.client_buffer};
340
341get_options(State, debug) ->
342  {debug, State#rtmp_socket.debug};
343
344get_options(State, address) ->
345  {address, {State#rtmp_socket.address, State#rtmp_socket.port}};
346
347get_options(_State, []) ->
348  [];
349  
350get_options(State, [Key | Options]) ->
351  [get_options(State, Key) | get_options(State, Options)].
352
353get_stat(State) ->
354  get_stat(State, [recv_oct, send_oct]).
355
356get_stat(State, recv_oct) ->
357  {recv_oct, State#rtmp_socket.bytes_read};
358
359get_stat(State, send_oct) ->
360  {send_oct, State#rtmp_socket.bytes_sent};
361
362get_stat(_State, []) ->
363  [];
364
365get_stat(State, [Key | Options]) ->
366  [get_stat(State, Key) | get_stat(State, Options)].
367
368set_options(State, [{amf_version, Version} | Options]) ->
369  set_options(State#rtmp_socket{amf_version = Version}, Options);
370
371set_options(#rtmp_socket{socket = Socket, buffer = Data} = State, [{active, Active} | Options]) ->
372  State1 = flush_send(State#rtmp_socket{active = Active}),
373  State2 = case Active of
374    false -> 
375      inet:setopts(Socket, [{active, false}]),
376      State1;
377    true ->
378      inet:setopts(Socket, [{active, true}]),
379      State1;
380    once when size(Data) > 0 ->
381      handle_rtmp_data(State1);
382    once ->
383      activate_socket(Socket),
384      State1
385  end,
386  set_options(State2, Options);
387
388set_options(#rtmp_socket{} = State, [{debug, Debug} | Options]) ->
389  io:format("Set debug to ~p~n", [Debug]),
390  set_options(State#rtmp_socket{debug = Debug}, Options);
391
392set_options(#rtmp_socket{consumer = undefined} = State, [{consumer, Consumer} | Options]) ->
393  erlang:monitor(process, Consumer),
394  set_options(State#rtmp_socket{consumer = Consumer}, Options);
395
396set_options(State, [{chunk_size, ChunkSize} | Options]) ->
397  State1 = send_data(State, #rtmp_message{type = chunk_size, body = ChunkSize}),
398  set_options(State1#rtmp_socket{server_chunk_size = ChunkSize}, Options);
399
400set_options(State, []) -> State.
401  
402%%-------------------------------------------------------------------------
403%% Func: handle_event/3
404%% Returns: {next_state, NextStateName, NextStateData}          |
405%%          {next_state, NextStateName, NextStateData, Timeout} |
406%%          {stop, Reason, NewStateData}
407%% @private
408%%-------------------------------------------------------------------------
409handle_event({setopts, Options}, StateName, State) ->
410  NewState = set_options(State, Options),
411  {next_state, StateName, NewState, ?RTMP_TIMEOUT};
412
413handle_event(Event, StateName, StateData) ->
414  {stop, {StateName, undefined_event, Event}, StateData}.
415
416
417%%-------------------------------------------------------------------------
418%% Func: handle_sync_event/4
419%% Returns: {next_state, NextStateName, NextStateData}            |
420%%          {next_state, NextStateName, NextStateData, Timeout}   |
421%%          {reply, Reply, NextStateName, NextStateData}          |
422%%          {reply, Reply, NextStateName, NextStateData, Timeout} |
423%%          {stop, Reason, NewStateData}                          |
424%%          {stop, Reason, Reply, NewStateData}
425%% @private
426%%-------------------------------------------------------------------------
427
428handle_sync_event({getopts, Options}, _From, StateName, State) ->
429  {reply, get_options(State, Options), StateName, State, ?RTMP_TIMEOUT};
430
431handle_sync_event({getstat, Options}, _From, StateName, State) ->
432  {reply, get_stat(State, Options), StateName, State, ?RTMP_TIMEOUT};
433
434handle_sync_event(getstat, _From, StateName, State) ->
435  {reply, get_stat(State), StateName, State, ?RTMP_TIMEOUT};
436
437handle_sync_event(get_socket, _From, StateName, #rtmp_socket{socket = Socket} = State) ->
438  {reply, Socket, StateName, State, ?RTMP_TIMEOUT};
439
440handle_sync_event({setopts, Options}, _From, StateName, State) ->
441  NewState = set_options(State, Options),
442  {reply, ok, StateName, NewState, ?RTMP_TIMEOUT};
443
444handle_sync_event(Event, _From, StateName, StateData) ->
445  {stop, {StateName, undefined_event, Event}, StateData}.
446
447
448%%-------------------------------------------------------------------------
449%% Func: handle_info/3
450%% Returns: {next_state, NextStateName, NextStateData}          |
451%%          {next_state, NextStateName, NextStateData, Timeout} |
452%%          {stop, Reason, NewStateData}
453%% @private
454%%-------------------------------------------------------------------------
455handle_info({tcp, Socket, Data}, handshake_c1, #rtmp_socket{socket=Socket, buffer = Buffer, bytes_read = BytesRead} = State) when size(Buffer) + size(Data) < ?HS_BODY_LEN + 1 ->
456  activate_socket(Socket),
457  {next_state, handshake_c1, State#rtmp_socket{buffer = <<Buffer/binary, Data/binary>>, bytes_read = BytesRead + size(Data)}, ?RTMP_TIMEOUT};
458  
459handle_info({tcp, Socket, Data}, handshake_c1, #rtmp_socket{socket=Socket, buffer = Buffer, bytes_read = BytesRead} = State) ->
460  activate_socket(Socket),
461  <<Handshake:(?HS_BODY_LEN+1)/binary, Rest/binary>> = <<Buffer/binary, Data/binary>>,
462  State1 = case rtmp_handshake:server(Handshake) of
463    {uncrypted, Reply} -> 
464  	  send_data(State, Reply);
465    {crypted, Reply, KeyIn, KeyOut} ->
466  	  NewState = send_data(State, Reply),
467  	  ?D({"Established RTMPE connection"}),
468      NewState#rtmp_socket{key_in = KeyIn, key_out = KeyOut}
469  end,
470	{next_state, 'handshake_c3', State1#rtmp_socket{buffer = Rest, bytes_read = BytesRead + size(Data)}, ?RTMP_TIMEOUT};
471
472
473handle_info({tcp, Socket, Data}, handshake_c3, #rtmp_socket{socket=Socket, buffer = Buffer, bytes_read = BytesRead} = State) when size(Buffer) + size(Data) < ?HS_BODY_LEN ->
474  activate_socket(Socket),
475  {next_state, handshake_c3, State#rtmp_socket{buffer = <<Buffer/binary, Data/binary>>, bytes_read = BytesRead + size(Data)}, ?RTMP_TIMEOUT};
476  
477handle_info({tcp, Socket, Data}, handshake_c3, #rtmp_socket{socket=Socket, consumer = Consumer, buffer = Buffer, bytes_read = BytesRead, active = Active, key_in = KeyIn} = State) ->
478  <<_HandShakeC3:?HS_BODY_LEN/binary, CryptedData/binary>> = <<Buffer/binary, Data/binary>>,
479  Consumer ! {rtmp, self(), connected},
480  case Active of
481    true -> inet:setopts(Socket, [{active, true}]);
482    _ -> ok
483  end,
484
485  {NewKeyIn, Rest} = case KeyIn of
486    undefined -> {undefined, CryptedData};
487    _ -> rtmpe:crypt(KeyIn, CryptedData)
488  end,
489  % ?D({decrypt, Rest, CryptedData == element(2, rtmpe:crypt(NewKeyIn, Rest))}),
490  
491  State1 = State#rtmp_socket{bytes_read = BytesRead + size(Data), key_in = NewKeyIn, buffer = Rest},
492  case Rest of
493    <<>> -> {next_state, loop, State1, ?RTMP_TIMEOUT};
494    _ -> {next_state, loop, handle_rtmp_data(State1), ?RTMP_TIMEOUT}
495  end;
496
497handle_info({tcp, Socket, Data}, handshake_s1, #rtmp_socket{socket=Socket, buffer = Buffer, bytes_read = BytesRead} = State) when size(Buffer) + size(Data) < ?HS_BODY_LEN*2 + 1 ->
498  activate_socket(Socket),
499  {next_state, handshake_s1, State#rtmp_socket{buffer = <<Buffer/binary, Data/binary>>, bytes_read = BytesRead + size(Data)}, ?RTMP_TIMEOUT};
500
501handle_info({tcp, Socket, Data}, handshake_s1, #rtmp_socket{socket=Socket, consumer = Consumer, buffer = Buffer, bytes_read = BytesRead, active = Active} = State) ->
502  <<?HS_UNCRYPTED, S1:?HS_BODY_LEN/binary, _S2:?HS_BODY_LEN/binary, Rest/binary>> = <<Buffer/binary, Data/binary>>,
503  send_data(State, rtmp_handshake:c2(S1)),
504  Consumer ! {rtmp, self(), connected},
505  case Active of
506    true -> inet:setopts(Socket, [{active, true}]);
507    _ -> ok
508  end,
509  
510  {next_state, loop, handle_rtmp_data(State#rtmp_socket{bytes_read = BytesRead + size(Data), buffer = Rest}), ?RTMP_TIMEOUT};
511
512
513
514handle_info({tcp, Socket, CryptedData}, loop, #rtmp_socket{socket=Socket, buffer = Buffer, bytes_read = BytesRead, bytes_unack = BytesUnack, key_in = KeyIn} = State) ->
515  State1 = flush_send(State),
516  {NewKeyIn, Data} = case KeyIn of
517    undefined -> {undefined, CryptedData};
518    _ -> rtmpe:crypt(KeyIn, CryptedData)
519  end,
520  {next_state, loop, handle_rtmp_data(State1#rtmp_socket{bytes_read = BytesRead + size(Data), bytes_unack = BytesUnack + size(Data), key_in = NewKeyIn, buffer = <<Buffer/binary, Data/binary>>}), ?RTMP_TIMEOUT};
521
522handle_info({tcp_closed, Socket}, _StateName, #rtmp_socket{socket = Socket, consumer = Consumer} = StateData) ->
523  Consumer ! {rtmp, self(), disconnect, get_stat(StateData)},
524  {stop, normal, StateData};
525
526handle_info(#rtmp_message{} = Message, loop, State) ->
527  State1 = send_data(State, Message),
528  State2 = flush_send(State1),
529  {next_state, loop, State2, ?RTMP_TIMEOUT};
530
531handle_info({rtmpt, RTMPT, alive}, StateName, #rtmp_socket{socket = RTMPT} = State) ->
532  {next_state, StateName, State#rtmp_socket{pinged = false}, ?RTMP_TIMEOUT};
533
534handle_info({rtmpt, RTMPT, Data}, StateName, State) ->
535  handle_info({tcp, RTMPT, Data}, StateName, State);
536
537handle_info({'DOWN', _, process, _Client, _Reason}, _StateName, State) ->
538  {stop, normal, State};
539
540handle_info(_Info, StateName, StateData) ->
541  error_logger:error_msg("Unknown message to rtmp socket: ~p ~p ~p~n", [_Info, StateName, StateData]),
542  {next_state, StateName, StateData, ?RTMP_TIMEOUT}.
543
544% flush_send(State) -> flush_send([], State).
545
546flush_send(State) ->
547  receive
548    #rtmp_message{} = Message ->
549      NewState = send_data(State, Message),
550      % ?D({out, Message}),
551      % {NewState, Data} = rtmp:encode(State, Message),
552      % flush_send([Data | Packet], NewState)
553      flush_send(NewState)
554  after
555    0 -> State
556  end.
557  
558activate_socket(Socket) when is_port(Socket) ->
559  inet:setopts(Socket, [{active, once}]);
560activate_socket(Socket) when is_pid(Socket) ->
561  ok.
562  
563
564send_data(#rtmp_socket{sent_audio_notify = false} = Socket, 
565          #rtmp_message{type = audio, timestamp = DTS, stream_id = StreamId, body = Body} = Message) when size(Body) > 0 ->
566  State1 = send_data(Socket#rtmp_socket{sent_audio_notify = true}, rtmp_lib:empty_audio(StreamId, DTS)),
567  send_data(State1, Message#rtmp_message{ts_type = new});
568
569send_data(#rtmp_socket{sent_video_notify = false} = Socket, #rtmp_message{type = video, timestamp = DTS, stream_id = StreamId} = Message) ->
570  Msg = [
571    #rtmp_message{type = video, channel_id = rtmp_lib:channel_id(video, StreamId), timestamp = DTS, stream_id = StreamId, body = <<87,0>>, ts_type = new},
572    #rtmp_message{type = video, channel_id = rtmp_lib:channel_id(video, StreamId), timestamp = DTS, stream_id = StreamId, body = <<23,2,0,0,0>>, ts_type = new},
573    #rtmp_message{type = video, channel_id = rtmp_lib:channel_id(video, StreamId), timestamp = DTS, stream_id = StreamId, body = <<87,1>>, ts_type = delta}
574  ],
575  
576  State2 = lists:foldl(fun(M, State1) ->
577    send_data(State1, M)
578  end, Socket#rtmp_socket{sent_video_notify = true}, Msg),
579  send_data(State2, Message);
580  
581send_data(#rtmp_socket{sent_audio_notify = true} = Socket, #rtmp_message{type = stream_end} = Message) ->
582  send_data(Socket#rtmp_socket{sent_audio_notify = false}, Message);
583
584send_data(#rtmp_socket{sent_video_notify = true} = Socket, #rtmp_message{type = stream_end} = Message) ->
585  send_data(Socket#rtmp_socket{sent_video_notify = false}, Message);
586
587send_data(#rtmp_socket{socket = Socket, key_out = KeyOut} = State, Message) ->
588  case State#rtmp_socket.debug of
589    true -> print_rtmp_message(out, Message);
590    _ -> ok
591  end,
592  {NewState, Data} = case Message of
593    #rtmp_message{} ->
594      % ?D({Socket,Message#rtmp_message.type, Message#rtmp_message.timestamp, Message#rtmp_message.ts_type}),
595      rtmp:encode(State, Message);
596    _ -> 
597      {State, Message}
598  end,
599  {NewKeyOut, Crypt} = case KeyOut of
600    undefined -> {undefined, Data};
601    _ -> rtmpe:crypt(KeyOut, Data)
602  end,
603  % (catch rtmp_stat_collector:out_bytes(self(), iolist_size(Crypt))),
604  if
605    is_port(Socket) ->
606      gen_tcp:send(Socket, Crypt);
607    is_pid(Socket) ->
608      rtmpt:write(Socket, Crypt)
609  end,
610  NewState#rtmp_socket{key_out = NewKeyOut}.
611
612
613
614handle_rtmp_data(#rtmp_socket{buffer = <<>>} = State) ->
615  % ?D({empty_input, erlang:get_stacktrace()}),
616  State;
617
618handle_rtmp_data(#rtmp_socket{bytes_unack = Bytes, window_size = Window} = State) when Bytes >= Window ->
619  State1 = send_data(State, #rtmp_message{type = ack_read}),
620  handle_rtmp_data(State1);
621
622handle_rtmp_data(#rtmp_socket{buffer = Data} = State) ->
623  got_rtmp_message(rtmp:decode(State, Data)).
624
625
626print_rtmp_message(InOut, #rtmp_message{channel_id = Channel, ts_type = TSType, timestamp = TS, type = Type, stream_id = StreamId, body = Body}) ->
627  DecodedBody = case Type of
628    video when size(Body) > 10 -> erlang:setelement(5, flv:decode_video_tag(Body), size(Body));
629    audio when size(Body) > 0 -> erlang:setelement(7, flv:decode_audio_tag(Body), size(Body));
630    _ -> Body
631  end,
632  io:format("~p ~p ~p ~p ~p ~p ~p~n", [InOut, Channel, TSType, TS, Type, StreamId, DecodedBody]),
633  ok;
634
635print_rtmp_message(InOut, Msg) ->
636  io:format("~p ~s~n", [InOut, io_lib_pretty_limited:print(Msg, 20)]),
637  ok.
638  
639
640
641got_rtmp_message({#rtmp_socket{debug = true}, Msg, _} = Message) ->
642  print_rtmp_message(in, Msg),
643  handle_rtmp_message(Message);
644
645got_rtmp_message(Decoded) ->
646  handle_rtmp_message(Decoded).
647
648
649handle_rtmp_message({#rtmp_socket{consumer = Consumer} = State, #rtmp_message{type = window_size, body = Size} = Message, Rest}) ->
650  Consumer ! {rtmp, self(), Message},
651  rtmp_message_sent(State#rtmp_socket{window_size = Size, buffer = Rest});
652
653handle_rtmp_message({#rtmp_socket{consumer = Consumer, pinged = true} = State, #rtmp_message{type = pong} = Message, Rest}) ->
654  Consumer ! {rtmp, self(), Message},
655  rtmp_message_sent(State#rtmp_socket{pinged = false, buffer = Rest});
656
657handle_rtmp_message({#rtmp_socket{consumer = Consumer} = State, #rtmp_message{type = ping, body = Timestamp} = Message, Rest}) ->
658  Consumer ! {rtmp, self(), Message},
659  send_data(State, #rtmp_message{type = pong, body = Timestamp}),
660  rtmp_message_sent(State#rtmp_socket{buffer = Rest});
661
662handle_rtmp_message({#rtmp_socket{consumer = Consumer} = State, Message, Rest}) ->
663  Consumer ! {rtmp, self(), Message},
664  rtmp_message_sent(State#rtmp_socket{buffer = Rest});
665
666handle_rtmp_message({#rtmp_socket{socket=Socket} = State, Rest}) -> 
667  activate_socket(Socket),
668  State#rtmp_socket{buffer = Rest}.
669
670rtmp_message_sent(#rtmp_socket{active = true,socket = Socket} = State) ->
671  activate_socket(Socket),
672  handle_rtmp_data(State);
673
674rtmp_message_sent(#rtmp_socket{active = _} = State) ->
675  handle_rtmp_data(State);
676
677rtmp_message_sent(State) -> 
678  State.
679
680
681%%-------------------------------------------------------------------------
682%% Func: terminate/3
683%% Purpose: Shutdown the fsm
684%% Returns: any
685%% @private
686%%-------------------------------------------------------------------------
687terminate(_Reason, _StateName, #rtmp_socket{socket=Socket}) when is_pid(Socket) ->
688  gen_server:cast(Socket, close),
689  ok;
690  
691terminate(_Reason, _StateName, _State) ->
692  ok.
693
694
695%%-------------------------------------------------------------------------
696%% Func: code_change/4
697%% Purpose: Convert process state when code is changed
698%% Returns: {ok, NewState, NewStateData}
699%% @private
700%%-------------------------------------------------------------------------
701code_change(_OldVersion, _StateName, _State, _Extra) ->
702  ok.