Skip to content

Commit

Permalink
feat: rtmp server side redirection(302)
Browse files Browse the repository at this point in the history
  • Loading branch information
ireader committed Jan 11, 2025
1 parent 2ac42a8 commit e3186d5
Show file tree
Hide file tree
Showing 10 changed files with 136 additions and 91 deletions.
1 change: 1 addition & 0 deletions libflv/include/amf0.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ uint8_t* AMFWriteDouble(uint8_t* ptr, const uint8_t* end, double value);
uint8_t* AMFWriteString(uint8_t* ptr, const uint8_t* end, const char* string, size_t length);
uint8_t* AMFWriteDate(uint8_t* ptr, const uint8_t* end, double milliseconds, int16_t timezone);

uint8_t* AMFWriteNamed(uint8_t* ptr, const uint8_t* end, const char* name, size_t length);
uint8_t* AMFWriteNamedString(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, const char* value, size_t length2);
uint8_t* AMFWriteNamedDouble(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, double value);
uint8_t* AMFWriteNamedBoolean(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, uint8_t value);
Expand Down
5 changes: 5 additions & 0 deletions libflv/source/amf0.c
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ uint8_t* AMFWriteDate(uint8_t* ptr, const uint8_t* end, double milliseconds, int
return AMFWriteInt16(ptr + 9, end, timezone);
}

uint8_t* AMFWriteNamed(uint8_t* ptr, const uint8_t* end, const char* name, size_t length)
{
return AMFWriteString16(ptr, end, name, length);
}

uint8_t* AMFWriteNamedBoolean(uint8_t* ptr, const uint8_t* end, const char* name, size_t length, uint8_t value)
{
if (ptr + length + 2 + 2 > end)
Expand Down
12 changes: 6 additions & 6 deletions libflv/source/flv-demuxer.c
Original file line number Diff line number Diff line change
Expand Up @@ -236,8 +236,8 @@ static int flv_demuxer_video(struct flv_demuxer_t* flv, const uint8_t* data, int
}
else
{
assert(0);
return -EINVAL;
assert(video.avpacket >= FLV_PACKET_TYPE_CODED_FRAMES_X && video.avpacket <= FLV_PACKET_TYPE_MULTITRACK);
return 0;
}
}
else if (FLV_VIDEO_H266 == video.codecid)
Expand Down Expand Up @@ -275,8 +275,8 @@ static int flv_demuxer_video(struct flv_demuxer_t* flv, const uint8_t* data, int
}
else
{
assert(0);
return -EINVAL;
assert(video.avpacket >= FLV_PACKET_TYPE_CODED_FRAMES_X && video.avpacket <= FLV_PACKET_TYPE_MULTITRACK);
return 0;
}
}
else if (FLV_VIDEO_AV1 == video.codecid)
Expand All @@ -298,8 +298,8 @@ static int flv_demuxer_video(struct flv_demuxer_t* flv, const uint8_t* data, int
}
else
{
assert(0);
return -EINVAL;
assert(video.avpacket >= FLV_PACKET_TYPE_CODED_FRAMES_X && video.avpacket <= FLV_PACKET_TYPE_MULTITRACK);
return 0;
}
}
else if (FLV_VIDEO_AVS3 == video.codecid)
Expand Down
11 changes: 11 additions & 0 deletions librtmp/include/rtmp-netconnection.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ enum rtmp_encoding_amf_t
RTMP_ENCODING_AMF_3 = 3,
};

enum rtmp_capsex_t
{
RTMP_CAPSEX_RECONNECT = 0x01, // Support for reconnection
RTMP_CAPSEX_MULTITRACK = 0x02, // Support for multitrack
RTMP_CAPSEX_MODEX = 0x04, // Can parse ModEx signal
RTMP_CAPSEX_TIMESTAMPNANOOFFSET = 0x08, // Support for nano offset
};

