318 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C#
		
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			7.5 KiB
		
	
	
	
		
			C#
		
	
	
	
| //-----------------------------------------------------------------------------
 | |
| // Copyright 2015-2018 RenderHeads Ltd.  All rights reserverd.
 | |
| //-----------------------------------------------------------------------------
 | |
| 
 | |
| #if !UNITY_WSA_10_0 && !UNITY_WINRT_8_1 && !UNITY_WSA && !UNITY_WEBPLAYER
 | |
| 	#define SUPPORT_SSL
 | |
| #endif
 | |
| 
 | |
| using System;
 | |
| using System.IO;
 | |
| using System.Collections.Generic;
 | |
| using UnityEngine;
 | |
| #if SUPPORT_SSL
 | |
| using System.Security.Cryptography.X509Certificates;
 | |
| using System.Net.Security;
 | |
| using System.Net;
 | |
| #endif
 | |
| 
 | |
| namespace RenderHeads.Media.AVProVideo
 | |
| {
 | |
| 	/// <summary>
 | |
| 	/// Utility class to parses an HLS(m3u8) adaptive stream from a URL to 
 | |
| 	/// allow easy inspection and navitation of the stream data
 | |
| 	/// </summary>
 | |
| 	public class HLSStream : Stream
 | |
| 	{
 | |
| 		private const string BANDWITH_NAME = "BANDWIDTH=";
 | |
| 		private const string RESOLUTION_NAME = "RESOLUTION=";
 | |
| 		private const string CHUNK_TAG = "#EXTINF";
 | |
| 		private const string STREAM_TAG = "#EXT-X-STREAM-INF";
 | |
| 
 | |
| 		private List<Stream> _streams;
 | |
| 		private List<Chunk> _chunks;
 | |
| 		private string _streamURL;
 | |
| 		private int _width;
 | |
| 		private int _height;
 | |
| 		private int _bandwidth;
 | |
| 
 | |
| 		public override int Width
 | |
| 		{
 | |
| 			get { return _width; }
 | |
| 		}
 | |
| 
 | |
| 		public override int Height
 | |
| 		{
 | |
| 			get { return _height; }
 | |
| 		}
 | |
| 
 | |
| 		public override int Bandwidth
 | |
| 		{
 | |
| 			get { return _bandwidth; }
 | |
| 		}
 | |
| 
 | |
| 		public override string URL
 | |
| 		{
 | |
| 			get { return _streamURL; }
 | |
| 		}
 | |
| 
 | |
| 		public override List<Chunk> GetAllChunks()
 | |
| 		{
 | |
| 			List<Chunk> chunks = new List<Chunk>();
 | |
| 
 | |
| 			for(int i = 0; i < _streams.Count; ++i)
 | |
| 			{
 | |
| 				var streamChunks = _streams[i].GetAllChunks();
 | |
| 				chunks.AddRange(streamChunks);
 | |
| 			}
 | |
| 
 | |
| 			chunks.AddRange(_chunks);
 | |
| 
 | |
| 			return chunks;
 | |
| 		}
 | |
| 
 | |
| 		public override List<Chunk> GetChunks()
 | |
| 		{
 | |
| 			return _chunks;
 | |
| 		}
 | |
| 
 | |
| 		public override List<Stream> GetAllStreams()
 | |
| 		{
 | |
| 			List<Stream> streams = new List<Stream>();
 | |
| 			for(int i = 0; i < _streams.Count; ++i)
 | |
| 			{
 | |
| 				var childrenStreams = _streams[i].GetAllStreams();
 | |
| 				streams.AddRange(childrenStreams);
 | |
| 			}
 | |
| 
 | |
| 			streams.AddRange(_streams);
 | |
| 
 | |
| 			return streams;
 | |
| 		}
 | |
| 
 | |
| 		public override List<Stream> GetStreams()
 | |
| 		{
 | |
| 			return _streams;
 | |
| 		}
 | |
| 
 | |
| 		private bool ExtractStreamInfo(string line, ref int width, ref int height, ref int bandwidth)
 | |
| 		{
 | |
| 			if (line.StartsWith(STREAM_TAG))
 | |
| 			{
 | |
| 				int bandwidthPos = line.IndexOf(BANDWITH_NAME);
 | |
| 				if (bandwidthPos >= 0)
 | |
| 				{
 | |
| 					int endPos = line.IndexOf(',', bandwidthPos + BANDWITH_NAME.Length);
 | |
| 					if (endPos < 0)
 | |
| 					{
 | |
| 						endPos = line.Length - 1;
 | |
| 					}
 | |
| 
 | |
| 					if (endPos >= 0 && endPos - BANDWITH_NAME.Length > bandwidthPos)
 | |
| 					{
 | |
| 						int length = endPos - bandwidthPos - BANDWITH_NAME.Length;
 | |
| 
 | |
| 						string bandwidthString = line.Substring(bandwidthPos + BANDWITH_NAME.Length, length);
 | |
| 						if (!int.TryParse(bandwidthString, out bandwidth))
 | |
| 						{
 | |
| 							bandwidth = 0;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					bandwidth = 0;
 | |
| 				}
 | |
| 
 | |
| 				int resolutionPos = line.IndexOf(RESOLUTION_NAME);
 | |
| 				if (resolutionPos >= 0)
 | |
| 				{
 | |
| 					int endPos = line.IndexOf(',', resolutionPos + RESOLUTION_NAME.Length);
 | |
| 					if (endPos < 0)
 | |
| 					{
 | |
| 						endPos = line.Length - 1;
 | |
| 					}
 | |
| 
 | |
| 					if (endPos >= 0 && endPos - RESOLUTION_NAME.Length > resolutionPos)
 | |
| 					{
 | |
| 						int length = endPos - resolutionPos - RESOLUTION_NAME.Length;
 | |
| 						string resolutionString = line.Substring(resolutionPos + RESOLUTION_NAME.Length, length);
 | |
| 						int xPos = resolutionString.IndexOf('x');
 | |
| 
 | |
| 						if (xPos < 0 || !int.TryParse(resolutionString.Substring(0, xPos), out width) ||
 | |
| 							!int.TryParse(resolutionString.Substring(xPos + 1, resolutionString.Length - (xPos + 1)), out height))
 | |
| 						{
 | |
| 							width = height = 0;
 | |
| 						}
 | |
| 					}
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					width = height = 0;
 | |
| 				}
 | |
| 
 | |
| 				return true;
 | |
| 			}
 | |
| 
 | |
| 			return false;
 | |
| 		}
 | |
| 
 | |
| 		private static bool IsChunk(string line)
 | |
| 		{
 | |
| 			return line.StartsWith(CHUNK_TAG);
 | |
| 		}
 | |
| 
 | |
| 		private void ParseFile(string[] text, string path)
 | |
| 		{
 | |
| 			bool nextIsChunk = false;
 | |
| 			bool nextIsStream = false;
 | |
| 			int width = 0, height = 0, bitrate = 0;
 | |
| 
 | |
| 			for (int i = 0; i < text.Length; ++i)
 | |
| 			{
 | |
| 				if (ExtractStreamInfo(text[i], ref width, ref height, ref bitrate))
 | |
| 				{
 | |
| 					nextIsStream = true;
 | |
| 					nextIsChunk = false;
 | |
| 				}
 | |
| 				else if (IsChunk(text[i]))
 | |
| 				{
 | |
| 					nextIsChunk = true;
 | |
| 					nextIsStream = false;
 | |
| 				}
 | |
| 				else if (nextIsChunk)
 | |
| 				{
 | |
| 					Chunk chunk;
 | |
| 					chunk.name = path + text[i];
 | |
| 					_chunks.Add(chunk);
 | |
| 
 | |
| 					nextIsChunk = false;
 | |
| 					nextIsStream = false;
 | |
| 				}
 | |
| 				else if (nextIsStream)
 | |
| 				{
 | |
| 					try
 | |
| 					{
 | |
| 						string fullpath = text[i].IndexOf("://") < 0 ? path + text[i] : text[i];
 | |
| 						HLSStream stream = new HLSStream(fullpath, width, height, bitrate);
 | |
| 						_streams.Add(stream);
 | |
| 					}
 | |
| 					catch (Exception e)
 | |
| 					{
 | |
| 						Debug.LogError("[AVProVideo]HLSParser cannot parse stream " + path + text[i] + ", " + e.Message);
 | |
| 					}
 | |
| 
 | |
| 					nextIsChunk = false;
 | |
| 					nextIsStream = false;
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					nextIsChunk = false;
 | |
| 					nextIsStream = false;
 | |
| 				}
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		public HLSStream(string filename, int width = 0, int height = 0, int bandwidth = 0)
 | |
| 		{
 | |
| 			_streams = new List<Stream>();
 | |
| 			_chunks = new List<Chunk>();
 | |
| 
 | |
| 			_width = width;
 | |
| 			_height = height;
 | |
| 			_bandwidth = bandwidth;
 | |
| 			_streamURL = filename;
 | |
| 
 | |
| 			try
 | |
| 			{
 | |
| 				string[] fileLines = null;
 | |
| 
 | |
| 				if (filename.ToLower().StartsWith("http://") || filename.ToLower().StartsWith("https://"))
 | |
| 				{
 | |
| #if UNITY_WSA_10_0 || UNITY_WINRT_8_1 || UNITY_WSA
 | |
| 					WWW www = new WWW(filename);
 | |
| 					int watchdog = 0;
 | |
| 					while (!www.isDone && watchdog < 5000)
 | |
| 					{
 | |
| #if NETFX_CORE
 | |
| 						System.Threading.Tasks.Task.Delay(1).Wait();
 | |
| #else
 | |
| 						System.Threading.Thread.Sleep(1);
 | |
| #endif
 | |
| 						watchdog++;
 | |
| 					}
 | |
| 					if (www.isDone && watchdog < 5000)
 | |
| 					{
 | |
| 						string fileString = www.text;
 | |
| 						fileLines = fileString.Split('\n');
 | |
| 					}
 | |
| #else
 | |
| 
 | |
| #if SUPPORT_SSL
 | |
| 					if (filename.ToLower().StartsWith("https://"))
 | |
| 					{
 | |
| 						ServicePointManager.ServerCertificateValidationCallback = MyRemoteCertificateValidationCallback;
 | |
| 					}
 | |
| #endif
 | |
| 					using (System.Net.WebClient webClient = new System.Net.WebClient())
 | |
| 					{
 | |
| 						string fileString = webClient.DownloadString(filename);
 | |
| 						fileLines = fileString.Split('\n');
 | |
| 					}
 | |
| #endif
 | |
| 				}
 | |
| 				else
 | |
| 				{
 | |
| 					fileLines = File.ReadAllLines(filename);
 | |
| 				}	
 | |
| 
 | |
| 				int lastDash = filename.LastIndexOf('/');
 | |
| 				if(lastDash < 0)
 | |
| 				{
 | |
| 					lastDash = filename.LastIndexOf('\\');
 | |
| 				}
 | |
| 
 | |
| 				string path = _streamURL.Substring(0, lastDash + 1);
 | |
| 
 | |
| 				ParseFile(fileLines, path);
 | |
| 			}
 | |
| 			catch (Exception e)
 | |
| 			{
 | |
| 				throw e;
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| #if SUPPORT_SSL
 | |
| 		private bool MyRemoteCertificateValidationCallback(System.Object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
 | |
| 		{
 | |
| 			bool isOk = true;
 | |
| 			// If there are errors in the certificate chain,
 | |
| 			// look at each error to determine the cause.
 | |
| 			if (sslPolicyErrors != SslPolicyErrors.None)
 | |
| 			{
 | |
| 				for (int i = 0; i < chain.ChainStatus.Length; i++)
 | |
| 				{
 | |
| 					if (chain.ChainStatus[i].Status == X509ChainStatusFlags.RevocationStatusUnknown)
 | |
| 					{
 | |
| 						continue;
 | |
| 					}
 | |
| 					chain.ChainPolicy.RevocationFlag = X509RevocationFlag.EntireChain;
 | |
| 					chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
 | |
| 					chain.ChainPolicy.UrlRetrievalTimeout = new TimeSpan(0, 1, 0);
 | |
| 					// Note: change flags to X509VerificationFlags.AllFlags to skip all security checks
 | |
| 					chain.ChainPolicy.VerificationFlags = X509VerificationFlags.NoFlag;
 | |
| 					bool chainIsValid = chain.Build((X509Certificate2)certificate);
 | |
| 					if (!chainIsValid)
 | |
| 					{
 | |
| 						isOk = false;
 | |
| 						break;
 | |
| 					}
 | |
| 				}
 | |
| 			}
 | |
| 			return isOk;
 | |
| 		}
 | |
| #endif
 | |
| 	}
 | |
| }
 | |
| 
 |