diff options
Diffstat (limited to 'MediaBrowser.Server.Implementations/LiveTv')
20 files changed, 1473 insertions, 105 deletions
diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs index 6acb0783eb..8fa34109d2 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs @@ -33,7 +33,7 @@ using Microsoft.Win32; namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV { - public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IHasRegistrationInfo, IDisposable + public class EmbyTV : ILiveTvService, ISupportsNewTimerIds, IDisposable { private readonly IApplicationHost _appHpst; private readonly ILogger _logger; @@ -46,7 +46,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private readonly LiveTvManager _liveTvManager; private readonly IFileSystem _fileSystem; - private readonly ISecurityManager _security; private readonly ILibraryMonitor _libraryMonitor; private readonly ILibraryManager _libraryManager; @@ -62,7 +61,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private readonly ConcurrentDictionary<string, ActiveRecordingInfo> _activeRecordings = new ConcurrentDictionary<string, ActiveRecordingInfo>(StringComparer.OrdinalIgnoreCase); - public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ISecurityManager security, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement) + public EmbyTV(IApplicationHost appHost, ILogger logger, IJsonSerializer jsonSerializer, IHttpClient httpClient, IServerConfigurationManager config, ILiveTvManager liveTvManager, IFileSystem fileSystem, ILibraryManager libraryManager, ILibraryMonitor libraryMonitor, IProviderManager providerManager, IFileOrganizationService organizationService, IMediaEncoder mediaEncoder, IPowerManagement powerManagement) { Current = this; @@ -71,7 +70,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _httpClient = httpClient; _config = config; _fileSystem = fileSystem; - _security = security; _libraryManager = libraryManager; _libraryMonitor = libraryMonitor; _providerManager = providerManager; @@ -851,29 +849,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV var recordPath = RecordingPath; var config = GetConfiguration(); - if (info.IsMovie) - { - var customRecordingPath = config.MovieRecordingPath; - var allowSubfolder = true; - if (!string.IsNullOrWhiteSpace(customRecordingPath)) - { - allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase); - recordPath = customRecordingPath; - } - - if (allowSubfolder && config.EnableRecordingSubfolders) - { - recordPath = Path.Combine(recordPath, "Movies"); - } - - var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); - if (info.ProductionYear.HasValue) - { - folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; - } - recordPath = Path.Combine(recordPath, folderName); - } - else if (info.IsSeries) + if (info.IsSeries) { var customRecordingPath = config.SeriesRecordingPath; var allowSubfolder = true; @@ -910,6 +886,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = Path.Combine(recordPath, folderName); } } + else if (info.IsMovie) + { + var customRecordingPath = config.MovieRecordingPath; + var allowSubfolder = true; + if (!string.IsNullOrWhiteSpace(customRecordingPath)) + { + allowSubfolder = string.Equals(customRecordingPath, recordPath, StringComparison.OrdinalIgnoreCase); + recordPath = customRecordingPath; + } + + if (allowSubfolder && config.EnableRecordingSubfolders) + { + recordPath = Path.Combine(recordPath, "Movies"); + } + + var folderName = _fileSystem.GetValidFilename(info.Name).Trim(); + if (info.ProductionYear.HasValue) + { + folderName += " (" + info.ProductionYear.Value.ToString(CultureInfo.InvariantCulture) + ")"; + } + recordPath = Path.Combine(recordPath, folderName); + } else if (info.IsKids) { if (config.EnableRecordingSubfolders) @@ -995,6 +993,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV recordPath = recorder.GetOutputPath(mediaStreamInfo, recordPath); recordPath = EnsureFileUnique(recordPath, timer.Id); + _libraryManager.RegisterIgnoredPath(recordPath); _libraryMonitor.ReportFileSystemChangeBeginning(recordPath); _fileSystem.CreateDirectory(Path.GetDirectoryName(recordPath)); activeRecordingInfo.Path = recordPath; @@ -1046,6 +1045,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV semaphore.Release(); } + _libraryManager.UnRegisterIgnoredPath(recordPath); _libraryMonitor.ReportFileSystemChangeComplete(recordPath, true); ActiveRecordingInfo removed; @@ -1114,7 +1114,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV if (config.EnableRecordingEncoding) { - var regInfo = await _security.GetRegistrationStatus("embytvrecordingconversion").ConfigureAwait(false); + var regInfo = await _liveTvManager.GetRegistrationInfo("embytvrecordingconversion").ConfigureAwait(false); if (regInfo.IsValid) { @@ -1171,8 +1171,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private async Task UpdateTimersForSeriesTimer(List<ProgramInfo> epgData, SeriesTimerInfo seriesTimer, bool deleteInvalidTimers) { var newTimers = GetTimersForSeries(seriesTimer, epgData, true).ToList(); - - var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); + + var registration = await _liveTvManager.GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); if (registration.IsValid) { @@ -1349,20 +1349,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - public Task<MBRegistrationRecord> GetRegistrationInfo(string feature) - { - if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase)) - { - return _security.GetRegistrationStatus("embytvseriesrecordings"); - } - - return Task.FromResult(new MBRegistrationRecord - { - IsValid = true, - IsRegistered = true - }); - } - public List<VirtualFolderInfo> GetRecordingFolders() { var list = new List<VirtualFolderInfo>(); diff --git a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs index fc3a507d17..75ad3de59e 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs @@ -46,18 +46,41 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV _httpClient = httpClient; } + private string OutputFormat + { + get + { + var format = _liveTvOptions.RecordingEncodingFormat; + + if (string.Equals(format, "mkv", StringComparison.OrdinalIgnoreCase)) + { + return "mkv"; + } + + return "mp4"; + } + } + public string GetOutputPath(MediaSourceInfo mediaSource, string targetFile) { - return Path.ChangeExtension(targetFile, ".mp4"); + return Path.ChangeExtension(targetFile, "." + OutputFormat); } public async Task Record(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { + if (mediaSource.Path.IndexOf("m3u8", StringComparison.OrdinalIgnoreCase) != -1) + { + await RecordWithoutTempFile(mediaSource, targetFile, duration, onStarted, cancellationToken) + .ConfigureAwait(false); + + return; + } + var tempfile = Path.Combine(_appPaths.TranscodingTempPath, Guid.NewGuid().ToString("N") + ".ts"); try { - await RecordInternal(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) + await RecordWithTempFile(mediaSource, tempfile, targetFile, duration, onStarted, cancellationToken) .ConfigureAwait(false); } finally @@ -73,7 +96,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV } } - public async Task RecordInternal(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + private async Task RecordWithoutTempFile(MediaSourceInfo mediaSource, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) + { + var durationToken = new CancellationTokenSource(duration); + cancellationToken = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token).Token; + + await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationToken).ConfigureAwait(false); + + _logger.Info("Recording completed to file {0}", targetFile); + } + + private async Task RecordWithTempFile(MediaSourceInfo mediaSource, string tempFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken) { var httpRequestOptions = new HttpRequestOptions() { @@ -215,15 +248,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.EmbyTV private string GetAudioArgs(MediaSourceInfo mediaSource) { - // do not copy aac because many players have difficulty with aac_latm - var copyAudio = new[] { "mp3" }; var mediaStreams = mediaSource.MediaStreams ?? new List<MediaStream>(); var inputAudioCodec = mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Select(i => i.Codec).FirstOrDefault() ?? string.Empty; - if (copyAudio.Contains(inputAudioCodec, StringComparer.OrdinalIgnoreCase)) - { - return "-codec:a:0 copy"; - } + // do not copy aac because many players have difficulty with aac_latm if (_liveTvOptions.EnableOriginalAudioWithEncodedRecordings && !string.Equals(inputAudioCodec, "aac", StringComparison.OrdinalIgnoreCase)) { return "-codec:a:0 copy"; diff --git a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs index e37109c148..1a5ebedc2a 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs @@ -194,14 +194,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings return station; } - if (string.IsNullOrWhiteSpace(channelName)) + if (!string.IsNullOrWhiteSpace(channelName)) { - return null; - } + channelName = NormalizeName(channelName); + + var result = channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase)); - channelName = NormalizeName(channelName); + if (result != null) + { + return result; + } + } - return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.callsign ?? string.Empty), channelName, StringComparison.OrdinalIgnoreCase)); + if (!string.IsNullOrWhiteSpace(channelNumber)) + { + return channelPair.Values.FirstOrDefault(i => string.Equals(NormalizeName(i.stationID ?? string.Empty), channelNumber, StringComparison.OrdinalIgnoreCase)); + } } return null; @@ -348,7 +356,11 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings if (programInfo.audioProperties != null) { - if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) + if (programInfo.audioProperties.Exists(item => string.Equals(item, "atmos", StringComparison.OrdinalIgnoreCase))) + { + audioType = ProgramAudio.Atmos; + } + else if (programInfo.audioProperties.Exists(item => string.Equals(item, "dd 5.1", StringComparison.OrdinalIgnoreCase))) { audioType = ProgramAudio.DolbyDigital; } @@ -405,6 +417,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings if (programInfo.videoProperties != null) { info.IsHD = programInfo.videoProperties.Contains("hdtv", StringComparer.OrdinalIgnoreCase); + info.Is3D = programInfo.videoProperties.Contains("3d", StringComparer.OrdinalIgnoreCase); } if (details.contentRating != null && details.contentRating.Count > 0) @@ -785,9 +798,10 @@ namespace MediaBrowser.Server.Implementations.LiveTv.Listings get { return "Schedules Direct"; } } + public static string TypeName = "SchedulesDirect"; public string Type { - get { return "SchedulesDirect"; } + get { return TypeName; } } private async Task<bool> HasLineup(ListingsProviderInfo info, CancellationToken cancellationToken) diff --git a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs index 88017aa59d..b3ced55a51 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/LiveTvManager.cs @@ -31,7 +31,11 @@ using CommonIO; using IniParser; using IniParser.Model; using MediaBrowser.Common.Events; +using MediaBrowser.Common.Security; +using MediaBrowser.Controller.Entities.Movies; +using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Model.Events; +using MediaBrowser.Server.Implementations.LiveTv.Listings; namespace MediaBrowser.Server.Implementations.LiveTv { @@ -49,6 +53,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv private readonly ITaskManager _taskManager; private readonly IJsonSerializer _jsonSerializer; private readonly IProviderManager _providerManager; + private readonly ISecurityManager _security; private readonly IDtoService _dtoService; private readonly ILocalizationManager _localization; @@ -71,7 +76,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv public event EventHandler<GenericEventArgs<TimerEventInfo>> TimerCreated; public event EventHandler<GenericEventArgs<TimerEventInfo>> SeriesTimerCreated; - public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem) + public LiveTvManager(IApplicationHost appHost, IServerConfigurationManager config, ILogger logger, IItemRepository itemRepo, IImageProcessor imageProcessor, IUserDataManager userDataManager, IDtoService dtoService, IUserManager userManager, ILibraryManager libraryManager, ITaskManager taskManager, ILocalizationManager localization, IJsonSerializer jsonSerializer, IProviderManager providerManager, IFileSystem fileSystem, ISecurityManager security) { _config = config; _logger = logger; @@ -83,6 +88,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv _jsonSerializer = jsonSerializer; _providerManager = providerManager; _fileSystem = fileSystem; + _security = security; _dtoService = dtoService; _userDataManager = userDataManager; @@ -1423,6 +1429,49 @@ namespace MediaBrowser.Server.Implementations.LiveTv return new QueryResult<BaseItem>(); } + var includeItemTypes = new List<string>(); + var excludeItemTypes = new List<string>(); + var genres = new List<string>(); + + if (query.IsMovie.HasValue) + { + if (query.IsMovie.Value) + { + includeItemTypes.Add(typeof(Movie).Name); + } + else + { + excludeItemTypes.Add(typeof(Movie).Name); + } + } + if (query.IsSeries.HasValue) + { + if (query.IsSeries.Value) + { + includeItemTypes.Add(typeof(Episode).Name); + } + else + { + excludeItemTypes.Add(typeof(Episode).Name); + } + } + if (query.IsSports.HasValue) + { + if (query.IsSports.Value) + { + genres.Add("Sports"); + } + } + if (query.IsKids.HasValue) + { + if (query.IsKids.Value) + { + genres.Add("Kids"); + genres.Add("Children"); + genres.Add("Family"); + } + } + return _libraryManager.GetItemsResult(new InternalItemsQuery(user) { MediaTypes = new[] { MediaType.Video }, @@ -1430,13 +1479,75 @@ namespace MediaBrowser.Server.Implementations.LiveTv AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(), IsFolder = false, ExcludeLocationTypes = new[] { LocationType.Virtual }, - Limit = Math.Min(200, query.Limit ?? int.MaxValue), + Limit = query.Limit, SortBy = new[] { ItemSortBy.DateCreated }, SortOrder = SortOrder.Descending, - EnableTotalRecordCount = query.EnableTotalRecordCount + EnableTotalRecordCount = query.EnableTotalRecordCount, + IncludeItemTypes = includeItemTypes.ToArray(), + ExcludeItemTypes = excludeItemTypes.ToArray(), + Genres = genres.ToArray() }); } + public async Task<QueryResult<BaseItemDto>> GetRecordingSeries(RecordingQuery query, DtoOptions options, CancellationToken cancellationToken) + { + var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); + if (user != null && !IsLiveTvEnabled(user)) + { + return new QueryResult<BaseItemDto>(); + } + + if (_services.Count > 1) + { + return new QueryResult<BaseItemDto>(); + } + + if (user == null || (query.IsInProgress ?? false)) + { + return new QueryResult<BaseItemDto>(); + } + + var folders = EmbyTV.EmbyTV.Current.GetRecordingFolders() + .SelectMany(i => i.Locations) + .Distinct(StringComparer.OrdinalIgnoreCase) + .Select(i => _libraryManager.FindByPath(i, true)) + .Where(i => i != null) + .Where(i => i.IsVisibleStandalone(user)) + .ToList(); + + if (folders.Count == 0) + { + return new QueryResult<BaseItemDto>(); + } + + var includeItemTypes = new List<string>(); + var excludeItemTypes = new List<string>(); + + includeItemTypes.Add(typeof(Series).Name); + + var internalResult = _libraryManager.GetItemsResult(new InternalItemsQuery(user) + { + Recursive = true, + AncestorIds = folders.Select(i => i.Id.ToString("N")).ToArray(), + Limit = query.Limit, + SortBy = new[] { ItemSortBy.DateCreated }, + SortOrder = SortOrder.Descending, + EnableTotalRecordCount = query.EnableTotalRecordCount, + IncludeItemTypes = includeItemTypes.ToArray(), + ExcludeItemTypes = excludeItemTypes.ToArray() + }); + + RemoveFields(options); + + var returnArray = (await _dtoService.GetBaseItemDtos(internalResult.Items, options, user).ConfigureAwait(false)).ToArray(); + + return new QueryResult<BaseItemDto> + { + Items = returnArray, + TotalRecordCount = internalResult.TotalRecordCount + }; + } + public async Task<QueryResult<BaseItem>> GetInternalRecordings(RecordingQuery query, CancellationToken cancellationToken) { var user = string.IsNullOrEmpty(query.UserId) ? null : _userManager.GetUserById(query.UserId); @@ -1492,6 +1603,30 @@ namespace MediaBrowser.Server.Implementations.LiveTv recordings = recordings.Where(i => i.Status == val); } + if (query.IsMovie.HasValue) + { + var val = query.IsMovie.Value; + recordings = recordings.Where(i => i.IsMovie == val); + } + + if (query.IsSeries.HasValue) + { + var val = query.IsSeries.Value; + recordings = recordings.Where(i => i.IsSeries == val); + } + + if (query.IsKids.HasValue) + { + var val = query.IsKids.Value; + recordings = recordings.Where(i => i.IsKids == val); + } + + if (query.IsSports.HasValue) + { + var val = query.IsSports.Value; + recordings = recordings.Where(i => i.IsSports == val); + } + if (!string.IsNullOrEmpty(query.SeriesTimerId)) { var guid = new Guid(query.SeriesTimerId); @@ -1950,16 +2085,16 @@ namespace MediaBrowser.Server.Implementations.LiveTv dto.Number = channel.Number; dto.ChannelNumber = channel.Number; dto.ChannelType = channel.ChannelType; - dto.ServiceName = GetService(channel).Name; + dto.ServiceName = channel.ServiceName; if (options.Fields.Contains(ItemFields.MediaSources)) { dto.MediaSources = channel.GetMediaSources(true).ToList(); } - var channelIdString = channel.Id.ToString("N"); if (options.AddCurrentProgram) { + var channelIdString = channel.Id.ToString("N"); var currentProgram = programs.FirstOrDefault(i => string.Equals(i.ChannelId, channelIdString)); if (currentProgram != null) @@ -2091,6 +2226,14 @@ namespace MediaBrowser.Server.Implementations.LiveTv public async Task CreateSeriesTimer(SeriesTimerInfoDto timer, CancellationToken cancellationToken) { + var registration = await GetRegistrationInfo("seriesrecordings").ConfigureAwait(false); + + if (!registration.IsValid) + { + _logger.Info("Creating series recordings requires an active Emby Premiere subscription."); + return; + } + var service = GetService(timer.ServiceName); var info = await _tvDtoService.GetSeriesTimerInfo(timer, true, this, cancellationToken).ConfigureAwait(false); @@ -2653,33 +2796,28 @@ namespace MediaBrowser.Server.Implementations.LiveTv } } - public Task<MBRegistrationRecord> GetRegistrationInfo(string channelId, string programId, string feature) + public Task<MBRegistrationRecord> GetRegistrationInfo(string feature) { - ILiveTvService service; - - if (string.IsNullOrWhiteSpace(programId)) - { - var channel = GetInternalChannel(channelId); - service = GetService(channel); - } - else + if (string.Equals(feature, "seriesrecordings", StringComparison.OrdinalIgnoreCase)) { - var program = GetInternalProgram(programId); - service = GetService(program); + feature = "embytvseriesrecordings"; } - var hasRegistration = service as IHasRegistrationInfo; - - if (hasRegistration != null) + if (string.Equals(feature, "dvr", StringComparison.OrdinalIgnoreCase)) { - return hasRegistration.GetRegistrationInfo(feature); + var config = GetConfiguration(); + if (config.TunerHosts.Count(i => i.IsEnabled) > 0 && + config.ListingProviders.Count(i => (i.EnableAllTuners || i.EnabledTuners.Length > 0) && string.Equals(i.Type, SchedulesDirect.TypeName, StringComparison.OrdinalIgnoreCase)) > 0) + { + return Task.FromResult(new MBRegistrationRecord + { + IsRegistered = true, + IsValid = true + }); + } } - return Task.FromResult(new MBRegistrationRecord - { - IsValid = true, - IsRegistered = true - }); + return _security.GetRegistrationStatus(feature); } public List<NameValuePair> GetSatIniMappings() diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs index ffe95c862a..8095a6989b 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/M3uParser.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; @@ -70,7 +71,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } else if (!string.IsNullOrWhiteSpace(extInf) && !line.StartsWith("#", StringComparison.OrdinalIgnoreCase)) { - var channel = GetChannelnfo(extInf, tunerHostId); + var channel = GetChannelnfo(extInf, tunerHostId, line); channel.Id = channelIdPrefix + urlHash + line.GetMD5().ToString("N"); channel.Path = line; channels.Add(channel); @@ -79,7 +80,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts } return channels; } - private M3UChannel GetChannelnfo(string extInf, string tunerHostId) + private M3UChannel GetChannelnfo(string extInf, string tunerHostId, string mediaUrl) { var titleIndex = extInf.LastIndexOf(','); var channel = new M3UChannel(); @@ -87,8 +88,6 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts channel.Number = extInf.Trim().Split(' ')[0] ?? "0"; channel.Name = extInf.Substring(titleIndex + 1); - - if(channel.Number == "-1") { channel.Number = "0"; } //Check for channel number with the format from SatIp int number; @@ -101,6 +100,17 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts channel.Name = channel.Name.Substring(numberIndex + 1); } } + + if (string.Equals(channel.Number, "-1", StringComparison.OrdinalIgnoreCase) && !string.IsNullOrWhiteSpace(mediaUrl)) + { + channel.Number = Path.GetFileNameWithoutExtension(mediaUrl.Split('/').Last()); + } + + if (string.Equals(channel.Number, "-1", StringComparison.OrdinalIgnoreCase)) + { + channel.Number = "0"; + } + channel.ImageUrl = FindProperty("tvg-logo", extInf, null); channel.Number = FindProperty("tvg-id", extInf, channel.Number); channel.Number = FindProperty("channel-id", extInf, channel.Number); diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs new file mode 100644 index 0000000000..dddd771790 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/ReportBlock.cs @@ -0,0 +1,79 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class ReportBlock + { + /// <summary> + /// Get the length of the block. + /// </summary> + public int BlockLength { get { return (24); } } + /// <summary> + /// Get the synchronization source. + /// </summary> + public string SynchronizationSource { get; private set; } + /// <summary> + /// Get the fraction lost. + /// </summary> + public int FractionLost { get; private set; } + /// <summary> + /// Get the cumulative packets lost. + /// </summary> + public int CumulativePacketsLost { get; private set; } + /// <summary> + /// Get the highest number received. + /// </summary> + public int HighestNumberReceived { get; private set; } + /// <summary> + /// Get the inter arrival jitter. + /// </summary> + public int InterArrivalJitter { get; private set; } + /// <summary> + /// Get the timestamp of the last report. + /// </summary> + public int LastReportTimeStamp { get; private set; } + /// <summary> + /// Get the delay since the last report. + /// </summary> + public int DelaySinceLastReport { get; private set; } + + /// <summary> + /// Initialize a new instance of the ReportBlock class. + /// </summary> + public ReportBlock() { } + + /// <summary> + /// Unpack the data in a packet. + /// </summary> + /// <param name="buffer">The buffer containing the packet.</param> + /// <param name="offset">The offset to the first byte of the packet within the buffer.</param> + /// <returns>An ErrorSpec instance if an error occurs; null otherwise.</returns> + public void Process(byte[] buffer, int offset) + { + SynchronizationSource = Utils.ConvertBytesToString(buffer, offset, 4); + FractionLost = buffer[offset + 4]; + CumulativePacketsLost = Utils.Convert3BytesToInt(buffer, offset + 5); + HighestNumberReceived = Utils.Convert4BytesToInt(buffer, offset + 8); + InterArrivalJitter = Utils.Convert4BytesToInt(buffer, offset + 12); + LastReportTimeStamp = Utils.Convert4BytesToInt(buffer, offset + 16); + DelaySinceLastReport = Utils.Convert4BytesToInt(buffer, offset + 20); + + + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs new file mode 100644 index 0000000000..990b6dd949 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpAppPacket.cs @@ -0,0 +1,68 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + class RtcpAppPacket : RtcpPacket + { + /// <summary> + /// Get the synchronization source. + /// </summary> + public int SynchronizationSource { get; private set; } + /// <summary> + /// Get the name. + /// </summary> + public string Name { get; private set; } + /// <summary> + /// Get the identity. + /// </summary> + public int Identity { get; private set; } + /// <summary> + /// Get the variable data portion. + /// </summary> + public string Data { get; private set; } + + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + SynchronizationSource = Utils.Convert4BytesToInt(buffer, offset + 4); + Name = Utils.ConvertBytesToString(buffer, offset + 8, 4); + Identity = Utils.Convert2BytesToInt(buffer, offset + 12); + + int dataLength = Utils.Convert2BytesToInt(buffer, offset + 14); + if (dataLength != 0) + Data = Utils.ConvertBytesToString(buffer, offset + 16, dataLength); + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Application Specific.\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource); + sb.AppendFormat("Name : {0} .\n", Name); + sb.AppendFormat("Identity : {0} .\n", Identity); + sb.AppendFormat("Data : {0} .\n", Data); + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs new file mode 100644 index 0000000000..c79ea31a89 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpByePacket.cs @@ -0,0 +1,59 @@ +using System.Collections.ObjectModel; +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class RtcpByePacket :RtcpPacket + { + public Collection<string> SynchronizationSources { get; private set; } + public string ReasonForLeaving { get; private set; } + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + SynchronizationSources = new Collection<string>(); + int index = 4; + + while (SynchronizationSources.Count < ReportCount) + { + SynchronizationSources.Add(Utils.ConvertBytesToString(buffer, offset + index, 4)); + index += 4; + } + + if (index < Length) + { + int reasonLength = buffer[offset + index]; + ReasonForLeaving = Utils.ConvertBytesToString(buffer, offset + index + 1, reasonLength); + } + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("ByeBye .\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("SynchronizationSources : {0} .\n", SynchronizationSources); + sb.AppendFormat("ReasonForLeaving : {0} .\n", ReasonForLeaving); + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs new file mode 100644 index 0000000000..2c54f06654 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpListener.cs @@ -0,0 +1,203 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class RtcpListener + { + private readonly ILogger _logger; + private Thread _rtcpListenerThread; + private AutoResetEvent _rtcpListenerThreadStopEvent = null; + private UdpClient _udpClient; + private IPEndPoint _multicastEndPoint; + private IPEndPoint _serverEndPoint; + private TransmissionMode _transmissionMode; + + public RtcpListener(String address, int port, TransmissionMode mode,ILogger logger) + { + _logger = logger; + _transmissionMode = mode; + switch (mode) + { + case TransmissionMode.Unicast: + _udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(address), port)); + _serverEndPoint = new IPEndPoint(IPAddress.Any, 0); + break; + case TransmissionMode.Multicast: + _multicastEndPoint = new IPEndPoint(IPAddress.Parse(address), port); + _serverEndPoint = new IPEndPoint(IPAddress.Any, 0); + _udpClient = new UdpClient(); + _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); + _udpClient.ExclusiveAddressUse = false; + _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, port)); + _udpClient.JoinMulticastGroup(_multicastEndPoint.Address); + break; + } + //StartRtcpListenerThread(); + } + + public void StartRtcpListenerThread() + { + // Kill the existing thread if it is in "zombie" state. + if (_rtcpListenerThread != null && !_rtcpListenerThread.IsAlive) + { + StopRtcpListenerThread(); + } + + if (_rtcpListenerThread == null) + { + _logger.Info("SAT>IP : starting new RTCP listener thread"); + _rtcpListenerThreadStopEvent = new AutoResetEvent(false); + _rtcpListenerThread = new Thread(new ThreadStart(RtcpListenerThread)); + _rtcpListenerThread.Name = string.Format("SAT>IP tuner RTCP listener"); + _rtcpListenerThread.IsBackground = true; + _rtcpListenerThread.Priority = ThreadPriority.Lowest; + _rtcpListenerThread.Start(); + } + } + + public void StopRtcpListenerThread() + { + if (_rtcpListenerThread != null) + { + if (!_rtcpListenerThread.IsAlive) + { + _logger.Info("SAT>IP : aborting old RTCP listener thread"); + _rtcpListenerThread.Abort(); + } + else + { + _rtcpListenerThreadStopEvent.Set(); + if (!_rtcpListenerThread.Join(400 * 2)) + { + _logger.Info("SAT>IP : failed to join RTCP listener thread, aborting thread"); + _rtcpListenerThread.Abort(); + } + } + _rtcpListenerThread = null; + if (_rtcpListenerThreadStopEvent != null) + { + _rtcpListenerThreadStopEvent.Close(); + _rtcpListenerThreadStopEvent = null; + } + } + } + + private void RtcpListenerThread() + { + try + { + bool receivedGoodBye = false; + try + { + _udpClient.Client.ReceiveTimeout = 400; + IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Any, 0); + while (!receivedGoodBye && !_rtcpListenerThreadStopEvent.WaitOne(1)) + { + byte[] packets = _udpClient.Receive(ref serverEndPoint); + if (packets == null) + { + continue; + } + + int offset = 0; + while (offset < packets.Length) + { + switch (packets[offset + 1]) + { + case 200: //sr + var sr = new RtcpSenderReportPacket(); + sr.Parse(packets, offset); + offset += sr.Length; + break; + case 201: //rr + var rr = new RtcpReceiverReportPacket(); + rr.Parse(packets, offset); + offset += rr.Length; + break; + case 202: //sd + var sd = new RtcpSourceDescriptionPacket(); + sd.Parse(packets, offset); + offset += sd.Length; + break; + case 203: // bye + var bye = new RtcpByePacket(); + bye.Parse(packets, offset); + receivedGoodBye = true; + OnPacketReceived(new RtcpPacketReceivedArgs(bye)); + offset += bye.Length; + break; + case 204: // app + var app = new RtcpAppPacket(); + app.Parse(packets, offset); + OnPacketReceived(new RtcpPacketReceivedArgs(app)); + offset += app.Length; + break; + } + } + + } + } + finally + { + switch (_transmissionMode) + { + case TransmissionMode.Multicast: + _udpClient.DropMulticastGroup(_multicastEndPoint.Address); + _udpClient.Close(); + break; + case TransmissionMode.Unicast: + _udpClient.Close(); + break; + } + } + } + catch (ThreadAbortException) + { + } + catch (Exception ex) + { + _logger.Info(string.Format("SAT>IP : RTCP listener thread exception"), ex); + return; + } + _logger.Info("SAT>IP : RTCP listener thread stopping"); + } + public delegate void PacketReceivedHandler(object sender, RtcpPacketReceivedArgs e); + public event PacketReceivedHandler PacketReceived; + public class RtcpPacketReceivedArgs : EventArgs + { + public Object Packet { get; private set; } + + public RtcpPacketReceivedArgs(Object packet) + { + Packet = packet; + } + } + protected void OnPacketReceived(RtcpPacketReceivedArgs args) + { + if (PacketReceived != null) + { + PacketReceived(this, args); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs new file mode 100644 index 0000000000..0a949eb7ed --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpPacket.cs @@ -0,0 +1,37 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public abstract class RtcpPacket + { + public int Version { get; private set; } + public bool Padding { get; private set; } + public int ReportCount { get; private set; } + public int Type { get; private set; } + public int Length { get; private set; } + + public virtual void Parse(byte[] buffer, int offset) + { + Version = buffer[offset] >> 6; + Padding = (buffer[offset] & 0x20) != 0; + ReportCount = buffer[offset] & 0x1f; + Type = buffer[offset + 1]; + Length = (Utils.Convert2BytesToInt(buffer, offset + 2) * 4) + 4; + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs new file mode 100644 index 0000000000..abb8636522 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpReceiverReportPacket.cs @@ -0,0 +1,68 @@ +using System.Collections.ObjectModel; +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class RtcpReceiverReportPacket :RtcpPacket + { + public string SynchronizationSource { get; private set; } + public Collection<ReportBlock> ReportBlocks { get; private set; } + public byte[] ProfileExtension { get; private set; } + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + SynchronizationSource = Utils.ConvertBytesToString(buffer, offset + 4, 4); + + ReportBlocks = new Collection<ReportBlock>(); + int index = 8; + + while (ReportBlocks.Count < ReportCount) + { + ReportBlock reportBlock = new ReportBlock(); + reportBlock.Process(buffer, offset + index); + ReportBlocks.Add(reportBlock); + index += reportBlock.BlockLength; + } + + if (index < Length) + { + ProfileExtension = new byte[Length - index]; + + for (int extensionIndex = 0; index < Length; index++) + { + ProfileExtension[extensionIndex] = buffer[offset + index]; + extensionIndex++; + } + } + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Receiver Report.\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource); + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs new file mode 100644 index 0000000000..dda5d6a033 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSenderReportPacket.cs @@ -0,0 +1,105 @@ +using System.Collections.ObjectModel; +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + public class RtcpSenderReportPacket : RtcpPacket + { + #region Properties + /// <summary> + /// Get the synchronization source. + /// </summary> + public int SynchronizationSource { get; private set; } + /// <summary> + /// Get the NPT timestamp. + /// </summary> + public long NPTTimeStamp { get; private set; } + /// <summary> + /// Get the RTP timestamp. + /// </summary> + public int RTPTimeStamp { get; private set; } + /// <summary> + /// Get the packet count. + /// </summary> + public int SenderPacketCount { get; private set; } + /// <summary> + /// Get the octet count. + /// </summary> + public int SenderOctetCount { get; private set; } + /// <summary> + /// Get the list of report blocks. + /// </summary> + public Collection<ReportBlock> ReportBlocks { get; private set; } + /// <summary> + /// Get the profile extension data. + /// </summary> + public byte[] ProfileExtension { get; private set; } + #endregion + + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + SynchronizationSource = Utils.Convert4BytesToInt(buffer, offset + 4); + NPTTimeStamp = Utils.Convert8BytesToLong(buffer, offset + 8); + RTPTimeStamp = Utils.Convert4BytesToInt(buffer, offset + 16); + SenderPacketCount = Utils.Convert4BytesToInt(buffer, offset + 20); + SenderOctetCount = Utils.Convert4BytesToInt(buffer, offset + 24); + + ReportBlocks = new Collection<ReportBlock>(); + int index = 28; + + while (ReportBlocks.Count < ReportCount) + { + ReportBlock reportBlock = new ReportBlock(); + reportBlock.Process(buffer, offset + index); + ReportBlocks.Add(reportBlock); + index += reportBlock.BlockLength; + } + + if (index < Length) + { + ProfileExtension = new byte[Length - index]; + + for (int extensionIndex = 0; index < Length; index++) + { + ProfileExtension[extensionIndex] = buffer[offset + index]; + extensionIndex++; + } + } + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Sender Report.\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("SynchronizationSource : {0} .\n", SynchronizationSource); + sb.AppendFormat("NTP Timestamp : {0} .\n", Utils.NptTimestampToDateTime(NPTTimeStamp)); + sb.AppendFormat("RTP Timestamp : {0} .\n", RTPTimeStamp); + sb.AppendFormat("Sender PacketCount : {0} .\n", SenderPacketCount); + sb.AppendFormat("Sender Octet Count : {0} .\n", SenderOctetCount); + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs new file mode 100644 index 0000000000..0a95a44133 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/RtcpSourceDescriptionPacket.cs @@ -0,0 +1,57 @@ +using System.Collections.ObjectModel; +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + class RtcpSourceDescriptionPacket :RtcpPacket + { /// <summary> + /// Get the list of source descriptions. + /// </summary> + public Collection<SourceDescriptionBlock> Descriptions; + public override void Parse(byte[] buffer, int offset) + { + base.Parse(buffer, offset); + Descriptions = new Collection<SourceDescriptionBlock>(); + + int index = 4; + + while (Descriptions.Count < ReportCount) + { + SourceDescriptionBlock descriptionBlock = new SourceDescriptionBlock(); + descriptionBlock.Process(buffer, offset + index); + Descriptions.Add(descriptionBlock); + index += descriptionBlock.BlockLength; + } + } + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("Source Description.\n"); + sb.AppendFormat("Version : {0} .\n", Version); + sb.AppendFormat("Padding : {0} .\n", Padding); + sb.AppendFormat("Report Count : {0} .\n", ReportCount); + sb.AppendFormat("PacketType: {0} .\n", Type); + sb.AppendFormat("Length : {0} .\n", Length); + sb.AppendFormat("Descriptions : {0} .\n", Descriptions); + + sb.AppendFormat(".\n"); + return sb.ToString(); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs new file mode 100644 index 0000000000..bf56087cd8 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionBlock.cs @@ -0,0 +1,65 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System.Collections.ObjectModel; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + class SourceDescriptionBlock + { + /// <summary> + /// Get the length of the block. + /// </summary> + public int BlockLength { get { return (blockLength + (blockLength % 4)); } } + + /// <summary> + /// Get the synchronization source. + /// </summary> + public string SynchronizationSource { get; private set; } + /// <summary> + /// Get the list of source descriptioni items. + /// </summary> + public Collection<SourceDescriptionItem> Items; + + private int blockLength; + + public void Process(byte[] buffer, int offset) + { + SynchronizationSource = Utils.ConvertBytesToString(buffer, offset, 4); + Items = new Collection<SourceDescriptionItem>(); + int index = 4; + bool done = false; + do + { + SourceDescriptionItem item = new SourceDescriptionItem(); + item.Process(buffer, offset + index); + + if (item.Type != 0) + { + Items.Add(item); + index += item.ItemLength; + blockLength += item.ItemLength; + } + else + { + blockLength++; + done = true; + } + } + while (!done); + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs new file mode 100644 index 0000000000..5dd0336421 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtcp/SourceDescriptionItem.cs @@ -0,0 +1,60 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtcp +{ + /// <summary> + /// The class that describes a source description item. + /// </summary> + public class SourceDescriptionItem + { + /// <summary> + /// Get the type. + /// </summary> + public int Type { get; private set; } + /// <summary> + /// Get the text. + /// </summary> + public string Text { get; private set; } + + /// <summary> + /// Get the length of the item. + /// </summary> + public int ItemLength { get { return (Text.Length + 2); } } + + /// <summary> + /// Initialize a new instance of the SourceDescriptionItem class. + /// </summary> + public SourceDescriptionItem() { } + + /// <summary> + /// Unpack the data in a packet. + /// </summary> + /// <param name="buffer">The buffer containing the packet.</param> + /// <param name="offset">The offset to the first byte of the packet within the buffer.</param> + /// <returns>An ErrorSpec instance if an error occurs; null otherwise.</returns> + public void Process(byte[] buffer, int offset) + { + Type = buffer[offset]; + if (Type != 0) + { + int length = buffer[offset + 1]; + Text = Utils.ConvertBytesToString(buffer, offset + 2, length); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs new file mode 100644 index 0000000000..ea6a9ba6aa --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpListener.cs @@ -0,0 +1,160 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading; +using MediaBrowser.Model.Logging; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtp +{ + public class RtpListener + { + private readonly ILogger _logger; + private AutoResetEvent _rtpListenerThreadStopEvent; + private Thread _rtpListenerThread; + private UdpClient _udpClient; + private IPEndPoint _multicastEndPoint; + private IPEndPoint _serverEndPoint; + private TransmissionMode _transmissionMode; + public RtpListener(String address, int port,TransmissionMode mode,ILogger logger) + { + _logger = logger; + _transmissionMode = mode; + switch (mode) + { + case TransmissionMode.Unicast: + _udpClient = new UdpClient(new IPEndPoint(IPAddress.Parse(address), port)); + _serverEndPoint = new IPEndPoint(IPAddress.Any, 0); + break; + case TransmissionMode.Multicast: + _multicastEndPoint = new IPEndPoint(IPAddress.Parse(address), port); + _serverEndPoint = null; + _udpClient = new UdpClient(); + _udpClient.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1); + _udpClient.ExclusiveAddressUse = false; + _udpClient.Client.Bind(new IPEndPoint(IPAddress.Any, _multicastEndPoint.Port)); + _udpClient.JoinMulticastGroup(_multicastEndPoint.Address); + break; + } + //StartRtpListenerThread(); + } + public void StartRtpListenerThread() + { + // Kill the existing thread if it is in "zombie" state. + if (_rtpListenerThread != null && !_rtpListenerThread.IsAlive) + { + StopRtpListenerThread(); + } + + if (_rtpListenerThread == null) + { + _logger.Info("SAT>IP : starting new RTP listener thread"); + _rtpListenerThreadStopEvent = new AutoResetEvent(false); + _rtpListenerThread = new Thread(new ThreadStart(RtpListenerThread)); + _rtpListenerThread.Name = string.Format("SAT>IP tuner RTP listener"); + _rtpListenerThread.IsBackground = true; + _rtpListenerThread.Priority = ThreadPriority.Lowest; + _rtpListenerThread.Start(); + } + } + + public void StopRtpListenerThread() + { + if (_rtpListenerThread != null) + { + if (!_rtpListenerThread.IsAlive) + { + _logger.Info("SAT>IP : aborting old RTP listener thread"); + _rtpListenerThread.Abort(); + } + else + { + _rtpListenerThreadStopEvent.Set(); + if (!_rtpListenerThread.Join(400 * 2)) + { + _logger.Info("SAT>IP : failed to join RTP listener thread, aborting thread"); + _rtpListenerThread.Abort(); + } + } + _rtpListenerThread = null; + if (_rtpListenerThreadStopEvent != null) + { + _rtpListenerThreadStopEvent.Close(); + _rtpListenerThreadStopEvent = null; + } + } + } + + private void RtpListenerThread() + { + try + { + try + { + + while (!_rtpListenerThreadStopEvent.WaitOne(1)) + { + byte[] receivedbytes = _udpClient.Receive(ref _serverEndPoint); + RtpPacket packet = RtpPacket.Decode(receivedbytes); + OnPacketReceived(new RtpPacketReceivedArgs(packet)); + } + } + finally + { + switch (_transmissionMode) + { + case TransmissionMode.Multicast: + _udpClient.DropMulticastGroup(_multicastEndPoint.Address); + _udpClient.Close(); + break; + case TransmissionMode.Unicast: + _udpClient.Close(); + break; + } + } + } + catch (ThreadAbortException) + { + } + catch (Exception ex) + { + _logger.Info(string.Format("SAT>IP : RTP listener thread exception"), ex); + return; + } + _logger.Info("SAT>IP : RTP listener thread stopping"); + } + public delegate void PacketReceivedHandler(object sender, RtpPacketReceivedArgs e); + public event PacketReceivedHandler PacketReceived; + public class RtpPacketReceivedArgs : EventArgs + { + public RtpPacket Packet { get; private set; } + + public RtpPacketReceivedArgs(RtpPacket packet) + { + Packet = packet; + } + } + protected void OnPacketReceived(RtpPacketReceivedArgs args) + { + if (PacketReceived != null) + { + PacketReceived(this, args); + } + } + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs new file mode 100644 index 0000000000..489d7f087c --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Rtp/RtpPacket.cs @@ -0,0 +1,116 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ + +using System; +using System.Collections.ObjectModel; +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp.Rtp +{ + public class RtpPacket + { + private static int MinHeaderLength = 12; + public int HeaderSize = MinHeaderLength; + public int Version { get; set; } + public Boolean Padding { get; set; } + public Boolean Extension { get; set; } + public int ContributingSourceCount { get; set; } + public Boolean Marker { get; set; } + public int PayloadType { get; set; } + public int SequenceNumber { get; set; } + public long TimeStamp { get; set; } + public long SynchronizationSource { get; set; } + public Collection<string> ContributingSources { get; private set; } + public int ExtensionHeaderId = 0; + public int ExtensionHeaderLength = 0; + public bool HasPayload { get; set; } + public byte[] Payload { get; set; } + public RtpPacket() + { + + } + public static RtpPacket Decode(byte[] buffer) + { + var packet = new RtpPacket(); + packet.Version = buffer[0] >> 6; + packet.Padding = (buffer[0] & 0x20) != 0; + packet.Extension = (buffer[0] & 0x10) != 0; + packet.ContributingSourceCount = buffer[0] & 0x0f; + + packet.Marker = (buffer[1] & 0x80) != 0; + packet.PayloadType = buffer[1] & 0x7f; + + packet.SequenceNumber = Utils.Convert2BytesToInt(buffer, 2); + packet.TimeStamp = Utils.Convert4BytesToLong(buffer, 4); + packet.SynchronizationSource = Utils.Convert4BytesToLong(buffer, 8); + + int index = 12; + + if (packet.ContributingSourceCount != 0) + { + packet.ContributingSources = new Collection<string>(); + + while (packet.ContributingSources.Count < packet.ContributingSourceCount) + { + packet.ContributingSources.Add(Utils.ConvertBytesToString(buffer, index, 4)); + index += 4; + } + } + var dataoffset = 0; + if (!packet.Extension) + dataoffset = index; + else + { + packet.ExtensionHeaderId = Utils.Convert2BytesToInt(buffer, index); + packet.ExtensionHeaderLength = Utils.Convert2BytesToInt(buffer, index + 2); + dataoffset = index + packet.ExtensionHeaderLength + 4; + } + + var dataLength = buffer.Length - dataoffset; + if (dataLength > dataoffset) + { + packet.HasPayload = true; + packet.Payload = new byte[dataLength]; + Array.Copy(buffer, dataoffset, packet.Payload, 0, dataLength); + } + else + { + packet.HasPayload = false; + } + return packet; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + sb.AppendFormat("RTP Packet"); + sb.AppendFormat("Version: {0} \n", Version); + sb.AppendFormat("Padding: {0} \n", Padding); + sb.AppendFormat("Extension: {0} \n", Extension); + sb.AppendFormat("Contributing Source Identifiers Count: {0} \n", ContributingSourceCount); + sb.AppendFormat("Marker: {0} \n", Marker); + sb.AppendFormat("Payload Type: {0} \n", PayloadType); + sb.AppendFormat("Sequence Number: {0} \n", SequenceNumber); + sb.AppendFormat("Timestamp: {0} .\n", TimeStamp); + sb.AppendFormat("Synchronization Source Identifier: {0} \n", SynchronizationSource); + sb.AppendFormat("\n"); + return sb.ToString(); + } + + } + +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs index ba5c604ed0..cb0e573da2 100644 --- a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/SatIpDiscovery.cs @@ -237,7 +237,7 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp string modelurl = ""; string serialnumber = ""; string presentationurl = ""; - string capabilities = ""; + //string capabilities = ""; string m3u = ""; var document = XDocument.Load(locationUri.AbsoluteUri); var xnm = new XmlNamespaceManager(new NameTable()); @@ -284,22 +284,22 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp var capabilitiesElement = deviceElement.Element(n1 + "X_SATIPCAP"); if (capabilitiesElement != null) { - //_capabilities = capabilitiesElement.Value; - //if (capabilitiesElement.Value.Contains(',')) - //{ - // string[] capabilities = capabilitiesElement.Value.Split(','); - // foreach (var capability in capabilities) - // { - // ReadCapability(capability); - // } - //} - //else - //{ - // ReadCapability(capabilitiesElement.Value); - //} + //_capabilities = capabilitiesElement.Value; + if (capabilitiesElement.Value.Contains(',')) + { + string[] capabilities = capabilitiesElement.Value.Split(','); + foreach (var capability in capabilities) + { + ReadCapability(capability); + } } else { + ReadCapability(capabilitiesElement.Value); + } + } + else + { _supportsDVBS = true; _tunerCountDVBS =1; } @@ -314,8 +314,8 @@ namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp Id = uniquedevicename, IsEnabled = true, Type = SatIpHost.DeviceType, - Tuners = 1, - TunersAvailable = 1, + Tuners = _tunerCountDVBS, + TunersAvailable = _tunerCountDVBS, M3UUrl = m3u }; diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs new file mode 100644 index 0000000000..71d7656d95 --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/TransmissionMode.cs @@ -0,0 +1,25 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp +{ + public enum TransmissionMode + { + Unicast, + Multicast + } +} diff --git a/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs new file mode 100644 index 0000000000..3595e4b0ad --- /dev/null +++ b/MediaBrowser.Server.Implementations/LiveTv/TunerHosts/SatIp/Utils.cs @@ -0,0 +1,90 @@ +/* + Copyright (C) <2007-2016> <Kay Diefenthal> + + SatIp.RtspSample is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + SatIp.RtspSample is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with SatIp.RtspSample. If not, see <http://www.gnu.org/licenses/>. +*/ +using System; +using System.Text; + +namespace MediaBrowser.Server.Implementations.LiveTv.TunerHosts.SatIp +{ + public class Utils + { + public static int Convert2BytesToInt(byte[] buffer, int offset) + { + int temp = (int)buffer[offset]; + temp = (temp * 256) + buffer[offset + 1]; + + return (temp); + } + public static int Convert3BytesToInt(byte[] buffer, int offset) + { + int temp = (int)buffer[offset]; + temp = (temp * 256) + buffer[offset + 1]; + temp = (temp * 256) + buffer[offset + 2]; + + return (temp); + } + public static int Convert4BytesToInt(byte[] buffer, int offset) + { + int temp =(int)buffer[offset]; + temp = (temp * 256) + buffer[offset + 1]; + temp = (temp * 256) + buffer[offset + 2]; + temp = (temp * 256) + buffer[offset + 3]; + + return (temp); + } + public static long Convert4BytesToLong(byte[] buffer, int offset) + { + long temp = 0; + + for (int index = 0; index < 4; index++) + temp = (temp * 256) + buffer[offset + index]; + + return (temp); + } + public static long Convert8BytesToLong(byte[] buffer, int offset) + { + long temp = 0; + + for (int index = 0; index < 8; index++) + temp = (temp * 256) + buffer[offset + index]; + + return (temp); + } + public static string ConvertBytesToString(byte[] buffer, int offset, int length) + { + StringBuilder reply = new StringBuilder(4); + for (int index = 0; index < length; index++) + reply.Append((char)buffer[offset + index]); + return (reply.ToString()); + } + public static DateTime NptTimestampToDateTime(long nptTimestamp) { return NptTimestampToDateTime((uint)((nptTimestamp >> 32) & 0xFFFFFFFF), (uint)(nptTimestamp & 0xFFFFFFFF),null); } + + public static DateTime NptTimestampToDateTime(uint seconds, uint fractions, DateTime? epoch ) + { + ulong ticks =(ulong)((seconds * TimeSpan.TicksPerSecond) + ((fractions * TimeSpan.TicksPerSecond) / 0x100000000L)); + if (epoch.HasValue) return epoch.Value + TimeSpan.FromTicks((Int64)ticks); + return (seconds & 0x80000000L) == 0 ? UtcEpoch2036 + TimeSpan.FromTicks((Int64)ticks) : UtcEpoch1900 + TimeSpan.FromTicks((Int64)ticks); + } + + //When the First Epoch will wrap (The real Y2k) + public static DateTime UtcEpoch2036 = new DateTime(2036, 2, 7, 6, 28, 16, DateTimeKind.Utc); + + public static DateTime UtcEpoch1900 = new DateTime(1900, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + public static DateTime UtcEpoch1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + } +} |
