aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs10
-rw-r--r--Emby.Server.Implementations/Localization/Core/es-MX.json10
-rw-r--r--Emby.Server.Implementations/Localization/Core/es.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ja.json97
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json156
-rw-r--r--Emby.Server.Implementations/Localization/Core/zh-TW.json5
-rw-r--r--MediaBrowser.Api/ApiEntryPoint.cs15
-rw-r--r--MediaBrowser.Api/Playback/BaseStreamingService.cs189
-rw-r--r--MediaBrowser.Api/Playback/StreamState.cs94
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs58
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs7
-rw-r--r--MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs20
12 files changed, 348 insertions, 315 deletions
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 0dea5041a..7c2ea50e2 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -647,7 +647,6 @@ namespace Emby.Server.Implementations.IO
public virtual bool IsPathFile(string path)
{
// Cannot use Path.IsPathRooted because it returns false under mono when using windows-based paths, e.g. C:\\
-
if (path.IndexOf("://", StringComparison.OrdinalIgnoreCase) != -1 &&
!path.StartsWith("file://", StringComparison.OrdinalIgnoreCase))
{
@@ -655,8 +654,6 @@ namespace Emby.Server.Implementations.IO
}
return true;
-
- //return Path.IsPathRooted(path);
}
public virtual void DeleteFile(string path)
@@ -667,13 +664,14 @@ namespace Emby.Server.Implementations.IO
public virtual List<FileSystemMetadata> GetDrives()
{
- // Only include drives in the ready state or this method could end up being very slow, waiting for drives to timeout
- return DriveInfo.GetDrives().Where(d => d.IsReady).Select(d => new FileSystemMetadata
+ // check for ready state to avoid waiting for drives to timeout
+ // some drives on linux have no actual size or are used for other purposes
+ return DriveInfo.GetDrives().Where(d => d.IsReady && d.TotalSize != 0 && d.DriveType != DriveType.Ram)
+ .Select(d => new FileSystemMetadata
{
Name = d.Name,
FullName = d.RootDirectory.FullName,
IsDirectory = true
-
}).ToList();
}
diff --git a/Emby.Server.Implementations/Localization/Core/es-MX.json b/Emby.Server.Implementations/Localization/Core/es-MX.json
index 2285f2808..003632968 100644
--- a/Emby.Server.Implementations/Localization/Core/es-MX.json
+++ b/Emby.Server.Implementations/Localization/Core/es-MX.json
@@ -18,11 +18,11 @@
"HeaderAlbumArtists": "Artistas del Álbum",
"HeaderCameraUploads": "Subidos desde Camara",
"HeaderContinueWatching": "Continuar Viendo",
- "HeaderFavoriteAlbums": "Álbumes Favoritos",
- "HeaderFavoriteArtists": "Artistas Favoritos",
- "HeaderFavoriteEpisodes": "Episodios Preferidos",
- "HeaderFavoriteShows": "Programas Preferidos",
- "HeaderFavoriteSongs": "Canciones Favoritas",
+ "HeaderFavoriteAlbums": "Álbumes favoritos",
+ "HeaderFavoriteArtists": "Artistas favoritos",
+ "HeaderFavoriteEpisodes": "Episodios favoritos",
+ "HeaderFavoriteShows": "Programas favoritos",
+ "HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en Vivo",
"HeaderNextUp": "A Continuación",
"HeaderRecordingGroups": "Grupos de Grabaciones",
diff --git a/Emby.Server.Implementations/Localization/Core/es.json b/Emby.Server.Implementations/Localization/Core/es.json
index 1850b8f25..f03184d5b 100644
--- a/Emby.Server.Implementations/Localization/Core/es.json
+++ b/Emby.Server.Implementations/Localization/Core/es.json
@@ -21,7 +21,7 @@
"HeaderFavoriteAlbums": "Álbumes favoritos",
"HeaderFavoriteArtists": "Artistas favoritos",
"HeaderFavoriteEpisodes": "Episodios favoritos",
- "HeaderFavoriteShows": "Programas favoritos",
+ "HeaderFavoriteShows": "Series favoritas",
"HeaderFavoriteSongs": "Canciones favoritas",
"HeaderLiveTV": "TV en directo",
"HeaderNextUp": "Siguiendo",
diff --git a/Emby.Server.Implementations/Localization/Core/ja.json b/Emby.Server.Implementations/Localization/Core/ja.json
index 0967ef424..53a43a125 100644
--- a/Emby.Server.Implementations/Localization/Core/ja.json
+++ b/Emby.Server.Implementations/Localization/Core/ja.json
@@ -1 +1,96 @@
-{}
+{
+ "Albums": "アルバム",
+ "AppDeviceValues": "アプリ: {0}, デバイス: {1}",
+ "Application": "アプリケーション",
+ "Artists": "アーティスト",
+ "AuthenticationSucceededWithUserName": "{0} 認証に成功しました",
+ "Books": "ブック",
+ "CameraImageUploadedFrom": "新しいカメライメージが {0}からアップロードされました",
+ "Channels": "チャンネル",
+ "ChapterNameValue": "チャプター {0}",
+ "Collections": "コレクション",
+ "DeviceOfflineWithName": "{0} が切断されました",
+ "DeviceOnlineWithName": "{0} が接続されました",
+ "FailedLoginAttemptWithUserName": "ログインを試行しましたが {0}によって失敗しました",
+ "Favorites": "お気に入り",
+ "Folders": "フォルダ",
+ "Genres": "ジャンル",
+ "HeaderAlbumArtists": "アルバムアーティスト",
+ "HeaderCameraUploads": "カメラアップロード",
+ "HeaderContinueWatching": "視聴中",
+ "HeaderFavoriteAlbums": "お気に入りのアルバム",
+ "HeaderFavoriteArtists": "お気に入りのアーティスト",
+ "HeaderFavoriteEpisodes": "お気に入りのエピソード",
+ "HeaderFavoriteShows": "お気に入りの番組",
+ "HeaderFavoriteSongs": "お気に入りの曲",
+ "HeaderLiveTV": "ライブ テレビ",
+ "HeaderNextUp": "次",
+ "HeaderRecordingGroups": "レコーディンググループ",
+ "HomeVideos": "ホームビデオ",
+ "Inherit": "継承",
+ "ItemAddedWithName": "{0} をライブラリに追加しました",
+ "ItemRemovedWithName": "{0} をライブラリから削除しました",
+ "LabelIpAddressValue": "IPアドレス: {0}",
+ "LabelRunningTimeValue": "稼働時間: {0}",
+ "Latest": "最新",
+ "MessageApplicationUpdated": "Jellyfin Server が更新されました",
+ "MessageApplicationUpdatedTo": "Jellyfin Server が {0}に更新されました",
+ "MessageNamedServerConfigurationUpdatedWithValue": "サーバー設定項目の {0} が更新されました",
+ "MessageServerConfigurationUpdated": "サーバー設定が更新されました",
+ "MixedContent": "ミックスコンテンツ",
+ "Movies": "ムービー",
+ "Music": "ミュージック",
+ "MusicVideos": "ミュージックビデオ",
+ "NameInstallFailed": "{0}のインストールに失敗しました",
+ "NameSeasonNumber": "シーズン {0}",
+ "NameSeasonUnknown": "不明なシーズン",
+ "NewVersionIsAvailable": "新しいバージョンの Jellyfin Server がダウンロード可能です。",
+ "NotificationOptionApplicationUpdateAvailable": "アプリケーションの更新があります",
+ "NotificationOptionApplicationUpdateInstalled": "アプリケーションは最新です",
+ "NotificationOptionAudioPlayback": "オーディオの再生を開始",
+ "NotificationOptionAudioPlaybackStopped": "オーディオの再生をストップしました",
+ "NotificationOptionCameraImageUploaded": "カメライメージがアップロードされました",
+ "NotificationOptionInstallationFailed": "インストール失敗",
+ "NotificationOptionNewLibraryContent": "新しいコンテンツを追加しました",
+ "NotificationOptionPluginError": "プラグインに障害が発生しました",
+ "NotificationOptionPluginInstalled": "プラグインがインストールされました",
+ "NotificationOptionPluginUninstalled": "プラグインがアンインストールされました",
+ "NotificationOptionPluginUpdateInstalled": "プラグインのアップデートをインストールしました",
+ "NotificationOptionServerRestartRequired": "サーバーを再起動してください",
+ "NotificationOptionTaskFailed": "スケジュールされていたタスクの失敗",
+ "NotificationOptionUserLockedOut": "ユーザーはロックされています",
+ "NotificationOptionVideoPlayback": "ビデオの再生を開始しました",
+ "NotificationOptionVideoPlaybackStopped": "ビデオを停止しました",
+ "Photos": "フォト",
+ "Playlists": "プレイリスト",
+ "Plugin": "プラグイン",
+ "PluginInstalledWithName": "{0} がインストールされました",
+ "PluginUninstalledWithName": "{0} がアンインストールされました",
+ "PluginUpdatedWithName": "{0} が更新されました",
+ "ProviderValue": "プロバイダ: {0}",
+ "ScheduledTaskFailedWithName": "{0} が失敗しました",
+ "ScheduledTaskStartedWithName": "{0} が開始されました",
+ "ServerNameNeedsToBeRestarted": "{0} を再起動してください",
+ "Shows": "番組",
+ "Songs": "曲",
+ "StartupEmbyServerIsLoading": "Jellyfin Server は現在読み込み中です。しばらくしてからもう一度お試しください。",
+ "SubtitleDownloadFailureFromForItem": "{0} から {1}の字幕のダウンロードに失敗しました",
+ "SubtitlesDownloadedForItem": "{0} の字幕がダウンロードされました",
+ "Sync": "同期",
+ "System": "システム",
+ "TvShows": "テレビ番組",
+ "User": "ユーザー",
+ "UserCreatedWithName": "ユーザー {0} が作成されました",
+ "UserDeletedWithName": "User {0} を削除しました",
+ "UserDownloadingItemWithValues": "{0} が {1} をダウンロードしています",
+ "UserLockedOutWithName": "ユーザー {0} はロックされています",
+ "UserOfflineFromDevice": "{0} は {1} から切断しました",
+ "UserOnlineFromDevice": "{0} は {1} からオンラインになりました",
+ "UserPasswordChangedWithName": "ユーザー {0} のパスワードは変更されました",
+ "UserPolicyUpdatedWithName": "ユーザーポリシーが{0}に更新されました",
+ "UserStartedPlayingItemWithValues": "{0} は {2}で{1} を再生しています",
+ "UserStoppedPlayingItemWithValues": "{0} は{2}で{1} の再生が終わりました",
+ "ValueHasBeenAddedToLibrary": "{0}はあなたのメディアライブラリに追加されました",
+ "ValueSpecialEpisodeName": "スペシャル - {0}",
+ "VersionNumber": "バージョン {0}"
+}
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index 21808fd18..5a7ba8ba7 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -1,88 +1,88 @@
{
- "Albums": "Albums",
- "AppDeviceValues": "App: {0}, Device: {1}",
- "Application": "Application",
- "Artists": "Artists",
- "AuthenticationSucceededWithUserName": "{0} successfully authenticated",
- "Books": "Books",
- "CameraImageUploadedFrom": "A new camera image has been uploaded from {0}",
- "Channels": "Channels",
- "ChapterNameValue": "Chapter {0}",
- "Collections": "Collections",
- "DeviceOfflineWithName": "{0} has disconnected",
- "DeviceOnlineWithName": "{0} is connected",
- "FailedLoginAttemptWithUserName": "Failed login attempt from {0}",
- "Favorites": "Favorites",
- "Folders": "Folders",
- "Genres": "Genres",
+ "Albums": "앨범",
+ "AppDeviceValues": "앱: {0}, 디바이스: {1}",
+ "Application": "애플리케이션",
+ "Artists": "아티스트",
+ "AuthenticationSucceededWithUserName": "{0} 인증에 성공했습니다.",
+ "Books": "책",
+ "CameraImageUploadedFrom": "새로운 카메라 이미지가 {0}에서 업로드되었습니다.",
+ "Channels": "채널",
+ "ChapterNameValue": "챕터 {0}",
+ "Collections": "컬렉션",
+ "DeviceOfflineWithName": "{0}가 접속이 끊어졌습니다.",
+ "DeviceOnlineWithName": "{0}가 접속되었습니다.",
+ "FailedLoginAttemptWithUserName": "{0}에서 로그인이 실패했습니다.",
+ "Favorites": "즐겨찾기",
+ "Folders": "폴더",
+ "Genres": "장르",
"HeaderAlbumArtists": "앨범 아티스트",
- "HeaderCameraUploads": "Camera Uploads",
+ "HeaderCameraUploads": "카메라 업로드",
"HeaderContinueWatching": "계속 시청하기",
- "HeaderFavoriteAlbums": "Favorite Albums",
- "HeaderFavoriteArtists": "Favorite Artists",
+ "HeaderFavoriteAlbums": "좋아하는 앨범",
+ "HeaderFavoriteArtists": "좋아하는 아티스트",
"HeaderFavoriteEpisodes": "Favorite Episodes",
"HeaderFavoriteShows": "즐겨찾는 쇼",
- "HeaderFavoriteSongs": "Favorite Songs",
- "HeaderLiveTV": "Live TV",
- "HeaderNextUp": "Next Up",
- "HeaderRecordingGroups": "Recording Groups",
- "HomeVideos": "Home videos",
- "Inherit": "Inherit",
- "ItemAddedWithName": "{0} was added to the library",
- "ItemRemovedWithName": "{0} was removed from the library",
- "LabelIpAddressValue": "Ip address: {0}",
- "LabelRunningTimeValue": "Running time: {0}",
- "Latest": "Latest",
- "MessageApplicationUpdated": "Jellyfin Server has been updated",
- "MessageApplicationUpdatedTo": "Jellyfin Server has been updated to {0}",
- "MessageNamedServerConfigurationUpdatedWithValue": "Server configuration section {0} has been updated",
- "MessageServerConfigurationUpdated": "Server configuration has been updated",
- "MixedContent": "Mixed content",
- "Movies": "Movies",
- "Music": "Music",
- "MusicVideos": "Music videos",
- "NameInstallFailed": "{0} installation failed",
- "NameSeasonNumber": "Season {0}",
- "NameSeasonUnknown": "Season Unknown",
- "NewVersionIsAvailable": "A new version of Jellyfin Server is available for download.",
- "NotificationOptionApplicationUpdateAvailable": "Application update available",
- "NotificationOptionApplicationUpdateInstalled": "Application update installed",
- "NotificationOptionAudioPlayback": "Audio playback started",
- "NotificationOptionAudioPlaybackStopped": "Audio playback stopped",
- "NotificationOptionCameraImageUploaded": "Camera image uploaded",
- "NotificationOptionInstallationFailed": "Installation failure",
- "NotificationOptionNewLibraryContent": "New content added",
- "NotificationOptionPluginError": "Plugin failure",
- "NotificationOptionPluginInstalled": "Plugin installed",
- "NotificationOptionPluginUninstalled": "Plugin uninstalled",
- "NotificationOptionPluginUpdateInstalled": "Plugin update installed",
- "NotificationOptionServerRestartRequired": "Server restart required",
- "NotificationOptionTaskFailed": "Scheduled task failure",
- "NotificationOptionUserLockedOut": "User locked out",
- "NotificationOptionVideoPlayback": "Video playback started",
- "NotificationOptionVideoPlaybackStopped": "Video playback stopped",
- "Photos": "Photos",
- "Playlists": "Playlists",
- "Plugin": "Plugin",
- "PluginInstalledWithName": "{0} was installed",
- "PluginUninstalledWithName": "{0} was uninstalled",
- "PluginUpdatedWithName": "{0} was updated",
- "ProviderValue": "Provider: {0}",
- "ScheduledTaskFailedWithName": "{0} failed",
- "ScheduledTaskStartedWithName": "{0} started",
- "ServerNameNeedsToBeRestarted": "{0} needs to be restarted",
- "Shows": "Shows",
- "Songs": "Songs",
+ "HeaderFavoriteSongs": "좋아하는 노래",
+ "HeaderLiveTV": "TV 방송",
+ "HeaderNextUp": "다음으로",
+ "HeaderRecordingGroups": "녹화 그룹",
+ "HomeVideos": "홈 비디오",
+ "Inherit": "상속",
+ "ItemAddedWithName": "{0} 라이브러리에 추가됨",
+ "ItemRemovedWithName": "{0} 라이브러리에서 제거됨",
+ "LabelIpAddressValue": "IP 주소: {0}",
+ "LabelRunningTimeValue": "상영 시간: {0}",
+ "Latest": "최근",
+ "MessageApplicationUpdated": "Jellyfin 서버 업데이트됨",
+ "MessageApplicationUpdatedTo": "Jellyfin 서버가 {0}로 업데이트됨",
+ "MessageNamedServerConfigurationUpdatedWithValue": "서버 환경 설정 {0} 섹션 업데이트 됨",
+ "MessageServerConfigurationUpdated": "서버 환경 설정 업데이드됨",
+ "MixedContent": "혼합 콘텐츠",
+ "Movies": "영화",
+ "Music": "음악",
+ "MusicVideos": "뮤직 비디오",
+ "NameInstallFailed": "{0} 설치 실패.",
+ "NameSeasonNumber": "시즌 {0}",
+ "NameSeasonUnknown": "알 수 없는 시즌",
+ "NewVersionIsAvailable": "새 버전의 Jellyfin 서버를 사용할 수 있습니다.",
+ "NotificationOptionApplicationUpdateAvailable": "애플리케이션 업데이트 사용 가능",
+ "NotificationOptionApplicationUpdateInstalled": "애플리케이션 업데이트가 설치됨",
+ "NotificationOptionAudioPlayback": "오디오 재생을 시작함",
+ "NotificationOptionAudioPlaybackStopped": "오디오 재생이 중지됨",
+ "NotificationOptionCameraImageUploaded": "카메라 이미지가 업로드됨",
+ "NotificationOptionInstallationFailed": "설치 실패",
+ "NotificationOptionNewLibraryContent": "새 콘텐트가 추가됨",
+ "NotificationOptionPluginError": "플러그인 실패",
+ "NotificationOptionPluginInstalled": "플러그인이 설치됨",
+ "NotificationOptionPluginUninstalled": "플러그인이 설치 제거됨",
+ "NotificationOptionPluginUpdateInstalled": "플러그인 업데이트가 설치됨",
+ "NotificationOptionServerRestartRequired": "서버를 다시 시작하십시오",
+ "NotificationOptionTaskFailed": "예약 작업 실패",
+ "NotificationOptionUserLockedOut": "사용자가 잠겼습니다",
+ "NotificationOptionVideoPlayback": "비디오 재생을 시작함",
+ "NotificationOptionVideoPlaybackStopped": "비디오 재생이 중지됨",
+ "Photos": "사진",
+ "Playlists": "재생목록",
+ "Plugin": "플러그인",
+ "PluginInstalledWithName": "{0} 설치됨",
+ "PluginUninstalledWithName": "{0} 설치 제거됨",
+ "PluginUpdatedWithName": "{0} 업데이트됨",
+ "ProviderValue": "제공자: {0}",
+ "ScheduledTaskFailedWithName": "{0} 실패",
+ "ScheduledTaskStartedWithName": "{0} 시작",
+ "ServerNameNeedsToBeRestarted": "{0} 를 재시작하십시오",
+ "Shows": "프로그램",
+ "Songs": "노래",
"StartupEmbyServerIsLoading": "Jellyfin 서버를 불러오고 있습니다. 잠시후 다시시도 해주세요.",
"SubtitleDownloadFailureForItem": "Subtitles failed to download for {0}",
- "SubtitleDownloadFailureFromForItem": "Subtitles failed to download from {0} for {1}",
- "SubtitlesDownloadedForItem": "Subtitles downloaded for {0}",
- "Sync": "Sync",
- "System": "System",
- "TvShows": "TV Shows",
- "User": "User",
- "UserCreatedWithName": "User {0} has been created",
- "UserDeletedWithName": "User {0} has been deleted",
+ "SubtitleDownloadFailureFromForItem": "{0}에서 {1} 자막 다운로드에 실패했습니다",
+ "SubtitlesDownloadedForItem": "{0} 자막을 다운로드했습니다",
+ "Sync": "동기화",
+ "System": "시스템",
+ "TvShows": "TV 쇼",
+ "User": "사용자",
+ "UserCreatedWithName": "사용자 {0} 생성됨",
+ "UserDeletedWithName": "사용자 {0} 삭제됨",
"UserDownloadingItemWithValues": "{0} is downloading {1}",
"UserLockedOutWithName": "User {0} has been locked out",
"UserOfflineFromDevice": "{0} has disconnected from {1}",
diff --git a/Emby.Server.Implementations/Localization/Core/zh-TW.json b/Emby.Server.Implementations/Localization/Core/zh-TW.json
index effff5566..293fc28a8 100644
--- a/Emby.Server.Implementations/Localization/Core/zh-TW.json
+++ b/Emby.Server.Implementations/Localization/Core/zh-TW.json
@@ -89,5 +89,8 @@
"UserStoppedPlayingItemWithValues": "{0} 已停止在 {2} 播放 {1}",
"ValueHasBeenAddedToLibrary": "{0} 已新增至您的媒體庫",
"ValueSpecialEpisodeName": "特典 - {0}",
- "VersionNumber": "版本 {0}"
+ "VersionNumber": "版本 {0}",
+ "HeaderRecordingGroups": "錄製組",
+ "Inherit": "繼承",
+ "SubtitleDownloadFailureFromForItem": "無法為 {1} 從 {0} 下載字幕"
}
diff --git a/MediaBrowser.Api/ApiEntryPoint.cs b/MediaBrowser.Api/ApiEntryPoint.cs
index 700cbb943..a223a4fe3 100644
--- a/MediaBrowser.Api/ApiEntryPoint.cs
+++ b/MediaBrowser.Api/ApiEntryPoint.cs
@@ -415,7 +415,7 @@ namespace MediaBrowser.Api
public void OnTranscodeEndRequest(TranscodingJob job)
{
job.ActiveRequestCount--;
- //Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
+ Logger.LogDebug("OnTranscodeEndRequest job.ActiveRequestCount={0}", job.ActiveRequestCount);
if (job.ActiveRequestCount <= 0)
{
PingTimer(job, false);
@@ -428,7 +428,7 @@ namespace MediaBrowser.Api
throw new ArgumentNullException(nameof(playSessionId));
}
- //Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
+ Logger.LogDebug("PingTranscodingJob PlaySessionId={0} isUsedPaused: {1}", playSessionId, isUserPaused);
List<TranscodingJob> jobs;
@@ -443,7 +443,7 @@ namespace MediaBrowser.Api
{
if (isUserPaused.HasValue)
{
- //Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
+ Logger.LogDebug("Setting job.IsUserPaused to {0}. jobId: {1}", isUserPaused, job.Id);
job.IsUserPaused = isUserPaused.Value;
}
PingTimer(job, true);
@@ -601,7 +601,6 @@ namespace MediaBrowser.Api
{
Logger.LogInformation("Stopping ffmpeg process with q command for {Path}", job.Path);
- //process.Kill();
process.StandardInput.WriteLine("q");
// Need to wait because killing is asynchronous
@@ -701,7 +700,7 @@ namespace MediaBrowser.Api
{
try
{
- //Logger.LogDebug("Deleting HLS file {0}", file);
+ Logger.LogDebug("Deleting HLS file {0}", file);
_fileSystem.DeleteFile(file);
}
catch (FileNotFoundException)
@@ -840,12 +839,12 @@ namespace MediaBrowser.Api
{
if (KillTimer == null)
{
- //Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ Logger.LogDebug("Starting kill timer at {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer = new Timer(new TimerCallback(callback), this, intervalMs, Timeout.Infinite);
}
else
{
- //Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite);
}
}
@@ -864,7 +863,7 @@ namespace MediaBrowser.Api
{
var intervalMs = PingTimeout;
- //Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
+ Logger.LogDebug("Changing kill timer to {0}ms. JobId {1} PlaySessionId {2}", intervalMs, Id, PlaySessionId);
KillTimer.Change(intervalMs, Timeout.Infinite);
}
}
diff --git a/MediaBrowser.Api/Playback/BaseStreamingService.cs b/MediaBrowser.Api/Playback/BaseStreamingService.cs
index ae259a4f5..399401624 100644
--- a/MediaBrowser.Api/Playback/BaseStreamingService.cs
+++ b/MediaBrowser.Api/Playback/BaseStreamingService.cs
@@ -8,7 +8,6 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Common.Extensions;
-using MediaBrowser.Controller;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Devices;
using MediaBrowser.Controller.Dlna;
@@ -16,7 +15,6 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Net;
using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Diagnostics;
using MediaBrowser.Model.Dlna;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -32,6 +30,8 @@ namespace MediaBrowser.Api.Playback
/// </summary>
public abstract class BaseStreamingService : BaseApiService
{
+ protected virtual bool EnableOutputInSubFolder => false;
+
/// <summary>
/// Gets or sets the application paths.
/// </summary>
@@ -65,9 +65,13 @@ namespace MediaBrowser.Api.Playback
protected IFileSystem FileSystem { get; private set; }
protected IDlnaManager DlnaManager { get; private set; }
+
protected IDeviceManager DeviceManager { get; private set; }
+
protected ISubtitleEncoder SubtitleEncoder { get; private set; }
+
protected IMediaSourceManager MediaSourceManager { get; private set; }
+
protected IJsonSerializer JsonSerializer { get; private set; }
protected IAuthorizationContext AuthorizationContext { get; private set; }
@@ -75,6 +79,12 @@ namespace MediaBrowser.Api.Playback
protected EncodingHelper EncodingHelper { get; set; }
/// <summary>
+ /// Gets the type of the transcoding job.
+ /// </summary>
+ /// <value>The type of the transcoding job.</value>
+ protected abstract TranscodingJobType TranscodingJobType { get; }
+
+ /// <summary>
/// Initializes a new instance of the <see cref="BaseStreamingService" /> class.
/// </summary>
protected BaseStreamingService(
@@ -113,12 +123,6 @@ namespace MediaBrowser.Api.Playback
protected abstract string GetCommandLineArguments(string outputPath, EncodingOptions encodingOptions, StreamState state, bool isEncoding);
/// <summary>
- /// Gets the type of the transcoding job.
- /// </summary>
- /// <value>The type of the transcoding job.</value>
- protected abstract TranscodingJobType TranscodingJobType { get; }
-
- /// <summary>
/// Gets the output file extension.
/// </summary>
/// <param name="state">The state.</param>
@@ -133,31 +137,24 @@ namespace MediaBrowser.Api.Playback
/// </summary>
private string GetOutputFilePath(StreamState state, EncodingOptions encodingOptions, string outputFileExtension)
{
- var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
-
var data = GetCommandLineArguments("dummy\\dummy", encodingOptions, state, false);
- data += "-" + (state.Request.DeviceId ?? string.Empty);
- data += "-" + (state.Request.PlaySessionId ?? string.Empty);
+ data += "-" + (state.Request.DeviceId ?? string.Empty)
+ + "-" + (state.Request.PlaySessionId ?? string.Empty);
- var dataHash = data.GetMD5().ToString("N");
+ var filename = data.GetMD5().ToString("N");
+ var ext = outputFileExtension.ToLowerInvariant();
+ var folder = ServerConfigurationManager.ApplicationPaths.TranscodingTempPath;
if (EnableOutputInSubFolder)
{
- return Path.Combine(folder, dataHash, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant());
+ return Path.Combine(folder, filename, filename + ext);
}
- return Path.Combine(folder, dataHash + (outputFileExtension ?? string.Empty).ToLowerInvariant());
+ return Path.Combine(folder, filename + ext);
}
- protected virtual bool EnableOutputInSubFolder => false;
-
- protected readonly CultureInfo UsCulture = new CultureInfo("en-US");
-
- protected virtual string GetDefaultH264Preset()
- {
- return "superfast";
- }
+ protected virtual string GetDefaultH264Preset() => "superfast";
private async Task AcquireResources(StreamState state, CancellationTokenSource cancellationTokenSource)
{
@@ -171,7 +168,6 @@ namespace MediaBrowser.Api.Playback
var liveStreamResponse = await MediaSourceManager.OpenLiveStream(new LiveStreamRequest
{
OpenToken = state.MediaSource.OpenToken
-
}, cancellationTokenSource.Token).ConfigureAwait(false);
EncodingHelper.AttachMediaSourceInfo(state, liveStreamResponse.MediaSource, state.RequestedUrl);
@@ -209,22 +205,16 @@ namespace MediaBrowser.Api.Playback
if (state.VideoRequest != null && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
var auth = AuthorizationContext.GetAuthorizationInfo(Request);
- if (auth.User != null)
+ if (auth.User != null && !auth.User.Policy.EnableVideoPlaybackTranscoding)
{
- if (!auth.User.Policy.EnableVideoPlaybackTranscoding)
- {
- ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
+ ApiEntryPoint.Instance.OnTranscodeFailedToStart(outputPath, TranscodingJobType, state);
- throw new ArgumentException("User does not have access to video transcoding");
- }
+ throw new ArgumentException("User does not have access to video transcoding");
}
}
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
- var transcodingId = Guid.NewGuid().ToString("N");
- var commandLineArgs = GetCommandLineArguments(outputPath, encodingOptions, state, true);
-
var process = new Process()
{
StartInfo = new ProcessStartInfo()
@@ -239,7 +229,7 @@ namespace MediaBrowser.Api.Playback
RedirectStandardInput = true,
FileName = MediaEncoder.EncoderPath,
- Arguments = commandLineArgs,
+ Arguments = GetCommandLineArguments(outputPath, encodingOptions, state, true),
WorkingDirectory = string.IsNullOrWhiteSpace(workingDirectory) ? null : workingDirectory,
ErrorDialog = false
@@ -250,7 +240,7 @@ namespace MediaBrowser.Api.Playback
var transcodingJob = ApiEntryPoint.Instance.OnTranscodeBeginning(outputPath,
state.Request.PlaySessionId,
state.MediaSource.LiveStreamId,
- transcodingId,
+ Guid.NewGuid().ToString("N"),
TranscodingJobType,
process,
state.Request.DeviceId,
@@ -261,27 +251,26 @@ namespace MediaBrowser.Api.Playback
Logger.LogInformation(commandLineLogMessage);
var logFilePrefix = "ffmpeg-transcode";
- if (state.VideoRequest != null)
+ if (state.VideoRequest != null
+ && string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase)
- && string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(state.OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
logFilePrefix = "ffmpeg-directstream";
}
- else if (string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ else
{
logFilePrefix = "ffmpeg-remux";
}
}
var logFilePath = Path.Combine(ServerConfigurationManager.ApplicationPaths.LogDirectoryPath, logFilePrefix + "-" + Guid.NewGuid() + ".txt");
- Directory.CreateDirectory(Path.GetDirectoryName(logFilePath));
// FFMpeg writes debug/error info to stderr. This is useful when debugging so let's put it in the log directory.
- state.LogFileStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
+ Stream logStream = FileSystem.GetFileStream(logFilePath, FileOpenMode.Create, FileAccessMode.Write, FileShareMode.Read, true);
var commandLineLogMessageBytes = Encoding.UTF8.GetBytes(Request.AbsoluteUri + Environment.NewLine + Environment.NewLine + JsonSerializer.SerializeToString(state.MediaSource) + Environment.NewLine + Environment.NewLine + commandLineLogMessage + Environment.NewLine + Environment.NewLine);
- await state.LogFileStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
+ await logStream.WriteAsync(commandLineLogMessageBytes, 0, commandLineLogMessageBytes.Length, cancellationTokenSource.Token).ConfigureAwait(false);
process.Exited += (sender, args) => OnFfMpegProcessExited(process, transcodingJob, state);
@@ -298,13 +287,10 @@ namespace MediaBrowser.Api.Playback
throw;
}
- // MUST read both stdout and stderr asynchronously or a deadlock may occurr
- //process.BeginOutputReadLine();
-
state.TranscodingJob = transcodingJob;
// Important - don't await the log task or we won't be able to kill ffmpeg when the user stops playback
- new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, state.LogFileStream);
+ _ = new JobLogger(Logger).StartStreamingLog(state, process.StandardError.BaseStream, logStream);
// Wait for the file to exist before proceeeding
while (!File.Exists(state.WaitForPath ?? outputPath) && !transcodingJob.HasExited)
@@ -368,25 +354,16 @@ namespace MediaBrowser.Api.Playback
Logger.LogDebug("Disposing stream resources");
state.Dispose();
- try
+ if (process.ExitCode == 0)
{
- Logger.LogInformation("FFMpeg exited with code {0}", process.ExitCode);
+ Logger.LogInformation("FFMpeg exited with code 0");
}
- catch
+ else
{
- Logger.LogError("FFMpeg exited with an error.");
+ Logger.LogError("FFMpeg exited with code {0}", process.ExitCode);
}
- // This causes on exited to be called twice:
- //try
- //{
- // // Dispose the process
- // process.Dispose();
- //}
- //catch (Exception ex)
- //{
- // Logger.LogError(ex, "Error disposing ffmpeg.");
- //}
+ process.Dispose();
}
/// <summary>
@@ -439,55 +416,55 @@ namespace MediaBrowser.Api.Playback
{
if (videoRequest != null)
{
- videoRequest.AudioStreamIndex = int.Parse(val, UsCulture);
+ videoRequest.AudioStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 7)
{
if (videoRequest != null)
{
- videoRequest.SubtitleStreamIndex = int.Parse(val, UsCulture);
+ videoRequest.SubtitleStreamIndex = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 8)
{
if (videoRequest != null)
{
- videoRequest.VideoBitRate = int.Parse(val, UsCulture);
+ videoRequest.VideoBitRate = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 9)
{
- request.AudioBitRate = int.Parse(val, UsCulture);
+ request.AudioBitRate = int.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 10)
{
- request.MaxAudioChannels = int.Parse(val, UsCulture);
+ request.MaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 11)
{
if (videoRequest != null)
{
- videoRequest.MaxFramerate = float.Parse(val, UsCulture);
+ videoRequest.MaxFramerate = float.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 12)
{
if (videoRequest != null)
{
- videoRequest.MaxWidth = int.Parse(val, UsCulture);
+ videoRequest.MaxWidth = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 13)
{
if (videoRequest != null)
{
- videoRequest.MaxHeight = int.Parse(val, UsCulture);
+ videoRequest.MaxHeight = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 14)
{
- request.StartTimeTicks = long.Parse(val, UsCulture);
+ request.StartTimeTicks = long.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 15)
{
@@ -500,14 +477,14 @@ namespace MediaBrowser.Api.Playback
{
if (videoRequest != null)
{
- videoRequest.MaxRefFrames = int.Parse(val, UsCulture);
+ videoRequest.MaxRefFrames = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 17)
{
if (videoRequest != null)
{
- videoRequest.MaxVideoBitDepth = int.Parse(val, UsCulture);
+ videoRequest.MaxVideoBitDepth = int.Parse(val, CultureInfo.InvariantCulture);
}
}
else if (i == 18)
@@ -556,7 +533,7 @@ namespace MediaBrowser.Api.Playback
}
else if (i == 26)
{
- request.TranscodingMaxAudioChannels = int.Parse(val, UsCulture);
+ request.TranscodingMaxAudioChannels = int.Parse(val, CultureInfo.InvariantCulture);
}
else if (i == 27)
{
@@ -643,16 +620,25 @@ namespace MediaBrowser.Api.Playback
return null;
}
- if (value.IndexOf("npt=", StringComparison.OrdinalIgnoreCase) != 0)
+ const string Npt = "npt=";
+ if (!value.StartsWith(Npt, StringComparison.OrdinalIgnoreCase))
{
throw new ArgumentException("Invalid timeseek header");
}
- value = value.Substring(4).Split(new[] { '-' }, 2)[0];
+ int index = value.IndexOf('-');
+ if (index == -1)
+ {
+ value = value.Substring(Npt.Length);
+ }
+ else
+ {
+ value = value.Substring(Npt.Length, index);
+ }
if (value.IndexOf(':') == -1)
{
// Parses npt times in the format of '417.33'
- if (double.TryParse(value, NumberStyles.Any, UsCulture, out var seconds))
+ if (double.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var seconds))
{
return TimeSpan.FromSeconds(seconds).Ticks;
}
@@ -667,7 +653,7 @@ namespace MediaBrowser.Api.Playback
foreach (var time in tokens)
{
- if (double.TryParse(time, NumberStyles.Any, UsCulture, out var digit))
+ if (double.TryParse(time, NumberStyles.Any, CultureInfo.InvariantCulture, out var digit))
{
secondsSum += digit * timeFactor;
}
@@ -707,7 +693,7 @@ namespace MediaBrowser.Api.Playback
var enableDlnaHeaders = !string.IsNullOrWhiteSpace(request.Params) /*||
string.Equals(Request.Headers.Get("GetContentFeatures.DLNA.ORG"), "1", StringComparison.OrdinalIgnoreCase)*/;
- var state = new StreamState(MediaSourceManager, Logger, TranscodingJobType)
+ var state = new StreamState(MediaSourceManager, TranscodingJobType)
{
Request = request,
RequestedUrl = url,
@@ -728,13 +714,10 @@ namespace MediaBrowser.Api.Playback
// state.SegmentLength = 6;
//}
- if (state.VideoRequest != null)
+ if (state.VideoRequest != null && !string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
{
- if (!string.IsNullOrWhiteSpace(state.VideoRequest.VideoCodec))
- {
- state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
- state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
- }
+ state.SupportedVideoCodecs = state.VideoRequest.VideoCodec.Split(',').Where(i => !string.IsNullOrWhiteSpace(i)).ToArray();
+ state.VideoRequest.VideoCodec = state.SupportedVideoCodecs.FirstOrDefault();
}
if (!string.IsNullOrWhiteSpace(request.AudioCodec))
@@ -779,12 +762,12 @@ namespace MediaBrowser.Api.Playback
var mediaSources = (await MediaSourceManager.GetPlayackMediaSources(LibraryManager.GetItemById(request.Id), null, false, false, cancellationToken).ConfigureAwait(false)).ToList();
mediaSource = string.IsNullOrEmpty(request.MediaSourceId)
- ? mediaSources.First()
- : mediaSources.FirstOrDefault(i => string.Equals(i.Id, request.MediaSourceId));
+ ? mediaSources[0]
+ : mediaSources.Find(i => string.Equals(i.Id, request.MediaSourceId));
if (mediaSource == null && request.MediaSourceId.Equals(request.Id))
{
- mediaSource = mediaSources.First();
+ mediaSource = mediaSources[0];
}
}
}
@@ -834,11 +817,11 @@ namespace MediaBrowser.Api.Playback
if (state.OutputVideoBitrate.HasValue && !string.Equals(state.OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
var resolution = ResolutionNormalizer.Normalize(
- state.VideoStream == null ? (int?)null : state.VideoStream.BitRate,
- state.VideoStream == null ? (int?)null : state.VideoStream.Width,
- state.VideoStream == null ? (int?)null : state.VideoStream.Height,
+ state.VideoStream?.BitRate,
+ state.VideoStream?.Width,
+ state.VideoStream?.Height,
state.OutputVideoBitrate.Value,
- state.VideoStream == null ? null : state.VideoStream.Codec,
+ state.VideoStream?.Codec,
state.OutputVideoCodec,
videoRequest.MaxWidth,
videoRequest.MaxHeight);
@@ -846,17 +829,13 @@ namespace MediaBrowser.Api.Playback
videoRequest.MaxWidth = resolution.MaxWidth;
videoRequest.MaxHeight = resolution.MaxHeight;
}
-
- ApplyDeviceProfileSettings(state);
- }
- else
- {
- ApplyDeviceProfileSettings(state);
}
+ ApplyDeviceProfileSettings(state);
+
var ext = string.IsNullOrWhiteSpace(state.OutputContainer)
? GetOutputFileExtension(state)
- : ("." + state.OutputContainer);
+ : ('.' + state.OutputContainer);
var encodingOptions = ApiEntryPoint.Instance.GetEncodingOptions();
@@ -970,18 +949,18 @@ namespace MediaBrowser.Api.Playback
responseHeaders["transferMode.dlna.org"] = string.IsNullOrEmpty(transferMode) ? "Streaming" : transferMode;
responseHeaders["realTimeInfo.dlna.org"] = "DLNA.ORG_TLAG=*";
- if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
+ if (state.RunTimeTicks.HasValue)
{
- if (state.RunTimeTicks.HasValue)
+ if (string.Equals(GetHeader("getMediaInfo.sec"), "1", StringComparison.OrdinalIgnoreCase))
{
var ms = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalMilliseconds;
responseHeaders["MediaInfo.sec"] = string.Format("SEC_Duration={0};", Convert.ToInt32(ms).ToString(CultureInfo.InvariantCulture));
}
- }
- if (state.RunTimeTicks.HasValue && !isStaticallyStreamed && profile != null)
- {
- AddTimeSeekResponseHeaders(state, responseHeaders);
+ if (!isStaticallyStreamed && profile != null)
+ {
+ AddTimeSeekResponseHeaders(state, responseHeaders);
+ }
}
if (profile == null)
@@ -1046,8 +1025,8 @@ namespace MediaBrowser.Api.Playback
private void AddTimeSeekResponseHeaders(StreamState state, IDictionary<string, string> responseHeaders)
{
- var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(UsCulture);
- var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(UsCulture);
+ var runtimeSeconds = TimeSpan.FromTicks(state.RunTimeTicks.Value).TotalSeconds.ToString(CultureInfo.InvariantCulture);
+ var startSeconds = TimeSpan.FromTicks(state.Request.StartTimeTicks ?? 0).TotalSeconds.ToString(CultureInfo.InvariantCulture);
responseHeaders["TimeSeekRange.dlna.org"] = string.Format("npt={0}-{1}/{1}", startSeconds, runtimeSeconds);
responseHeaders["X-AvailableSeekRange"] = string.Format("1 npt={0}-{1}", startSeconds, runtimeSeconds);
diff --git a/MediaBrowser.Api/Playback/StreamState.cs b/MediaBrowser.Api/Playback/StreamState.cs
index 8d4b0cb3d..7396b5c99 100644
--- a/MediaBrowser.Api/Playback/StreamState.cs
+++ b/MediaBrowser.Api/Playback/StreamState.cs
@@ -1,17 +1,14 @@
using System;
-using System.IO;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Net;
-using Microsoft.Extensions.Logging;
namespace MediaBrowser.Api.Playback
{
public class StreamState : EncodingJobInfo, IDisposable
{
- private readonly ILogger _logger;
private readonly IMediaSourceManager _mediaSourceManager;
+ private bool _disposed = false;
public string RequestedUrl { get; set; }
@@ -30,11 +27,6 @@ namespace MediaBrowser.Api.Playback
public VideoStreamRequest VideoRequest => Request as VideoStreamRequest;
- /// <summary>
- /// Gets or sets the log file stream.
- /// </summary>
- /// <value>The log file stream.</value>
- public Stream LogFileStream { get; set; }
public IDirectStreamProvider DirectStreamProvider { get; set; }
public string WaitForPath { get; set; }
@@ -72,6 +64,7 @@ namespace MediaBrowser.Api.Playback
{
return 3;
}
+
return 6;
}
@@ -94,82 +87,57 @@ namespace MediaBrowser.Api.Playback
public string UserAgent { get; set; }
- public StreamState(IMediaSourceManager mediaSourceManager, ILogger logger, TranscodingJobType transcodingType)
- : base(transcodingType)
- {
- _mediaSourceManager = mediaSourceManager;
- _logger = logger;
- }
-
public bool EstimateContentLength { get; set; }
+
public TranscodeSeekInfo TranscodeSeekInfo { get; set; }
public bool EnableDlnaHeaders { get; set; }
- public override void Dispose()
- {
- DisposeTranscodingThrottler();
- DisposeLogStream();
- DisposeLiveStream();
+ public DeviceProfile DeviceProfile { get; set; }
- TranscodingJob = null;
+ public TranscodingJob TranscodingJob { get; set; }
+
+ public StreamState(IMediaSourceManager mediaSourceManager, TranscodingJobType transcodingType)
+ : base(transcodingType)
+ {
+ _mediaSourceManager = mediaSourceManager;
}
- private void DisposeTranscodingThrottler()
+ public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
{
- if (TranscodingThrottler != null)
- {
- try
- {
- TranscodingThrottler.Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error disposing TranscodingThrottler");
- }
+ ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
+ }
- TranscodingThrottler = null;
- }
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
}
- private void DisposeLogStream()
+ protected virtual void Dispose(bool disposing)
{
- if (LogFileStream != null)
+ if (_disposed)
{
- try
- {
- LogFileStream.Dispose();
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "Error disposing log stream");
- }
-
- LogFileStream = null;
+ return;
}
- }
- private async void DisposeLiveStream()
- {
- if (MediaSource.RequiresClosing && string.IsNullOrWhiteSpace(Request.LiveStreamId) && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
+ if (disposing)
{
- try
- {
- await _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).ConfigureAwait(false);
- }
- catch (Exception ex)
+ // REVIEW: Is this the right place for this?
+ if (MediaSource.RequiresClosing
+ && string.IsNullOrWhiteSpace(Request.LiveStreamId)
+ && !string.IsNullOrWhiteSpace(MediaSource.LiveStreamId))
{
- _logger.LogError(ex, "Error closing media source");
+ _mediaSourceManager.CloseLiveStream(MediaSource.LiveStreamId).GetAwaiter().GetResult();
}
+
+ TranscodingThrottler?.Dispose();
}
- }
- public DeviceProfile DeviceProfile { get; set; }
+ TranscodingThrottler = null;
+ TranscodingJob = null;
- public TranscodingJob TranscodingJob;
- public override void ReportTranscodingProgress(TimeSpan? transcodingPosition, float framerate, double? percentComplete, long bytesTranscoded, int? bitRate)
- {
- ApiEntryPoint.Instance.ReportTranscodingProgress(TranscodingJob, this, transcodingPosition, framerate, percentComplete, bytesTranscoded, bitRate);
+ _disposed = true;
}
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index 916d691b8..34af3b156 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -374,14 +374,14 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (AudioStream != null)
{
return AudioStream.SampleRate;
}
}
-
else if (BaseRequest.AudioSampleRate.HasValue)
{
// Don't exceed what the encoder supports
@@ -397,7 +397,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
if (AudioStream != null)
{
@@ -405,13 +406,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- //else if (BaseRequest.AudioSampleRate.HasValue)
- //{
- // // Don't exceed what the encoder supports
- // // Seeing issues of attempting to encode to 88200
- // return Math.Min(44100, BaseRequest.AudioSampleRate.Value);
- //}
-
return null;
}
}
@@ -446,7 +440,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.BitDepth;
}
@@ -463,7 +458,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.RefFrames;
}
@@ -479,7 +475,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate);
}
@@ -545,7 +542,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.CodecTag;
}
@@ -558,7 +556,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.IsAnamorphic;
}
@@ -571,14 +570,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- var codec = OutputVideoCodec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.Codec;
}
- return codec;
+ return OutputVideoCodec;
}
}
@@ -586,14 +583,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- var codec = OutputAudioCodec;
-
- if (string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(OutputAudioCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return AudioStream?.Codec;
}
- return codec;
+ return OutputAudioCodec;
}
}
@@ -601,7 +596,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (BaseRequest.Static || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
+ if (BaseRequest.Static
+ || string.Equals(OutputVideoCodec, "copy", StringComparison.OrdinalIgnoreCase))
{
return VideoStream?.IsInterlaced;
}
@@ -636,6 +632,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetMediaStreamCount(MediaStreamType.Video, int.MaxValue);
}
+
return GetMediaStreamCount(MediaStreamType.Video, 1);
}
}
@@ -648,17 +645,12 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return GetMediaStreamCount(MediaStreamType.Audio, int.MaxValue);
}
+
return GetMediaStreamCount(MediaStreamType.Audio, 1);
}
}
- public int HlsListSize
- {
- get
- {
- return 0;
- }
- }
+ public int HlsListSize => 0;
private int? GetMediaStreamCount(MediaStreamType type, int limit)
{
@@ -677,10 +669,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
Progress.Report(percentComplete.Value);
}
-
- public virtual void Dispose()
- {
- }
}
/// <summary>
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index 2755bf581..ac989f6ba 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -3,6 +3,7 @@ using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
+using System.Threading.Tasks;
using MediaBrowser.Model.Extensions;
using Microsoft.Extensions.Logging;
@@ -18,10 +19,11 @@ namespace MediaBrowser.Controller.MediaEncoding
_logger = logger;
}
- public async void StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
+ public async Task StartStreamingLog(EncodingJobInfo state, Stream source, Stream target)
{
try
{
+ using (target)
using (var reader = new StreamReader(source))
{
while (!reader.EndOfStream && reader.BaseStream.CanRead)
@@ -97,8 +99,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var currentMs = startMs + val.TotalMilliseconds;
- var percentVal = currentMs / totalMs;
- percent = 100 * percentVal;
+ percent = 100.0 * currentMs / totalMs;
transcodingPosition = val;
}
diff --git a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
index 901d81c5f..e52951dd0 100644
--- a/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
+++ b/MediaBrowser.Model/Dlna/ContentFeatureBuilder.cs
@@ -13,7 +13,8 @@ namespace MediaBrowser.Model.Dlna
_profile = profile;
}
- public string BuildImageHeader(string container,
+ public string BuildImageHeader(
+ string container,
int? width,
int? height,
bool isDirectStream,
@@ -28,8 +29,7 @@ namespace MediaBrowser.Model.Dlna
DlnaFlags.InteractiveTransferMode |
DlnaFlags.DlnaV15;
- string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}",
- DlnaMaps.FlagsToString(flagValue));
+ string dlnaflags = string.Format(";DLNA.ORG_FLAGS={0}", DlnaMaps.FlagsToString(flagValue));
ResponseProfile mediaProfile = _profile.GetImageMediaProfile(container,
width,
@@ -37,7 +37,7 @@ namespace MediaBrowser.Model.Dlna
if (string.IsNullOrEmpty(orgPn))
{
- orgPn = mediaProfile == null ? null : mediaProfile.OrgPn;
+ orgPn = mediaProfile?.OrgPn;
}
if (string.IsNullOrEmpty(orgPn))
@@ -50,7 +50,8 @@ namespace MediaBrowser.Model.Dlna
return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
}
- public string BuildAudioHeader(string container,
+ public string BuildAudioHeader(
+ string container,
string audioCodec,
int? audioBitrate,
int? audioSampleRate,
@@ -102,7 +103,8 @@ namespace MediaBrowser.Model.Dlna
return (contentFeatures + orgOp + orgCi + dlnaflags).Trim(';');
}
- public List<string> BuildVideoHeader(string container,
+ public List<string> BuildVideoHeader(
+ string container,
string videoCodec,
string audioCodec,
int? width,
@@ -206,7 +208,7 @@ namespace MediaBrowser.Model.Dlna
return contentFeatureList;
}
- private string GetImageOrgPnValue(string container, int? width, int? height)
+ private static string GetImageOrgPnValue(string container, int? width, int? height)
{
MediaFormatProfile? format = new MediaFormatProfileResolver()
.ResolveImageFormat(container,
@@ -216,7 +218,7 @@ namespace MediaBrowser.Model.Dlna
return format.HasValue ? format.Value.ToString() : null;
}
- private string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
+ private static string GetAudioOrgPnValue(string container, int? audioBitrate, int? audioSampleRate, int? audioChannels)
{
MediaFormatProfile? format = new MediaFormatProfileResolver()
.ResolveAudioFormat(container,
@@ -227,7 +229,7 @@ namespace MediaBrowser.Model.Dlna
return format.HasValue ? format.Value.ToString() : null;
}
- private string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
+ private static string[] GetVideoOrgPnValue(string container, string videoCodec, string audioCodec, int? width, int? height, TransportStreamTimestamp timestamp)
{
return new MediaFormatProfileResolver().ResolveVideoFormat(container, videoCodec, audioCodec, width, height, timestamp);
}