struct rtmp_connect_t
{
char app[128]; // Server application name, e.g.: testapp
Expand All @@ -58,6 +66,9 @@ struct rtmp_connect_t
double videoFunction; // double default: 1
double encoding;
char pageUrl[256]; // http://host/sample.html

// https://veovera.org/docs/enhanced/enhanced-rtmp-v2.html#enhancing-netconnection-connect-command
double capsEx;
};

uint8_t* rtmp_netconnection_connect(uint8_t* out, size_t bytes, double transactionId, const struct rtmp_connect_t* connect);
Expand Down
1 change: 1 addition & 0 deletions librtmp/include/rtmp-netstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ uint8_t* rtmp_netstream_fcunsubscribe(uint8_t* out, size_t bytes, double transac

uint8_t* rtmp_netstream_onstatus(uint8_t* out, size_t bytes, double transactionId, const char* level, const char* code, const char* description);
uint8_t* rtmp_netstream_onreconnect(uint8_t* out, size_t bytes, double transactionId, const char* tcurl, const char* description);
uint8_t* rtmp_netstream_onconnect_rejected(uint8_t* out, size_t bytes, double transactionId, const char* tcurl, const char* description);

uint8_t* rtmp_netstream_rtmpsampleaccess(uint8_t* out, size_t bytes);

Expand Down
161 changes: 79 additions & 82 deletions librtmp/source/rtmp-client-invoke-handler.h
Original file line number Diff line number Diff line change
Expand Up @@ -118,42 +118,48 @@ static int rtmp_command_onresult(struct rtmp_t* rtmp, double transaction, const
}

// s -> c
static int rtmp_command_onerror(struct rtmp_t* rtmp, double transaction, const uint8_t* data, uint32_t bytes)
{
struct rtmp_result_t result;
struct amf_object_item_t info[3];
struct amf_object_item_t items[2];

AMF_OBJECT_ITEM_VALUE(info[0], AMF_STRING, "code", result.code, sizeof(result.code));
AMF_OBJECT_ITEM_VALUE(info[1], AMF_STRING, "level", result.level, sizeof(result.level));
AMF_OBJECT_ITEM_VALUE(info[2], AMF_STRING, "description", result.description, sizeof(result.description));

AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", NULL, 0);
AMF_OBJECT_ITEM_VALUE(items[1], AMF_OBJECT, "Information", info, sizeof(info) / sizeof(info[0]));

if (NULL == amf_read_items(data, data + bytes, items, sizeof(items) / sizeof(items[0])))
{
return -EINVAL; // format error
}

//rtmp->onerror(rtmp->param, -1, result.code);
(void)transaction;
(void)rtmp;
return -1;
}
//static int rtmp_command_onerror(struct rtmp_t* rtmp, double transaction, const uint8_t* data, uint32_t bytes)
//{
// struct rtmp_result_t result;
// struct amf_object_item_t info[3];
// struct amf_object_item_t items[2];
//
// AMF_OBJECT_ITEM_VALUE(info[0], AMF_STRING, "code", result.code, sizeof(result.code));
// AMF_OBJECT_ITEM_VALUE(info[1], AMF_STRING, "level", result.level, sizeof(result.level));
// AMF_OBJECT_ITEM_VALUE(info[2], AMF_STRING, "description", result.description, sizeof(result.description));
//
// AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", NULL, 0);
// AMF_OBJECT_ITEM_VALUE(items[1], AMF_OBJECT, "Information", info, sizeof(info) / sizeof(info[0]));
//
// if (NULL == amf_read_items(data, data + bytes, items, sizeof(items) / sizeof(items[0])))
// {
// return -EINVAL; // format error
// }
//
// //rtmp->onerror(rtmp->param, -1, result.code);
// (void)transaction;
// (void)rtmp;
// return -1;
//}

// s -> c
static int rtmp_command_onstatus(struct rtmp_t* rtmp, double transaction, const uint8_t* data, uint32_t bytes)
{
char tcurl[256];
double code = 0.0;
char tcurl[256] = { 0 };
struct rtmp_result_t result;
struct amf_object_item_t info[4];
struct amf_object_item_t info[5];
struct amf_object_item_t items[2];
struct amf_object_item_t redirect[2];

AMF_OBJECT_ITEM_VALUE(redirect[0], AMF_NUMBER, "code", &code, sizeof(code));
AMF_OBJECT_ITEM_VALUE(redirect[1], AMF_STRING, "redirect", tcurl, sizeof(tcurl));

AMF_OBJECT_ITEM_VALUE(info[0], AMF_STRING, "code", result.code, sizeof(result.code));
AMF_OBJECT_ITEM_VALUE(info[1], AMF_STRING, "level", result.level, sizeof(result.level));
AMF_OBJECT_ITEM_VALUE(info[2], AMF_STRING, "description", result.description, sizeof(result.description));
AMF_OBJECT_ITEM_VALUE(info[3], AMF_STRING, "tcUrl", tcurl, sizeof(tcurl));
AMF_OBJECT_ITEM_VALUE(info[3], AMF_STRING, "tcUrl", tcurl, sizeof(tcurl)); // enhanced rtmp v2
AMF_OBJECT_ITEM_VALUE(info[4], AMF_OBJECT, "ex", redirect, sizeof(redirect)); // AMS(adoble media server) serverside redirect

AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", NULL, 0); // Command object
AMF_OBJECT_ITEM_VALUE(items[1], AMF_OBJECT, "information", info, sizeof(info) / sizeof(info[0])); // Information object
Expand All @@ -168,71 +174,62 @@ static int rtmp_command_onstatus(struct rtmp_t* rtmp, double transaction, const
|| 0 == strcmp(RTMP_LEVEL_WARNING, result.level)
|| 0 == strcmp(RTMP_LEVEL_FINISH, result.level));

if (0 == strcmp(RTMP_LEVEL_ERROR, result.level))
if (0 == strcasecmp(result.code, "NetStream.Play.Start")
|| 0 == strcasecmp(result.code, "NetStream.Record.Start")
|| 0 == strcasecmp(result.code, "NetStream.Publish.Start"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_START) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Seek.Notify"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_SEEK) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Pause.Notify"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_PAUSE) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Unpause.Notify"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_START) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Play.Reset"))
{
//rtmp->u.client.onnotify(rtmp->param, RTMP_NOTIFY_RESET);
}
else if (0 == strcasecmp(result.code, "NetStream.Play.Stop")
|| 0 == strcasecmp(result.code, "NetStream.Record.Stop")
|| 0 == strcasecmp(result.code, "NetStream.Play.Complete"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_STOP) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Play.PublishNotify")
|| 0 == strcasecmp(result.code, "NetStream.Play.UnpublishNotify"))
{
}
else if (0 == strcasecmp(result.code, "NetConnection.Connect.InvalidApp")
|| 0 == strcasecmp(result.code, "NetStream.Connect.IllegalApplication") // ksyun cdn: level finish, auth failed
|| 0 == strcasecmp(result.code, "NetStream.Publish.AlreadyExistStream") // ksyun cdn: level finish, description Already exist stream!
|| 0 == strcasecmp(result.code, "NetStream.Failed")
|| 0 == strcasecmp(result.code, "NetStream.Play.Failed")
|| 0 == strcasecmp(result.code, "NetStream.Play.StreamNotFound"))
{
//rtmp->onerror(rtmp->param, -1, result.code);
return -1;
}
else if (0 == strcasecmp(result.code, "NetConnection.Connect.ReconnectRequest") // enhanced rtmp v2
|| (302 == (int)code && 0 == strcasecmp(result.code, "NetConnection.Connect.Rejected"))) // AMS(adoble media server) server-side redirect
{
return rtmp->client.onreconnect ? rtmp->client.onreconnect(rtmp->param, tcurl, result.description) : 0;
}
else
{
if (0 == strcasecmp(result.code, "NetStream.Play.Start")
|| 0 == strcasecmp(result.code, "NetStream.Record.Start")
|| 0 == strcasecmp(result.code, "NetStream.Publish.Start"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_START) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Seek.Notify"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_SEEK) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Pause.Notify"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_PAUSE) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Unpause.Notify"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_START) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Play.Reset"))
{
//rtmp->u.client.onnotify(rtmp->param, RTMP_NOTIFY_RESET);
}
else if (0 == strcasecmp(result.code, "NetStream.Play.Stop")
|| 0 == strcasecmp(result.code, "NetStream.Record.Stop")
|| 0 == strcasecmp(result.code, "NetStream.Play.Complete"))
{
rtmp->client.onnotify ? rtmp->client.onnotify(rtmp->param, RTMP_NOTIFY_STOP) : 0;
}
else if (0 == strcasecmp(result.code, "NetStream.Play.PublishNotify")
|| 0 == strcasecmp(result.code, "NetStream.Play.UnpublishNotify"))
{
}
else if (0 == strcasecmp(result.code, "NetConnection.Connect.InvalidApp")
|| 0 == strcasecmp(result.code, "NetConnection.Connect.Rejected")
|| 0 == strcasecmp(result.code, "NetStream.Connect.IllegalApplication") // ksyun cdn: level finish, auth failed
|| 0 == strcasecmp(result.code, "NetStream.Publish.AlreadyExistStream") // ksyun cdn: level finish, description Already exist stream!
|| 0 == strcasecmp(result.code, "NetStream.Failed")
|| 0 == strcasecmp(result.code, "NetStream.Play.Failed")
|| 0 == strcasecmp(result.code, "NetStream.Play.StreamNotFound"))
{
//rtmp->onerror(rtmp->param, -1, result.code);
return -1;
}
else if (0 == strcasecmp(result.code, "NetConnection.Connect.ReconnectRequest"))
{
// enhanced rtmp v2
rtmp->client.onreconnect ? rtmp->client.onreconnect(rtmp->param, tcurl, result.description) : 0;
}
else
{
assert(0);
printf("%s: level: %s, code: %s, description: %s\n", __FUNCTION__, result.level, result.code, result.description);
return 0;
}
assert(0);
printf("%s: level: %s, code: %s, description: %s\n", __FUNCTION__, result.level, result.code, result.description);
return 0;
}

