< Summary - Jellyfin

Information
Class: Jellyfin.Api.Helpers.HlsHelpers
Assembly: Jellyfin.Api
File(s): /srv/git/jellyfin/Jellyfin.Api/Helpers/HlsHelpers.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 21
Coverable lines: 21
Total lines: 138
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 8
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
GetFmp4InitFileName(...)0%4260%
GetLivePlaylistText(...)0%620%

File(s)

/srv/git/jellyfin/Jellyfin.Api/Helpers/HlsHelpers.cs

#LineLine coverage
 1using System;
 2using System.Globalization;
 3using System.IO;
 4using System.Threading;
 5using System.Threading.Tasks;
 6using Jellyfin.Api.Models.StreamingDtos;
 7using MediaBrowser.Controller.MediaEncoding;
 8using MediaBrowser.Controller.Streaming;
 9using MediaBrowser.Model.IO;
 10using Microsoft.Extensions.Logging;
 11
 12namespace Jellyfin.Api.Helpers;
 13
 14/// <summary>
 15/// The hls helpers.
 16/// </summary>
 17public static class HlsHelpers
 18{
 19    /// <summary>
 20    /// Waits for a minimum number of segments to be available.
 21    /// </summary>
 22    /// <param name="playlist">The playlist string.</param>
 23    /// <param name="segmentCount">The segment count.</param>
 24    /// <param name="logger">Instance of the <see cref="ILogger"/> interface.</param>
 25    /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
 26    /// <returns>A <see cref="Task"/> indicating the waiting process.</returns>
 27    public static async Task WaitForMinimumSegmentCount(string playlist, int? segmentCount, ILogger logger, Cancellation
 28    {
 29        logger.LogDebug("Waiting for {0} segments in {1}", segmentCount, playlist);
 30
 31        while (!cancellationToken.IsCancellationRequested)
 32        {
 33            try
 34            {
 35                // Need to use FileShare.ReadWrite because we're reading the file at the same time it's being written
 36                var fileStream = new FileStream(
 37                    playlist,
 38                    FileMode.Open,
 39                    FileAccess.Read,
 40                    FileShare.ReadWrite,
 41                    IODefaults.FileStreamBufferSize,
 42                    FileOptions.Asynchronous | FileOptions.SequentialScan);
 43                await using (fileStream.ConfigureAwait(false))
 44                {
 45                    using var reader = new StreamReader(fileStream);
 46                    var count = 0;
 47
 48                    while (!reader.EndOfStream)
 49                    {
 50                        var line = await reader.ReadLineAsync(cancellationToken).ConfigureAwait(false);
 51                        if (line is null)
 52                        {
 53                            // Nothing currently in buffer.
 54                            break;
 55                        }
 56
 57                        if (line.Contains("#EXTINF:", StringComparison.OrdinalIgnoreCase))
 58                        {
 59                            count++;
 60                            if (count >= segmentCount)
 61                            {
 62                                logger.LogDebug("Finished waiting for {0} segments in {1}", segmentCount, playlist);
 63                                return;
 64                            }
 65                        }
 66                    }
 67                }
 68
 69                await Task.Delay(100, cancellationToken).ConfigureAwait(false);
 70            }
 71            catch (IOException)
 72            {
 73                // May get an error if the file is locked
 74            }
 75
 76            await Task.Delay(50, cancellationToken).ConfigureAwait(false);
 77        }
 78    }
 79
 80    /// <summary>
 81    /// Gets the #EXT-X-MAP string.
 82    /// </summary>
 83    /// <param name="outputPath">The output path of the file.</param>
 84    /// <param name="state">The <see cref="StreamState"/>.</param>
 85    /// <param name="isOsDepends">Get a normal string or depends on OS.</param>
 86    /// <returns>The string text of #EXT-X-MAP.</returns>
 87    public static string GetFmp4InitFileName(string outputPath, StreamState state, bool isOsDepends)
 88    {
 089        var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) 
 090        var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath);
 091        var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension);
 092        var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
 93
 94        // on Linux/Unix
 95        // #EXT-X-MAP:URI="prefix-1.mp4"
 096        var fmp4InitFileName = outputFileNameWithoutExtension + "-1" + outputExtension;
 097        if (!isOsDepends)
 98        {
 099            return fmp4InitFileName;
 100        }
 101
 0102        if (OperatingSystem.IsWindows())
 103        {
 104            // on Windows
 105            // #EXT-X-MAP:URI="X:\transcodes\prefix-1.mp4"
 0106            fmp4InitFileName = outputPrefix + "-1" + outputExtension;
 107        }
 108
 0109        return fmp4InitFileName;
 110    }
 111
 112    /// <summary>
 113    /// Gets the hls playlist text.
 114    /// </summary>
 115    /// <param name="path">The path to the playlist file.</param>
 116    /// <param name="state">The <see cref="StreamState"/>.</param>
 117    /// <returns>The playlist text as a string.</returns>
 118    public static string GetLivePlaylistText(string path, StreamState state)
 119    {
 0120        var text = File.ReadAllText(path);
 121
 0122        var segmentFormat = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer).TrimStart('.');
 0123        if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
 124        {
 0125            var fmp4InitFileName = GetFmp4InitFileName(path, state, true);
 0126            var baseUrlParam = string.Format(
 0127                CultureInfo.InvariantCulture,
 0128                "hls/{0}/",
 0129                Path.GetFileNameWithoutExtension(path));
 0130            var newFmp4InitFileName = baseUrlParam + GetFmp4InitFileName(path, state, false);
 131
 132            // Replace fMP4 init file URI.
 0133            text = text.Replace(fmp4InitFileName, newFmp4InitFileName, StringComparison.InvariantCulture);
 134        }
 135
 0136        return text;
 137    }
 138}