| 
					
				 | 
			
			
				@@ -30,25 +30,8 @@ namespace Jellyfin.LiveTv.TunerHosts 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     public class M3UTunerHost : BaseTunerHost, ITunerHost, IConfigurableTunerHost 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        private static readonly string[] _disallowedMimeTypes = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "text/plain", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "text/html", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "video/x-matroska", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "video/mp4", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "application/vnd.apple.mpegurl", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "application/mpegurl", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "application/x-mpegurl", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            "video/vnd.mpeg.dash.mpd" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				- 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        private static readonly string[] _disallowedSharedStreamExtensions = 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            ".mkv", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            ".mp4", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            ".m3u8", 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-            ".mpd" 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        }; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private static readonly string[] _mimeTypesCanShareHttpStream = ["video/MP2T"]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        private static readonly string[] _extensionsCanShareHttpStream = [".ts", ".tsv", ".m2t"]; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         private readonly IHttpClientFactory _httpClientFactory; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         private readonly IServerApplicationHost _appHost; 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -113,28 +96,34 @@ namespace Jellyfin.LiveTv.TunerHosts 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             if (mediaSource.Protocol == MediaProtocol.Http && !mediaSource.RequiresLooping) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                using var response = await _httpClientFactory.CreateClient(NamedClient.Default) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    .SendAsync(message, cancellationToken) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    .ConfigureAwait(false); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                if (response.IsSuccessStatusCode) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                if (string.IsNullOrEmpty(extension)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    if (!_disallowedMimeTypes.Contains(response.Content.Headers.ContentType?.MediaType, StringComparison.OrdinalIgnoreCase)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    try 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        using var message = new HttpRequestMessage(HttpMethod.Head, mediaSource.Path); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        using var response = await _httpClientFactory.CreateClient(NamedClient.Default) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            .SendAsync(message, cancellationToken) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            .ConfigureAwait(false); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        if (response.IsSuccessStatusCode) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            if (_mimeTypesCanShareHttpStream.Contains(response.Content.Headers.ContentType?.MediaType, StringComparison.OrdinalIgnoreCase)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                            } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                else 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    // Fallback to check path extension when the server does not support HEAD method 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    // Use UriBuilder to remove all query string as GetExtension will include them when used directly 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    var extension = Path.GetExtension(new UriBuilder(mediaSource.Path).Path); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                    if (!_disallowedSharedStreamExtensions.Contains(extension, StringComparison.OrdinalIgnoreCase)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    catch (Exception) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-                        return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                        Logger.LogWarning("HEAD request to check MIME type failed, shared stream disabled"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                else if (_extensionsCanShareHttpStream.Contains(extension, StringComparison.OrdinalIgnoreCase)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                    return new SharedHttpStream(mediaSource, tunerHost, streamId, FileSystem, _httpClientFactory, Logger, Config, _appHost, _streamHelper); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             return new LiveStream(mediaSource, tunerHost, FileSystem, Logger, Config, _streamHelper); 
			 |