(void)transaction;
return 0;
return strcmp(RTMP_LEVEL_ERROR, result.level) ? 0 : -1;
}

/*
Expand Down
5 changes: 3 additions & 2 deletions librtmp/source/rtmp-invoke-handler.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ static int rtmp_command_onconnect(struct rtmp_t* rtmp, double transaction, const
int r;
struct rtmp_connect_t connect;
struct amf_object_item_t items[1];
struct amf_object_item_t commands[8];
struct amf_object_item_t commands[9];

memset(&connect, 0, sizeof(connect));
connect.encoding = (double)RTMP_ENCODING_AMF_0;
Expand All @@ -25,6 +25,7 @@ static int rtmp_command_onconnect(struct rtmp_t* rtmp, double transaction, const
AMF_OBJECT_ITEM_VALUE(commands[5], AMF_NUMBER, "videoCodecs", &connect.videoCodecs, 8);
AMF_OBJECT_ITEM_VALUE(commands[6], AMF_NUMBER, "videoFunction", &connect.videoFunction, 8);
AMF_OBJECT_ITEM_VALUE(commands[7], AMF_NUMBER, "objectEncoding", &connect.encoding, 8);
AMF_OBJECT_ITEM_VALUE(commands[8], AMF_NUMBER, "capsEx", &connect.capsEx, 8);

AMF_OBJECT_ITEM_VALUE(items[0], AMF_OBJECT, "command", commands, sizeof(commands) / sizeof(commands[0]));

Expand Down Expand Up @@ -213,7 +214,7 @@ struct rtmp_command_handler_t
const static struct rtmp_command_handler_t s_command_handler[] = {
// client side
{ "_result", rtmp_command_onresult },
{ "_error", rtmp_command_onerror },
{ "_error", rtmp_command_onstatus },
{ "onStatus", rtmp_command_onstatus },

// { "close", rtmp_command_onclose },
Expand Down
2 changes: 2 additions & 0 deletions librtmp/source/rtmp-netconnection.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ uint8_t* rtmp_netconnection_connect(uint8_t* out, size_t bytes, double transacti
out = AMFWriteNamedDouble(out, end, "videoCodecs", 11, connect->videoCodecs);
out = AMFWriteNamedDouble(out, end, "videoFunction", 13, connect->videoFunction);
out = AMFWriteNamedDouble(out, end, "objectEncoding", 14, connect->encoding);
out = AMFWriteNamedDouble(out, end, "capsEx", 6, RTMP_CAPSEX_RECONNECT);
out = AMFWriteObjectEnd(out, end);
return out;
}
Expand All @@ -45,6 +46,7 @@ uint8_t* rtmp_netconnection_connect_reply(uint8_t* out, size_t bytes, double tra
out = AMFWriteNamedString(out, end, "code", 4, code, strlen(code));
out = AMFWriteNamedString(out, end, "description", 11, description, strlen(description));
out = AMFWriteNamedDouble(out, end, "objectEncoding", 14, encoding);
out = AMFWriteNamedDouble(out, end, "capsEx", 6, RTMP_CAPSEX_RECONNECT);
out = AMFWriteObjectEnd(out, end);
return out;
}
Expand Down
24 changes: 24 additions & 0 deletions librtmp/source/rtmp-netstream.c
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,30 @@ uint8_t* rtmp_netstream_onreconnect(uint8_t* out, size_t bytes, double transacti
return out;
}

uint8_t* rtmp_netstream_onconnect_rejected(uint8_t* out, size_t bytes, double transactionId, const char* tcurl, const char* description)
{
uint8_t* end = out + bytes;
const char* command = "_error";

out = AMFWriteString(out, end, command, strlen(command)); // Command Name
out = AMFWriteDouble(out, end, transactionId); // Transaction ID
out = AMFWriteNull(out, end); // command object

out = AMFWriteObject(out, end);
out = AMFWriteNamedString(out, end, "level", 5, RTMP_LEVEL_ERROR, strlen(RTMP_LEVEL_ERROR));
out = AMFWriteNamedString(out, end, "code", 4, "NetConnection.Connect.Rejected", 31);
out = AMFWriteNamedString(out, end, "description", 11, description ? description : "", strlen(description ? description : ""));

out = AMFWriteNamed(out, end, "ex", 2);
out = AMFWriteObject(out, end);
out = AMFWriteNamedDouble(out, end, "code", 4, 302);
out = AMFWriteNamedString(out, end, "redirect", 8, tcurl ? tcurl : "", strlen(tcurl ? tcurl : ""));
out = AMFWriteObjectEnd(out, end);

out = AMFWriteObjectEnd(out, end);
return out;
}

uint8_t* rtmp_netstream_rtmpsampleaccess(uint8_t* out, size_t bytes)
{
uint8_t* end = out + bytes;
Expand Down
5 changes: 4 additions & 1 deletion librtmp/source/rtmp-server.c
Original file line number Diff line number Diff line change
Expand Up @@ -539,7 +539,10 @@ int rtmp_server_start(rtmp_server_t* rtmp, int r, const char* msg)
{
if (RTMP_SERVER_START_RECONNECT == r)
{
r = (int)(rtmp_netstream_onreconnect(rtmp->payload, sizeof(rtmp->payload), 0, msg, "") - rtmp->payload);
if((unsigned int)rtmp->info.capsEx & RTMP_CAPSEX_RECONNECT)
r = (int)(rtmp_netstream_onreconnect(rtmp->payload, sizeof(rtmp->payload), 0, msg, "") - rtmp->payload);
else
r = (int)(rtmp_netstream_onconnect_rejected(rtmp->payload, sizeof(rtmp->payload), 0, msg, "") - rtmp->payload);
r = rtmp_server_send_control(&rtmp->rtmp, rtmp->payload, r, rtmp->stream_id);
}
else if (RTMP_SERVER_ONPLAY == rtmp->start.play)
Expand Down

0 comments on commit e3186d5

Please sign in to comment.