123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191 |
- #pragma warning disable CS1591
- using System;
- using System.Collections.Generic;
- using System.Globalization;
- using System.IO;
- using System.Linq;
- using System.Text.Json;
- using System.Threading;
- using System.Threading.Tasks;
- using Jellyfin.Extensions.Json;
- using MediaBrowser.Common.Configuration;
- using MediaBrowser.Common.Extensions;
- using MediaBrowser.Controller.MediaEncoding;
- using MediaBrowser.Model.Dlna;
- using MediaBrowser.Model.Dto;
- using MediaBrowser.Model.Entities;
- using MediaBrowser.Model.IO;
- using MediaBrowser.Model.MediaInfo;
- using Microsoft.Extensions.Logging;
- namespace Emby.Server.Implementations.Library
- {
- public class LiveStreamHelper
- {
- private readonly IMediaEncoder _mediaEncoder;
- private readonly ILogger _logger;
- private readonly IApplicationPaths _appPaths;
- private readonly JsonSerializerOptions _jsonOptions = JsonDefaults.Options;
- public LiveStreamHelper(IMediaEncoder mediaEncoder, ILogger logger, IApplicationPaths appPaths)
- {
- _mediaEncoder = mediaEncoder;
- _logger = logger;
- _appPaths = appPaths;
- }
- public async Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, string? cacheKey, bool addProbeDelay, CancellationToken cancellationToken)
- {
- var originalRuntime = mediaSource.RunTimeTicks;
- var now = DateTime.UtcNow;
- MediaInfo? mediaInfo = null;
- var cacheFilePath = string.IsNullOrEmpty(cacheKey) ? null : Path.Combine(_appPaths.CachePath, "mediainfo", cacheKey.GetMD5().ToString("N", CultureInfo.InvariantCulture) + ".json");
- if (cacheFilePath is not null)
- {
- try
- {
- FileStream jsonStream = AsyncFile.OpenRead(cacheFilePath);
- await using (jsonStream.ConfigureAwait(false))
- {
- mediaInfo = await JsonSerializer.DeserializeAsync<MediaInfo>(jsonStream, _jsonOptions, cancellationToken).ConfigureAwait(false);
- // _logger.LogDebug("Found cached media info");
- }
- }
- catch (IOException ex)
- {
- _logger.LogDebug(ex, "Could not open cached media info");
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error opening cached media info");
- }
- }
- if (mediaInfo is null)
- {
- if (addProbeDelay)
- {
- var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
- delayMs = Math.Max(3000, delayMs);
- _logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
- await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
- }
- mediaSource.AnalyzeDurationMs = 3000;
- mediaInfo = await _mediaEncoder.GetMediaInfo(
- new MediaInfoRequest
- {
- MediaSource = mediaSource,
- MediaType = isAudio ? DlnaProfileType.Audio : DlnaProfileType.Video,
- ExtractChapters = false
- },
- cancellationToken).ConfigureAwait(false);
- if (cacheFilePath is not null)
- {
- Directory.CreateDirectory(Path.GetDirectoryName(cacheFilePath) ?? throw new InvalidOperationException("Path can't be a root directory."));
- FileStream createStream = AsyncFile.OpenWrite(cacheFilePath);
- await using (createStream.ConfigureAwait(false))
- {
- await JsonSerializer.SerializeAsync(createStream, mediaInfo, _jsonOptions, cancellationToken).ConfigureAwait(false);
- }
- _logger.LogDebug("Saved media info to {0}", cacheFilePath);
- }
- }
- var mediaStreams = mediaInfo.MediaStreams;
- if (!string.IsNullOrEmpty(cacheKey))
- {
- var newList = new List<MediaStream>();
- newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Video).Take(1));
- newList.AddRange(mediaStreams.Where(i => i.Type == MediaStreamType.Audio).Take(1));
- foreach (var stream in newList)
- {
- stream.Index = -1;
- stream.Language = null;
- }
- mediaStreams = newList;
- }
- _logger.LogInformation("Live tv media info probe took {0} seconds", (DateTime.UtcNow - now).TotalSeconds.ToString(CultureInfo.InvariantCulture));
- mediaSource.Bitrate = mediaInfo.Bitrate;
- mediaSource.Container = mediaInfo.Container;
- mediaSource.Formats = mediaInfo.Formats;
- mediaSource.MediaStreams = mediaStreams;
- mediaSource.RunTimeTicks = mediaInfo.RunTimeTicks;
- mediaSource.Size = mediaInfo.Size;
- mediaSource.Timestamp = mediaInfo.Timestamp;
- mediaSource.Video3DFormat = mediaInfo.Video3DFormat;
- mediaSource.VideoType = mediaInfo.VideoType;
- mediaSource.DefaultSubtitleStreamIndex = null;
- // Null this out so that it will be treated like a live stream
- if (!originalRuntime.HasValue)
- {
- mediaSource.RunTimeTicks = null;
- }
- var audioStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Audio);
- if (audioStream is null || audioStream.Index == -1)
- {
- mediaSource.DefaultAudioStreamIndex = null;
- }
- else
- {
- mediaSource.DefaultAudioStreamIndex = audioStream.Index;
- }
- var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video);
- if (videoStream is not null)
- {
- if (!videoStream.BitRate.HasValue)
- {
- var width = videoStream.Width ?? 1920;
- if (width >= 3000)
- {
- videoStream.BitRate = 30000000;
- }
- else if (width >= 1900)
- {
- videoStream.BitRate = 20000000;
- }
- else if (width >= 1200)
- {
- videoStream.BitRate = 8000000;
- }
- else if (width >= 700)
- {
- videoStream.BitRate = 2000000;
- }
- }
- // This is coming up false and preventing stream copy
- videoStream.IsAVC = null;
- }
- mediaSource.AnalyzeDurationMs = 3000;
- // Try to estimate this
- mediaSource.InferTotalBitrate(true);
- }
- public Task AddMediaInfoWithProbe(MediaSourceInfo mediaSource, bool isAudio, bool addProbeDelay, CancellationToken cancellationToken)
- {
- return AddMediaInfoWithProbe(mediaSource, isAudio, null, addProbeDelay, cancellationToken);
- }
- }
- }
|