├── .gitattributes ├── .gitignore ├── README.md ├── RTMP ├── AmfObject.cs ├── AmfReader.cs ├── AmfWriter.cs ├── Client.cs ├── DataFrame.cs ├── Flv.cs ├── FlvStreamer.cs ├── Globals.cs ├── Helper.cs ├── Properties │ └── AssemblyInfo.cs ├── RTMP.csproj └── YouTube │ └── VideoClient.cs └── RTMPTests ├── App.config ├── Program.cs ├── Properties └── AssemblyInfo.cs └── RTMPTests.csproj /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | *_i.c 48 | *_p.c 49 | *.ilk 50 | *.meta 51 | *.obj 52 | *.pch 53 | *.pdb 54 | *.pgc 55 | *.pgd 56 | *.rsp 57 | *.sbr 58 | *.tlb 59 | *.tli 60 | *.tlh 61 | *.tmp 62 | *.vspscc 63 | .builds 64 | *.dotCover 65 | 66 | ## TODO: If you have NuGet Package Restore enabled, uncomment this 67 | #packages/ 68 | 69 | # Visual C++ cache files 70 | ipch/ 71 | *.aps 72 | *.ncb 73 | *.opensdf 74 | *.sdf 75 | 76 | # Visual Studio profiler 77 | *.psess 78 | *.vsp 79 | 80 | # ReSharper is a .NET coding add-in 81 | _ReSharper* 82 | 83 | # Installshield output folder 84 | [Ee]xpress 85 | 86 | # DocProject is a documentation generator add-in 87 | DocProject/buildhelp/ 88 | DocProject/Help/*.HxT 89 | DocProject/Help/*.HxC 90 | DocProject/Help/*.hhc 91 | DocProject/Help/*.hhk 92 | DocProject/Help/*.hhp 93 | DocProject/Help/Html2 94 | DocProject/Help/html 95 | 96 | # Click-Once directory 97 | publish 98 | 99 | # Others 100 | [Bb]in 101 | [Oo]bj 102 | sql 103 | TestResults 104 | *.Cache 105 | ClientBin 106 | stylecop.* 107 | ~$* 108 | *.dbmdl 109 | Generated_Code #added for RIA/Silverlight projects 110 | 111 | # Backup & report files from converting an old project file to a newer 112 | # Visual Studio version. Backup files are not needed, because we have git ;-) 113 | _UpgradeReport_Files/ 114 | Backup*/ 115 | UpgradeLog*.XML 116 | 117 | 118 | 119 | ############ 120 | ## Windows 121 | ############ 122 | 123 | # Windows image file caches 124 | Thumbs.db 125 | 126 | # Folder config file 127 | Desktop.ini 128 | 129 | 130 | ############# 131 | ## Python 132 | ############# 133 | 134 | *.py[co] 135 | 136 | # Packages 137 | *.egg 138 | *.egg-info 139 | dist 140 | build 141 | eggs 142 | parts 143 | bin 144 | var 145 | sdist 146 | develop-eggs 147 | .installed.cfg 148 | 149 | # Installer logs 150 | pip-log.txt 151 | 152 | # Unit test / coverage reports 153 | .coverage 154 | .tox 155 | 156 | #Translations 157 | *.mo 158 | 159 | #Mr Developer 160 | .mr.developer.cfg 161 | 162 | # Mac crap 163 | .DS_Store 164 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | RTMP-CSharp 2 | =========== 3 | 4 | RTMP Library for CSharp 5 | 6 | 7 | (Still a work in progress) 8 | -------------------------------------------------------------------------------- /RTMP/AmfObject.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RTMP 8 | { 9 | public class AmfObject 10 | { 11 | public string CurrentProperty; 12 | 13 | public Dictionary Numbers; //NUMBERS NUMBERS NUMBERS 14 | public Dictionary Strings; 15 | public Dictionary Booleans; 16 | public Dictionary Objects; //Objectceptionnnn~ 17 | public uint Nulls; 18 | 19 | public AmfObject() 20 | { 21 | CurrentProperty = ""; 22 | 23 | Numbers = new Dictionary(); 24 | Strings = new Dictionary(); 25 | Booleans = new Dictionary(); 26 | Objects = new Dictionary(); 27 | Nulls = 0; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /RTMP/AmfReader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MiscUtil.IO; 7 | using MiscUtil.Conversion; 8 | 9 | namespace RTMP 10 | { 11 | public class AmfData 12 | { 13 | public List Objects; 14 | public List Strings; 15 | public List Numbers; 16 | public List Booleans; 17 | public uint Nulls; 18 | public AmfData() 19 | { 20 | Objects = new List(); 21 | Strings = new List(); 22 | Numbers = new List(); 23 | Booleans = new List(); 24 | Nulls = 0; 25 | } 26 | } 27 | public class AmfReader 28 | { 29 | private Stack objectStack; 30 | public AmfData amfData; 31 | 32 | public AmfReader() 33 | { 34 | objectStack = new Stack(); 35 | amfData = new AmfData(); 36 | } 37 | 38 | public void Parse(EndianBinaryReader reader, uint size) 39 | { 40 | var maxReadPos = reader.BaseStream.Position + size; 41 | while(reader.BaseStream.Position < maxReadPos) 42 | { 43 | if (objectStack.Count != 0) 44 | { 45 | var count = reader.ReadUInt16(); 46 | var propString = ""; 47 | for (var i = 0; i < count; i++) 48 | { 49 | propString += (char) reader.ReadByte(); 50 | } 51 | objectStack.Peek().CurrentProperty = propString; 52 | } 53 | 54 | var type = (Amf0Types)reader.ReadByte(); 55 | 56 | switch (type) 57 | { 58 | case Amf0Types.Number: 59 | { 60 | var value = reader.ReadDouble(); 61 | if(objectStack.Count != 0) 62 | { 63 | objectStack.Peek().Numbers.Add(objectStack.Peek().CurrentProperty, value); 64 | } 65 | else 66 | { 67 | amfData.Numbers.Add(value); 68 | } 69 | } 70 | break; 71 | case Amf0Types.Boolean: 72 | { 73 | var value = reader.ReadBoolean(); 74 | if (objectStack.Count != 0) 75 | { 76 | objectStack.Peek().Booleans.Add(objectStack.Peek().CurrentProperty, value); 77 | } 78 | else 79 | { 80 | amfData.Booleans.Add(value); 81 | } 82 | } 83 | break; 84 | case Amf0Types.String: 85 | { 86 | var count = reader.ReadUInt16(); 87 | var pushString = ""; 88 | for (var i = 0; i < count; i++) 89 | { 90 | pushString += (char) reader.ReadByte(); 91 | } 92 | 93 | if (objectStack.Count != 0) 94 | { 95 | objectStack.Peek().Strings.Add(objectStack.Peek().CurrentProperty, pushString); 96 | } 97 | else 98 | { 99 | amfData.Strings.Add(pushString); 100 | } 101 | } 102 | break; 103 | case Amf0Types.Null: 104 | 105 | if (objectStack.Count != 0) 106 | { 107 | objectStack.Peek().Nulls++; 108 | } 109 | else 110 | { 111 | amfData.Nulls++; 112 | } 113 | break; 114 | case Amf0Types.Object: 115 | case Amf0Types.Array: 116 | { 117 | if(type == Amf0Types.Array) 118 | { 119 | var arrayLength = reader.ReadInt32(); 120 | } 121 | var objectAdd = new AmfObject(); 122 | objectStack.Push(objectAdd); 123 | } 124 | break; 125 | case Amf0Types.ObjectEnd: 126 | { 127 | if(objectStack.Count == 1) 128 | { 129 | amfData.Objects.Add(objectStack.Pop()); 130 | } 131 | else if(objectStack.Count > 1) 132 | { 133 | var mostRecentObject = objectStack.Pop(); 134 | objectStack.Peek().Objects.Add(objectStack.Peek().CurrentProperty, mostRecentObject); 135 | } 136 | } 137 | break; 138 | default: 139 | throw new ArgumentOutOfRangeException(); 140 | } 141 | /* 142 | switch (type) 143 | { 144 | case Amf0Types.Array: 145 | case Amf0Types.Object: 146 | { 147 | if(type == Amf0Types.Array) 148 | { 149 | var arrayLength = reader.ReadInt32(); 150 | Console.WriteLine("Array:" + arrayLength); 151 | } 152 | bool hasProperty = false; 153 | string property = ""; 154 | var objectAdd = new AmfObject(); 155 | while (reader.BaseStream.Position < maxReadPos) 156 | { 157 | if(!hasProperty) 158 | { 159 | property = ""; 160 | var propertyStringLength = reader.ReadUInt16(); 161 | for (var i = 0; i < propertyStringLength; i++) 162 | { 163 | property += (char)reader.ReadByte(); 164 | } 165 | hasProperty = true; 166 | } 167 | 168 | if (hasProperty == true) 169 | { 170 | if(property.Length == 0) 171 | { 172 | amfData.Objects.Add(objectAdd); 173 | 174 | break; 175 | } 176 | var objtype = (Amf0Types)reader.ReadByte(); 177 | parseType(objtype, reader, ref objectAdd.Nulls, objectNumbers: objectAdd.Numbers, 178 | objectBooleans: objectAdd.Booleans, objectStrings: objectAdd.Strings, 179 | property: property); 180 | hasProperty = false; 181 | } 182 | } 183 | } 184 | break; 185 | default: 186 | parseType(type, reader, ref amfData.Nulls, amfData.Numbers, amfData.Booleans, amfData.Strings); 187 | break; 188 | }*/ 189 | } 190 | } 191 | 192 | //jesus fuck note to self: fix this 193 | private static void parseType(Amf0Types type, EndianBinaryReader reader, 194 | ref uint Nulls, 195 | List Numbers = null, 196 | List Booleans = null, 197 | List Strings = null, 198 | Dictionary objectStrings = null, 199 | Dictionary objectNumbers = null, 200 | Dictionary objectBooleans = null, 201 | string property = "") 202 | { 203 | Console.WriteLine(BitConverter.ToString(BitConverter.GetBytes((byte)type)) + ":" + type); 204 | switch (type) 205 | { 206 | case Amf0Types.Number: 207 | if (Numbers != null) 208 | Numbers.Add(reader.ReadDouble()); 209 | else 210 | objectNumbers.Add(property, reader.ReadDouble()); 211 | break; 212 | case Amf0Types.Boolean: 213 | if (Booleans != null) 214 | Booleans.Add(reader.ReadBoolean()); 215 | else 216 | objectBooleans.Add(property, reader.ReadBoolean()); 217 | break; 218 | case Amf0Types.String: 219 | { 220 | var count = reader.ReadUInt16(); 221 | var pushString = ""; 222 | for (var i = 0; i < count; i++) 223 | { 224 | pushString += (char) reader.ReadByte(); 225 | } 226 | if (Strings != null) 227 | Strings.Add(pushString); 228 | else 229 | objectStrings.Add(property, pushString); 230 | } 231 | break; 232 | case Amf0Types.Null: 233 | Nulls++; 234 | break; 235 | case Amf0Types.Array: 236 | break; 237 | case Amf0Types.ObjectEnd: 238 | break; 239 | default: 240 | throw new ArgumentOutOfRangeException(); 241 | } 242 | } 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /RTMP/AmfWriter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using MiscUtil.IO; 8 | using MiscUtil.Conversion; 9 | 10 | namespace RTMP 11 | { 12 | 13 | public class AmfWriter 14 | { 15 | private MemoryStream memory; 16 | private EndianBinaryWriter writer; 17 | 18 | public AmfWriter() 19 | { 20 | memory = new MemoryStream(); 21 | writer = new EndianBinaryWriter(EndianBitConverter.Big, memory); 22 | } 23 | 24 | public void WriteNumber(double number) 25 | { 26 | writer.Write((byte) Amf0Types.Number); 27 | writer.Write(number); 28 | } 29 | 30 | public void WriteBoolean(bool boolean) 31 | { 32 | writer.Write((byte) Amf0Types.Boolean); 33 | writer.Write(boolean); 34 | } 35 | 36 | public void WriteString(string str, bool objectStart = false) 37 | { 38 | if(objectStart == false) 39 | writer.Write((byte) Amf0Types.String); 40 | 41 | writer.Write((ushort) str.Length); 42 | 43 | for (var i = 0; i < str.Length; i++) 44 | { 45 | writer.Write(str[i]); 46 | } 47 | } 48 | 49 | public void WriteNull() 50 | { 51 | writer.Write((byte) Amf0Types.Null); 52 | } 53 | 54 | public void WriteObject(AmfObject amfObject, bool isArray = false) 55 | { 56 | if(!isArray) 57 | writer.Write((byte) Amf0Types.Object); 58 | else 59 | { 60 | writer.Write((byte) Amf0Types.Array); 61 | writer.Write( 62 | (int) 63 | (amfObject.Booleans.Count + amfObject.Numbers.Count + amfObject.Strings.Count + amfObject.Nulls)); 64 | } 65 | foreach (var s in amfObject.Strings) 66 | { 67 | WriteString(s.Key, true); 68 | WriteString(s.Value); 69 | } 70 | 71 | foreach (var s in amfObject.Numbers) 72 | { 73 | WriteString(s.Key, true); 74 | WriteNumber(s.Value); 75 | } 76 | 77 | foreach (var s in amfObject.Booleans) 78 | { 79 | WriteString(s.Key, true); 80 | WriteBoolean(s.Value); 81 | } 82 | //objects end with 0x00,0x00, (oject end identifier [0x09 in this case]) 83 | writer.Write((byte)0x00); 84 | writer.Write((byte)0x00); 85 | writer.Write((byte) Amf0Types.ObjectEnd); 86 | } 87 | 88 | public byte[] GetByteArray() 89 | { 90 | return memory.ToArray(); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /RTMP/Client.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Net.Sockets; 8 | using MiscUtil.Conversion; 9 | using MiscUtil.IO; 10 | 11 | namespace RTMP 12 | { 13 | public class Client 14 | { 15 | public const ushort HANDSHAKE_RAND_LENGTH = 1536; 16 | public const byte PROTOCOL_VERSION = 0x03; 17 | 18 | public int StreamId; 19 | public string PublisherId; 20 | 21 | public DataFrame MyDataFrame; 22 | BigEndianBitConverter converter = new BigEndianBitConverter(); 23 | public uint CurrentChunkSize { get; private set; } 24 | public enum ClientStates 25 | { 26 | None, 27 | Handshaking, 28 | WaitingForAcknowledge, 29 | WaitForPeerBandwidth, 30 | WaitForStreamBeginControl, 31 | WaitForConnectResult, 32 | WaitForCreateStreamResponse, 33 | WaitForPublishStreamBeginResult, 34 | Streaming, 35 | } 36 | 37 | public ClientStates CurrentState { get; private set; } 38 | 39 | private Random random; 40 | private TcpClient tcpClient; 41 | private byte[] serverS1RandomBytes; 42 | 43 | private MemoryStream sMemory; 44 | private BinaryWriter sWriter; 45 | 46 | public Client() 47 | { 48 | CurrentChunkSize = 0; 49 | MyDataFrame = new DataFrame(); 50 | 51 | CurrentState = ClientStates.None; 52 | tcpClient = new TcpClient(); 53 | random = new Random(); 54 | serverS1RandomBytes = new byte[HANDSHAKE_RAND_LENGTH]; 55 | 56 | sMemory = new MemoryStream(); 57 | sWriter = new BinaryWriter(sMemory); 58 | 59 | StreamId = 1; 60 | PublisherId = ""; 61 | } 62 | 63 | public void Connect(string ip, int port = 1935) 64 | { 65 | tcpClient.Connect(ip, port); 66 | tcpClient.NoDelay = false; 67 | CurrentState = ClientStates.Handshaking; 68 | } 69 | 70 | public void Start() 71 | { 72 | SendC0Handshake(); 73 | SendC1Handshake(); 74 | } 75 | 76 | private void SendC0Handshake() 77 | { 78 | tcpClient.GetStream().Write(new byte[1]{PROTOCOL_VERSION}, 0, 1); 79 | } 80 | 81 | private void SendC1Handshake() 82 | { 83 | var byteBuffer = new byte[HANDSHAKE_RAND_LENGTH]; 84 | for(var i = 0; i < byteBuffer.Length; i++) 85 | { 86 | byteBuffer[i] = 0x00; 87 | } 88 | //random.NextBytes(byteBuffer); 89 | 90 | tcpClient.GetStream().Write(byteBuffer, 0, byteBuffer.Length); 91 | } 92 | 93 | private void ParseS1Handshake() 94 | { 95 | sMemory.Position = 0; 96 | var reader = new BinaryReader(sMemory); 97 | 98 | if(reader.ReadByte() != PROTOCOL_VERSION) 99 | { 100 | throw new Exception("PROTOCOL DOES NOT MATCH"); 101 | } 102 | for(var i = 0; i < serverS1RandomBytes.Length; i++) 103 | { 104 | serverS1RandomBytes[i] = reader.ReadByte(); 105 | } 106 | 107 | SendC2Handshake(); 108 | } 109 | 110 | private void SendC2Handshake() 111 | { 112 | tcpClient.GetStream().Write(serverS1RandomBytes, 0, serverS1RandomBytes.Length); 113 | Connect("app"); 114 | CurrentState = ClientStates.WaitingForAcknowledge; 115 | 116 | HandshakeOver(); 117 | } 118 | 119 | public void SendAmf(AmfWriter amf) 120 | { 121 | sendMessage(amf.GetByteArray(), RtmpMessageTypeId.AMF0); 122 | } 123 | 124 | public void SendChunkSize(uint chunkSize) 125 | { 126 | sendMessage(converter.GetBytes(chunkSize), RtmpMessageTypeId.SetChunkSize); 127 | CurrentChunkSize = chunkSize; 128 | } 129 | 130 | public void Stop() 131 | { 132 | var amfWriter = new AmfWriter(); 133 | amfWriter.WriteString("deleteStream"); 134 | amfWriter.WriteNumber(7); 135 | amfWriter.WriteNull(); 136 | amfWriter.WriteNumber(1); 137 | SendAmf(amfWriter); 138 | 139 | tcpClient.Close(); 140 | } 141 | 142 | private void Connect(string type = "app") 143 | { 144 | var writer = new AmfWriter(); 145 | writer.WriteString("connect"); 146 | writer.WriteNumber(1); 147 | var connectObject = new AmfObject(); 148 | connectObject.Strings.Add("app", "app"); 149 | writer.WriteObject(connectObject); 150 | SendAmf(writer); 151 | } 152 | 153 | private void SendWindowAcknowledgementSize() 154 | { 155 | SendChunkSize(5000); 156 | } 157 | 158 | private void createStream() 159 | { 160 | var writer = new AmfWriter(); 161 | writer.WriteString("createStream"); 162 | writer.WriteNumber(4); 163 | writer.WriteNull(); 164 | SendAmf(writer); 165 | } 166 | 167 | private void publish(string id) 168 | { 169 | var writer = new AmfWriter(); 170 | writer.WriteString("publish"); 171 | writer.WriteNumber(0); 172 | writer.WriteNull(); 173 | writer.WriteString(id); 174 | writer.WriteString("live"); 175 | SendAmf(writer); 176 | } 177 | 178 | public void SendDebugVideoData() 179 | { 180 | var memory = new MemoryStream(); 181 | var writer = new BinaryWriter(memory); 182 | 183 | writer.Write(0x17); 184 | var bytes = 185 | //Helper.StringToByteArray( 186 | // "FF0000c8000003b6419af0344c414ffe07e2511173ef4fa8e5fed86bf8c15b7a579ffd50464c7f3115705c306bccce001a5e7c94fda3e03784dac50bb3d23f492d3764fd91fbdfa039d37bc2077d60dd68c77b64e4ae08e424a5c397f07525c30508dcf55430007048b6cccd9673704fc3f9781f0daea4e4d0b871730f89be10e5f2d32f673b21aefc871484efe6eaac122861d7263fe99e043ed891400644b267c356dbd73fb3400b16c79c9f1ae206e209325b5982729a4d4d414b6bc1b8d5e93c5dcfffa5550f445da3f3d6012e74dc5af7a6ce77edc04bdcf7cdbfe4a826b7ddaee5c85d5b3618a265b74ed3d0614df141e88f349ca229115b59ca7ad88aa302bc0a8fd25ad9c842b6b3eaa8e338f19df2911ab491d16daec91ca07dcb06b3e72fc045b24c83ec7a7c13a0497b6ac0f29bfb71bf777f3a7064291d5bf0b79c420922b84e4bf57fc843370b6a346de6b16f684450f2118cab739d03fa3c35971c657af3b713ed24de85497a6a3f3e1671fe1b7556cc03adc9a5375742eb77fed13607da8ca193a838aa2b61034717766f724bdb83d2970996e6cad33583dd161545c78b5e0a296bc309eb11a248af8ead8629213349ddd85c026d795f03af0c9a1e56452afcc3295de62d7046722ab223f0761d887ef818471c2c80dfc54286d5723f969139f8a0b5f9013bf432d03a2853db77b63f403adfaed29299a85bb99b5f9f1d781e0a48e8445622fc65d1e33708fc7cbd91a4ea128f8abbd0118fc8111dc69deff1e3ac48a0482a9c7b1124bd7e0db87cfa04403c6708c8ee1c41d88c1cf6a20f78d5503eb8c9c77cfa0c34faa99469065885bd0688004217c4a52ab887407b1729dcb851d42e214997d1d62b545de1c2796368f91324766378e57035ce54c2591fadb2a618cb3cab1893a9306b373f43b6b88531314107fc7c0b96822c22b2e688a0c0a685f3e1d88c0e7cc0d1ebe98f01e540576f4d91700e8d4290bf524b533fe0432273aed4a6d124ba2fb337662aae1320337dbd9fe5678f660b66027851ad4b4aa5c604aaf5e6d261acd6e6ca79d8ed1145b067cef9d3b676523f40222a38538b6409498d9b7a3dc43ce3e8025962f6a82088d57f7183e1094908e75bc3e983ab7d67fe5c68fce8ee446e273635fca2e19c6a6fd2aabbca96ef080fcb227529a1fc7fcbd96a2bcace9c5b7e10142802d51cad4d2a5fa0962a182c2ff5f1cde8fe8d83ada401901a410390b872316a2a35b6ab900fdc7b1ffe0635d8f54997b274e36eea1eadcc77988cffdaa726c611ad343780105d615497219f55647b69c9b6c839c8f574266478956c4ccc3f8757770954f07981"); 187 | Helper.StringToByteArray("FFFF"); 188 | writer.Write(bytes); 189 | sendMessage(memory.ToArray(), RtmpMessageTypeId.Video); 190 | 191 | } 192 | public void SendDebugAudioData() 193 | { 194 | var memory = new MemoryStream(); 195 | var writer = new BinaryWriter(memory); 196 | 197 | writer.Write(0x17); 198 | var bytes = 199 | //Helper.StringToByteArray( 200 | // "FF0000c8000003b6419af0344c414ffe07e2511173ef4fa8e5fed86bf8c15b7a579ffd50464c7f3115705c306bccce001a5e7c94fda3e03784dac50bb3d23f492d3764fd91fbdfa039d37bc2077d60dd68c77b64e4ae08e424a5c397f07525c30508dcf55430007048b6cccd9673704fc3f9781f0daea4e4d0b871730f89be10e5f2d32f673b21aefc871484efe6eaac122861d7263fe99e043ed891400644b267c356dbd73fb3400b16c79c9f1ae206e209325b5982729a4d4d414b6bc1b8d5e93c5dcfffa5550f445da3f3d6012e74dc5af7a6ce77edc04bdcf7cdbfe4a826b7ddaee5c85d5b3618a265b74ed3d0614df141e88f349ca229115b59ca7ad88aa302bc0a8fd25ad9c842b6b3eaa8e338f19df2911ab491d16daec91ca07dcb06b3e72fc045b24c83ec7a7c13a0497b6ac0f29bfb71bf777f3a7064291d5bf0b79c420922b84e4bf57fc843370b6a346de6b16f684450f2118cab739d03fa3c35971c657af3b713ed24de85497a6a3f3e1671fe1b7556cc03adc9a5375742eb77fed13607da8ca193a838aa2b61034717766f724bdb83d2970996e6cad33583dd161545c78b5e0a296bc309eb11a248af8ead8629213349ddd85c026d795f03af0c9a1e56452afcc3295de62d7046722ab223f0761d887ef818471c2c80dfc54286d5723f969139f8a0b5f9013bf432d03a2853db77b63f403adfaed29299a85bb99b5f9f1d781e0a48e8445622fc65d1e33708fc7cbd91a4ea128f8abbd0118fc8111dc69deff1e3ac48a0482a9c7b1124bd7e0db87cfa04403c6708c8ee1c41d88c1cf6a20f78d5503eb8c9c77cfa0c34faa99469065885bd0688004217c4a52ab887407b1729dcb851d42e214997d1d62b545de1c2796368f91324766378e57035ce54c2591fadb2a618cb3cab1893a9306b373f43b6b88531314107fc7c0b96822c22b2e688a0c0a685f3e1d88c0e7cc0d1ebe98f01e540576f4d91700e8d4290bf524b533fe0432273aed4a6d124ba2fb337662aae1320337dbd9fe5678f660b66027851ad4b4aa5c604aaf5e6d261acd6e6ca79d8ed1145b067cef9d3b676523f40222a38538b6409498d9b7a3dc43ce3e8025962f6a82088d57f7183e1094908e75bc3e983ab7d67fe5c68fce8ee446e273635fca2e19c6a6fd2aabbca96ef080fcb227529a1fc7fcbd96a2bcace9c5b7e10142802d51cad4d2a5fa0962a182c2ff5f1cde8fe8d83ada401901a410390b872316a2a35b6ab900fdc7b1ffe0635d8f54997b274e36eea1eadcc77988cffdaa726c611ad343780105d615497219f55647b69c9b6c839c8f574266478956c4ccc3f8757770954f07981"); 201 | Helper.StringToByteArray("FFFF"); 202 | writer.Write(bytes); 203 | sendMessage(memory.ToArray(), RtmpMessageTypeId.Audio); 204 | 205 | } 206 | 207 | public void SendFlv(FlvTag[] flvs) 208 | { 209 | var memory = new MemoryStream(); 210 | var writer = new EndianBinaryWriter(EndianBitConverter.Big, memory); 211 | 212 | const byte chunkHeaderType = 0x03; 213 | 214 | var chunkCount = 0; 215 | for (var i = 0; i < flvs.Length; i++) 216 | { 217 | var flv = flvs[i]; 218 | chunkCount += flv.Data.Length; 219 | writer.Write(chunkHeaderType); 220 | writer.Write(flv.TimeStamp, 0, 3); 221 | writer.Write(flv.Length, 0, 3); 222 | writer.Write((byte) flv.TagType); 223 | 224 | 225 | //writer.Write(new byte[] {0x00, flv.StreamId[0], flv.StreamId[1], flv.StreamId[2]}); 226 | 227 | var streamIdBytes = converter.GetBytes(StreamId); 228 | for (int id = streamIdBytes.Length - 1; id >= 0; id--) 229 | { 230 | writer.Write(streamIdBytes[id]); 231 | } 232 | 233 | writer.Write(flv.Data); 234 | } 235 | if(chunkCount > CurrentChunkSize) 236 | SendChunkSize((uint)chunkCount); 237 | tcpClient.GetStream().Write(memory.ToArray(), 0, memory.ToArray().Length); 238 | } 239 | 240 | private void sendMessage(byte[] data, RtmpMessageTypeId messageType) 241 | { 242 | 243 | const byte chunkHeaderType = 0x03; 244 | 245 | var timeStampDelta = new byte[3] { 0x00, 0x00, 0x10 }; 246 | 247 | //The packet length is only 3 bytes long when sent, so the last byte of the integer needs to be cut off 248 | var packetLengthBytes = new byte[3]; 249 | { 250 | var packetLengthValue = data.Length; 251 | 252 | var packetLengthBytesFull = converter.GetBytes(packetLengthValue); 253 | for (var i = 0; i < packetLengthBytes.Length; i++) 254 | { 255 | packetLengthBytes[i] = packetLengthBytesFull[i + 1]; 256 | } 257 | } 258 | 259 | var memory = new MemoryStream(); 260 | var writer = new EndianBinaryWriter(EndianBitConverter.Big, memory); 261 | writer.Write(chunkHeaderType); 262 | writer.Write(timeStampDelta); 263 | writer.Write(packetLengthBytes); 264 | writer.Write((byte)messageType); 265 | 266 | var streamIdBytes = converter.GetBytes(StreamId); 267 | for (int i = streamIdBytes.Length - 1; i >= 0; i--) 268 | { 269 | writer.Write(streamIdBytes[i]); 270 | } 271 | writer.Write(data); 272 | tcpClient.GetStream().Write(memory.ToArray(), 0, memory.ToArray().Length); 273 | } 274 | 275 | public void Update() 276 | { 277 | if(tcpClient.Available != 0) 278 | { 279 | var buffer = new byte[tcpClient.Available]; 280 | tcpClient.GetStream().Read(buffer, 0, buffer.Length); 281 | 282 | if(CurrentState == ClientStates.Handshaking) 283 | { 284 | if(sMemory.Length < HANDSHAKE_RAND_LENGTH * 2) 285 | { 286 | sWriter.Write(buffer); 287 | if(sMemory.Length >= HANDSHAKE_RAND_LENGTH * 2) 288 | { 289 | ParseS1Handshake(); 290 | } 291 | } 292 | else 293 | { 294 | ParseS1Handshake(); 295 | } 296 | } 297 | else 298 | { 299 | var memory = new MemoryStream(buffer); 300 | var reader = new EndianBinaryReader(EndianBitConverter.Big, memory); 301 | 302 | while (memory.Position < memory.Length) 303 | { 304 | reader.ReadBytes(4); //as of now not used data 305 | var bodySizeBytes = new byte[] {0, reader.ReadByte(), reader.ReadByte(), reader.ReadByte()}; 306 | var bodySize = converter.ToUInt32(bodySizeBytes, 0); 307 | 308 | var messageId = (RtmpMessageTypeId) reader.ReadByte(); 309 | reader.ReadInt32(); //stream id is not needed as of now 310 | 311 | switch (messageId) 312 | { 313 | case RtmpMessageTypeId.SetChunkSize: 314 | { 315 | ParseSetChunkSize(reader.ReadInt32()); 316 | } 317 | break; 318 | case RtmpMessageTypeId.UserControlMessage: 319 | { 320 | //No fawking clue why it's six bytes atm 321 | ParseUserControlMessage(reader.ReadBytes(6)); 322 | if(CurrentState == ClientStates.WaitForStreamBeginControl) 323 | { 324 | //Console.WriteLine("SWITCh3"); 325 | CurrentState = ClientStates.WaitForConnectResult; 326 | } 327 | if(CurrentState == ClientStates.WaitForPublishStreamBeginResult) 328 | { 329 | //Console.WriteLine("Switch6"); 330 | CurrentState = ClientStates.Streaming; 331 | SendChunkSize(100); 332 | } 333 | } 334 | break; 335 | case RtmpMessageTypeId.ServerBandwidth: 336 | { 337 | if(CurrentState == ClientStates.WaitingForAcknowledge) 338 | { 339 | //Console.WriteLine("SWITCH1"); 340 | SendWindowAcknowledgementSize(); 341 | CurrentState = ClientStates.WaitForPeerBandwidth; 342 | } 343 | ParseServerBandwidth(reader.ReadInt32()); 344 | } 345 | break; 346 | case RtmpMessageTypeId.ClientBandwitdh: 347 | { 348 | if(CurrentState == ClientStates.WaitForPeerBandwidth) 349 | { 350 | //Console.WriteLine("SWITCh2"); 351 | CurrentState = ClientStates.WaitForStreamBeginControl; 352 | } 353 | ParseClientBandwidth(reader.ReadInt32(), reader.ReadByte()); 354 | } 355 | break; 356 | case RtmpMessageTypeId.Audio: 357 | break; 358 | case RtmpMessageTypeId.Video: 359 | break; 360 | case RtmpMessageTypeId.AMF3: 361 | break; 362 | case RtmpMessageTypeId.Invoke: 363 | break; 364 | case RtmpMessageTypeId.AMF0: 365 | { 366 | var amfReader = new AmfReader(); 367 | amfReader.Parse(reader, bodySize); 368 | 369 | if(CurrentState == ClientStates.WaitForConnectResult) 370 | { 371 | if (amfReader.amfData.Strings.Contains("_result")) 372 | { 373 | //Console.WriteLine("SWITCH4"); 374 | createStream(); 375 | CurrentState = ClientStates.WaitForCreateStreamResponse; 376 | } 377 | } 378 | if(CurrentState == ClientStates.WaitForCreateStreamResponse) 379 | { 380 | if (amfReader.amfData.Strings.Contains("_result")) 381 | { 382 | //Console.WriteLine("SWITCH5"); 383 | publish(PublisherId); 384 | CurrentState = ClientStates.WaitForPublishStreamBeginResult; 385 | } 386 | } 387 | 388 | ParseAmf(amfReader.amfData); 389 | } 390 | break; 391 | case RtmpMessageTypeId.Acknowledgement: 392 | ParseAcknowledgement(reader.ReadInt32()); 393 | break; 394 | default: 395 | Console.WriteLine(messageId); 396 | break; 397 | } 398 | 399 | ParseMessage(messageId, reader); 400 | } 401 | } 402 | } 403 | } 404 | 405 | protected virtual void ParseAcknowledgement(int value) 406 | { 407 | 408 | } 409 | 410 | 411 | protected virtual void ParseSetChunkSize(int chunkSize) 412 | { 413 | //this should be done in the derived classes 414 | } 415 | 416 | protected virtual void ParseUserControlMessage(byte[] eventType) 417 | { 418 | //this should be done in the derived classes 419 | } 420 | 421 | protected virtual void ParseServerBandwidth(int amount) 422 | { 423 | //this should be done in the derived classes 424 | } 425 | 426 | protected virtual void ParseClientBandwidth(int amount, byte limitType) 427 | { 428 | //this should be done in the derived classes 429 | } 430 | 431 | protected virtual void ParseAmf(AmfData amf) 432 | { 433 | //this should be done in the derived classes 434 | } 435 | 436 | protected virtual void ParseMessage(RtmpMessageTypeId messageType, EndianBinaryReader reader) 437 | { 438 | //this should be done in the derived classes 439 | } 440 | 441 | protected virtual void HandshakeOver() 442 | { 443 | //this should be done in the derived classes 444 | } 445 | } 446 | } 447 | -------------------------------------------------------------------------------- /RTMP/DataFrame.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RTMP 8 | { 9 | //mfw I finally understand what something in this protocol means 10 | public class DataFrame 11 | { 12 | //DOUBLES DOUBLES DOUBLES 13 | public double Duration; 14 | public double Width; 15 | public double Height; 16 | public double VideoDataRate; 17 | public double FrameRate; 18 | public double VideoCodeCid; 19 | public double AudioDataRate; 20 | public double AudioSampleRate; 21 | public double AudioSampleSize; 22 | public bool Stereo; 23 | public double AudioCodecId; 24 | public string Encoder; 25 | public double FileSize; 26 | 27 | public DataFrame() 28 | { 29 | Duration = 0; 30 | Width = 100; 31 | Height = 100; 32 | VideoDataRate = 732.421875; 33 | FrameRate = 1000; 34 | VideoCodeCid = 7; 35 | AudioDataRate = 250; 36 | AudioSampleRate = 44100; 37 | AudioSampleSize = 16; 38 | Stereo = true; 39 | AudioCodecId = 10; 40 | Encoder = "Lavf54.25.100"; 41 | FileSize = 0; 42 | } 43 | 44 | public RTMP.AmfWriter GetAmf() 45 | { 46 | var amfWriter = new RTMP.AmfWriter(); 47 | 48 | amfWriter.WriteString("@setDataFrame"); 49 | amfWriter.WriteString("onMetaData"); 50 | 51 | var dataOjbect = new RTMP.AmfObject(); 52 | dataOjbect.Numbers.Add("duration", Duration); 53 | dataOjbect.Numbers.Add("width", Width); 54 | dataOjbect.Numbers.Add("height", Height); 55 | dataOjbect.Numbers.Add("videodatarate", VideoDataRate); 56 | dataOjbect.Numbers.Add("framerate", FrameRate); 57 | dataOjbect.Numbers.Add("videocodecid", VideoCodeCid); 58 | dataOjbect.Numbers.Add("audiodatarate", AudioDataRate); 59 | dataOjbect.Numbers.Add("audiosamplerate", AudioSampleRate); 60 | dataOjbect.Numbers.Add("audiosamplesize", AudioSampleSize); 61 | dataOjbect.Booleans.Add("stereo", Stereo); 62 | dataOjbect.Numbers.Add("audiocodecid", AudioCodecId); 63 | dataOjbect.Strings.Add("encoder", Encoder); 64 | dataOjbect.Numbers.Add("filesize", FileSize); 65 | amfWriter.WriteObject(dataOjbect, true); 66 | 67 | return amfWriter; 68 | } 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /RTMP/Flv.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using MiscUtil.Conversion; 8 | using MiscUtil.IO; 9 | 10 | namespace RTMP 11 | { 12 | public class FlvTag 13 | { 14 | public enum TagTypes : byte 15 | { 16 | MetaData = 0x12, 17 | Audio = 0x08, 18 | Video = 0x09, 19 | } 20 | 21 | public TagTypes TagType; 22 | public byte[] Length; 23 | 24 | public int LengthValue 25 | { 26 | get 27 | { 28 | var convert = new BigEndianBitConverter(); 29 | var bytes = new byte[4] { 0x00, Length[0], Length[1], Length[2] }; 30 | return convert.ToInt32(bytes, 0); 31 | } 32 | } 33 | 34 | public byte[] TimeStamp; //NOTE: THAT THE TIMESTAMP IS 4 BYTES BUT IN RTMP IT'S 3 BYTES WHEN SENT 35 | public uint TimeStampValue { get; set; } 36 | public byte[] TimeStampNormal; 37 | 38 | public byte[] StreamId; //NOTE: THAT THE STREAM ID IS 3 BYTES BUT IN RTMP IT'S 4 BYTES WHEN SENT 39 | public uint StreamIdIdValue { get; set; } 40 | 41 | public byte[] Data; 42 | 43 | public FlvTag() 44 | { 45 | Reset(); 46 | } 47 | 48 | public void Reset() 49 | { 50 | TagType = TagTypes.Video; 51 | Length = null; 52 | TimeStamp = null; 53 | Data = null; 54 | TimeStampNormal = null; 55 | } 56 | 57 | public void Load(EndianBinaryReader reader) 58 | { 59 | TagType = (TagTypes)reader.ReadByte(); 60 | Length = reader.ReadBytes(3); 61 | TimeStamp = reader.ReadBytes(4); 62 | StreamId = reader.ReadBytes(3); 63 | Data = reader.ReadBytes((int)LengthValue); 64 | 65 | //BECAUSE SOMEONE AT ADOBE THOUGHT IT'D BE A GRAND IDEA TO DO MIXED ENDIAN 66 | //( ͡° ͜ʖ ͡°) comic saaaanssss, comic saaaaaaaaaaaaaaaanssssssss 67 | TimeStampNormal = new byte[TimeStamp.Length]; 68 | TimeStampNormal[0] = TimeStamp[3]; 69 | TimeStampNormal[1] = TimeStamp[0]; 70 | TimeStampNormal[2] = TimeStamp[1]; 71 | TimeStampNormal[3] = TimeStamp[2]; 72 | } 73 | } 74 | 75 | public class Flv 76 | { 77 | public enum BitmaskTypes : byte 78 | { 79 | Audio = 0x04, 80 | Video = 0x01, 81 | AudioAndVideo = 0x05, 82 | } 83 | 84 | public BitmaskTypes Bitmask { get; private set; } 85 | public uint HeaderSize { get; private set; } 86 | public byte Version { get; private set; } 87 | 88 | public List Tags; 89 | 90 | public Flv() 91 | { 92 | Version = 0; 93 | Bitmask = BitmaskTypes.Audio; 94 | Version = 0; 95 | Tags = new List(); 96 | } 97 | 98 | 99 | public void Load(Stream stream) 100 | { 101 | var reader = new EndianBinaryReader(EndianBitConverter.Big, stream); 102 | 103 | //Header 104 | reader.ReadBytes(3); // "FLV" 105 | Version = reader.ReadByte(); 106 | Bitmask = (BitmaskTypes) reader.ReadByte(); 107 | HeaderSize = reader.ReadUInt32(); 108 | 109 | //Start reading tags 110 | while(stream.Position < stream.Length) 111 | { 112 | var footer = reader.ReadUInt32(); 113 | if(stream.Position >= stream.Length) 114 | { 115 | break; 116 | } 117 | var tag = new FlvTag(); 118 | 119 | tag.Load(reader); 120 | Tags.Add(tag); 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /RTMP/FlvStreamer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.Diagnostics; 7 | 8 | namespace RTMP 9 | { 10 | public class FlvStreamer 11 | { 12 | public Flv Video; 13 | 14 | private Stopwatch timer; 15 | private uint tagId; 16 | private List sendTags; 17 | 18 | public uint SendRate; 19 | public byte TagsPerMessage; 20 | 21 | 22 | public FlvStreamer(Flv vid = null) 23 | { 24 | tagId = 0; 25 | 26 | Video = vid; 27 | 28 | TagsPerMessage = 70; 29 | SendRate = 550; 30 | 31 | sendTags = new List(); 32 | 33 | timer = new Stopwatch(); 34 | 35 | } 36 | 37 | public void Reset() 38 | { 39 | tagId = 0; 40 | timer.Reset(); 41 | } 42 | 43 | public void Stop() 44 | { 45 | timer.Stop(); 46 | } 47 | 48 | public void Restart() 49 | { 50 | tagId = 0; 51 | timer.Restart(); 52 | } 53 | 54 | public void Start() 55 | { 56 | timer.Start(); 57 | } 58 | 59 | public void Update(Client[] clients) 60 | { 61 | if(Video.Tags.Count > tagId && timer.ElapsedMilliseconds >= SendRate) 62 | { 63 | timer.Restart(); 64 | 65 | 66 | for(uint count = 0; tagId < Video.Tags.Count && count < TagsPerMessage; tagId++, count++) 67 | { 68 | //Console.WriteLine(tagId); 69 | sendTags.Add(Video.Tags[(int)tagId]); 70 | } 71 | 72 | for (var i = 0; i < clients.Length; i++) 73 | if(clients[i].CurrentState == Client.ClientStates.Streaming) 74 | clients[i].SendFlv(sendTags.ToArray()); 75 | sendTags.Clear(); 76 | } 77 | } 78 | 79 | public void Update(Client client) 80 | { 81 | Update(new Client[1] {client}); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /RTMP/Globals.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RTMP 8 | { 9 | public enum Amf0Types : byte 10 | { 11 | Number = 0x00, 12 | Boolean = 0x01, 13 | String = 0x02, 14 | Object = 0x03, 15 | Null = 0x05, 16 | Array = 0x08, 17 | ObjectEnd = 0x09, 18 | } 19 | 20 | public enum RtmpMessageTypeId : byte 21 | { 22 | SetChunkSize = 0x01, 23 | Acknowledgement = 0x03, 24 | UserControlMessage = 0x04, 25 | ServerBandwidth = 0x05, 26 | ClientBandwitdh = 0x06, 27 | Audio = 0x08, 28 | Video = 0x09, 29 | AMF3 = 0x11, 30 | Invoke = 0x12, 31 | AMF0 = 0x14, 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /RTMP/Helper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace RTMP 8 | { 9 | public class Helper 10 | { 11 | public static byte[] StringToByteArray(string hex) 12 | { 13 | return Enumerable.Range(0, hex.Length) 14 | .Where(x => x % 2 == 0) 15 | .Select(x => Convert.ToByte(hex.Substring(x, 2), 16)) 16 | .ToArray(); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RTMP/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("RTMP")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("RTMP")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("59d90d1a-0375-4569-ba5c-ab4cbb533120")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /RTMP/RTMP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {BC866839-E331-4742-8552-5CB69E11BF29} 8 | Library 9 | Properties 10 | RTMP 11 | RTMP 12 | v4.5 13 | 512 14 | 15 | 16 | true 17 | full 18 | false 19 | bin\Debug\ 20 | DEBUG;TRACE 21 | prompt 22 | 4 23 | 24 | 25 | pdbonly 26 | true 27 | bin\Release\ 28 | TRACE 29 | prompt 30 | 4 31 | 32 | 33 | 34 | ..\MiscUtil.dll 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | False 45 | ..\..\..\..\..\Downloads\YoutubeExtractor-master\YoutubeExtractor-master\YoutubeExtractor\YoutubeExtractor\bin\Release\YoutubeExtractor.dll 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 69 | -------------------------------------------------------------------------------- /RTMP/YouTube/VideoClient.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * SPECIAL THANKS TO THE CREATOR OF YOUTUBE-EXTRACTOR, CHECK IT OUT HERE: https://github.com/flagbug/YoutubeExtractor 3 | * Although I modified it a bit to work with streaming, sorry doods. 4 | */ 5 | 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text; 10 | using System.Threading.Tasks; 11 | using System.Web; 12 | using System.Net; 13 | using System.IO; 14 | using MiscUtil.IO; 15 | using MiscUtil.Conversion; 16 | using YoutubeExtractor; //Thanks again <3 17 | 18 | 19 | 20 | namespace RTMP.YouTube 21 | { 22 | public class VideoClient 23 | { 24 | public delegate void AddTagDeleage(FlvTag tag); 25 | 26 | public event AddTagDeleage AddedTag; 27 | 28 | //FLV Related Checks 29 | public List Tags; 30 | private VideoDownloader downloader; 31 | 32 | public static BigEndianBitConverter convert = new BigEndianBitConverter(); 33 | 34 | public void GrabVideo(string link) 35 | { 36 | AddedTag = null; 37 | 38 | Tags = new List(); 39 | 40 | var videoInfos = DownloadUrlResolver.GetDownloadUrls(link); 41 | var video = videoInfos.First(info => info.VideoType == VideoType.Flash); 42 | 43 | downloader = new VideoDownloader(video, ""); 44 | downloader.Execute(true); 45 | } 46 | 47 | public bool Update() 48 | { 49 | var bitStream = downloader.DownloadStream; 50 | var reader = new EndianBinaryReader(EndianBitConverter.Big, bitStream); 51 | 52 | if(bitStream != null) 53 | { 54 | var stream = new StreamReader(bitStream); 55 | { 56 | reader.ReadBytes(3); //"FLV" 57 | reader.ReadBytes(6); //Other starter shit 58 | 59 | while (true) 60 | { 61 | try 62 | { 63 | var footer = reader.ReadUInt32(); 64 | var tag = new FlvTag(); 65 | tag.Load(reader); 66 | 67 | AddedTag(tag); 68 | 69 | } 70 | catch (Exception) 71 | { 72 | reader.Close(); 73 | //End of stream 74 | return false; 75 | } 76 | } 77 | } 78 | } 79 | 80 | return true; 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /RTMPTests/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /RTMPTests/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using MiscUtil.Conversion; 7 | using RTMP; 8 | using System.IO; 9 | using MiscUtil.IO; 10 | using System.Diagnostics; 11 | using RTMP.YouTube; 12 | using TwitchSharp; 13 | using System.Web; 14 | using System.Net; 15 | 16 | namespace RTMPTests 17 | { 18 | class Program 19 | { 20 | static void Main(string[] args) 21 | { 22 | FlvStreamer streamer = new FlvStreamer(); 23 | 24 | var client = new Client(); 25 | 26 | client.PublisherId = "live_YourStreamID"; 27 | client.Connect("199.9.255.53"); 28 | client.Start(); 29 | 30 | 31 | var vclient = new VideoClient(); 32 | vclient.GrabVideo("http://www.youtube.com/watch?v=at68PMbgyhw"); 33 | vclient.AddedTag += delegate(FlvTag tag) 34 | { 35 | client.SendFlv(new FlvTag[1] {tag}); 36 | }; 37 | 38 | 39 | while(true) 40 | { 41 | client.Update(); 42 | if(client.CurrentState == Client.ClientStates.Streaming) 43 | vclient.Update(); 44 | } 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /RTMPTests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("RTMPTests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("RTMPTests")] 13 | [assembly: AssemblyCopyright("Copyright © 2012")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("2838ac4c-19bc-4fa4-bc61-6cbea25e6e01")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /RTMPTests/RTMPTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EDEBCB56-7D8D-4E6B-A214-25BB01F9B2A4} 8 | Exe 9 | Properties 10 | RTMPTests 11 | RTMPTests 12 | v4.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | ..\MiscUtil.dll 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | {bc866839-e331-4742-8552-5cb69e11bf29} 56 | RTMP 57 | 58 | 59 | {ff0e453d-d6b7-4d19-a28f-3f224ff179a6} 60 | TwitchSharp 61 | 62 | 63 | 64 | 71 | --------------------------------------------------------------------------------